← 返回文章列表
July 30, 2026
5 分钟阅读

用于系统化交易流程的 AutoML

用于系统化交易流程的 AutoML
#AutoML
#NAS
#feature-engineering
#automation
#quant

本博客花了很长的篇幅讨论如何搜索:使用哪个采样器以及何时使用(交叉是评估成本),如何为您运行的搜索定价(压缩夏普比率),如何对选择过程本身进行评分(PBO),以及整个装置诚实落地的地方(无鲁棒边缘)。

它从未涵盖的是您搜索*的内容。这些文章都以给定的功能集和策略系列为基础,并优化其中的参数。 AutoML 攻击上面的层——它自动构建候选者本身。其中三个部分没有出现在该博客的其他任何地方:

  1. 自动特征提取 - tsfresh 将一个价格系列转化为约 794 个统计特征特征,而 Featuretools 通过深度特征合成在相关市场表中组合特征。
  2. 预算感知 AutoML — FLAML 的成本节约优化,它可以在计算预算内找到最佳模型,而不是不考虑成本。
  3. 公式化 alpha 工厂 — WorldQuant 的 101 个公式化 Alpha 作为机器可以枚举的可组合运算符语法。

其他所有内容——目标函数、验证方案、多重测试校正——都已经在此处的其他地方进行了测量,因此本文只是链接而不是重新教授。这比平常更重要,因为 AutoML 使试验计数激增,而试验计数正是弧线其余部分的内容。

AutoML 系统交易管道

AutoML 自动化什么

AutoML 不是单一算法。它是一系列自动化管道不同阶段的技术:

舞台 手动方法 AutoML 方法
特征工程 领域专家工艺指标 自动提取(tsfresh、Featuretools)
特征选择 相关性分析,直觉 统计假设检验,SHAP
选型 尝试 2-3 个模型 搜索数十名学习者
超参数调优 网格搜索,手动调整 贝叶斯/成本节约搜索(Optuna、FLAML)
建筑设计 固定神经网络拓扑 神经架构搜索(NAS)
合奏施工 手动堆垛 自动合奏选择

前提是,在系统交易中,候选空间是巨大的——单个 OHLCV 系列会生成数百个技术特征,而多种资产、订单簿数据和替代来源使其具有组合性。这个前提也是危险的:每个候选人都是一次试验,而试验会产生错误的发现。

关于 AutoML 目标的一个特定点:搜索最大化的指标必须是财务指标,因为搜索将准确优化您写下的内容,而不是您想要的内容。您选择的标量会默默地选择您的策略 - 请参阅目标函数设计,其中天真的每笔交易夏普在 600 个种子中的 56% 中获得了低于 5% 的曝光率,并发布了样本内夏普为 21 的样本,该夏普在样本外为 0.13。从一开始就将财务指标连接到 AutoML 评分器中;不优化 roc_auc 并希望它能够转移。

自动化特征工程

特征工程是大多数 alpha 生活的地方。原始价格数据对每个人来说都是相同的。将数据转化为预测信号是将盈利策略与噪音区分开来的关键。

自动化特征工程工厂

tsfresh:从单一价格系列到 800 多种功能

