import sys import os import pandas as pd from datetime import datetime from tqdm import tqdm # 添加项目根目录到路径 sys.path.insert(0, os.path.dirname(os.path.abspath(__name__))) from core.data_providers import BacktestDataProvider from core.risk import RiskController from execution.dynamic_weights import DynamicWeightManager # Still needed for strategy list from config import SYMBOL, TIMEFRAME, BACKTEST_COUNT, BACKTEST_START_DATE, BACKTEST_END_DATE, USE_DATE_RANGE, BACKTEST_CONFIG, INITIAL_CAPITAL, SIGNAL_THRESHOLDS, DEFAULT_WEIGHTS from logger import logger # Import all strategy classes from strategies.ma_cross import MACrossStrategy from strategies.rsi import RSIStrategy from strategies.bollinger import BollingerStrategy from strategies.mean_reversion import MeanReversionStrategy from strategies.momentum_breakout import MomentumBreakoutStrategy from strategies.macd import MACDStrategy from strategies.kdj import KDJStrategy from strategies.turtle import TurtleStrategy from strategies.daily_breakout import DailyBreakoutStrategy from strategies.wave_theory import WaveTheoryStrategy def run_full_backtest(): """完整的、独立的、已重构的回测流程""" # 1. 加载数据 from core.utils import get_rates, initialize, shutdown initialize() if USE_DATE_RANGE: rates = get_rates(SYMBOL, TIMEFRAME, BACKTEST_COUNT, BACKTEST_START_DATE, BACKTEST_END_DATE) else: rates = get_rates(SYMBOL, TIMEFRAME, BACKTEST_COUNT) shutdown() if rates is None: logger.error("未能获取历史数据,回测终止。") return logger.info(f"实际获取数据量: {len(rates)} 条 (请求: {BACKTEST_COUNT} 条)") # 如果实际获取的数据量超过配置,则截取配置的数据量 if len(rates) > BACKTEST_COUNT: rates = rates[-BACKTEST_COUNT:] # 取最新的数据 logger.info(f"截取数据量为: {len(rates)} 条") df = pd.DataFrame(rates) df.set_index(pd.to_datetime(df['time'], unit='s'), inplace=True) # 2. 初始化组件 (data_provider, risk_controller) data_provider = BacktestDataProvider(df, initial_equity=INITIAL_CAPITAL) risk_controller = RiskController(data_provider, trade_direction=BACKTEST_CONFIG['trade_direction']) # weight_manager is not directly used for signal generation in this new architecture, # but its strategy_blueprints can be used to instantiate strategies. # 3. 策略实例化和信号预生成 (NEW STAGE 1) logger.info("开始预生成所有策略信号...") # Define strategy blueprints directly here or get from DynamicWeightManager if it's refactored # For now, let's define them directly as DynamicWeightManager is for real-time dynamic weights. strategy_blueprints = { 'ma_cross': (MACrossStrategy, {}), 'rsi': (RSIStrategy, {}), 'bollinger': (BollingerStrategy, {}), 'mean_reversion': (MeanReversionStrategy, {}), 'momentum_breakout': (MomentumBreakoutStrategy, {}), 'macd': (MACDStrategy, {}), 'kdj': (KDJStrategy, {}), 'turtle': (TurtleStrategy, {}), 'daily_breakout': (DailyBreakoutStrategy, {}), 'wave_theory': (WaveTheoryStrategy, {}) } all_signals_df = pd.DataFrame(index=df.index) # Map strategy class names to config keys for weights strategy_name_to_config_key = { "MACrossStrategy": "ma_cross", "RSIStrategy": "rsi", "BollingerStrategy": "bollinger", "MeanReversionStrategy": "mean_reversion", "MomentumBreakoutStrategy": "momentum_breakout", "MACDStrategy": "macd", "KDJStrategy": "kdj", "TurtleStrategy": "turtle", "DailyBreakoutStrategy": "daily_breakout", "WaveTheoryStrategy": "wave_theory" } strategies_for_backtest = [] for name, (strategy_class, params) in strategy_blueprints.items(): # Pass None for data_provider, symbol, timeframe as run_backtest only needs df # But strategy __init__ expects them. So, pass dummy values. strategies_for_backtest.append(strategy_class(None, SYMBOL, TIMEFRAME, **params)) for strategy in tqdm(strategies_for_backtest, desc="Generating Strategy Signals"): if hasattr(strategy, 'run_backtest') and callable(getattr(strategy, 'run_backtest')): try: strategy_signals = strategy.run_backtest(df.copy()) # Pass a copy to avoid modifying original df if strategy_signals is not None: all_signals_df[strategy.name] = strategy_signals logger.info(f"策略 {strategy.name} 信号生成成功") else: logger.warning(f"策略 {strategy.name} 返回空信号") except Exception as e: logger.error(f"策略 {strategy.name} 执行失败: {str(e)}") # 为失败的策略创建全0信号序列 all_signals_df[strategy.name] = pd.Series(0, index=df.index) continue else: logger.warning(f"策略 {strategy.name} 没有实现 'run_backtest' 方法,将被跳过。") # 组合信号 logger.info("开始组合策略信号...") combined_weighted_signals = pd.Series(0, index=df.index) if not all_signals_df.empty: weighted_signals_list = [] for col_name in all_signals_df.columns: # Extract strategy class name from column name (e.g., 'MACrossStrategy') strategy_class_name = col_name config_key = strategy_name_to_config_key.get(strategy_class_name) if config_key and config_key in DEFAULT_WEIGHTS: weight = DEFAULT_WEIGHTS[config_key] weighted_signals_list.append(all_signals_df[col_name] * weight) else: logger.warning(f"未找到策略 {strategy_class_name} 的默认权重,使用权重1.0。") weighted_signals_list.append(all_signals_df[col_name] * 1.0) if weighted_signals_list: combined_weighted_signals = pd.concat(weighted_signals_list, axis=1).sum(axis=1) else: logger.warning("没有生成任何加权信号。") # 应用阈值得到最终信号 final_signals = pd.Series(0, index=df.index) buy_threshold = SIGNAL_THRESHOLDS.get("buy_threshold", 1.0) sell_threshold = SIGNAL_THRESHOLDS.get("sell_threshold", -1.0) final_signals[combined_weighted_signals > buy_threshold] = 1 final_signals[combined_weighted_signals < sell_threshold] = -1 logger.info("信号预生成和组合完成。") # 4. 回测主循环 (NEW STAGE 2) logger.info(f"回测主循环开始... 数据量: {len(df)} 条") # Reset data_provider's internal index to 0 for the loop data_provider.current_index = 0 for i in tqdm(range(len(df)), desc=f"Backtesting ({len(df)} bars)"): try: # Get current price from the data_provider (which uses its internal index) current_price = data_provider.get_current_price(SYMBOL) if not current_price: logger.warning(f"无法获取当前价格在索引 {i},跳过。") continue # Get the pre-generated signal for the current bar current_signal = final_signals.iloc[i] direction = None if current_signal == 1: direction = "buy" elif current_signal == -1: direction = "sell" if direction: risk_controller.process_trading_signal(direction, current_price, abs(current_signal)) # Monitor and update positions risk_controller.monitor_positions(current_price) # Advance data provider to the next time step data_provider.tick() except Exception as e: logger.error(f"回测循环中在索引 {i} 发生错误: {str(e)}") # 继续下一个bar,不中断整个回测 continue # 5. 结束和报告 (Keep as is) logger.info("回测完成,生成性能报告...") summary = risk_controller.position_manager.get_trade_summary() # 打印报告 logger.info("=" * 80) logger.info("回测性能报告") logger.info(f"总交易次数: {summary.get('total_trades', 0)}") logger.info(f"胜率: {summary.get('win_rate', 0):.2f}%") logger.info(f"总盈亏: ${summary.get('total_profit_loss', 0):.2f}") logger.info("=" * 80) # 保存交易记录 try: risk_controller.position_manager.save_trade_history("backtest_trades") logger.info("交易记录保存成功") except Exception as e: logger.error(f"保存交易记录失败: {str(e)}") # 尝试手动保存 try: import json trades = risk_controller.position_manager.closed_trades if trades: with open("backtest_trades_manual.json", 'w', encoding='utf-8') as f: json.dump(trades, f, ensure_ascii=False, indent=2, default=str) logger.info("手动保存交易记录到 backtest_trades_manual.json") except Exception as e2: logger.error(f"手动保存交易记录也失败: {str(e2)}") def main(): print("=" * 60) print("MetaTrader 5 智能交易系统 - 回测") print("=" * 60) try: run_full_backtest() print("\n回测完成!") except Exception as e: import traceback print(f"\n回测出错: {e}") traceback.print_exc() if __name__ == "__main__": main()