import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import itertools

# --- Helper functions for metrics (no changes here) ---
def calculate_cagr_from_pnl_atr(total_pnl_atr, num_years, initial_capital_atr_units=100):
    if num_years <= 0: return 0
    if initial_capital_atr_units <=0: return total_pnl_atr / num_years if num_years > 0 else 0
    ending_value = initial_capital_atr_units + total_pnl_atr
    if ending_value <=0 : return -1.0
    return (ending_value / initial_capital_atr_units) ** (1 / num_years) - 1

def calculate_sharpe_ratio(returns_series, risk_free_rate_per_trade=0):
    if returns_series.empty or len(returns_series) < 2: return np.nan
    std_dev = returns_series.std()
    if std_dev == 0 or pd.isna(std_dev): return np.nan if returns_series.mean() - risk_free_rate_per_trade == 0 else np.inf * np.sign(returns_series.mean() - risk_free_rate_per_trade)
    excess_returns = returns_series - risk_free_rate_per_trade
    return excess_returns.mean() / std_dev

def calculate_sortino_ratio(returns_series, risk_free_rate_per_trade=0, target_return_per_trade=0):
    if returns_series.empty or len(returns_series) < 2 : return np.nan
    avg_return = returns_series.mean()
    downside_diff = target_return_per_trade - returns_series
    downside_returns_values = downside_diff[downside_diff > 0]
    if not len(downside_returns_values): return np.inf
    downside_std = np.sqrt(np.mean(downside_returns_values**2))
    if downside_std == 0 or pd.isna(downside_std): return np.inf
    return (avg_return - risk_free_rate_per_trade) / downside_std

