cyb50_realistic.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 创业板50指数 - 基于真实统计特征的回测
  5. 使用与真实指数相同的统计特征(均值、波动率、偏度、峰度)
  6. """
  7. import pandas as pd
  8. import numpy as np
  9. import matplotlib
  10. matplotlib.use('Agg')
  11. import matplotlib.pyplot as plt
  12. import warnings
  13. warnings.filterwarnings('ignore')
  14. def load_real_data():
  15. """加载创业板50指数真实数据 - cyb50_baostock.csv"""
  16. df = pd.read_csv('cyb50_baostock.csv')
  17. df['date'] = pd.to_datetime(df['date'])
  18. df = df.set_index('date').sort_index()
  19. # 转换数据类型
  20. for col in ['open', 'high', 'low', 'close', 'volume']:
  21. df[col] = pd.to_numeric(df[col], errors='coerce')
  22. # 统计验证
  23. daily_returns = df['close'].pct_change().dropna()
  24. print(f"真实数据统计特征:")
  25. print(f" 日收益均值: {daily_returns.mean()*100:.4f}%")
  26. print(f" 日收益标准差: {daily_returns.std()*100:.2f}%")
  27. print(f" 年化收益: {daily_returns.mean()*252*100:.1f}%")
  28. print(f" 年化波动: {daily_returns.std()*np.sqrt(252)*100:.1f}%")
  29. return df
  30. class RealisticStrategy:
  31. """
  32. 高收益趋势策略 - 激进版
  33. 目标:年化25%+
  34. """
  35. def __init__(self, leverage=1.5):
  36. self.leverage = leverage
  37. self.position = 0
  38. self.entry_price = 0
  39. self.peak_price = 0
  40. self.trades = []
  41. def generate_signal(self, data):
  42. """生成交易信号 - 激进策略"""
  43. close = data['close'].values
  44. high = data['high'].values
  45. low = data['low'].values
  46. if len(close) < 30:
  47. return 0, "INIT"
  48. # 超短周期指标(更敏感)
  49. ma3 = np.mean(close[-3:])
  50. ma10 = np.mean(close[-10:])
  51. ma30 = np.mean(close[-30:])
  52. # 趋势判断
  53. trend_up = (close[-1] > ma3) and (ma3 > ma10)
  54. trend_strong = trend_up and (ma10 > ma30)
  55. # 动量
  56. ret10 = (close[-1] / close[-10] - 1) if len(close) >= 10 else 0
  57. ret30 = (close[-1] / close[-30] - 1) if len(close) >= 30 else 0
  58. # 突破检测(更敏感)
  59. high_10 = np.max(high[-10:])
  60. low_10 = np.min(low[-10:])
  61. breakout_up = close[-1] >= high_10 * 0.998
  62. breakout_down = close[-1] <= low_10 * 1.002
  63. curr_price = close[-1]
  64. # 仓位决策
  65. if trend_strong and breakout_up and ret10 > 0.02:
  66. # 强趋势+突破+正动量 = 满仓加杠杆
  67. target_pos = 1.0 * self.leverage
  68. elif trend_up and breakout_up:
  69. # 趋势向上+突破 = 满仓
  70. target_pos = 1.0
  71. elif trend_up and ret10 > 0:
  72. # 趋势向上 = 半仓
  73. target_pos = 0.5
  74. elif breakout_down or (ret10 < -0.03):
  75. # 突破下轨或大跌 = 清仓
  76. target_pos = 0.0
  77. else:
  78. target_pos = self.position
  79. # 移动止损(更宽松)
  80. if self.position > 0:
  81. if curr_price > self.peak_price:
  82. self.peak_price = curr_price
  83. drawdown = (curr_price - self.peak_price) / self.peak_price
  84. if drawdown < -0.12: # 12%移动止损
  85. target_pos = 0.0
  86. elif drawdown < -0.08: # 8%减仓
  87. target_pos = target_pos * 0.5
  88. # 入场后亏损8%止损
  89. if self.entry_price > 0:
  90. loss = (curr_price - self.entry_price) / self.entry_price
  91. if loss < -0.08:
  92. target_pos = 0.0
  93. # 状态更新
  94. if target_pos > 0 and self.position == 0:
  95. self.entry_price = curr_price
  96. self.peak_price = curr_price
  97. state = "ENTRY"
  98. elif target_pos == 0 and self.position > 0:
  99. self.entry_price = 0
  100. self.peak_price = 0
  101. state = "EXIT"
  102. elif target_pos >= 1.0 * self.leverage:
  103. state = "FULL_LEV"
  104. elif target_pos >= 1.0:
  105. state = "FULL"
  106. elif target_pos > 0:
  107. state = "PARTIAL"
  108. else:
  109. state = "EMPTY"
  110. self.position = target_pos
  111. return target_pos, state
  112. def backtest(data, strategy, start_date, end_date, warmup=60):
  113. """回测引擎"""
  114. data = data[(data.index >= start_date) & (data.index <= end_date)]
  115. results = []
  116. nav = 1.0
  117. for i in range(warmup, len(data)):
  118. curr_data = data.iloc[:i+1]
  119. pos, state = strategy.generate_signal(curr_data)
  120. if i > warmup:
  121. daily_ret = data['close'].iloc[i] / data['close'].iloc[i-1] - 1
  122. strategy_ret = daily_ret * results[-1]['pos']
  123. nav *= (1 + strategy_ret)
  124. results.append({
  125. 'date': data.index[i],
  126. 'pos': pos,
  127. 'nav': nav,
  128. 'state': state,
  129. 'close': data['close'].iloc[i]
  130. })
  131. df = pd.DataFrame(results).set_index('date')
  132. df['index_nav'] = df['close'] / df['close'].iloc[0]
  133. return df
  134. def calculate_metrics(nav, index_nav):
  135. """计算绩效指标"""
  136. s_returns = nav.pct_change().dropna()
  137. total_return = nav.iloc[-1] - 1
  138. days = len(nav)
  139. annual_return = (1 + total_return) ** (252 / days) - 1
  140. index_return = index_nav.iloc[-1] - 1
  141. index_annual = (1 + index_return) ** (252 / days) - 1
  142. running_max = nav.expanding().max()
  143. max_dd = ((nav - running_max) / running_max).min()
  144. volatility = s_returns.std() * np.sqrt(252)
  145. sharpe = (annual_return - 0.03) / volatility if volatility > 0 else 0
  146. calmar = annual_return / abs(max_dd) if max_dd != 0 else 0
  147. win_rate = (s_returns > 0).mean()
  148. return {
  149. 'annual_return': annual_return,
  150. 'index_annual': index_annual,
  151. 'excess_annual': annual_return - index_annual,
  152. 'max_drawdown': max_dd,
  153. 'volatility': volatility,
  154. 'sharpe': sharpe,
  155. 'calmar': calmar,
  156. 'win_rate': win_rate,
  157. 'total_return': total_return,
  158. 'index_return': index_return
  159. }
  160. def plot_results(results, title, filename):
  161. """绘制回测结果"""
  162. fig, axes = plt.subplots(3, 1, figsize=(14, 10))
  163. # 净值曲线
  164. ax1 = axes[0]
  165. ax1.plot(results.index, results['nav'], 'r-', linewidth=2, label='Strategy')
  166. ax1.plot(results.index, results['index_nav'], 'gray', linewidth=1, alpha=0.7, label='Index')
  167. ax1.set_title(title, fontsize=14)
  168. ax1.set_ylabel('NAV')
  169. ax1.legend()
  170. ax1.grid(True, alpha=0.3)
  171. # 仓位变化
  172. ax2 = axes[1]
  173. ax2.fill_between(results.index, 0, results['pos'], alpha=0.5, color='green')
  174. ax2.set_ylabel('Position')
  175. ax2.set_ylim(0, 1.1)
  176. ax2.grid(True, alpha=0.3)
  177. # 回撤
  178. ax3 = axes[2]
  179. running_max = results['nav'].expanding().max()
  180. drawdown = (results['nav'] - running_max) / running_max
  181. ax3.fill_between(results.index, drawdown, 0, alpha=0.3, color='red')
  182. ax3.set_ylabel('Drawdown')
  183. ax3.set_xlabel('Date')
  184. ax3.grid(True, alpha=0.3)
  185. plt.tight_layout()
  186. plt.savefig(filename, dpi=150)
  187. print(f" 图表已保存: {filename}")
  188. def main():
  189. print("="*70)
  190. print("创业板50指数 - 基于真实统计特征的回测")
  191. print("="*70)
  192. # 加载真实数据
  193. print("\n[1] 加载真实数据...")
  194. data = load_real_data()
  195. print(f" 数据区间: {data.index[0].date()} ~ {data.index[-1].date()}")
  196. print(f" 总交易日: {len(data)}")
  197. # 训练阶段
  198. print("\n[2] 训练阶段 (2018-2023)...")
  199. strategy = RealisticStrategy()
  200. train_results = backtest(data, strategy, '2018-01-01', '2023-12-31')
  201. train_metrics = calculate_metrics(train_results['nav'], train_results['index_nav'])
  202. print(f"\n ╔══════════════════════════════════════╗")
  203. print(f" ║ 训 练 集 结 果 ║")
  204. print(f" ╠══════════════════════════════════════╣")
  205. print(f" ║ 策略总收益: {train_metrics['total_return']*100:8.1f}% ║")
  206. print(f" ║ 指数总收益: {train_metrics['index_return']*100:8.1f}% ║")
  207. print(f" ║ ───────────────────────────────── ║")
  208. print(f" ║ 策略年化: {train_metrics['annual_return']*100:8.1f}% ║")
  209. print(f" ║ 指数年化: {train_metrics['index_annual']*100:8.1f}% ║")
  210. print(f" ║ 超额收益: {train_metrics['excess_annual']*100:8.1f}% ║")
  211. print(f" ║ ───────────────────────────────── ║")
  212. print(f" ║ 最大回撤: {train_metrics['max_drawdown']*100:8.1f}% ║")
  213. print(f" ║ 年化波动: {train_metrics['volatility']*100:8.1f}% ║")
  214. print(f" ║ 夏普比率: {train_metrics['sharpe']:8.2f} ║")
  215. print(f" ║ 卡玛比率: {train_metrics['calmar']:8.2f} ║")
  216. print(f" ║ 胜率: {train_metrics['win_rate']*100:8.1f}% ║")
  217. print(f" ╚══════════════════════════════════════╝")
  218. plot_results(train_results, "Training Set (2018-2023)", "train_realistic.png")
  219. # 验证阶段
  220. print("\n[3] 验证阶段 (2024-2025)...")
  221. strategy_val = RealisticStrategy()
  222. val_results = backtest(data, strategy_val, '2024-01-01', '2025-12-31')
  223. val_metrics = calculate_metrics(val_results['nav'], val_results['index_nav'])
  224. print(f"\n ╔══════════════════════════════════════╗")
  225. print(f" ║ 验 证 集 结 果 ║")
  226. print(f" ╠══════════════════════════════════════╣")
  227. print(f" ║ 策略总收益: {val_metrics['total_return']*100:8.1f}% ║")
  228. print(f" ║ 指数总收益: {val_metrics['index_return']*100:8.1f}% ║")
  229. print(f" ║ ───────────────────────────────── ║")
  230. print(f" ║ 策略年化: {val_metrics['annual_return']*100:8.1f}% ║")
  231. print(f" ║ 指数年化: {val_metrics['index_annual']*100:8.1f}% ║")
  232. print(f" ║ 超额收益: {val_metrics['excess_annual']*100:8.1f}% ║")
  233. print(f" ║ ───────────────────────────────── ║")
  234. print(f" ║ 最大回撤: {val_metrics['max_drawdown']*100:8.1f}% ║")
  235. print(f" ║ 夏普比率: {val_metrics['sharpe']:8.2f} ║")
  236. print(f" ╚══════════════════════════════════════╝")
  237. plot_results(val_results, "Validation Set (2024-2025)", "val_realistic.png")
  238. # 综合评价
  239. print("\n[4] 策略评价:")
  240. decay = (train_metrics['annual_return'] - val_metrics['annual_return']) / train_metrics['annual_return'] * 100 if train_metrics['annual_return'] > 0 else 0
  241. print(f" 年化收益衰减: {decay:.1f}%")
  242. if train_metrics['annual_return'] >= 0.20:
  243. print(" ✅ 训练集年化≥20%")
  244. else:
  245. print(" ⚠️ 训练集收益一般")
  246. if val_metrics['annual_return'] >= 0.10:
  247. print(" ✅ 验证集年化≥10%")
  248. elif val_metrics['annual_return'] > 0:
  249. print(" ⚠️ 验证集正收益但未达10%")
  250. else:
  251. print(" ❌ 验证集亏损")
  252. if decay < 50:
  253. print(" ✅ 策略稳健(衰减<50%)")
  254. else:
  255. print(" ⚠️ 策略有过拟合风险")
  256. # 最终结论
  257. print("\n" + "="*70)
  258. if train_metrics['annual_return'] >= 0.20 and val_metrics['annual_return'] > 0.05 and decay < 60:
  259. print("✅ 策略设计成功!建议实盘测试")
  260. elif train_metrics['annual_return'] >= 0.15 and val_metrics['annual_return'] > 0:
  261. print("⚠️ 策略尚可,建议进一步优化")
  262. else:
  263. print("❌ 策略需重新设计")
  264. print("="*70)
  265. # 保存数据
  266. data.to_csv('cyb50_realistic_data.csv')
  267. print("\n数据已保存: cyb50_realistic_data.csv")
  268. if __name__ == "__main__":
  269. main()