feat: v4.0消融家族8路径+v5.0融合 — 量比带是最大瓶颈(73%胜率15笔),移动止盈证伪,v5.0达24笔66.7%
This commit is contained in:
+95
-7
@@ -11,6 +11,14 @@ import sys
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from backtest_framework import prepare_bars, compute_single_score, compute_kelly
|
||||
|
||||
# bars 缓存:批量跑多版本时共享 TA 计算
|
||||
_BARS_CACHE = {}
|
||||
def _bars(code, start_date, end_date):
|
||||
key = (code, start_date, end_date)
|
||||
if key not in _BARS_CACHE:
|
||||
_BARS_CACHE[key] = prepare_bars(code, start_date, end_date)
|
||||
return _BARS_CACHE[key]
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# 策略版本注册表
|
||||
# ══════════════════════════════════════════════════════
|
||||
@@ -108,6 +116,72 @@ STRATEGIES = {
|
||||
}
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════
|
||||
# v4.0 分支家族:消融实验(每次只动一个维度)
|
||||
# ══════════════════════════════════════════════════════
|
||||
_V40_BASE = STRATEGIES["v4.0"]["config"]
|
||||
|
||||
def _v40_branch(version, name, summary, hypothesis, entry_overrides=None, exit_overrides=None):
|
||||
import copy
|
||||
cfg = copy.deepcopy(_V40_BASE)
|
||||
for k, v in (entry_overrides or {}).items():
|
||||
if k in ("min_score", "min_momentum"):
|
||||
cfg["entry"][k] = v
|
||||
else:
|
||||
cfg["entry"]["filters"][k] = v
|
||||
for k, v in (exit_overrides or {}).items():
|
||||
cfg["exit"][k] = v
|
||||
return {
|
||||
"version": version, "name": name, "summary": summary,
|
||||
"hypothesis": hypothesis, "parent": "v4.0",
|
||||
"created": "2026-07-28", "config": cfg,
|
||||
}
|
||||
|
||||
STRATEGIES.update({
|
||||
# A组: 出场优化(严格入场不变)
|
||||
"v4.0a": _v40_branch("v4.0a", "移动止盈路径",
|
||||
"v4.0入场不变;出场改移动止盈(跟踪1.5×ATR),无固定目标,让利润奔跑",
|
||||
"v4.0五笔4赢且均赢+13%,固定15%目标可能截断大趋势;跟踪止损可锁定利润同时保留上行空间",
|
||||
exit_overrides={"tp_pct": None, "trail_atr": 1.5, "sl_atr": 1.5, "max_hold_days": 25}),
|
||||
"v4.0b": _v40_branch("v4.0b", "延长持仓路径",
|
||||
"v4.0入场不变;持仓期20→25天,给趋势更多兑现时间",
|
||||
"v3归因显示15天+持仓胜率45.6%为各档最高,强信号可能需要更长时间兑现",
|
||||
exit_overrides={"max_hold_days": 25}),
|
||||
# B组: 单维度放宽(消融,找瓶颈)
|
||||
"v4.0c": _v40_branch("v4.0c", "单放ROC",
|
||||
"v4.0只放宽ROC: 10~25 → 8~30,其余全保持",
|
||||
"消融实验:ROC带是否是交易数瓶颈?放宽后若胜率不降则ROC带可永久放宽",
|
||||
entry_overrides={"roc_min": 8, "roc_max": 30}),
|
||||
"v4.0d": _v40_branch("v4.0d", "单放ATR",
|
||||
"v4.0只放宽ATR%: 3.5~5.5 → 2.8~6.5,其余全保持",
|
||||
"消融实验:ATR带是否过窄排除了高波动赢家?",
|
||||
entry_overrides={"atr_pct_min": 2.8, "atr_pct_max": 6.5}),
|
||||
"v4.0e": _v40_branch("v4.0e", "单放MACD",
|
||||
"v4.0只放宽MACD柱: 0.25~1.3 → 0~2.0,其余全保持",
|
||||
"消融实验:MACD柱0~0.25区间(v3:36%胜率)和>1.3区间(28%)是否真该排除?",
|
||||
entry_overrides={"macd_hist_min": 0, "macd_hist_max": 2.0}),
|
||||
"v4.0f": _v40_branch("v4.0f", "单放量比",
|
||||
"v4.0只放宽量比: 1.2~1.5 → 0.9~2.0,其余全保持",
|
||||
"消融实验:量比带的贡献度几何?",
|
||||
entry_overrides={"vol_ratio_min": 0.9, "vol_ratio_max": 2.0}),
|
||||
"v4.0g": _v40_branch("v4.0g", "单放大盘斜率",
|
||||
"v4.0只放宽大盘MA20斜率: ≤-0.05 → ≤0.5,其余全保持",
|
||||
"消融实验:大盘斜率是最强信号(38pp)但也是最大限制——放到0.5还能保住边缘吗?",
|
||||
entry_overrides={"mkt_slope_max": 0.5}),
|
||||
# C组: 结构替代
|
||||
"v4.0h": _v40_branch("v4.0h", "去高点结构",
|
||||
"v4.0去掉hh_only(更高高点结构)要求,其余全保持",
|
||||
"hh结构+15pp但样本仅4笔False组——这个过滤器可能既限数量又未必真实有效",
|
||||
entry_overrides={"hh_only": False}),
|
||||
# D组: 融合胜出路径
|
||||
"v5.0": _v40_branch("v5.0", "量比+ATR融合",
|
||||
"v4.0 + 量比0.9~2.0 + ATR 2.8~6.5(消融胜出的双放宽融合)",
|
||||
"消融结果:单放量比+10笔保73%胜率/盈亏比2.92,单放ATR+4笔保78%胜率——两者是唯一不稀释质量的放宽,融合期望20+笔且保住70%胜率",
|
||||
entry_overrides={"vol_ratio_min": 0.9, "vol_ratio_max": 2.0,
|
||||
"atr_pct_min": 2.8, "atr_pct_max": 6.5}),
|
||||
})
|
||||
|
||||
|
||||
def get_strategy(version):
|
||||
if version not in STRATEGIES:
|
||||
raise ValueError(f"未知策略版本: {version},可用: {list(STRATEGIES.keys())}")
|
||||
@@ -287,7 +361,7 @@ def run_backtest(strategy_version, start_date, end_date, capital=1000000, save=T
|
||||
|
||||
for code, name in stocks:
|
||||
screened += 1
|
||||
bars = prepare_bars(code, start_date, end_date)
|
||||
bars = _bars(code, start_date, end_date)
|
||||
if not bars or len(bars) < 25:
|
||||
continue
|
||||
|
||||
@@ -323,6 +397,8 @@ def run_backtest(strategy_version, start_date, end_date, capital=1000000, save=T
|
||||
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']
|
||||
elif exit_cfg.get('trail_atr'):
|
||||
target = None # 移动止盈模式无固定目标
|
||||
else:
|
||||
target = close * 1.10
|
||||
if exit_cfg.get('sl_atr') and atr_val > 0:
|
||||
@@ -334,20 +410,32 @@ def run_backtest(strategy_version, start_date, end_date, capital=1000000, save=T
|
||||
|
||||
kelly = 0
|
||||
if cfg['sizing'].get('kelly'):
|
||||
kelly = compute_kelly(total_score, (target-close)/close if close>0 else 0.1,
|
||||
(close-stop)/close if close>0 else 0.07)
|
||||
rr_est = ((target - close) / close) if target else (2 * (close - stop) / close)
|
||||
kelly = compute_kelly(total_score, rr_est if close > 0 else 0.1,
|
||||
(close - stop) / close if close > 0 else 0.07)
|
||||
|
||||
max_hold = exit_cfg.get('max_hold_days', 20)
|
||||
trail_atr = exit_cfg.get('trail_atr')
|
||||
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 fh >= target:
|
||||
if target and fh >= target:
|
||||
exit_price, exit_reason, hold_days = target, 'target', k+1
|
||||
break
|
||||
elif fl <= stop:
|
||||
exit_price, exit_reason, hold_days = fc, 'stop', k+1
|
||||
# 移动止损线:随最高收盘价上移,从不下移
|
||||
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
|
||||
@@ -366,7 +454,7 @@ def run_backtest(strategy_version, start_date, end_date, capital=1000000, save=T
|
||||
'score_comp': comp,
|
||||
'kelly': round(kelly, 3),
|
||||
'stop_loss': round(stop, 2),
|
||||
'target': round(target, 2),
|
||||
'target': round(target, 2) if target else None,
|
||||
'factors': {k: (round(v, 3) if isinstance(v, float) else v)
|
||||
for k, v in factors.items()},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user