# --- Comprehensive Metrics Calculation & Reporting Function (no changes here) ---
def generate_report(trades_df, report_label, years_in_data_param, current_config_params, initial_capital_atr=100):
    # (Function content is the same - omitted for brevity)
    print(f"\n--- Performance Report: {report_label} ---")
    metrics = {'Label': report_label}
    if trades_df.empty or 'PNL_ATR' not in trades_df.columns or trades_df['PNL_ATR'].empty:
        print(f"No trades to analyze for {report_label}.")
        metrics.update({'TotalTrades': 0, 'Error': "No trades or PNL empty"})
        for k in ['WinRate', 'AvgPnL_ATR', 'MAR', 'MaxDrawdownAbs_ATR', 'ProfitFactor', 'PayoffRatio', 'AvgWinAmount_ATR', 'AvgLossAmount_ATR', 'CAGR_Pct', 'SharpeRatio', 'SortinoRatio', 'CalmarRatio', 'LongestLossStreak', 'TotalPnL_ATR', 'Expectancy_ATR', 'AvgHoldingPeriod_Candles', 'MaxDrawdownPct']:
            metrics[k] = 0.0 if k not in ['TotalTrades', 'LongestLossStreak', 'WinningTrades', 'LosingTrades'] else 0
        return metrics

    num_trades = len(trades_df); total_pnl = trades_df['PNL_ATR'].sum()
    avg_pnl = trades_df['PNL_ATR'].mean() if num_trades > 0 else 0.0
    wins_df = trades_df[trades_df['PNL_ATR'] > 0]; losses_df = trades_df[trades_df['PNL_ATR'] < 0]
    winning_trades = len(wins_df); losing_trades = len(losses_df)
    win_rate = winning_trades / num_trades if num_trades > 0 else 0.0
    avg_win_amount = wins_df['PNL_ATR'].mean() if winning_trades > 0 else 0.0
    avg_loss_amount = abs(losses_df['PNL_ATR'].mean()) if losing_trades > 0 else 0.0
    if pd.isna(avg_win_amount): avg_win_amount = 0.0
    if pd.isna(avg_loss_amount): avg_loss_amount = 0.0

    profit_factor_numerator = wins_df['PNL_ATR'].sum()
    profit_factor_denominator = abs(losses_df['PNL_ATR'].sum())
    profit_factor = (profit_factor_numerator / profit_factor_denominator) if profit_factor_denominator > 1e-9 else (np.inf if profit_factor_numerator > 1e-9 else 0.0)
    payoff_ratio = (avg_win_amount / avg_loss_amount) if avg_loss_amount > 1e-9 else (np.inf if avg_win_amount > 0 else 0.0)
    expectancy = (win_rate * avg_win_amount) - ((1 - win_rate) * avg_loss_amount) if num_trades > 0 else 0.0
    avg_holding_period = trades_df['Duration_Candles'].mean() if 'Duration_Candles' in trades_df and num_trades > 0 else 0.0

    equity_curve_val = [initial_capital_atr] + (initial_capital_atr + trades_df['PNL_ATR'].cumsum()).tolist()
    equity_curve_series = pd.Series(equity_curve_val)
    running_max_equity = equity_curve_series.cummax()
    drawdown_series_abs_atr = running_max_equity - equity_curve_series
    max_dd_abs_atr = drawdown_series_abs_atr.max() if not drawdown_series_abs_atr.empty else 0.0
    peak_equity_for_dd_pct_calc = initial_capital_atr
    if not drawdown_series_abs_atr.empty and max_dd_abs_atr > 0:
        idx_max_dd_end = drawdown_series_abs_atr.idxmax(); peak_equity_for_dd_pct_calc = running_max_equity.iloc[idx_max_dd_end]
    elif not running_max_equity.empty: peak_equity_for_dd_pct_calc = running_max_equity.max()
    if peak_equity_for_dd_pct_calc <= 1e-9: peak_equity_for_dd_pct_calc = initial_capital_atr
    max_dd_pct = (max_dd_abs_atr / peak_equity_for_dd_pct_calc) * 100 if peak_equity_for_dd_pct_calc > 0 else 0.0

    cagr = calculate_cagr_from_pnl_atr(total_pnl, years_in_data_param, initial_capital_atr)
    annual_pnl_proxy = total_pnl / years_in_data_param if years_in_data_param > 0 else 0.0
    mar_ratio = annual_pnl_proxy / max_dd_abs_atr if max_dd_abs_atr > 1e-9 else (np.inf if annual_pnl_proxy > 0 else 0.0)
    calmar_ratio = (cagr * 100 if cagr is not None and cagr > -1 else -100.0) / max_dd_pct if max_dd_pct > 1e-9 else (np.inf if cagr is not None and cagr > 0 else 0.0)
    avg_trades_per_year_for_annualization = num_trades / years_in_data_param if years_in_data_param > 0 else 1.0
    annualization_factor = np.sqrt(max(1.0, avg_trades_per_year_for_annualization))
    sharpe_ratio_val = calculate_sharpe_ratio(trades_df['PNL_ATR']); sharpe_ratio = sharpe_ratio_val * annualization_factor if pd.notna(sharpe_ratio_val) else np.nan
    sortino_ratio_val = calculate_sortino_ratio(trades_df['PNL_ATR']); sortino_ratio = sortino_ratio_val * annualization_factor if pd.notna(sortino_ratio_val) else np.nan

    longest_win_streak = 0; current_ws = 0; longest_loss_streak = 0; current_ls = 0
    for pnl_val in trades_df['PNL_ATR']:
        if pnl_val > 0: current_ws += 1; current_ls = 0
        elif pnl_val < 0: current_ls += 1; current_ws = 0
        else: current_ws = 0; current_ls = 0
        longest_win_streak = max(longest_win_streak, current_ws); longest_loss_streak = max(longest_loss_streak, current_ls)

    metrics.update({
        'TotalTrades': num_trades, 'WinningTrades': winning_trades, 'LosingTrades': losing_trades,
        'WinRate': win_rate, 'TotalPnL_ATR': total_pnl, 'AvgPnL_ATR': avg_pnl, 'Expectancy_ATR': expectancy,
        'AvgHoldingPeriod_Candles': avg_holding_period, 'MaxDrawdownAbs_ATR': max_dd_abs_atr,
        'MaxDrawdownPct': max_dd_pct, 'CAGR_Pct': cagr * 100 if cagr is not None and cagr > -1 else -100.0,
        'MAR': mar_ratio, 'SharpeRatio': sharpe_ratio, 'ProfitFactor': profit_factor,
        'SortinoRatio': sortino_ratio, 'CalmarRatio': calmar_ratio,
        'AvgWinAmount_ATR': avg_win_amount, 'AvgLossAmount_ATR': avg_loss_amount,
        'PayoffRatio': payoff_ratio, 'LongestWinStreak': longest_win_streak,
        'LongestLossStreak': longest_loss_streak
    })
    tc = current_config_params.get('tc_atr', 0.10)
    if 'Raw_PNL_ATR' in trades_df.columns and num_trades > 0:
        metrics['AvgRawPnL_ATR_ALL'] = trades_df['Raw_PNL_ATR'].mean()
        raw_wins_df = trades_df[trades_df['Raw_PNL_ATR'] > tc if tc > 0 else trades_df['Raw_PNL_ATR'] > 0] 
        raw_losses_df = trades_df[trades_df['Raw_PNL_ATR'] < 0] 
        metrics['AvgRawPnL_ATR_WIN'] = raw_wins_df['Raw_PNL_ATR'].mean() if not raw_wins_df.empty else 0.0
        metrics['AvgRawPnL_ATR_LOSS'] = raw_losses_df['Raw_PNL_ATR'].mean() if not raw_losses_df.empty else 0.0

    print(f"{'Metric':<30}{'Value':<20}"); print("-" * 50)
    for k_met, v_met in metrics.items():
        if k_met == 'Label' or k_met in current_config_params: continue
        is_float = isinstance(v_met, (float, np.floating)) and not k_met.endswith('Streak') and k_met not in ['TotalTrades', 'WinningTrades', 'LosingTrades']
        print(f"{k_met:<30}{v_met:<20.3f}" if is_float and pd.notna(v_met) else f"{k_met:<30}{str(v_met):<20}")

    if 'Direction' in trades_df.columns and num_trades > 0:
        long_trades = trades_df[trades_df['Direction'] == 1]
        short_trades = trades_df[trades_df['Direction'] == -1]
        print("\n--- Long Trades Performance ---")
        if not long_trades.empty:
            lt_num = len(long_trades); lt_wr = (long_trades['PNL_ATR'] > 0).sum() / lt_num if lt_num > 0 else 0
            lt_ap = long_trades['PNL_ATR'].mean(); lt_tp = long_trades['PNL_ATR'].sum()
            lt_wins_sum = long_trades[long_trades['PNL_ATR'] > 0]['PNL_ATR'].sum()
            lt_loss_sum_abs = abs(long_trades[long_trades['PNL_ATR'] < 0]['PNL_ATR'].sum())
            lt_pf = lt_wins_sum / lt_loss_sum_abs if lt_loss_sum_abs > 1e-9 else (np.inf if lt_wins_sum > 1e-9 else 0)
            lt_aw = long_trades[long_trades['PNL_ATR'] > 0]['PNL_ATR'].mean() if len(long_trades[long_trades['PNL_ATR'] > 0]) >0 else 0
            lt_al = abs(long_trades[long_trades['PNL_ATR'] < 0]['PNL_ATR'].mean()) if len(long_trades[long_trades['PNL_ATR'] < 0]) >0 else 0
            lt_pr = lt_aw / lt_al if lt_al > 1e-9 else (np.inf if lt_aw > 0 else 0)
            print(f"Long Trades: {lt_num}, Win Rate: {lt_wr:.3%}, Avg PnL: {lt_ap:.3f} ATR, Total PnL: {lt_tp:.3f} ATR, PF: {lt_pf:.3f}, Payoff: {lt_pr:.3f}")
        else: print("No long trades.")
        print("\n--- Short Trades Performance ---")
        if not short_trades.empty:
            st_num = len(short_trades); st_wr = (short_trades['PNL_ATR'] > 0).sum() / st_num if st_num > 0 else 0
            st_ap = short_trades['PNL_ATR'].mean(); st_tp = short_trades['PNL_ATR'].sum()
            st_wins_sum = short_trades[short_trades['PNL_ATR'] > 0]['PNL_ATR'].sum()
            st_loss_sum_abs = abs(short_trades[short_trades['PNL_ATR'] < 0]['PNL_ATR'].sum())
            st_pf = st_wins_sum / st_loss_sum_abs if st_loss_sum_abs > 1e-9 else (np.inf if st_wins_sum > 1e-9 else 0)
            st_aw = short_trades[short_trades['PNL_ATR'] > 0]['PNL_ATR'].mean() if len(short_trades[short_trades['PNL_ATR'] > 0]) >0 else 0
            st_al = abs(short_trades[short_trades['PNL_ATR'] < 0]['PNL_ATR'].mean()) if len(short_trades[short_trades['PNL_ATR'] < 0]) >0 else 0
            st_pr = st_aw / st_al if st_al > 1e-9 else (np.inf if st_aw > 0 else 0)
            print(f"Short Trades: {st_num}, Win Rate: {st_wr:.3%}, Avg PnL: {st_ap:.3f} ATR, Total PnL: {st_tp:.3f} ATR, PF: {st_pf:.3f}, Payoff: {st_pr:.3f}")
        else: print("No short trades.")

    if not trades_df.empty and 'Vol_Regime' in trades_df.columns and num_trades > 0:
            print(f"\n--- Volatility Regime Analysis for {report_label} ---")
            def safe_win_rate(x):
                if len(x) == 0: return 0.0
                return (x > 0).sum() / len(x)
            regime_perf = trades_df.groupby('Vol_Regime')['PNL_ATR'].agg(
                Trades='count', AvgPnL_ATR='mean', TotalPnL_ATR='sum', WinRate=safe_win_rate
            ).reset_index()
            regime_perf['AvgPnL_ATR'] = regime_perf['AvgPnL_ATR'].fillna(0.0)
            regime_perf['WinRate'] = regime_perf['WinRate'].fillna(0.0)
            print(regime_perf.to_string(index=False))

    if not equity_curve_series.empty and num_trades > 0:
        plt.figure(figsize=(12,6)); plt.plot(equity_curve_val)
        plt.title(f'Equity Curve ({report_label}) - {num_trades} Trades', fontsize=14)
        plt.xlabel('Trade Number'); plt.ylabel(f'Cumulative PNL (ATR) from {initial_capital_atr} ATR')
        plt.grid(True); safe_report_label = "".join(c if c.isalnum() else "_" for c in report_label)
        plot_filename = f"equity_curve_{safe_report_label}.png"
        try: plt.savefig(plot_filename); print(f"Saved equity curve: {plot_filename}")
        except Exception as e_plot: print(f"Error saving plot {plot_filename}: {e_plot}")
        plt.close()
    elif num_trades > 0 : print(f"Equity curve not plotted for {report_label} due to empty equity series but trades exist.")
    return metrics

