16. 深度学习应用:LSTM时序预测基差,端到端交易信号生成。

基差交易做到最后,你会发现一个核心问题:基差到底会往哪个方向走?

传统的时间序列模型,像ARIMA、GARCH,我用了好多年。坦白说,在震荡行情里它们还行。但一旦遇到市场结构突变,比如政策干预、交割月临近,这些模型就很容易“掉链子”。

后来我开始尝试LSTM。为什么选它?因为LSTM能记住长期依赖关系。你想想看,基差的走势往往不是孤立的,它受近月合约、远月合约、库存、资金情绪等多重因素影响。LSTM的门控机制,说白了就是能决定“哪些历史信息该记住,哪些该忘掉”。

这一章,我就带你完整走一遍:如何用LSTM预测基差,并直接生成交易信号

16.1 为什么是LSTM?不是Transformer?

你可能要问:现在Transformer这么火,为什么不用?

我个人习惯是:先看数据量。基差数据通常是日频或小时频,样本量几千到几万条。对于这种规模,LSTM的训练稳定性和可解释性都优于Transformer。我在项目中遇到过用Transformer做基差预测,结果在小样本下过拟合严重,调参调到头秃。

另外,LSTM的时序因果性更自然。它从左到右读数据,不会像Transformer那样需要位置编码。对于金融时序这种“顺序就是一切”的数据,LSTM的归纳偏置更合适。

核心结论: 数据量 < 10万条,优先选LSTM;数据量 > 100万条,可以考虑Transformer。

16.2 数据准备:从Tick到监督学习样本

做LSTM之前,数据必须整理成“监督学习”格式。说白了,就是用过去N天的特征,预测未来M天的基差。

我的标准流程:

  1. 计算基差: 基差 = 近月合约价格 - 远月合约价格。注意,要剔除交割日附近的数据,避免换月跳空。
  2. 特征工程: 除了基差本身,我还会加入:持仓量变化、成交量比率、波动率、以及跨期价差的一阶差分。
  3. 归一化: LSTM对输入尺度敏感。我习惯用MinMaxScaler,把每个特征缩放到[0,1]区间。
  4. 构建序列: 设定时间步长(lookback)为20天。也就是说,用过去20天的数据预测第21天的基差。
import numpy as np
import pandas as pd
from sklearn.preprocessing import MinMaxScaler

def create_sequences(data, lookback=20):
    X, y = [], []
    for i in range(lookback, len(data)):
        X.append(data[i-lookback:i])
        y.append(data[i, 0])  # 假设第一列是基差
    return np.array(X), np.array(y)

# 假设df已经包含基差和其他特征
features = ['basis', 'oi_change', 'volume_ratio', 'volatility']
scaler = MinMaxScaler()
scaled_data = scaler.fit_transform(df[features])

X, y = create_sequences(scaled_data, lookback=20)
print(f"X shape: {X.shape}, y shape: {y.shape}")
# 输出: X shape: (980, 20, 4), y shape: (980,)

避坑指南: 我曾经在构建序列时忘了shuffle数据,导致训练集和测试集时间重叠。结果回测曲线漂亮得不像话,实盘直接打脸。记住:时序数据绝对不能随机打乱,必须按时间顺序切分。

16.3 模型架构:简单但有效

很多人一上来就堆层数,觉得层数越多越厉害。其实不然。对于基差预测这种任务,两层LSTM + 一层全连接通常就够了。

我的默认配置:

  • 第一层LSTM:64个单元,return_sequences=True
  • 第二层LSTM:32个单元,return_sequences=False
  • Dropout层:0.2,防止过拟合
  • 全连接层:16个神经元,ReLU激活
  • 输出层:1个神经元,线性激活(回归任务)
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout

model = Sequential([
    LSTM(64, return_sequences=True, input_shape=(20, 4)),
    Dropout(0.2),
    LSTM(32, return_sequences=False),
    Dropout(0.2),
    Dense(16, activation='relu'),
    Dense(1)
])

model.compile(optimizer='adam', loss='mse', metrics=['mae'])
model.summary()

嗯,这里要注意:return_sequences这个参数。第一层设为True,是因为我们要把完整的序列输出给第二层LSTM。最后一层LSTM设为False,只输出最后一个时间步的结果。

16.4 训练策略:早停与学习率衰减

训练LSTM时,我最怕的就是过拟合。基差数据本身信噪比低,模型很容易学到噪声。

我的做法:

  1. 早停(EarlyStopping): 监控验证集loss,连续10个epoch不下降就停止。
  2. 学习率衰减(ReduceLROnPlateau): 当验证集loss停滞时,学习率乘以0.5。
  3. 验证集比例: 用最后20%的数据做验证,保证时间顺序。
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau

