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 知识体系总览

下面这张图,把动作空间和风控逻辑的关系梳理清楚了:

动作空间与风控逻辑知识体系 动作空间 离散动作 买/卖/持有 训练快,但粗糙 连续动作 仓位比例[-1,1] 精细但难训练 动作约束 仓位限制 单笔≤20% 交易频率 最小间隔5min 杠杆限制 总杠杆≤3x 持仓集中度 单品种≤30% 风控逻辑(硬底线)

从图上你能看到,动作空间是基础,动作约束是中间层,风控逻辑是最后一道防线。三者层层递进,缺一不可。

好了,关于动作空间的定义,我就讲这么多。记住一句话:好的动作空间设计,能让智能体事半功倍;差的设计,再怎么调参也救不回来


公众号:蓝海资料掘金营,微信deep3321