ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

期货价格预测毕设:相关性分析+CNN-Attention-LSTM实战指南

期货价格预测毕设:相关性分析+CNN-Attention-LSTM实战指南 简介这份资源是面向计算机、人工智能、自动化等专业学生与教师的期货价格预测毕业设计项目核心为基于相关性分析的CNN-Attention-LSTM组合模型实现可用于课程设计、毕设、作业或项目初期立项演示。压缩包共29个文件、约30.28MB包含8个Python源码文件、6个npy数据文件、3个xlsx表格、2个pdf教程文档以及checkpoint模型权重、sql数据表、md说明等覆盖数据预处理、相关性分析、时间步处理、模型训练与预测接口等完整环节。项目代码经过测试运行成功并附有配置教程与算法使用说明注释详细便于理解CNN、Attention与LSTM的融合思路。目前已有677人学习下载适合希望快速搭建期货价格预测实验、对照源码复现结果或在此基础上二次开发的学习者参考使用。1. 期货价格预测毕设相关性分析加 CNN-Attention-LSTM 到底在解决什么做期货价格预测的毕设最容易翻车的地方不是模型不够深而是把一堆品种、一堆指标不加筛选地塞进网络指望 LSTM 自己学出权重。我见过太多人用 python 把 OHLCV 五个字段直接喂进去跑出来的 RMSE 看着还行一放到测试集就完全失效。问题出在输入端的信噪比期货数据里真正有预测力的因子往往只有少数几个其余全是噪声。相关性分析就是用来做这道筛选的而 CNN-Attention-LSTM 是在筛选后的序列上做时序建模的组合结构。这套方案适合正在做量化方向毕设、手里有 python 基础、需要一套能跑通且能写进论文的完整流程的人。它不承诺盈利但能让你把「数据筛选—特征构造—时序建模—结果评估」这条链路走完整而不是停在调包层面。2. 相关性分析怎么筛因子从 spearman 到特征矩阵2.1 为什么期货数据优先用 spearman 而不是 pearson期货价格序列有两个特点非正态分布和存在极端值。涨跌停、跳空、主力换月都会让收益率分布出现厚尾pearson 相关系数在这种数据上会被极端值带偏。spearman 相关性分析基于秩次衡量的是单调关系而非线性关系对异常值的鲁棒性明显更好。我一般会先对每个候选因子做 spearman 计算取绝对值排序保留前 60% 到 70% 的因子进入下一轮。这一步不是拍脑袋而是给后面的 CNN 减少无效卷积核的负担。实际操作时候选因子池通常包括自身历史收益率lag 1 到 lag 20、成交量变化率、持仓量变化率、以及跨品种价差。跨品种价差是期货特有的比如螺纹钢和铁矿石的比价这类因子的 spearman 值往往比单品种量价指标更高。import pandas as pd import numpy as np from scipy.stats import spearmanr def select_factors_by_spearman(df, target_col, factor_cols, top_ratio0.65): df: 包含目标列和候选因子列的 DataFrame target_col: 预测目标通常是未来 N 期收益率 factor_cols: 候选因子列名列表 top_ratio: 保留比例 corr_dict {} for col in factor_cols: # 去掉 NaN 后再算避免 spearman 返回 nan valid df[[col, target_col]].dropna() if len(valid) 30: # 样本太少直接跳过 continue corr, p_value spearmanr(valid[col], valid[target_col]) # 只保留统计显著的因子 if p_value 0.05: corr_dict[col] abs(corr) # 按相关系数绝对值降序排列 sorted_factors sorted(corr_dict.items(), keylambda x: x[1], reverseTrue) keep_num max(1, int(len(sorted_factors) * top_ratio)) selected [f[0] for f in sorted_factors[:keep_num]] return selected, sorted_factors这段代码的逻辑是先对每个因子单独计算 spearman 相关系数和 p 值p 值大于 0.05 的直接丢弃剩下的按相关系数绝对值排序取前 top_ratio 比例。参数 top_ratio 我一般设在 0.6 到 0.7 之间设太低会丢掉弱但有用的因子设太高等于没筛。注意 dropna 那一步不能省期货数据里持仓量在换月前后经常有缺失不处理会让 spearmanr 返回 nan。2.2 把筛选结果拼成 CNN 能吃的三维张量LSTM 需要的是 (样本数, 时间步, 特征数) 三维结构但 CNN 做一维卷积时对输入维度有额外要求。常见做法是先把筛选后的因子做标准化再按滑动窗口切分。标准化用滚动 z-score 而不是全局 z-score因为期货价格的均值和方差随时间漂移全局标准化会把早期和晚期的尺度差异抹掉。def build_sequences(features, target, window_size30, horizon5): features: 筛选后的因子矩阵 (T, F) target: 目标收益率序列 (T,) window_size: 回看窗口长度 horizon: 预测未来第几期 X, y [], [] for i in range(window_size, len(features) - horizon): X.append(features[i-window_size:i, :]) y.append(target[i horizon]) return np.array(X), np.array(y) def rolling_zscore(series, window60): 滚动 z-score避免未来函数 mean series.rolling(windowwindow, min_periodswindow//2).mean() std series.rolling(windowwindow, min_periodswindow//2).std() return (series - mean) / (std 1e-8)window_size 设 30 是我在日线数据上的经验值对应大约一个半月的交易日。horizon 设 5 表示预测未来 5 天收益率这个值要和你的调仓周期匹配做日内预测就设 1。rolling_zscore 的 window 设 60min_periods 设一半是为了让序列开头不至于全是 NaN。这里有个容易忽略的点标准化必须在切分窗口之前做而且只能用历史数据算均值和方差否则就是未来函数论文答辩时被问到会很难解释。3. CNN-Attention-LSTM 模型搭起来结构、参数与训练细节3.1 三块结构各自负责什么CNN 部分用一维卷积在时间维度上滑动提取局部模式比如连续三天的量价背离。Attention 层放在 LSTM 输出之后给不同时间步的隐藏状态分配权重让模型自己决定回看窗口里哪些天更重要。LSTM 负责捕捉长程依赖。这个顺序不能乱先 CNN 再 LSTM 再 Attention是经过验证的常见做法。如果先 LSTM 再 CNN卷积会在隐藏状态上操作可解释性会变差。import torch import torch.nn as nn class CNNAttentionLSTM(nn.Module): def __init__(self, input_dim, cnn_channels64, kernel_size3, lstm_hidden128, num_layers2, dropout0.3): super().__init__() # 一维卷积在时间维度上提取局部模式 self.conv1 nn.Conv1d(in_channelsinput_dim, out_channelscnn_channels, kernel_sizekernel_size, paddingkernel_size//2) self.bn1 nn.BatchNorm1d(cnn_channels) self.relu nn.ReLU() self.dropout nn.Dropout(dropout) # LSTM输入维度是 CNN 的输出通道数 self.lstm nn.LSTM(input_sizecnn_channels, hidden_sizelstm_hidden, num_layersnum_layers, batch_firstTrue, dropoutdropout) # Attention对 LSTM 所有时间步输出做加权 self.attn nn.Linear(lstm_hidden, 1) # 输出层 self.fc nn.Linear(lstm_hidden, 1) def forward(self, x): # x: (batch, seq_len, input_dim) - Conv1d 需要 (batch, input_dim, seq_len) x x.permute(0, 2, 1) x self.relu(self.bn1(self.conv1(x))) x self.dropout(x) x x.permute(0, 2, 1) # 换回 (batch, seq_len, channels) lstm_out, _ self.lstm(x) # (batch, seq_len, hidden) # Attention 权重 attn_scores self.attn(lstm_out) # (batch, seq_len, 1) attn_weights torch.softmax(attn_scores, dim1) context torch.sum(attn_weights * lstm_out, dim1) # (batch, hidden) out self.fc(context) return out.squeeze(-1)参数说明cnn_channels 设 64 是起点因子数少于 10 时可以降到 32。kernel_size 设 3 对应三个交易日想捕捉更长模式可以设 5 但参数量会增加。lstm_hidden 设 128num_layers 设 2这是我在单卡上跑得比较稳的配置。dropout 设 0.3 是为了对抗过拟合期货数据样本量通常不大dropout 低于 0.2 容易在验证集上看到 loss 反弹。Attention 部分用单层 Linear 加 softmax没有用多头因为毕设场景下多头注意力带来的提升有限反而增加调参负担。3.2 训练时的损失函数和早停策略期货预测的标签分布往往不对称涨跌幅度也不一致。直接用 MSE 会让模型偏向预测大幅波动。我一般用 HuberLoss它在误差小的时候接近 MSE误差大的时候接近 MAE对极端值更稳。from torch.utils.data import DataLoader, TensorDataset def train_model(model, X_train, y_train, X_val, y_val, epochs100, batch_size64, lr1e-3, patience10): device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) train_ds TensorDataset(torch.FloatTensor(X_train), torch.FloatTensor(y_train)) val_ds TensorDataset(torch.FloatTensor(X_val), torch.FloatTensor(y_val)) train_loader DataLoader(train_ds, batch_sizebatch_size, shuffleTrue) val_loader DataLoader(val_ds, batch_sizebatch_size) criterion nn.HuberLoss(delta1.0) optimizer torch.optim.Adam(model.parameters(), lrlr, weight_decay1e-5) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, modemin, factor0.5, patience5) best_val_loss float(inf) wait 0 for epoch in range(epochs): model.train() for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() pred model(xb) loss criterion(pred, yb) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() model.eval() val_loss 0 with torch.no_grad(): for xb, yb in val_loader: xb, yb xb.to(device), yb.to(device) val_loss criterion(model(xb), yb).item() * len(xb) val_loss / len(val_ds) scheduler.step(val_loss) if val_loss best_val_loss: best_val_loss val_loss torch.save(model.state_dict(), best_model.pth) wait 0 else: wait 1 if wait patience: print(fEarly stop at epoch {epoch}) break model.load_state_dict(torch.load(best_model.pth)) return modelHuberLoss 的 delta 设 1.0这个值对应收益率的标准差量级如果你的收益率已经标准化过delta 设 0.5 到 1.0 都合理。梯度裁剪 max_norm 设 1.0 是 LSTM 训练的标配不裁剪的话偶尔会出现梯度爆炸导致 loss 变 nan。早停 patience 设 10配合 ReduceLROnPlateau 的 patience 5学习率会在验证 loss 停滞时先降再停。注意保存的是 best_model.pth训练结束后要重新加载否则你用的是最后一轮而不是最优轮。4. 避坑与排查相关性分析和 CNN-Attention-LSTM 的五个血泪教训4.1 现象验证集 loss 正常但测试集完全失效原因滚动标准化时用了全量数据的均值和方差或者滑动窗口切分时把未来数据混进了训练集。这是毕设里最高频的翻车点答辩老师一问「你怎么保证没有未来函数」就露馅。解决所有统计量必须用 expanding 或 rolling 方式计算切分训练集和测试集之后再分别做窗口构造。写一个检查函数确认训练集的最大时间戳小于测试集的最小时间戳。4.2 现象spearman 筛选出的因子在训练中权重几乎为零原因筛选时用的是全样本相关性但训练集只占前 70%两个区间的相关性结构可能完全不同。因子在全样本上显著在训练集上不显著。解决相关性分析只在训练集上做用训练集选出的因子去构造测试集输入。如果训练集和测试集的因子排序差异很大说明市场结构发生了变化这时候要缩短回看窗口或者加入 regime 判断。4.3 现象模型输出几乎是一条直线原因HuberLoss 的 delta 设得太小或者收益率没有标准化导致 loss 被大量接近零的样本主导模型学会了直接输出均值。解决先检查标签的分布如果 80% 的样本收益率在正负 0.5% 以内要么做样本加权要么改用分位数损失。另一个办法是把预测目标从绝对收益率改成方向分类用交叉熵损失这样模型至少会输出有区分度的概率。4.4 现象Attention 权重全部接近均匀分布原因LSTM 的隐藏状态在时间维度上区分度不够或者 Attention 层的初始化让 softmax 输入太接近。常见于 LSTM 层数太少或者 hidden 维度太低。解决把 lstm_hidden 从 64 提到 128 以上num_layers 至少 2。另外可以在 Attention 的 Linear 之前加一个 tanh 激活让分数分布更分散。如果还是均匀检查输入序列是否本身就没有时序模式比如因子全是常数或者变化极小的指标。4.5 现象训练 loss 下降但验证 loss 从第 3 个 epoch 就开始上升原因模型参数量相对于样本量太大。期货日线数据一年约 250 个样本五年也才 1250 个而 CNN-Attention-LSTM 的参数量轻松过百万。解决先减 cnn_channels 和 lstm_hidden再加大 dropout。如果还不行用 K 折交叉验证代替单次划分把每一折的验证 loss 平均后再看趋势。另外 weight_decay 从 1e-5 提到 1e-4 也有帮助但不要超过 1e-3否则模型会欠拟合。5. 让模型真正可用的两个进阶技巧5.1 用注意力权重做因子归因Attention 层的权重不只是训练副产品它可以告诉你模型在回看窗口里更关注哪些天。把权重按时间步平均后和 spearman 筛选出的因子做交叉验证如果模型持续关注某几天而那几天正好对应某个因子的极端值说明这个因子确实被模型用上了。这个分析写进论文的「模型可解释性」章节比单纯堆准确率更有说服力。def extract_attention_weights(model, X_sample): 提取单样本的注意力权重用于归因分析 model.eval() with torch.no_grad(): x torch.FloatTensor(X_sample).unsqueeze(0) x x.permute(0, 2, 1) x model.relu(model.bn1(model.conv1(x))) x x.permute(0, 2, 1) lstm_out, _ model.lstm(x) attn_scores model.attn(lstm_out) attn_weights torch.softmax(attn_scores, dim1) return attn_weights.squeeze().numpy()拿到权重后按时间步画一条曲线看权重峰值出现在窗口的哪个位置。如果峰值集中在最近几天说明模型偏向短期动量如果分散在窗口中部说明模型在捕捉中期反转。这个观察结果可以直接写进论文讨论部分。5.2 滚动重训练而不是一次训练用到底期货市场的统计特性会随时间变化一次训练好的模型在三个月后可能就失效了。我一般用滚动重训练每 60 个交易日重新训练一次用最近 500 个交易日作为训练集最近 60 个作为验证集。这样模型能跟上市场结构的变化代价是计算量增加但毕设场景下用单卡跑一晚也能完成。具体做法是把训练函数封装成 pipeline输入是截止到某个日期的数据输出是模型和预测结果。然后按时间顺序滚动调用每次只预测未来 5 天再往前滚。这个流程跑通之后你得到的不是单一模型而是一条随时间更新的预测曲线评估指标也更稳健。提示滚动重训练时每次的 spearman 筛选也要重新做不能复用上一次的因子列表。市场结构变了因子排序也会变。我自己做这类项目最大的教训是不要等到模型搭完才回头看数据。相关性分析那一步花的时间越多后面调参越省事。我习惯在筛选因子之后先画一遍因子和目标的相关性热力图肉眼确认没有明显的未来函数痕迹再进模型。这个习惯帮我省掉了至少三次返工。希望帮到你。本文还有配套的精品资源点击获取
返回列表