4. 交易环境设计与配置

4.交易环境设计与配置

交易环境是强化学习应用中最关键的组件之一,它定义了智能体与市场交互的接口。在 FinRL 中,环境层负责将真实的金融数据转化为符合 OpenAI Gym 规范的模拟市场,让智能体能够在历史数据上学习交易策略,同时确保训练过程能够无缝迁移到回测和实盘环节。设计一个合理的交易环境,需要在状态表示、动作定义和奖励机制之间取得精妙的平衡。

股票市场环境基础架构

FinRL 的环境设计遵循 OpenAI Gym 的标准接口,这意味着每个交易环境都必须实现 reset()step()render() 等核心方法。这种标准化设计带来了极大的灵活性,任何符合 Gym 规范的强化学习算法都可以直接接入,无需修改算法本身的代码。

时间驱动模拟机制

与游戏环境的事件驱动不同,金融交易环境采用时间驱动模拟。环境按照时间序列逐日(或逐分钟)推进,每个时间步对应市场的一个交易日。在每一步,智能体观察当前市场状态,做出交易决策,环境则根据决策更新持仓和资金,并计算奖励值。

这种机制的核心在于数据流的组织。FinRL 使用 pandas DataFrame 存储 OHLCV 数据和技术指标,通过 self.day 指针控制时间进度。当 step() 方法被调用时,环境从 DataFrame 中取出当前日期的所有股票数据,构建状态向量,执行交易动作,然后推进指针到下一个交易日。

def step(self, actions):
    self.terminal = self.day >= len(self.df.index.unique())-1
    
    if self.terminal:
        # 回测结束,计算最终绩效
        return self.state, self.reward, self.terminal, {}
    else:
        # 执行交易逻辑
        actions = actions * HMAX_NORMALIZE
        # ... 交易执行代码 ...
        
        self.day += 1
        self.data = self.df.loc[self.day,:]
        # 构建新状态
        self.state = [self.state[0]] + \
                    self.data.adjcp.values.tolist() + \
                    list(self.state[(STOCK_DIM+1):(STOCK_DIM*2+1)]) + \
                    self.data.macd.values.tolist() + \
                    self.data.rsi.values.tolist()
        
        # 计算奖励
        end_total_asset = self.state[0] + \
                         sum(np.array(self.state[1:(STOCK_DIM+1)]) * \
                         np.array(self.state[(STOCK_DIM+1):(STOCK_DIM*2+1)]))
        self.reward = end_total_asset - begin_total_asset
        
        return self.state, self.reward, self.terminal, {}

这段代码展示了环境的核心循环。self.day 指针控制时间进度,self.df.loc[self.day,:] 获取当日所有股票的数据。状态构建时保留了持仓信息(self.state[(STOCK_DIM+1):(STOCK_DIM*2+1)]),而价格和技术指标则从数据集中实时获取,确保智能体看到的是"当前"市场情况。

三层架构的解耦设计

FinRL 的三层架构中,环境层与数据层、智能体层保持松耦合。环境不关心数据来自 Yahoo Finance 还是本地 CSV 文件,也不关心上层使用的是 PPO 还是 SAC 算法。这种设计使得我们可以轻松替换数据源或尝试不同的 DRL 算法,而无需重写环境代码。

环境层通过标准化接口向上层提供两个关键抽象:状态空间和动作空间。这两个空间的定义决定了智能体能够观察到什么信息,以及能够执行哪些操作。设计良好的空间定义既要包含足够的决策信息,又要避免维度灾难。

状态空间设计

状态空间是智能体观察市场的窗口。在股票交易场景中,一个全面的状态表示应该包含账户信息、市场价格、持仓情况和技术分析指标。FinRL 的多股票交易环境将状态向量设计为 121 维,这个维度不是随意确定的,而是经过权衡后的选择。

状态向量的构成要素

状态向量通常包含以下几个部分:

  1. 账户余额:当前可用的现金,是状态的第一个元素。这决定了智能体的购买力上限。
  2. 股票价格:所有股票的调整后收盘价,反映当前市场估值。
  3. 持仓数量:每只股票当前持有的股数,体现投资组合的构成。
  4. 技术指标:MACD、RSI 等技术分析指标,提供市场趋势和动量信息。
# 状态空间定义示例
self.observation_space = spaces.Box(low=0, high=np.inf, shape=(121,))

# 状态构建逻辑
self.state = [INITIAL_ACCOUNT_BALANCE] + \
             self.data.adjcp.values.tolist() + \
             [0]*STOCK_DIM + \
             self.data.macd.values.tolist() + \
             self.data.rsi.values.tolist()

这个状态向量包含 1 个余额值、30 个价格、30 个持仓量、30 个 MACD 值和 30 个 RSI 值,总共 121 维。每个部分都有其特定作用:价格提供市场信息,持仓反映当前风险敞口,技术指标则捕捉价格序列中的模式。

技术指标的选择与计算

