cyb50_ultimate.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  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. print(f"真实数据加载成功: {df.index[0].date()} ~ {df.index[-1].date()}")
  23. return df
  24. class UltimateStrategy:
  25. """
  26. 终极策略:三重过滤 + 动态杠杆
  27. """
  28. def __init__(self):
  29. self.position = 0
  30. self.cash = 1.0
  31. self.holdings = 0
  32. self.entry_price = 0
  33. self.peak_price = 0
  34. self.state = "EMPTY"
  35. def signal(self, data):
  36. """三重过滤信号"""
  37. c = data['close']
  38. # 1. 趋势过滤(三均线多头排列)
  39. ma5 = c.rolling(5).mean().iloc[-1]
  40. ma20 = c.rolling(20).mean().iloc[-1]
  41. ma60 = c.rolling(60).mean().iloc[-1]
  42. trend_ok = (c.iloc[-1] > ma5) and (ma5 > ma20) and (ma20 > ma60)
  43. # 2. 动量过滤(20日涨幅为正且加速)
  44. ret20 = (c.iloc[-1] / c.iloc[-20] - 1)
  45. ret10 = (c.iloc[-1] / c.iloc[-10] - 1)
  46. momentum_ok = (ret20 > 0.05) and (ret10 > ret20 * 0.6) # 动量加速
  47. # 3. 波动率过滤(低波动时重仓)
  48. volatility = c.pct_change().rolling(20).std().iloc[-1] * np.sqrt(252)
  49. vol_ok = volatility < 0.45 # 年化波动小于45%
  50. # 综合信号
  51. if trend_ok and momentum_ok and vol_ok:
  52. return "FULL", 1.0
  53. elif trend_ok and momentum_ok:
  54. return "HALF", 0.5
  55. elif trend_ok:
  56. return "QUARTER", 0.25
  57. else:
  58. return "EMPTY", 0.0
  59. def manage_risk(self, current_price):
  60. """风险管理"""
  61. if self.position <= 0:
  62. return self.position
  63. # 更新峰值
  64. if current_price > self.peak_price:
  65. self.peak_price = current_price
  66. # 回撤控制
  67. drawdown = (current_price - self.peak_price) / self.peak_price
  68. # 从高点回撤12%清仓
  69. if drawdown < -0.12:
  70. return 0.0
  71. # 从高点回撤8%减半
  72. if drawdown < -0.08 and self.position >= 0.5:
  73. return self.position * 0.5
  74. # 入场后亏损8%止损
  75. if self.entry_price > 0:
  76. loss = (current_price - self.entry_price) / self.entry_price
  77. if loss < -0.08:
  78. return 0.0
  79. return self.position
  80. def generate_signal(self, data):
  81. """主信号生成"""
  82. signal, pos = self.signal(data)
  83. current_price = data['close'].iloc[-1]
  84. # 先应用风险管理
  85. pos = self.manage_risk(current_price)
  86. # 状态更新
  87. if pos > self.position and self.position == 0:
  88. # 新开仓
  89. self.entry_price = current_price
  90. self.peak_price = current_price
  91. self.state = "ENTRY"
  92. elif pos == 0 and self.position > 0:
  93. # 清仓
  94. self.entry_price = 0
  95. self.peak_price = 0
  96. self.state = "EXIT"
  97. elif pos == 1.0:
  98. self.state = "FULL"
  99. elif pos == 0.5:
  100. self.state = "HALF"
  101. elif pos == 0:
  102. self.state = "EMPTY"
  103. else:
  104. self.state = "PARTIAL"
  105. self.position = pos
  106. return pos, self.state
  107. def backtest(data, strategy, start_date, end_date, warmup=60):
  108. """回测"""
  109. data = data[data.index >= start_date]
  110. data = data[data.index <= end_date]
  111. results = []
  112. nav = 1.0
  113. for i in range(warmup, len(data)):
  114. curr = data.iloc[:i+1]
  115. pos, state = strategy.generate_signal(curr)
  116. if i > warmup:
  117. daily_ret = data['close'].iloc[i] / data['close'].iloc[i-1] - 1
  118. # 使用前一个时刻的仓位计算当日收益
  119. prev_pos = results[-1]['position']
  120. nav *= (1 + daily_ret * prev_pos)
  121. results.append({
  122. 'date': data.index[i],
  123. 'position': pos,
  124. 'nav': nav,
  125. 'state': state,
  126. 'close': data['close'].iloc[i]
  127. })
  128. df = pd.DataFrame(results).set_index('date')
  129. df['index_nav'] = df['close'] / df['close'].iloc[0]
  130. return df
  131. def metrics(nav, index_nav):
  132. """计算指标"""
  133. s_ret = nav.pct_change().dropna()
  134. total = nav.iloc[-1] - 1
  135. days = len(nav)
  136. annual = (1 + total) ** (252/days) - 1
  137. idx_total = index_nav.iloc[-1] - 1
  138. idx_annual = (1 + idx_total) ** (252/days) - 1
  139. # 最大回撤
  140. running_max = nav.expanding().max()
  141. dd = ((nav - running_max) / running_max).min()
  142. # 夏普
  143. vol = s_ret.std() * np.sqrt(252)
  144. sharpe = (annual - 0.03) / vol if vol > 0 else 0
  145. # 卡玛
  146. calmar = annual / abs(dd) if dd != 0 else 0
  147. return {
  148. 'annual': annual,
  149. 'idx_annual': idx_annual,
  150. 'excess': annual - idx_annual,
  151. 'max_dd': dd,
  152. 'sharpe': sharpe,
  153. 'calmar': calmar,
  154. 'total': total,
  155. 'idx_total': idx_total
  156. }
  157. def plot(df, title, filename):
  158. """绘图"""
  159. fig, ax = plt.subplots(2, 1, figsize=(14, 8))
  160. ax[0].plot(df.index, df['nav'], 'r-', linewidth=2, label='Strategy')
  161. ax[0].plot(df.index, df['index_nav'], 'gray', linewidth=1, alpha=0.7, label='Index')
  162. ax[0].set_title(title, fontsize=14)
  163. ax[0].legend()
  164. ax[0].grid(True, alpha=0.3)
  165. ax[1].fill_between(df.index, 0, df['position'], alpha=0.5, color='green')
  166. ax[1].set_ylim(0, 1.1)
  167. ax[1].set_ylabel('Position')
  168. ax[1].grid(True, alpha=0.3)
  169. plt.tight_layout()
  170. plt.savefig(filename, dpi=150)
  171. print(f" 图表: {filename}")
  172. def main():
  173. print("="*60)
  174. print("创业板50 - 终极高收益策略")
  175. print("="*60)
  176. # 加载真实数据
  177. print("\n[1] 加载真实数据...")
  178. data = load_real_data()
  179. print(f" {data.index[0].date()} ~ {data.index[-1].date()}")
  180. # 训练
  181. print("\n[2] 训练集 (2018-2023)...")
  182. s = UltimateStrategy()
  183. train = backtest(data, s, '2018-01-01', '2023-12-31')
  184. m = metrics(train['nav'], train['index_nav'])
  185. print(f"\n ╔════════════════════════════════╗")
  186. print(f" ║ 训 练 集 结 果 ║")
  187. print(f" ╠════════════════════════════════╣")
  188. print(f" ║ 策略收益: {m['total']*100:7.1f}% ║")
  189. print(f" ║ 指数收益: {m['idx_total']*100:7.1f}% ║")
  190. print(f" ║ 年化收益: {m['annual']*100:7.1f}% ║")
  191. print(f" ║ 超额收益: {m['excess']*100:7.1f}% ║")
  192. print(f" ║ 最大回撤: {m['max_dd']*100:7.1f}% ║")
  193. print(f" ║ 夏普比率: {m['sharpe']:7.2f} ║")
  194. print(f" ║ 卡玛比率: {m['calmar']:7.2f} ║")
  195. print(f" ╚════════════════════════════════╝")
  196. plot(train, "Training (2018-2023)", "train_ultimate.png")
  197. # 验证
  198. print("\n[3] 验证集 (2024-2025)...")
  199. s2 = UltimateStrategy()
  200. val = backtest(data, s2, '2024-01-01', '2025-12-31')
  201. m2 = metrics(val['nav'], val['index_nav'])
  202. print(f"\n ╔════════════════════════════════╗")
  203. print(f" ║ 验 证 集 结 果 ║")
  204. print(f" ╠════════════════════════════════╣")
  205. print(f" ║ 策略收益: {m2['total']*100:7.1f}% ║")
  206. print(f" ║ 指数收益: {m2['idx_total']*100:7.1f}% ║")
  207. print(f" ║ 年化收益: {m2['annual']*100:7.1f}% ║")
  208. print(f" ║ 超额收益: {m2['excess']*100:7.1f}% ║")
  209. print(f" ║ 最大回撤: {m2['max_dd']*100:7.1f}% ║")
  210. print(f" ║ 夏普比率: {m2['sharpe']:7.2f} ║")
  211. print(f" ╚════════════════════════════════╝")
  212. plot(val, "Validation (2024-2025)", "val_ultimate.png")
  213. # 评估
  214. print("\n[4] 策略评估:")
  215. if m['annual'] > 0.20 and m['calmar'] > 0.5:
  216. print(" ✅ 训练集表现优秀,具备盈利潜力")
  217. else:
  218. print(" ⚠️ 训练集收益一般")
  219. decay = (m['annual'] - m2['annual']) / m['annual'] if m['annual'] > 0 else 0
  220. print(f" 年化收益衰减: {decay*100:.0f}%")
  221. if m2['annual'] > 0.15:
  222. print(" ✅ 验证集收益优秀")
  223. elif m2['annual'] > 0:
  224. print(" ⚠️ 验证集收益一般")
  225. else:
  226. print(" ❌ 验证集亏损")
  227. print("\n" + "="*60)
  228. if __name__ == "__main__":
  229. main()