跳转到内容

状态机与马尔可夫转移检验

从这一章开始进入第三编。第一编给了你研究的底盘,第二编把底盘跑在六类经典策略上;第三编要讲的,是当代实证量化研究里最常用的几件「高阶武器」——它们的数学仍然不超过这本教程的约定(高中数学 + 一点点对数),但它们回答的问题,比「这个策略赚不赚钱」再深一层。

第一站是一个前面所有章节都在偷偷回避的假设。

第 2 章讲 t 检验时说过,它假设观测值相互独立、服从同一分布(独立同分布,IID)。回测的每一天被当成一次独立的抽签。但市场里大量现象根本不是「每天的收益」能描述的,而是以状态和事件呈现的:

  • 某只股票平时缩量横盘(平静),某天突然放量暴涨(脉冲),随后连续几天巨震;
  • 某个题材平时无人问津,一旦点火,连板、分歧、退潮,自成一段生命周期;
  • 恐慌与贪婪不是均匀撒在每一天上的,而是扎堆出现。

这些问题共同的形态是:把连续的行情压缩成一个离散的标签,然后问标签之间的接续有没有规律。这就是状态机视角。

以「游资爆炒」为例,把它改写成第 3 章式的可检验假设。先定义两个状态,每个交易日收盘后判定一次:

