check_market_regime.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 分层分析:区分"下跌趋势低波"与其他市场状态后,各指标的真实表现
  5. """
  6. import sys, io
  7. if sys.platform == 'win32':
  8. sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
  9. sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
  10. import pandas as pd
  11. import numpy as np
  12. import warnings
  13. warnings.filterwarnings('ignore')
  14. df = pd.read_csv(
  15. 'D:/work/project/cyb50-quant/cat-fly/t1/t1_trades_with_environment_20260327_141655.csv',
  16. encoding='utf-8-sig'
  17. )
  18. cols = [
  19. '交易方向','开仓时间','平仓时间','开仓价格','平仓价格','仓位',
  20. '盈亏金额','盈亏百分比','退出原因','持仓周期数','持仓小时数',
  21. 'T1调整','原平仓时间','原平仓价格','原盈亏','盈亏变化',
  22. '入场信号','开仓市值','平仓时资金','市场状态',
  23. '趋势短期','趋势中期','趋势强度','波动率分位','波动率水平',
  24. '成交量分位','布林带位置','布林带区域','RSI分位','RSI区域',
  25. '1日动量','入场价格'
  26. ]
  27. df.columns = cols
  28. df['开仓时间'] = pd.to_datetime(df['开仓时间'])
  29. df['年份'] = df['开仓时间'].dt.year
  30. df['盈利'] = df['盈亏金额'] > 0
  31. # ── 核心分层 ──────────────────────────────────────────────
  32. death_zone = df['市场状态'] == '下跌趋势低波'
  33. good_zone = ~death_zone
  34. dz = df[death_zone] # 死亡区:下跌趋势低波
  35. gz = df[good_zone] # 其余环境
  36. SEP = '=' * 70
  37. def show(title):
  38. print(f'\n{SEP}')
  39. print(f' {title}')
  40. print(SEP)
  41. def breakdown(sub, name):
  42. n = len(sub)
  43. wr = sub['盈利'].mean() if n > 0 else 0
  44. avg = sub['盈亏金额'].mean() if n > 0 else 0
  45. total = sub['盈亏金额'].sum()
  46. print(f' {name}: {n}笔 | 胜率{wr:.1%} | 均盈亏{avg:+,.0f}元 | 总盈亏{total:+,.0f}元')
  47. # ── 总览 ─────────────────────────────────────────────────
  48. show('总览:下跌趋势低波 vs 其余环境')
  49. breakdown(df, '全部')
  50. breakdown(dz, '下跌趋势低波 (死亡区)')
  51. breakdown(gz, '其余市场状态 (非死亡区)')
  52. print()
  53. print(' 年份分布:')
  54. for y in sorted(df['年份'].unique()):
  55. total_y = len(df[df['年份'] == y])
  56. dz_y = len(dz[dz['年份'] == y])
  57. pct = dz_y / total_y if total_y > 0 else 0
  58. gz_sub = df[(df['年份'] == y) & good_zone]
  59. gz_wr = gz_sub['盈利'].mean() if len(gz_sub) > 0 else 0
  60. print(f' {y}年: 死亡区占比{pct:.1%}({dz_y}/{total_y}笔) | 非死亡区胜率{gz_wr:.1%}({len(gz_sub)}笔)')
  61. # ── 在死亡区内,各指标表现 ───────────────────────────────
  62. show('死亡区内各指标表现(理解为何之前的规则误导)')
  63. for col in ['波动率水平', 'RSI区域', 'T1调整']:
  64. print(f'\n [{col}] 在死亡区:')
  65. for val in dz[col].dropna().unique():
  66. sub = dz[dz[col] == val]
  67. if len(sub) < 3:
  68. continue
  69. wr = sub['盈利'].mean()
  70. avg = sub['盈亏金额'].mean()
  71. print(f' {val}: {len(sub)}笔, 胜率{wr:.1%}, 均盈亏{avg:+,.0f}元')
  72. # ── 在非死亡区,各指标真实表现 ───────────────────────────
  73. show('非死亡区内各指标表现(剔除死亡区噪音后的真实信号)')
  74. for col in ['波动率水平', 'RSI区域', 'T1调整', '市场状态']:
  75. print(f'\n [{col}] 在非死亡区:')
  76. results = []
  77. for val in gz[col].dropna().unique():
  78. sub = gz[gz[col] == val]
  79. if len(sub) < 5:
  80. continue
  81. wr = sub['盈利'].mean()
  82. avg = sub['盈亏金额'].mean()
  83. results.append((val, len(sub), wr, avg))
  84. results.sort(key=lambda x: x[2], reverse=True)
  85. for val, n, wr, avg in results:
  86. print(f' {val}: {n}笔, 胜率{wr:.1%}, 均盈亏{avg:+,.0f}元')
  87. # ── 非死亡区内 波动率分位 精确阈值 ─────────────────────
  88. show('非死亡区内 波动率分位 阈值扫描')
  89. valid = gz[gz['波动率分位'].notna()].copy()
  90. thresholds = [0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
  91. print(f'\n 波动率分位区间 笔数 胜率 均盈亏')
  92. bins = [(0, 0.3), (0.3, 0.5), (0.5, 0.7), (0.7, 0.85), (0.85, 1.01)]
  93. for lo, hi in bins:
  94. sub = valid[(valid['波动率分位'] >= lo) & (valid['波动率分位'] < hi)]
  95. if len(sub) < 3:
  96. continue
  97. wr = sub['盈利'].mean()
  98. avg = sub['盈亏金额'].mean()
  99. print(f' [{lo:.2f}, {hi:.2f}): {len(sub):>4}笔 {wr:.1%} {avg:>+8,.0f}元')
  100. # ── 非死亡区内 RSI分位 精确阈值 ────────────────────────
  101. show('非死亡区内 RSI分位 阈值扫描')
  102. valid2 = gz[gz['RSI分位'].notna()].copy()
  103. print(f'\n RSI分位区间 笔数 胜率 均盈亏')
  104. bins2 = [(0, 0.05), (0.05, 0.1), (0.1, 0.2), (0.2, 0.35), (0.35, 0.6), (0.6, 1.01)]
  105. for lo, hi in bins2:
  106. sub = valid2[(valid2['RSI分位'] >= lo) & (valid2['RSI分位'] < hi)]
  107. if len(sub) < 3:
  108. continue
  109. wr = sub['盈利'].mean()
  110. avg = sub['盈亏金额'].mean()
  111. print(f' [{lo:.2f}, {hi:.2f}): {len(sub):>4}笔 {wr:.1%} {avg:>+8,.0f}元')
  112. # ── 非死亡区内 双指标组合 ────────────────────────────────
  113. show('非死亡区内 双指标组合(市场状态 × 波动率水平)')
  114. combos = []
  115. for ms in gz['市场状态'].dropna().unique():
  116. for vl in gz['波动率水平'].dropna().unique():
  117. sub = gz[(gz['市场状态'] == ms) & (gz['波动率水平'] == vl)]
  118. if len(sub) < 5:
  119. continue
  120. wr = sub['盈利'].mean()
  121. avg = sub['盈亏金额'].mean()
  122. combos.append((f'{ms} × {vl}', len(sub), wr, avg))
  123. combos.sort(key=lambda x: x[2], reverse=True)
  124. print(f'\n {"组合":<25} 笔数 胜率 均盈亏')
  125. for cond, n, wr, avg in combos[:15]:
  126. print(f' {cond:<25} {n:>4} {wr:.1%} {avg:>+8,.0f}元')
  127. # ── 非死亡区内 RSI × 波动率 ────────────────────────────
  128. show('非死亡区内 RSI区域 × 波动率水平 组合')
  129. combos2 = []
  130. for rs in gz['RSI区域'].dropna().unique():
  131. for vl in gz['波动率水平'].dropna().unique():
  132. sub = gz[(gz['RSI区域'] == rs) & (gz['波动率水平'] == vl)]
  133. if len(sub) < 5:
  134. continue
  135. wr = sub['盈利'].mean()
  136. avg = sub['盈亏金额'].mean()
  137. combos2.append((f'{rs} × {vl}', len(sub), wr, avg))
  138. combos2.sort(key=lambda x: x[2], reverse=True)
  139. print(f'\n {"组合":<28} 笔数 胜率 均盈亏')
  140. for cond, n, wr, avg in combos2[:15]:
  141. print(f' {cond:<28} {n:>4} {wr:.1%} {avg:>+8,.0f}元')
  142. # ── 非死亡区内 趋势强度 分段 ─────────────────────────────
  143. show('非死亡区内 趋势强度 分段')
  144. valid3 = gz[gz['趋势强度'].notna()].copy()
  145. bins3 = [(0, 1.0), (1.0, 1.5), (1.5, 2.5), (2.5, 4.0), (4.0, 10)]
  146. print(f'\n 趋势强度区间 笔数 胜率 均盈亏')
  147. for lo, hi in bins3:
  148. sub = valid3[(valid3['趋势强度'] >= lo) & (valid3['趋势强度'] < hi)]
  149. if len(sub) < 3:
  150. continue
  151. wr = sub['盈利'].mean()
  152. avg = sub['盈亏金额'].mean()
  153. print(f' [{lo:.1f}, {hi:.1f}): {len(sub):>4}笔 {wr:.1%} {avg:>+8,.0f}元')
  154. # ── 验证:用"仅排除死亡区"作为过滤,各年表现 ─────────────
  155. show('仅排除死亡区后的各年绩效(最简化规则的效果)')
  156. for y in sorted(df['年份'].unique()):
  157. all_y = df[df['年份'] == y]
  158. keep_y = gz[gz['年份'] == y]
  159. drop_y = dz[dz['年份'] == y]
  160. kn, kwr, ktotal = len(keep_y), keep_y['盈利'].mean() if len(keep_y) else 0, keep_y['盈亏金额'].sum()
  161. dn = len(drop_y)
  162. print(f' {y}年: 保留{kn}笔 胜率{kwr:.1%} {ktotal:+,.0f}元 | 过滤{dn}笔')
  163. print()
  164. print(SEP)
  165. print(' 结论摘要')
  166. print(SEP)
  167. print()
  168. print(' "下跌趋势低波"是唯一应该被完全排除的市场状态。')
  169. print(' 在此之外的所有环境,包括极高波动、RSI超卖,策略依然有效甚至更好。')
  170. print()
  171. print(' 非死亡区内的辅助加分指标(待从上方组合数据中提炼):')
  172. print(' 详见上方各维度组合分析结果。')