PPO 强化学习交易:用近端策略优化学会在连续动作空间下单
监督学习只能回答『下一期涨还是跌』,回答不了『该持多少仓』——仓位是个连续决策,且换手有成本、决策有惯性,这天然是强化学习问题。PPO 是策略梯度的工程化版本:用概率比 r(θ) 度量新旧策略偏移,clip 目标把更新钉死在信任域 [1-ε,1+ε] 内,防止一次大步毁掉整个策略。本文纯 numpy 实现线性高斯策略 PPO(tanh 均值头 + 可学习 σ + GAE 优势估计),在 OU 均值回复合成市场上训练 200 轮:episode 奖励从 -8.9 爬到 +7.0,agent 自己学会『涨多了做空、跌多了做多』的平滑连续仓位函数。样本外对比揭示连续动作的真正价值:PPO Sharpe 1.57 与硬规则 ±1 满仓反转的 1.75 接近,但最大回撤只有其 1/5(-3.5% vs -17.2%)、换手只有其 1/5——它学到的不是更强的信号,而是更聪明的仓位。拆穿 reward=收益率就够了/训练曲线涨=真学会了/RL 免疫过拟合/σ 收敛=策略成熟四类陷阱(中阶)。
先说结论:PPO 在交易里最值钱的产出不是”预测得更准”,而是”仓位下得更聪明”。本文用纯 numpy 从零实现一个连续动作空间的 PPO 交易 agent,在均值回复的合成市场上训练 200 轮后,它自己学会了”涨多了做空、跌多了做多”——而且是平滑渐进的仓位函数,不是硬规则的 ±1 满仓跳变。样本外结果把这个差异量化得很清楚:PPO 的 Sharpe(1.57)与满仓硬规则(1.75)接近,但最大回撤只有硬规则的五分之一(-3.5% vs -17.2%),换手率也只有五分之一。同样的信号,不一样的仓位艺术。
为什么下单量是个强化学习问题#
监督学习范式下你训练一个分类器:输入特征,输出”明天涨/跌”。但从预测到下单之间隔着三个监督学习管不了的东西:
- 动作是连续的。不是”买或不买”,是”持仓 -1 到 +1 之间的哪一点”。0.3 仓和 0.9 仓在风险上是完全不同的决策。
- 决策有状态依赖。从 +0.8 调到 -0.8 要付双倍换手成本;当前持仓是决策的一部分,这是序贯决策而非独立预测。
- 优化目标是轨迹级的。你要的是整段时间的风险调整收益,不是每一步的预测准确率。一个准确率 55% 但重仓错误时刻的模型,比准确率 52% 但仓位控制得当的模型亏得多。
强化学习的建模刚好对上:状态 s(市场特征 + 当前持仓)、动作 a(目标仓位,连续标量)、奖励 r(仓位 × 次期收益 − 换手成本)。agent 的目标是最大化折现累积奖励——它直接优化你真正关心的东西。
从策略梯度到 PPO:为什么需要 clip#
策略梯度方法直接参数化策略 π(a|s;θ),沿着”提高高优势动作概率”的方向更新 θ。裸的策略梯度(REINFORCE)有个致命弱点:步长极难调。步子小了训练慢如蜗牛;步子大了策略一次跳变,跳到一个烂区域后采出来的数据也是烂的,从此螺旋坠毁——策略梯度的数据是自己采的,坏策略产生坏数据,坏数据强化坏策略。
PPO 的解法优雅在于用概率比度量策略偏移:
新旧策略对同一动作给出的概率之比。r=1 表示没变,r=1.5 表示新策略让这个动作概率提高了 50%。PPO 的目标函数:

