
做了这么多年程序开发身边不少同事都心动过量化投资。程序员搞量化确实有天然优势能写代码、能清洗数据、能自动化跑重复劳动但大多数人卡在了从“写了个策略”到“策略在实盘账户里自动交易”这一步。回测跑得再漂亮一上实盘就各种翻车数据对不上、订单没成交、程序盘中崩溃、成交回报错乱。我自己从零折腾了三次才把一条主策略真正跑上实盘。回头看能跑通实盘的关键不是策略有多玄而是把流程拆成三步把想法写成可回测的策略代码把策略放进模拟盘里验证系统再把系统工程化后小资金实盘上线。这三步每一步都有各自的坑这篇文章就按这个顺序完整拆一遍给有编程基础、想做量化投资但不知道从哪下手的程序员做个参考。不推荐任何具体投资品种只聊技术实现和工程经验。1. 第一步把你的交易想法变成可回测的策略代码1.1 先写清楚策略逻辑再写代码动手写代码之前我建议先在纸上把你的交易想法写清楚。见过太多同事一上来就打开 Jupyter 调库各种指标试一圈最后回测结果一塌糊涂根本不知道是数据问题、逻辑问题还是运气问题。写清楚的核心是回答这几个问题交易什么品种或股票池看什么周期什么时候开仓、平仓、止损每次用多少资金每一条规则都要能在代码里用 if else 表达清楚不能留模糊地带。举一个最简单的双均线例子交易沪深 300 相关的 ETF 产品日线级别ma20 上穿 ma60 买入下穿卖出每次固定仓位。听起来很简单但实现时会冒出很多边界问题什么叫“上穿”是今天快线大于慢线且昨天快线小于慢线还是只需要当前快线大于慢线用复权价格还是原始价格停牌的日子怎么处理这些都必须明确。程序员最擅长的是把模糊描述变成确定性逻辑但前提是规则本身要定义得足够死。还要提醒一点策略里不要掺入主观判断。如果你在回测过程中手动挑过数据区间、手动微调过参数或者看到结果不好就换一段历史再跑一遍那这个回测结果就不代表系统的真实表现。我自己的习惯是每一条规则都写进代码回测全程黑盒运行连日志都固定格式输出事后不去人为干预。对初学者来说从极简策略开始跑通流程比研究一个“高大上”的指标更有价值双均线、布林带、动量突破都行。1.2 选一个趁手的回测框架backtrader 是程序员的好朋友回测框架不需要追求大而全关键是能快速迭代。最早我也自己从头写过回测引擎每天 K 线遍历一遍手续费和滑点全用常数结果发现策略一换又要重新改数据预处理越写越累。后来换到 backtrader老牌开源框架数据喂进去买卖逻辑写在 Strategy 里撮合、记账、分析器这些框架都帮你处理好了。它对不同市场的适配性也不错股票、期货、加密资产的数据都能通过 DataFeed 接入社区资料多搜问题很方便。下面是一个最小回测模板我用 Pandas 读 CSV再转成 backtrader 的 PandasData 喂进去import backtrader as bt class MaCross(bt.Strategy): def __init__(self): self.ma_fast bt.indicators.SMA(self.data.close, period20) self.ma_slow bt.indicators.SMA(self.data.close, period60) def next(self): if not self.position: if self.ma_fast[0] self.ma_slow[0] and self.ma_fast[-1] self.ma_slow[-1]: self.buy(size100) elif self.ma_fast[0] self.ma_slow[0]: self.close() cerebro bt.Cerebro() cerebro.addstrategy(MaCross) data bt.feeds.PandasData(datanamedf) cerebro.adddata(data) cerebro.broker.setcash(100000) cerebro.broker.setcommission(commission0.0002) cerebro.addanalyzer(bt.analyzers.Returns, _nameret) cerebro.addanalyzer(bt.analyzers.SharpeRatio, _namesharpe) cerebro.addanalyzer(bt.analyzers.DrawDown, _namedd) result cerebro.run()这个模板里有一个特别容易踩的坑PandasData 要求 DataFrame 的索引必须是 DatetimeIndex如果索引只是字符串日期时间序列会错位策略里计算指标和触发买卖的位置全乱。另外数据里如果有停牌导致的缺失 bar最好在预处理时补齐或者至少让策略清楚知道当前 bar 是否有效。手续费也要按实际市场规则设置股票按成交额比例期货按每手固定金额还有最低收费、印花税这些越贴近真实越好。我见过有人回测只设一个佣金率高频策略实盘直接被手续费干成负数这就是典型的口袋有个洞没补上。1.3 回测里最容易被忽略的三大细节滑点、复权、未来函数先讲滑点。回测如果忽略滑点结果大概率偏乐观。滑点可以理解成你看到的行情价和实际成交价之间的差距尤其在快速上涨或下跌时你按买一价挂单轮到你的时候价格可能已经上去了。回测中可以简单设置一个固定滑点比如固定 0.1% 或 0.5 个最小变动价位也可以写一个根据波动率动态变化的模型。backtrader 里可以重写相关参数或自定义 Analyzer 来实现不需要太复杂固定值已经能过滤掉一批“回测很牛实盘很惨”的策略。再讲复权。股票有分红、送股价格会除权如果直接拿原始价格算指标会出现莫名其妙的跳空回测结果失真。处理方式是使用前复权数据保证历史价格连续。这里有一个隐蔽的坑回测用的是复权价实盘行情是真实价两者之间存在映射关系。你在信号计算里看到的价格和实际下单价格可能不是一个体系订单模块一定要把复权价转换回真实盘口价否则会出现“代码看到的价格”和“实际下单价格”对不上订单被拒绝或者成交在意外价位。最后是未来函数这直接决定回测质量。未来函数的意思是你在第 t 根 bar 做决策时使用了 t1 甚至更晚的数据。常见错误有两种一是用当日收盘后才知道的数据在当日开盘就交易二是指标计算窗口里不小心包含了当前 bar 之后的数值。backtrader 默认不会帮你做这种检查需要自己在策略里保证任何指标在 bar 推进时都只包含当前及历史数据。一个简单的排查方法在 next() 里加一条日志把当前日期和关键数据项打出来对比 K 线时间线看有没有日期超前的现象。这个动作虽然笨但很有效。1.4 别只看收益率回测评价和过拟合问题回测最简单的评价是累计收益但远远不够。更多时候要看最大回撤、夏普比率、胜率和盈亏比。最大回撤决定了资金曲线的痛苦程度回撤 50% 需要翻倍才能回本这个账要先算清楚。夏普比率衡量的是单位风险对应的超额收益越高说明收益稳定性越好但不同周期、不同频率的策略适用性不同要统一口径去比较。我建议直接把 Returns、SharpeRatio、DrawDown 这三个分析器结果汇总成一个表格函数后续每个策略跑完只出一张标准报表横向对比才直观。过拟合问题更隐蔽。参数扫描很常见但如果你只在某一段历史数据上找到一组表现最好的参数样本外表现很可能一塌糊涂。更务实的方法是滚动窗口回测把历史数据切成训练段和验证段在训练段上定参数在验证段上做验证再滚动推进。参数数量也要控制策略里可调参数越少过拟合风险越低。我自己的体会是一个能跑上实盘的策略背后一定要有简单的经济逻辑支撑比如趋势、反转、波动率聚集而不是纯靠数据挖掘拼出来的一组数字。模型解释性越强实盘里出现异常时你也更容易判断是策略失效还是系统故障。2. 第二步把回测策略搬到模拟盘验证系统而不是验证收益2.1 为什么回测盈利模拟盘却总是别扭回测系统忽略了很多真实世界的因素。首先是撮合机制回测里你以当前 bar 的收盘价或者开盘价成交真实市场里订单会进入交易所撮合价格受盘口挂单、对手方行为、订单量大小影响。其次是延迟从信号产生到指令发出再到成交中间有网络、接口、券商柜台的处理时间快时几百毫秒慢时好几秒。如果你的策略是日内频繁交易这个延迟会直接影响成交质量。还有数据连续性回测用的 K 线是确认后的历史数据模拟盘却会遇到瞬时断流、重连、错序推送程序处理不好就会漏单或重复下单。所以模拟盘的第一个目标不是验证策略能不能赚钱而是验证系统在真实环境下能不能稳定跑起来。2.2 先做一个事件驱动的模拟交易外壳做模拟盘前我自己先搭了一个很轻量的模拟交易外壳。行情数据从免费接口拉K 线每 1 分钟生成一次策略模块负责判断买卖信号订单模块把信号转换成限价单或市价单抛给一个简单的模拟撮合逻辑。这个外壳不追求性能核心是把整条链路跑通。主要模块有五个行情订阅、策略引擎、订单管理、风控、日志。运行方式是用定时器每 5 秒触发一次从行情接口拉最新 Tick 数据聚合成当前分钟 K 线更新到全局变量。策略引擎在每根 K 线闭合后判断信号有信号就生成订单经过风控检查后放进待发队列订单模块按当前价和预估成交量更新持仓每一步都写日志。跑了一周之后我发现了很多在回测里完全暴露不出来的问题K 线时间戳对不齐策略在同一个价格上重复触发信号持仓和订单状态更新不一致以及数据源偶尔返回空值导致整段逻辑报错。这些问题单看都不严重但叠加起来就会打破自动化流程。模拟盘存在的意义就是把这些问题一个一个逼出来用最小的成本让它们尽早暴露。2.3 把 backtrader 从回测扩展到模拟/实盘的一种思路如果不想把整个交易系统重写backtrader 也可以做扩展。核心思路是自定义一个继承自bt.Store或bt.Broker的适配器把真实交易 API 包装进去然后在 Cerebro 里设置 broker 或 store让策略继续用buy()、close()这些方法底层成交由真实接口驱动。这种方法可以复用已经写好的策略逻辑从回测平滑切到模拟。一个模拟盘 Store 的伪代码大概长这样class DemoStore(bt.Store): def __init__(self, api_key, base_url): super().__init__() self.api DemoClient(api_key, base_url) def getbroker(self): return DemoBroker(apiself.api)DemoBroker 内部负责调用模拟下单接口定期查询成交回报把订单状态同步到本地持仓。这样 Strategy 层基本不需要改就能从本地回测切到模拟盘。这个方法适合已经有 backtrader 代码、想快速尝试交易通道的开发者。当然你也可以直接用成熟的量化交易平台很多平台自带回测、模拟和实盘功能但灵活性相对低。我个人建议程序员都动手把 Store 这一层写一遍因为写完之后你会对订单状态、持仓同步、资金流转这些概念有非常具体的认知后面遇到实盘问题排查起来快很多。2.4 模拟盘阶段一定要做好的几件枯燥但重要的事模拟盘阶段不需要追求高收益目标是让系统连续稳定运行两周以上在无人工干预的情况下所有交易记录都能对得上账。我通常会检查四个点持仓一致、成交回报一致、资金曲线连续、重启后状态能恢复。这里要坚持写一个对账脚本每天收盘后拉一次账户实际持仓和本地记录的持仓做比较不一致就报警。对账逻辑本身很简单遍历每一个合约代码对比本地持仓数量和账户持仓数量再对比未完成委托单。真正麻烦的是重启恢复。程序重启后要从历史成交记录里重建持仓状态而不是简单读一个本地变量否则最后一笔成交如果没来得及写入文件状态就会丢。这些工作很枯燥但它们是实盘上线的安全网。我见过有人跳过模拟盘直接上真实小资金结果第一个星期就遇到断线重连后持仓对不上慌忙手动平仓最后亏了一笔本可以避免的损失。3. 第三步实盘上线前的工程化准备3.1 交易通道合规与稳定性优先实盘交易需要连接真实的行情和交易柜台。国内常见的通道以期货的 CTP、股票场景下券商提供的极速交易系统为主还有些第三方开放平台也能接入。不管选哪条路核心要看两点接口是否稳定权限是否合规。个人获取实盘接口通常需要申请对应账户权限比如期货开好账户后向期货公司申请 CTP 权限股票要向券商确认是否开放程序化交易接口有些券商有资金门槛有些场景下还要求登记协议。这些需要在开发之前确认清楚否则系统做到最后才发现接不了实盘就很被动了。我自己的经验是尽量让模拟盘和实盘共用一套核心代码只替换 Store 里的接入地址和鉴权方式。别把模拟和实盘拆成两套系统否则逻辑分叉之后模拟盘验证过的功能在实盘里完全不是一回事维护成本会翻倍。切换时多做一次接口验证先手工调用一遍下单、撤单、查询资金确认返回字段符合预期再让策略跑起来。3.2 仓位和资金管理把防爆仓写进代码里很多程序员做量化喜欢研究开平仓信号却很少认真写资金管理。实盘里最能保命的反而是仓位控制。我的建议是每一笔交易的初始风险控制在总资金的 1% 以内。比如总资金 10 万单笔最大亏损预算就是 1000 元。假设某次入场价 100 元止损价 98 元每股最大亏损 2 元那最大可买数量就是 1000/2500 股。再考虑最小交易单位实际可以下 400 股或 500 股。这个计算可以写成一个风控函数每次下单前自动校验超过限额直接拒绝。不要把风险控制寄托在手动上。实盘操作时人容易犹豫程序反而更可靠。除此以外还要设一个全账户层面的风控比如单日亏损达到 2% 就停止新开仓持仓只减不加比如最大回撤达到某个阈值就整体暂停交易。这些规则都写死在代码里想修改必须经过重启流程避免盘中因为情绪临时拍脑袋。风控参数越简单越好两三个就够别搞得跟参数迷宫一样。3.3 系统监控与故障恢复不看盘的自动化也要有人盯着实盘系统即使全自动化也必须保留一个最小化的人工监控。最简单有效的做法是程序每 30 秒往日志中心写一条心跳监控程序检查心跳超过 3 次没收到就报警。报警工具可以用企业微信或钉钉群机器人或者直接邮件总之要能在手机上快速看到。除了心跳还要检查行情数据和成交回报的健康状态比如行情数据连续 100 秒没有更新就要告警订单卡在未成交超过 5 分钟也要提醒这些情况通常意味着接口连接出了问题需要人工介入。故障恢复方面我习惯在主交易进程旁边放一个看门狗进程如果主进程没有响应看门狗直接把它杀掉并重启重启后先做账户对账再重新挂策略。部署环境建议用云服务器别用家里的普通电脑云服务器的网络链路和电源稳定性好很多能减少一类非常低级但破坏力极大的故障。进程托管尽量用 systemd 或容器编排这样程序崩了能自动拉起日志也有统一收集。3.4 实盘验证清单小资金、短周期、可回滚正式实盘前我列过一个上线清单每次上策略都会逐项打勾第一使用独立小资金账户金额要控制在即使全部亏完也不影响正常生活的范围第二确认佣金、滑点、保证金等参数在代码里和实际账户一致第三备份线上代码版本打上 tag记录日志路径第四准备好一键清仓脚本以及在程序里留一个总开关遇到异常可以快速暂停所有新开仓。上线之后先让它跑一个完整交易周每周做一次复盘对比策略预期收益和实际收益归因。不要单看盈亏要看成交均价和回测假设的差距、订单延迟、错过信号次数这些过程指标。如果连续几周稳定再考虑逐步放大资金。放大资金的节奏也要保守一点每周增量最多不超过当前可用资金的一定比例给系统一个适应过程。4. 实盘跑通后我踩过的五个坑4.1 订单状态不等于成交状态维护好状态机实盘里最典型的一个坑是你提交了一个限价单接口返回“已提交”但后续没有成交回报代码却默认已经成交了导致持仓和实际严重不符。因为“提交成功”不等于“成交”。正确做法是维护一个订单状态机每个订单的状态至少包括待发送、已发送、部分成交、全部成交、已撤销、已拒绝。收到回报后更新状态由状态触发对应逻辑比如全部成交才更新持仓部分成交要累计已成交数量已撤销要判断是否重发。这个状态机在回测里几乎用不到实盘却是核心模块。我能顺利跑通实盘很大程度归功于先把这一层写扎实了。4.2 行情中断后策略被“冻死”而不是停止一次模拟盘演练时我发现行情接口断流后由于策略是等待当前 K 线闭合才执行逻辑程序一直卡在等待状态既不报错也不操作看起来还在运行实际已经变成一具僵尸。解决方法是给行情更新加超时检测比如每个合约在最近 100 秒内必须有新推送否则触发重连和告警。同时在策略循环里加一个“行情新鲜度”检查超过 N 秒没有更新就暂停交易等恢复后再继续。这个细节在实盘中很重要因为一次行情断流后后续订单可能一直挂着资金占用和风险敞口都会失控。4.3 回测里很稳的滑点假设实盘被打脸回测时我设置了一个固定滑点数据很好看结果实盘后发现频繁交易的策略滑点成本比预期高很多。原因是回测中的固定滑点没有考虑盘口深度当订单量稍微大一点会吃掉好几档挂单实际成交均价和看到的买一价差得很远。后来我把单笔下单量限制在日成交量的一定比例内并且尽量使用限价单而不是市价单挂在买一或卖一价附近等待成交成交成本明显改善。对于流动性差的品种更要谨慎回测里哪怕只是一个百分点仓位实盘也可能造成明显冲击成本。4.4 时间戳不一致导致的“马后炮”信号有一次我发现系统在当日收盘后重新计算了一遍当天已经交易的信号排查了很久才发现策略里一部分逻辑用了系统当前时间另一部分用了交易时间两套时间源在跨日时没有对齐导致同一根 K 线的信号被重复触发。这里的原则是策略上下文里只使用明确的 bar 时间戳所有判断都基于 bar 时间戳不要混用当前时间和行情时间。特别是有夜盘或者涉及跨时区品种时更要统一时间基准。可以在初始化时定义一个全局变量current_bar_time每次新 bar 开始时更新所有逻辑都引用它。4.5 别在实盘运行中手动改参数改一次就崩一次量化系统上线后最难管住的是自己。有一次我看到行情异常剧烈想临时把止损放宽一点结果在配置文件里改了参数并热加载另一块逻辑也在读同一个参数触发了重复开仓。从那以后我给自己定了一条铁规矩实盘运行期间任何参数修改都必须走完整的重启和对账流程不允许热更新。宁可错过几次交易也不要在没有完整验证的情况下改变运行中的系统状态。这是纪律问题也是稳定运行的前提。这几步走下来我最大的体会是实盘的第一目标不是立刻赚钱而是让整个链路在小资金下可重复、可解释、可控制。之后再写新策略只需要把策略模块替换掉复用这套工程框架按同样的三步法走一遍验证成本会低很多。希望这篇从 0 到 1 跑通实盘的实战拆解能给同样在折腾量化的程序员朋友提供一些能直接落地的经验。