4、套利信号识别:阈值设定方法、信号生成逻辑、回测框架搭建、策略绩效评估

各位同学,欢迎来到第四讲。前面我们聊了集合竞价和连续竞价的价格差异是怎么产生的,也看了数据怎么清洗。今天这节,是真正动手干活的时候了——我们要把那些藏在数据里的套利机会,一个一个揪出来。

说白了,套利信号识别就是回答三个问题:什么时候该进场?什么时候该离场?这笔交易到底赚没赚钱? 我做了这么多年量化,见过太多人把精力花在复杂的模型上,结果连最基本的阈值都没设对。嗯,咱们今天就从最基础、也最关键的阈值设定开始。

核心逻辑: 集合竞价与连续竞价之间的价差,本质上是一个均值回复过程。当价差偏离到一定程度,就会大概率回归。我们的任务就是找到那个“偏离到一定程度”的临界点。

4.1 阈值设定方法:别拍脑袋,要讲数据

阈值怎么设?我见过有人直接拍脑袋设个“价差超过0.5%就进场”。这种做法,说白了就是赌运气。真正靠谱的方法,得从历史数据里找规律。

我个人习惯用以下三种方法,你可以根据品种特性选一个:

  • 固定百分位法: 取过去N个交易日价差序列的90%或95%分位数作为阈值。比如,过去20天价差的95%分位数是0.8%,那价差超过0.8%就触发信号。
  • 移动标准差法: 用滚动窗口计算价差的均值和标准差,阈值 = 均值 ± k × 标准差。k通常取2或3。这个方法能自适应市场波动。
  • 动态分位数法: 结合时间衰减权重,近期数据权重更大。适合市场结构经常变化的品种。

我的经验: 对于流动性好的股票或ETF,我一般用移动标准差法,k值取2.5。为什么是2.5?不是2也不是3?因为我在回测中发现,2.5能在信号频率和胜率之间取得比较好的平衡。你可以根据自己的数据调参。

这里给出一段阈值计算的代码示例,用的是移动标准差法:

import pandas as pd
import numpy as np

def calculate_threshold(price_df, window=20, k=2.5):
    """
    计算动态阈值
    :param price_df: DataFrame,包含'call_auction'和'continuous'两列价格
    :param window: 滚动窗口大小
    :param k: 标准差倍数
    :return: 带阈值的DataFrame
    """
    df = price_df.copy()
    # 计算价差(百分比)
    df['spread'] = (df['call_auction'] - df['continuous']) / df['continuous'] * 100
    
    # 滚动均值和标准差
    df['mean'] = df['spread'].rolling(window=window).mean()
    df['std'] = df['spread'].rolling(window=window).std()
    
    # 上下阈值
    df['upper_threshold'] = df['mean'] + k * df['std']
    df['lower_threshold'] = df['mean'] - k * df['std']
    
    return df

注意: 滚动窗口的大小很关键。窗口太小,阈值会频繁跳动,产生大量假信号;窗口太大,阈值反应迟钝,错过行情。我个人建议从20个交易日开始试,然后根据回测结果调整。

4.2 信号生成逻辑:从数据到交易指令

阈值设好了,接下来就是生成交易信号。这一步其实不复杂,但容易踩坑。我曾经犯过一个错误——信号生成时没考虑交易成本,结果实盘一跑,利润全被手续费吃掉了。

信号生成的基本逻辑是这样的:

  1. 开仓信号: 当价差向上突破上阈值时,做空集合竞价、做多连续竞价(预期价差缩小);当价差向下突破下阈值时,做多集合竞价、做空连续竞价。
  2. 平仓信号: 当价差回归到均值附近(比如触及均值线或0轴)时,平掉所有头寸。
  3. 止损信号: 如果价差继续扩大,超过某个更极端的阈值(比如3倍标准差),强制平仓止损。

你想想看,如果只设开仓不设止损,遇到极端行情怎么办?我记得2020年3月那波波动,很多套利策略就是因为没设止损,价差一直不回,最后亏得很惨。

下面是一个完整的信号生成函数:

def generate_signals(df, entry_k=2.5, exit_k=0.5, stop_k=4.0):
    """
    生成交易信号
    :param df: 包含spread, upper_threshold, lower_threshold的DataFrame
    :param entry_k: 开仓阈值倍数
    :param exit_k: 平仓阈值倍数(相对于均值)
    :param stop_k: 止损阈值倍数
    :return: 带信号的DataFrame
    """
    df = df.copy()
    df['signal'] = 0  # 0: 无持仓, 1: 多头, -1: 空头
    
    # 计算平仓线和止损线
    df['exit_upper'] = df['mean'] + exit_k * df['std']
    df['exit_lower'] = df['mean'] - exit_k * df['std']
    df['stop_upper'] = df['mean'] + stop_k * df['std']
    df['stop_lower'] = df['mean'] - stop_k * df['std']
    
    position = 0
    for i in range(1, len(df)):
        # 开仓逻辑
        if position == 0:
            if df['spread'].iloc[i] > df['upper_threshold'].iloc[i]:
                position = -1  # 做空价差
            elif df['spread'].iloc[i] < df['lower_threshold'].iloc[i]:
                position = 1   # 做多价差
        
        # 平仓逻辑
        elif position == -1:
            if df['spread'].iloc[i] <= df['exit_upper'].iloc[i]:
                position = 0
            elif df['spread'].iloc[i] >= df['stop_upper'].iloc[i]:
                position = 0  # 止损
        
        elif position == 1:
            if df['spread'].iloc[i] >= df['exit_lower'].iloc[i]:
                position = 0
            elif df['spread'].iloc[i] <= df['stop_lower'].iloc[i]:
                position = 0  # 止损
        
        df.loc[df.index[i], 'signal'] = position
    
    return df

