第 8 章 · 02 特征工程与标签 本节摘要:特征工程是 FreqAI 策略成败的关键——模型只是学生,特征和标签才是教材。本节讲清两件事:一是怎么用 等四个函数定义基础特征,再靠配置里的 、 、 、 四个维度自动裂变成成千上万个特征;二是标签设计—— 决定模型「学什么」,回归模型要连续值标签,分类模型要离散字符串标签。还会讲特征标准化流水线(MinMaxScaler、方差过滤、PCA、离群点检测)怎么自动保护模型。 内容来源:原项目文档 、 ,汉化并套用体系化模板。 ⚠️ 风险提示:特征里只要混入「未来函数」(用了未来才能知道的数据),回测就会虚高、实盘必亏。务必检查所有特征只用到当前及历史 K 线。
本节摘要:特征工程是 FreqAI 策略成败的关键——模型只是学生,特征和标签才是教材。本节讲清两件事:一是怎么用
feature_engineering_expand_all等四个函数定义基础特征,再靠配置里的include_timeframes、include_corr_pairlist、include_shifted_candles、indicator_periods_candles四个维度自动裂变成成千上万个特征;二是标签设计——set_freqai_targets决定模型「学什么」,回归模型要连续值标签,分类模型要离散字符串标签。还会讲特征标准化流水线(MinMaxScaler、方差过滤、PCA、离群点检测)怎么自动保护模型。
内容来源:原项目文档
docs/freqai-feature-engineering.md、docs/freqai-configuration.md,汉化并套用体系化模板。
⚠️ 风险提示:特征里只要混入「未来函数」(用了未来才能知道的数据),回测就会虚高、实盘必亏。务必检查所有特征只用到当前及历史 K 线。
阅读完本节,你应当能够:
FreqAI 在策略里提供四个函数,前缀和用途各不相同:
| 函数 | 必填 | 裂变维度 | 典型用途 |
|---|---|---|---|
feature_engineering_expand_all |
可选 | 周期×周期×位移×关联对 | RSI、MFI、EMA 等带 period 参数的指标 |
feature_engineering_expand_basic |
可选 | 周期×位移×关联对(无 period) | pct_change、原始成交量等不带 period 的 |
feature_engineering_standard |
可选 | 不裂变(只调一次) | 星期几、小时等不该被扩展的特征 |
set_freqai_targets |
必填 | 不裂变 | 定义标签 |
💡 命名约定:特征列必须以
%开头(如%-rsi-period),FreqAI 内部靠这个前缀识别。标签列必须以&开头(如&-s_close)。不想传给模型但想保留的列用%%开头(可用于绘图)。
feature_engineering_expand_all 是主力。你在这里写一个指标,FreqAI 会自动把它套到配置里所有的时间周期、指标周期、位移 K 线数、关联币对上:
def feature_engineering_expand_all(self, dataframe, period, metadata, **kwargs): 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
注意 period 参数是 FreqAI 传进来的,对应配置里的 indicator_periods_candles。你不要自己写死周期,让配置来控制。
基础特征定义好后,在配置的 feature_parameters 里开「裂变」:
"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_all中的特征数 × include_corr_pairlist数 × include_shifted_candles数 × indicator_periods_candles数
以上面的例子(3 个 expand_all 特征 × 3 时间周期 × 3 关联对 × 2 位移 × 2 指标周期)= 3 × 3 × 3 × 2 × 2 = 108 个特征。如果再多加几个指标和维度,轻松破万。
💡 精细控制:所有
feature_engineering_*函数都收到一个metadata字典,里面有pair、tf(时间周期)、period。你可以用它做条件判断,比如只在 1h 周期上加 ROC 指标:if metadata["tf"] == "1h": dataframe["%-roc-period"] = ta.ROC(dataframe, timeperiod=period)
set_freqai_targets 是唯一必填的函数。标签直接决定模型学什么——标签是「未来 N 根 K 线的收益率」,模型就学预测收益率;标签是「涨还是跌」,模型就学分类。
回归标签(配合 LightGBMRegressor 等):
def set_freqai_targets(self, dataframe, metadata, **kwargs): # 未来 label_period_candles 根 K 线的平均收益率 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
⚠️ 注意这里用了
shift(-N),是故意向未来看的——标签本来就该用未来数据,因为它是训练目标,不是特征。但特征里绝对不能这么做。
分类标签(配合 LightGBMClassifier 等):
def set_freqai_targets(self, dataframe, metadata, **kwargs): self.freqai.class_names = ["down", "up"] # 必须声明类名 dataframe['&s-up_or_down'] = np.where( dataframe["close"].shift(-100) > dataframe["close"], 'up', 'down') return dataframe
分类标签要用字符串表示类别。多分类时在同一列里叠加:
dataframe['&s-up_or_down'] = np.where( dataframe["close"].shift(-100) > dataframe["close"], 'up', 'down') dataframe['&s-up_or_down'] = np.where( dataframe["close"].shift(-100) == dataframe["close"], 'same', dataframe['&s-up_or_down'])
💡 多目标:可以定义多个
&-标签列,但需要多输出模型(如LightGBMClassifierMultiTarget)配合。
FreqAI 默认会构建一条数据流水线,按顺序处理特征:
默认两步是 MinMaxScaler(-1,1) 和 VarianceThreshold(去掉方差为 0 的列)。其余步骤靠配置开关激活:
| 配置项 | 作用 | 适用场景 |
|---|---|---|
DI_threshold: 1 |
相异度指数,剔除远离训练集的预测 | 想过滤「没见过的行情」 |
use_SVM_to_remove_outliers: true |
支持向量机检测离群点 | 训练数据里有异常噪声 |
use_DBSCAN_to_remove_outliers: true |
DBSCAN 聚类去噪 | 数据分布不规则 |
principal_component_analysis: true |
主成分分析降维 | 特征太多想加速 |
noise_standard_deviation: 0.1 |
给训练特征加高斯噪声 | 防过拟合 |
相异度指数(DI) 特别重要。它衡量新预测点离训练数据有多远——离得太远说明这个预测「没把握」,FreqAI 会把 do_predict 标志减 1,策略里可以据此拒绝下单。DI_threshold 越小越严格(剔除更多),越大越宽松。
⚠️ outlier_protection_percentage:默认 30,意思是如果离群检测删掉超过 30% 的数据点,FreqAI 会报警并放弃这次离群检测,保留原始数据集。这防止过度清洗导致数据不够。
想更精细地控制流水线,可以在自定义模型里覆写 define_data_pipeline / define_label_pipeline:
from datasieve.transforms import SKLearnWrapper, DissimilarityIndex from datasieve.pipeline import Pipeline from sklearn.preprocessing import QuantileTransformer, StandardScaler class MyFreqaiModel(BaseRegressionModel): def define_data_pipeline(self) -> Pipeline: return Pipeline([ ('qt', SKLearnWrapper(QuantileTransformer(output_distribution='normal'))), ('di', DissimilarityIndex(di_threshold=1)) ]) def define_label_pipeline(self) -> Pipeline: return Pipeline([ ('qt', SKLearnWrapper(StandardScaler())), ])
DataSieve 流水线兼容 SKlearn API,多数 SKlearn 变换器用 SKLearnWrapper 包一层即可。
时间加权让模型更关注近期数据。设 weight_factor 后,FreqAI 按指数函数给数据点加权:
其中 W_i 是第 i 个数据点的权重,n 是总点数。近期数据权重高,远期数据权重低——这对快速变化的市场尤其有用。
特征工程最大的雷是未来函数:特征里不小心用了未来才知道的数据。后果是回测虚高、实盘暴亏。
防护要点:
shift(-N)(负位移是看未来)。rolling().mean() 这类窗口函数时,确保窗口不延伸到未来。set_freqai_targets 里的 shift(-N) 是允许的——那是标签,不是特征。backtest_period_days 分窗口调用 set_freqai_targets,模拟实盘无前视偏差;但 feature_engineering_* 是在整个训练区间上一次性调用的,所以特征里的前视不会被自动纠正,必须自己把关。💡 startup_candle_count:策略里设的启动 K 线数,要≥
indicator_periods_candles的最大值。经验法则是再乘 2,确保 TA-Lib 函数有足够数据、不在开头产生 NaN。看到日志dropped 0 training points due to NaNs才放心。
feature_engineering_expand_all(主力,带 period)、_expand_basic(不带 period)、_standard(不裂变)、set_freqai_targets(必填,定义标签)。%、标签 &、保留绘图 %%。时间周期 × 特征数 × 关联对 × 位移 × 指标周期,几个指标轻松破万。shift(-N);标签可以。startup_candle_count 建议≥最大指标周期×2。下一节,我们看 FreqAI 的配置与运行——
freqai配置块怎么写、train_period_days和backtest_period_days怎么定、回测与实盘的差异在哪里。