跳转到内容

假设与信号

炒股群里每天都有这样的话:「最近小盘股涨得好」「放量突破的股票能追」「跌多了总会反弹」。这些话听起来都像规律,但它们有一个共同的问题:没办法被证明是错的。涨得好是多好?最近是多近?跌多少算多?每句话都留了足够大的模糊空间,事后无论行情怎么走,说的人都能圆回来。这样的「规律」不可能转化成策略——你连它什么时候算对、什么时候算错都说不清,更谈不上回测。

量化研究的第一步,就是把这种模糊的想法改写成可检验的假设:一句话,拿去问数据,数据能给出「是」或「否」的答案。这一章做两件事:怎么改写,以及改写完之后怎么把规则落成计算机能执行的信号。

模糊想法 → 可检验假设:三个改写示例

Section titled “模糊想法 → 可检验假设:三个改写示例”

改写的方法就是不停地追问「具体是什么意思」,问到没有歧义为止:

模糊想法追问可检验假设
小盘股涨得好多小算小盘?跟谁比算好?多长时间?每月末按总市值排序,持有全市场市值最小 20% 的股票,未来一个月它们的平均收益显著高于其余 80% 的股票
放量突破能追放多大量?突破什么?追了之后拿多久?当日成交量大于 20 日均量的 2 倍且收盘价创 60 日新高时,次日开盘买入、持有 5 天,平均收益为正且胜率超过 55%
跌多了会反弹跌多少算多?反弹多少算反弹?个股连续 5 日累计跌幅超过 15% 后,未来 3 个交易日的平均收益为正

右边这一列的每句话,拿去问历史数据,都能得到一个确定的答案。这就是可检验的含义。

注意改写后的假设都变「丑」了——加了数字、加了期限、加了比较对象。模糊的想法听起来永远正确,具体的假设随时可能被打脸。但能被打脸才是价值所在:一个不可能错的命题,也不可能帮你赚钱。

回头看上面的改写,每一句可检验的假设都含齐了三个要素,缺一个就退化回模糊想法:

  1. 明确的标的池:作用在哪些股票上。「全市场市值最小 20%」「当日创 60 日新高的股票」——池子怎么圈,必须写成机械的规则,不能是「我感觉不错的那些」。注意标的池本身也是数据问题:用今天的成分股名单回测过去,就掉进了第 1 章讲过的幸存者偏差。
  2. 明确的规则:什么时候进、什么时候出、仓位多少,每一步都能写成「如果……就……」的形式,交给没有感情的程序执行,结果和人脑执行一模一样。规则里出现「走势健康就拿着」这种词,改写就没完成。
  3. 明确的成功标准:什么叫「这个假设成立」。「未来一个月平均收益显著高于其余股票」——这里的「显著」就是第 2 章讲的假设检验:先算收益差,再看 p 值。标准必须在动手回测之前写死,否则就成了先射箭再画靶。

三要素齐备的假设,回测跑出来只有两个结局:达标,假设暂时成立;不达标,假设被证伪,丢掉换一个。两种结局都是进展。

规则是给人读的,信号是给机器用的。中间的转换是本章的核心动作:把规则改写成「对每个交易日 t,输出一个信号」。

以本教程后面要反复使用的双均线策略为例。先定义均线:

MAN(t)=1N∑i=0N−1Pt−i\text{MA}_N(t) = \frac{1}{N}\sum_{i=0}^{N-1} P_{t-i}

人话解释:MAN(t)\text{MA}_N(t) 就是截至第 tt 天、最近 NN 天收盘价的平均值,∑\sum 是求和记号(第 2 章见过)。N=5N = 5 是「最近一周的平均成本」,N=20N = 20 是「最近一个月的平均成本」。短期均线灵敏,长期均线迟钝;快线从下方穿过慢线,说明最近一周的平均持仓成本开始高于最近一个月的——短期情绪转强,这就是金叉;反过来是死叉。

手算一遍:某只股票最近 5 天的收盘价是 10.0、10.2、10.1、10.4、10.6 元:

MA5=10.0+10.2+10.1+10.4+10.65=51.35=10.26 元\text{MA}_5 = \frac{10.0 + 10.2 + 10.1 + 10.4 + 10.6}{5} = \frac{51.3}{5} = 10.26 \text{ 元}

MA20\text{MA}_{20} 同理,只是窗口拉长到 20 天。现在假设昨天 MA5=10.20\text{MA}_5 = 10.20、MA20=10.30\text{MA}_{20} = 10.30(快线在下),今天收盘后重算得 MA5=10.35\text{MA}_5 = 10.35、MA20=10.30\text{MA}_{20} = 10.30(快线翻到上面)——快慢线的上下关系发生了翻转,金叉成立。

有了均线,双均线规则就能写成一个分段函数——这就是信号:

