gym-anytrading
AnyTrading
是一个用于基于强化学习的交易算法的OpenAI Gym环境集合。
交易算法主要在两个市场中实施:外汇和股票。AnyTrading旨在提供一些Gym环境,以改进并促进该领域基于强化学习算法的开发和测试过程。这一目的通过实现三个Gym环境来实现:TradingEnv、ForexEnv和StocksEnv。
TradingEnv是一个抽象环境,旨在支持各种交易环境。ForexEnv和StocksEnv只是简单地继承和扩展TradingEnv的两个环境。在后面的章节中,将对它们进行更多解释,但在此之前,需要讨论一些环境属性。
**注意:**对于专家来说,建议查看gym-mtsim项目。
安装
通过PIP
pip install gym-anytrading
从仓库
git clone https://github.com/AminHP/gym-anytrading
cd gym-anytrading
pip install -e .
## 或
pip install --upgrade --no-deps --force-reinstall https://github.com/AminHP/gym-anytrading/archive/master.zip
环境属性
首先,在如此复杂的交易市场中,你不能简单地期望一个强化学习代理为你做所有事情,而你只需坐在椅子上! 为了让代理能够更快、更高效地学习,需要尽可能地简化事情。在所有交易算法中,首先要做的是定义动作和仓位。在接下来的两个小节中,我将解释这些动作和仓位,以及如何简化它们。
交易动作
如果你在互联网上搜索交易算法,你会发现它们使用了许多动作,如买入、卖出、持有、进入、退出等。 回到本节的第一句话,一个典型的强化学习代理只能解决这个领域主要问题的一部分。如果你在交易市场工作,你会了解到决定是否持有、进入或退出一个货币对(在外汇中)或股票(在股票市场中)是一个依赖于许多参数的统计决策,如你的预算、你交易的货币对或股票、你在多个市场的资金分配策略等。让一个强化学习代理考虑所有这些参数是一个巨大的负担,可能需要数年时间来开发这样一个代理!在这种情况下,你肯定不会使用这个环境,而是会扩展你自己的环境。
所以经过几个月的工作,我最终发现这些动作只会使事情变得复杂,没有真正的积极影响。事实上,它们只会增加学习时间,而像持有这样的动作几乎不会被训练良好的代理使用,因为它不想错过一分钱。因此,没有必要有如此多的动作,只有卖出=0
和买入=1
这两个动作就足以很好地训练代理。
交易仓位
如果你不熟悉交易仓位,请参考这里。这是一个非常重要的概念,你应该尽快学习。
简单来说:多头仓位希望在价格低时买入股票,并在价值上涨时持有以获利,而空头仓位希望以高价值卖出股票,并使用这个价值以较低的价值买入股票,将差价作为利润。
同样,在一些交易算法中,你可能会发现许多仓位,如空头、多头、平仓等。如前所述,我只使用空头=0
和多头=1
这两种仓位。
交易环境
正如我之前提到的,现在是时候介绍这三个环境了。在创建这个项目之前,我花了很多时间搜索一个简单灵活的适用于任何交易市场的Gym环境,但没有找到。它们几乎都是一堆复杂的代码,有许多不清楚的参数,你无法简单地看一眼就理解发生了什么。所以我决定实施这个项目,非常注重简单性、灵活性和全面性。
在接下来的三个小节中,我将介绍我们的交易环境,在下一节中,将提到一些IPython示例并简要解释。
TradingEnv
TradingEnv是一个继承自gym.Env
的抽象类。这个类旨在为所有类型的交易市场提供一个通用环境。这里我解释它的公共属性和方法。但请随意查看完整的源代码。
- 属性:
df
:DataFrame的缩写。它是一个包含你的数据集的pandasDataFrame,在类的构造函数中传入。
prices
:随时间变化的实际价格。用于计算利润和渲染环境。
signal_features
:随时间提取的特征。用于创建Gym观察。
window_size
:作为Gym观察返回的刻度数(当前和之前的刻度)。在类的构造函数中传入。
action_space
:Gym action_space属性。包含离散值0=卖出和1=买入。
observation_space
:Gym observation_space属性。每个观察是signal_features
从索引current_tick - window_size + 1到current_tick的窗口。所以环境的_start_tick
等于window_size
。此外,_last_trade_tick
的初始值为window_size - 1。
shape
:单个观察的形状。
history
:存储所有步骤的信息。
- 方法:
seed
:典型的Gym seed方法。
reset
:典型的Gym reset方法。
step
:典型的Gym step方法。
render
:典型的Gym render方法。渲染环境当前刻度的信息。
render_all
:渲染整个环境。
close
:典型的Gym close方法。
- 抽象方法:
_process_data
:在构造函数中被调用,返回一个包含prices
和signal_features
的元组。在不同的交易市场中,需要获取不同的特征。因此,这个方法使我们的TradingEnv成为一个通用环境,可以为特定的环境(如外汇、股票等)返回特定的特征。
_calculate_reward
:强化学习代理的奖励函数。
_update_profit
:计算并更新强化学习代理到目前为止获得的总利润。利润表示从1.0单位开始获得的货币单位数量(利润 = 最终金额 / 起始金额)。
max_possible_profit
:强化学习代理在不考虑交易费用的情况下可以获得的最大可能利润。
ForexEnv
这是一个继承自TradingEnv并实现其抽象方法的具体类。它还具有一些针对外汇市场的特定属性。更多信息请参考源代码。
- 属性:
frame_bound
:一个指定df
开始和结束的元组。在类的构造函数中传入。
unit_side
:指定开始交易的一方。包含字符串值left(默认值)和right。如你所知,外汇中的货币对有两个方向。例如,在EUR/USD对中,当你选择left
方向时,你的货币单位是EUR,你从1欧元开始交易。在类的构造函数中传入。
trade_fee
:每次交易时从实际价格中扣除的默认固定费用。
StocksEnv
与ForexEnv类似,但适用于股票市场。更多信息请参考源代码。
- 属性:
frame_bound
:一个指定df
开始和结束的元组。在类的构造函数中传入。
trade_fee_bid_percent
:买入的默认固定费用百分比。例如,trade_fee_bid_percent=0.01时,每次卖出股票时你将损失1%的资金。
trade_fee_ask_percent
:卖出的默认固定费用百分比。例如,trade_fee_ask_percent=0.005时,每次买入股票时你将损失0.5%的资金。
此外,你可以通过扩展TradingEnv或者ForexEnv或StocksEnv来创建自己的自定义环境,根据你需要的奖励计算、利润计算、费用等策略进行定制。
示例
创建环境
import gymnasium as gym
import gym_anytrading
env = gym.make('forex-v0')
# env = gym.make('stocks-v0')
- 这将创建默认环境。你可以更改任何参数,如数据集、frame_bound等。
使用自定义参数创建环境
我为外汇和股票提供了两个默认数据集,但你也可以使用自己的数据集。
from gym_anytrading.datasets import FOREX_EURUSD_1H_ASK, STOCKS_GOOGL
custom_env = gym.make(
'forex-v0',
df=FOREX_EURUSD_1H_ASK,
window_size=10,
frame_bound=(10, 300),
unit_side='right'
)
# custom_env = gym.make(
# 'stocks-v0',
# df=STOCKS_GOOGL,
# window_size=10,
# frame_bound=(10, 300)
# )
- 需要注意的是,
frame_bound
的第一个元素应该大于或等于window_size
。
打印一些信息
print("环境信息:")
print("> shape:", env.unwrapped.shape)
print("> df.shape:", env.unwrapped.df.shape)
print("> prices.shape:", env.unwrapped.prices.shape)
print("> signal_features.shape:", env.unwrapped.signal_features.shape)
print("> max_possible_profit:", env.unwrapped.max_possible_profit())
print()
print("自定义环境信息:")
print("> shape:", custom_env.unwrapped.shape)
print("> df.shape:", custom_env.unwrapped.df.shape)
print("> prices.shape:", custom_env.unwrapped.prices.shape)
print("> signal_features.shape:", custom_env.unwrapped.signal_features.shape)
print("> max_possible_profit:", custom_env.unwrapped.max_possible_profit())
环境信息:
> shape: (24, 2)
> df.shape: (6225, 5)
> prices.shape: (6225,)
> signal_features.shape: (6225, 2)
> max_possible_profit: 4.054407219413578
自定义环境信息:
> shape: (10, 2)
> df.shape: (6225, 5)
> prices.shape: (300,)
> signal_features.shape: (300, 2)
> max_possible_profit: 1.1228998536878634
- 这里的
max_possible_profit
表示,如果市场没有交易费用,你可以从起始的1.0单位货币赚到4.054414887146572(或1.1229001800089833)单位。换句话说,你的钱几乎翻了四倍。
绘制环境
env.reset()
env.render()
- 做空和做多的位置分别用
红色
和绿色
表示。 - 如你所见,环境的起始位置始终是做空。
完整示例
import numpy as np
import matplotlib.pyplot as plt
import gymnasium as gym
import gym_anytrading
from gym_anytrading.envs import TradingEnv, ForexEnv, StocksEnv, Actions, Positions
from gym_anytrading.datasets import FOREX_EURUSD_1H_ASK, STOCKS_GOOGL
env = gym.make('forex-v0', frame_bound=(50, 100), window_size=10)
# env = gym.make('stocks-v0', frame_bound=(50, 100), window_size=10)
observation = env.reset(seed=2023)
while True:
action = env.action_space.sample()
observation, reward, terminated, truncated, info = env.step(action)
done = terminated or truncated
# env.render()
if done:
print("info:", info)
break
plt.cla()
env.unwrapped.render_all()
plt.show()
info: {'total_reward': 27.89616584777832, 'total_profit': 0.989812615901, 'position': <Positions.Long: 1>}
- 您可以使用
render_all
方法来避免在每个步骤上渲染,从而节省时间。 - 如您所见,图表上的前10个点(
window_size
=10)没有位置。因为它们不参与计算奖励、利润等。它们只是显示初始观察值。所以环境的_start_tick
和初始_last_trade_tick
分别为10和9。
更多示例
这里有一些示例,将gym-anytrading
与一些知名库(如Stable-Baselines3
和QuantStats
)结合使用,并展示了如何在其他强化学习或交易库中使用我们的交易环境。
扩展和操作TradingEnv
如果您想在环境外处理数据和提取特征,可以通过两种方法简单实现:
方法1(推荐):
def my_process_data(env):
start = env.frame_bound[0] - env.window_size
end = env.frame_bound[1]
prices = env.df.loc[:, 'Low'].to_numpy()[start:end]
signal_features = env.df.loc[:, ['Close', 'Open', 'High', 'Low']].to_numpy()[start:end]
return prices, signal_features
class MyForexEnv(ForexEnv):
_process_data = my_process_data
env = MyForexEnv(df=FOREX_EURUSD_1H_ASK, window_size=12, frame_bound=(12, len(FOREX_EURUSD_1H_ASK)))
方法2:
def my_process_data(df, window_size, frame_bound):
start = frame_bound[0] - window_size
end = frame_bound[1]
prices = df.loc[:, 'Low'].to_numpy()[start:end]
signal_features = df.loc[:, ['Close', 'Open', 'High', 'Low']].to_numpy()[start:end]
return prices, signal_features
class MyStocksEnv(StocksEnv):
def __init__(self, prices, signal_features, **kwargs):
self._prices = prices
self._signal_features = signal_features
super().__init__(**kwargs)
def _process_data(self):
return self._prices, self._signal_features
prices, signal_features = my_process_data(df=STOCKS_GOOGL, window_size=30, frame_bound=(30, len(STOCKS_GOOGL)))
env = MyStocksEnv(prices, signal_features, df=STOCKS_GOOGL, window_size=30, frame_bound=(30, len(STOCKS_GOOGL)))