ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Python的股票价格走势预测:从数据准备到LSTM回测

基于Python的股票价格走势预测:从数据准备到LSTM回测 简介这是一套基于TensorFlow实现的股票价格走势预测示例面向对金融数据挖掘、时间序列预测感兴趣的Python开发者和量化分析初学者。项目通过tushare接口获取股票历史行情并对缺失值、日期索引等做必要处理利用pandas完成数据清洗、格式转换与特征整理再以TensorFlow搭建并训练模型当前版本可预测第二天的开盘价matplotlib则用于在同一幅图中叠加真实价格曲线与预测价格曲线便于直观对比模型效果。压缩包内共5个文件包含2个Python脚本模型加载与测试以及训练好的checkpoint、index等模型权重文件整体仅456KB轻量易用资源结构简洁下载后即可对照演练。目前已有2142人学习浏览是一份适合入门的小型实战参考读者可借此熟悉tushare数据获取、pandas数据处理、TensorFlow模型保存与加载、结果可视化等完整流程即使对深度学习不熟悉也能通过示例脚本快速上手。1. 从压缩包到可信预测python股票价格走势预测到底在预测什么拿到“基于python实现股票价格走势预测.zip”这类项目解压后通常是十几个脚本加一段 README。跑通很容易但把预测曲线和真实行情叠在一起看你会发现训练集上拟合得越漂亮的东西实盘往往越没用。这个方向真正的难点从来不是模型而是数据流水线和评估方式用什么数据、构造什么标签、怎么切分时间序列、怎么防止未来的信息混进训练集。这篇笔记的目标是帮你从零搭一套能跑、能回测、能判断“到底有没有用”的股票价格走势预测流程。新手跟着步骤能把环境配好、把代码跑通熟手重点看第 3、4、5 章滚动前推的评估方式和常见的未来函数泄漏问题是这类项目里最容易翻车的地方。2. 数据准备与特征工程先把价格序列变成模型能学的东西2.1 拉取数据用 yfinance 抓日线并处理复权股票预测项目的第一步是拿到干净的历史行情。常见做法是用 yfinance 拉 Yahoo Finance 的日线数据A 股代码带后缀例如贵州茅台是600519.SS美股直接写AAPL。先解决环境问题python 安装完成后在终端里用pip install yfinance pandas numpy把依赖装上用 VSCode 配置好 python 环境后直接在集成终端里跑省去路径和解释器切换的麻烦。import yfinance as yf import pandas as pd # 拉取贵州茅台最近5年的日线数据 df yf.download(600519.SS, start2019-01-01, end2024-12-31) # 只保留需要的列并删除缺失值 df df[[Open, High, Low, Close, Volume]].dropna() df.columns [open, high, low, close, volume] # 复权处理yfinance 的 auto_adjustTrue 默认会返回复权后的价格 df yf.download(600519.SS, start2019-01-01, end2024-12-31, auto_adjustTrue) df df[[Open, High, Low, Close, Volume]].dropna() df.columns [open, high, low, close, volume] print(df.tail())逻辑说明auto_adjustTrue会把历史价格按分红、拆股做后复权调整否则遇到除权除息日K 线上会出现一根突兀的“暴跌”模型会把它当成真实的价格跳变来学习预测结果自然失真。dropna()删除停牌或数据缺失的行避免空值进入后续计算。参数说明start和end可按需调整但建议至少取 2000 根以上日线否则模型没有足够的样本学习不同市场状态。如果只拉一年数据训练集里基本只覆盖一种行情单边上涨或单边下跌模型泛化能力会很差。2.2 构造标签用未来 N 日收益率代替“涨/跌”分类很多入门代码把任务定义成二分类——明天涨输出 1明天跌输出 0。这个做法有个问题涨 0.1% 和涨 5% 都被算作“涨”模型学不到幅度信息预测结果也没法直接指导仓位。更合理的做法是预测未来 N 日的收益率让模型输出一个连续值。import numpy as np df[ret_future] df[close].shift(-5) / df[close] - 1.0 # 删除最后5行它们没有未来5日的收益作为标签 df df.dropna() # 特征列与标签列分离 feature_cols [open, high, low, close, volume] X df[feature_cols].values y df[ret_future].values print(f样本数: {len(df)}特征数: {len(feature_cols)}) print(f标签范围: {y.min():.4f} ~ {y.max():.4f})逻辑说明shift(-5)把未来的收盘价往当前行移动ret_future表示“从今天收盘持有 5 天后的收益率”。用连续收益率做标签模型既能学到方向也能学到幅度回测时可以直接按预测收益率排序选股。参数说明5代表预测周期可以改成1次日或20月线级别。周期越长标签噪声越小但训练样本的有效数量也会下降。这里有一个折中经验做日频策略用 3 到 5 天做低频配置用 20 天。2.3 技术指标特征用 talib 生成 RSI、MACD、ATR只用 OHLCV 五个原始特征模型很难学到趋势和波动状态。常见做法是用 TA-Lib 生成技术指标作为额外特征。安装 TA-Lib 前需要先装 C 库Windows 用户可以直接pip install TA-LibLinux 用户建议用apt install libta-lib-dev避免编译报错。import talib # 在原始数据基础上追加指标特征 df[rsi] talib.RSI(df[close], timeperiod14) df[macd], df[macd_signal], df[macd_hist] talib.MACD( df[close], fastperiod12, slowperiod26, signalperiod9 ) df[atr] talib.ATR(df[high], df[low], df[close], timeperiod14) # 删除指标计算产生的 NaN df df.dropna() feature_cols [open, high, low, close, volume, rsi, macd, macd_signal, macd_hist, atr] X df[feature_cols].values y df[ret_future].values print(df[feature_cols].head())逻辑说明RSI 刻画超买超卖状态MACD 表示趋势动能ATR 表示波动幅度。这三个指标覆盖了趋势、动量、波动三个维度比单纯堆二十个指标更不容易过拟合。参数说明指标参数使用默认值14、12/26/9是常见做法不需要反复调优。原因在于技术指标本身就是对价格的降维变换参数微调带来的边际收益远小于数据泄漏带来的破坏。特征加入后用 python 的np.isnan()检查一遍防止指标计算在前几行产生缺失值。3. 模型选型与训练参数为什么优先用 LSTM 而不是直接上 Transformer3.1 股票数据适合用 LSTM 的三个理由股票价格走势预测的建模对象是时间序列样本之间存在顺序依赖这正是循环神经网络RNN和 LSTM 的设计场景。LSTM 通过门控机制保留长期信息能学到“连续几天放量上涨后可能回调”这类跨时间步的模式。相比之下普通全连接网络把每一天当成独立样本完全丢掉了顺序关系预测效果通常更差。Transformer 近年在时间序列领域表现不错但在股票数据上有一个现实问题股票日线数据量通常只有几千条而 Transformer 是数据饥渴型模型样本不足时训练不稳定很容易过拟合。常见的做法是先用 LSTM 搭基线如果后期数据量扩充到分钟级几十万条再考虑换 Transformer 做对比实验。模型所需数据量时序依赖建模训练稳定性适用场景全连接网络少无高特征工程极强时的基线LSTM中有中日线级别的常规预测任务Transformer多有低分钟级高频、样本量充足时3.2 构造三维输入把数据切成“时间步 × 特征”LSTM 的输入形状是三维的(样本数, 时间步, 特征数)。时间步表示模型每次回溯多少天的历史特征数对应上一章构造的特征列数量。切片操作是这里的核心用 python 数组切片把一条长序列切成长度为lookback的窗口序列。def create_sequences(X, y, lookback20): X_seq, y_seq [], [] for i in range(len(X) - lookback): X_seq.append(X[i:i lookback]) y_seq.append(y[i lookback]) return np.array(X_seq), np.array(y_seq) lookback 20 X_seq, y_seq create_sequences(X, y, lookback) # 按时间顺序切分训练集/验证集/测试集比例 7:1.5:1.5 split1 int(len(X_seq) * 0.7) split2 int(len(X_seq) * 0.85) X_train, X_val, X_test X_seq[:split1], X_seq[split1:split2], X_seq[split2:] y_train, y_val, y_test y_seq[:split1], y_seq[split1:split2], y_seq[split2:] print(f训练集形状: {X_train.shape}, 验证集形状: {X_val.shape}, 测试集形状: {X_test.shape})逻辑说明切分时必须按照时间顺序不能像普通分类任务那样随机打乱否则未来的数据会混进训练集造成未来函数泄漏。create_sequences里的X[i:i lookback]取的是第 i 天到第 i20 天的数据作为特征y[i lookback]对应的是第 i20 天之后的收益严格保持“用过去预测未来”。参数说明lookback20对应一个月交易日覆盖一个相对完整的短期趋势周期。切分比例 7:1.5:1.5 是时间序列任务里常用的分配方式测试集保留最近 15% 的样本模拟“用过去训练、在最近行情上验证”的真实场景。3.3 归一化与模型定义把特征缩放到同一量级股票特征里价格是几十到几千成交量是几百万RSI 是 0 到 100直接喂给模型会让优化器把注意力全放在量级大的特征上。常见做法是用标准缩放StandardScaler把每个特征变成均值为 0、方差为 1 的分布。from sklearn.preprocessing import StandardScaler # 特征归一化注意只在训练集上 fit防止验证集信息泄漏到训练过程 scaler StandardScaler() # X 是三维的需要先 reshape 成二维再 fit n_samples, n_steps, n_features X_train.shape X_train_2d X_train.reshape(-1, n_features) X_train_scaled scaler.fit_transform(X_train_2d).reshape(n_samples, n_steps, n_features) # 验证集和测试集使用训练集的 scaler 做 transform X_val_scaled scaler.transform( X_val.reshape(-1, n_features) ).reshape(X_val.shape[0], X_val.shape[1], n_features) X_test_scaled scaler.transform( X_test.reshape(-1, n_features) ).reshape(X_test.shape[0], X_test.shape[1], n_features)逻辑说明fit_transform只用在训练集上验证集和测试集直接调用transform这一点是股票预测项目里最容易忽略的泄漏源。如果对整个数据集先归一化再切分训练过程中相当于偷看了未来数据的均值和方法回测指标会虚高实盘立刻现出原形。参数说明StandardScaler适用于大多数量纲差异大的特征场景。如果特征包含大量离群点也可以换RobustScaler它对极端值的抗性更强但绝大多数日线数据用 StandardScaler 就够了。3.4 搭建 LSTM 回归模型输出层不加激活函数标签是连续收益率因此输出层应该是一个线性单元而不是 softmax 或 sigmoid。模型结构用两层 LSTM 加一个全连接层Dropout 放在 LSTM 层之间用来抑制过拟合。import tensorflow as tf model tf.keras.Sequential([ tf.keras.layers.LSTM(64, return_sequencesTrue, input_shape(lookback, n_features)), tf.keras.layers.Dropout(0.2), tf.keras.layers.LSTM(32, return_sequencesFalse), tf.keras.layers.Dropout(0.2), tf.keras.layers.Dense(16, activationrelu), tf.keras.layers.Dense(1) # 回归头输出预测收益率 ]) model.compile( optimizertf.keras.optimizers.Adam(learning_rate0.001), lossmse, metrics[mae] ) model.summary()逻辑说明第一层 LSTM 设置return_sequencesTrue把完整序列传给第二层第二层return_sequencesFalse只输出最后一个时间步的隐藏状态。Dense 层用 ReLU 提取非线性特征最后一层不加激活函数让模型自由输出任意实数对应预测收益率可正可负的特性。参数说明learning_rate0.001是 Adam 优化器在序列预测任务里的常用起点。如果训练损失震荡不下降可以降到 0.0003如果收敛太慢可以提到 0.003但要配合早停机制防止发散。batch_size默认 32数据量小少于 5000 条时建议调到 16让梯度更新更稳定。4. 滚动预测与回测评估把模型放到真实交易节奏里检验4.1 滚动前推训练集随时间向前移动静态切分有个问题模型只训练一次然后去预测最近几个月的行情。但市场状态会变模型需要不断吸收新数据。常见的做法是滚动前推walk-forward validation每次用最近 3 年的数据训练预测下一个月然后整个窗口向前推进一个月重复这个流程。def walk_forward_predict(df, feature_cols, lookback20, train_months36, test_months1): all_preds, all_trues [], [] step 21 # 约一个月交易天数 start_idx lookback 252 * train_months # 一年约252个交易日 i start_idx while i step len(df): train_df df.iloc[i - 252 * train_months - lookback:i] test_df df.iloc[i:i step] # 构造序列并归一化 X_tr, y_tr create_sequences(train_df[feature_cols].values, train_df[ret_future].values, lookback) X_te, y_te create_sequences(test_df[feature_cols].values, test_df[ret_future].values, lookback) scaler StandardScaler() X_tr_2d X_tr.reshape(-1, len(feature_cols)) X_tr_scaled scaler.fit_transform(X_tr_2d).reshape(X_tr.shape) X_te_scaled scaler.transform(X_te.reshape(-1, len(feature_cols))).reshape(X_te.shape) # 训练模型 model tf.keras.Sequential([ tf.keras.layers.LSTM(32, return_sequencesTrue, input_shape(lookback, len(feature_cols))), tf.keras.layers.Dropout(0.2), tf.keras.layers.LSTM(16, return_sequencesFalse), tf.keras.layers.Dense(1) ]) model.compile(optimizeradam, lossmse) # 每个窗口只训练 10 个 epoch减少验证集过拟合风险 model.fit(X_tr_scaled, y_tr, epochs10, batch_size16, verbose0) preds model.predict(X_te_scaled, verbose0).flatten() all_preds.extend(preds) all_trues.extend(y_te) i step return np.array(all_preds), np.array(all_trues)逻辑说明循环中的train_df截取是从当前窗口往前 3 年的数据test_df是接下来 1 个月的行情。每次滚动都重新训练模型模拟真实迭代节奏。这里有意把模型结构设成单层 32 单元的轻量版本因为每个窗口只训练 10 个 epoch模型结构太重反而学不完。参数说明train_months36表示用 3 年数据训练test_months1表示每次预测 1 个月step21是 1 个月的平均交易日数。这套参数组合在大多数日线数据上运行时间可控约几分钟同时保证测试结果覆盖多种市场状态。4.2 评估指标方向准确率、IC、累计收益曲线跑完滚动预测后需要一组能说明问题的指标。方向准确率是最直观的——预测收益率和真实收益率同号的比例。信息系数IC衡量预测值与实际值的秩相关性比方向准确率更细腻。累计收益曲线能直观展示策略净值走势但要同时画出基准线对照。from scipy.stats import spearmanr # 方向准确率 direction_acc np.mean((all_preds 0) (all_trues 0)) print(f方向准确率: {direction_acc:.4f}) # IC预测值与真实值的 Spearman 秩相关系数 ic, p_value spearmanr(all_preds, all_trues) print(fIC: {ic:.4f}, p_value: {p_value:.4f}) # 模拟累计收益按预测值符号做多/空每期收益等于真实收益率 strategy_returns np.where(all_preds 0, all_trues, 0) # 只做多 cumulative np.cumprod(1 strategy_returns) print(f累计净值: {cumulative[-1]:.4f})逻辑说明np.where(all_preds 0, all_trues, 0)模拟的是“预测涨就持有预测跌就空仓”的最简单策略。真实交易里还要考虑手续费和滑点但这里的目的只是评估预测信号本身是否有效不急着加交易成本。参数说明IC 的绝对值大于 0.05 就可以认为有一定预测力大于 0.1 属于较强的信号。注意 p_value 要小于 0.05 才说明相关性显著否则几千次预测里出现一点正相关可能是噪声。方向准确率在 55% 以上配合正 IC才有继续优化的价值。4.3 和朴素基线对比随机预测与“明天等于今天”任何预测模型都必须跑赢一个最简单的基线用当天的收益率作为明天的预测值动量策略。如果 LSTM 连这个基线都跑不赢说明模型没有学到超越“价格延续性”的信息。# 基线预测值等于当天收益率 baseline_preds np.roll(all_trues, 1) baseline_preds[0] 0 baseline_direction_acc np.mean((baseline_preds 0) (all_trues 0)) baseline_ic, _ spearmanr(baseline_preds, all_trues) print(f基线方向准确率: {baseline_direction_acc:.4f}) print(f基线IC: {baseline_ic:.4f}) print(fLSTM方向准确率: {direction_acc:.4f}) print(fLSTM IC: {ic:.4f})逻辑说明np.roll把真实收益率的序列向后移一位模拟“用今天的涨跌预测明天的涨跌”这个朴素思路。如果 LSTM 的指标和基线接近大概率是因为模型学到了“价格延续性”这个本来就存在于序列中的统计规律而不是发现了新的交易信号。参数说明这个对比必须在同一个测试集上进行即使用相同的all_trues。如果基线的方向准确率已经达到 53%而 LSTM 只有 54%差距在统计上通常不显著不能说明模型有效。5. 避坑股票价格预测里最常见的 5 个翻车场景5.1 训练损失很低实盘回测却稳定亏损现象模型在训练集上损失降到极低验证集曲线拟合得非常漂亮但放到最近几个月的真实行情里回测收益曲线一路向下。原因数据泄漏。最常见的是归一化时对全样本做了fit_transform或者构造标签时用了包含未来信息的特征比如把未来 N 天的均线当特征模型本质上在“抄答案”。解决严格按时间顺序切分归一化只在训练集上fit测试集只transform。构造特征时逐个检查是否用到了当前时间点之后的数据。5.2 换一只股票就完全失效现象在贵州茅台数据上训练好的模型直接拿去预测五粮液或招商银行方向准确率掉到 50% 以下和抛硬币一样。原因每一只股票的波动特性、交易量分布、趋势特征都不同模型把个股特有的规律学成了通用规律。全市场统一训练的模型在个股上表现通常也更差。解决不要跨股票直接迁移模型。如果目标是多只股票就把多只股票的数据合并训练并加入股票代码作为特征如果是单股票预测每只股票从头训练超参数用验证集重选。5.3 预测值总是比真实行情滞后一天现象把预测曲线和真实价格曲线叠在一起看预测走势整体向右平移了一天看着非常像但现实中根本没法交易。原因标签构造时可能无意中用了当天的收盘价预测当天的收益率即未来函数或者特征里包含当天收盘后才能确定的值却被当成盘前已知信息。滞后看起来像“预测准了”其实是模型在复现昨天的价格。解决检查标签列是否用了shift(-N)做对齐特征列是否全部来自 T 日及之前。严格测试把预测时点假设为 T 日收盘预测 T1 日到 TN 日的收益所有特征都必须是 T 日收盘前能拿到的值。5.4 训练时损失值剧烈震荡模型不收敛现象训练过程中 loss 忽高忽低验证集指标波动很大训练一轮后模型预测结果全是同一个常数。原因学习率过大、序列长度过长、数据中极端值未处理。股票数据偶有涨停或跌停收益率分布尾部极厚一次极端值就能把梯度的方向带偏。解决把学习率降到 0.0001 到 0.0003lookback降到 20 以内收益率先做截断例如把超过 ±20% 的收益率压缩到 ±20%再检查归一化后的特征是否有极端离群点。5.5 除权除息日出现异常暴跌现象回测净值曲线在某一天突然向下跳空但真实行情并没有出现这么大的跌幅。原因没有使用复权价格。股票分红除息后历史价格会向下调整如果直接用原始价格除息日当天会出现一根人为制造的下跌 K 线模型会把这种事件当成真实下跌信号。解决拉数据时设置auto_adjustTrue或者拉取后手动用复权因子计算。检查复权结果的标准是复权后价格序列中不应该出现分红日前后的大幅跳空缺口。6. 从预测到可用验证预测质量的三个硬标准预测模型跑完后先别急着和真实行情对比。我通常会做三个验证任何一个不合格就退回前面的步骤重新检查。第一个验证是收益分布分层测试。把预测值按大小分成 5 组例如按分位数分为 Q1 到 Q5分别计算每组真实收益率的均值。如果预测有效Q5预测收益最高的一组的真实平均收益应该显著高于 Q1预测收益最低的一组并且呈近似单调递增。这个测试比单纯看方向准确率更能反映预测的排序能力因为交易信号的核心不是猜对涨跌而是能区分“更可能涨”和“不太可能涨”的股票。第二个验证是预测残差的自相关检验。对预测值和真实值的差值残差做自相关分析如果残差在某些滞后阶数上表现出显著相关性说明模型遗漏了重要的时序模式还有改进空间。这里可以用一个简单的 python 循环计算滞后 1 到 10 天的残差自相关系数超过 ±0.1 就要检查特征集是否缺了对应的因子。第三个验证是模拟交易的稳健性测试。把模型预测信号用在不同的标的、不同的起始时间上重复回测观察收益曲线是否只在某一段特定行情里有效。我把这个测试叫做“退后一步看曲线”——放大到 3 年尺度如果曲线中的大部分收益来自某一段单边上涨行情那模型学到的大概率是趋势延续性而不是真正的选股能力。落地上我还有一个死规矩每个预测模型必须同时保存训练日期、数据范围和当时的验证指标。因为市场状态会漂移三个月前的有效模型很可能已经失效没有这些元数据你就不知道什么时候该重训、该用哪版本的去实盘。我习惯把这个信息直接写到模型文件名里比如lstm_600519_20250101_acc0.56.h5下次看到就能判断这个模型的“保质期”是否已过。这个方向的底线是预测模型只是信号生成器不是印钞机。把评估做扎实比把模型做复杂更重要。希望帮到你。本文还有配套的精品资源点击获取
返回列表