[ts新鲜](https://tsfresh.readthedocs.io/)(基于可扩展假设检验的时间序列特征提取)是专门为时间序列特征提取而构建的。它计算 63 种表征方法,默认情况下扩展到约 794 个特征,包括:

  • 统计矩(均值、方差、偏度、峰度)
  • 多个滞后的自相关
  • 傅立叶系数和光谱能量
  • 复杂性度量(近似熵、样本熵)
  • 非线性特征(弗里德里希系数、最大朗之万定点)
  • 更改分位数和范围计数

对于金融数据,这意味着单个价格系列会产生数百个候选特征,其中许多特征捕获了手动特征工程可能会错过的动态。这与传播建模DeepLOB中使用的手工制作的、领域衍生的特征集有本质上的不同:人类决定订单簿不平衡很重要;这里,图书馆枚举了所有内容并让假设检验来决定。

import pandas as pd
import numpy as np
from tsfresh import extract_features, select_features
from tsfresh.utilities.dataframe_functions import impute

def prepare_rolling_windows(df: pd.DataFrame, window_size: int = 20) -> pd.DataFrame:
    """Convert OHLCV DataFrame into rolling windows for tsfresh."""
    records = []
    for i in range(window_size, len(df)):
        window = df.iloc[i - window_size:i].copy()
        window["id"] = i  # window identifier
        window["time"] = range(window_size)
        records.append(window[["id", "time", "close", "volume", "high", "low"]])
    return pd.concat(records, ignore_index=True)

df_ohlcv = pd.read_csv("btc_1h.csv", parse_dates=["timestamp"])
df_rolled = prepare_rolling_windows(df_ohlcv, window_size=24)

features = extract_features(
    df_rolled,
    column_id="id",
    column_sort="time",
    n_jobs=8,  # parallel extraction
    disable_progressbar=False,
)

impute(features)

y = df_ohlcv["close"].pct_change(4).shift(-4).iloc[24:].reset_index(drop=True)
y_binary = (y > 0).astype(int)

features_selected = select_features(features, y_binary, fdr_level=0.05)
print(f"Selected {features_selected.shape[1]} features from {features.shape[1]}")

select_features 函数应用 Benjamini-Yekutieli 过程来控制整个功能组的错误发现率 - 这是正确的本能,Deflated Sharpe 文章 同样的本能也适用于策略搜索。请注意它“不”做什么:对特征选择步骤的 FDR 控制没有提及其后的模型搜索的试验成本。这是两个不同的多重性,都必须支付。

特征工具:深度特征合成

虽然 tsfresh 专注于时间序列表征,[Featuretools](https://www.featuretools.com/)擅长关系特征工程——通过跨相关表组合原始操作来构造特征。

对于交易来说,当您拥有多表数据时,这是非常强大的:交易历史记录、订单簿快照、资金费率和链上指标。 Featuretools 应用深度特征合成 (DFS),它堆叠转换和聚合基元:

import featuretools as ft

es = ft.EntitySet(id="crypto_trading")

es = es.add_dataframe(
    dataframe_name="candles",
    dataframe=df_candles,
    index="candle_id",
    time_index="timestamp",
)

es = es.add_dataframe(
    dataframe_name="orderbook",
    dataframe=df_orderbook,
    index="snapshot_id",
    time_index="timestamp",
)

es = es.add_dataframe(
    dataframe_name="funding",
    dataframe=df_funding,
    index="funding_id",
    time_index="timestamp",
)

feature_matrix, feature_defs = ft.dfs(
    entityset=es,
    target_dataframe_name="candles",
    agg_primitives=["mean", "std", "max", "min", "skew", "trend"],
    trans_primitives=["percentile", "diff", "cum_mean"],
    max_depth=2,  # compose up to 2 primitives deep
    features_only=False,
)

print(f"Generated {len(feature_defs)} features via DFS")

time_index 声明是承载的:它允许Featuretools仅使用截止处存在的行来计算聚合。如果出现错误,DFS 将很乐意在未来构建一个功能 - 在前瞻偏差分类法 中编目的泄漏的关系数据实例。

tsfresh(时间序列表征)和 Featuretools(关系综合)的结合为您提供了一个特征工厂,可以从原始市场数据中生成数千个候选信号,而无需手动指标编码。

预算感知 AutoML:FLAML

我们假设 Optuna 和树结构 Parzen 估计器作为背景 - 推导、采样器比较和基准在坐标下降与贝叶斯优化中。关于要达到的哪个采样器,本博客自己的测量是参考,而不是民间传说的答案:交叉是由评估成本决定的,而不是算法的聪明程度——当回测几乎免费时,加扰的 Sobol 在吞吐量上获胜(2,830 cfg/s,TPE 的154),并且只有昂贵的评估才能提高样本效率([随机 vs 智能]搜索](/en/blog/post/search-method-crossover-eval-cost))。

预算感知 AutoML 搜索

这种框架使得 FLAML 成为一个不同的轴,而不是一个竞争的采样器。 Optuna 询问下一步在哪里采样; [FLAML](https://microsoft.github.io/FLAML/)(快速轻量级 AutoML)询问我能负担得起采样什么。它使用 CFO(成本节俭优化)在一段时间内或计算预算内优化最佳模型,该优化会尽早优先考虑廉价评估配置,并仅在估计的边际收益证明支出合理时才升级。

from flaml import AutoML
from sklearn.metrics import make_scorer
import numpy as np

def sharpe_score(y_true, y_pred):
    """Custom scorer: Sharpe ratio of predicted signals."""
    signal = np.sign(y_pred - 0.5)
    returns = signal * y_true  # y_true contains forward returns
    if returns.std() == 0:
        return 0.0
    return np.sqrt(252) * returns.mean() / returns.std()

sharpe_scorer = make_scorer(sharpe_score, greater_is_better=True)

automl = AutoML()
automl_settings = {
    "time_budget": 300,          # 5 minutes
    "metric": sharpe_scorer,     # optimize the financial objective, not accuracy
    "task": "classification",
    "estimator_list": [
        "lgbm", "xgboost", "rf", "extra_tree", "catboost",
    ],
    "eval_method": "cv",
    "split_type": "time",        # time series split (no future leakage)
    "n_splits": 5,
    "log_file_name": "flaml_trading.log",
    "seed": 42,
}

automl.fit(
    X_train=X_train,
    y_train=y_train,
    **automl_settings,
)

print(f"Best model: {automl.best_estimator}")
print(f"Best config: {automl.best_config}")

from flaml.data import get_output_from_log
time_history, best_valid_loss_history, valid_loss_history, config_history, metric_history = \
    get_output_from_log(filename="flaml_trading.log", time_budget=300)

FLAML 的交易优势是跨异构模型类型的计算分配。如果 LightGBM 配置的评估速度比 CatBoost 快 10 倍,则 FLAML 会尽早探索更多 LightGBM,并且仅在估计边际改进证明成本合理时才进行切换。这是交叉文章中廉价制度的见解,概括地说:吞吐量是搜索预算中的一流术语,而不是实现细节。

轨迹日志是值得保留的部分。 get_output_from_log 为您提供完整的配置历史记录,即您的试用计数 - 试用计数是您即将欠的每个通货紧缩的输入。

交易模型的神经架构搜索

神经架构搜索可以自动化网络拓扑的设计,而不是调整固定的拓扑。金融系列的架构选择确实不明显——非平稳性、低信噪比、混合频率和跨越几分钟到几个月的依赖关系,所有这些都朝着不同的方向发展——但这种动机已经在其他地方具体争论过:请参阅Temporal Fusion Transformer,了解 TFT 何时击败 LSTM 击败普通 Transformer,以及DeepLOB 关于 CNN+Inception+LSTM 权衡,以及精心手工设计的固定架构已经实现的目标。 NAS 的论点是这个选择本身应该被搜索。

【股票收益预测的神经进化 NAS】研究(https://arxiv.org/html/2410.17212v1)报告称,通过 EXAMM(增强记忆模型的进化探索)进化而来的 RNN 在熊市(2022 年)和牛市(2023 年)中使用简单的多空策略,表现均优于 DJI 和标准普尔 500 指数。将其视为第三方声明,而不是此处复制的结果 - 这是最佳搜索数据,没有报告通货紧缩。

NAS 交易搜索空间

实用的 NAS 交易模型搜索空间包括:

Search Space:
  - Cell types: {LSTM, GRU, Temporal Conv, Transformer block, Linear}
  - Number of layers: [1, 8]
  - Hidden dimensions: {32, 64, 128, 256}
  - Attention heads (if Transformer): {2, 4, 8}
  - Dropout rate: [0.0, 0.5]
  - Skip connections: {True, False}
  - Normalization: {BatchNorm, LayerNorm, None}
  - Activation: {ReLU, GELU, SiLU, Mish}
  - Lookback window: {20, 60, 120, 252}

借助 Microsoft 的 NNI(神经网络智能),您可以定义和搜索该空间:

import nni
from nni.nas.nn.pytorch import LayerChoice, InputChoice
import torch
import torch.nn as nn

class TradingNASModel(nn.Module):
    """NAS-searchable model for financial time series."""

    def __init__(self, input_dim: int, seq_len: int):
        super().__init__()
        self.input_proj = nn.Linear(input_dim, 128)

        self.temporal_block = LayerChoice([
            nn.LSTM(128, 128, num_layers=2, batch_first=True, dropout=0.1),
            nn.GRU(128, 128, num_layers=2, batch_first=True, dropout=0.1),
            nn.TransformerEncoder(
                nn.TransformerEncoderLayer(d_model=128, nhead=4, batch_first=True),
                num_layers=2,
            ),
        ], label="temporal_cell")

        self.head = LayerChoice([
            nn.Sequential(nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, 1)),
            nn.Sequential(nn.Linear(128, 32), nn.GELU(), nn.Linear(32, 1)),
            nn.Linear(128, 1),
        ], label="prediction_head")

        self.skip = InputChoice(n_candidates=2, n_chosen=1, label="skip_conn")
        self.skip_proj = nn.Linear(input_dim, 128)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        h = self.input_proj(x)
        out = self.temporal_block(h)

        if isinstance(out, tuple):
            out = out[0]

        last = out[:, -1, :]

        skip_val = self.skip_proj(x[:, -1, :])
        last = self.skip([last, last + skip_val])

        return self.head(last).squeeze(-1)

搜索算法(ENAS、DARTS 或进化)使用财务目标在验证集上评估候选架构,然后逐步缩小范围以实现高性能拓扑。请注意本文中与其他所有内容相比的成本不对称性:NAS 位于评估成本轴的昂贵一端,这是样本高效采样器和多保真修剪真正收回成本的一种机制。

WorldQuant Alpha 工厂方法

WorldQuant 的 alpha 发现方法是 AutoML 原则在金融领域最具工业规模的实施。 WorldQuant 由 Igor Tulchinsky 创立,运营着所谓的“阿尔法工厂”——一个用于生成、测试和组合数百万个预测信号的系统流程。

哲学:指数 Alpha 生成及其修正

WorldQuant 在 2010 年设定了达到 100 万个 alpha 的目标,而当时他们每年只生产几千个。他们在 2016 年实现了这一目标。这一理念基于主动管理的基本法则:

IRICBRIR \approx IC \cdot \sqrt{BR}

在哪里 IRIR 为信息比, ICIC 信息系数,以及 BRBR 广度 — 独立投注的数量。每个人 αi\alpha_i 是一个弱预测变量;他们的主张是,将足够多的弱相关因素组合起来,将微小的 IC 组合成有意义的风险调整回报。

造成或破坏这一点的修正,以及对公式的天真的解读隐藏了:广度不是试验计数。 BRBR 计算独立投注,并且生成的 alpha 不是独立的 - 它们由相同价格和交易量字段上的相同运算符组成,因此它们继承了彼此的相关结构。本博客直接在Signal Correlation中测量了效果:有效宽度为 Neff=N/CfN_{eff} = N / C_f,并具有典型的加密相关因子 Cf3C_f \approx 3,10 个信号大约相当于 3.3 个独立信号。这 BR\sqrt{BR} term 不会随着您生成的 alpha 数量而增长;它随着有多少“不相关”的幸存而增长。一百万个相关阿尔法给你带来的收益远远低于公式所宣传的。

这就是为什么下面的步骤 3(与现有库去相关)不是 alpha 工厂中的卫生步骤。这是首先产生宽度的步骤。

101 个公式阿尔法

论文[“101 Formulaic Alphas”](https://arxiv.org/pdf/1601.00991)由 Kakushadze(与 WorldQuant 研究相关)发表了一组公式化的 alpha 表达式——关于价格、交易量和基本数据的数学表达式:

Alpha#1:  (rank(Ts_ArgMax(SignedPower(((returns < 0) ? stddev(returns, 20)
          : close), 2.), 5)) - 0.5)

