简介:基于Python的循环神经网络(RNN)股票价格预测源码包,面向正在完成毕业设计、期末大作业或初涉时间序列预测的Python学习者,提供一套可直接运行参考的完整项目。源码均经过本地编译验证,评审分达95分以上,难度适中,内容由助教审定,能较好贴合常见课程设计要求。压缩包共125个文件,核心为75个Python脚本,涵盖数据加载与清洗、特征构造、RNN模型搭建、训练及预测评估等常规流程;26个CSV数据文件包含多组时序数据(如空气污染预测样例),可用于对照测试;另附15个TXT说明、h5模型权重、checkpoint断点及索引文件,整体包体仅5.46MB,文件结构清晰便于按需查阅。目前已有239人学习浏览,资源适合需要完整源码、数据处理思路及模型调参经验的读者,也可作为毕业设计或大作业的实用参考。
1. 拿到基于Python的循环神经网络股票价格预测源码后别急着跑:先想清楚三件事
“基于Python的循环神经网络股票价格预测源码.zip”这个包,解压之后最常见的翻车现场不是缺依赖,而是训练集loss低得离谱、验证集曲线贴合得像复刻,换一段行情立刻变成废纸。这个标题背后并不是“跑一个LSTM就能预测股价”,而是一整套从行情数据到滑窗样本、从模型结构到滚动预测的序列建模流程。它能帮你搞懂RNN循环神经网络在时间序列任务里的真实边界,也适合刚接触python数据分析与可视化、想复现一个完整预测工程的从业者。如果你指望跑完源码就去下单,趁早把预期降下来;如果你是想理解时序预测的坑,这份源码值得慢慢拆。
2. 循环神经网络为什么适合股价序列:从记忆机制到LSTM/GRU选型
股价不是一组独立样本的集合。今天这根K线受昨天影响,也受前两周趋势影响,甚至受一个季度前的成交密集区影响。普通神经网络会把过去20个交易日摊平成20个并列特征,等于让模型自己重新学“先后关系”;循环神经网络则在时间步上共享权重,天然假设相邻两天有关联。源码里常见的input_shape=(lookback, n_features),就是在告诉模型“按天看,而不是按特征看”。
2.1 从滑动窗口到记忆:RNN处理时间序列的基本方式
RNN的核心是一个在时间维上反复使用的隐状态。用最简形式表达就是:
h_t = tanh(W_x * x_t + W_h * h_{t-1} + b)
每一步的隐状态既来自当前输入x_t,也来自上一步的h_{t-1},所以网络有了“记忆”。你要在源码里看懂的数据形状通常是三维的:
import numpy as np # 32个样本,每个样本20个交易日,每天5个特征 x_batch = np.random.rand(32, 20, 5)第一个维度是batch_size,第二个维度是lookback,第三个维度是特征数。普通神经网络会把20个交易日当成20个无关输入节点,RNN则逐个时间步读入这20天,每一步的隐状态都携带前序信息。我在看源码时,第一件事就是找input_shape,确认第二维是时间步、第三维是特征,而不是把时间步和特征混在一起拍平。
2.2 普通RNN为什么记不住:梯度消失与长期依赖
普通RNN在反向传播时需要沿时间步展开,误差信号连乘多步之后会指数级缩小,这就是典型的梯度消失。股价数据里真正有用的信号往往分布在几周甚至几个月里,比如均线系统、成交密集区、趋势惯性,普通RNN很难把十几步之前的信息带回当前时间步。所以你会发现市面上的股票预测源码很少直接用SimpleRNN,多半是LSTM或GRU。
LSTM用遗忘门、输入门、输出门控制信息流动,GRU把门控精简成两个,参数更少。我的选择习惯是:数据量只有几千根日线时,优先GRU,不容易过拟合;数据量超过两万条,再换LSTM对比验证集效果。如果源码里用了LSTM,我也不会一上来就改结构,而是先固定数据流程,再看验证集方向准确率。
2.3 LSTM和GRU怎么选:一个能跑通的网络骨架
一个最小可跑的LSTM骨架长这样:
from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout model = Sequential() model.add(LSTM(units=64, return_sequences=True, input_shape=(20, 5))) model.add(Dropout(0.2)) model.add(LSTM(units=32, return_sequences=False)) model.add(Dense(units=1, activation="linear"))第一层LSTM返回所有时间步的输出,所以return_sequences=True,这样第二层LSTM才能继续按时间步读取;最后一层LSTM只保留最后一个隐状态,输出一个预测值。input_shape里的20是时间窗口,5是特征数量。这里我一般把units从32起调,数据多再放大到64,不要一上来就是128,股价序列噪声大,单元太多会把短期波动当信号学进去。
| 参数 | 我习惯的初始值 | 调节方向 |
|---|---|---|
| lookback | 20 | 特征噪声大就降到10 |
| units | 32或64 | 样本量过万再加 |
| batch_size | 32 | 训练震荡就调小 |
| epochs | 100配合早停 | 以验证集为准 |
这套参数不是公式,而是一个起点。源码里如果写得比这个复杂,先留结构,不要急着删层,等问题暴露再改。
3. 本地跑通循环神经网络股票预测工程:四个代码块一次到位
源码包的命名各不相同,但核心模块一般就是“数据获取、序列构造、模型训练、反归一化”。我拿到包之后不会直接执行整个训练脚本,而是把数据这一环先拆出来单独跑,确定顺序、字段、样本数都对得上再进模型。
3.1 行情数据获取:优先数据接口而不是python爬虫
很多入门帖会教你用python爬虫去抓行情页,但网页改版、反爬、停牌、复权都会让数据对不齐。常见做法是直接通过数据接口拿日线,akshare这类接口不需要token,字段也接近日常习惯。
import akshare as ak import pandas as pd df = ak.stock_zh_a_hist( symbol="600519", period="daily", start_date="20150101", end_date="20241231", adjust="qfq", ) df = df.rename(columns={ "日期": "date", "开盘": "open", "收盘": "close", "最高": "high", "最低": "low", "成交量": "volume", }) df["date"] = pd.to_datetime(df["date"]) df = df.sort_values("date").reset_index(drop=True)这段代码把接口返回的原始字段改成自己习惯的英文列名,然后按日期升序排列。接口返回顺序不一定是时间正序,所以sort_values不能省。adjust="qfq"表示前复权,避免除权除息造成价格跳空。注意akshare字段名偶尔随版本调整,跑之前先打印df.head()看一眼,别让后面画图的日期对不上。
3.2 构造滑窗样本:lookback窗口与标签对齐
模型不是拿整张表直接训练的,它需要切成“过去20个交易日预测下一个交易日”的样本对。这里最容易出错的是标签对齐,我习惯写成这样:
import numpy as np def create_sequences(data, close_idx=0, lookback=20): X, y = [], [] # 窗口从i到i+lookback-1,标签取i+lookback的收盘价 for i in range(len(data) - lookback): X.append(data[i:i + lookback]) y.append(data[i + lookback, close_idx]) return np.array(X), np.array(y)逻辑说明:data[i:i + lookback]是一段长度为lookback的历史窗口,data[i + lookback, close_idx]是窗口之后第一个交易日的收盘价。不要写成data[i + lookback - 1],那等于用当天收盘价预测当天,属于信息泄漏。参数close_idx指向收盘价所在列,如果数据只有close一列就是0;如果后面加了别的特征,再调整下标。
3.3 按时间顺序切分数据并训练:先fit归一化参数,再喂给LSTM
时间序列不能用train_test_split随机打散,必须按时间先后切。归一化也只能在训练段上求min/max,否则未来数据会偷看答案。
from sklearn.preprocessing import MinMaxScaler train_end = int(len(df) * 0.7) val_end = int(len(df) * 0.85) data = df[["close", "volume"]].copy() close_scaler = MinMaxScaler() volume_scaler = MinMaxScaler() # 只对训练段 fit,验证集和测试集只做 transform close_scaler.fit(data["close"].values[:train_end].reshape(-1, 1)) volume_scaler.fit(data["volume"].values[:train_end].reshape(-1, 1)) data["close"] = close_scaler.transform(data["close"].values.reshape(-1, 1)) data["volume"] = volume_scaler.transform(data["volume"].values.reshape(-1, 1)) X_train, y_train = create_sequences(data.values[:train_end], close_idx=0) X_val, y_val = create_sequences(data.values[train_end:val_end], close_idx=0) X_test, y_test = create_sequences(data.values[val_end:], close_idx=0)close_scaler.fit只作用在训练段,测试集的min/max可能超出[0, 1]区间,这是正常的,不要回去重新fit。这样能保证模型在验证时没见过未来的价格极值。切分原始数据之后再构造滑窗,否则一个训练样本可能同时包含训练段和测试段的数据,序列就串味了。
训练部分配合早停:
from tensorflow.keras.callbacks import EarlyStopping model.compile(loss="mse", optimizer="adam", metrics=["mae"]) early_stop = EarlyStopping( monitor="val_loss", patience=15, restore_best_weights=True, ) history = model.fit( X_train, y_train, validation_data=(X_val, y_val), batch_size=32, epochs=100, callbacks=[early_stop], verbose=1, )batch_size=32是常见起点,数据量小可以降到16,训练loss震荡就调小;patience=15意味着验证loss连续15个epoch不下降就回退到最佳权重,这是防止过拟合的后悔药。不要迷信epochs=100一定全部跑完,早停触发后停掉反而更好。
3.4 反归一化与可视化:别把归一化数字直接画成股价
预测出来的是归一化之后的收盘价,直接画图会发现纵轴是0到1,完全看不出价格意义。必须用训练段保存的close_scaler还原:
import matplotlib.pyplot as plt pred_scaled = model.predict(X_test, verbose=0) pred_close = close_scaler.inverse_transform(pred_scaled.reshape(-1, 1)) true_close = close_scaler.inverse_transform(y_test.reshape(-1, 1)) plt.figure(figsize=(12, 5)) plt.plot(true_close, label="true close", linewidth=1) plt.plot(pred_close, label="pred close", linewidth=1) plt.legend() plt.show()为什么单独对close做MinMaxScaler?因为如果一开始把close和volume拼成一个矩阵统一缩放,反归一化时只取第一列,得到的价格范围会对不上。我见过不少源码把全部特征一锅端缩放,最后inverse_transform出来的“股价”离谱得没法看。做python数据分析与可视化时,一定要为每个物理量保存独立的scaler对象。
4. 循环神经网络预测的排查清单:六条让LSTM股票预测翻车的根因
这一章是血泪经验。下面六条我都在不同源码里见过,每一条都是“现象、原因、解决”三段式,排查时照着看就行。
4.1 随机切分训练集:把明天的答案混进了昨天的考题
现象:用train_test_split随机划分后,训练集和测试集的预测曲线贴合得完美,但模型拿到新一段行情就失效。
原因:股价序列前后存在自相关,随机打散会让未来样本出现在训练集里。模型在训练时已经见过“明天”长什么样,测试自然开卷。
解决:按时间顺序切分原始数据,永远用前面的部分训练、后面的部分验证。还要注意先切分再生成滑窗,不能在已经生成的全体样本上直接切片。
4.2 归一化偷看全量未来:min/max把测试期信息提早泄露了
现象:验证集loss很低,实盘却出现预测值异常偏大或偏小。
原因:有人上来就执行scaler.fit_transform(whole_data),测试期的最高价和最低价在训练时已经参与了归一化。模型等于提前知道了未来区间的上下界,所以测试期怎么预测都“合理”。
解决:先按时间切分,只对训练段调用scaler.fit(),验证集和测试集只调用transform()。
注意:先切分原始数据,再分别构造滑窗,否则窗口会跨切分边界,同样造成泄漏。
4.3 预测曲线比真实股价慢半拍:这不是调参能消除的“好结果”
现象:预测曲线几乎贴着真实曲线走,但整体向右平移了一天,看上去非常漂亮,根本没法用来交易。
原因:股价接近随机游走时,模型在MSE目标下的最优预测就是“等于上一根K线收盘价”。LSTM学到的是复制最近价格,而不是发现了什么规律。
解决:不要再用“价格曲线贴不贴”作为第一评价指标。把预测目标改成“明天收盘相对今天收盘的涨跌幅”,再看方向命中率。曲线平滑、滞后一天,在实盘中没有任何价值。
4.4 用MSE评估涨跌方向:回归分数和交易效果错位
现象:模型均方误差很小,但预测方向准确率只有50%,实盘盈亏基本靠手续费打平。
原因:MSE衡量的是数值偏差,股价噪声很大,预测值偏离两块钱可能方向正好反了;交易赚不赚钱主要看方向对不对,而不是价格差多小。
解决:加一个方向准确率指标再看模型:
from sklearn.metrics import accuracy_score gt = close_scaler.inverse_transform(y_test.reshape(-1, 1)) pred = close_scaler.inverse_transform(pred_scaled.reshape(-1, 1)) correct = (np.diff(gt.ravel()) > 0).astype(int) pred_dir = (np.diff(pred.ravel()) > 0).astype(int) direction_acc = accuracy_score(correct, pred_dir)gt.ravel()把二维数组拉平,然后差分得到每天的涨跌方向,预测方向也做同样处理。我一般看方向准确率超过55%才考虑下一步,这只是经验值,不是数学结论。很多回测漂亮的python量化交易策略代码也是掉在这个坑里,历史曲线好看,方向准确率不过半。
4.5 标签shift漏一行:最后一天的NaN让样本数悄悄缩水
现象:明明有1000根K线,训练时X和y的第一维对不上,或者预测结果少一天。
原因:常见写法是df["close"].shift(-1)生成标签,最后一行变成NaN,dropna()把最后一行删掉,但特征和标签没有对齐。
解决:构造序列时循环到len(data) - lookback,标签取data[i + lookback, close_idx],天然避开最后没有未来标签的位置。跑完可以加一句断言:assert X.shape[0] == y.shape[0],防止静默错位。
4.6 训练集能学到一条直线:只用单维收盘价,模型没有足够信息
现象:训练loss下降,但预测输出几乎是一条水平直线,或者一直在最近收盘价附近小幅波动。
原因:输入只有close一个特征。随机游走数据里,过去价格对明天价格几乎没有稳定预测力,LSTM没有额外信息可学,只好输出最近值。
解决:加入成交量、换手率、价格高低位、简单均线差等候选特征,或者把回归问题改成涨跌分类。不要指望只靠一根收盘价线跑出稳定规律,那不是模型笨,是任务设计本身信息不足。
5. 滚动多步预测:让模型从“看一天”进化到“看五天”
单步预测每次都用真实历史窗口,只预测下一个交易日,这在回测时相当于开卷考试。实盘里你不可能等到下一天收盘再让模型预测再下一天,因为第二天还没发生。这就是滚动多步预测要解决的问题:把模型自己的预测值回填到窗口里,连续推出未来五天。
5.1 单步预测和滚动预测的区别
单步预测的窗口始终来自真实数据,误差不会累积;滚动预测从第二步开始,输入里混入模型自己的预测值,误差会一层层叠加。所以滚动预测的曲线不会像单步预测那么贴合真实,越往后越发散,这反而是正常的。
| 模式 | 第一次输入 | 第二天输入 |
|---|---|---|
| 单步预测 | 真实历史窗口 | 重新用真实历史窗口 |
| 滚动预测 | 真实历史窗口 | 窗口里最后一天换成模型预测值 |
5.2 滚动预测最小实现
def multi_step_forecast(model, last_window, steps=5, close_idx=0): forecasts = [] window = last_window.copy() for _ in range(steps): # 用当前窗口预测下一个收盘价 pred = model.predict(np.expand_dims(window, axis=0), verbose=0)[0, 0] forecasts.append(pred) # 把窗口向前推一天,最后一天用预测值覆盖 window = np.roll(window, -1, axis=0) window[-1, close_idx] = pred return np.array(forecasts)np.roll把整个二维窗口沿时间轴向前移动,原本最后一天的位置被腾出来,再写入刚生成的预测值。close_idx=0表示收盘价在特征矩阵的第一列。这里有个简化:滚动时其它特征列没有跟着联动,还是沿用前一天数值,所以误差会比严格的多步预测更大。想更好一点,可以把成交量等特征也按最近值回填,但不要指望多步预测像单步那样贴曲线。
我现在拿到任何股票预测源码,第一件事不是看模型结构,而是看预测函数里有没有把预测值回填到窗口——没有的话,先怀疑它是不是用了个测试集上取巧的预测方式。做完滚动预测,打印最后三行窗口,确认最后一个时间步不是真实价,再谈后续。希望帮到你。
本文还有配套的精品资源,点击获取