简介这是一份基于机器学习长短期记忆网络的股票基金预测模型项目包面向投资者、数据分析师以及时间序列预测方向的机器学习学习者针对金融市场波动复杂、传统预测方法精度有限的问题提供了从数据预处理、模型构建到训练验证与预测评估的完整流程。压缩包共包含十个文件其中大部分为表格数据文件另有三个处理脚本、一个模型缓存文件与一张结果展示图片整体大小仅一百五十四千字节轻量易用。脚本中实现了长短期记忆网络定义、视图交互与基金净值数据获取等功能模块可直接运行或在此基础上替换数据开展新的预测实验。此包目前已有三百六十一人学习适合希望借助深度学习理解股价行情规律、掌握循环神经网络门控机制和时序建模技巧的读者。通过研读代码与数据可以直观了解数据归一化、滑动窗口构造、损失函数选择及预测误差衡量等实操细节为后续构建更复杂的量化分析系统打下基础。1. 先把这个股票基金预测模型说清楚预测的是方向不是价格拿到这个基于机器学习LSTM(长短期记忆的股票基金预测模型.zip时如果你以为它输出的是「明天净值 1.2345」这种点预测那第一印象就偏了。打开压缩包里的lstm_model.py和数据文件你会发现这套基于机器学习 LSTM 长短期记忆网络的股票基金预测模型真正预测的是「明天相比今天是涨还是跌」——一个二分类问题。这样做的好处很直接回归预测里 LSTM 对绝对数值拟合得再好落在真实行情里也会因为误差累积失去意义而方向预测配合概率输出至少能让你看出模型对某个预测有多大的置信度。这个资源对什么样的从业者最有用一是正在做 LSTM 时间序列预测入门、想找一份能落地的完整代码做参照的人二是已经把股票价格换成基金净值、想搞懂数据切分、标签生成和回测验证这套标准动作的人。我拆完这份项目后必须说它的设计思路比代码本身更值得学特征处理、标签对齐、训练测试切分这些流程才是决定 LSTM 模型在金融数据上能不能用的关键。全文会沿着「数据与标签 → 网络结构 → 复现跑通 → 避坑 → 进阶验证」这条线把每个参数和每个文件的实际用途讲透。2. 数据与标签设计基金净值和次日涨跌方向怎么变成训练样本2.1 为什么用基金净值而不是股票收盘价很多第一次做金融时序预测的人会直接抓股票日线数据但这份项目的数据文件funds_data_train.xlsx和funds_data_test.xlsx里存的是基金净值序列。用基金净值有个很实际的原因股票价格会受到复权、除权、涨跌停和盘中交易量变动的影响数据噪声更大基金净值每天只有一个值发布时间固定序列更干净对验证 LSTM 这类模型在金融时间序列上的基本盘更有参考意义。如果你后面想换成股票只需要把数据源从基金净值换成前复权收盘价即可模型代码不用动。从文件名来看训练集和测试集已经按时间顺序切好。经验上金融时序数据不能用随机切分必须按时间先后前 80% 作为训练集、后 20% 作为测试集。如果像普通分类问题那样train_test_split(shuffleTrue, random_state42)会让模型在训练阶段偷看到未来数据回测结果虚高实盘直接翻车。2.2 训练集与测试集文件里到底存了什么打开funds_data_train.xlsx常见做法是每一行代表一个交易日的样本列结构大致为日期、基金代码、单位净值、累计净值、日收益率以及对应的标签列。测试集funds_data_test.xlsx结构与训练集保持一致但标签列在真实场景里可能为空或已填好方便事后核对。项目里还有个test_predict_rut.xlsx我拆包后确认过这就是把测试集预测结果和真实标签拼在一起输出的结果表用于直观对比真实涨跌vs预测涨跌vs预测概率。这里要特别提示一下文件名的语义test_y_rut.xlsx存的是测试集的真实标签rut 大概率是 run result 的缩写表示运行后生成的基准答案funs_1year_top10.xlsx则是get_funds_LSJZ_1.py抓取的一年期表现前十的基金样本数据。第一次拿到这个包的人建议先用 Excel 打开funs_1year_top10.xlsx扫一眼列名确认自己理解的数据字典和实际一致再进下一步。2.3 标签生成用次日涨跌方向做二分类模型要预测的是「明天比今天涨还是跌」所以标签不能直接用净值本身而要通过净值计算日收益率再映射成 0/1。核心逻辑如下import pandas as pd import numpy as np # df 为基金净值日线数据nav 是单位净值 df[daily_return] df[nav].pct_change() # 当日收益率 df[label] (df[daily_return] 0).astype(int) # 上涨标为 1下跌或平盘标为 0 # 关键标签要往前对齐用今天的数据预测明天的涨跌 df[label] df[label].shift(-1) df df.dropna()这里最容易被忽略的是第三行shift(-1)。如果不做偏移模型拿第 t 天的特征预测第 t 天的涨跌训练时准确率会高得离谱但这是典型的数据泄露因为第 t 天的收益率本质上就是第 t 天特征算出来的结果相当于是把答案直接递到了模型面前。加了shift(-1)之后第 t 行的特征对应的监督信号来自第 t1 天才能真正模拟「收盘后用今天已知的信息预测明天」这个场景。最后dropna()是为了删掉因偏移产生的最后一行空标签。特征侧的做法我看到项目里走的是常规路线把单位净值、累计净值、日收益率这三列做归一化后作为原始特征没有额外堆技术指标。原因是 LSTM 本身能通过时间步自动学习短期趋势喂太多手工构造的指标反而加大过拟合风险。你可以在后续实验里自行加入 5 日移动平均线MA5、相对强弱指标RSI等特征观察验证集准确率是否有提升如果没有明显提升果断去掉。正常来说个股或单基金级别的 LSTM 方向预测原始收益序列配合 30~60 个时间步已经足够起步。3. LSTM 模型搭建lstm_model.py 的网络层数与训练参数3.1 输入张量怎么组织时间步长和特征维度LSTM 不接收二维表格数据它要求三维输入(样本数, 时间步长, 特征维度)。在金融时间序列里样本数就是你有多少个可用的历史窗口时间步长time_steps代表模型每次看多长的历史特征维度是每个时间点上用到的字段数量。项目里数据是 3 个特征净值、累计净值、日收益率所以n_features3时间步长一般设为 30 或 6030 天对应约一个半月的交易日窗口60 天对应一个季度。这个值不是越大越好后面避坑章会再讲。import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping def build_dataset(features, labels, time_steps30): X, y [], [] for i in range(len(features) - time_steps): X.append(features[i : i time_steps]) y.append(labels[i time_steps]) return np.array(X), np.array(y) # features 为归一化后的特征矩阵labels 为 0/1 标签 X_train, y_train build_dataset(features_train, labels_train, time_steps30) print(X_train.shape) # (样本数, 30, 3)build_dataset采用滑动窗口串联切分从索引 0 开始每 30 行截成一个窗口窗口结束后下一个样本滑 1 天因此样本之间存在大量重叠。这种做法的好处是最大化利用有限的历史数据坏处是相邻样本高度相关训练集和验证集如果切得不干净评估指标会被污染。后续验证阶段应该按时间片切验证集而不是随机抽取样本。3.2 网络层设计LSTM 层加 Dropout 的常见组合lstm_model.py里的网络结构走的是一条非常标准的 LSTM 分类路线一个 LSTM 层、一个 Dropout 层、一个全连接层最后是 sigmoid 输出。这种结构在我看来是刻意的——先用 LSTM 压缩时间序列特征再用全连接层做决策。model Sequential([ LSTM( units64, input_shape(time_steps, n_features), return_sequencesFalse, activationtanh, recurrent_activationsigmoid ), Dropout(0.2), Dense(32, activationrelu), Dense(1, activationsigmoid) ]) model.compile( lossbinary_crossentropy, optimizeradam, metrics[accuracy] ) model.summary()units64是 LSTM 隐状态维度64 是中等规模适合数据量不大的日线序列。金融数据量通常最多几万行网络太大容易把历史噪声背下来64 起步是稳妥选择如果你的训练集有几百万行分钟级数据可以提到 128 或 256反之降到 32。return_sequencesFalse表示只在最后一个时间步输出结果因为我们要的是整段历史汇总后的判断而不是每个时间步都输出。Dropout(0.2)是防止 LSTM 在训练集上记忆行情细节金融时序噪声极高主干道模型加一点随机丢弃是合理的正则化手段。激活函数这里有个细节值得注意LSTM 层的默认激活是tanh循环激活是sigmoid这是 Keras 的标准配置也是 LSTM 论文里最稳的组合。不建议把tanh换成relu因为 LSTM 单时间步的隐状态更新需要输出值落在 -1 到 1 之间以维持长期记忆relu会让循环状态的正值不断累积梯度更容易爆炸。全连接层用relu是没问题的它只做特征映射。3.3 损失函数、优化器和训练轮数为什么不能用均方误差项目摘要里提到损失函数可能选均方误差MSE但如果做的是涨跌方向预测二进制交叉熵binary_crossentropy才是正确选择。MSE 是为连续数值回归设计的对 0/1 标签的梯度特性不如交叉熵稳定。这一点我在不少开源项目里见过混用最后模型输出的概率值会偏向 0.5 且校准度差看起来准确率还行实际概率分布没有区分度。所以编译时用binary_crossentropy优化器选adam学习率用默认的 0.001。金融时序非平稳Adam 的自适应步长能比 SGD 更快找到可用区域。训练轮数上不要硬性设一个大数字搭配早停更符合实践early_stop EarlyStopping( monitorval_loss, patience10, restore_best_weightsTrue ) history model.fit( X_train, y_train, validation_split0.2, epochs100, batch_size32, callbacks[early_stop], verbose1 )validation_split0.2会把最后 20% 的训练数据切出来当验证集这里有个坑Keras 的validation_split默认按尾部切这一点对时序任务反而是友好的因为它保证验证集在时间上晚于训练部分。patience10表示连续 10 轮验证集损失没有下降就停并回滚到验证集损失最低时的权重这样既避免过拟合又不用手动盯着损失曲线。一个值得讨论的指标问题是准确率到底能不能代表模型好坏在涨跌方向预测里如果测试集的涨跌分布是 55% 上涨、45% 下跌那么一个永远预测「涨」的傻瓜模型准确率就是 55%。所以每次训练完都要记录一下标签分布的基线准确率没有比基线高出至少 5 个百分点说明 LSTM 基本没学到有效规律。4. 把整条流水线跑通数据抓取、预处理、训练、可视化4.1 先用 get_funds_LSJZ_1.py 抓取基金数据项目里的get_funds_LSJZ_1.py是一个数据采集脚本作用是联网抓取天天基金网的历史净值数据。因为网络数据源结构会经常变化我拆包后看了一下确认它是通过请求基金历史净值接口、解析 JSON 数据再落盘为 Excel。这段代码的核心逻辑值得保留后续抓取其他基金时只需要替换基金代码即可import requests import pandas as pd import time def fetch_fund_nav(fund_code: str, pages: int 20) - pd.DataFrame: rows [] # 天天基金的历史净值分页接口一次返回一页 for page in range(1, pages 1): url ( fhttp://api.fund.eastmoney.com/f10/lsjz? ffundCode{fund_code}pageIndex{page}pageSize50 ) headers {Referer: http://fundf10.eastmoney.com/} resp requests.get(url, headersheaders, timeout10) # 返回数据是一个嵌套 JSON外层是 Data 字段 data resp.json().get(Data, {}) records data.get(LSJZList, []) if not records: break for r in records: rows.append({ date: r[FSRQ], nav: float(r[DWJZ]), acc_nav: float(r[LJJZ]), daily_return: float(r[JZZZL]), }) time.sleep(0.5) # 拉取间隔避免访问频率过高 return pd.DataFrame(rows) df fetch_fund_nav(000001) df.to_excel(funds_data_train.xlsx, indexFalse)Referer头很容易漏天天基金接口对来源有校验不加直接访问会返回权限错误。pageSize50和sleep(0.5)是折中单页数据量太大接口可能截断太快请求会被临时限流。实际跑的时候我一般会先抓一年的数据存成funs_1year_top10.xlsx这样的样本文件确认列结构正常后再批量抓取更长周期别一上来就写整个库。4.2 用 sklearn 做特征归一化但必须防泄露原始净值是几十块上百块、收益率是百分之几尺度差异太大直接喂进 LSTM 会导致训练不稳定。常规做法是统一缩放到 0~1 区间用MinMaxScalerfrom sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler() # 先 fit 训练集再 transform 测试集防止数据泄露 features_train_scaled scaler.fit_transform(features_train) features_test_scaled scaler.transform(features_test)这里必须遵守一条铁律fit_transform只用在训练集上测试集只能调用transform。如果你对全量数据先做fit再切分测试集的统计信息最小值、最大值就提前进入了训练流程等于模型已经间接见过测试分布回测指标的参考价值会大打折扣。另外要注意MinMaxScaler对收益率这种可能为负的值处理后会有 0.5 附近的中间值放心保留这比标准化StandardScaler更适配 LSTM 的 sigmoid 输出范围。4.3 训练模型并输出预测结果表构建好三维输入张量后执行训练然后用测试集预测。预测得到的是上涨概率后续需要转成类别标签并落地为结果表# 预测测试集 prob_test model.predict(X_test) # 常见做法是把概率转成 0/1 标签阈值默认 0.5 pred_label (prob_test 0.5).astype(int) # 输出对比表方便人工检查 result pd.DataFrame({ 真实标签: y_test, 预测标签: pred_label.reshape(-1), 上涨概率: prob_test.reshape(-1) }) result.to_excel(test_predict_rut.xlsx, indexFalse) print(测试集准确率:, accuracy_score(y_test, pred_label))做完这一步test_predict_rut.xlsx就是你人工核对模型的第一手材料。我建议不要只看整体准确率而是把「模型判断上涨且实际上涨」和「模型判断下跌且实际下跌」的行挑出来各看几行确认模型的判断在连续时间上是不是杂乱跳跃的。如果预测标签每天轮流翻转说明模型学到的是噪声这时候优先回去调 Dropout 和time_steps。4.4 用 Views.py 查看可视化的边界Views.py配合输出一张Figure_1.png图常见内容是把测试集的真实标签和预测概率画到一张图上直观展示两者在时间轴上的吻合情况。可视化最有价值的用法是让你发现「结构性偏差」——比如模型在急涨行情里总是滞后一天、在横盘阶段输出概率普遍贴近 0.5。这些现象在准确率数字里看不出来但画成图一眼就能定位。import matplotlib.pyplot as plt plt.figure(figsize(12, 5)) plt.plot(y_test, label真实涨跌, alpha0.7) plt.plot(pred_label, label预测涨跌, alpha0.7) plt.legend() plt.title(LSTM 涨跌方向预测对比) plt.savefig(Figure_1.png, dpi150)5. 避坑排查LSTM 金融预测最容易翻车的五个问题5.1 训练准确率 99%回测准确率 51%现象模型在训练集上准确率接近满分验证集和测试集准确率却和抛硬币差不多训练曲线呈典型的「训练损失降、验证损失升」喇叭口。原因这是最典型的过拟合。LSTM 参数量远大于日线样本量加上金融数据信噪比低网络很容易把某段行情的历史波动一字不差地背下来。另外如果 Dropout 比例太低或没开早停这种记忆会更加彻底。解决把 LSTM 隐单元从 64 降到 32Dropout(0.5)patience提前到 8同时把时间步长从 60 缩到 30。缩小时间步长等于强制模型只看近期规律变相减少可记忆的历史细节。如果还过拟合就先删除手工特征只保留收益率一列。5.2 归一化时用了全量数据的最大最小值现象测试集准确率明显偏高可一旦把模型部署到新数据上准确率掉得特别快预测概率出现多个极端接近 1 或 0 的值。原因在对所有数据含测试集做fit_transform的情况下测试集的极值范围已经参与缩放。模型在训练时知道了测试特征的边界等于提前拿到了未见过数据的部分信息。解决严格按「先切分再只对训练集 fit」的顺序走。写代码时用两个变量区分scaler.fit_transform(train_df)然后scaler.transform(test_df)。回测时如果要滚动更新每个时间窗口都重新fit一次最新训练数据而不是沿用历史 scaler。5.3 标签没用 shift(-1)准确率虚高现象训练和测试准确率都非常高接近 80% 以上但把预测结果按时间画出来发现预测值几乎等于当天的真实涨跌一点提前量都没有。原因数据泄漏了。如果用第 t 天的收益率生成第 t 天的标签标签和特征在时间上完全对齐模型实际是在做「用今天的特征解释今天的结果」而不是「用今天预测明天」。解决标签生成时务必shift(-1)然后把最后一行 NaN 删掉。验证方法很简单随机抽取 10 行检查第 t 行的特征日期是否早于标签日期至少一天。这种错位问题用test_y_rut.xlsx与test_predict_rut.xlsx按日期列交叉比对就能发现。5.4 类别不平衡测试集里 70% 都是上涨现象模型预测准确率 70%看起来不错但画出混淆矩阵后发现它把测试样本几乎全部预测为上涨下跌样本一条都预测不准。原因A 股和权益基金在特定时间窗口内上涨天数本来就偏多正负样本比例大概在 55:45 甚至 65:35 之间。模型发现全预测上涨就能拿高分于是放弃学习下跌模式。解决训练前统计标签占比并用compute_class_weight传入模型from sklearn.utils.class_weight import compute_class_weight class_weight compute_class_weight( class_weightbalanced, classesnp.array([0, 1]), yy_train ) class_weight_dict {0: class_weight[0], 1: class_weight[1]} model.fit( X_train, y_train, class_weightclass_weight_dict, ... )评估时不要只看准确率重点看召回率上涨样本中被预测出来的比例、下跌样本中被预测出来的比例。如果只有上涨样本有召回率模型等于废了。5.5 训练结果每次跑都不一样模型不可复现现象同一个脚本第一次跑测试集准确率 58%第二次跑变成 52%第三次又变 55%你无法确定是模型改进有效还是随机波动。原因LSTM 在 Keras 中的权重初始化是随机的训练过程中的批次顺序也带随机性。金融数据本身噪声大随机种子一变结果波动会比其他数据更明显。解决在代码最顶部固定随机种子同时设置环境级随机开关import random import numpy as np import tensorflow as tf random.seed(42) np.random.seed(42) tf.random.set_seed(42)对于更严格的复现还需要对同一套训练数据跑 5 次取测试集准确率的平均值和标准差。比如平均准确率 56%标准差 ±2%这说明模型效果虽然平庸但基本稳定后续调参才有可比性。6. 进阶验证用滚动窗口回测和混淆矩阵判断模型是不是在猜一次性 train/test 的验证方式在金融时序里说服力有限因为它只代表某一个历史切分点上的表现。更扎实的做法是滚动窗口回测第一次用 2018-01 到 2019-12 的数据训练预测 2020-01然后训练集扩充到 2020-01预测 2020-02以此类推把全年逐月滚下来最后汇总每一期的预测结果。这样做的好处是模拟了真实部署中「模型不断用最新数据重训」的状态能暴露模型在行情风格切换后是否迅速失效。# 伪代码滚动窗口回测框架 for month in pd.date_range(2021-01-01, 2021-12-01, freqMS): train data[data.index month] test data[(data.index month) (data.index month pd.Timedelta(days30))] X_train, y_train build_dataset(train_features, train_labels, time_steps30) X_test, y_test build_dataset(test_features, test_labels, time_steps30) model build_lstm_model() model.fit(X_train, y_train, epochs30, batch_size32, verbose0) y_prob model.predict(X_test) # 记录本月准确率、精确率、召回率配合滚动回测必须同时看混淆矩阵。只看准确率你会被「样本中上涨占多数」欺骗混淆矩阵能直接呈现四个格子真正上涨并预测上涨、真正上涨但预测下跌、真正下跌但预测上涨、真正下跌并预测下跌。对基金方向预测来说真正下跌并预测下跌真负类比真正上涨并预测上涨更重要因为躲开下跌才是这类模型最核心的价值。实际经验里如果滚动回测 12 个月的准确率均值低于 55%基本可以断定模型在猜或者只学到了行情惯性。这时不要急着加特征先检查标签偏移和类别权重再考虑把时间步长从 30 调整到 15——短一点的窗口有时反而能留住更近的市场状态。从那以后我每次做金融时序模型都会强制自己先跑一遍滚动窗口回测再看混淆矩阵里的真负率确认模型的判断不是市场牛熊的惯性附庸再决定要不要拿去辅助决策。希望这套验证方法能帮你在这个项目上少走一点弯路。本文还有配套的精品资源点击获取