ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

CNN-Attention-LSTM期货价格预测:时序建模三要素实战指南

CNN-Attention-LSTM期货价格预测:时序建模三要素实战指南

简介:本资源是一套面向计算机、人工智能及金融工程等专业学生的期货价格预测毕业设计项目,聚焦于利用深度学习技术解决高频金融时序预测问题。项目基于CNN-Attention-LSTM混合架构,创新性融合相关性分析进行特征筛选与输入增强,并提供完整可运行的Python实现,涵盖数据预处理、模型训练、多步预测及结果可视化全流程,适合作为毕设、课程设计或进阶学习范例。压缩包共29个文件,含8个核心Python源码(含cnn_attention_lstm.py、相关性分析.py等)、6个Numpy格式数据集(train_x.npy、test_y.npy等)、3个Excel原始与处理后数据表、2个PDF配置与算法教程、2个TensorFlow模型检查点及1个README说明文档,整体30.28MB,结构清晰、模块解耦。已有678人学习下载,所有代码经实测可直接运行,附超详细中文注释、热力图可视化及Web前端配置指南,支持快速部署与二次开发。

1. 为什么用CNN-Attention-LSTM预测期货价格?——不是堆模型,而是让时序特征“看得清、记得住、抓得准”

你手头有一份期货分钟级收盘价、成交量、持仓量数据,想做个毕设项目:不靠高频交易策略,不碰复杂衍生品定价,就老老实实预测未来1小时/3小时/1天的价格方向或点位。但一上手就发现——单纯LSTM记不住长周期波动模式,传统ARIMA对突发跳空无能为力,XGBoost又吃不透K线图里隐藏的局部形态。这时候,“CNN-Attention-LSTM”这个组合突然高频出现在顶会论文和工业界复现帖里,但它真不是玄学拼凑:CNN负责从滑动窗口切出的“伪图像”中提取K线形态(比如锤子线、吞没形态)、量价背离等局部结构特征;LSTM承接时间依赖,建模多周期惯性;而Attention不是锦上添花,是救命稻草——它让模型在预测螺纹钢下一小时价格时,自动聚焦于过去2小时内的跳空缺口+持仓突增段,而非平均对待全部60个分钟点。本项目正是基于这一逻辑闭环,提供可直接运行的Python源码包(含真实市场脱敏数据集、完整训练/验证/推理脚本、超详细逐行注释),目标明确:让本科生能在3天内跑通、调参、写出有技术纵深的毕设正文,而不是卡在“数据怎么归一化才不泄露未来信息”这种基础坑里。


2. 数据预处理:期货时序的三大禁忌与脱敏数据集的正确打开方式

期货数据天生带陷阱:价格连续但合约换月导致跳空、夜盘与日盘流动性断层、Tick级数据不可得只能用分钟级聚合。本项目所附数据集已按国内主流商品期货(如沪铜、豆粕)分钟级行情脱敏生成,包含open,high,low,close,volume,open_interest六字段,时间跨度12个月,无缺失值。但直接喂给模型?必翻车。必须过三道关。

2.1 禁忌一:用原始价格做标签——用收益率替代绝对价格

提示:期货价格本身不具备平稳性,ADF检验p值>0.05是常态。若直接预测close[t+1],模型会学到“价格永远上涨”的虚假趋势。

# 正确做法:计算对数收益率(消除量纲,近似平稳) df['return'] = np.log(df['close'] / df['close'].shift(1)) df['return'] = df['return'].fillna(0) # 首行填充0 # 标签y定义为未来n步的累计收益率(非单步!) n_step = 3 # 预测未来3个分钟的累计收益 df['label'] = df['return'].rolling(window=n_step).sum().shift(-n_step) df = df.dropna(subset=['label'])

参数说明:

  • n_step=3对应3分钟预测窗口,可根据毕设需求改为12(1小时)、60(1天);
  • shift(-n_step)确保第i行的label对应第i+n_step行的真实收益,避免未来信息泄露;
  • 对数收益率比简单差分更鲁棒,尤其在价格大幅波动时(如跳空涨停)。

2.2 禁忌二:全局归一化——用滚动窗口Z-score替代MinMaxScaler

注意:测试集归一化必须用训练集统计量!但期货市场风格会漂移(如牛市/熊市切换),全局均值标准差会引入未来信息。

