☰
PSO-LSTM神经网络股票调整收盘价预测Python源码详解
2026/10/3 8:52:53 网站建设 项目流程

简介:基于粒子群优化长短期记忆网络的股票调整收盘价预测Python源码,适合完成金融数据分析类课程设计或期末大作业的高校学生,目标是借助PSO自动寻优LSTM参数,实现调整收盘价的单维单步预测。包内共10个文件,以Python脚本为核心,附带7个csv行情数据文件(涵盖BTC-USD、DJI、AAPL等常见标的)、1份README说明和1个txt文档,整体仅490KB,非常轻量。代码注释完整,运行流程清晰,读者可依据README快速部署,也可替换成自己的交易数据观察预测效果。截至目前已有171人学习下载,是一份能直接用于答辩演示、具备实际参考价值的完整源码资源。

1. PSO-LSTM预测股票调整收盘价:这份Python源码能做什么,适合谁

期末大作业和课程设计里,股票预测是最热门也最容易翻车的选题。绝大多数人卡在同一个地方:模型跑通了,但预测的是原始收盘价,分红、拆股一发生,数据里全是“假跳变”,答辩时被老师一句“你这个序列不平稳怎么解释”问住。这份基于PSO-LSTM神经网络的股票调整收盘价预测Python源码,核心价值在于把目标变量换成了调整收盘价(Adjusted Close),同时用粒子群算法自动搜LSTM的超参数,解决了新手调参全靠玄学的问题。压缩包里带了BTC、AAPL、DJI、上证指数等七个CSV数据集,主脚本是单维单步MSE预测,注释齐全,属于那种“下载下来改个路径就能跑”的课程设计资源。适合三类人:急着交期末大作业的本科生、想把深度学习预测流程完整走一遍的入门者、以及答辩前想给模型加一两个亮点参数的进阶玩家。

2. 为什么是PSO-LSTM:调整收盘价这个选题的建模逻辑与参数边界

2.1 调整收盘价和原始收盘价的差别:预测目标要先选对

股票CSV里通常有Open、High、Low、Close、Adj Close、Volume六列。Close是当天收盘的原始价格,Adj Close是经过复权处理后的价格,已经剔除了分红、送股、拆股对价格连续性的影响。用原始收盘价做预测,遇到除权除息日价格会瞬间跳空,LSTM会把这个跳空当成真实趋势去学,训练出来的loss虚高,画出来的预测曲线在除权日附近有明显毛刺。

调整收盘价的价值在于价格序列是连续的,适合直接做时间序列建模。这也是为什么这份源码把Datasets目录里的数据都落在Adj Close字段上。实际动手时要注意:Yahoo Finance下载的数据里Adj Close在靠后位置,有的版本是最后一列,读CSV时不要用数字索引硬编码,直接用列名df['Adj Close']最稳。另外,数据文件里如果同时存在Close和Adj Close,对比着看一眼能发现两者差异大的日期,往往就是发生过分红或拆股的日期,这是一个很有用的数据校验技巧。

2.2 LSTM凭什么比前馈神经网络更适合预测股票序列

前馈神经网络(Feedforward Neural Network)处理的是固定大小的输入,输入和输出之间没有时序依赖。拿过去10天的收盘价预测明天的价格,前馈网络的做法是把10个值拼成一个向量塞进去,模型完全不知道这10个值的先后顺序,第1天的数据和第10天的数据在模型眼里地位相同,趋势信息就这么丢了。

LSTM通过三个门结构——遗忘门、输入门、输出门——维护一个细胞状态,让信息有选择地跨时间步保留或丢弃。股票序列恰好有这种特点:过去五天的走势形态往往比某一天的绝对值更重要。PSO-LSTM里的LSTM部分一般用单层或双层LSTM加上全连接输出层,输入形状是(batch_size, time_step, feature_dim),这里的feature_dim在单维预测里就是1,只喂调整收盘价本身。需要提醒的是:LSTM不是神秘的黑匣子,它的记忆容量由隐藏层神经元个数决定,神经元太少记不住趋势,太多则吃训练时间还容易过拟合,这个值正是PSO要搜的参数之一。