# 按时间顺序切分
split_idx = int(len(X) * 0.8)
X_train, X_val = X[:split_idx], X[split_idx:]
y_train, y_val = y[:split_idx], y[split_idx:]

callbacks = [
    EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True),
    ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=5)
]

history = model.fit(
    X_train, y_train,
    validation_data=(X_val, y_val),
    epochs=100,
    batch_size=32,
    callbacks=callbacks,
    verbose=1
)

注意: 不要用默认的batch_size=32就万事大吉。我建议根据数据量调整:数据量小(<5000条)用batch_size=16;数据量大用32或64。batch_size太大容易陷入局部最优。

16.5 从预测到交易信号:端到端生成

模型预测出基差后,怎么变成交易信号?这里有个关键点:不要直接拿预测值开仓

我的做法是:

  • 预测未来5天的基差均值
  • 与当前基差比较,计算预期变化幅度
  • 设定阈值:变化幅度 > 2倍历史标准差,才生成信号

说白了,就是过滤掉小波动,只抓大趋势

def generate_signals(predictions, current_basis, threshold_std=2):
    """
    predictions: 模型预测的未来5天基差
    current_basis: 当前基差
    """
    expected_change = np.mean(predictions) - current_basis
    std_basis = np.std(predictions)  # 预测值的标准差
    
    if expected_change > threshold_std * std_basis:
        return 1  # 做多基差(买入近月,卖出远月)
    elif expected_change < -threshold_std * std_basis:
        return -1  # 做空基差(卖出近月,买入远月)
    else:
        return 0  # 无信号

# 假设model已经训练好
last_20_days = scaled_data[-20:].reshape(1, 20, 4)
pred_future = []
for _ in range(5):
    pred = model.predict(last_20_days, verbose=0)
    pred_future.append(pred[0, 0])
    # 滚动预测:将新预测加入序列,移除最早的数据
    new_row = np.append(last_20_days[0, 1:], [[pred[0,0], 0, 0, 0]], axis=0)
    last_20_days = new_row.reshape(1, 20, 4)

signal = generate_signals(pred_future, current_basis)
print(f"交易信号: {signal}")

16.6 回测与评估:别只看R²

模型预测的R²达到0.8,是不是就稳了?不一定。我见过R²很高但交易信号一塌糊涂的情况。

为什么?因为预测精度和交易收益是两码事。模型可能把基差的小波动预测得很准,但真正赚钱的大趋势反而没抓住。

我的评估指标:

指标 说明 我的经验阈值
方向准确率 预测基差涨跌方向是否正确 > 55%
信号胜率 有信号时,开仓后盈利的比例 > 60%
夏普比率 风险调整后收益 > 1.5
最大回撤 策略净值从峰值回落的最大幅度 < 15%

我的经验: 方向准确率比R²重要得多。一个R²只有0.3但方向准确率60%的模型,实盘表现往往好于R²=0.8但方向准确率52%的模型。

16.7 避坑指南:LSTM在基差交易中的常见问题

最后,分享几个我踩过的坑:

  • 数据泄露: 归一化时用了全量数据的min/max,导致未来信息泄露。正确做法:只用训练集计算scaler参数。
  • 序列长度选择: 我试过lookback=60,结果模型学到的全是长期趋势,对短期反转毫无反应。后来改成20,效果反而更好。
  • 预测步长: 预测未来1天,信号噪声太大;预测未来10天,模型误差累积严重。5天是个不错的折中。
  • 模型更新频率: 不要每天重新训练。我习惯每周训练一次,用最新数据微调。

嗯,LSTM不是万能药。但它确实能捕捉到传统模型忽略的非线性模式。如果你能把数据处理好、阈值设合理,这套端到端的流程在基差交易里是能稳定盈利的。

LSTM基差预测与交易信号生成流程 数据准备 基差计算 特征工程 归一化 序列构建 lookback=20 监督学习格式 时间顺序切分 LSTM训练 2层LSTM+Dropout 早停+学习率衰减 验证集监控 基差预测 滚动预测未来5天 计算预测均值 与当前基差比较 信号生成 阈值过滤 2倍标准差 输出1/-1/0 回测评估 方向准确率 信号胜率 夏普比率/最大回撤 模型迭代优化

这套流程,我跑了快两年。从最初的手工调参,到后来加入自动化早停,再到现在的端到端信号生成,每一步都是踩坑踩出来的。希望你能少走弯路。

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