#!/usr/bin/env python3 """ Diagnostic script to identify the root cause of backtesting issues Focuses on the specific problem: 100% max drawdown and terrible performance across all strategies """ import sys import os sys.path.append(os.path.dirname(os.path.abspath(__file__))) import pandas as pd import yfinance as yf from core.backtesting.engine import run_backtest as run_original_backtest from core.backtesting.enhanced_engine import run_enhanced_backtest from core.strategies.bollinger_squeeze import BollingerSqueezeStrategy from datetime import datetime, timedelta def download_test_data(): """Download recent EURUSD data for testing""" try: # Download EURUSD data for the last 6 months end_date = datetime.now() start_date = end_date - timedelta(days=180) # Download EURUSD data ticker = yf.Ticker("EURUSD=X") df = ticker.history(start=start_date, end=end_date, interval="1h") if df.empty: print("āŒ Failed to download EURUSD data") return None # Standardize column names df.columns = df.columns.str.lower() df.reset_index(inplace=True) print(f"āœ… Downloaded {len(df)} EURUSD bars from {df['datetime'].min()} to {df['datetime'].max()}") return df except Exception as e: print(f"āŒ Error downloading data: {e}") return None def test_strategy_signals(df, strategy_class, params): """Test if strategy is generating reasonable signals""" print(f"\nšŸ” Testing {strategy_class.name} signal generation...") # Create mock bot class MockBot: def __init__(self): self.market_for_mt5 = "EURUSD" self.timeframe = "H1" self.tf_map = {} try: # Initialize strategy strategy_instance = strategy_class(bot_instance=MockBot(), params=params) # Analyze data df_with_signals = strategy_instance.analyze_df(df.copy()) # Count signals signal_counts = df_with_signals['signal'].value_counts() print(f"Signal distribution: {dict(signal_counts)}") # Check if we have reasonable signals buy_signals = len(df_with_signals[df_with_signals['signal'] == 'BUY']) sell_signals = len(df_with_signals[df_with_signals['signal'] == 'SELL']) hold_signals = len(df_with_signals[df_with_signals['signal'] == 'HOLD']) total_bars = len(df_with_signals) print(f"BUY signals: {buy_signals} ({buy_signals/total_bars*100:.1f}%)") print(f"SELL signals: {sell_signals} ({sell_signals/total_bars*100:.1f}%)") print(f"HOLD signals: {hold_signals} ({hold_signals/total_bars*100:.1f}%)") # Check for indicators print(f"Available columns: {list(df_with_signals.columns)}") # Check ATR if 'ATRr_14' in df_with_signals.columns: atr_stats = df_with_signals['ATRr_14'].describe() print(f"ATR stats: min={atr_stats['min']:.6f}, max={atr_stats['max']:.6f}, mean={atr_stats['mean']:.6f}") else: print("āŒ ATR indicator missing!") return df_with_signals except Exception as e: print(f"āŒ Error in strategy analysis: {e}") return None def test_backtest_calculations(df_with_signals, params): """Test the actual backtest calculations step by step""" print(f"\n🧮 Testing backtest calculations...") # Parameters for testing risk_percent = float(params.get('lot_size', 1.0)) sl_atr_multiplier = float(params.get('sl_pips', 2.0)) tp_atr_multiplier = float(params.get('tp_pips', 4.0)) print(f"Risk: {risk_percent}%, SL: {sl_atr_multiplier}x ATR, TP: {tp_atr_multiplier}x ATR") # Simulate a few trades manually trades_found = 0 capital = 10000.0 initial_capital = capital for i in range(1, min(len(df_with_signals), 100)): # Check first 100 bars current_bar = df_with_signals.iloc[i] signal = current_bar.get("signal", "HOLD") if signal in ['BUY', 'SELL']: trades_found += 1 entry_price = current_bar['close'] atr_value = current_bar.get('ATRr_14', 0) print(f"\nTrade #{trades_found} at bar {i}:") print(f" Signal: {signal}") print(f" Entry price: {entry_price}") print(f" ATR: {atr_value}") print(f" Capital: ${capital:.2f}") if atr_value > 0: # Calculate position size sl_distance = atr_value * sl_atr_multiplier tp_distance = atr_value * tp_atr_multiplier # For EURUSD (forex major) contract_size = 100000 amount_to_risk = capital * (risk_percent / 100.0) risk_in_currency_per_lot = sl_distance * contract_size if risk_in_currency_per_lot > 0: calculated_lot_size = amount_to_risk / risk_in_currency_per_lot lot_size = max(0.01, min(calculated_lot_size, 10.0)) else: lot_size = 0.01 print(f" SL distance: {sl_distance:.5f}") print(f" TP distance: {tp_distance:.5f}") print(f" Amount to risk: ${amount_to_risk:.2f}") print(f" Risk per lot: ${risk_in_currency_per_lot:.2f}") print(f" Calculated lot size: {calculated_lot_size:.4f}") print(f" Final lot size: {lot_size:.2f}") # Set SL/TP levels if signal == 'BUY': sl_price = entry_price - sl_distance tp_price = entry_price + tp_distance else: sl_price = entry_price + sl_distance tp_price = entry_price - tp_distance print(f" SL price: {sl_price:.5f}") print(f" TP price: {tp_price:.5f}") # Check if prices are reasonable if sl_price <= 0 or tp_price <= 0: print(" āŒ Invalid SL/TP prices!") elif abs((sl_price - entry_price) / entry_price) > 0.1: print(" āŒ SL distance too large (>10%)!") elif abs((tp_price - entry_price) / entry_price) > 0.2: print(" āŒ TP distance too large (>20%)!") else: print(" āœ… Trade setup looks reasonable") else: print(" āŒ Invalid ATR value!") if trades_found >= 5: # Limit to first 5 trades break print(f"\nFound {trades_found} trades in first 100 bars") return trades_found > 0 def run_diagnostic(): """Main diagnostic function""" print("šŸš€ Starting Backtesting Diagnostic...") print("=" * 60) # 1. Download test data df = download_test_data() if df is None: return # 2. Test strategy parameters params = { 'bb_length': 20, 'bb_std': 2.0, 'squeeze_window': 10, 'squeeze_factor': 0.7, 'rsi_period': 14, 'lot_size': 1.0, # 1% risk 'sl_pips': 2.0, # 2x ATR 'tp_pips': 4.0 # 4x ATR } print(f"Test parameters: {params}") # 3. Test Bollinger Squeeze strategy signals df_with_signals = test_strategy_signals(df, BollingerSqueezeStrategy, params) if df_with_signals is None: return # 4. Test manual calculations if not test_backtest_calculations(df_with_signals, params): print("āŒ No valid trades found for manual testing") return # 5. Run original backtest print(f"\nšŸ”„ Running original backtest...") try: original_result = run_original_backtest('bollinger_squeeze', params, df, 'EURUSD') print(f"Original engine result:") print(f" Total trades: {original_result.get('total_trades', 0)}") print(f" Total profit: ${original_result.get('total_profit_usd', 0):.2f}") print(f" Win rate: {original_result.get('win_rate_percent', 0):.1f}%") print(f" Max drawdown: {original_result.get('max_drawdown_percent', 0):.1f}%") except Exception as e: print(f"āŒ Original backtest failed: {e}") original_result = None # 6. Run enhanced backtest print(f"\nšŸš€ Running enhanced backtest...") try: enhanced_result = run_enhanced_backtest('bollinger_squeeze', params, df, 'EURUSD') print(f"Enhanced engine result:") print(f" Total trades: {enhanced_result.get('total_trades', 0)}") print(f" Gross profit: ${enhanced_result.get('total_profit_usd', 0):.2f}") print(f" Spread costs: ${enhanced_result.get('total_spread_costs', 0):.2f}") print(f" Net profit: ${enhanced_result.get('net_profit_after_costs', 0):.2f}") print(f" Win rate: {enhanced_result.get('win_rate_percent', 0):.1f}%") print(f" Max drawdown: {enhanced_result.get('max_drawdown_percent', 0):.1f}%") # Check individual trades if enhanced_result.get('trades'): print(f"\nLast few trades:") for i, trade in enumerate(enhanced_result['trades'][-3:]): print(f" Trade {i+1}: {trade['position_type']} | Entry: {trade['entry']:.5f} | Exit: {trade['exit']:.5f} | Profit: ${trade['profit']:.2f}") except Exception as e: print(f"āŒ Enhanced backtest failed: {e}") enhanced_result = None # 7. Analysis and recommendations print(f"\nšŸ“Š DIAGNOSIS SUMMARY:") print("=" * 60) if original_result and enhanced_result: if enhanced_result.get('max_drawdown_percent', 0) > 90: print("āŒ CRITICAL ISSUE: Enhanced engine shows extreme drawdown!") print(" Possible causes:") print(" - Position sizing too aggressive") print(" - Spread costs too high") print(" - SL/TP calculation errors") print(" - Strategy generating bad signals") elif original_result.get('max_drawdown_percent', 0) > 90: print("āŒ CRITICAL ISSUE: Original engine shows extreme drawdown!") print(" Possible causes:") print(" - Position sizing calculation error") print(" - SL/TP logic bug") print(" - Strategy overfitting") else: print("āœ… Backtest engines working reasonably") print("\nRecommendations:") print("1. Check position sizing calculations") print("2. Verify SL/TP distance calculations") print("3. Test with more conservative parameters") print("4. Validate strategy signal quality") if __name__ == '__main__': run_diagnostic()