简介:面向股票量化分析与深度学习入门人群的一套TensorFlow股票预测实践资料包,聚焦如何用CNN(卷积神经网络)处理时间序列并预测股价走势,同时拓展了DQN、KD指标等多方向实验,适合具备基础Python语法、想将神经网络应用到金融场景的读者参考。资源共34个文件,压缩包约5.16MB,包含Python脚本(.py)、Jupyter Notebook(.ipynb)、说明文档(.md/.pdf)及图表(.png)等,另有训练好的模型权重、checkpoint与数据文件,方便直接复现和二次调整。已有1440人学习下载。包内不仅给出CNN股票预测的完整建模流程,还包含DQN强化学习、KD指标绘图、买入点回测等扩展代码,以及对应实验对比图和PDF说明文档,可帮助理解特征工程、模型训练与结果评估的完整链路。
1. 用 Python + TensorFlow 跑通 CNN 股票预测,先搞清楚它在预测什么
用 Python 配合 TensorFlow 跑 CNN(卷积神经网络)来预测股票走势,是量化交易里被反复讨论又经常被误解的方向。很多人以为把 K 线图塞进 CNN 就能直接得到涨跌结论,实际上这个方案预测的并不是“股价明天涨多少”,而是从序列数据里捕捉局部的上涨/下跌形态。它的本质是监督学习下的时间序列预测:构造历史样本、定义未来标签、让卷积层去提取局部特征。这套做法适合已经会 Python 基础语法、希望把深度学习落地到行情数据的开发者,它能解决“用 CNN 处理一维时序数据怎么做数据管道、模型怎么搭、评估怎么看”这一整条链路的问题。下面我按实际落地顺序展开,从数据构造到模型训练再到踩坑排查,全部给出能直接跑的代码和参数说明。
2. 数据准备与特征构造:先把行情数据变成 CNN 能吃的形状
2.1 行情数据的口径选择:前复权、日线还是分钟线
CNN 预测股票走势第一步不是建模型,而是选数据口径。我一般建议从日线数据开始,原因很直接:日线数据容易获取、信噪比相对稳定、不需要处理盘中停牌和集合竞价的细节。如果你想直接拿分钟线练手,会立刻遇到两个问题:一是分钟线数据量大,训练样本容易过拟合;二是分钟线的微观结构噪声(买卖价差、大单冲击)会掩盖 CNN 想提取的形态特征。
复权方式必须统一。股票有除权除息事件,不复权数据在除权日会出现价格断层,CNN 的卷积核会把这个断层当成一个强特征去学习,结果就是模型学会了“识别除权日”,而不是“识别上涨形态”。我常用前复权数据,它保证历史价格连续,且当前价格保持真实。数据源用 yfinance 或者 tushare 都可以,自己本地维护一份 CSV 也比反复拉接口更可控。
2.2 用 yfinance 拉取日线数据并清洗对齐
下面先用 yfinance 拉取一只股票的日线数据。如果你本地还没装,直接pip install yfinance pandas numpy。这个库不是 TensorFlow 官方组件,但它能稳定拿到 OHLCV 数据,适合做学习项目。
import yfinance as yf import pandas as pd # 拉取股票日线数据,period=5y 表示最近5年 df = yf.download("AAPL", period="5y", interval="1d", auto_adjust=True) # 只保留 OHLCV 五列 df = df[["Open", "High", "Low", "Close", "Volume"]].copy() # 删除全空行,按日期升序排序 df.dropna(how="all", inplace=True) df.sort_index(inplace=True) # 检查是否有重复日期 print(f"数据量: {len(df)} 行") print(f"日期范围: {df.index.min()} -> {df.index.max()}")这段代码里auto_adjust=True是关键参数。它让 yfinance 自动返回前复权后的 OHLCV 数据,避免你自己处理除权除息。dropna(how="all")只删除整行全为空的数据,不删除单列缺失,因为单独某个字段缺了可能是停牌导致的,强行删行会破坏时间连续性。排序和去重是防止后续构造滑动窗口时出现时间倒序或重复日期。
这里有一个容易忽略的坑:yfinance 返回的索引是DatetimeIndex,对应股票交易日。CNN 不关心日期本身,只关心序列位置,所以后续构造样本时直接把 DataFrame 转成 numpy 数组,按行顺序作为时间步。
2.3 标签怎么定义:预测上涨概率而不是预测未来价格
CNN 股票预测里,标签定义直接决定模型能学到什么。常见两类做法:回归任务直接预测未来 n 日收盘价,分类任务预测未来 n 日是否上涨。我的经验是优先做二分类,即未来5日收益率 > 0判为 1,否则判为 0。理由有两个:第一,价格序列非平稳,直接回归股价模型要额外拟合绝对价格水平,通常效果很差;第二,交易决策本质上是方向判断,分类目标更贴近实际使用。
import numpy as np # 计算未来5日的收益率 df["future_close"] = df["Close"].shift(-5) df["future_return"] = (df["future_close"] - df["Close"]) / df["Close"] # 生成标签:未来5日收益大于0为1,否则为0 df["label"] = (df["future_return"] > 0).astype(int) # 删除最后5行没有未来数据的样本 df.dropna(subset=["future_close"], inplace=True) print(df["label"].value_counts())shift(-5)把未来第 5 天的收盘价挪到当前行,这样当前行就能和未来数据对齐。dropna删除尾部没有未来数据的样本,这是防止模型学到“空值”的必要操作。label分布如果出现极端不平衡,比如上涨占比不到 30%,后面训练时要考虑类别权重或者换标签口径。我这里选了 5 日预测窗口,你也可以改成 1 日或 10 日,窗口越长信号越平滑但样本越少,后面滑动窗口构造时需要一起协调。
3. 特征工程与样本序列化:把表格数据变成三维张量
3.1 基础特征之外该加什么:技术指标与归一化
光有 OHLCV 五个字段喂给 CNN,卷积核能提取的形态有限。常见做法是加入一些衍生特征,比如收益率、振幅、成交量变化率。注意这里不要一次性堆几十个指标,CNN 的卷积核会在通道维度上做加权组合,特征太多而样本量不够时,模型会去拟合特征之间的噪声关系。我一般控制在 8 到 12 个特征列。
一个务实的特征集合是这样:Close、Volume、open_close_diff(当日开盘收盘差)、high_low_diff(当日振幅)、return_1d(当日收益率)、ma5(5日均线)、ma20(20日均线)。均线特征是趋势信息,CNN 卷积核很难直接从原始价格序列中提取出这种跨时间步的统计量,显式算出来更稳妥。
3.2 滑动窗口构造样本:lookback 窗口怎么定
CNN 处理股票序列时,输入不能是一行数据,而是一个长度为lookback的窗口,形状是(lookback, num_features)。窗口大小就是卷积核能看到的“历史视野”。我常用 20 到 60 之间的值,对应 1 个月到 3 个月的交易日。窗口太小卷积核只看到局部波动,窗口太大样本数量急剧减少且可能引入无关历史信息。
def make_sequences(data, lookback=30): X, y = [], [] for i in range(len(data) - lookback): X.append(data[i:i+lookback]) y.append(data.iloc[i+lookback]["label"]) return np.array(X), np.array(y) # 选特征列,注意 label 不进入特征 feature_cols = ["Close", "Volume", "open_close_diff", "high_low_diff", "return_1d", "ma5", "ma20"] data_for_seq = df[feature_cols].copy() # 构造序列样本 X, y = make_sequences(data_for_seq, lookback=30) print(f"X shape: {X.shape}, y shape: {y.shape}")X.shape是(总样本数, 30, 7),这就是 CNN 的输入格式。第一维是样本数,第二维是时间步,第三维是特征通道。make_sequences里的循环从第 0 行开始滑动,每次取 30 行特征,用第 31 行的 label 作为该样本的标签。注意这里data[i:i+lookback]是特征,data.iloc[i+lookback]["label"]是标签,两者没有时空间重叠,避免标签泄漏。
3.3 标准化顺序:先切分再 fit,杜绝数据泄漏
这是做 CNN 股票预测最容易被忽视的一步。很多新手把全部数据先做标准化,再切训练集和验证集,这会导致验证集的信息进入训练过程。标准化器(比如 StandardScaler)是在整个数据集上计算的均值和方差,验证集数据参与了这些统计量的计算,模型在训练时等于提前知道了验证集的分布范围,验证集分数会虚高。
from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split # 先按时间顺序切分,不打乱时序 train_size = int(len(X) * 0.8) X_train, X_val = X[:train_size], X[train_size:] y_train, y_val = y[:train_size], y[train_size:] # 对每个特征通道分别做标准化,scaler 只 fit 训练集 num_features = X_train.shape[2] X_train_scaled = np.zeros_like(X_train) X_val_scaled = np.zeros_like(X_val) for f in range(num_features): scaler = StandardScaler() # 把 (样本数, 时间步) 展平后 fit flat_train = X_train[:, :, f].reshape(-1, 1) scaler.fit(flat_train) X_train_scaled[:, :, f] = scaler.transform(X_train[:, :, f].reshape(-1, 1)).reshape(X_train.shape[0], -1) X_val_scaled[:, :, f] = scaler.transform(X_val[:, :, f].reshape(-1, 1)).reshape(X_val.shape[0], -1)这里按时间顺序切分非常关键。股票数据有强时间自相关性,如果随机打乱切分,训练集会包含未来的信息,验证集评估的是模型“看到未来”的能力而不是泛化能力。StandardScaler对每个特征通道单独处理,是因为 Close 和 Volume 的量纲差异巨大,共享一个 scaler 会让低量纲特征在卷积计算中被淹没。展平后再 fit 是为了让 scaler 学到整个时间序列的统计量,而不是单独某一行的。
4. TensorFlow 中的 CNN 模型:Conv1D 在股票序列上怎么搭
4.1 一维卷积在时序上的作用:提取局部形态特征
股票预测里用的 CNN 不是处理图像的二维卷积,而是处理序列的一维卷积 Conv1D。Conv1D 在时间维度上滑动,每个卷积核相当于一个模板匹配器,学习价格走势的局部形态。比如某个卷积核可能学到“连续三天下跌后出现长下影线”,另一个学到“放量突破 5 日均线”。多个卷积核叠加,模型就能从原始序列里提取出比手工特征更丰富的形态组合。
池化层对时序数据的作用和图像不同。MaxPooling1D 在时间维度上降采样,保留每个局部窗口的最大激活值。它让模型对时间位置不那么敏感——某个形态早一天出现还是晚一天出现,经过池化后差异变小,这符合股票形态识别中对相位偏移的容忍需求。但池化步长不要设太大,默认pool_size=2, strides=2会把 30 个时间步压缩到 15,信息损失尚可接受。
4.2 搭建一个可复现的 CNN 回归分类模型:完整代码
下面用 TensorFlow 的 Keras API 搭一个可运行的模型。环境要求是 Python 3.8 以上,TensorFlow 2.10 以上。如果还没有装 TensorFlow,pip install tensorflow即可,注意安装时看下 Python 版本兼容性。
import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout, BatchNormalization # 搭建模型 model = Sequential([ Conv1D(filters=32, kernel_size=5, activation="relu", padding="same", input_shape=(30, 7)), MaxPooling1D(pool_size=2), Conv1D(filters=64, kernel_size=3, activation="relu", padding="same"), MaxPooling1D(pool_size=2), Flatten(), Dense(64, activation="relu"), Dropout(0.3), Dense(1, activation="sigmoid") ]) model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss="binary_crossentropy", metrics=["accuracy"]) model.summary()Conv1D第一层input_shape=(30, 7)必须和滑动窗口的数据形状严格对应,30 是时间步,7 是特征数。kernel_size=5表示卷积核覆盖 5 个交易日,这最开始时可以从 3 到 7 之间选,小于 3 感受野太小,大于 7 容易把短期波动噪声学进来。padding="same"保持时间步长度不变,这样第二层卷积能继续在完整序列上滑动。Dropout(0.3)是防止过拟合,股票数据噪声大,dropout 比例不要太低,0.3 到 0.5 都常见。最后一层用sigmoid输出一个 0 到 1 之间的概率值,对应未来 5 日上涨的概率。
4.3 训练参数:batch_size、epochs 和早停的设置
训练 CNN 股票模型和训练图像模型的习惯不太一样。股票数据样本量通常只有几千到几万,batch_size 不宜太大,我用 64 或 128。batch_size 太大会让每个 batch 的梯度方向过于平均,模型难以学到少数上涨样本的形态特征。epochs 需要配合早停使用,不要固定训练一百轮,因为验证集 loss 通常在十几轮之后就开始反弹。
from tensorflow.keras.callbacks import EarlyStopping # 早停:监控验证集loss,连续5个epoch不下降就停止 early_stop = EarlyStopping(monitor="val_loss", patience=5, restore_best_weights=True) # 训练 history = model.fit( X_train_scaled, y_train, validation_data=(X_val_scaled, y_val), epochs=50, batch_size=64, callbacks=[early_stop], verbose=1 )restore_best_weights=True是保命设置。它会在 early stopping 触发后把模型权重恢复到验证集 loss 最低的那个 epoch,而不是停留在训练的最后一步,否则接下来的评估结果会失真。patience=5表示允许验证集 loss 连续 5 个 epoch 不改善才停止,太短可能错过更优的区间,太长则浪费时间。训练完成后保存模型权重,model.save("cnn_stock.h5"),后面加载做预测用的就是早停后的最佳权重。
5. 股票预测 CNN 的避坑指南:数据泄漏、样本不均衡与过拟合
5.1 数据泄漏:验证集 loss 低得离谱但实盘不赚钱
现象:训练完看验证集准确率高达 90% 以上,loss 也一直下降,但把模型拿到最近几个月的数据上去预测,准确率直接跌到 50% 附近。原因是数据预处理阶段让未来信息混进了训练过程。最常见的是两种:第一种是对全量数据做标准化再切分,我用过的最经典的翻车现场;第二种是构造标签时使用了未来函数,比如用当天的收盘价和未来数据计算了某个特征列,然后这个特征列又被当成了输入特征。解决方法是严格按时间顺序先切分,再在训练集上 fit scaler。另外检查特征列里有没有出现shift(-n)之类的代码,这类代码通常是在构造未来收益时误用的。
5.2 样本不均衡:模型学会“永远看跌”
现象:训练集准确率有 65%,但模型跑出来的预测几乎全是 0,也就是永远输出下跌。股票市场上涨天数占比本来就不高,我的经验是 A 股日线上未来 5 日上涨概率常在 45% 到 55% 之间,但如果你的标签窗口和特征窗口不匹配,比如用 1 日收益做标签但用 60 日窗口做特征,样本里下跌样本可能明显偏多。模型发现输出全 0 就能拿到不错的准确率,于是梯度下降不再优化。解决方法是先看y_train的分布,如果正负样本比超过 1.5 比 1,就需要给少数类加权重。model.fit里设置class_weight={0: 1.0, 1: 1.5},或者用train_test_split后单独对少数类做重复采样。
5.3 过拟合:训练集 90 分,验证集 51 分
现象:训练集准确率 90% 以上,验证集准确率只有 51%,和抛硬币差不多。这是 CNN 股票预测最大的敌人,原因是股票数据信噪比极低,模型很容易把训练集里的随机噪声当作规律记住。我的解决经验是三层防线:一是增大 dropout,从默认 0.2 调高到 0.4;二是减少模型容量,Conv1D 的 filters 从 64 降到 32,Dense 层从 128 降到 64;三是加早停,patience设成 3 到 5 就行。如果降容量后训练集准确率仍然快速接近 100%,检查一下是不是特征里混入了未来信息。另外还可以考虑加BatchNormalization,它对缓解过拟合有一定帮助,虽然这不是它的主要目的。
5.4 序列划分错位:把随机打乱的时间步重新输入 CNN
现象:用同一个模型,第一次训练后准确率 58%,什么都没改再训练一次准确率变成 63%,结果不稳定。原因是在切分数据时用了train_test_split默认的shuffle=True,把时间上相邻的样本打乱了。CNN 的卷积核学到的是序列内的局部模式,但训练集和验证集的时间跨度在打乱后互相穿插,模型验证时可能已经“见过”同一段行情。解决方法是始终使用时间顺序切分:train_test_split(X, y, test_size=0.2, shuffle=False),或者像我前面用train_size=int(len(X)*0.8)手动切。这一点做对比实验时特别重要,随机打乱会让你的 A/B 测试失去意义。
5.5 评价指标陷阱:准确率不是交易盈利指标
现象:模型在测试集上准确率 68%,看起来不错,但按这个信号做交易,一个月下来反而亏钱。准确率高不一定赚钱,因为股市预测里上涨样本和下跌样本的赔率不对称。下跌往往比上涨更急,预测错误的代价可能落在跌幅更大的那侧。我一般会加一个更直接的评估:把模型预测输出和真实标签组合看收益。比如对每一笔预测,假设上涨预测时做多,下跌预测时做空,按未来真实收益结算盈亏。如果累计盈亏曲线是下降的,说明模型虽然在分类上占优,但没抓住真正的行情结构。这一步能帮你判断是该继续调模型,还是该放弃这个特征集。
6. 模型评估与实盘验证:从预测概率到可回测的交易信号
6.1 用评估指标判断模型是否有效:准确率、Precision、Recall 与盈亏比
模型训练完后不能只看 accuracy,还需要看 Precision 和 Recall,尤其是在样本不均衡的情况下。把验证集的预测结果和真实标签放在一起,用classification_report快速查看。下面这段代码可以直接在验证集上运行。
from sklearn.metrics import classification_report, precision_recall_curve from sklearn.metrics import confusion_matrix # 预测概率 y_pred_prob = model.predict(X_val_scaled) y_pred = (y_pred_prob > 0.5).astype(int) # 分类报告 print(classification_report(y_val, y_pred, target_names=["下跌", "上涨"])) # 混淆矩阵 print(confusion_matrix(y_val, y_pred))classification_report里的 confusion matrix 能告诉你模型错在哪里。如果分类报告显示上涨样本 Recall 低而下跌样本 Recall 高,说明模型在预测下跌时更自信,这时可以把预测概率阈值从 0.5 调低到 0.4,让上涨预测更容易触发,再观察盈亏是否改善。阈值调整对 CNN 股票模型来说和网络结构一样重要,没有最优阈值,只能在验证集上按你的交易偏好试探。
6.2 滚动式验证:用时间推进的方式模拟真实交易
静态的训练集和验证集切分只能验证模型在过去的表现,要检验模型是否真的可用,我会用滚动验证。思路是每次用过去 3 年数据训练,预测未来 1 个月,然后把窗口向前推 1 个月,重复多次。这样得到的回测结果更接近真实交易中“不断重新训练”的状态。
def rolling_validate(data, lookback=30, train_years=3, test_month=1): results = [] start = 0 step = test_month * 21 # 约1个月交易日数 train_len = train_years * 252 while start + train_len + lookback + step < len(data): train_data = data[start:start+train_len] test_data = data[start+train_len:start+train_len+step] # 这里重复前面构造样本、标准化、训练、预测的流程 # 记录预测准确率或累计收益 start += step return results这段代码故意合并了多个环节,实际使用时把前面第 3、4 章的样本构造和训练逻辑包进去。滚动验证的价值在于它能暴露模型在不同市场环境下的稳定性。我踩过的坑是:在牛市数据上训练的 CNN 放到震荡市里预测,准确率会明显下滑。如果滚动验证的多个时间窗口结果差异非常大,说明模型学到的是特定行情特征而不是通用模式。
6.3 从预测概率到交易信号:软信号和硬信号的区别
模型输出的概率值不是直接买或卖的指令,还需要转成可执行信号。硬信号是设一个固定阈值,比如概率大于 0.6 做多,小于 0.4 做空,中间区间空仓。软信号是直接把概率值当作仓位比例,比如预测上涨概率 0.7 就投入 70% 仓位。我建议初学先从硬信号开始,因为软信号在回测中容易产生过度乐观的结果,它假定概率是真实的统计概率,但 CNN 输出的概率往往过于极端。
做最后一个记录的习惯:把每次实验的模型结构、数据范围、特征列表、训练超参数、验证集指标都记在一个表格里。这样可以知道改了什么导致结果变好还是变差。下面是一个记录模板。
| 实验编号 | 特征列表 | lookback | 卷积核 | 训练集准确率 | 验证集准确率 | 滚动验证盈亏 |
|---|---|---|---|---|---|---|
| v1 | OHLCV+MA | 30 | 5 | 87% | 54% | 亏损 |
| v2 | 加振幅特征 | 20 | 3 | 82% | 57% | 小幅盈利 |
| v3 | 加dropout | 30 | 5 | 79% | 55% | 亏损 |
数值是我过往实验的大致典型结果,不是恒定值。做对比实验时一次只改一个变量,数据样本不足的情况下并行改多个超参数会让结果完全无法归因。我的习惯是先用小模型把数据管道跑通,再逐步加容量和调参,因为 CNN 股票预测的瓶颈通常不在模型复杂度,而在数据质量和评估方式是否诚实。希望这套流程能帮你在 TensorFlow 股票分析方向上少走弯路。
本文还有配套的精品资源,点击获取