news 2026/10/1 13:24:46

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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天的开盘价预测第N+1天的开盘价,这是很多人在股票预测上迈出的第一步。反直觉的结论是:模型本身十几行就能跑通,真正决定成败的是数据口径、窗口构造和验证方式。比如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_code='600519.SH', start_date='20190101', end_date='20231231') 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_code='600519.SH', adj='qfq', start_date='20190101', end_date='20231231') print(df.head())

pro_bar返回的字段和daily基本一致,区别是价格会按复权因子调整。后面所有建模都用pro_bar的输出,就能绕开分红除权这个大坑。

2.2 前复权还是不复权:分红除权让开盘价序列出现虚假跳空

贵州茅台每年都有现金分红,股价在除权除息日会有一个向下的跳空。这个跳空反映的是股本和现金分红导致的价格调整,不是市场真实的下跌。但如果直接用不复权价格建模,RNN和LSTM会把某一天的突然低开当成真实特征,训练出来的模型会在每年分红日附近产生明显的尖峰误判。常见的做法是统一用前复权qfq,它把历史价格按最新复权因子整体调整,序列连续,不同年份之间的价格可对比。后复权hfq虽然也让序列连续,但早期价格数值特别大,对MinMaxScaler和模型训练都不够友好,我一般只在算收益率时才考虑它。

对比一下不复权和前复权的差异:把两套数据按trade_date对齐,打印除权除息日附近的open,能看到不复权价格出现一个台阶。为了让你在本地能快速验证,可以跑这样一段代码。

df_raw = pro.daily(ts_code='600519.SH', start_date='20220101', end_date='20231231') df_qfq = ts.pro_bar(ts_code='600519.SH', adj='qfq', start_date='20220101', end_date='20231231') raw_open = df_raw.sort_values('trade_date')['open'].reset_index(drop=True) qfq_open = df_qfq.sort_values('trade_date')['open'].reset_index(drop=True) # 对齐长度后,肉眼找一下每年分红除权日前后的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(drop=True) df = df.drop_duplicates(subset='trade_date', keep='last').reset_index(drop=True) print(df.head())

sort_values按交易日期升序排列,reset_index确保后续窗口切片的索引从0开始连续。drop_duplicates按trade_date去重,keep='last'保留最后拉取到的那一条记录。A股周五到周一之间有三天空档,遇到国庆春节更长,这是正常现象,不要用resample把缺失日期填平。填平之后的平开和零成交量会让模型误以为市场每天都在交易,反而引入噪声。这一步做完,df里的open就是一列按时间升序的前复权开盘价序列,可以直接进入下一章的样本构造。

3. tensorflow2.0构造序列样本:把贵州茅台开盘价变成RNN需要的时间窗口

3.1 窗口样本构造:用过去N个交易日预测第N+1天的开盘价

RNN和LSTM拿到的不是一条单独的价格,而是一段连续的历史。如果把每一天的开盘价当成独立样本喂给全连接网络,时间顺序信息就完全丢了。标准做法是滑窗采样:用一个长度为window_size的窗口在开盘价序列上滑动,窗口内的价格作为输入,窗口后一天的价格作为标签。下面是我常用的窗口构造函数。

import numpy as np def make_window(data, window_size=10): 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_size=10) 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和max,transform对测试集套用同一组参数。这样测试集虽然也落在[0, 1]区间,但缩放边界完全来自训练集,不包含未来信息。打印train_scaled的最小值和最大值,确认训练集被压到[0, 1]。预测完成后要做inverse_transform还原,用的也必须是这一个scaler对象,不能重新fit。

3.3 搭建第一个SimpleRNN:tf.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(units=32, activation='tanh', input_shape=(window_size, 1)), Dense(1) ]) model.compile(optimizer='adam', loss='mse', metrics=['mae']) model.summary()

units是循环层隐藏状态维度,32是起步值。activation默认就是tanh,不要换成sigmoid,RNN的梯度在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(monitor='val_loss', patience=20, restore_best_weights=True) history = model.fit( X_train_fit, y_train_fit, epochs=200, batch_size=32, validation_data=(X_val, y_val), callbacks=[early_stop], verbose=1 )

这里没有用validation_split,而是手动按时间从训练集尾部切出最近10%作为验证集,避免随机抽样破坏时间顺序。validation_data直接传(X_val, y_val),训练时EarlyStopping盯验证集loss,连续20个epoch不下降就停,并恢复到验证集最好的权重。batch_size=32对几千个样本够用。跑完后用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~_t,h_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(units=32, input_shape=(window_size, 1)), Dense(1) ]) model.compile(optimizer='adam', loss='mse', metrics=['mae']) model.summary()

LSTM默认activation='tanh',recurrent_activation='sigmoid',一般不用改。参数量相比SimpleRNN大约变成四倍:32单元的SimpleRNN参数量是32×(32+1+1)=1088,LSTM因为内部有三组门加一个候选状态共四套权重,是32×(32+1+1)×4=4352。训练代码和3.3完全一样,EarlyStopping照用。跑完之后把SimpleRNN和LSTM在同一份测试集上的MAE放在一起对比,重点不是看谁训练loss低,而是看谁对测试集末尾的滞后更小。窗口小于10时LSTM的优势通常不明显,窗口拉到20甚至30时,LSTM的长期记忆优势才会真正体现出来。

