《量化交易入门》第 10 章:实盘落地——模拟盘、API 与自动化下单
到上一章为止,你的策略已经过了两关:回测有效、扣费后仍有效。很多人在这里直接开户充钱,然后亏得莫名其妙。这一章讲回测和实盘之间那段最容易翻车的路,要过三座桥:模拟盘、交易接口、自动下单的保险。
10.1 第一座桥:模拟盘(纸面交易)
模拟盘就是用实时的真实行情、虚拟的钱,让策略跑起来。国内几乎所有券商 App 都自带”模拟炒股”,量化框架(如 vnpy、backtrader 的 live 模式)也支持模拟账户。
为什么回测漂亮还必须先跑模拟?因为回测和实盘之间藏着三个回测永远看不见的坑:
- 未来函数在实时中无处藏身。 回测里你可能不小心用了收盘时才知道的数据在盘中下单,历史曲线毫无破绽;实时运行时,数据只能一根一根地来,这类错误立刻暴露;
- 成交是被撮合出来的,不是假设出来的。 回测里写”按 10.00 元成交”,实盘中你的限价单可能根本排不上队,或只成交一半;
- 你自己。 第一次看着程序自动花钱,手会不会抖、想不想手动干预,只有实盘模拟能给你答案。
建议模拟盘至少跑一个完整的调仓周期(月频策略就跑一个月以上),把每一笔预期信号和实际成交记下来对账。
10.2 第二座桥:交易 API 长什么样
程序化交易靠的是券商提供的 API(应用程序接口),本质上就是一组可以用代码调用的功能。无论哪家券商,都逃不开三类接口:
- 行情接口:订阅实时报价和 K 线,策略的数据来源;
- 交易接口:下单、撤单、改单,是唯一会动真钱的部分;
- 账户接口:查询资金、持仓、当日委托和成交。
一段典型的下单代码,骨架长这样(仅示意,各券商函数名不同):
# 伪代码:真实下单流程的骨架order_id = api.buy(code="600000", price=10.00, volume=1000) # 发单while True: order = api.query_order(order_id) # 轮询委托状态 if order.status in ("filled", "cancelled"): break api.cancel_order(order_id) # 超时未成交可撤单现实中个人直接接券商柜台的门槛较高,多数人走的是 QMT、Ptrade 这类券商量化终端,或支持 API 的加密货币交易所。无论用哪个,先用官方文档里的模拟环境跑通,再碰实盘。
10.3 动手写:一个最小模拟券商
光说不练不过瘾。下面用 Python 实现一个”模拟券商”:它接收买卖指令,用你给定的价格撮合成交,管理现金和持仓,并自动扣减手续费(费率接上一章的设定)。不依赖任何第三方库,可以直接运行:
class PaperBroker: def __init__(self, cash, commission_rate=0.00025, stamp_tax=0.0005, slippage=0.001): self.cash = cash # 可用现金 self.positions = {} # 股票代码 -> 持股数 self.rate = commission_rate self.stamp = stamp_tax self.slip = slippage
def _fee(self, amount, is_buy): commission = max(amount * self.rate, 5) # 最低 5 元 tax = 0 if is_buy else amount * self.stamp return commission + tax
def buy(self, code, price, shares): fill_price = price * (1 + self.slip) # 买入滑点:买贵一点 amount = fill_price * shares cost = amount + self._fee(amount, True) if cost > self.cash: print("现金不足,买单被拒绝") return self.cash -= cost self.positions[code] = self.positions.get(code, 0) + shares print(f"买入 {code} {shares} 股,成交价 {fill_price:.2f}," f"花费 {cost:.2f}")
def sell(self, code, price, shares): held = self.positions.get(code, 0) if shares > held: print("持仓不足,卖单被拒绝") return fill_price = price * (1 - self.slip) # 卖出滑点:卖便宜一点 amount = fill_price * shares net = amount - self._fee(amount, False) self.cash += net self.positions[code] = held - shares print(f"卖出 {code} {shares} 股,成交价 {fill_price:.2f}," f"到手 {net:.2f}")
def equity(self, prices): """prices:当前各股票价格的字典,算总市值""" market = sum(prices[c] * n for c, n in self.positions.items()) return self.cash + market
# 跑一段完整流程:10 万本金,买入、持有、卖出、结算b = PaperBroker(100000)b.buy("600000", price=10.00, shares=5000)b.sell("600000", price=10.50, shares=5000)print("期末总资产:", round(b.equity({"600000": 10.50}), 2), "元")运行结果:
买入 600000 5000 股,成交价 10.01,花费 50062.51卖出 600000 5000 股,成交价 10.49,到手 52408.16期末总资产: 102345.65 元股价明明从 10.00 涨到 10.50,账面涨幅 5%,但扣掉双边滑点、佣金和印花税后,总资产只涨了约 2.3%。把这个类里的手动调用换成实时行情驱动的策略信号,它就是一个最小的自动化交易内核——而这中间所有数字,都和真钱账户一一对应。
10.4 第三座桥:自动化下单的四道保险
程序能自动花钱,是力量也是深渊。上线自动交易前,请务必装好这四道保险:
- 仓位硬上限:在代码里写死单只股票和总仓位上限,超过就拒单。永远不要让程序有权限一把梭哈;
- 一键停止开关(Kill Switch):一个最简单的命令或按钮,立刻停止一切新开仓。程序失控、网络异常、行情源抽风时,你的手指要能一秒钟掐断它;
- 防重复下单:每笔委托带唯一编号,发单前先查询当日委托,避免网络重试时同一信号连下三次单——这类事故在新手身上真实地反复发生;
- 断线保护:行情或交易连接断开时,默认动作是”停止交易并告警”,而不是”用旧数据继续下单”。
此外,所有实盘程序都要写日志:什么时间、收到什么信号、下了什么单、成交没有,逐条落盘。出事复盘时,日志是你唯一的现场。
10.5 实盘起步:小到你能睡着觉
模拟盘稳定盈利一个周期后,实盘请用一笔小到亏光也不影响生活的钱开始,比如几千元。这个阶段的目标不是赚钱,而是验证三件事:接口是否稳定、对账是否对得上、你自己能不能睡着觉。
等小额实盘跑满两三个月、每笔账都清清楚楚、心态不再随程序起伏,再逐步加仓。量化里最贵的学费,从来都交给了”跳过这一步”的人。
10.6 这一章你该记住的
- 回测→实盘必过三桥:模拟盘、交易 API、自动化保险;
- 模拟盘暴露未来函数、真实撮合和你自己的心态问题;
- API 三类接口:行情、交易、账户,交易接口是唯一动钱的;
- 自动下单四道保险:仓位上限、急停开关、防重复、断线保护,外加完整日志;
- 实盘从亏光也无所谓的小钱开始,先验证系统,再谈赚钱。
能自动交易了,你手里就有了一台不知疲倦、严格执行规则的机器。但机器的好坏,取决于喂给它的”判断”。下一章:机器学习入门:过拟合与模型陷阱,我们聊聊当策略不满足于均线和因子、开始让算法自己从数据里”学规律”时,那些闪闪发光的高收益预测,为什么大多是陷阱,以及普通人该怎么避坑。
📚 《量化交易入门》系列:零基础小白向量化交易,每章一个主题 + 可运行 Python 示例。第 10 章已读,下一章《机器学习入门:过拟合与模型陷阱》明天见。