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。嗯,这其实不太靠谱。
我建议分两步走:
- 统计法:先看历史Z-score的分位数。比如取90%分位数作为开仓阈值,50%分位数作为平仓阈值。
- 回测法:在历史数据上跑一遍,调整阈值参数,找到夏普比率最高的组合。
下面是我常用的一个阈值设定框架:
| 信号类型 | 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%。从那以后,我给自己定了个铁律:任何套利策略都必须有止损。
止损怎么设?我推荐三种方式:
- 固定Z-score止损:比如Z-score超过3.5或4.0,无条件平仓。这是最粗暴但最有效的方法。
- 固定金额止损:每笔交易亏损达到账户资金的1%或2%,立即止损。
- 时间止损:开仓后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-score标准化、阈值动态调整、止损必须执行。这三条做到了,你的套利策略就成功了一半。
公众号:蓝海资料掘金营,微信deep3321