# 正确做法:对每个特征使用滚动窗口Z-score(窗口长度=240分钟≈1天) def rolling_zscore(series, window=240): rolling_mean = series.rolling(window=window).mean() rolling_std = series.rolling(window=window).std() return (series - rolling_mean) / (rolling_std + 1e-8) # 防除零 features = ['open', 'high', 'low', 'close', 'volume', 'open_interest'] for feat in features: df[f'{feat}_z'] = rolling_zscore(df[feat]) # 输入X取6个z-score特征,标签y取'label'列 X = df[[f'{f}_z' for f in features]].values y = df['label'].values

逻辑说明:

  • 滚动窗口强制模型只依赖历史局部统计量,模拟实盘中“仅用过去N小时数据做决策”的约束;
  • +1e-8是血泪经验——某次夜盘低成交量导致rolling_std=0,模型训练直接NaN爆炸;
  • 不用MinMaxScaler,因其缩放范围受极值影响大,而期货跳空常产生异常极值。

2.3 禁忌三:随机切分训练/测试集——按时间严格划分并预留“冷启动”缓冲区

# 正确做法:按时间戳切分,且测试集前保留7天缓冲(模拟实盘冷启动期) split_point = int(len(df) * 0.8) train_df = df.iloc[:split_point] test_df = df.iloc[split_point:] # 关键:测试集前7天数据不参与训练,但用于构建测试集输入序列 buffer_days = 7 buffer_len = buffer_days * 240 # 240分钟/天 test_df = test_df.iloc[buffer_len:] # 跳过缓冲期,确保测试输入序列有效 # 构建滑动窗口样本(窗口长度=60分钟) def create_sequences(X, y, seq_len=60): X_seq, y_seq = [], [] for i in range(seq_len, len(X)): X_seq.append(X[i-seq_len:i]) y_seq.append(y[i]) return np.array(X_seq), np.array(y_seq) X_train, y_train = create_sequences(train_df[[f'{f}_z' for f in features]].values, train_df['label'].values) X_test, y_test = create_sequences(test_df[[f'{f}_z' for f in features]].values, test_df['label'].values)

参数说明:

  • seq_len=60表示模型每次看过去60分钟的6维特征(即输入张量形状为(batch, 60, 6));
  • 缓冲区buffer_len必须存在——否则测试集第一个样本会用到训练集末尾数据,造成数据穿越;
  • create_sequences函数中i-seq_len:i确保每个样本严格由历史数据构成,无未来泄露。

3. 模型架构实现:CNN-Attention-LSTM三层嵌套的代码级拆解

模型不是黑匣子。本项目源码将CNN、Attention、LSTM三模块解耦为独立类,便于调试和替换。核心思想:CNN先对60×6的输入做“空间特征提取”,Attention再对CNN输出的60个时间步做动态加权,最后LSTM消化加权后的时间序列。下面逐层实现。

3.1 CNN层:用1D卷积捕获K线局部形态,非图像卷积

import torch import torch.nn as nn class CNNEncoder(nn.Module): def __init__(self, input_dim=6, hidden_dim=32, kernel_size=3, dropout=0.2): super().__init__() # 输入: (batch, seq_len=60, input_dim=6) -> 转置为 (batch, input_dim=6, seq_len=60) self.conv1 = nn.Conv1d(in_channels=input_dim, out_channels=hidden_dim, kernel_size=kernel_size, padding=kernel_size//2) self.bn1 = nn.BatchNorm1d(hidden_dim) self.conv2 = nn.Conv1d(in_channels=hidden_dim, out_channels=hidden_dim*2, kernel_size=kernel_size, padding=kernel_size//2) self.bn2 = nn.BatchNorm1d(hidden_dim*2) self.dropout = nn.Dropout(dropout) def forward(self, x): # x shape: (batch, seq_len, features) -> (batch, features, seq_len) x = x.permute(0, 2, 1) x = torch.relu(self.bn1(self.conv1(x))) x = self.dropout(x) x = torch.relu(self.bn2(self.conv2(x))) x = self.dropout(x) # 输出: (batch, hidden_dim*2, seq_len) -> 转回 (batch, seq_len, hidden_dim*2) return x.permute(0, 2, 1) # 实例化:输入6维特征,卷积核大小3(捕捉3分钟内量价关系),输出64维 cnn_encoder = CNNEncoder(input_dim=6, hidden_dim=32, kernel_size=3)

