Skip to content

特征工程 (Feature engineering)

定义特征 (Defining the features)

低级特征工程在用户策略的一组名为 feature_engineering_* 的函数中执行。这些函数设置“基础特征”,如 RSIMFIEMASMA、时刻、成交量等。基础特征可以是自定义指标,也可以从您能找到的任何技术分析库中导入。FreqAI 配备了一组函数来简化大规模快速特征工程:

函数描述
feature_engineering_expand_all()此可选函数将根据配置中定义的 indicator_periods_candlesinclude_timeframesinclude_shifted_candlesinclude_corr_pairs 自动扩展定义的特征。
feature_engineering_expand_basic()此可选函数将根据配置中定义的 include_timeframesinclude_shifted_candlesinclude_corr_pairs 自动扩展定义的特征。注意:此函数不会穿过 indicator_periods_candles 展开。
feature_engineering_standard()此可选函数将仅使用基础时间框架的数据帧调用一次。这是最后被调用的函数,这意味着进入该函数的数据帧将包含由其他 feature_engineering_expand 函数创建的基础指标的所有特征和列。此函数是进行自定义异域特征提取(例如 tsfresh)的好地方。此函数也是编写不应被自动扩展的特征(例如星期几)的好地方。
set_freqai_targets()用于设置模型目标的必需函数。所有目标必须以 & 开头,以便被 FreqAI 内部识别。

同时,高级特征工程在 FreqAI 配置中的 "feature_parameters":{} 内处理。在此文件中,可以决定在基础特征之上的大规模特征扩展,例如“包含相关交易对”、“包含通知时间框架”或甚至“包含最近的 K 线”。

建议从提供的示例策略(位于 templates/FreqaiExampleStrategy.py)中的 feature_engineering_* 函数模板开始,以确保特征定义遵循正确的约定。以下是如何在策略中设置指标和标签的示例:

python
    def feature_engineering_expand_all(self, dataframe: DataFrame, period, metadata, **kwargs) -> DataFrame:
        """
        *仅在启用 FreqAI 的策略中有效*
        此函数将根据配置中定义的 `indicator_periods_candles`、`include_timeframes`、
        `include_shifted_candles` 和 `include_corr_pairs` 自动扩展定义的特征。
        换句话说,在该函数中定义的一个特征将自动扩展,并添加总计 
        `indicator_periods_candles` * `include_timeframes` * `include_shifted_candles` *
        `include_corr_pairs` 个特征到模型中。

        所有特征必须以 `%` 开头,以便被 FreqAI 内部识别。

        访问元数据,如当前交易对/时间框架/周期:
        `metadata["pair"]` `metadata["tf"]` `metadata["period"]`

        :param df: 将接收特征的策略数据帧
        :param period: 指标的周期 - 使用示例:
        :param metadata: 当前交易对的元数据
        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)

        bollinger = qtpylib.bollinger_bands(
            qtpylib.typical_price(dataframe), window=period, stds=2.2
        )
        dataframe["bb_lowerband-period"] = bollinger["lower"]
        dataframe["bb_middleband-period"] = bollinger["mid"]
        dataframe["bb_upperband-period"] = bollinger["upper"]

        # 布林带宽度
        dataframe["%-bb_width-period"] = (
            dataframe["bb_upperband-period"]
            - dataframe["bb_lowerband-period"]
        ) / dataframe["bb_middleband-period"]
        # 收盘价与下轨的比例
        dataframe["%-close-bb_lower-period"] = (
            dataframe["close"] / dataframe["bb_lowerband-period"]
        )

        dataframe["%-roc-period"] = ta.ROC(dataframe, timeperiod=period)

        # 相对成交量
        dataframe["%-relative_volume-period"] = (
            dataframe["volume"] / dataframe["volume"].rolling(period).mean()
        )

        return dataframe

    def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata, **kwargs) -> DataFrame:
        """
        *仅在启用 FreqAI 的策略中有效*
        此函数将根据配置中定义的 `include_timeframes`、`include_shifted_candles` 
        和 `include_corr_pairs` 自动扩展定义的特征。
        换句话说,在该函数中定义的一个特征将自动扩展,并添加总计
        `include_timeframes` * `include_shifted_candles` * `include_corr_pairs`
        个特征到模型中。

        这里定义的特征 *不会* 在用户定义的 `indicator_periods_candles` 上自动重复。

        访问元数据,如当前交易对/时间框架:
        `metadata["pair"]` `metadata["tf"]`

        所有特征必须以 `%` 开头,以便被 FreqAI 内部识别。

        :param df: 将接收特征的策略数据帧
        :param metadata: 当前交易对的元数据
        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, metadata, **kwargs) -> DataFrame:
        """
        *仅在启用 FreqAI 的策略中有效*
        此可选函数将仅使用基础时间框架的数据帧调用一次。
        这是最后被调用的函数,这意味着进入该函数的数据帧将包含由所有
        其他 freqai_feature_engineering_* 函数创建的所有特征和列。

        此函数是进行自定义异域特征提取(例如 tsfresh)的好地方。
        此函数也是编写不应被自动扩展的特征(例如星期几)的好地方。

        访问元数据,如当前交易对:
        `metadata["pair"]`

        所有特征必须以 `%` 开头,以便被 FreqAI 内部识别。

        :param df: 将接收特征的策略数据帧
        :param metadata: 当前交易对的元数据
        使用示例: 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, metadata, **kwargs) -> DataFrame:
        """
        *仅在启用 FreqAI 的策略中有效*
        用于设置模型目标的必需函数。
        所有目标必须以 `&` 开头,以便被 FreqAI 内部识别。

        访问元数据,如当前交易对:
        `metadata["pair"]`

        :param df: 将接收目标的策略数据帧
        :param metadata: 当前交易对的元数据
        使用示例: 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

