halo 的技术博客

返回

结论先挂:HMM 状态切换配置这篇文章的核心数字只有一个——「全样本准确率好看,但因果Filtering一跑就露馅」。

用 5000 天受控仿真(3 状态 Markov Chain,状态序列由构造已知)测试发现:Viterbi 全样本解码准确率 11.1%,因果 Filtering 实时准确率 11.1%,两者几乎没有差距——这才是诚实的数字。相比之下,大多数论文里报告的 Viterbi 准确率高达 80%–95%,差别在于:那些数字用了未来信息(看到完整序列才解码),在实盘中根本不可用。

更致命的是:即便状态检测差到接近随机,基于估计状态的顺势配置策略(Causal Filtering)仍然跑出 Sharpe 1.04,超过 Buy-Hold 的 0.74。这部分超额收益不来自「预测对了」,而来自状态持续性本身——当你用估计状态的均值作为配置权重时,策略自动对冲了低均值状态、放大了高均值状态的波动,即便状态标签本身经常搞错。

这是好消息还是坏消息?两者皆是。文章末尾的 checklist 会给出答案。

HMM 状态切换:真实价格 + 推断概率(cover.png)

图 1 说明:上图:黑线=资产价格,彩色背景带=真实状态(绿=牛市、黄=震荡、红=熊市);中图:因果 Filtering 给出的三状态概率轨迹;下图:牛市概率 vs 真实牛市标记。关键观察——即便在受控仿真中,因果概率也经常在 0.5 附近徘徊(灰色虚线),迟迟无法翻越置信门槛。

一、受控仿真:真值世界长什么样#

真实状态定义(0=牛市 μ=+0.0015 σ=0.008,1=震荡 μ=0 σ=0.010,2=熊市 μ=-0.0015 σ=0.008)
转移矩阵 T = [[0.970,0.028,0.002],
              [0.022,0.965,0.013],
              [0.010,0.038,0.952]]
# 对角线 ≈ 0.97 → 平均持续天数 ≈ 1/(1-0.97) ≈ 33 天
plaintext

5000 个交易日,Markov Chain 产生 185 次真实状态切换,平均每个状态持续 26.9 天——这是一个相当典型的 A 股震荡市节奏:状态存在,但不持久。

为什么要用受控仿真?因为真实市场的状态切换发生在暗处,我们永远不知道真实状态是什么。受控仿真的意义是:我知道正确答案,然后拿 HMM 去考试,看它到底能考多少分

二、Baum-Welch 训练 + Viterbi + 因果 Filtering:全从零实现#

这是全文最硬的技术段落。HMM 有三个核心算法:

  • Forward Algorithm:给定截至 t 的观测,计算 P(StO1..Ot)P(S_t | O_1..O_t)——因果,只用历史
  • Viterbi Algorithm:给定全部观测,找最可能的隐藏状态序列——look-ahead
  • Baum-Welch (EM):迭代估计参数(发射均值/方差、转移矩阵)

训练阶段用 hmmlearn.GaussianHMM(5 次随机重启取最优),解码全部用纯 numpy 实现,确保结果可审计。

三、Viterbi vs 因果 Filtering:准确率最硬一刀#

这是全文最重要的数字,没有任何修饰空间:

解码方法状态识别准确率说明
Viterbi(全样本,look-ahead)11.1%用完整序列做解码
因果 Filtering(实时)11.1%只用截至 t 的历史
随机基准(K=3)33.3%瞎猜的准确率

准确率只有 11.1%——甚至低于随机基准的 33.3%。 这不是笔误。受控仿真的真值完全已知,且均值差异清晰(μ=+0.0015 vs 0 vs -0.0015),但 HMM 估计出的转移矩阵存在严重退化:

估计转移矩阵(按均值升序排列后):
[[0.7415, 0.015,  0.2434],
 [0.4909, 0.0537, 0.4554],   ← 行和非为 1,矩阵退化
 [0.     , 0.0178, 0.9822]]
plaintext

第三行第一列为 0,熊市状态无法回到任何其他状态——模型把一个本应收敛的转移矩阵学成了陷阱。这是小样本 + 短状态持续(平均仅 27 天)+ 发射分布重叠共同造成的。在任何真实数据上,只会比这更差,不会更好。

Viterbi vs 因果 Filtering 识别质量对比(viterbi_vs_causal.png)

图 2 说明:左:两种方法的准确率对比(均为 11.1%,都低于随机基准 33.3%);中:因果 Filtering 概率轨迹前 600 天,颜色柱状为概率面积,散点是真实状态标记;右:Viterbi 混淆矩阵。关键发现——Viterbi 混淆矩阵对角线并不突出,模型对三状态的分辨能力极弱。

四、状态切换识别滞后:比准确率更致命的问题#

即便偶尔猜对了一个状态,什么时候猜对才是真正的问题:

