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,结果策略几乎不动,错过了大行情。记住,平滑不是越慢越好,要在「稳定」和「灵敏」之间找平衡。
动作平滑的核心逻辑图
下面这张图展示了动作平滑在整个交易流程中的位置:
从这张图可以看出,动作平滑模块位于策略网络和交易所之间。它就像一个「缓冲层」,把策略的激进决策变得温和。
实盘部署的注意事项
嗯,这里要注意几个细节:
- 平滑参数要动态调整:市场波动率大的时候,可以适当降低平滑力度。我习惯用ATR(平均真实波幅)作为参考指标。
- 回测和实盘要一致:很多人在回测里忘了加平滑,实盘一加上去,策略表现完全不一样。记住,回测环境必须包含平滑模块。
- 极端行情要特殊处理:比如开盘跳空、重大消息发布时,平滑可能会让你错过最佳入场点。我一般会加一个「紧急模式」,当价格波动超过3%时,临时降低平滑力度。
核心总结:动作平滑不是限制策略的盈利能力,而是让策略在实盘中活得久。我见过太多策略死在滑点和手续费上,加了平滑之后,虽然单笔收益可能变小了,但长期下来,复利效应会让收益曲线更平滑、更稳定。
最后说一句,动作平滑的参数调优没有标准答案。我建议你先用一阶低通滤波,alpha从0.2开始试,观察一周实盘表现,再慢慢调整。别一上来就想找到最优参数,交易这件事,慢就是快。