第26章 机器学习预测基差:LSTM时序预测、特征工程构建、XGBoost回归模型
说实话,基差交易做到一定阶段,你会发现一个尴尬的事实——传统统计模型对基差的预测能力,越来越不够用了。为什么?因为基差这东西,它不光受供需影响,还受市场情绪、资金流向、政策预期这些乱七八糟的因素干扰。线性模型根本抓不住这些非线性关系。
所以这一章,我们来聊聊机器学习怎么预测基差。我会重点讲两个方向:LSTM做时序预测,和XGBoost做回归建模。前者擅长捕捉时间序列里的长期依赖,后者在特征工程做好的情况下,预测精度非常能打。
核心观点:机器学习预测基差,不是要替代传统模型,而是要在传统模型的基础上,捕捉那些被忽略的非线性信号。我个人的经验是——两者结合,效果最好。
26.1 基差预测的难点在哪
先说说基差预测为什么难。基差 = 现货价格 - 期货价格。表面上是个简单的差值,但影响它的因素太多了:
- 持有成本:仓储费、资金成本、保险——这些相对稳定,好预测
- 供需预期:比如下个月的产量数据、库存变化——这个就开始复杂了
- 市场情绪:资金流向、持仓结构、期限结构——非线性,难搞
- 政策扰动:突然的关税调整、储备投放——黑天鹅,谁都猜不到
传统方法,比如ARIMA、VAR,它们假设数据是线性的、平稳的。但基差数据,你想想看,它经常出现尖峰厚尾、波动率聚集这些特征。线性模型遇到这些,基本就歇菜了。
我记得有一次,我用VAR模型预测螺纹钢的基差,回测结果漂亮得不行。结果一上实盘,连续三天预测方向全错。后来复盘发现,那几天正好是环保限产政策密集出台期,VAR模型根本抓不住这种政策冲击。
我的建议:不要迷信任何一个模型。机器学习也好,传统统计也罢,都只是工具。关键是你对市场的理解,以及你构建的特征能不能反映这种理解。
26.2 LSTM时序预测:抓住基差的长期依赖
LSTM,长短期记忆网络,说白了就是一种特殊的循环神经网络。它最大的特点,就是能记住很久以前的信息,同时还能决定哪些信息该忘掉。
为什么这对基差预测重要?因为基差的走势,往往受过去很长一段时间的累积影响。比如,过去30天的库存变化趋势,可能比昨天一天的库存数据更有预测价值。普通RNN记不住这么长的依赖,但LSTM可以。
26.2.1 LSTM的核心结构
我不打算把LSTM的数学公式全讲一遍,那太枯燥了。你只需要记住三个门:
- 遗忘门:决定哪些历史信息要丢掉。比如,三个月前的某个异常数据,可能现在没用了
- 输入门:决定哪些新信息要记住。比如,今天刚出的库存数据,很重要,要记下来
- 输出门:决定当前时刻要输出什么。基于记忆和新输入,给出预测
这三个门配合工作,让LSTM能灵活地处理时间序列中的长期依赖。我在做股指期货基差预测时,发现LSTM对跨期价差的预测效果明显优于传统模型,尤其是在趋势行情中。
26.2.2 LSTM预测基差的实战代码
下面是一个简化的LSTM预测基差的代码框架。注意,这只是演示逻辑,实际使用时需要根据你的数据做调整。
import numpy as np
import pandas as pd
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout
from sklearn.preprocessing import MinMaxScaler
# 加载数据
data = pd.read_csv('basis_data.csv')
# 假设数据包含:日期、基差、库存、持仓量、成交量等字段
# 特征选择
features = ['basis', 'inventory', 'open_interest', 'volume']
target = 'basis' # 预测未来基差
# 数据归一化
scaler = MinMaxScaler()
scaled_data = scaler.fit_transform(data[features])
# 构造时间序列样本
def create_sequences(data, seq_length=30):
X, y = [], []
for i in range(len(data) - seq_length):
X.append(data[i:i+seq_length])
y.append(data[i+seq_length, 0]) # 预测基差
return np.array(X), np.array(y)
seq_length = 30
X, y = create_sequences(scaled_data, seq_length)
# 划分训练集和测试集
split = int(0.8 * len(X))
X_train, X_test = X[:split], X[split:]
y_train, y_test = y[:split], y[split:]
# 构建LSTM模型
model = Sequential()
model.add(LSTM(units=50, return_sequences=True, input_shape=(seq_length, len(features))))
model.add(Dropout(0.2))
model.add(LSTM(units=50, return_sequences=False))
model.add(Dropout(0.2))
model.add(Dense(units=1))
model.compile(optimizer='adam', loss='mean_squared_error')
# 训练
model.fit(X_train, y_train, epochs=50, batch_size=32, validation_data=(X_test, y_test))
# 预测
predictions = model.predict(X_test)
# 记得反归一化
predictions = scaler.inverse_transform(np.concatenate([predictions, np.zeros((len(predictions), len(features)-1))], axis=1))[:, 0]
注意:LSTM对数据尺度非常敏感。一定要做归一化。另外,序列长度(seq_length)是个超参数,我一般用30到60天,但具体要看你的品种和周期。我曾经在原油基差上用90天的序列长度,效果反而更差——因为太久远的数据已经跟当前市场没关系了。
26.3 特征工程构建:决定模型上限的关键
说实话,模型选得再好,特征不行也是白搭。我见过太多人,一上来就调LSTM的参数,结果特征就用了基差本身的历史值。这能预测好才怪。
特征工程,说白了就是把你对市场的理解,转化成模型能看懂的数字。我个人习惯把特征分成三类:
| 特征类别 | 具体特征 | 说明 |
|---|---|---|
| 价格衍生特征 | 基差历史值、收益率、波动率、动量 | 最基础,但必须做 |
| 基本面特征 | 库存变化率、产量、消费量、仓单数量 | 反映供需格局 |
| 市场结构特征 | 持仓量变化、期限结构斜率、资金流向 | 反映市场情绪和资金行为 |
这里我重点讲几个我常用的特征构建技巧:
26.3.1 滞后特征
基差的变化往往有惯性。过去1天、3天、5天的基差变化,对预测未来基差有帮助。我一般会构建滞后1、3、5、10、20期的特征。
26.3.2 滚动统计特征
比如过去10天的基差均值、标准差、最大值、最小值。这些能反映基差的短期趋势和波动情况。我在做铁矿石基差时,发现滚动20天的标准差对预测基差反转特别有效。
26.3.3 交叉特征
两个特征的乘积或比值,往往能产生意想不到的效果。比如,基差 × 库存变化率,这个特征能反映「库存变化对基差的影响程度」。XGBoost对这种交叉特征非常敏感。
避坑指南:我曾经在特征工程里加了太多相关性高的特征,结果模型过拟合得一塌糊涂。后来我养成了一个习惯——每次加新特征前,先算一下它跟目标变量的相关性,以及跟已有特征的多重共线性。特征不是越多越好,精炼才是王道。
26.4 XGBoost回归模型:非线性拟合的利器
XGBoost,说白了就是梯度提升决策树的升级版。它为什么适合基差预测?因为基差数据里充满了非线性关系、缺失值、异常值,而XGBoost对这些东西的容忍度很高。
我个人的经验是:如果特征工程做得好,XGBoost的预测精度往往不输LSTM,而且训练速度快得多,调参也更容易。
26.4.1 XGBoost的核心优势
- 处理缺失值:自动学习缺失值的最佳分裂方向,不用你手动填充
- 特征重要性:训练完直接告诉你哪些特征最重要,方便你迭代优化
- 正则化:内置L1和L2正则,防止过拟合
- 并行计算:CPU多核并行,训练速度比传统GBDT快一个数量级
26.4.2 XGBoost预测基差的实战代码
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
# 假设你已经构建好了特征矩阵 X 和目标变量 y
# X 包含:滞后基差、滚动统计、库存变化、持仓变化等特征
# y 是未来1天的基差
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, shuffle=False)
# 构建XGBoost模型
model = xgb.XGBRegressor(
n_estimators=500,
max_depth=5,
learning_rate=0.01,
subsample=0.8,
colsample_bytree=0.8,
reg_alpha=0.1,
reg_lambda=1.0,
random_state=42
)
# 训练
model.fit(X_train, y_train, eval_set=[(X_test, y_test)], early_stopping_rounds=50, verbose=False)
# 预测
y_pred = model.predict(X_test)
# 评估
mse = mean_squared_error(y_test, y_pred)
print(f'Test MSE: {mse:.4f}')
# 特征重要性
importance = model.feature_importances_
feature_names = X.columns
for name, imp in sorted(zip(feature_names, importance), key=lambda x: x[1], reverse=True)[:10]:
print(f'{name}: {imp:.4f}')
关键参数说明:
max_depth:树的最大深度。我一般设3-6,太深容易过拟合learning_rate:学习率。设小一点(0.01-0.1),配合多棵树,效果更好subsample:每棵树用的样本比例。0.8左右,增加随机性,防止过拟合colsample_bytree:每棵树用的特征比例。特征多的时候设小一点
26.5 LSTM vs XGBoost:怎么选?
这个问题我经常被问到。我的回答是:看你的数据量和业务场景。
| 对比维度 | LSTM | XGBoost |
|---|---|---|
| 数据量要求 | 大(至少几千条) | 中等(几百条也能跑) |
| 特征工程 | 相对简单(自动提取时序特征) | 非常重要(需要手动构建) |
| 训练速度 | 慢(GPU加速后还行) | 快(CPU就能跑) |
| 可解释性 | 差(黑箱) | 中等(有特征重要性) |
| 对非线性拟合 | 强 | 强 |
| 对缺失值处理 | 差(需要填充) | 好(自动处理) |
我个人习惯的做法是:先用XGBoost快速验证特征的有效性,等特征稳定了,再上LSTM做精细化预测。这样既能快速迭代,又能保证最终效果。
一个小技巧:你可以把XGBoost的预测结果作为一个特征,输入到LSTM里。这种「模型堆叠」的方式,我试过几次,效果都比单一模型好。说白了,就是让两个模型互相补充。
26.6 本章知识体系
下面这张图,是我对本章核心逻辑的总结。你可以看到,从数据到特征,再到模型选择,最后到预测输出,每一步都有讲究。
嗯,这一章的内容就到这里。机器学习预测基差,说到底是个系统工程。数据、特征、模型、调参,每个环节都不能马虎。我见过太多人,模型调得花里胡哨,结果特征就用了两三个,最后效果还不如一个简单的移动平均。记住:特征工程才是真正的护城河。