4.3 堆叠LSTM与Dropout:两层模型的标准配法

一层LSTM的表示能力有限,很多场景下堆两层效果更稳。两层LSTM和一层的关键差异是:第一层要为第二层输出每个时间步的隐藏状态,所以必须return_sequences=True;最后一层LSTM只输出最后一个时间步的结果给Dense。标准配法如下。

from tensorflow.keras.layers import LSTM, Dropout model = Sequential([ LSTM(units=64, return_sequences=True, input_shape=(window_size, 1)), Dropout(0.2), LSTM(units=32), 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(units=32, dropout=0.2, recurrent_dropout=0.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(adj='qfq')拉前复权数据,训练、验证、测试全链路统一用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, verbose=0)[0, 0] preds.append(pred) return scaler.inverse_transform(np.array(preds).reshape(-1, 1)).flatten()

series_scaled是归一化后的序列,窗口从i - window_size取到i,i对应真实标签的位置。predict用verbose=0避免刷屏,最后把预测值统一还原成元。拿到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(f"LSTM MAE={mae:.2f} MAPE={mape:.2f}% | naive MAE={naive_mae:.2f}")

naive_mae用t-1时刻的真实值当t时刻的预测,这一步能立刻看出LSTM是否只是学到了跟着前一天走。建议把不同参数组合的结果记录成一张表,比如:

window_size网络结构test_maenaive_mae
10SimpleRNN-32
10LSTM-32
20LSTM-32
20LSTM-64-Dropout

只要记录过一次,你会发现很多调参决策不需要凭感觉。这个流程跑通之后,我对预测类项目的态度变了很多。最开始我用tushare拉数据、tf.keras搭LSTM,训练曲线漂亮得不行,拿到新时间段一滚回来就翻车,问题全出在数据泄漏和验证集切分上,网络结构反而是最后才需要动的地方。现在我的标准动作是:先固定前复权口径,再按时间切分,跑naive baseline,最后才轮到RNN和LSTM之间的对比。窗口、层数、dropout都放在回测结果后面去说明,结论才站得住。这套方法论对开盘价有效,对收盘价、最高价等其他序列同样适用,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 13:24:32

制造业AI智能体落地:五道门槛与选型复制指南

去年年底参加一场制造业数字化转型的交流会,茶歇时有位汽车零部件厂的IT负责人对我说了句印象很深的话:朋友圈里别人的AI智能体都能写代码、自动做报表了,我们车间里连设备报工还得靠人工录,这差距是不是已经追不上了?…

作者头像 李华
网站建设 2026/10/1 13:23:43

LSTM温度预测实战:从数据预处理到PyTorch模型训练与避坑指南

简介:一套用于温度预测的Python期末大作业项目,基于LSTM神经网络实现,面向计算机相关专业正在完成课程设计或期末大作业的学生,也适合需要时间序列预测实战经验的开发者。项目经导师指导并以98分评审通过,完整呈现数据…

作者头像 李华
网站建设 2026/10/1 13:23:14

普通人如何走好工程师之路:从自学到站稳脚跟的完整指南

拿到这个标题,我就知道帖主想聊的不只是技术,而是一条完整的成长轨迹。入行多年,我见过太多人把“工程师”理解成单纯的写代码、调接口,结果被现实撞得头破血流。“我的工程师之路,给需要的同学”这个标题,…

作者头像 李华
网站建设 2026/10/1 13:23:06

基于LSTM的电商评论情感分析:从数据清洗到模型部署的完整实战

简介:这份资源是面向计算机相关专业学生与Python实战学习者的深度学习项目包,以LSTM为核心完成电商购物评论的情感分析任务,可直接用于毕业设计、课程设计或期末大作业。项目围绕京东商城购物评论展开,涵盖数据采集、中文分词与停…

作者头像 李华
网站建设 2026/10/1 13:22:33

爬虫与LSTM预测实战:从数据采集到机器学习分析完整链路

简介:这是“爬虫与数据分析实践”完整项目包,集成信息爬取、LSTM时间序列预测与机器学习分析三条主线,覆盖从数据采集、清洗、建模到结果可视化的完整流程,适合计算机、人工智能、自动化、物联网等专业高校学生用于毕设、课设、作…

作者头像 李华
网站建设 2026/10/1 13:21:36

基于Three.js封装view3D组件:网页3D模型交互展示与性能优化实践

1. 网页展示3D模型这件事,比想象中更贴近业务1.1 需求场景拆解:客户要看的是“能动”的模型先说个比较典型的场景。做工业设备、家装、电商或者教育类项目时,经常会遇到这样一个需求:产品经理或者甲方爸爸拿来一个做好的模型文件&…

作者头像 李华