7. 构建交易信号:Z-score阈值设定、开仓与平仓逻辑、止损机制

好,咱们进入实战中最关键的一环——交易信号的构建。

前面我们讲了协整检验、讲了价差计算,但那些都是准备工作。真正决定你能不能赚钱的,是信号怎么发、什么时候进场、什么时候跑路。说白了,信号系统就是你的交易大脑。

7.1 Z-score:把价差变成标准分

先聊聊Z-score。这东西其实不复杂,就是把价差序列标准化一下。

import numpy as np
import pandas as pd

def calculate_zscore(spread, window=20):
    """
    计算价差的Z-score
    spread: 价差序列
    window: 滚动窗口
    """
    rolling_mean = spread.rolling(window=window).mean()
    rolling_std = spread.rolling(window=window).std()
    zscore = (spread - rolling_mean) / rolling_std
    return zscore

Z-score的公式很简单:(当前价差 - 均值) / 标准差。它告诉你当前价差偏离均值多少个标准差。

我个人习惯用20期滚动窗口。为什么是20?因为一个月大概20个交易日,能捕捉到中短期的均值回归特征。当然,你要是做高频,用10期也行;做长线,用60期也可以。这个没有标准答案。

核心理解:Z-score = 2.0,意味着当前价差偏离均值2个标准差。在正态分布假设下,这种情况发生的概率只有5%左右。也就是说,价差大概率会回归。

7.2 阈值设定:开仓与平仓的触发点

阈值怎么设?我见过很多新手直接拍脑袋设±2。嗯,这其实不太靠谱。

我建议分两步走:

  1. 统计法:先看历史Z-score的分位数。比如取90%分位数作为开仓阈值,50%分位数作为平仓阈值。
  2. 回测法:在历史数据上跑一遍,调整阈值参数,找到夏普比率最高的组合。

下面是我常用的一个阈值设定框架:

信号类型 Z-score条件 操作
开多仓 Z-score < -2.0 做多价差(买强卖弱)
开空仓 Z-score > 2.0 做空价差(卖强买弱)
平多仓 Z-score > -0.5 平掉多头仓位
平空仓 Z-score < 0.5 平掉空头仓位

你可能会问:为什么平仓阈值设得这么近?

嗯,这里有个经验。价差回归到均值附近后,往往还会来回震荡。如果你等到Z-score回到0才平仓,很容易被反复打脸。设±0.5,相当于提前锁定利润,把最后一段利润让给市场。

我的经验:开仓阈值不要设得太死。我曾经在螺纹钢和热卷的套利中,把阈值设成±2.5,结果一年只开了3次仓。后来改成±1.8,交易频率上来了,收益反而更好。阈值要根据品种波动率动态调整。

7.3 开仓与平仓逻辑:完整的信号流程

光有阈值还不够,你得有一套完整的逻辑流程。我把它总结成三步:

第一步:计算实时Z-score

每来一个新数据点,就更新滚动窗口,重新算Z-score。

第二步:判断开仓条件

  • 如果Z-score > 开仓阈值(如2.0),且当前没有持仓 → 开空仓
  • 如果Z-score < -开仓阈值(如-2.0),且当前没有持仓 → 开多仓
  • 如果已有持仓,不再开新仓(防止重复开仓)

第三步:判断平仓条件

  • 如果持有多仓,且Z-score > 平仓阈值(如-0.5)→ 平多仓
  • 如果持有空仓,且Z-score < 平仓阈值(如0.5)→ 平空仓

代码实现大概是这样的:

def generate_signals(zscore, entry_threshold=2.0, exit_threshold=0.5):
    """
    生成交易信号
    返回: 1=做多, -1=做空, 0=平仓/无操作
    """
    position = 0
    signals = []
    
    for z in zscore:
        if position == 0:  # 无持仓
            if z > entry_threshold:
                position = -1  # 做空
                signals.append(-1)
            elif z < -entry_threshold:
                position = 1   # 做多
                signals.append(1)
            else:
                signals.append(0)
        else:  # 有持仓
            if position == 1 and z > -exit_threshold:
                position = 0
                signals.append(0)
            elif position == -1 and z < exit_threshold:
                position = 0
                signals.append(0)
            else:
                signals.append(position)
    
    return signals

7.4 止损机制:别让亏损失控

止损,这是我最想强调的部分。统计套利不是稳赚不赔的,价差有时候就是不回归,甚至越走越远。

我遇到过最惨的一次,是2015年股指期货的套利。价差从2个标准差一路走到5个标准差,我硬扛了三天,亏了15%。从那以后,我给自己定了个铁律:任何套利策略都必须有止损

止损怎么设?我推荐三种方式:

  1. 固定Z-score止损:比如Z-score超过3.5或4.0,无条件平仓。这是最粗暴但最有效的方法。
  2. 固定金额止损:每笔交易亏损达到账户资金的1%或2%,立即止损。
  3. 时间止损:开仓后N个交易日价差还没回归,主动平仓。我一般设5个交易日。

注意:止损不是让你亏钱,而是让你活着。价差不回归的原因很多——可能是协整关系破裂了,可能是市场结构变了。这时候死扛,只会越亏越多。

我个人习惯把三种止损结合起来:

def check_stop_loss(zscore, entry_z, current_pnl, 
                    z_stop=3.5, pnl_stop=0.02, time_stop=5):
    """
    检查是否触发止损
    zscore: 当前Z-score
    entry_z: 开仓时的Z-score
    current_pnl: 当前盈亏比例
    time_stop: 持仓天数
    """
    # Z-score止损
    if abs(zscore) > z_stop:
        return True, "Z-score止损"
    
    # 盈亏止损
    if current_pnl < -pnl_stop:
        return True, "盈亏止损"
    
    # 时间止损(假设已记录持仓天数)
    if time_stop >= 5:
        return True, "时间止损"
    
    return False, "继续持有"

7.5 知识体系总览

说了这么多,我画个图帮你理清思路:

交易信号构建核心流程 价差序列输入 第一步:计算Z-score(滚动窗口) 第二步:阈值判断(开仓/平仓/持仓) 开仓信号(做多/做空) 平仓信号(止盈/止损) 止损检查(Z/盈亏/时间) 利润锁定 / 风险控制

这张图把整个信号构建流程串起来了。从价差输入,到Z-score计算,再到阈值判断,最后到开平仓和止损,每一步都环环相扣。

避坑指南:我曾经在实盘中犯过一个低级错误——忘记检查持仓状态,导致同一时间开了多笔同方向仓位。后来我在代码里加了个全局变量记录持仓状态,每次开仓前先检查。这个习惯我一直保留到现在。

好了,关于交易信号的构建,核心就是这些。记住三件事:Z-score标准化、阈值动态调整、止损必须执行。这三条做到了,你的套利策略就成功了一半。


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