手里有一套自己攒的量化系统,最早那版是拿表格软件加脚本硬拼出来的,数据靠手工导,回测靠循环跑,策略改一个参数要等半小时。后来接触到免费开源的专业级量化交易软件,才意识到自己前面那两年基本是在重复造轮子——轮子还没造圆。这类软件的核心价值不是"免费",而是它把数据接入、事件驱动回测、订单管理、风控拦截这些又脏又容易出错的底层活儿,用一套经过大量用户验证的工程结构固化下来了,你只需要把精力放在策略逻辑本身。我身边有做期货 CTA 的朋友、有做股票多因子的研究员、也有纯粹想验证自己想法的业余爱好者,他们用同一套开源框架,做的却是完全不同的事。这篇内容就围绕"一个免费开源的专业级量化交易软件"这个主题,把它能做什么、内部怎么搭、上手要踩哪些坑,按我自己的实操路径完整拆一遍,适合刚入门想少走弯路的人,也适合用过但总感觉回测和实盘对不上的老手对照检查。
1. 先把需求想透:开源量化软件到底替我们省了什么
1.1 三类使用者的真实诉求其实完全不同
很多人一上来就问"哪个量化软件最好",这个问题没有答案,因为提问的人往往没想清楚自己属于哪一类。我把接触过的人粗分成三类,他们对同一套开源软件的要求差别极大,选型前先给自己归个类,能省掉后面大量返工。
第一类是策略验证型,典型特征是脑子里有个想法,想快速知道它在历史上到底有没有效。这类人对数据精度要求中等,对回测速度要求极高,最好改完参数几秒钟出结果。他们的核心诉求是"快",框架的易用性和向量化计算能力比什么都重要,实盘接口对他们反而是负担。
第二类是实盘交易型,已经在市场里真金白银跑着,需要的是稳定的订单管理、断线重连、风控拦截。他们对回测精度要求苛刻,因为回测和实盘的偏差直接等于亏钱。这类人最在意的是事件驱动的撮合模拟是否真实、订单状态机是否严谨、异常处理是否完备。
第三类是工程复用型,可能是在做研究、做教学、或者给团队搭基础设施,他们要把量化能力嵌进更大的系统里。这类人关心的是模块解耦程度、接口是否清晰、能不能替换其中的数据层或撮合层。
我自己的路径是从第一类起步,踩了半年坑才意识到必须往第二类迁移——因为你用向量化方式算出来的漂亮曲线,一旦换成逐笔撮合,经常直接塌掉一半。所以选开源软件时,我会优先看它是否同时支持向量化和事件驱动两种回测模式,以及实盘模块和回测模块是否共用同一份策略代码。这个判断标准比看 GitHub 星标数有用得多。
1.2 开源不等于白嫖,隐性成本要先算清
"免费开源"这四个字最容易被误解的地方,是让人以为总成本为零。实际账不是这么算的。我第一年用开源框架,软件本身零成本,但为了让它跑起来,额外付出的东西折算下来并不便宜。
显性成本里最容易被低估的是数据。框架开源,数据不开源。历史行情、财务数据、成分股调整记录,这些要么自己攒,要么买。自己攒的代价是时间和准确性,买的话一年几千到几万不等,取决于品种和频率。我见过不少人框架装得很顺,结果卡在数据这一步半个月动弹不得,就是因为一开始没把数据预算算进去。
隐性成本里最大的一块是学习和调试时间。开源软件的文档质量参差不齐,很多细节只能读源码。一个字段含义搞错,可能让你多花两天排查。我给自己定过一个粗略的换算标准:如果某个框架的架构设计合理但文档差,前期多花的时间大约是两周;如果架构本身混乱,那建议直接放弃,后面维护成本是无穷的。判断架构是否合理,有个简单办法——看它的目录结构,数据、策略、撮合、风控、接口是否分在不同模块里,如果一个文件几千行什么都干,基本可以绕道。
还有一块常被忽略的是运维成本。策略跑起来之后,服务器、监控、备份、日志清理,这些活儿不会因为你用的是开源软件就消失。一台入门级云主机一年几百块不算多,但半夜策略挂掉没人知道、第二天发现错过一波行情,这种损失才是真的疼。所以我在第二年开始强制给自己加监控告警,后面会细说。
1.3 一套"专业级"软件必须凑齐的五块拼图
市面上叫量化框架的东西很多,但够得上"专业级"三个字的,我认为至少要具备五块能力,缺一块在实盘里都会露出短板。
数据层要能接多种来源,支持增量更新和历史回溯,并且内置清洗逻辑,比如缺失值处理、异常价格过滤、复权因子对齐。回测层必须是事件驱动的,能模拟撮合、滑点、手续费、涨跌停无法成交这些真实约束。策略层要提供统一的生命周期钩子,让同一份代码在回测和实盘里跑出一样的行为。风控层要在下单前做拦截,而不是事后统计,单笔仓位、总仓位、单日亏损这些硬约束必须能配置。接口层要封装交易通道,处理好鉴权、重连、订单状态同步和幂等。
这五块里,业余项目最容易忽略的是风控层和接口层的异常处理。我见过自己写的框架,回测漂亮得不像话,一上实盘就因为重复下单把仓位翻了一倍。原因很简单——网络抖动导致下单请求超时,程序没做幂等判断就重发了。这种事在开源成熟框架里通常已经处理过,但你要知道它处理在哪、怎么触发的,否则换个通道又得重来一遍。
2. 架构拆解:一个能跑实盘的量化系统长什么样
2.1 分层设计与数据流向,先看懂再动手
拿到一套开源量化软件,我的习惯是先不看策略示例,而是把它的数据流从入口追到出口。这一步花两三个小时,能省后面几十个小时的瞎猜。
一条完整的链路大致是这样:数据采集模块从接口或本地文件拿到原始行情,写入存储;数据清洗模块做复权、去重、补缺、时间对齐;策略模块在每根 K 线或每个 tick 到来时被事件引擎唤醒,读取当前上下文,输出信号;组合与风控模块把信号翻译成目标仓位,再做约束校验;撮合模块在回测模式下模拟成交,在实盘模式下把订单发给通道;账户模块更新持仓、可用资金、浮动盈亏;记录模块落盘成交明细和每日净值。
理解这条链路的关键,是搞清楚哪一层负责状态、哪一层负责计算。策略层应该是无状态的纯函数式逻辑,给它同样的输入就产出同样的输出;所有状态,包括持仓、资金、未成交订单,都应该由框架的账户对象统一维护。这个设计如果做对了,你换策略、换品种、换周期都不用改框架代码;如果做错了,策略里塞满全局变量,那实盘一出异常就是灾难。
我踩过的一个坑是在策略里自己维护了一个"上次下单时间"的全局变量,用来做频率控制。回测时一切正常,实盘时因为框架重启,这个变量被重置,结果启动瞬间连发了好几笔。后来改成把这类状态写进框架的持久化上下文里,才彻底解决。所以看架构时,我会特别留意框架有没有提供可持久化的策略上下文,这是区分玩具和工具的一个硬指标。
2.2 回测引擎为什么必须事件驱动
这个问题值得单独说,因为它决定了你的回测结果有没有参考价值。
向量化回测的做法是把整段历史数据当成一个矩阵,信号和收益一次性算完。优点是快,几百万行数据几秒钟出结果。缺点是它天然假设"信号出现就能按当根 K 线收盘价成交",这个假设在真实市场里几乎不成立。你的信号是基于收盘价算出来的,可收盘那一刻你根本来不及下单,最早也只能在下一根开盘成交。向量化引擎如果不做额外处理,会系统性地高估收益。
事件驱动回测则是模拟真实的时间推进:每来一根 K 线,引擎先更新行情,再调用策略,策略下单后订单进入待成交队列,下一根 K 线到来时才根据开盘价、最高价、最低价、成交量判断能否成交、成交多少、成交价是多少。这套流程慢,但和实盘的执行顺序一致。
我用同一份策略在两种模式下跑过对比,差异相当直观:一个日频的均值回归策略,向量化回测年化收益 28%,事件驱动回测降到 19%,差出来的 9 个百分点基本全是"当日收盘价成交"这个不真实假设贡献的。所以我的做法是——研究阶段用向量化快速筛因子,进入策略定型阶段一定切到事件驱动重跑一遍,只有事件驱动的结果才拿来判断能不能上实盘。
2.3 实盘与回测共用一套策略代码的关键抽象
这是开源专业框架最有价值的地方,也是最容易用错的地方。
共用代码的前提是框架把"行情来源"和"下单通道"抽象成了可替换的组件。回测时注入历史数据源和模拟撮合器,实盘时注入实时行情和真实通道,策略代码完全不用改。听起来很美好,但实际用起来有几个必须注意的点。
第一,时间语义要统一。回测里now()返回的是历史 K 线的时间戳,实盘里返回的是系统时间,如果策略里混用了这两种语义,逻辑就会错乱。我的做法是策略里只使用框架提供的"当前 K 线时间",绝不直接调系统时间。
第二,数据可得性要一致。回测时你手上可能有完整的未来数据,实盘时只有到当前为止的数据。框架通常会做隔离,但如果你自己在策略里读了外部文件或者缓存,隔离就失效了。我给自己定了个规矩:策略文件里除了框架传入的上下文,不读任何外部状态。
第三,撮合细节要显式配置。很多框架默认回测手续费为零、滑点为零,你不动它就一路零成本跑下去。上线前必须把这两项按真实情况配好,否则回测和实盘的差距会让你怀疑人生。
3. 数据管道:量化系统里最脏最累的活
3.1 数据源分层与清洗流程的落地做法
策略亏钱可以怪市场,数据错了只能怪自己。我把数据分成三层来管,这个分层方式用了两年没出过大问题。
原始层保存从各个渠道抓下来的原始数据,不做任何修改,按日期分文件存。这层的价值是"后悔药"——清洗逻辑写错了可以回滚重跑。清洗层是加工后的标准化数据,统一字段名、统一时间戳格式、统一复权口径。特征层是根据策略需要计算出来的衍生指标,比如各种窗口的均线、波动率、换手率。
清洗流程我固定做五步:去重、排序、补缺、异常值处理、时间对齐。去重按主键加时间戳,重复的直接丢;排序保证时间单调递增,否则后面所有按时间遍历的逻辑都会出问题;补缺分两种,交易日缺失就标记为停牌,非交易日不补;异常值处理主要针对价格,涨幅超过合理阈值、价格为负或为零、成交量为负的记录一律标记待查,不能直接删,删了会造成虚假的连续性;时间对齐是把不同频率的数据统一到策略主周期上,这一步最容易埋雷。
注意:补缺时千万不要用前值填充一切。停牌期间用前收盘价填充是合理的,但如果把整个非交易日都填上,回测时你会莫名其妙多出一堆"僵尸交易日",信号计算全乱。
异常值我还加了一道自动告警:每天清洗完成后统计异常记录数,超过阈值就发通知。有一次某个数据源调整了字段顺序,导致价格和成交量串位,就是靠这道告警在当天发现的,不然跑一周策略才发现,前面的结论全废。
3.2 复权、停牌、涨跌停这三个坑必须提前处理
复权是新手最容易翻车的地方。分红送股会让价格出现跳空,你不复权直接算收益率,会把一次除权当成一次暴跌。复权分前复权和后复权,我统一用后复权做回测:以最早的价格为基准往后推,当前价格是真实价格乘以累计因子。原因是前复权会随着新数据不断变化,今天跑出来的历史信号,明天数据更新后就变了,结论不可复现。后复权虽然价格看起来和实际盘面差很远,但历史是钉死的。
代价是,回测里算出来的价格不能直接用于实盘下单,必须换算回真实价格。换算公式很简单:真实价 = 后复权价 / 累计复权因子。我在下单模块里专门写了一层转换,并且每天核对一次换算后的价格和行情软件一致,防止因子更新出问题。
停牌的处理原则是:停牌日不产生新信号,持仓按停牌前最后价格计算市值,不允许交易。听起来简单,但如果你不做标记,策略可能在停牌期间"以为"自己能卖出,回测收益就虚高了。
涨跌停更麻烦。回测里信号让你买入,但那天如果是一字涨停,你根本买不进。成熟框架一般提供成交限制配置,把你的委托量和当日成交量、涨跌停状态做比对。我通常设置一个保守的成交上限:单笔委托不超过当日成交量的 5%,触及涨跌停则整笔拒绝。这个 5% 是我从自己的实盘成交率倒推出来的经验值,参与率再往上,冲击成本会明显抬头。
3.3 数据质量校验的几条硬规则
我给数据加的校验规则不多,但每条都卡得很死,因为我知道哪一条出问题都会让回测结论失真。
| 校验项 | 判定规则 | 处理方式 |
|---|---|---|
| 价格连续性 | 相邻交易日涨跌幅超过预设阈值 | 标记待查,不自动修正 |
| 成交量合理性 | 成交量为负或为零但价格有变动 | 整行剔除并记录 |
| 时间戳唯一性 | 同一标的同一时间出现多条 | 保留最新写入,旧记录归档 |
| 复权因子单调性 | 累计因子出现回落 | 阻断当次更新,人工核对 |
| 缺失率 | 单标的近 250 日缺失比例超 5% | 该标的暂停入池 |
第五条是我后来加的。早期有个标的因为退市前数据残缺,被策略选中跑了一段,回测里贡献了不小收益,实际上是幸存者偏差的反向版本。加了缺失率过滤后,这类脏数据进不来了。
校验跑完会生成一份日报,包含总记录数、异常数、各标的覆盖度。我不看细节,只看异常数和覆盖度两个数字有没有突变,突变就人工介入。这套机制让我在过去两年里避免了至少三次因为数据源变更导致的静默错误。
4. 回测引擎的四个致命陷阱与参数建模
4.1 未来函数:最隐蔽也最致命的错误
未来函数的意思是,你的策略在时间点 T 使用了 T 之后才能知道的信息。它可能藏在你想不到的地方,而且往往让回测结果变得异常漂亮,漂亮到你舍不得怀疑。
最常见的三种形态:第一,用当日收盘价计算信号,又用当日收盘价成交。前文说过,这是时序问题。第二,用了全样本的统计量做标准化。比如你算因子时用了整段历史的均值和标准差,那在回测早期,你实际上用了未来的数据。正确做法是滚动窗口,只用当前时点之前的数据。第三,财务数据的公布延迟没处理。财报是季度末的时点数据,但实际公布要滞后一两个月,如果你按报告期直接对齐到行情上,等于提前知道了消息。
我排查未来函数有个笨办法但很有效:把回测区间截断成前后两段,分别跑一遍,看前半段的结果和整段结果的前半段是否一致。如果不一致,说明前面的计算受到了后面数据的影响,八成有未来函数。这个办法我管它叫"时间切片自检",五分钟就能做完,建议每个策略上线前都过一遍。
还有一个辅助手段是延迟一天测试:把所有信号统一延后一根 K 线执行,如果收益断崖式下跌,说明你的策略高度依赖即时性,在真实环境里大概率做不到。这个测试不是要你改用延迟信号,而是用来识别策略的脆弱程度。
4.2 幸存者偏差与样本选择偏差,收益虚高的隐形推手
如果你只用当前还在交易的标的做回测,那你已经默认剔除了所有退市和暴雷的标的,这就是幸存者偏差。它对收益的高估有多大?我做过一次对比:同一套选股逻辑,一次用当前成分池,一次用历史时点的真实成分池,十年区间年化收益差了接近 6 个百分点。这 6 个点完全是"上帝视角"送的。
解决办法只有一个:用历史时点的成分记录。指数的成分股调整、行业的分类变更,这些历史记录必须要有,并且回测时严格按照当时时点取池。这块数据不好搞,但绕不过去。如果实在拿不到,退而求其次的做法是在标的池里主动保留一部分已经退市的标的,哪怕数据残缺,也比完全没有强。
样本选择偏差的另一个来源是参数调优。你在同一段历史上试了上百组参数,挑了最好的一组,这组参数的表现必然带有运气成分。我现在的做法是:参数搜索用一个区间,验证用另一个不相交的区间,最后再用第三段做样本外测试。三段数据按时间顺序切,绝不重叠。如果三段表现差异超过 30%,我会认为这组参数不可靠,宁可放弃。
4.3 滑点、手续费、成交量的撮合建模与参数估算
回测失真最大的来源往往不是逻辑,而是成本建模太乐观。我把成本拆成三块来配。
手续费包括佣金和卖出端的税费。佣金按万分之一到万分之三估,具体看你的通道;卖出端还有一项单项税费,按成交金额比例计。这块好算,直接按比例配进框架即可。
固定滑点是指买卖价差带来的损失。可以用历史盘口数据统计,没有盘口就按经验值:流动性好的标的设 1 到 2 个基点,中小市值设 5 到 10 个基点。冲击成本取决于你的委托量占市场成交量的比例,这个必须动态算。
我用一个简化的平方根模型来估冲击成本:
冲击成本(bp) ≈ k × sqrt(参与率) 参与率 = 委托金额 / 当日成交金额其中 k 是经验系数,股票市场我取 10 到 20 之间,期货取 5 到 10。举个例子:某标的当日成交额 2 亿元,我下单 200 万元,参与率 1%,开方后是 0.1,k 取 15,冲击成本约 1.5 个基点。如果下单 2000 万元,参与率 10%,开方约 0.316,冲击成本约 4.7 个基点。可以看到参与率涨 10 倍,冲击成本只涨约 3.16 倍,这就是平方根关系,也是为什么大资金必须拆单。
框架里如果支持自定义滑点函数,我会把这三块写进去:
def slippage_model(order, bar, k=15, fixed_bp=2.0): participation = order.value / max(bar.turnover, 1e-8) impact_bp = k * (participation ** 0.5) total_bp = fixed_bp + impact_bp return order.price * total_bp / 10000.0这段代码的关键不是公式多精确,而是要让成本随规模变化。很多人滑点配一个固定值,结果小资金回测很漂亮,一放大资金就崩,因为固定滑点无法反映规模效应。加了平方根项之后,你的回测会自动告诉你"这个策略最多能装多少钱",这个信息比收益率有价值得多。
我做过一个资金容量测试:把初始资金从 100 万逐步放大到 1 亿,看年化收益随规模怎么衰减。一个日频选股策略,100 万时年化 22%,1000 万时 18%,5000 万时 11%,1 亿时只剩 5%。这条衰减曲线直接决定了策略的适用规模,比空谈"策略有效"实在得多。
4.4 绩效指标的手工核算:别只看年化收益
框架一般会输出一堆指标,但我坚持自己再手算一遍核心的几个,因为框架的默认算法有时和我的口径不一致,尤其是无风险利率和年化方式。
年化收益用复利口径:
年化收益 = (期末净值 / 期初净值) ^ (252 / 交易日数) - 1年化波动率:
日收益标准差 × sqrt(252)注意这里用的是日收益的样本标准差,不是总体标准差,差别在小样本时不可忽略。
夏普比率:
(年化收益 - 无风险利率) / 年化波动率无风险利率我统一取 2%,这个值要在回测配置里显式设置,别用框架默认的 0。
最大回撤:
max(1 - 当前净值 / 历史净值峰值)这个计算必须逐日滚动,不能只看期初期末。
卡玛比率= 年化收益 / 最大回撤,这个指标我比夏普更看重,因为它直接反映"每一份回撤换来多少收益"。
举个实际算例:某策略三年(约 750 个交易日)净值从 1 涨到 1.62。
年化收益 = 1.62^(252/750) - 1 ≈ 1.62^0.336 - 1 ≈ 17.8%。
假设日收益标准差为 0.9%,年化波动率 = 0.9% × 15.87 ≈ 14.3%。
夏普 = (17.8% - 2%) / 14.3% ≈ 1.10。
期间最大回撤 12%,卡玛 = 17.8 / 12 ≈ 1.48。
这三个数字放在一起看才有意义:夏普 1.1 说明风险调整后收益尚可,卡玛 1.48 说明回撤控制还算合理。如果夏普高但卡玛低,通常是某一次极端回撤拖累的,说明策略有尾部风险,实盘时要格外小心。
提示:任何回测指标都要看样本长度。三年数据的夏普 1.1,统计上并不显著,别拿来做重仓决策。我自己的门槛是至少两个完整市场周期、样本外表现不低于样本内的 60%,才考虑小仓位试跑。
5. 从回测到实盘:部署、监控与风控的工程细节
5.1 部署形态与资源配置,别一上来就搞集群
第一次上实盘的人容易犯两个极端:要么在笔记本上跑,关机就断;要么直接上分布式集群,结果自己都搞不清哪个进程在干什么。我的建议是从单机起步,跑稳了再说。
一台入门级云主机,4 核 8G 内存,加一块 SSD,足够跑日频或分钟频的中低频策略。资源分配大致是这样:数据存储占大头,关系型数据库存结构化数据,时序数据库存 K 线,内存缓存存当日快照;策略进程独立运行,和数据库分开,避免数据查询阻塞交易;日志单独落盘,按天轮转,保留 30 天。
部署方式我推荐容器化,但不是为了炫技,而是为了环境可复现。策略依赖的库版本一旦漂移,行为可能就变了。我吃过一次亏:升级了某个数值计算库的大版本,默认精度变了,回测结果凭空多了 2 个百分点收益。从那以后,所有依赖都锁死在配置文件里,镜像固定版本,不在服务器上直接改代码。
进程管理用系统自带的服务管理器即可,关键是配好自动重启和启动顺序:数据库先起,数据同步服务次之,策略进程最后。策略进程启动时要检查数据是否是最新的,如果落后超过一个交易日,直接退出并告警,而不是带着旧数据硬跑。
5.2 风控模块的四道硬约束
风控不是回测里的一个指标,是下单前的最后一道闸门。我在框架里固定加了四道约束,任何一道触发就拒绝委托。
第一道,单标的仓位上限。按策略类型设,选股类我设 10%,CTA 类设 20%。这个数字来自历史回撤分析:如果单票最大回撤 30%,10% 仓位贡献 3 个点回撤,组合能扛住。
第二道,总仓位与杠杆上限。股票策略总仓位不超过 100%,期货策略按保证金占用算,总保证金不超过 60%。留出缓冲是为了应对极端行情下的追保。
第三道,单日亏损熔断。当日累计亏损超过总资产的 3%,停止开新仓,只允许平仓。这个阈值是根据历史单日最大回撤的 1.5 倍设的,目的是在策略失效的第一天就止损,而不是等一周后才反应过来。
第四道,订单合理性校验。委托价格偏离最新价超过 5% 直接拒绝,单笔委托金额超过账户总资产 20% 直接拒绝,同一标的 1 秒内重复委托直接拒绝。这三条主要是防程序 bug,我见过因为循环写错导致连续发单的事故,有了这道校验至少不会失控。
这四道约束的配置要写在独立文件里,不放在策略代码里。原因是策略迭代频繁,风控参数应该相对稳定,分开管理才能避免改策略时误改风控。
5.3 监控告警与日志,半夜出事也能第一时间知道
实盘最怕的不是亏钱,是出事了不知道。我现在的监控面板只看四个数字,但每个都有告警阈值。
心跳:策略进程每 30 秒写一次心跳到缓存,超过 3 分钟没更新就发告警。数据新鲜度:最近一根 K 线时间与当前时间的差距,超过两个周期就告警。订单成功率:当日委托被拒或超时的比例,超过 10% 告警。净值偏离:实时净值与当日预期净值的偏离度,超过 2% 告警。
告警渠道我用的是手机推送加邮件双通道,重要告警必须手机推送,因为邮件可能几小时才看。阈值设置要克制,太敏感会把自己搞疲,我的经验是每周告警次数控制在 3 次以内,超过了说明阈值太紧或者系统确实有问题。
日志分三类:交易日志记录每笔委托的下单时间、价格、数量、状态变更;策略日志记录信号触发的原因和上下文;系统日志记录进程启停、数据同步、异常堆栈。三类分开存,排查问题时按 trace id 串起来。我经历过一次订单状态不同步的问题,就是靠交易日志里同一笔订单的三条状态记录还原出问题在现场的——通道返回超时,但实际成交了,程序按失败处理重发,结果双倍持仓。
6. 常见问题速查与排查思路实录
6.1 典型问题对照表
下面这张表是我这两年记下来的高频问题,基本覆盖了从装环境到实盘的各个环节。遇到问题先查表,能省不少时间。
| 现象 | 最可能的原因 | 排查步骤 | 处理办法 |
|---|---|---|---|
| 回测收益高得离谱 | 未来函数或零成本撮合 | 做时间切片自检,检查手续费滑点配置 | 修正数据时序,补齐成本项 |
| 回测与实盘偏差大 | 成交假设不真实 / 复权口径不一致 | 对比同一区间的逐笔成交记录 | 统一复权方式,加成交限制 |
| 净值曲线出现直线上涨 | 停牌或缺失数据被前值填充 | 检查该标的当日数据完整性 | 标记停牌,禁止交易 |
| 策略进程内存持续上涨 | 缓存未清理 / 数据全量加载 | 看内存曲线是否单调 | 分批加载,加缓存淘汰 |
| 实盘重复下单 | 超时重试缺幂等校验 | 查交易日志的委托编号 | 加订单去重键 |
| 信号时间与实际不符 | 时区或时间戳语义混用 | 打印事件时间与系统时间 | 统一使用框架事件时间 |
| 参数微调后结果剧变 | 过拟合 / 样本区间过短 | 换样本外区间重跑 | 扩大样本,降低调参粒度 |
| 数据同步卡住 | 接口限流或字段变更 | 看同步日志的报错信息 | 加退避重试,字段校验 |
这张表里我标红最多的是第一行和第五行。前者让你误以为找到了圣杯,后者让你真金白银亏钱,都是能直接毁掉一个项目的级别。
6.2 几个只有实操才会遇到的坑
说几个表里放不下、但我觉得很值得单独讲的细节。
第一个坑:回测跑得快不代表跑得对。有段时间我为了提速,把数据全部预加载进内存,回测确实快了三倍。后来发现一个策略的收益突然变好,查了半天才明白——预加载时把未来数据也载入了,策略里某个滚动计算无意中读到了超出当前时点的数据。这个 bug 极其隐蔽,因为代码看上去毫无问题。所以现在我的原则是:性能优化放在正确性验证之后做,而且每次优化完都要跑一遍基准测试,确认结果和优化前完全一致。
第二个坑:参数搜索的边界效应。做网格搜索时,如果最优参数出现在搜索区间的边缘,那说明真正的最优点在区间外,你得扩大范围重搜。更麻烦的是,连续两次搜索最优都在边缘,往往是参数本身不稳定,不是范围不够。我现在的做法是先在粗网格上定位,再在最优附近细化,同时记录每个参数邻域的收益分布,如果最优点是个孤立的尖峰,周围稍偏就掉得厉害,我直接放弃这组参数——实盘里参数一定会漂移,尖峰撑不住。
第三个坑:实盘首日不要满仓。这个不是技术问题,是心态和验证问题。我的习惯是新策略上线先用 10% 资金跑两周,两周内对比实盘成交价和回测假设成交价的偏差,如果平均偏差在滑点模型覆盖范围内,再逐步加仓。这个过程中我记录过很多有用的数据,比如某类标的的实际冲击成本比我模型估的高一倍,那就得回头调参数。
第四个坑:备份不是可选项。我有一次因为磁盘写满,数据库写不进去,策略读到的是旧数据,当天信号全错。事后复盘,磁盘告警没配,备份也没做。现在固定做三件事:每天凌晨自动备份数据库并校验完整性,磁盘使用率超过 80% 告警,关键配置和历史订单另存一份到对象存储。成本很低,能救命。
第五个坑:别迷信默认参数。开源框架的默认配置是为了让示例跑通,不是为了让你实盘赚钱。默认无风险利率、默认手续费、默认滑点、默认成交价类型,这四个我一定逐个改。尤其是成交价类型,有的框架默认用收盘价成交,有的默认用开盘价,这个选择对结果影响巨大,必须明确设置成你实盘能做到的价格。
最后再分享一个我自己一直在用的检核清单,策略上实盘前必须逐项打勾:时间切片自检通过、样本外表现达标、滑点手续费按真实配置、涨跌停和停牌限制生效、风控四道约束已配置、监控告警已联调、首周小仓位计划已定、数据备份已验证。这八项里任何一项没打勾,我就不让策略上实盘。听起来繁琐,但比事后复盘一笔莫名其妙的亏损要轻松得多。