简介这份资源是面向高校学生与机器学习入门者的LSTM股票/基金预测实战项目可作为期末大作业、课程设计或自学练手的高分参考方案。项目围绕长短期记忆网络在金融时序数据上的建模流程展开涵盖数据读取、模型搭建、训练与预测等核心环节帮助读者理解如何用深度学习处理基金净值与股票价格序列。压缩包共11个文件约155KB以xlsx数据表、py源码、md说明文档和png结果图为主其中数据表用于训练与测试集划分脚本文件承载模型定义与预测逻辑说明文档便于快速了解项目结构。目前已有224人学习下载。读者可从中获得一套可运行的LSTM预测代码、配套训练与测试数据、预测结果输出示例以及项目说明适合对照复现实验、修改参数并迁移到自己的时序预测任务中。1. 从一份“高分项目”压缩包说起LSTM 股票基金预测到底能不能落地打开招聘网站或者课程设计选题表你大概率会看到类似“基于机器学习 LSTM 的股票/基金预测模型高分项目”这样的标题。它通常以一个压缩包的形式流传里面塞着几份 csv、一个 train.py、一个 requirements.txt外加一张看起来还不错的预测曲线图。很多人拿到手的第一反应是跑起来截图交作业。但如果你真的想把它用在自己的量化研究、基金定投策略或者求职作品集里光会跑通是远远不够的。LSTM长短期记忆网络在时间序列预测里确实有它的位置尤其是处理股票、基金这类带有时序依赖和长期记忆特征的数据。但“预测”这个词在金融场景下非常危险——你预测的是价格、收益率还是涨跌方向输入的是 OHLCV 还是加了技术指标训练集和验证集怎么切才能避免未来函数这些问题不解决模型在测试集上 R² 再高实盘也会翻车。这篇文章不打算复述 LSTM 的公式推导而是把一份典型的高分项目拆开讲清楚从数据获取、特征工程、模型搭建到回测验证的完整路径适合已经会 Python 和 PyTorch 基础、想把这个方向做扎实的从业者和学生。2. 数据层用 akshare 拉 A 股/基金数据别急着喂给 LSTM2.1 为什么选 akshare 而不是 tushare 或 yfinance做 A 股和公募基金预测数据源的选择直接决定你后面能不能复现。tushare 老版本积分门槛高yfinance 对 A 股支持不稳定akshare 是目前免费方案里覆盖最全、更新最勤的一个。它封装了东方财富、新浪财经、天天基金等公开接口能直接拿到股票日线、基金净值、指数成分、财务指标。常见做法是用ak.stock_zh_a_hist拉个股用ak.fund_open_fund_info_em拉场外基金净值。注意一点akshare 的接口字段名偶尔会变写代码时不要把列名硬编码得太死用.rename统一成英文再往下走。import akshare as ak import pandas as pd # 拉取贵州茅台 2020-01-01 到 2024-12-31 的日线数据 df ak.stock_zh_a_hist( symbol600519, perioddaily, start_date20200101, end_date20241231, adjustqfq # 前复权避免除权除息造成价格跳空 ) df df.rename(columns{ 日期: date, 开盘: open, 收盘: close, 最高: high, 最低: low, 成交量: volume, 成交额: amount, 换手率: turnover }) df[date] pd.to_datetime(df[date]) df df.sort_values(date).reset_index(dropTrue) print(df.head())这段代码的关键参数是adjustqfq。股票分红送股后价格会跳空不复权的话 LSTM 会把这个跳空当成真实波动学进去预测结果完全失真。基金净值一般没有复权问题但要注意区分单位净值和累计净值定投策略通常用累计净值。拉完数据先做三件事检查缺失值、检查停牌日、检查涨跌停日。停牌日直接 drop涨跌停日如果比例过高说明这段数据不适合做高频预测。2.2 特征工程把 OHLCV 变成 LSTM 能吃的序列原始价格序列直接喂 LSTM 效果通常很差因为价格是非平稳的。常见做法是转成收益率、对数收益率再叠加技术指标。我一般会构造以下几类特征特征类别具体字段计算方式价格变换log_returnln(close_t / close_{t-1})波动率volatility_55 日收益率标准差动量momentum_10close_t / close_{t-10} - 1均线偏离ma_bias_20(close - MA20) / MA20成交量volume_ratiovolume_t / MA5(volume)基金特有nav_growth单位净值日增长率构造完特征后要做标准化。注意标准化参数只能从训练集计算然后应用到验证集和测试集。很多人图省事在全量数据上做 z-score这就是典型的未来函数模型在回测里表现虚高实盘一塌糊涂。import numpy as np def make_features(df): df df.copy() df[log_return] np.log(df[close] / df[close].shift(1)) df[volatility_5] df[log_return].rolling(5).std() df[momentum_10] df[close] / df[close].shift(10) - 1 df[ma20] df[close].rolling(20).mean() df[ma_bias_20] (df[close] - df[ma20]) / df[ma20] df[volume_ma5] df[volume].rolling(5).mean() df[volume_ratio] df[volume] / df[volume_ma5] df df.dropna().reset_index(dropTrue) return df feature_cols [log_return, volatility_5, momentum_10, ma_bias_20, volume_ratio] data make_features(df)这里dropna()会丢掉前 20 行左右因为滚动窗口需要预热。如果你的数据量本来就少比如只有两三年可以把窗口缩短到 10 日或 5 日。特征列不是越多越好LSTM 对噪声很敏感一般控制在 5 到 15 个特征之间。基金数据没有成交量时把 volume 相关特征换成净值增长率、同类排名变化等。3. 模型层PyTorch LSTM 的输入形状、隐藏层和训练循环3.1 把 DataFrame 转成 (batch, seq_len, input_size) 张量LSTM 在 PyTorch 里的输入必须是三维张量batch size、序列长度、特征数。很多人第一次写会卡在维度上报错Expected hidden[0] size (1, batch, hidden_size)。解决办法是写一个 Dataset 类用滑动窗口切序列。假设用过去 30 个交易日预测下一天的对数收益率import torch from torch.utils.data import Dataset, DataLoader class StockDataset(Dataset): def __init__(self, data, feature_cols, target_col, seq_len30): self.features data[feature_cols].values.astype(np.float32) self.targets data[target_col].values.astype(np.float32) self.seq_len seq_len def __len__(self): return len(self.features) - self.seq_len def __getitem__(self, idx): x self.features[idx: idx self.seq_len] y self.targets[idx self.seq_len] return torch.tensor(x), torch.tensor(y) dataset StockDataset(data, feature_cols, log_return, seq_len30) loader DataLoader(dataset, batch_size64, shuffleFalse)注意shuffleFalse。时间序列数据不能随机打乱否则模型会看到未来的样本。训练集、验证集、测试集按时间顺序切比如 70% / 15% / 15%。标准化参数从训练集算然后 transform 验证集和测试集。3.2 LSTM 网络结构几层、多少隐藏单元、dropout 放哪一个能用的 baseline 不需要太复杂。我一般用两层 LSTM隐藏单元 64 或 128最后接一个全连接层输出标量。dropout 放在 LSTM 层之间和全连接层之前。注意 PyTorch 的nn.LSTM如果num_layers 1dropout参数才会生效。import torch.nn as nn class LSTMModel(nn.Module): def __init__(self, input_size, hidden_size64, num_layers2, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout ) self.fc nn.Sequential( nn.Linear(hidden_size, 32), nn.ReLU(), nn.Dropout(dropout), nn.Linear(32, 1) ) def forward(self, x): out, _ self.lstm(x) # out: (batch, seq_len, hidden) out out[:, -1, :] # 取最后一个时间步 return self.fc(out).squeeze(-1)batch_firstTrue让输入形状变成 (batch, seq_len, feature)符合大多数人的直觉。取out[:, -1, :]表示只用最后一个时间步的隐藏状态做预测。如果你做的是多步预测可以取多个时间步或者用 seq2seq 结构但股票日频预测一般单步就够了。隐藏单元不是越大越好64 到 128 在几千条数据上足够再大会过拟合。3.3 训练循环损失函数、优化器和早停回归任务用 MSELoss优化器用 Adam学习率 1e-3 起步。训练过程中记录验证集损失如果连续 10 个 epoch 没有下降就早停。注意股票收益率信噪比极低验证集损失震荡是正常的不要因为一两轮上升就停。device torch.device(cuda if torch.cuda.is_available() else cpu) model LSTMModel(input_sizelen(feature_cols)).to(device) criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) best_val_loss float(inf) patience, counter 10, 0 for epoch in range(100): model.train() for x, y in train_loader: x, y x.to(device), y.to(device) optimizer.zero_grad() pred model(x) loss criterion(pred, y) loss.backward() optimizer.step() model.eval() val_loss 0.0 with torch.no_grad(): for x, y in val_loader: x, y x.to(device), y.to(device) val_loss criterion(model(x), y).item() val_loss / len(val_loader) if val_loss best_val_loss: best_val_loss val_loss torch.save(model.state_dict(), best_lstm.pth) counter 0 else: counter 1 if counter patience: print(fEarly stop at epoch {epoch}) break保存验证集最优的模型权重而不是最后一个 epoch 的。学习率可以加ReduceLROnPlateau验证损失不降时自动减半。batch size 64 或 128 都行太小训练不稳定太大显存吃紧且泛化变差。4. 避坑与排查LSTM 股票预测最常见的 5 个翻车现场4.1 现象训练损失降到 1e-4测试集 R² 却是负数原因未来函数。最常见的是在全量数据上做了标准化或者用train_test_split(shuffleTrue)切了时间序列。另一个隐蔽原因是特征里包含了当天收盘后才知道的信息比如用当日收盘价算的 MA 去预测当日收益率。解决所有预处理只在训练集 fit验证集和测试集 transform。切分必须按时间顺序。特征计算时确认每个字段在预测时点是否可得滞后一期再喂给模型。4.2 现象预测曲线是一条直线完全不波动原因模型退化成预测均值。股票收益率均值接近零MSE 损失下模型发现输出 0 附近就能让损失很小于是不再学习波动。这是金融时序预测的经典问题。解决换损失函数比如 IC loss、方向准确率 loss或者对收益率做截面标准化。另一个办法是预测涨跌方向而不是具体数值变成二分类问题用 BCEWithLogitsLoss。4.3 现象每次重新训练结果都不一样方向准确率在 48% 到 56% 之间跳原因随机种子没固定权重初始化、dropout、数据加载顺序都有随机性。股票信噪比低模型本身就不稳定。解决固定torch.manual_seed、np.random.seed、random.seed。但更重要的是接受这个现实单次结果不可信跑 10 次取平均看方向准确率的均值和标准差。如果均值在 52% 以下说明特征或模型有问题。4.4 现象验证集损失一直降测试集方向准确率只有 50%原因过拟合。LSTM 参数量相对于几千条日频数据来说太大了。另一个原因是验证集和测试集分布不同比如训练集包含牛市测试集是熊市。解决减小隐藏单元和层数增大 dropout加 L2 正则化。用 walk-forward 验证代替单次切分滚动训练滚动预测更接近实盘。4.5 现象akshare 拉数据报错或返回空 DataFrame原因接口变更、网络超时、股票代码格式不对。akshare 的接口依赖第三方网站偶尔会失效。解决先升级pip install akshare --upgrade再检查代码格式。股票代码不带前缀基金代码要区分场内外。加 try-except 和重试机制把拉到的数据存本地 parquet避免每次训练都重新拉。5. 验证与进阶从单只股票到组合回测以及一个我常用的技巧5.1 用 walk-forward 验证代替单次切分单次 train/val/test 切分在金融数据上很容易过拟合。更靠谱的做法是 walk-forward用前 3 年训练第 4 年预测然后窗口向前滚动一年重复。这样你能得到多个样本外的预测片段拼起来看整体表现。def walk_forward(data, train_years3, test_months6): results [] start data[date].min() end data[date].max() while start pd.DateOffset(yearstrain_years) end: train_end start pd.DateOffset(yearstrain_years) test_end train_end pd.DateOffset(monthstest_months) train data[(data[date] start) (data[date] train_end)] test data[(data[date] train_end) (data[date] test_end)] if len(train) 500 or len(test) 50: break # 在这里训练模型并预测 test results.append((train_end, test)) start pd.DateOffset(monthstest_months) return results这个函数把时间轴切成多个训练-测试对每个窗口独立训练和预测。注意每次都要重新做标准化用当前窗口的训练集参数。这样得到的样本外结果更有说服力。5.2 从单标的到组合多只股票一起预测单只股票的 LSTM 预测噪声太大一个实用的进阶方向是组合预测。拉 20 到 50 只同行业股票每只都构造特征然后两种做法一是把股票代码做 embedding用一个共享 LSTM 预测所有标的二是每只单独训练最后按预测收益率排序做多空组合。第二种实现简单但计算量大。第一种更优雅但需要处理不同股票序列长度不一致的问题。我一般先用第二种快速验证特征有效性如果单标的平均方向准确率能到 53% 以上再考虑共享模型。组合回测时注意交易成本A 股双边千分之三左右基金申赎费另算。很多“高分项目”的回测曲线漂亮是因为没扣成本。5.3 一个我常用的技巧把预测目标从收益率换成排序直接预测收益率数值很难但预测“明天哪只股票涨得比另一只多”相对容易。做法是每天在截面内对收益率做 rank 标准化然后 LSTM 输出排序分数用 pairwise ranking loss 训练。这样模型学的是相对强弱而不是绝对数值对噪声更鲁棒。实盘时每天选分数最高的前 10% 买入持有 5 到 10 个交易日再调仓。这个思路在基金预测上同样适用把多只基金放在一起排序预测未来一周哪只基金净值增长排名靠前。比单只基金预测净值曲线要靠谱得多。最后说一句血泪经验LSTM 股票预测这个方向模型结构不是关键数据和验证方式才是。我见过太多人花一周调 LSTM 层数和学习率却不肯花半天检查特征里有没有未来函数。先把 walk-forward 回测框架搭好再谈模型优化。希望帮到你。本文还有配套的精品资源点击获取
