《量化交易入门》第 5 章:回测——让历史告诉你答案
上一章我们手里有了一个完整策略,但它到底灵不灵?总不能先拿真金白银试。这一章给你一台”时光机”:把策略放回过去的数据里跑一遍,看看如果当初严格照做,今天会是什么结果。这就是回测——量化里最便宜、也最容易被自己骗的一步。
5.1 回测是什么
回测(Backtest)= 假设自己带着策略回到过去,模拟做一遍交易,最后算账。
做法朴素:准备一段历史价格,程序从第一天走到最后一天,每一天按策略判断该不该买、该不该卖,虚拟成交,记下账户里有多少钱。走完,你就得到一份”假如过去 60 天我这么干”的成绩单。
它的价值在于便宜:一个策略实盘试错,可能要亏几万、花半年;回测一次,几秒钟,电费几分钱。如果一个策略在过去的数据上都赚不到钱,你基本没有理由相信它未来会突然灵。
但反过来要记住:回测赚钱,不等于实盘赚钱——这一点 5.6 节专门拆。
5.2 回测的四个零件
任何回测程序,拆开都是这四块:
| 零件 | 作用 |
|---|---|
| 历史数据 | 过去每天的价格(这一章用模拟数据) |
| 信号 | 每天按规则算金叉、死叉 |
| 成交模拟 | 信号出现后买入/卖出,扣减现金和持仓 |
| 净值曲线 | 每天收盘记下账户总市值 |
注意”成交”这一步藏着讲究:信号是收盘后才算出来的(要用收盘价),你不可能在同一个收盘价上成交。所以严谨的回测都规定:今天出信号,明天才能成交。这就是下一节代码里 i+1 的含义。
5.3 完整代码:60 天回测
下面把第 4 章的均线交叉策略放进 60 个交易日里完整回测,初始资金 10 万,金叉次日全仓买入、死叉次日全部卖出。
closes = [ # 第1段:下跌 12.0,11.8,11.6,11.4,11.2,11.0,10.8,10.6,10.4,10.2, 10.0, 9.8, 9.7, 9.6, 9.5, 9.4, 9.5, 9.7,10.0,10.4, # 上涨 10.8,11.2,11.6,12.0,12.3,12.6,12.8,13.0,12.9,12.7, # 见顶回落 12.4,12.0,11.6,11.3,11.1,11.0,11.2,11.5,11.8,11.6, # 第二段震荡 11.3,11.0,10.8,10.7,10.8,11.0,11.3,11.6,11.4,11.1, 10.9,10.8,10.9,11.1,11.4,11.6,11.5,11.3,11.0,10.9,]
def ma(data, i, w): return sum(data[i-w+1:i+1]) / w
cash = 100000.0shares = 0trades = []equity_curve = []prev_diff = None
for i in range(19, len(closes)): diff = ma(closes, i, 5) - ma(closes, i, 20) # 今天确认的信号,明天(i+1)才成交——避免未来函数 if prev_diff is not None and i + 1 < len(closes): if prev_diff <= 0 < diff and shares == 0: # 金叉:次日买入 price = closes[i+1] shares = cash / price cash = 0 trades.append(("买入", i+2, price)) elif prev_diff >= 0 > diff and shares > 0: # 死叉:次日卖出 price = closes[i+1] cash = shares * price shares = 0 trades.append(("卖出", i+2, price)) equity = cash + shares * closes[i] # 当日收盘总市值 equity_curve.append(equity) prev_diff = diff
for action, day, price in trades: print(f"第{day}天 {action},成交价 {price}")
final = equity_curve[-1]print(f"期末资金: {final:.2f}")print(f"总收益率: {(final/100000-1)*100:+.2f}%")peak = equity_curve[0]max_dd = 0for e in equity_curve: peak = max(peak, e) max_dd = min(max_dd, e/peak - 1)print(f"最大回撤: {max_dd*100:.2f}%")运行结果(可复制到本地验证):
第23天 买入,成交价 11.6第37天 卖出,成交价 11.2第52天 买入,成交价 10.8第53天 卖出,成交价 10.9第58天 买入,成交价 11.3期末资金: 93996.32总收益率: -6.00%最大回撤: -16.13%5.4 读懂这份成绩单
别看数字小,每个都在说话:
- 总收益率 -6%:这个策略在这段行情里是亏的。第一次交易 11.6 买、11.2 卖,期间价格明明冲到过 13——这就是第 4 章说的滞后,等死叉确认时利润全还了回去还倒亏。
- 最大回撤 -16.13%:账户从最高点最多亏下去 16%。它衡量的是”过程有多难受”——很多人不是策略不行,是扛不住中途这个坑。
- 交易只有 4 次:均线交叉是低频策略,不会天天折腾。
请注意:这份”不及格”的成绩单,恰恰是回测最大的成功。 我们花几秒钟、零元,就避免了带着 10 万块进市场亏 6000。回测的第一使命是证伪——帮你排除烂策略,而不是证明你聪明。
还有一个容易被忽略的问题:这份成绩单的样本太少了。 60 天里只交易 4 次,4 次里错一次,“胜率”这种数字就会剧烈跳动。现实中,如果你只用四五次交易就判定一个策略行或不行,结论非常脆弱——赶上的可能只是一小段特殊行情。所以评估策略时,除了看收益率,还要问:一共交易了多少次?跨越了多少种市场环境(上涨、下跌、横盘都遇到过吗)?次数越多、经历的环境越全,结论才越可信。 这也是为什么真实的策略研究往往要用几年、甚至十几年的数据,而不是一两个月。
5.5 最致命的坑:未来函数
新手回测最容易犯、也最隐蔽的错误,叫未来函数(look-ahead bias):在某一天做决定时,用了那天还不可能知道的信息。
比如这一行,如果写成”今天金叉、按今天收盘价买入”,回测会好看很多——因为你等于看着答案下单:收盘瞬间你”知道”了均线、又用同一个收盘价成交。现实中你收盘后才算出信号,最早只能明天买。代码里 closes[i+1] 就是在堵这个洞。
未来函数的可怕之处是:它不会报错,只会给你一份假得很漂亮的成绩单。 凡是回测收益高到离谱,先怀疑有没有偷看未来。
5.6 回测好看,为什么还会亏
即使堵住未来函数,回测漂亮、实盘亏钱依然常见,主要有三个原因,后面章节会逐个解决:
- 市场会变:过去有效的规律可能失效,趋势行情里测出来的策略,赶上长期横盘就废(第 4 章的震荡打脸)。
- 没算成本:手续费、滑点每次看着小,几十次交易累积起来能吃掉全部利润——第 9 章专门算这笔账。
- 过度调参:为了贴合历史反复改参数,策略成了”背诵历史”,一遇新行情就露馅,这叫过拟合,第 11 章细讲。
所以看回测,多问一句:这个成绩是策略抓住了规律,还是只是把历史背熟了?
5.7 这一章你该记住的
- 回测 = 带策略回历史数据模拟交易,核心价值是便宜地证伪
- 四个零件:数据、信号、成交、净值曲线;今天出信号、明天才成交
- 必看指标:总收益率、最大回撤、交易次数
- 未来函数不报错但最致命;回测好 ≠ 实盘赚,成本和过拟合后面拆
有了回测这台时光机,你就有了检验任何想法的能力。下一章我们离开均线,去认识量化里真正的”原材料”——常见因子:动量、波动与量价,看看除了交叉,还有哪些规律能变成信号。
📚 《量化交易入门》系列:零基础小白向量化交易,每章一个主题 + 可运行 Python 示例。第 5 章已读,下一章《常见因子:动量、波动与量价》明天见。