tushare+TensorFlow2.0:用RNN/LSTM预测贵州茅台开盘价

发布时间:2026/10/1 13:24:49
tushare+TensorFlow2.0:用RNN/LSTM预测贵州茅台开盘价 简介面向金融时序预测与深度学习入门人群这份资源以贵州茅台历史行情为例演示如何通过tushare获取真实A股数据并基于TensorFlow 2.0搭建RNN和LSTM模型预测开盘价完整覆盖数据抓取、清洗归一化、模型构建、训练评估与预测应用适合具备一定Python基础、希望用真实股票数据快速上手序列建模的开发者也可作为金融科技实践教学的项目参考。压缩包共5个文件包含3个Python脚本对应tushare数据下载、RNN预测、LSTM预测、1个Markdown说明文档和1个CSV股票数据集整体仅56KB轻量易读脚本分离清晰便于按环节阅读修改。已有1912人学习下载。配套代码可直接运行复现实验README梳理了环境准备与实现思路CSV数据免去自行采集的麻烦方便读者对比标准RNN与LSTM在相同任务上的预测表现是入门时空序列预测的实用范例。1. 用tushare拉取贵州茅台历史行情再用tensorflow2.0搭RNN和LSTM预测开盘价链路能走通但瓶颈不在模型把tushare拉到的贵州茅台日线数据按时间窗口切成样本喂给tensorflow2.0里搭的RNN和LSTM用过去N天的开盘价预测第N1天的开盘价这是很多人在股票预测上迈出的第一步。反直觉的结论是模型本身十几行就能跑通真正决定成败的是数据口径、窗口构造和验证方式。比如tushare的daily接口返回的是不复权原始价贵州茅台每年分红除权会产生虚假跳空又比如归一化如果直接fit全量数据测试集就相当于开卷考试。这篇笔记按数据获取、样本构造、RNN基线、LSTM替换、踩坑排查到回测验证的顺序展开适合有Python和TensorFlow基础、想用真实金融数据做时间序列预测的从业者把RNN和LSTM从概念落到可复现的代码上。2. 用tushare拉取贵州茅台日线数据token、前复权与倒序清洗三个关键点2.1 申请token并用pro_bar接口拉取600519.SH日线数据的最小配置tushare现在主推的是pro接口旧版的ts.get_k_data虽然还能用但字段不规范、复权处理也很粗糙做预测建模我不建议碰。标准做法是在tushare主页拿到一个token用它初始化pro api然后按股票代码拉日线。下面是拉取贵州茅台最近五年日线数据的最小代码。import tushare as ts ts.set_token(把你在tushare主页申请的token填到这里) pro ts.pro_api() # 贵州茅台在上交所代码后缀是SH df pro.daily(ts_code600519.SH, start_date20190101, end_date20231231) print(df.shape) print(df.head())set_token把token写进全局配置换成你自己的字符串再运行。600519.SH是贵州茅台在tushare里的标准代码SH后缀不能省深交所股票是.SZ。daily接口返回的是不复权的原始日线包含ts_code、trade_date、open、high、low、close、pre_close、change、pct_chg、vol、amount这些字段。单次调用tushare有行数上限日线数据五年大概1200行不会触顶如果拉十年以上的分钟线或复权因子要注意分批。token对应的积分决定你能调哪些接口daily和pro_bar基础积分就够不需要额外付费接口。如果调接口时报权限错误先去tushare的控制台确认积分是否够用。如果不想手动处理复权tushare提供了pro_bar接口代码只需要把daily换成pro_bar并传一个adj参数。df ts.pro_bar(ts_code600519.SH, adjqfq, start_date20190101, end_date20231231) print(df.head())pro_bar返回的字段和daily基本一致区别是价格会按复权因子调整。后面所有建模都用pro_bar的输出就能绕开分红除权这个大坑。2.2 前复权还是不复权分红除权让开盘价序列出现虚假跳空贵州茅台每年都有现金分红股价在除权除息日会有一个向下的跳空。这个跳空反映的是股本和现金分红导致的价格调整不是市场真实的下跌。但如果直接用不复权价格建模RNN和LSTM会把某一天的突然低开当成真实特征训练出来的模型会在每年分红日附近产生明显的尖峰误判。常见的做法是统一用前复权qfq它把历史价格按最新复权因子整体调整序列连续不同年份之间的价格可对比。后复权hfq虽然也让序列连续但早期价格数值特别大对MinMaxScaler和模型训练都不够友好我一般只在算收益率时才考虑它。对比一下不复权和前复权的差异把两套数据按trade_date对齐打印除权除息日附近的open能看到不复权价格出现一个台阶。为了让你在本地能快速验证可以跑这样一段代码。df_raw pro.daily(ts_code600519.SH, start_date20220101, end_date20231231) df_qfq ts.pro_bar(ts_code600519.SH, adjqfq, start_date20220101, end_date20231231) raw_open df_raw.sort_values(trade_date)[open].reset_index(dropTrue) qfq_open df_qfq.sort_values(trade_date)[open].reset_index(dropTrue) # 对齐长度后肉眼找一下每年分红除权日前后的open跳变 print((qfq_open - raw_open).describe())选定qfq之后整个训练和评估过程都要保持qfq不要在中间切换口径。如果项目最终要求预测真实成交价再单独研究复权因子的还原计算但模型训练阶段统一用前复权是最省事的做法。2.3 排序、去重与缺失交易日tushare返回倒序RNN需要严格的升序tushare的daily和pro_bar返回结果都是按日期倒序排列最新一天在最上面。RNN和LSTM是按时间顺序学习序列依赖的必须先排序否则模型学到的是一条倒着走的价格曲线。另一个坑是重复行多次调用或合并数据时可能产生重复记录直接去重。下面是我每次拉完数据都会做的标准清洗。import pandas as pd # trade_date在tushare里是YYYYMMDD的字符串或整数 df[trade_date] pd.to_datetime(df[trade_date], format%Y%m%d) df df.sort_values(trade_date).reset_index(dropTrue) df df.drop_duplicates(subsettrade_date, keeplast).reset_index(dropTrue) print(df.head())sort_values按交易日期升序排列reset_index确保后续窗口切片的索引从0开始连续。drop_duplicates按trade_date去重keeplast保留最后拉取到的那一条记录。A股周五到周一之间有三天空档遇到国庆春节更长这是正常现象不要用resample把缺失日期填平。填平之后的平开和零成交量会让模型误以为市场每天都在交易反而引入噪声。这一步做完df里的open就是一列按时间升序的前复权开盘价序列可以直接进入下一章的样本构造。3. tensorflow2.0构造序列样本把贵州茅台开盘价变成RNN需要的时间窗口3.1 窗口样本构造用过去N个交易日预测第N1天的开盘价RNN和LSTM拿到的不是一条单独的价格而是一段连续的历史。如果把每一天的开盘价当成独立样本喂给全连接网络时间顺序信息就完全丢了。标准做法是滑窗采样用一个长度为window_size的窗口在开盘价序列上滑动窗口内的价格作为输入窗口后一天的价格作为标签。下面是我常用的窗口构造函数。import numpy as np def make_window(data, window_size10): X, y [], [] for i in range(len(data) - window_size): X.append(data[i:i window_size]) y.append(data[i window_size]) return np.array(X), np.array(y) # data是归一化后的开盘价一维数组长度设为L X, y make_window(data, window_size10) print(X.shape, y.shape)i从0取到len(data) - window_size - 1一个窗口取data[i:i window_size]共10个价格标签取窗口之后那一天的data[i window_size]。这里最容易写错的是边界如果range写成len(data) - window_size 1最后一个样本的y就超出序列长度训练时会报索引越界。X的初始shape是(样本数, window_size)对RNN来说还需要扩展一个维度变成(样本数, window_size, 1)这一步一般在喂给模型前用X.reshape(-1, window_size, 1)完成。窗口大小是后面最值得调的超参数10代表用两周交易日的开盘价预测第11天20代表一个月。窗口太小模型看不到趋势窗口太大又引入与当前关系不大的历史噪声建议以5、10、20、30做一组对比实验。3.2 归一化与切分先切分再fit MinMaxScaler防止测试集泄漏开盘价的绝对值从几百到一千七量级对tanh激活函数不友好必须先归一化。常见的错误是先对全量序列fit一遍MinMaxScaler再切分这样测试集的min和max参与了训练时所用的缩放参数测试集信息泄漏进了模型验证结果会虚高。正确顺序是先按时间切分只对训练集fit再用同一个scaler去transform测试集。from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) train_size int(len(open_qfq) * 0.8) train_raw open_qfq[:train_size].reshape(-1, 1) test_raw open_qfq[train_size:].reshape(-1, 1) scaler.fit(train_raw) train_scaled scaler.transform(train_raw).flatten() test_scaled scaler.transform(test_raw).flatten() print(train_scaled.min(), train_scaled.max())MinMaxScaler要求输入是二维列向量所以reshape成(-1, 1)。fit只计算训练集的min和maxtransform对测试集套用同一组参数。这样测试集虽然也落在[0, 1]区间但缩放边界完全来自训练集不包含未来信息。打印train_scaled的最小值和最大值确认训练集被压到[0, 1]。预测完成后要做inverse_transform还原用的也必须是这一个scaler对象不能重新fit。3.3 搭建第一个SimpleRNNtf.keras里跑通最小模型数据准备好后先搭一个最简单的RNN做baseline网络不要复杂目的是验证数据链路是否通。tensorflow2.0把RNN封装在tf.keras.layers里SimpleRNN是最直接的循环层。在3.1的make_window和3.2的scaler基础上把训练段和测试段分别做窗口化再扩展成三维输入。import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import SimpleRNN, Dense X_train, y_train make_window(train_scaled, window_size) X_test, y_test make_window(test_scaled, window_size) X_train X_train.reshape(-1, window_size, 1) X_test X_test.reshape(-1, window_size, 1) model Sequential([ SimpleRNN(units32, activationtanh, input_shape(window_size, 1)), Dense(1) ]) model.compile(optimizeradam, lossmse, metrics[mae]) model.summary()units是循环层隐藏状态维度32是起步值。activation默认就是tanh不要换成sigmoidRNN的梯度在sigmoid下衰减更快序列稍长就学不动。input_shape(window_size, 1)表示每个样本有window_size个时间步、每步1个特征。Dense(1)输出一个标量作为下一个交易日的开盘价。损失用mse回归任务默认口径。训练时加一个EarlyStopping避免epoch设太大导致过拟合。from tensorflow.keras.callbacks import EarlyStopping # 手动从训练集尾部切出最近10%作为验证集保持时间顺序 val_size int(len(X_train) * 0.1) X_val, y_val X_train[-val_size:], y_train[-val_size:] X_train_fit, y_train_fit X_train[:-val_size], y_train[:-val_size] early_stop EarlyStopping(monitorval_loss, patience20, restore_best_weightsTrue) history model.fit( X_train_fit, y_train_fit, epochs200, batch_size32, validation_data(X_val, y_val), callbacks[early_stop], verbose1 )这里没有用validation_split而是手动按时间从训练集尾部切出最近10%作为验证集避免随机抽样破坏时间顺序。validation_data直接传(X_val, y_val)训练时EarlyStopping盯验证集loss连续20个epoch不下降就停并恢复到验证集最好的权重。batch_size32对几千个样本够用。跑完后用model.evaluate看测试集mae再把预测值inverse_transform回去画图。这个最简单的RNN预测曲线通常有明显滞后不要慌这是标量回归里常见的naive现象下一章换上LSTM之后再做对比。4. 从RNN到LSTM用tensorflow2.0的门控机制解决梯度衰减和记忆不足4.1 LSTM的三个门遗忘、输入、输出在开盘价预测中的角色SimpleRNN在时间步上做的是一个带tanh激活的全连接变换权重连乘导致梯度指数衰减序列一长前面的开盘价对当前预测的影响就越来越弱。LSTM也就是长短期记忆网络它在隐藏状态之外增加了一条细胞状态通道用三个门控制信息的写入、保留和输出。用开盘价预测的场景来理解这三个门比背公式直观得多。门控制的对象在开盘价预测里的作用遗忘门上一时刻细胞状态保留多少决定10天前的暴涨暴跌是否还要影响明天的预测输入门当前时刻新信息写入多少决定今天的价格突变是否值得记入长期记忆输出门当前细胞状态输出多少给隐藏层决定当前记忆中有多少用来产生明天的开盘价输出把公式压缩成两行的话C_t f_t × C_{t-1} i_t × C~_th_t o_t × tanh(C_t)。f、i、o分别是遗忘门、输入门、输出门。LSTM给循环网络配了一个可学习的记忆读写器这也是它处理时间序列比普通RNN更稳的核心原因。代价是参数量和计算量上去了训练时间变长。对贵州茅台这种千元价位的股票开盘价序列里既有长期趋势又有分红除权留下的短期扰动LSTM的记忆机制理论上比SimpleRNN更适合吃下这种混合信号。4.2 一层LSTM替换SimpleRNN代码差异与参数量变化在tf.keras里把SimpleRNN换成LSTM只改一行。下面是和3.3对应的完整模型定义。from tensorflow.keras.layers import LSTM model Sequential([ LSTM(units32, input_shape(window_size, 1)), Dense(1) ]) model.compile(optimizeradam, lossmse, metrics[mae]) model.summary()LSTM默认activationtanhrecurrent_activationsigmoid一般不用改。参数量相比SimpleRNN大约变成四倍32单元的SimpleRNN参数量是32×(3211)1088LSTM因为内部有三组门加一个候选状态共四套权重是32×(3211)×44352。训练代码和3.3完全一样EarlyStopping照用。跑完之后把SimpleRNN和LSTM在同一份测试集上的MAE放在一起对比重点不是看谁训练loss低而是看谁对测试集末尾的滞后更小。窗口小于10时LSTM的优势通常不明显窗口拉到20甚至30时LSTM的长期记忆优势才会真正体现出来。4.3 堆叠LSTM与Dropout两层模型的标准配法一层LSTM的表示能力有限很多场景下堆两层效果更稳。两层LSTM和一层的关键差异是第一层要为第二层输出每个时间步的隐藏状态所以必须return_sequencesTrue最后一层LSTM只输出最后一个时间步的结果给Dense。标准配法如下。from tensorflow.keras.layers import LSTM, Dropout model Sequential([ LSTM(units64, return_sequencesTrue, input_shape(window_size, 1)), Dropout(0.2), LSTM(units32), Dropout(0.2), Dense(1) ])第一层LSTM返回完整的时间步序列shape是(batch_size, window_size, 64)第二层LSTM默认只返回最后一个时间步的输出直接接到Dense(1)。两个Dropout加在LSTM输出之后ratio取0.2作为起步。如果数据量只有几百个交易日、窗口又不大两层LSTM很容易在训练集上记住噪声验证集和测试集反而变差。我一般会先跑一层32单元看训练曲线是否过拟合再决定要不要加层和加dropout。LSTM层也自带dropout参数比如LSTM(units32, dropout0.2, recurrent_dropout0.2)效果类似但recurrent_dropout会让训练明显变慢数据量不大时优先用Dropout层更省时间。5. 避坑tushare与tensorflow2.0组合下预测开盘价的5个常见翻车点5.1 归一化泄漏scaler.fit全量数据导致测试集变成开卷考试现象训练集loss正常下降测试集预测曲线紧贴真实曲线但一换到新的时间区间就崩。原因对全量序列先fit MinMaxScaler再切分测试集的min和max已经写进缩放参数模型等于提前见过测试集的价格区间验证结果虚高。解决先按时间切出训练集和测试集只在训练集上fit测试集只transform。第3.2节的代码顺序就是为这个服务的切分和fit两步的顺序不能调换。5.2 除权跳空tushare的daily返回不复权价格分红日附近出现虚假下跌现象预测序列每年固定某个时间段出现一个明显向下的大误差过了这个时间段又恢复正常。原因贵州茅台每年分红除权不复权价格在除权日出现跳空RNN把除权当成了真实暴跌学习到错误的下跌模式。解决用ts.pro_bar(adjqfq)拉前复权数据训练、验证、测试全链路统一用qfq口径中途不要混用不复权价格。如果项目要求预测真实成交价再单独研究复权因子的还原计算。5.3 验证集切分随意validation_split与shuffle结合破坏时间顺序现象模型在验证集上loss很低换成自己留的测试集后指标明显变差。原因model.fit里用validation_split并保留默认shuffle验证集可能来自被打乱后的随机位置时间顺序被破坏验证分数不可信。解决手动按时间顺序切分验证集比如把训练集最后10%的样本作为验证集通过validation_data传入同时保持训练数据本身按时间升序。5.4 用MAE当唯一指标股价上千元时20元的误差看起来很大其实很准现象LSTM的测试MAE一直在20到30元之间感觉模型完全没用。原因贵州茅台价格在千元以上20元误差对应的MAPE只有2%左右而股价正常波动一天就能有20元绝对误差不能直接反映模型质量。解决同时记录MAE、MAPE和RMSE并加一个naive baseline用t-1的开盘价直接当t时刻开盘价的预测值。LSTM如果连这个zero-shot baseline都打不过先别调网络结构回到数据口径和窗口构造上查问题。5.5 预测阶段窗口不够长在线预测时报维度错误现象模型训练完拿最新一天的真实数据去predict输入维度报错。原因预测明天开盘价时需要用截至今天为止的最近window_size个开盘价作为输入如果只取了不到window_size个样本reshape出来的维度就不对。解决写一个取最近窗口的函数输入完整序列和当前position从position - window_size取到position作为输入。预测目标永远是窗口之后那一天的标签不要把当天的开盘价提前塞进输入。6. 用滑窗回测验证LSTM预测贵州茅台开盘价把准不准变成可对比的数字模型训练完只看最后一次测试集loss没有说服力。我的习惯是做滑窗回测在测试期从第一天开始每次只用截止到当前时刻之前的数据构造窗口预测下一天然后向前滚动一步把整段测试期的预测值和真实值拼起来再统一算指标。这一步能把模型大概能跑变成每个位置预测偏差多少。def rolling_predict(model, scaler, series_scaled, window_size): preds [] for i in range(window_size, len(series_scaled)): win series_scaled[i - window_size:i].reshape(1, window_size, 1) pred model.predict(win, verbose0)[0, 0] preds.append(pred) return scaler.inverse_transform(np.array(preds).reshape(-1, 1)).flatten()series_scaled是归一化后的序列窗口从i - window_size取到ii对应真实标签的位置。predict用verbose0避免刷屏最后把预测值统一还原成元。拿到pred之后再和真实测试段对齐rolling_predict返回的第一个值对应测试期第window_size个位置之后的第一个点。接着算MAE、MAPE和naive baseline。from sklearn.metrics import mean_absolute_error, mean_squared_error mae mean_absolute_error(actual, pred) mape np.mean(np.abs((actual - pred) / actual)) * 100 # naive baseline用t-1的开盘价直接当t时刻的预测 naive_mae mean_absolute_error(actual[:-1], actual[1:]) print(fLSTM MAE{mae:.2f} MAPE{mape:.2f}% | naive MAE{naive_mae:.2f})naive_mae用t-1时刻的真实值当t时刻的预测这一步能立刻看出LSTM是否只是学到了跟着前一天走。建议把不同参数组合的结果记录成一张表比如window_size网络结构test_maenaive_mae10SimpleRNN-3210LSTM-3220LSTM-3220LSTM-64-Dropout只要记录过一次你会发现很多调参决策不需要凭感觉。这个流程跑通之后我对预测类项目的态度变了很多。最开始我用tushare拉数据、tf.keras搭LSTM训练曲线漂亮得不行拿到新时间段一滚回来就翻车问题全出在数据泄漏和验证集切分上网络结构反而是最后才需要动的地方。现在我的标准动作是先固定前复权口径再按时间切分跑naive baseline最后才轮到RNN和LSTM之间的对比。窗口、层数、dropout都放在回测结果后面去说明结论才站得住。这套方法论对开盘价有效对收盘价、最高价等其他序列同样适用希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询