feat: v8筹码结构出场 — 趋势持有(破MA10/出货识别)+波段先出再进,v8.1收益率+12.6%超v7.1达57%,v8.0盈亏比5.06/均亏仅-2.81%

This commit is contained in:
hmo
2026-07-29 01:56:21 +08:00
parent c7aa01a5da
commit c711f6ff80
+104
View File
@@ -218,6 +218,30 @@ STRATEGIES.update({
entry_overrides={"vol_ratio_min": 0.9, "vol_ratio_max": 2.0,
"flow_5d_min": -2.5, "sector_slope_max": 1.0},
exit_overrides={"tp_pct": None, "staged_tp": [[0.5, 0.08], [0.5, 0.15]], "sl_atr": 1.5, "max_hold_days": 20}),
# G组: 筹码/结构出场(趋势持有与波段)
"v8.0": _v40_branch("v8.0", "趋势持有",
"v7.1入场;出场改结构驱动:破MA10两日/破MA20/横盘出货识别,无固定目标,最长40天",
"用户经验:拉伸段不必早出场,固定15%目标截断利润。让利润奔跑至结构破位或高位放量滞涨(出货)信号出现",
entry_overrides={"vol_ratio_min": 0.9, "vol_ratio_max": 2.0, "sector_slope_max": 1.0,
"hl_only": True, "rsi_delta_min": 6},
exit_overrides={"tp_pct": None, "exit_mode": "structure", "sl_atr": 1.5, "max_hold_days": 40}),
"v8.1": _v40_branch("v8.1", "波段先出再进",
"v7.1入场;跌破MA10先出,10日内收回MA10且创新高再进,各段复合计算,最长60天",
"用户经验:调整时先出再进可避开回撤段——破MA10锁定利润,结构恢复再进场吃下一波",
entry_overrides={"vol_ratio_min": 0.9, "vol_ratio_max": 2.0, "sector_slope_max": 1.0,
"hl_only": True, "rsi_delta_min": 6},
exit_overrides={"tp_pct": None, "exit_mode": "swing", "sl_atr": 1.5, "max_hold_days": 60, "reentry_days": 10}),
"v8.2": _v40_branch("v8.2", "趋势持有(宽入场)",
"v6.1入场;出场同v8.0结构驱动——消融对比:结构出场本身贡献多少",
"对照实验:v6.1固定15%目标 vs v8.2结构持有,同入场下隔离出场模式的贡献",
entry_overrides={"vol_ratio_min": 0.9, "vol_ratio_max": 2.0, "sector_slope_max": 1.0},
exit_overrides={"tp_pct": None, "exit_mode": "structure", "sl_atr": 1.5, "max_hold_days": 40}),
"v8.3": _v40_branch("v8.3", "波段40天",
"v8.1持仓期60→40天,检验长尾巴交易的必要性",
"v8.1平均持仓58天接近上限,若40天版收益率不降说明长尾可砍、资金周转更优",
entry_overrides={"vol_ratio_min": 0.9, "vol_ratio_max": 2.0, "sector_slope_max": 1.0,
"hl_only": True, "rsi_delta_min": 6},
exit_overrides={"tp_pct": None, "exit_mode": "swing", "sl_atr": 1.5, "max_hold_days": 40, "reentry_days": 10}),
})
@@ -589,6 +613,86 @@ def run_backtest(strategy_version, start_date, end_date, capital=1000000, save=T
exit_reason = 'staged_end' if any(realized) else 'keep'
pnl = realized_pnl * 100
exit_price = close * (1 + realized_pnl)
elif exit_cfg.get('exit_mode') == 'structure':
# ── 趋势持有(筹码视角):无固定目标,破位/出货才走 ──
below_ma10 = 0
dist_lookback = exit_cfg.get('dist_gain', 12) # 涨幅超此值才识别出货
for k, fb in enumerate(future):
fh, fl, fc = fb.get('high') or 0, fb.get('low') or 0, fb.get('close') or 0
fv = fb.get('volume') or 0
fma10, fma20 = fb.get('ma10') or 0, fb.get('ma20') or 0
if fl <= stop:
exit_price, exit_reason, hold_days = fc, 'stop', k+1
break
# 横盘出货识别:涨幅>12%后,5日振幅<4% 且 均量>前20日均量1.3倍
if k >= 5 and (fc - close)/close*100 > dist_lookback:
recent = future[k-4:k+1]
lo = min(x.get('low') or 1e9 for x in recent)
hi = max(x.get('high') or 0 for x in recent)
amp = (hi - lo)/lo*100 if lo > 0 else 99
avg_vol = sum(x.get('volume') or 0 for x in recent)/5
base_win = bars[max(0, i-19):i+1]
base_vol = sum(x.get('volume') or 0 for x in base_win)/len(base_win) if base_win else 0
if amp < 4 and base_vol > 0 and avg_vol > 1.3*base_vol:
exit_price, exit_reason, hold_days = fc, 'distribution', k+1
break
# 结构破位:连续2日收破MA10,或单日收破MA20
if fma10 > 0 and fc < fma10:
below_ma10 += 1
if below_ma10 >= 2:
exit_price, exit_reason, hold_days = fc, 'ma10_break', k+1
break
else:
below_ma10 = 0
if fma20 > 0 and fc < fma20:
exit_price, exit_reason, hold_days = fc, 'ma20_break', k+1
break
if exit_price is None:
exit_price = future[-1].get('close') if future else close
exit_reason, hold_days = 'keep', len(future)
pnl = (exit_price - close) / close * 100 if close > 0 else 0
elif exit_cfg.get('exit_mode') == 'swing':
# ── 波段操作(先出再进):破MA10出,10日内收回MA10且创新高再进 ──
reentry_window = exit_cfg.get('reentry_days', 10)
legs = []
in_pos = True
entry_p = close
stop_cur = stop
wait = 0
exit_reason = 'keep'
for k, fb in enumerate(future):
fh, fl, fc = fb.get('high') or 0, fb.get('low') or 0, fb.get('close') or 0
fma10 = fb.get('ma10') or 0
if in_pos:
if fl <= stop_cur:
legs.append(fc/entry_p - 1)
exit_reason = 'stop'
in_pos = False
break
if fma10 > 0 and fc < fma10:
legs.append(fc/entry_p - 1)
in_pos = False
wait = reentry_window
exit_reason = 'swing_out'
else:
wait -= 1
if wait < 0:
break
prev_high = future[k-1].get('high') or 0 if k > 0 else 0
# 重新站上MA10且当天创新高 → 结构恢复,再进场
if fma10 > 0 and fc > fma10 and fh > prev_high:
in_pos = True
entry_p = fc
stop_cur = fc - atr_val * exit_cfg.get('sl_atr', 1.5) if atr_val > 0 else fc * 0.93
exit_reason = 'swing_re'
if in_pos:
legs.append((future[-1].get('close') if future else entry_p)/entry_p - 1)
total_ret = 1.0
for l in legs:
total_ret *= (1 + l)
pnl = (total_ret - 1) * 100
exit_price = close * total_ret
hold_days = len(future) if future else 0
else:
exit_price = exit_reason = None
hold_days = 0