9.1 回测最小模型
课前导读
本节目标
学完能解决什么困惑:回测为什么不是一张“历史赚钱截图”,而是一套从信号到资金曲线的实验流程。
适合基础:已经学完因子、选股名单、Python 与 pandas,知道 shift、收益率和交易成本的读者。
难度:intermediate
预计用时:约 40 分钟
你可以把回测想成一次“模拟考试”。
真正的模拟考试不是把答案抄在卷子上,再宣布自己满分。
它要按考试时间、题目顺序、可用资料和评分规则来做。
回测也是如此。
它不是把历史上已经涨过的股票圈出来,再截图说“如果当时买入就赚了”。
它要站在过去每一个交易日,只使用当时已经能看到的信息。
然后把信号、仓位、交易、成本和资金变化一步一步记录下来。
本节只搭最小模型。
不追求复杂系统。
先让你看清一件事:回测的核心是流程,而不是神奇按钮。
知识正文
一、先用“沙盘演练”理解回测
沙盘演练常用于训练应急决策。
桌面上摆着地图、队伍、补给和规则。
参演者不能说“如果我提前知道敌人从东边来,我就一定赢”。
他只能按当时可见的信息做决定。
交易回测也是一张投资沙盘。
历史行情是地图。
因子或技术条件是侦察信息。
仓位是你投入多少兵力。
交易规则是你怎么下达买卖指令。
交易成本是路上的油费、损耗和摩擦。
资金曲线则是整场演练结束后留下的路线图。
关键直觉
回测不是证明“过去能赚钱”,而是检查“如果严格按这套规则走,过去的资金路径会长什么样”。
二、最小回测的五个零件
最小回测不需要一开始就很大。
但它必须把五个零件接起来。
| 零件 | 通俗类比 | 在回测里回答的问题 |
|---|---|---|
| 信号 | 老师发出的做题提示 | 今天是否满足买入、持有或卖出条件 |
| 仓位 | 这道题投入多少时间 | 账户资金中有多少比例放在股票上 |
| 交易 | 真正写到答题卡上 | 从目标仓位变化成实际买卖动作 |
| 成本 | 考试扣分和路费 | 佣金、印花税、过户费、滑点等摩擦 |
| 资金曲线 | 模拟考试成绩轨迹 | 每个交易日结束后账户价值是多少 |
这五个零件缺一不可。
只有信号,没有仓位,无法知道买多少。
只有仓位,没有交易,无法知道何时从现金变成股票。
只有交易,不扣成本,结果往往过于乐观。
只有最后收益,没有资金曲线,看不到中途是否很难坚持。
三、流程图:从想法到资金曲线
下面这张图是最小模型的骨架。
它适合新手先背下来。
flowchart TD
A["准备历史数据"] --> B["生成交易信号"]
B --> C["把信号延后到可交易日期"]
C --> D["计算目标仓位"]
D --> E["形成买卖交易"]
E --> F["扣除成本和滑点"]
F --> G["更新每日资金曲线"]
G --> H["检查收益与风险"]注意中间的“延后到可交易日期”。
这是很多新手回测最容易出错的地方。
如果你用今天收盘价计算信号,就不能假设今天收盘前已经知道结果并完成买入。
在日线入门模型中,更稳妥的做法是:今天收盘后产生信号,下一交易日再执行。
这也是 shift(1) 在 pandas 回测里经常出现的原因。
四、A 股边界:信号不等于一定成交
A 股市场有一些现实规则。
这些规则会让“纸面信号”和“实际交易”之间出现距离。
第一,普通股票买入后通常不能当天卖出。
这就是你前面学过的 T+1。
如果模型周一买入,周一盘中看到不对也不能马上卖掉这笔新买入的股票。
第二,涨跌停会限制成交。
如果模型给出买入信号,但下一天股票一字涨停,可能买不到。
如果模型给出卖出信号,但下一天股票一字跌停,可能卖不出。
第三,停牌期间不能交易。
停牌不是价格不动这么简单。
它意味着你的交易动作需要等待复牌后才可能发生。
第四,滑点会让成交价偏离你想象中的价格。
例如你以为可以按 10.00 元买入,实际成交可能是 10.03 元。
这 0.03 元看起来很小,但频繁交易时会不断累积。
风险提醒
回测里如果默认所有信号都能按理想价格成交,就会把 A 股的 T+1、涨跌停、停牌和滑点边界全部忽略,结果容易虚高。
五、一个最小 A 股案例
假设你研究一只主板股票。
你先不关心公司名称。
只用收盘价和 5 日均线做练习。
规则非常简单。
当收盘价站上 5 日均线时,次日持有 100% 仓位。
当收盘价跌破 5 日均线时,次日空仓。
这个例子不是推荐策略。
它只是用来练流程。
你需要问五个问题。
第一个问题:信号何时出现?
第二个问题:信号何时可以用于交易?
第三个问题:目标仓位是多少?
第四个问题:为了到达目标仓位,需要买入还是卖出?
第五个问题:扣完成本后,资金曲线如何变化?
六、pandas 最小示例
下面这段代码只演示思路。
它不依赖外部数据。
你可以把它当成一张小型练习表。
import pandas as pd
prices = pd.DataFrame(
{
"close": [10.00, 10.20, 10.10, 10.50, 10.80, 10.40, 10.60],
},
index=pd.to_datetime(
[
"2024-01-02",
"2024-01-03",
"2024-01-04",
"2024-01-05",
"2024-01-08",
"2024-01-09",
"2024-01-10",
]
),
)
prices["ma3"] = prices["close"].rolling(3).mean()
prices["signal"] = prices["close"] > prices["ma3"]
prices["position"] = prices["signal"].shift(1).fillna(False).astype(int)
prices["stock_return"] = prices["close"].pct_change().fillna(0)
prices["strategy_return_before_cost"] = prices["position"] * prices["stock_return"]
prices["trade"] = prices["position"].diff().abs().fillna(prices["position"])
prices["cost"] = prices["trade"] * 0.001
prices["strategy_return"] = prices["strategy_return_before_cost"] - prices["cost"]
prices["equity"] = (1 + prices["strategy_return"]).cumprod()
print(prices)这里最重要的不是代码长度。
最重要的是 position = signal.shift(1)。
它提醒你:今天收盘后才知道的信号,不能倒回今天去成交。
trade 表示仓位变化。
仓位从 0 变成 1,代表买入。
仓位从 1 变成 0,代表卖出。
cost 是一个简化成本。
真实 A 股还要区分买入佣金、卖出佣金、卖出印花税、过户费和滑点。
新手先把成本作为“每次换仓扣一点”来理解即可。
七、资金曲线才是回测主角
很多人喜欢盯着最终收益。
例如从 1.00 变成 1.20,就说赚了 20%。
但资金曲线会告诉你中间发生了什么。
如果它先从 1.00 跌到 0.70,再涨到 1.20,你可能很难坚持。
如果它从 1.00 稳稳涨到 1.12,虽然最终收益小一些,但过程更容易执行。
回测最小模型必须每天更新资金。
每天更新,才能看见波动、回撤和成本侵蚀。
只看起点和终点,就像只看考试总分,不看错题分布。
八、常见错误清单
新手写回测时,最常见的错误不是代码语法。
而是流程逻辑。
| 错误 | 为什么危险 | 修正思路 |
|---|---|---|
| 当天收盘信号当天成交 | 偷看了收盘后的答案 | 用 shift(1) 延后执行 |
| 不扣交易成本 | 频繁交易会被美化 | 至少加入简化成本 |
| 忽略涨跌停 | 假设所有买卖都能完成 | 对不可成交日做限制 |
| 忽略停牌 | 把不能交易当成可交易 | 停牌日保持原仓位 |
| 只看最终收益 | 看不到中途压力 | 保存每日资金曲线 |
这些错误会让回测像“改过答案的模拟考试”。
分数漂亮,但不能帮助你面对真实市场。
九、从老师、投资者、开发者三个角度看
从老师角度,回测是一套教学实验。
它帮助你把抽象的因子和规则变成可检查步骤。
从投资者角度,回测是一面风险镜子。
它让你提前看到这套规则可能经历怎样的亏损和等待。
从量化开发角度,回测是一条数据流水线。
每个字段都要说明来源、时间和计算方式。
这三个角度合在一起,才能避免把回测误解成“历史发财截图”。
交互实验室
练习:手工走一遍 5 日最小回测
请你把下面这张表复制到纸上或表格软件中。
目标不是算得很快。
目标是亲手感受“信号、仓位、交易、成本、资金曲线”的顺序。
| 日期 | 收盘价 | 昨日是否产生买入信号 | 今日仓位 | 今日是否交易 | 简化成本 | 今日资金 |
|---|---|---|---|---|---|---|
| 第 1 天 | 10.00 | 无 | 0 | 否 | 0 | 1.0000 |
| 第 2 天 | 10.20 | 否 | 0 | 否 | 0 | 1.0000 |
| 第 3 天 | 10.50 | 是 | 0 | 否 | 0 | 1.0000 |
| 第 4 天 | 10.30 | 是 | 1 | 买入 | 0.0010 | 待填 |
| 第 5 天 | 10.60 | 否 | 1 | 否 | 0 | 待填 |
练习步骤如下。
第一步,假设第 3 天收盘后才知道“买入信号”。
第二步,因此第 4 天才把仓位从 0 调整到 1。
第三步,第 4 天买入时扣 0.0010 的简化成本。
第四步,第 5 天继续持有,资金随收盘价变化。
第五步,在表格旁边写一句话:这个回测有没有偷看未来?
如果你能把这五步讲给别人听,就已经掌握了最小回测的骨架。
可执行小任务
用你自己的 pandas 环境新建一个 DataFrame。
手动输入 7 个交易日的收盘价。
写出 signal、position、trade、cost、equity 五列。
然后把 shift(1) 去掉再运行一次。
比较两条资金曲线的差异。
你会直观看到:偷看未来的模型通常更漂亮,但也更不真实。
核心复盘
知识清单:
- [ ] 我知道回测不是历史赚钱截图,而是按过去可见信息进行的规则实验。
- [ ] 我能说出最小回测的五个零件:信号、仓位、交易、成本、资金曲线。
- [ ] 我理解为什么日线信号通常要延后一日执行,不能把收盘后才知道的信息用于当日成交。
- [ ] 我能在 A 股语境下主动检查 T+1、涨跌停、停牌和滑点边界。
- [ ] 我知道资金曲线比单个最终收益更能反映策略过程。
金句总结
回测的价值不是告诉你过去赚了多少钱,而是让你看见一套规则在历史沙盘里如何一步一步走过。