
简介基于LSTM的财务因子预测选股模型Python源码是一份答辩评审达98分的高分毕业设计项目适合计算机、人工智能、量化金融等相关专业学生及从业者用于毕设、课程设计或进阶学习。压缩包共15个文件整体约1.19MB其中7个Python脚本构成项目主体覆盖数据预处理、LSTM/BP模型训练、MACD_RSI指标计算与选股预测流程并附带训练完成的模型检查点文件.meta、.index、.data-00000-of-00001可直接加载推理另有Git相关配置与JSON设置便于环境复现。代码已经过调试可稳定运行既能帮助入门者快速理解时序预测选股的完整实现也为基础较好的读者提供了清晰的扩展接口。目前已有184人学习下载整体结构清晰可作财务因子量化选股从研究到落地的参考样例。1. LSTM财务因子预测选股模型一份能跑通的量化毕设而不是一个“选股黑匣子”拿到这份“基于LSTM的财务因子预测选股模型”源码时我的第一反应是它和我平时见到的大多数课程设计不一样压缩包里除了 LSTM 模型还有一份 BP 神经网络做对照模型 checkpoint 权重文件也已经训练好连 MACD、RSI 技术指标代码都配齐了。也就是说你不是拿到一个只能“看着跑”的 demo而是一整套从数据到模型再到选股信号的闭环流程。这个项目适合两类人一类是拿它做毕业设计或课程大作业需要快速出一个有 LSTM 亮点、有对比实验、能演示的完整系统另一类是想入门量化选股但不知道从哪下手的 Python 开发者——你能从里面看到财务因子怎么组织、LSTM 训练脚本怎么写、训练好的模型怎么拿去做预测选股。但先说清楚这个项目的价值在流程完整和工程骨架不在“预测百分百赚钱”。理解这一点你后面踩的坑会少一半。2. 为什么用 LSTM 做财务因子预测时序结构、门控机制与适用边界2.1 LSTM 与 BP 在因子预测上的本质区别这份源码里同时给出了LSTM_class.py和BP_class.py这个对照设计本身就很有教学价值。传统 BP 神经网络做因子预测本质上是在做一张静态映射表输入一批财务指标输出一个预测值样本之间彼此独立。它没有“记忆”能力上一个季度的财报好不好、股价对财报的滞后反应规律是什么这些信息在 BP 里是丢失的。LSTM 不同它在网络结构里加了三个门——遗忘门、输入门、输出门让信息可以沿着时间步有选择地保留和丢弃。放到财务因子预测的场景里一家公司连续三个季度的 ROE 下滑这个“趋势”本身就是强信号而单看某一个季度的 ROE 绝对值看不出问题。LSTM 能把过去若干期的因子数据作为一个序列输入捕捉这种跨期的趋势和依赖。那为什么不是所有选股模型都用 LSTM因为它的训练成本更高、对数据量和数据质量更敏感。BP 可能用几百条样本就能拟合个大概LSTM 需要对每个样本构造一个“时间窗口”样本量直接缩水一个数量级。这也是我在拿到这份代码时最关心的问题——它到底在什么频率的数据上训练。2.2 财务因子怎么选、怎么构造训练样本财务因子预测选股第一步不是调参是把“预测目标”定义清楚。常见的做法分两种一种是预测未来一段时间的收益率另一种是预测某个关键财务因子比如未来一期的 ROE 或净利润增长率的取值。前者的好处是直接跟选股目标对齐后者的可解释性更强。因子维度一般会从这几类里挑估值类PE、PB、PS、盈利类ROE、毛利率、净利率、成长类营收同比增长率、净利润同比增长率、运营类资产负债率、存货周转率、现金流比率。你的模型里有多少个因子input_dim就得设多大。我在拆这份代码时看到它把 LSTM 模型类拆成了独立文件输入参数支持自定义说明作者当时是把因子选择做成配置项的——这对毕设答辩很实用你可以直接告诉评委“因子维度可配置换一组因子就能扩展实验”。训练样本的构造方式是这类项目最容易翻车的地方。假设你有 5 年的季度财务数据每个季度末算一批因子值那么一个样本就是“连续 T 期的因子序列 → 下一期的收益标签”。如果 T 取 4意味着用过去 4 个季度一年的财务因子预测下个季度初买入并持有 N 天的收益。这里有个财务数据特有的坑财报数据是滞后披露的Q1 的财报往往 4 月底才公布你在 4 月初用 Q1 数据做训练就是提前拿到了未来数据——量化里管这个叫“未来函数”。后面避坑章节会专门展开。2.3 模型结构与关键参数设计从single_model.ckpt这个权重文件和后缀ckpt.index、ckpt.meta能判断出这份项目的 LSTM 模型是用 TensorFlow 训练的单模型结构。一个标准的 LSTM 选股模型长这样输入层维度[batch_size, time_step, input_dim]其中time_step是回溯的历史期数input_dim是因子个数。中间是 12 层 LSTM 或堆叠 LSTM 单元后面接一个 Dropout 层防过拟合再经过全连接层压缩维度最后输出一个标量预测值。损失函数用均方误差MSE优化器一般选 Adam 或 RMSProp学习率从 0.001 起步。单层 LSTM 和双层 LSTM 在这个场景的区别要取舍一下财务数据本身是低频的季度或月度信息量有限单层 LSTM 往往就够用双层 LSTM 虽然拟合能力更强但在小样本数据上更容易过拟合。对于毕业设计来说不妨做一组对比实验用同一份数据分别跑单层和双层 LSTM把 loss 曲线和回测结果放进论文附录这个对比比调参本身要有说服力得多。3. 源码包拆解六个 py 文件各自管哪一段流程3.1 文件结构与模块职责打开压缩包去掉.DS_Store、.vscode之类的环境和系统文件核心代码是这几个文件职责运行环境mindgo_lstm.py掘金量化平台上的 LSTM 策略主流程数据获取→训练→预测→选股信号输出MindGo 策略运行环境mindgo_LSTM_single_model.py单模型版本的 LSTM 选股脚本逻辑更精简MindGo 策略运行环境mindgo_bp.pyBP 神经网络选股脚本用于与 LSTM 做对照实验MindGo 策略运行环境LSTM_class.pyLSTM 模型类定义独立封装便于复用任意 Python 环境BP_class.py/BP.pyBP 模型类定义与训练脚本任意 Python 环境MACD_RSI.py基于 MACD、RSI 技术指标的选股脚本任意 Python 环境这个文件结构的设计思路很清楚mindgo_前缀的文件是直接跑在量化平台上的_class.py是模型层封装MACD_RSI.py是传统技术指标的对照组。你下载到的是“三层分离”的工程结构不是一坨揉在一起的脚本。这对复现和二次开发都是友好的——你可以不动策略文件只改模型类就能换一种网络结构重新跑。3.2 mindgo_lstm.py 的主流程逻辑mindgo_lstm.py是最值得先读的文件。它的整体流程可以概括成五个阶段阶段一用平台的财务数据接口获取股票池的因子数据阶段二做数据清洗和归一化阶段三构造滑动窗口训练集阶段四初始化LSTM_class.py里的模型并训练阶段五把训练好的模型对新一期因子数据做预测按预测值排序生成选股列表。# mindgo_lstm.py 核心流程示意 from LSTM_class import LSTMModel import numpy as np # 1. 获取财务因子数据以ROE和营收增长率为例 # MindGo平台中get_fundamentals是常用的财务数据接口 fundamentals get_fundamentals( tablefinancial_indicator, columnsroe, yoyprofits, or_yoy, date2020-03-31 ) # 2. 因子预处理填充缺失值、归一化 data fundamentals.fillna(0.0).values from sklearn.preprocessing import StandardScaler scaler StandardScaler() data_scaled scaler.fit_transform(data) # 3. 构造滑动窗口样本前time_step期预测下一期 time_step 4 # 用过去4期预测下一期 X, y [], [] for i in range(len(data_scaled) - time_step): X.append(data_scaled[i : i time_step]) y.append(data_scaled[i time_step, 0]) # 预测目标取第一个因子 X np.array(X).reshape(-1, time_step, data_scaled.shape[1]) y np.array(y)这段是这类项目通用的数据管线写法。get_fundamentals拿的是横截面数据——某个时点全部股票在某一个报告期的财务指标fillna处理的是部分股票缺失因子值的情况量化里通常不会直接删行因为不同股票的报表齐全程度不一样删了会破坏面板数据的完整性StandardScaler把不同量纲的因子PE 可能到几百ROE 只有个位数压缩到同一尺度。有个细节值得留意训练样本构造时我们用了reshape(-1, time_step, data_scaled.shape[1])这里time_step放在第二维对应 LSTM 输入要求的形状[batch, time, feature]顺序别搞反这是新手最常见的维度报错来源。3.3 LSTM_class.py 的类封装LSTM_class.py把模型定义、训练、预测封装成了一个类这是典型的工程化写法。好处是mindgo_lstm.py和mindgo_LSTM_single_model.py可以共用同一个模型类只用初始化参数做区分。# LSTM_class.py 核心结构 import tensorflow as tf class LSTMModel: def __init__(self, input_dim, time_step, lstm_units64, num_layers1, learning_rate0.001): self.input_dim input_dim self.time_step time_step self.lstm_units lstm_units self.num_layers num_layers self.learning_rate learning_rate self._build_model() def _build_model(self): self.model tf.keras.Sequential([ tf.keras.layers.LSTM( self.lstm_units, return_sequences(self.num_layers 1), input_shape(self.time_step, self.input_dim) ), tf.keras.layers.Dropout(0.2), tf.keras.layers.Dense(1) ]) self.model.compile( optimizertf.keras.optimizers.Adam(learning_rateself.learning_rate), lossmse ) def train(self, X_train, y_train, epochs50, batch_size32): # 返回训练过程中的loss历史便于画图 history self.model.fit( X_train, y_train, epochsepochs, batch_sizebatch_size, validation_split0.2, verbose0 ) return historylstm_units64是 LSTM 层的隐藏状态维度不是因子维度这一点要跟input_dim区分。Dropout(0.2)是训练时随机丢弃 20% 的神经元连接防止模型死记训练样本。validation_split0.2表示从训练集尾部切 20% 做验证用来观察是否过拟合——如果训练 loss 持续下降但验证 loss 开始回升就是过拟合的信号。对照组的BP_class.py结构类似只是把 LSTM 层换成了Dense层。拿 BP 做对照的核心意义在于回答一个问题把同样的因子数据喂给同样规模的普通神经网络效果比 LSTM 差多少如果答辩时评委问“为什么不用随机森林、不用 XGBoost”这就是你的对照组论据。4. 从配置环境到跑通训练一份可复现的完整路径4.1 环境准备TensorFlow 版本、Python 环境和导入顺序项目用到的是 TensorFlow 框架模型权重文件是ckpt后缀。这里要特别留个心眼ckpt.index和ckpt.meta是 TensorFlow 1.x 中 Saver 保存的格式风格如果你是刚装了 TensorFlow 2.x 就跑这份代码很可能在恢复模型时报错或行为不一致。我建议先确认你本地的 TF 版本python -c import tensorflow as tf; print(tf.__version__)如果是 2.x 环境代码里tf.Session()这类写法会直接报错。常见做法是优先在 1.x 兼容模式下跑通或者用tf.compat.v1替换旧 API。把tf.Session()全局替换成tf.compat.v1.Session()这类改动在文件不多的情况下十分钟能搞定。mindgo_前缀的脚本依赖掘金量化终端的环境本地直接跑会卡在平台接口调用上。我的建议是分两步先在本地把LSTM_class.py和BP_class.py用随机的因子数据跑通验证模型层没问题再接 MindGo 环境跑完整流程。不要一上来就指望平台环境一步到位。4.2 数据预处理考核点对齐、去极值与中性化比训练更重要的是数据预处理的三板斧去极值、标准化、中性化。去极值常用分位数缩尾MAD 法把偏离均值过多倍标准差的数据拉到边界防止个别极端值主导 loss标准化就是把不同量纲压到均值 0、方差 1中性化一般用的是一元线性回归剔除市值、行业等对因子的干扰。这份代码里在预处理部分做了标准化你要做二次开发时可以按下面的逻辑补上去极值和中值化# 因子预处理MAD去极值 标准化 from scipy.stats import median_abs_deviation def mad_winsorize(factor, n5): MAD绝对中位差去极值n是缩尾倍数 median np.median(factor) mad median_abs_deviation(factor) upper median n * mad lower median - n * mad return np.clip(factor, lower, upper)n取 5 是行业常见配置表示把超出中位数 5 倍绝对中位差的样本拉回边界。这个函数可以在训练前对每一个因子列调用一遍。注意先做去极值再做标准化顺序反了的话极值会被标准化放大反而更影响训练。4.3 训练与模型恢复加载已有权重还是从头训练项目里已经带了训练好的权重文件single_model.ckpt你可以选择直接加载它做预测也可以重新训练。如果是毕设演示我建议两条路都走先加载现有权重跑通演示流程然后清空权重重新训练把训练曲线放进论文。加载权重的方式参考下面的写法from LSTM_class import LSTMModel # 定义与训练时一致的模型结构 model LSTMModel( input_dim8, # 因子个数按你的数据维度调整 time_step4, # 与训练时保持一致 lstm_units64, num_layers1 ) # 加载已有checkpoint权重 model.model.load_weights(single_model.ckpt) # 用最新一期因子数据做预测 latest_factors scaler.transform(new_fundamentals) # 同一套scaler别重新fit X_pred latest_factors.reshape(1, 4, 8) predicted model.model.predict(X_pred)注意load_weights时要求你构建的模型结构跟原模型结构完全一致差别在input_dim或time_step上都会报形状不匹配。另外如果加载时报unexpected key in checkpoint之类的错大概率是保存时带上优化器状态而你加载时没有重建优化器。这种小问题常见但容易卡人可以直接把报错信息复制到搜索引擎找同款。4.4 数据流检查清单在本地运行这份模型前建议先按下面这份清单过一遍数据流很多问题在这步就能排查掉数据流节点预期输出形状常见问题get_fundamentals返回[股票数, 因子数]列名与表格字段不一致预处理后数据无缺失值、无量纲差异极值未处理导致 loss 异常滑动窗口构造X: [样本数, time_step, 因子数]维度顺序写成[样本数, 因子数, time_step]模型输入层input_shape(time_step, 因子数)与 X 的第二、三维不对齐预测输出[样本数, 1]输出维度大于 1检查最后一个 Dense 层5. 避坑指南训练 LSTM 选股模型最常见的五个坑5.1 checkpoint 文件恢复失败TensorFlow 版本不兼容现象按教程load_weights(single_model.ckpt)恢复权重报Failed to load the checkpoint或AttributeError: CheckpointReader has no attribute get_tensor。原因项目的模型权重是用特定版本的 TensorFlow 保存的ckpt.meta和ckpt.index的组合形态是 TensorFlow 1.x Saver 风格。本地环境如果是最新的 TensorFlow 2.xAPI 变了恢复机制不兼容。解决先pip list | grep tensorflow看版本如果是 2.x尝试在代码开头加一行tf.compat.v1.disable_eager_execution()并把tf.Session换成tf.compat.v1.Session如果你确实需要用 2.x 的load_weights那就要重新训练一次用新格式保存权重。不要强行混用保存格式这是浪费一个小时的最快方式。5.2 财务数据滞后导致的“未来函数”现象训练时 loss 很低回测时选出的股票收益也很可观但拿到实盘模拟盘跑完全不是那么回事。原因典型的未来函数问题。财务因子数据本身有披露滞后A 股上市公司一季报要在 4 月 30 日前披露你在 4 月初用一季度因子数据就是在用未来信息做当下决策。训练集里如果直接把当期因子对齐当期收益模型学到的“规律”里就混入了未来信息。解决构造训练样本时把因子数据统一往后推一期对齐。假设你有 2019Q1 的因子数据对应收益标签用 2019Q2 之后的收益中间至少留一个完整季度的披露缓冲期。我在做这类项目时习惯在因子表里加一列announce_date而不是只用报告期用来做精确的披露日期对齐。这份源码里没有专门处理披露日期你自己二次开发时要补上。5.3 归一化泄漏scaler 不能在全样本上 fit现象训练 loss 正常验证 loss 也正常但预测阶段结果一塌糊涂预测值全部被压在一个很小的范围内。原因归一化的 fit 过程用到了全部数据包括未来数据的均值和方差。这等于把未来的信息泄露给了模型。特别是在按时间序列构造训练集的情况下用全样本的均值和方差做归一化在用前半段数据训练、后半段数据测试时测试数据的信息已经提前进入了参数。解决用训练集的统计量来做归一化代码上的体现是关键是“先分割再 fit再 transform”或者至少是在时间序列场景下用滚动的方式计算归一化参数。修改也不复杂# 先按时间顺序分割 split_idx int(len(data) * 0.8) train_data data[:split_idx] test_data data[split_idx:] # 只用训练集fit scaler scaler StandardScaler() scaler.fit(train_data) # train和test都transform train_scaled scaler.transform(train_data) test_scaled scaler.transform(test_data)5.4 样本量太小导致过拟合现象训练集 loss 一路降到接近 0验证集 loss 从某个 epoch 开始反弹模型对训练集记忆得好对没见过的数据没泛化能力。原因财务因子数据是季频或月频的一年最多 4 条季度数据、12 条月度数据股票池假设 300 只股票一年下来也才千把条样本再乘以历史年份总量不大。LSTM 又是参数密集型模型一个小 LSTM 层就有几千个参数样本量远小于参数量的情况非常普遍。解决可以从三个方向解决。扩充样本把股票池放大到 800 只以上或者把因子数据从季频降成月频能摊薄很多正则化把Dropout从默认 0.2 提到 0.4 或 0.5另外就是早期停止——监控验证 loss连续多个 epoch 没有变好就提前终止训练。5.5 MindGo 平台和本地环境跑出来结果不一致现象同一份代码在本地能跑通放到 MindGo 策略运行环境上报错或者结果对不上。原因MindGo 策略运行在它自己的沙箱环境里Python 版本、第三方库版本、数据接口返回格式都可能与你本地环境有差异。最典型的是get_fundamentals返回的列名和你本地模拟数据里用的不一样。解决拿到代码后先在本地用假数据把模型层跑通。再把假的因子数据替换成 MindGo 返回的数据逐段打印输出形状和列名跟模型输入的期望做对比。不要一上来就整体跑分阶段验证数据流最后才拼起来。平台的环境变量也可以在配置里指定用的 Python 路径确认用的不是系统默认环境。6. 把预测结果变成选股信号验证方法与两个进阶改法6.1 选股信号的落地流程与验证方法模型训练好之后真正的难点是把预测值转化成可验证的选股信号。先定义一个“打分—排序—分仓位”的流程模型对全市场股票做收益率预测预测值排名前 N 的进候选池按分数线性映射到仓位权重。为了防止模型预测值集中在很小的区间很多回归模型的通病可以用一个百分位排名替代原始预测值这样选股信号对极端预测值不那么敏感from scipy.stats import rankdata # pred是模型对所有股票的预测收益率 pred model.model.predict(X_all).flatten() # 把预测值转成0-100的百分位排名分 scores rankdata(pred) / len(pred) * 100 # 取得分最高的20只股票等权买入 top_n 20 selected np.argsort(scores)[-top_n:]验证这份代码到底行不行不要只看训练 loss。量化里的标准做法是三层验证第一层看 IC信息系数计算预测值和实际收益率的 Spearman 相关系数绝对值大于 0.03 算有正向预测力第二层做分组回测按分数分成五组看最高组是否稳定跑赢最低组第三层做样本外测试用前 80% 时间训练、后 20% 时间做预测看曲线是否还能保持正收益。这三层做完如果结果都能过你再谈实盘模拟。6.2 进阶改法一LSTM 加 BP 做集成预测项目里同时提供 LSTM 和 BP 模型你可以直接把两者组合成集成模型。两个模型分别预测然后加权平均。这个做法在量化比赛中出现频率很高因为单模型容易过拟合集成后偏差和方差都能摊薄from LSTM_class import LSTMModel from BP_class import BPModel lstm_pred lstm_model.model.predict(X_all).flatten() bp_pred bp_model.model.predict(X_all).flatten() # 归一化到同一量纲再加权 from sklearn.preprocessing import MinMaxScaler scaler_lstm MinMaxScaler() scaler_bp MinMaxScaler() lstm_scaled scaler_lstm.fit_transform(lstm_pred.reshape(-1, 1)).flatten() bp_scaled scaler_bp.fit_transform(bp_pred.reshape(-1, 1)).flatten() # 加权融合权重可以按验证集IC来定 final_score 0.6 * lstm_scaled 0.4 * bp_scaled收益率预测的绝对值本身误差很大但排序信息是有价值的所以集成时用MinMaxScaler把两组预测压到可比区间再按各自的验证集 IC 或准确率来决定0.6和0.4的权重这比拍脑袋定权重更有说服力。6.3 进阶改法二把预测目标从收益率改成超额收益如果你的回测结果显示模型选的股票和沪深 300 指数走势差不多那不是模型没用是预测目标选错了。用绝对收益做标签模型学到的是全市场的系统性上涨选股信号区分度不高。把标签改成个股收益率减去同期指数收益率模型聚焦在“相对跑赢”的分化上这样选出来的股票才是真正有 alpha 的那部分。改动就是标签替换为stock_return df[close].pct_change(periodshold_days).shift(-hold_days) benchmark_return df_index[close].pct_change(periodshold_days).shift(-hold_days) # 超额收益作为新标签 label stock_return - benchmark_return整个项目拆下来最值钱的不是那一层 LSTM 网络本身而是这个把财务数据变成训练样本、把训练结果变成选股信号、把预测效果放在回测框架里验证的完整骨架。这份代码能让你在一个下午内跑通整条链路也能为论文提供完整的 LSTM 与 BP 对比实验。把那几个坑提前排掉训练一个属于自己的 LSTM 选股模型并不需要太多代码功底但需要足够的耐心把数据流捋顺。从那以后我每拿到一个量化相关项目都强制自己先画完“数据→标签→对齐→归一化”这条链路再碰网络结构而不是急着把模型跑起来。这个习惯帮我省下的调试时间远比训练模型花的时间多。希望帮到你。本文还有配套的精品资源点击获取