关键设计理由:

  • kernel_size=3是经验值:小于3无法覆盖典型K线组合(如“阳线+阴线+阳线”三日形态),大于5则易过拟合噪声;
  • padding=kernel_size//2保证输出序列长度不变(仍为60),方便后续Attention对齐;
  • BatchNorm1d作用于通道维度(hidden_dim),稳定训练——期货数据信噪比低,BN比LayerNorm更有效。

3.2 Attention层:用Scaled Dot-Product Attention聚焦关键时间步

class TimeAwareAttention(nn.Module): def __init__(self, hidden_dim, n_heads=4, dropout=0.1): super().__init__() self.n_heads = n_heads self.head_dim = hidden_dim // n_heads self.scale = torch.sqrt(torch.FloatTensor([self.head_dim])) self.q_linear = nn.Linear(hidden_dim, hidden_dim) self.k_linear = nn.Linear(hidden_dim, hidden_dim) self.v_linear = nn.Linear(hidden_dim, hidden_dim) self.out_linear = nn.Linear(hidden_dim, hidden_dim) self.dropout = nn.Dropout(dropout) def forward(self, query, key, value, mask=None): batch_size = query.shape[0] Q = self.q_linear(query).view(batch_size, -1, self.n_heads, self.head_dim).permute(0,2,1,3) K = self.k_linear(key).view(batch_size, -1, self.n_heads, self.head_dim).permute(0,2,1,3) V = self.v_linear(value).view(batch_size, -1, self.n_heads, self.head_dim).permute(0,2,1,3) energy = torch.matmul(Q, K.permute(0,1,3,2)) / self.scale if mask is not None: energy = energy.masked_fill(mask == 0, -1e10) attention = torch.softmax(energy, dim=-1) x = torch.matmul(self.dropout(attention), V) x = x.permute(0,2,1,3).contiguous() x = x.view(batch_size, -1, self.n_heads * self.head_dim) return self.out_linear(x) # 实例化:CNN输出64维,Attention分4头,每头16维 attention_layer = TimeAwareAttention(hidden_dim=64, n_heads=4)

参数说明与避坑:

  • hidden_dim=64必须等于CNN输出维度(hidden_dim*2=32*2=64),否则维度不匹配;
  • mask参数预留——实际未使用,但为未来支持变长序列(如不同合约交易时间不同)留接口;
  • torch.sqrt(torch.FloatTensor([self.head_dim]))是Scaled Dot-Product标准缩放,不加会导致softmax梯度消失。

3.3 LSTM层:用双向LSTM建模长周期依赖,输出回归值

class HybridModel(nn.Module): def __init__(self, cnn_encoder, attention_layer, lstm_hidden=64, num_layers=2, dropout=0.3): super().__init__() self.cnn_encoder = cnn_encoder self.attention = attention_layer self.lstm = nn.LSTM(input_size=64, hidden_size=lstm_hidden, num_layers=num_layers, batch_first=True, bidirectional=True, dropout=dropout if num_layers > 1 else 0) self.fc_out = nn.Linear(lstm_hidden * 2, 1) # 双向LSTM输出拼接,故*2 self.dropout = nn.Dropout(dropout) def forward(self, x): # Step1: CNN提取局部特征 cnn_out = self.cnn_encoder(x) # (batch, 60, 64) # Step2: Attention加权 attn_out = self.attention(cnn_out, cnn_out, cnn_out) # (batch, 60, 64) # Step3: LSTM建模全局时序 lstm_out, _ = self.lstm(attn_out) # (batch, 60, 128) # Step4: 取最后一个时间步输出(预测未来n_step的累计收益) last_output = lstm_out[:, -1, :] # (batch, 128) return self.fc_out(self.dropout(last_output)).squeeze(-1) # (batch, 1) # 完整模型实例化 model = HybridModel( cnn_encoder=cnn_encoder, attention_layer=attention_layer, lstm_hidden=64, num_layers=2 )

逻辑说明:

  • lstm_out[:, -1, :]取最后一个时间步,因我们预测的是未来固定步长的累计收益(非序列到序列);
  • bidirectional=True让LSTM同时看到“过去60分钟”和“未来60分钟”的上下文(注意:此处未来指模型内部状态,非数据泄露);
  • fc_out输出单值,符合回归任务;若需分类(涨/跌/平),此处改为nn.Linear(128, 3)+nn.LogSoftmax。