FinRL 默认集成了多种技术指标,包括 MACD、布林带、RSI、ADX 等。这些指标通过 FeatureEngineer 类自动计算并添加到 DataFrame 中。在状态构建时,环境直接从 DataFrame 读取这些预计算的指标值。

# 特征工程配置
df = FeatureEngineer(df.copy(),
                     use_technical_indicator=True,
                     tech_indicator_list=config.INDICATORS,
                     use_turbulence=True,
                     user_defined_feature=False).preprocess_data()

config.INDICATORS 定义了要使用的技术指标列表。每个指标都会为每只股票生成一个时间序列,在状态构建时,环境取当前日期的指标值加入状态向量。这种设计允许用户通过修改配置文件轻松添加或移除指标,无需改动环境代码。

高维状态空间的挑战

当股票数量增加时,状态空间维度会线性增长。30 只股票的环境状态维度是 121,100 只股票就会超过 400 维。高维状态空间会带来两个挑战:一是训练效率下降,二是需要更大的神经网络来提取特征。

FinRL 通过两种方式缓解这个问题。首先,使用卷积神经网络(如 EIIE 架构)处理高维状态,利用参数共享降低模型复杂度。其次,在组合优化场景中采用不同的状态表示,使用协方差矩阵而非个股持仓,将状态维度从 O(n) 降低到 O(n²) 但结构更紧凑。

动作空间定义

动作空间定义了智能体在每个时间步可以执行的操作。在股票交易中,动作通常表示买卖决策。FinRL 支持两种动作空间设计:离散动作和连续动作,分别适用于不同的交易场景。

离散动作空间

最简单的动作空间是 {-1, 0, 1},分别表示卖出、持有和买入一股。为了支持不同规模的交易,FinRL 扩展为 {-k, ..., -1, 0, 1, ..., k},其中 k 是最大交易股数。这种设计直观易懂,但存在粒度限制。

# 离散动作空间定义
self.action_space = spaces.Discrete(2*k+1)

# 动作执行逻辑
def _sell_stock(self, index, action):
    if self.state[index+STOCK_DIM+1] > 0:
        # 卖出逻辑
        sell_amount = min(abs(action), self.state[index+STOCK_DIM+1])
        self.state[0] += self.state[index+1] * sell_amount * (1-TRANSACTION_FEE_PERCENT)
        self.state[index+STOCK_DIM+1] -= sell_amount

def _buy_stock(self, index, action):
    available_amount = self.state[0] // self.state[index+1]
    buy_amount = min(available_amount, action)
    self.state[0] -= self.state[index+1] * buy_amount * (1+TRANSACTION_FEE_PERCENT)
    self.state[index+STOCK_DIM+1] += buy_amount

在离散动作空间中,智能体输出一个整数,环境根据数值正负决定买卖方向,根据绝对值决定交易数量。这种设计适合初学者理解,但在多股票场景下会导致动作空间维度爆炸。

连续动作空间

更灵活的设计是使用连续动作空间。FinRL 的多股票交易环境采用 spaces.Box(low=-1, high=1, shape=(STOCK_DIM,)),智能体为每只股票输出一个 [-1, 1] 区间的连续值。

# 连续动作空间定义
self.action_space = spaces.Box(low=-1, high=1, shape=(STOCK_DIM,))

# 动作执行逻辑
actions = actions * HMAX_NORMALIZE  # 缩放为实际交易数量
sell_index = np.where(actions < 0)[0]
buy_index = np.where(actions > 0)[0]

for index in sell_index:
    self._sell_stock(index, actions[index])

for index in buy_index:
    self._buy_stock(index, actions[index])

连续动作空间的优势在于维度不随交易粒度增加而增长。无论最大交易股数是 100 还是 1000,动作空间始终是 30 维。智能体输出的是交易意图的相对强度,环境通过 HMAX_NORMALIZE 因子将其映射为实际交易数量。

动作归一化的重要性

在连续动作空间中,归一化至关重要。智能体的策略网络通常使用 tanh 激活函数输出 [-1, 1] 区间的值。环境接收到动作后,需要将其转换为实际交易数量。这个转换过程必须考虑账户余额和持仓限制,确保不会产生无效交易。

