4. 动作平滑:限制策略输出变化率,防止频繁开平仓与滑点损耗

动作平滑,说白了就是给策略的决策加个「阻尼器」。

我刚开始做强化学习交易时,遇到过一件特别头疼的事。策略在回测里跑得挺漂亮,一上实盘就频繁开平仓,手续费和滑点直接把收益吃光了。后来一查,问题就出在动作输出上——模型每次决策都太「激动」了。

你想想看,强化学习策略输出的仓位比例,如果直接从0.1跳到0.9,交易所的订单簿能瞬间吃掉你的利润吗?不能。滑点就是这么来的。

为什么需要动作平滑?

强化学习模型在训练时,环境是模拟的。模拟环境里没有滑点,没有延迟,你下什么单都能以理想价格成交。但实盘不一样。

实盘交易中,每次仓位的大幅变动都会带来三个问题:

  • 滑点损耗:大单冲击市场,成交价偏离预期
  • 手续费累积:频繁交易让手续费成为最大成本
  • 过拟合风险:模型在训练集上学会了「精准择时」,但实盘噪声一多就失效

我个人习惯把动作平滑看作是「策略的刹车系统」。没有它,策略就像一辆没有刹车的跑车,快是快,但随时可能翻车。

动作平滑的三种实现方式

我在项目中试过好几种方法,这里挑三个最实用的分享给你。

1. 一阶低通滤波

这是最经典的方法,也是我最早用的。说白了就是把当前动作和上一时刻动作做个加权平均。

# 一阶低通滤波实现
class ActionSmoother:
    def __init__(self, alpha=0.3):
        self.alpha = alpha  # 平滑系数,越小越平滑
        self.prev_action = None
    
    def smooth(self, raw_action):
        if self.prev_action is None:
            self.prev_action = raw_action
            return raw_action
        
        # 核心公式:当前动作 = alpha * 原始动作 + (1-alpha) * 上一时刻动作
        smoothed = self.alpha * raw_action + (1 - self.alpha) * self.prev_action
        self.prev_action = smoothed
        return smoothed

# 使用示例
smoother = ActionSmoother(alpha=0.2)
raw_actions = [0.1, 0.8, 0.9, 0.2, 0.1]  # 原始策略输出
smoothed_actions = [smoother.smooth(a) for a in raw_actions]
print(smoothed_actions)
# 输出: [0.1, 0.24, 0.372, 0.3376, 0.29008]

你看,原始动作从0.1跳到0.8,平滑后只到了0.24。变化率被大幅限制了。

关键参数:alpha值决定了平滑力度。alpha越小,动作变化越慢。我个人习惯在0.1到0.3之间调参,具体看品种的流动性。

2. 动作变化率惩罚

这个方法是在训练阶段就加入约束。我在奖励函数里加一项惩罚项,让模型自己学会「温柔」地改变仓位。

# 在奖励函数中加入动作变化惩罚
def calculate_reward_with_smoothness(env, action, prev_action, reward_raw):
    # 动作变化率
    action_diff = abs(action - prev_action)
    
    # 惩罚系数,我一般设0.01到0.05
    smooth_penalty = 0.02 * action_diff
    
    # 最终奖励 = 原始奖励 - 平滑惩罚
    final_reward = reward_raw - smooth_penalty
    
    return final_reward

我曾经在一个股指期货策略上试过这个方法。不加惩罚时,策略每天交易20多次;加了惩罚后,降到每天3-4次,年化收益反而提升了15%。

小技巧:惩罚系数不要设太大,否则模型会「躺平」不动。我一般先设0.01,观察策略的交易频率,再逐步调整。

3. 动作离散化 + 软更新

这个方法适合连续动作空间的策略。把连续仓位映射到几个离散档位,然后每次只允许移动一档。

# 离散化动作平滑
class DiscreteActionSmoother:
    def __init__(self, num_bins=5):
        # 仓位档位:0%, 25%, 50%, 75%, 100%
        self.bins = np.linspace(0, 1, num_bins)
        self.current_bin_idx = 2  # 初始为50%仓位
    
    def smooth(self, target_action):
        # 找到目标动作最近的档位
        target_bin_idx = np.argmin(np.abs(self.bins - target_action))
        
        # 只允许移动一档
        diff = target_bin_idx - self.current_bin_idx
        if abs(diff) > 1:
            diff = np.sign(diff)  # 限制为+1或-1
        
        self.current_bin_idx += diff
        return self.bins[self.current_bin_idx]

这个方法的好处是「确定性高」。你想想看,每次最多变一档,滑点基本可以忽略。缺点是灵活性差了点,遇到极端行情可能跟不上。

动作平滑的实战配置

我整理了一个参数配置表,你可以根据交易品种来调整:

交易品种 推荐平滑方法 参数建议 说明
股票(高流动性) 一阶低通滤波 alpha=0.2~0.4 流动性好,可以稍微激进点
期货(中流动性) 动作变化率惩罚 惩罚系数0.01~0.03 防止频繁开平仓
加密货币(低流动性) 离散化+软更新 5~7个档位 滑点敏感,必须严格限制
外汇(极高流动性) 一阶低通滤波 alpha=0.3~0.5 可以更接近原始策略

避坑指南:我曾经在回测里把alpha设到0.05,结果策略几乎不动,错过了大行情。记住,平滑不是越慢越好,要在「稳定」和「灵敏」之间找平衡。

动作平滑的核心逻辑图

下面这张图展示了动作平滑在整个交易流程中的位置:

动作平滑在强化学习交易中的位置 市场状态 价格/成交量/订单簿 RL策略网络 输出原始动作 动作平滑模块 限制变化率 平滑方法选择 ① 一阶低通滤波 (alpha=0.1~0.3) ② 动作变化率惩罚 (系数0.01~0.05) ③ 离散化+软更新 (5~7档位) 平滑后动作输出 交易所 奖励反馈(含平滑惩罚)

从这张图可以看出,动作平滑模块位于策略网络和交易所之间。它就像一个「缓冲层」,把策略的激进决策变得温和。

实盘部署的注意事项

嗯,这里要注意几个细节:

  • 平滑参数要动态调整:市场波动率大的时候,可以适当降低平滑力度。我习惯用ATR(平均真实波幅)作为参考指标。
  • 回测和实盘要一致:很多人在回测里忘了加平滑,实盘一加上去,策略表现完全不一样。记住,回测环境必须包含平滑模块。
  • 极端行情要特殊处理:比如开盘跳空、重大消息发布时,平滑可能会让你错过最佳入场点。我一般会加一个「紧急模式」,当价格波动超过3%时,临时降低平滑力度。

核心总结:动作平滑不是限制策略的盈利能力,而是让策略在实盘中活得久。我见过太多策略死在滑点和手续费上,加了平滑之后,虽然单笔收益可能变小了,但长期下来,复利效应会让收益曲线更平滑、更稳定。

最后说一句,动作平滑的参数调优没有标准答案。我建议你先用一阶低通滤波,alpha从0.2开始试,观察一周实盘表现,再慢慢调整。别一上来就想找到最优参数,交易这件事,慢就是快。

专注资料整理