返回 FEED
ORIGINAL2026-05-07

对冲基金如何用神经网络在交易前就赢得每一次交易

大多数交易者亏钱的原因跟他们的市场判断无关。他们对方向的判断往往是对的,问题是只用了一个信号、一个指标、一个直觉——在概率框架缺失的情况下做概率决策,市场会可靠地、反复地惩罚这个错误。

神经网络解决的是一个根本不同的问题:它们不预测未来。它们做的是从历史数据中学习隐藏的期望函数——从你现在能观察到的变量,到市场统计上最可能下一步做什么之间的数学关系。而且它们同时在数千个变量上做这件事,人眼和单一指标都做不到。

Two Sigma 在机器学习模型中运行超过 10,000 个实时信号。Citadel 的量化研究部门在股票、期权、宏观策略上部署深度学习架构。Renaissance Technologies 的 Medallion Fund 在几十年前就完全基于这套框架构建——扣除费用后 30 年年化回报 66%。这不是运气,是统计学习在大规模上的系统性应用。

神经网络实际在计算什么

神经网络是一个参数化复合函数:输入向量 → 一系列线性变换 + 非线性激活函数 → 输出。

训练的核心是最小化损失函数。对于回归任务,标准选择是均方误差:

θ ← θ - α · ∇_θ L(θ)

这是随机梯度下降。所有现代深度学习优化器都是这个的变体——自适应步长、动量项、二阶近似叠加。

关键洞察来了。当我们训练一个网络最小化平方误差时,它学到了什么?是 Y 在给定 X 下的条件期望 E[Y|X]。最优预测器在平方误差下恰好是条件均值。

掷 10,000 次公平骰子,训练网络预测结果,它会预测 3.5。骰子能掷出 3.5 吗?不能。但 3.5 是最小化期望平方误差的值。网络计算的是期望,不是下一次实现。

这意味着:如果你能用神经网络找到输入特征和目标变量之间任何平滑的数学关系,它就能学会。问题从来不是网络能不能学到这个函数,而是这个函数在时间上是否足够稳定,值得学习。

为什么直接用价格预测注定失败

这是每个第一次尝试把神经网络用于交易的交易者都会犯的错:

取 500 天收盘价 → 喂给 LSTM → 预测第 501 天。

样本内:预测曲线平滑、接近实际价格。样本外:模型预测一条水平线或回归到近期均值,而价格走向完全不同。看起来模型什么都没学到。

这不是模型失败,是数据分布失败。

市场收益的分布随时间持续迁移。在左侧市场状态训练的模型,学到的是对右侧市场状态的错误期望函数。形式上,这叫非平稳性问题。

时间序列 {Xₜ} 是平稳的,当它的联合分布在时间平移下不变。金融价格序列在最强意义上是非平稳的——2008 年股权收益的分布结构上不同于 2021 年。均值、方差、自相关、尾部行为都随状态、流动性、波动率聚集和宏观条件变化。

正确特征工程:平稳性检验

解法:特征工程产生平稳或近似平稳的输入,目标变量本身也要构造为近似平稳。

有合理平稳性的特征:

  • 对数收益率:r_t = ln(P_t / P_{t-k}),k = 1, 5, 20
  • 波动率比率:σ_short / σ_long(不同窗口滚动实现波动率)
  • 动量信号:r_t / σ_t(收益按实现风险缩放)
  • 成交量 Z-score:(V_t - μ_V) / σ_V
  • 价差类信号:买卖价差相对历史分布、有效价差
  • 状态指标:VWAP 偏离、距滚动高低的距离、隐含波动率与实现波动率比率

目标变量:不要预测下一期价格,而是预测下一期风险调整后收益方向(二分类)或 Z-score 化后的收益。这两个目标比原始价格甚至原始收益都更稳定。

实用检验:用 Augmented Dickey-Fuller 检验。p 值低于 0.05 说明序列是平稳的。对于未通过检验的特征,先做一阶差分或按滚动标准差归一化。