# 动作缩放与约束
actions = actions * HMAX_NORMALIZE  # 缩放
actions = np.clip(actions, -self.state[STOCK_DIM+1:], self.state[0]//self.state[1:STOCK_DIM+1])

这段代码确保卖出动作不超过持仓量,买入动作不超过可用资金。这种约束在环境中实现,而不是让智能体学习,可以大大加快训练速度,因为智能体不需要探索无效动作。

奖励函数定制与风险调整

奖励函数是强化学习的核心信号源,直接决定了智能体学习到的策略特征。在交易场景中,简单的利润最大化会导致过度冒险,因此需要精心设计奖励函数来平衡收益与风险。

基础奖励函数:组合价值变化

最直接的奖励函数是组合价值的变化量。在每个时间步,环境计算执行动作前后的总资产价值差,作为即时奖励。

# 奖励计算逻辑
begin_total_asset = self.state[0] + \
                   sum(np.array(self.state[1:(STOCK_DIM+1)]) * \
                   np.array(self.state[(STOCK_DIM+1):(STOCK_DIM*2+1)]))

# 执行交易动作...

end_total_asset = self.state[0] + \
                 sum(np.array(self.state[1:(STOCK_DIM+1)]) * \
                 np.array(self.state[(STOCK_DIM+1):(STOCK_DIM*2+1)]))

self.reward = end_total_asset - begin_total_asset

这种奖励函数简单直观,智能体会努力最大化每个时间步的收益。但问题在于,它只关注短期利润,可能导致频繁交易和高风险操作。

风险调整奖励

为了控制风险,FinRL 引入了奖励缩放机制。通过 REWARD_SCALING 参数,将奖励值乘以一个较小的系数(如 1e-4),避免奖励值过大导致梯度爆炸。

REWARD_SCALING = 1e-4
self.reward = self.reward * REWARD_SCALING

更高级的风险调整方法包括夏普比率奖励和最大回撤惩罚。夏普比率奖励将风险纳入考虑,鼓励智能体在承担单位风险时获取更高收益。实现方式是在奖励函数中加入收益波动率的分母项。

# 夏普比率风格的奖励计算
daily_return = (end_total_asset - begin_total_asset) / begin_total_asset
self.reward = daily_return / (self.return_std + 1e-8)

交易成本与市场摩擦

真实市场存在佣金、滑点等摩擦。FinRL 在环境中模拟了这些成本,确保训练出的策略具有现实可行性。

TRANSACTION_FEE_PERCENT = 0.001  # 0.1% 交易费率

def _sell_stock(self, index, action):
    self.state[0] += self.state[index+1] * sell_amount * (1 - TRANSACTION_FEE_PERCENT)
    self.cost += self.state[index+1] * sell_amount * TRANSACTION_FEE_PERCENT

def _buy_stock(self, index, action):
    self.state[0] -= self.state[index+1] * buy_amount * (1 + TRANSACTION_FEE_PERCENT)
    self.cost += self.state[index+1] * buy_amount * TRANSACTION_FEE_PERCENT

交易成本的引入会显著改变智能体的行为。高频交易策略在考虑成本后可能变得无利可图,智能体会倾向于减少交易次数,持有更长时间。这种机制自动惩罚了过度交易,让策略更接近真实投资逻辑。

湍流指数与风险控制

FinRL 创新性地引入了湍流指数(Turbulence Index)来控制极端市场风险。当市场波动异常剧烈时,环境会强制智能体清仓或停止交易,避免在金融危机中遭受巨大损失。

# 湍流指数控制逻辑
if self.turbulence >= self.turbulence_threshold:
    # 强制清仓
    for index in range(STOCK_DIM):
        if self.state[index+STOCK_DIM+1] > 0:
            self.state[0] += self.state[index+1] * self.state[index+STOCK_DIM+1] * (1 - TRANSACTION_FEE_PERCENT)
            self.state[index+STOCK_DIM+1] = 0

湍流指数的计算基于资产价格的协方差矩阵,衡量市场整体的异常波动程度。当指数超过预设阈值(如 140)时,触发风险控制机制。这种设计让智能体在训练过程中经历"危机"场景,学会在极端情况下保护资本。

自定义奖励函数

FinRL 允许用户自定义奖励函数,只需重写环境的 step() 方法中的奖励计算部分。例如,可以加入最大回撤惩罚:

# 最大回撤惩罚示例
current_drawdown = (self.peak_value - end_total_asset) / self.peak_value
drawdown_penalty = max(0, current_drawdown - 0.2)  # 超过20%回撤开始惩罚
self.reward = (end_total_asset - begin_total_asset) - drawdown_penalty * 1000

或者加入持仓集中度惩罚,避免过度集中投资于单一资产:

# 持仓集中度惩罚
position_concentration = max(self.state[STOCK_DIM+1:STOCK_DIM*2+1]) / sum(self.state[STOCK_DIM+1:STOCK_DIM*2+1])
concentration_penalty = max(0, position_concentration - 0.5)  # 单只股票超过50%开始惩罚
self.reward -= concentration_penalty

通过调整奖励函数,可以引导智能体学习不同的交易风格。保守型投资者可以加大回撤惩罚,激进型投资者可以提高收益权重。这种灵活性让 FinRL 能够适应不同的风险偏好和投资目标。

交易环境的设计是一门艺术,需要在真实性、可训练性和计算效率之间找到平衡点。FinRL 提供的环境框架既保留了市场的关键特征,又通过合理的抽象降低了训练难度。理解状态空间、动作空间和奖励函数的设计原理,是构建成功交易策略的第一步。在下一章中,我们将深入探讨如何在这些环境上训练 DRL 智能体,并调优超参数以获得更好的交易性能。