第三章 单Agent智能体设计:基于强化学习的交易Agent
好,咱们进入实战环节。
前面两章我们聊了多Agent系统的整体架构,还有数据怎么在各Agent之间流转。今天这一章,我打算把镜头拉近,聚焦到最核心的一个单元——单Agent智能体。
说白了,就是怎么设计一个能自己学会交易的“大脑”。
我个人习惯把强化学习交易Agent的设计拆成三块:状态空间、动作空间、奖励函数。这三块搞定了,Agent的基本骨架就有了。咱们一个一个来拆。
3.1 状态空间设计:Agent能看到什么?
状态空间,就是Agent观察市场的“眼睛”。你给它看什么,它就学什么。给少了,它看不清;给多了,它容易学歪。
我在项目中遇到过一个问题:一开始我把所有技术指标都塞进去,MACD、RSI、布林带、成交量……结果模型训练了三天,收益曲线还是跟心电图似的。后来我意识到,信息冗余比信息不足更可怕。
一个合理的状态空间,我建议包含以下几类:
- 价格数据:过去N步的收盘价、最高价、最低价。这是最原始的信号。
- 成交量数据:过去N步的成交量。量价配合,才能看出资金意图。
- 技术指标:选2-3个就够了。我个人常用的是RSI和ATR。RSI看超买超卖,ATR看波动率。
- 持仓状态:当前仓位、当前盈亏。Agent得知道自己“兜里有多少钱”。
- 市场环境:比如大盘指数涨跌、行业板块热度。这个看你的策略定位。
嗯,这里要注意:状态空间一定要归一化。价格从100涨到101,和从1000涨到1001,意义完全不同。我习惯用Z-score或者Min-Max归一化,把数据压到[-1, 1]之间。
核心原则:状态空间要“够用但不过剩”。你想想看,一个交易员盯盘时,真正关注的也就那几个数字。Agent也一样。
3.2 动作空间设计:Agent能做什么?
动作空间,就是Agent的“手”。它决定了Agent怎么跟市场互动。
你可能会想,动作空间不就是“买、卖、持有”吗?对,但也没那么简单。
我见过很多新手把动作空间设计成离散的:
- 动作0:清仓
- 动作1:买入10%仓位
- 动作2:买入20%仓位
- ……
这样做有个问题:动作空间太大。如果仓位步长是1%,那就有100个动作。Agent学起来很慢。
我个人更推荐连续动作空间。比如:
- 动作输出是一个实数,范围[-1, 1]
- 负数表示卖出,正数表示买入
- 绝对值表示仓位比例
举个例子:输出0.3,就是买入30%仓位;输出-0.8,就是卖出80%仓位。这样Agent只需要学一个数,效率高很多。
避坑指南:我曾经把动作空间设计成“买入/卖出固定股数”,结果Agent学会了在低价股上频繁交易,因为手续费占比小。后来改成按仓位比例,这个问题就解决了。
3.3 奖励函数设计:Agent为什么而学?
奖励函数,是Agent的“老师”。它告诉Agent什么行为是好的,什么行为是坏的。
这是整个设计里最微妙的部分。奖励函数设计得好,Agent自己就能找到赚钱的路子;设计得不好,Agent可能会学会一些“歪门邪道”。
我常用的奖励函数框架是这样的:
reward = alpha * 收益率 + beta * 夏普比率 - gamma * 换手率 - delta * 最大回撤
解释一下:
- 收益率:最直接的奖励。但只追求收益率,Agent会变得很激进。
- 夏普比率:衡量风险调整后的收益。这个指标能让Agent更“稳健”。
- 换手率:惩罚频繁交易。手续费和滑点都是成本。
- 最大回撤:惩罚大幅亏损。这个很重要,不然Agent可能为了赚10%而冒亏50%的风险。
alpha、beta、gamma、delta是权重系数。我一般从alpha=1.0, beta=0.5, gamma=0.1, delta=0.2开始调。
注意:奖励函数不要设计得太复杂。我见过有人加了十几个惩罚项,结果Agent直接“躺平”了——因为不管怎么做都是负奖励,它干脆不交易了。
3.4 核心逻辑框架图
下面这张图,是我自己画的一个单Agent交易系统的核心逻辑。你看一眼就能明白整个流程:
3.5 一个简单的代码示例
光说不练假把式。我写了一个最小化的交易Agent代码框架,用的是PPO算法。你感受一下:
import numpy as np
import torch
import torch.nn as nn
class TradingAgent:
def __init__(self, state_dim, action_dim):
# 策略网络
self.policy = nn.Sequential(
nn.Linear(state_dim, 128),
nn.ReLU(),
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, action_dim),
nn.Tanh() # 输出范围[-1, 1]
)
self.optimizer = torch.optim.Adam(self.policy.parameters(), lr=1e-4)
def get_action(self, state):
# state: 归一化后的状态向量
state_tensor = torch.FloatTensor(state).unsqueeze(0)
action = self.policy(state_tensor)
return action.item() # 返回仓位比例
def update(self, states, actions, rewards):
# 简化版PPO更新
# 实际项目中需要计算优势函数、clip等
loss = -torch.mean(rewards * torch.log(self.policy(states)))
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
# 使用示例
agent = TradingAgent(state_dim=10, action_dim=1)
state = np.random.randn(10) # 模拟状态
action = agent.get_action(state)
print(f"建议仓位比例: {action:.2f}")
这段代码很糙,但骨架是对的。你把它跑起来,就能看到一个Agent在“思考”了。
3.6 一些实战中的坑
最后,我分享几个我在实战中踩过的坑:
- 过拟合历史数据:Agent可能在回测中表现完美,但一到实盘就崩。我习惯在训练时加入随机噪声,或者用多个时间段的数据做交叉验证。
- 奖励延迟:有时候一笔交易要持有好几天才能看到收益。这时候需要设计“中间奖励”,比如持仓期间的风险控制奖励。
- 动作平滑:Agent可能今天满仓,明天空仓,后天又满仓。这种高频切换在实盘中手续费和滑点会吃掉所有利润。我一般会在奖励函数里加一个“动作变化惩罚”。
我的建议:先从最简单的版本开始。状态空间用5个特征,动作空间用连续值,奖励函数只用收益率+夏普比率。跑通了再慢慢加东西。别一上来就想搞个“全能Agent”。
好了,单Agent的设计思路就这些。你想想看,其实核心就是三个问题:看什么?做什么?为什么? 把这三个问题想清楚,Agent就能开始学习了。