2.3 PSO到底在优化谁:三个超参数的搜索空间

如果不用PSO,LSTM需要手工调的东西很多:时间步长、隐藏层神经元数、学习率、批量大小、训练轮数、优化器选择,每一个都影响最终MSE。课程设计里时间有限,网格搜索动辄几十组实验,GPU差一点的机器一天都跑不完。PSO的做法是把一组超参数当成一个“粒子”,每个粒子在参数空间里飞行,靠个体最优和群体最优更新速度和位置,十几轮迭代就能收敛到一组比较合理的参数。

在这份单维单步MSE预测脚本里,PSO搜索的超参数通常是三个:时间步长、LSTM隐藏层神经元数、学习率。粒子群规模一般设10到20,迭代次数10到20轮。搜索范围建议按经验值锁定:时间步长5到20,隐藏层神经元4到64,学习率0.0001到0.01。这三个范围太宽会让PSO收敛很慢,太窄又等于人工预设了答案。这里的参数设定直接决定了PSO的适应度函数每一次要训练多少个epoch的LSTM,评估一次LSTM要几秒到几十秒,乘上粒子数和迭代次数,就是整个寻参过程的总耗时。跑之前先拿单组参数试一次,估算单次LSTM训练时间,再反推粒子群规模和迭代次数,是控制总耗时的核心办法。

2.4 单维单步与MSE:这个任务的边界要清楚

文件名里写着“单维单步mse预测”,这是整个项目的任务定义,也是答辩时最容易被追问的地方。单维指只用调整收盘价这一个特征,没有引入成交量、技术指标或新闻情绪;单步指用过去time_step天的数据预测明天一个点,不是多步外推;MSE指均方误差,既当损失函数又当PSO的适应度函数。这个设置简化了问题,让新手能在两小时内跑通全流程,但代价是模型的上限受限:单维输入本身不含成交量信息,遇到缩量上涨的日子,预测误差会明显变大。理解这个边界后,你可以在答辩里主动说“这是实验基准版本,后续可以扩展到多特征输入”,这比被老师指出要体面得多。

3. 数据集与目录结构:七个CSV怎么读、怎么选、怎么对齐

3.1 包内文件的主干结构

压缩包解开之后,顶层是PSO-LSTM-for-Prediction-main目录,里面核心内容如下表:

文件/目录类型作用
Datasets/BTC-USD.csv数据比特币日线数据,主要用于加密货币价格预测实验
Datasets/DJI.csv数据道琼斯工业指数日线数据,代表美股大盘
Datasets/AAPL.csv数据苹果公司日线数据,典型个股样本
Datasets/GSPC.csv数据标普500指数日线数据,另一个大盘指数
Datasets/IXIC.csv数据纳斯达克综合指数日线数据
Datasets/000001.SS.csv数据上证指数日线数据,A股样本
Datasets/Gold_daily.csv数据黄金日线数据,贵金属资产
会议PSO-LSTM单维单步mse预测.py脚本主程序,包含数据读取、滑窗、PSO寻参、LSTM训练与预测
README.md文档项目说明(我拆包时一般是先看这个文件再动脚本)

1.txt这个文件我猜测是数据下载说明、来源链接或预处理备注,不属于核心代码。动手第一步别急着跑脚本,先打开README和那个txt,确认数据格式、字段含义以及作者对Python版本的要求。这个习惯能在后续少踩很多坑,下面第5章的翻车记录里一半都和数据格式有关。

3.2 读入与清洗的标准流程

七个CSV的列结构基本一致,都是标准OHLCV格式。但不同来源的数据有几个坑:日期格式不统一,有的是2024-01-01,有的是01/01/2024;千位分隔符会导致数值被读成字符串;表头可能多一个空格或BOM字符。主脚本里的数据读入顺序一般是:

import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler def load_data(path, usecols=['Date', 'Adj Close']): df = pd.read_csv(path, usecols=usecols, parse_dates=['Date']) df.dropna(inplace=True) # 去掉停牌或缺失行 df.sort_values('Date', inplace=True) # 按时间升序,防止乱序 return df df = load_data('Datasets/AAPL.csv') price = df['Adj Close'].values.reshape(-1, 1) print(f"样本量: {len(price)}, 时间范围: {df['Date'].min()} 至 {df['Date'].max()}")

