深度强化学习驱动的股票交易策略:PPO、A2C与DDPG实战解析
简介基于深度强化学习的自动化股票交易策略设计源码面向金融量化研究人员和具备Python基础的投资者使用近端策略优化、优势演员评论家、深度确定性策略梯度这三种Actor-Critic算法训练交易代理在贴近真实的模拟市场环境中自动学习买卖决策降低人工情绪干扰并提升策略对行情的适应能力。整个压缩包共40个文件以Python脚本为主配套XML参数配置、CSV行情数据、PNG绩效图表和Jupyter Notebook交互演示整体约6.4MB目录划分清晰便于本地复现与二次开发。目前已有306人学习浏览。除了完整的策略实现代码以外还提供数据预处理、自定义交易环境、模型训练与回测等模块可帮助读者完整走通从数据到策略评估的流程理解强化学习在量化交易中的建模思路与关键调参点。1. 需求背景为什么把股票交易决策交给深度强化学习代理做过量化策略的人都有体会传统技术指标策略最怕的不是行情差而是参数失效。均线周期、布林带阈值、RSI 超买超卖线都是先定规则再跑回测行情一变规则就废只能反复人工调参本质上是在做“手工过拟合”。更深层的问题是交易本身是一个顺序决策过程——今天买不买会影响明天的持仓明天卖不卖又取决于今天的结果这类“决策影响后续状态”的问题正好是强化学习的适用场景。这套基于深度强化学习的自动化股票交易策略设计源码就是让 PPO、A2C、DDPG 三种 Actor-Critic 算法代理在历史行情环境中自主学习买卖动作目标只有一个——最大化累计投资回报。它适合两类人一类是做金融科技方向毕设或课设的学生需要一套完整可跑通、能讲清楚原理的交易策略实验平台另一类是想研究 DRL 交易方案的从业者需要一个包含数据预处理、环境建模、训练验证、回测对比全流程的参照实现。接下来我会按方案落地的顺序把技术选型、总体架构、核心实现、避坑点和验收方法完整拆开讲。2. 技术选型与项目结构PPO、A2C、DDPG 三种策略在 35 个文件中的分工2.1 三种 Actor-Critic 算法连续动作空间下的选型差异项目把 PPO、A2C、DDPG 三个算法放在同一套环境里对比训练这一设计的直接好处是评估 DRL 交易策略时不会因为“只试了一种算法效果不好”就否定整个方向。三者的选型逻辑在源码的模型目录里有清晰体现。PPOProximal Policy Optimization通过裁剪目标函数限制每次策略更新的步长训练稳定性最好适合作为交易策略的基线模型A2CAdvantage Actor-Critic用优势函数减少策略梯度的方差在数据量不大、需要快速迭代验证时更轻量DDPGDeep Deterministic Policy Gradient结合 DQN 的经验回放与策略梯度适合连续动作输出——交易的持仓比例就是一个连续值比如从 0 到 1 之间的任意仓位DDPG 可以直接输出这个比例而不用把动作离散成“全仓、半仓、空仓”。另外补充一点交易环境的动作空间如果离散化太粗会损失调仓的精细度这是很多自写 DRL 交易策略效果不理想的原因之一。三种算法在模型结构上共享同一个多层感知机骨干区别主要在策略更新方式这降低了工程量。对比实验也方便同一份数据、同一个环境只换 agent 类型回测结果就能横向比较。2.2 数据集与基准从 DJI、SPY 到 30 支道指成分股的评估口径数据目录里的文件不是随便放的它们构成了一套完整的评估口径。^DJI.csv是道琼斯工业指数历史行情用来代表市场整体走势ETF_SPY_2009_2020.csv是标普 500 ETF 行情作为策略收益的对照基准——如果一个 DRL 交易策略跑不赢 SPY那这个策略在实操层面意义不大dow_30_2009_2020.csv是道指 30 支成分股的后复权价格序列作为交易标的池dow30_turbulence_index.csv是湍流指数用来衡量市场异常波动程度。需要特别说明湍流指数的用途当湍流指数超过阈值时市场处于高风险状态训练和交易环境会把动作强制置为“空仓”。这是金融 DRL 里常用的风控手段本质上是给代理加了一个外部安全护栏避免在极端行情下模型做出赌徒式决策。数据划分上项目按时间顺序切分训练集和验证集而不是随机打乱这是时间序列数据建模的红线随机打乱会造成未来数据泄漏。2.3 项目文件结构9 个脚本、4 个配置、8 个数据文件分别承担什么职责拿到源码包后建议先按职责把文件分成四组避免迷失细节。第一组是入口与流程控制run_DRL.py作为主控脚本接收算法名、训练时间、交易时间等参数并编排训练—验证—回测全流程config/目录下是参数配置文件。第二组是环境与模型env/目录下StockTradingRLEnv.py是核心交易环境实现EnvMultipleStock_train.py、EnvMultipleStock_validation.py、EnvMultipleStock_trade.py分别对应训练、验证和模拟交易场景的环境封装models/models.py是三种算法的模型定义。第三组是数据与预处理data/目录存放原始行情和湍流指数preprocessing/preprocessors.py负责把原始数据切分、归一化并生成特征矩阵。第四组是分析与演示backtesting.ipynb提供交互式回测分析流程。文件逻辑可以参考下面的对应关系职责相关文件作用说明主控入口run_DRL.py解析参数调度训练/验证/交易环境建模env/StockTradingRLEnv.py定义状态、动作、奖励、终止条件训练与回测env/EnvMultipleStock_train.py/trade.py训练模式与回测模式的环境差异封装数据加工preprocessing/preprocessors.py数据切分、特征归一化、湍流指数接入参数配置config/config.py算法超参、路径配置、随机种子管理交互分析backtesting.ipynb加载模型、回测、可视化对比整套源码在文件组织上是标准的“数据进 → 环境交互 → 策略输出 → 回测验证”流水线第 3 章会展开讲这条流程的设计逻辑。3. 总体架构设计从数据预处理到回测验证的四阶段流水线3.1 数据管线湍流指数与价格序列的标准化处理先看数据是怎么变成状态输入的。preprocessing/preprocessors.py里默认的处理流程是取收盘价计算收益序列再叠加几个技术指标如 MACD、RSI、CCI、ADX构成每个时间步的特征向量。这里有两个关键设计一是特征序列要按时间窗口切片每个状态包含过去 N 天的特征让代理能看到短期趋势而不是只看单日涨跌二是归一化要在训练集上计算均值方差再用同一组参数去归一化验证集和测试集这一点项目在代码注释里反复强调过实际跑的时候也特别容易写错——如果分别在训练集和验证集上各自归一化等于偷偷把未来的统计信息带进来了。湍流指数的接入方式我一般会重点关注。它在项目里的作用不是作为其中一个特征列而是作为一个独立的“环境风险门控”当turbulence_index超过设定阈值时环境会将代理的仓位动作强制置零并且把奖励设为一个较小的惩罚值相当于告诉代理“现在风控系统接管你的决策无效”。这段逻辑写在EnvMultipleStock_trade.py里做课设的同学可以在答辩时重点讲这一设计因为它体现了 DRL 落地时的工程思维——纯强化学习代理并不天然具备风险意识需要外部规则约束极端风险。3.2 环境模块训练、验证、模拟交易三个场景的职责划分项目把环境拆成三个文件这个细节值得展开。训练环境EnvMultipleStock_train.py允许代理在湍流指数低于阈值时自由交易reward 直接取当期资产净值的变化率目的是让代理在常规行情中充分探索盈利动作验证环境EnvMultipleStock_validation.py在训练过程中周期性调用用于评估当前策略在未见数据上的表现会不会触发早停由这里的回报决定模拟交易环境EnvMultipleStock_trade.py则更严格它不仅启用湍流指数强制清仓逻辑还把动作输出从“对单只股票的持仓比例”扩展为对所有股票的持仓矩阵同时限制了单次交易手续费。三个环境共享底层的StockTradingRLEnv.py核心逻辑状态空间是“历史窗口特征 当前持仓 账户余额”的拼接向量动作空间是每个标的的持仓权重reward 是扣除交易成本后的资产净值变化率。区别只在于冠层约束——训练环境更宽松验证环境更中立模拟交易环境更保守。这个“三个环境一个核心”的结构是为了同时满足探索、评估、风控三个目标也是这类交易仿真项目里比较成熟的做法。3.3 训练-验证-回测闭环backtesting.ipynb 里的反思池机制backtesting.ipynb不是简单地把模型拿来回测就结束它实现了更完整的闭环。训练完成后脚本会加载最优模型在回测图上同时绘制三组曲线基准SPY/DJI、策略净值、湍流指数。关键机制是一个“反思池”设计回测结束后程序会把策略跑输基准的时间段单独截出来将对应区间的湍流指数、持仓比例和收益数据存入一个内存池供下一步分析。用大白话讲就是让模型自己找出它“哪些时间段做错了”然后你可以针对这些片段去调环境奖励或者改风控阈值。从这套闭环可以提炼出一个通用的 DRL 交易策略设计模式离线训练——在线校验——失败模式归因。很多学生写课设卡在最后一步模型也训了、回测也跑了但老师问“为什么比不过 SPY”时答不上来。反思池这种机制本质上是把“策略为什么失效”变成一个可追溯的技术问题这在答辩时非常加分。3.4 奖励塑形与早停避免“浮盈回撤”和“过拟合反弹”奖励函数设计的细节项目里有一处很容易被忽略但很关键的设置训练环境中除了使用资产净值变化率作为主要奖励还叠加了一个负向的仓位变动惩罚。换句话说如果代理频繁买卖即使每次交易没有亏损累计手续费也会侵蚀收益因此在 reward 里直接扣掉调仓成本让代理学会“少动、动得准”。这比把手续费硬塞进环境计算更有效因为它作用在梯度方向上代理能直观感受到“动一下就要付出代价”。早停机制则写在训练循环中——验证回报连续一定轮次没有创出新高就把学习率降低一档再继续训练如果降低学习率后验证回报继续走平则终止训练并保存历史最优模型。我通常会再配合一个检查点策略每轮训练结束都对比当前验证回报与历史最优值只有刷新最优时才写盘覆盖trained_models下的模型文件这是防止“训练中后期验证效果震荡导致最终保存模型反而变差”的后悔药。项目的模型保存逻辑走的也是这条路。4. 核心模块实现配置、模型与主控脚本的可复现细节4.1 config.py参数集中管理与随机种子固定config/config.py是所有实验复现的起点。文件里定义了数据路径、训练与策略参数以及模型的超参数池。超参数池的设计比较实用它把多个候选参数组合放进一个列表训练时逐个尝试最后按验证回报选优。# config/config.py 中核心参数池的结构逻辑示意 timesteps_dict { PPO: 20000, # PPO 训练总步数 A2C: 20000, # A2C 训练总步数 DDPG: 40000 # DDPG 收敛慢步数加倍 } hyperparams_pool { PPO: { hiddens: [128, 128], # 策略网络隐藏层宽度 layers: 2, # 隐藏层层数 batch_size: 64, # 每次更新采样的数据量 lr: 3e-4, # 学习率 gamma: 0.99, # 折扣因子 clip_range: 0.2, # PPO 裁剪范围 ent_coef: 0.01 # 熵系数控制探索程度 }, A2C: { hiddens: [128, 128], lr: 7e-4, # A2C 通常用更高一点的学习率 gamma: 0.99, n_steps: 5 # 优势估计的回看步数 }, DDPG: { hiddens: [256, 256], # DDPG 网络结构通常更大 lr: 1e-3, # actor 学习率 qf_lr: 1e-3, # critic 学习率 tau: 0.005, # 目标网络软更新系数 gamma: 0.99 } }这段配置说明几个设计点DDPG 所需的训练步数是 PPO 的两倍原因在于 DDPG 是离线策略算法经验回放缓冲区的样本利用率较低需要更多环境交互才能稳定收敛A2C 用较低的n_steps做优势估计是因为交易环境的奖励密度不高过长的回看步数容易引入噪声。随机种子固定这里也需要强调项目在config.py里显式设置了seed同时要求环境创建和模型初始化时都继承这个种子否则即使超参数完全一致不同机器上复现出来的结果也会差很多。4.2 models.py 与训练循环三种算法共用的模型初始化逻辑models/models.py里通过一个工厂函数统一创建 model 实例代码逻辑上类似下面的结构。它的核心意义在于后续跑实验只需要传算法名不需要关心每个算法内部构造函数的差异。# models/models.py 模型工厂函数结构示意 from stable_baselines3 import PPO, A2C, DDPG def create_model(algo_name, env, config): 根据算法名创建对应的 DRL 模型并统一设置随机种子。 common_params { env: env, gamma: config[gamma], seed: config[seed], tensorboard_log: config[tensorboard_dir] } if algo_name PPO: return PPO( policyMlpPolicy, n_stepsconfig[n_steps], batch_sizeconfig[batch_size], clip_rangeconfig[clip_range], ent_coefconfig[ent_coef], **common_params ) if algo_name A2C: return A2C( policyMlpPolicy, n_stepsconfig[n_steps], **common_params ) if algo_name DDPG: return DDPG( policyMlpPolicy, buffer_size1_000_000, # 经验回放缓冲区大小 learning_starts1000, # 开始更新前最少收集的样本数 tauconfig[tau], **common_params ) raise ValueError(fUnsupported algorithm: {algo_name})工厂模式在这里的价值在于解耦训练脚本run_DRL.py不需要关心某个算法内部有多少超参数只要把config字典传进去就行。DDPG 单独设置learning_starts1000是因为它依赖经验回放训练初期缓冲区样本不足时强行更新会导致严重偏差。另外项目为 DDPG 设置了更大的隐藏层256 而非 128这是因为它需要同时拟合策略网络和 Q 值网络表达能力不足时容易出现对动作价值估计不准的问题。4.3 run_DRL.py训练-评估-模拟交易三个入口的参数路由主控脚本是整个项目的总开关。它接收--mode训练或测试、--algo算法名等参数不同模式走不同的环境与模型加载路径。# 训练 PPO 策略 python run_DRL.py --mode train --algo PPO # 加载已训练模型进行回测 python run_DRL.py --mode test --algo PPO训练流程在执行时大致遵循下面的逻辑数据预处理 → 根据模式选择环境类 → 创建或加载模型 → 训练循环内周期性调用验证环境评估 → 验证回报刷新最优值时保存模型 → 训练结束后加载最优模型跑回测并绘制对比图。关键在于模式与环境的绑定训练模式绝不加载验证或模拟交易环境模拟交易模式也绝不允许从零训练一个新模型必须加载已有权重。这个约束挡掉了不少误操作比如把训练环境训出来的模型直接拿到模拟交易环境里跑而导致维度不匹配的崩溃问题。模型保存路径统一在trained_models/下文件名包含算法名与验证回报值比如PPO_20250401_123456.zip。这里有个细节值得借鉴文件名里带上验证回报后续回测时不需要打开模型看日志就能判断哪个模型效果最好。我自己的习惯是在训练循环里加一个日志回调每个轮次记录验证集夏普比率和最大回撤这两个指标比累计回报更能反映策略质量也是课设答辩时老师最常问的问题。4.4 一套代码的落地要点本地跑通与数据替换策略这一节讲把项目跑起来的具体步骤默认环境是 Python 3.8 及以上依赖由requirements.txt管理核心包括gym、stable-baselines3、pandas、numpy和matplotlib。第一步是安装依赖并确认版本兼容关系建议指定stable-baselines3的次版本号避免 API 变更导致models/models.py里的 import 报错。第二步是直接运行训练命令如果使用的是dow_30_2009_2020.csv全量数据单次 PPO 训练在普通 CPU 机器上大约需要 20 到 40 分钟属于可接受范围。第三步是替换自己的数据做实验注意保持 CSV 列名与原数据一致——日期列必须叫date价格列必须包含open、high、low、close以及volume否则preprocessors.py的特征计算会直接 KeyError。跑通之后建议把湍流指数阈值作为一个自由参数来做敏感性分析阈值调低意味着风控更频繁介入策略会更保守调高则代理有更大空间在动荡行情中做决策但回撤也可能变大。这个分析做到位在答辩时可以很有底气地回答“你的策略有什么风险控制手段”。5. 常见问题与避坑训练不收敛、数据泄漏与赚指数不赚钱5.1 现象训练 reward 一路上涨验证集却不赚钱最常见的情况没有之一。训练环境里代理的累计回报越涨越高甚至高到单日翻倍但加载模型跑回测时策略净值跑不赢 SPY甚至在下跌行情里比指数跌得更狠。原因通常有两个。一是训练环境允许代理在湍流指数较低的行情中频繁做多而这些高回报时段集中在某些特定趋势段模型把这些片段背下来了并没有学到可泛化的交易逻辑。二是 reward 函数里调仓惩罚设置过小代理可以通过频繁买卖赚取微小波动而这些差价在扣除真实手续费后就消失了。解决方法是先调大调仓惩罚系数让代理的单次调仓必须带来明确收益才划算然后在验证环境里开启湍流指数强制清仓逻辑观察策略在包含动荡段的数据上的最大回撤是否收敛最后检查训练集和验证集的区间划分是否严格按时间顺序最好的自查方式是打印两段数据的起止日期确认没有重叠。5.2 现象湍流指数为 0 时模型频繁误交易有同学跑完回测发现策略在某些波动很小的横盘段疯狂调仓交易记录里一天内买入卖出十几次手续费把收益吃掉了大半。原因不在算法而在于环境对“零波动”状态的处理。湍流指数在横盘期接近 0代理看到的状态特征是价格几乎不动它会倾向于频繁小幅交易来试探奖励信号因为在这种状态下无论买还是卖reward 都不会显著变负探索的代价很低。解决方式是给环境加一个持仓不变的动作倾向当湍流指数低于阈值且当前收益率的绝对值小于某个极小门限时屏蔽动作变更。具体实现可以在StockTradingRLEnv.py的step方法里做判断如果动作与当前持仓的差异度小于比如 0.01就直接按持仓不变处理不扣除调仓费用。这个改动对策略稳定性立竿见影。另一种思路是调整熵系数PPO 的ent_coef降一点减少策略网络的随机探索行为。5.3 现象不同机器、不同运行次数结果差异大训练两次 PPO保存的两个模型回测结果一个盈利一个亏损明明参数完全没改这就是经典的强化学习可复现性问题。原因有三层一是随机种子没有同时作用于环境初始化、模型初始化和数据加载三处只是配置了其中一处二是stable-baselines3依赖的 PyTorch 在 CPU 上默认开启多线程线程调度顺序不同会导致数值结果偏差三是数据归一化顺序依赖全局状态如果preprocessors.py里用了字典按无序键遍历顺序不固定也会引入差异。解决的顺序是先确认config.py中的seed被同时传给环境和模型工厂函数再给 PyTorch 设置固定线程数写入主控脚本开头最后把数据预处理做成全函数式输入输出之间不依赖任何全局变量。做完这三处同一台机器上两次训练的验证回报曲线应该几乎完全重合。5.4 现象训练结束后加载模型测试环境中出现维度不匹配报错训练环境里动作维度是单只股票持仓比例加载到模拟交易环境后传入动作矩阵时直接抛 ValueErrorshape 对不上。原因基本可以锁定在三处环境初始化参数不同——模拟交易环境要求stock_dim为实际股票数而训练环境可能被配置成单标的第二个是动作归一化边界不一致训练环境动作空间是 [-1, 1]模拟交易环境期望 [0, 1]第三处是turbulence_threshold在验证时传入了一个训练时不存在的新值导致内部逻辑分支异常。解决时先检查EnvMultipleStock_trade.py的初始化参数确保stock_dim和特征维度与训练时完全一致再对比训练和交易两个环境里action_space的定义如果边界不同需要在加载模型后对动作做一次线性映射。我一般会在训练结束前打印一次环境配置字典并在测试入口处增加断言两个环境的所有关键配置必须完全相等。5.5 现象模型保存后重新加载回测跑出来全是零或 NaN模型文件能正常载入model.predict()也不报错但回测结果全是 0 或者 NaN净值曲线直接断掉。原因通常是归一化参数在测试阶段丢失。训练时preprocessors.py计算出的均值和标准差被保存在内存里但模型保存时只保存了策略网络权重没有保存归一化参数测试脚本重新加载数据时又在全量数据上重新计算了均值和方差导致状态特征分布错位策略输出进入无效区间。解决方法是把归一化参数持久化训练结束后将均值和标准差存入一个npy文件测试时从文件加载而不是重新计算。另一个容易踩的坑是 NaN 来自湍流指数的计算dow30_turbulence_index.csv里个别日期存在缺失值读入后没有做填充直接参与特征拼接。检查数据文件并用前向填充处理缺失值这个问题就能消失。6. 验收与答辩从修复回测到说清“策略有效”的四步走6.1 静态验收不跑代码也能过的检查清单在开启动态回测前先按清单确认代码质量这几项都是答辩评委最容易盯住的地方。验收项检查内容通过标准数据划分训练/验证集是否按时间顺序切分验证集全部日期晚于训练集归一化一致性验证/测试是否复用训练集统计量归一化参数来自文件而非现场计算风控逻辑生效湍流指数超阈值是否强制清仓回测日志中动荡段持仓为 0随机种子完备seed 是否传递到环境与模型二次运行训练曲线重合手续费计入reward 是否扣除调仓成本持仓不变时 reward 为 0这五条对应第 5 章里最容易翻车的点静态验收的核心思路是不依赖跑出来的结果是否赚钱先保证实验过程本身科学。6.2 动态验收把“赚钱”换成“跑得赢、回撤小、可持续”动态验收我习惯分三步走。第一步是与基准对比在backtesting.ipynb中同时绘制策略净值、SPY 净值、DJI 净值三条曲线计算超额收益策略累计收益减基准累计收益第二步是风险指标统计最大回撤和夏普比率策略夏普比率至少要高于基准 0.3 以上回撤不应超过基准太多第三步是分段评估把回测区间按湍流指数高低切成“平静期、动荡期、极端期”三段观察策略是否只在平静期赚钱如果动荡期回撤明显放大说明风控阈值没起到预期作用。这一步如果策略整体跑赢了 SPY那这份源码的价值就落地了。如果跑输也不要慌分析方向很明确先看湍流阈值设置是否过松再看调仓惩罚是否过小最后检查训练步数是否不足。多数情况下问题出在训练步数不够DDPG 尤其明显——4 万步在股票数据上往往只能算热身可以先把训练步数翻倍重训。6.3 答辩技巧把黑匣子讲成可解释的项目故事答辩时不要试图把强化学习的数学公式全部推导一遍评委更想听到“为什么选这个方案、怎么验证、结果说明什么”。我建议围绕三个问题组织表述为什么用 DRL 做交易策略——因为交易是序贯决策传统监督学习无法处理“当前决策影响后续状态”的问题而强化学习的马尔可夫决策过程天然契合为什么用这三种算法对比——PPO 保稳定、A2C 提效率、DDPG 处理连续仓位三者实验结果互为对照避免单算法偶发性偏差为什么结果可信——因为验证期完全在时间序列上隔离手续费被计入奖励湍流指数提供了极端风险的外部屏障。结尾用我自己的习惯收个尾从那以后我每次拿到一个 DRL 交易项目都强制自己先跑一遍第 6.1 节清单再做任何策略调优——先证明实验过程没有科学瑕疵再讨论赚钱能力顺序反了就会被结果带到沟里。希望这篇文章能帮你把整套源码落地成一份拿得出手的完整方案。本文还有配套的精品资源点击获取