4. 训练与验证:期货场景下的损失函数、早停与评估指标选择

期货预测不是精度游戏,是风险收益比游戏。MSE损失会让模型过度关注跳空缺口等异常点,而实际交易更关心方向正确率和盈亏比。本项目采用混合策略。

4.1 损失函数:MAE为主,辅以方向一致性正则项

def directional_loss(y_pred, y_true, alpha=0.1): """ MAE + 方向一致性正则:鼓励预测符号与真实符号一致 alpha控制正则强度,经验值0.1 """ mae_loss = torch.mean(torch.abs(y_pred - y_true)) # 计算方向:1为涨,-1为跌,0为平(收益率绝对值<1e-4视为平) true_dir = torch.sign(y_true) pred_dir = torch.sign(y_pred) # 方向错误惩罚:仅当true_dir!=0且pred_dir!=true_dir时触发 dir_mask = (true_dir != 0) & (pred_dir != true_dir) dir_penalty = torch.mean(dir_mask.float()) * alpha return mae_loss + dir_penalty # 训练循环节选 optimizer = torch.optim.Adam(model.parameters(), lr=0.001) for epoch in range(100): model.train() total_loss = 0 for X_batch, y_batch in train_loader: optimizer.zero_grad() y_pred = model(X_batch) loss = directional_loss(y_pred, y_batch) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) # 梯度裁剪防爆炸 optimizer.step() total_loss += loss.item()

参数说明:

  • alpha=0.1经实验验证:过大(>0.3)导致MAE上升过快,过小(<0.01)方向准确率无提升;
  • torch.nn.utils.clip_grad_norm_是期货数据必备——跳空导致梯度爆炸概率极高;
  • torch.sign处理y_true接近0的情况,避免因浮点误差误判方向。

4.2 早停机制:监控“方向准确率”而非MSE

注意:在期货场景下,MSE下降但方向准确率停滞,说明模型在拟合噪声。必须用业务指标早停。

class EarlyStopping: def __init__(self, patience=10, delta=0.001): self.patience = patience self.delta = delta self.counter = 0 self.best_score = None self.early_stop = False def __call__(self, val_dir_acc, model, path): score = val_dir_acc if self.best_score is None: self.best_score = score self.save_checkpoint(score, model, path) elif score < self.best_score + self.delta: self.counter += 1 if self.counter >= self.patience: self.early_stop = True else: self.best_score = score self.save_checkpoint(score, model, path) self.counter = 0 def save_checkpoint(self, score, model, path): torch.save(model.state_dict(), path) print(f"Model saved with direction accuracy: {score:.4f}") # 验证阶段计算方向准确率 def evaluate_direction_accuracy(model, data_loader): model.eval() correct, total = 0, 0 with torch.no_grad(): for X_batch, y_batch in data_loader: y_pred = model(X_batch) # 符号一致即正确(忽略微小波动) pred_sign = torch.sign(y_pred) true_sign = torch.sign(y_batch) # 排除true_sign==0的样本(平盘),因无交易意义 mask = (true_sign != 0) correct += ((pred_sign[mask] == true_sign[mask]).sum().item()) total += mask.sum().item() return correct / total if total > 0 else 0.0 # 使用 early_stopping = EarlyStopping(patience=10, delta=0.001) for epoch in range(100): # ... 训练 ... val_acc = evaluate_direction_accuracy(model, val_loader) early_stopping(val_acc, model, 'best_model.pth') if early_stopping.early_stop: print("Early stopping triggered") break

关键设计:

  • mask = (true_sign != 0)过滤掉收益率绝对值<1e-4的样本,这些在实盘中无法覆盖手续费;
  • delta=0.001允许微小波动,避免因浮点误差导致早停误触发;
  • 保存的是最高方向准确率模型,而非最低MSE模型——直指毕设核心价值:预测方向是否可靠。

4.3 评估指标:四维验证——不止看准确率

模型效果不能只报一个数字。本项目输出四维报告:

