强化学习¶
安装包大小
强化学习依赖项包含一些大型包(如 torch),在执行 ./setup.sh -i 时需显式请求:当出现提示 "Do you also want dependencies for freqai-rl (~700mb additional space required) [y/N]?" 时回答 "y"。偏好使用 Docker 的用户应确保使用带有 _freqairl 后缀的镜像。
背景与术语¶
什么是强化学习,以及为什么 FreqAI 需要它?¶
强化学习包含两个关键组成部分:智能体(agent)和训练环境(environment)。在智能体训练过程中,智能体会逐根 K 线遍历历史数据,并在一组预设动作中做出选择:做多入场、做多离场、做空入场、做空离场或保持中性。在此训练过程中,环境会追踪这些动作的表现,并根据用户自定义的 calculate_reward() 函数给予智能体奖励(此处我们提供了一个默认奖励函数,供用户在此基础上进行扩展,详情见此)。该奖励用于训练神经网络中的权重。
FreqAI 强化学习实现中的另一个重要部分是使用状态信息。每一步都会将状态信息输入网络,包括当前收益、当前持仓状态和当前交易持续时间。这些信息用于在训练环境中训练智能体,并在模拟或实盘交易中对智能体进行强化(此功能在回测中不可用)。FreqAI 与 Freqtrade 是这一强化机制的理想组合,因为这些信息在实际部署中可直接获取。
强化学习是 FreqAI 的自然演进方向,因为它引入了一层新的适应性和市场响应能力,这是分类器和回归模型无法比拟的。然而,分类器和回归模型也有其优势,例如预测更加稳健。训练不当的强化学习智能体可能会找到“捷径”或“技巧”来最大化奖励,但实际上并未赢得任何交易。因此,强化学习更为复杂,要求使用者具备更高的理解水平。
强化学习接口¶
在当前框架下,我们旨在通过通用的“预测模型”文件暴露训练环境,该文件是一个用户继承的 BaseReinforcementLearner 对象(例如 freqai/prediction_models/ReinforcementLearner)。在这个用户类内部,可以通过 MyRLEnv 访问并定制强化学习环境,如下所示。
我们预计大多数用户将主要精力集中在创造性地设计 calculate_reward() 函数上 详情见此,而保持环境其余部分不变。其他用户可能完全不修改环境,仅调整配置参数以及利用 FreqAI 中已有的强大特征工程功能。同时,我们也为高级用户提供支持,使其能够完全自定义自己的模型类。
该框架基于 stable_baselines3(torch)和 OpenAI gym 的基础环境类构建。但总体而言,模型类具有良好的隔离性,因此可以轻松集成其他竞争性库。对于环境部分,它继承自 gym.Env,这意味着若要切换到不同的库,则必须编写一个全新的环境。
重要注意事项¶
如上所述,代理是在一个人工交易“环境”中进行“训练”的。在我们的案例中,该环境可能看起来与真实的 Freqtrade 回测环境非常相似,但实际上它并不相同。事实上,强化学习训练环境被大大简化了,它并未包含任何复杂的策略逻辑,例如 custom_exit、custom_stoploss、杠杆控制等回调函数。相反,RL 环境是对真实市场的非常“原始”的表示,在此环境中,代理可以自由地学习策略(即:止损、止盈等),而这些策略由 calculate_reward() 函数来实施。因此,必须认识到,代理的训练环境与现实世界并不完全一致。
运行强化学习¶
设置并运行强化学习模型的过程与运行回归器或分类器相同。命令行中必须定义相同的两个标志:--freqaimodel 和 --strategy:
freqtrade trade --freqaimodel ReinforcementLearner --strategy MyRLStrategy --config config.json
其中 ReinforcementLearner 将使用位于 freqai/prediction_models/ReinforcementLearner 的模板化 ReinforcementLearner(或用户自定义并存放于 user_data/freqaimodels 中的模型)。另一方面,策略遵循与其他典型回归器相同的特征工程流程,使用 feature_engineering_* 方法。不同之处在于目标值的生成——强化学习不需要显式的目标标签。然而,FreqAI 要求在动作列中设置一个默认(中性)值:
def set_freqai_targets(self, dataframe, **kwargs) -> DataFrame:
"""
*Only functional with FreqAI enabled strategies*
Required function to set the targets for the model.
All targets must be prepended with `&` to be recognized by the FreqAI internals.
More details about feature engineering available:
https://www.freqtrade.io/en/latest/freqai-feature-engineering
:param df: strategy dataframe which will receive the targets
usage example: dataframe["&-target"] = dataframe["close"].shift(-1) / dataframe["close"]
"""
# For RL, there are no direct targets to set. This is filler (neutral)
# until the agent sends an action.
dataframe["&-action"] = 0
return dataframe
大部分函数的功能保持不变,但以下函数展示了策略如何将原始价格数据传递给代理,以便其在训练环境中能够访问原始的 OHLCV 数据:
def feature_engineering_standard(self, dataframe: DataFrame, **kwargs) -> DataFrame:
# The following features are necessary for RL models
dataframe[f"%-raw_close"] = dataframe["close"]
dataframe[f"%-raw_open"] = dataframe["open"]
dataframe[f"%-raw_high"] = dataframe["high"]
dataframe[f"%-raw_low"] = dataframe["low"]
return dataframe
最后,无需明确创建“标签”——而是需要分配 &-action 列,该列将在 populate_entry/exit_trends() 中被访问时包含代理的动作。在当前示例中,中性动作为 0。该值应与所使用的环境保持一致。FreqAI 提供了两个环境,均使用 0 作为中性动作。
当用户意识到无需设置标签后,他们很快就会明白,代理正在自行做出进出仓决策。这使得策略构建变得相当简单。入场和离场信号以整数形式由代理提供,并直接用于在策略中决定买卖操作:
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
enter_long_conditions = [df["do_predict"] == 1, df["&-action"] == 1]
if enter_long_conditions:
df.loc[
reduce(lambda x, y: x & y, enter_long_conditions), ["enter_long", "enter_tag"]
] = (1, "long")
enter_short_conditions = [df["do_predict"] == 1, df["&-action"] == 3]
if enter_short_conditions:
df.loc[
reduce(lambda x, y: x & y, enter_short_conditions), ["enter_short", "enter_tag"]
] = (1, "short")
return df
def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
exit_long_conditions = [df["do_predict"] == 1, df["&-action"] == 2]
if exit_long_conditions:
df.loc[reduce(lambda x, y: x & y, exit_long_conditions), "exit_long"] = 1
exit_short_conditions = [df["do_predict"] == 1, df["&-action"] == 4]
if exit_short_conditions:
df.loc[reduce(lambda x, y: x & y, exit_short_conditions), "exit_short"] = 1
return df
需要注意的是,&-action 的含义取决于所选择的环境。上述示例展示了 5 种动作:0 表示中性,1 表示做多入场,2 表示做多离场,3 表示做空入场,4 表示做空离场。
配置强化学习器¶
为了配置 Reinforcement Learner,必须在 freqai 配置文件中存在以下字典:
"rl_config": {
"train_cycles": 25,
"add_state_info": true,
"max_trade_duration_candles": 300,
"max_training_drawdown_pct": 0.02,
"cpu_count": 8,
"model_type": "PPO",
"policy_type": "MlpPolicy",
"model_reward_parameters": {
"rr": 1,
"profit_aim": 0.025
}
}
参数详情请参见此处,但通常来说,train_cycles 决定了智能体在其人工环境中对 K 线数据进行训练时循环的次数,以调整模型中的权重。model_type 是一个字符串,用于从 stable_baselines(外部链接)中选择可用的模型之一。
注意
如果你想尝试 continual_learning 功能,则应在主 freqai 配置字典中将该值设为 true。这会通知强化学习库在每次重新训练时,从先前模型的最终状态继续训练新模型,而不是每次都从头开始重新训练。
注意
请注意,通用的 model_training_parameters 字典应包含针对特定 model_type 的所有模型超参数自定义设置。例如,PPO 的参数可在 此处 找到。
创建自定义奖励函数¶
不适用于生产环境
警告!Freqtrade 源代码中提供的奖励函数仅用于功能展示,旨在尽可能多地演示/测试环境控制的各种特性,同时也设计为可在小型计算机上快速运行。这是一个基准示例,不可用于实际生产。请注意,你需要自行创建自定义的 reward() 函数,或使用其他用户在 Freqtrade 源代码之外构建的模板。
当你开始修改策略和预测模型时,很快就会意识到强化学习器与回归器/分类器之间的一些重要区别。首先,策略不会设定目标值(即没有标签!)。相反,你需要在 MyRLEnv 类中设置 calculate_reward() 函数(见下文)。默认的 calculate_reward() 函数位于 prediction_models/ReinforcementLearner.py 中,用于展示创建奖励所需的必要组件,但这并不适用于生产环境。用户必须创建自己的自定义强化学习模型类,或使用来自 Freqtrade 源代码之外的预建模型,并将其保存至 user_data/freqaimodels 目录。正是在 calculate_reward() 中,你可以表达关于市场的创造性理论。例如,当智能体完成一笔盈利交易时给予奖励,亏损交易时予以惩罚;或者你希望在智能体开仓时给予奖励,持仓时间过长时进行惩罚。以下我们展示了这些奖励的具体计算方式:
提示
最佳的奖励函数应具备连续可导性和良好的缩放性。换句话说,对罕见事件施加单次大幅负向惩罚并不是一个好的做法,神经网络难以从中有效学习。更好的方法是对常见事件施加较小的负向惩罚,这有助于智能体更快地学习。不仅如此,你还可以通过线性或指数函数使奖励/惩罚随严重程度变化,从而提升其连续性。例如,随着持仓时间延长,逐步增加惩罚力度,这比在某一时刻突然施加一次重大惩罚效果更好。
from freqtrade.freqai.prediction_models.ReinforcementLearner import ReinforcementLearner
from freqtrade.freqai.RL.Base5ActionRLEnv import Actions, Base5ActionRLEnv, Positions
class MyCoolRLModel(ReinforcementLearner):
"""
User created RL prediction model.
Save this file to `freqtrade/user_data/freqaimodels`
then use it with:
freqtrade trade --freqaimodel MyCoolRLModel --config config.json --strategy SomeCoolStrat
Here the users can override any of the functions
available in the `IFreqaiModel` inheritance tree. Most importantly for RL, this
is where the user overrides `MyRLEnv` (see below), to define custom
`calculate_reward()` function, or to override any other parts of the environment.
This class also allows users to override any other part of the IFreqaiModel tree.
For example, the user can override `def fit()` or `def train()` or `def predict()`
to take fine-tuned control over these processes.
Another common override may be `def data_cleaning_predict()` where the user can
take fine-tuned control over the data handling pipeline.
"""
class MyRLEnv(Base5ActionRLEnv):
"""
User made custom environment. This class inherits from BaseEnvironment and gym.Env.
Users can override any functions from those parent classes. Here is an example
of a user customized `calculate_reward()` function.
Warning!
This is function is a showcase of functionality designed to show as many possible
environment control features as possible. It is also designed to run quickly
on small computers. This is a benchmark, it is *not* for live production.
"""
def calculate_reward(self, action: int) -> float:
# first, penalize if the action is not valid
if not self._is_valid(action):
return -2
pnl = self.get_unrealized_profit()
factor = 100
pair = self.pair.replace(':', '')
# you can use feature values from dataframe
# Assumes the shifted RSI indicator has been generated in the strategy.
rsi_now = self.raw_features[f"%-rsi-period_10_shift-1_{pair}_"
f"{self.config['timeframe']}"].iloc[self._current_tick]
# reward agent for entering trades
if (action in (Actions.Long_enter.value, Actions.Short_enter.value)
and self._position == Positions.Neutral):
if rsi_now < 40:
factor = 40 / rsi_now
else:
factor = 1
return 25 * factor
# discourage agent from not entering trades
if action == Actions.Neutral.value and self._position == Positions.Neutral:
return -1
max_trade_duration = self.rl_config.get('max_trade_duration_candles', 300)
trade_duration = self._current_tick - self._last_trade_tick
if trade_duration <= max_trade_duration:
factor *= 1.5
elif trade_duration > max_trade_duration:
factor *= 0.5
# discourage sitting in position
if self._position in (Positions.Short, Positions.Long) and \
action == Actions.Neutral.value:
return -1 * trade_duration / max_trade_duration
# close long
if action == Actions.Long_exit.value and self._position == Positions.Long:
if pnl > self.profit_aim * self.rr:
factor *= self.rl_config['model_reward_parameters'].get('win_reward_factor', 2)
return float(pnl * factor)
# close short
if action == Actions.Short_exit.value and self._position == Positions.Short:
if pnl > self.profit_aim * self.rr:
factor *= self.rl_config['model_reward_parameters'].get('win_reward_factor', 2)
return float(pnl * factor)
return 0.
使用 Tensorboard¶
强化学习模型需要跟踪训练过程中的各项指标。FreqAI 已集成 Tensorboard,允许用户跨所有币种及多次重训练持续追踪训练和评估性能。可通过以下命令启用 Tensorboard:
tensorboard --logdir user_data/models/unique-id
其中 unique-id 是在 freqai 配置文件中设置的 identifier。该命令必须在单独的 shell 中运行,以便在浏览器中访问 127.0.0.1:6006 查看输出(6006 是 Tensorboard 使用的默认端口)。

