第17章 机器学习在统计套利中的应用:聚类寻找配对、LSTM预测价差
说实话,传统统计套利走到今天,很多老方法已经不够用了。你想想看,几千只股票里手工找配对,那得看到猴年马月?而且价差预测用简单的均值回归模型,遇到市场结构变化就失灵。所以这一章,我打算聊聊怎么用机器学习来升级我们的基差交易工具箱。
我个人习惯把机器学习在统计套利里的应用分成两块:配对发现和价差预测。前者解决「跟谁做交易」的问题,后者解决「什么时候进场出场」的问题。咱们一个一个来。
17.1 聚类算法:自动寻找交易配对
传统方法找配对,要么算相关系数,要么算协整检验。但这两招都有个毛病——你得先有个候选池。比如你盯着银行板块,那就只能在银行股里找。万一跨板块的配对更赚钱呢?
聚类算法可以打破这个限制。它不看行业标签,只看价格行为特征。说白了,就是把走势相似的股票自动归到一堆,然后我们在同一堆里找配对。
17.1.1 特征工程:怎么描述一只股票的价格行为?
聚类之前,得先把每只股票变成一组数字特征。我一般用这几类:
- 收益率统计特征:过去60天的均值、标准差、偏度、峰度
- 波动率特征:已实现波动率、波动率聚类程度(用GARCH残差)
- 相关性特征:与大盘的相关性、与板块指数的相关性
- 动量特征:过去5天、20天、60天的累计收益率
嗯,这里要注意:特征不是越多越好。我在项目中遇到过,加了太多噪音特征,聚类结果反而变差了。建议先用PCA降个维,保留解释方差95%以上的主成分。
17.1.2 聚类实战:KMeans + 层次聚类
我个人偏好先用KMeans做粗聚类,再用层次聚类做细分类。为什么?KMeans快,但需要指定K值;层次聚类慢,但不需要预设类别数。两者结合,效果不错。
from sklearn.cluster import KMeans
from scipy.cluster.hierarchy import linkage, fcluster
import numpy as np
# 假设 features 是 (n_stocks, n_features) 的矩阵
# 第一步:KMeans粗聚类
kmeans = KMeans(n_clusters=10, random_state=42)
coarse_labels = kmeans.fit_predict(features)
# 第二步:对每个粗聚类内部做层次聚类
final_labels = np.zeros_like(coarse_labels)
for cluster_id in range(10):
mask = coarse_labels == cluster_id
if np.sum(mask) < 5: # 少于5只股票就不细分了
final_labels[mask] = cluster_id * 100
continue
sub_features = features[mask]
Z = linkage(sub_features, method='ward')
sub_labels = fcluster(Z, t=3, criterion='maxclust')
final_labels[mask] = cluster_id * 100 + sub_labels
# 最终,同一 final_labels 的股票就是潜在配对池
这段代码跑完之后,你会得到一堆标签。比如标签 203 表示第2个粗聚类下的第3个子类。同一标签下的股票,走势高度相似,可以进一步做协整检验来确认配对。
17.2 LSTM预测价差:从回归到序列建模
找到配对之后,传统做法是假设价差均值回归,用布林带或Z-score触发交易。但市场不是永远均值回归的——趋势行情里,价差可能长期偏离。这时候就需要一个能捕捉时序依赖的模型。
LSTM(长短期记忆网络)天然适合干这个。它能记住过去一段时间的价差模式,预测未来几步的走势。说白了,就是让模型学会「价差现在这样走,接下来大概率会怎么走」。
17.2.1 数据准备:构造监督学习样本
LSTM需要把时间序列转成「特征-标签」对。假设我们预测未来5分钟的价差:
import numpy as np
def create_sequences(spread, lookback=60, horizon=5):
X, y = [], []
for i in range(lookback, len(spread) - horizon):
X.append(spread[i-lookback:i])
y.append(spread[i+horizon])
return np.array(X), np.array(y)
# spread 是价差序列,lookback=60表示用过去60个时间步
# horizon=5表示预测未来第5步的价差
X, y = create_sequences(spread, lookback=60, horizon=5)
这里有个细节:我习惯把价差做差分后再输入LSTM。为什么?因为原始价差可能非平稳,差分后模型更容易学到变化模式。预测完再反差分回去就行。
17.2.2 模型搭建:一个轻量级LSTM
别一上来就搭几十层,过拟合会让你哭的。我一般用2-3层LSTM,每层32-64个单元,加个Dropout防过拟合。
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout
model = Sequential([
LSTM(64, return_sequences=True, input_shape=(60, 1)),
Dropout(0.2),
LSTM(32, return_sequences=False),
Dropout(0.2),
Dense(16, activation='relu'),
Dense(1)
])
model.compile(optimizer='adam', loss='mse')
model.fit(X_train, y_train, epochs=50, batch_size=32, validation_split=0.2)
训练完之后,模型会输出一个预测值。你可以把这个预测值跟当前价差比较:如果预测价差远高于当前价差,说明价差可能要涨,那就做多价差(买一腿卖另一腿)。反之亦然。
17.2.3 实战中的坑与对策
我在实盘里用过LSTM预测价差,踩过几个坑,分享给你:
- 过拟合严重:LSTM在训练集上表现完美,一到测试集就崩。对策是增加Dropout、减少层数、用早停法。
- 预测滞后:LSTM预测的价差往往比实际慢半拍。我试过用双向LSTM(BiLSTM),滞后问题有所缓解。
- 市场结构变化:模型在牛熊转换时完全失效。我的做法是定期重训练,比如每周用最新数据微调一次。
17.3 知识体系总览
下面这张图是我自己画的,把本章的核心逻辑串起来了。你看一眼就能明白整个流程:
你看,整个流程其实就五步:数据→特征→聚类→检验→预测。每一步都有坑,但每一步也都有对应的解法。我个人觉得,机器学习在统计套利里最大的价值不是替代传统方法,而是辅助决策——帮你更快找到配对,更准预测价差。
最后说一句:别指望模型100%准确。我见过太多人把LSTM当印钞机,结果亏得底朝天。记住,任何模型都是工具,风控才是命根子。