在所示示例中,用户不希望将 bb_lowerband 作为特征传递给模型,因此没有在其前面加 %。然而,用户希望将 bb_width 传递给模型进行训练/预测,因此在其前面加了 %

定义好“基础特征”后,下一步是使用配置文件中强大的 feature_parameters 进行扩展:

json
    "freqai": {
        //...
        "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]
        },
        //...
    }

上述配置中的 include_timeframes 是策略中每次调用 feature_engineering_expand_*() 的时间框架 (tf)。在本例中,用户要求将 rsimfirocbb_width5m15m4h 时间框架包含在特征集中。

您还可以使用 include_corr_pairlist 要求将每个定义的特征也包含在通知交易对(informative pairs)中。这意味着特征集将包含来自配置中定义的每个相关交易对(本例中为 ETH/USDLINK/USDBNB/USD)的所有 include_timeframes 上的 feature_engineering_expand_*() 的所有特征。

include_shifted_candles 表示特征集中包含的之前 K 线的数量。例如,include_shifted_candles: 2 告诉 FreqAI 为特征集中的每个特征包含过去 2 根 K 线。

总计,所示示例策略的用户创建的特征数量为:include_timeframes 的长度 * feature_engineering_expand_*() 中的特征数量 * include_corr_pairlist 的长度 * include_shifted_candles 的数量 * indicator_periods_candles 的长度 $= 3 * 3 * 3 * 2 * 2 = 108$。

了解更多关于创意特征工程的信息

查看我们的 Medium 文章,旨在帮助用户学习如何有创意地设计特征。

通过 metadata 获得对 feature_engineering_* 函数的精细控制

所有 feature_engineering_*set_freqai_targets() 函数都会传递一个 metadata 字典,其中包含 FreqAI 自动化构建特征所需的 pair(交易对)、tf(时间框架)和 period(周期)信息。因此,用户可以在 feature_engineering_* 函数内部使用 metadata 作为标准来阻止/保留某些时间框架、周期、交易对等的特征。

python
def feature_engineering_expand_all(self, dataframe: DataFrame, period, metadata, **kwargs) -> DataFrame:
    if metadata["tf"] == "1h":
        dataframe["%-roc-period"] = ta.ROC(dataframe, timeperiod=period)

这将阻止 ta.ROC() 被添加到除 "1h" 之外的任何时间框架。

从培训中返回额外的信息

重要的指标可以在模型训练结束时返回给策略,方法是在自定义预测模型类中将它们分配给 dk.data['extra_returns_per_train']['my_new_value'] = XYZ

FreqAI 会获取该字典中分配的 my_new_value 并将其扩展以适应返回给策略的数据帧。然后,您可以通过 dataframe['my_new_value'] 在策略中使用返回的指标。FreqAI 中如何使用返回值的示例是 &*_mean&*_std 值,它们被用于 创建一个动态目标阈值

另一个示例是用户想要使用来自交易数据库的实时指标,如下所示:

json
    "freqai": {
        "extra_returns_per_train": {"total_profit": 4}
    }

您需要在配置中设置标准字典,以便 FreqAI 能够返回正确的数据帧形状。这些值可能会被预测模型覆盖,但在模型尚未设置它们或需要默认初始值的情况下,返回的是预设值。

对特征进行权重分配以通过时间重要性加成

FreqAI 允许您设置 weight_factor,通过指数函数赋予近期数据比过去数据更大的权重:

$$ W_i = \exp(\frac{-i}{\alpha*n}) $$

其中 $W_i$ 是包含 $n$ 个数据点的完整数据集中数据点 $i$ 的权重。下图显示了不同权重因子对特征集中数据点的影响。