St={S1(脉冲),当日涨幅>5% 且成交量>3 倍 20 日均量S0(平静),其他情况S_t = \begin{cases} S_1 \text{(脉冲)}, & \text{当日涨幅} > 5\% \ \text{且成交量} > 3 \text{ 倍 20 日均量} \\ S_0 \text{(平静)}, & \text{其他情况} \end{cases}

「爆炒有记忆吗」这句玄学,现在可以改写成一句能问数据的话:今天处于脉冲状态,会改变明天继续脉冲的概率吗? 要回答它,需要转移概率矩阵。

设 nijn_{ij} 为序列中「昨天是状态 ii、今天是状态 jj」出现的次数,转移概率的定义就是条件概率的频数版本:

Pij=P(St=j∣St−1=i)=nij∑jnijP_{ij} = P(S_t = j \mid S_{t-1} = i) = \frac{n_{ij}}{\sum_j n_{ij}}

人话解释:P11P_{11} 是「脉冲日之后仍是脉冲日」的频率——把所有脉冲日找出来,看它们的第二天有几个还是脉冲日。P01P_{01} 是「平静日之后升起飞」的频率。四个数 P00,P01,P10,P11P_{00}, P_{01}, P_{10}, P_{11} 排成 2×2 的表,就是转移概率矩阵。

手算一遍:某只股票 20 个交易日的状态序列(0=平静,1=脉冲):

0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 00,\ 0,\ 1,\ 0,\ 0,\ 0,\ 1,\ 1,\ 0,\ 0,\ 1,\ 0,\ 0,\ 0,\ 0,\ 1,\ 0,\ 0,\ 0,\ 0

19 次相邻转移,数频数:n00=10n_{00} = 10(如第 1→2、4→5、5→6 天……),n01=4n_{01} = 4(第 2→3、6→7、10→11、15→16 天),n10=4n_{10} = 4(第 3→4、8→9、11→12、16→17 天),n11=1n_{11} = 1(第 7→8 天)。于是:

P11=14+1=0.20,P01=1010+4=414≈0.286P_{11} = \frac{1}{4+1} = 0.20, \qquad P_{01} = \frac{10}{10+4} = \frac{4}{14} \approx 0.286

解读分三种情形,对照着看:

  • 正惯性:P11>P01P_{11} > P_{01}——脉冲日会「自我延续」,今天爆了,明天再爆的概率高于平时起飞的概率。趋势、题材发酵期是这种形态。
  • 反持续:P11<P01P_{11} < P_{01}——脉冲之后反而更难脉冲,像有一段强制的冷却期。涨跌停制度、监管问询、获利盘出逃都可能造成这种形态。
  • 无记忆:P11≈P01P_{11} \approx P_{01}——今天处于什么状态,对明天毫无影响,状态的出现像抛硬币。

这 20 天的手算结果是 P11=0.20<P01=0.286P_{11} = 0.20 < P_{01} = 0.286,看起来是反持续。但先别下结论:20 天只有 19 次转移、5 个脉冲日,P11P_{11} 的分母只有 5——它换个样本就能从 0.20 跳到 0.60。这正是第 2 章的老问题换了件衣服:小样本的频率不是概率。手算给了你直觉,结论必须交给更长样本和正式检验。

上面的框架默认了一个强假设:明天的状态只依赖今天(一阶马尔可夫性质):

P(St+1∣St,St−1,…)=P(St+1∣St)P(S_{t+1} \mid S_t, S_{t-1}, \ldots) = P(S_{t+1} \mid S_t)

人话解释:市场是条金鱼,只记得昨天。如果真相是「连着两天脉冲才算真发酵」,那么一阶矩阵就把两天的信息压成了一天,检验会失真。验证方法不复杂:把状态改成「昨天和前天的组合」(00、01、10、11 四个新状态),看四格的转移概率是否真的等于一阶预测——如果差别明显,说明记忆长度超过一天,需要更高阶的状态定义。

还有一条更隐蔽的坑:状态本身是研究自己定义的。「涨幅 > 5% 且量 > 3 倍」换成 7% 和 2 倍,频数矩阵就变了。定义必须在检验之前写死(第 3 章的可检验三要素、第 6 章的数据挖掘纪律,在这里同样生效)。

正式检验:惯性差的置换零分布

Section titled “正式检验:惯性差的置换零分布”

把比较写成单个统计量更方便检验——惯性差:

D=P11−P01D = P_{11} - P_{01}

D>0D > 0 正惯性,D≈0D \approx 0 无记忆,D<0D < 0 反持续。上面 20 天手算的 D=0.20−0.286=−0.086D = 0.20 - 0.286 = -0.086。

现在面对的问题是:拿一段 500 日的真实序列算出 D=0.39D = 0.39,这算大还是小?多小的 DD 可以当作噪声放过?t 检验帮不上忙(DD 的分布没人知道),这里要用第 17 章的主角提前登场一小段——置换检验的思路:

零假设是「状态的出现与时间顺序无关」(无记忆)。如果它为真,那么把状态序列随机打乱——脉冲日的个数一个不变、只是位置乱放——重新算出的 D′D' 应该和实测 DD 没有区别。打乱几百次,得到 DD 在零假设下的分布,看实测值排在第几位。

本章的演示数据是 500 个交易日的合成序列(状态机与转移检验演示:500 日两状态马尔可夫链(合成),真实过程带正惯性 P(1→1)=0.45、P(0→1)=0.06(平稳分布 P(S1)≈0.098)。states 为逐日状态(0=平静 1=脉冲);p11/p01 为实测转移概率;diff 为惯性差 D = P(S1→S1) − P(S0→S1);nullDiffs 为 400 次随机打乱状态序列(脉冲日个数不变、销毁时间结构)重算的 D;pValue = (零分布中 D ≥ 实测 D 的次数 + 1) / (401))。先看图确认直觉:

脉冲日明显成串出现——这正是正惯性的视觉形态;如果无记忆,红点会像撒芝麻一样均匀散在时间轴上。量化它:实测 n11=c[′11′]n_{11} = {c['11']}、n10=c[′10′]n_{10} = {c['10']}、n01=c[′01′]n_{01} = {c['01']}、n00=c[′00′]n_{00} = {c['00']}(从脉冲出发共 43 次、从平静出发共 456 次转移),代入公式得 P11=markov.p11.toFixed(3)P_{11} = {markov.p11.toFixed(3)}、P01=markov.p01.toFixed(3)P_{01} = {markov.p01.toFixed(3)}、D=markov.diff.toFixed(3)D = {markov.diff.toFixed(3)}。

再把序列随机打乱 400 次、每次重算 DD,得到零分布:

实测 DD(红线)远在零分布的右侧,p=markov.pValue.toFixed(3)p = {markov.pValue.toFixed(3)}——这段序列的惯性是真的,不是抛硬币抛出来的。注意零分布的中心几乎在 0:打乱保持了脉冲日个数不变,所以「一年有多少个脉冲日」这个信息被完整保留,零世界和真实世界唯一的区别是时间结构。这正是置换检验的精髓:平行世界必须和真实世界「同样公平」,只销毁你想检验的那一件事。

输入:状态序列 S[1..n](每个元素 ∈ {S0, S1})、置换次数 K = 400
输出:转移概率 P11、P01,惯性差 D,及其置换 p 值
第一步:数转移频数
对 i 从 1 到 n-1:
cnt[S[i], S[i+1]] += 1
P11 = cnt[1,1] / (cnt[1,0] + cnt[1,1])
P01 = cnt[0,1] / (cnt[0,0] + cnt[0,1])
D = P11 - P01
第二步:构造零分布
重复 K 次:
S' ← 把 S 随机打乱(脉冲日个数不变,只打乱时间位置)
按第一步的公式重算 D',记录
p 值 = (零分布中 D' ≥ D 的次数 + 1) / (K + 1)
第三步:判读
p ≤ 0.05 且 D > 0:正惯性(状态自我延续)
p ≤ 0.05 且 D < 0:反持续(状态后有冷却期)
p > 0.05:看不出记忆,按无记忆处理

+1 出现在分子分母各一次,是为了不让 p 值等于 0(「400 次都没超过」和「400 次里超过 0 次」的区别,保守写法多记一次)。

  1. 状态定义是挑出来的。阈值 5%、3 倍试了一打组合,挑出「结论最漂亮」的那组再报告——这是第 6 章数据挖掘 bias 的标准形态,只是把调参对象从均线窗口换成了状态阈值。定义先写死,检验后不改口。
  2. 脉冲日太稀。脉冲状态的平稳占比常常不到 10%,一年 250 个交易日只有二十来个样本,P11P_{11} 的分母是个位数,摇摆剧烈。样本不够就诚实地承认「检验不出来」,而不是把 0.45 报成 0.6。
  3. 非平稳。前半段有惯性、后半段没有(市场结构变了,比如涨跌停规则调整、监管周期),全样本一平均会得到一个具有误导性的「中等惯性」。分段各算一次,看结论是否稳定。
  4. 把相关当因果。P11P_{11} 高不等于「今天的脉冲导致明天的脉冲」——可能是同一个外部原因(题材发酵期、流动性泛滥)在背后同时驱动两天。转移矩阵只回答「有没有结构」,不回答「为什么」。

状态不只能刻画一只股票的爆炒,也能刻画整个市场的体温。下一章把状态机升格为主开关:市场处于什么环境、策略要不要开门营业——这一步做对了,第 5 章里最顽固的那个指标(最大回撤)会发生质变。