先补丁再选型
补丁是环境级动作,与选哪个 env_id 无关。先让四支都能实例化,你才有条件对比它们的观测与 reward 差异,而不是被报错挡住。
TradingGym · 环境核验 · 实测
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_v1 在 reset() 报 AttributeErrorbacktest_v1 仍留着 v0 的取数写法(backtest_v1.py:91)reset() 实测绘制的阶段示意(非官方流程图)。左侧三支 v0 系环境在第二阶段即失败,只有 training_v1 能走到 step()。TradingGym 与 EasyClaw 无已证实集成。TradingGym · 核验矩阵
下表每一行都是本机实跑的结果。观测形状用 obs_data_len=64 取值,换成其它窗口长度时形状会等比变化(特征列数 + 8)。
| 核验对象 | 取数实现 | 观测形态 | reward 口径 | 日间推进 | 本机实测 | 适用场景 | 注意点 |
|---|---|---|---|---|---|---|---|
training_v0 | Series.as_matrix()(training_v0.py:72-73) | 仅特征列(持仓特征被注释掉) | 单步 reward,按 fluc_div 缩放 | 随机抽日;另有 backtest() 方法(:442) | 失败:reset() 抛 AttributeError | 复现 2017–2020 年的老教程写法 | 它的持仓特征在源码里被注释掉了 |
training_v1 | Series.to_numpy()(training_v1.py:82/84) | 特征列 + 8 列(默认 return_transaction=True) | 观测窗口内 reward 之和 | 随机抽日,无 seed 参数 | 通过:obs shape (64,16) | 第一次摸接口、做 RL 实验 | 抽日随机且无 seed,实验不可复现 |
backtest_v0 | Series.as_matrix()(backtest_v0.py:67-68) | 仅特征列 | 单步 reward,含 gameover 提前终止(:270) | 随机抽日 | 失败:reset() 抛 AttributeError | 需要浮亏提前终止的老逻辑 | 抽日随机,回测结果无法归因 |
backtest_v1 | Series.as_matrix()(backtest_v1.py:91/93,仍是 v0 写法) | 特征列 + 8 列 | 观测窗口内 reward 之和(:280) | reset() 逐个交易日推进,backtest_done 收尾(:72-84) | 失败:reset() 抛 AttributeError;打补丁后可跑 | 逐日回测规则策略、取逐 tick 账本 | 必须先打 as_matrix 补丁 |
命令级:python -m trading_env.test | 走 training_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)。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 · 双代差异
两代实现不只是写法新旧,观测内容与 reward 口径都变了。把同一个 Agent 分别接到 v0 与 v1 上,得到的「reward 曲线」量级完全不同——这不是模型变强了,而是口径变了。
| 对照项 | 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_feature) | 以 position、position_variation、entry_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.randint) | training_v1 随机抽日;backtest_v1 用 reset() 顺序推进 | 回测要可复现就得用 backtest_v1 | 随机抽日又无 seed,两次运行日期不同 |
observation_space 声明是 np.array([obs_len*feature_len,])(一维 512),但 reset()/step() 实际返回 (64,16) 的二维数组(backtest_v1.py:48 与实测)。也就是说,声明与实际不一致,任何按声明建网络的代码都会对不上形状。TradingGym · 实测复现
补丁只补「取数」这一层:pandas 1.0 起 as_matrix() 被 to_numpy() 取代,语义等价。它不改变项目逻辑,也不代表上游会合并(仓库 HEAD 停在 2023-08-12)。
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']。
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。
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 · 补丁边界
很多中文教程把「装完能 import」当成跑通,本页把补丁的边界写清楚,避免把「能 reset」误当成「可以训练」。
| 类别 | 项目 | 现状 | 适用场景 | 注意点 |
|---|---|---|---|---|
| 补丁解决 | reset() 取数崩溃 | 四支环境均可实例化并取数 | 复现实验、读源码、做数据适配 | 只解决取数,不碰任何业务逻辑 |
| 补丁解决 | backtest_v1 能跑完整日回测 | 可顺序推进 8 个交易日并生成 env.info | 规则策略验证 | 账本口径仍需自行重建净值 |
| 补丁解决 | 官方自测可复跑 | python -m trading_env.test 通过(0.2658s) | 确认安装无误 | 注意写法是点号,CI 里的斜杠写法会报模块找不到 |
| 补丁不解决 | 抽日随机性 | training_v1 用 np.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 · 选型
选择顺序建议是「先确定要不要顺序推进交易日」→「再确定观测里要不要持仓」→「最后才是 v0/v1 的写法问题」。
| 你的场景 | 建议 env_id | 理由 | 注意点 |
|---|---|---|---|
| 第一次摸接口,想先看到 obs 和 reward | training_v1 | 四支里唯一在当前 pandas 上免补丁 reset() 通过 | 它随机抽日且无 seed,实验不可复现 |
| 做 RL 训练实验 | training_v1(自己接算法) | 观测含持仓与成本,信息更完整;仓库内没有现成算法 | 算法要自备;接 SB3 需先写适配层 |
| 逐日回测一条规则策略,要能复现 | backtest_v1 | reset() 顺序推进交易日、backtest_done 收尾,日期确定 | 必须先打 as_matrix 补丁 |
| 研究奖励塑形(reward shaping) | training_v1 或 backtest_v1 | 两代的 reward 都是源码里可改的显式表达式,改完立即可观测 | 改 reward 后净值必须独立重建,别沿用旧口径 |
| 只要逐 tick 账本做统计 | backtest_v1 | 日终生成 env.info,含 position / price_mean / reward 等列 | 账本列的含义要按源码区分,两列 reward 不可混用 |
| 照抄网上老教程(多为 2017–2020 年) | 先按教程的 env_id,再补丁 | 老教程基本用 v0 系写法,直接跑会报错 | 注意 v0 的持仓特征是被注释掉的,教程里的「状态包含仓位」说法不成立 |
补丁是环境级动作,与选哪个 env_id 无关。先让四支都能实例化,你才有条件对比它们的观测与 reward 差异,而不是被报错挡住。
只有它按顺序推进交易日。用 training_v1 的随机抽日做「回测」,等于每次跑在不同的日子上,结果无法归因。
v1 观测里已含持仓与成本,这会让「策略学会控制仓位」变得更容易,但也让不同论文之间的对比失去可比性。
TradingGym · 观测结构
以 8 个特征列为例,obs 的形状是 (obs_data_len, 16):前 8 列是你传入的 feature_names,后 8 列由环境拼接(backtest_v1.py:118-128)。return_transaction=False 时后 8 列不会出现。
| 下标 | 列名 | 含义 | 取值/来源 | 注意点 |
|---|---|---|---|---|
| 0–7 | feature_names 各列 | 你传入的特征(价格、量、买卖价等) | 原样切片自 DataFrame 对应列 | 顺序与传入列表一致;换列表等于换观测语义 |
| 8 | position | 当前持仓(正为多、负为空) | posi_arr,随动作加减 1 | 上限由 max_position 决定,到达上限同向动作被改写为不动 |
| 9 | position_variation | 仓位变动量(+1 加一手、-1 减一手) | 发生成交的 tick 上非零 | 用它数真实成交次数比数 action 更准 |
| 10 | entry_cover | 动作性质标记 | 新开=1、加仓=2、平仓=-1、减仓=-2 | 源码注释的约定;注意正负与「开/平」不是简单对应 |
| 11 | price | 该 tick 的价格(deal_col_name 列) | 原样切片自数据 | 撮合价取的是观测窗口之后的第一根 tick,不是窗口内最后一根 |
| 12 | price_mean | 当前持仓的平均成本 | 开仓时为成交价,加仓时按手数加权 | 用它核对「账本里的成本价」是否符合预期 |
| 13 | reward_fluctuant | 浮动盈亏水平值 | (price − price_mean) × position − |position| × fee | 是水平值不是增量,逐 tick 相加会放大数十倍 |
| 14 | reward_makereal | 是否发生平仓结算的标记 | 平仓 tick 上为 1,其余为 0 | 初始化为 0;可用它定位结算点 |
| 15 | reward | 该 tick 的已实现盈亏 | 平仓时按 (成交价 − 成本价) × 方向 − fee 计算 | 这一列逐 tick 相加才与真实增量盈亏一致(实测单日 -0.5 对 -0.5) |
reward)累加;想观察浮盈波动,用下标 13(reward_fluctuant)但不要累加;想统计成交行为,用下标 9(position_variation)。三列口径不同,混用是「reward 上升但收益没涨」这类困惑的常见来源,详见「奖励与基准」页。TradingGym · FAQ
能跑,但要看你选哪支。本机实测:training_v1 不补丁即可 reset();training_v0、backtest_v0、backtest_v1 报 AttributeError: 'Series' object has no attribute 'as_matrix',补两行兼容代码后四支都能跑。以上结论限于 Python 3.11.9 + pandas 2.2.3,其它版本请以官方源码与你本机实跑为准。
v1 用 to_numpy() 取数、观测里额外拼了 8 列持仓/成本/盈亏、step() 返回观测窗口内的 reward 之和;v0 用 as_matrix()、观测只有特征列(持仓特征在源码里被注释掉)、返回单步 reward。多数情况下从 training_v1 开始更省事,但如果你要复现的是「Agent 只能看到行情」的设置,就得用 return_transaction=False 或自行切列。以官方源码为准。
因为它的文件名是 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),这些仍会让「看起来一样」的两段代码给出不同数字。所有结论以官方源码与你本机实测为准。