usecols只读需要的列,避免内存浪费;parse_dates把日期字符串转成时间类型,方便后续按时间排序;dropna必须有,因为有些日期行可能缺失收盘价,不去掉会在归一化时算出NaN。sort_values这行最容易被忽略但最重要:如果CSV本身是乱序的,滑窗样本里出现在“相邻”位置的两个样本实际跨了很长的日期,LSTM学到的全是噪声。拿到任何一份股票数据,先对时间列排序并打印时间范围,是十分钟内必做的操作。

3.3 七个数据集怎么选:不同资产适合展示不同结论

七个数据集覆盖了加密货币、美股个股、美股指数、A股指数和黄金,这份资源最值钱的地方之一是可以用同一套代码跑多个市场做横向对比。实验时建议按资产类别选三组:AAPL或DJI(成熟市场)、000001.SS(A股)、BTC-USD(加密货币)。

这三类资产性质差异明显:美股个股机构主导,趋势性和均值回复特征比较明显,LSTM拟合效果通常最好;上证指数受政策影响大,波动集聚性强,预测误差普遍偏大;比特币波动剧烈且存在周期性牛熊切换,训练集里如果带上了暴跌段,预测曲线会在转折点附近严重滞后。这组对比放进课程设计报告里,比单跑一个数据集有说服力得多——它证明了模型不是只在某一支股票上碰巧有效,也暴露了模型在不同波动特征下的行为差异。答辩时老师问“为什么选这个数据集”,你就可以回答“选择波动性不同的资产做对比,检验模型对波动特征的鲁棒性”,这一句就能把报告深度拉开一截。

4. 核心脚本拆解:从CSV到MSE的七步完整流程

4.1 整体流程先看在哪个阶段动手改参数

Pandas读数据、MinMaxScaler归一化、滑窗切样本、PSO粒子群寻超参数、用最优参数训练LSTM、反归一化、计算MSE并绘图,这是典型的七步流程。主脚本的函数划分直接对应这七个阶段,找代码时按函数名定位比按行号定位更高效。我第一次拆这类源码时会先用编辑器把函数定义全部列出来,大概了解脚本的骨架结构,然后从最核心的PSO适应度函数开始读,因为这个函数里藏着整个项目的关键逻辑。

一个值得注意的细节是归一化在所有数据上进行还是只在训练集上进行。多个源码包我拆下来,发现新手写的最多的情况是:先对整个序列做MinMaxScaler,再切训练测试集。这个写法简单,代码短,但有一个隐患——测试集的极值信息被提前泄漏给了归一化器,评估结果会比真实水平略好。严格的做法是先切再归一化,训练集单独fit。大多数课程设计的源码不会抠到这个粒度,但答辩时如果你能主动提一句“这里测试集数据参与了归一化,会导致结果略乐观”,老师会认为你对数据泄漏有认知。

4.2 滑窗样本构建:时间步长决定LSTM“看几天”

LSTM不能直接吃一整段连续序列,需要把序列切成固定长度的窗口。窗口长度就是前面说的time_step,含义是“用过去N天的收盘价预测第N+1天”。滑窗函数的写法通常是:

def create_sequences(data, time_step): X, y = [], [] for i in range(len(data) - time_step): X.append(data[i:i + time_step, 0]) # 取连续 time_step 天 y.append(data[i + time_step, 0]) # 预测下一天 return np.array(X), np.array(y) time_step = 10 # 用过去10个交易日预测第11天 X, y = create_sequences(scaled_data, time_step) X = X.reshape(X.shape[0], X.shape[1], 1) # LSTM 要求三维输入

X的形状是(样本数, time_step, 1),第三维的1代表单维特征。time_step=10在交易日历里就是两个自然周,这个数值对股票日线数据还算合理。注意最后time_step条数据是没有对应的预测目标的,所以滑窗后样本总量是len(data) - time_step,这是排查“为什么样本数少了一截”的关键原因。reshape那行很容易写错,少了一个维度LSTM会直接报ValueError,这也是常见报错之一。

