模拟盘对接:回测通过后先跑两周模拟盘
系列·回测篇 05 — 回测数字好看不等于能上真钱,中间隔着滑点、涨跌停、停牌和你自己的手
📖 本篇是《量化实操教程》第 5 篇,前置:backtrader 回测入门(策略在历史数据上先站住脚)。
01 · 为什么回测和实盘之间必须隔一层
回测有三个先天盲区:成交假设理想化(信号价就当成交价,实盘有滑点和买不到)、未来函数漏网(写得再小心,也可能有一处用了当天才知道的信息)、没有你自己(回测不会手抖,你会)。模拟盘用活的数据、死的纪律把这三个盲区 在不上真钱的情况下暴露出来。
02 · 四种方案,怎么选
- ① 自建纸面账本(推荐起步) — 策略每天出信号,脚本按"次日开盘价"记账进 SQLite。 最透明、口径和回测完全可比,半小时能搭起来;本文按这条走。
- ② backtrader 实时模式 — 把数据 feed 换成每日增量,引擎帮你记账;和回测同一套代码, 一致性好,但成交仍是你写的假设。
- ③ 券商/平台模拟炒股 — 同花顺、雪球组合等,有"真实感"但数据导出麻烦,难以程序化对账, 适合手动验证心态。
- ④ easytrader 等客户端对接 — 直接模拟真实下单路径,最接近实盘,但环境折腾、维护成本高, 第一轮模拟不建议。
03 · 纸面账本骨架(方案①)
每天收盘后跑一次,三张表讲清全部事实:
import sqlite3
conn = sqlite3.connect("paper.db")
conn.executescript("""
CREATE TABLE IF NOT EXISTS signals( -- 策略出的原始信号
date TEXT, code TEXT, side TEXT, note TEXT);
CREATE TABLE IF NOT EXISTS fills( -- 次日按开盘价虚拟成交
date TEXT, code TEXT, side TEXT, price REAL, shares INTEGER);
CREATE TABLE IF NOT EXISTS equity( -- 每日收盘估值
date TEXT, cash REAL, market_value REAL);
""")# 成交规则(写死在代码里,模拟期间不许改):
# 买入价 = 信号次日开盘价; 涨停价买不到 -> 记 "missed"
# 卖出价 = 触发条件次日开盘价; 跌停卖不出 -> 顺延到能卖的那天
# 手续费按万 2.5 + 千 1 印花税(卖出) 计,和回测口径一致跑两周,你就有了一份和回测同口径的活体对照。
04 · 对账:模拟 vs 回测,差异去哪了
模拟期结束时,把两边同期的成交逐笔对比,差异通常落在这四类:
- 成交价差 — 回测按信号收盘价、模拟按次日开盘,系统性偏移是正常的,量化它。
- 买不到/卖不出 — 一字板、临时停牌;统计 missed 比例,高就要在回测里补上同样的约束。
- 信号不一致 — 同一天回测有信号、实盘没有(或反过来),九成是未来函数, 回去逐行查数据时间戳。
- 现金与费用口径 — 最低 100 股取整、手续费,小事但会让净值曲线整体漂移。
05 · 什么时候算"通过",可以上小资金
- ☐ 模拟期 ≥ 10 个交易日且覆盖至少一段下跌(只涨的样本说明不了问题)。
- ☐ 模拟净值与回测同期的偏差可以被解释(滑点/费用),而不是莫名亏一块。
- ☐ 零未解释的信号不一致(= 没抓到未来函数)。
- ☐ 最大回撤你心理上能接受——想象这笔钱是真的。
四条全过,再用亏光了也不心疼的钱上小额实盘,继续记录;任何一条不过,回炉。
06 · 避坑清单
- 模拟期间改参数 — 看到模拟亏了就调优策略,模拟就永远"刚好通过"。 规则:模拟期冻结策略,问题记下来,下一轮再改。
- 只模拟顺风市 — 挑行情好的时候模拟两周不算数;至少经历一次回撤才算见过世面。
- 把模拟当儿戏 — 不是真钱就不按流程执行,会养成坏习惯。模拟盘练的就是执行本身。
🏁 路径二主线(工作台 → 数据 → 回测 → 模拟盘)到此走完。 数据源想扩展到美股,回看 EODHD 海外数据实操; 全部教程见 教程路径总览。
教程不构成任何投资建议;A 股无官方统一模拟盘接口,文中方案为自建对照,用于研究流程验证。