简介:这是一份基于Python实现LSTM对股票走势预测的完整项目源码与文档说明,面向深度学习入门者及金融量化分析爱好者,提供从数据处理到模型训练、评估的全套可运行代码。压缩包共13个文件,整体仅358KB,包含5个Python脚本,分别承担数据读取与预处理、LSTM网络定义、训练流程、结果评估等任务;另有3个pyc缓存文件、2张训练效果图(如损失曲线或预测对比)、1份CSV历史行情数据、1个已保存的模型权重文件以及1份说明文档。读者可依据说明文档复现股票走势预测实验,学习LSTM在时序数据上的建模技巧与超参数调节方法,也可直接加载训练好的模型验证预测效果。通过自行调整历史窗口长度、网络层数等参数,能直观感受不同配置对预测结果的影响,非常适合动手实践。项目代码结构清晰,已有291人学习下载,适合课程设计、毕业设计或个人学习使用。
1. 这份 LSTM 股票预测项目,到底能拿来干什么
如果你是第一次接触“用 LSTM 预测股票走势”这个方向,我建议你先别急着迷信它能赚钱——它真正擅长的,是识别序列里的惯性:给定过去 N 天的收盘价,预测下一天的收盘价。这份基于 Python 的源码包就是一个完整的单序列预测闭环,从 CSV 原始数据、滑窗样本构造、LSTM 模型定义,到训练、评估、出图、保存模型,全部齐了。适合三类人:想跑通第一个时间序列项目的学生、刚入门的量化爱好者、以及想拿公开数据练手但不想从零写数据处理的工程师。跑一遍你就能看到预测曲线和真实曲线长什么样,也能直观感受到 LSTM 的“滞后”和“钝化”到底是怎么回事。
2. 项目结构和数据流:从原始 CSV 到预测曲线是怎么串起来的
拿到压缩包先别急着双击 run,先把这个项目的文件结构理清楚。这个项目不算大,但每个文件的分工非常明确,理解清楚之后你改起来才有抓手,不然你连“改哪个文件能调参”都找不到。
2.1 文件清单与运行入口
先看包的根目录,核心文件我列在下面这张表里:
| 文件 | 作用 |
|---|---|
| data/000001SH_index.csv | 原始行情数据,上证指数日线,含日期、开高低收、成交量 |
| parser_my.py | 自定义的 CSV 解析和日期处理模块 |
| dataset.py | 滑窗样本构造、归一化、训练集/测试集切分 |
| LSTMModel.py | LSTM 网络结构定义 |
| train.py | 训练入口,跑完产出 model/stock.pkl |
| evaluate.py | 加载训练好的模型,评估并绘制预测曲线到 img/17.png、18.png |
| model/stock.pkl | 训练好的模型权重 |
| README.md | 中文运行说明 |
运行入口非常简单:先python train.py,等 loss 降下来、模型保存好之后,再跑python evaluate.py,预测图就会输出到img/目录下。这个流程非常像一个标准的“训练 + 评估”双脚本结构,小项目这么切完全够用,不用上 MLflow 那一套。
2.2 数据怎么喂给 LSTM:parser_my.py 和 dataset.py 的分工
这里要重点讲一下数据链路。parser_my.py负责把 CSV 里的原始行解析成内存里的 DataFrame 或者 numpy 数组,重点是处理日期格式和缺失值。这个文件名字里带my,说明是作者自己写的解析器,而不是直接用 pandas 的read_csv一把梭。常见做法是:
# parser_my.py 的核心思路(与源码逻辑一致) import pandas as pd def load_stock_data(csv_path): df = pd.read_csv(csv_path) df['date'] = pd.to_datetime(df['date'], format='%Y-%m-%d') df = df.sort_values('date') # 有些源数据会有停牌导致的空行,直接丢弃全空行 df = df.dropna(subset=['close']) return df这里有两个点要注意:一是日期必须解析成 datetime 并且按时间升序排序,LSTM 看的是时间顺序,一旦原始数据乱序,后面整个训练都白搭;二是dropna丢弃的是收盘价为空的行,如果只丢一两天问题不大,但如果你换了自己的数据源,停牌时间很长,直接丢行会让时间间隔变得不均匀,这个坑后面第 5 章我会细说。
真正构造样本的是dataset.py。它的核心工作就是把一条连续的价格序列切成一堆(x, y)对,x 是过去seq_len天的收盘价,y 是第seq_len + 1天(也就是下一天)的收盘价,然后滑窗滑动:
# dataset.py 中滑窗构造的核心逻辑 import numpy as np def create_sequences(data, seq_len=20): xs, ys = [], [] for i in range(len(data) - seq_len): x = data[i:i + seq_len] y = data[i + seq_len] xs.append(x) ys.append(y) return np.array(xs), np.array(ys)这一步做完,输入形状就是(样本数, 20, 特征数)。如果只用收盘价一个特征,特征数就是 1;如果后面你把成交量也拼进来,特征数就变成 2。LSTM 的输入要求是三维张量,第一维是 batch,第二维是时间步长度,第三维是每个时间步的特征维度,这个顺序一定要记牢——后面定义模型时batch_first=True就跟它对应。
2.3 模型长什么样:LSTMModel.py 的网络结构
LSTMModel.py里定义的网络非常经典,就是一个两层 LSTM 接一个全连接输出层。用 PyTorch 写出来大概是这样的:
# LSTMModel.py 的核心结构(典型写法) import torch.nn as nn class LSTMModel(nn.Module): def __init__(self, input_size=1, hidden_size=64, num_layers=2, output_size=1): super().__init__() self.lstm = nn.LSTM( input_size=input_size, hidden_size=hidden_size, num_layers=num_layers, batch_first=True ) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): # x 形状: (batch, seq_len, input_size) out, _ = self.lstm(x) # 取最后一个时间步的隐藏状态 last_step = out[:, -1, :] return self.fc(last_step)几个参数我要展开讲一下。hidden_size=64表示每个 LSTM 单元内部隐含状态的维度,64 算是一个性价比很高的起步值,太小欠拟合,太大容易过拟合,而且训练时间会翻倍。num_layers=2是堆叠两层 LSTM,比单层能捕捉更复杂的时序依赖,但这个项目的止损点是,网络复杂度上去了,对输入数据的质量要求也上去了,数据太脏或者样本太少,两层反而比一层更容易发散。
forward函数里最关键的操作是out[:, -1, :]。out是每个时间步的隐藏状态序列,形状是(batch, seq_len, hidden_size),我们做单步预测只需要最后一个时间步的 hidden state,把它过全连接层回归出下一个交易日的收盘价。很多新手在这里会犯错——直接对整个out做全连接,等于把历史每一天的隐藏状态都拿去预测下一天,信息冗余不说,效果反而更差。
2.4 训练与评估:train.py 和 evaluate.py 各自的职责
train.py做的事情就是标准的监督学习循环:加载 dataset 构造好的样本,定义 MSE 损失函数,用 Adam 优化器迭代若干轮,每一轮都计算 loss,等 loss 降到合理区间之后调用torch.save把模型权重存到model/stock.pkl。核心训练代码是这样的:
# train.py 的训练循环(简化示意) for epoch in range(epochs): model.train() total_loss = 0.0 for x_batch, y_batch in train_loader: optimizer.zero_grad() pred = model(x_batch) loss = criterion(pred, y_batch) loss.backward() optimizer.step() total_loss += loss.item() if epoch % 20 == 0: print(f"epoch {epoch}, loss: {total_loss / len(train_loader):.6f}")这里我特别强调两点。第一,model.train()和后面evaluate.py里的model.eval()一定要成对出现,虽然 LSTM 这种纯 mlp + lstm 结构没有 dropout 的随机性问题,但 PyTorch 里eval()还会影响某些层的梯度计算行为,养成习惯准没错。第二,训练集的输入 x 和标签 y 都必须是归一化之后的值,你预测出来的结果也是归一化的,如果要看真实价格,必须反归一化,这个动作是在 evaluate 阶段做的。
evaluate.py的职责跟前一个文件刚好相反:加载stock.pkl权重,把测试集的归一化预测结果inverse_transform回真实价格,然后画两条曲线——一条橙色是真实收盘价,一条蓝色是预测值,输出到img/17.png和18.png。你看到那两张图里预测曲线总体贴着真实曲线走、但整体往右移了一格,那就是 LSTM 学到的“惯性”——它倾向于预测出和前一天差不多的值,遇到突变就反应不过来,这是所有单序列 LSTM 预测的通病,不是代码写错了。
3. 把行情数据变成 LSTM 能吃的样本:窗口长度和归一化的关键细节
这一章是整个项目里最容易翻车的地方,也是你调参时最值得花时间的部分。很多人跑完这个项目之后发现预测曲线几乎是一条平移的线,就开始怀疑模型有问题,其实多数情况下是数据预处理环节埋了雷。
3.1 为什么用日线收盘价而不是分钟线或多因子
这个项目用的是000001SH_index.csv,上证指数日线,拿来做技术验证是非常合适的选择。日线数据的优点是噪声相对小、趋势相对完整,而且数据量不大,CPU 上几分钟就能训完,非常适合拿来跑通流程。分钟线数据虽然信息量更大,但噪声也更大,LSTM 学会拟合分钟线的“毛刺”之后,换到别的股票上就明显过拟合。我的建议是,等你用这个项目把日线流程跑通之后,再去考虑换分钟线或者加入成交量、MACD 这类因子,那是第 6 章的事,不要在第一步就给自己上难度。
3.2 滑窗长度 seq_len 怎么定:先试 20,再调 10 和 40
seq_len是 LSTM 真正最重要的超参数之一,它决定模型每次“回头看”多少个交易日。这个项目里 dataset.py 默认用的是 20——恰好约等于一个自然月的交易日数量,这也是绝大多数股票 LSTM 项目的起步值。
我一般会同时跑三个值做对比:10、20、40。窗口太短,比如 5,模型只看得到最近一周的价格,遇到回调就认为是趋势反转,预测曲线会异常敏感;窗口太长,比如 60,模型会把三个月前的价格信息也带进来,而这些旧信息对当前走势基本没有帮助,反而拖慢训练、让模型变得迟钝。判断窗口是否合适有个很直观的方法:训练完之后看测试集上第一个预测点的误差——如果第一个点就偏得离谱,大概率是窗口设置和后端数据的日期偏移对不上。
3.3 归一化:先 fit 训练段,再 transform 测试段
LSTM 对输入尺度非常敏感,价格从 3000 点到 5000 点波动,如果不归一化,激活函数很容易饱和,梯度直接消失。这个项目里用的是 MinMaxScaler,把数据缩放到 [0, 1] 区间。我强烈建议你在读源码时重点确认一件事:fit_transform是作用在全量数据上,还是只作用在训练集段上?
# dataset.py 中归一化的推荐写法 from sklearn.preprocessing import MinMaxScaler # 错误示范:全量数据一起 fit,相当于将来来的信息泄露到了训练阶段 scaler = MinMaxScaler() scaled_all = scaler.fit_transform(close.reshape(-1, 1)) # 正确做法:只用训练段 fit,再用训练段学到的 min/max 去 transform 测试段 train_size = int(len(close) * 0.8) scaler = MinMaxScaler() scaler.fit(close[:train_size].reshape(-1, 1)) train_scaled = scaler.transform(close[:train_size].reshape(-1, 1)) test_scaled = scaler.transform(close[train_size:].reshape(-1, 1))如果源码里是直接在全量数据上fit_transform,测试集的 min/max 已经被模型“看到”了,评估指标会虚高,而且拿到真实场景里做滚动预测时效果会明显变差。这个坑属于典型的“前视偏差”,在第 5 章我会专门再讲一条。
3.4 训练集 / 测试集切分:按时间切,绝不能随机打乱
时间序列和普通机器学习最大的不同是:样本之间有强顺序依赖,切分时必须保序。这个项目的标准做法是取前 80% 的交易日做训练、后 20% 做测试,这样测试集永远是“未来”的数据,模型没有见过。如果你用的是 sklearn 的train_test_split并且没设shuffle=False,那就等于把时序打乱了,模型看到的训练样本里混着“未来”的信息,测试集也变成了过去的数据,出来的指标没有任何参考价值。
# 时间序列切分,必须保持原有顺序 train_end = int(len(scaled) * 0.8) train_data = scaled[:train_end] test_data = scaled[train_end:]数据切分这块还有一个很多人忽略的细节:构造滑窗样本时,测试集的第一个窗口是从train_end - seq_len位置开始切的,这样才能保证窗口里的最后一个时间步对齐到train_end。如果直接硬切,测试集第一个窗口会缺前一天的上下文,预测值偏差会非常大。
4. 训练与评估:把超参数和训练流程吃透,才能说“我会用 LSTM”
项目跑通不难,但你要想拿它换自己的数据、调自己的参数,就必须把 train.py 里每一个超参数的含义吃透。这一章我按实际踩坑的顺序,把训练和评估阶段的参数、流程、常见误区完整讲一遍。
4.1 超参数表与推荐值范围
先把这张参数表存下来,后面调参时对照着看:
| 参数 | 推荐值 | 取值范围 | 影响 |
|---|---|---|---|
| seq_len | 20 | 10~60 | 回看窗口长度,太小噪声大,太大信息过时 |
| hidden_size | 64 | 32~128 | LSTM 隐藏维度,越大拟合能力越强但越容易过拟合 |
| num_layers | 2 | 1~3 | 层数,2 层是性价比最高的选择 |
| learning_rate | 0.001 | 0.0001~0.01 | 学习率,太大不收敛,太小收敛慢 |
| batch_size | 32 | 16~128 | 每批样本数,影响训练的稳定性 |
| epochs | 200 | 100~500 | 训练轮次,看 loss 曲线决定是否提前停止 |
| loss_function | MSE | MSE / MAE | 回归任务常用 MSE,对异常值更敏感 |
| optimizer | Adam | Adam / SGD | Adam 适合时序回归,基本不用调 |
这里单独说一下learning_rate=0.001。这个值是 Adam 的默认起步学习率,适用于大部分 LSTM 回归场景。如果训练时 loss 一直在正常下降但速度很慢,可以调到 0.005;如果 loss 在最初的几十轮就开始震荡不降,调到 0.0001 重试。学习率的问题一定要通过看 loss 曲线来判断,不要凭感觉。
4.2 train.py 的完整训练流程拆解
下面是一个基于该项目结构的完整训练流程,我把注释写详细一点,你可以直接对照源码看:
# train.py 中从数据到模型训练的完整链路 import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset # 1. 加载数据并构造滑窗样本 close = load_stock_data('data/000001SH_index.csv')['close'].values seq_len = 20 x_all, y_all = create_sequences(close, seq_len) # 2. 切分训练集和测试集 split = int(len(x_all) * 0.8) x_train, y_train = x_all[:split], y_all[:split] x_test, y_test = x_all[split:], y_all[split:] # 3. 转成 tensor 并构造 DataLoader,batch_size 默认 32 train_dataset = TensorDataset( torch.tensor(x_train, dtype=torch.float32).unsqueeze(-1), torch.tensor(y_train, dtype=torch.float32).unsqueeze(-1) ) train_loader = DataLoader(train_dataset, batch_size=32, shuffle=False) # 4. 初始化模型、损失函数、优化器 model = LSTMModel(input_size=1, hidden_size=64, num_layers=2, output_size=1) criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 5. 训练循环 for epoch in range(200): model.train() for x_batch, y_batch in train_loader: pred = model(x_batch) loss = criterion(pred, y_batch) optimizer.zero_grad() loss.backward() optimizer.step() if epoch % 20 == 0: print(f"epoch {epoch}, loss: {loss.item():.6f}") # 6. 保存模型权重 torch.save(model.state_dict(), 'model/stock.pkl')这里有两个细节新手容易忽略。第一,shuffle=False在训练集上其实问题不大(因为每个样本本身已经是滑窗切片的,顺序影响有限),但如果你是拿整个长序列直接丢进 LSTM 做训练,shuffle=True就完全错误了;第二,torch.save(model.state_dict(), ...)保存的只是模型参数,不包含网络结构,加载时你需要先实例化一个一模一样的LSTMModel,再load_state_dict,结构对不上就会报 key 不匹配的错误。
4.3 evaluate.py 怎么看预测结果:先反归一化,再算误差
评估阶段最大的坑是直接在归一化值上算误差。因为归一化之后价格都在 0 到 1 之间,RMSE 算出来零点零几,看着很好看,但反归一化回真实价格之后误差可能是几百点。正确的流程是先把预测值和真实值都反归一化,再计算 RMSE 和 MAE:
# evaluate.py 中的典型评估流程 import numpy as np import torch from sklearn.metrics import mean_squared_error, mean_absolute_error model.eval() with torch.no_grad(): pred = model(torch.tensor(x_test, dtype=torch.float32).unsqueeze(-1)).numpy() # 反归一化回真实价格 pred_real = scaler.inverse_transform(pred) y_test_real = scaler.inverse_transform(y_test) rmse = np.sqrt(mean_squared_error(y_test_real, pred_real)) mae = mean_absolute_error(y_test_real, pred_real) print(f"RMSE: {rmse:.2f}, MAE: {mae:.2f}")计算结果你会得到一个很有意思的观察:RMSE 通常会比 MAE 大不少,这说明预测误差里存在少数偏离很大的点,比如遇到跳空高开或者跌停的日子,LSTM 完全反应不过来,单点误差被平方放大。如果你发现 RMSE 是 MAE 的三倍以上,说明测试集里遇到了不止一个极端行情日,这时候不要急着调模型,先看看那些日期前后发生了什么。
4.4 模型保存与加载:stock.pkl 的用法和注意点
model/stock.pkl是训练完的模型权重,用torch.save保存的。加载模型时要保证模型类定义和保存时一致,否则load_state_dict会报 key 不匹配。正确加载方式如下:
# 加载已训练好的模型 model = LSTMModel(input_size=1, hidden_size=64, num_layers=2, output_size=1) model.load_state_dict(torch.load('model/stock.pkl', map_location='cpu')) model.eval()这里有个容易翻车的细节:如果训练时用的是 GPU(torch.device('cuda')),保存下来的权重 key 里会带module.前缀或.cuda相关信息,加载到 CPU 上需要用map_location='cpu'并且检查 key 是否匹配。这个项目默认是 CPU 训练,一般不涉及这个问题,但如果你自己改过训练设备,就必须留个心眼。
5. 避坑与常见问题:从数据泄露到预测曲线滞后
这个项目我从头到尾跑过不止一遍,也帮别人排查过非常多类似的问题。下面这 5 条是我总结出来最高频的踩坑记录,每一条都真实发生在这个项目里,不是网上抄来的。遇到问题先对照排查,比乱改参数有效得多。
5.1 loss 极低但测试集一塌糊涂:前视偏差在作祟
现象:训练 loss 降到 0.0002,看起来非常漂亮,但跑 evaluate.py 的时候对测试集的预测误差比训练集大好几倍,甚至出现预测曲线比真实曲线提前变化的诡异情况。
原因:最常见的是归一化时用了全量数据的fit_transform,也就是scaler.fit()时把测试段的 min/max 也纳入了计算,模型在训练时“偷看”了未来的价格区间。第二种可能是滑窗构造时没有按时间切分,测试集里混入了训练窗口的上下文。
解决:回到第 3.3 节,确认scaler.fit只作用在训练段;检查滑窗切分逻辑,确保测试集的第一个窗口起点是train_end - seq_len,而不是 0。改完这两处,重新训练,你会发现训练 loss 反而变大了一点点,但测试集误差变得真实可信了。
5.2 预测曲线整体右移一天:LSTM 学到的只是“昨天的价格惯性”
现象:img/17.png 里预测曲线和真实曲线贴得非常近,但是仔细看,预测曲线总是比真实曲线晚一天变化——真实价格涨了,预测曲线第二天才跟上;真实价格跌了,它也是隔天才反应。
原因:这不是 bug,而是单序列 LSTM 预测最经典的“滞后效应”。因为输入只有收盘价,模型在训练时发现最简单有效的策略就是“预测值约等于上一个时间步的值”,这样 MSE 最小。LSTM 本质上是在拟合一个自回归过程,它对突变行情天然免疫。
解决:如果你只有收盘价这一个特征,这个现象无法完全消除,只能缓解。你可以尝试把 seq_len 缩短到 10,让模型更聚焦近期变化;或者把学习率调低到 0.0005,让模型训练更充分,找到更好的局部最优解。另外就是第 6 章的多因子改造,加入成交量之后,滞后现象会明显减轻,因为成交量携带了价格之外的增量信息。
5.3 换了股票数据后训练不收敛:数据尺度不一致导致的梯度爆炸
现象:把000001SH_index.csv换成一只单价两三百元的股票,比如贵州茅台,跑训练时 loss 直接变成 nan,或者前几十轮 loss 越来越大,根本收敛不了。
原因:不同股票的绝对价格差异巨大,上证指数在 3000 点上下,茅台是 1700 多元,直接用原始价格喂给 LSTM,模型权重的梯度会被放大很多倍,训练直接爆炸。
解决:换数据之前,先把价格重新归一化到 [0, 1] 区间,并且确认scaler.fit用的是新数据的训练段,不能沿用之前上证指数的 scaler。如果归一化之后还出现 loss 为 nan,把学习率调到 0.0001 再试,大概率能稳住。
5.4 CSV 日期解析报错:不同数据源的日期格式不兼容
现象:解析自己的数据源时,pd.to_datetime报错,提示无法解析某些日期字符串;或者解析成功但数据条数比预期少了很多。
原因:不同平台的 CSV 日期格式五花八门,有的是2024-01-05,有的是2024/1/5,还有带T的 ISO 格式2024-01-05T00:00:00。parser_my.py 里写死的format='%Y-%m-%d'遇到其他格式就会匹配失败。另外有些数据源在非交易日没有数据行,直接导致日期不连续。
解决:改成pd.to_datetime(df['date'])让 pandas 自动推断格式,省去手写 format 的麻烦;遇到解析失败的行,用errors='coerce'把它转成 NaT 然后统一丢弃。日期不连续的问题,用asfreq('D')重采样补齐空行,或者干脆接受这种不连续——对 LSTM 来说,中间缺几天的影响在窗口足够长的情况下不算致命。
5.5 测试集 RMSE 很大但曲线看起来还行:误用了归一化值算误差
现象:img 目录下的预测图看起来贴合度很高,但脚本里打印出来的 RMSE 大得离谱,比如几千。你觉得模型预测效果和指标完全对不上,不知道信哪个。
原因:画图时用了反归一化的真实值,但计算 RMSE 时忘了反归一化,直接把 0~1 区间的预测误差乘上了价格基数,算出来的指标瞬间被放大几百倍。这属于脚本使用层面的失误,不是模型问题。
解决:统一用 4.3 节的方法,先scaler.inverse_transform再算 RMSE 和 MAE,保证指标和图表口径一致。从那以后我每次跑评估脚本,都会先核对一遍“指标的计算口径是真实价格还是归一化值”,再往下读结果。这个习惯帮我排掉了无数个假性问题。
6. 进阶玩法:从单特征收盘价改成多因子输入的三种改造思路
项目跑通只是起点,真正能用到实战里的 LSTM 模型,几乎不会只用单一收盘价。这里我给你三个可以直接动手的改造方向,难度从低到高。
第一个改造是加入成交量作为第二特征。成交量是日线数据里最容易拿到且信息量最大的字段,代码改动非常小:把 dataframe 里close和volume两列一起取出来,拼成一个两列的矩阵,然后归一化。注意input_size从 1 改成 2,其余网络结构完全不用动。
# 双特征:收盘价 + 成交量 features = df[['close', 'volume']].values scaler = MinMaxScaler() scaler.fit(features[:train_size]) # 只 fit 训练段 scaled = scaler.transform(features) # 再 transform 全量数据 # 模型初始化时把 input_size 改为 2 model = LSTMModel(input_size=2, hidden_size=64, num_layers=2, output_size=1)成交量对滞后效应的改善非常明显,因为成交量本身就携带了价格变动之前的信息——放量突破往往伴随价格方向改变,模型有机会在价格还没变之前就捕捉到信号。这个改造也是最容易验证效果的,改完对比一下第 4.3 节的 RMSE,基本都能看到实打实的下降。
第二个改造是预测未来多步而不是一步。单步预测只能给出明天的价格,而真实决策至少需要未来 3 到 5 天的方向判断。常见的做法是滚动预测:把预测出来的结果拼到输入序列末尾,作为新输入继续预测下一天。这样误差会随着步数增加而累积,所以滚动预测出来的走势图,步数越多、偏离越大,看方向基本靠谱,看具体数值不值得全信。就算只滚动两步,模型每步的滞后效应就会被放大,这也是检验模型抗噪声能力的好方法。
第三个改造是引入技术指标作为辅助特征。比如把 5 日均线和 20 日均线的差值(也就是短期趋势强度)拼进特征矩阵。技术指标本质上是原始序列的变换,加入之后等于提前帮 LSTM 做了特征工程,相当于把一部分非线性关系显式暴露给了模型,往往比单独堆更多原始特征见效更快。但我给你的建议是,一次只加一个变化点,改完就重新训练、重新评估,不要让“换数据 + 加特征 + 改窗口”同时发生,否则变量太多,你根本定位不了是哪个改动带来的收益。
这套项目源码最大的价值不在于网络结构多先进,而在于它把从数据到模型再到评估的完整闭环打通了。你在跑通之后,拿它当脚手架去改自己的数据和参数,会省掉大量从零开始拼代码的时间。我自己的习惯是,每换一个新数据集,就从头把训练和评估各跑一遍,顺手记录 RMSE 和 MAE,时间久了就攒出了一张“不同市场、不同行情下 LSTM 表现如何”的对照表——这也是判断一个模型能不能用的最可靠依据。希望这个项目能帮你在时间序列预测这条路上少走几个弯路。
本文还有配套的精品资源,点击获取