# Load the data
file_path = r"C:\Users\Bl4ckP3ngu1n\Documents\SigmaEA\GBPUSD_M15_202201030000_202506041200.csv"
try:
    df = pd.read_csv(file_path, delimiter='\t')
    expected_columns = ['<DATE>', '<TIME>', '<OPEN>', '<HIGH>', '<LOW>', '<CLOSE>', '<TICKVOL>', '<VOL>', '<SPREAD>']
    if all(col in df.columns for col in expected_columns):
        df.columns = ['Date', 'Time', 'Open', 'High', 'Low', 'Close', 'Tickvol', 'Volume', 'Spread']
        df['Timestamp'] = pd.to_datetime(df['Date'] + ' ' + df['Time'])
        df = df.set_index('Timestamp')
        df_ohlc_raw = df[['Open', 'High', 'Low', 'Close', 'Tickvol', 'Volume']].copy()
    else: print("Columns do not match expected MT5 export format."); df_ohlc_raw = None
except Exception as e: print(f"Error loading {file_path}: {e}"); df_ohlc_raw = None

if df_ohlc_raw is not None:
    df_ohlc_prepared = df_ohlc_raw.copy()
    period_atr = 14
    df_ohlc_prepared['H-L'] = df_ohlc_prepared['High'] - df_ohlc_prepared['Low']
    df_ohlc_prepared['H-PC'] = abs(df_ohlc_prepared['High'] - df_ohlc_prepared['Close'].shift(1))
    df_ohlc_prepared['L-PC'] = abs(df_ohlc_prepared['Low'] - df_ohlc_prepared['Close'].shift(1))
    df_ohlc_prepared['TR_temp'] = df_ohlc_prepared[['H-L', 'H-PC', 'L-PC']].max(axis=1)
    df_ohlc_prepared['ATR_calc'] = df_ohlc_prepared['TR_temp'].rolling(window=period_atr).mean()
    df_ohlc_prepared['ATR_Smooth_3'] = df_ohlc_prepared['ATR_calc'].rolling(window=3).mean()
    df_ohlc_prepared['ATR_Slope_Smooth_3'] = df_ohlc_prepared['ATR_Smooth_3'].diff()
    df_ohlc_prepared['ATR_100_Rolling_Q10'] = df_ohlc_prepared['ATR_calc'].rolling(window=100).quantile(0.10)
    donchian_period = 20
    df_ohlc_prepared['Donchian_High'] = df_ohlc_prepared['High'].rolling(window=donchian_period).max().shift(1)
    df_ohlc_prepared['Donchian_Low'] = df_ohlc_prepared['Low'].rolling(window=donchian_period).min().shift(1)
    df_ohlc_prepared['ATR_100_Rolling_Q33'] = df_ohlc_prepared['ATR_calc'].rolling(window=100).quantile(0.33)
    df_ohlc_prepared['ATR_100_Rolling_Q66'] = df_ohlc_prepared['ATR_calc'].rolling(window=100).quantile(0.66)
    df_ohlc_prepared.dropna(subset=['ATR_calc', 'ATR_Slope_Smooth_3', 'ATR_100_Rolling_Q10', 
                                    'Donchian_High', 'Donchian_Low',
                                    'ATR_100_Rolling_Q33', 'ATR_100_Rolling_Q66'], inplace=True)
    print(f"Data prepared: {len(df_ohlc_prepared)} rows from {df_ohlc_prepared.index.min()} to {df_ohlc_prepared.index.max()}")

    base_signals_list = []
    if not df_ohlc_prepared.empty:
        start_idx_for_signals = 1 
        if len(df_ohlc_prepared) > start_idx_for_signals + 1: 
            for i in range(start_idx_for_signals, len(df_ohlc_prepared) - 1):
                signal_candle = df_ohlc_prepared.iloc[i]; prev_candle = df_ohlc_prepared.iloc[i-1]; t_plus_1_candle = df_ohlc_prepared.iloc[i+1]
                current_atr_val_signal = signal_candle['ATR_calc']; atr_slope_val_signal = signal_candle['ATR_Slope_Smooth_3']
                atr_q10_signal = signal_candle['ATR_100_Rolling_Q10']
                if pd.isna(current_atr_val_signal) or current_atr_val_signal <= 1e-9 or \
                   pd.isna(atr_slope_val_signal) or pd.isna(atr_q10_signal) or \
                   any(pd.isna(val) for val in [signal_candle['Donchian_High'], signal_candle['Donchian_Low'],
                                                prev_candle['Close'], prev_candle['Donchian_High'], prev_candle['Donchian_Low'],
                                                t_plus_1_candle['Open'], t_plus_1_candle['Close'], 
                                                signal_candle['High'], signal_candle['Low']]): continue
                if not (atr_slope_val_signal > 0): continue
                if current_atr_val_signal < atr_q10_signal: continue
                direction = 0
                if signal_candle['Close'] > signal_candle['Donchian_High'] and not (prev_candle['Close'] > prev_candle['Donchian_High']):
                    if t_plus_1_candle['Close'] > signal_candle['High']: direction = 1
                elif signal_candle['Close'] < signal_candle['Donchian_Low'] and not (prev_candle['Close'] < prev_candle['Donchian_Low']):
                    if t_plus_1_candle['Close'] < signal_candle['Low']: direction = -1
                if direction != 0:
                    atr_q33_signal = signal_candle['ATR_100_Rolling_Q33']; atr_q66_signal = signal_candle['ATR_100_Rolling_Q66']
                    vol_regime = 'Mid Vol'
                    if pd.notna(atr_q33_signal) and current_atr_val_signal < atr_q33_signal: vol_regime = 'Low Vol (Q10-Q33)'
                    elif pd.notna(atr_q66_signal) and current_atr_val_signal > atr_q66_signal: vol_regime = 'High Vol'
                    base_signals_list.append({
                        'Timestamp': signal_candle.name, 'Signal_Iloc_Index': df_ohlc_prepared.index.get_loc(signal_candle.name),
                        'Entry_Price_Actual': t_plus_1_candle['Open'], 'Direction': direction,
                        'ATR_at_Signal': current_atr_val_signal, 'Vol_Regime_Signal': vol_regime,
                    })
    all_qualified_signals_df = pd.DataFrame(base_signals_list)
    print(f"--- Generated {len(all_qualified_signals_df)} qualified signals ---")

    iter19_replication_params = {
        'donchian_period': 20, 'chandelier_k': 1.5, 'chandelier_atr_dynamic': False, 
        'hard_sl_mult': 2.0, 'time_stop': 45, 'pl_trigger': 1.0, 'pl_level': 0.3,
        'profit_lock_active': True, 'tc_atr': 0.10,
        'max_consecutive_losses_for_half_size': 2, 'wins_to_recover_full_size': 1,
        'label_suffix': "Iter19_PL_GapLogic_v5" # Suffix for this specific run
    }
    current_config = iter19_replication_params
    config_label = current_config['label_suffix']
    print(f"\n--- Running Backtest for Config: {config_label} ---")
    print(f"Parameters: {current_config}")
    
    all_bars_detailed_log_list_for_config = []
    trade_results_list_for_config = []
    
    if not all_qualified_signals_df.empty:
        consecutive_losses = 0
        for trade_idx, signal_row in all_qualified_signals_df.iterrows():
            trade_id_timestamp = signal_row['Timestamp']
            current_position_size_multiplier = 1.0
            if consecutive_losses >= current_config['max_consecutive_losses_for_half_size']:
                current_position_size_multiplier = 0.5

            signal_iloc_in_df = signal_row['Signal_Iloc_Index']
            entry_price = signal_row['Entry_Price_Actual']
            direction = signal_row['Direction']
            atr_for_this_trade = signal_row['ATR_at_Signal'] 
            vol_regime_at_entry = signal_row['Vol_Regime_Signal']

            if pd.isna(atr_for_this_trade) or atr_for_this_trade <= 1e-9 or pd.isna(entry_price):
                continue

            pnl_atr_raw = 0.0; exit_price_trade = np.nan 
            exit_reason_trade_summary = "Unknown"
            
            highest_high_for_chandelier_calc = entry_price 
            lowest_low_for_chandelier_calc = entry_price   
            mfe_tracker_high = entry_price 
            mfe_tracker_low = entry_price  
            locked_profit_stop_price = None 
            initial_hard_sl_price = entry_price - (direction * current_config['hard_sl_mult'] * atr_for_this_trade)
            trade_duration_candles = 0 

            for k_trade in range(current_config['time_stop']): 
                trade_duration_candles = k_trade + 1
                current_candle_iloc = signal_iloc_in_df + 1 + k_trade
                current_bar_log_details = { 
                    'Trade_ID_TS': trade_id_timestamp, 'Bar_Num_In_Trade': trade_duration_candles,
                    'Entry_Price_Trade': entry_price, 'Direction_Trade': direction, 
                    'ATR_at_Entry_Trade': atr_for_this_trade,
                    'Initial_Hard_SL_Price': initial_hard_sl_price,
                    'Exit_Price_Triggered': np.nan, 'Exit_Reason_Triggered': ""
                }
                if current_candle_iloc >= len(df_ohlc_prepared):
                    last_close = df_ohlc_prepared.iloc[-1]['Close']
                    if pd.notna(last_close) and atr_for_this_trade > 0: pnl_atr_raw = (last_close - entry_price) * direction / atr_for_this_trade
                    else: pnl_atr_raw = 0.0
                    exit_price_trade = last_close
                    exit_reason_trade_summary = "Data_End_In_Trade"
                    current_bar_log_details.update({'Candle_Timestamp': 'Data_End', 'Exit_Price_Triggered': exit_price_trade, 'Exit_Reason_Triggered': exit_reason_trade_summary})
                    all_bars_detailed_log_list_for_config.append(current_bar_log_details)
                    break
                current_candle = df_ohlc_prepared.iloc[current_candle_iloc]
                current_bar_log_details['Candle_Timestamp'] = current_candle.name
                current_bar_log_details.update({'Candle_O': current_candle['Open'], 'Candle_H': current_candle['High'], 
                                                'Candle_L': current_candle['Low'], 'Candle_C': current_candle['Close']})
                if any(pd.isna(current_candle[col]) for col in ['Open', 'High', 'Low', 'Close']):
                    candle_open_val = current_candle['Open'] 
                    if pd.notna(candle_open_val) and atr_for_this_trade > 0: pnl_atr_raw = (candle_open_val - entry_price) * direction / atr_for_this_trade
                    else: pnl_atr_raw = 0.0
                    exit_price_trade = candle_open_val if pd.notna(candle_open_val) else entry_price
                    exit_reason_trade_summary = "NaN_Data_In_Trade"
                    current_bar_log_details.update({'Exit_Price_Triggered': exit_price_trade, 'Exit_Reason_Triggered': exit_reason_trade_summary})
                    all_bars_detailed_log_list_for_config.append(current_bar_log_details)
                    break
                
                atr_for_chandelier_calc = atr_for_this_trade 
                current_bar_log_details['HH_for_Chandelier_Used'] = highest_high_for_chandelier_calc 
                current_bar_log_details['LL_for_Chandelier_Used'] = lowest_low_for_chandelier_calc   
                current_bar_log_details['ATR_for_Chandelier_Used'] = atr_for_chandelier_calc
                chandelier_stop_val_current_bar = 0.0 
                if direction == 1: chandelier_stop_val_current_bar = highest_high_for_chandelier_calc - current_config['chandelier_k'] * atr_for_chandelier_calc
                else: chandelier_stop_val_current_bar = lowest_low_for_chandelier_calc + current_config['chandelier_k'] * atr_for_chandelier_calc
                current_bar_log_details['Chandelier_Stop_Calculated'] = chandelier_stop_val_current_bar
                
                if direction == 1:
                    mfe_tracker_high = max(mfe_tracker_high, current_candle['High'])
                    current_mfe_atr_for_pl = (mfe_tracker_high - entry_price) / atr_for_this_trade if atr_for_this_trade > 0 else 0
                else: 
                    mfe_tracker_low = min(mfe_tracker_low, current_candle['Low'])
                    current_mfe_atr_for_pl = (entry_price - mfe_tracker_low) / atr_for_this_trade if atr_for_this_trade > 0 else 0
                current_bar_log_details['MFE_ATR_for_PL_Check'] = current_mfe_atr_for_pl
                
                if current_config.get('profit_lock_active', True): 
                    if locked_profit_stop_price is None and current_mfe_atr_for_pl >= current_config['pl_trigger']:
                        locked_profit_stop_price = entry_price + (current_config['pl_level'] * atr_for_this_trade * direction)
                current_bar_log_details['Locked_Profit_Stop_Set_To'] = locked_profit_stop_price
                
                current_effective_sl = initial_hard_sl_price
                if direction == 1:
                    current_effective_sl = max(initial_hard_sl_price, chandelier_stop_val_current_bar) 
                    if current_config.get('profit_lock_active', True) and locked_profit_stop_price is not None: 
                        current_effective_sl = max(current_effective_sl, locked_profit_stop_price)
                else: 
                    current_effective_sl = min(initial_hard_sl_price, chandelier_stop_val_current_bar)
                    if current_config.get('profit_lock_active', True) and locked_profit_stop_price is not None:
                        current_effective_sl = min(current_effective_sl, locked_profit_stop_price)
                current_bar_log_details['Effective_SL_Final'] = current_effective_sl
                
                exit_hit_this_bar = False
                temp_exit_reason_bar = ""
                is_profit_lock_exit_type = False # Flag to identify PL exit type for special gap logic

                if direction == 1 and current_candle['Low'] <= current_effective_sl:
                    exit_hit_this_bar = True
                    if current_config.get('profit_lock_active', True) and locked_profit_stop_price is not None and \
                       abs(current_effective_sl - locked_profit_stop_price) < (1e-9 * max(1, atr_for_this_trade)):
                        temp_exit_reason_bar = "ProfitLockEx"
                        exit_price_trade = locked_profit_stop_price 
                        is_profit_lock_exit_type = True
                    elif abs(current_effective_sl - chandelier_stop_val_current_bar) < (1e-9 * max(1, atr_for_this_trade)):
                        temp_exit_reason_bar = "ChandelierEx"
                        exit_price_trade = chandelier_stop_val_current_bar
                    else:
                        temp_exit_reason_bar = "HardSLEx"
                        exit_price_trade = initial_hard_sl_price
                    
                    # --- REFINED CONDITIONAL GAP LOGIC ---
                    if pd.notna(exit_price_trade): # Ensure exit_price_trade was set by a stop
                        if is_profit_lock_exit_type:
                            # For Profit Lock: only take Open if it gapped to an actual LOSS
                            if current_candle['Open'] < entry_price: # Open is worse than entry
                                exit_price_trade = min(exit_price_trade, current_candle['Open']) # Take the worse of PL or Open(loss)
                            # If Open is >= entry_price but < locked_profit_stop_price, exit_price_trade remains locked_profit_stop_price
                        else: # For Chandelier or HardSL (standard stop loss behavior)
                            if current_candle['Open'] < exit_price_trade: # Open is worse than the stop
                                exit_price_trade = current_candle['Open']
                    # --- END OF REFINED CONDITIONAL GAP LOGIC ---

                elif direction == -1 and current_candle['High'] >= current_effective_sl:
                    exit_hit_this_bar = True
                    if current_config.get('profit_lock_active', True) and locked_profit_stop_price is not None and \
                       abs(current_effective_sl - locked_profit_stop_price) < (1e-9 * max(1, atr_for_this_trade)):
                        temp_exit_reason_bar = "ProfitLockEx"
                        exit_price_trade = locked_profit_stop_price
                        is_profit_lock_exit_type = True
                    elif abs(current_effective_sl - chandelier_stop_val_current_bar) < (1e-9 * max(1, atr_for_this_trade)):
                        temp_exit_reason_bar = "ChandelierEx"
                        exit_price_trade = chandelier_stop_val_current_bar
                    else:
                        temp_exit_reason_bar = "HardSLEx"
                        exit_price_trade = initial_hard_sl_price

                    # --- REFINED CONDITIONAL GAP LOGIC ---
                    if pd.notna(exit_price_trade):
                        if is_profit_lock_exit_type:
                            if current_candle['Open'] > entry_price: # Open is worse than entry
                                exit_price_trade = max(exit_price_trade, current_candle['Open']) # Take the worse of PL or Open(loss)
                        else:
                            if current_candle['Open'] > exit_price_trade: # Open is worse than the stop
                                exit_price_trade = current_candle['Open']
                    # --- END OF REFINED CONDITIONAL GAP LOGIC ---
                
                # Debug print remains useful
                if exit_hit_this_bar and temp_exit_reason_bar == "ProfitLockEx" and trade_duration_candles == 1:
                    if pd.notna(exit_price_trade) and atr_for_this_trade > 1e-9:
                        calculated_raw_pnl_for_debug = (exit_price_trade - entry_price) * direction / atr_for_this_trade
                        expected_pl_raw_pnl = current_config['pl_level']
                        if abs(calculated_raw_pnl_for_debug - expected_pl_raw_pnl) > 0.01 or \
                           (expected_pl_raw_pnl > 0 and calculated_raw_pnl_for_debug < (expected_pl_raw_pnl - 0.01)) : # If significantly different or unexpectedly low
                            print(f"\nDEBUG: ProfitLockEx_Bar1 with UNEXPECTED Raw PNL for Trade ID: {trade_id_timestamp}")
                            print(f"  Entry: {entry_price:.5f}, ATR: {atr_for_this_trade:.5f}, Dir: {direction}, PL Level: {expected_pl_raw_pnl:.3f}")
                            print(f"  Locked Profit Stop Price Set To: {locked_profit_stop_price:.5f if locked_profit_stop_price else 'None'}")
                            print(f"  Effective SL Final: {current_effective_sl:.5f}")
                            print(f"  Candle T+1 O: {current_candle['Open']:.5f}, H: {current_candle['High']:.5f}, L: {current_candle['Low']:.5f}")
                            print(f"  Final exit_price_trade (after gap check): {exit_price_trade:.5f}")
                            print(f"  >>> Calculated Raw PNL ATR for debug: {calculated_raw_pnl_for_debug:.3f} (Expected ~{expected_pl_raw_pnl:.3f})")

                current_bar_log_details['Exit_Price_Triggered'] = exit_price_trade 
                current_bar_log_details['Exit_Reason_Triggered'] = temp_exit_reason_bar 
                all_bars_detailed_log_list_for_config.append(current_bar_log_details)
                
                if exit_hit_this_bar:
                    if pd.notna(exit_price_trade) and atr_for_this_trade > 1e-9: 
                        pnl_atr_raw = (exit_price_trade - entry_price) * direction / atr_for_this_trade
                    else: 
                        pnl_atr_raw = 0.0 
                        if pd.isna(exit_price_trade): 
                            print(f"ERROR: Exit hit but exit_price_trade is NaN for {trade_id_timestamp} on bar {trade_duration_candles}, reason: {temp_exit_reason_bar}")
                            # Fallback if exit_price_trade somehow remained NaN (should be rare with new logic)
                            exit_price_trade = current_effective_sl 
                            if atr_for_this_trade > 1e-9: pnl_atr_raw = (exit_price_trade - entry_price) * direction / atr_for_this_trade
                    exit_reason_trade_summary = temp_exit_reason_bar + f"_Bar{trade_duration_candles}"
                    break
                    
                if trade_duration_candles >= current_config['time_stop']:
                    exit_price_trade = current_candle['Close']
                    if atr_for_this_trade > 0: pnl_atr_raw = (exit_price_trade - entry_price) * direction / atr_for_this_trade
                    else: pnl_atr_raw = 0.0
                    exit_reason_trade_summary = "TimeStop"
                    all_bars_detailed_log_list_for_config[-1]['Exit_Price_Triggered'] = exit_price_trade 
                    all_bars_detailed_log_list_for_config[-1]['Exit_Reason_Triggered'] = "TimeStop"
                    break
                    
                if direction == 1: highest_high_for_chandelier_calc = max(highest_high_for_chandelier_calc, current_candle['High'])
                else: lowest_low_for_chandelier_calc = min(lowest_low_for_chandelier_calc, current_candle['Low'])
            
            pnl_atr_after_cost = pnl_atr_raw - current_config['tc_atr']
            final_pnl_atr = pnl_atr_after_cost * current_position_size_multiplier
            trade_results_list_for_config.append({
                'Timestamp': trade_id_timestamp, 'PNL_ATR': final_pnl_atr, 'Raw_PNL_ATR': pnl_atr_raw,
                'Direction': direction, 'Vol_Regime': vol_regime_at_entry,
                'Duration_Candles': trade_duration_candles, 'Exit_Reason': exit_reason_trade_summary,
                'Entry_Price': entry_price, 'ATR_at_Entry_Signal': atr_for_this_trade
            })
            if pnl_atr_after_cost < 0: consecutive_losses += 1
            elif pnl_atr_after_cost > 0 :
                if current_config['wins_to_recover_full_size'] == 1: consecutive_losses = 0
        
    current_results_df = pd.DataFrame(trade_results_list_for_config)
    years_in_data_global = (df_ohlc_prepared.index.max() - df_ohlc_prepared.index.min()).days / 365.25 if len(df_ohlc_prepared) > 1 else 1.0/365.25
    metrics = generate_report(current_results_df, config_label, years_in_data_global, current_config)
    
    # Detailed Log Saving and Analysis (same as before)
    detailed_log_df = pd.DataFrame(all_bars_detailed_log_list_for_config)
    if not detailed_log_df.empty:
        print(f"\n\n--- DETAILED INTRA-TRADE LOG ({config_label}) ---")
        full_detailed_log_filename = f"detailed_log_all_trades_{config_label}.csv"
        detailed_log_df.to_csv(full_detailed_log_filename, index=False, float_format='%.5f')
        print(f"Saved full detailed log to {full_detailed_log_filename}")
        if not current_results_df.empty: 
            cols_to_merge_from_summary = ['Timestamp', 'PNL_ATR', 'Raw_PNL_ATR', 'Exit_Reason']
            actual_cols_to_merge = [col for col in cols_to_merge_from_summary if col in current_results_df.columns]
            merged_detailed_log_df = pd.merge(detailed_log_df, current_results_df[actual_cols_to_merge], 
                                              left_on='Trade_ID_TS', right_on='Timestamp', how='left') 
            exit_reason_col_name_in_merged = 'Exit_Reason'; raw_pnl_col_name_in_merged = 'Raw_PNL_ATR'
            pnl_atr_col_name_in_merged = 'PNL_ATR' 
            required_cols_for_analysis = [exit_reason_col_name_in_merged, raw_pnl_col_name_in_merged, pnl_atr_col_name_in_merged]
            all_necessary_cols_present = all(col in merged_detailed_log_df.columns for col in required_cols_for_analysis + ['Trade_ID_TS', 'Bar_Num_In_Trade'])
            if all_necessary_cols_present:
                # Focus on ProfitLockEx_Bar1 that are NOT near pl_level for raw PNL
                problematic_profit_lock_trades_df = merged_detailed_log_df[
                    (merged_detailed_log_df[exit_reason_col_name_in_merged] == 'ProfitLockEx_Bar1') &
                    (abs(merged_detailed_log_df[raw_pnl_col_name_in_merged] - current_config['pl_level']) > 0.01) & # Not close to pl_level
                    (merged_detailed_log_df[raw_pnl_col_name_in_merged] < (current_config['pl_level'] - 0.01) ) # And significantly less than pl_level
                ].copy()
                print("\n\n--- Detailed Log for ProfitLockEx_Bar1 with Raw PNL significantly different from PL_Level ---")
                if not problematic_profit_lock_trades_df.empty:
                    problematic_pl_filename = f"detailed_log_problematic_PL_Bar1_{config_label}.csv"
                    if all(c in problematic_profit_lock_trades_df for c in ['Trade_ID_TS', 'Bar_Num_In_Trade']):
                        problematic_profit_lock_trades_df.sort_values(by=['Trade_ID_TS', 'Bar_Num_In_Trade'], inplace=True)
                    problematic_profit_lock_trades_df.to_csv(problematic_pl_filename, index=False, float_format='%.5f')
                    print(f"Saved detailed log for problematic PL trades to {problematic_pl_filename}")
                    print(f"Number of such trades: {problematic_profit_lock_trades_df['Trade_ID_TS'].nunique()}")
                else: print("No ProfitLockEx_Bar1 trades with Raw PNL significantly different from PL_Level found.")
            # ... (rest of small loss analysis) ...
            else:
                 missing_cols = [col for col in required_cols_for_analysis + ['Trade_ID_TS', 'Bar_Num_In_Trade'] if col not in merged_detailed_log_df.columns]
                 print(f"Warning: Required columns for analysis missing in merged log: {missing_cols}. Skipping.")
        else: print("No trade summary results to merge with detailed log.")
    else: print("No detailed bar-by-bar logs generated.")
    print(f"\n--- End of Test for Config: {config_label} ---")
else:
    print("DataFrame df_ohlc_prepared is not available. Cannot proceed with analysis.")