【量化投资从入门到精通 #26】把想法写成可回测的代码策略工程化骨架
策略层收官。前面 6 篇讲了 6 类策略思想,这篇讲怎么把任何一个想法,变成能跑、能测、能交接的代码。
一、这个方法解决什么投资问题
大多数人的策略是这样的:"我觉得放量突破 20 日线可以买。" 然后打开行情软件手动翻。 问题在于:这句话无法被验证。涨的时候你记得,跌的时候你忘了。
工程化要解决的就是一件事:把主观描述变成一段确定性的代码,让历史来投票。
二、核心思想:三段分离
一个可维护的策略骨架,永远只有三块,而且必须互相隔离:
| 模块 | 职责 | 铁律 |
|---|---|---|
signal(kl, i) |
第 i 根 K 线,输出仓位 0/1 | 只能读 t ≤ i 的数据 |
backtest(kl) |
遍历、撮合、算净值 | 决策与成交错开一根 K 线 |
calc_metrics(eq) |
收益/回撤/夏普 | 只吃净值曲线,不碰行情 |
三段分离的价值:换策略只改 signal(),回测和绩效永远复用;出了问题也能快速定位是"想法错了"还是"回测写错了"。
三、实操演示
def signal(kl, i):
"""第 i 根 K 线,返回 0/1 仓位。所有'取数只用 t<=i'。"""
if i < 20: return 0
ma20 = sum(r["close"] for r in kl[i-20:i]) / 20
return 1 if kl[i]["close"] > ma20 else 0
def backtest(kl):
# 关键:第 i 根的仓位由"截至 i-1 的已知数据"决定,应用到第 i 根收益
pos = [0] * len(kl)
for i in range(1, len(kl)):
pos[i] = signal(kl, i - 1)
equity = [1.0]
for i in range(1, len(kl)):
day = kl[i]["close"] / kl[i-1]["close"] - 1
equity.append(equity[-1] * (1 + pos[i] * day))
return equity
真实输出(2026-08-06,600519): 骨架示例(站上 20 日线持有,当日决策次日生效)累计 −9.54%、夏普 −0.66。
四、结果解读
注意这个负收益——它恰恰是这篇文章最有价值的地方。
修 bug 前,同一段逻辑跑出 +41.49%。 原因是第一版写成了 pos[i] = signal(kl, i):用第 i 天的收盘价决定第 i 天的仓位。收盘价出来的时候,当天已经收盘了,你根本买不进去。这就是 #04 讲过的前视偏差。
改成 pos[i] = signal(kl, i-1)(昨天收盘决策、今天持有),收益从 +41.49% 直接掉到 −9.54%。一行下标之差,51 个百分点。
结论:骨架不是"代码整洁"这种审美问题,而是结构本身决定了你会不会作弊。三段分离 + 决策/成交错位,是从架构层面堵死前视偏差。
五、常见陷阱
signal()里偷看未来:出现kl[i+1]、max(closes)、全样本mean()都是红灯。- 决策与成交同一根 K 线:最高频的错误,收益虚高几十个点。
- 绩效函数里混行情:一旦
calc_metrics读了原始 K 线,就无法用合成净值做单元测试。 - 参数写死在函数体:换周期要改源码,无法做 #20 那样的敏感性扫描。
六、小结 + 下篇预告
策略工程化 = signal() + backtest() + calc_metrics() 三段分离,决策与成交错一根 K 线。这套骨架配上统一回测引擎,本系列所有策略都用同一个引擎跑出来。
策略层(#20–#26)到此收官。下篇进入评价与风控层 #27:样本外与前推验证——回测好看不算数,怎么证明策略不是拟合出来的。
免责声明
本文为量化方法教学,标的为示例,不构成投资建议,不承诺收益。投资有风险,入市需谨慎。



