四因子框架回测引擎 v2.0 · 报告(2026-09-27)
核心底仓永久持有黄金不择时;
四因子框架回测引擎 v2.0 · 报告(2026-09-27)
一句话结论:把四因子框架封装成可运行回测引擎并按三条设计指令(降频 / 宏观否决权 / 核心卫星)重构后,
样本外夏普由 0.286 提升到 0.754(框架"降频"判断正确),
但在等敞口对比下,自适应卫星在收益、夏普、回撤三项上全部输给"同平均敞口的被动持有" ——
卫星没有 alpha。因此本文的建议是:不要再调这个模型的参数,应该换问题(见 §六、§七)。
一、交付物
1.1 引擎(回测/engine_v1/,独立可运行)
| 文件 | 说明 |
|---|---|
duckerry_backtest.py | 主引擎(约 1490 行):L0–L4 决策栈 + 四因子 A/B/C + 降频 + 核心卫星 + 否决权 + walk-forward |
plot_compare.py | 独立绘图脚本(从已落盘 equity.csv 出图,规避 iSH 长进程被 kill) |
README.md | 使用说明与口径边界 → 回测/engine_v1/README |
1.2 资产曲线与回测数据(已随引擎入库)
| 文件 | 内容 |
|---|---|
engine_v1/compare.csv | 六策略横向对比表(实测值) |
engine_v1/compare_curve.png | 六策略净值对比图(对数轴) |
engine_v1/curves/{BuyHold,Cash6040,D-L0L2VT,W-Adp60,W-Adp60NoEv,M-Adp60}.csv | 各策略净值曲线(date, equity, dd, sat_pos),可直接画图 |
engine_v1/wf/oos_equity.csv | 样本外 walk-forward 拼接净值(626 条周线,2014-01→2025-12) |
engine_v1/wf/oos_summary.json | 样本外汇总指标 |
engine_v1/wf/fold_0..5.json | 各折择优参数、网格前四、测试段表现 |
engine_v1/metrics_weekly_core60.json | 主推配置的完整指标 + 实际生效 config |
数据口径:回测口径 ≠ 实盘 ≠ 生产管线(
数据/panel.csv+paper_engine.py),数字不可互相引用。
二、三条设计指令与实现
指令 1:强制降频(不用日频)
--freq D|W|M;重采样发生在指标计算之前,所有周期长度以 bar 为单位(5 bar = 5 周)- 日频校准阈值按 √(252/ppy) 自动缩放(周 ×2.20、月 ×4.58),覆盖 L0-1/L0-2/L0-3 与 L1 强弱档
- 年化口径随频率切换(252 / 52 / 12),夏普与波动不再误用日频因子
- 无前视:bar 值在周期末才可知,信号在下一 bar 生效
指令 2:宏观主观否决权
| 否决来源 | 口径 |
|---|---|
| L0 断路 | 金/美同跌、VIX 跳升、股债金三杀(阈值已按频率缩放) |
| 分布型极端日 | |收益| ≥ veto_z × σ(默认 2.5σ,即"−3σ 日"级别) |
| 重大宏观事件 | 规则化「非农」= 每月第一个周五;--event-calendar 可追加 FOMC/CPI |
| L0 警告 | 默认不否决,--veto-warn 打开 |
--veto-mode flat(默认):否决 → 卫星直接清仓进现金--veto-mode noentry:只禁止新开仓--veto-scope auto:事件否决只在 D/W 生效,月频自动禁用(见 §五 P2-a)
指令 3:核心 60% + 卫星 40%
总仓位 = core_pct + (1 − core_pct) × 卫星仓位
成本 = (1 − core_pct) × |Δ卫星仓位| × 单边成本 # 只有卫星换手计费核心底仓永久持有黄金不择时;卫星在趋势态(ADX(14)≥20 且 Kaufman 效率比 ER(20)≥0.30,可选 Hurst≥0.5)顺势做多,在震荡态做超卖均值回归。
三、实测结果(2006-01-03 → 2026-09-25,含 6bps 单边成本)
3.1 策略横向对比
| 策略 | 总收益 | 年化 | 波动 | 夏普 | 最大回撤 | 卡玛 | 交易 |
|---|---|---|---|---|---|---|---|
| 买入持有(100% 金) | 703.5% | 10.63% | 18.2% | 0.646 | −44.6% | 0.238 | 1 |
| 被动 60/40(金/现金) | 279.3% | 6.68% | 10.9% | 0.646 | −28.7% | 0.233 | 0 |
| 日频·L0–L2 + 波动目标 | 77.1% | 2.81% | 7.3% | 0.418 | −17.9% | 0.157 | 894 |
| 周频·自适应 + 核心60% | 263.0% | 6.39% | 12.6% | 0.556 | −33.0% | 0.194 | 208 |
| 周频·同上但无事件否决 | 320.2% | 7.14% | 13.5% | 0.579 | −33.6% | 0.213 | 126 |
| 月频·自适应 + 核心60% | 339.9% | 7.40% | 14.7% | 0.562 | −39.7% | 0.186 | 37 |
3.2 ⭐ 等敞口公平对比(最关键)
卫星版本平均敞口 > 60%,必须与同平均敞口的被动组合比:
| 变体 | 均敞口 | 收益 | 夏普 | 回撤 | 等敞口被动 收益 / 夏普 / 回撤 |
|---|---|---|---|---|---|
| 周频·自适应+核心60%(事件否决) | 68.4% | 263.0% | 0.556 | −33.0% | 345.1% / 0.644 / −31.5% |
| 周频·自适应+核心60%(无事件否决) | 72.8% | 320.2% | 0.579 | −33.6% | 383.9% / 0.644 / −33.2% |
| 月频·自适应+核心60% | 75.3% | 339.9% | 0.562 | −39.7% | 383.9% / 0.640 / −32.3% |
三个自适应变体在收益、夏普、回撤三项上全部输给"等敞口的被动持有"。
即「会择时的卫星」不如「把同样的平均仓位傻傻拿住」。
3.3 样本外 walk-forward(周频·自适应+核心60%,训练 8y → 测试 2y,6 折)
样本外窗口 2014-01-03 → 2025-12-26(626 条周线,全部为样本外)
| 指标 | 策略(OOS) | 100% 买入持有 | 等敞口被动(敞口 67.6%) |
|---|---|---|---|
| 总收益 | 126.2% | 251.0% | 139.8% |
| 年化 | 7.02% | — | 7.54% |
| 年化波动 | 9.61% | — | — |
| 夏普 | 0.754 | 0.812 | 0.812 |
| 最大回撤 | −15.7% | −23.6% | −16.3% |
| 卡玛 | 0.447 | ~0.34 | ~0.46 |
各折样本外收益:−9.3% / +17.9% / +10.1% / +8.7% / +7.1% / +64.9% 网格择优:vol_target 6 折中 4 折选 0(不缩放)、2 折选 0.10;hard_stop 5 折选 8% —— 参数选择不稳定。
四、三条设计指令的实测评价
| 指令 | 是否有效 | 证据 |
|---|---|---|
| 1 降频 | ✔ 明确有效 | 日频纯策略 OOS 夏普 0.286(落后基准 0.236)→ 周频核心卫星 OOS 夏普 0.754(落后等敞口被动仅 0.058) |
| 2 宏观否决权 | ⚠ 有效但代价明确 | 同配置下非农否决使总收益 320% → 263%(−57pp),回撤仅改善 0.6pp(−33.6% → −33.0%)。这是"用真实收益买的保险" |
| 3 核心60%+卫星40% | ✔ 方向正确(限损有效) | 核心底仓贡献约 +250%,把总回撤从 −44.6% 压到 −33.0%;但卫星本身的净贡献很小甚至为负 |
五、本次回测暴露的问题
P1 · 方法论层面(核心,未解决)
| # | 问题 | 证据 |
|---|---|---|
| P1-1 | 等敞口下卫星无 alpha | §3.2 三项全输;OOS 也低于等敞口被动(0.754 vs 0.812) |
| P1-2 | 网格择优不稳定 | vol_target 6 折中 4 折选 0;hard_stop 5 折选 8% → 不存在稳定可择优的结构,择优本身接近噪声 |
| P1-3 | 有效样本极少 | 周频 1082 bar、月频 249 bar;且 2006–2026 是单一样本(黄金大牛市)→ 统计功效严重不足 |
| P1-4 | 无多重检验校正 | 12 组网格 × 6 折 = 72 次尝试,取最优必然被乐观估计(选择偏差)。未做 Deflated Sharpe / White's Reality Check |
| P1-5 | 四因子权重不可检验 | 权重是主观先验;实测 L2 二值门控是 no-op(完整 L0–L2 与 L1 基线 逐日相同);分数加权虽介入但仓位常年偏轻(持仓 21%) |
| P1-6 | 唯一正 alpha 的态样本太小 | B 态(风险溢价)年化 +65%,但只有 374 天 → 无法据此下注 |
| P1-7 | L2 与 L1 存在结构性重叠 | 价格因子与 L1 共用输入,按总纲 §3.3 已去重,但去重后 L2 的独立信息几乎只剩宏观腿 |
P2 · 实现层面(已修复,留档备查)
| # | 问题 | 修法 |
|---|---|---|
| P2-a | 月频否决把每个月都否掉 —— 按"月内有非农"映射 → 每月都有首个周五 → 卫星永不交易(交易次数 0,结果退化成"60%金+40%现金"却看不出来) | 新增 --veto-scope auto:月频自动禁用事件否决 |
| P2-b | 年化因子硬编码 252 —— 周/月频夏普与波动被高估 √(252/ppy) 倍(周频"1.32"实为 0.556) | 新增 infer_ppy(),按索引间距推断 252/52/12 |
| P2-c | build_event_veto 每 bar 调 pd.date_range —— 单次 34s | 改 searchsorted 向量化 + 三层缓存(原始帧/降频帧/层级判定),--compare 由 ~10 分钟压到 ~1 分钟 |
| P2-d | tgt = tgt * w 使 int 变 float,f"{tgt:+d}" 抛 ValueError | 改 {tgt:+.2f} |
P3 · 数据层面(已知局限)
| # | 问题 |
|---|---|
| P3-1 | 历史事件日历缺失:仓库 数据/econ_calendar.json 只覆盖 2026-09-18→09-25 一周;回测只能用规则化非农(可复现);FOMC/CPI 需外部日历 |
| P3-2 | DTWEXBGS 与生产库重构 DXY 不是同一序列(相关性高但口径不同) |
| P3-3 | 2006–2016 缺 SP500 与债券腿完整历史 → L0-3 在该段频繁"无法评估" |
| P3-4 | 未建模:滑点随波动变化、借贷成本、税、期货/ETF 展期、核心底仓再平衡成本 |
P4 · 环境层面(iSH,影响复现)
- 单个 Python 进程约 2 分钟被静默 kill(无报错、rc=0,
nohup也挡不住)→ 全折 walk-forward、批量绘图必须拆成多个短进程串行跑。 A && nohup B &会把 A 也放进后台并随 shell 退出被杀(与nohup sh -c '…' &不同)。- 容器无任何 CJK 字体,matplotlib 图例必须 ASCII;绘图本身也可能跑过 2 分钟 → 把"算"与"画"拆成两个进程(
plot_compare.py即为此拆出)。
六、是否需要改进?(商榷结论)
6.1 明确不该做的
不要继续在这个模型上调参数。
理由:P1-2(择优不稳定)+ P1-3(单样本、有效样本少)+ P1-4(无多重检验校正)三条同时成立时,
任何"调出来更好看"的参数都极可能是过拟合。黄金尤其容易过拟合——20 年只有一个大牛市样本,
任何在此窗口内表现优异的方向性规则,很可能只是复述了"2006–2026 涨了 7 倍"这一件事。
6.2 建议做的是"换问题",不是"换参数"
| 方向 | 从 | 到 | 对应工具 |
|---|---|---|---|
| ① 从猜方向 → 管风险 | 预测黄金涨跌 | 只做风险预算 / 波动目标 / 核心-卫星配比求解 | PyPortfolioOpt · Riskfolio-Lib · cvxportfolio |
| ② 从单资产择时 → 多资产分散 | 给黄金一个方向 | 把 DXY/实际利率/VIX 等做成各自独立的小仓位预测器,靠分散而非预测取胜 | pysystemtrade(Carver 路线) |
| ③ 从手搓 regime → 统计 regime | ADX + 效率比 + Hurst 阈值 | HMM / GMM 状态推断(仓库已有 regime_hybrid.py,应对齐而非另起一套) | hmmlearn · machine-learning-for-trading |
| ④ 从单次网格 → 正式防过拟合流程 | 训练段取最优 | 嵌套 walk-forward + Deflated Sharpe + White's Reality Check;用组合层回测(含分腿) | vectorbt · backtesting.py · bt |
6.3 一个更根本的建议
提高样本质量,而不是增加模型复杂度。
- 用更长历史:LBMA 金价 1968 年起可得(DXY/TIPS 只有 2006 起,但可用代理拓展早期段)
- 用横截面换时间序列:把"黄金一个品种 1080 周"换成"黄金+白银+铜+油+债券 多品种",有效样本数量级提升,且是 Carver 路线的核心
- 把"预测方向"降级为"预测风险"(波动可预测性远高于收益可预测性)——本次实测也支持这点:稳定有效的是风险端,不是方向端
七、GitHub 参考仓库(2026-09-27 经 GitHub API 实测核实)
⚠ 先明确一件事:下列几乎全是引擎/工具/方法论,不是现成的"能赚钱的策略公式"。
没有任何仓库能直接给出对黄金有效的 alpha;能拿到的是更可靠的流程与更少的自欺。
这正对应 §6 的结论:缺的不是公式,是流程与样本。
7.1 方法论 / 防过拟合(最对症)
| 仓库 | ★ | 为什么推荐 |
|---|---|---|
| [pst-group/pysystemtrade](https://github.com/pst-group/pysystemtrade)(原 robcarver17,已改名) | 3.5k | Rob Carver 的系统化交易框架。核心理念正好对症:"不要预测,要分散"、小仓位多品种、forecast scaling、风险目标化。其著作《Advanced Futures Trading Strategies》整本在讲"don't fit"。首选 |
| [stefan-jansen/machine-learning-for-trading](https://github.com/stefan-jansen/machine-learning-for-trading) | 21.0k | 含 regime detection 专章(HMM/GMM,可直接替换本文手搓的 ADX/Hurst)、特征重要性、以及回测陷阱(look-ahead / 幸存者偏差)章节 |
| [hmmlearn/hmmlearn](https://github.com/hmmlearn/hmmlearn) | 3.4k | 标准 HMM 库(sklearn 风格 API)。仓库已有 regime_hybrid.py(wess_hmm 移植),建议对齐而非另起一套 |
7.2 回测引擎(替换/增强手写引擎)
| 仓库 | ★ | 适用点 |
|---|---|---|
| [polakowo/vectorbt](https://github.com/polakowo/vectorbt) | 9.2k | 向量化,参数扫描与 walk-forward 极快——正对我们"网格 + 滚动"的场景。维护活跃(2026-09) |
| [mementum/backtrader](https://github.com/mementum/backtrader) | 23.3k | 最成熟的 Python 回测框架;注意最后 push 为 2024-08,维护放缓 |
| [kernc/backtesting.py](https://github.com/kernc/backtesting.py) | 9.0k | 轻量、API 干净,内置优化器与多资产支持 |
| [pmorissette/bt](https://github.com/pmorissette/bt) | 3.0k | 组合层回测,天然支持"核心+卫星"分腿与再平衡——恰好对应本项目的结构 |
| [stefan-jansen/zipline-reloaded](https://github.com/stefan-jansen/zipline-reloaded) | 1.9k | Zipline 的现代维护版 |
7.3 组合构建(回答"核心到底该 60% 还是别的")
| 仓库 | ★ | 适用点 |
|---|---|---|
| [PyPortfolio/PyPortfolioOpt](https://github.com/PyPortfolio/PyPortfolioOpt) | 6.1k | HRP / Black-Litterman / 有效前沿 —— 可用于正式求解核心-卫星配比与卫星内部权重,替代拍脑袋的 60/40 |
| [dcajasn/Riskfolio-Lib](https://github.com/dcajasn/Riskfolio-Lib) | 4.5k | 风险预算 / 风险平价 / 最差回撤优化 |
| [cvxgrp/cvxportfolio](https://github.com/cvxgrp/cvxportfolio) | 1.3k | Boyd 组的凸优化组合回测,多期 + 含交易成本,理论上最严谨 |
7.4 绩效归因与报告
| 仓库 | ★ | 适用点 |
|---|---|---|
| [ranaroussi/quantstats](https://github.com/ranaroussi/quantstats) | 7.7k | 一键输出全套绩效报告与回撤图,可直接替换本引擎手写的 performance() |
| [stefan-jansen/pyfolio-reloaded](https://github.com/stefan-jansen/pyfolio-reloaded) | 616 | 组合/风险归因(pyfolio 现代维护版) |
| [stefan-jansen/alphalens-reloaded](https://github.com/stefan-jansen/alphalens-reloaded) | 657 | 因子层面的 IC / 分层回测——用于检验"四因子到底有没有信息量" |
7.5 索引与数据(自己挑)
| 仓库 | ★ | 说明 |
|---|---|---|
| [wilsonfreitas/awesome-quant](https://github.com/wilsonfreitas/awesome-quant) | 29.8k | 量化资源总索引 |
| [wangzhe3224/awesome-systematic-trading](https://github.com/wangzhe3224/awesome-systematic-trading) | 5.2k | 系统化交易资源索引(期货/持仓/组合方向更全) |
| [akfamily/akshare](https://github.com/akfamily/akshare) | 22.8k | 已在「武器库」;A 股/宏观数据接口 |
八、诚实声明(须与任何"模型有效"表述并列)
- 回测口径 ≠ 实盘 ≠ 生产管线(
数据/panel.csv+paper_engine.py),数字不可互相引用。 - 四因子权重是主观先验
[待验证];A/B/C 评分是可计算操作化代理(原文只给权重未给公式)。 - L0 阈值标定未完成(框架/数据口径定义 §9.4),全部
[待验证]。 - 样本内单次结果不构成证据;本文所有关键结论均以等敞口对比与样本外为准。
- 不构成投资建议。
九、修订记录
- 2026-09-27 v2.0:按三条设计指令重构(降频 / 宏观否决权 / 核心卫星);新增
--strategy adaptive与--l1-method trendstate;新增等敞口公平对比与周频样本外 walk-forward;修 4 个实现缺陷(P2-a~d);产出否定式结论(卫星无 alpha)+ 换问题建议 + GitHub 参考仓库清单。 - 2026-09-26 v1.1(工作区,未入库):新增
--sizing score证据分加权 + 日频 walk-forward(7 折,OOS 夏普 0.286)。 - 2026-09-26 v1.0(工作区,未入库):首版引擎(L0–L4 单资产择时,2006–2026)。