第4章:Gymnasium环境搭建——从安装到自定义交易环境
强化学习里有个核心概念叫「环境交互」。说白了,智能体跟环境的关系就像小孩学走路——摔倒了爬起来,慢慢就知道怎么迈腿了。在量化投资里,这个环境就是市场,智能体就是我们的交易策略。而Gymnasium,就是帮我们搭建这个「市场模拟器」的标准工具。
我个人习惯把Gymnasium比作「乐高底座」。它本身提供了一些经典环境(比如倒立摆、赛车游戏),但真正做量化投资,你得自己搭积木——创建自定义的交易环境。这一章,我们就从安装开始,一步步走到自定义交易环境的交互循环。
4.1 安装Gymnasium
先搞定环境安装。我建议用Python 3.8以上版本,用pip直接装:
pip install gymnasium
如果你需要用到经典控制类环境(比如CartPole),再加个扩展包:
pip install gymnasium[classic-control]
嗯,这里要注意:有些老教程还在用gym库,但那个已经停止维护了。Gymnasium是它的官方继承者,API基本兼容,但修复了不少坑。我曾经在项目里因为用了旧版gym,结果环境重置时状态初始化出了问题,排查了半天……后来果断切到Gymnasium,世界清净了。
验证安装是否成功,跑个简单测试:
import gymnasium as gym
env = gym.make("CartPole-v1")
obs, info = env.reset()
print(obs)
如果打印出一串数字(比如[0.0, 0.0, 0.0, 0.0]),说明装好了。
4.2 Gymnasium的核心概念
在动手搭环境之前,先理解Gymnasium的几个核心组件。你想想看,一个强化学习环境其实就干三件事:
- 状态(Observation):当前市场是什么情况?比如价格、持仓、账户余额。
- 动作(Action):智能体能做什么?比如买入、卖出、持有。
- 奖励(Reward):这一步做得好不好?比如赚了钱给正奖励,亏了给负奖励。
Gymnasium用一套标准接口把这些封装好了。核心方法就这几个:
| 方法 | 作用 | 我常用的场景 |
|---|---|---|
reset() | 重置环境到初始状态 | 每个交易周期开始前调用 |
step(action) | 执行动作,返回新状态、奖励、是否结束等 | 每次交易决策后调用 |
render() | 可视化环境(可选) | 调试时看看交易曲线 |
close() | 关闭环境 | 训练结束后清理资源 |
为什么要有这套标准?因为强化学习算法(比如DQN、PPO)都依赖这个接口。你只要实现了step和reset,任何算法都能直接跑你的环境。这就是「乐高底座」的威力。
4.3 创建自定义交易环境
好,重头戏来了。我们要创建一个股票交易环境。这个环境模拟了:你有一笔初始资金,每天可以决定买多少、卖多少,目标是最大化最终资产。
我会继承gym.Env类,然后实现几个关键方法。先看整体框架:
import gymnasium as gym
import numpy as np
from gymnasium import spaces
class TradingEnv(gym.Env):
"""自定义股票交易环境"""
def __init__(self, price_data, initial_balance=10000):
super().__init__()
self.price_data = price_data # 历史价格数据
self.initial_balance = initial_balance
self.current_step = 0
# 定义动作空间:0=持有, 1=买入, 2=卖出
self.action_space = spaces.Discrete(3)
# 定义状态空间:价格、持仓量、账户余额
self.observation_space = spaces.Box(
low=0, high=np.inf, shape=(3,), dtype=np.float32
)
def reset(self, seed=None, options=None):
super().reset(seed=seed)
self.current_step = 0
self.balance = self.initial_balance
self.holdings = 0 # 持仓股数
return self._get_obs(), {}
def step(self, action):
# 获取当前价格
current_price = self.price_data[self.current_step]
# 执行动作
if action == 1: # 买入
can_buy = self.balance // current_price
self.holdings += can_buy
self.balance -= can_buy * current_price
elif action == 2: # 卖出
self.balance += self.holdings * current_price
self.holdings = 0
# 移动到下一步
self.current_step += 1
# 计算奖励:资产变化
new_total = self.balance + self.holdings * current_price
reward = new_total - self._last_total
self._last_total = new_total
# 判断是否结束
terminated = self.current_step >= len(self.price_data) - 1
return self._get_obs(), reward, terminated, False, {}
def _get_obs(self):
current_price = self.price_data[self.current_step]
return np.array([current_price, self.holdings, self.balance], dtype=np.float32)
这段代码看起来简单,但有几个关键点我要强调一下:
动作空间设计:我用的是离散动作(0,1,2),因为交易决策本质上是分类问题。如果你要做连续仓位控制(比如买入30%仓位),那就得用spaces.Box。
奖励函数的小技巧:我习惯用「资产变化量」作为即时奖励,而不是直接用总资产。因为强化学习对奖励的尺度很敏感,用变化量可以让训练更稳定。曾经我直接用总资产做奖励,结果智能体学会了「躺平」——因为只要不交易,资产就不变,奖励为0,反而比亏钱好……
注意状态归一化:价格和余额的量级可能差很多(价格几十块,余额几万块)。如果不做归一化,神经网络训练时会出问题。我一般会把价格除以初始价格,余额除以初始余额,让所有状态都在0~1之间。
4.4 环境交互循环
环境搭好了,怎么用?标准交互循环长这样:
# 准备数据(假设有100天的价格)
price_data = np.random.randn(100) * 10 + 100 # 模拟价格
# 创建环境
env = TradingEnv(price_data, initial_balance=10000)
# 交互循环
obs, _ = env.reset()
total_reward = 0
done = False
while not done:
# 随机策略(实际会用训练好的模型)
action = env.action_space.sample()
# 执行动作
obs, reward, terminated, truncated, info = env.step(action)
total_reward += reward
done = terminated or truncated
print(f"Step {env.current_step}: Action={action}, Reward={reward:.2f}, Balance={env.balance:.2f}")
print(f"Total Reward: {total_reward:.2f}")
env.close()
这个循环就是强化学习的「心脏」。每次step返回五个值:
obs:新的状态reward:这一步的奖励terminated:是否达到终止条件(比如交易结束)truncated:是否被截断(比如超时)info:额外信息(调试用)
为什么要有terminated和truncated两个标志?这是Gymnasium的新设计。前者表示「自然结束」(比如数据用完了),后者表示「人为截断」(比如为了防止无限循环设置的最大步数)。在量化交易里,我通常把数据用完设为terminated,把单次交易最大持仓天数设为truncated。
4.5 知识体系总览
这一章的内容,我用一张图来总结:
这张图把本章的知识点串起来了。从安装开始,理解核心概念,然后动手搭自定义环境,最后用交互循环跑起来。每一步都是下一环的基础。
4.6 避坑指南
最后分享几个我踩过的坑:
- 状态空间维度不匹配:
observation_space定义的shape必须和_get_obs()返回的数组维度一致。我曾经少写了一个维度,结果算法训练时直接报错,排查了半天才发现是shape写错了。 - 奖励尺度问题:如果奖励值太大(比如几百万),神经网络梯度会爆炸。我一般会把奖励除以一个常数(比如1000),让它在-1到1之间。
- 环境重置不彻底:
reset()方法里一定要把所有状态变量都初始化。我遇到过忘记重置holdings,结果第二个交易周期一开始就带着上一轮的持仓……
嗯,这一章的内容就到这里。环境搭好了,下一章我们就可以开始训练智能体了。记住:环境是强化学习的基石,花时间把环境搭好,后面会省很多事。
公众号:蓝海资料掘金营,微信deep3321