feat: v7家族 — 分批止盈引擎+亏损尸检因子(hl结构/RSI增量),v7.1双赢73%/+8.01%,v7.2达79.7%胜率,v7.3达85.7%/回撤9.68%

This commit is contained in:
hmo
2026-07-29 01:45:40 +08:00
parent 45cd076a2e
commit c7aa01a5da
2 changed files with 80 additions and 22 deletions
+1 -1
View File
@@ -2059,7 +2059,7 @@ function showStrategyResult(data) {
'<th class="text-left px-2 py-1">代码</th><th class="text-left px-2 py-1">名称</th><th class="text-left px-2 py-1">入场</th>' +
'<th class="text-right px-2 py-1">买入</th><th class="text-right px-2 py-1">卖出</th><th class="text-right px-2 py-1">收益</th>' +
'<th class="text-center px-2 py-1">结果</th><th class="text-right px-2 py-1">持仓</th><th class="text-right px-2 py-1">评分</th></tr></thead><tbody>';
const reasonMap = { target: '🎯止盈', stop: '🛑止损', keep: '⏳到期' };
const reasonMap = { target: '🎯止盈', stop: '🛑止损', keep: '⏳到期', staged_end: '🪜分批', trail: '📈跟踪' };
for (const t of trades) {
const pc = (t.profit_pct || 0) >= 0 ? 'text-green-400' : 'text-red-400';
html += '<tr class="border-b border-slate-800/40">' +
+79 -21
View File
@@ -195,6 +195,29 @@ STRATEGIES.update({
"资金流(37.5%→55-78%)与板块(27.3%→80%)两个独立维度的负向排除叠加,期望在v4.0f基础上再提升胜率且不显著减样本",
entry_overrides={"vol_ratio_min": 0.9, "vol_ratio_max": 2.0,
"flow_5d_min": -2.5, "sector_slope_max": 1.0}),
# F组: 出场优化 + 尸检因子(基于 v6.1)
"v7.0": _v40_branch("v7.0", "分批止盈",
"v6.1入场不变;出场改50%@+8%落袋+50%@+15%,止损不变",
"亏损尸检:12笔亏损9笔为止损出局——先到+8%落袋一半可将部分止损单转为盈利单;牺牲部分大赢换取胜率",
entry_overrides={"vol_ratio_min": 0.9, "vol_ratio_max": 2.0, "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}),
"v7.1": _v40_branch("v7.1", "尸检因子过滤",
"v6.1 + 必须hl结构(更高低点) + RSI增量≥6(动量加速)",
"亏损尸检:盈利组100%具备hl结构而亏损组仅75%;盈利组RSI增量11.3 vs 亏损组6.1——动量加速度区分输赢",
entry_overrides={"vol_ratio_min": 0.9, "vol_ratio_max": 2.0, "sector_slope_max": 1.0,
"hl_only": True, "rsi_delta_min": 6}),
"v7.2": _v40_branch("v7.2", "尸检+分批",
"v7.1入场 + 分批止盈出场(双管齐下)",
"入场端尸检因子过滤+出场端分批止盈,两个独立改进点叠加",
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, "staged_tp": [[0.5, 0.08], [0.5, 0.15]], "sl_atr": 1.5, "max_hold_days": 20}),
"v7.3": _v40_branch("v7.3", "资金+板块+分批",
"v6.2入场(资金+板块双滤) + 分批止盈出场",
"v6.2的82.1%胜率入场叠加分批止盈,目标在不损胜率前提下改善盈亏结构",
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}),
})
@@ -372,9 +395,11 @@ def pass_filters(factors, filters):
# 趋势变化
if not chk('ma20_slope', filters.get('ma20_slope_min'), filters.get('ma20_slope_max')): return False
if not chk('macd_hist_delta', filters.get('macd_hist_delta_min'), filters.get('macd_hist_delta_max')): return False
if not chk('rsi_delta', filters.get('rsi_delta_min'), filters.get('rsi_delta_max')): return False
if filters.get('adx_rising') and not factors.get('adx_rising'): return False
if filters.get('trend_only') and not factors.get('trend_aligned'): return False
if filters.get('hh_only') and not factors.get('hh_structure'): return False
if filters.get('hl_only') and not factors.get('hl_structure'): return False
if filters.get('no_new_high') and factors.get('near_high_20d'): return False
# 大盘
if filters.get('mkt_above_ma20') and factors.get('mkt_above_ma20') is not True: return False
@@ -529,32 +554,65 @@ def run_backtest(strategy_version, start_date, end_date, capital=1000000, save=T
max_hold = exit_cfg.get('max_hold_days', 20)
trail_atr = exit_cfg.get('trail_atr')
staged_tp = exit_cfg.get('staged_tp') # [[frac, pct], ...] 分批止盈
future = bars[i+1:i+1+max_hold]
exit_price = exit_reason = None
hold_days = 0
highest_close = close
for k, fb in enumerate(future):
fh, fl, fc = fb.get('high') or 0, fb.get('low') or 0, fb.get('close') or 0
if target and fh >= target:
exit_price, exit_reason, hold_days = target, 'target', k+1
break
# 移动止损线:随最高收盘价上移,从不下移
eff_stop = stop
if trail_atr and atr_val > 0:
highest_close = max(highest_close, fc)
eff_stop = max(stop, highest_close - atr_val * trail_atr)
if fl <= eff_stop:
if trail_atr and eff_stop > stop:
exit_price, exit_reason = eff_stop, 'trail'
else:
exit_price, exit_reason = (eff_stop if trail_atr else fc), 'stop'
hold_days = 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
if staged_tp:
# ── 分批止盈模拟:按目标分批落袋,止损约束剩余仓位 ──
remaining = 1.0
realized_pnl = 0.0
realized = [False] * len(staged_tp)
for k, fb in enumerate(future):
fh, fl, fc = fb.get('high') or 0, fb.get('low') or 0, fb.get('close') or 0
# 保守假设:同日先触止损
if fl <= stop:
realized_pnl += remaining * ((fc - close) / close)
remaining = 0
exit_reason, hold_days = 'stop', k + 1
break
for si, (frac, tp) in enumerate(staged_tp):
if not realized[si] and fh >= close * (1 + tp):
realized_pnl += frac * tp
remaining -= frac
realized[si] = True
if remaining <= 1e-9:
exit_reason, hold_days = 'target', k + 1
break
if remaining > 1e-9:
last_c = future[-1].get('close') if future else close
realized_pnl += remaining * ((last_c - close) / close)
hold_days = len(future)
if exit_reason is None:
exit_reason = 'staged_end' if any(realized) else 'keep'
pnl = realized_pnl * 100
exit_price = close * (1 + realized_pnl)
else:
exit_price = exit_reason = None
hold_days = 0
for k, fb in enumerate(future):
fh, fl, fc = fb.get('high') or 0, fb.get('low') or 0, fb.get('close') or 0
if target and fh >= target:
exit_price, exit_reason, hold_days = target, 'target', k+1
break
# 移动止损线:随最高收盘价上移,从不下移
eff_stop = stop
if trail_atr and atr_val > 0:
highest_close = max(highest_close, fc)
eff_stop = max(stop, highest_close - atr_val * trail_atr)
if fl <= eff_stop:
if trail_atr and eff_stop > stop:
exit_price, exit_reason = eff_stop, 'trail'
else:
exit_price, exit_reason = (eff_stop if trail_atr else fc), 'stop'
hold_days = 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
trades.append({
'code': code, 'name': name,
'entry_date': date,