框架

卡尔曼滤波特征(回测/kalman/)

噪声三参数 σ_obs²(R)、σ_level²(Q_l)、σ_slope²(Q_s)。

回测 · 资产·机制 · 回测/kalman/README.md · 8564 B

卡尔曼滤波特征(回测/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.pyV2 闸门机制诊断(只读):逐折统计两条闸各闸掉多少 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.shA/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 保持不变)是
V2V1 + 全 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/min1.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.0374/6+3.2%未达门槛
V2(全 4 特征)0.778+0.0254/6−68.5%未达门槛

结论:【无改善】→ 不引入(不接入 scripts/、不改 portfolio.py、不产生 order/signal)。 V2 的表面改善来自少交易/不交易(fold_5 交易 0 次),不是更好的择时 → 更不构成引入理由。 ⚠ 复核后追加诊断(报告 §七·补,未改码):V2 的 uncertainty 闸设计有误(拿近似常量比扩张 90% 分位 → 每折稳定闸掉 ~76% 的 bar),innovation 闸因残差退化永不触发 ⇒ V2 未被公平检验, 其折级 Δ 不构成对"全 4 特征"的否定;修复留待下一轮。

八、红线与复现

  • 回测/kalman/ 不 import scripts/;只读 数据/、不写 数据/;不产生 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。