第 8 章 · 03 Fees Slippage 与 TimeInForce


文档摘要

第 8 章 · 03 Fees Slippage 与 TimeInForce 本节摘要:本节钻取订单成交的三大"修饰器"——手续费(Fees)、滑点(Slippage)、有效期(TimeInForce),以及把它们统一封装的 ,最后收束"成交对 Portfolio 的影响"与"回测模拟 vs 实盘真实"的差异总结。Fees 由 / 实现,按券商配置(美股 IB 每股 0.005 美元、最低 1 美元、最高交易额 0.5%;印度每股 0.

第 8 章 · 03 Fees Slippage 与 TimeInForce

本节摘要:本节钻取订单成交的三大"修饰器"——手续费(Fees)、滑点(Slippage)、有效期(TimeInForce),以及把它们统一封装的 IBrokerageModel,最后收束"成交对 Portfolio 的影响"与"回测模拟 vs 实盘真实"的差异总结。Fees 由 IFeeModel/FeeModel 实现,按券商配置(美股 IB 每股 0.005 美元、最低 1 美元、最高交易额 0.5%;印度每股 0.01 卢比起),实盘成交时引擎按券商回报的 OrderFee 扣;Slippage 由 ISlippageModel 实现(默认 NullSlippageModel 不滑点,可选 ConstantSlippageModel/VolumeShareSlippageModel),在 Fill 模型内部被调用叠加到成交价;TimeInForce 由 TimeInForce 抽象类与 ITimeInForceHandler 实现(GTC 一直有效/Day 当日有效/GTD 指定日期前有效),通过 IsOrderExpired/IsFillValid 两个钩子影响订单是否还活着。三者都挂在 IBrokerageModel(券商模型)上,用户用 SetBrokerageModel(BrokerageName.InteractiveBrokers) 一行设置,引擎自动给每个 Security 配上对应的 Fill/Fee/Slippage/Settlement/BuyingPower 模型。期权行权(OptionExerciseOrder)走特殊的 IOptionExerciseModel——行权产生标的买卖。读完本节,本章"订单→成交→入账"的完整闭环就拼齐了。

内容来源:原项目源码 Common/Orders/Fees/IFeeModel.csFeeModel.csInteractiveBrokersFeeModel.csCommon/Orders/Slippage/ISlippageModel.csConstantSlippageModel.csCommon/Orders/TimeInForce.csTimeInForces/GoodTilDateTimeInForce.csCommon/Interfaces/ITimeInForceHandler.csCommon/Orders/OptionExercise/IOptionExerciseModel.csCommon/Brokerages/IBrokerageModel.csDefaultBrokerageModel.csAlgorithm/QCAlgorithm.cs,精读并套用体系化模板。

⚠️ 注意:本节是第 8 章的收束节。Fees/Slippage/TimeInForce 是"成交的修饰器"——它们不改变"是否成交",但改变"成交价多少、扣多少费、订单什么时候过期"。把它们和上两节(订单类型、Fill 模型)合起来,就是订单的完整生命周期。

学习目标

阅读完本节,你应当能够:

  1. 读懂 IFeeModel/FeeModelInteractiveBrokersFeeModel 的费率规则。
  2. 解释 ISlippageModel 在 Fill 模型里何时被调用、如何影响成交价。
  3. 区分 GTC/Day/GTD 三种 TimeInForce 的过期判定逻辑。
  4. 复述 IBrokerageModel 统一封装的六大模型(Fill/Fee/Slippage/Settlement/BuyingPower/MarginInterestRate)。
  5. 说清 SetBrokerageModel 如何把券商模型下放到每个 Security。
  6. 理解 OptionExerciseOrderIOptionExerciseModel 产生标的买卖。
  7. 总结成交对 Portfolio 的影响(现金扣减、持仓更新、盈亏记账)。
  8. 对比回测模拟成交与实盘真实成交的差异。

一、Fees:手续费模型

Common/Orders/Fees/IFeeModel.cs:25(接口,一个方法):

25 public interface IFeeModel 33 OrderFee GetOrderFee(OrderFeeParameters parameters); // 返回 OrderFee(金额+币种)

FeeModel.cs:25(基类,默认零手续费):

25 public class FeeModel : IFeeModel 33 public virtual OrderFee GetOrderFee(OrderFeeParameters parameters) 35 return new OrderFee(new CashAmount(0, "USD")); // 默认免费

Fees/ 下 30+ 个券商专属费率模型(InteractiveBrokersFeeModel/BinanceFeeModel/ZerodhaFeeModel/IndiaFeeModel/...),全部继承 FeeModel 重写 GetOrderFee。看 IB 美股(InteractiveBrokersFeeModel.cs:138-159):

138 case SecurityType.Equity: 140 switch (market) 142 case Market.USA: 143 equityFee = new EquityFee(Currencies.USD, feePerShare: 0.005m, minimumFee: 1, maximumFeeRate: 0.005m); 146 case Market.India: 147 equityFee = new EquityFee(Currencies.INR, feePerShare: 0.01m, minimumFee: 6, maximumFeeRate: 20); 151 var tradeValue = Math.Abs(order.GetValue(security)); 154 var tradeFee = equityFee.FeePerShare * quantity; // 每股费率 × 股数 158 var maximumPerOrder = equityFee.MaximumFeeRate * tradeValue; // 单笔上限 159 if (tradeFee < equityFee.MinimumFee) ... // 不低于最低费

IB 美股规则:每股 0.005 美元、最低 1 美元/笔、最高不超过交易额的 0.5%——典型的"按股数阶梯计费 + 兜底"。印度市场不同(每股 0.01 卢比、最低 6 卢比、最高 20 卢比封顶)。FX 走"按交易额百分比 + 最低单笔费"(L94-101),期权走"按合约数 × 函数"(L103-114),期货按合约 + 交易所费(L116-136)。每种资产类型一套规则,这正是为什么需要专门的 FeeModel 而不是一个常量。

💡 钻取要点:期权行权在 IB 是免费的——InteractiveBrokersFeeModel.cs:76-86 显式判断 OrderType.OptionExerciseSecurityType.Option 直接返回 OrderFee.Zero(期货期权行权按期货费收)。这是实盘的真实规则在引擎里的精确复刻。

二、Slippage:滑点模型

Common/Orders/Slippage/ISlippageModel.cs:23(接口):

23 public interface ISlippageModel 28 decimal GetSlippageApproximation(Security asset, Order order); // 返回绝对滑点金额

ConstantSlippageModel.cs:24(按固定百分比滑点):

24 public class ConstantSlippageModel : ISlippageModel 31 public ConstantSlippageModel(decimal slippagePercent) // 0~1 之间 39 public decimal GetSlippageApproximation(Security asset, Order order) 41 var lastData = asset.GetLastData(); 42 if (lastData == null) return 0; 44 return lastData.Value * _slippagePercent; // 当前价 × 滑点比例

Slippage/ 下五个实现:NullSlippageModel(默认,返回 0,不滑点)、ConstantSlippageModel(固定比例)、VolumeShareSlippageModel(按成交量份额,大单滑点多——回测机构策略常用)、MarketImpactSlippageModel(市场冲击模型)、AlphaStreamsSlippageModel

关键在于滑点何时被调用——回顾上一节 FillModel.InternalMarketFill 的 L321:var slip = asset.SlippageModel.GetSlippageApproximation(asset, order),然后买单 FillPrice += slip、卖单 FillPrice -= slip。即滑点是在 Fill 模型算出"理论成交价"之后,再叠加一个不利方向的偏移。回测默认 NullSlippageModel(不滑点,偏乐观),实盘滑点真实存在(尤其大单和流动性差的标的)。

三、TimeInForce:有效期

Common/Orders/TimeInForce.cs:28(抽象基类,同时实现 ITimeInForceHandler):

28 public abstract class TimeInForce : ITimeInForceHandler 33 public static readonly TimeInForce GoodTilCanceled = new GoodTilCanceledTimeInForce(); // GTC 默认 38 public static readonly TimeInForce Day = new DayTimeInForce(); // Day 当日 43 public static Func<DateTime, TimeInForce> GoodTilDate => // GTD 指定日期 (DateTime expiry) => new GoodTilDateTimeInForce(expiry); 51 public abstract bool IsOrderExpired(Security security, Order order); // 订单是否过期 60 public abstract bool IsFillValid(Security security, Order order, OrderEvent fill); // 成交是否有效

TimeInForces/ 下三个实现:GoodTilCanceledTimeInForce(永不过期,IsOrderExpired 恒 false)、DayTimeInForce(当日收盘过期)、GoodTilDateTimeInForce(指定日期的市场收盘过期)。看 GTD 的过期判定(GoodTilDateTimeInForce.cs:54):

54 public override bool IsOrderExpired(Security security, Order order) 61 switch (order.SecurityType) 63 case SecurityType.Forex: 64 case SecurityType.Cfd: 68 expired = time.ConvertToUtc(...) >= GetForexOrderExpiryDateTime(order); // FX:纽约 17 点 71 case SecurityType.Crypto: 74 expired = time.Date > Expiry.Date; // 币:过期日次日零点 77 default: // 股票/期权/期货 84 expired = time >= exchangeHours.GetLastDailyMarketClose(Expiry.Date, false); // 当日收盘

不同资产类型的"当日"定义不同:FX 用纽约时间 17 点(全球 FX 日切),币种用日历日零点,股票/期权/期货用市场收盘。IsOrderExpiredIsFillValid 由 TransactionHandler 在每次扫描订单时调用——过期订单会被撤销,无效成交会被拒绝。

💡 钻取要点:OrderProperties.cs:42 默认 TimeInForce = GoodTilCanceled,即所有订单默认"一直挂着直到成交或撤销"。要改成 Day 或 GTD,需在 DefaultOrderProperties 或单笔订单的 orderProperties 参数里设置。注意 QCAlgorithm.Trading.cs:1735InvalidateGoodTilDateTimeInForce——MOO(开盘市价)和 MOC(收盘市价)不支持 GTD,会被强制重置为 GTC。

四、IBrokerageModel:统一封装

上述 Fee/Slippage/Fill/TimeInForce 都是"单个维度"的模型,IBrokerageModel(Common/Brokerages/IBrokerageModel.cs:34)把它们打包成"一家券商的完整画像":

34 public interface IBrokerageModel 39 AccountType AccountType { get; } // 现金/保证金账户 48 decimal RequiredFreeBuyingPowerPercent { get; } // 留存购买力比例 56 IReadOnlyDictionary<SecurityType, string> DefaultMarkets { get; } // 默认市场 69 bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message); // 能否下单 79 bool CanUpdateOrder(Security, Order, UpdateOrderRequest, out BrokerageMessageEvent); // 能否改单 91 bool CanExecuteOrder(Security security, Order order); // 能否执行(回测模拟用) 105 decimal GetLeverage(Security security); // 杠杆 119 IFillModel GetFillModel(Security security); // 成交模型 126 IFeeModel GetFeeModel(Security security); // 手续费模型 133 ISlippageModel GetSlippageModel(Security security); // 滑点模型 140 ISettlementModel GetSettlementModel(Security security); // 结算模型(T+N) 147 IMarginInterestRateModel GetMarginInterestRateModel(Security); // 保证金利率 163 IBuyingPowerModel GetBuyingPowerModel(Security security); // 购买力模型 178 IShortableProvider GetShortableProvider(Security security); // 做空提供商

一个接口管"这家券商怎么对待每种订单"。DefaultBrokerageModel.cs:222 是默认实现:

222 public virtual IFillModel GetFillModel(Security security) 224 switch (security.Type) 226 case SecurityType.Equity: return new EquityFillModel(); 228 case SecurityType.FutureOption: return new FutureOptionFillModel(); 230 case SecurityType.Future: return new FutureFillModel(); 234 default: return new ImmediateFillModel(); // 其他用立即成交 252 public virtual IFeeModel GetFeeModel(Security security) 258 case SecurityType.Forex: case SecurityType.Crypto: ... return new ConstantFeeModel(0m); 264 case SecurityType.Equity: case SecurityType.Option: ... return new InteractiveBrokersFeeModel(); 281 public virtual ISlippageModel GetSlippageModel(Security security) 283 return NullSlippageModel.Instance; // 默认不滑点

InteractiveBrokersBrokerageModel 等子类会重写 GetFeeModel 返回 InteractiveBrokersFeeModel、重写 GetFillModel 返回带 IB 特定规则(如涨跌停)的 Fill 模型。用户在算法里一行设置(QCAlgorithm.cs:1353):

1353 public void SetBrokerageModel(BrokerageName brokerage, AccountType accountType = AccountType.Margin) 1355 SetBrokerageModel(Brokerages.BrokerageModel.Create(Transactions, brokerage, accountType)); 1364 public void SetBrokerageModel(IBrokerageModel model) 1366 BrokerageModel = model; 1371 var brokerageSecurityInitializer = new BrokerageModelSecurityInitializer(model, SecuritySeeder.Null);

BrokerageModelSecurityInitializer 会在每个 Security 添加时,自动给它配 FillModel/FeeModel/SlippageModel/SettlementModel/BuyingPowerModel。这就是"一行 SetBrokerageModel,全套模型换好"的机制——回测想模拟 IB,就 SetBrokerageModel(BrokerageName.InteractiveBrokers);想模拟币安,就 SetBrokerageModel(BrokerageName.Binance)

五、OptionExercise:期权行权特殊处理

期权行权不是普通成交——它产生标的资产的买卖。Common/Orders/OptionExercise/IOptionExerciseModel.cs:25:

25 public interface IOptionExerciseModel 34 IEnumerable<OrderEvent> OptionExercise(Option option, OptionExerciseOrder order); // 返回多个事件

行权返回多个 OrderEvent(而不是一个):一个"期权合约归零"事件 + 一个"按行权价买卖标的"事件。看-call 行权:持有 1 张 IBM 看涨期权(行权价 150),行权后产生"买入 100 股 IBM @ 150"事件 + "期权合约数量归零"事件。DefaultExerciseModel.cs 是默认实现,会判断"实值则行权产生标的持仓、虚值则放弃"。注意 OptionExerciseOrder.GetValueImpl(上一节 Order.cs):行权价值按 option.GetExerciseQuantity(Quantity) * StrikePrice 算,不是按期权市场价。

六、成交对 Portfolio 的影响

每次成交(OrderEvent)回流后,引擎调 SecurityPortfolioManager.ProcessFill(orderEvent) 更新账本,影响三个维度:

  1. 持仓更新:Holdings.SetHoldings(avgPrice, newQuantity) 重算平均成本与持仓数量。买入加仓、卖出减仓、反向平仓再开仓。
  2. 现金扣减:CashBook[quoteCurrency].AddAmount(-(fillPrice * fillQuantity + fee))——成交金额 + 手续费从对应币种现金扣除。卖出现金增加,买入现金减少。多币种时还涉及汇率换算。
  3. 盈亏记账:平仓部分(Math.Min(|fillQuantity|, |holdings|))产生已实现盈亏 (fillPrice - avgCost) * closedQuantity * sign,记入 Holdings.Profit;未平仓部分更新浮动盈亏 LastTradeProfitTradeBuilder 同时记录这笔 Trade 的盈亏与最大回撤。

这三步合起来就是"成交→入账"的闭环。回测里这套在 Portfolio.ProcessFill 同步完成;实盘里由券商回报触发 AccountChanged 事件,BrokerageTransactionHandler 同步刷新 Portfolio 持仓与现金(以券商实际余额为准)。

七、回测模拟成交 vs 实盘真实成交(差异总结)

把本章三节合起来,回测与实盘的成交差异全貌:

维度 回测(BacktestingTransactionHandler) 实盘(BrokerageTransactionHandler)
成交价来源 Fill 模型用历史 bar 算(理论价) 交易所真实撮合(实际价)
成交时机 BacktestingBrokerage.Scan 同步扫描 券商异步推送 OrderEvent
滑点 默认 NullSlippageModel(可配置) 真实存在(无法关闭)
手续费 IBrokerageModel.GetFeeModel 模拟 券商实际扣费
部分成交 默认全成交(可配 PartialFillModel) 常见(流动性不足时)
拒单 极少(除非 PreOrderChecks 不过) 常见(资金不足/限价异常/券商规则)
延迟 零(同步) 有(网络 + 撮合)
确定性 完全确定(相同数据相同结果) 不确定(受市场环境影响)
时钟驱动 数据时间(Synchronizer) 墙钟(LiveSynchronizer + DateTime.UtcNow)

这张表呼应第 3 章的"回测实盘统一"——引擎主体对两者零感知,差异完全收敛到 TransactionHandler 的两套实现 + config.json 的 environment 字段。回测的"乐观假设"(零滑点、全成交、零延迟)是回测收益通常高于实盘的主因,生产级回测建议配置真实的 Slippage/Fee 模型与 PartialFill 行为以贴近实盘。

本节要点回顾

  1. Fees:IFeeModel.GetOrderFee 返回 OrderFee(金额+币种);IB 美股每股 0.005 美元/最低 1 美元/最高 0.5%;期权行权免费;30+ 券商专属费率模型。
  2. Slippage:ISlippageModel.GetSlippageApproximation 返回绝对滑点金额,在 Fill 模型算出理论价后叠加(买单加、卖单减);默认 NullSlippageModel 不滑点,可选 Constant/VolumeShare/MarketImpact。
  3. TimeInForce:GTC(永不过期,默认)/Day(当日收盘)/GTD(指定日期);不同资产类型"当日"定义不同(FX 纽约 17 点、币种零点、股票收盘);MOO/MOC 不支持 GTD。
  4. IBrokerageModel 统一封装 Fill/Fee/Slippage/Settlement/BuyingPower/MarginInterestRate/Shortable 七大模型,SetBrokerageModel(BrokerageName.XXX) 一行设置,经 BrokerageModelSecurityInitializer 下放到每个 Security。
  5. OptionExercise 返回多个 OrderEvent(期权归零 + 标的买卖),由 IOptionExerciseModel 处理;实值行权产生标的持仓,虚值放弃。
  6. 成交对 Portfolio 影响:持仓更新(SetHoldings)、现金扣减(成交金额+手续费)、盈亏记账(已实现 + 浮动),TradeBuilder 同步记录 Trade 盈亏与最大回撤。
  7. 回测 vs 实盘:成交价来源(模型 vs 交易所)、时机(同步扫描 vs 异步推送)、滑点(可关 vs 真实)、拒单(极少 vs 常见)、确定性(完全确定 vs 不确定)八大维度差异;引擎主体对两者零感知,差异收敛到两套 TransactionHandler。

本章完。下一章(第 9 章)钻取 Algorithm Framework 五层——把本章的订单与上章的指标组织成可组装的策略框架(Alpha/Risk Management/Portfolio/Execution/Universe Selection)。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U