资讯动态

沪深300股票预测实战:LSTM时序模型从数据清洗到滚动预测全流程

发布时间:2026/9/28 16:11:51 来源:尧图企业网站定制
简介这是一套面向人工智能、通信、自动化、电子信息等计算机相关专业学生与科研人员的沪深300股票预测项目覆盖数据下载、清洗、长短期记忆网络LSTM模型训练及实时预测全流程适合毕业设计、课程设计或科研演示。项目源码经过完整测试、运行稳定并配有详细设计文档能帮助读者快速理解从原始行情数据到预测结果的整体处理思路。资源共11个文件压缩包3MB核心为5个Python脚本分别负责配置、数据处理、LSTM建模与主程序、3个CSV训练/测试数据集以及Markdown说明文档和Transformer模块结构清晰便于按需查阅。已有50人学习下载。通过该项目读者可掌握数据清洗与特征构建、LSTM网络设计与调参、实时预测实现等关键方法还可基于脚本进行二次开发直接用于课题研究或工程实践。1. 沪深300股票预测一条从数据到预测的完整管线沪深300股票预测这类深度学习项目听起来高大上拆开看就是四件事数据下载、数据清洗、LSTM 模型训练、实时预测。这套项目的价值在于它把四件事完整串成了一条可复现的管线——你拿到的不是一段孤立模型代码而是从行情 CSV 到预测值的全链路还附了详细文档说明。做毕设、课程设计或者当第一个时间序列实战练习都能直接上手。适合两类人想快速跑通 LSTM 时序预测闭环的学生以及想搞懂训练参数怎么影响最终结果、预测曲线为什么滞后一天的从业者。一句话解决你的核心疑问今晚能不能让代码跑起来、跑完之后怎么解释输出。2. 项目骨架与数据下载六个文件分工和行情落盘方式2.1 文件分工入口、数据处理、模型定义各就各位拿到解压后的目录先别急着跑stock_main.py。把code文件夹里的几个文件职责搞清楚后面调参和排错会省很多事。文件/目录职责关键点stock_main.py主入口串联数据加载、训练、评估全流程dataprocess.py数据读取与清洗负责把 CSV 转成模型能吃的张量lstm_model.pyLSTM 网络定义模型结构、损失函数都在这里data_utils.py工具函数序列构建、数据集划分、归一化config.py全局配置所有可调参数集中在这一个文件transformer_to_stock/Transformer 对比实现想换模型做对比实验时用我一般建议先打开config.py和详细文档说明.md把窗口大小、学习率、训练轮数这几个参数抄下来再去看stock_main.py的调用顺序。常见毕设项目里stock_main.py的逻辑是读dataprocess.py清洗后的数据交给data_utils.py切窗口最后调用lstm_model.py里的模型训练。整套流程是单文件从头走到尾的结构没有复杂的工程框架适合学习和二次开发。transformer_to_stock/这个目录从命名看是 Transformer 做同一预测任务的版本适合在论文里当对比模型后续替换时保持输入输出接口一致就行。2.2 行情数据怎么拉目录结构与 CSV 的字段约定data目录下默认给了三份 CSV命名方式已经透露了数据划分策略文件内容典型用途train_mix-17-18.csv2017-2018 年特征宽表训练集train_mix-19.csv2019 年特征宽表验证集或追加训练test_mix.csv晚于训练集的测试数据最终回测与实时预测验证mix表示这是多特征混合后的宽表按日期排序字段一般包含开高低收和成交量。这种按年份切分的方式比随机切分靠谱得多——时间序列严禁随机打乱否则模型会偷看到未来数据。如果数据源需要重新拉取常见做法是用 akshare 这类免费数据源拉沪深 300 指数日线pip install akshare pandasimport akshare as ak import pandas as pd # 拉取沪深300指数日线symbol 对应 sh000300 df ak.stock_zh_index_daily(symbolsh000300) df.columns [date, open, high, low, close, volume] df.to_csv(data/sh000300_daily.csv, indexFalse) print(df.head())ak.stock_zh_index_daily返回的是历史日线数据包含日期、OHLC 和成交量六列symbolsh000300明确指定沪深 300 指数。如果你想要成分股数据而不是指数可以用ak.stock_zh_a_hist(symbol600519, perioddaily)拉单只股票。保存成 CSV 后再把技术指标、涨跌幅等派生特征横向拼进去就得到train_mix这样的宽表。值注意的是不同数据源的价格可能包含前复权、后复权或不复权三种状态务必统一用一种复权方式否则模型看到的价格断层会被当成真实行情。2.3 数据下载脚本的最小实现从拉取到落盘如果你需要自己重建数据集我习惯把下载和初步整理放在一个脚本里避免每次手工操作。下面这个最小实现可以直接替换或补充到dataprocess.py里import akshare as ak import pandas as pd def download_index_daily(symbol: str, start: str, end: str) - pd.DataFrame: # 拉取指定日期范围的日线数据 df ak.stock_zh_index_daily(symbolsymbol) df.columns [date, open, high, low, close, volume] df[date] pd.to_datetime(df[date]) mask (df[date] start) (df[date] end) return df.loc[mask].sort_values(date).reset_index(dropTrue) def build_feature_table(df: pd.DataFrame) - pd.DataFrame: # 构造派生特征收益率、振幅、5日均值等 df[ret] df[close].pct_change() df[amplitude] (df[high] - df[low]) / df[close] df[ma5] df[close].rolling(5).mean() return df.dropna().reset_index(dropTrue) # 生成三份 CSV17-18、19、20之后 for name, start, end in [ (train_mix-17-18, 2017-01-01, 2018-12-31), (train_mix-19, 2019-01-01, 2019-12-31), (test_mix, 2020-01-01, 2021-06-30), ]: raw download_index_daily(sh000300, start, end) table build_feature_table(raw) table.to_csv(fdata/{name}.csv, indexFalse) print(name, table.shape)pct_change()计算收益率列注意第一行会得到NaN后续清洗阶段要处理rolling(5).mean()是简单移动平均窗口取 5 个交易日正好一周。落盘时我建议不设索引date列作为后续排序依据。这里先构造派生特征、再切分年份排列顺序是「先算好特征、再按年份拆分」——这个顺序很重要因为用整段数据算出来的ma5在年份边界上会带入前一年的信息严格来说属于轻微泄漏但对毕设规模的项目影响不大文档里说明即可。3. 数据清洗与序列构建让 LSTM 吃到干净的时间窗口3.1 pandas 数据清洗流程缺失、重复、异常值处理顺序train_mix这类宽表最常见的脏数据问题有三个同一交易日重复出现、停牌导致的NaN、极端的inf值。pandas 数据清洗在这类时间序列项目里是第一个坑处理顺序错了后面全乱。import pandas as pd import numpy as np def clean_feature_table(df: pd.DataFrame) - pd.DataFrame: # 1. 日期统一并排序 df[date] pd.to_datetime(df[date]) df df.sort_values(date).reset_index(dropTrue) # 2. 同一交易日重复时保留最后一条 df df.drop_duplicates(subsetdate, keeplast) # 3. 价格列强制转数值脏数据变成 NaN for col in [open, high, low, close, volume]: df[col] pd.to_numeric(df[col], errorscoerce) # 4. 无穷值替换成 NaN再按时间方向前向填充 df df.replace([np.inf, -np.inf], np.nan) df df.ffill() # 5. 删除价格仍然缺失的行 return df.dropna(subset[close]).reset_index(dropTrue)步骤拆解先排序再查重避免排序前drop_duplicates保留的行不是最新一条pd.to_numeric的errorscoerce会把字符串脏值统一变成NaN这是 pandas 数据清洗里最常见的兜底手法ffill()表示用最近一个有效值向前填充停牌日期的价格沿用上一交易日收盘价符合市场常用的估值逻辑。注意第 4 步不要在ffill之前dropna否则停牌空洞会被直接删掉窗口构建时日期不连续的问题依旧存在。清洗完成后建议顺手打印一下每列的缺失统计确认没有大段空洞print(df.isnull().sum()) print(f清洗后数据范围: {df[date].min()} - {df[date].max()})如果某列缺失比例超过 5%要回头检查是不是数据源字段对齐出了问题不要盲目填充——特别是成交量这类波动大的列前向填充会造成假信号。3.2 时间窗口滑动两种写法与参数选择LSTM 输入是「一段连续时间步的特征」不是单行数据。把宽表切成(样本数, 窗口长度, 特征数)的三维张量是data_utils.py的核心工作。最基础的是for循环滑动窗口import numpy as np def build_sequences(data: np.ndarray, window: int 20, horizon: int 1): X, y [], [] for i in range(window, len(data) - horizon 1): X.append(data[i - window:i]) # 过去 window 天 y.append(data[i horizon - 1]) # 未来 horizon 天的目标 return np.array(X), np.array(y) # 假设 feature_table 是清洗后的多列特征 feature_cols [open, high, low, close, volume] values feature_table[feature_cols].values X, y build_sequences(values, window20, horizon1) print(X.shape, y.shape) # 例如 (1200, 20, 5) (1200, 5)window20对应约一个月的交易日模型用过去 20 天预测第 21 天horizon1指预测下一步若要预测未来 5 天就改成 5。X[i]的形状是(20, 5)20 个时间步、5 个特征LSTM 会按时间顺序读入这 20 步。y[i]直接取第ihorizon-1行的所有特征也就是说训练目标是预测未来那一整天的 OHLCV 五列如果你的任务是只预测收盘价就把y改成data[ihorizon-1, 4]训练时目标维度就是(样本数, 1)。数据量大时for循环慢可以用滑动窗口视图加速from numpy.lib.stride_tricks import sliding_window_view def build_sequences_fast(data: np.ndarray, window: int 20, horizon: int 1): X sliding_window_view(data, window, axis0) # 自动生成滑动窗口 X X[:-horizon 1] if horizon 1 else X y data[window horizon - 1:] return np.asarray(X), np.asarray(y)sliding_window_view是 numpy 1.20 之后的内置函数用步幅视图做窗口滑动不复制数据速度快一个量级。两种写法结果等价for循环胜在直观、适合教学大数据量时用sliding_window_view。这里有个细节X和y的长度要对齐否则训练时维度报错建议构建完马上打印X.shape[0] y.shape[0]检查。3.3 清洗阶段的雷区shuffle 和归一化别在这里做我见过不少项目在清洗阶段顺手np.random.shuffle所有行这在分类问题里没错但时间序列里这是致命错误。shuffle 之后相邻样本的时间关系全部断裂模型训练时会不断「穿越」到未来再回看过去验证指标虚高实盘一测就现原形。正确顺序是清洗 → 构造特征 → 按时间切分训练/验证/测试 → 归一化 → 构建窗口。窗口构建必须发生在归一化之后、训练之前而且切分和归一化都要分段操作。常见做法是train_test_split不用随机参数直接按行号切train_end int(len(values) * 0.7) val_end int(len(values) * 0.85) train_raw values[:train_end] val_raw values[train_end:val_end] test_raw values[val_end:] # 归一化对象只 fit 训练段验证和测试只 transform from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler() train_scaled scaler.fit_transform(train_raw) val_scaled scaler.transform(val_raw) test_scaled scaler.transform(test_raw)fit_transform和transform的差别是这套代码最关键的语义fit会记录训练段的最大最小值验证和测试段直接套用模拟的是「未来数据在训练时不可见」的真实场景。如果对整个数据集一次性fit_transform测试段的极值就泄漏进了归一化参数验证分数会乐观得一塌糊涂。这个坑我在第 5 章还会展开讲。4. LSTM 模型训练从 config.py 参数到评估指标4.1 模型结构选型层数、隐藏维度、dropout 和损失函数lstm_model.py里的网络结构通常是两层 LSTM 加全连接输出。为什么是两层而不是一层或三层一层 LSTM 学不到高层的时序抽象三层在小数据集上容易过拟合两层是时间序列预测里性价比最高的折中。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout def build_lstm(window: int 20, n_features: int 5): model Sequential([ # 第一层 LSTM 输出完整序列给第二层继续编码 LSTM(64, return_sequencesTrue, input_shape(window, n_features)), Dropout(0.2), # 第二层 LSTM 只输出最后一个时间步 LSTM(32, return_sequencesFalse), Dropout(0.2), # 全连接层把隐状态映射到预测目标 Dense(16, activationrelu), Dense(5) # 预测 OHLCV 五列 ]) model.compile( optimizeradam, lossmse, metrics[mae] ) return model model build_lstm(window20, n_features5) model.summary()几个参数对应config.py里的常见默认值第一层 64 个隐藏单元、第二层 32 个return_sequencesTrue表示第一层每个时间步都输出第二层只输出最后一步这个组合是序列到序列预测的标配Dropout(0.2)放在 LSTM 层之间随机丢弃 20% 的神经元连接防过拟合。损失函数用mse而不是mae的原因是mse对偏离较大的预测惩罚更重梯度更平缓输出层Dense(5)对应五列特征如果你只预测收盘价改成Dense(1)并调整标签。4.2 训练循环与日志epochs、batch_size 与学习率怎么定stock_main.py里的训练段一般长这样配合早停回调防止过拟合from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau X_train, y_train build_sequences(train_scaled, window20, horizon1) X_val, y_val build_sequences(val_scaled, window20, horizon1) early_stop EarlyStopping( monitorval_loss, patience10, restore_best_weightsTrue ) reduce_lr ReduceLROnPlateau( monitorval_loss, factor0.5, patience5, min_lr1e-5 ) history model.fit( X_train, y_train, validation_data(X_val, y_val), epochs50, batch_size32, callbacks[early_stop, reduce_lr], verbose1 )patience10表示验证损失连续 10 轮不下降就停止训练restore_best_weightsTrue会把权重回滚到验证损失最低那轮这两个参数组合是避免过拟合的后悔药。ReduceLROnPlateau在验证损失停滞 5 轮后把学习率减半最低降到1e-5比手动调学习率省事。batch_size32是默认值数据量大时调到 64 或 128 加快训练学习率在config.py里默认0.001这是 Adam 优化器的标准起步值训练日志里如果loss震荡不降优先把学习率降到0.0005而不是动网络结构。训练过程的判断标准训练loss稳步下降、验证loss先降后略有回升说明模型已经到达拟合边界早停会在回升初期自动截断。如果验证loss从一开始就不降先检查窗口数据和标签是否对齐不要急着调网络。4.3 评估指标MAE、RMSE 和 MAPE 哪个更贴近投资场景模型训练完stock_main.py会输出一组回归指标。常见做法是三个都算但解读时要分清各自脾气from sklearn.metrics import mean_absolute_error, mean_squared_error import numpy as np y_true y_test[:, 4] # 假设测试目标只取 close 列 y_pred model.predict(X_test)[:, 4] mae mean_absolute_error(y_true, y_pred) rmse np.sqrt(mean_squared_error(y_true, y_pred)) mape np.mean(np.abs((y_true - y_pred) / y_true)) * 100 print(fMAE{mae:.4f} RMSE{rmse:.4f} MAPE{mape:.2f}%)MAE是平均绝对误差单位就是价格沪深 300 指数动辄几千点MAE 显示几十点很正常RMSE放大了大误差的惩罚如果RMSE明显大于MAE说明存在少量预测偏差很大的样本常见于突发跳空行情MAPE是百分比误差但价格接近 0 时会被分母放大到离谱指数价格不会接近 0所以这里可用。三个指标一起看比单独看一个更能判断模型是「整体偏」还是「个别样本崩」。毕设报告里我建议把 loss 曲线和这三个指标都贴出来评估段用 2019 年数据做验证、测试段用 2020 年之后数据正好对应项目里train_mix-19.csv和test_mix.csv的划分逻辑。5. 避坑手册LSTM 时序预测最常见的五个翻车现场5.1 数据泄漏全量归一化和随机 shuffle 是头号敌人现象训练损失低得漂亮验证集效果也还行但拿test_mix.csv一测预测曲线几乎是一条平线完全跟不上真实波动。原因两个操作叠加造成。第一MinMaxScaler对全部数据fit_transform归一化参数里包含了测试集的最大最小值相当于模型提前知道了未来价格的量纲第二构建窗口前对整个矩阵做了random.shuffle训练样本被打乱LSTM 学到的是「跨时间片段拼接」的假规律。解决归一化严格按第 3.3 节的分段做法scaler.fit(train_raw)之后transform验证和测试。窗口构建放在归一化之后保持时间顺序不 shuffle。检查代码时重点看有没有np.random.shuffle(values)和scaler.fit_transform(values)这两行出现任何一个都要改。5.2 预测曲线看着「滞后一天」不是 bug 是 shift现象把预测价格和真实收盘价画在同一张图上预测曲线整体比真实曲线晚了一个交易日看起来像是把昨天的价格搬到了今天。原因金融时间序列接近随机游走当模型学不到有效信号时t1 时刻的最优估计就是 t 时刻的值。LSTM 在训练时发现「预测值约等于上一个收盘价」能让损失最小于是策略性地输出滞后值。这不是代码 bug是预测任务本身的信噪比决定的。解决先确认标签构建没问题——y[i]确实对应iwindow之后的日子然后不要只用拟合误差评价模型加一个方向准确率指标统计预测涨跌方向与真实涨跌方向一致的比例。如果方向准确率在 50% 附近徘徊说明模型没有可用的预测能力滞后现象无法通过调参消除。5.3 停牌导致的日期空洞用前向填充还是直接剔除现象清洗后数据从 1200 行变成 1100 行打印日期发现中间缺了好几天构建窗口时某段序列时间跨度被拉长模型看到的「20 天」实际上是 25 个自然日。原因股票停牌或指数成分调整导致交易日不连续dropna时把空洞整行删掉却没有在窗口滑动时意识到时间间隔不均。解决如果空洞不多用df.ffill()填充后保留前向填充的逻辑是「停牌日沿用前一交易日价格」如果空洞横跨长假或长期停牌填充会造成一段僵直的价格平台这类情况直接dropna删掉空洞段更干净。判断标准是空洞长度连续缺失不超过 3 个交易日可以填充超过就剔除。代码里加一行df[date].diff().dt.days.max()检查最大间隔超过 7 就报警。5.4 预测结果一直是常数模型退化与损失不下降现象训练 50 轮后loss停在某个值不动测试集预测输出几乎全是同一个数值画出来是一条水平直线。原因常见两种。一是学习率过大Adam 在初始阶段震荡后陷入局部平原梯度消失二是归一化后目标值的方差被压得太小比如close列被缩放到0.0001量级模型输出随便一个数损失都差不多梯度信号微弱。解决先把归一化后的close列打印std()如果标准差小于0.01说明缩放异常检查是不是有异常值把MinMaxScaler的min和max拉偏了。然后给模型加梯度裁剪Keras 里用Adam(clipnorm1.0)把梯度范数限制在 1.0 以内学习率降到0.0005重训。最后确认标签和特征里没有把当天的close同时放进X和y这种自我预测会让模型偷懒输出常数。5.5 复现性seed 固定、数据顺序、框架环境差异现象同一份代码、同一个config.py两次运行出来的验证损失差 20%你怀疑数据被改过。原因LSTM 的随机初始化、GPU 上 CUDA 的非确定性算子、数据加载顺序任何一环没固定都会导致结果波动。毕设答辩时被老师问「为什么两次结果不一样」答不上来很尴尬。解决在stock_main.py最开头固定随机种子import random import numpy as np import tensorflow as tf def set_seed(seed: int 42): random.seed(seed) np.random.seed(seed) tf.random.set_seed(seed) set_seed(42)PyTorch 版本额外加torch.manual_seed(seed)和torch.cuda.manual_seed_all(seed)。还要注意两个环境差异numpy 版本不同会影响sliding_window_view的内存视图行为TensorFlow 在 CPU 和 GPU 上的浮点累加顺序不同结果略有差异。项目文档里如果声明了 Python 版本尽量按文档要求建环境省掉「环境玄学」。6. 实时预测的最小推理脚本与滚动验证技巧6.1 推理脚本把最近 20 个交易日喂给模型拿到明天的价格训练完模型实时预测就是加载权重、取最近窗口、输出一个数值。stock_main.py末尾或者单独写一个推理函数常见最小实现长这样import numpy as np def predict_next_price(model, df, feature_cols, close_scaler, window20): # 取最近 window 天的原始特征注意 df 要按日期升序 recent df[feature_cols].iloc[-window:].values # 只对 close 做逆归一化其他特征沿用训练时的标准化 scaled scaler.transform(recent) x scaled.reshape(1, window, len(feature_cols)) # 模型输出预测的 close 归一化值 pred_scaled model.predict(x, verbose0)[0, 4] pred_price close_scaler.inverse_transform([[pred_scaled]])[0, 0] return pred_price next_price predict_next_price(model, df_test, feature_cols, close_scaler) print(f下一个交易日预测收盘价: {next_price:.2f})核心在于close_scaler.inverse_transform训练时把close单独用一个MinMaxScaler缩放推理时才能干净还原成价格。如果特征里close和其他列用同一个scaler缩放还原时要做 dummy 向量填充很容易写错所以我建议训练预处理阶段就把close拆出来单独缩放。predict输出的是一个(1, 5)数组取索引4对应 close 列这段逻辑要和feature_cols的列顺序保持一致。6.2 滚动验证在 test_mix.csv 上模拟一遍实盘节奏实时预测不能只看一次输出。我在验证模型时习惯在测试集上做滚动预测——每天只预测下一天第二天把真实数据加入窗口继续预测模拟真实交易节奏preds, actuals [], [] for i in range(window, len(test_df) - 1): # 每次用截止到当天的历史窗口 history test_df.iloc[:i1] pred predict_next_price(model, history, feature_cols, close_scaler) true test_df.iloc[i1][close] preds.append(pred) actuals.append(true) # 计算滚动预测的方向准确率 pred_arr np.array(preds) act_arr np.array(actuals) direction_acc np.mean(np.sign(np.diff(pred_arr)) np.sign(np.diff(act_arr))) print(f滚动预测方向准确率: {direction_acc:.2%})方向准确率比 MSE 更适合评价「能不能用」如果模型预测的涨跌方向和真实一致率超过 55%至少说明有参考价值如果只有 50% 上下那就当练习项目看效果别当真金白银的策略。从那以后我每次跑完训练都会强制走一遍这个滚动验证看看预测曲线和真实曲线的贴合度、方向准确率、以及换一段测试数据会不会崩。这套「训练完先滚动验证再谈效果」的习惯帮我避开了不少模型过拟合的假象希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