Alpha#7:  ((adv20 < volume) ? ((-1 * ts_rank(abs(delta(close, 7)), 60))
          * sign(delta(close, 7))) : (-1 * 1))

Alpha#42: (rank(vwap - close) / rank(vwap + close))

AutoML 的有趣之处在于它是一种“语法”。 alpha 是一个小的算子集的组合(rank, delta, ts_rank, stddev, correlation, signedpower)在一个小字段集(close, volume, vwap, returns, adv20)。语法是可枚举的,这意味着搜索空间是机器可生成的,而“想到一个好特征”则不然。 AutoML 系统可以:

  1. 通过在数据字段上组合运算符来生成候选表达式
  2. 评估每个表达式的 IC、营业额和回撤
  3. 过滤,找出在统计测试中幸存下来的表达式 并且 与现有库完全不相关
  4. 合并到一个投资组合中

请注意,步骤 1 和 2 很简单,步骤 3 是值所在的位置 - 请参阅上面的宽度校正。

LLM-增强阿尔法发现

WorldQuant 已开始将大型语言模型集成到 alpha 工厂中。据财经媒体报道,该公司正在探索法学硕士如何“转变和发现不同领域的阿尔法”——生成新颖的表达方式,将研究论文转化为可测试的假设,以及揭示纯粹的枚举搜索可能会错过的跨领域关系。这是与从文本中提取信号不同的机制,本博客在 LLM Alpha Mining on Earnings Calls 中对此进行了介绍;这里法学硕士写的是公式,而不是特征

