gs-quant量化回测交易成本模型实战:一份让回测与实盘少打架的完整方案
【免费下载链接】gs-quantPython toolkit for quantitative finance项目地址: https://gitcode.com/GitHub_Trending/gs/gs-quant
回测年化 20%,实盘只剩 8%——差的那 12 个点,大半死在成交假设上。你的回测默认每一笔单子都按历史中间价、全额、零摩擦成交,可实盘里你的大买单自己就会把价格顶上去。这篇文章用 gs-quant 的量化回测模块,帮你把交易成本模型接进回测流程,把"回测与实盘差异"里最玄学的一块变成可调参数。
上图是 gs-quant 官方材料里的执行优化流程:订单进来后,先评估流动性与成本,再结合风险约束由优化器生成执行计划,成交行为还会持续回流修正。你可以把它理解成 gs-quant 执行链路想逼近的目标形态。
收益是怎么被市场冲击吃掉的
先说清两个词:
- 市场冲击:你的大买单本身把成交价推高了,买得越多、推得越狠。
- 流动性:一笔标的在不大扰动价格的前提下能吞下多少量。
因为冲击成本随下单量变大而放大,所以同样一条策略,单子越大、标的越冷门,回测和实盘的差距就越大。要缩小这个差距,思路只有两条:把冲击成本算进成交价,把大单拆细摊到更长时间里。gs-quant 的回测链路正好支持这两条。
gs-quant 回测引擎、执行引擎、组合管理器的分工
三个部件各管一段:
- 回测引擎:
Backtest类管理回测生命周期,跑完后用get_results()取结果,代码在 gs_quant/backtests/core.py。 - 数据层:
DataHandler负责按时间窗口取历史行情。 - 执行引擎:
SimulatedExecutionEngine决定每笔订单以什么价格、什么数量成交,代码在 gs_quant/backtests/execution_engine.py。
执行引擎的ping(state)方法在每次状态推进时被调用:它检查订单是否到了执行窗口,到了就调用订单的execution_price(self.data_handler)算出成交价,生成成交事件FillEvent:
fill = FillEvent( order=order, filled_price=order.execution_price(self.data_handler), filled_units=order.execution_quantity(), )注意成交价不是引擎拍脑袋给的,而是订单自己说了算。基类OrderBase要求子类实现execution_price(),见 gs_quant/backtests/order.py:
def execution_price(self, data_handler: DataHandler) -> float: price = self._execution_price(data_handler) if np.isnan(price): raise RuntimeError('can not compute the execution price') return price现成的OrderTWAP把成交均价取在TimeWindow起止时刻之间的窗口均价——大单按时间切片摊薄冲击,就是在订单层实现的。
回测跑完,绩效归因交给组合管理器PortfolioManager:它对历史绩效报告按批次调度,months_per_batch控制每批覆盖的月份,见 gs_quant/markets/portfolio_manager.py:
pm.schedule_reports(backcast=False, months_per_batch=6)所以完整分工是:Backtest 管时间线,执行引擎管成交,组合管理器管算账。冲击成本加在第二层,前后对比在第一、三层完成。
冲击因子怎么接进执行引擎:自定义执行引擎的骨架
最省事的做法是继承SimulatedExecutionEngine,把你估算冲击的公式挂进成交价计算:
class LiquidityAwareExecutionEngine(SimulatedExecutionEngine): def __init__(self, data_handler: DataHandler, impact_factor: float = 0.03): super().__init__(data_handler) self.impact_factor = impact_factor def calculate_impact(self, order, volume) -> float: return self.impact_factor * order.execution_quantity() / volume思路是:在ping()里算出成交价后,用DataHandler取该标的历史成交量volume,按mid_price * (1 + impact_factor * order_size / average_volume)修正,再组装FillEvent。公式可以先简单,关键是你有一个能对比的开关——同一策略、同一段行情,只切执行引擎,夏普差多少就是冲击吃了多少。
大单拆批次、定仓位上限:关键参数怎么取值
| 参数 | 作用 | 推荐取值 |
|---|---|---|
| impact_factor | 市场冲击系数,决定成交价上浮比例 | 0.01–0.05,从 0.01 起逐档上调 |
| months_per_batch | 绩效报告调度批次周期(月数,必须 > 0) | 3–6 个月 |
| max_position_size | 单标的下单上限 | 不超过日均交易量的 5% |
调法:impact_factor 拿 0.01 和 0.05 各跑一遍,用无成本基线的夏普做参照,落在中间取保守值;批次太长,单批内冲击被低估,太短则调度开销上升,3–6 个月是折中;仓位上限 5% 是经验起点,冷门标的往下压。
动手清单:三步把成本模型接上
- 先用默认 TWAP 执行引擎跑一遍无成本基线,记下夏普;
- 挂上冲击公式重跑,量化收益衰减幅度——这是你实盘要打的折扣;
- 对调仓金额大的标的启用时间切片,单标的成交控制在日均量 5% 以内,再用
months_per_batch=6的批次报告复查成本前后对比。
gs-quant 把这些都拆成了可替换的部件,成本模型不再是玄学,而是一段可以单测、可以回归的参数。
【免费下载链接】gs-quantPython toolkit for quantitative finance项目地址: https://gitcode.com/GitHub_Trending/gs/gs-quant
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考