卡尔曼滤波特征(回测/kalman/)
噪声三参数 σ_obs²(R)、σ_level²(Q_l)、σ_slope²(Q_s)。
卡尔曼滤波特征(回测/kalman/)
一句话:用纯 numpy/scipy 手写一个局部线性趋势卡尔曼滤波器,MLE + 滚动重估产出 4 个
点内(point-in-time) 特征,嵌进
engine_v1的 L1 动量与趋势态判定做样本外 A/B 对照。样本外判定 = 无改善(Δ夏普 +0.037 / +0.025,门槛 ≥ +0.10)→ 不引入生产,
并且三参数 MLE 全部钉在事前边界上(似然退化)——这两条都是不利于部署的证据,如实归档。
完整结论、四张表与 14 条 checklist 逐条判定见 回测/kalman/报告_卡尔曼嵌入评估_2026-10-02。
一、文件
| 文件 | 说明 |
|---|---|
kalman_filter.py | 滤波器 + MLE + 滚动重估(单文件、零联网、只读 数据/;纯 numpy/scipy) |
kalman_embed.py | 特征注入 + A/B 对照驱动(只调用 engine_v1 既有函数,不修改其任何文件);含 pre-flight 断言 |
kalman_report.py | 从 ab/*/fold_*.json 汇总四张表(Markdown) |
v2_gate_diag.py | V2 闸门机制诊断(只读):逐折统计两条闸各闸掉多少 bar + 量纲/分位(输出留档 cache/v2_gate_diag.log) |
kalman_selftest.py | 自带自检 7 项(--quick 抽样模式;iSH 上 ≥2min 风险 → 建议 --only K 分项跑) |
cache/ | 重估缓存 refits_W_full.json(逐条落盘、可续跑)、特征 kf_features_W.csv、对冲比率、证据日志 |
ab/ | A/B 结果:base/·v1/·v2/ 各自 独立子目录(防缓存串结果的隔离要求)+ oos_curve_<臂>.csv |
ab_run.sh | A/B 串行跑折的辅助脚本(逐折 <2 分钟短进程、已存在的折自动跳过可续跑) |
报告_卡尔曼嵌入评估_2026-10-02.md | 结论报告(四张表 + pre-flight 原始输出 + 判定) |
二、模型规格(照任务书实现,未自创更复杂模型)
- 状态
[level, slope],转移[[1,1],[0,1]](局部线性趋势);观测 = log 价格;
噪声三参数 σ_obs²(R)、σ_level²(Q_l)、σ_slope²(Q_s)。
- 三参数一律极大似然(MLE),禁止手工试参;优化器 =
L-BFGS-B(log 方差, 有界),
回退链 Nelder-Mead → 确定性默认值(每次回退计数 + 记日志,绝不静默返回 NaN; 样本 < MIN_FIT_OBS=60 亦走确定性回退)。
- 无前视(硬性):参数向前滚动重估(窗口 104 周 bar ≈ 2 年、步长 13 ≈ 1 季度),
每次重估只用 prices[fit_start:fit_end];首个重估生效之前的 bar 一律 NaN(不外推、不回填)。 边界与窗口事前写死(取自 ML4T §9.2 的 (-5,-5,-15) 与 504/63 sessions 的周频换算), 跑 A/B 前后均未调整。
- 产出列名固定:
kf_level/kf_slope/kf_innovation(标准化残差)/kf_uncertainty(后验协方差迹);
另有 kf_hedge_ratio(金 vs 纳指动态对冲比率,随机游走系数回归;burn-in 252 bar 后才报告)。
三、事前写死的注入设计(V1 判据 / V2 并列报告;详见 kalman_embed.py 头部)
| 版本 | 注入内容 | 是否判据 |
|---|---|---|
| V1 | 只注入 kf_level + kf_slope:chg5 → 100×Δ₅kf_level、up5/dn5 → sign(Δ₁kf_level) 计数、趋势态 er → kf_er(沿用引擎原阈值;ADX 保持不变) | 是 |
| V2 | V1 + 全 4 特征:|kf_innovation| ≥ 3σ 闸 + kf_uncertainty > 扩张 90% 分位 闸(只用历史)→ 该 bar 置「无法评估」 | 同表并列报告 |
| 诊断 | innovation / uncertainty 单独出统计量 | 不作判据 |
不得事后挑选版本;L0 阈值/G1 闸门语义/L2 四因子/L3 止损/成本/事件否决一律未动。
四、无前视证明(机器证据)
# 断言:把 t 之后的数据全部删掉,t 及之前的 kf_* 输出必须**逐位不变**
python3 kalman_selftest.py --only 2 # 真实 gold_pm 尾 900 bar
# → T2 对比:全样本 900 bar vs 截断 801 bar(cut=800);截断点之前非空行 697
# → T2 OK 删除 t 之后全部数据 → t 及之前 kf_level/kf_slope/kf_innovation/kf_uncertainty 逐位不变比较用 a.tobytes() == b.tobytes()(逐位,含 NaN payload),非近似;写法参照 tests/test_pipeline.py::PortfolioRecordTests::test_no_lookahead_in_weights。 另有 T3(每次重估 fit_end == 生效位置、n_obs ≤ 窗口、首个生效位置之前无任何参数)与 T6(对冲比率截断逐位不变)两项结构性断言。
五、口径(引用本目录数字前必读)
- 年化一律
bar 数 / ppy(周频ppy=52),与 回测/perf_audit/README §四 同源;
本目录不使用日历跨度年化。索提诺/卡玛如需引用须注明定义(沿用 engine_v1.performance)。
- 回测口径 ≠ 纸面 ≠ 生产管线;本目录数字不得与
数据/panel.csv/paper_engine混用。 - 换手 =
engine_v1的卫星换手turnover列累计;成本 6bps 单边(1bp 佣金 + 5bps 滑点)。
六、退化诊断(本模块最重要的负面发现)
| 诊断 | 实测 | 判读 |
|---|---|---|
重估窗口中 logθ 钉在事前下界的比例(R / Q_l / Q_s) | 100% / 100% / 100% | 参数不可识别,边界承担全部工作 |
| R 漂移幅度 max/min | 1.000 | 恒定 = 无数据驱动更新 |
std(kf_innovation) | 0.179(日频段 0.114) | 理想 ≈1 → 标准化残差严重欠离散 = 模型失配 |
| 无界性证据 | nll −526.66(事前边界)→ −1356.81(放松边界) | 似然可继续下降 → MLE 退化 |
⇒ 局部线性趋势对价格序列的似然无上界(R→0 时模型可逐点跟踪观测),这是 ML4T §9.2 原文自述的 同一现象("the bound is the only thing standing between this filter and the identity function")。 已登记为 回测/回测审核清单_backtest_pitfalls 第 15 条。 合法修正方向(v2,须事前写好并与 v1 结果并列报告):固定 R 为归一化常数,只估信噪比 —— 此时 一步预测方差 ≥ R > 0,似然有下界、无界性消除(退化为角点最优)。
七、判定与结论
事前门槛(跑之前写死):样本外夏普改善 ≥ 0.10 且 ≥ 4/6 折同向 且 换手不劣化 > 20%。
| 臂 | 拼接样本外夏普 | Δ夏普 | 同向折数 | 换手变化 | 判定 |
|---|---|---|---|---|---|
| 基线 | 0.754 | — | — | — | — |
| V1(level/slope) | 0.790 | +0.037 | 4/6 | +3.2% | 未达门槛 |
| V2(全 4 特征) | 0.778 | +0.025 | 4/6 | −68.5% | 未达门槛 |
结论:【无改善】→ 不引入(不接入 scripts/、不改 portfolio.py、不产生 order/signal)。 V2 的表面改善来自少交易/不交易(fold_5 交易 0 次),不是更好的择时 → 更不构成引入理由。 ⚠ 复核后追加诊断(报告 §七·补,未改码):V2 的 uncertainty 闸设计有误(拿近似常量比扩张 90% 分位 → 每折稳定闸掉 ~76% 的 bar),innovation 闸因残差退化永不触发 ⇒ V2 未被公平检验, 其折级 Δ 不构成对"全 4 特征"的否定;修复留待下一轮。
八、红线与复现
回测/kalman/不 importscripts/;只读数据/、不写数据/;不产生 order/signal。- 未修改
scripts/、回测/engine_v1/、回测/engine_multi/任何既有文件(只调用)。 - 未安装任何依赖;
pysystemtrade只读禁装;未动框架/数据口径定义.md、归档区、已发布复盘、predictions.jsonl。 - 回滚点:
/var/minis/shared/backup/DUCKERRY_pre_kalman_20261001/(pre/+post/+md5_before.txt+md5_after.txt+REVERT.md)。 - 复现:
python3 kalman_selftest.py --quick→python3 kalman_embed.py --preflight
→ --build-kf(可多次续跑)→ --fold K --arm base|v1|v2(逐折短进程串行) → --emit-curves → python3 kalman_report.py。