读这张图:优势 A>0(动作比平均好)时,目标随 r 上升,但r 超过 1+ε 后被削平——继续提高这个动作的概率不再有奖励,梯度归零。A<0 时对称:r 压到 1-ε 以下后不再有惩罚减免。效果是把每次更新钉死在旧策略附近的”信任域”里,用一个 min 和一个 clip 替代了 TRPO 的二阶约束优化,实现只要几行代码。
纯 numpy 实现:线性高斯策略#
为了把机制看透,我们不用任何 RL 框架。策略是线性高斯的:均值由特征线性组合过 tanh 得到(保证仓位在 ±1 内),标准差 σ 是可学习的全局参数:
def policy_mean(x, theta):
return np.tanh(phi(x) @ theta) # phi(x) = [特征, 1],带截距
# 采样动作:均值 + 高斯探索噪声
means = np.tanh(P @ theta) # P: (HORIZon, D+1) 特征矩阵
acts_raw = means + np.exp(log_std) * rng.normal(0, 1, HORIZON)
acts = np.clip(acts_raw, -1, 1) # 仓位物理约束pythonσ 就是探索的旋钮:训练早期 σ 大,agent 广泛试探各种仓位;随着策略变好,继续乱试的动作拿到负优势,σ 的梯度自然把它压小。探索强度不用人排程,它是学出来的。
奖励函数是全部金融语义的注入点:
reward[i] = act[i] * ret[t+1] * 100 - COST * abs(act[i] - pos_prev) * 100
# 仓位 × 次期收益 换手成本(惩罚仓位跳变)python注意 ret[t+1]——决策在 t 日收盘,吃 t+1 日收益,这是回测里”信号日不吃收益”纪律在 RL 里的等价物。换手成本项让 agent 学会”没必要就别动仓位”。
优势估计用 GAE(广义优势估计),本质是”这个动作比价值函数的预期好多少”的指数加权版本;再加一个线性价值头做 baseline 回归。PPO 更新循环的核心:
logp_old = gaussian_logp(acts_raw, means, std) # 冻结旧策略概率
for epoch in range(4): # 同批数据重用 4 次
means_new = np.tanh(P @ theta)
ratio = np.exp(gaussian_logp(acts_raw, means_new, std) - logp_old)
clipped = np.clip(ratio, 1 - 0.2, 1 + 0.2)
use_clip = ratio * adv > clipped * adv # 被 clip 的样本
# 只对未触发 clip 的样本回传梯度(clip 区梯度为零)
coef = np.where(~use_clip, ratio * adv * dlogp_dmu * dmu_dz, 0.0)
theta += LR * P.T @ coef / HORIZONpythonuse_clip 这行就是 PPO 的全部秘密:触发 clip 的样本梯度直接归零,它们对更新没有发言权。这也是为什么同一批数据敢重用 4 个 epoch——偏移一大就被自动断电。
实验:OU 均值回复市场#
合成市场用 Ornstein-Uhlenbeck 过程(持续系数 0.95)叠加轻微下行漂移和噪声——一个”涨多必跌、跌多必涨”但带噪声的均值回复环境。特征 4 维:3 日累计收益、20 日累计收益、20 日波动、当日 z-score。训练段 4000 天,测试段 1000 天(不同随机种子生成,完全样本外)。双边换手成本 2bp。

训练 200 轮,每轮采 512 步:episode 总奖励从前 10 轮均值 -8.9 爬到后 10 轮 +7.0。右图两条辅助曲线是 PPO 的健康监测仪:clip 触发比例维持低位(更新一直待在信任域内,学习率没开过头),σ 从 0.61 缓慢下降(探索在收敛,但仍保留随机性)。
学到的参数本身就可读——20 日累计收益特征的权重是 -0.947,一家独大且为负:过去 20 天涨得越多,仓位越空。agent 从零知识自己发现了市场的均值回复结构。
学到的策略长什么样#

蓝线是学到的确定性策略 μ(s) 沿”20 日累计收益”特征的切面:一条平滑的递减 S 曲线。近期涨幅深度负值(超跌)→ 仓位接近 +0.9;涨幅温和 → 仓位温和;深度超涨 → 接近 -0.9。绿点是测试集上 1000 个实际交易日的仓位散布,贴着曲线走。
这条平滑曲线正是连续动作空间的价值所在。对比硬规则”20 日收益为正就满空、为负就满多”:信号方向完全一样,但硬规则只有 ±1 两档,信号强度信息全部丢失——刚刚转正的弱信号和极度超涨的强信号,下的都是同一个满仓。
样本外:Sharpe 打平,回撤只有五分之一#

