简介这份源码包面向对量化交易与深度学习交叉领域感兴趣的研究者与学生聚焦使用Keras实现LSTM网络对股票收益率进行多变量时间序列预测。资源围绕数据转换、模型拟合、结果逆缩放等关键环节展开以600000.SH股票2016年3月至2017年12月数据为回测基准适合作为入门LSTM在金融场景落地的实验参考。包内共4个文件主要包含20支股票数据压缩包、LSTM模型训练源码、回测损失与RMSE计算Excel以及股票背景介绍文档整体体积3.87MB。源码与数据配合使用可帮助学习者快速复现预测流程并理解指标变化。已有4802人学习浏览可作为深度学习与量化交易课程设计或毕业设计的基础资料。1. 用Keras实现LSTM股票预测源代码能跑通不代表你能用好我见过太多人拿到一份用Keras实现LSTM股票预测的源代码和数据集跑出来的训练损失曲线漂亮得不像话换一段行情就原形毕露。这不是LSTM不能预测股票而是大部分入门代码把时间序列当成普通回归问题处理在数据切分、归一化、特征构造上埋了一堆雷。这个标题下的源码加数据集解决的是“用Keras跑通LSTM股票预测”的完整闭环从K线数据整理、滑窗样本构造、LSTM建模到预测和回测。它适合三批人想快速验证LSTM在自己数据集上是否有效的人想拿现成源码改特征做对比实验的人以及刚学完循环神经网络、需要一个完整落地点的人。先别急着调模型先问数据经不经得起顺序和泄漏这两关。2. 把股票数据集做成LSTM能吃的样本K线整理、滑窗与时间切分股票数据集不是拿来就能训练的。常见的数据集长这样date, open, close, high, low, volume, amount。你拿到手直接训练会遇到时间序列错乱、缺失值和除权导致的断崖。这一章先把数据链路打通否则后面模型训得再好也只是在脏数据上拟合噪声。2.1 数据源与数据集自检免费接口、CSV文件和ETF优先先从两个层面说数据从哪来怎么确认能用。以我的经验第一次做LSTM股票预测最稳妥的做法是用免费数据接口把日K线拉下来或者用整理好的CSV文件。常见的免费接口如tushare、akshare都能拿到带复权因子的日线关键是拿到后先检查再动手。优先选ETF或者指数而不是个股原因很简单个股停牌、涨跌停、除权除息会打断时间序列ETF和指数连续性更好对新手更友好做入门验证不容易出现莫名其妙的大坑。这就像帕德劳恩轴承故障数据集是连续的振动信号清洗思路和股票完全不同——股票数据每一行必须按时间排序一旦打乱模型学到的时序关系就全废了。拿到数据集后先做基本自检常见做法是加载CSV后确认日期范围、行数、缺失值import pandas as pd df pd.read_csv(stock_data.csv, parse_dates[date]) print(df.shape, df[date].min(), df[date].max()) # 三个关键自检日期唯一、升序、无大量缺失 print(df[date].is_unique) print(df[date].is_monotonic_increasing) print(df.isnull().sum())这段代码的逻辑是先把日期列解析成datetime类型然后看行数和时间边界。is_unique检查有没有重复日期is_monotonic_increasing检查日期是否严格升序isnull().sum()列出每一列的缺失数量。很多人拿到数据集直接建模结果测试集准确率看着高实际上里面混进了未排序的数据顺序错乱是对时间序列模型最大的伤害。如果发现日期乱序先用df.sort_values(date)修复再继续后面的清洗。2.2 清洗三个关键点缺失值、复权、停牌与涨跌停缺失值处理上短的缺口可以前向填充保住时间轴完整但不能用0填。填0等于告诉模型那一段价格为零模型会学到断崖下跌。长的缺口最好直接剔除或用接口重新拉取靠填充会让序列产生虚假的“长时间横盘”结构。复权问题是最隐蔽的坑。如果不复权除权除息当天价格瞬间掉一截模型会把分红送股造成的价格跳变当成暴跌信号去学习。常见做法是统一用前复权也就是把历史价格按当前复权因子折算保证价格序列连续。预测阶段和训练阶段必须用同一条复权链路训练用前复权、预测用未复权是最典型的翻车现场。停牌和涨跌停的处理方式取决于实验目的。如果做的是日频预测停牌日前后会出现不连续的价格跳跃建议要么把停牌段落剔除要么前向填充后额外加一列标记位。下面是清洗流程的示例# 1. 排序去重保证时间轴干净 df df.sort_values(date).drop_duplicates(subsetdate) # 2. 复权如果数据里有复权因子列按因子换算 df[close_adj] df[close] * df[adj_factor] df[open_adj] df[open] * df[adj_factor] # 3. 处理停牌造成的缺失短缺口前向填充同时打上标记 df[is_filled] df[close_adj].isna().astype(int) df[close_adj] df[close_adj].ffill() df[open_adj] df[open_adj].ffill() # 4. 涨跌停标记阈值按不同板块规则调整 df[limit_up] (df[close_adj].pct_change() 0.095).astype(int)这里的adj_factor列不是每个数据集都有如果只有close而没有复权因子那就优先找带adj_close字段的数据接口。ffill()在pandas里是前向填充的推荐写法老代码里的fillna(methodffill)在新版本会报警告。涨跌停阈值0.095只是A股主板的近似值ST、创业板和科创板都不一样你要按实际交易规则改。2.3 滑窗样本构造从K线到(Lookback, Features)的监督学习样本LSTM不能直接吃一整段K线它需要的是固定长度的历史窗口。常见做法是把序列切成许多个长度固定的样本用过去20根日K线预测第21根的目标值。这个窗口长度叫lookback是整条流水线最重要的超参数之一。LSTM输入形状是(样本数, lookback, 特征数)输出形状是(样本数, 输出步长)。构造滑窗的代码如下import numpy as np def make_sequence(dataset, lookback20, horizon1): X, y [], [] for i in range(lookback, len(dataset) - horizon 1): # 取 i-lookback 到 i-1 这 lookback 行作为特征 X.append(dataset[i - lookback:i, :]) # 取第 ihorizon-1 行的第 0 列作为标签 y.append(dataset[i horizon - 1, 0]) return np.array(X), np.array(y)逻辑说明循环从lookback开始保证每个样本都有完整的窗口。X的每一个元素形状是(lookback, 特征数)对应一个历史片段y是未来第horizon天的目标值这里取的是第0列也就是你希望在标签里放的那个字段。horizon1表示预测下一天想预测未来三天就设3但预测越远误差越大序列本身的可预测性也越弱。参数说明lookback选20对应大约一个月的交易日但这只是起点。换到分钟级数据可能要看120到240根K线才有意义换到周线20根就是20周。不要机械复制别人的窗口长度要看数据本身的波动周期和采样频率。2.4 按时间切分训练、验证、测试集宁可少训练不可切错数据股票数据的切分原则是一条铁律必须按时间顺序切不能像图像分类那样随机打散。随机打散后验证集和测试集就混入了未来信息这叫未来数据泄漏模型相当于开卷考试回测指标全部失真。常见做法是按行数比例切比如训练占70%、验证占15%、测试占15%。但要注意边界窗口的泄漏切分时最好在接口处留出lookback个不参与训练的安全区。train_ratio, valid_ratio 0.7, 0.15 train_size int(len(X) * train_ratio) valid_size int(len(X) * valid_ratio) X_train, y_train X[:train_size], y[:train_size] X_valid, y_valid X[train_size:train_size valid_size], y[train_size:train_size valid_size] X_test, y_test X[train_size valid_size:], y[train_size valid_size:]这段代码是对构造好的样本直接切片顺手但不够严谨。更稳妥的做法是先按时间索引切原始行情再对训练段、验证段、测试段分别构造序列样本。道理很简单样本切片时最后一个窗口可能横跨边界验证集开头的一批样本吃到的是训练集末尾的上下文。以我的经验先切原始行情再构造序列比先构造序列再切样本更干净能避免验证集和测试集用到训练区间最后一根K线的隐藏上下文。提示切分完成后存下切分索引或日期边界。回测阶段你会频繁回去对比不同时间段的预测表现有边界信息能快速定位问题出在数据段还是模型。3. Keras LSTM建模训练从最小可运行代码到参数自检数据整理干净后建模本身反而是最不费脑的部分。Keras把LSTM封装得很友好几行就能搭起来。但“能跑”和“能用来做预测”是两回事这一章给出一个最小可运行的模型然后拆开讲每个参数为什么这么设。3.1 最小可运行的Keras LSTM回归模型先让代码跑起来完整代码如下依赖tensorflow和numpy。注意Keras版本与TensorFlow版本匹配是常见坑keras安装教程满大街都是但如果你用的TensorFlow 2.x正确做法是from tensorflow.keras导入而不是单独import keras否则版本不一致会出现各种维度算不拢的玄学报错。import numpy as np import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint tf.random.set_seed(42) # 固定随机种子保证结果可复现 model Sequential() model.add(LSTM(units64, return_sequencesTrue, input_shape(X_train.shape[1], X_train.shape[2]))) model.add(Dropout(0.2)) model.add(LSTM(units32, return_sequencesFalse)) model.add(Dropout(0.2)) model.add(Dense(units1)) model.compile(optimizeradam, lossmse, metrics[mae]) model.summary()逻辑说明第一层LSTM的return_sequencesTrue很关键它让这层输出完整的隐藏状态序列而不是只输出最后一个时间步这样第二层LSTM才能继续处理时间维度。第二层return_sequencesFalse只输出最后一个向量接一个全连接层压缩成单值。Dropout放在每个LSTM层之后是常见的防过拟合手段对股票这种噪声大的序列尤其有用。input_shape的第一个维度留空Keras会自动从训练数据推断样本数。参数说明units64是LSTM隐藏状态的维度不是神经元个数。model.summary()会显示每层参数量第一次跑建议看一眼防止维度设错。如果你在这里遇到参数量大到离谱多半是input_shape写错了检查X_train.shape是不是(样本数, lookback, 特征数)。3.2 四个必调参数units、lookback、dropout、学习率这四个参数直接决定模型容量和泛化能力不是越大越好。参数参考范围对结果的影响LSTM units32~128隐藏状态维度过大会过拟合且训练变慢lookback20~60时间窗口长度要和数据采样周期匹配dropout0.1~0.3越大越能防过拟合但太大会欠拟合learning_rate1e-4 ~ 3e-3Adam默认1e-3股票loss较小太大会跳成NaNunits并不是越大越好。股票序列的平稳性有限64左右通常够用128只适合数据量很大的场景。lookback如果数据是日K20对应一个月40对应两个月。股价的短期动量大约在5到20个交易日过长的窗口会把无关噪声也塞进模型。dropout在损失震荡加剧时可以从0.1调向0.3但超过0.4往往会让模型学不到有效结构。学习率踩坑最典型loss出现NaN时第一时间把学习率调到1e-4通常比换模型结构更有效。3.3 模型训练早停、模型快照与训练日志股票数据的训练不能硬跑固定轮数。很多代码让模型跑100轮结果训练集loss降到接近0验证集早就在某个epoch后开始恶化最后保存的权重反而是最差的阶段。常见的做法是用EarlyStopping监控验证集loss配合ModelCheckpoint保存最优权重。early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) checkpoint ModelCheckpoint(best_lstm.h5, monitorval_loss, save_best_onlyTrue) history model.fit( X_train, y_train, validation_data(X_valid, y_valid), epochs100, batch_size64, callbacks[early_stop, checkpoint], verbose1 )逻辑说明EarlyStopping会监视val_loss连续10轮没有改善就停止训练并把权重恢复到验证集最优状态。restore_best_weightsTrue必须开否则停住时保留的是最后一轮的权重而不是最优轮次的权重。ModelCheckpoint把每个轮次中验证集loss最低的模型写入best_lstm.h5这是你的后悔药训练结束后用这个文件做预测而不是直接用内存里的模型对象。参数说明patience10在日K数据上通常够用如果验证loss震荡剧烈可以放到20。batch_size64适合几千到几万条样本数据量小就减到32太大收敛慢太小训练不稳定。如果你的验证loss在第一个epoch就到几百万先别调网络回去检查标签和归一化大概率是数据没喂对。3.4 评估指标方向准确率比MSE更能说明预测是否可用股票预测里MSE低不代表模型有用。因为价格序列大部分时间波动很小模型只需要输出一个接近昨日收盘的值MSE就很低但它完全不知道明天是涨是跌。以我的经验至少要同时看方向准确率就是预测值和真实值变动方向一致的比例。from sklearn.metrics import accuracy_score # 把预测价格和真实价格转成涨跌方向 pred_diff np.sign(np.diff(pred.flatten())) true_diff np.sign(np.diff(y_test.flatten())) acc accuracy_score(true_diff, pred_diff) print(方向准确率: {:.2f}.format(acc))逻辑说明np.diff把价格序列转成相邻两天的差值np.sign把差值映射成1、-1、0分别代表涨、跌、平。然后统计模型方向和真实方向的一致比例。如果横盘样本很多accuracy_score会被“预测平”的样本拉高更严格的做法是过滤掉价格变动小于0.1%的样本再统计。还有一个重要对照用“昨日涨跌预测今日涨跌”作为基线。如果模型的方向准确率比这个naive基线还低那这个模型就是负优化不值得继续调参。训练结束后的预测代码很简单model.predict(X_test)得到的就是连续数值后续再换算成价格或收益。4. 股票预测最容易翻车的5个坑数据泄漏、归一化污染、跳空与除权这一章全部是实操里反复出现的坑每一条我都按“现象 → 原因 → 解决”来写。如果你跑出来的结果一会儿好一会儿坏先对照这里排查。4.1 未来数据泄漏回测准确率90%换段行情就崩现象训练集和验证集上的方向准确率高达80%~90%把模型挪到一段没见过的历史行情里准确率只剩50%甚至稳定亏损。原因最常见的是切分前对整个数据集做了归一化或者把X和y整体随机打乱后切割。这样验证集和训练集共享了同一套统计信息模型等于开卷考试。另一类泄漏更隐蔽构造标签时用了时间t的未来价格又在特征里保留了当日收盘价模型只要复制上一天收盘价就能把MSE做得很低。解决先按时间切原始行情再对训练、验证、测试三段分别构造样本测试段的归一化参数只能来自训练段。严格检查特征列里有没有哪一列直接或间接包含未来信息比如当日已经收盘的close和经过未来复权因子调整的前复权价格它们在回测时就已经引入了未来因子。4.2 归一化污染MinMaxScaler在全量数据上fit导致的开卷考试现象训练loss和验证loss都很好看但把模型部署到新数据上第一天预测值就离谱范围完全错位。原因全量fit的scaler会把整个序列的最高价和最低价写进参数。测试段样本被压缩到和训练段相同的区间里模型只学会了“在有限区间里做外推”的假规律。等真实的新数据进来价格分布稍微偏移预测立刻失真。解决标准流程是先切分再在训练段上fit归一化参数保存scaler然后在测试段和验证段上只调用transform。from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler() # 只对训练段做 fit验证段和测试段只做 transform scaled_train scaler.fit_transform(X_train.reshape(-1, X_train.shape[2])) scaled_train scaled_train.reshape(X_train.shape) def transform_x(X, scaler): shape X.shape return scaler.transform(X.reshape(-1, shape[2])).reshape(shape) X_valid_scaled transform_x(X_valid, scaler) X_test_scaled transform_x(X_test, scaler)逻辑说明fit_transform只用在训练段把归一化的最小值最大值锁定在训练集上。验证段和测试段的transform使用同样的参数这样就能模拟“未来数据不被提前看到”的真实推理过程。如果换了新行情后预测值普遍偏小或偏大多半是scaler被新数据“带偏”检查是不是在切分前全量fit了。4.3 开盘跳空与涨跌停LSTM天生不擅长预测价格跳跃现象测试集上loss在每根跳空K线处突然爆高整体指标被少数极端样本拖着走。原因次日开盘价相对今日收盘价的跳空是由盘后消息、集合竞价、流动性和情绪决定的K线自身的收盘价序列里没有这些信息。LSTM在时间维度上默认相邻状态是平滑过渡的遇到不连续跳跃会学得很难受。涨跌停更极端价格被封在板位上特征里只剩连续性截断。解决优先把标签从“绝对价格”改成“收益率”。具体做法是用close_adj.pct_change()生成收益率列作为预测目标模型学的是回报增量而不是绝对价格跳空也被纳入收益计算损失不会瞬间爆炸。训练时还可以按阈值筛掉极端涨跌停样本保留常见行情训练主体然后在评估时单独统计跳空日表现。4.4 除权除息和复权不一致数据断崖让模型误判暴涨暴跌现象数据在每年分红除权日出现一条断崖式下跌模型把它当成恐慌性抛售于是频繁输出“继续下跌”的悲观信号。原因用未复权数据训练但预测时下载的最新价是未复权或者训练段和验证段使用了不同的复权口径。前复权数据会随最新价不断变化后复权数据则不会混用会导致模型学到一个假断崖。解决统一用一种复权口径。常见做法是统一前复权把历史价格全部按当前除权因子换算。如果数据接口提供复权因子优先用因子计算如果没有就找带adj_close的数据源。最重要的纪律是训练和预测必须走同一条复权链路这一点在换数据源或者跨时间回测时特别容易忽略。4.5 标签漂移与样本不平衡模型学会了“永远输出横盘”现象方向准确率看似不错但把预测结果画出来几乎是水平线完全不跟随真实行情波动。原因如果标签是“未来N天涨跌”二分类而历史行情大部分时间波动很小模型发现输出“不涨不跌”的代价最低于是它变成了一个只会输出均值的录音机。解决过滤每日绝对涨跌幅小于特定阈值的样本或改成回归未来收益率并配合方向准确率一起看。更稳的做法是做多步预测评估不只盯单日方向。比如把未来5天的累计收益作为标签让模型必须做出有方向的判断而不是靠输出接近均值来糊弄损失函数。5. 让模型真正“用起来”滚动预测、模型快照回退与参数自检模型训完不是终点要落地到连续预测场景还差一个关键技巧滚动预测。静态预测只测一次测试集得到的指标好看但不够真实真实使用里你会不断拿到新K线每根新K线都要重新推理、重新评估。5.1 滚动预测循环每来一根新K线就重新预测一次滚动预测的思路是每次都只用截至当前时刻的历史数据构造窗口预测完就丢弃结果等下一根K线补进来后再重复。这样回测出来的表现比一次性预测更接近真实部署。def rolling_predict(model, scaler, df, lookback20, horizon1): preds [] data df[[close_adj, open_adj, volume]].values # 每根K线到来时取最近 lookback 根做预测 for i in range(lookback, len(data) - horizon 1): window data[i - lookback:i] scaled scaler.transform(window) X scaled.reshape(1, lookback, -1) pred model.predict(X, verbose0) preds.append(pred.flatten()[0]) return np.array(preds)逻辑说明scaler.transform(window)里的scaler必须是在训练段fit过的同一个实例不能在循环里重新fit。每轮预测只依赖截至i时刻的历史数据下一轮窗口右移一根K线这就是逐步推进的滚动回测。参数说明horizon1时预测的是下一根K线如果要看未来多步就把预测输出连续拼接但误差会随步长递增。以我的经验固定参数滚动预测比每天重训练更接近真实使用习惯每天重训练不仅慢还会让模型产生灾难性遗忘。5.2 复现前先做四件事参数自检清单无论你是拿这套方案去跑自己的数据集还是对照别人的源代码做实验动手前先过一遍这张表。大部分“为什么代码一跑就崩、一换数据就废”的问题出在外围而不在LSTM本身。检查项通过标准训练/验证/测试按时间切分切分点前无未来数据接口处留lookback安全区归一化只fit训练段验证段和测试段只调用transform复权口径统一训练、验证、测试全部同一种复权方式方向准确率高于naive基线低于50%时检查标签和样本构造这几项不通过后面调多少参数都是原地打转。换数据集重训练时这套自检同样适用只是要把归一化参数、lookback、标签字段重新按新数据风格调整一遍。不要指望源代码里的参数能原样迁移到另一个股票池数据分布变了窗口和阈值都要重新验证。我自己的习惯是保留训练过程中验证集loss最低的模型快照而不是最后一个epoch的权重。原因很简单股票序列噪声太大最后一步权重往往已经在验证集上过拟合了。如果你在回测时发现结果不稳定第一反应不是改模型结构而是先检查数据切分、归一化和复权口径这三关过了再谈调参。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?