4.3 回测框架搭建:别让回测骗了你

回测框架,说白了就是一台时光机。你把策略放进去,它告诉你“如果过去这么干,能赚多少钱”。但这里有个大坑——过拟合。我见过太多人在回测里跑出漂亮的曲线,一上实盘就崩。

搭建回测框架,我建议至少包含以下几个模块:

  • 数据模块: 加载历史行情数据,包括集合竞价和连续竞价的价格、成交量、时间戳。
  • 信号模块: 调用上面写的信号生成函数,产生交易指令。
  • 执行模块: 模拟交易执行,考虑滑点和手续费。这里要注意,集合竞价的成交规则和连续竞价不一样,别搞混了。
  • 绩效模块: 计算收益率、夏普比率、最大回撤等指标。

下面是一个简化版的回测框架:

def backtest(df, initial_capital=100000, fee_rate=0.0003, slippage=0.0001):
    """
    回测主函数
    :param df: 带信号的DataFrame
    :param initial_capital: 初始资金
    :param fee_rate: 手续费率(双边)
    :param slippage: 滑点(百分比)
    :return: 回测结果
    """
    df = df.copy()
    df['position'] = df['signal'].shift(1)  # 信号滞后一期,模拟实际交易延迟
    df['position'].fillna(0, inplace=True)
    
    # 计算每笔交易的收益率
    df['return'] = df['position'] * df['spread'].diff() / 100
    
    # 扣除交易成本
    df['trade'] = df['position'].diff().abs()
    df['cost'] = df['trade'] * fee_rate * 2 + df['trade'] * slippage  # 双边手续费+滑点
    df['net_return'] = df['return'] - df['cost']
    
    # 计算净值曲线
    df['equity'] = initial_capital * (1 + df['net_return']).cumprod()
    
    return df

避坑指南: 我曾经在回测中忘记考虑“信号滞后一期”这个问题。实盘时发现,信号出来的时候价格已经变了,根本成交不了。记住:回测里一定要模拟真实交易中的延迟和滑点。

4.4 策略绩效评估:别只看收益率

很多人看策略好不好,第一眼就看收益率。嗯,这其实是个误区。收益率高不代表策略好,可能是运气好,也可能是风险太高。

我一般会从以下几个维度评估策略:

指标 计算公式 说明
年化收益率 (最终净值/初始净值)^(252/交易日数) - 1 衡量赚钱能力
夏普比率 (策略收益率 - 无风险利率) / 收益率标准差 衡量风险调整后收益,一般大于1算不错
最大回撤 净值从峰值到谷底的最大跌幅 衡量策略的最大亏损风险
胜率 盈利交易次数 / 总交易次数 衡量信号准确度
盈亏比 平均盈利 / 平均亏损 衡量单笔交易的盈亏平衡

这里有一个我自己的经验:对于套利策略,我更看重夏普比率最大回撤。为什么?因为套利本身就是赚取低风险的钱,如果回撤太大,说明策略逻辑可能有问题。

下面是一个绩效评估函数:

def evaluate_performance(df, risk_free_rate=0.03):
    """
    策略绩效评估
    :param df: 回测结果DataFrame
    :param risk_free_rate: 无风险利率(年化)
    :return: 绩效指标字典
    """
    equity = df['equity']
    daily_returns = df['net_return']
    
    # 年化收益率
    total_days = len(daily_returns)
    annual_return = (equity.iloc[-1] / equity.iloc[0]) ** (252 / total_days) - 1
    
    # 夏普比率
    excess_returns = daily_returns - risk_free_rate / 252
    sharpe = np.sqrt(252) * excess_returns.mean() / excess_returns.std()
    
    # 最大回撤
    peak = equity.expanding().max()
    drawdown = (equity - peak) / peak
    max_drawdown = drawdown.min()
    
    # 胜率
    trades = df[df['trade'] > 0]
    winning_trades = trades[trades['net_return'] > 0]
    win_rate = len(winning_trades) / len(trades) if len(trades) > 0 else 0
    
    # 盈亏比
    avg_win = winning_trades['net_return'].mean() if len(winning_trades) > 0 else 0
    losing_trades = trades[trades['net_return'] <= 0]
    avg_loss = losing_trades['net_return'].mean() if len(losing_trades) > 0 else 0
    profit_loss_ratio = abs(avg_win / avg_loss) if avg_loss != 0 else float('inf')
    
    return {
        'annual_return': annual_return,
        'sharpe_ratio': sharpe,
        'max_drawdown': max_drawdown,
        'win_rate': win_rate,
        'profit_loss_ratio': profit_loss_ratio
    }

4.5 知识体系总览

为了让你更直观地理解本章的知识结构,我画了一张流程图:

套利信号识别与回测知识体系 历史行情数据 阈值设定方法 固定百分位法 | 移动标准差法 | 动态分位数法 窗口大小、k值参数调优 信号生成逻辑 开仓信号 | 平仓信号 | 止损信号 考虑交易成本、滑点、信号滞后 回测框架搭建 → 策略绩效评估 年化收益率 | 夏普比率 | 最大回撤 | 胜率 | 盈亏比 避免过拟合,考虑实盘环境差异

这张图把今天的内容串起来了。从数据输入开始,经过阈值设定、信号生成,最后到回测和绩效评估。每一步都有坑,每一步也都有优化空间。

最后说一句: 套利策略的核心不是预测价格涨跌,而是捕捉价格关系的偏离。阈值设得好,信号生成得准,回测做得扎实,绩效评估看得全面——这套流程走下来,你的策略才算真正有了底气。


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