简介:一份基于深度强化学习的自动化股票交易策略完整源码,面向金融量化研究者、投资者及强化学习爱好者,用于通过PPO、A2C、DDPG等Actor-Critic算法训练交易代理,实现在股票市场中的自动决策与回报优化。压缩包共40个文件,以Python脚本为核心,辅以Jupyter Notebook交互式分析、CSV行情与指数数据、XML算法配置、PNG策略图表等,整体体积仅6.4MB,目录划分清晰便于按模块研读。目前已有306人学习浏览。内容包括训练、回测与模拟交易环境的多模块Python实现,模型定义、预处理流程与三套强化学习算法对比框架完整可运行,可直接用于复现论文实验;同时附带的Notebook提供了交互式编程入口,方便调整参数观察策略表现。适合希望深入掌握强化学习在金融决策中应用的人群,拿到后即可从数据加载、模型训练到绩效可视化形成闭环学习。
1. 做自动化股票交易策略,先想清楚强化学习要优化的是出手时机还是仓位轻重
手动交易最大的问题是情绪干扰:追涨杀跌、拿不住盈利单、亏损后赌徒式加仓。于是我把目光放到深度强化学习上,希望训练一个代理,通过反复与环境交互,自动学会买卖节奏。这个方向听起来很性感,但真正落地时第一个坑就来了:很多人一上来就设计一个“预测涨跌,然后按信号全仓买或卖”的离散动作,结果训练半天发现代理学会了高频交易,手续费比收益还高。所以这套源码最值得参考的地方,是它没有把问题简化成“涨跌分类”,而是老老实实按照强化学习的马尔可夫决策过程来建模:状态用什么、动作怎么定义、奖励函数怎么设计,都直接决定了训练能否收敛。它用 PPO、A2C、DDPG 三种 Actor-Critic 算法分别训练代理,再通过集成策略做最终决策,还附带完整的回测 notebook。适合三种人:做强化学习课设和毕设的学生、想入门量化交易的开发者、以及想找一个可复现基准的金融科技方向研究者。下面我会把它的设计思路、关键实现和验收方法完整拆开讲。
2. 总体设计:三套环境引擎与状态动作空间的划分逻辑
先用一句话概括这套源码的整体结构:它不是一个大而全的框架,而是把强化学习交易任务拆成了 数据预处理、环境定义、模型训练、回测分析 四个阶段。拿到压缩包后,第一眼看到的是data、env、preprocessing、config、results这几个目录,外加run_DRL.py和backtesting.ipynb。这个分层的结构本身就是一张很好的设计图,我建议你直接把它作为自己项目的模块划分参考。
2.1 把股票交易问题建模成强化学习的马尔可夫决策过程
要使用深度强化学习,就必须先把交易行为抽象为一个标准的马尔可夫决策过程(MDP)。在env/StockTradingRLEnv.py里,核心就是定义状态、动作、奖励这三件事。
状态(State)由特征张量和对齐的观测窗口组成。这个项目不只是把收盘价丢给代理,而是把历史价格、成交量以及一个非常重要的风险因子——湍流指数(turbulence index)拼进观测空间。你可以把它理解为“市场情绪波动程度的度量”,当市场出现极端行情时,这个指标通常会明显升高。dow30_turbulence_index.csv里存放的就是这个因子的时间序列。
动作(Action)定义的是持有现金和股票的仓位比例。更具体一点,动作是一个向量,它说明每个交易日要把多少比例的资金分配给组合中的每只股票。这就避免了“只能全买或全卖”的粗糙控制,代理可以输出连续仓位,比如 30% 仓位、70% 仓位,而不是简单的 1 或 0。
奖励(Reward)这一步容易被初学者忽略,但它是整套代码里最能体现工程经验的地方。项目采用的不是简单的“当日收盘价减去昨日收盘价”,而是把交易成本也纳入到了奖励信号里。也就是说,如果代理当天频繁调仓,哪怕账面浮盈,扣除手续费后奖励也可能是负的。这就在强化学习内部天然形成了一种“少动手、动准手”的约束,而不是靠外部规则硬性限制交易频率。
2.2 训练环境、验证环境和交易环境为什么要拆成三个文件
打开env目录会看到三个环境文件:EnvMultipleStock_train.py、EnvMultipleStock_validation.py和EnvMultipleStock_trade.py。很多人第一次看会觉得冗余,甚至图省事只用一个环境。但是把它们分开,其实是防止时间穿越和防止过拟合的关键。
训练环境负责在样本内数据上交互,它允许代理以探索性的策略试探各种交易行为,每天结束时会根据奖励更新策略参数。验证环境则在时间上严格排在训练期之后的另一段数据上运行,这段数据代理在训练时完全没见过,用来做模型选择。交易环境模拟的是“今天决定明天的持仓”这种真实情况,它只使用截至当前时刻已知的数据,不会像训练环境那样频繁地重新随机初始化。
三个环境如果合并,很容易在验证阶段不小心用到未来数据,导致回测指标虚高。这种“数据泄漏”往往不回测不知道,一回测吓一跳——收益曲线看似完美,但放到新数据上立刻崩盘。拆分后,每个阶段的数据边界是清晰的,训练环境跑完保存模型,验证环境加载模型做评估,交易环境做最终的模拟落盘。这是整套源码中我认为最值得直接照搬的设计。
2.3 配置和预处理的职责都收拢到统一入口
config/config.py负责集中管理所有可调参数。这个文件是训练前的“总闸门”,我建议你把它打开看一眼,里面通常会有:数据路径、随机种子、时间窗口长度、批量大小、学习率、Episode 数量、模型保存路径等。把超参数统一放到一个文件里的好处特别明显——做实验时只需要改配置,不需要去每个脚本里挖参数。
preprocessing/preprocessors.py则负责把原始行情数据做成环境能消费的格式。比如计算收益率、滑动窗口标准化、生成技术指标等。它输出的是一个包含所有特征列的对齐数据表,并且会按时间排序。这里有个细节值得注意:数据预处理一定要放在训练之前一次性完成,而不是在环境 step 里临时算。否则每一步推理都重复计算窗口特征,训练速度会慢到让人怀疑机器坏了。
3. 核心算法实现:PPO、A2C、DDPG 三种 Actor-Critic 的落地与集成决策
深度强化学习算法有很多,但真正适合做连续动作交易的基本都落在 Actor-Critic 这个大家族里。项目选了 PPO、A2C、DDPG 三种算法,它们共享一套 Actor-Critic 骨架,却在策略更新方式上走了三条不同的路。
3.1 为什么不用 DQN 系列,而是用 Actor-Critic
交易动作本质上是连续空间——你可以把资金分配为 37.5% 和 38%,而不是只有“买”或“卖”两个离散选项。DQN 类算法处理离散动作是强项,但在连续动作上要么做离散化牺牲精度,要么用额外的优化器去逼近连续策略,复杂度反而更高。Actor-Critic 的做法更直接:Actor 网络根据状态输出动作分布的参数,Critic 网络评估这个状态下动作的好坏;两者相互博弈、协同提升。这也是项目把模型统一放在env/models.py里的原因——三种算法都可以基于同一套网络结构构建,只是优化目标和更新频率不同。
3.2 PPO 的实现要点与环境交互主循环
PPO(Proximal Policy Optimization)能成为当下强化学习主流选择,主要是因为它通过裁剪目标函数控制策略更新的幅度,让训练过程不那么“抽风”。在run_DRL.py里,训练主循环通常是这样组织的:
for episode in range(config.episode_count): state = env.reset() done = False while not done: action = agent.predict(state, deterministic=False) next_state, reward, done, info = env.step(action) agent.collect_rollout(state, action, reward, next_state, done) state = next_state agent.update() # PPO 在每轮 episode 结束后统一更新这段代码里最关键的是env.reset()和env.step(action)的返回值结构。在股票这类非 episodic 的时间序列里,一个 episode 就是一段连续交易日,当到达数据集末尾时done变为True。PPO 每收集完一整段交互数据后,一次性对所有样本做梯度更新,因此训练过程中不需要维护经验回放缓冲区,这也让它在大规模并行训练时更高效。
参数调优上,PPO 的裁剪范围通常在 0.1 到 0.3 之间,项目里用的默认配置大概率落在 0.2 附近。如果训练时发现策略更新不充分,可以尝试增大裁剪范围;如果发现回报曲线震荡剧烈,就应该减小裁剪范围。此外,Actor 网络和 Critic 网络的学习率通常需要分开设置,Critic 的收敛速度要比 Actor 快,所以给 Critic 设稍小一点的学习率是我常用的做法。
3.3 A2C 和 DDPG 的区别:同步更新与确定性策略
A2C(Advantage Actor-Critic)的核心是使用优势函数来降低策略梯度的方差。它的更新方式是每个时间步都进行一次策略和值函数的更新,而不是像 PPO 那样等一个完整 episode。由于股票数据每个 step 之间相关性很强,A2C 这种步步更新模式训练速度快,但也容易因为单步噪声导致策略震荡。项目把 A2C 作为对比算法纳入,正好方便你在实验报告里写“不同更新策略对收敛速度和稳定性的影响”。
DDPG(Deep Deterministic Policy Gradient)则完全不同。它是确定性策略方法,Actor 直接输出一个确定性的动作值,而不是动作分布的参数;Critic 通过 Q-learning 的方式评估动作价值。划分到经验回放缓冲区的结构上边,DDPG 必须有一个足够大的 buffer 来打破时间序列上的相关性,所以models.py里 DDPG 的实现通常会额外包含 buffer 管理逻辑和目标网络的软更新机制。连续动作空间是 DDPG 的主场,尤其是你需要控制数十支股票的仓位权重时,DDPG 有天然优势。
3.4 用湍流指数做集成决策:一个可复用的工程技巧
项目名里的 “Ensemble” 不是把三种算法训练完随便求平均。它更精妙的地方是引入了市场风险因子来做动态切换。我的理解是:当dow30_turbulence_index显示的波动低于阈值时,系统允许策略正常按模型信号交易;当湍流指数超过某一阈值时,说明市场环境已经和训练数据分布产生明显偏移,此时模型容易给出不靠谱的信号,策略会把仓位调低或切换回更稳定的算法决策。
这种做法的本质是给强化学习策略加了一层人工风险管理罩。现实中,很多强化学习训练出的策略在正常行情下收益漂亮,遇到异常波动就大幅回撤。加入湍流指数后,系统相当于学会了“在不确定的时候少犯错”。如果你要在自己的项目里复刻,可以在config.py中增加一个turbulence_threshold参数,默认取历史分位数,例如 95 分位。该阈值不宜取过低,否则会频繁阻断模型交易,导致策略颗粒无收。
4. 数据预处理与回测闭环:从原始行情到可汇报的绩效报告
一个强化学习交易项目能否成立,很大程度取决于回测流程是否可信。这里有一个顺序原则:先是数据准备,再是模型训练,最后是回测评估。如果回测本身设计不合理,前面训练得再辛苦也是白搭。
4.1 数据目录里的文件们怎么用
data目录中的dow_30_2009_2020.csv是核心训练数据,它是一只股票组合的日线数据,包含每只成分股的 OHLCV 和多只股票的历史值。ETF_SPY_2009_2020.csv则是对标的基准——训练出的策略表现好不好,必须拿它来和 SPY 指数这种“买入持有”策略对比。^DJI.csv是道琼斯指数数据,通常用来计算湍流指数或做宏观行情参照。
预处理这块容易出现一个低级错误:不同 CSV 的日期格式、列名大小写不一致,直接合并时会报错。我一般会在preprocessors.py里先统一日期列格式,再按时间排序,最后用后向填充补齐缺失值。股票数据不能用平均值填充缺口,因为未来信息会被填充进去,造成数据泄漏。
4.2 backtesting notebook 的角色
backtesting.ipynb不是用来训练模型的,它是训练完成后的分析工作台。里面通常会做几件事:加载训练好的模型权重、加载交易环境或回测环境、逐日模拟交易、记录每一笔调仓和资产净值变化、最后生成绩效曲线和对比表格。
Notebook 的好处是便于把过程分段展示:你可以把一个 cell 专门打印模型加载后对最近 20 个交易日的动作输出,另一个 cell 展示资产曲线图。这在课设或述职答辩时非常加分,因为评委能直接看到“训练 → 加载 → 模拟 → 出图”的完整链路,而不是只能看到一张孤零零的结果图。
4.3 回测结果中你需要重点盯住的四项指标
项目自带的figs/performance.png和figs/stock_trading.png就是回测结果的输出样例。我从图中拆出的四个核心指标你需要盯住:
# 回测完成后,统计关键绩效指标 total_return = (final_value / initial_value) - 1 annual_return = total_return / years sharpe_ratio = mean_daily_return / std_daily_return * sqrt(252) max_drawdown = max((peak - trough) / peak) print(f"总收益率: {total_return:.2%}") print(f"年化收益率: {annual_return:.2%}") print(f"夏普比率: {sharpe_ratio:.2f}") print(f"最大回撤: {max_drawdown:.2%}")计算max_drawdown时,必须按累积净值曲线的峰值来算,而不是简单看到哪天亏得最多。你还需要把策略曲线和 SPY 指数曲线画在同一张图里,这样才能看出策略在哪个阶段跑输基准。如果策略在所有上行阶段都跑输、只在下跌阶段少亏一点,那它本质上只是一个“低波动版本”的持仓,不能说它为投资带来了超额收益。
5. 避坑指南:训练与回测中常见的四个翻车点
这部分是我认为最有价值的实战经验。把这几个问题列出来,不是为了凑字数,而是训练这套源码时真的会陆续碰到。我照着项目跑通后又改了好几轮配置,翻过车才总结出这些。
5.1 训练集和验证集划分不当导致“时间穿越”
现象:训练时 reward 一直在涨,模型在训练数据上回测年化收益高得吓人,但一换到验证环境,绩效立刻打回原形。
原因:环境初始化时,如果训练环境和验证环境都从同一段数据里采样,代理实际上“记住”了训练数据中的价格形态,验证阶段没有真正的样本外验证。更隐蔽的版本是:验证数据集包含了训练集的最后几天,导致 overlapping 窗口泄漏。
解决:把数据按时间顺序硬切三段,例如训练:2009-2016,验证:2017-2018,交易测试:2019-2020。切分后检查两边数据的时间索引是否存在重叠。在EnvMultipleStock_validation.py中应明确设置验证数据的起止日期,并保证模型只在训练环境的轨迹上更新。
5.2 动作空间设计不当时,策略会陷入过度交易
现象:回测报告中显示策略年化换手率达到几百倍,单笔交易净收益却接近零。账户资产统计时资产净值看着正常,但毛收益扣除手续费后所剩无几。
原因:连续的仓位向量给了代理随意微调动作的自由度,而奖励函数没有对交易频率施加惩罚,代理发现“每日微调”能带来少量奖励,于是形成了高频交易的习惯。
解决:在奖励函数中减去换手惩罚项。具体做法是计算当前动作与上一时刻动作的绝对差,乘以一个交易成本系数再扣减进 reward:
turnover = abs(action - prev_action).sum() step_reward = portfolio_return - cost_coef * turnover考虑到股票市场的实际交易成本(佣金、冲击成本),一般取cost_coef为 0.001 到 0.002 之间。改完之后你会发现交易频率立刻降下来了,但收益不一定下降,因为省下来的手续费本来就在钝刀割肉。实现时,需要在StockTradingRLEnv.py的step方法中保存prev_action。
5.3 奖励信号尺度忽大忽小,模型难以收敛
现象:训练过程出现 reward 曲线大幅度震荡,策略时而满仓时天空仓,loss 呈周期性爆炸。
原因:股票单日收益率通常在千分之几的数量级,但某些暴涨或暴跌日的收益率会突破 3%。这种动态范围变化在强化学习里表现为“奖励信号量级不稳定”,导致 value network 的估计方差特别大,策略也随波逐流。
解决:在保留收益信息的前提下,对奖励做 clip 或标准化。我采用过两种方案:一种是对单步 reward 做分位数截断,比如超过 ±2% 的按 2% 计;另一种是除以最近 20 个交易日的收益率标准差,让 reward 变成“单位风险调整值”。直接从源码结构看,项目在预处理阶段就做了特征标准化,但在 env 内部的 reward 层面仍然值得再做一道稳定的处理。
5.4 湍流指数阈值设置不合理,策略长期处于休眠状态
现象:加入湍流指数后,策略几乎不出手,回测期间的净值曲线变成一条近乎水平的直线。
原因:湍流指数阈值设得太低,市场稍有波动就被定义为“高风险”,每次决策都被阻止,策略参与了长时间的空仓状态,错过行情。
解决:把湍流指数的分位数统计出来,确定阈值落在历史数据的 90~95 分位上,也就是只有极端行情才触发风险关闭。修改配置时,先在 notebook 中画出湍流指数的分布图,不进入训练流程就能快速确认阈值是否合理。这里要特别说一句:湍流指数是一个非常有用的风险开关,但它不应该成为频繁触发的主控信号,更合理的定位是“用 5% 的时间挡住 80% 的最大风险”。
6. 验收方法与答辩要点:让回测结果真正立得住
项目做完不能只在本地自我感动,最终是要经过答辩或在真实数据上说话的。以下是我实际带课设和项目时都会采用的验收路径。
6.1 建议的验收指标对照表
把结果整理成一张表格,能够帮评分老师迅速定位你的核心贡献:
| 验收项 | 参考指标 | 通过标准 |
|---|---|---|
| 环境完整性 | 训练、验证、交易三个环境可独立运行 | 三份代码各自能输出日志 |
| 策略有效性 | 年化收益率、夏普比率、最大回撤 | 策略夏普高于 SPY 基准,回撤小于基准 |
| 算法对比 | PPO/A2C/DDPG 三组结果 | 三种算法都有训练曲线和回测曲线 |
| 风险控制 | 湍流指数开关有效 | 极端波动时段策略仓位自动降低 |
| 可复现性 | 相同随机种子重复训练 | 两次训练最终资产净值误差在 5% 以内 |
6.2 答辩时建议的讲解顺序
讲方案的时候,不要上来放代码,也不要只展示一张收益图。按下面的顺序讲,条理会清楚很多:
先讲问题怎么建模,用一两句话说明“我把股票交易定义成马尔可夫决策过程,状态由技术指标和湍流指数构成,动作是持仓权重,奖励是扣交易成本的组合收益”。然后讲环境怎么划分,强调训练、验证、交易三个环境的时间隔离,这是最体现工程意识的地方。再讲三种算法的选择理由和对比结论,PPT 上放一个收敛曲线对比图,宁可收敛慢一点,也要能看出三种算法训练过程的差异。最后再放回测指标和风险关闭机制,用 turblence index 的图说明策略在市场异常时如何保护本金。
这个顺序的逻辑是“设计依据 → 工程实现 → 实验对比 → 风险控制”,每一层都是靠前一层的结论引出的,老师顺着你的思路走完,基本没有死角。
6.3 讲清楚局限性和后续改进方向
答辩时最容易翻车的地方就是把话说满。深度强化学习交易策略有一个天然缺陷:历史数据训练出的策略,无法保证未来有效。市场结构一旦变化,模型分布外表现的脆弱性就暴露出来。我实践中最稳妥的说法是:“实验验证了该框架在 2009-2020 年区间内能够跑赢基准,但它仍然依赖特征选择和市场环境假设;后续可以利用图神经网络引入股票间关系,或者加入更细粒度的订单流数据。”这样既承认了边界,又展示出了下一步思考。
这让我想起第一次拿着策略的收益曲线去给导师看,被问了一句“你这份收益扣除手续费后还剩多少”。当时我愣住了,因为代码里根本没考虑手续费。从那以后,我每次做训练前都会强制自己走一遍提问清单:数据泄漏查没查?手续费扣没扣?湍流指数阈值有没有可视化过?同一份模型换到新时间段能不能复现?这套复盘习惯帮我躲过了无数次“看着好看、实际翻车”的作业事故。希望这套方案的拆解过程也能帮你在自己的强化学习交易项目里少走一段弯路。
本文还有配套的精品资源,点击获取