用Python+TensorFlow实现CNN股票预测:从数据管道到避坑指南
简介面向股票量化分析与深度学习入门人群的一套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 # 拉取股票日线数据period5y 表示最近5年 df yf.download(AAPL, period5y, interval1d, auto_adjustTrue) # 只保留 OHLCV 五列 df df[[Open, High, Low, Close, Volume]].copy() # 删除全空行按日期升序排序 df.dropna(howall, inplaceTrue) df.sort_index(inplaceTrue) # 检查是否有重复日期 print(f数据量: {len(df)} 行) print(f日期范围: {df.index.min()} - {df.index.max()})这段代码里auto_adjustTrue是关键参数。它让 yfinance 自动返回前复权后的 OHLCV 数据避免你自己处理除权除息。dropna(howall)只删除整行全为空的数据不删除单列缺失因为单独某个字段缺了可能是停牌导致的强行删行会破坏时间连续性。排序和去重是防止后续构造滑动窗口时出现时间倒序或重复日期。这里有一个容易忽略的坑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], inplaceTrue) 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当日收益率、ma55日均线、ma2020日均线。均线特征是趋势信息CNN 卷积核很难直接从原始价格序列中提取出这种跨时间步的统计量显式算出来更稳妥。3.2 滑动窗口构造样本lookback 窗口怎么定CNN 处理股票序列时输入不能是一行数据而是一个长度为lookback的窗口形状是(lookback, num_features)。窗口大小就是卷积核能看到的“历史视野”。我常用 20 到 60 之间的值对应 1 个月到 3 个月的交易日。窗口太小卷积核只看到局部波动窗口太大样本数量急剧减少且可能引入无关历史信息。def make_sequences(data, lookback30): X, y [], [] for i in range(len(data) - lookback): X.append(data[i:ilookback]) y.append(data.iloc[ilookback][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, lookback30) print(fX shape: {X.shape}, y shape: {y.shape})X.shape是(总样本数, 30, 7)这就是 CNN 的输入格式。第一维是样本数第二维是时间步第三维是特征通道。make_sequences里的循环从第 0 行开始滑动每次取 30 行特征用第 31 行的 label 作为该样本的标签。注意这里data[i:ilookback]是特征data.iloc[ilookback][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_size2, strides2会把 30 个时间步压缩到 15信息损失尚可接受。4.2 搭建一个可复现的 CNN 回归分类模型完整代码下面用 TensorFlow 的 Keras API 搭一个可运行的模型。环境要求是 Python 3.8 以上TensorFlow 2.10 以上。如果还没有装 TensorFlowpip 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(filters32, kernel_size5, activationrelu, paddingsame, input_shape(30, 7)), MaxPooling1D(pool_size2), Conv1D(filters64, kernel_size3, activationrelu, paddingsame), MaxPooling1D(pool_size2), Flatten(), Dense(64, activationrelu), Dropout(0.3), Dense(1, activationsigmoid) ]) model.compile(optimizertf.keras.optimizers.Adam(learning_rate0.001), lossbinary_crossentropy, metrics[accuracy]) model.summary()Conv1D第一层input_shape(30, 7)必须和滑动窗口的数据形状严格对应30 是时间步7 是特征数。kernel_size5表示卷积核覆盖 5 个交易日这最开始时可以从 3 到 7 之间选小于 3 感受野太小大于 7 容易把短期波动噪声学进来。paddingsame保持时间步长度不变这样第二层卷积能继续在完整序列上滑动。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(monitorval_loss, patience5, restore_best_weightsTrue) # 训练 history model.fit( X_train_scaled, y_train, validation_data(X_val_scaled, y_val), epochs50, batch_size64, callbacks[early_stop], verbose1 )restore_best_weightsTrue是保命设置。它会在 early stopping 触发后把模型权重恢复到验证集 loss 最低的那个 epoch而不是停留在训练的最后一步否则接下来的评估结果会失真。patience5表示允许验证集 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 降到 32Dense 层从 128 降到 64三是加早停patience设成 3 到 5 就行。如果降容量后训练集准确率仍然快速接近 100%检查一下是不是特征里混入了未来信息。另外还可以考虑加BatchNormalization它对缓解过拟合有一定帮助虽然这不是它的主要目的。5.4 序列划分错位把随机打乱的时间步重新输入 CNN现象用同一个模型第一次训练后准确率 58%什么都没改再训练一次准确率变成 63%结果不稳定。原因是在切分数据时用了train_test_split默认的shuffleTrue把时间上相邻的样本打乱了。CNN 的卷积核学到的是序列内的局部模式但训练集和验证集的时间跨度在打乱后互相穿插模型验证时可能已经“见过”同一段行情。解决方法是始终使用时间顺序切分train_test_split(X, y, test_size0.2, shuffleFalse)或者像我前面用train_sizeint(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, lookback30, train_years3, test_month1): 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:starttrain_len] test_data data[starttrain_len:starttrain_lenstep] # 这里重复前面构造样本、标准化、训练、预测的流程 # 记录预测准确率或累计收益 start step return results这段代码故意合并了多个环节实际使用时把前面第 3、4 章的样本构造和训练逻辑包进去。滚动验证的价值在于它能暴露模型在不同市场环境下的稳定性。我踩过的坑是在牛市数据上训练的 CNN 放到震荡市里预测准确率会明显下滑。如果滚动验证的多个时间窗口结果差异非常大说明模型学到的是特定行情特征而不是通用模式。6.3 从预测概率到交易信号软信号和硬信号的区别模型输出的概率值不是直接买或卖的指令还需要转成可执行信号。硬信号是设一个固定阈值比如概率大于 0.6 做多小于 0.4 做空中间区间空仓。软信号是直接把概率值当作仓位比例比如预测上涨概率 0.7 就投入 70% 仓位。我建议初学先从硬信号开始因为软信号在回测中容易产生过度乐观的结果它假定概率是真实的统计概率但 CNN 输出的概率往往过于极端。做最后一个记录的习惯把每次实验的模型结构、数据范围、特征列表、训练超参数、验证集指标都记在一个表格里。这样可以知道改了什么导致结果变好还是变差。下面是一个记录模板。实验编号特征列表lookback卷积核训练集准确率验证集准确率滚动验证盈亏v1OHLCVMA30587%54%亏损v2加振幅特征20382%57%小幅盈利v3加dropout30579%55%亏损数值是我过往实验的大致典型结果不是恒定值。做对比实验时一次只改一个变量数据样本不足的情况下并行改多个超参数会让结果完全无法归因。我的习惯是先用小模型把数据管道跑通再逐步加容量和调参因为 CNN 股票预测的瓶颈通常不在模型复杂度而在数据质量和评估方式是否诚实。希望这套流程能帮你在 TensorFlow 股票分析方向上少走弯路。本文还有配套的精品资源点击获取