run_backtest.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. """
  2. 运行 CYB50-Pro 回测验证
  3. 使用 2018-2025 年历史数据验证目标指标:
  4. - 年化收益 25-35%
  5. - 最大回撤 <12%
  6. - 夏普比率 >1.5
  7. """
  8. import sys
  9. import os
  10. sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
  11. import pandas as pd
  12. import numpy as np
  13. from datetime import datetime
  14. import json
  15. from backtest import CYB50ProBacktester
  16. def load_historical_data(data_path: str = None) -> pd.DataFrame:
  17. """
  18. 加载历史数据
  19. 优先从现有项目加载,如果没有则生成模拟数据
  20. """
  21. # 尝试从现有项目加载
  22. possible_paths = [
  23. data_path,
  24. '../../quant/cyb50_historical_data.csv',
  25. '../../quant/cyb50_baostock.csv',
  26. '../../market-regime-identifier/SZ#399673.txt',
  27. '../data/SZ#399673.csv'
  28. ]
  29. for path in possible_paths:
  30. if path and os.path.exists(path):
  31. print(f"Loading data from: {path}")
  32. if path.endswith('.csv'):
  33. df = pd.read_csv(path)
  34. elif path.endswith('.txt'):
  35. # 处理txt格式
  36. df = pd.read_csv(path, sep='\t', header=None,
  37. names=['date', 'open', 'high', 'low', 'close', 'volume'])
  38. else:
  39. continue
  40. # 标准化列名
  41. df.columns = [c.lower() for c in df.columns]
  42. # 解析日期
  43. if 'date' in df.columns:
  44. df['date'] = pd.to_datetime(df['date'])
  45. df.set_index('date', inplace=True)
  46. elif 'datetime' in df.columns:
  47. df['datetime'] = pd.to_datetime(df['datetime'])
  48. df.set_index('datetime', inplace=True)
  49. return df
  50. # 如果没有找到数据,生成模拟数据
  51. print("No historical data found. Generating synthetic data for testing...")
  52. return generate_synthetic_data()
  53. def generate_synthetic_data(start='2018-01-01', end='2025-01-01') -> pd.DataFrame:
  54. """生成模拟的创业板50指数数据"""
  55. np.random.seed(42)
  56. dates = pd.date_range(start=start, end=end, freq='D')
  57. # 生成带有趋势和波动率聚类的收益率
  58. returns = []
  59. current_regime = 'bull'
  60. regime_days = 0
  61. for i in range(len(dates)):
  62. # 随机切换市场状态
  63. if regime_days <= 0:
  64. current_regime = np.random.choice(['bull', 'bear', 'sideways'], p=[0.5, 0.2, 0.3])
  65. regime_days = np.random.randint(60, 252) # 60天到1年
  66. # 根据状态生成收益
  67. if current_regime == 'bull':
  68. daily_return = np.random.normal(0.001, 0.015)
  69. elif current_regime == 'bear':
  70. daily_return = np.random.normal(-0.001, 0.025)
  71. else: # sideways
  72. daily_return = np.random.normal(0, 0.012)
  73. returns.append(daily_return)
  74. regime_days -= 1
  75. returns = np.array(returns)
  76. prices = 1000 * np.exp(np.cumsum(returns))
  77. # 生成OHLCV
  78. df = pd.DataFrame(index=dates)
  79. df['close'] = prices
  80. df['open'] = df['close'] * (1 + np.random.normal(0, 0.005, len(dates)))
  81. df['high'] = df[['open', 'close']].max(axis=1) * (1 + abs(np.random.normal(0, 0.01, len(dates))))
  82. df['low'] = df[['open', 'close']].min(axis=1) * (1 - abs(np.random.normal(0, 0.01, len(dates))))
  83. df['volume'] = np.random.lognormal(20, 0.5, len(dates))
  84. return df
  85. def main():
  86. """主函数"""
  87. print("=" * 60)
  88. print("CYB50-Pro Backtest Validation")
  89. print("Target Metrics:")
  90. print(" - Annual Return: 25-35%")
  91. print(" - Max Drawdown: <12%")
  92. print(" - Sharpe Ratio: >1.5")
  93. print("=" * 60)
  94. # 加载数据
  95. data = load_historical_data()
  96. print(f"\nData loaded: {len(data)} bars from {data.index[0]} to {data.index[-1]}")
  97. print(f"Price range: {data['close'].min():.2f} - {data['close'].max():.2f}")
  98. # 初始化回测器
  99. backtester = CYB50ProBacktester(
  100. initial_capital=1_000_000,
  101. commission_rate=0.0005,
  102. slippage=0.001
  103. )
  104. # 运行回测
  105. print("\nRunning backtest...")
  106. result = backtester.run_backtest(
  107. price_data=data,
  108. start_date='2018-01-01',
  109. end_date='2024-12-31'
  110. )
  111. # 打印结果
  112. print("\n" + "=" * 60)
  113. print("BACKTEST RESULTS")
  114. print("=" * 60)
  115. print(f"\n[Period]")
  116. print(f" Start: {result.start_date.strftime('%Y-%m-%d')}")
  117. print(f" End: {result.end_date.strftime('%Y-%m-%d')}")
  118. print(f"\n[Returns]")
  119. print(f" Total Return: {result.total_return:+.2%}")
  120. print(f" Annual Return: {result.annual_return:+.2%}")
  121. print(f"\n[Risk Metrics]")
  122. print(f" Max Drawdown: {result.max_drawdown:.2%}")
  123. print(f" Max DD Duration: {result.max_drawdown_duration} days")
  124. print(f" Volatility (Ann): {result.volatility:.2%}")
  125. print(f"\n[Risk-Adjusted Returns]")
  126. print(f" Sharpe Ratio: {result.sharpe_ratio:.2f}")
  127. print(f" Sortino Ratio: {result.sortino_ratio:.2f}")
  128. print(f"\n[Trading Statistics]")
  129. print(f" Total Trades: {result.total_trades}")
  130. print(f" Win Rate: {result.win_rate:.1%}")
  131. print(f" Profit Factor: {result.profit_factor:.2f}")
  132. # Target Validation
  133. print(f"\n[Target Validation]")
  134. targets_met = 0
  135. if 0.25 <= result.annual_return <= 0.35:
  136. print(f" [OK] Annual Return 25-35%: {result.annual_return:.2%}")
  137. targets_met += 1
  138. else:
  139. print(f" [FAIL] Annual Return 25-35%: {result.annual_return:.2%}")
  140. if result.max_drawdown > -0.12:
  141. print(f" [OK] Max Drawdown <12%: {result.max_drawdown:.2%}")
  142. targets_met += 1
  143. else:
  144. print(f" [FAIL] Max Drawdown <12%: {result.max_drawdown:.2%}")
  145. if result.sharpe_ratio > 1.5:
  146. print(f" [OK] Sharpe Ratio >1.5: {result.sharpe_ratio:.2f}")
  147. targets_met += 1
  148. else:
  149. print(f" [FAIL] Sharpe Ratio >1.5: {result.sharpe_ratio:.2f}")
  150. print(f"\n[Summary]")
  151. print(f" Targets Met: {targets_met}/3")
  152. if targets_met == 3:
  153. print(" Status: ALL TARGETS ACHIEVED [OK][OK][OK]")
  154. elif targets_met >= 2:
  155. print(" Status: MOSTLY SATISFACTORY")
  156. else:
  157. print(" Status: NEEDS OPTIMIZATION")
  158. # Regime Analysis
  159. print(f"\n[Regime Performance]")
  160. for regime, stats in result.regime_performance.items():
  161. print(f" {regime}: {stats['trades']} trades, "
  162. f"Win Rate {stats['win_rate']:.1%}, "
  163. f"Avg PnL {stats['avg_pnl']:,.0f}")
  164. # Agent Contributions
  165. print(f"\n[Agent Contributions]")
  166. for agent, pnl in sorted(result.agent_contributions.items(),
  167. key=lambda x: x[1], reverse=True):
  168. print(f" {agent}: {pnl:+,.0f}")
  169. # Generate Report
  170. output_dir = os.path.join(os.path.dirname(__file__), '../reports')
  171. os.makedirs(output_dir, exist_ok=True)
  172. report_path = os.path.join(output_dir, f'backtest_report_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json')
  173. report = backtester.generate_report(result, report_path)
  174. print(f"\n[Report Saved]")
  175. print(f" {report_path}")
  176. print("\n" + "=" * 60)
  177. return result
  178. if __name__ == "__main__":
  179. main()