自定义日志记录¶
FreqAI 还提供了一个内置的回合制摘要记录器,名为 self.tensorboard_log,用于向 Tensorboard 日志添加自定义信息。默认情况下,该函数已在环境中的每一步调用一次,以记录代理的动作。在单个回合中所有步骤累积的所有值将在该回合结束时统一报告,随后所有指标将重置为 0,为下一个回合做好准备。
self.tensorboard_log 也可以在环境内的任意位置使用,例如,可以将其添加到 calculate_reward 函数中,以收集有关奖励函数中各个部分被调用频率的更详细信息:
class MyRLEnv(Base5ActionRLEnv):
"""
User made custom environment. This class inherits from BaseEnvironment and gym.Env.
Users can override any functions from those parent classes. Here is an example
of a user customized `calculate_reward()` function.
"""
def calculate_reward(self, action: int) -> float:
if not self._is_valid(action):
self.tensorboard_log("invalid")
return -2
注意
self.tensorboard_log() 函数仅用于跟踪递增对象,即训练环境中的事件或动作。如果关注的事件是一个浮点数,则可将该浮点数作为第二个参数传入,例如 self.tensorboard_log("float_metric1", 0.23)。在这种情况下,指标值不会累加。
选择基础环境¶
FreqAI 提供了三种基础环境:Base3ActionRLEnvironment、Base4ActionEnvironment 和 Base5ActionEnvironment。顾名思义,这些环境分别适用于可以选择 3 种、4 种或 5 种动作的代理。Base3ActionEnvironment 最为简单,代理可选择持有、做多或做空。此环境也可用于仅做多的机器人(它会自动遵循策略中的 can_short 标志),其中做多表示入场条件,做空表示出场条件。而在 Base4ActionEnvironment 中,代理可以选择做多入场、做空入场、保持空仓或平仓。最后,在 Base5ActionEnvironment 中,代理具备与 Base4 相同的操作,但不同于单一的平仓操作,它将平多头和平空头分开。选择不同环境带来的主要变化包括:
- 在
calculate_reward中可用的动作 - 用户策略所消费的动作
所有 FreqAI 提供的环境都继承自一个与动作/持仓无关的基础环境对象 BaseEnvironment,该对象包含所有共享逻辑。整个架构设计为易于定制。最简单的定制方式是重写 calculate_reward()(详见此处)。然而,定制还可以进一步扩展到环境内的任意函数。你可以通过在预测模型文件中的 MyRLEnv 内直接重写这些函数来实现。对于更高级的定制,建议创建一个全新并继承自 BaseEnvironment 的环境。
注意
只有 Base3ActionRLEnv 支持仅做多的训练/交易(需设置用户策略属性 can_short = False)。