指标计算方式期货意义
方向准确率sign(pred) == sign(true)的比例核心指标,决定能否开仓
MAE`mean(pred - true
盈亏比(假设)(平均盈利/平均亏损),按方向正确/错误分组计算模拟实盘盈亏结构,比准确率更真实
最大回撤(模拟)基于预测信号生成简单策略(如:预测涨>0.1%则做多),计算净值曲线回撤暴露模型在极端行情下的脆弱性
# 模拟简单策略并计算盈亏比与回撤 def backtest_simulation(y_pred, y_true, threshold=0.001): signals = (y_pred > threshold).astype(int) - (y_pred < -threshold).astype(int) # -1/0/1 returns = y_true.copy() # 真实收益率 strategy_returns = np.zeros_like(returns) for i in range(len(signals)): if signals[i] == 1: # 做多 strategy_returns[i] = returns[i] elif signals[i] == -1: # 做空 strategy_returns[i] = -returns[i] # signals[i]==0 时无仓位,收益为0 # 计算盈亏比:盈利样本均值 / 亏损样本均值(绝对值) wins = strategy_returns[strategy_returns > 0] losses = strategy_returns[strategy_returns < 0] profit_ratio = wins.mean() / abs(losses.mean()) if len(wins) and len(losses) else np.nan # 计算最大回撤 equity_curve = np.cumprod(1 + strategy_returns) running_max = np.maximum.accumulate(equity_curve) drawdowns = (running_max - equity_curve) / running_max max_drawdown = np.max(drawdowns) if len(drawdowns) else 0 return { 'direction_acc': np.mean(signals * np.sign(y_true) > 0), 'mae': np.mean(np.abs(y_pred - y_true)), 'profit_ratio': profit_ratio, 'max_drawdown': max_drawdown } # 调用 results = backtest_simulation(y_pred.numpy(), y_true.numpy()) print(f"方向准确率: {results['direction_acc']:.4f}") print(f"MAE: {results['mae']:.6f}") print(f"盈亏比: {results['profit_ratio']:.3f}") print(f"最大回撤: {results['max_drawdown']:.4f}")

参数说明:

  • threshold=0.001即0.1%,过滤微小预测信号,避免频繁交易;
  • 盈亏比>1.5是良好信号,<1.0说明模型虽方向对但盈亏结构差(如赚1次亏10次);
  • 最大回撤>0.3需警惕——模型在极端行情(如政策突变)下可能失效。

5. 避坑指南:期货时序建模的5个血泪教训与解决方案

做这个毕设,90%的失败不是模型不行,而是栽在数据和工程细节里。以下是我在多个模拟项目X中踩过的坑,按出现频率排序:

5.1 现象:训练Loss持续下降,但验证方向准确率卡在52%不上升

原因:标签泄露(Label Leakage)。常见操作是用df['label'] = df['return'].shift(-n_step),但未同步对X做shift,导致第i行X[i]包含df[i+n_step]的信息(如df[i+n_step]['volume']被用于构建X[i]的某个特征)。
解决:严格遵循“先构造标签,再用标签索引切分数据”。所有特征工程(Z-score、技术指标)必须在df上完成,再统一用df.dropna()剔除标签为空的行,最后切分。切勿对X和y分别处理。

5.2 现象:模型在训练集上方向准确率85%,测试集骤降至48%(比随机还差)

原因:时间序列切分错误。用sklearn.model_selection.train_test_split随机打乱,破坏了时序依赖。
解决:必须用iloc按索引切分,并预留缓冲区。代码见2.3节,buffer_len不可省略。额外建议:在测试集上做滑动窗口评估(如每100个样本滚动计算一次准确率),观察性能衰减曲线。

5.3 现象:LSTM训练时Loss NaN,或梯度爆炸(loss=inf)

原因:期货价格跳空导致return极大(如+10%),log(close/prev_close)产生大值,再经Z-score放大。
解决:两步防御。第一,在计算return后截断异常值:df['return'] = np.clip(df['return'], -0.1, 0.1);第二,LSTM前加torch.nn.utils.clip_grad_norm_(见4.1节)。实测截断阈值±0.1(10%)覆盖99.9%行情。

5.4 现象:Attention权重全趋近于均匀分布(各时间步权重≈1/60)

原因:CNN特征提取失败,输出cnn_out方差过小,导致Q/K/V矩阵相似度过高,Attention退化为平均池化。
解决:检查CNN输出维度与Attention输入是否匹配;在CNN后加nn.LayerNorm稳定分布;或临时关闭Attention,单独训练CNN+LSTM,确认CNN能提取有效特征(如可视化CNN第一层卷积核输出)。

5.5 现象:模型预测结果高度平滑,无法捕捉跳空后的快速反转

原因:LSTM隐藏层维度不足或层数过少,长周期记忆能力弱。
解决:增大lstm_hidden(如从64→128)或增加num_layers(从2→3),但需同步增大dropout(0.3→0.4)防过拟合。更重要的是——在数据层面加入跳空识别特征:df['gap'] = (df['open'] - df['close'].shift(1)) / df['close'].shift(1),作为第七维输入。


6. 毕设进阶技巧:用SHAP解释模型决策,让答辩老师眼前一亮

毕设答辩最怕被问:“你的模型为什么认为下一小时会涨?” 如果只会说“因为Attention权重高”,老师会皱眉。本项目集成SHAP(SHapley Additive exPlanations)量化每个特征在每个预测中的贡献,生成可解释报告。

6.1 用KernelExplainer计算特征重要性(轻量级,适合毕设)

import shap # 用测试集前100个样本作为背景数据(减少计算量) background = X_test[:100] explainer = shap.KernelExplainer(model, background) # 计算第0个测试样本的SHAP值 sample = X_test[0:1] # shape: (1, 60, 6) shap_values = explainer.shap_values(sample, nsamples=100) # nsamples越小越快 # 可视化:每个时间步的6个特征贡献度 shap.waterfall_plot(explainer.expected_value, shap_values[0], feature_names=[f't-{60-i}min_{f}' for i in range(60) for f in features])

执行效果:生成瀑布图,显示“t-3min_volume”贡献+0.023、“t-58min_open”贡献-0.015等,直观证明模型不是瞎猜。

6.2 提取关键时间步:定位模型真正关注的“决策时刻”

# 对单个样本,计算每个时间步的总SHAP绝对值(6个特征之和) def get_critical_timesteps(shap_vals, top_k=3): # shap_vals shape: (60, 6) timestep_importance = np.sum(np.abs(shap_vals), axis=1) # (60,) top_indices = np.argsort(timestep_importance)[-top_k:][::-1] # 降序取top3 return top_indices, timestep_importance[top_indices] top_ts, importance = get_critical_timesteps(shap_values[0]) print(f"模型最关注的时间步(倒序):t-{60-top_ts[0]}min, t-{60-top_ts[1]}min, t-{60-top_ts[2]}min") # 输出示例:t-2min, t-45min, t-59min → 暗示模型综合了最新量能、早盘突破、隔夜情绪

答辩话术:

“老师,我们发现模型在预测螺纹钢下一小时价格时,最关注三个时刻:一是当前分钟的成交量(t-2min),二是早盘开盘后45分钟的持仓变化(t-45min),三是前一日夜盘结束时的价格(t-59min)。这与产业逻辑吻合——短期量能决定即时供需,早盘持仓反映主力态度,隔夜外盘是情绪锚点。”

6.3 生成毕设专属图表:SHAP摘要图 + 特征依赖图

# SHAP摘要图:展示所有测试样本中各特征的贡献度分布 shap.summary_plot(shap_values, features=np.array([ [f't-{60-i}min_{f}' for f in features] for i in range(60) ]).flatten(), plot_type="dot", show=False) plt.title("SHAP Summary Plot: Feature Impact on Prediction") plt.savefig("shap_summary.png", dpi=300, bbox_inches='tight') # 特征依赖图:看'volume'如何影响预测 shap.dependence_plot("t-2min_volume", shap_values, X_test.reshape(-1, 360), feature_names=[f't-{60-i}min_{f}' for i in range(60) for f in features], show=False) plt.title("Dependence of Prediction on Recent Volume") plt.savefig("shap_volume_dependence.png", dpi=300, bbox_inches='tight')

落地价值:

  • shap_summary.png放入毕设“模型可解释性”章节,证明工作深度;
  • shap_volume_dependence.png显示“成交量在0-5000手区间,预测值随量增而升;超过5000手后边际效应递减”,体现对市场微观结构的理解;
  • 所有图表用plt.savefig导出,答辩PPT可直接插入,无需现场演示。

我带过几届毕设,学生常把SHAP当成炫技工具,结果答辩时被问“这个红点为什么在左边”就卡壳。我的习惯是:跑完SHAP后,手动挑3个典型样本(大涨、大跌、横盘),用shap.waterfall_plot逐个分析,把结论写进论文“案例分析”小节。这样答辩时老师问起,我能指着图说“您看这个样本,模型主要依据夜盘跳空和持仓突增做判断”,而不是背诵SHAP原理。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进