配置¶
FreqAI可以通过典型的Freqtrade配置文件和标准的Freqtrade策略进行配置。FreqAI的配置文件示例和策略文件示例可以在config_examples/config_freqai.example.json
和freqtrade/templates/FreqaiExampleStrategy.py
找到。
设置配置文件¶
虽然还有很多其他参数可以选择,如参数表中所示,但是FreqAI配置文件至少必须包含以下参数(示例中的参数值仅供参考):
"freqai": {
"enabled": true,
"purge_old_models": 2,
"train_period_days": 30,
"backtest_period_days": 7,
"identifier" : "unique-id",
"feature_parameters" : {
"include_timeframes": ["5m","15m","4h"],
"include_corr_pairlist": [
"ETH/USD",
"LINK/USD",
"BNB/USD"
],
"label_period_candles": 24,
"include_shifted_candles": 2,
"indicator_periods_candles": [10, 20]
},
"data_split_parameters" : {
"test_size": 0.25
}
}
可以在config_examples/config_freqai.example.json
中找到完整的配置示例。
构建一个 FreqAI 策略¶
在标准的Freqtrade策略中,需要包含以下代码行:
# 用户应定义最大启动蜡烛计数(传递给任何单个指标的最大蜡烛数)
startup_candle_count: int = 20
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# 模型将会返回用户在`set_freqai_targets()`中创建的所有标签,
# (& 添加的目标),一个指示是否接受预测的指示,
# 对于`set_freqai_targets()`中用户创建的每个标签,每个训练周期都有目标的平均/标准值。```python
dataframe = self.freqai.start(dataframe, metadata, self)
return dataframe
def feature_engineering_expand_all(self, dataframe: DataFrame, period, **kwargs) -> DataFrame:
"""
*Only valid for strategies enabled with FreqAI*
This function automatically expands the features defined in the configuration, including `indicator_periods_candles`,
`include_timeframes`, `include_shifted_candles`, and `include_corr_pairs`.
In other words, a single feature defined in this function will automatically be expanded to `indicator_periods_candles`
* `include_timeframes` * `include_shifted_candles` * `include_corr_pairs` features added to the model.
All features must start with `%` in order to be recognized internally by FreqAI.
:param df: The strategy dataframe that will receive the features
:param period: The period of the indicator - Usage example:
dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period)
"""
dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period)
dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period)
dataframe["%-adx-period"] = ta.ADX(dataframe, timeperiod=period)
dataframe["%-sma-period"] = ta.SMA(dataframe, timeperiod=period)
dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period)
return dataframe
def feature_engineering_expand_basic(self, dataframe: DataFrame, **kwargs) -> DataFrame:
"""
*Only functional with FreqAI enabled strategies*
This function will automatically expand the defined features on the config defined
`include_timeframes`, `include_shifted_candles`, and `include_corr_pairs`.
In other words, a single feature defined in this function
will automatically expand to a total of
`include_timeframes` * `include_shifted_candles` * `include_corr_pairs`
numbers of features added to the model.
Features defined here will *not* be automatically duplicated on user defined
`indicator_periods_candles`
All features must be prepended with `%` to be recognized by FreqAI internals.
:param df: strategy dataframe which will receive the features
dataframe["%-pct-change"] = dataframe["close"].pct_change()
dataframe["%-ema-200"] = ta.EMA(dataframe, timeperiod=200)
"""
dataframe["%-pct-change"] = dataframe["close"].pct_change()
dataframe["%-raw_volume"] = dataframe["volume"]
dataframe["%-raw_price"] = dataframe["close"]
return dataframe
def feature_engineering_standard(self, dataframe: DataFrame, **kwargs) -> DataFrame:
"""
*只能在启用FreqAI的策略下使用*
此可选函数将使用基本时间框架的数据框调用一次。
这是最后一个要调用的函数,这意味着进入此函数的数据框包含所有其他freqai_feature_engineering_*函数创建的特征和列。
这个函数是进行自定义的非凡特征提取的好地方(例如tsfresh)。
这个函数适合放置任何不应该被自动扩展的特征(例如星期几)。
所有特征必须以“%”作为前缀,以便被FreqAI内部识别。
:param df: 策略数据框,将接收特征
usage example: dataframe["%-day_of_week"] = (dataframe["date"].dt.dayofweek + 1) / 7
"""
dataframe["%-day_of_week"] = (dataframe["date"].dt.dayofweek + 1) / 7
dataframe["%-hour_of_day"] = (dataframe["date"].dt.hour + 1) / 25
return dataframe
def set_freqai_targets(self, dataframe: DataFrame, **kwargs) -> DataFrame:
"""
*只能在启用FreqAI的策略下使用*
设置模型的目标所需的函数。
所有目标必须以“&”作为前缀,以便被FreqAI内部识别。
:param df: 策略数据框,将接收目标
usage example: dataframe["&-target"] = dataframe["close"].shift(-1) / dataframe["close"]
"""
dataframe["&-s_close"] = (
dataframe["close"]
.shift(-self.freqai_info["feature_parameters"]["label_period_candles"])
.rolling(self.freqai_info["feature_parameters"]["label_period_candles"])
.mean()
/ dataframe["close"]
- 1
)
return dataframe
Notice how the feature_engineering_*()
is where features are added. Meanwhile set_freqai_targets()
adds the labels/targets. A full example strategy is available in templates/FreqaiExampleStrategy.py
.
Note
The self.freqai.start()
function cannot be called outside the populate_indicators()
.
Note
Features must be defined in feature_engineering_*()
. Defining FreqAI features in populate_indicators()
will cause the algorithm to fail in live/dry mode. In order to add generalized features that are not associated with a specific pair or timeframe, you should use feature_engineering_standard()
(as exemplified in freqtrade/templates/FreqaiExampleStrategy.py
).
重要的数据框键模式¶
以下是您可以期望在典型策略数据框(df[]
)中包含/使用的值:
数据框键 | 描述 |
---|---|
df['&*'] |
set_freqai_targets() 中以& 开头的任何数据框列都被视为FreqAI中的训练目标(标签)。通常遵循命名约定&-s* 。例如,要预测未来40个蜡烛周期内的收盘价,可以设置df['&-s_close'] = df['close'].shift(-self.freqai_info["feature_parameters"]["label_period_candles"]) ,其中配置中的"label_period_candles": 40 。FreqAI进行预测,并将其以相同的键(df['&-s_close'] )返回以在populate_entry/exit_trend() 中使用。 数据类型: 取决于模型的输出。 |
df['&*_std/mean'] |
训练期间(或使用fit_live_predictions_candles 进行实时跟踪)定义的标签的标准偏差和均值。通常用于了解预测的稀有性(使用templates/FreqaiExampleStrategy.py 中显示的z-score,并在这里解释如何评估在训练期间或使用fit_live_predictions_candles 进行的历史统计中观察到特定预测的频率)。数据类型: 浮点数。 |
df['do_predict'] |
异常数据点的指示。返回值是-2到2之间的整数,可告知您预测是否可信。do_predict==1 表示预测可信。如果输入数据点的差异性指数(DI,请参阅这里)超过配置中定义的阈值,则FreqAI将从do_predict 中减去1,结果为do_predict==0 。如果use_SVM_to_remove_outliers 处于激活状态,支持向量机(SVM,请参阅这里)还可以检测训练和预测数据中的异常值。在这种情况下,SVM也会从do_predict 中减去1。如果输入数据点被SVM视为异常值,但DI未将其视为异常值,或者反之,结果将为do_predict==0 。如果DI和SVM都将输入数据点视为异常值,则结果为do_predict==-1 。与SVM类似,如果use_DBSCAN_to_remove_outliers 处于激活状态,DBSCAN(请参阅这里)也可以检测异常值并从do_predict 中减去1。因此,如果SVM和DBSCAN都处于激活状态并且识别出一个超过DI阈值的数据点为异常值,则结果为do_predict==-2 。一个特例是do_predict == 2 ,表示模型已过期,因为超过了expired_hours 的阈值。数据类型: -2到2之间的整数。 |
df['DI_values'] |
差异性指数(DI)值是FreqAI对预测的信心水平的代理。较低的DI意味着预测接近训练数据,即预测的置信度较高。有关DI的详细信息,请参阅这里。 数据类型: 浮点数。 |
df['%*'] |
feature_engineering_*() 中以% 开头的任何数据框列都被视为训练特征。例如,您可以将RSI包含在训练特征集中(与templates/FreqaiExampleStrategy.py 中类似)。将rsi 列添加到数据框中的方法是df['%-rsi'] 。有关详细信息,请参阅此处。注意:由于以 % 开头的特征的数量可能会快速增加(使用乘法功能,例如include_shifted_candles 和include_timeframes 作为frequency-table.md中所描述的方式易于创建成千上万个特征),这些特征从FreqAI返回给策略的数据框中移除。要保留特定类型的特征以用于绘图目的,可以在其前面加上%% (详细信息请参见下文)。数据类型: 取决于用户创建的特征。 |
df['%%*'] |
feature_engineering_*() 中以%% 开头的任何数据框列都被视为训练特征,与上述以% 开头的情况相同。但是,在这种情况下,特征将返回给策略,用于FreqUI/plot-dataframe的绘图和Dry/Live/Backtesting的监视。数据类型: 取决于用户创建的特征。请注意,使用 feature_engineering_expand() 创建的特征将根据您配置的扩展(例如include_timeframes 、include_corr_pairlist 、indicators_periods_candles 、include_shifted_candles )具有自动的FreqAI命名模式。因此,如果要绘制feature_engineering_expand_all() 中的%%-rsi ,则绘图配置的最终命名方案将是:%%-rsi-period_10_ETH/USDT:USDT_1h ,用于rsi 特征,其中period=10 ,timeframe=1h ,pair=ETH/USDT:USDT (如果使用期货交易对,则添加了:USDT )。在populate_indicators() 后的self.freqai.start() 之后添加print(dataframe.columns) 可查看返回给策略以供绘图目的的所有可用特征的完整列表。 |
设置startup_candle_count
¶
请按照与标准Freqtrade策略相同的方式设置FreqAI策略中的startup_candle_count
(请参阅此处了解详细信息)。该值由Freqtrade用于在调用dataprovider
时确保提供足够的数据,以避免在第一次训练的开始部分出现NaN值。您可以通过识别传递给指标创建函数(例如TA-Lib函数)的最长周期(以蜡烛单位)来轻松设置此值。在所示示例中,startup_candle_count
为20,因为这是indicators_periods_candles
中的最大值。
注意
有时,TA-Lib函数实际上需要比传递的period
更多的数据,否则特征数据集将填充为NaN值。据经验,将预期的startup_candle_count
乘以2总是导致全面没有NaN的训练数据集。因此,通常最安全的做法是将预期的startup_candle_count
乘以2。请注意查看此日志消息以确认数据是否干净:
2022-08-31 15:14:04 - freqtrade.freqai.data_kitchen - INFO - dropped 0 training points due to NaNs in populated dataset 4319.
创建动态目标阈值¶
可以以动态方式决定何时进入或退出交易,以反映当前的市场情况。FreqAI允许您从模型的训练中返回附加信息(更多信息请参阅这里)。例如,&*_std/mean
返回值描述了最近训练期间目标/标签的统计分布。将给定的预测与这些值进行比较,可以了解预测的稀有程度。在templates/FreqaiExampleStrategy.py
中,target_roi
和sell_roi
被定义为距离均值1.25个z-score的距离,这导致较接近均值的预测被过滤掉。```python
dataframe["target_roi"] = dataframe["&-s_close_mean"] + dataframe["&-s_close_std"] * 1.25
dataframe["sell_roi"] = dataframe["&-s_close_mean"] - dataframe["&-s_close_std"] * 1.25
为了考虑 *历史预测* 的人口来创建动态目标,而不是如上所讨论的使用训练信息,您可以将配置中的 `fit_live_predictions_candles` 设置为您希望用于生成目标统计信息的历史预测蜡烛的数量。```json
"freqai": {
"fit_live_predictions_candles": 300,
}
If this value is set, FreqAI will first use the predicted values from the training data and then start incorporating actual prediction data. If you stop and restart the model with the same identifier
, FreqAI will save this historical data for reloading.
Using Different Prediction Models¶
FreqAI comes with several example prediction model libraries that can be used directly with the --freqaimodel
flag. These libraries include regression models, classification models, and multi-target models for CatBoost
, LightGBM
, and XGBoost
, which can be found in freqai/prediction_models/
.
Regression models and classification models differ in the targets they predict - regression models predict continuous value targets, such as the price of BTC tomorrow, while classification models predict discrete value targets, such as whether the price of BTC will increase tomorrow. This means that you have to specify your target differently depending on the type of model you are using (see details below).
All of the mentioned model libraries implement the gradient boosting decision tree algorithm. They are all based on the principle of ensemble learning, where multiple predictions from simple learners are combined to obtain a more stable and generalized final prediction result. The simple learners used here are decision trees. Gradient boosting refers to the learning method in which each simple learner is sequentially built - the next learner is used to improve the mistakes of the previous learners. If you want more information about the different model libraries, please refer to their respective documentation:
- CatBoost: https://catboost.ai/en/docs/
- LightGBM: https://lightgbm.readthedocs.io/en/v3.3.2/#
- XGBoost: https://xgboost.readthedocs.io/en/stable/
There are also many online articles that describe and compare these algorithms. Some relatively lightweight examples include CatBoost vs. LightGBM vs. XGBoost — Which is the best algorithm? and XGBoost, LightGBM or CatBoost — which boosting algorithm should I use?. Keep in mind that the performance of each model heavily depends on the application, so any reported metrics may not apply to your specific use case of the model. In addition to the models already provided in FreqAI, you can also customize and create your own prediction models using the IFreqaiModel
class. You can customize various aspects of the training process by inheriting fit()
, train()
, and predict()
. You can place your custom FreqAI models in the user_data/freqaimodels
directory, and freqtrade will find them in this directory based on the provided --freqaimodel
name, which must correspond to the class name of your custom model. Make sure to use a unique name to avoid overwriting built-in models.
Setting the Model Targets¶
Regressors¶
如果您使用回归器,需要指定目标为连续值。FreqAI中包含各种回归器,例如使用--freqaimodel CatboostRegressor
可以使用CatboostRegressor
。下面是一个示例,展示如何为预测未来100个蜡烛价格设置回归目标的代码:
df['&s-close_price'] = df['close'].shift(-100)
如果您想预测多个目标,可以使用与上述示例相同的语法定义多个标签。
分类器¶
如果您使用分类器,需要指定目标为离散值。FreqAI中包含各种分类器,例如使用--freqaimodel CatboostClassifier
可以使用CatboostClassifier
。如果选择使用分类器,类别必须使用字符串来表示。例如,如果要预测未来100个蜡烛价格是涨还是跌,可以使用以下代码进行设置:
df['&s-up_or_down'] = np.where(df["close"].shift(-100) > df["close"], 'up', 'down')
如果要预测多个目标,您必须在同一标签列中指定所有标签。可以通过添加标签same
来表示价格没有变化的情况。
df['&s-up_or_down'] = np.where(df["close"].shift(-100) > df["close"], '上涨', '下跌')
df['&s-up_or_down'] = np.where(df["close"].shift(-100) == df["close"], '相同', df['&s-up_or_down'])
PyTorch 模块¶
快速入门¶
运行 PyTorch 模型最简单的方法是使用以下命令(用于回归任务):
freqtrade trade --config config_examples/config_freqai.example.json --strategy FreqaiExampleStrategy --freqaimodel PyTorchMLPRegressor --strategy-path freqtrade/templates
安装/容器化
PyTorch 模块需要 torch
等大型包,在 ./setup.sh -i
运行期间应该明确地请求这些包,方法是在问题 "Do you also want dependencies for freqai-rl or PyTorch (~700mb additional space required) [y/N]?" 中回答 "y"。
偏向于使用容器的用户应该确保使用末尾带 _freqaitorch
的 docker 镜像。
我们在 docker/docker-compose-freqai.yml
中提供了一个专门的 docker-compose 文件,可以通过 docker compose -f docker/docker-compose-freqai.yml run ...
来使用,或者可以复制覆盖原始的 docker 文件。
这个 docker-compose 文件还包含一个(已禁用)部分,用于在 docker 容器中启用 GPU 资源。当然,这假设系统有可用的 GPU 资源。
结构¶
模型¶
您可以通过在自定义的 IFreqaiModel
文件 中定义自己的 PyTorch 神经网络架构,并在 def train()
函数中使用该类来构建自己的 PyTorch 模型。下面是一个使用 PyTorch 实现的逻辑回归模型示例(应该与 nn.BCELoss 准则一起用于分类任务)。
class LogisticRegression(nn.Module):
def __init__(self, input_size: int):
super().__init__()
# Define your layers
self.linear = nn.Linear(input_size, 1)
self.activation = nn.Sigmoid()
def forward(self, x: torch.Tensor) -> torch.Tensor:
# Define the forward pass
out = self.linear(x)
out = self.activation(out)
return out
class MyCoolPyTorchClassifier(BasePyTorchClassifier):
"""
This is a custom IFreqaiModel that shows how users can define their own custom neural network architecture for their training setups.
"""
@property
def data_convertor(self) -> PyTorchDataConvertor:
return DefaultPyTorchDataConvertor(target_tensor_type=torch.float)
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
config = self.freqai_info.get("model_training_parameters", {})
self.learning_rate: float = config.get("learning_rate", 3e-4)
self.model_kwargs: Dict[str, Any] = config.get("model_kwargs", {})
self.trainer_kwargs: Dict[str, Any] = config.get("trainer_kwargs", {})
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
用户在这里设置他们所需的模型的训练和测试数据
:param data_dictionary: 包含所有训练、测试数据、标签和权重的字典
:param dk: 当前币种/模型的 DataKitchen 对象
"""
class_names = self.get_class_names()
self.convert_label_column_to_int(data_dictionary, dk, class_names)
n_features = data_dictionary["train_features"].shape[-1]
model = LogisticRegression(
input_dim=n_features
)
model.to(self.device)
optimizer = torch.optim.AdamW(model.parameters(), lr=self.learning_rate)
criterion = torch.nn.CrossEntropyLoss()
init_model = self.get_init_model(dk.pair)
trainer = PyTorchModelTrainer(
model=model,
optimizer=optimizer,
criterion=criterion,
model_meta_data={"class_names": class_names},
device=self.device,
init_model=init_model,
data_convertor=self.data_convertor,
**self.trainer_kwargs,
)
trainer.fit(data_dictionary, self.splits)
return trainer
训练器¶
PyTorchModelTrainer
执行惯用的 PyTorch 训练循环:定义模型、损失函数和优化器,并将它们移动到适当的设备(GPU 或 CPU)。循环内部,我们遍历数据加载器中的批次,将数据移动到设备上,计算预测和损失,进行反向传播,并使用优化器更新模型参数。此外,训练器负责以下任务:
- 保存和加载模型
- 将数据从 pandas.DataFrame
转换为 torch.Tensor
。
与 Freqai 模块的集成¶
像所有的 freqai 模型一样,PyTorch 模型也继承了 IFreqaiModel
接口。IFreqaiModel
声明了三个抽象方法:train
、fit
和 predict
。我们在三个层次的层级结构中实现了这些方法。从上到下:
BasePyTorchModel
- 实现了train
方法,所有的BasePyTorch*
类都继承它,负责通用的数据准备(例如数据归一化)和调用fit
方法。设置了子类使用的device
属性。设置了父类使用的model_type
属性。BasePyTorch*
- 实现了predict
方法。这里的*
表示一组算法,例如分类器或回归器。负责数据预处理、预测以及必要的后处理。PyTorch*Classifier
/PyTorch*Regressor
- 实现了fit
方法。负责主要的训练流程,其中我们初始化了训练器和模型对象。
完整示例¶
使用 MLP(多层感知机)模型、MSELoss 损失函数和 AdamW 优化器构建一个 PyTorch 回归器。
class PyTorchMLPRegressor(BasePyTorchRegressor):
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
config = self.freqai_info.get("model_training_parameters", {})
self.learning_rate: float = config.get("learning_rate", 3e-4)
self.model_kwargs: Dict[str, Any] = config.get("model_kwargs", {})
self.trainer_kwargs: Dict[str, Any] = config.get("trainer_kwargs", {})
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
n_features = data_dictionary["train_features"].shape[-1]
model = PyTorchMLPModel(
input_dim=n_features,
output_dim=1,
**self.model_kwargs
)
model.to(self.device)
optimizer = torch.optim.AdamW(model.parameters(), lr=self.learning_rate)
criterion = torch.nn.MSELoss()
init_model = self.get_init_model(dk.pair)
trainer = PyTorchModelTrainer(
model=model,
optimizer=optimizer,
criterion=criterion,
device=self.device,
init_model=init_model,
target_tensor_type=torch.float,
**self.trainer_kwargs,
)
trainer.fit(data_dictionary)
return trainer
在这里,我们创建了一个 PyTorchMLPRegressor
类,它实现了 fit
方法。fit
方法指定了训练的构建模块:模型、优化器、损失函数和训练器。我们同时继承了 BasePyTorchRegressor
和 BasePyTorchModel
,前者实现了适合我们回归任务的 predict
方法,后者实现了训练方法。
??? 注意"为分类器设置类名"的部分。
在使用分类器时,用户必须通过覆盖 IFreqaiModel.class_names
属性来声明类名(或目标)。这可以通过在 FreqAI 策略中的 set_freqai_targets
方法中设置 self.freqai.class_names
来实现。
例如,如果您使用二元分类器来预测价格的上涨或下跌,可以按如下设置类名:
```python
def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
self.freqai.class_names = ["下跌", "上涨"]
dataframe['&s-up_or_down'] = np.where(dataframe["收盘价"].shift(-100) >
dataframe["收盘价"], '上涨', '下跌')
return dataframe
```
若要查看完整示例,可以参考[分类器测试策略类](https://github.com/freqtrade/freqtrade/blob/develop/tests/strategy/strats/freqai_test_classifier.py)。
使用 torch.compile()
提升性能¶
Torch 提供了 torch.compile()
方法,可以用于提升特定 GPU 硬件的性能。更多详细信息可以在这里找到。简单来说,您只需将您的 model
包装在 torch.compile()
中:
model = PyTorchMLPModel(
input_dim=n_features,
output_dim=1,
**self.model_kwargs
)
model.to(self.device)
model = torch.compile(model)
然后继续像平常一样使用模型。请注意,这样做会移除即时执行,这意味着错误和回溯将不会提供详细信息。