from statsmodels.tsa.stattools import adfuller

def check_stationarity(series, name):
    result = adfuller(series.dropna())
    print(f"{name}: ADF statistic={result[0]:.4f}, p-value={result[1]:.4f}")
    return result[1] < 0.05

returns = df['close'].pct_change()
vol_20 = returns.rolling(20).std()
vol_5 = returns.rolling(5).std()

features = {
    'return_1d': returns,
    'return_5d': df['close'].pct_change(5),
    'vol_ratio': vol_5 / vol_20,
    'momentum': returns / vol_20,
}

架构选择:LSTM

市场数据是序列数据,事件顺序本身携带信息——自相关、动量、均值回归、微观结构动态。标准前馈网络把每个输入独立对待,完全丢失了这些信息。

LSTM(长短期记忆网络)通过门控记忆架构解决这个问题。维持隐藏状态 h_t 和细胞状态 c_t 两条记忆线,通过三个门控制信息流:

  • 遗忘门:决定丢弃多少前期细胞状态。市场状态转换时,遗忘门允许模型释放过时模式。
  • 输入门:决定写入哪些新信息。
  • 输出门:决定从细胞状态输出什么。
class TradingLSTM(nn.Module):
    def __init__(self, input_size, hidden_size=64, num_layers=2, dropout=0.2):
        super(TradingLSTM, self).__init__()
        self.lstm = nn.LSTM(input_size=input_size, hidden_size=hidden_size,
                            num_layers=num_layers, dropout=dropout, batch_first=True)
        self.dropout = nn.Dropout(dropout)
        self.fc = nn.Linear(hidden_size, 1)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size)
        c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size)
        out, _ = self.lstm(x, (h0, c0))
        out = self.dropout(out[:, -1, :])
        return self.sigmoid(self.fc(out))

lookback 窗口是关键超参数:日内策略 5 分钟 K 线,用 24 期(约 2 小时);日线策略用 10-20 交易日。

训练框架:早停与三向分割

过拟合是金融机器学习的核心失败模式。过拟合模型在训练数据上表现漂亮,新数据上概率甚至更差。样本内漂亮表现和真正优势看起来完全一样——你无法仅从训练指标区分。

解决方案:严格的三向顺序数据分割

  • 训练集:梯度下降运行的地方。永远不要用这个评估泛化性能。
  • 验证集:模型从未训练但持续监控的数据。每轮结束后计算验证损失——当验证损失停止下降并开始上升、而训练损失继续下降时,立即停止训练并保存最低验证损失的权重。这叫早停
  • 测试集:模型从未以任何形式影响过的数据。只用一次:在所有架构决策、超参数选择、特征工程决策都基于训练集和验证集确定后。测试集结果是你对真实性能的真实估计。
def train_model(model, train_loader, val_loader, epochs=100, patience=10):
    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
    criterion = nn.BCELoss()

    best_val_loss = float('inf')
    best_weights = None
    patience_counter = 0

    for epoch in range(epochs):
        # train step
        model.train()
        for X_batch, y_batch in train_loader:
            optimizer.zero_grad()
            loss.backward()
            torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)  # 防止梯度爆炸
            optimizer.step()

        # validation step
        model.eval()
        val_loss = 0
        with torch.no_grad():
            for X_batch, y_batch in val_loader:
                val_loss += criterion(model(X_batch).squeeze(), y_batch).item()

        if val_loss < best_val_loss:
            best_val_loss = val_loss
            best_weights = model.state_dict().copy()
            patience_counter = 0
        else:
            patience_counter += 1
            if patience_counter >= patience:
                break

    model.load_state_dict(best_weights)
    return model

Walk-Forward 验证

最严格的框架是walk-forward 验证:不做一个固定分割,而是让训练窗口沿时间滚动——训练 → 测试紧接着的下一期 → 窗口前移 → 重复。所有窗口的样本外预测拼接,给你一个现实的性能估计,模拟模型在每个历史时间点部署会怎样表现。