← 返回文章列表 执行

把回测成本模型校准到真实成交回报

从券商流水反推冲击成本系数,让回测和实盘的差距落在可解释范围。

固定 bp 不够用

回测里写死「单边 8bp」的问题在于它和交易量无关。同一个策略资金从 500 万涨到 5000 万,换手率不变,但实际成本可能翻三倍。一旦策略规模变化,回测就彻底失效了。

业界通用的形式是平方根律的变体:成本随委托量占日均成交额的比例呈幂函数上升。

def slippage_bps(q, adv, sigma, k=0.9, alpha=0.6):
    """冲击成本估计(单位:bp)

    q     : 本次委托股数
    adv   : 该标的近 20 日平均成交股数
    sigma : 该标的日波动率
    k     : 冲击系数,需要用真实成交回报校准
    alpha : 幂次,股票通常落在 0.5 ~ 0.7
    """
    participation = q / adv
    return k * sigma * 1e4 * participation ** alpha

用流水反推 k

k 是唯一需要你自己校准的参数。做法是把每一笔真实成交和它的决策价(下单那一刻的中间价)对比,算出实际滑点,然后对模型做最小二乘拟合。

import numpy as np

fills['real_bps'] = (fills['fill_px'] / fills['decision_px'] - 1) * 1e4
fills.loc[fills['side'] == 'sell', 'real_bps'] *= -1

x = fills['sigma'] * 1e4 * (fills['qty'] / fills['adv']) ** 0.6
y = fills['real_bps']

k_hat = float(np.linalg.lstsq(x.values[:, None], y.values, rcond=None)[0][0])
resid = y - k_hat * x
print(f'k = {k_hat:.3f}, 残差标准差 = {resid.std():.1f} bp')

样本量的最低要求

至少要有几百笔覆盖不同参与率的成交,且不能全来自同一段行情。如果你的流水里参与率都挤在 0.5% 附近,拟合出来的 k 只在那个点附近有效,外推到 5% 会严重低估。

校准之后再看回测

把校准好的成本函数放回回测,通常会看到年化收益掉 3 到 8 个点,换手越高掉得越多。这个落差不是坏消息——它把「策略能装多少钱」这个问题变成了可计算的:解 k * sigma * (q/adv)^0.6 = 预期收益,得到的 q 就是容量上界。

下一篇讲怎么在这个成本函数下反过来优化换手,也就是把交易成本写进目标函数。

成本模型冲击成本执行
下一篇

IC、IR 与分组单调性:因子体检的最小集合