TensorFlow 2.0 与 LSTM:用 tushare 行情数据预测茅台开盘价实战
发布时间:2026/10/1 23:35:24来源:尧图网络
简介面向金融数据挖掘与深度学习入门人群的实践型资源演示如何用tushare抓取贵州茅台历史行情并在TensorFlow 2.0框架下搭建RNN与LSTM模型预测开盘价。压缩包共5个文件包含数据获取脚本、RNN预测脚本、LSTM预测脚本各一个另附历史数据CSV和README说明文档整体仅56KB轻量便捷适合快速阅读与二次修改。代码覆盖数据预处理、日期整数编码、开盘价归一化、模型构建、Adam优化训练及误差评估等环节LSTM部分突出门控机制对长期依赖的捕捉便于对比两类循环网络在股价序列上的表现。已有1912人学习下载可直接替换股票代码与时间窗口迁移到其他品种或指标是理解时序预测与TensorFlow 2.0实战的实用参考。1. 用 tushare 喂给 TensorFlow 2.0RNN 与 LSTM 预测茅台开盘价到底靠不靠谱用 tushare 把贵州茅台的日线数据拉下来再在 TensorFlow 2.0 里分别搭 RNN 和 LSTM 预测下一日开盘价——这个项目我在本地从头到尾跑通过一遍。先说结论纯历史行情喂给循环模型对短周期开盘价能学到一定规律但指望它稳定战胜市场不太现实这个项目真正的价值在于把「tushare 取数 → 序列预处理 → TensorFlow 2.0 建模 → 训练评估」这一整条链路完整走通了。适合刚开始接触时间序列预测、手头需要一份真实股票数据练手的开发者。项目自带 SH600519.csv 和两个 Python 脚本照着跑就能出预测曲线。2. 数据准备把 tushare 的日线变成模型真正能吃的序列2.1 拉取与清洗token 配置、字段筛选和交易日索引tushare 目前主推的是 pro 版本接口先要注册拿到 token再调用ts.pro_api()。日常拉日线用pro.daily()就够了接口返回的是 DataFrame字段比老版get_k_data全很多包括开高低收、昨收、涨跌幅、成交量、成交额。老接口虽然不用积分但数据字段少、维护状态不明确不推荐在这个项目里用。import tushare as ts import pandas as pd ts.set_token(你的tushare_token) pro ts.pro_api() df pro.daily(ts_code600519.SH, start_date20150101, end_date20231231) df df.sort_values(trade_date) # tushare 默认按日期倒序必须升序 df.to_csv(SH600519.csv, indexFalse) print(df.head())这段代码做完三件事配置 token、按 ts_code 拉取贵州茅台日线、按 trade_date 升序排列后落盘。ts_code的格式是「交易所代码.市场后缀」上交所股票后缀是.SH。trade_date是YYYYMMDD字符串直接字符串排序碰巧和日期排序一致但要养成显式排序的习惯避免后续和 datetime 混用时翻车。这里有个新手最容易卡住的点pro.daily()有积分门槛注册后基础积分低直接调用会报权限错误。常见做法是先用项目自带的 SH600519.csv 把流程跑通再去 tushare 官网攒积分开接口权限。如果非要用接口老版ts.get_k_data(600519)不需要积分但字段少、接口稳定性一般只适合拿来做数据下载的练习。字段含义单位/说明open开盘价元high / low最高价 / 最低价元close收盘价元pre_close前收盘价元change / pct_chg涨跌额 / 涨跌幅元 / 百分比vol成交量手amount成交额千元vol单位是手amount单位是千元这个细节容易在后续做特征工程时算错量纲。tushare 的日线数据是「有交易日才有记录」停牌日直接没有这一行。这里不要用自然日去补全否则等于往序列里塞了大量重复价格模型学到的是「价格不变」的假规律。后面避坑章节会展开讲。2.2 归一化、滑动窗口与数据划分为什么开盘价在第 0 列模型输入不能直接用原始价格。茅台开盘价一千多块成交量几十万手数值量级差太多直接丢进神经网络会让梯度被大数值特征主导。常规做法是 MinMaxScaler 把所有特征压到 01 区间。特别注意scaler 只能 fit 在训练段上不能先对全量数据 fit 再做切分否则测试集的 min/max 已经参与了缩放等价于让模型偷看了未来统计量。import numpy as np import pandas as pd from sklearn.preprocessing import MinMaxScaler data pd.read_csv(SH600519.csv) features data[[open, high, low, close, vol, amount]].values # 先按时间顺序切分原始数据再做归一化 split_idx int(len(features) * 0.8) train_raw features[:split_idx] test_raw features[split_idx:] scaler MinMaxScaler(feature_range(0, 1)) scaler.fit(train_raw) # 只在训练段上学习 min/max train_scaled scaler.transform(train_raw) test_scaled scaler.transform(test_raw) def make_sequences(data, lookback30): X, y [], [] for i in range(lookback, len(data)): X.append(data[i-lookback:i]) # 过去 lookback 个交易日 y.append(data[i, 0]) # 预测第 i 日的开盘价open 在第 0 列 return np.array(X), np.array(y) X_train, y_train make_sequences(train_scaled) X_test, y_test make_sequences(test_scaled) print(X_train.shape, y_train.shape)这里有两个关键选择。第一特征列顺序固定为[open, high, low, close, vol, amount]所以 open 在索引 0后续取data[i, 0]就是第 i 日的开盘价。第二lookback30表示用过去 30 个交易日预测下一个交易日对应大约一个半月的交易天数是股票序列预测里比较常见的窗口长度。窗口太短如 5模型只能看到短期波动窗口太长如 120样本量急剧下降且容易过拟合。划分比例 8:2 是按行切分不是按日期切分。代码里split_idx取总行数的 80%前 80% 做训练后 20% 做验证。这里绝对不能随机 shuffle时间序列一旦打乱模型等于既看了过去又看了未来训练出来的 loss 低得没有意义。提示做完这一步检查一下X_train.shape。正常结果是(样本数, 30, 6)三个维度分别对应样本数、时间步数、特征数。如果看到的是二维说明滑窗逻辑有问题后面喂给 LSTM 一定会报维度不匹配。3. 上手 TensorFlow 2.0SimpleRNN 与 LSTM 两个模型对比实现3.1 RNN 基线一层 SimpleRNN 加 Dropout先把流程跑通RNN 循环神经网络的特点是每个时间步的隐藏状态会往后传递理论上能捕捉序列中的前后依赖。但标准 RNN 对长序列存在梯度消失问题30 个时间步不算特别长用它做基线模型是合理的起步选择。项目里的 rnn_stock.py 用 Keras Sequential 搭模型几行代码就能出来。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import SimpleRNN, Dense, Dropout model_rnn Sequential([ SimpleRNN(64, activationtanh, return_sequencesFalse, input_shape(30, 6)), Dropout(0.2), Dense(1) ]) model_rnn.compile(optimizeradam, lossmse, metrics[mae]) model_rnn.summary()SimpleRNN的units64是隐藏状态维度64 起步是常见做法32 偏小、128 训练慢。activationtanh是循环层的标准激活函数不需要额外纠结。return_sequencesFalse表示只返回最后一个时间步的输出因为最终目标是预测一个具体的开盘价数值不需要保存完整的时间步序列。input_shape(30, 6)对应前面滑窗构造的 30 个时间步、6 个特征。Dropout(0.2)在循环层输出之后加用来压过拟合。股票序列噪声大RNN 参数量不多但同样容易记住训练集上的随机波动。lossmse是回归问题的默认选择metrics[mae]是为了看平均绝对误差数值更直观。整个 SimpleRNN 单层 64 单元的参数量不到一万训练速度非常快CPU 上几十秒就能跑完一个 epoch。这个模型最大的意义是作为基线。不要指望它给出多惊艳的预测而是拿它的 loss 和后面 LSTM 的结果做对比验证 LSTM 的门控机制到底有没有带来实质提升。如果 RNN 和 LSTM 结果几乎一样说明问题不在模型结构而在数据或滑窗构造上。3.2 LSTM 翻版双层的门控机制更适合价格序列但参数量翻倍LSTM 通过输入门、遗忘门、输出门控制信息保留和丢弃能有效缓解梯度消失。股票价格序列里既有长期趋势也有短期波动LSTM 理论上比 SimpleRNN 更适合这类数据。项目里的 LSTM_stock.py 采用了双层 LSTM 结构第一层返回完整时间步序列给第二层继续提取特征。from tensorflow.keras.layers import LSTM model_lstm Sequential([ LSTM(64, return_sequencesTrue, input_shape(30, 6)), Dropout(0.2), LSTM(32, return_sequencesFalse), Dense(1) ]) model_lstm.compile(optimizeradam, lossmse, metrics[mae]) model_lstm.summary()第一层LSTM(64, return_sequencesTrue)的return_sequencesTrue是双层 LSTM 的关键必须输出所有时间步的隐藏状态第二层才能继续按序列处理。第二层LSTM(32, return_sequencesFalse)只输出最后一步直接接全连接层输出预测价格。参数量上单层 LSTM 因为内部有四个门控结构大约是同样 units 的 SimpleRNN 的四倍。双层 6432 的话总参数量在三万左右训练速度明显比 RNN 慢但也没有到不能接受的程度。训练时加上 EarlyStopping 和模型保存是这类项目的标准配套。归一化后的 MSE 在 0.0010.003 区间算是正常水平对应真实价格误差大约在几十元。如果验证集 loss 始终降不下去先回去检查数据别急着调网络结构。from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint callbacks [ EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue), ModelCheckpoint(best_lstm.h5, monitorval_loss, save_best_onlyTrue) ] history model_lstm.fit( X_train, y_train, epochs50, batch_size32, validation_data(X_test, y_test), callbackscallbacks, verbose1 )epochs50搭配patience10的意思是最多训练 50 轮但连续 10 轮验证集 loss 不下降就提前停并恢复最优权重。这个组合能在过拟合之前「踩刹车」。batch_size32是常规选择数据量不大时不用刻意调。validation_data直接用之前按时间切出来的后 20% 数据每轮训练结束都能看到验证集指标。模型循环层参数量级训练速度适用判断SimpleRNN64 units × 1约 1 万快流程基线验证链路通不通LSTM64 32 units × 2约 3 万中等正式预测捕捉中长期依赖4. 避坑股票时间序列预测里我踩过的五个坑第一个坑把日期当成特征喂给模型。现象把 trade_date 归一化后拼进特征矩阵训练时 loss 一直居高不下预测曲线几乎是一条水平线。原因日期编码和价格之间没有可学习的因果关系归一化后的日期接近单调递增模型只学到了一个无意义的时间趋势项。解决只保留 ohlcv 和成交量特征日期只做索引不做输入。摘要里提到的「将日期转换为连续的整数编码」在早期版本里常见但这个思路现在基本被放弃建议直接丢弃日期列。第二个坑Keras 报输入维度不匹配。现象执行 fit 时抛出expected ndim3, found ndim2。原因DataFrame 是二维结构直接传进去等于把每个样本当成一个独立时间步根本没有构造(样本数, 时间步, 特征数)的三维张量。解决先跑make_sequences滑窗函数再打印X.shape确认是三维最后才进模型。第三个坑预测曲线整体延迟一天看起来贴合度极高但其实是假象。现象把预测值和真实开盘价画在一起两条曲线几乎重合但整体向右平移了一天MAE 还异常低。原因模型其实在拟合「昨日收盘价 → 今日开盘价」的跳空关系本质上是拿前一天的信息做无脑预测并没有学到多日模式。解决用「上一交易日收盘价当作今日开盘价的预测值」做一个无脑基准线如果模型跑不赢这条基准线说明学到的东西没有增量价值。第四个坑全样本归一化造成数据泄漏。现象训练时 loss 很漂亮但拿模型去预测「只有过去数据」的真实场景误差明显变大。原因MinMaxScaler 在全部数据上 fit测试段的 min/max 参与了缩放相当于模型间接看到了未来统计量。解决严格按照「先按时间切分 → 只用训练段 fit scaler → 再 transform 训练和测试段」的顺序走这也是第 2 章代码里特意先切分再 fit 的原因。第五个坑停牌日补全导致序列失真。现象用 pandas 按自然日resample补全停牌日之后模型预测值长期不变。原因补全操作把停牌日填充成前值序列里出现大量重复价格模型学到「价格永远不变」的假规律。解决保持 tushare 返回的交易日序列不要用自然日对齐。构造滑窗时直接按行索引走有交易记录才算一个时间步。注意pro.daily()的积分权限报错不属于数据处理问题但极其常见。现象是调用接口时提示没有权限或访问受限原因是 tushare 对 daily 接口有积分门槛解决方式是先用项目自带 CSV 文件跑通流程再去 tushare 官网攒积分或换旧版get_k_data接口应急。5. 验证与预测回测里看模型能不能信以及怎么预测下一天5.1 反归一化与滚动预测补零列的小技巧模型输出的是 01 区间的归一化预测值直接看没有意义。反归一化时有个常见坑scaler.inverse_transform需要完整特征维度但模型只预测了 open 一个字段。方式不是只传一列而是构造一个 6 列的零矩阵把预测值塞进第 0 列再取反变换后的第 0 列。pred_scaled model_lstm.predict(X_test) dummy np.zeros((len(pred_scaled), 6)) dummy[:, 0] pred_scaled[:, 0] pred_open scaler.inverse_transform(dummy)[:, 0] true_open scaler.inverse_transform(test_scaled[30:])[:, 0]这里test_scaled[30:]对应测试集中第一个滑窗的起始位置要和pred_scaled的行数对齐。更严格的验证方式是滚动预测每预测完一天把真实观测值追加到窗口末尾再预测下一天模拟真实的逐日预测过程。一次性预测全部测试集会有信息优势滚动预测的结果更接近实际使用情况。5.2 误差指标与使用边界评估时别只盯 MSE加一个 MAPE 更直观。计算公式是mean(abs((真实值-预测值)/真实值)) * 100%茅台开盘价在 15002000 元区间时MAPE 在 2% 以内已经算是这个任务里不错的水平。我习惯把预测值、真实值和「上一日收盘价」三条线画在一张图里肉眼判断模型到底有没有跑赢无脑基准。预测未来一天只是把最后 30 个交易日的数据滑窗出来丢进模型拿到归一化结果再反变换逻辑和测试集完全一致。但有一点要认清模型只学了历史价格模式突发事件、政策变化、市场情绪这些信息在行情数据里没有体现预测结果只能当参考不能当交易信号。从那以后我每次跑这类预测项目都强制走一遍「严格时间划分 无未来函数 无脑基准对比」三步检查过滤掉了很多看似漂亮实则没用的模型。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网