st={买入,MA5(t−1)≤MA20(t−1) 且 MA5(t)>MA20(t)(金叉)卖出,MA5(t−1)≥MA20(t−1) 且 MA5(t)<MA20(t)(死叉)持有,其他情况s_t = \begin{cases} \text{买入}, & \text{MA}_5(t-1) \leq \text{MA}_{20}(t-1) \ \text{且}\ \text{MA}_5(t) > \text{MA}_{20}(t) \quad(\text{金叉}) \\ \text{卖出}, & \text{MA}_5(t-1) \geq \text{MA}_{20}(t-1) \ \text{且}\ \text{MA}_5(t) < \text{MA}_{20}(t) \quad(\text{死叉}) \\ \text{持有}, & \text{其他情况} \end{cases}

人话解释:每个交易日收盘后算一次,结果只有三种——昨天快线还在慢线下面、今天翻上去了,输出「买入」;昨天在上面、今天掉下去了,输出「卖出」;没发生穿越,输出「持有」(维持现状,手上有就拿着,没有就空仓等着)。sts_t 是第 tt 天的信号,下标 tt 强调它只能用第 tt 天收盘及之前的数据。

一个必须强调的细节:sts_t 用到了第 tt 天的收盘价,而收盘价 15:00 才确定,所以「买入」实际只能发生在第 t+1t+1 天。回测里如果让信号在当天收盘价成交,就犯了第 1 章讲的未来函数——用了成交那一刻才知道的价格来决定是否成交。这个「信号当日、成交次日」的错位,下一章回测引擎会严格遵守。

伪代码:由价格序列生成双均线信号

Section titled “伪代码:由价格序列生成双均线信号”

完整流程,输入一串复权收盘价,输出每天的信号。这份信号定义是下一章回测的输入——回测引擎只负责按信号模拟买卖,信号怎么来的,就以下面这段为准:

输入:复权收盘价序列 C[1..n](共 n 个交易日)
参数:短窗口 SHORT = 5,长窗口 LONG = 20
输出:信号序列 s[1..n],每个元素 ∈ {买入, 持有, 卖出}
第一步:初始化
对所有 t:s[t] = 持有 // 默认不动
position = 空仓 // 当前是否持有头寸
第二步:计算均线
对 t 从 LONG 到 n: // 前 LONG-1 天数据不够,均线不存在
MA5[t] = (C[t-4] + C[t-3] + ... + C[t]) / 5
MA20[t] = (C[t-19] + C[t-18] + ... + C[t]) / 20
第三步:识别交叉,生成信号
对 t 从 LONG 到 n:
若 position 为 空仓
且 MA5[t-1] <= MA20[t-1] 且 MA5[t] > MA20[t]:
s[t] = 买入 // 金叉,次日开盘建仓
position = 持仓
若 position 为 持仓
且 MA5[t-1] >= MA20[t-1] 且 MA5[t] < MA20[t]:
s[t] = 卖出 // 死叉,次日开盘清仓
position = 空仓
第四步:健全性检查
统计 买入 和 卖出 信号的次数,两者之差只能是 0 或 1
(不可能卖一个不存在的仓位,也不可能买两次)
确认每个 s[t] 的计算只用到 C[1..t],不含 t 之后的数据

第三步的 position 状态是关键:没有它,连续两天满足「快线在上」会重复发买入信号;有了它,信号严格地在「买入 → 持有 → 卖出 → 持有 → 买入……」之间交替。第四步的两条检查分别防逻辑错误和未来函数,每次改完信号代码都应该跑一遍。

把这段伪代码跑在本教程的合成股价(合成股价(GBM+漂移切换),250 个交易日)上,结果如下图:灰线是收盘价,浅绿色背景是 position = 持仓 的区间——每段绿色从金叉日开始、到死叉日结束:

一年里一共发出 6 次买入信号,对应 6 段绿色区间。比如第一段从 2025-03-27(金叉)到 2025-03-31(死叉),只持有 3 个交易日就被洗出来了——双均线是趋势策略,震荡市里会频繁地假突破、假跌破,这是它的固有成本。最长的绿色区间从 2025-04-04 到 2025-05-20,持有 33 个交易日,完整吃下了一段趋势。这张图只回答「信号长什么样」,不回答「信号赚不赚钱」——后者要扣除成本、逐日撮合,那是下一章回测方法的工作。

  1. 先有结论再找数据。心里已经认定「小盘股就是好」,然后翻数据专门挑支持自己的时间段和口径——这不是检验,是找证据。它是第 6 章要讲的数据挖掘的前奏:在足够大的数据里翻得足够久,什么结论都能找到「证据」。防御方法只有一个:假设、标的池、规则、成功标准,全部在碰数据之前写下来,回测只负责判对错,不负责改题目。
  2. 规则里藏着主观词。「放量突破、走势健康就追」——「健康」两个字让整条规则无法执行,因为十个人执行出十个结果。检验方法:把规则念给一个不懂股票的人听,他照着做能否和你做出一模一样的决策?不能,就继续改写。
  3. 信号里混进未来数据。信号用当天收盘价、假设当天收盘价成交;或者用全样本的均值做标准化再生成信号——都是第 1 章的未来函数换了件马甲。回到定义检查:sts_t 的每一项,下标都不能大于 tt。
  4. 成功标准事后改口。回测不达标,就把「平均收益显著为正」降格成「至少跑赢了大盘」,再不行改成「最大回撤比大盘小」。标准一松,什么策略都能「成功」。标准要和假设一起预先写死,不达标就是证伪。

到这里,一个模糊的想法已经变成了:一句三要素齐备的假设、一个分段函数 sts_t、一段能跑出买入卖出序列的伪代码。下一章给它配上回测引擎:按信号模拟逐日成交,把「信号长什么样」变成「这个策略到底赚不赚钱、赚的是不是运气」。