feat: 枢轴S/R注入回测引擎+v11系列——枢轴波段(v11.0)4仓+86.3%/回撤1.3%碾压MA10波段,证明复合强弱支撑优于简单均线;固定15%/ATR仍优于裸枢轴强压止盈

This commit is contained in:
hmo
2026-07-29 13:21:28 +08:00
parent dfdd7490f2
commit ff54d37915
2 changed files with 93 additions and 8 deletions
+25 -1
View File
@@ -170,9 +170,32 @@ def prepare_bars(code, start_date, end_date):
trend_strength = calc_trend_strength(highs, lows, closes)
obv = calc_obv(closes, volumes)
roc = calc_roc(closes)
bars = []
for i in range(len(dates)):
# ── 枢轴S/R(与实盘 technical_analysis.calc_support_resistance 同算法)──
piv = {}
if i >= 1:
h, l, c = highs[i], lows[i], closes[i]
yc = closes[i-1]
# 多日波幅(近20日)
win = bars[max(0, i-19):i] if i > 0 else []
multi_high = max([h] + [x.get('high', 0) or 0 for x in win])
multi_low = min([l] + [x.get('low', 1e9) or 1e9 for x in win])
eff_range = max(h - l, multi_high - multi_low, c * 0.05)
tp = (c - multi_low) / (multi_high - multi_low) if multi_high > multi_low else 0.5
if tp > 0.8 or tp < 0.2:
eff_range = max(eff_range, c * 0.08)
pp = (h + l + c) / 3
s1 = 2 * pp - h
s2 = pp - eff_range
r1 = 2 * pp - l
r2 = pp + eff_range
if yc < s1: s1 = yc
if yc > r1: r1 = yc
piv = {'pivot': pp, 'weak_support': s1, 'strong_support': s2,
'weak_resist': r1, 'strong_resist': r2}
bars.append({
'date': dates[i],
'open': opens[i],
@@ -192,6 +215,7 @@ def prepare_bars(code, start_date, end_date):
'adx': trend_strength[i] if i < len(trend_strength) else None,
'obv': obv[i] if i < len(obv) else None,
'roc': roc[i] if i < len(roc) else None,
**piv,
})
return bars
+68 -7
View File
@@ -258,6 +258,19 @@ STRATEGIES.update({
"v9归因反用:v7.1交易中weekly_up=False胜率78.4% vs True 60.9%——周线级回调中的日线动量回归正是本策略的核心边缘",
entry_overrides={"vol_ratio_min": 0.9, "vol_ratio_max": 2.0, "sector_slope_max": 1.0,
"hl_only": True, "rsi_delta_min": 6, "weekly_down_only": True}),
# I组: 枢轴S/R出场(与实盘technical_analysis同算法)
"v11.0": _v40_branch("v11.0", "波段·枢轴版",
"v8.1入场;出场改枢轴体系:破弱支撑先出,收复弱压且创新高再进",
"v8.1的MA10只是弱支撑的粗糙代理——用实盘同款枢轴S/R验证复合技术位是否优于简单均线",
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_pivot", "sl_atr": 1.5, "max_hold_days": 60, "reentry_days": 10}),
"v11.1": _v40_branch("v11.1", "强压止盈弱撑止损",
"v7.1入场;出场改实盘口径:强压止盈+弱支撑止损(枢轴点体系,替代固定15%/1.5ATR",
"实盘策略的真实出场方式就是枢轴S/R——回测必须验证这个口径而非固定百分比",
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, "use_pivot_sr": True, "max_hold_days": 20}),
# ── 港股专用版本(港股通宇宙归因推导,2026-07-29)──
"h1.0": {
"version": "h1.0",
@@ -694,7 +707,13 @@ def run_backtest(strategy_version, start_date, end_date, capital=1000000, save=T
if pass_filters(factors, filters):
atr_val = last.get('atr') or 0
if exit_cfg.get('tp_pct'):
if exit_cfg.get('use_pivot_sr'):
# 实盘口径:强压止盈 + 弱支撑止损(枢轴点体系)
target = last.get('strong_resist') if (last.get('strong_resist') or 0) > close else None
stop = last.get('weak_support') if (last.get('weak_support') or 0) < close else (close - atr_val * exit_cfg.get('sl_atr', 1.5) if atr_val > 0 else close * 0.93)
if target is None:
target = close * (1 + exit_cfg.get('tp_pct', 0.15))
elif exit_cfg.get('tp_pct'):
target = close * (1 + exit_cfg['tp_pct'])
elif exit_cfg.get('tp_atr') and atr_val > 0:
target = close + atr_val * exit_cfg['tp_atr']
@@ -702,12 +721,13 @@ def run_backtest(strategy_version, start_date, end_date, capital=1000000, save=T
target = None # 移动止盈模式无固定目标
else:
target = close * 1.10
if exit_cfg.get('sl_atr') and atr_val > 0:
stop = close - atr_val * exit_cfg['sl_atr']
elif exit_cfg.get('sl_pct'):
stop = close * (1 - exit_cfg['sl_pct'])
else:
stop = close * 0.93
if not exit_cfg.get('use_pivot_sr'):
if exit_cfg.get('sl_atr') and atr_val > 0:
stop = close - atr_val * exit_cfg['sl_atr']
elif exit_cfg.get('sl_pct'):
stop = close * (1 - exit_cfg['sl_pct'])
else:
stop = close * 0.93
kelly = 0
if cfg['sizing'].get('kelly'):
@@ -832,6 +852,47 @@ def run_backtest(strategy_version, start_date, end_date, capital=1000000, save=T
pnl = (total_ret - 1) * 100
exit_price = close * total_ret
hold_days = len(future) if future else 0
elif exit_cfg.get('exit_mode') == 'swing_pivot':
# ── 波段·枢轴版:破弱支撑先出,收复弱压且创新高再进 ──
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
fws, fwr = fb.get('weak_support') or 0, fb.get('weak_resist') or 0
if in_pos:
if fl <= stop_cur:
legs.append(fc/entry_p - 1)
exit_reason = 'stop'
in_pos = False
break
if fws > 0 and fc < fws:
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
if fwr > 0 and fc > fwr and fh > prev_high:
in_pos = True
entry_p = fc
stop_cur = fb.get('weak_support') or (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