假设与信号
炒股群里每天都有这样的话:「最近小盘股涨得好」「放量突破的股票能追」「跌多了总会反弹」。这些话听起来都像规律,但它们有一个共同的问题:没办法被证明是错的。涨得好是多好?最近是多近?跌多少算多?每句话都留了足够大的模糊空间,事后无论行情怎么走,说的人都能圆回来。这样的「规律」不可能转化成策略——你连它什么时候算对、什么时候算错都说不清,更谈不上回测。
量化研究的第一步,就是把这种模糊的想法改写成可检验的假设:一句话,拿去问数据,数据能给出「是」或「否」的答案。这一章做两件事:怎么改写,以及改写完之后怎么把规则落成计算机能执行的信号。
模糊想法 → 可检验假设:三个改写示例
Section titled “模糊想法 → 可检验假设:三个改写示例”改写的方法就是不停地追问「具体是什么意思」,问到没有歧义为止:
| 模糊想法 | 追问 | 可检验假设 |
|---|---|---|
| 小盘股涨得好 | 多小算小盘?跟谁比算好?多长时间? | 每月末按总市值排序,持有全市场市值最小 20% 的股票,未来一个月它们的平均收益显著高于其余 80% 的股票 |
| 放量突破能追 | 放多大量?突破什么?追了之后拿多久? | 当日成交量大于 20 日均量的 2 倍且收盘价创 60 日新高时,次日开盘买入、持有 5 天,平均收益为正且胜率超过 55% |
| 跌多了会反弹 | 跌多少算多?反弹多少算反弹? | 个股连续 5 日累计跌幅超过 15% 后,未来 3 个交易日的平均收益为正 |
右边这一列的每句话,拿去问历史数据,都能得到一个确定的答案。这就是可检验的含义。
注意改写后的假设都变「丑」了——加了数字、加了期限、加了比较对象。模糊的想法听起来永远正确,具体的假设随时可能被打脸。但能被打脸才是价值所在:一个不可能错的命题,也不可能帮你赚钱。
可检验三要素
Section titled “可检验三要素”回头看上面的改写,每一句可检验的假设都含齐了三个要素,缺一个就退化回模糊想法:
- 明确的标的池:作用在哪些股票上。「全市场市值最小 20%」「当日创 60 日新高的股票」——池子怎么圈,必须写成机械的规则,不能是「我感觉不错的那些」。注意标的池本身也是数据问题:用今天的成分股名单回测过去,就掉进了第 1 章讲过的幸存者偏差。
- 明确的规则:什么时候进、什么时候出、仓位多少,每一步都能写成「如果……就……」的形式,交给没有感情的程序执行,结果和人脑执行一模一样。规则里出现「走势健康就拿着」这种词,改写就没完成。
- 明确的成功标准:什么叫「这个假设成立」。「未来一个月平均收益显著高于其余股票」——这里的「显著」就是第 2 章讲的假设检验:先算收益差,再看 p 值。标准必须在动手回测之前写死,否则就成了先射箭再画靶。
三要素齐备的假设,回测跑出来只有两个结局:达标,假设暂时成立;不达标,假设被证伪,丢掉换一个。两种结局都是进展。
从规则到信号:s_t
Section titled “从规则到信号:s_t”规则是给人读的,信号是给机器用的。中间的转换是本章的核心动作:把规则改写成「对每个交易日 t,输出一个信号」。
以本教程后面要反复使用的双均线策略为例。先定义均线:
人话解释: 就是截至第 天、最近 天收盘价的平均值, 是求和记号(第 2 章见过)。 是「最近一周的平均成本」, 是「最近一个月的平均成本」。短期均线灵敏,长期均线迟钝;快线从下方穿过慢线,说明最近一周的平均持仓成本开始高于最近一个月的——短期情绪转强,这就是金叉;反过来是死叉。
手算一遍:某只股票最近 5 天的收盘价是 10.0、10.2、10.1、10.4、10.6 元:
同理,只是窗口拉长到 20 天。现在假设昨天 、(快线在下),今天收盘后重算得 、(快线翻到上面)——快慢线的上下关系发生了翻转,金叉成立。
有了均线,双均线规则就能写成一个分段函数——这就是信号:
人话解释:每个交易日收盘后算一次,结果只有三种——昨天快线还在慢线下面、今天翻上去了,输出「买入」;昨天在上面、今天掉下去了,输出「卖出」;没发生穿越,输出「持有」(维持现状,手上有就拿着,没有就空仓等着)。 是第 天的信号,下标 强调它只能用第 天收盘及之前的数据。
一个必须强调的细节: 用到了第 天的收盘价,而收盘价 15:00 才确定,所以「买入」实际只能发生在第 天。回测里如果让信号在当天收盘价成交,就犯了第 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 状态是关键:没有它,连续两天满足「快线在上」会重复发买入信号;有了它,信号严格地在「买入 → 持有 → 卖出 → 持有 → 买入……」之间交替。第四步的两条检查分别防逻辑错误和未来函数,每次改完信号代码都应该跑一遍。
图:价格与多头区间
Section titled “图:价格与多头区间”把这段伪代码跑在本教程的合成股价(合成股价(GBM+漂移切换),250 个交易日)上,结果如下图:灰线是收盘价,浅绿色背景是 position = 持仓 的区间——每段绿色从金叉日开始、到死叉日结束:
一年里一共发出 6 次买入信号,对应 6 段绿色区间。比如第一段从 2025-03-27(金叉)到 2025-03-31(死叉),只持有 3 个交易日就被洗出来了——双均线是趋势策略,震荡市里会频繁地假突破、假跌破,这是它的固有成本。最长的绿色区间从 2025-04-04 到 2025-05-20,持有 33 个交易日,完整吃下了一段趋势。这张图只回答「信号长什么样」,不回答「信号赚不赚钱」——后者要扣除成本、逐日撮合,那是下一章回测方法的工作。
- 先有结论再找数据。心里已经认定「小盘股就是好」,然后翻数据专门挑支持自己的时间段和口径——这不是检验,是找证据。它是第 6 章要讲的数据挖掘的前奏:在足够大的数据里翻得足够久,什么结论都能找到「证据」。防御方法只有一个:假设、标的池、规则、成功标准,全部在碰数据之前写下来,回测只负责判对错,不负责改题目。
- 规则里藏着主观词。「放量突破、走势健康就追」——「健康」两个字让整条规则无法执行,因为十个人执行出十个结果。检验方法:把规则念给一个不懂股票的人听,他照着做能否和你做出一模一样的决策?不能,就继续改写。
- 信号里混进未来数据。信号用当天收盘价、假设当天收盘价成交;或者用全样本的均值做标准化再生成信号——都是第 1 章的未来函数换了件马甲。回到定义检查: 的每一项,下标都不能大于 。
- 成功标准事后改口。回测不达标,就把「平均收益显著为正」降格成「至少跑赢了大盘」,再不行改成「最大回撤比大盘小」。标准一松,什么策略都能「成功」。标准要和假设一起预先写死,不达标就是证伪。
到这里,一个模糊的想法已经变成了:一句三要素齐备的假设、一个分段函数 、一段能跑出买入卖出序列的伪代码。下一章给它配上回测引擎:按信号模拟逐日成交,把「信号长什么样」变成「这个策略到底赚不赚钱、赚的是不是运气」。