多资产组合回测模块(回测/engine_multi/)
全部为周频再平衡 + 6bps 单边成本口径(除注明外),窗口 1986-01→2026-09。
多资产组合回测模块(回测/engine_multi/)
本目录回答的是"换问题"后的第一问:不猜方向,只做分散 —— 分散到底比择时好多少?
只读消费者:只读
数据/的原始序列,不写生产文件、不参与fetch.py管线、不 import 第三方组合库。不改
回测/engine_v1/(v2.0 需保持可复现)。上游:阶段 1 任务书 → optimization/提示词/继续任务提示词_2026-09-27_回测引擎v2.0与多资产。
一、结论摘要(三张表 + 两张辅助表)
全部为周频再平衡 + 6bps 单边成本口径(除注明外),窗口 1986-01→2026-09。
| 组合(池 = 黄金/纳指/WTI) | 年化 | 波动 | 夏普 | 最大回撤 | 卡玛 |
|---|---|---|---|---|---|
| 黄金 100% | 6.38% | 15.9% | 0.469 | −49.4% | 0.129 |
| 纳指100 100% | 14.01% | 25.9% | 0.637 | −82.9% | 0.169 |
| WTI 100% | 3.25% | 43.5% | 0.294 | −93.9% | 0.035 |
| 等权 1/N | 10.71% | 17.8% | 0.661 | −46.5% | 0.230 |
| 逆波动率 | 9.79% | 14.1% | 0.735 | −44.2% | 0.221 |
| 风险平价 ERC | 10.11% | 13.9% | 0.766 | −42.1% | 0.240 |
要点
- "金 + 纳指"两资产(交接件 §2.4 的原型)复现成功:41 年 夏普 0.856 / 回撤 −31.8% / 卡玛 0.332,与预检的 0.88 / −33% / 0.33 一致(详见 §六 验证)。
- 加 WTI 会让组合变差(夏普 0.856 → 0.766),因为 WTI 自身是"高波动 + 零长期收益 + −94% 回撤"的资产:1986–2026 年化仅 3.25%、卡玛 0.035。
→ 资产池不是越大越好,这正是把 2/3/4 资产池分开跑的原因。
- ERC ≈ 逆波动率 > 等权:三资产池中 ERC 夏普 0.766 vs 逆波动率 0.735 vs 等权 0.661 —— 等权在含 WTI 时被拖累(它给 WTI 33% 权重)。
- 2000–2009 是唯一"分散失效"的十年(ERC 夏普 0.676 < 黄金 0.809),与交接件 §2.4 一致。
- 1/N 与 ERC 的比较必须"同性质"才算数(v1.1 修正):把 1/N 按常数 k 缩放到与 ERC 同波动时,夏普尺度不变(= 0.832,与 1/N 完全相同);而 ERC 是 0.855 → 在两资产池里 ERC 同样优于 1/N。2006–2026 两资产池两者几乎并列(0.983 / 0.998),但三资产(0.678 / 0.818)与四资产(0.955 / 1.140)ERC 明显更好。腿越多、相关性越复杂,1/N 越吃亏。
- ⚠ 曾出现过的"1/N 同波动缩放 0.871 > ERC 0.855"是口径错误:那一行用的是逐 bar 时变的 k(滚动 60bar),实为 vol targeting(择时叠加),不是纯配置 —— 现已拆成两行(见 §七 表 3)。
- 成本不是主要矛盾,但"看数据/调仓的频率"很重要(表 D,已升级为数据频率 × 调仓频率二维):6bps 单边相对零成本,夏普普遍只降 0.005–0.017;跨频率看,"少动"占优 —— 两池都是 月数据 + 季调仓最好(g2 ERC 0.907、g3 ERC 0.854),而 日数据 + 日调仓最差(0.883 / 0.761)。"越勤越好"是错的。
- 相关性分段看(表 E):黄金–纳指全样本 −0.008,但分段为 −0.037 / −0.060 / −0.009 / +0.140(2020–2026 转正);2008 金融危机时 −0.151(黄金确实是那时的分散器)。"全样本平均相关系数"会骗人,分散度必须分段看。
二、目录内容
| 文件 | 说明 |
|---|---|
multiasset_backtest.py | 主模块(约 1000 行,单文件零联网,只读 数据/) |
plot_multi.py | 独立绘图脚本(从已落盘 CSV 出图;容器无 CJK 字体 → 图例一律 ASCII) |
tables/A_全样本.csv | 表 1:全样本(1986–2026 / 2006–2026 / 四资产 2016–2026)各资产与各组合指标 |
tables/B_子区间.csv | 表 2:子区间稳健性(周频 + 6bps,热启动 2 年) |
tables/B2_子区间_对齐口径.csv | 表 2′:同上但对齐交接件 §2.4 口径(日频再平衡 · 零成本 · 热启动) |
tables/C_同风险对比.csv | 表 3:同风险对比(1/N 按常数 k 缩放到与 ERC 同波动)+ 1/N + 波动率目标(择时叠加,单列) + 恒定配比基准(含"不再平衡"与"真·恒定配比"两种)+ 平均权重 |
tables/D_再平衡x成本矩阵.csv | 表 4:数据频率 × 调仓频率 × 成本 矩阵(列名即口径:如"周数据/季调仓";其中"周数据/周调仓"与表 A/C 逐位一致) |
tables/E_分段相关系数.csv | 表 5:分段相关系数(含 2008 与 2020-02→03 崩盘段) |
tables/守卫日志.json | 数据守卫逐条记录(非正价格 / 极端收益),可审计 |
curves/*.csv | 净值曲线(date, equity)与各资产归一化价格(g2_assets_norm.csv 等) |
curves_compare.png | 组合净值对比图(对数轴,两池并排) |
三、运行
cd "/var/minis/mounts/DUCKERRY_Warehouse/回测/engine_multi"
python3 multiasset_backtest.py --task sanity # 数据审计 + 守卫明细
python3 multiasset_backtest.py --task align # 复现测试(对齐交接件 §2.4)
python3 multiasset_backtest.py --task table_a # 表 1(可 --only 窗口 / --pools g2,g3)
python3 multiasset_backtest.py --task table_b # 表 2
python3 multiasset_backtest.py --task table_b2 # 表 2′(对齐口径)
python3 multiasset_backtest.py --task table_c --only 全样本
python3 multiasset_backtest.py --task table_d --pools g2 --methods erc
python3 multiasset_backtest.py --task table_e
python3 multiasset_backtest.py --task curves
python3 plot_multi.py # 出图(约 1 分钟)
# 任意自定义组合(资产池是 CLI 参数)
python3 multiasset_backtest.py --pool gold,ndx --freq D --rebal W --cost-bps 6 --only 全样本参数:--task | --pool <逗号分隔资产> | --pools <预定义池 g2/g3/g4 过滤> | --freq {D,W,M}(数据/bar 频率)| --rebal {D,W,M,Q}(再平衡频率,与 freq 独立)| --cost-bps | --vol-window(默认 60 bar)| --only(子集过滤,iSH 拆短进程用)| --methods | --pools | --dfreqs(表 4 数据频率过滤)。
数据目录默认 /var/minis/mounts/DUCKERRY_Warehouse/数据,可用 --data 覆盖。
四、口径与设计(读结论前必看)
- 数据频率 ≠ 再平衡频率:
--freq决定 bar 与滚动估计频率,--rebal决定调仓节奏。二者独立(这是能做表 D 矩阵的前提)。 - 无前视:t 期收盘算权重 → t+1 生效;段内不再平衡,权重随价格自然漂移,未投部分为现金。
- 成本:
换手(|Δw| 之和) × cost_bps/1e4,在调仓当刻从净值扣除(默认 6bps 单边,与engine_v1一致)。 - 年化期数随频率推断(252/52/12/4),不硬编码 252。
- 年化口径已统一(2026-09-27,v5.3.2):
metrics()的years由日历跨度
(idx[-1]-idx[0]).days/365.25 改为 bar 数 / ppy(复用 infer_ppy),与 engine_v1 和 quantstats 同源。旧口径在本表上系统性高估年化约 0.4%(相对),卡玛同比例受影响; 改动后 回测/perf_audit 实测 engine_multi vs quantstats 年化相对差 0.36% → 0.05%。
- 全部方法零参数:等权 1/N、逆波动率(60 bar)、ERC(等风险贡献,手写对数空间阻尼不动点迭代,不引外部库)、1/N 同波动缩放(缩放到与 ERC 同一事前波动率,k 封顶 1.0 不加杠杆)。没有任何"可调"的旋钮 —— 这是"不再调参"的直接体现。
- 样本对齐:各市场假日不同 → 先外连接对齐,再
ffill(limit=5)补齐假日,最后截掉池内仍有缺值的前导行(保证所有腿同时起步)。 - 数据守卫(逐条记录,不静默):
- 非正价格 → 该腿置 NaN(视为停牌,收益按 0 计入),记录于
tables/守卫日志.json; |单期收益| > 60%→ 同样置 NaN + 记录(在非正价格剔除后判定);- 实测:池 g3/g4 仅触发 1 条 —— WTI 2020-04-20 = −36.98(其 −302% 与 −124% 两期收益随之被消除)。
- 黄金 5405 极值 sanity check:2026-01-29 的 5405 是真实行情(前值 5306.95 / 5064.25,后续 4981.85 / 4714.75,是 2026-01 那波急涨-急回的局部高点),不是孤立尖峰,故不剔除。
五、⚠ 数据保真度警告(不做这三条,表 1 的"分散有效"会被误当成可直接落地的结论)
| 资产 | 问题 | 后果(方向) |
|---|---|---|
| WTI | fred_DCOILWTICO.csv 是 Cushing 现货价,不是可交易期货收益(无展期 / contango 损耗;原油 ETF 长期损耗显著) | 高估原油腿贡献 [待验证]。本模块未计展期成本,含 WTI 的组合结果应视为上限 |
| NDX / SPX | FRED 这两条是价格指数,不含股息 | 低估股票腿约 1%/年 → 41 年累计差约 50% → 低估分散收益 |
| 黄金 | LBMA PM Fix 是定盘价,非可直接成交 | 与生产库口径一致,保留;但严格说不可按当日定盘价无摩擦成交 |
三条已写入 tables/*.csv 的 脚注 列与 数据库说明 的口径约定;任何对外引用必须一并带上。
六、验证(复现测试与自检)
| 验证项 | 结果 |
|---|---|
| 复现交接件 §2.4(池 g2 · 逆波动率 · 日频再平衡 · 零成本 · 1986–2026) | 夏普 0.896(对照 0.88)|波动 12.66%(12.6%)|回撤 −33.04%(−33%)|卡玛 0.336(0.33)→ 全部落在容差内(Δ夏普 +0.016 < 0.05) |
| 单资产交叉核对 | 黄金 6.38%/15.87%/0.469/−49.4%/0.129(对照 6.38%/15.9%/0.47/−49%/0.13)|纳指 14.01%/25.85%/0.637/−82.9%/0.169(对照 14.02%/25.9%/0.64/−83%/0.17)——口径统一后与对照几乎逐位吻合 |
| ERC 求解器正确性 | 对角协方差下 ERC ≡ 逆波动率(精确一致);一般协方差下与 scipy.optimize 解逐位一致(w=[0.3842, 0.3922, 0.2236]);时间序列平均风险贡献占比 = [0.3331, 0.3331, 0.3338] ≈ 1/3 |
| 两资产特例 | 池 g2 的 逆波动率 与 ERC 完全相同(md5 一致的净值曲线)—— 2 资产时等风险贡献 ≡ 逆波动率,是理论正确性的旁证 |
| 子区间偏差(诚实披露) | 表 2′ vs 交接件 §2.4 子区间:1986–99 0.824/0.77 | 2000–09 0.676/0.64 | 2010–19 0.932/0.94 | 2020–26 1.318/1.42。方向与排序一致(含"2000–2009 唯一跑输黄金");残差仅部分解释:把滚动窗口由冷启动改为热启动 2 年,2020–26 由 1.255 → 1.318、2000–09 由 0.730 → 0.676,均向对照靠拢但未闭合(2020–26 仍差 0.10)。余下残差(窗口长度与复利/对齐细节)保留,不声称已定位 |
| 运行自检 | arch_check 11/11|doc_sync_check --strict rc=0|moc_index 无需更新 |
偏离来源固定说明:交接件 §2.4 是日频再平衡 + 零成本;本模块表 1/2 是周频 + 6bps(更贴近实盘)。两类数字不可直接对比;要对比请用
--task align或表 2′。
七、结果细节
表 1(A_全样本.csv)· 全样本
| 窗口 / 池 | 组合 | 年化 | 波动 | 夏普 | 最大回撤 | 卡玛 |
|---|---|---|---|---|---|---|
| 1986–2026 · 金+纳指 | 等权 1/N | 11.38% | 14.19% | 0.832 | −52.1% | 0.218 |
| 1986–2026 · 金+纳指 | 逆波动率 = ERC | 10.54% | 12.68% | 0.855 | −31.8% | 0.332 |
| 1986–2026 · 金+纳指+WTI | 等权 1/N | 10.71% | 17.81% | 0.661 | −46.5% | 0.230 |
| 1986–2026 · 金+纳指+WTI | 逆波动率 | 9.79% | 14.07% | 0.735 | −44.2% | 0.221 |
| 1986–2026 · 金+纳指+WTI | ERC | 10.11% | 13.85% | 0.766 | −42.1% | 0.240 |
| 2006–2026 · 金+纳指 | 等权 1/N | 13.54% | 13.93% | 0.983 | −30.3% | 0.447 |
| 2006–2026 · 金+纳指 | ERC | 13.50% | 13.64% | 0.998 | −31.8% | 0.425 |
| 2006–2026 · 金+纳指+WTI | ERC | 11.82% | 15.09% | 0.818 | −42.1% | 0.281 |
| 2016–2026 · 四资产 | 等权 1/N | 16.17% | 17.33% | 0.955 | −34.6% | 0.467 |
| 2016–2026 · 四资产 | 逆波动率 | 15.21% | 13.82% | 1.098 | −27.6% | 0.551 |
| 2016–2026 · 四资产 | ERC | 15.61% | 13.58% | 1.140 | −25.8% | 0.605 |
表 2 / 2′ · 子区间夏普(池 = 金+纳指)
| 组合 | 1986–1999 | 2000–2009 | 2010–2019 | 2020–2026 |
|---|---|---|---|---|
| 黄金 100% | 0.001 | 0.809 | 0.269 | 0.930 |
| 纳指100 100% | 1.134 | −0.001 | 0.963 | 0.849 |
| 等权 1/N | 1.109 | 0.382 | 0.927 | 1.227 |
| 逆波动率 = ERC | 0.799 | 0.635 | 0.981 | 1.234 |
| *(表 2′ 对齐口径)* ERC | *0.824* | *0.676* | *0.932* | *1.318* |
→ 只有 2000–2009 组合跑输黄金,与交接件一致;其余三段组合完胜单资产,且回撤显著更小。
表 3(C_同风险对比.csv)· 同风险对比
口径(v1.1 修正,必读):等权/逆波动率/ERC 都是恒 100% 敞口的纯配置方法,"同敞口被动"会退化成它们自己 → 改用同波动对比。但"缩放到同波动"必须区分两种做法:
· 常数 k(
k = 全样本已实现 σ_ERC/σ_1N)→ 纯归一化,夏普尺度不变(与 1/N 相同,这是最干净的对照);· 时变 k(逐 bar 用滚动 60bar 事前波动匹配)→ 那是 vol targeting(择时叠加),不是纯配置,与 ERC 不可直接比。
| 池 | 组合 | 类型 | 常数k | 平均敞口 | 年化 | 波动 | 夏普 | 最大回撤 | 卡玛 |
|---|---|---|---|---|---|---|---|---|---|
| 金+纳指 | 等权 1/N | 纯配置 | 1.00 | 11.38% | 14.19% | 0.832 | −52.1% | 0.218 | |
| 金+纳指 | 1/N 纯缩放(常数k) | 纯配置·归一化 | 0.8936 | 0.89 | 10.22% | 12.68% | 0.832(= 1/N,尺度不变) | −47.9% | 0.214 |
| 金+纳指 | 风险平价 ERC | 纯配置 | 1.00 | 10.54% | 12.68% | 0.855 | −31.8% | 0.332 | |
| 金+纳指 | 1/N + 波动率目标(滚动60bar) | 择时叠加 | 0.89 | 10.67% | 12.58% | 0.871 | −39.4% | 0.271 | |
| 金+纳指 | 初始 60/40(金/纳指)·不再平衡 | 基准(买入持有) | 1.00 | 11.75% | 21.75% | 0.619 | −79.5% | 0.148 | |
| 金+纳指 | 恒定配比 60/40(每周再平衡) | 基准 | 1.00 | 10.56% | 13.35% | 0.820 | −42.6% | 0.248 | |
| 金+纳指+WTI | 等权 1/N | 纯配置 | 1.00 | 10.71% | 17.81% | 0.661 | −46.5% | 0.230 | |
| 金+纳指+WTI | 1/N 纯缩放(常数k) | 纯配置·归一化 | 0.7777 | 0.78 | 8.53% | 13.85% | 0.661(= 1/N) | −38.0% | 0.224 |
| 金+纳指+WTI | 风险平价 ERC | 纯配置 | 1.00 | 10.11% | 13.85% | 0.766 | −42.1% | 0.240 | |
| 金+纳指+WTI | 1/N + 波动率目标(滚动60bar) | 择时叠加 | 0.76 | 7.88% | 13.89% | 0.616 | −42.7% | 0.185 | |
| 四资产 | 等权 1/N | 纯配置 | 1.00 | 16.17% | 17.33% | 0.955 | −34.6% | 0.467 | |
| 四资产 | 1/N 纯缩放(常数k) | 纯配置·归一化 | 0.7834 | 0.78 | 12.75% | 13.58% | 0.955(= 1/N) | −27.9% | 0.457 |
| 四资产 | 风险平价 ERC | 纯配置 | 1.00 | 15.61% | 13.58% | 1.140 | −25.8% | 0.605 |
读数(修正后):
- 纯配置对比下 ERC 全面胜出:两资产池 0.855 vs 0.832;三资产 0.766 vs 0.661;四资产 1.140 vs 0.955。回撤与卡玛的差距更大。
- "1/N 同波动缩放"若用常数 k,夏普必然不变(0.832 = 0.832)—— 这条同时是尺度不变性的实现自检。
- 那行 0.871 是滚动波动目标(vol targeting)跑出来的,属择时叠加:即便接受它,ERC 的回撤(−31.8% vs −39.4%)与卡玛(0.332 vs 0.271)仍更好 → 不需要引入 vol timing。
- "恒定配比"必须区分是否再平衡:金+纳指初始 60/40 若 41 年不再平衡,NDX 腿会主导(波动 21.75%、回撤 −79.5%);真·每周再平衡到 60/40 则是 13.35% / −42.6%。"60/40"不是一个策略,加上"是否再平衡"才是。
表 4(D_再平衡x成本矩阵.csv)· 数据频率 × 调仓频率 × 成本(夏普,6bps)
口径(v1.1 修正):原表 4 用日频数据跑各种调仓频率,与表 A/C(周数据+周调仓)不是同一个组合,两处数字不可比(g2 等权:表 D 0.779 vs 表 A/C 0.832)。现把"数据频率"作为显式一维;"周数据/周调仓"一列与表 A/C 逐位一致(g2 等权 0.8317 / 11.38% / 14.19% / −52.14% / 0.2183 —— 已实测核对)。
| 池 · 方法(6bps) | 月数据/季调仓 | 月数据/月调仓 | 日数据/日调仓 | 周数据/月调仓 | 周数据/季调仓 | 周数据/周调仓 | 日数据/日调仓以外的日数据行… |
|---|---|---|---|---|---|---|---|
| 金+纳指 · ERC | 0.907 | 0.899 | 0.883 | 0.864 | 0.859 | 0.855 | 日数据/周 0.881 | 日数据/月 0.877 | 日数据/季 0.850 |
| 金+纳指+WTI · ERC | 0.854 | 0.824 | 0.761 | 0.764 | 0.811 | 0.766 | 日数据/周 0.760 | 日数据/月 0.745 | 日数据/季 0.784 |
| 金+纳指 · 等权 1/N | 0.865 | 0.855 | 0.784 | 0.836 | 0.850 | 0.832 | 日数据/周 0.779 | 日数据/月 0.786 | 日数据/季 0.797 |
| 金+纳指+WTI · 等权 1/N | 0.739 | 0.693 | 0.675 | 0.660 | 0.709 | 0.661 | 日数据/周 0.669 | 日数据/月 0.659 | 日数据/季 0.716 |
- 成本影响很小(零成本 vs 6bps,夏普差 0.005–0.017);只有"日数据 + 日调仓"降幅稍大(g3 ERC 0.778 → 0.761)。
- 跨频率的稳定结论是"少动占优":两池的最优都在"月数据 + 季调仓"(ERC 0.907 / 0.854),而"日数据 + 日调仓"反而最差(0.883 / 0.761)。
- ⚠ 频率网格上的差异(+0.09~0.10 夏普)大于成本差异一个量级,且看数据频率与调仓频率都有效 → 这是一个设计选择,不是可忽略的实现细节。但它不是"择时":只是"多久看一眼、多久动一次"。
表 5(E_分段相关系数.csv)· 相关系数分段
| 资产对 | 全样本 | 1986–1999 | 2000–2009 | 2010–2019 | 2020–2026 | 2008 危机 |
|---|---|---|---|---|---|---|
| 黄金–纳指 | −0.008 | −0.037 | −0.060 | −0.009 | +0.140 | −0.151 |
| 黄金–WTI | 0.081 | 0.080 | 0.136 | 0.080 | 0.024 | 0.224 |
| 纳指–WTI | 0.061 | −0.060 | 0.087 | 0.255 | 0.082 | 0.237 |
→ 黄金与股票的低/负相关并不是恒定的:2008 是 −0.15(真的分散),但 2020–2026 已转正(+0.14)。用"全样本相关系数"给分散度定论会系统性乐观。
八、诚实声明(不许省略)
- 回测口径 ≠ 实盘 ≠ 生产管线(
数据/panel.csv+scripts/paper_engine.py);数字不可互相引用。不构成投资建议。 - 本节所有正收益未扣除:股息缺失(低估股票腿)、原油展期损耗(高估原油腿)、税费与真实冲击成本、货币换算。
- 结果是单一样本上的无参数方法对比,没有样本外切分(本模块的设计目的正是"没有可择优的参数");子区间表是稳健性检查,不是样本外检验。
- 组合方法只决定权重,不预测方向;没有任何"择时"成分。
九、环境备注(iSH)
- 单进程约 2 分钟被静默 kill(rc=0、无 traceback)→ 本模块所有表任务逐块增量落盘,并提供
--only / --pools / --methods把长任务拆成多个短进程。 - 容器无 CJK 字体 → 绘图脚本一律 ASCII。
- 性能工程(本模块的关键实现决定):
- 滚动均值/波动/协方差用 cumsum 全时段批量(不用逐 bar pandas);
- ERC 用对数空间阻尼不动点并跨全部时点批量迭代(逐 bar 调用的 numpy 固定开销在 iSH 上是主要成本);
- 净值路径全向量化:段内漂移用
nav(t)/nav(anchor)精确表达,与逐 bar 循环严格等价; - 因此日频 × 10475 次调仓的一次回测从 41s 降到 0.2s,且结果逐位一致(0.896 / −33.04% / 0.336 三次相同)。
- 设备负载抖动极大(同样的计算 6.8s~28.6s),不要依赖单次计时。
- ✔ 表 C 增量落盘翻倍 bug:已修复(v5.3.2)+ 幂等回归通过 —— 现在重跑
--task all无需再手工删表。 - 原缺陷:
task_table_c用_load_rows()做增量累积(为抗 iSH 静默 kill),循环内_save_table_c()有
drop_duplicates(keep="last"),但循环结束后的最后一次 df.to_csv() 绕过了去重 → 在已存在的 C 表上重跑会把整表翻倍(实测 45 行 → 90 行,旧口径行在前,按下标读会读到过期值)。
- 修复:
task_table_c末尾的df.to_csv(...)→_save_table_c(rows);并新增
_assert_table_ok():所有表落盘后强制自检「数据行 == 唯一键数」,违规直接 SystemExit, 不再静默产出翻倍表(_save_table_c / _save_table_d 均已挂载)。
- 幂等回归(原始输出):
--task table_c连续跑两次,rc 均为 0,
两次落盘均为 46 行(1 表头 + 45 数据)、md5 完全相同(0259fc00f8b3226ddd81fbf90f91e090)→ 幂等成立。
- 内容中性验证:把修复前的「末尾 raw 落盘」版本与修复后版本放在同一份数据上各跑一次
(--only 全样本,输出重定向到 /tmp,不动真表)→ 两边落盘 逐字节一致 (md5 e102bc692f06924d208683adaea55eaf,19 行)→ 修复只消除累积副作用,不改变内容。
十、修订记录
- 2026-09-27 v1.2(P3-③:年化口径统一,v5.3.2):
metrics()年化口径由「日历跨度」改为「bar 数 / ppy」(years = len(eq)/ppy,复用已有的
infer_ppy)→ 与 engine_v1 / quantstats 同源。只改年化公式,信号/权重/净值路径/成本全未动。
- 全量复算(
--task all,34 个子任务全部 rc=0)覆盖tables/*.csv与curves/*.csv;
除「年化」「卡玛」两列外,所有列逐字符未变;curves/*.csv 与 tables/E_分段相关系数.csv 逐字节未变(净值路径与相关系数不受年化口径影响 —— 同时是复算确定性的旁证)。
- 方向与量级:旧口径系统性高估年化约 0.4%(相对)、卡玛同比例(如两资产 ERC 10.59% → 10.54%)。
所有排序、所有结论均未改变(差异远小于结论间距)。
- 交叉验证:
回测/perf_audit实测engine_multivs quantstats 年化相对差 0.36% → 0.05%
(残留 0.05% = len(eq) vs quantstats returns.count() 的 1 个观测之差,属定义细节而非口径分歧)。
- 本 README 全部引用值已同步更新(表 1 / 表 3 / 表 4 注解 / §六 验证表)。
- 修复表 C 增量落盘翻倍 bug(用户单独授权):
task_table_c末尾落盘改走_save_table_c()(带去重),
并新增 _assert_table_ok() 落盘自检(数据行 ≠ 唯一键数 → SystemExit)。 幂等回归:--task table_c 连跑两次均 rc=0、46 行、md5 相同; 内容中性:修复前后在同一份数据上产出逐字节一致(见 §九)。
- 2026-09-27 v1.1(按用户核验意见修正 4 项):
- 表 3 口径拆行:原"1/N 同波动缩放"用的是逐 bar 时变 k(实测 k ∈ [0.21, 1.0]、std 0.162)→ 实为 vol targeting(择时叠加),夏普自然不同于 1/N(0.871 vs 0.832)。现拆为「1/N 纯缩放(常数 k,夏普尺度不变 = 0.832)」与「1/N + 波动率目标(时变 k,标注择时叠加)」两行。随之修正结论:纯配置对比下 ERC(0.855)> 1/N(0.832),"1/N 缩放到同波动后更好"的说法不成立。
- 表 4 升级为「数据频率 × 调仓频率」二维:原表全部用日频数据,与表 A/C 不同口径(g2 等权 0.779 vs 0.832)。现显式列出数据频率,并实测核对"周数据/周调仓"行与表 A/C 逐位一致。随之修正结论:不是"两资产日频最优",而是两池都是"月数据 + 季调仓"最优、日数据+日调仓最差。
- 修 bug:「恒定配比 1/N」原硬编码
1/3→ 四资产池敞口 1.333(隐含杠杆);改为1/n,现敞口恒为 1.000。 - "恒定配比"歧义:原「恒定配比 60/40」实为买入持有(不再平衡),权重漂移使 NDX 腿主导(波动 21.75% / 回撤 −79.5%)。现拆为「初始 60/40·不再平衡」与「恒定配比 60/40(每周再平衡)」(13.35% / −42.6%)两行。
- 另:§六 子区间残差表述改为"仅部分解释,残差保留"(不再声称已定位)。
- 2026-09-27 v1.0:首版(阶段 1)。新建
回测/engine_multi/:多资产组合回测模块(等权 / 逆波动率 / ERC / 1÷N 同波动缩放),数据频率与再平衡频率分离,WTI 负价与极端收益守卫逐条记录,产出五张表(全样本 / 子区间 / 子区间对齐口径 / 同风险对比 / 再平衡×成本矩阵 / 分段相关系数)+ 净值曲线与对比图;完成对交接件 §2.4 的复现测试与 ERC 求解器正确性验证。未改动回测/engine_v1/与scripts/任何文件。