1000 天样本外测试段(买入持有亏 -29.8% 的下行市):
| 策略 | 总收益 | Sharpe | 最大回撤 | 日均换手 |
|---|---|---|---|---|
| PPO 连续仓位 | +30.8% | 1.57 | -3.5% | 0.053 |
| 硬规则 ±1 满仓反转 | +278.7% | 1.75 | -17.2% | 0.269 |
| 买入持有 | -29.8% | -0.33 | -35.4% | — |
读数要诚实:硬规则的总收益远高——满仓押注在信号确实有效的市场里杠杆拉满,赚得自然多。但看风险调整后的全貌:Sharpe 几乎打平(1.57 vs 1.75),而 PPO 的最大回撤只有硬规则的 1/5、换手只有 1/5。PPO 在信号弱的时候只下小仓——错的时候亏得少;换手成本敏感的真实环境里,1/5 的换手意味着 1/5 的摩擦损耗。
**PPO 学到的不是更强的信号,而是信号强度到仓位大小的正确映射。**这件事监督学习给不了你——分类器输出的 0.55 概率和 0.85 概率,最后都被你手写的规则拍成同一个仓位。
四个必须拆穿的陷阱#
陷阱一:reward = 收益率就完事了。 奖励函数是 RL 里唯一的”价值观注入点”,裸收益率奖励教出来的 agent 是个赌徒——它对波动无感,会学出在高波动时刻满仓的策略。本文加了换手成本项;生产级实现还应考虑风险惩罚(如 reward 减 λ·|仓位|·波动率)。你在 reward 里没写的东西,agent 一定不在乎。
陷阱二:训练曲线涨 = 真学会了。 训练奖励爬升只说明 agent 适应了训练段。金融数据的残酷之处在于 regime 会变:在趋势市里训练的 agent 学会追涨,切到均值回复市就是绞肉机。本文测试段与训练段同分布(同一 OU 参数、不同种子),这是教学上限设定;真实市场里没人保证分布不漂移,必须做滚动样本外和跨 regime 测试。
陷阱三:RL 免疫过拟合。 恰恰相反——RL 的过拟合更隐蔽。监督学习过拟合看训练/验证 loss 分叉就能发现;RL 的 agent 可能记住了训练段的具体路径模式(“第 1200 天附近有个大反弹”),而你只看到奖励在涨。本文用线性策略(5 个参数)天然限制了记忆容量——这是刻意的。**换成神经网络策略前,先问自己有没有对应的正则化和验证机制。**参数越多的策略网络,越需要把”多种子重训 + 跨段测试”当成标配。
陷阱四:σ 收敛到很小 = 策略成熟。 σ 塌缩到接近零意味着 agent 停止探索,此后它对没见过的状态只会外推。如果 σ 在训练早期就塌缩(学习率过大或奖励尺度不对),agent 会锁死在第一个凑合的局部解上。健康的训练里 σ 应该缓慢下降且保有余量——本文从 0.61 降到 0.58 的缓坡就是这种形态。σ 的轨迹和 clip 触发比例一样,是必须监控的生命体征。
收尾#
PPO 把交易决策还原成它本来的样子:一个带成本、带状态、带风险偏好的连续控制问题。本文的极简实现展示了完整闭环——clip 目标守住更新步长、GAE 估计动作优势、可学习 σ 自动调节探索,200 轮训练让 agent 从零知识学出均值回复市场的平滑仓位函数;样本外 Sharpe 1.57 的同时把回撤和换手都压到硬规则的五分之一。
但请记住实验的边界:合成市场、同分布测试、5 参数线性策略——每放宽一项,陷阱三和陷阱二的威胁就放大一分。RL 交易的工程难度不在算法(PPO 的代码就这几十行),在于奖励设计、分布漂移防御和过拟合验证这三件算法之外的事。
(本文为合成数据教学演示,不构成投资建议;实盘应用还需处理滑点、容量、执行延迟与 regime 漂移。)