weight-factor

构建数据管道 (Building the data pipeline)

默认情况下,FreqAI 根据用户配置设置构建动态管道。默认设置很稳健,旨在支持多种方法。这两个步骤是 MinMaxScaler(-1,1)VarianceThreshold(移除任何方差为 0 的列)。用户可以使用更多配置参数激活其他步骤。例如,如果用户在 freqai 配置中添加 use_SVM_to_remove_outliers: true,则 FreqAI 将自动向管道中添加 SVMOutlierExtractor。同样,用户可以在 freqai 配置中添加 principal_component_analysis: true 以激活 PCA。不相似度指数 (DI) 通过 DI_threshold: 1 激活。最后,还可以通过 noise_standard_deviation: 0.1 向数据中添加噪声。最后,用户可以通过 use_DBSCAN_to_remove_outliers: true 添加 DBSCAN 异常值移除。

更多信息可用

请查看 参数表 以获取有关这些参数的更多信息。

自定义管道

鼓励用户根据需要通过构建自己的数据管道来定制数据管道。这可以通过在他们的 IFreqaiModel train() 函数中简单地将 dk.feature_pipeline 设置为他们所需的 Pipeline 对象来实现,或者如果他们不想修改 train() 函数,他们可以在 IFreqaiModel 中重写 define_data_pipeline/define_label_pipeline 函数:

更多信息可用

FreqAI 使用的是 DataSieve 管道,它遵循 SKlearn 管道 API,但增加了(以及其他功能)X、y 和 sample_weight 向量点移除之间的一致性、特征移除、特征名称跟踪等。

python
from datasieve.transforms import SKLearnWrapper, DissimilarityIndex
from datasieve.pipeline import Pipeline
from sklearn.preprocessing import QuantileTransformer, StandardScaler
from freqai.base_models import BaseRegressionModel


class MyFreqaiModel(BaseRegressionModel):
    """
    一个很酷的自定义模型
    """
    def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
        """
        我的自定义 fit 函数
        """
        model = cool_model.fit()
        return model

    def define_data_pipeline(self) -> Pipeline:
        """
        用户在这里定义他们的自定义特征管道(如果愿意)
        """
        feature_pipeline = Pipeline([
            ('qt', SKLearnWrapper(QuantileTransformer(output_distribution='normal'))),
            ('di', ds.DissimilarityIndex(di_threshold=1))
        ])

        return feature_pipeline
    
    def define_label_pipeline(self) -> Pipeline:
        """
        用户在这里定义他们的自定义标签管道(如果愿意)
        """
        label_pipeline = Pipeline([
            ('qt', SKLearnWrapper(StandardScaler())),
        ])

        return label_pipeline

在这里,您定义了训练和预测期间将用于特征集的确切管道。您可以通过将其包装在 SKLearnWrapper 类中来使用 大部分 SKLearn 转换步骤,如上所示。此外,您可以使用 DataSieve 中可用的任何转换。

您可以轻松地通过创建一个继承自 datasieve BaseTransform 的类并实现您的 fit()transform()inverse_transform() 方法来添加您自己的转换:

python
from datasieve.transforms.base_transform import BaseTransform
# 导入您需要的任何其他东西

class MyCoolTransform(BaseTransform):
    def __init__(self, **kwargs):
        self.param1 = kwargs.get('param1', 1)

    def fit(self, X, y=None, sample_weight=None, feature_list=None, **kwargs):
        # 对 X, y, sample_weight, 或者/以及 feature_list 做些什么
        return X, y, sample_weight, feature_list

    def transform(self, X, y=None, sample_weight=None,
                  feature_list=None, outlier_check=False, **kwargs):
        # 对 X, y, sample_weight, 或者/以及 feature_list 做些什么
        return X, y, sample_weight, feature_list

    def inverse_transform(self, X, y=None, sample_weight=None, feature_list=None, **kwargs):
        # 做/不做些什么
        return X, y, sample_weight, feature_list

提示

您可以在与 IFreqaiModel 相同的文件中定义此自定义类。

将自定义 IFreqaiModel 迁移到新管道

如果您创建了自己的自定义 IFreqaiModel,带有自定义的 train()/predict() 函数,并且您仍然依赖 data_cleaning_train/predict(),那么您将需要迁移到新管道。如果您的模型 依赖 data_cleaning_train/predict(),那么您不需要担心这种迁移。

有关迁移的更多详细信息可以在 此处 找到。

异常值检测 (Outlier detection)

股票和加密货币市场饱受高度无规律噪声的影响,这些噪声表现为异常数据点。FreqAI 实施了多种方法来识别此类异常值,从而降低风险。

使用不相似度指数 (DI) 识别异常值

