15. 机器学习辅助:使用随机森林预测基差方向,构建分类信号
说实话,做基差交易做久了,你会发现一个规律——传统统计模型在大多数时候够用,但遇到市场结构突变,就容易翻车。我自己的经验是,当基差走势出现非线性特征时,线性回归基本就废了。这时候,机器学习就派上用场了。
这一章,我们聊聊怎么用随机森林来预测基差方向。说白了,就是把基差涨跌当成一个分类问题来处理。涨了算1,跌了算0,让模型去学。
为什么选随机森林?
你可能要问,深度学习那么火,为啥不直接用LSTM?嗯,这里有个现实问题——基差数据量其实不大,日频数据一年也就两百多个样本。你拿这点数据去训神经网络,过拟合是大概率事件。
随机森林有几个好处:
- 抗过拟合能力强——集成多棵树,不容易学偏
- 对特征重要性有天然解释——能告诉你哪个因子最管用
- 不需要做太多特征缩放——基差数据量纲差异大,但树模型不敏感
- 训练速度快——我笔记本上跑几百棵树也就几秒钟
我在项目中遇到过最头疼的事,就是特征工程做了一大堆,结果模型告诉我大部分特征都没用。随机森林的feature_importances_属性,能帮你快速筛选出真正有效的因子。
特征工程:基差预测需要哪些输入?
预测基差方向,不能只拿基差本身的历史数据。你得给它喂一些"有营养"的东西。我个人习惯,会从以下几个维度构造特征:
| 特征类别 | 具体特征 | 说明 |
|---|---|---|
| 基差自身 | 过去N日基差均值、标准差、斜率 | 捕捉趋势和波动 |
| 价格动量 | 现货/期货的N日收益率、RSI | 反映短期情绪 |
| 波动率 | 基差波动率、价格波动率 | 波动率突变往往预示方向变化 |
| 持仓量 | 期货持仓量变化率 | 资金进出信号 |
| 期限结构 | 近远月价差、升贴水状态 | 反映市场预期 |
| 宏观因子 | 利率、库存、季节性虚拟变量 | 基本面驱动 |
你想想看,这些特征其实都是交易员平时盯盘会看的东西。机器学习只是帮我们把它们组合起来,找到最优的决策边界。
标签构造:怎么定义基差方向?
这里有个坑,我踩过。如果你直接用明天的基差涨跌作为标签,模型会学得很痛苦——因为基差经常在零轴附近来回震荡,噪声太大。
我的做法是:
- 计算未来N日的基差累计变化
- 设定一个阈值(比如0.5个标准差)
- 超过阈值算上涨(1),低于负阈值算下跌(-1),中间算震荡(0)
这样做的好处是,模型只去预测那些"有意义的"方向变化,忽略掉随机波动。我曾经试过直接用原始涨跌做三分类,结果准确率只有52%,跟抛硬币差不多。加了阈值之后,准确率能提到65%以上。
代码实现:从数据到信号
下面是我实际项目中用的一段代码,做了简化处理。核心逻辑都在:
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, confusion_matrix
# 假设df已经包含基差数据和特征
# 构造标签:未来5日基差变化超过0.5个标准差
df['basis_change_5d'] = df['basis'].shift(-5) - df['basis']
threshold = 0.5 * df['basis'].std()
df['label'] = 0
df.loc[df['basis_change_5d'] > threshold, 'label'] = 1
df.loc[df['basis_change_5d'] < -threshold, 'label'] = -1
# 剔除标签为0的样本(震荡行情不参与训练)
df_train = df[df['label'] != 0].copy()
# 特征列
feature_cols = ['basis_ma5', 'basis_std5', 'spot_ret1', 'fut_ret1',
'basis_vol', 'oi_change', 'spread', 'rate']
X = df_train[feature_cols]
y = df_train['label']
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, shuffle=False
)
# 训练随机森林
rf = RandomForestClassifier(
n_estimators=200,
max_depth=6,
min_samples_leaf=10,
random_state=42,
class_weight='balanced'
)
rf.fit(X_train, y_train)
# 预测
y_pred = rf.predict(X_test)
print(classification_report(y_test, y_pred))
# 特征重要性
importance = pd.DataFrame({
'feature': feature_cols,
'importance': rf.feature_importances_
}).sort_values('importance', ascending=False)
print(importance)
模型评估:别只看准确率
做分类模型,很多人喜欢盯着准确率看。但做交易信号,准确率只是及格线。我更关心的是:
- 精确率和召回率——预测上涨时到底准不准?上涨行情有没有漏掉?
- 混淆矩阵——模型是容易把下跌误判为上涨,还是反过来?
- 信号稳定性——回测里信号会不会频繁翻转?
我曾经遇到过一个模型,测试集准确率68%,看着不错。但一上回测,信号一天三变,交易成本直接吃掉所有收益。后来加了信号平滑处理,才稳住。
信号生成:把预测转成交易指令
模型输出的是分类标签(-1, 0, 1),但你不能直接拿这个下单。我个人习惯再加一层过滤:
# 获取预测概率,而不是直接取标签
prob = rf.predict_proba(X_test)
# 只有概率超过阈值才发信号
confidence_threshold = 0.6
signal = np.zeros(len(prob))
for i in range(len(prob)):
if prob[i][2] > confidence_threshold: # 上涨概率高
signal[i] = 1
elif prob[i][0] > confidence_threshold: # 下跌概率高
signal[i] = -1
# 否则信号为0,不交易
知识体系总览
下面这张图,把整个流程串起来了。从原始数据到最终信号,每一步都有讲究:
你看,整个流程其实不复杂。关键是把每个环节做扎实——特征要选对,标签要合理,模型参数要调好,信号要过滤干净。任何一个环节偷懒,最终信号质量都会打折扣。
嗯,这一章就聊到这儿。代码你可以直接拿去跑,但记得根据你自己的品种和参数做调整。每个市场的基差特性都不一样,没有万能模型。