第四章:标签与目标变量构建

好,咱们进入第四章。前面几章我们把数据拿来了,也清洗干净了。但有个核心问题没解决——模型到底要学什么?

说白了,就是给模型一个“标准答案”。你想想看,没有标准答案,模型怎么知道自己的预测是对是错?这就是标签和目标变量的构建。

我个人习惯把这一步叫做“给数据注入灵魂”。数据本身只是冰冷的数字,但有了标签,它就有了方向。

4.1 未来N日收益率计算

这是最基础的一步。我们要计算未来N天的收益率,作为模型的预测目标。

公式很简单:

未来N日收益率 = (未来第N日的收盘价 / 当前收盘价) - 1

但这里有个坑——你用的是复权价格还是原始价格?

我在项目中遇到过,有人直接用原始价格算收益率,结果遇到分红除权,收益率直接跳变。嗯,这里要注意,一定要用后复权价格。

代码实现也很直接:

import pandas as pd
import numpy as np

def calculate_future_return(df, n=5):
    """
    计算未来N日收益率
    df: 包含'close'列的DataFrame
    n: 未来天数
    """
    df['future_return'] = df['close'].shift(-n) / df['close'] - 1
    return df

# 示例
df = pd.DataFrame({'close': [100, 102, 101, 105, 108, 110]})
df = calculate_future_return(df, n=3)
print(df)

输出结果:

   close  future_return
0    100       0.050000
1    102       0.058824
2    101       0.089109
3    105            NaN
4    108            NaN
5    110            NaN

看到最后三行的NaN了吗?因为未来没有足够的数据了。这就是边界问题,后面会讲怎么处理。

4.2 分类标签构建:涨/跌/平

回归任务直接预测收益率,但很多时候我们只需要知道方向——涨还是跌?

分类标签的构建,说白了就是把连续收益率映射到离散类别。我常用的方法有三种:

方法 描述 适用场景
阈值法 收益率 > 阈值 → 涨;< -阈值 → 跌;其余 → 平 趋势交易、方向预测
分位数法 按收益率分位数划分,比如前30%为涨,后30%为跌 多空策略、排序问题
符号法 收益率 > 0 → 涨;< 0 → 跌;= 0 → 平 简单方向预测

我个人习惯用阈值法。为什么?因为符号法太敏感了,0.01%的涨幅也算涨,这在实际交易中意义不大。你想想看,手续费都不够。

阈值怎么选?我一般用历史收益率的波动率来定。比如:

threshold = df['future_return'].std() * 0.5

这样阈值会自适应市场波动。市场波动大,阈值就大;波动小,阈值就小。嗯,这个技巧我在实盘项目中用过,效果不错。

代码实现:

def create_classification_labels(df, threshold=None):
    """
    构建分类标签
    1: 涨, -1: 跌, 0: 平
    """
    if threshold is None:
        threshold = df['future_return'].std() * 0.5
    
    df['label'] = 0
    df.loc[df['future_return'] > threshold, 'label'] = 1
    df.loc[df['future_return'] < -threshold, 'label'] = -1
    
    return df

4.3 回归目标构建:收益率与波动率

分类标签适合方向预测,但如果你想做更精细的预测,比如预测具体的收益率,那就需要回归目标。

回归目标有两种常见选择:

  1. 未来N日收益率:直接预测收益率数值
  2. 未来N日波动率:预测价格波动的幅度

波动率的计算方式:

未来N日波动率 = 未来N日收益率的标准差

或者用更常用的方法:

未来N日波动率 = np.log(未来N日最高价 / 未来N日最低价)

我个人建议,如果你做的是趋势策略,用收益率作为回归目标;如果你做的是波动率交易或者期权定价,用波动率作为目标。

我曾经犯过一个错误——把收益率和波动率混在一起作为多目标输出。结果模型学得四不像,收益率预测不准,波动率也预测不准。后来我改成单目标,效果反而更好。

代码示例:

def calculate_volatility_target(df, n=5):
    """
    计算未来N日波动率
    """
    df['future_volatility'] = df['close'].rolling(n).std().shift(-n)
    return df

4.4 避免未来信息泄露:防窥探

这是整个章节里最重要的部分。没有之一。

什么叫未来信息泄露?简单说,就是你在构建特征时,不小心用到了未来的数据。

举个例子:

# 错误示范
df['future_return'] = df['close'].shift(-5) / df['close'] - 1
df['ma_5'] = df['close'].rolling(5).mean()  # 这里用了当前及过去的数据,没问题

# 但如果你这样做:
df['future_ma'] = df['close'].shift(-5).rolling(5).mean()  # 用了未来的数据!

你想想看,在实盘交易中,你只能看到过去和现在的数据,未来的数据你是看不到的。如果你在训练时用了未来数据,模型在回测中表现会非常好,但一到实盘就崩盘。

我曾经接手过一个项目,回测年化收益50%,实盘直接亏了20%。查了三天,发现是特征构建时不小心用了未来数据。嗯,从那以后,我每次构建特征都会做一次“时间对齐检查”。

防窥探的核心原则:

  • 特征只能使用当前时刻及之前的数据
  • 标签必须使用未来的数据
  • 特征和标签之间必须有时间间隔

具体操作时,我习惯用shift()函数来控制时间偏移:

# 正确做法
df['feature_1'] = df['close'].shift(1)  # 使用昨天的收盘价
df['feature_2'] = df['volume'].rolling(5).mean().shift(1)  # 使用过去5天的平均成交量(昨天及之前)
df['label'] = df['close'].shift(-5) / df['close'] - 1  # 未来5天的收益率

注意看,所有特征都用了shift(1),确保特征只包含历史信息。而标签用了shift(-5),指向未来。

⚠️ 重要警告:

千万不要在特征计算中使用shift(-n)!这是最常见的未来信息泄露来源。

另外,rolling()函数默认是包含当前值的,如果你不小心,也会引入未来信息。

4.5 知识体系总览

说了这么多,咱们用一张图来总结一下本章的核心逻辑:

标签与目标变量构建知识体系 目标变量构建 未来N日收益率计算 分类标签构建(涨/跌/平) 回归目标构建(收益率/波动率) 分类方法 阈值法 | 分位数法 | 符号法 自适应阈值:std * 0.5 回归方法 收益率预测 | 波动率预测 波动率 = 未来N日收益率标准差 ⚠️ 避免未来信息泄露 特征用shift(1),标签用shift(-n) 核心原则:特征只能使用历史数据,标签指向未来
💡 个人经验:

我每次构建完标签后,都会做一个简单的验证:随机抽取100个样本,手动检查特征和标签的时间对齐。虽然麻烦,但能避免很多坑。

📌 本章要点:
  • 未来N日收益率是基础标签,注意使用复权价格
  • 分类标签用阈值法,阈值自适应市场波动
  • 回归目标分收益率和波动率,根据策略选择
  • 未来信息泄露是最大敌人,时刻检查时间对齐

公众号:蓝海资料掘金营,微信deep3321