2095 字
10 分钟

交易成本建模:佣金、滑点与冲击成本

本课目标:把 A 股交易成本拆成显性与隐性两层,学会在回测中合理建模,并用敏感性测试判断策略对成本的耐受度。

为什么成本建模值得单独一课#

很多在回测里光鲜的策略,实盘后表现平平,问题往往不在信号,而在成本。回测里一行 ret - 0.001 的粗糙假设,掩盖了真实交易中一整套摩擦:佣金、税费、买卖价差、排队等待、以及你的订单本身对价格的推动。策略换手越高,这层摩擦越致命——信号带来的毛收益是不确定的,成本却是确定要付的。成本建模的目标不是精确到分毫,而是保证回测的乐观偏差可控:宁可把成本估得略重,让活下来的策略在实盘里有惊喜,也不要估得太轻,让每个上线的策略都在还回测欠下的债。本文为研究方法讨论,不构成投资建议。

显性成本:看得见的账单#

A 股股票交易的显性成本主要有三块:

项目方向说明
佣金双向券商收取,费率与最低收费以券商实际为准,通常有单笔最低门槛
过户费双向按成交金额的固定比例收取,以实际执行标准为准
印花税仅卖出按卖出金额的比例征收,具体税率以现行规定为准

两点容易被忽略:一是佣金的单笔最低收费,对小资金、拆单频繁的策略影响不小;二是印花税只在卖出端征收,意味着买卖两边的成本不对称,回测里应分开建模而不是简单取平均。ETF 交易通常不涉及印花税,这也是高换手策略偏好 ETF 的原因之一——具体品种的费用仍以交易所和券商公示为准。

def explicit_cost(amount, side, commission=2.5e-4, min_fee=5.0,
transfer=1e-5, stamp=5e-4):
fee = max(amount * commission, min_fee) + amount * transfer
if side == 'sell':
fee += amount * stamp
return fee

以上费率仅为示意参数,实际数值请以券商与交易所公布为准。

滑点:成交价与决策价的差距#

滑点是你决策时看到的价格与最终成交价之间的差。来源至少有四类:

  • 延迟:信号在 T 日收盘后生成,实际 T+1 才能成交,隔夜价格已经变了。
  • 价差:市价单要跨过买卖价差才能立即成交,流动性差的品种价差更宽。
  • 排队:限价单可能排不到,被迫追价或放弃。
  • 同向拥挤:你的信号如果和大量资金同源(比如都用公开的动量信号),大家会在同一时点挤同一个门。

建模上,入门做法是固定比例滑点(如单边万分之几);进一步可以让滑点与波动率挂钩——高波动日的滑点显著更大,用当日振幅或 ATR 的一个比例来近似,比固定值更贴近现实。

# 波动率挂钩滑点:以当日振幅的一个比例作为单边滑点
day_range = (high - low) / close # 当日振幅
slippage = np.clip(0.1 * day_range, 1e-4, 5e-3)
buy_price = ref_price * (1 + slippage)
sell_price = ref_price * (1 - slippage)

订单类型也影响滑点的形态:市价单的滑点是”确定成交、价格不确定”,限价单则是”价格确定、成交不确定”。回测里如果假设限价单总能成交,等于免费拿走了价差收益;诚实的做法是要求价格穿过限价(例如买单要求当日最低价低于限价)才判定成交,否则视为未成交并按策略逻辑处理落单。

如果有分钟级数据,还可以事后度量真实滑点:把决策时刻的参考价与模拟成交价之差逐笔记录下来,形成滑点分布,再回头校准回测参数——让成本假设从”拍脑袋”进化为”有数据支撑”。

冲击成本与参与率约束#

当你的订单相对市场成交量不再是”沧海一粟”,订单本身就会推动价格,这就是冲击成本。经验上冲击成本随参与率非线性上升:买得越急、占当日成交量比例越高,付出的价格让步越大。

对个人研究者而言,精确估计冲击成本既无必要也无数据条件,回测中更实用的做法是直接施加参与率约束,把”买不进那么多”变成硬规则:

max_participation = 0.05 # 单日成交不超过该股当日成交量的 5%
tradable = volume * max_participation * price
fill = np.minimum(target_amount, tradable) # 超出部分顺延到次日

这条约束还能顺带回答一个关键问题:策略的容量有多大。把资金规模逐步放大重跑回测,当参与率约束开始频繁触发、收益明显衰减时,就摸到了策略容量的边界。

不可成交状态:涨跌停与停牌#

A 股回测必须处理两类”想交易但交易不了”的状态:

  • 涨跌停:一字涨停时买单大概率排不到,一字跌停时卖单同理。保守处理是:涨停禁止买入、跌停禁止卖出;更细一点可以用”当日是否打开涨跌停”来判断部分成交。
  • 停牌:停牌期间既不能买也不能卖,持仓只能挂着。回测数据里停牌日往往没有行情记录,如果简单地把持仓按最后价格估值并允许交易,会高估策略的灵活性。

这两条规则对小市值、高波动股票上的策略影响尤其大——很多”涨停板买入”类信号在加入可成交性约束后收益大幅缩水,这正是约束在起作用。

工程实现上,建议在数据层预先算好每日的”可买""可卖”两个布尔标记(综合涨跌停、停牌、上市状态),执行模块只需查表。这样规则集中在一处维护,将来交易规则理解加深时(例如不同板块的涨跌幅限制不同),只改标记生成逻辑即可,所有策略自动受益。

成本敏感性测试:策略的压力测验#

最后一步是把成本当成一个可调参数做压力测试:

  1. 以基准成本假设跑出净值。
  2. 将单边总成本按梯度放大(例如 0.5 倍、1 倍、2 倍、4 倍),逐档重跑。
  3. 画出年化收益随成本倍数变化的曲线,找到收益归零的”盈亏平衡成本”。

盈亏平衡成本与真实成本之间的距离,就是策略的安全边际。如果基准成本稍微上浮策略就不赚钱,说明策略本质上在”刀口上舔血”,对执行质量的要求高得不现实。同时算一下策略的年换手率——年化收益除以年换手率得到的”单位换手收益”,是衡量信号强度能否覆盖摩擦的直观指标。

本课小结#

  • 显性成本要分买卖两端建模:印花税只在卖出端,佣金有单笔最低收费,费率以券商实际为准。
  • 滑点来自延迟、价差、排队和拥挤,可从固定比例起步,进阶为与波动率挂钩。
  • 用参与率约束近似冲击成本,同时顺带估计策略容量。
  • 涨停不能买、跌停不能卖、停牌不能动,这三条规则必须写进撮合逻辑。
  • 用成本梯度压力测试找到盈亏平衡成本,评估策略的安全边际。

动手练习:给你上一课实现的最小回测器加上完整成本模块:分端佣金与印花税、固定比例滑点、5% 参与率约束、涨跌停禁止成交。然后对任一策略做成本敏感性测试,画出年化收益随成本倍数(0.5/1/2/4 倍)变化的曲线,并计算它的盈亏平衡成本。