第 1 步 · 建环境(含兼容补丁与 MA 特征)
import numpy as np, pandas as pd, trading_env
if not hasattr(pd.Series, "as_matrix"): # 老代码在新 pandas 上取数所需的补丁
pd.Series.as_matrix = lambda self: self.to_numpy()
pd.DataFrame.as_matrix = lambda self: self.to_numpy()
df = pd.read_csv('trading_env/test/data/SGXTWsample.csv', index_col=0, parse_dates=['datetime'])
df = df.reset_index(drop=True) # 索引契约:必须是 0..N 的整数
df['MA'] = df['Price'].rolling(20).mean()
df = df.iloc[20:].copy() # 裁掉 MA 未定义的头部,避免 NaN 进观测
FEATURES = ['Price', 'MA']
env = trading_env.make(env_id='backtest_v1', obs_data_len=64, step_len=32, df=df,
fee=0.0, max_position=1, deal_col_name='Price', feature_names=FEATURES)
把 MA 直接算进 DataFrame 是最省事的做法,但要注意 feature_names 必须与实际列名完全一致。
预期输出:无报错;env.obs_return 在 reset 后形状为 (64, 10)(2 个特征 + 8 个附加列)。
第 2 步 · 日间循环(关键在这里)
np.random.seed(0)
days, env.reset()
while True:
done = False
while not done: # 单日:跑完这一天的所有 step
obs = env.obs_return # 当前观测(含持仓列)
action = int(np.random.randint(3)) # 0=不动 1=做多 2=做空
obs, reward, done, info = env.step(action)
det = env.info.copy() # 当日逐 tick 账本
days.append(det)
if env.backtest_done: # 没有下一个交易日了
break
env.reset() # 推进到下一个交易日
这段循环在任何中文教程里都还找不到:reset() 承担「换一天」的职责,而 backtest_done 只在最后一个交易日跑完后才为 True。
预期输出:5 个交易日全部跑完;len(days) == 5,每日 len(det) 等于该日 tick 数(8,550 / 10,451 / 14,071 / 10,182 / 15,543)。
第 3 步 · 从账本算结果(不要用 step 返回值加总)
for det in days:
px, pos = det['Price'].to_numpy(float), det['position'].to_numpy(float)
env_reward = det['reward'].sum() # 与环境账本一致
site_pnl = ((px[1:] - px[:-1]) * pos[:-1]).sum() # 独立重算,应相等
trades = int((det['position_variation'] != 0).sum())
bh = px[-1] - px[0] # Buy&Hold 对照(1 份敞口)
print(det['datetime'].iloc[0].date(), trades, round(env_reward,3), round(site_pnl,3), round(bh,3))
本站用这套算法核对过:env.info['reward'].sum() 与「逐 tick 增量盈亏 × 持仓」的独立重算完全吻合,这也是判断「结果有没有算错」最直接的检查。
预期输出:每行打印一个交易日的日期、笔数、环境账本收益、独立重算收益与 Buy&Hold 收益;前两者应一致。