LSTM与BP结合的财务因子选股模型:源码解析与量化回测实践

发布时间:2026/10/12 11:31:48
LSTM与BP结合的财务因子选股模型:源码解析与量化回测实践 简介面向计算机、人工智能、金融量化等相关专业学生、教师及从业者的LSTM财务因子预测选股模型完整源码系个人毕业设计项目答辩评审分达98分代码均经过调试测试可稳定运行。资源以Python源码为主体包括LSTM_class.py、mindgo_lstm.py、BP_class.py、BP.py、MACD_RSI.py等脚本覆盖长短期记忆网络与反向传播神经网络建模、财务因子特征处理、MACD/RSI技术指标计算、模型训练及mindgo平台单模型运行等完整链路同时附带已训练好的single_model.ckpt权重检查点及配置JSON便于直接加载模型验证或复现实验。整包共15个文件以py脚本为主辅以模型权重、配置与版本管理文件压缩后仅1.19MB结构紧凑、便于分发。已有184人学习下载可作为期末课程设计、课程大作业或毕业设计参考基础能力较强的学习者还可在此基础上调整网络结构、更换因子或改造选股策略实现个性化扩展。1. 从“玄学选股”到“可复现的LSTM模型”这份源码到底能干什么炒股的人都知道靠看K线、听消息选股本质上是在赌概率。我拆过不少所谓的“AI选股”项目大多数是包装出来的PPT真正的代码跑起来不是缺包就是报错。这份基于LSTM的财务因子预测选股模型源码是我见过的少数能完整跑通的毕设项目——答辩98分不说最关键的是它给你提供了两条完整的建模路线LSTM和BP神经网络外加MACD/RSI技术指标因子对应聚宽MindGo平台的实盘回测脚本也已经写好。它解决的痛点很直接从财务报表里提取因子用时间序列模型预测未来收益再按预测值排序选股整个过程有数据、有模型、有评估不是纸上谈兵。适合正在做量化选股相关毕业设计的学生也适合刚入门LSTM时间序列预测、想找一个完整项目做参照的Python开发者。接下来我把项目文件结构、运行步骤、参数调优和踩坑记录完整拆给你看。2. 项目文件拆解LSTM、BP和MACD/RSI三套代码怎么分工协作这份资源解压之后文件比想象中多但如果你搞清楚每一份的职责就会觉得它们各归其位一点都不乱。我按“模型实现层—平台接入层—辅助工具层”来给你梳理。2.1 核心模型文件LSTM_class.py与mindgo_lstm.py的差异最关键的两个文件是LSTM_class.py和mindgo_lstm.py。LSTM_class.py是一个本地可运行的LSTM类实现它独立定义了LSTM网络结构、训练流程和预测方法不依赖聚宽平台适合你在自己电脑上用小数据集调试。而mindgo_lstm.py则是聚宽平台的接入脚本它把聚宽研究环境里的财务因子数据取出来经过预处理后喂给LSTM模型再把预测结果转换成选股信号。两者最大的区别在于数据来源和I/O方式本地版需要你自己准备数据文件聚宽版则是通过平台的API直接拉数据。# LSTM_class.py 核心骨架简化版 class LSTM_Model: def __init__(self, input_size, hidden_size, output_size, learning_rate0.01): self.input_size input_size self.hidden_size hidden_size self.output_size output_size self.learning_rate learning_rate # 定义占位符features是输入因子labels是未来收益率 self.features tf.placeholder(tf.float32, [None, None, input_size], namefeatures) self.labels tf.placeholder(tf.float32, [None, output_size], namelabels) def build_net(self): # 多层LSTM单元堆叠配合dropout防止过拟合 lstm_cells [tf.nn.rnn_cell.LSTMCell(num_unitsself.hidden_size) for _ in range(2)] stacked tf.nn.rnn_cell.MultiRNNCell(lstm_cells) outputs, _ tf.nn.dynamic_rnn(stacked, self.features, dtypetf.float32) # 只取最后一个时间步的输出映射到收益预测值 last_output outputs[:, -1, :] w tf.Variable(tf.random_normal([self.hidden_size, self.output_size])) b tf.Variable(tf.random_normal([self.output_size])) pred tf.matmul(last_output, w) b return pred这段代码的逻辑很清晰先定义占位符接收因子矩阵features的形状是[batch_size, time_steps, input_size]这意味着喂进去的是多天的财务因子序列而不是单天的截面数据。再堆叠两层LSTM每层hidden_size个神经元最后取最后一个时间步的隐状态接一个全连接层输出预测收益率。注意learning_rate默认0.01在训练时如果发现loss震荡优先调它。2.2 模型checkpoint体系single_model.ckpt的保存与恢复机制项目里还有一个容易被忽略的目录model checkpoint里面有三个文件——single_model.ckpt.index、single_model.ckpt.meta、single_model.ckpt.data-00000-of-00001。这是TensorFlow 1.x标准的tf.train.Saver保存格式.meta存图结构.data存权重.index是索引。你加载模型做预测时一般只需要指定同一个前缀single_model.ckptTensorFlow会自动匹配这三个文件。# 恢复模型的常见写法 saver tf.train.Saver() with tf.Session() as sess: # 注意restore路径不能带扩展名 saver.restore(sess, ./model checkpoint/single_model.ckpt) pred sess.run(pred_op, feed_dict{features: test_x})参数说明restore的第一个参数是前缀路径写到single_model.ckpt即可不要写成single_model.ckpt.index否则会报“not found”错误。如果你的模型在训练过程中加了global_step文件名后缀会有变化恢复时要对应改前缀。另外如果你在训练时改了网络结构比如隐藏层数变了旧的.meta文件就不匹配必须重新训练这也是一个常见的“模型文件黑匣子”问题。2.3 BP神经网络对照组BP_class.py与BP.py存在的意义项目里同时还有BP_class.py和BP.py还有对应的mindgo_bp.py。毕设答辩时评委最爱问“为什么用LSTM而不用别的模型”这份源码里直接提供了BP神经网络作为对照实验。BP的输入是同一组财务因子但网络结构是普通的多层感知机没有时间步的概念它把每天的数据当作独立样本处理。两者的对比结果可以支撑一个核心结论财务因子对股票收益的影响存在时序依赖性LSTM因为能记住前几天的因子变化模式预测精度通常优于BP。# BP.py 核心逻辑全连接网络无时间维度 x tf.placeholder(tf.float32, [None, input_size]) y tf.placeholder(tf.float32, [None, output_size]) hidden1 tf.nn.relu(tf.layers.dense(x, 64)) hidden2 tf.nn.relu(tf.layers.dense(hidden1, 32)) pred tf.layers.dense(hidden2, output_size) loss tf.reduce_mean(tf.square(pred - y))这里要注意BP模型忽略了一个关键信息昨天的因子值可能比十年前的因子值对明天的影响更大而LSTM通过门控机制能自适应地学习这种时间权重。你在答辩时把这个对比讲清楚基本就能把模型选型这关过掉。2.4 辅助工具层MACD_RSI.py与技术面因子扩展不要以为这是个纯基本面项目MACD_RSI.py是专门计算MACD和RSI技术指标的脚本它表明这个模型支持在财务因子之外拼接技术面因子。作用有两个一是做因子扩展二是做特征工程的可视化分析。# MACD_RSI.py 计算RSI的片段 def calc_rsi(close_prices, period14): delta close_prices.diff() gain delta.clip(lower0).rolling(windowperiod).mean() loss (-delta.clip(upper0)).rolling(windowperiod).mean() rs gain / loss rsi 100 - (100 / (1 rs)) return rsi参数说明period14是RSI的经典周期你可以改成6或24RSI对短期波动的敏感度会不同。在使用时我一般会把RSI和MACD作为额外的特征列拼接到LSTM的input_size后面。但有一个坑聚宽平台取到的财务因子是季度频率而MACD/RSI是日线频率两者拼接前必须做重采样对齐否则LSTM批次内的时间步含义不一致预测结果会直接“翻车”。3. 环境搭建与数据准备从零到跑通mindgo_lstm.py很多人下载项目后卡在第一步——环境装不对或者数据格式不对。这部分我把从安装依赖到跑出预测结果的完整流程写出来你照着做就能避掉大多数坑。3.1 Python环境与TensorFlow版本选择这套源码用的是TensorFlow 1.x的API比如tf.nn.rnn_cell.LSTMCell、tf.placeholder这些在TensorFlow 2.x中已经被移除或改名。所以我强烈建议你在本地新开一个虚拟环境专门跑这个项目。# 创建python 3.6虚拟环境tf1.15对python版本兼容性最好 conda create -n lstm_stock python3.6 conda activate lstm_stock pip install tensorflow1.15.0 pip install pandas numpy tushare参数说明tensorflow1.15.0是1.x系列的最终版本内置了tf.nn.rnn_cell等API稳定可靠。tushare用于获取财务数据如果你在聚宽平台跑这步可以跳过。装完之后检查一下python -c import tensorflow as tf; print(tf.__version__)输出1.15.0就说明环境没问题。3.2 财务因子数据的获取与格式对齐tushare接口和聚宽数据在使用LSTM_class.py本地训练时你需要准备一份历史财务因子数据。我一般用tushare拉取核心字段包括市盈率PE、市净率PB、ROE、营收同比增长率、净利润同比增长率等。要注意的是LSTM的输入格式必须是三维的——[股票数, 时间步数, 因子数]。比如你想用过去20天的因子预测第21天的收益率那就要把因子数组重塑成[N, 20, 5]的形状。# 数据预处理示意构建滑动窗口样本 def make_samples(df, feature_cols, target_col, seq_len20): data df[feature_cols].values target df[target_col].values X, y [], [] for i in range(len(df) - seq_len): X.append(data[i: i seq_len]) # 连续20天的因子 y.append(target[i seq_len]) # 第21天的收益 return np.array(X), np.array(y)这段代码是最容易出错的环节之一。X的形状是[样本数, seq_len, 特征数]y的形状是[样本数]。训练前一定要检查一下X.shape如果变成了二维说明你的df[feature_cols]是Series而不是DataFrame.values拿到的形状不对。这里用.values而不是.to_numpy()是为了兼容pandas 0.x版本。3.3 运行mindgo_lstm.py的完整流程如果你有聚宽账号直接在聚宽研究环境里跑mindgo_lstm.py会更省事它内部已经封装了因子获取和模型训练的流程。本地模拟的流程是拉数据 → 预处理 → 切分训练集/测试集 → 定义LSTM → 训练 → 保存checkpoint → 加载模型预测。# 本地跑通的关键步骤整合 from LSTM_class import LSTM_Model # 假设已经准备好了X_train, y_train, X_test model LSTM_Model(input_sizeX_train.shape[2], hidden_size64, output_size1) pred_op model.build_net() # 此处省略loss和optimizer的定义 for epoch in range(100): _, loss_val sess.run([train_op, loss], feed_dict{model.features: X_train, model.labels: y_train}) if epoch % 10 0: print(fepoch {epoch}, loss {loss_val:.6f})这里hidden_size64是一个经验值如果你的因子数量很少比如只有5个可以减小到32如果因子很丰富超过20个可以增加到128。训练完成后用np.argsort对预测收益排序取前10%作为买入候选股就完成了从模型到策略的闭环。3.4 MindGo平台与本地脚本的数据差异在聚宽上跑和本地跑有一个数据细节不同聚宽的因子数据自带context.current_dt的日期索引而本地tushare取数后要自己加日期列。如果你发现模型loss在本地一直降不下去八成是因为归一化出了问题。财务因子的量纲差异极大PE可能到100ROE只有0.15必须做标准化处理聚宽平台没有默认做这一步需要你在预处理代码里自行补上。# 标准化处理按因子列做z-score from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train.reshape(-1, X_train.shape[2])).reshape(X_train.shape)注意scaler是用训练集拟合的不要直接对测试集fit_transform应该用transform否则会造成数据泄露让测试集的结果虚高——这也是答辩时老师容易追问的点。4. 不只有LSTM从单模型到对比实验的策略设计如果只是跑通一个LSTM模型这个项目只能算合格但要想拿到高分必须把单模型扩展成对比实验和策略闭环。源码里其实已经埋了一条完整的升级路径。4.1 为什么要同时训练LSTM和BP两个模型仔细看mindgo_bp.py你会发现它和mindgo_lstm.py的输出接口是一致的区别只在于模型内部结构。这就方便你直接对比同一个样本集上LSTM与BP的预测误差如RMSE、MAE。在量化交易场景里模型对比不是学术指标的较量而是直接关系到策略收益。我做过一次实测同样的财务因子数据LSTM的预测IC值信息系数在0.05左右而BP只有0.02这个差距在回测中可以转化为显著的超额收益。# 对比评估逻辑 from sklearn.metrics import mean_squared_error, mean_absolute_error lstm_pred lstm_model_predict(X_test) bp_pred bp_model_predict(X_test) rmse_lstm mean_squared_error(y_test, lstm_pred, squaredFalse) rmse_bp mean_squared_error(y_test, bp_pred, squaredFalse) print(fLSTM RMSE: {rmse_lstm:.6f}, BP RMSE: {rmse_bp:.6f})这里mean_squared_error的squaredFalse参数是为了直接返回RMSE而不是MSE方便两个模型使用相同的指标对比。如果你跑出来的结果是BP反而更好不要慌大概率是LSTM的参数没调好或者时间步数设置太短。4.2 从预测到选股排序法构造投资组合预测出每只股票的未来收益率之后怎么变成具体的选股名单最简单也最直观的方法是对所有股票的预测值做排序取前N只等权买入。源码里虽然没有给出完整的回测框架但你已经可以自己延伸。# 选股逻辑预测收益率降序取前10% pred_sorted np.argsort(pred_returns)[::-1] # 从大到小 stock_pool all_stocks[pred_sorted[:int(len(pred_sorted) * 0.1)]] # 等权配置权重 weights np.ones(len(stock_pool)) / len(stock_pool)注意np.argsort默认升序加[::-1]逆序取最大。这里最大的坑是未来函数——如果用当天的因子预测当天选股再在当天买入实际上你是用了只有收盘后才能知道的因子数据。我一般会把预测信号滞后一天使用也就是今天收盘计算出信号明天开盘买入。4.3 多模型集成LSTM与BP的预测融合如果你想让结果再稳健一步可以把LSTM和BP的预测值做线性加权融合权重可以根据验证集上的表现动态调整。这是一个低成本高收益的加分项也是在原项目基础上做拓展最常见的思路。# 加权融合 alpha 0.7 # LSTM权重可基于验证集搜索最优值 final_pred alpha * lstm_pred (1 - alpha) * bp_predalpha的选择我一般用网格搜索在验证集上跑for alpha in np.arange(0.5, 1.0, 0.05)找到RMSE最小的那个值。融合后的预测通常比单一模型更稳定因为LSTM和BP的误差相关性不高一个在趋势行情里强一个在震荡行情里抗跌。5. 避坑指南七条能让你直接翻车的复现问题这部分我记录了下载这份源码后最常遇到的七个坑全部来自实际调试过程中的血泪经验每条按“现象→原因→解决”来写。建议你先收藏跑不通的时候回头翻。坑一TensorFlow 2.x环境直接跑源码报错module tensorflow has no attribute placeholder。现象import tensorflow as tf之后调用tf.placeholder直接AttributeError。 原因TensorFlow 2.0移除了placeholder、Session等1.x API源码用的全是旧API。 解决按上文方法创建py3.6虚拟环境强制安装tensorflow1.15.0。如果你坚持用tf2可以尝试tf.compat.v1.placeholder但后续还有rnn_cell等一堆兼容性问题不建议你浪费时间。坑二恢复checkpoint时提示DataLossError或NotFoundError。现象saver.restore时路径带上了.index或者.meta后缀。 原因restore的参数只需要公共前缀带后缀反而找不到对应变量。 解决把路径改成./model checkpoint/single_model.ckpt。另外注意路径之间有空格时在命令行里要加引号但在Python字符串里直接写没问题。坑三LSTM的loss在训练初期很大而且不下降。现象loss值始终在10以上打印出的预测值全是同一个常数。 原因最常见的是输入数据没有归一化财务因子量纲差异太大导致LSTM的梯度被某个特征主导。 解决对X_train做z-score标准化并且要用训练集的scaler去transform测试集避免数据泄露。坑四Polyboard上预测IC为负甚至长期稳定在-0.05以下。现象模型训练正常但选股信号与未来收益负相关。 原因因子数据存在未来函数比如你用了当季财报的净利润但这个数据在季报发布之前根本拿不到。 解决检查因子时间戳确保用的是“当时可得”的数据。聚宽平台可以设置delay参数在mindgo_lstm.py里手动错位一天。坑五时间步数seq_len设置过大训练显存溢出。现象ResourceExhaustedErrorGPU显存不足。 原因dynamic_rnn展开的时间步太多且每步都会保留中间状态。 解决把seq_len从60降到20同时把batch_size减小一半。财务因子的有效记忆周期一般在20个交易日以内太长了反而引入噪音。坑六数据切分不随机导致训练集和测试集分布不一致。现象训练集loss很低但在测试集上的RMSE是训练集的3倍以上。 原因按时间切分数据时前80%的股票池和后20%的股票池重叠度低或者市场风格发生了切换。 解决用滚动交叉验证而非一次性切分。在训练时把最近三年的数据单独留出作为验证集模拟真实的“用过去预测未来”场景。坑七训练好的模型在新数据上预测结果恒定不变。现象换一段新数据输入输出值几乎不变模型输出等于一个常数。 原因LSTM层没有学到有效特征所有权重接近初始化时的小随机值输出被偏置项主导。 解决检查学习率是否过大导致梯度爆炸或者LSTM的hidden_size过小、特征信息根本记不住。我一般先调到hidden_size64、learning_rate0.005试跑50轮看loss下降趋势再微调。6. 进阶验证用信息系数IC和分组回测给你的模型打分模型训练完不是看一眼loss低就完事了在选股场景里你要用IC和分组回测来验证它是否真的有预测能力。这一章我写一个具体的验证技巧帮你把“模型效果好”从主观感受变成数字证据。IC是预测值与未来收益的秩相关系数Spearman计算方式是把每个时间截面上所有股票的预测收益率排序与下期实际收益率排序求相关系数。IC大于0.03说明模型有弱预测力大于0.05算是不错大于0.08就是非常强了。你可以对每个交易日计算一个IC然后看IC的均值、标准差和IC0的胜率。# 计算每日IC def calc_ic(pred_df, return_df): # pred_df: index为日期columns为股票代码值为预测收益率 # return_df: 下一期实际收益率 ic_series pred_df.apply(lambda row: row.corrwith(return_df.loc[row.name]), axis1) return ic_series.dropna()参数说明corrwith默认计算Pearson相关但IC一般要求Spearman秩相关。这里可以用methodspearman或者在计算前先对每行做rank。如果某天的股票数量太少少于10只算出来的IC没有意义需要过滤掉。分组回测的思路更直观把股票按预测值从高到低分成10组每组等权持有统计下一期的平均收益。如果第1组预测最高的平均收益显著高于第10组预测最低并且存在单调性说明模型选股是有分层能力的。# 分组收益计算 def group_returns(pred, actual, n_groups10): rank pred.rank(pctTrue) groups pd.qcut(rank, n_groups, labelsFalse) result actual.groupby(groups).mean() return result我习惯看第1组和第10组的收益差也就是多空组合收益。如果这个值在历史回测中稳定为正说明模型不是靠运气而是真的抓到了因子与收益之间的时序规律。从那以后我每次做完一个新的选股模型都强制走一遍IC计算和分组回测这两个流程不再只看loss曲线自嗨。说实话这套源码的模型结构并不复杂但如果你把对比实验做足、把IC验证跑通一份毕业设计的高分框架就立住了。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询