time_step的取值对预测效果影响明显:取值太小比如3到5,模型看到的最近几天噪声太大,预测值会紧贴着上一个值走,看起来“预测很准”实际上只是滞后搬运;取值太大比如30到60,模型被过长历史拖累,对最近变化的反应变慢,在趋势反转处误差变大。PSO把我们手动调这个参数的过程自动化了,但要理解搜索结果的合理性:PSO给出的time_step如果落在20以上,说明这组数据趋势成分较强;如果落在5以下,说明序列短期波动主导。带着这个理解去看输出,你才能真正解释结果,而不是只会截个MSE的图。

4.3 PSO适应度函数:每评估一次就要训练一遍LSTM

PSO的适应度函数是整个脚本的计算瓶颈,它的任务是:给一组超参数(time_step、神经元数、学习率),训练一个LSTM,返回验证集MSE。粒子群算法用这个MSE来比较粒子好坏。简化伪代码如下:

def fitness(params): step, units, lr = params X, y = create_sequences(scaled_data, int(step)) split = int(len(X) * 0.8) X_train, X_test = X[:split], X[split:] y_train, y_test = y[:split], y[split:] model = Sequential() model.add(LSTM(int(units), activation='tanh', input_shape=(X.shape[1], 1))) model.add(Dense(1)) model.compile(optimizer=Adam(learning_rate=lr), loss='mse') model.fit(X_train, y_train, epochs=20, batch_size=32, verbose=0) mse = model.evaluate(X_test, y_test, verbose=0) return mse

适应度函数每一次调用都要完整执行“建模型、训练20轮、评估”的流程,所以它有多慢,PSO整个寻参过程就有多慢。粒子数乘迭代次数就是适应度函数的调用次数:20个粒子、15轮迭代,就是300次LSTM训练,一次训练2秒的话总耗时10分钟,可以接受;一次训练30秒的话总耗时就是2.5小时,做实验前要估算清楚。这里的split是固定比例切分,不是随机切分,时间序列不能打乱,否则未来数据泄漏到训练集中,这一点记住即可,下面避坑章还会展开。

此外,每个粒子在搜索空间里的初始位置是随机的,但不同参数的量纲差别很大——time_step大概在5到20,学习率却小于0.01。如果PSO的速度公式不做归一化处理,学习率的搜索会被time_step的数值变化完全压制。处理方式一般是把每个维度分别归一化到0到1区间再搜索,解析时再映射回真实范围。源码里的PSO部分如果没做这个处理,收敛会异常慢,这是一个值得动手检查的细节。

4.4 LSTM训练与最终预测:反归一化是最后一道坎

PSO返回最优参数后,脚本会用最优参数重新训练一次LSTM,然后对测试集逐点预测。注意这部分采用的是单步滚动预测还是直接多步预测,两种方式差别很大:直接预测是把测试集前time_step个真实值喂进去,一次性输出测试集所有预测点;滚动预测则是每预测出一天,就把这个预测值拼回输入序列,再用它预测下一天。前者在每一步都用了真实历史值,误差不会累积,看起来总是“贴近真实曲线”;后者预测误差会随步数累积,曲线会逐渐偏离,但更接近真实应用场景。课程设计报告里要写清楚用的是哪一种,写不清楚会被判定为实验不严谨。

最后,模型输出的是归一化后的预测值,范围在0到1之间,必须用训练时的scaler做逆变换才能得到真正的价格。反归一化这行代码虽然只有一行,却是新手最容易忘记的:

pred_price = scaler.inverse_transform(pred_scaled.reshape(-1, 1))

这里有个细节:如果标准做法是先切分再各自归一化,那么预测值要拿训练集的scaler来反变换,而不是测试集的scaler。如果拿错了scaler,预测曲线整体偏移,而且这个偏移在图上看起来不是平移,而是被压缩或拉伸,很难一眼发现。判断方法很简单——用训练集scaler反变换测试集真实值,和CSV里的真实价格对不上,就说明scaler用错了。

4.5 从下载到首张预测图:二十分钟内的实验路径

