缘起
2025年底读到一篇论文,讲的是将图像分解为"语义token"和"细节token"进行分治建模。我觉得这个思路可以迁移到金融时间序列上:
价格运动可以被分解为两个物理过程——
- 趋势延续(Trend Continuation):价格沿着已形成的趋势惯性运行,这应该是一个低频、近乎确定性的过程。
- 均值回归(Mean Reversion):价格因短期过度偏离而出现向长期均值回归的倾向,这是一个高频、类似弹性恢复的过程。
传统的端到端模型试图同时逼近两个不同物理过程的复合。如果能拆开分别建模再组合,理论上应该更高效。
于是有了因子化世界模型(Factorized World Model, FWM)。
架构设计
状态分解
从原始 OHLCV 数据中提取趋势状态序列和回归残差序列:
HP滤波:用 Hodrick-Prescott 滤波从收盘价中提取一条平滑的趋势线 \(\tau_t\)。参数 \(\lambda = 5000\) 控制了趋势的光滑程度。
趋势Token:计算趋势线的滑动斜率,做 z-score 标准化后离散化为 5 类 Token——强下降、弱下降、横盘、弱上升、强上升。阈值用 \(\pm 0.3\sigma\) 和 \(\pm 1.0\sigma\)。
残差特征:定义残差 \(r_t = p_t - \tau_t\),提取 8 维特征向量:标准化残差、RSI(14)、布林带 %B 位置、ATR 比率、成交量比率、K线振幅比率、斜率 z-score、EMA20 偏离比率。
均值回归标签:当标准化残差超过 \(2\sigma\) 且未来 \(K\) 根 K 线内价格向趋势线方向回归超过一定阈值,标记为回归触发点。注意这是一个前视标签,仅用于训练。
模型
1 | 输入: 趋势Token + 8维残差特征 |
决策映射
将趋势概率分布与 MR 概率结合生成交易信号:
| 最大概率趋势 | MR < 30% | MR > 70% |
|---|---|---|
| 上升 | 做多 (+1) | 平仓 (0) |
| 横盘 | 观望 (0) | 观望 (0) |
| 下降 | 做空 (-1) | 平仓 (0) |
趋势概率分布的熵用作不确定性度量,熵过高时强制平仓。
实现
代码约 900 行,包括:
factorized_world_model.py— 核心模型:StateDecomposer、FactorizedEncoder、TemporalWorldModel(Transformer)、DecisionInterpretertrain_factorized_world_model.py— CLI 驱动的完整流水线:数据加载 → 状态分解 → 训练 → 回测 → 报告
训练过程中修了三个 Bug:
- HP滤波内存溢出:\(48,000 \times 48,000\) 的稠密矩阵需要 17GB,改用 scipy.sparse 五对角矩阵
- 斜率z-score的O(T²)循环:
_compute_slope_z对每个时间步都重算所有历史斜率,全量数据需要 \(\sum_{t=24}^{47999} (t-12) \approx 1.15\times 10^9\) 次 polyfit,预计运行数小时。改为一次卷积预计算所有斜率 - 回测索引越界:最后一个 bar 没有下一个 K 线数据
修复后全量 48,000 根 K 线的状态分解从数小时降至 19.6 秒。
初试:单次分割的假象
第一次训练用标准的 70/15/15 时序分割,结果看起来非常漂亮:
1 | 趋势准确率: 96.0% |
当时差点就被骗了。"夏普 12" 在真实市场中不存在。文艺复兴大奖章基金的历史最佳也才 3-4 的水平。这个数字完美得不像真的——因为它就是假的。
问题:单次分割只测了最后 15% 的数据,而那段时间 BTC 处于单边下跌。模型只要无脑做空就能赚钱。MR 预警全程负贡献:
1 | 趋势跟随(无MR): +4579% |
在 BTC 1h 上,均值回归预警的所有触发都是错误的。
滚动训练:真相大白
构建了累积窗口的 walk-forward 训练模式,18 个窗口覆盖 2022-2026 全年:
1 | 窗口1: 训练 [0-10000] 测试 [10000-12000] → -37.7% |
18 个窗口,全部亏损,无一例外。
这不是运气问题,这是系统性问题。在上涨的窗口、下跌的窗口、震荡的窗口——模型都在稳定亏钱。
换成山寨币试试
也许只是 BTC 太有效了?我用同样的配置测了三个波动等级不同的山寨币:
| 币种 | 等级 | 累计收益 | 平均夏普 |
|---|---|---|---|
| SOLUSDT | 高波主流 | -100.0% | -2.28 |
| DOGEUSDT | 中高波meme | -100.0% | -4.07 |
| ZECUSDT | 中波老币 | -100.0% | -2.18 |
结果完全一致。全部 -100%,无一幸免。
独立实验的交叉验证
FWM 的失败不是孤立的。在之前的项目中:
- DeepSeek-LLM 交易回测:从 1h K 线挖掘 50+ 技术指标,通过 LLM 获取交易决策。方向预测准确率 44%,接近随机。
- XGBoost/随机森林:在 BTC 1h 上反复尝试,无法产生稳定正收益。
- LSTM 集成模型:三个模型的集成,同样没有正预期。
这些独立实验用完全不同的方法得出了一致结论:BTC 1h 的价格变化在统计意义上接近随机游走。
失败的根本原因
现在复盘,有两个致命的设计错误:
1. HP 滤波的前视偏差
StateDecomposer 对整个训练集做了一次性双向 HP 滤波:
1 | trend = spsolve(A, series) # 一次性求解整个序列 |
这意味着训练集里时间 \(t\) 的趋势是拿 \(t\) 以后的数据算出来的。模型学到的"规律"本质上包含了未来信息,到真没见过的新数据上自然崩溃。
这解释了为什么单次分割(70/15/15)的测试结果看起来那么漂亮——验证集和测试集的数据也被同样方式分解了,信息泄漏是一体的。
2. OHLCV 的单向预测信噪比太低
本质上我在做的是:用 \([t-47, t]\) 的 OHLCV 数据预测 \(t+1\) 时刻的方向。这个任务的信噪比在加密货币 1h 级别上极低。无论是浅层模型(XGBoost)、深层模型(LSTM)、还是大型模型(Transformer/LLM),都无法从中提取出能稳定盈利的信号。
教训
好的教训
- Walk-forward 测试是必需的:单次分割的夏普 12 是假象,滚动训练的 -99.9% 是真相。
- M 个独立实验得到同样的负结论,这个结论值得信任:LLM、XGBoost、LSTM、Transformer 都不行,那就是这条路行不通。
- 在开始之前先估计上限:如果 \(R^2\) 的天花板是 0.01,任何模型都不可能赚钱。
坏的教训
花了整整一天,写了 900 行代码,跑了几小时的 GPU 训练,得到了一个字:不行。
但其实这个结果的价值比一次"成功的回测"更大——它排除了一个看起来很有前景的方向,让精力可以集中在真正有效的事情上。
代码归档
FWM 项目代码留在仓库中,作为一次诚实的失败记录:
ml_models/factorized_world_model.pyml_models/train_factorized_world_model.pynotes/factorized_world_model_TECH.md
之前验证有效的策略(低波山寨币暴跌抄底,胜率 57.5%,夏普 4.01)继续实盘模拟。