第六章:交易信号生成——阈值设定与信号过滤
信号生成,说白了就是给策略装上一双「眼睛」。什么时候该进场?什么时候该跑路?止损设在哪?这些问题的答案,都藏在阈值设定和信号过滤里。
我做了这么多年量化,见过太多策略在回测里跑得漂亮,一上实盘就崩。原因往往不是策略逻辑有问题,而是信号处理太粗糙。今天咱们就把这块硬骨头啃下来。
6.1 阈值设定方法
阈值,就是判断信号是否有效的「门槛」。门槛设得太低,假信号满天飞;设得太高,真机会全错过。这里有两种主流方法:固定阈值和动态阈值。
6.1.1 固定阈值
固定阈值最简单,也最直接。比如价差超过2个标准差就开仓,回到0.5个标准差就平仓。我早期做股指期货跨期套利时,用的就是这套逻辑。
核心思路:基于历史统计量(均值、标准差)设定固定数值,不随时间变化。
# 固定阈值示例:基于历史标准差
import numpy as np
def fixed_threshold(spread, window=60):
mean = np.mean(spread[-window:])
std = np.std(spread[-window:])
open_long = spread[-1] < mean - 2 * std # 开多信号
open_short = spread[-1] > mean + 2 * std # 开空信号
close_all = abs(spread[-1] - mean) < 0.5 * std # 平仓信号
return open_long, open_short, close_all
嗯,这里要注意:固定阈值对市场结构变化非常敏感。我记得2020年3月那波行情,很多用固定阈值的策略直接被打穿。为什么?因为波动率变了,但阈值没变。
避坑指南:我曾经用固定阈值做螺纹钢和热卷的跨品种套利,回测三年年化15%。结果实盘第二个月就亏了8%。后来发现是样本期内波动率偏低,阈值设得太窄,实盘里假信号太多。
6.1.2 动态阈值
动态阈值,就是让阈值跟着市场走。波动大的时候放宽,波动小的时候收紧。我个人习惯用滚动窗口的均值和标准差,或者用EWMA(指数加权移动平均)来动态调整。
# 动态阈值示例:EWMA动态调整
def dynamic_threshold(spread, decay=0.05):
# 计算EWMA均值和标准差
ewma_mean = spread.ewm(alpha=decay).mean()
ewma_std = spread.ewm(alpha=decay).std()
current_mean = ewma_mean.iloc[-1]
current_std = ewma_std.iloc[-1]
# 动态阈值:2倍滚动标准差
upper = current_mean + 2 * current_std
lower = current_mean - 2 * current_std
return lower, upper
动态阈值的好处是自适应。你想想看,市场波动率从10%飙到30%,固定阈值还在用原来的标准,那不是找死吗?动态阈值会自动调整,说白了就是「见人说人话,见鬼说鬼话」。
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 固定阈值 | 简单、回测快、逻辑清晰 | 对市场结构变化敏感 | 波动率稳定的品种 |
| 动态阈值 | 自适应、鲁棒性强 | 参数敏感、过拟合风险 | 波动率变化大的品种 |
6.2 开仓信号与平仓信号
阈值设好了,接下来就是具体的开仓和平仓逻辑。这里我分享一套我自己用了多年的框架。
6.2.1 开仓信号设计
开仓信号,核心就四个字:价差偏离。当价差偏离到一定程度,我们认为市场出现了定价错误,这时候进场。
- 开多信号:价差低于下阈值(价差被低估,做多价差)
- 开空信号:价差高于上阈值(价差被高估,做空价差)
但光有阈值还不够。我建议加一个确认条件,比如价差连续N根K线都在阈值之外,或者成交量放大配合。为什么?因为单根K线的突破可能是噪音。
个人经验:我在做豆粕和菜粕的跨品种套利时,发现单纯用阈值开仓,胜率只有55%。后来加了「连续3根5分钟K线都在阈值外」的确认条件,胜率直接提到68%。
6.2.2 平仓信号设计
平仓比开仓更重要。为什么?因为开仓决定你赚不赚,平仓决定你赚多少。
我常用的平仓逻辑有三种:
- 回归平仓:价差回到均值附近(比如0.5倍标准差内)
- 目标平仓:价差达到预设的目标位(比如3倍标准差)
- 时间平仓:持仓超过一定时间(比如24小时)强制平仓
# 综合平仓信号示例
def close_signal(spread, mean, std, hold_time):
# 回归平仓
if abs(spread - mean) < 0.5 * std:
return "回归平仓"
# 目标平仓
if abs(spread - mean) > 3 * std:
return "目标平仓"
# 时间平仓
if hold_time > 24 * 60: # 24小时
return "时间平仓"
return None
6.3 止损信号设计
止损,是量化交易者的最后一道防线。我见过太多人,策略逻辑没问题,就是不舍得止损,最后爆仓出局。
止损信号设计,我建议从三个维度考虑:
- 价格止损:价差反向突破某个阈值(比如3.5倍标准差)
- 时间止损:持仓超过最大持有时间
- 波动率止损:波动率突然放大,说明市场结构变了
血的教训:我曾经做铁矿石跨期套利,设了2%的价格止损。结果有一次价差瞬间拉大,直接打穿止损,亏了5%。后来我改成「波动率自适应止损」——波动率大的时候止损放宽,波动率小的时候收紧。效果好了很多。
# 波动率自适应止损
def adaptive_stop(spread, entry_price, current_vol):
base_stop = 0.02 # 基础止损2%
vol_factor = current_vol / 0.15 # 假设基准波动率15%
stop_loss = entry_price * (1 + base_stop * vol_factor)
return stop_loss
6.4 信号过滤机制
信号过滤,就是给信号「洗个澡」,把噪音去掉。我常用的过滤方法有:
- 时间过滤:避开开盘和收盘的混乱期
- 成交量过滤:成交量太低时,价差容易被操纵
- 趋势过滤:大趋势明显时,套利信号可能失效
- 相关性过滤:两个品种的相关性突然下降,说明套利关系破裂
核心原则:宁可错过,不可做错。信号过滤的本质,是用「交易频率」换「胜率」。
# 信号过滤示例
def signal_filter(spread_signal, volume, correlation):
# 成交量过滤
if volume < 1000:
return False
# 相关性过滤
if correlation < 0.6:
return False
# 时间过滤(避开开盘前30分钟)
if current_time < "09:30":
return False
return True
我个人习惯把信号过滤做成一个「漏斗」——每一层过滤掉一部分信号,最后剩下的才是真正值得交易的。你想想看,100个信号里能过滤出10个,这10个的胜率大概率比那90个高。
最后说一句:信号生成没有银弹。固定阈值还是动态阈值?开仓信号要不要加确认?止损设多少?这些问题的答案,都取决于你的品种、周期和风险偏好。我的建议是:先用历史数据回测,找到最优参数组合,然后留20%的数据做样本外验证。别一上来就实盘,那是送钱。
我的习惯:每次调整信号参数,我都会记录下调整的原因和预期效果。三个月后回头看,哪些判断对了,哪些错了。这个习惯帮我避开了很多坑。