ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

期货量化回测必知:滑点建模让回测更贴近实盘

期货量化回测必知:滑点建模让回测更贴近实盘 做期货量化的人十有八九都遇到过这种怪事策略在回测里跑得风生水起年化曲线平滑得像教科书案例结果一上实盘就蔫了不是频繁小亏就是收益大幅缩水。我见过太多人把锅甩给“市场变了”“策略失效”但真相往往很朴素——你的回测根本没把滑点当回事。滑点建模这个词听起来像是高频交易团队才需要操心的事但只要你做的是期货量化无论日内还是波段无论用手工交易还是程序化下单滑点都在日复一日地啃噬你的真实收益。回测里默认的“理想成交价”跟实盘里你实际拿到的成交价中间隔着的就是滑点这座冰山。这篇文章我打算把期货滑点的来龙去脉、建模方法、代码实现和避坑经验一次讲透尤其是怎么用滑点建模让回测更贴近实盘希望对正在被“回测很美、实盘很惨”困扰的朋友有点帮助。1. 滑点到底在偷走什么先拆解期货成交的“摩擦成本”1.1 滑点的三块构成缺一不可很多人以为滑点就是“买贵了几个点、卖便宜了几个点”这个理解太粗糙了。真正的滑点由三块构成每一块的来源和影响方式都完全不同。第一块是价差成本。期货的盘口上有买一、卖一两个报价你下市价单买入撮合价几乎永远是卖一价而不是买一价反过来卖出则是买一价。这个买一卖一之间的价差在流动性好的品种上可能只有1个tick在流动性差的品种上有可能是好几个tick。你每做一次开仓相当于已经先吃了一部分价差。第二块是冲击成本。当你的下单量超过了对手价位置的挂单量订单就会吃穿到下一个价位。比如你想买20手螺纹钢盘口卖一位置只有5手剩下15手就要吃卖二的单成交均价就比卖一价高出一截。资金量越大、下单越急冲击成本就越高。这是大账户和机构账户最头疼的问题散户往往感受不明显。第三块是延迟成本。信号产生到你按下回车再到订单到达交易所撮合中间有时间差。对期货这种tick跳动极快的品种延迟一两百毫秒价格可能已经跑出去好几个tick。程序化交易里这个延迟成本是常态手工交易里人的反应时间更是把延迟放大到秒级。这三块加在一起才是一个完整的滑点。如果你在做回测时只按固定几个点去拍脑袋等于把这三个完全不同性质的摩擦成本混成了一笔糊涂账。1.2 期货市场特有的滑点放大器股票回测和期货回测对滑点的敏感度完全不同因为期货有几个股票没有的“滑点放大器”。首先是合约乘数。螺纹钢一手是10吨一个点就是10块钱沪深300股指期货一个点300块钱原油期货一个点1000块。这意味着同样的滑点点数在不同品种上对应的资金损失差了一个数量级。在股指期货上滑点1个点跟螺纹钢滑点1个点完全是两个世界。其次是主力合约换月。期货每个品种都有多个合约同时在交易资金集中在主力合约上。换月前后旧主力合约的流动性急剧萎缩买卖价差拉大盘口挂单稀疏这时候你的限价单可能半天成交不了市价单则要付出比平时大得多的滑点。回测如果不考虑换月窗口的滑点突变实盘中就会在这里吃大亏。还有一个容易被忽略的是盘中时段差异。期货有日盘、夜盘开盘头几分钟和收盘前几分钟是全天滑点最大的时段。夜盘开盘时尤其夸张价格跳空频繁流动性还没完全恢复此时下单的滑点可能是日间正常时段的2到3倍。理解了滑点的构成和期货市场的特殊性你才能明白滑点建模不是“加一个固定值就完事”的流程而是要在不同场景下还原真实的成交环境。2. 回测里不建模滑点等于默认每次成交都在“理想价位”2.1 交易频率越高滑点的杀伤力越大我常跟朋友说一句话回测不扣滑点等于裸奔。尤其对高频、日内这种交易次数多的策略滑点的杀伤力是指数级放大的。举一个简单的算账逻辑。假设你做一个螺纹钢日内策略平均每天交易4次开平仓各算一次一天两个来回每次成交滑点按1个tick算。螺纹钢最小变动价位是1元一手10吨一个tick就是10块钱。一天4次交易滑点成本就是40块。一个月22个交易日滑点成本880块。听起来不多但螺纹钢一手保证金才几千块这880块对应的年化损失相当可观。如果你做的品种是股指期货情况更夸张。IF一个点300块每来回滑1个点一天两个来回就是600块一个月就是上万。大部分个人量化账户一年下来交易成本里滑点占比往往超过手续费甚至超过净利润。交易越频繁、持仓越短单位收益需要覆盖的滑点成本就越高。我在评估策略时有个习惯先看扣除滑点后还有没有利润。如果一个策略在回测里年化收益30%把滑点加上去之后变成8%那这个策略的真实盈利能力就是这个8%而不是那个漂亮的30%。很多人的策略翻车不是策略逻辑错了而是那22%的利润水分被实盘挤掉了。2.2 回测的“秒成交假设”与实盘的“排队现实”回测框架在处理成交时普遍有一个默认假设信号出现的那一刻就能以当前价格或者下一个bar的开盘价成交。这个假设在流动性充裕、订单小的场景下勉强成立但在很多真实场景里是完全失真的。举个例子。你在回测里做突破策略价格突破20日高点时市价买入。回测里信号触发瞬间你就能在突破那一根K线的高点附近买到。但实盘中突破那一刻往往是盘口瞬间变化最快的时候——大量跟你同样逻辑的单子同时涌入买单瞬间堆起你的市价单成交价可能比突破点高出去好几个tick。这就是为什么突破策略在实盘里的表现总是明显差于回测不是突破逻辑失效了而是突破瞬间大家都在抢滑点天然放大。另一种情况更隐蔽限价单的“虚假成交”。回测里你挂一个限价单价格触及就自动成交。但实盘里限价单要排队盘口变化太快价格刚碰到你的限价又立刻弹开你的单子可能根本来不及成交。回测告诉你“成交了”实盘告诉你“还在挂着”。这种回测幻觉成交不建模根本无法暴露。2.3 一个典型的收益衰减测算为了说得更直观我用一个典型的CTA趋势策略来测算一下滑点对收益的影响。假设某商品期货策略回测期一年总交易次数240次120个来回原始回测年化收益25%最大回撤15%。我按三个档次来扣滑点成本滑点假设每次开平仓来回合计滑点按合约价值估算全年滑点成本占初始资金比例扣除滑点后年化收益无滑点回测默认0025%低滑点1个tick/来回约2.5%仓位资金约6%19%中滑点2个tick/来回约5%仓位资金约12%13%极端滑点开盘冲击场景约8%仓位资金约19%6%你发现没有策略本身的逻辑没变只是把滑点从“忽略不计”改成“正常水平”收益就掉了将近一半。这时候你再回头看自己的回测曲线还会觉得那个25%是真实可预期的吗这也是为什么我一直强调滑点建模不是为了让回测更好看而是让回测提前暴露实盘风险。宁可回测里少赚一点也不要让实盘资金来承担“回测太乐观”带来的落差。3. 滑点建模的四种方式与选择逻辑3.1 固定点数滑点简单直接适合起步固定点数滑点是最常见、最容易落地的方式逻辑很简单每次买入成交价在信号价基础上加上N个tick每次卖出成交价减掉N个tick。N是你在回测前先设定好的固定值。这个方法的优点是清晰、可控、代码实现只要几行。缺点是它把所有滑点来源都压缩到一个固定值里完全忽略了波动率、流动性、时段等因素的变化。但在很多场景下固定滑点已经是性价比最高的选择——尤其是起步阶段你的策略还没有积累足够的实盘成交数据用固定滑点作为基准假设总比完全忽略滑点好得多。固定滑点的N怎么确定我的建议是先用你所交易品种最小变动价位tick size的1到3倍。流动性好的品种取1倍流动性一般的取2倍冷门合约或你本身资金量较大时取3倍。宁可先取大一点回测结果保守一些也不要取小了给自己带来虚假信心。3.2 比例滑点跟价格挂钩自动适应品种差异比例滑点是把滑点定义成成交价格的一个百分比。比如设定0.02%的滑点那么价格10000的品种每次成交要付出2个点的滑点价格5000的品种滑点只有1个点。它的优势在于能自动适应不同价位品种的差异尤其适合在多品种回测中使用。比如你的策略池里既有4000元的螺纹钢又有40万的股指期货固定点数滑点需要分别去设置比例滑点则只需要一个全局参数就行。但比例滑点有一个技术细节要注意它默认滑点和价格成正比但现实中这并不严格成立。例如黄金期货价格高但流动性极好滑点可能只有1个tick而一些低价品种流动性差滑点反而可能是好几个tick。比例模型在跨品种横向比较时提供了便利但牺牲了对单个品种微观结构的还原度。3.3 波动率自适应滑点更接近真实市场行为如果你想让回测更贴近实盘固定值和比例值都不够精细就需要考虑波动率自适应滑点。原理是滑点本质上来源于市场微观结构的不确定性而波动率正是这种不确定性的直接度量。波动率放大时买卖价差拉宽、盘口变薄、冲击成本上升滑点自然随之增大。具体实现上可以用当前周期的真实波动率比如ATR作为滑点系数的基础。滑点 基准系数 × 当前bar的ATR。这样在行情平稳时滑点小行情激烈时滑点自动放大更贴近真实的成交环境。这种建模方式尤其适合突破策略和趋势策略因为这类策略的信号往往出现在波动扩大的时刻而恰好那也正是滑点最大的时刻。如果你只用固定滑点相当于默认信号出现的市场状态永远是平均状态这跟实盘经验是不符的。3.4 成交量冲击成本模型大资金和机构的选择当你的单笔下单量已经大到足以撼动盘口时以上三种方式都会失真你需要的是成交量冲击成本模型。金融市场有一个经典的实证规律市场冲击成本大致与你的下单量占市场成交量的比例的平方根成正比。冲击成本公式通常写成Impact k × σ × sqrt(Q / V)其中Q是你的下单量V是同期市场成交量σ是波动率k是经验系数。意思是你交易量占市场比例越高冲击成本越大但增长速度是平方根级别的——所以大单分拆成多个小单逐步成交能有效降低冲击成本。对于个人量化和大部分中小资金团队来说Q/V通常小到可以忽略成交量冲击模型的意义不大。但如果你管理的是千万以上资金或者交易的是流动性偏弱的品种这个模型早晚用得上。即使不写代码实现至少要在回测参数里给冲击成本留一个余量。3.5 四种方式的选择逻辑做个总结性的对比建模方式优点缺点适用场景固定点数滑点简单易实现、直观无法反映市场状态变化起步阶段、单品种、低频策略比例滑点自动适应不同价格品种忽略了流动性差异多品种回测、横向对比波动率自适应贴近真实市场行为需要额外计算波动率因子突破/趋势策略、中高频策略成交量冲击模型还原大资金真实成本参数估计复杂、难以验证大资金账户、流动性差的品种我的建议是不要一开始就上复杂模型。先用固定点数滑点跑通回测然后对照实盘成交记录看看偏差方向再逐步升级到波动率自适应甚至加入冲击成本。建模的精细度必须跟你的数据和资金规模匹配过度建模跟完全忽略滑点一样是问题。4. Backtrader滑点配置实战从默认无滑点到逼近实盘4.1 Backtrader默认回测环境的滑点盲区既然热搜里多次提到backtrader我就把它当主要工具来讲。Backtrader作为开源回测框架在期货多品种回测上用得很多但默认情况下它跟多数回测框架一样成交价就是当前bar或者下一根bar的开盘价没有内置滑点。如果你不做任何设置回测结果默认就是“理想成交”这在不建模滑点的前提下等于给策略加了水分。而且Backtrader默认是按照股票模式来处理的做期货还需要手动设置保证金模式、合约乘数和手续费结构。滑点这个问题很多backtrader初学者根本不知道有broker.set_slippage_*这类方法直到某天把策略接到模拟盘才发现回测和模拟盘的成交价差得离谱。4.2 内置滑点参数的正确打开方式Backtrader的broker对象提供了两个内置的滑点设置方法import backtrader as bt cerebro bt.Cerebro() # 固定点数滑点每次买入价 2.0每次卖出价 -2.0 cerebro.broker.set_slippage_fixed(points2.0) # 比例滑点按成交价格的百分比0.001 表示0.1% cerebro.broker.set_slippage_perc(perc0.001)这里要注意几个细节第一固定点位滑点是按“价格单位”算的。螺纹钢一张合约的最小变动价位是1元设points2.0意味着每次成交在价格上多付2元/每吨的成本一手10吨对应的滑点总金额是20元。第二比例滑点的perc是百分比的小数形式0.001就是0.1%不是万分之一。很多人把0.001理解成万分之一结果滑点设大了十倍回测利润被误杀。第三不要在同一套代码里同时调用set_slippage_fixed和set_slippage_percBacktrader内部会取最后一次调用的设置生效混用容易导致你得出的结论莫名其妙。如果你想要更灵活的控制可以在策略内部重写buy和sell方法自己计算滑点后再发出订单class MyStrategy(bt.Strategy): def buy_with_slippage(self, sizeNone): price self.data.close[0] self.p.slippage_points self.buy(sizesize, priceprice, exectypebt.Order.Limit) def sell_with_slippage(self, sizeNone): price self.data.close[0] - self.p.slippage_points self.sell(sizesize, priceprice, exectypebt.Order.Limit)这样做的价值在于滑点逻辑完全由你自己控制可以做得更精细。但代价是你失去了Broker内置滑点的简洁性同时要小心订单类型Limit单不保证成交带来的额外假设。4.3 让滑点参数随品种和时段变化多品种回测时不同品种用一个滑点参数肯定不合理。我常用的做法是做一个按品种维度配置的滑点字典slippage_config { rb: 1.0, # 螺纹钢1个tick IF: 0.4, # 沪深300股指按指数点算 au: 0.2, # 黄金期货按克算 cu: 1.0, # 沪铜 }然后在加载每个品种数据时先判断数据对应的品种再把该品种滑点作为策略参数传进去class MultiSlippageStrategy(bt.Strategy): params ( (slippage_map, slippage_config), ) def __init__(self): self.slippage_points self.p.slippage_map.get(self.data._name, 1.0)如果你做的品种有夜盘或者你的策略倾向于在开盘时段交易还可以考虑按时段设置滑点。比如日盘正常时段滑点1个tick夜盘开盘头30分钟滑点2个tick。把时段判断写进策略的滑点逻辑里比用统一的固定值更接近实盘。4.4 用实盘成交记录反向校准滑点滑点参数到底设多少才合理手头没有实盘数据时靠估计有了实盘或模拟盘成交记录后就要用数据来校准了。校准逻辑不复杂。你有一组历史成交记录每一笔都有信号触发时的盘口价格和实际成交价格两者之差就是这笔交易的滑点。把这些差额收集起来做统计取中位数或者75分位数作为回测里的滑点基准值。举个例子。你在模拟盘跑了一个螺纹钢策略收集到最近500笔成交逐笔计算成交价相对当时卖一价的偏差买单和相对买一价的偏差卖单统计结果如下统计量滑点值按价格单位最小值-1.025分位0.5中位数1.275分位2.0最大值6.5回测取中位数1.2还是取75分位2.0取决于你的风险偏好。如果你希望回测结果是“中性预期”取中位数如果你希望回测偏向保守、能覆盖大部分场景取75分位。我不建议取最大值——那相当于用最极端的情况惩罚所有策略会误杀不少原本在实盘可以存活的策略。把校准的结果反馈到backtrader的set_slippage_fixed里你的回测参数就从“拍脑袋”变成了“有据可依”。这才是滑点建模最正确的姿势从实盘中来到回测中去。4.5 从回测走向模拟盘和实盘时滑点参数如何带过去热词里有一句很关键的话——“将backtrader从回测框架扩展到模拟或实盘交易”。很多人把backtrader当纯回测工具但它的架构实际上支持通过自定义broker和store接入模拟交易甚至实盘交易。而滑点参数正是从回测过渡到实盘最重要的桥梁之一。我的建议是回测阶段使用保守滑点实盘阶段使用实际成交数据持续校准。当你把backtrader接到模拟盘之后每天记录实际成交价和信号价的差按周统计滑点分布然后回填到回测参数里。如果实际滑点长期大于回测假设说明回测还是太乐观了要继续调大如果实际滑点显著小于回测假设说明你可以适当放宽参数让回测反映更真实的盈利能力。这个“回测-模拟-校准-再回测”的闭环才是滑点建模真正发挥价值的地方。它让滑点从一个静态参数变成了连接回测和实盘的动态校准器。5. 怎么判断回测已经“足够真实”验证滑点模型的几个信号5.1 关键指标成交价偏差的分布对齐设好滑点参数不代表完事大吉你要验证回测是否真的贴合实盘。最直接的验证方式是对比回测成交价和实盘成交价的偏差分布。操作方法拿同一套策略同一段时间分别跑一次回测和一次模拟盘然后从两个系统里导出每一笔成交记录。算一下每笔回测成交价距离当时bar开盘价的偏差以及实盘成交价距离当时盘口的偏差。理想情况下两个偏差的分布应当大致对齐——中位数接近、尾部行为接近。如果回测成交价的偏差分布明显窄于实盘说明你的滑点参数偏小了。如果偏差分布的中位数比实盘低不少说明你可能设得有些保守。这个对账过程很简单但能帮你客观地判断回测系统的可信度而不是凭感觉觉得“差不多”。5.2 别把手续费和滑点混在一起算有个很容易犯的错误是把滑点并进佣金里一起设置。Backtrader里设置手续费是通过CommissionInfo设置滑点是通过set_slippage_*两者是独立的。有些人不设置滑点只把手续费调高一点来“覆盖成本”这是不可取的。原因在于手续费和滑点对策略的影响方式不同。手续费是每笔确定的支出滑点则与行情状态、盘口深度、下单时机高度相关。突破行情里滑点可能放大3倍而手续费不会因为行情变化而改变。如果你把两者合并成一个参数回测就失去了在“滑点放大时段”暴露风险的能力。正确做法是手续费按交易所标准加简单覆盖来设置滑点单独建模。这样才能从回测结果里拆出两类成本清楚看到是手续费吃掉了利润还是滑点吃掉了利润。5.3 压力测试把滑点参数翻倍看策略是否“突变”我评估一个策略的滑点敏感性常用一个很粗暴的方法把滑点参数直接翻倍观察收益变化幅度。做完之后无非两种结果。第一种滑点翻倍之后收益仍然为正只是少了一些说明策略的安全边际足够实盘里大概率能扛住滑点波动。第二种滑点翻倍之后收益直接转负或者回撤急剧放大说明这个策略的盈利逻辑建立在“成交理想”的前提上实盘存活率堪忧。这个测试本质上是对策略容错能力的一次体检。我见过不少策略回测里漂漂亮亮把滑点从1个tick调到2个tick收益就从正20%变成负5%——这种策略我绝不会上实盘因为1个tick的滑点变化在真实市场中太常见了。价格稍微一个波动实际滑点就会超过回测假设策略就直接从盈利滑向亏损。5.4 限价单成交率是隐藏的真实性标尺最后还有一个容易忽略的指标限价单的成交率。如果你的策略逻辑里经常使用限价单回测里几乎100%触及就成交那大概率是有问题的。真实市场的盘口动态远比回测模型复杂价格触及限价不代表你的单子能排到队。验证方法也简单在回测引擎里记录每笔限价单成交时的价格穿透深度或者干脆设置一个规则——市价单必须至少吃穿对手价1个tick才算成交限价单必须被“穿越”而不是仅仅是“触及”才视为成交。这个调整会明显减少回测里的成交笔数但出来的结果反而更容易跟实盘对上。6. 滑点建模的实战心得这几类坑我替你踩过了6.1 开盘第一根K线的滑点远超均值这是我最开始做期货量化时踩过的坑没有之一。我曾在螺纹钢上做过一个日内策略回测里滑点按1个tick设置曲线很稳。上了模拟盘之后发现策略每次交易都差一截仔细核对成交记录才发现问题集中在开盘后前几根K线——夜盘开盘和日盘开盘的滑点平均在3到4个tick远高于全天的平均1个tick。原因很好理解开盘集合竞价结束后多空双方重新定价价格快速跳动盘口挂单还没完全积累起来流动性短暂真空。这种时段的滑点放大是固定滑点模型完全无法覆盖的。解决方案有两个一是在回测里给开盘时段单独设置更高的滑点系数二是更务实的做法——直接限制策略在开盘后N分钟内不开新仓避开滑点最大的时段。这两个方案可以同时用效果最好。6.2 主力合约换月日的成交“幽灵区”第二个坑是主力合约换月。我做一个趋势策略时回测里一直在交易某个合约测试阶段数据很正常。实盘跑到换月日附近我发现成交记录里出现了大量离谱的价格——有些成交价比当时市场行情高出几十个点。排查后确认问题出在合约代码的映射当旧主力合约流动性下降后我的下单仍然是按旧合约执行的但旧合约的盘口已经稀疏市价单直接吃穿了好几个价位。回测里使用的数据是合并后的主力连续合约压根体现不出换月时段旧合约流动性枯竭的问题。这提醒我滑点建模不只是建模价格偏差还要建模流动性结构和合约生命周期。现在的做法是在回测数据里把主力合约切换日前后一两天的交易排除掉或者给那几天单独设置一个特殊的大滑点系数逼着策略避开这段“幽灵区”。6.3 涨跌停附近滑点模型会彻底失真如果你交易过涨跌停附近的行情你就会知道滑点模型在那时候完全失效。涨跌停板上盘口单边倒挂买单排队排到几百手之后你的市价单根本成交不了或者被迫在涨停板附近以远高于正常价的价格成交。这种情况下的“滑点”已经不是几个tick能描述的了而是成交可能性的问题。回测里如果不处理涨跌停场景默认你还能正常成交那等于自动忽略了一种极端风险。我的处理方式是读取当日涨跌停价格如果信号触发时价格距涨跌停板小于一定阈值比如0.5%就直接跳过这笔交易。宁可错过不要硬做。这个规则加入后回测的真实性提升非常明显。6.4 滑点越大越安全是误区跟“设太小了粉饰太平”相反的另一个极端是“滑点设得越大越保守”。经常有人一上来就把滑点设成5个tick甚至10个tick觉得这样回测收益还能正就能实盘赚钱。这个思路不对。滑点设太大回测会过度惩罚策略导致很多本可以盈利的策略被误杀。尤其对短线策略滑点过大可能把收益完全吃掉让你误以为策略没有价值实际上只是滑点假设偏离了合理范围。滑点建模的目标是贴近真实不是制造悲观。正确的做法是先获取一批实盘或模拟盘成交数据用数据来确定滑点是多少而不是凭“设大点更安全”的直觉。6.5 滑点模型越复杂参数越多过拟合风险越大最后提醒一点滑点建模的精细度是有限度的。有些人会把滑点做成分时段、分波动率、分合约流动性的多维模型看起来非常专业但实际上每一个新参数都在引入新的拟合自由度。如果你没有足够的实盘成交数据支撑这些参数的估计复杂的滑点模型反而会让回测结果更不可信——因为你可能是在用历史数据把滑点“拟合”到过去的特定行情上而非真实反映未来的交易成本。我的经验是从最简单的固定滑点开始数据积累够了再加维度。每增加一个滑点模型的复杂度都要问自己一个问题我的数据量能支撑这个参数的稳定估计吗如果答案不确定宁可保持简单。做期货量化这几年我最大的体会是回测和实盘之间永远有距离但滑点建模可以把这段距离缩短到你可以控制的范围。它不能帮你预测市场但能帮你提前看清策略的真实成本结构避免在实盘中突然发现“利润被摩擦成本吃光了”。如果你目前的回测环境还没有加入任何滑点处理我建议你今天就做两件事第一在框架里加上基础的固定滑点第二把模拟盘的成交记录导出来统计一下真实的成交偏差。当你把滑点从“忽略不计”变成“有据可依”回测结果的分量会比以前重很多。
返回列表