4、动作空间定义:离散动作(买卖持有)vs 连续动作(仓位比例)、动作约束与风控逻辑
动作空间,说白了就是告诉智能体「你能干什么」。
我刚开始做RL交易系统时,觉得这步很简单——不就是买、卖、持有吗?后来踩了坑才发现,动作空间定义得好不好,直接决定了策略能不能赚钱、会不会爆仓。
4.1 离散动作:最直观的选择
离散动作空间,就是给智能体几个固定的选项。最常见的就是三选一:
- 买入(Buy):用全部可用资金买入
- 卖出(Sell):卖出全部持仓
- 持有(Hold):什么都不做
嗯,听起来很简单对吧?但实际用起来,坑不少。
离散动作的典型代码实现:
import gym
from gym import spaces
class TradingEnv(gym.Env):
def __init__(self):
super().__init__()
# 离散动作:0=持有, 1=买入, 2=卖出
self.action_space = spaces.Discrete(3)
def step(self, action):
if action == 0: # 持有
pass
elif action == 1: # 买入
self._buy()
elif action == 2: # 卖出
self._sell()
return obs, reward, done, info
我个人习惯在初期验证策略时用离散动作。为什么?因为训练快、收敛稳。你想想看,智能体只需要从3个选项里选,探索空间小,很快就能找到规律。
但离散动作有个致命问题——它太粗糙了。我在项目中遇到过,明明市场信号很好,但智能体只能全仓买入。结果第二天回调,直接套牢。说白了,离散动作缺少「力度」的控制。
4.2 连续动作:精细化的仓位控制
连续动作空间,让智能体输出一个具体的仓位比例。比如输出0.3,就是30%仓位买入;输出-0.5,就是做空50%仓位。
这才是实战中真正好用的东西。
连续动作的典型代码实现:
class ContinuousTradingEnv(gym.Env):
def __init__(self):
super().__init__()
# 连续动作:[-1, 1] 区间
# -1 表示全仓做空,1 表示全仓做多
self.action_space = spaces.Box(
low=-1.0, high=1.0, shape=(1,), dtype=np.float32
)
def step(self, action):
# action[0] 是仓位比例
target_position = action[0] * self.max_position
# 执行调仓
self._adjust_position(target_position)
return obs, reward, done, info
你可能会问:「连续动作这么好,为什么还有人用离散?」
原因很简单——连续动作难训练。智能体要在无限大的空间里找最优解,探索效率低,容易陷入局部最优。我见过不少团队,连续动作训练了几天都不收敛,最后换回离散反而效果好。
我的经验:先用离散动作快速验证策略可行性,再用连续动作做精细化调优。别一上来就搞连续,容易心态崩。
4.3 动作约束:别让智能体乱来
不管是离散还是连续,你都得给智能体加约束。否则它会干出一些「理论上最优、实际上找死」的事情。
常见的约束有:
| 约束类型 | 说明 | 示例 |
|---|---|---|
| 仓位限制 | 单次操作不能超过最大仓位 | 单笔交易不超过总资产的20% |
| 交易频率 | 限制最小交易间隔 | 同一标的5分钟内不能重复交易 |
| 杠杆限制 | 控制最大杠杆倍数 | 总杠杆不超过3倍 |
| 持仓集中度 | 单一品种占比上限 | 单只股票不超过总仓位30% |
我曾经犯过一个错误——没加交易频率约束。结果智能体学会了「高频刷单」,每秒钟买卖几十次,虽然每次赚一点点,但手续费把利润全吃掉了。嗯,这教训挺深刻的。
避坑指南:动作约束一定要在环境层面实现,别指望智能体自己学会。我曾经试过让智能体「自觉」遵守约束,结果它总能找到漏洞。记住:智能体是贪婪的,它只关心奖励最大化。
4.4 风控逻辑:最后的防线
动作约束是「软限制」,风控逻辑是「硬底线」。
风控逻辑通常放在环境的最外层,不管智能体输出什么动作,风控层都会做最终检查。比如:
- 最大回撤保护:当日回撤超过5%,强制平仓
- 资金安全垫:账户净值低于初始资金80%,停止交易
- 异常检测:连续亏损N笔,降低仓位
风控逻辑的代码示例:
class RiskManager:
def __init__(self, max_drawdown=0.05, max_loss_count=3):
self.max_drawdown = max_drawdown
self.max_loss_count = max_loss_count
self.loss_count = 0
self.peak_value = 0
def check(self, action, current_value):
# 1. 最大回撤检查
self.peak_value = max(self.peak_value, current_value)
drawdown = (self.peak_value - current_value) / self.peak_value
if drawdown > self.max_drawdown:
return 'force_close' # 强制平仓
# 2. 连续亏损检查
if self._is_loss(action):
self.loss_count += 1
else:
self.loss_count = 0
if self.loss_count > self.max_loss_count:
return 'reduce_position' # 降低仓位
return 'normal'
我个人习惯把风控逻辑和动作空间分开。动作空间管「能做什么」,风控管「不能做什么」。这样代码清晰,也方便后期调整风控参数。
4.5 动作空间与风控的协同设计
这里有个关键点——动作空间和风控逻辑要一起设计。
举个例子:如果你用连续动作输出仓位比例,风控层可以对这个比例做二次修正。比如智能体输出80%仓位,但风控检测到市场波动率飙升,自动把仓位降到50%。
这种设计的好处是:智能体只需要关注「理想仓位」,风控负责「安全落地」。
实战建议:在训练阶段,风控可以宽松一些,让智能体多探索。上线实盘时,风控要严格,宁可少赚不能大亏。我见过太多策略在回测里表现完美,一上实盘就爆仓——基本都是风控没做好。
4.6 知识体系总览
下面这张图,把动作空间和风控逻辑的关系梳理清楚了:
从图上你能看到,动作空间是基础,动作约束是中间层,风控逻辑是最后一道防线。三者层层递进,缺一不可。
好了,关于动作空间的定义,我就讲这么多。记住一句话:好的动作空间设计,能让智能体事半功倍;差的设计,再怎么调参也救不回来。
公众号:蓝海资料掘金营,微信deep3321