ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

深度强化学习自动化股票交易策略:PPO/A2C/DDPG实战与回测

深度强化学习自动化股票交易策略:PPO/A2C/DDPG实战与回测 简介基于深度强化学习的自动化股票交易策略设计源码面向金融AI研究者和量化交易初学者提供从数据预处理、模型训练到回测评估的完整可运行项目解决如何利用PPO、A2C、DDPG三类Actor-Critic算法自动学习股票交易决策的问题。压缩包共40个文件约6.4MB以14个Python脚本为核心含多环境模拟、模型定义与训练入口另配4个XML算法配置、4个CSV交易数据、3张结果图、1个Jupyter Notebook、许可证与说明文档等目录结构清晰便于按模块研究。已有304人学习下载适合用于策略对比实验和强化学习入门实践。源码包含训练集/验证集/交易环境构建、模型参数配置和回测可视化可复现股票市场环境下的代理交易过程同时保留readme与requirements说明便于快速安装依赖并调整参数。支持自定义参数与训练/回测阶段切换适合二次开发。1. 深度强化学习自动化股票交易策略这个开源包到底能复现什么先给结论这是一份把「深度强化学习 自动化股票交易策略设计」落到可运行代码的完整源码包核心是让代理通过 PPO、A2C、DDPG 三种 Actor-critic 算法在历史行情上反复交互自己学出一套买卖动作。它不是量化平台不是选股公式而是一整套「环境构建 — 数据预处理 — 策略训练 — 回测验证」的流水线适合两类人一是正在做强化学习金融应用研究的从业者想看 State/Action/Reward 怎么设计二是已经跑通基础 RL 代码、想拿真实行情数据体验「训练一个交易代理」完整过程的工程师。我用 Jupyter Notebook 和 run_DRL.py 各跑过一遍可以负责任地说它解决的核心问题不是「怎么预测涨跌」而是「如何在不确定性极高的市场里通过学习历史数据来优化投资决策」——预测是回归任务交易是序列决策任务后者才是强化学习的用武之地。下文我会把环境设计、算法选型、训练流程、回测验证逐层拆开并附上参数细节和踩坑记录。2. 交易环境是黑匣子的入口State、Action、Reward 与三个环境文件的取舍2.1 为什么训练、验证、实盘要拆成三个环境打开 env 目录会发现项目把环境拆成了 EnvMultipleStock_train、EnvMultipleStock_validation、EnvMultipleStock_trade 三个文件。很多第一次接触强化学习交易的人会问同一个市场数据为什么要维护三份几乎一样的代码核心原因在于训练、验证、交易三个阶段的「动作自由度」不同。训练阶段代理允许做空验证阶段为了贴近真实交易动作会被限制在更保守的范围内而实盘交易环境还需要额外加载训练好的模型权重并且要处理 turbulence index市场波动率异常指标的实时裁剪逻辑。三份代码看似重复实则是把「探索 — 评估 — 部署」三个阶段的状态转换逻辑彻底隔离避免在训练时误用了部署阶段的风控规则。我一般会直接复用 train 环境做快速验证但做结果汇报时必须切到 validation 环境——因为 train 环境里的 reward 包含探索性噪声直接用它会高估策略真实收益。2.2 State 与 Reward 的构造方式环境的状态空间不是简单的「过去 N 天收盘价」。以 StockTradingRLEnv 为例每个时间步的 state 由三个 part 拼接而成part 1当前持仓权重向量shape 为 (num_stocks 1,)最后一位是现金占比part 2当前股价与历史股价的比值关系part 3技术指标窗口macd、rsi、cci、adx 等来自 preprocessing 阶段构造的特征这样的设计让代理在每个决策点既知道「我手里有什么」也知道「市场现在处于什么技术形态」。reward 的计算是这段代码的关键# 简化的 reward 计算逻辑摘自 StockTradingRLEnv.step() weights self.action # 代理输出的权重向量长度 股票数 1 portfolio_value np.dot(self.price_vector, weights) # 当前持仓市值 reward (portfolio_value - self.previous_portfolio_value) / self.previous_portfolio_value reward - self.turbulence_penalty # 市场异常波动惩罚 self.previous_portfolio_value portfolio_value代码逻辑很直白reward 等于持仓市值的变化率再减去一个 turbulence 惩罚项。这里有个容易被忽略的细节惩罚项不是固定值而是和当前 turbulence index 挂钩——市场波动越异常代理解到的负奖励越大从而学会在极端行情下降低仓位甚至空仓观望。2.3 step() 里发生了什么动作校验与惩罚项再把 step 拆细一点。代理每次输出的是一个连续动作向量长度等于股票数加一含现金需要经过 softmax 归一化成权重。环境拿到动作后要做三件事检查合法性、更新持仓、计算 reward。实际项目里常见的一个暗坑是「权重向量和为 1 但存在负值」——如果代理被允许做空softmax 就不会出现负权重但如果环境允许负权重就必须手动截断。这个项目默认走 softmax所以动作天然满足非负且和为 1省去了这层校验。但如果你想扩展成允许做空得自己去改动作解析逻辑否则代理会在非法动作空间里瞎逛训练根本收敛不了。惩罚项除了 turbulence还有一个隐形设计如果代理连续多步持仓不变reward 会趋近于零从而鼓励它「该出手时就出手」。这本质上是给探索加了个惯性惩罚防止策略退化成「永远满仓不动」的懒人策略。理解了这三个文件的分工你就拿到了打开整个项目黑匣子的第一把钥匙。3. PPO、A2C、DDPG 三套算法怎么选config 参数对照与适用边界3.1 config.py 的核心参数怎么读config/config.py 是整个项目的「总闸门」所有算法参数、数据路径、训练轮数、模型保存位置都在这里定义。打开文件你会发现它把每个算法的关键超参都集中在一个字典里比如 PPO 的 learning_rate、gamma、clip_rangeDDPG 的 tau、action_noise 等。读取方式很简单# 典型的配置读取方式 algo_name ppo # 可选 ppo / a2c / ddpg params config.PPO_CONFIG if algo_name ppo else ( config.A2C_CONFIG if algo_name a2c else config.DDPG_CONFIG ) env EnvMultipleStock_train( dfdata, stock_dimparams[stock_dim], state_spaceparams[state_space], action_spaceparams[action_space], )配置读取本身不复杂真正值得花时间的是理解每个参数背后的取舍。下面我按算法逐个说。3.2 PPO稳定第一适合当策略基线PPO 在这个项目里是默认首选原因很实在它用 clip 机制限制了每次策略更新的幅度训练过程不像传统 policy gradient 那样一波三折。把 clip_range 从 0.2 调到 0.1训练稳定性会明显提升但收敛速度会变慢反过来调到 0.3收敛快但容易在回测阶段出现净值曲线大起大落。我的使用习惯是第一天先拿 PPO 默认参数跑通全流程确认环境、数据、reward都没有问题再动参数调优。它在道琼斯 30 股票上的表现可以作为其他两个算法的基准线。3.3 A2C 与 DDPG两个极端方向的取舍A2C 是多进程版 Actor-Critic优势是训练速度快、方差小但在连续动作空间的股票交易场景里它的表达力比 PPO 弱一些——因为它没有 clip 机制策略更新幅度不受控对 reward 尺度变化很敏感。这个项目里 A2C 更适合拿来对比「同参数下不同算法的收敛速度差异」而不是直接当作主力策略。DDPG 则完全是另一个极端它适合连续动作空间用确定性策略 经验回放 target network 三重机制。它的坑在于「确定性」三个字——训练完成后直接拿它做回测容易碰到动作剧烈抖动导致的净值崩塌。项目里 DDPG 配了 OU noise奥恩斯坦-乌伦贝克过程来探索但回测时必须把 noise 关掉否则模型的确定性优势荡然无存。3.4 参数对照表与调参顺序下面是三个算法在 config 里的核心参数对比我按实际调参经验做了标注算法关键参数本项目典型值调参优先级容易翻车的点PPOlearning_rate0.00025先调学习率偏大导致策略崩坏PPOclip_range0.2次之太大则训练曲线震荡A2Cn_steps5先调太小则方差爆炸A2Clearning_rate0.0007次之对 reward 尺度极敏感DDPGtau0.001先调target 更新过快则不稳定DDPGaction_noise0.1次之回测不关 noise 直接翻车调参顺序我一般遵循「先环境后模型」第一步固定一套参数跑通流程第二步只调学习率观察 loss 曲线第三步调 clip 或 noise 改进探索质量最后才同时动多个超参做网格搜索。一次只动一个变量是铁律否则你会说不清是哪个参数救了模型。选型层面给个参考时间有限跑 PPO想看传统 Actor-Critic 的基线表现跑 A2C做连续动作空间研究或对比实验再碰 DDPG。这个优先级本身就符合三者的算法定位。4. 从 CSV 到训练完成run_DRL.py 的完整落地流程4.1 数据预处理归一化、turbulence 裁剪与训练集划分进入训练之前必须先过 preprocessing 这一关。项目自带的 data 目录里有道琼斯 30 成分股 2009 到 2020 年的日线数据dow_30_2009_2020.csv以及交易所交易基金数据ETF_SPY_2009_2020.csv预处理脚本 preprocessors.py 负责把原始 CSV 变成强化学习环境能直接消费的形式。# 数据加载与归一化的关键片段 df pd.read_csv(data/dow_30_2009_2020.csv, parse_dates[date]) df.sort_values([date, tic], ascendingTrue, inplaceTrue) # 技术指标计算得到 macd / rsi / cci / adx 等特征列 df add_technical_indicator(df, tech_indicator_list[ macd, rsi, cci, adx, close, high, low, open, volume ]) # 按日期分训练集、验证集、回测集 train_df df[df.date 2016-01-01] trade_df df[df.date 2016-01-01]这里的三个细节值得注意。第一排序必须是 date tic同一天内 30 只股票的记录要连续排列否则后续按窗口滑动的 state 拼接会错位第二技术指标列的顺序必须固定训练和预测时特征顺序不一致会导致模型输出完全不可用第三训练集和回测集按日期切分而不是随机切分这是时间序列数据的底线逻辑。turbulence 裁剪是另一道关键工序。dow30_turbulence_index.csv 存储了每个交易日整个市场的异常波动指标预处理时会计算一个阈值通常是按历史分位数超过阈值的日期会被标记为「异常状态」。这些日期进入环境后代理不会执行正常买卖而是强制降低仓位——这个设计直接呼应了第 2 章里那个 turbulence_penalty。4.2 训练入口 run_DRL.py 的执行顺序run_DRL.py 是项目的总入口参数解析、环境实例化、模型训练、模型保存全部由它串起来。它的执行顺序可以概括为四步# 训练 PPO 模型并保存到 trained_models 目录 python run_DRL.py --algo_name ppo --mode train --env_name dow30第一步加载数据和配置根据 --env_name 决定加载哪份数据根据 --algo_name 决定加载哪套超参。第二步构建训练环境注意这里用的是 EnvMultipleStock_train 而不是 validation。第三步进入训练循环每次 episode 结束时打印累计 reward定期保存 checkpoint。第四步把最终模型权重写到 trained_models 目录下文件名一般带 algo 名和时间戳。训练循环内部还有一个关键逻辑叫「ensemble 策略」三个算法不是各自封闭训练的而是共享同一个环境实例、同一份数据切分。这意味着最终对比实验结果时三者的训练数据分布完全一致差异只来自算法本身。这比「各跑各的随机种子」公平得多也是项目标题里「Ensemble Strategy」的体现。4.3 回测与结果落盘训练完成后回测流程通过 --mode test 触发。它会加载 trained_models 里的权重在 EnvMultipleStock_trade 环境上重放一遍历史数据计算每一步的持仓、现金、总资产曲线并生成成交记录。# 回测循环的核心逻辑 obs env.reset() while not done: action model.predict(obs, deterministicTrue) # 关闭随机探索 obs, reward, done, info env.step(action) daily_portfolio_value.append(env.portfolio_value)这里的 model.predict 传了 deterministicTrue含义是「只取策略网络输出的均值不做采样」。这个细节直接回答了很多人回测失败的问题训练时 DDPG 需要 noise 探索回测时坚决不能带 noise否则净值曲线会像锯齿一样来回抖动。回测结果会保存在 results 目录包含每日资产变化、交易次数、最大回撤等指标同时 figs 目录下会生成净值曲线和收益分布图。跑完整个流程你会发现这个项目把「训练 — 回测」做成了一次性流水线输入是一张历史行情表输出是一组模型文件和一组回测图表。后续所有策略分析、参数调整、算法对比都围绕着这组产物展开。5. 避坑清单turbulence 风控、reward 设计与结果复现的五个常见问题5.1 数据时间索引变成 object训练时索引错乱现象训练刚开始就报 KeyError或者 state 序列里出现重叠和跳跃。 原因用 pandas 读取 CSV 时没指定 parse_datesdate 列被当成字符串对象按日期滑窗时排序和切片都错位。 解决读取时强制 pd.read_csv(..., parse_dates[date])并在分组前做 dtype 检查。我每次拿到新数据都会先执行 df.dtypes 和 df.head() 确认索引类型这个习惯能省一小时排错。5.2 turbulence 超阈值后模型「沉默」误以为训练失败现象回测曲线有一段水平直线交易次数骤降为零。 原因这不是 bug是风控逻辑正常工作——当市场波动指数超过阈值环境屏蔽了交易动作代理被迫空仓。 解决在回测代码里打印每个时间步的 turbulence 值把「无交易区间」和「模型失效」区分开。如果你不想让风控介入可以直接调低 turbulence 阈值或把惩罚项权重设为零但这是自己承担风险的选择。5.3 DDPG 回测净值锯齿抖动最大回撤异常现象训练曲线正常一跑回测就断崖式下跌单日涨跌幅出现不可理喻的大数。 原因模型.predict() 在回测时还在做随机采样OU noise 没关闭。 解决回测时强制 deterministicTrue或者重建一个不含 noise 的 DDPG 实例加载权重。我在这个坑上花了一整天最后是逐行打断点才发现 predict 默认 behavior 是随机模式。5.4 同样参数跑两遍结果却不一致现象相同 seed、相同参数两次训练的累计 reward 曲线差异明显。 原因强化学习训练涉及的随机源太多——环境初始化的 shuffle、模型权重初始化、动作采样。项目默认没有对所有随机源做统一 seed 控制。 解决在 run_DRL.py 开头强制设置 random.seed(seed)、np.random.seed(seed)、torch.manual_seed(seed)并关闭 cuDNN 的自动调优。严格来说只有同时固定环境 shuffle 顺序和模型初始化才能真正复现。5.5 stable-baselines 老版本与新版 API 不兼容现象import 直接 ModuleNotFoundError或者 model.train() 报参数类型错误。 原因这个项目源码基于 ICAIF 2020 时期的 stable-baselines 接口和 stable-baselines3 完全不同。老版的 model.predict(obs, deterministic...) 风格在新版需要传 model.predict(obs, deterministic...) 之外还要注意 gym 环境注册方式。 解决先看 requirements.txt 里的依赖版本范围优先创建独立虚拟环境安装对应版本。如果必须升级到 sb3就得同时改环境类的 reset 返回值和 step 返回值的格式工作量大不太建议新手硬迁。以上五条是我复现这个项目的全部血泪经验核心教训是强化学习交易项目里看起来像 bug 的一半是风控设计看起来像设计的有时候是真 bug。动手之前先把逻辑边界画清楚。6. 把训练好的模型拉回 Notebook验证、回测与参数敏感性检查跑完 run_DRL.pytrained_models 目录下应该有几个已经收敛的权重文件。但训练完成不等于验证完成我习惯在 backtesting.ipynb 里做最后三道检查。第一道是「模型加载与单步推演」。哪怕训练正常也不能跳过这个 step——它验证的是模型文件和环境类能否在 Notebook 进程里重新组合起来。# 加载训练好的 PPO 模型并执行一个 step model PPO.load(trained_models/ppo_dow30.zip) obs env.reset() action, _states model.predict(obs, deterministicTrue) print(action sum:, action.sum(), action shape:, action.shape)如果 action.sum() 输出结果在 0.98 到 1.02 之间说明模型输出能正确解释成仓位权重如果偏差过大多半是 state 特征顺序和训练时不一致优先回头查预处理脚本。这一步同时验证 checkpoint 没有损坏是最便宜的后悔药。第二道是「参数敏感性检查」把 turbulence 阈值从 0.8 调到 0.5重新回测一遍对比两条净值曲线。这个对比能直观回答「策略收益是来自选股能力还是来自风控避让」是检验策略鲁棒性最有效的一张表。如果阈值调低后收益明显恶化说明策略本质是「靠躲大跌赚钱」而非「靠选股赚钱」。第三道是「权重导出」。把模型里 actor 网络的权重转成 numpy 数组供其他系统如实时行情接口的风控模块调用。# 提取 PPO 策略网络的参数为 numpy 数组 params model.policy.state_dict() actor_weights {k: v.cpu().numpy() for k, v in params.items() if actor in k} np.save(trained_models/ppo_actor_weights.npy, actor_weights, allow_pickleTrue)从那以后我每次拿到训练好的模型都会强制走一遍这三步先单步推演确认动作和权重匹配再做阈值敏感性分析排除运气成分最后导出权重留作系统对接。三个步骤加起来不过十分钟却能把「模型能跑」和「模型能用」之间的边界看得清清楚楚。希望帮到你。本文还有配套的精品资源点击获取
返回列表