ARTICLE DETAIL

资讯详情

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

期货量化滑点建模实战:用backtrader让回测更贴近实盘

期货量化滑点建模实战:用backtrader让回测更贴近实盘 做期货量化的人十有八九都遇到过同一个场景回测跑出来的资金曲线漂亮得像印钞机年化收益30%、最大回撤只有5%一丢进实盘第一个月就开始怀疑人生。曲线形状倒是还能对上可就是比回测少了一大块利润——这是策略失效了还是行情变了多数情况下都不是。那部分钱是被滑点吃掉了。今天想聊的就是这件事期货量化里的滑点建模以及怎么让回测更贴近实盘。滑点是个特别容易被忽略、又特别能“杀死”策略的细节。尤其是在期货这种T0、双向交易、带杠杆的品种上滑点会直接影响开平仓价格、止损触发、加仓节奏甚至决定一个策略是稳定盈利还是稳定亏损。我会从滑点的本质拆起讲清楚期货市场里滑点到底从哪来再把几种实用的滑点模型铺开最后落到backtrader这个回测框架里用实际代码把带滑点的回测流程走一遍连带着把参数标定、对比评估、实盘验证和坑点排查都聊透。这篇内容适合所有做期货量化的人看不论你是刚上手回测的新手还是已经在实盘里被滑点虐过的老手应该都能找到些用得上的东西。特别是那些已经在用backtrader做多品种回测、想把回测质量再往上提一档的朋友这篇大概能帮你避开几个我踩过的坑。1. 为什么回测和实盘总差一截滑点才是那层窗户纸先把话说清楚我在这里聊的“滑点”是指你下委托单时预期的成交价格和实际成交价格之间的差值。回测软件里理论上价格到了你的止损位就按止损位成交价格突破了你的开仓条件就按突破价成交。但实盘不是这样实盘里限价单可能不成交市价单可能成交在一个比你预期更差的位置这个差值就是滑点。有些刚开始接触量化的人觉得滑点不就是一两跳的事嘛能有多少钱如果你做的是日线级别的低频趋势策略滑点确实占比不大一年几十次交易每次多付出几跳成本顶多就是收益缩水几个百分点。但如果你是做日内短线、分钟级别甚至tick级别的策略滑点就是生死线。日内策略一年可能交易上千次每次双向都滑点哪怕只是0.5个最小变动价位累积起来就能把原本年化20%的策略干到亏损。1.1 滑点不是小数目而是收益的“沉默失血”我见过太多回测报告里面手续费、保证金比例都设了唯独滑点设成0或者设了一个象征性的固定值。结果呢回测年化40%实盘做出来15%中间这25%的差额你复盘来看几乎全是滑点加冲击成本贡献的。举一个直观的例子螺纹钢期货盘口买一价3500卖一价3501一跳是1元/吨。你下一个市价单买入通常成交在3501甚至3502。表面上看你只多花了1到2跳也就是每吨1到2块钱一手10吨也就10到20块钱。但如果你一天做10个来回呢一手就是200到400块钱的成本。再算上你在这个合约上还要承受价格波动、手续费、资金占用滑点就从一个“小数目”变成了收益曲线里的一个大窟窿。更麻烦的是普通回测框架里的滑点模型通常假设滑点是均匀的、线性的但实盘里的滑点会随波动率、成交量、盘口厚度的变化而剧烈波动。我早年间吃过一个大亏一个基于5分钟K线的突破策略回测时只设了1跳滑点心里想着“已经很保守了吧”结果实盘第一周就发现每次突破时的实际滑点普遍在3到5跳因为突破的时候往往伴随放量和快速行情盘口瞬间变薄滑点自然放大。回测用1跳实盘吃5跳这个策略就废了。1.2 期货市场滑点的四大来源要把滑点建模做准先得知道滑点从哪来。期货市场的滑点我一般归成四类第一盘口深度不足。这是最直接的来源。你手上的单子一多而盘口的挂单量不够你就得往更远的价位吃单。买个10手螺纹钢盘口卖一到卖五也许只有十几手挂单你的订单就会穿过好几档价位实际成交价自然比看到的那一档差很多。这个因素的杀伤力和你的订单大小成正比单子越大冲击成本越高。第二波动率的放大效应。行情剧烈波动时盘口会瞬间“抽空”报价的跳动速度大幅加快你看到价位的下一秒价格已经跑远了。期货市场里经常出现一根大阴线或者大阳线分钟级别的K线连续收涨收跌这时候市价单的滑点会被明显拉大。业界有个经验值波动率越高滑点相对于最小变动价位的倍数就越高通常能到平时的2到3倍。第三报价与撮合机制。期货是连续竞价价格优先、时间优先。限价单如果价格设得不合理可能只能让你成交在更好的方向或者根本不成交市价单则一定会成交但成交价依赖当时盘口状态。对回测来说最大的坑是回测框架假设你以那个K线的收盘价或者下一个K线开盘价成交但实际盘口的买一卖一价和K线价格之间往往存在偏差。第四网络延迟与手动操作。这属于“交易基础设施”带来的滑点。如果你的策略程序是跑在自己家里电脑上网络延迟几百毫秒等你信号发出去行情已经走了一段。量化平台和交易所机房更近延迟更低这一部分的滑点差异就出来了。这个在回测里通常不会体现但如果你做的是高频或者抢突破的玩法延迟就是滑点的亲兄弟。2. 滑点建模的四种实用模型理解了滑点的来源我们就能开始建模了。市面上的滑点模型五花八门但在期货回测里真正常用、可落地、不会把人绕晕的大致是下面这四种。它们从简到繁适用场景各不相同。2.1 固定滑点最朴素也最不容易出错固定滑点就是不管价格多少、波动多大每次开平仓都固定扣掉N个最小变动价位。比如螺纹钢一跳是1元/吨你设置每笔交易滑点2跳也就是2元/吨。在backtrader里这种模型对应的是set_slippage_fixedimport backtrader as bt cerebro bt.Cerebro() cerebro.broker.set_slippage_fixed(fixed2.0) # 固定2个价位单位需与数据价格单位一致注意这里的单位问题backtrader的set_slippage_fixed里的fixed是价格的绝对数值不是“几跳”。对螺纹钢来说一跳是1.0所以fixed2.0就代表2跳对焦炭来说一跳是0.5那2跳就得传fixed1.0。这个细节特别容易搞错我见过有人在焦炭上设fixed2结果滑点直接变成4跳回测结果被白白糟蹋了。固定滑点模型的优点就是简单、直观、跑得飞快适合策略初步筛选时用。缺点也很明显它假设所有环境下滑点都一样对短线高频策略来说这种假设太粗暴了。2.2 比例滑点与大票价格挂钩的粗略修正比例滑点是指按照成交价的比例来计算滑点比如买入时加0.02%卖出时减0.02%。这种模型好处是能跟价格水平挂钩价格越高滑点越大这比固定滑点要合理一些因为高价品种盘口单价高、冲击成本也相应更大。backtrader里对应set_slippage_perccerebro.broker.set_slippage_perc(perc0.0002) # 0.02%的滑点比例这里perc其实是“单边比例”买入时在现价基础上加perc*价格卖出时减去perc*价格。实操时要特别注意backtrader的perc内部实现会把slippage与价格相乘但如果你用了apply_to这样的参数还得搞清楚作用在成交价上还是信号价上。比例滑点模型的粒度比固定滑点好一些但它仍没考虑到一个关键因素波动率变化。同样0.02%的滑点在平静行情里也许是2跳在剧烈行情里可能只有1跳都不到因为价格基数大了但波动也大了。所以这个模型适合中低频、比如日线级别策略做粗略评估但短线策略还是不够精细。2.3 波动率动态滑点把ATR搬进来这是我在期货实战里用得最多的一种模型。思路很简单滑点应该跟当前市场的波动情况动态挂钩。具体来说前一天或者当前周期的ATR平均真实波幅Average True Range越大滑点就设置得越大波动越小滑点越小。具体做法我一般是这样写class VolSlippage(bt.SlippageBase): def __init__(self, atr_period14, slip_ratio0.05, min_slip0.0): self.atr bt.indicators.ATR(periodatr_period) self.slip_ratio slip_ratio # 滑点占ATR的比例 self.min_slip min_slip # 最低滑点绝对数值 def __call__(self, order): if order.executed.exectype bt.Order.Market: atr_value self.atr[0] # 当前周期ATR slip max(atr_value * self.slip_ratio, self.min_slip) if order.isbuy(): return order.executed.price slip else: return order.executed.price - slip这个模型背后是这样一个逻辑在波动大的行情里价格短期内的自然摆动本来就很剧烈你的订单成交价相对于理论价格的偏离也更大在波动小的行情里盘口更稳定滑点自然小。滑点取ATR的一个比例一般是0.05到0.1之间再叠加一个最低滑点下限防止在极低波动品种上把滑点估得过小。这种动态模型的好处是你能根据市场状态自动调整成本预期回测里在震荡市和趋势市中都更贴近实盘。坏处是你需要额外调slippage ratio和min_slip两个参数而且ATR周期选择也会影响结果。我的经验是先用固定滑点做一轮粗筛等到策略候选池缩小到几个值得深挖的再换成波动率动态滑点做精细回测。2.4 冲击成本模型给大资金和重仓交易准备的前三种模型都假设你的单量不会自己影响市场价格但资金量一大这个假设就不成立了。当你下单的手数超过了盘口深度你的订单本身就是推动价格的力量这时候产生的成本叫冲击成本Impact Cost。业界常用的冲击成本模型有几类线性冲击模型冲击成本 k × 成交量占比k是经验系数但线性假设太粗糙实际中冲击成本往往跟成交量的平方根成正比。平方根冲击模型Sqrt Model这是相对经典的经验模型公式为ImpactCost λ × sqrt(订单量 / 市场成交量)λ是品种相关的常数。这个模型能更好的刻画“你下了多大单子、市场这段时间的资金吞吐量有多大”之间的关系。分段冲击模型当订单量小于某个阈值时用线性大于阈值时用平方根。这更贴近做市商和流动性提供者的实际行为模式。对绝大多数个人量化交易者来说冲击成本模型在回测里用得不多因为资金量没到那个级别。但如果你管理的是千万级别以上的资金或者做的是某些流动性极差的冷门品种忽略冲击成本会出大事。我认识一位做一手小品种CTA的朋友资金不大但经常重仓结果回测比实盘好得多后来发现就是没考虑冲击成本——他手数重到采购了自己都推不动市场。3. backtrader里的滑点实现从配置到自定义工具选型上我一直用backtrader做期货回测框架。这个老牌框架的好处是回测逻辑透明社区资料多迭代速度快。尤其是它的broker层处理订单、撮合、滑点的方式逻辑清晰回头排查问题很方便。3.1 内置滑点机制速览backtrader的broker里滑点通过set_slippage_perc和set_slippage_fixed这两个方法配置我再补充几个新增的参数细节方便你查看backtrader文档时对得上号方法作用常见参数适合场景set_slippage_fixed(fixed)固定绝对滑点fixed2.0价格单位明确固定点差set_slippage_perc(perc)按价格比例滑点perc0.0002价格基数大或品种多、需统一比例自定义SlippageBase子类任意逻辑滑点自定义参数需要动态滑点、波动率挂钩、冲击成本一个高频踩坑点set_slippage_perc的perc参数内部计算方式很多新手会误以为0.0002就是万分之二但backtrader内部把slippage作用于成交价时方向是买入加价、卖出减价而perc的单位不是百分比是小数比例。我建议你直接在代码里打印一下最终成交价确认它是不是你想要的那个数字。3.2 自定义滑点模型的接入方式如果你用的是内置的固定或比例滑点那基本不需要写额外代码。但如果你是做动态滑点模型比如我上面提到的波动率模型那你需要继承bt.SlippageBase来写一个自定义类。上面VolSlippage的示例代码里关键点有两个__call__方法的返回值决定了成交价。如果是买入滑点要给“加价”返回值就比order.executed.price大卖出要“减价”返回值就比order.executed.price小。类里的self.atr是数据序列self.atr[0]获取当前周期的ATR值。backtrader在撮合时会调用这个自定义滑点对象对每一笔订单计算一个合适的滑点价格。从backtrader的架构来说这种自定义滑点已经能覆盖绝大多数场景。但我一般建议自定义滑点平滑一点别设计得太“聪明”否则回测时候不好解释你的“回测实际情况”。3.3 参数标定的经验值我整理过一份我自己在期货品种上常用的滑点标定经验表仅供参考实际得按你自己的数据再调品种类型建议初始滑点(最小变动价位倍数)说明螺纹钢、热卷、铁矿2~3跳流动性好但波动大日内突破时滑点放大显著豆粕、菜粕1~2跳盘口深度相对厚但夜盘时段滑点偏高铜、铝、锌2~4跳合约金额大打板时滑点会被放大黄金、白银1~3跳有明显盘口厚度差异夜盘时段尤其要注意原油、燃料油3~5跳受外盘联动影响跳空滑点偏高冷门品种如某些农产品果品合约5~10跳流动性差市价单成交难度大这组数值的来源其实是我在不同行情、不同时间段用市价单做过几百次“测试单”之后总结出来的。怎么测拿一小笔资金分别在不同时段下同等数量的市价单记录成交价跟同时刻盘口中价的差值多测几次取中位数。这个方法虽然土但很有效。比直接拍脑袋设置滑点靠谱一百倍。提示滑点参数的标定最靠谱的方式就是用真实盘口数据做“影子记录”。如果你有模拟盘或者实盘测试环境每天记录策略信号的虚拟成交价和实际成交价积累一个月滑点的分布就是你的标定依据。这个经验我花了很长时间才悟到比任何模型都准。4. 实操演示一个带动态滑点的期货回测案例这一章我走一遍完整的实操流程。以螺纹钢为例做一个简单的双均线趋势策略分别用“无滑点”“固定滑点”“波动率动态滑点”三种配置跑回测对比结果。4.1 数据准备与策略搭建我用的是螺纹钢主力合约日线数据假设你有本地数据或者从某个数据源导入了CSV文件字段包含open, high, low, close, volume, openinterest。用backtrader读取import backtrader as bt class DualSMA(bt.Strategy): params dict( fast5, slow20, ) def __init__(self): sma_fast bt.ind.SMA(periodself.p.fast) sma_slow bt.ind.SMA(periodself.p.slow) self.cross bt.ind.CrossOver(sma_fast, sma_slow) def next(self): if not self.position: if self.cross 0: self.buy() elif self.cross 0: self.sell()加载数据并配置brokerclass CSVPandasData(bt.feeds.PandasData): # 这里假设pandas数据源已经准备好了 pass import pandas as pd df pd.read_csv(rb_daily.csv, index_col0, parse_datesTrue) data bt.feeds.PandasData(datanamedf) cerebro bt.Cerebro() cerebro.adddata(data) cerebro.addstrategy(DualSMA) # 初始资金20万按20%保证金比例 cerebro.broker.setcash(200000.0) cerebro.broker.setcommission( commission0.0001, # 万分之一 mult10.0, # 每点乘数10元/吨 margin0.2 # 20%保证金 ) results cerebro.run()这里有个细节backtrader的期货模拟里mult表示合约每点价值螺纹钢mult10。但别把margin设为0.2就以为万事大吉——backtrader的保证金计算跟实际期货公司有差异实盘中还需要每日盯市追缴保证金。这个涉及到资金管理的细节我先不展开重点还是滑点对比。4.2 三组滑点配置对比我现在跑三组对比A组无滑点基准# 不调用set_slippage任何方法默认就是0滑点B组固定滑点2跳cerebro.broker.set_slippage_fixed(fixed2.0)C组自定义波动率滑点# 用前面写的VolSlippage假设ATR周期14滑点比例为0.08最低滑点1.0 cerebro.broker.set_slippage(VolSlippage(atr_period14, slip_ratio0.08, min_slip1.0))每组跑完记录总收益率、最大回撤、交易次数、胜率这些指标。假设结果大致如下指标A组无滑点B组固定2跳C组动态滑点总收益率15.2%11.8%8.6%最大回撤5.1%5.8%6.2%交易次数585858胜率42%42%42%看出来了吗滑点对交易次数、胜率这类信号层面的指标基本没影响但对收益率、回撤的影响是真实存在的。更关键的是C组动态滑点的回撤明显比A组大说明滑点不仅拉低收益还会加大实盘中的回撤幅度——因为亏损交易往往出现在波动剧烈的时候而那时候滑点恰恰更大。4.3 结果评估与实盘验证的心法回测对比的意义不在于“选一个看起来更保守的参数”而在于找到策略盈亏平衡点和容错边界。具体做法是用一个参数网格扫描滑点从0跳一直试到10跳记录每个滑点水平下策略的年化收益率和回撤。然后看这个策略在滑点达到几跳时从盈利转亏损这个“破位点”越靠后说明策略对交易成本的容忍度越高实盘容错率也越高。我见过很多策略在0跳滑点下年化50%3跳滑点下年化就变负了。这种策略工资不稳定实盘一上打脸概率极大。反过来一个策略在0到5跳滑点区间都能保持正收益那它拿到实盘去打安全边际就大多了。至于实盘验证建议用模拟盘或小资金实盘跑一个月每天记录“策略信号理论成交价”和“实际成交价”把每天的滑点分布画出来。如果回测假设的滑点落在实际分布的中间60%区间内这个回测就是够用的如果实际滑点显著大于回测假设那就要回头修正参数再重新评估策略的预期收益。5. 常见问题与排查技巧实录最后我把这些年实操里遇到的高频问题集中整理一下做成一个速查表省得你重复踩坑。5.1 回测优、实盘差排查清单很多时候回测结果和实盘差异不是某个单一原因而是一堆小问题叠加。我建议按下面清单逐项排查排查项常见表现处理办法滑点设置过小回测年化30%实盘只有10%统计实盘滑点分布回看回测假设是否在实盘分布的下四分位忽略了冲击成本重仓交易时实盘明显亏损加剧检查单笔订单占盘口比例必要时引入平方根冲击模型用收盘价成交假设实盘成交价跟当日收盘价偏差大回测改用信号发出后一定延迟如1根K线后或开盘价成交手续费设错实盘手续费远高于回测佣金、平今费、申报费按期货公司结算单复核手续费明细忽略了保证金追缴行情变动大时被强平回测却活下来了在回测中加入保证金余量和强平逻辑模拟盘与回测环境不一致模拟盘靠谱回测不靠谱把模拟盘成交数据导出来反过来校准回测这里面我尤其想强调很多回测框架默认用K线收盘价做成交但你的策略大概率是根据这个K线收盘后的信号下单这么一来真实成交只能是下一根K线的开盘价附近。滑点建模再好如果成交时机都错了那回测的参考价值就大打折扣。我自己做回测时几乎不会用“收盘价立刻成交”这个默认假设都是把成交价调整到下一根K线开盘价或者实际测试后加入一个时间延迟。5.2 品种差异下的标定思路“一套滑点参数打天下”是我早期犯过的最严重的错误。螺纹钢和苹果流动性和波动特性是两回事用同一套滑点必然有一边被高估、另一边被低估。不同品种的滑点标定我一般用这么几条思路看盘口厚度盘口挂单量大的品种螺纹钢、豆粕滑点天然偏低挂单薄的某些工业硅、碳酸锂合约上市初期滑点天然偏高。看买卖价差。价差越小越平滑滑点越可控价差对本来就大的品种滑点至少应该能覆盖住一个价差。看合约换月主力合约临近交割月时流动性收缩滑点会明显变差回测如果你一直用主力连续合约最好换月换月附近的价格跳跃也纳入滑点估算。我个人的做法是给每个品种单独维护一套滑点分布数据用影子记录法采集一个月更新一次回测的时候直接用当前品种的滑点分布去抽样而不是用一个单一数字。5.3 滑点之外的隐性成本你还没算的那些钱这篇文章讲的是滑点但我不想把话题圈死在一个点上。因为很多人在实盘里亏的钱名义上叫“滑点”实际上有三个隐性成本藏在里面。第一是资金成本或者叫隔夜持仓成本。期货是有隔夜资金的虽然保证金制度下资金占用比较少但你长期持仓的方向与市场利率相关这个成本你得算进策略成本里。第二是移仓换月成本。拿着快到期的合约必须移到下个主力这个过程中旧合约和新合约之间价差、交易摩擦、滑点都是隐形损益。有些策略在“主力连续合约”回测看起来没问题一到真实换月就发现需要挪仓的那几天滑点跟价差都能让你吐出一大块利润。第三是开平仓方向的非对称滑点。趋势策略里开仓往往是行情加速、市场情绪高涨时进行的这期间的滑点显著高于平仓而止损平仓往往出现在恐慌或快速反转时滑点同样会放大。这两种情形下的滑点方向都是“不利方向”意味着真实交易中成本会集中爆发。我习惯在回测时区分“开仓滑点”和“平仓滑点”分别设置不同的倍数。注意不要因为回测结果变差了就想着去“优化”滑点参数来包装曲线。滑点标定这事的最终目标永远是“尽可能贴近实盘”而不是“让回测曲线好看”。你骗过了回测也骗不了实盘。最后再分享一个小技巧在backtrader里跑多品种滑点对比时别一个品种一个品种手动改参数。把滑点参数放进策略的params字典里在cerebro.addstrategy时按品种动态传入。这样你一次就能跑出一个大网格的参数矩阵回测质量和排查效率都会大幅提升。我现在的做法是每个品种单独维护一份config.yaml里面存好品种名、手续费率、滑点模型类型、滑点参数、保证金比例回测的时候自动化加载。看起来多了一步配置工作但长期下来这比在代码里到处硬编码滑点数值省心太多了。
返回列表