3. 滑点建模:基于买卖价差(Bid-Ask Spread)的滑点模型

做量化交易的朋友,应该都遇到过这种情况:策略回测跑得漂漂亮亮,年化收益看着挺美,结果一上实盘,收益直接打对折。问题出在哪?

十有八九,是滑点没算对。

滑点这东西,说白了就是你的成交价和预期价之间的差距。我刚开始做高频策略那会儿,就吃过这个亏。回测里每笔交易只算1个tick的滑点,结果实盘跑起来,流动性差的品种直接滑出去3-4个tick,那叫一个心疼。

今天咱们就聊聊滑点建模里最基础、也最实用的一种方法——基于买卖价差的滑点模型。

3.1 买卖价差:滑点的核心来源

先想一个问题:你下单的时候,为什么成交价总是不太理想?

原因很简单。市场上有买一价和卖一价,中间差着一段距离。你想买,就得吃卖一价;你想卖,就得吃买一价。这一来一回,价差就是你的成本。

举个例子:

  • 买一价:10.00元
  • 卖一价:10.02元
  • 价差:0.02元

如果你用市价单买入,成交价就是10.02元。比买一价贵了0.02元。这0.02元,就是滑点。

核心公式:

滑点成本 = 买卖价差 × 交易方向系数

买入时:滑点 = 卖一价 - 买一价 = 价差

卖出时:滑点 = 买一价 - 卖一价 = -价差

嗯,这里要注意:滑点不是固定的。不同品种、不同时间、不同市场环境下,价差变化很大。我见过最夸张的,是某小盘股在开盘瞬间价差能拉到1%以上。

3.2 价差模型:从简单到复杂

实际建模时,我们不能直接用实时价差——因为回测时你拿不到未来的数据。我们需要一个模型来估算。

3.2.1 固定价差模型

最简单粗暴的方法:给所有交易设定一个固定价差。

# 固定价差模型
def fixed_spread_model(price, spread_bps=2.0):
    """
    price: 当前价格
    spread_bps: 价差,单位基点(1bp = 0.01%)
    """
    spread = price * spread_bps / 10000
    return spread

这个方法的好处是简单。坏处嘛,太粗糙了。我早期做回测时就用这个,结果发现实盘表现和回测差距很大。后来才明白,不同时间段价差波动太剧烈了。

3.2.2 基于历史均值的价差模型

稍微改进一点:用过去N天的平均价差来估算。

# 历史均值价差模型
def historical_avg_spread_model(ticker, lookback_days=20):
    """
    用过去20天的平均价差作为当前价差估计
    """
    historical_spreads = get_historical_spreads(ticker, lookback_days)
    avg_spread = np.mean(historical_spreads)
    return avg_spread

这个模型比固定价差靠谱一些。但有个问题:它假设价差是平稳的。实际上,价差在开盘、收盘、重大新闻发布时,会突然放大。

3.2.3 基于波动率的价差模型

我个人比较喜欢这个模型。因为价差和波动率高度相关——波动越大,做市商越不敢报价,价差自然就宽了。

# 波动率驱动的价差模型
def volatility_spread_model(price, volatility, base_spread_bps=1.0):
    """
    price: 当前价格
    volatility: 当前波动率(年化)
    base_spread_bps: 基础价差(基点)
    """
    # 波动率越高,价差越大
    vol_factor = 1 + 2 * (volatility / 0.2)  # 假设基准波动率20%
    spread_bps = base_spread_bps * vol_factor
    spread = price * spread_bps / 10000
    return spread

你想想看,为什么这个模型更合理?因为当市场剧烈波动时,你的交易成本会显著上升。如果回测时没考虑这一点,策略在震荡市里表现好,一到波动大的行情就崩了。

3.3 滑点模型实战:完整实现

好了,理论说完了,咱们直接上代码。下面是一个完整的滑点模型实现,包含了价差估算和滑点计算。

import numpy as np
import pandas as pd

