简介:基于长短期记忆网络的股票价格预测Python实现源码包,面向高校数据科学与金融工程方向的课程设计、期末大作业及毕业设计场景,提供完整可运行的预测项目,适合作为深度学习与量化交易练习的入门范例。资源共89个文件,压缩包约8.89MB,核心包含24个Python脚本、13个说明文档、若干HTML/CSS/JS前端页面及SQLite数据库文件,既能支撑LSTM模型训练与预测流程,也可用于结果可视化与交互展示。已有738人学习下载。项目中不仅含可直接运行的Django代码与核心预测脚本,还附带使用说明文档、运行截图及README文件,覆盖数据预处理、模型构建、训练评估到结果输出全过程,目录结构清晰,便于快速定位和理解模块关系。整个项目达到高分课程设计要求,下载后无需额外修改即可复现演示,适合作为期末大作业或课程设计的参考蓝本。
1. 拿到"基于LSTM的股票价格预测"源码包,先别急着跑:你要解决的不只是模型
看到"基于LSTM的股票价格预测 python实现 源码+使用说明文档.zip"这个标题时,我猜你和我一样,第一反应是下载下来解压再说。等你真正跑起来会发现,这个包解决的并不是"预测明天的涨跌",而是"用 Python 把 LSTM 模型应用到股票时间序列上"的完整流程:数据读取、窗口构造、模型训练、结果预测,外加一份使用说明文档解释每个文件是干嘛的。
我建议你在敲下python train.py之前,先弄清楚三个问题:模型输入是什么、标签怎么构造、验证集有没有偷看未来。很多源码包能跑通,但预测曲线是一条水平直线,问题往往不在 LSTM,而在数据处理。我会从解压 zip 开始,把模型选型、参数调整、常见翻车现场和滚动回测完整过一遍,让你拿到手不是只会跑,而是能改、能说清楚、能换到自己的数据上。
2. 拆开这个 zip 之前:LSTM 为什么被用在股票价格预测上,以及它的三个硬边界
2.1 LSTM 在时间序列里的角色:记忆门控和"长期依赖"的通俗账
股票价格数据本质上是一条时间序列,今天的价格和十几天前的价格之间可能存在关联,但中间又夹杂了大量无关波动。传统 RNN 在反向传播时,梯度会随着时间步反复相乘,超过一定长度就消失或爆炸,于是模型只能记住最近几步,学不到"十天前的放量"这种长距离信息。LSTM 引入遗忘门、输入门、输出门三个门控,把信息更新变成逐元素的加法和乘法,让梯度有一条能穿越时间步的公路。通俗地说,遗忘门决定要不要清掉旧状态,输入门决定新信息怎么写进去,输出门决定当前隐藏状态给谁看。
在股票预测这种信噪比很低的场景里,LSTM 并不保证比线性模型强,但门控结构让它至少能处理变长窗口,并有机会记住"当前趋势可能要反转"的隐状态。很多开源 LSTM 模型代码正是把它当作默认模型。你需要注意的是,房价、天气、交通流量这类序列也可以复用同一套 python 实现,只要把输入特征和归一化方式换掉。这也是这类源码包价值所在:模型本身是通用的,股票数据只是载体。
2.2 股票价格预测任务的建模选择:单变量、多变量、还是多步预测
打开源码包里的数据处理脚本,你会发现建模方式通常分成三类。单变量只取收盘价 close,构造成[t-lookback+1, ..., t]预测t+1的价格。多变量则把 open、high、low、close、volume 甚至 MACD、RSI 一起作为输入。多步预测是在输出端一次预测未来 N 天,或者用滚动方式迭代预测 N 次。三种组合差别很大,源码包里默认的往往是单变量单步,因为它最容易在课程设计和毕业设计里跑通。
| 模型类型 | 输入特征 | 输出目标 | 适合场景 | 常见坑 |
|---|---|---|---|---|
| 单变量单步 | 收盘价序列 | 次日收盘价 | 入门、基线 | 归一化泄漏 |
| 多变量单步 | OHLCV+指标 | 次日收盘价/涨跌幅 | 有一定特征工程经验 | 特征过多过拟合 |
| 单变量多步 | 收盘价序列 | 未来 N 天价格 | 观察中期趋势 | 误差逐日累积 |
| 多变量多步 | OHLCV+指标 | 未来 N 天价格 | 研究型项目 | 数据对齐复杂 |
我建议你拿到源码后,先跑通单变量单步,把预测曲线稳定下来,再加成交量之类的特征。不要一上来就塞十几个指标,否则你很难判断 LSTM 学到的是市场规律还是噪声。使用说明文档里如果给了参数表,通常也只会覆盖这个默认组合,所以先把基础流程摸熟,再往多变量方向扩。
关于选型,常见的问题还有为什么不直接用 ARIMA 或者 Transformer。ARIMA 对线性关系更擅长,但对多变量、非线性交互无能为力;Transformer 需要大量数据,且计算注意力矩阵时会把时间顺序打散得更彻底,需要额外的位置编码和 mask。LSTM 模型代码在几千条日线数据上就能训练,而且社区里的 python 源码包大多围绕它实现,调试成本最低。所以不管行业里怎么争论,在课程设计、毕业设计和量化入门阶段,LSTM 是这个标题下性价比最稳的选择。
2.3 硬边界一:收益率的不可预测部分;硬边界二:滑点与手续费;硬边界三:数据穿越
先说边界,免得你对 LSTM 抱有不切实际的期望。股票收益率里大部分是不可预测的,LSTM 能学到的主要是短期的动量效应、均值回归和波动聚集。源码包在测试集上展示的 MSE 很低,不一定是因为它"看懂了市场",而是因为它在预测一条缓慢上涨的曲线,涨跌方向的胜率可能和抛硬币差不多。所以评估模型时不要只看 Loss,要看方向准确率、回撤和扣掉交易成本后的净收益。
第二道边界是滑点与手续费。很多源码包回测时假设成交价等于收盘价,也不扣手续费,于是策略在纸面上年化收益很高。你把预测结果变成交易信号之前,至少按万分之二的手续费加千分之五的滑点压测一次,很多高频信号会直接变成负收益。这也是我拿到任何预测源码都会先问一句:它的回测模块有没有把交易成本算进去。
第三道边界是数据穿越,这是源码包最容易埋雷的地方。典型写法是把全部历史价格拿去算均值方差,再做归一化,然后用前 80% 训练、后 20% 测试。看似没问题,但归一化的统计量已经偷看了测试集。更隐蔽的是,某些包用shift(-1)把明天的收盘价搬到今天的特征里。遇到这种情况,训练出来的模型准确率高得离谱,一到真实环境立刻失效。第 5 章我会专门讲怎么排查这类问题。
说这三个边界不是劝退,而是告诉你这类源码包的适用位置:它最适合用在学习、研究和策略探索上,而不是作为自动印钞机。想用 LSTM 做股票价格预测,先接受大部分收益不可预测,再把精力放在数据质量、特征构造和回测框架上,这才是源码包真正值得投入的地方。
3. 从 zip 到第一个预测结果:源码包的目录结构、数据准备和最小跑通命令
3.1 解压后的常见目录划分与入口脚本说明
这类源码包解压后,通常不是一个大杂烩,而是按功能分好目录。我经手过的 LSTM 股票预测 python 项目,最常见骨架是:一个data/目录放原始 CSV,一个models/或utils/放网络结构和通用函数,根目录放 train.py 和 predict.py,另有 requirements.txt 和使用说明文档。你第一件事不是跑 train.py,而是打开使用说明文档,确认 Python 版本和依赖。
| 文件/目录 | 作用 | 你需要改什么 |
|---|---|---|
| data/xxx.csv | 历史行情数据 | 换成自己的股票数据 |
| train.py | 训练主入口 | 数据路径、模型参数 |
| predict.py | 加载模型并预测 | 窗口大小、预测长度 |
| utils/ | 数据读取、归一化 | 检查 scaler 是否被重复 fit |
| requirements.txt | 依赖声明 | 按实际环境固定版本 |
| 使用说明文档 | 参数说明和示例命令 | 对照本文查漏 |
有些压缩包会把使用说明文档放在根目录,命名为 README.md 或者 使用说明.docx/pdf。如果里面连环境要求都没写,你就要小心,这份源码可能从未在干净环境跑通过。
3.2 安装依赖:Python 版本、TensorFlow/PyTorch 的选择,以及 requirements.txt 的处理
先把环境隔离出来。项目默认可能是 TensorFlow 的 Keras 实现,也可能是 PyTorch,两者代码风格完全不同。建议你先看使用说明文档里写的是哪个框架。我一般会用 Python 3.8 以上的版本建一个虚拟环境,再按 requirements.txt 装依赖,避免把全局环境搞乱。
# 进入解压目录 cd lstm_stock_project # 创建并激活 Python 3 虚拟环境 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装依赖;如果 requirements.txt 缺失,可手动安装核心库 pip install -r requirements.txt这里的source venv/bin/activate是 Linux/macOS 的激活命令,Windows 上换成venv\Scripts\activate。如果包是 PyTorch 版本,建议到官网按显卡型号安装对应 CUDA 版本;只跑 CPU 的话,默认包也能跑通,只是慢一些。requirements.txt 里的版本不要照单全收,TensorFlow 2.x 在不同小版本间 API 差异明显,如果使用说明文档没有强制版本,我建议选一个稳定的 TensorFlow 2.x 组合,或者干脆换成 PyTorch 2.x 的对应实现。装完依赖后先跑python -c "import tensorflow as tf; print(tf.__version__)",确认导入不报错再继续。
3.3 原始数据长什么样:CSV列名、时间格式、除权除息与停牌的预处理
最常见的行情 CSV 长这样:第一行是列名,后面按时间升序排列,列名通常是 date、open、high、low、close、volume。但不同数据源的列名可能不同,有的用日期时间戳,有的是字符串,有的直接把股票代码放在第一列。拿到数据后先别急着训练,先做一次体检。
import pandas as pd # 读取原始数据,date 列先解析成 datetime df = pd.read_csv("data/000001.csv", parse_dates=["date"]) # 看缺失值和每一列的类型 print(df.info()) print(df.head()) # 统一按日期排序,确保时间升序 df = df.sort_values("date").reset_index(drop=True) # 删除空值;缺口太多就重新取数 df = df.dropna() # 对停牌造成的缺失价格做前向填充,但前提是知道停牌原因 df["close"] = df["close"].ffill()parse_dates会把日期列直接解析成 datetime 类型,省去后面手动格式转换。排序是时间序列任务的第一原则,否则滑动窗口会切乱。缺失值处理上,插值适合短暂缺口,连续停牌超过 5 个交易日我一般直接剔除或者重新下载复权数据。复权特别重要:除权除息当天价格会跳空,不处理会让 LSTM 学出一个假跳变,预测结果自然南辕北辙。所以如果源码包没有做复权处理,你要在预处理阶段补上。
这里要注意,很多使用说明文档只写"数据格式为 CSV",但不会告诉你应该用前复权还是后复权。对 LSTM 这类按价格绝对数值学习的模型,我建议用后复权数据,因为它不会因为最新价格变化而重写历史序列。
3.4 最小跑通命令:训练脚本 + 预测脚本,从命令行参数到输出文件
训练入口脚本的骨架通常是这样:读数据、构造窗口、训练、保存模型。下面是一个可运行的最小版本,你可以对照源码包里的 train.py 检查流程是否完整。
# train.py import numpy as np import pandas as pd from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from sklearn.preprocessing import MinMaxScaler def create_sequences(data, lookback): """把连续序列切成 (lookback, 1) 的输入窗口""" X, y = [], [] for i in range(lookback, len(data)): X.append(data[i-lookback:i]) y.append(data[i]) # 用前 lookback 天预测第 i 天 return np.array(X), np.array(y) df = pd.read_csv("data/000001.csv", parse_dates=["date"]) df = df.sort_values("date") close = df["close"].values.reshape(-1, 1) # 归一化:只用训练段 fit,测试段用同一个 scaler scaler = MinMaxScaler(feature_range=(0, 1)) scaled = scaler.fit_transform(close) lookback = 30 X, y = create_sequences(scaled, lookback) split = int(len(X) * 0.7) # 按时间顺序前 70% 训练 X_train, X_test = X[:split], X[split:] y_train, y_test = y[:split], y[split:] model = Sequential([ LSTM(64, return_sequences=True, input_shape=(lookback, 1)), Dropout(0.2), LSTM(32), Dropout(0.2), Dense(1) ]) model.compile(optimizer="adam", loss="mse") model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=30, batch_size=32, verbose=1) model.save("lstm_stock.h5")这里create_sequences函数把长度为 lookback 的历史窗口映射到下一个时间点的价格。注意我没有随机打乱样本,因为时间序列一旦打乱就破坏了顺序。归一化只对训练段调用fit_transform,测试段复用同一个 scaler,这是防止数据穿越的关键。模型用了两层 LSTM,第一层return_sequences=True保留每个时间步输出,才能拼第二层。
参数上,lookback 取 30 是日线比较常用的经验值;神经元从 64 开始,先看 loss 趋势再调整。训练轮数 30 轮,如果训练集 loss 还在降,再加 epochs。显卡内存不足时,把 batch_size 调小到 16 或 8,两个 LSTM 层对显存消耗不算小。
predict.py 要做的是加载保存的模型,用最后 lookback 个交易日的数据预测下一个交易日,再把归一化数值还原成原始价格。
# predict.py import numpy as np import pandas as pd from tensorflow.keras.models import load_model from sklearn.preprocessing import MinMaxScaler import joblib df = pd.read_csv("data/000001.csv", parse_dates=["date"]).sort_values("date") close = df["close"].values.reshape(-1, 1) # 注意:正式项目这里应加载训练时保存的 scaler scaler = MinMaxScaler() scaled = scaler.fit_transform(close) lookback = 30 last_seq = scaled[-lookback:].reshape(1, lookback, 1) model = load_model("lstm_stock.h5") pred_scaled = model.predict(last_seq)[0, 0] pred_price = scaler.inverse_transform([[pred_scaled]])[0, 0] print(f"next close: {pred_price:.2f}")predict.py 里如果重新fit_transform全部数据,scaler 的 min/max 就包含未来信息,相当于偷看答案。正确做法是在训练结束后用joblib.dump(scaler, "scaler.pkl")保存 scaler,predict.py 里只joblib.load,不做新的 fit。源码包里如果没这么做,你要自己补上。这也是为什么我说这份使用说明文档不能只教命令,还得教会你保存状态。
4. 把源码调成你自己的数据:LSTM 时间序列预测的 6 个必调参数与预处理细节
4.1 滑动窗口 lookback:窗口太短学不到趋势,太长把序列拉成白噪声
lookback 是第一个要调的参数。对日线数据,常见范围是 10 到 60。窗口太短,模型只能看到最近几天的波动,捉不到慢变量;窗口太长,序列里大部分信息冗余,LSTM 反而更难提取有用信号。一个经验做法是先画收盘价的自相关图,看多少阶之后自相关衰减到 0,再在那个附近选窗口。
from statsmodels.graphics.tsaplots import plot_acf import matplotlib.pyplot as plt plot_acf(df["close"], lags=60) plt.show()自相关只代表线性相关性,LSTM 还能捕捉非线性关系,所以它是个起点而不是硬指标。我一般会先跑 20、30、45 三组窗口做对比,看验证集 RMSE 的变化。注意每次换窗口都要重新训练,否则对比没有意义。另外 lookback 变了,输入维度也跟着变,model summary 里确认一下input_shape是否同步修改。
4.2 归一化方式:MinMaxScaler、StandardScaler,以及预测值还原的正确姿势
价格序列几乎总是用 MinMaxScaler 缩放到 [0,1],因为 LSTM 的激活函数在中间区间敏感、在两端饱和。收益序列或者技术指标可以另行处理。关键不是选哪个 scaler,而是训练和预测必须共用同一套统计量。
from sklearn.preprocessing import MinMaxScaler import joblib # 训练阶段:只对训练段做 fit scaler = MinMaxScaler() scaled_train = scaler.fit_transform(train_close) # 保存 scaler joblib.dump(scaler, "scaler.pkl") # 预测阶段:直接加载,不做新的 fit scaler = joblib.load("scaler.pkl") scaled_test = scaler.transform(test_close)fit_transform和transform的区别一定要分清。很多人图省事,在预测脚本里重新fit_transform全部数据,scaler 的 min/max 变成全量统计量,测试集信息就穿到了训练过程。我还见过一种做法:把价格除以一个常数比如 1 万来归一化,常数来自未来最高价,这同样是数据穿越。记住:归一化统计量一旦沾到未来,后面的模型评估全部作废。
4.3 训练/验证/测试集的切分:按时间切,不能随机打乱
很多表格型任务的代码习惯是train_test_split(X, y, test_size=0.2, random_state=42),但股票预测不能这样用。时间序列一旦随机打乱,昨天和明天的样本出现在同一个批次里,验证集的 loss 毫无意义。正确做法是直接用索引切。
split = int(len(X) * 0.7) # 前 70% 训练 val = int(len(X) * 0.9) # 再 20% 验证,最后 10% 测试 X_train, y_train = X[:split], y[:split] X_val, y_val = X[split:val], y[split:val] X_test, y_test = X[val:], y[val:] # 如果要数据增强,只在训练段内部打乱 idx = np.random.permutation(split) X_train, y_train = X_train[idx], y_train[idx]这里补充一个很多人容易误踩的点:训练集内部可以随机打乱,因为样本之间的顺序不影响单个样本的时间结构;验证集和测试集必须保持原始顺序,因为它们要模拟真实的逐日预测。如果不打乱训练集,LSTM 会学到训练段的整体趋势,验证 loss 可能异常高,也可能异常低。源码包如果给出了 shuffle 参数,默认开在训练阶段是合理的。
4.4 神经元数、层数、dropout、batch_size、epochs 的参考区间
LSTM 模型代码里的超参数,直接影响"欠拟合还是过拟合"。下面这组参数来自我调 LSTM 设备的通用经验,也适用于股票日线:
| 参数 | 参考范围 | 说明 |
|---|---|---|
| lookback | 10~60 | 日线常用 20/30 |
| LSTM 神经元 | 32~128 | 第一层可稍大 |
| 层数 | 1~3 | 超过 3 层需要大量数据 |
| dropout | 0.1~0.3 | 防止过拟合 |
| batch_size | 16~128 | 显存允许时取大值 |
| epochs | 20~100 | 配合 EarlyStopping |
从 1 层 32 个神经元开始,如果验证集 loss 不降,再往上加。我见过很多人上来就是三层 256 神经元,结果在几千条日线数据上严重过拟合,训练集 loss 接近 0,预测曲线几乎不动。宁可让模型欠拟合,也不要把它做成记忆器。epochs 最好配合 EarlyStopping。
from tensorflow.keras.callbacks import EarlyStopping early_stop = EarlyStopping(monitor="val_loss", patience=5, restore_best_weights=True) model.fit(X_train, y_train, validation_data=(X_val, y_val), epochs=50, batch_size=32, callbacks=[early_stop])patience=5表示验证集 loss 连续 5 个 epoch 不下降就停止,restore_best_weights会把权重恢复到验证集最优的那一步,这是防止过拟合的后悔药。
4.5 损失函数与评估指标:回归问题用 MSE/MAE,不要拿 Accuracy 说事
LSTM 的最后一层是 Dense(1),本质是回归。有人习惯在model.evaluate里看准确率,但回归任务没有 accuracy,你看到的准确率其实是 Keras 按 0.5 阈值硬切的假指标。应该看 MSE、MAE、RMSE 或 MAPE。MAPE 是百分比,更适合汇报:如果预测价格和真实价格平均差 2%,说明模型对幅度有基本把握。
from sklearn.metrics import mean_absolute_error, mean_squared_error y_pred = model.predict(X_test) mae = mean_absolute_error(y_test, y_pred) rmse = np.sqrt(mean_squared_error(y_test, y_pred)) # 方向准确率:预测涨跌方向和真实涨跌方向是否一致 direction = np.mean(np.sign(y_test[1:] - y_test[:-1]) == np.sign(y_pred[1:] - y_pred[:-1]))方向准确率比数值误差重要,因为投资决策真正关心的是方向。但注意方向准确率也要在扣掉手续费后才能算收益,不要被 55% 的准确率冲昏头脑。源码包如果只打印 MSE,我建议你自己补上方向准确率。
4.6 多步预测的两种实现方式:递归滚动与直接多输出
如果你想预测未来 5 天,源码包可能用的是递归滚动:用 t 预测 t+1,把预测值拼回去再预测 t+2。这种方式实现简单,但误差会逐日累积。另一种是直接多输出,把最后一层改成 Dense(5),损失是 5 个步长的平均误差,训练更稳,但会假设 5 个输出相互独立。
# 递归滚动预测 def recursive_forecast(model, last_seq, steps): preds = [] current = last_seq.copy() for _ in range(steps): p = model.predict(current, verbose=0)[0, 0] preds.append(p) current = np.concatenate( [current[:, 1:, :], np.array([[[p]]])], axis=1 ) return np.array(preds) # 直接多输出:只需把最后一层换成 Dense(5) # model.add(Dense(5))我建议在源码包里优先用直接多输出,因为每个输出节点在训练时都能得到梯度,而递归滚动在第二步之后全部依赖模型自己的输出,误差放大速度非常快。如果使用说明文档只讲了单步预测,你就先预测一天,把单步做稳再做 5 天。多步预测的评估要用"预测起点的对齐":比如第 1 天预测未来 5 天,第 2 天预测未来 5 天,不能把两条预测序列简单拼接,否则你会在同一时间点上叠加多个不同起点的预测,人为平滑掉误差。
5. 避坑清单:从 zip 解压到预测曲线,最常见的 5 个翻车现场
拿到源码包到真正跑通,大多数人都会在上面这些问题里至少翻车一次。下面按"现象 → 原因 → 解决"的框架记录,你可以直接跳到自己遇到的那条。
5.1 现象:loss 正常下降,但预测曲线是一条水平直线
这是最常见的翻车现场。训练过程中 loss 从 0.1 降到 0.001,看起来很漂亮,但把预测值和真实价格画在一起,发现预测值是一条贴着均值走平的线。原因通常是目标值在样本中变化太小,模型退化成"预测下一个点等于上一个点"的常数估计;另一种原因是最后一层用了 Tanh 激活,把输出钳制在固定区间,而股票价格归一化后分布很不均匀。解决方法是先看model.summary(),确认最后一层是Dense(1)不带激活;再看训练集标签的标准差,如果小到接近 0,需要换一个更长的时间窗口,或改用收益率作为预测目标。
还有一个容易忽视的原因:训练集和验证集整体趋势一致,模型只需要记住"今天的价格大概率是明天的价格"就能得到很低的 MSE,于是它学成了复制器。所以你看到水平直线,不一定是模型坏了,可能是任务本身太"容易",模型不值得学更多。解决思路是把标签从"价格"改成"涨跌方向"或者"未来 N 日收益率",强迫模型去学变化。
5.2 现象:用今天的全部数据预测明天,结果看起来"超级准"
预测曲线几乎完全贴合真实值,很多人以为 LSTM 通灵了,实际上十有八九是数据穿越。常见的偷看方式有三种:一是归一化时用全量数据 fit,scaler 的 min/max 里带着未来信息;二是构造特征时用了shift(-1),把明天的价格挪到今天的特征里;三是切分前先做了差分,差分的统计量跨了训练和测试期。排查办法是打开数据处理脚本,搜索shift(和fit_transform,确认所有统计量都只来自训练段。
另一个粗暴的验证方法:训练完把模型权重随机初始化再预测,如果预测精度仍然很高,说明你的测试集里确实藏了训练信息。或者把时间标签随机打乱再训练,如果测试集指标几乎不变,这套流程就有问题。数据穿越是 LSTM 时间序列预测里最隐蔽的坑,也是使用说明文档一般不会告诉你的部分。
5.3 现象:验证集 Loss 比训练集低一大截,怀疑模型作弊
这种情况往往不是模型作弊,而是验证集本身太简单。比如你按时间顺序切分,前 70% 是 2015 到 2021 年的震荡行情,后 30% 是 2021 到 2023 年的单边上涨,验证集里"昨天的价格"就是很好的预测器,MSE 天然就小。解决方法是不要只切一次,改用滚动验证:每训练一次就验证一段,最后取所有验证段的平均指标。滚动验证会明显增加训练时间,但对时间序列来说是必要的交学费。
如果验证集 Loss 反而低得异常,还有一种原因是训练阶段没有做随机打乱,模型把训练段尾部当成了趋势放大器,验证时遇到新趋势就失灵。所以先检查训练代码里有没有 shuffle 训练集,再检查验证集的行情结构。不要被一个看起来漂亮的验证数字影响判断。
5.4 现象:把股票代码的字符串列直接喂给模型,训练直接崩溃
如果原始表里有code、name这样的字符串列,而源码包又偷懒直接把它读进 DataFrame 当特征,你会看到类似cannot convert string to float的报错,或者模型训练起来 loss 非常不稳定。有些人的第一反应是删掉这一列,这是对的。但如果你想保留"股票代码"这个类别信息,应该用LabelEncoder编码后再作为普通特征或者 Embedding 输入。对于单只股票的预测,这列没有任何信息量,删掉最干净。
这里有个更隐蔽的变体:有些数据源会把date列也读成 string,然后被当成特征送进模型,模型训练也能跑,但日期字符串被转成整数后会产生无穷大的虚假时间趋势。解决办法是训练前显式df["date"] = pd.to_datetime(df["date"]),并且只保留数值列作为模型输入。
5.5 现象:保存的模型文件在换机器后无法加载,或者加载后预测结果错乱
用model.save("lstm_stock.h5")保存的模型,换到没有安装同样版本库的机器上经常报错。Keras 的 h5 文件把网络结构、权重和优化器状态打包在一起,TensorFlow 版本一换就拒载。更隐蔽的问题是训练时设置了随机种子,但预测脚本没设,结果每次预测值略有差异。解决方法是保存时用model.export("saved_model")导出 SavedModel 格式;另外把预测脚本里的随机种子固定成同一个值。如果 predict.py 里重新实现了预处理逻辑,记得把训练阶段的 scaler 一起复制过去。
还有一类问题是模型能加载,但预测结果全是 NaN。这通常是因为训练时输入里有 NaN,或者归一化时除零。Keras 在训练时不报错,会把 NaN 写进权重,预测自然出错。所以预处理阶段的dropna()和replace([np.inf, -np.inf], np.nan)要写进数据检查里,强制要求数据质量合格再开始训练。
6. 从"能跑通"到"敢实盘":先用滚动回测验证源码,再谈参数寻优
当模型能稳定输出预测价格后,我不建议直接把它接到交易 API 上。我会先写一个滚动回测脚本,把源码包里的单次训练/预测改成"每 20 个交易日重新训练一次,预测未来 5 天",然后把结果拼接成一条资金曲线。这样做能同时检验模型的稳定性、参数敏感度和交易成本的影响。
def rolling_backtest(df_scaled, lookback, step=20, horizon=5): preds, reals = [], [] start = lookback end = start + step while end + horizon < len(df_scaled): # 每次只用 [:end] 训练,确保不偷看未来 X, y = create_sequences(df_scaled[:end], lookback) model = build_lstm(lookback) # 重新建模型 model.fit(X, y, epochs=30, batch_size=32, verbose=0) # 从 end-lookback 开始递归预测 horizon 天 cur = df_scaled[end-lookback:end].reshape(1, lookback, 1) for _ in range(horizon): p = model.predict(cur, verbose=0)[0, 0] preds.append(p) cur = np.concatenate([cur[:, 1:, :], np.array([[[p]]])], axis=1) reals.extend(df_scaled[end:end+horizon].flatten()) end += step return np.array(preds), np.array(reals)这段代码的核心是每次只使用 end 之前的数据,保证预测时看不到未来。step 控制重训练频率,20 个交易日相当于每月更新一次。回测完成后,至少计算三个指标:方向准确率、累计收益率、最大回撤。方向准确率低于 52% 基本覆盖不了手续费。
参数寻优方面,使用说明文档一般只给一组默认参数,我会用 Optuna 做贝叶斯搜索,搜索空间限定在 lookback、神经元数、dropout、batch_size 四个维度,每个组合用滚动回测的验证段来评分,而不是用最后的测试段。这里有个血泪教训:如果直接对测试段调参数,调出来的结果只对那一段历史有效,换一只股票就翻车。我过去就这么干过,回测曲线漂亮得像印钞机,实盘一上去就偃旗息鼓。后来学乖了,先锁死一组保守参数,跑多只股票,看参数在不同标的上是不是都稳定,再去微调。
所以拿到这个 zip,真正值钱的不是 LSTM 本身,而是那条从数据准备、建模、回测到参数寻优的流水线。把滚动回测写进你的日常流程,比盯着一时的预测准确率有用得多。希望帮到你。
本文还有配套的精品资源,点击获取