建造你自己的迷你阿尔法工厂

这是一个关于运算符语法的紧凑但实用的 alpha 工厂:

import itertools
from dataclasses import dataclass
from typing import Callable, List
import pandas as pd
import numpy as np
from scipy.stats import spearmanr

@dataclass
class Alpha:
    name: str
    expression: Callable[[pd.DataFrame], pd.Series]
    ic: float = 0.0
    turnover: float = 0.0
    sharpe: float = 0.0

def ts_rank(series: pd.Series, window: int) -> pd.Series:
    return series.rolling(window).apply(lambda x: pd.Series(x).rank().iloc[-1] / len(x))

def ts_delta(series: pd.Series, period: int) -> pd.Series:
    return series.diff(period)

def ts_std(series: pd.Series, window: int) -> pd.Series:
    return series.rolling(window).std()

def cs_rank(series: pd.Series) -> pd.Series:
    return series.rank(pct=True)

def ts_mean(series: pd.Series, window: int) -> pd.Series:
    return series.rolling(window).mean()

ALPHA_TEMPLATES = [
    ("momentum_{w}",
     lambda df, w: cs_rank(ts_delta(df["close"], w))),
    ("mean_reversion_{w}",
     lambda df, w: -cs_rank(df["close"] / ts_mean(df["close"], w) - 1)),
    ("vol_surprise_{w}",
     lambda df, w: cs_rank(df["volume"] / ts_mean(df["volume"], w))),
    ("price_vol_divergence_{w}",
     lambda df, w: cs_rank(ts_delta(df["close"], w)) * -cs_rank(ts_delta(df["volume"], w))),
    ("volatility_rank_{w}",
     lambda df, w: -cs_rank(ts_std(df["close"].pct_change(), w))),
    ("high_low_range_{w}",
     lambda df, w: cs_rank(ts_mean(df["high"] - df["low"], w) / df["close"])),
]