拿到压缩包后,为了最快看到效果,我的建议是不要直接跑全量PSO,先做一个“单组参数冒烟测试”:绕过PSO,直接用time_step=10、units=32、lr=0.001跑一遍LSTM训练,确认数据读取、滑窗、归一化、反归一化和绘图整条链路通畅。确定所有环节都没问题后,再打开PSO主流程,把粒子数和迭代次数改小,比如各设8到10,这样总耗时可控。跑完一组后,依次完成以下步骤:

  1. 在load_data里把Datasets/AAPL.csv换成Datasets/000001.SS.csv,观察两组数据预测效果的差异;
  2. 打开create_sequences里的time_step,手动改成5、10、20各跑一遍,记录MSE变化,理解时间步的作用;
  3. 查看PSO每次迭代打印的Gbest值,确认MSE是否在持续下降,如果迭代中段就纹丝不动,说明搜索范围或PSO参数需要调整。

这套路径的优点是:每一步只改一个变量,出了问题能立刻定位是数据问题、模型问题还是超参数问题。直接一上来就跑完整PSO,三百多次LSTM训练跑完才看到结果,中途任何一个小报错都要从头来,心态很容易崩。

5. 避坑排查:PSO-LSTM股票预测里最常见的翻车点

5.1 归一化泄漏:测试集极值提前暴露

现象:模型在测试集上的MSE很低,预测曲线几乎贴着真实曲线走,但换一段新数据后效果急剧变差。

原因:最早我按“全量数据一个scaler”的方式写归一化,归一化器看过整个数据集的极值。测试集的最高、最低点在训练阶段已经泄露给了scaler,评估结果虚高。

解决:先切训练集和测试集,再在训练集上scaler.fit,然后用这个scaler分别transform训练集和测试集。具体改动是把fit_transform拆成fit和transform两步,这是一个所有时序预测项目通用的改动。

5.2 预测曲线滞后:LSTM把上一天的收盘价背下来了

现象:预测曲线和真实曲线形态一致,但整体向右平移一天,MSE却不高,看起来“很准”。

原因:时间序列里相邻两天的价格高度相关,LSTM学到的其实是“今天的价格和昨天差不多”,而不是学懂了趋势。这不是模型作弊,而是单维单步预测任务的天然陷阱。任何不用外部特征的纯时间序列预测都会遇到这个问题,模型只是在做一个很简单的复制任务。

解决:看方向准确率而不是只看MSE,方向准确率计算的是预测值相对真实值是涨是跌的命中率。如果方向准确率只有50%左右,说明模型看起来拟合得漂亮,实际毫无预测力。第6章我会给出这个指标的代码。此外,把time_step增大到10到20能在一定程度上缓解滞后,但无法根除。

5.3 PSO寻参耗时爆炸:300次LSTM训练跑了一整夜

现象:PSO循环打印的迭代进度很慢,预计总时长超过几小时,运行中途电脑风扇狂转,停了又不甘心。

原因:粒子数和迭代次数设置过大,加上LSTM训练epoch数偏高,每个粒子的单次评估就要几十秒。我见过把粒子数设成50、迭代50次、epoch设成100的写法,总训练次数是2500次,单机CPU上基本等于跑不完。

解决:先跑一次单参数训练估算单次适应度评估耗时;粒子数控制在10到15,迭代次数10到15;搜索范围收窄,学习率上限0.01、time_step上限20。记住一个经验:课程设计场景下,总耗时超过1小时就说明参数没控制好,直接改成小规模重跑。

5.4 对全序列做滑窗时混入了未排序数据

现象:代码跑完没有报错,但预测曲线在测试集后期明显偏离,MSE忽高忽低。

原因:CSV里的日期不是严格升序,或者从数据源下载时行序被打乱。滑窗是按行号切片的,乱序意味着窗口内的“最近N天”在时间上并不连续。

解决:在load_data里加一行df.sort_values('Date', inplace=True),然后打印df.head()和df.tail()核对首尾日期。对任何股票CSV,读入后先排序再操作,养成肌肉记忆。同理,检查是否有重复日期行,用df.drop_duplicates(subset=['Date'])兜底。

5.5 用全量数据归一化后切分,测试集信息泄漏进scaler

