TradingGym · 环境核验 · 实测

四个环境哪个能跑:training_v0/v1 与 backtest_v0/v1 逐支实测

TradingGym 把「训练」与「回测」各做了两代实现,四支并存于同一个包里。名字像版本号,实际是两套接口:v0 系用 as_matrix() 取数、观测只有特征列、step() 返回单步 reward;v1 系改用 to_numpy()backtest_v1 例外)、观测额外拼 8 列持仓与盈亏、返回窗口求和 reward。本页在 Python 3.11.9 + pandas 2.2.3 上把四支逐个 reset(),把报错原文和补丁后的形态都摆出来。

  • 实测结论training_v1 通过;training_v0 / backtest_v0 / backtest_v1reset()AttributeError
  • 最反直觉的一条:名字带 v1 的 backtest_v1 仍留着 v0 的取数写法(backtest_v1.py:91
  • 修法:两行 monkey patch 即可让四支都实例化,不改动原项目行为

TradingGym 环境生命周期有哪四个阶段?

make() 断言校验 env_id 与三组列名
reset() 取数v0:as_matrix() · v1:to_numpy()
step() 推进窗口 + step_len,reward 求和
done → env.info生成逐 tick 账本
依据 TradingGym 源码与本机 reset() 实测绘制的阶段示意(非官方流程图)。左侧三支 v0 系环境在第二阶段即失败,只有 training_v1 能走到 step()。TradingGym 与 EasyClaw 无已证实集成。

TradingGym · 核验矩阵

TradingGym 四个环境哪个能跑?可运行性核验矩阵

下表每一行都是本机实跑的结果。观测形状用 obs_data_len=64 取值,换成其它窗口长度时形状会等比变化(特征列数 + 8)。

四环境 + 命令级可运行性核验(Python 3.11.9 / pandas 2.2.3 / numpy 2.4.6,2026-09-22 实测)
核验对象取数实现观测形态reward 口径日间推进本机实测适用场景注意点
training_v0Series.as_matrix()training_v0.py:72-73仅特征列(持仓特征被注释掉)单步 reward,按 fluc_div 缩放随机抽日;另有 backtest() 方法(:442失败:reset() 抛 AttributeError复现 2017–2020 年的老教程写法它的持仓特征在源码里被注释掉了
training_v1Series.to_numpy()training_v1.py:82/84特征列 + 8 列(默认 return_transaction=True观测窗口内 reward 之和随机抽日,无 seed 参数通过:obs shape (64,16)第一次摸接口、做 RL 实验抽日随机且无 seed,实验不可复现
backtest_v0Series.as_matrix()backtest_v0.py:67-68仅特征列单步 reward,含 gameover 提前终止(:270随机抽日失败:reset() 抛 AttributeError需要浮亏提前终止的老逻辑抽日随机,回测结果无法归因
backtest_v1Series.as_matrix()backtest_v1.py:91/93,仍是 v0 写法)特征列 + 8 列观测窗口内 reward 之和(:280reset() 逐个交易日推进,backtest_done 收尾(:72-84失败:reset() 抛 AttributeError;打补丁后可跑逐日回测规则策略、取逐 tick 账本必须先打 as_matrix 补丁
命令级:python -m trading_env.testtraining_v1打印抽到的交易日与耗时随机抽日通过:exit 0,用时 0.2658s装完自检、确认依赖齐全正确写法是点号,不是斜杠
命令级:python -m trading_env/test—(命令本身非法)失败No module named trading_env/test—(不要这样调)官方 .travis.yml 里就是这个写法
四处实测报错原文完全一致:AttributeError: 'Series' object has no attribute 'as_matrix'。原因是 as_matrix() 在 pandas 1.0 就被移除,而这三支代码停在 pandas 0.2x 年代(官方 CI 只测到 Python 3.6,见 .travis.yml)。

复现方式:四支环境逐个 reset

import pandas as pd, trading_env

df = pd.read_csv('trading_env/test/data/SGXTWsample.csv', index_col=0, parse_dates=['datetime'])
for eid in trading_env.available_envs():          # ['training_v0','training_v1','backtest_v0','backtest_v1']
    env = trading_env.make(env_id=eid, obs_data_len=64, step_len=32, df=df, fee=0.1,
                           max_position=1, deal_col_name='Price',
                           feature_names=['Price','Volume','Ask_price','Bid_price',
                                          'Ask_deal_vol','Bid_deal_vol','Bid/Ask_deal','Updown'])
    try:
        env.reset(); print(eid, 'OK')
    except Exception as e:
        print(eid, 'FAILED:', type(e).__name__, e)

用官方仓库自带的样例数据(113,059 行 tick、8 个交易日)即可复现,不需要额外下载行情。

预期输出:training_v0 FAILED: AttributeError 'Series' object has no attribute 'as_matrix' / training_v1 OK / backtest_v0 FAILED: AttributeError … / backtest_v1 FAILED: AttributeError …

TradingGym · 双代差异

TradingGym v0 与 v1 的八项差异:为什么选错环境会让结果不可比

两代实现不只是写法新旧,观测内容与 reward 口径都变了。把同一个 Agent 分别接到 v0 与 v1 上,得到的「reward 曲线」量级完全不同——这不是模型变强了,而是口径变了。

v0 系与 v1 系逐项对照(源码行号为本机实读的 master HEAD 08bb534
对照项v0 系(training_v0 / backtest_v0)v1 系(training_v1 / backtest_v1)对你的影响注意点
取数实现as_matrix()to_numpy()仅 training_v1;backtest_v1 仍是 as_matrix)决定要不要打补丁别按名字判断新旧
观测内容feature_names特征列 + 8 列持仓/成本/盈亏Agent 能看到自己的仓位,策略行为会不同想复现纯行情观测的论文设置需自行裁剪
持仓特征代码里被注释掉(# self.position_featurepositionposition_variationentry_cover 三列给出v0 下 Agent 实际不知道当前仓位这是 v0 的隐藏限制,文档没写
reward 口径单步 reward_fluctuant / fluc_div(默认 100.0)观测窗口内 reward 数组之和相邻 step 之间重复计数,不能直接当净值净值要重建,见「奖励与基准」页
提前终止gameover_limit 生效(默认 5)不生效(v1 的 step 里没有该分支)v1 上一旦浮亏会一路跑满当日 tick想要风控要自己在外层加
观测开关return_transaction(默认 True)置 False 可退回「只给特征列」的形态置 False 后形状与 v0 一致但仍走 v1 逻辑
空间声明类型action_space = np.array([3,])action_space = 3(整数)都不是 Gym 的 Discrete(3),接 SB3 要包一层两代都不能直接喂给 SB3
日间推进随机抽日(np.random.randinttraining_v1 随机抽日;backtest_v1 用 reset() 顺序推进回测要可复现就得用 backtest_v1随机抽日又无 seed,两次运行日期不同
另有一条属于「文档没写」的差异:v1 的 observation_space 声明是 np.array([obs_len*feature_len,])(一维 512),但 reset()/step() 实际返回 (64,16) 的二维数组(backtest_v1.py:48 与实测)。也就是说,声明与实际不一致,任何按声明建网络的代码都会对不上形状。

TradingGym · 实测复现

怎么让 TradingGym 四个环境都能跑?三步复现与 2 行兼容补丁

补丁只补「取数」这一层:pandas 1.0 起 as_matrix()to_numpy() 取代,语义等价。它不改变项目逻辑,也不代表上游会合并(仓库 HEAD 停在 2023-08-12)。

第 1 步 · 安装并确认四支环境可见

git clone https://github.com/Yvictor/TradingGym.git
cd TradingGym
python setup.py install

官方只提供源码安装路径(PyPI 上没有 trading_env 这个包,实测返回 404;名字相近的 trading-gym 属于另一个项目)。

预期输出:trading_env.available_envs() 返回 ['training_v0','training_v1','backtest_v0','backtest_v1']

第 2 步 · 在 import 之后补两行

import pandas as pd
if not hasattr(pd.Series, "as_matrix"):        # pandas 1.0 起移除,这里等价映射到 to_numpy
    pd.Series.as_matrix = lambda self: self.to_numpy()
    pd.DataFrame.as_matrix = lambda self: self.to_numpy()

两行必须放在 import trading_env 之后、调用 reset() 之前;对已经定义了 as_matrix 的老环境(pandas < 1.0)不会生效,因此不会污染旧环境。

预期输出:无输出(静默生效);随后四支环境的 reset() 不再抛 AttributeError

第 3 步 · 复跑核验矩阵并确认形状

env = trading_env.make(env_id='backtest_v1', obs_data_len=64, step_len=32, df=df, fee=0.1,
                       max_position=1, deal_col_name='Price',
                       feature_names=['Price','Volume','Ask_price','Bid_price',
                                      'Ask_deal_vol','Bid_deal_vol','Bid/Ask_deal','Updown'])
obs = env.reset()
print(obs.shape)        # (64, 16)
obs, reward, done, info = env.step(1)

补丁后 backtest_v1 可以走完整日回测:reset() 取一个交易日,step() 循环到 done,此时 env.info 里就是逐 tick 账本。

预期输出:(64, 16)step(1) 返回 (obs, reward, done, info) 四元组,reward 为该观测窗口内 reward 之和。

TradingGym · 补丁边界

TradingGym 这 2 行补丁解决了什么、没解决什么

很多中文教程把「装完能 import」当成跑通,本页把补丁的边界写清楚,避免把「能 reset」误当成「可以训练」。

补丁能力边界(左栏为实测通过的项,右栏为补丁管不到的项)
类别项目现状适用场景注意点
补丁解决reset() 取数崩溃四支环境均可实例化并取数复现实验、读源码、做数据适配只解决取数,不碰任何业务逻辑
补丁解决backtest_v1 能跑完整日回测可顺序推进 8 个交易日并生成 env.info规则策略验证账本口径仍需自行重建净值
补丁解决官方自测可复跑python -m trading_env.test 通过(0.2658s)确认安装无误注意写法是点号,CI 里的斜杠写法会报模块找不到
补丁不解决抽日随机性training_v1np.random.randint,无 seed 参数做可复现实验时必须先固定 numpy 全局种子,或改用 backtest_v1
补丁不解决观测含持仓信息v1 默认把 8 列持仓/成本/盈亏拼进观测想复现纯行情观测return_transaction=False,或自行切列
补丁不解决v1 无 gameover浮亏提前终止只在 v0 生效需要风控的研究设置在外层循环里自己加熔断条件
补丁不解决空间声明不符observation_space 报一维、实际二维接 Gymnasium / SB3必须写适配层重声明空间
补丁不解决仓库不再更新HEAD 停在 2023-08-12(47 次提交)长期项目自行 fork 维护,并为老依赖补测试

结论:补丁把「能不能取数」这一关解决了,但把它当成「可以开始训练」仍然不成立——观测口径、随机性、空间声明与风控都要你自己处理。TradingGym 与 EasyClaw 无已证实集成,本页不涉及任何实盘或收益结论。

TradingGym · 选型

TradingGym 按目标选 env_id是什么?六个常见场景的推荐与理由

选择顺序建议是「先确定要不要顺序推进交易日」→「再确定观测里要不要持仓」→「最后才是 v0/v1 的写法问题」。

场景 → 环境对照(理由是源码事实,不是经验之谈)
你的场景建议 env_id理由注意点
第一次摸接口,想先看到 obs 和 rewardtraining_v1四支里唯一在当前 pandas 上免补丁 reset() 通过它随机抽日且无 seed,实验不可复现
做 RL 训练实验training_v1(自己接算法)观测含持仓与成本,信息更完整;仓库内没有现成算法算法要自备;接 SB3 需先写适配层
逐日回测一条规则策略,要能复现backtest_v1reset() 顺序推进交易日、backtest_done 收尾,日期确定必须先打 as_matrix 补丁
研究奖励塑形(reward shaping)training_v1backtest_v1两代的 reward 都是源码里可改的显式表达式,改完立即可观测改 reward 后净值必须独立重建,别沿用旧口径
只要逐 tick 账本做统计backtest_v1日终生成 env.info,含 position / price_mean / reward 等列账本列的含义要按源码区分,两列 reward 不可混用
照抄网上老教程(多为 2017–2020 年)先按教程的 env_id,再补丁老教程基本用 v0 系写法,直接跑会报错注意 v0 的持仓特征是被注释掉的,教程里的「状态包含仓位」说法不成立

先补丁再选型

补丁是环境级动作,与选哪个 env_id 无关。先让四支都能实例化,你才有条件对比它们的观测与 reward 差异,而不是被报错挡住。

回测优先用 backtest_v1

只有它按顺序推进交易日。用 training_v1 的随机抽日做「回测」,等于每次跑在不同的日子上,结果无法归因。

训练侧注意观测口径

v1 观测里已含持仓与成本,这会让「策略学会控制仓位」变得更容易,但也让不同论文之间的对比失去可比性。

TradingGym · 观测结构

TradingGym v1 观测的 16 列逐列说明是什么?特征 8 列 + 交易状态 8 列

以 8 个特征列为例,obs 的形状是 (obs_data_len, 16):前 8 列是你传入的 feature_names,后 8 列由环境拼接(backtest_v1.py:118-128)。return_transaction=False 时后 8 列不会出现。

观测列逐列说明(列序即数组下标顺序;行号为本机实读 master HEAD 的源码位置)
下标列名含义取值/来源注意点
0–7feature_names 各列你传入的特征(价格、量、买卖价等)原样切片自 DataFrame 对应列顺序与传入列表一致;换列表等于换观测语义
8position当前持仓(正为多、负为空)posi_arr,随动作加减 1上限由 max_position 决定,到达上限同向动作被改写为不动
9position_variation仓位变动量(+1 加一手、-1 减一手)发生成交的 tick 上非零用它数真实成交次数比数 action 更准
10entry_cover动作性质标记新开=1、加仓=2、平仓=-1、减仓=-2源码注释的约定;注意正负与「开/平」不是简单对应
11price该 tick 的价格(deal_col_name 列)原样切片自数据撮合价取的是观测窗口之后的第一根 tick,不是窗口内最后一根
12price_mean当前持仓的平均成本开仓时为成交价,加仓时按手数加权用它核对「账本里的成本价」是否符合预期
13reward_fluctuant浮动盈亏水平值(price − price_mean) × position − |position| × fee是水平值不是增量,逐 tick 相加会放大数十倍
14reward_makereal是否发生平仓结算的标记平仓 tick 上为 1,其余为 0初始化为 0;可用它定位结算点
15reward该 tick 的已实现盈亏平仓时按 (成交价 − 成本价) × 方向 − fee 计算这一列逐 tick 相加才与真实增量盈亏一致(实测单日 -0.5 对 -0.5)
读法建议:想做净值曲线,用下标 15(reward)累加;想观察浮盈波动,用下标 13(reward_fluctuant)但不要累加;想统计成交行为,用下标 9(position_variation)。三列口径不同,混用是「reward 上升但收益没涨」这类困惑的常见来源,详见「奖励与基准」页。

TradingGym · FAQ

TradingGym 环境核验常见问题是什么?

TradingGym 现在到底能不能跑起来?

能跑,但要看你选哪支。本机实测:training_v1 不补丁即可 reset()training_v0backtest_v0backtest_v1AttributeError: 'Series' object has no attribute 'as_matrix',补两行兼容代码后四支都能跑。以上结论限于 Python 3.11.9 + pandas 2.2.3,其它版本请以官方源码与你本机实跑为准。

training_v1 和 training_v0 有什么区别,该用哪个?

v1 用 to_numpy() 取数、观测里额外拼了 8 列持仓/成本/盈亏、step() 返回观测窗口内的 reward 之和;v0 用 as_matrix()、观测只有特征列(持仓特征在源码里被注释掉)、返回单步 reward。多数情况下从 training_v1 开始更省事,但如果你要复现的是「Agent 只能看到行情」的设置,就得用 return_transaction=False 或自行切列。以官方源码为准。

backtest_v1 名字带 v1,为什么也要打补丁?

因为它的文件名是 v1,取数代码却还是 v0 的写法:backtest_v1.py:91/93 仍调用 as_matrix()。本机实测它和两支 v0 环境报同一个 AttributeError。这也是「不要按文件名判断新旧」的一个现成例子。

为什么我的观测形状和文档说的不一样?

因为 observation_space 的声明与实际返回值不一致:源码把它声明成 np.array([obs_len*feature_len,])(一维),而 reset()/step() 实际返回二维数组(特征列 + 8 列交易状态)。本机实测 obs_data_len=64、8 个特征时返回 (64,16)。接深度学习框架时请按实际形状建网络,并以官方源码为准。

为什么两次运行抽到的交易日不一样?

因为训练侧环境用 np.random.randint(self.date_leng) 抽日(training_v1.py:68-77),且 make() 没有 seed 参数。要做可复现实验,先固定 numpy 全局随机种子,或者改用按顺序推进的 backtest_v1。这一行为以官方实现为准。

打补丁会不会改变回测结果?

不会改变逻辑:as_matrix()to_numpy() 在语义上等价,补丁只让老写法在新 pandas 上可用。但要注意,补丁并不修复其它口径问题(reward 窗口求和、reward_fluctuant 是水平值、v1 无 gameover、无 seed),这些仍会让「看起来一样」的两段代码给出不同数字。所有结论以官方源码与你本机实测为准。

下一步该怎么做?把四个环境跑起来之后,去看回测循环怎么写

README 里的回测示例在当前版本会报错,本页给出可运行的日间循环与账本读取方式。