不相似度指数 (DI) 旨在量化模型所做的每个预测相关的不确定性。

您可以通过在配置中包含以下语句,告诉 FreqAI 使用 DI 从训练/测试数据集中移除异常数据点:

json
    "freqai": {
        "feature_parameters" : {
            "DI_threshold": 1
        }
    }

这将在您的 feature_pipeline 中添加 DissimilarityIndex 步骤,并将阈值设置为 1。DI 允许因确定性低而丢弃作为异常值(不存在于模型特征空间中)的预测。为此,FreqAI 衡量每个训练数据点(特征向量) $X_{a}$ 与所有其他训练数据点之间的距离:

$$ d_{ab} = \sqrt{\sum_{j=1}^p(X_{a,j}-X_{b,j})^2} $$

其中 $d_{ab}$ 是归一化点 $a$ 和 $b$ 之间的距离,$p$ 是特征数,即向量 $X$ 的长度。一组训练数据点的特征距离 $\overline{d}$ 只是平均距离的平均值:

$$ \overline{d} = \sum_{a=1}^n(\sum_{b=1}^n(d_{ab}/n)/n) $$

$\overline{d}$ 量化了训练数据的传播范围,将其与新预测特征向量 $X_k$ 与所有训练数据之间的距离进行比较:

$$ d_k = \arg \min d_{k,i} $$

这使得不相似度指数的估计成为可能:

$$ DI_k = d_k/\overline{d} $$

您可以通过 DI_threshold 微调 DI,以增加或减少经过训练的模型的推断。较高的 DI_threshold 意味着 DI 更宽松,允许使用距离训练数据更远的预测,而较低的 DI_threshold 则具有相反的效果,因此会丢弃更多的预测。

下图描述了 3D 数据集的 DI。

DI

使用支持向量机 (SVM) 识别异常值

您可以通过在配置中包含以下语句,告诉 FreqAI 使用支持向量机 (SVM) 从训练/测试数据集中移除异常数据点:

json
    "freqai": {
        "feature_parameters" : {
            "use_SVM_to_remove_outliers": true
        }
    }

这将在您的 feature_pipeline 中添加 SVMOutlierExtractor 步骤。SVM 将在训练数据上进行训练,SVM 认为超出特征空间的任何数据点都将被移除。

您可以选择通过配置中的 feature_parameters.svm_params 字典为 SVM 提供额外参数,例如 shufflenu

参数 shuffle 默认设置为 False 以确保结果一致。如果设置为 True,在相同数据集上多次运行 SVM 可能会因为 max_iter 过低而导致算法无法达到要求的 tol 而产生不同的结果。增加 max_iter 可以解决此问题,但会导致过程耗时更长。

参数 nu(广义上讲)是应被视为异常值的数据点数量,应在 0 和 1 之间。

使用 DBSCAN 识别异常值

您可以通过在配置中激活 use_DBSCAN_to_remove_outliers,配置 FreqAI 使用 DBSCAN 从训练/测试数据集或预测中传入的异常值中聚类并移除异常值:

json
    "freqai": {
        "feature_parameters" : {
            "use_DBSCAN_to_remove_outliers": true
        }
    }

这将在您的 feature_pipeline 中添加 DataSieveDBSCAN 步骤。这是一种无监督机器学习算法,它对数据进行聚类,而无需知道应该有多少个簇。

给定数据点数量 $N$ 和距离 $\varepsilon$,DBSCAN 通过将距离 $\varepsilon$ 内具有 $N-1$ 个其他数据点的所有数据点设为核心点来对数据集进行聚类。距离核心点 $\varepsilon$ 以内但自身在 $\varepsilon$ 距离内没有 $N-1$ 个其他数据点的数据点被认为是边缘点。一个簇就是核心点边缘点的集合。在距离 $<\varepsilon$ 处没有其他数据点的数据点被认为是异常值。下图显示了一个 $N = 3$ 的簇。

dbscan

FreqAI 使用 sklearn.cluster.DBSCAN(详细信息可在 scikit-learn 网页 获得(外部网站)),其中 min_samples ($N$) 取为特征集中时间点(K线)数量的 1/4。eps ($\varepsilon$) 自动计算为从特征集中所有数据点的两两距离中的最近邻计算出的 k-距离图 中的拐点。

使用主成分分析进行数据降维 (Data dimensionality reduction with Principal Component Analysis)

您可以通过在配置中激活主成分分析来减少特征的维度:

json
    "freqai": {
        "feature_parameters" : {
            "principal_component_analysis": true
        }
    }

这将对特征执行 PCA 并降低其维度,使得数据集的解释方差 >= 0.999。降低数据维度可以加快模型训练速度,从而允许使用更及时的模型。