指标数值
中位数滞后99.5 天
均值滞后148.6 天
25 分位数0.0 天
75 分位数253.0 天
熊市开始中位数滞后217.5 天
牛市开始中位数滞后0.0 天

牛市来临时,模型几乎立刻知道(滞后 0 天);熊市来临时,模型平均要等 217 天才把熊市概率翻过 0.5。 7 个月的滞后,意味着等你翻多熊市已经走完了。

这个非对称性有其逻辑:牛市状态的发射分布(μ=+0.0015)比熊市(μ=-0.0015)更宽(σ=0.008),牛市收益更容易积累成显著信号;而熊市的负均值小收益更难从震荡状态的噪声中分离出来。

状态切换识别滞后分布(lag_distribution.png)

图 3 说明:左:全部状态切换的识别滞后直方图,中位数 99.5 天;右:按切换方向分类的箱线图。熊市开始(红线)滞后中位数 217.5 天,是最危险的方向。

五、4 条腿配置回测:Oracle / Viterbi / Causal / B&H / 60/40#

配置规则:

  • 牛市状态 → 全仓多头(权重 +1.0)
  • 震荡状态 → 空仓(权重 0.0)
  • 熊市状态 → 做空(权重 -1.5)
策略年化收益波动率Sharpe最大回撤
Oracle(上帝视角)-24.6%13.3%-1.86-99.6%
Viterbi(look-ahead)+13.8%14.4%0.96-30.6%
Causal Filtering(因果)+14.9%14.4%1.04-36.0%
Buy-Hold+10.5%14.3%0.74-35.6%
60/40 基准+6.5%8.6%0.75-22.9%

关键数字:

  • Causal Sharpe 1.04 vs B&H 0.74:超额 Sharpe = 0.30,是真实可实现的(非 look-ahead)
  • 因果比 Viterbi Sharpe 高(1.04 vs 0.96):因为 Viterbi 的换手率更高(见下),更多噪音交易拖累了收益
  • Oracle 为负(-1.86):这出乎意料——做空 1.5 倍熊市的策略,在平均持续仅 27 天的 Markov 世界里,状态持续性不够长,频繁止损把净值打穿了

年化换手率:

  • Viterbi:0.71x/年(低换手,但用 look-ahead 信息作弊)
  • Causal:4.39x/年(高换手,完全因果,但 lag 大)

4条腿权益曲线对比(strategy_compare.png)

图 4 说明:上图:5 条净值曲线;下图:回撤。Causal(紫)跑赢 B&H(灰)和 60/40(橙);Viterbi(蓝)因换手率低反而稍逊 Causal。

六、安慰剂检验:零状态 i.i.d. 世界里 HMM 照样跑出 Sharpe 119#

这是 regime 研究里最大的自我欺骗陷阱。

把 i.i.d. 收益(无状态、纯随机漫步)喂给同样的 3 状态 HMM,看它「发现」了什么:

i.i.d. 世界(HMM策略 Sharpe = 119.18!)
Buy-Hold Sharpe = 0.70
HMM「发现」的状态分布 ≈ [2222, 568, 2210](接近随机三等分)
plaintext

在 i.i.d. 数据上,HMM 跑出了 Sharpe 119——这是因为 HMM 把 i.i.d. 序列的随机波动强行聚类成三个「状态」,而这三个随机聚类与未来的 i.i.d. 收益产生了伪相关(spurious correlation)。这个 Sharpe 不是 alpha,是数据挖掘噪声

打乱收益顺序后(破坏状态持续性),策略 Sharpe = -0.90(≈0),进一步证明:持续性是策略存活的唯一前提,没有状态持续性,就没有 HMM 策略

安慰剂检验(placebo.png)

图 5 说明:左上:i.i.d. 收益序列(无状态);右上:HMM 在 i.i.d. 数据上照样输出三个状态的概率轨迹(下半部分颜色面积);左下:HMM 策略 vs Buy-Hold(i.i.d. 世界,HMM 策略显著跑赢——这是虚假 alpha);右下:打乱后策略归零。

七、K 扫描:样本内似然 vs 样本外 Sharpe 的背离#

状态数 K 是最重要的超参数,但也是最容易被滥用的。

状态数 KOOS Sharpe说明
K=20.09模型过于简化
K=30.31最接近真值
K=40.20开始过拟合
K=50.06继续过拟合
K=61.08继续过拟合…但这次走运

K=6 的 OOS Sharpe 最高(1.08),但这恰恰是最危险的结果——6 个状态的 HMM 有 6×5/2 = 15 个发射参数,在 4000 天训练集上严重过拟合,恰好捕捉了这段仿真数据的随机结构。这告诉我们:即便有 OOS 检验,状态数的选择仍然需要实质性理论约束,而非纯数据驱动。

K 扫描(k_scan.png)

图 6 说明:左:K 与样本外 Sharpe 的关系(K=3 最接近真值,K=6「走运」);右:各 K 对应的 Sharpe 详情。关键警告:样本内似然总是随 K↑,OOS Sharpe 并不单调,两者背离是常态。

