配置¶
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 中找到。
注意
identifier 常被新手忽略,但该值在配置中起着重要作用。此值是你自定义的唯一标识符,用于描述某次运行。保持该值不变有助于维持崩溃恢复能力并加快回测速度。一旦你想尝试新的运行(例如新特征、新模型等),就应该更改此值(或删除 user_data/models/unique-id 文件夹)。更多细节请参见参数表。
构建 FreqAI 策略¶
FreqAI 策略需要在标准的 Freqtrade 策略 中包含以下代码行:
# user should define the maximum startup candle count (the largest number of candles
# passed to any single indicator)
startup_candle_count: int = 20
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# the model will return all labels created by user in `set_freqai_targets()`
# (& appended targets), an indication of whether or not the prediction should be accepted,
# the target mean/std values for each of the labels created by user in
# `set_freqai_targets()` for each training period.
dataframe = self.freqai.start(dataframe, metadata, self)
return dataframe
def feature_engineering_expand_all(self, dataframe: DataFrame, period, **kwargs) -> DataFrame:
"""
*Only functional with FreqAI enabled strategies*
This function will automatically expand the defined features on the config defined
`indicator_periods_candles`, `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
`indicator_periods_candles` * `include_timeframes` * `include_shifted_candles` *
`include_corr_pairs` numbers of features added to the model.
All features must be prepended with `%` to be recognized by FreqAI internals.
:param df: strategy dataframe which will receive the features
:param period: 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:
"""
*Only functional with FreqAI enabled strategies*
This optional function will be called once with the dataframe of the base timeframe.
This is the final function to be called, which means that the dataframe entering this
function will contain all the features and columns created by all other
freqai_feature_engineering_* functions.
This function is a good place to do custom exotic feature extractions (e.g. tsfresh).
This function is a good place for any feature that should not be auto-expanded upon
(e.g. day of the week).
All features must be prepended with `%` to be recognized by FreqAI internals.
:param df: strategy dataframe which will receive the features
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:
"""
*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.
:param df: strategy dataframe which will receive the targets
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
请注意,feature_engineering_*() 是添加特征的地方,而 set_freqai_targets() 则用于添加标签/目标。完整策略示例可在 templates/FreqaiExampleStrategy.py 中找到。
注意
self.freqai.start() 函数不能在 populate_indicators() 之外调用。
注意
特征必须在 feature_engineering_*() 中定义。在 populate_indicators() 中定义 FreqAI 特征将导致算法在实盘/模拟模式下失败。若要添加与特定交易对或时间周期无关的通用特征,应使用 feature_engineering_standard()(参见 freqtrade/templates/FreqaiExampleStrategy.py 中的示例)。
重要的数据框键名模式¶
以下是典型策略数据框(df[])中可能包含或使用的值:
| DataFrame 键 | 描述 |
|---|---|
df['&*'] |
在 set_freqai_targets() 中以 & 开头的任何 dataframe 列都会被 FreqAI 视为训练目标(标签)(通常遵循命名约定 &-s*)。例如,若要预测未来 40 根 K 线的收盘价,您可以在配置中设置 "label_period_candles": 40,并在代码中使用 df['&-s_close'] = df['close'].shift(-self.freqai_info["feature_parameters"]["label_period_candles"])。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_*() 中,任何以 % 开头的 dataframe 列都会被视为训练特征。例如,你可以通过设置 df['%-rsi'] 将 RSI 加入训练特征集(类似于 templates/FreqaiExampleStrategy.py 中的做法)。有关具体实现方式的更多细节,请参见此处。注意: 由于以 % 开头的特征数量可能迅速增长(例如,通过参数表中描述的 include_shifted_candles 和 include_timeframes 的乘法功能,很容易生成数万个特征),这些特征在 FreqAI 返回给策略的 dataframe 中会被移除。若要保留某种特征用于绘图,可使用 %% 作为前缀(详见下文)。数据类型: 取决于用户创建的特征。 |
df['%%*'] |
在 feature_engineering_*() 中,任何以 %% 开头的 dataframe 列同样会被视为训练特征,与上述 % 前缀一致。但不同的是,这些特征会被返回给策略,以便在 Dry/Live/Backtesting 模式下通过 FreqUI 或 plot-dataframe 进行绘图和监控。数据类型: 取决于用户创建的特征。请注意,在 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,表示周期 period=10、时间框架 timeframe=1h、交易对 pair=ETH/USDT:USDT(若使用期货交易对,则会添加 :USDT)。你可以在 populate_indicators() 中调用 self.freqai.start() 后添加 print(dataframe.columns),以查看返回给策略用于绘图的所有可用特征的完整列表。 |
设置 startup_candle_count¶
FreqAI 策略中的 startup_candle_count 需要像标准 Freqtrade 策略一样进行设置(详见此处)。Freqtrade 使用该值来确保调用 dataprovider 时提供足够的数据,从而避免首次训练开始时出现 NaN 值。你可以通过识别传递给指标创建函数(例如 TA-Lib 函数)的最大周期(以 K 线数量为单位)来轻松设置此值。在本示例中,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)的位置,这会导致靠近均值的预测被过滤掉。
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 设置为你希望用于生成目标统计数据的历史预测蜡烛图数量。
"freqai": {
"fit_live_predictions_candles": 300,
}
如果设置了此值,FreqAI 最初会使用训练数据中的预测结果,并随后逐步引入实际生成的预测数据。FreqAI 会保存这些历史数据,以便在你停止并使用相同 identifier 重启模型时重新加载。
使用不同的预测模型¶
FreqAI 提供了多个可直接使用的示例预测模型库,可通过 --freqaimodel 参数启用。这些库包括 CatBoost、LightGBM 和 XGBoost 的回归、分类及多目标模型,位于 freqai/prediction_models/ 目录下。
回归模型和分类模型的区别在于它们所预测的目标类型——回归模型预测的是连续值目标,例如 BTC 明天的价格;而分类模型预测的是离散值目标,例如 BTC 明天价格是否会上涨。这意味着你需要根据所使用的模型类型不同,相应地设定你的目标(详见下方说明)。
上述所有模型库都实现了梯度提升决策树算法。它们的工作原理基于集成学习,即将多个简单学习器的预测结果组合起来,得到更稳定且泛化能力更强的最终预测结果。这里的简单学习器指的是决策树。梯度提升指的是这样一种学习方法:每个简单学习器依次构建,后一个学习器用于改进前一个学习器产生的误差。如果你想了解更多关于不同模型库的信息,可以在它们各自的文档中找到相关内容:
- CatBoost: https://catboost.ai/en/docs/
- LightGBM: https://lightgbm.readthedocs.io/en/v3.3.2/#
- XGBoost: https://xgboost.readthedocs.io/en/stable/#
此外,还有大量在线文章对这些算法进行了描述和比较。一些较为通俗易懂的例子包括:CatBoost vs. LightGBM vs. XGBoost — 哪个是最好的算法? 和 XGBoost、LightGBM 或 CatBoost — 我该选择哪种提升算法?。需要注意的是,每种模型的表现高度依赖于具体应用场景,因此任何报告中的性能指标可能并不适用于你对该模型的具体使用情况。
除了 FreqAI 中已有的模型外,您还可以通过继承 IFreqaiModel 类来自定义并创建自己的预测模型。建议重写 fit()、train() 和 predict() 方法,以自定义训练过程的各个方面。您可以将自定义的 FreqAI 模型放在 user_data/freqaimodels 目录中,freqtrade 将根据提供的 --freqaimodel 名称自动加载这些模型,该名称必须与您的自定义模型类名一致。请确保使用唯一的名称,以避免覆盖内置模型。
设置模型目标¶
回归器¶
如果您使用的是回归器,则需要指定具有连续值的目标。FreqAI 包含多种回归器,例如通过 --freqaimodel CatboostRegressor 参数启用的 CatboostRegressor。如果您希望设置一个回归目标来预测未来 100 根 K 线时的价格,示例如下:
df['&s-close_price'] = df['close'].shift(-100)
如果您想预测多个目标,则需要使用与上述相同的语法定义多个标签。
分类器¶
如果您使用的是分类器,则需要指定具有离散值的目标。FreqAI 包含多种分类器,例如通过 --freqaimodel CatboostClassifier 参数启用的 CatboostClassifier。如果选择使用分类器,则类别必须使用字符串形式设定。例如,如果您想预测未来 100 根 K 线时价格是上涨还是下跌,可以这样设置:
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"], 'up', 'down')
df['&s-up_or_down'] = np.where( df["close"].shift(-100) == df["close"], 'same', 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"。偏好使用 Docker 的用户应确保使用带有 _freqaitorch 后缀的镜像。我们为此提供了专用的 docker-compose 文件 docker/docker-compose-freqai.yml,可通过 docker compose -f docker/docker-compose-freqai.yml run ... 使用,也可复制并替换原始 docker 文件。该 docker-compose 文件还包含一个(默认禁用的)配置段,用于在 Docker 容器中启用 GPU 资源,当然这要求系统具备可用的 GPU 资源。
PyTorch 自版本 2.3 起已停止支持 macOS x64(基于 Intel 的 Apple 设备),因此 freqtrade 也相应地停止了在此平台上的 PyTorch 支持。
结构¶
模型¶
您可以在自定义的 IFreqaiModel 文件中定义自己的 nn.Module 类,从而在 PyTorch 中构建自定义神经网络架构,然后在 def train() 函数中使用该类。以下是一个使用 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 showing how a user might setup their own
custom Neural Network architecture for their training.
"""
@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:
"""
User sets up the training and test data to fit their desired model here
:param data_dictionary: the dictionary holding all data for train, test,
labels, weights
:param dk: The datakitchen object for the current coin/model
"""
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 方法,后者实现了 train 方法。
为分类器设置类别名称
使用分类器时,用户必须通过重写 IFreqaiModel.class_names 属性来声明类别名称(或目标)。这可以通过在 FreqAI 策略的 set_freqai_targets 方法中设置 self.freqai.class_names 来实现。
例如,如果你使用二分类器来预测价格上涨或下跌,可以按如下方式设置类别名称:
def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame: self.freqai.class_names = ["down", "up"] dataframe['&s-up_or_down'] = np.where(dataframe["close"].shift(-100) > dataframe["close"], 'up', 'down') return dataframe 使用 torch.compile() 提升性能¶
PyTorch 提供了 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)
然后像往常一样使用该模型。请注意,这样做会移除急切执行模式,这意味着错误和堆栈追踪将不再具有可读性。