
简介面向Python与TensorFlow股票预测学习者这份压缩包围绕CNN与DQN深度学习模型预测股票走势展开提供完整可运行的代码工程与教学配套适合具备Python基础并希望深入实践深度学习的开发者。包内共34个文件总大小5.16MB以6个py脚本、5个ipynb笔记本和8张PNG结果图为主体配套2份PDF说明文档并附带数据文件、模型checkpoint与pyc缓存方便直接复现数据预处理、模型训练与预测过程。已有1440人浏览学习适合对时序预测、强化学习及指标可视化感兴趣的Python开发者。资源按CNN股票分类、DQN图像预测、KD价值分析等模块组织覆盖数据预处理、模型搭建、训练评估与绘图展示全流程notebook与脚本相互对照可快速上手修改参数并重新训练结合PDF大纲还能进一步掌握分布式TensorFlow与股票预测的整体思路。1. TensorFlow CNN预测股票走势从卷积核到K线的落地拆解把卷积神经网络用在股票预测上第一眼看上去像玄学——CNN不是用来识别猫狗图片的吗但看完这套代码包你会发现卷积的局部感受野恰好能捕捉连续几天K线组合形成的小形态比如三连阳、长上影、量价背离。包里的内容分两条线一条用CNN直接回归收盘价和KD值一条把DQN和CNN组合到年线判断上。适合已经会调pandas、想往深度学习量化方向踩第一脚的人。这篇文章会把数据预处理、模型参数、三个预测场景的代码逻辑和踩坑点拆开来讲。2. 数据预处理与滑动窗口把时间序列变成CNN能识别的二维输入2.1 为什么CNN能用在股票时间序列上很多人一听到CNN预测股票就觉得是噱头其实CNN的本质是局部特征提取器。一张图片里边缘、纹理这些局部模式组合成语义一段K线里连续几天的开盘、收盘、高低价组合同样构成了有意义的局部模式。比如连续三天收盘价逐步抬高且量能放大这种形态跟图像里的边缘特征在数学表达上是一致的——都是局部窗口内的数值关系。这个项目里同时走了Conv1D和Conv2D两条路。Conv1D直接作用在价格序列上每个卷积核扫过一个长度为window_size的窗口提取出这个窗口内的价格形态Conv2D则先把行情数据渲染成图片再用图像卷积的思路去识别K线图上的视觉模式。后者在DQN_img_closePrice.py和DQN_kd_pic.py里体现得很明显文件名里的img和pic就是证据。这种把行情画成图再喂给卷积网络的做法在量化圈里并不少见核心动机是让CNN在它最擅长的图像领域发挥优势。但不管走哪条路前提都是数据要整理成CNN能吃的样子。CNN不关心你输入的是不是价格它只关心张量形状。所以第一步永远是数据处理。2.2 滑动窗口构造样本窗口长度、步长与标签对齐时间序列预测和普通机器学习任务最大的区别在于样本构造方式。你不能像分类猫狗那样把每张图独立打乱股票数据的每个样本天然带有时间顺序。常见做法是滑动窗口——用过去N天的数据预测下一天窗口不断往后移生成成百上千个样本。这个项目的数据预处理走的也是这个路子。import numpy as np import pandas as pd def build_window_dataset(df, feature_cols, target_colclose, window_size10, step1): X, y [], [] df df.reset_index(dropTrue) for i in range(window_size, len(df), step): X.append(df[feature_cols].iloc[i-window_size:i].values) y.append(df[target_col].iloc[i]) return np.array(X), np.array(y) # 假设df已经按日期排好序包含open/high/low/close/volume列 feature_cols [open, high, low, close, volume] X, y build_window_dataset(df, feature_cols, target_colclose, window_size10, step1) print(X.shape) # (样本数, 10, 5) print(y.shape) # (样本数,)这段代码的核心逻辑是从第window_size行开始往后遍历每次取前10行数据作为特征当前行收盘价作为标签。step控制滑动步长设为1表示每天都生成一个样本如果想让训练样本更稀疏、降低相关性可以调成2或3。X的形状是(样本数, window_size, 特征数)这个三维张量正好是Conv1D的输入格式。有几个细节要留意。第一df必须按时间升序排好否则窗口切出来的数据是乱的第二target_col既可以是收盘价本身也可以是未来第N天的价格取决于你想预测明天还是未来一周第三如果你想预测涨跌方向而不是具体价格y应该改成np.where(df[close].shift(-1) df[close], 1, 0)这样的二值标签。2.3 数据标准化与训练集/验证集划分防止未来数据泄漏标准化在股票预测里是个容易翻车的环节尤其是用StandardScaler或MinMaxScaler时稍不注意就会把验证集信息提前泄漏到训练集里。正确的做法是先切分数据再在训练集上fit标准化器然后用同一个标准化器去transform验证集和测试集。from sklearn.preprocessing import StandardScaler train_size int(len(X) * 0.7) val_size int(len(X) * 0.15) X_train, y_train X[:train_size], y[:train_size] X_val, y_val X[train_size:train_sizeval_size], y[train_size:train_sizeval_size] X_test, y_test X[train_sizeval_size:], y[train_sizeval_size:] # 注意scaler按特征维度fit需要先reshape成2D scaler StandardScaler() X_train_2d X_train.reshape(-1, X_train.shape[-1]) scaler.fit(X_train_2d) def transform_X(arr): shape arr.shape return scaler.transform(arr.reshape(-1, shape[-1])).reshape(shape) X_train transform_X(X_train) X_val transform_X(X_val) X_test transform_X(X_test)这里容易犯两个错。第一个是把所有数据混在一起fit_scaler导致验证集的最大值、均值参与了一训练集的归一化计算模型在评估时看到的分布是被剧透过的验证集指标虚高。第二个是随机打乱样本——train_test_split默认会洗牌这在时间序列场景下会破坏时间连续性模型等于偷看了未来数据。正确做法是直接按时间顺序切片像我上面这样。标准化之后还需要把标签也保存好原始尺度。因为模型预测出来的是标准化后的价格要还原成真实价格才能画图、算收益。这个反标准化的步骤需要保留 scaler 的mean_和scale_属性或者干脆用inverse_transform。项目里closePrice_rst.PNG那些结果图就是预测完反标准化之后画出来的。3. 搭建CNN回归模型从Sequential到卷积核参数的完整配置3.1 Conv1D和Conv2D怎么选先看输入数据的形状项目代码包里CNN_Classifier.ipynb用的是分类思路而DQN_img_closePrice.py则把数据渲染成了图片。这意味着项目本身对两种输入格式都有尝试。选型逻辑其实很直接如果你的X是(样本数, 窗口长度, 特征数)用Conv1D如果你把每天的行情画成了(高度, 宽度, 通道数)的图片用Conv2D。Conv1D的优点是结构简单、训练快对纯时间序列数据来说是更自然的选择。Conv2D的优势在于能捕捉K线图上的空间形态——比如影线长度、阳线阴线的视觉比例这些在像素层面有体现。但代价是数据渲染过程引入了额外的主观性图片怎么画、像素多大、颜色通道怎么编码都会影响模型效果。我一般建议先从Conv1D起步把预测流程打通之后再考虑Conv2D做对比实验。3.2 模型骨架卷积核数量、池化窗口、dropout怎么配这个场景下模型深度不用太夸张因为输入本身只有10到20个时间步总共就几十个特征维度。堆太深反而容易过拟合。下面这个结构是我按这套代码的常见风格搭的一个基线模型适用于收盘价回归任务。import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout, BatchNormalization model Sequential([ Conv1D(filters64, kernel_size3, activationrelu, paddingsame, input_shape(10, 5)), BatchNormalization(), MaxPooling1D(pool_size2), Conv1D(filters32, kernel_size3, activationrelu, paddingsame), BatchNormalization(), MaxPooling1D(pool_size2), Flatten(), Dense(64, activationrelu), Dropout(0.3), Dense(1) # 回归任务输出层不加激活函数 ]) model.compile(optimizeradam, lossmse, metrics[mae]) model.summary()Conv1D的第一个参数filters64是卷积核数量可以理解成模型想从窗口里提取64种不同的局部模式。kernel_size3表示每个卷积核看3个连续时间步的组合这是时间序列里常用的经验值相当于只观察三天内的价格关系。paddingsame保持序列长度不变避免卷积之后窗口尺寸缩得太快。BatchNormalization加在卷积层和激活函数之后作用是稳定训练、加速收敛尤其当你的数据标准化不够完美时它能兜底。MaxPooling1D(pool_size2)把序列长度减半降低计算量的同时强制模型关注更粗粒度的特征。两个卷积层之后接Flatten把二维特征图拉平再接两个全连接层。Dropout(0.3)在全连接层之前随机丢弃30%的神经元连接这是对付过拟合最常见的手段。最后一层Dense(1)没有加激活函数因为回归任务希望输出任意实数域的价格值而不是被sigmoid或者tanh限制在某个范围内。这一点和分类任务不一样——分类的最后一层通常是softmax而回归就是裸的线性输出。3.3 编译与训练损失函数、优化器和训练参数怎么定回归任务默认用mse均方误差作为损失函数这没问题但有个隐患如果价格序列里有极端值比如某天暴涨暴跌MSE会给这些样本非常大的梯度权重导致模型被几个极端样本带偏。稳健一点的替代方案是mae平均绝对误差它对离群点没那么敏感。我自己的习惯是lossmae、metrics[mae]因为股票数据的尾部波动本来就大没必要让模型拼命拟合那几次极端行情。优化器直接选adam初始学习率默认0.001就行。如果你的训练loss波动很大可以把学习率降到0.0005或者配合ReduceLROnPlateau回调动态调整。训练轮数上epochs50配batch_size64是一个比较稳妥的起点EarlyStopping 放到监控验证集loss上。from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau callbacks [ EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue), ReduceLROnPlateau(monitorval_loss, factor0.5, patience5, min_lr1e-6) ] history model.fit( X_train, y_train, validation_data(X_val, y_val), epochs50, batch_size64, callbackscallbacks, verbose1 )EarlyStopping的patience10表示如果验证集loss连续10个epoch没有改善就提前终止训练并自动恢复到验证集最优时的权重。ReduceLROnPlateau在loss停滞时把学习率减半帮助模型跨过局部极小点。这两个回调组合起来基本能避免训练集越训越好、验证集越训越差的困境。训练完之后评估要用测试集做一次彻底的体检test_loss, test_mae model.evaluate(X_test, y_test, verbose0) print(fTest MAE: {test_mae:.4f}) pred model.predict(X_test).flatten()model.predict返回的是标准化尺度下的预测值别忘了反标准化。这一步很多人会漏结果画出来的预测曲线和真实价格差了十万八千里。4. 项目实战走读收盘价、KD值、年线三条预测线的代码逻辑4.1 closePrice预测CNN直接回归收盘价项目里的DQN_img_closePrice.py和DQN_CNN_image目录盯的都是收盘价。收盘价是股票市场最基础的标签模型的目标是给出明天收在多少或未来N天收在多少的预测值。如果你打开DQN_img_closePrice.py会发现它走的路径是先取历史行情把收盘价序列和开高低收四条线画到一张图上然后CNN对这个图像做特征提取最后输出一个价格数值。这个思路比直接用Conv1D更重但有个隐性优势——图像渲染天然包含了K线的视觉结构比如阳线实体的长度、上下影线的比例这些几何信息在纯数值序列里需要通过多列特征组合才能表达。如果你是刚入门建议先用前面第3章的Conv1D模型跑通收盘价预测再去看这个文件里图像是怎么渲染的。图像渲染代码的核心就两步拿到matplotlib的Figure对象然后canvas.draw()转成numpy数组作为CNN输入。用收盘价做回归预测最直接的评估指标不是MSE而是方向准确率——预测值和真实值相比涨跌方向对不对。这个指标在交易里比数值误差更重要你预测明天收盘价是10.5元实际是10.4元数值只差了0.1元但如果方向判断反了做多就亏钱。所以后半段要单独设一个阈值来判断方向。4.2 KD值预测输出从价格换成随机指标DQN_KD_value目录下有一组和KD指标强相关的文件KD_draw.py、DQN_kd_pic.py。KD指标是随机指标Stochastic Oscillator由K线和D线组成取值在0到100之间用来衡量当前收盘价在最近一段涨跌区间中的相对位置。KD指标本身不是价格它是一个技术分析信号但很多量化策略把它当因子用所以直接建模预测KD值的人也不少。用CNN预测KD值思路和预测收盘价完全一致只是标签从原始价格换成了KD指标值。需要注意的是KD值本身做过平滑处理有粘性——今天的K值很大程度上取决于昨天的K值所以CNN很容易偷懒直接记住前一天的值就能把loss压得很低但这不代表模型真的学到了新的市场模式。做一个baseline对比就明白了用y_pred y_shifted_by_one也就是直接预测等于前一天值如果这个简单策略的误差和CNN差不多说明模型没有学到额外信息。预测KD值有一个实际用途当K值从低位上穿D值时常被视为买入信号。如果你能提前一到两天预测出这个上穿动作就有了先手优势。但要把预测KD数值转化成预测上穿事件需要另外加一层规则判断而不是直接看数值误差。4.3 yearline年线判断引入DQN强化学习思路项目里最有意思的应该是DQN_yearline_reward.py和DQN_draw_yearline.py。年线通常指250日均线是判断长期趋势的重要参考线。这个文件没有走标准的监督学习路径而是引入了DQNDeep Q-Network——一种强化学习算法。它不是直接预测年线上方还是下方作为标签而是让模型在模拟环境里做动作比如买入、持有、卖出reward是动作带来的收益变化DQN通过最大化累计收益来学会判断年线趋势。DQN在这套代码里的角色可以理解成一个把行情图像当作状态的决策器状态是当前和历史K线渲染出来的图像动作是交易决策奖励是账户收益的变化。saved_year_r目录里的文件应该是训练好的模型权重或者reward曲线数据。这种做法的优点是模型目标更贴近真实交易——它学的不是预测对错而是赚不赚钱缺点是强化学习训练不稳定reward尺度、折扣因子gamma、经验回放池大小任何一处设置不当都会导致训练发散。我用一个表格来梳理三条预测线的差异预测目标模型类型标签形式主要文件适用场景收盘价CNN回归标准化后的价格数值DQN_img_closePrice.py判断未来价格中枢KD值CNN回归0-100的KD指标DQN_kd_pic.py、KD_draw.py识别超买超卖拐点年线趋势DQN强化学习交易动作累计收益DQN_yearline_reward.py长期趋势策略三条线的数据预处理有一个共同点都是先把行情转成图像再进网络。这也是这套代码包和普通CNN股票预测教程最大的区别——别人用的是Conv1D直接吃数值这套代码把K线渲染成了图片跑Conv2D。两种方式各有利弊前者简单直接后者能利用CNN在图像识别上积累的成熟经验但渲染环节多出来的自由度图片尺寸、线条颜色、是否画均线本身就是一组需要调的超参数。5. 股票预测避坑指南数据泄漏、滞后预测与模型失效的排查5.1 训练集loss很低验证集却一路走高现象model.fit打印出的loss从0.1降到0.01但val_loss不仅不降还在往上跳。原因这个症状八成是数据泄漏。最常见的手法是先对全量数据做标准化再切分或者用train_test_split默认的随机切分导致验证集的信息提前进入了训练过程。另外一个隐蔽问题是标签错位——在构造y时用了np.roll或shift(-1)没处理边界值等于把当天数据同时放进了X和y里模型直接抄袭了答案。解决标准化只fit训练集验证集和测试集用同一个scalertransform切分必须按时间顺序走不shuffle检查标签构造逻辑确认X窗口的最后一天是y的前一天两者没有重叠列。如果怀疑是标签错位打印一条X[-1]和y[-1]的日期对比一下就知道。5.2 预测曲线总比真实价格慢半拍现象把预测值和真实价格画在一张图上预测曲线形状和真实曲线几乎一样但整体向右平移了一到两根K线像影子一样滞后。原因这是时间序列回归的经典陷阱。因为窗口里的最后一天和预测目标高度相关模型学到的不是预判未来而是复制昨天的收盘价。如果昨天涨了模型就预测今天涨今天继续涨模型就预测明天涨——在单边趋势里看起来命中率很高一进入震荡行情就原形毕露。解决把标签从明天收盘价改成未来N日平均价格或者未来N日的收益率方向迫使模型必须预测更远的未来而不是复读当天值。另一个做法是在评估时单独统计方向准确率别只看MSE或MAE。方向准确率低于52%的模型基本可以判定存在滞后效应需要重新设计标签和窗口结构。5.3 换一只股票模型就崩了现象模型在A股票上训练MAE很小、趋势拟合很好换成B股票测试预测结果完全不着调甚至方向准确率只剩40%。原因模型过拟合到了A股票的波动特性。每只股票的波动率、涨跌节奏、跳空习惯都不一样如果训练数据只有一只股票CNN提取的特征大概率是这只股票的专属规律而不是通用的市场规律。另外一个原因是训练集覆盖的市场环境太单一——比如只训练了上涨行情模型没见过下跌和横盘。解决训练集至少要包含3到5只不同行业、不同波动特征的股票并且覆盖至少一个完整的牛熊周期。更稳妥的做法是把多只股票的数据混合在一起训练每只股票按时间顺序切分验证集从没参与训练的股票里取。代码包里如果能同时跑通两只不同的股票模型才算有一点泛化能力。5.4 DQN训练不收敛或reward震荡剧烈现象DQN_yearline_reward.py训练时累计收益曲线上下乱跳几万个step之后也没有稳定上升的趋势有时候奖励值突然从正变成负。原因DQN对超参数比CNN敏感得多。reward尺度不统一是最常见的问题——如果一笔交易的盈利是100另一笔是0.1模型会把注意力全放在大额盈利上小波动直接被忽略。折扣因子gamma设得太大接近1会让模型过度关注远期收益在长周期策略里反而学不到东西设太小则只顾眼前利益。解决控制reward尺度把每次交易的收益除以一个基准数值或者用np.tanh把收益压到-1到1之间。gamma建议从0.9起步观察如果训练发散就调到0.8以下。经验回放池buffer_size通常不能小于一个epoch的样本总量采样时用优先经验回放之前TD误差大的样本更常被抽到效果更好。如果这些都不见效干脆先跑通CNN基础上加一层简单的决策规则不要一上来就碰强化学习。6. 让预测结果可验证一个简单的方向一致率评估方法训练完模型别急着下结论说能用来交易。我习惯先做一个方向一致率检查这是验证预测模型有没有价值的最快方式。原理很简单不看模型预测的价格准不准只看它预测的涨跌方向和真实方向是否一致。def direction_accuracy(y_true, y_pred, threshold0.0): y_true np.asarray(y_true).flatten() y_pred np.asarray(y_pred).flatten() if len(y_true) ! len(y_pred): raise ValueError(长度不一致) true_dir np.diff(np.concatenate(([y_true[0]], y_true))) pred_dir np.diff(np.concatenate(([y_pred[0]], y_pred))) correct ((true_dir threshold) (pred_dir threshold)) | \ ((true_dir -threshold) (pred_dir -threshold)) return np.mean(correct) acc direction_accuracy(y_test_original, y_pred_original) print(fDirection Accuracy: {acc:.2%})这个函数有两个隐藏细节。第一因为diff会丢一个值我用concatenate把预测首日作为基准保持序列对齐。第二threshold用来忽略微小波动——如果预测涨0.01元、实际跌0.005元这种方向错误本来就没什么参考意义把threshold设成当日振幅的0.1或0.2过滤掉噪声。我一般会把threshold调成0.5 * 当日平均波动幅度这样方向准确率不会因为几个擦边样本而失真。把方向准确率算出来之后还要结合滞后性检查一起看。我会把预测值和真实值画在同一张图上缩放之后直接叠在一起放大看波峰波谷处预测是不是普遍提前或滞后。如果预测曲线的波峰总是比真实波峰晚出现一两天说明模型大概率在复读行情方向准确率再高也不可信。从那以后我每次训练完模型都强制走一遍方向准确率 滞后性可视化这个流程不给模型自吹自擂的机会。这套代码包的价值不在于帮你直接赚钱而在于把CNN到DQN的完整链路走通让你在真实数据上对比不同预测目标的区别。希望这份拆解能帮你避开我踩过的那些坑下载后少走弯路。本文还有配套的精品资源点击获取