滑点模型:从定义到实战

做量化交易的朋友,一定对「滑点」这个词不陌生。说白了,滑点就是你下单时看到的那个价格,和实际成交价格之间的差距。我刚开始做回测时,总觉得滑点是个小问题,结果实盘一跑,收益直接少了一大截。嗯,这玩意儿真不能忽视。

滑点的定义与成因

滑点,英文叫 Slippage。它不是一个固定的数值,而是一个动态的、由市场微观结构决定的成本。

为什么会发生滑点?我总结下来,主要有三个原因:

  • 流动性不足:你想买100手,但卖一价只有10手。剩下的90手,就得用更高的价格去成交。我在做小市值策略时,经常遇到这种情况。
  • 市场冲击:你的订单本身就在推动价格。尤其是大单,一进去就把价格打上去了。这就像往平静的湖面扔一块石头,水花越大,滑点越大。
  • 延迟与抢跑:从你点击下单到交易所确认,中间有几十毫秒的延迟。这期间,价格可能已经变了。更糟的是,高频交易者可能嗅到你的订单,提前抢跑。

核心观点:滑点不是误差,而是交易中真实存在的成本。忽略它,你的回测就是自欺欺人。

固定滑点模型

这是最简单的模型。我早期做回测时,就喜欢用这个。它假设每次交易,滑点都是一个固定的数值。

比如,你设定固定滑点为0.01元。那么买入时,实际成交价 = 信号价 + 0.01元。卖出时,实际成交价 = 信号价 - 0.01元。

# 固定滑点模型示例
def apply_fixed_slippage(price, side, slippage=0.01):
    """
    side: 'buy' 或 'sell'
    """
    if side == 'buy':
        return price + slippage
    else:
        return price - slippage

# 使用示例
signal_price = 10.00
buy_price = apply_fixed_slippage(signal_price, 'buy')
print(f"买入成交价: {buy_price}")  # 输出: 10.01

这个模型的好处是简单、直观。但缺点也很明显——它不区分市场环境。在流动性好的时候,你可能多付了成本;在流动性差的时候,你又少算了成本。我个人习惯在快速验证策略时用这个,但正式回测时,我会换更精细的模型。

百分比滑点模型

比固定滑点稍微合理一点。它假设滑点与价格成正比。比如,设定滑点为0.1%。那么价格越高,滑点绝对值越大。

这个模型更贴近现实。你想想看,100块的股票和10块的股票,同样的0.1%滑点,绝对值差了10倍。这符合市场规律——高价股通常流动性更好,但绝对滑点也更大。

# 百分比滑点模型示例
def apply_percent_slippage(price, side, slippage_rate=0.001):
    """
    slippage_rate: 滑点比例,0.001 表示 0.1%
    """
    slippage = price * slippage_rate
    if side == 'buy':
        return price + slippage
    else:
        return price - slippage

# 使用示例
signal_price = 100.00
buy_price = apply_percent_slippage(signal_price, 'buy')
print(f"买入成交价: {buy_price}")  # 输出: 100.10

我的经验:百分比滑点模型适合大多数股票策略。但如果你做的是期货或外汇,杠杆高、波动大,这个模型可能不够用。

基于波动率的滑点模型

这才是真正有点技术含量的模型。它认为滑点不是固定的,而是跟市场波动率正相关。市场越动荡,滑点越大。

为什么?因为波动率高的时候,买卖价差会扩大,流动性提供者会要求更高的风险补偿。我曾经在VIX指数飙升的时候做回测,用固定滑点模型,结果实盘亏得一塌糊涂。后来改用波动率模型,才把问题暴露出来。

常见的做法是用ATR(平均真实波幅)或标准差来估计滑点。

# 基于波动率的滑点模型示例
import numpy as np

def calculate_atr(high, low, close, period=14):
    """计算ATR"""
    tr = np.maximum(high - low, 
                    np.abs(high - close.shift(1)),
                    np.abs(low - close.shift(1)))
    atr = tr.rolling(window=period).mean()
    return atr

def apply_volatility_slippage(price, side, atr, multiplier=0.5):
    """
    atr: 当前ATR值
    multiplier: 滑点乘数,通常0.3~0.8
    """
    slippage = atr * multiplier
    if side == 'buy':
        return price + slippage
    else:
        return price - slippage

# 使用示例
current_atr = 0.25  # 假设当前ATR为0.25
signal_price = 50.00
buy_price = apply_volatility_slippage(signal_price, 'buy', current_atr)
print(f"买入成交价: {buy_price}")  # 输出: 50.125

避坑指南:我曾经犯过一个错误——用未来数据计算ATR。回测时,ATR用的是当天的数据,但实际交易中,你只能用到昨天的数据。记住,一定要用前向数据,别偷看未来。

滑点对回测的影响

这个问题,我多说几句。滑点对回测的影响,比你想象的要大得多。

首先,它会直接降低收益率。一个年化20%的策略,加上0.1%的滑点,可能就变成15%了。如果策略交易频率高,比如每天换仓,那影响更大。

其次,它会改变策略的排序。有些策略在无滑点回测中表现很好,但加上滑点后,可能还不如一个稳健的低频策略。我见过太多人,拿着一个高频策略的漂亮回测曲线,结果实盘直接崩盘。

最后,它会暴露策略的脆弱性。如果一个策略对滑点极其敏感,那说明它本质上是在「吃」流动性,而不是在「赚」alpha。这种策略,实盘很难存活。

滑点模型 适用场景 优点 缺点
固定滑点 快速验证、流动性极好的品种 简单、计算快 不区分市场环境
百分比滑点 股票、ETF等 与价格挂钩,相对合理 忽略波动率变化
波动率滑点 期货、期权、高波动市场 动态调整,更贴近现实 需要计算波动率指标

我的建议:回测时,至少用两种滑点模型做对比。一种保守的(比如高波动率模型),一种激进的(比如低固定滑点)。看看你的策略在两种假设下,表现差距有多大。如果差距超过20%,那你要小心了。

嗯,滑点模型这部分,我就讲这么多。记住一句话:滑点不是成本,它是市场对你交易行为的反馈。你越急、越大、越频繁,它就越狠。尊重它,你的回测才有意义。

专注资料整理