WINDOWS = [5, 10, 20, 60, 120]

def generate_alphas(df: pd.DataFrame, forward_returns: pd.Series) -> List[Alpha]:
    """Generate and evaluate all alpha candidates."""
    alphas = []

    for (name_tpl, expr_fn), window in itertools.product(ALPHA_TEMPLATES, WINDOWS):
        name = name_tpl.format(w=window)
        try:
            signal = expr_fn(df, window)
            signal = signal.replace([np.inf, -np.inf], np.nan)

            valid = signal.notna() & forward_returns.notna()
            if valid.sum() < 100:
                continue

            ic, _ = spearmanr(signal[valid], forward_returns[valid])
            turnover = signal.diff().abs().mean()

            ret = (signal * forward_returns).dropna()
            sharpe = np.sqrt(252) * ret.mean() / (ret.std() + 1e-9)

            alphas.append(Alpha(
                name=name,
                expression=lambda df, fn=expr_fn, w=window: fn(df, w),
                ic=ic,
                turnover=turnover,
                sharpe=sharpe,
            ))
        except Exception:
            continue

    alphas.sort(key=lambda a: abs(a.ic), reverse=True)
    return alphas

五个窗口中的六个模板相当于 30 个配置的扫描。它生成的报告具有以下示意性形状 - 字段是工厂发出的内容,没有填写任何数字,因为这里没有测量任何数字:

SCHEMATIC — illustrative format only, not a measurement

Generated <N> alphas from <N> candidate configurations

Top by |IC|:
  <alpha_name>   IC=<±0.0xxx>   Sharpe=<±x.xxx>   Turnover=<0.0xxx>
  ...

30 个配置扫描中顶部 alpha 的原始夏普值 不是结果 — 它是 30 次抽取的最大值,在 1,000 个零边缘策略中,最佳年化夏普平均值为 1.63,而朴素测试则宣布 100% 的时间都有发现(压缩夏普比率)。因此,工厂诚实的交付成果不是“我们找到了一个 alpha”,而是“有多少 alpha 在通货紧缩中幸存下来”:IC 分布、试验计数、获胜者相对于该计数的 DSR 以及选择的 PBO。这就是本节仍欠的衡量标准。

护栏:你已经欠下的东西

AutoML 以同样的方式放大发现和过度拟合,并且在差异不再微妙的试验计数中这样做。这些内容中的每一个都在其他地方进行了深入介绍;这里的要点是,AutoML 使所有这些都是强制性的,而不是建议性的。

为搜索定价,而不仅仅是纠正它。 特征模型超参数扫描是一次尝试计数,尝试计数会移动条形 - 请参阅压缩夏普比率

通过清除和禁止进行临时验证。 请参阅前向优化前瞻偏差分类,了解在天真的时间分割中幸存下来的泄漏。

**对过程进行评分,而不仅仅是获胜者。**参见回测过拟合的概率,并进行其承载修正:PBO的零值是0.5,而不是1——一半不是“一半过拟合”,而是完全过拟合,就像一枚硬币翻转一样。

注意不存在的峰值。 有效地搜索噪声主导的表面只会更快地找到更清晰的错觉(高原分析);这对策略参数影响最大,对 ML 超参数影响最小,而 FLAML 和 Optuna 正是为此而构建的。

限制搜索空间。 最小叶子样本、上限深度和估计器计数、特征预算、周转惩罚——规范搜索比事后缩小搜索要便宜。

结论

AutoML 将自动化提升了一个层次:从调整固定候选对象内部的参数到生成候选对象本身。三个组件值得付出努力——用于特征生成的 tsfresh 和 Featuretools、用于预算感知模型选择的 FLAML 成本节约搜索,以及用于可枚举 alpha 构造的公式化运算符语法。只有当你的信噪比证明它合理时,NAS 才值得计算,并且它位于评估成本轴的昂贵一端,而样本效率最终会得到回报。

WorldQuant 广度论证仅以其更正后的形式与本博客自己的测量结果相联系: IRICBRIR \approx IC\sqrt{BR} 在哪里 BRBR 计算“独立”的赌注,而工厂的产出远不是独立的。生成一百万个阿尔法是容易的一半。确定它们不相关足以算作广度是决定公式是否有意义的一半。

诚实的警告不是道德,而是衡量的结果。该博客在 The Honest Negative 中对搜索和过度拟合弧线进行了总结:五个专业进行了数万次回测,在约 37,000 次试验中 DSR = 0.00,PBO 0.264 和 0.327,并且没有稳健边缘。 AutoML 将试验次数提高了几个数量级。无论发现什么,它都将面临相应更大的通货紧缩——而阿尔法工厂的有用产出是通货紧缩之后的生存率,而不是之前的顶线。

免责声明:本文提供的信息仅用于教育和参考目的,不构成财务、投资或交易建议。加密货币交易涉及重大损失风险。

Authors

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

紧跟市场步伐

订阅我们的时事通讯,获取独家 AI 交易见解、市场分析和平台更新。

我们尊重您的隐私。您可以随时退订。