现象:和5.1相似但位置不同,这次是滑窗之后才切分训练测试集,scaler又是在切分前做的。

原因:切分时机不对,归一化和切分的先后顺序遗漏了时间序列的因果约束。时间序列不满足独立同分布假设,任何在切分前基于全量数据做的统计变换,本质上都是间接泄漏。

解决:严格顺序是:排序、切分、归一化、滑窗。但滑窗会依赖time_step,而time_step本身又由PSO搜索,这就产生了循环依赖。我一般的做法是先按8:2切分,再对训练集滑窗和归一化,测试集用同样的scaler变换后滑窗。主脚本如果先滑窗再切分,测试集的滑窗构建也应在切分后完成,而不是全量切好再切分窗口。

5.6 反归一化用错scaler导致预测价格整体偏移

现象:预测出的价格数值比真实价格高出一截或低出一截,但趋势形状保持一致。

原因:反归一化时用了测试集的scaler,而预测值是基于训练集scaler归一化后预测的,两套scaler的min和max不同,换算回去的绝对价格当然对不上。

解决:统一用训练集的scaler做反归一化。哪怕一开始用了先全量归一化再切分的简化写法,反归一化也要用同一个scaler对象,不能重新fit一个。一行代码的事,但出错概率极高。

5.7 结果不可复现:每次跑出来的MSE都不一样

现象:同一份代码连续跑两次,PSO找出的最优参数和最终MSE都不同,写报告时不知道填哪个数。

原因:源脚本里没设随机种子。LSTM权重初始化、PSO初始粒子位置、训练时batch的随机性都会影响结果。

解决:在脚本开头固定随机种子:

import random, numpy as np, tensorflow as tf random.seed(42) np.random.seed(42) tf.random.set_seed(42)

固定种子后,同一环境下次运行结果基本一致。注意PyTorch和Keras的种子设置方式不同,如果你的复现环境是PyTorch,要额外设置torch.manual_seed。这是我拆了这么多源码包后体会最深的一点,种子这行代码是报告数据可复现的基石,放在文件开头比放在任何位置都管用。

6. 在答辩环节给模型加一个区分度:补上方向准确率与滚动预测验证

课程设计答辩里,MSE常被追问“低了又怎样,跟股票投资有什么关系”。这个问题的本质是评价指标脱离业务语义。MSE衡量的是数值拟合误差,但投资者真正关心的是涨跌方向。所以我每次拿到这类预测源码,做的第一件事就是加一个方向准确率指标。

def direction_accuracy(y_true, y_pred): diff_true = np.diff(y_true.flatten()) diff_pred = np.diff(y_pred.flatten()) correct = np.mean((diff_true * diff_pred) > 0) return correct acc = direction_accuracy(y_test_true, pred_price) print(f"方向准确率: {acc:.2%}")

这段代码的核心逻辑是计算真实序列和预测序列各自的相邻差值,两者方向一致就算一次命中。方向准确率高于52%才说明模型在趋势判断上有统计意义,低于50%则意味着预测曲线虽然贴合、方向却不如抛硬币,需要向老师如实说明。用这个指标重新评估PSO-LSTM的预测结果,往往比MSE更诚实。另一个有区分度的做法是滚动验证:从训练集末尾取固定窗口,训练一次模型预测下一天,然后把真实值加入窗口、丢掉最早一天,再重复。下面是一个简化的滚动预测示例:

# 滚动预测示意 for i in range(len(test_data) - time_step): window = np.concatenate([train_data, test_data[:i]])[-time_step:] window = window.reshape(1, time_step, 1) pred = model.predict(window, verbose=0) predictions.append(pred[0, 0])

滚动验证的每一步只使用截至当前时刻的数据,严格模拟真实交易中“今天收盘后用已知信息预测明天”的场景。PSO-LSTM在这里的优势是用寻优后的超参做滚动预测,误差不会因参数不匹配而虚增。从那以后,我拿到任何一份时间序列预测源码,都会强制走一遍这个流程:先设固定随机种子,再补方向准确率,最后跑一次滚动验证——数值和时间对齐方式都对得上,再谈效果。这份资源的底子是好的,把这几个点补全后,它的功能也就真正落到了你手里。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询