
简介本资源是一份面向深度学习初学者与金融量化实践者的MATLAB实战项目聚焦利用长短期记忆网络LSTM解决股票价格这一典型时间序列预测问题。资源提供完整可运行的LSTM建模流程含数据预处理、模型构建、训练调优及多指标评估MSE、RMSE、MAE、R²兼顾理论理解与工程落地适合具备基础MATLAB编程能力的学习者开展复现与拓展。压缩包共5个文件3个核心.m脚本实现主流程与评估1个.xlsx原始/可视化数据表1个.csv预测结果输出总大小仅114KB轻量易解压结构简洁、即下即用。目前已有1064人学习下载涵盖高校课程实践、毕业设计参考及个人量化工具开发场景读者可直接运行main2.m启动全流程结合Excel数据观察拟合趋势并通过结果CSV与评估脚本快速验证模型性能显著降低LSTM入门门槛。1. 为什么用 LSTM 做股票价格预测不是玄学是时间序列建模的刚性选择你手头有一组日频股票收盘价想让模型“看懂”过去 60 天的涨跌节奏再推断第 61 天的大概位置——这不是在猜硬币正反面而是在拟合一个强依赖历史状态的非线性动态系统。LSTM长短期记忆网络之所以被反复用于这类任务核心不在它“多深”而在于它天然携带门控机制遗忘门决定丢掉哪些旧记忆输入门控制新信息是否写入输出门筛选当前该暴露多少状态。这三重门恰好对应股票价格中常见的三种现象趋势延续性保留长期均线记忆、突发消息冲击快速覆盖旧状态、以及噪声衰减忽略单日异常振幅。Matlab 提供的lstmLayersequenceInputLayerregressionLayer组合封装了梯度裁剪、时序批处理、状态初始化等底层细节让一线工程师能跳过 PyTorch/TensorFlow 的张量维度拉扯直接聚焦在特征工程是否合理、序列长度是否匹配业务周期、验证策略是否防过拟合这三个致命环节上。本文不讲“AI 颠覆金融”只讲如何用 Matlab 写出可复现、可调试、上线前敢跑回测的 LSTM 股票预测脚本——从原始 CSV 加载到最终 RMSE 输出每一步都带参数依据和翻车记录。2. 搭建可运行的 LSTM 预测管道从数据加载到模型定义2.1 用readtable加载并清洗原始行情数据别让 NaN 毁掉整个训练股票数据常含停牌、复权错误或接口缺失导致的空值。Matlab 中若直接用csvread或importdata遇到空单元格会报错中断而readtable可自动将空字符串转为missing便于后续统一处理% 加载原始CSV假设列名为 Date, Open, High, Low, Close, Volume rawTbl readtable(stock_data.csv, ReadVariableNames, true, ReadRowNames, false); % 将Date列转为datetime并排序关键LSTM要求时间严格递增 rawTbl.Date datetime(rawTbl.Date, InputFormat, yyyy-MM-dd); rawTbl sortrows(rawTbl, Date); % 清洗Close列用前向填充处理连续停牌但不超过5天超限则插值 closeVec rawTbl.Close; nanIdx isnan(closeVec); if any(nanIdx) % 找出每个NaN段的起止位置 diffIdx diff([false; nanIdx; false]); startIdx find(diffIdx 1); endIdx find(diffIdx -1) - 1; for k 1:length(startIdx) segLen endIdx(k) - startIdx(k) 1; if segLen 5 % 前向填充用上一个有效值 closeVec(startIdx(k):endIdx(k)) closeVec(startIdx(k)-1); else % 超过5天用前后非空值线性插值避免引入虚假趋势 leftVal closeVec(startIdx(k)-1); rightVal closeVec(endIdx(k)1); closeVec(startIdx(k):endIdx(k)) linspace(leftVal, rightVal, segLen); end end end rawTbl.Close closeVec;逻辑说明readtable比csvread更鲁棒因它保留列名且支持缺失值标记sortrows强制时间对齐否则 LSTM 的sequenceInputLayer会因乱序报错清洗策略分两档——短停牌用前向填充符合实际交易逻辑停牌期间价格不变长停牌用线性插值避免用均值引入平滑假象。参数说明InputFormat必须与 CSV 中日期格式严格一致否则datetime返回NaT后续排序失效segLen 5是经验阈值对应 A 股常见连续停牌天数上限如重大资产重组超过则视为数据源问题需人工核查。2.2 构造监督学习样本把单变量序列转成 [X, Y] 对窗口大小决定模型“记忆深度”LSTM 不接受原始时间序列必须切片为固定长度的输入-输出对。例如用过去 60 天收盘价预测第 61 天——这 60 就是SequenceLength也是模型“看到的历史深度”。关键点在于不能简单滑动切片而要确保训练/验证/测试集在时间上严格割裂否则未来信息泄露future leakage会让 RMSE 虚低 30%% 提取清洗后的收盘价向量列向量 priceVec rawTbl.Close(:); % 归一化用训练集最大最小值缩放避免梯度爆炸 trainEnd floor(0.7 * length(priceVec)); % 70%训练 valEnd floor(0.85 * length(priceVec)); % 15%验证 trainVec priceVec(1:trainEnd); scaler [min(trainVec), max(trainVec)]; priceNorm (priceVec - scaler(1)) / (scaler(2) - scaler(1) eps); % eps防除零 % 定义滑动窗口参数 SequenceLength 60; % 模型“记忆长度”对应60个交易日 numSamples length(priceNorm) - SequenceLength; % 预分配X三维数组[SequenceLength, 1, numSamples]、Y列向量 X zeros(SequenceLength, 1, numSamples); Y zeros(numSamples, 1); % 滑动切片X(:,:,i) [t-59, t-58, ..., t], Y(i) t1 for i 1:numSamples X(:, 1, i) priceNorm(i:iSequenceLength-1); Y(i) priceNorm(iSequenceLength); end % 按时间分割确保X_train只含ttrainEnd的数据 trainIdx 1:(trainEnd - SequenceLength); valIdx (trainEnd - SequenceLength 1):(valEnd - SequenceLength); testIdx (valEnd - SequenceLength 1):numSamples; XTrain X(:, :, trainIdx); YTrain Y(trainIdx); XVal X(:, :, valIdx); YVal Y(valIdx); XTest X(:, :, testIdx); YTest Y(testIdx);逻辑说明scaler仅用训练集极值归一化测试集也用同一 scaler 变换——这是防止数据穿越的铁律X是三维数组因 Matlab LSTM 要求输入尺寸为[sequenceLength, numFeatures, numObservations]此处numFeatures1单变量索引分割trainIdx/valIdx/testIdx严格按原始时间顺序切分而非随机打乱时间序列禁用 shuffle。参数说明SequenceLength60对应约一个季度交易日适合捕捉中期趋势若预测分钟级高频数据应设为 10~30eps是数值稳定项避免maxmin时分母为零如新股上市初期价格不变。2.3 定义 LSTM 网络架构层数、隐藏单元、Dropout 的取舍依据Matlab 中 LSTM 网络由层序列Layer Array定义。常见误区是堆叠过多 LSTM 层——实测表明单层 LSTM 合理隐藏单元数比双层小单元更鲁棒因深层易引发梯度消失且增加过拟合风险% 输入层序列长度60特征数1 inputLayer sequenceInputLayer(1, Normalization, none, Name, input); % LSTM层隐藏单元数128平衡表达力与过拟合 lstmLayer lstmLayer(128, OutputMode, last, Name, lstm); % Dropout层防止LSTM状态过拟合率0.2经验值 dropLayer dropoutLayer(0.2, Name, dropout); % 全连接层映射到1维输出预测值 fcLayer fullyConnectedLayer(1, Name, fc); % 回归层计算MSE损失 regLayer regressionLayer(Name, regression); % 组合成网络 layers [ inputLayer lstmLayer dropLayer fcLayer regLayer ];逻辑说明OutputMode,last表示只取最后一个时间步的 LSTM 输出因我们预测单点比sequence模式省内存且符合任务dropoutLayer(0.2)作用于 LSTM 的隐藏状态而非输入故放在lstmLayer后fullyConnectedLayer(1)是必需的因 LSTM 输出是 128 维向量需降维到标量预测值。参数说明lstmLayer隐藏单元数 128 是折中选择——小于 64 则表达能力不足无法拟合复杂波动大于 256 则训练慢且验证误差上升见后文避坑dropout0.2是标准起点若验证损失持续高于训练损失可升至 0.3~0.5。3. 训练配置与超参调优为什么默认参数在股票数据上必然翻车3.1 设置训练选项adam优化器、学习率衰减、早停机制缺一不可股票价格噪声大模型易陷入局部最优。Matlab 默认的trainingOptions用sgdm带动量的随机梯度下降在 LSTM 上收敛慢且不稳定。必须显式指定adam并启用学习率调度% 关键训练选项 options trainingOptions(adam, ... MaxEpochs, 100, ... % 最大训练轮数 MiniBatchSize, 32, ... % 小批量大小32平衡内存与梯度稳定性 InitialLearnRate, 0.005, ... % 初始学习率0.005比默认0.01更稳 LearnRateSchedule, piecewise, ... % 分段学习率衰减 LearnRateDropFactor, 0.2, ... % 学习率下降倍数 LearnRateDropPeriod, 25, ... % 每25轮衰减一次 ValidationData, {XVal, YVal}, ... % 验证集 ValidationFrequency, 10, ... % 每10轮验证一次 Verbose, true, ... % 显示训练日志 Plots, training-progress, ... % 实时绘图 OutputNetwork, best-validation-loss, ... % 保存验证损失最低的模型 CheckpointPath, ./checkpoints, ... % 保存中间检查点 ExecutionEnvironment, auto, ... % 自动选择CPU/GPU GradientThreshold, 1, ... % 梯度裁剪阈值防爆炸 EarlyStoppingCriteria, validation-loss, ... % 早停依据 EarlyStoppingPatience, 15); % 连续15轮验证损失不降则停逻辑说明piecewise学习率调度比time-based更可靠因股票数据各阶段波动率不同best-validation-loss确保最终模型是泛化能力最强的而非最后一轮GradientThreshold1是 LSTM 训练的救命参数——未设置时梯度爆炸会导致 loss 突然变为Inf或NaN。参数说明MiniBatchSize32是经验安全值若 GPU 显存不足可降至 16EarlyStoppingPatience15需配合ValidationFrequency10即最多容忍 150 轮无提升约 1.5 个 epoch避免过早终止ExecutionEnvironment,auto在无 GPU 时自动切 CPU不影响代码复用。3.2 超参敏感性分析为什么SequenceLength和NumHiddenUnits必须联合调优单独调SequenceLength或NumHiddenUnits效果有限二者存在耦合关系长序列需更多隐藏单元捕获长期依赖但单元数过多又加剧过拟合。我们用网格搜索验证代码精简版% 定义候选参数组合 seqLens [30, 60, 90]; hiddenUnits [64, 128, 256]; % 预分配结果矩阵 valRMSE zeros(length(seqLens), length(hiddenUnits)); for i 1:length(seqLens) for j 1:length(hiddenUnits) % 重新构造数据不同SequenceLength需重切片 [XTrain_ij, YTrain_ij, XVal_ij, YVal_ij] prepareSequences(priceNorm, ... seqLens(i), trainEnd, valEnd); % 构建网络动态设置隐藏单元数 layers_ij [ sequenceInputLayer(1, Normalization, none) lstmLayer(hiddenUnits(j), OutputMode, last) dropoutLayer(0.2) fullyConnectedLayer(1) regressionLayer ]; % 训练简化选项仅10轮快速评估 opts_ij trainingOptions(adam, ... MaxEpochs, 10, ... MiniBatchSize, 32, ... InitialLearnRate, 0.005, ... ValidationData, {XVal_ij, YVal_ij}, ... Plots, none, Verbose, false); net_ij trainNetwork(XTrain_ij, YTrain_ij, layers_ij, opts_ij); YPred_ij predict(net_ij, XVal_ij); valRMSE(i,j) sqrt(mean((YPred_ij - YVal_ij).^2)); end end % 找最优组合 [minRMSE, idx] min(valRMSE(:)); [iOpt, jOpt] ind2sub(size(valRMSE), idx); fprintf(最优SequenceLength%d, NumHiddenUnits%d, 验证RMSE%.4f\n, ... seqLens(iOpt), hiddenUnits(jOpt), minRMSE);逻辑说明每次循环重建XTrain_ij/YTrain_ij因不同SequenceLength导致样本数变化predict直接调用训练好的网络避免重复编译valRMSE矩阵存储所有组合的验证误差而非仅训练损失——这才是泛化能力的真实指标。参数说明网格范围seqLens[30,60,90]覆盖短中长周期月/季/半年hiddenUnits[64,128,256]是典型搜索空间快速评估用MaxEpochs10足够区分优劣正式训练再用 100 轮。4. 避坑LSTM 股票预测中 4 个血泪经验换来的翻车现场4.1 现象训练 loss 快速下降至 0.001但验证 loss 却在 0.05 波动且预测曲线完全平直原因归一化 scaler 使用了全量数据而非仅训练集极值导致验证/测试集被“泄露”了全局分布信息模型学会拟合 scaler 而非价格动态。解决严格分离 scaler 计算范围。确认scaler [min(priceVec(1:trainEnd)), max(priceVec(1:trainEnd))]且测试集变换用YTestNorm (YTest - scaler(1)) / (scaler(2) - scaler(1) eps)。4.2 现象训练中途 loss 突然变为Inf或NaNtrainingProgress图中断原因梯度爆炸gradient explosion在 LSTM 中高频发生尤其当SequenceLength大、初始学习率高、或数据未归一化时。解决① 必加GradientThreshold,1② 若仍发生将InitialLearnRate从 0.005 降至 0.001③ 检查数据是否有极端离群值如某日涨跌幅 50%用isoutlier(priceVec,gesd)标记并修正。4.3 现象预测结果呈现明显滞后性prediction 总是 lagging 1-step behind actual原因模型学到的是恒等映射y_t ≈ y_{t-1}即“明天≈今天”因股票价格自相关性太强简单模型优先拟合此基线。解决① 在输入中加入差分特征X_diff [X(:,1,i); diff(X(:,1,i))]使模型学习变化量而非绝对值② 添加技术指标作为辅助特征如 5 日均线、RSI用featureInputLayer扩展输入维度。4.4 现象改变MiniBatchSize后相同 epoch 下验证 RMSE 差异达 20%原因小批量大小影响梯度估计方差——MiniBatchSize16时梯度噪声大易跳出局部最优但收敛慢64时梯度准但可能陷进尖锐极小值。股票数据信噪比低对此更敏感。解决固定MiniBatchSize32作为基准若需调整同步修改InitialLearnRatebatch_size 加倍lr 乘 1.4并重跑验证。5. 预测结果反归一化与业务验证如何让数字回归交易语义5.1 把归一化预测值转回真实价格并计算可解释误差指标训练时用scaler归一化预测后必须逆变换才能得到真实价格。注意predict输出是列向量需与scaler同维度运算% 加载已训练好的最优模型假设为 net_opt YPredNorm predict(net_opt, XTest); % 逆归一化y_real y_norm * (max-min) min YPred YPredNorm .* (scaler(2) - scaler(1)) scaler(1); YTestReal YTest .* (scaler(2) - scaler(1)) scaler(1); % 计算业务指标非仅RMSE rmse sqrt(mean((YPred - YTestReal).^2)); mae mean(abs(YPred - YTestReal)); mape mean(abs((YPred - YTestReal) ./ (YTestReal eps))) * 100; % 百分比误差 directionAcc mean(sign(YPred(2:end) - YPred(1:end-1)) ... sign(YTestReal(2:end) - YTestReal(1:end-1))); % 方向准确率 fprintf(测试集指标RMSE%.3f, MAE%.3f, MAPE%.2f%%, 方向准确率%.2f%%\n, ... rmse, mae, mape, directionAcc*100);逻辑说明sign(...)计算相邻两日涨跌方向是否一致这是比价格绝对误差更贴近交易决策的指标——即使价格误差大若方向判对仍可开仓MAPE对价格水平敏感低价股误差放大故需结合RMSE解读。参数说明eps在MAPE分母中防零除因股价永不为零此处仅为数值安全directionAcc是二分类准确率阈值为 0不设阈值纯看符号最简洁反映趋势把握能力。5.2 可视化预测效果用plot对齐时间轴拒绝“横轴为样本序号”的伪图Matlab 默认plot(YPred)横轴是索引但业务需看“哪一天预测错了”。必须用真实日期对齐% 提取测试集对应的真实日期从原始表中索引 testDates rawTbl.Date(valEnd 1 : end); % 因YTest对应tSequenceLength时刻 numTest length(testDates); % 绘制真实值、预测值、置信区间若做蒙特卡洛Dropout可得 figure(Position, [100, 100, 1200, 600]); plot(testDates, YTestReal, b-o, LineWidth, 1.5, MarkerSize, 4, DisplayName, 真实价格); hold on; plot(testDates, YPred, r--s, LineWidth, 1.5, MarkerSize, 4, DisplayName, LSTM预测); xlabel(日期); ylabel(价格元); title(LSTM股票价格预测效果测试集); legend(Location, best); grid on; datetick(x, yyyy-mm-dd, keepticks); % x轴显示日期逻辑说明testDates必须从rawTbl.Date中按测试样本索引提取而非生成等差序列——A 股有节假日休市日期非均匀datetick确保 x 轴显示可读日期避免1×100 double的混乱横轴。参数说明keepticks保持 tick 位置不变仅改标签格式若测试集过大500点可设TickStep, 30每30天一标防标签重叠。5.3 回测框架雏形用预测信号生成简单买卖规则验证策略可行性预测值本身不等于交易信号需映射为动作。一个极简回测示例仅演示逻辑非实盘建议% 生成信号预测上涨YPred YTestReal*1.005则买入下跌则卖出 signal zeros(numTest, 1); signal(YPred YTestReal * 1.005) 1; % 买入信号预测涨幅0.5% signal(YPred YTestReal * 0.995) -1; % 卖出信号预测跌幅0.5% % 模拟持仓1表示持有多头-1表示持有空头0表示空仓 position zeros(numTest, 1); for t 2:numTest if signal(t) ~ 0 position(t) signal(t); % 直接切换仓位 else position(t) position(t-1); % 持仓不变 end end % 计算日收益率position(t-1) * (return_t)其中 return_t (Close_t - Close_{t-1})/Close_{t-1} dailyRet [0; diff(YTestReal) ./ YTestReal(1:end-1)]; strategyRet position(1:end-1) .* dailyRet(2:end); % 策略日收益 % 累计收益曲线 cumRet cumprod(1 strategyRet) - 1; figure; plot(testDates(2:end), cumRet, g-, LineWidth, 2); xlabel(日期); ylabel(累计收益); title(基于LSTM信号的简易策略回测); grid on; datetick(x, yyyy-mm-dd, keepticks);逻辑说明position向量化更新避免 for 循环cumprod计算复利收益信号阈值1.005是为覆盖交易成本假设单边0.25%避免频繁交易此仅为逻辑验证真实回测需加入滑点、手续费、涨跌停限制。参数说明dailyRet用真实价格计算因预测值有误差不能用YPred算收益strategyRet长度比position少1因收益发生在持仓后一日。我坚持一个习惯每次调完超参必用plot(plotconfusion(YTestRealmean(YTestReal), YPredmean(YTestReal)))看二分类混淆矩阵——不是为了精度而是确认模型没学会“永远预测均值”。如果右下角真跌却预测涨的格子特别亮说明特征工程或损失函数该动刀了。希望帮到你。本文还有配套的精品资源点击获取