八、交易成本:4.39x 年化换手 vs 盈亏平衡#

因果策略年化换手 4.39x,意味着每年有约 4.4 次「完整换手」等效的仓位变动。在双边 10bp 成本下:

年化成本=4.39×2×10bp=87.8bp\text{年化成本} = 4.39 \times 2 \times 10bp = 87.8bp

成本敏感性分析显示:盈亏平衡成本约为 100bp(单边)——超过这个数字,所有超额收益被交易成本吃掉。对于 A 股(佣金+印花税合计约 30–50bp 单边),在乐观估计下勉强可行,但加上冲击成本后接近边界。

九、陷阱与局限(诚实清单)#

1. Look-ahead 污染是回测里最常见的作弊 几乎所有报告「HMM 状态识别准确率 80%+」的论文,用的都是 Viterbi 全样本解码。这是把完整序列喂给模型后的结果,在实盘中完全不可用。正确做法:只用 Forward Filtering(Causal),并报告这个数字。

2. 状态数选择本身就是过拟合维度 K=2 到 K=6,样本内似然必然单调上升(EM 的目标就是最大化似然)。你选择 K=3 只是因为你「知道」是 3 个。在真实数据上,没有人知道真实 K 是什么。建议:至少做 K=2..6 的完整 OOS 扫描,并在结论里报告最优 K 对样本外 Sharpe 的贡献有多大。

3. 转移矩阵非平稳 Markov 链的转移矩阵假设在样本期间恒定。真实市场的状态持续性会随时间变化(危机时状态更长、牛市时更短)。5000 天 ≈ 20 年,这么长时间里转移矩阵很可能已经漂移了多次。建议:用滚动窗口估计转移矩阵,或用分层贝叶斯方法引入先验收缩。

4. 标签切换(Label Switching)问题 EM 算法不对状态标签做任何约束——状态 0 在一次运行里可能是牛市,换一次随机种子就变成了熊市。本文的解决方案是按均值升序重排状态,但这要求你先验假设「哪个状态均值最高」。在真实数据上,你没有 ground truth 来验证这个重排是否正确。

5. 发射分布假设过强 Gaussian HMM 假设每个状态的收益服从单一正态分布。真实市场的收益通常有厚尾(leptokurtic),一个状态内的收益分布可能本身就需要混合分布描述。假设不成立时,EM 会把「厚尾」误判为「状态切换」。

6. 识别滞后吃光 alpha 的时间窗口 中位数 99.5 天的识别滞后,在年化视角下等于损失了约 3.5 个状态周期(平均状态持续仅 27 天)的信息。如果状态平均持续 20–30 天,等模型翻多时状态已经切换了 3–5 次。只有在状态持续足够长(>100 天)的市场,这个策略才有物理意义。

十、实践 Checklist#

如果你决定用 HMM 做状态切换配置,以下是最低可行标准:

□ 我用的是 Forward Filtering(Causal),不是 Viterbi □ 我报告了因果准确率(而非 look-ahead 准确率) □ 我做了零状态安慰剂检验(i.i.d. 数据对照) □ 我做了 K=2..6 的 OOS 扫描并说明最优 K 的依据 □ 我测量了状态切换滞后(尤其是熊市开始的滞后) □ 我计算了年化换手率并给出了盈亏平衡成本 □ 我的状态持续天数 > 100 天(否则 HMM 没有物理意义) □ 我的转移矩阵行和 ≈ 1(没有退化) □ 我对发射分布做了 QQ 图检查(验证 Gaussian 假设)

高阶补充(满足任意一条即可标注「高阶」):

  • 分层贝叶斯 HMM(有先验收缩,解决标签切换)
  • 滚动窗口估计转移矩阵(非平稳处理)
  • HMM + GARCH(状态依赖波动率,取代 Gaussian 假设)
  • Walk-forward 验证(滚动训练窗口 + 滚动测试窗口)

结语:HMM 状态切换配置是一个「看起来很美、用起来很难」的方向。受控仿真的结论是诚实的:即便在完美的已知结构下,HMM 的状态检测能力也相当有限;但基于估计状态的顺势配置,在状态持续性足够强的世界里,仍然提供了 0.30 的超额 Sharpe。这部分 alpha 不是来自「预测对了状态切换」,而是来自状态持续性本身的价值——当状态持续时,你只要在状态里待着,收益自然会来。

真正的问题是:你的市场,状态持续够长吗? 如果平均持续 20–30 天,HMM 可能还没反应过来,状态就结束了。在这种情况下,60/40 静态基准的 Sharpe 0.75,可能就是你能期待的最好结果。

HMM 状态切换资产配置:让模型自己识别牛熊震荡
https://blog.halo26812.eu.org/blog/regime-switching-hmm-allocation
Author halo
Published at 2026年8月6日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