class SpreadSlippageModel:
    """
    基于买卖价差的滑点模型
    """
    def __init__(self, model_type='volatility', base_spread_bps=1.5):
        self.model_type = model_type
        self.base_spread_bps = base_spread_bps
        self.historical_spreads = []
        
    def estimate_spread(self, price, volatility=None):
        """估算当前价差"""
        if self.model_type == 'fixed':
            spread = price * self.base_spread_bps / 10000
            
        elif self.model_type == 'volatility':
            if volatility is None:
                volatility = 0.2  # 默认20%波动率
            vol_factor = 1 + 2 * (volatility / 0.2)
            spread_bps = self.base_spread_bps * vol_factor
            spread = price * spread_bps / 10000
            
        elif self.model_type == 'historical':
            if len(self.historical_spreads) == 0:
                spread = price * self.base_spread_bps / 10000
            else:
                avg_spread_bps = np.mean(self.historical_spreads)
                spread = price * avg_spread_bps / 10000
                
        return spread
    
    def calculate_slippage(self, price, side, volatility=None):
        """
        计算滑点成本
        side: 'buy' 或 'sell'
        """
        spread = self.estimate_spread(price, volatility)
        
        if side == 'buy':
            # 买入时,成交价 = 卖一价 = 当前价 + 一半价差
            slippage = spread / 2
        else:
            # 卖出时,成交价 = 买一价 = 当前价 - 一半价差
            slippage = -spread / 2
            
        return slippage
    
    def update_historical_spreads(self, new_spread_bps):
        """更新历史价差数据"""
        self.historical_spreads.append(new_spread_bps)
        # 只保留最近1000个数据
        if len(self.historical_spreads) > 1000:
            self.historical_spreads.pop(0)

# 使用示例
model = SpreadSlippageModel(model_type='volatility', base_spread_bps=1.5)

# 假设当前价格100元,年化波动率30%
price = 100.0
volatility = 0.3

buy_slippage = model.calculate_slippage(price, 'buy', volatility)
sell_slippage = model.calculate_slippage(price, 'sell', volatility)

print(f"买入滑点: {buy_slippage:.4f} 元")
print(f"卖出滑点: {sell_slippage:.4f} 元")
print(f"单边交易成本: {abs(buy_slippage)/price*10000:.2f} 基点")

小技巧:

实际应用中,我建议把滑点模型和订单簿数据结合起来。如果你有Level 2数据,可以直接用实时的买一卖一价差。没有的话,用波动率模型也够用。

3.4 滑点对策略收益的影响

咱们做个简单的模拟,看看滑点到底能吃掉多少收益。

交易频率 年化交易次数 单次滑点(基点) 年化滑点成本 对收益的影响
低频(月频) 12 2 0.024% 几乎可忽略
中频(周频) 50 2 0.1% 轻微影响
高频(日频) 250 2 0.5% 显著影响
超高频(分钟级) 5000 2 10% 致命影响

看到没?超高频策略如果忽略滑点,回测收益可能虚高10%以上。我曾经帮一个朋友优化他的高频策略,回测年化30%,加上滑点模型后直接降到18%。他一开始还不信,后来实盘跑了一个月,发现确实如此。

避坑指南:

我曾经犯过一个错误:在回测中只算了单边滑点。实际上,一次完整的交易(买入+卖出)需要算两次滑点。很多新手在这里栽跟头,回测收益看着不错,实盘一算,双向滑点直接吃掉一大块利润。

3.5 知识体系总览

下面这张图,把滑点建模的核心逻辑串起来了:

滑点建模知识体系 买卖价差 (Bid-Ask Spread) 固定价差模型 历史均值模型 波动率驱动模型 滑点 = 价差 × 交易方向系数 回测滑点修正 实盘成本预估 策略优化

从图上可以看得很清楚:买卖价差是滑点的根源,三种模型各有适用场景。我个人建议,做中低频策略用历史均值模型就够了,高频策略一定要上波动率模型。

好了,滑点建模的核心内容就这些。记住一句话:回测里省下的滑点,实盘里都会加倍还回来。把滑点算准了,你的策略收益才真实可信。

核心要点回顾:

  • 买卖价差是滑点的直接来源
  • 三种模型:固定、历史均值、波动率驱动
  • 波动率模型最贴近真实市场
  • 高频策略对滑点极其敏感
  • 别忘了算双向滑点

公众号:蓝海资料掘金营,微信deep3321