4、标签构建:如何定义涨跌方向?

好,咱们进入第四讲。前面几章我们把数据拿回来了,也把特征工程做完了。但有个核心问题一直没解决——机器到底要学什么?

说白了,你得告诉它什么是「涨」,什么是「跌」。这就是标签构建。

我见过不少新手,上来就把「明天收盘价比今天高」定义为涨。听起来没毛病对吧?但实际跑起来,模型准确率死活上不去。为什么?因为市场有噪音,单日的涨跌随机性太强了。

嗯,这里要注意。我们真正要预测的,不是明天那一下跳动,而是一段趋势

4.1 未来N日收益率:标签的数学基础

我个人习惯用未来N日收益率来定义涨跌。公式很简单:

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

举个例子。今天收盘价是100元,5天后收盘价是105元。那未来5日收益率就是5%。

你可能会问:「N取多少合适?」

我在项目中遇到过这个问题。取1天,噪声太大。取20天,又太滞后,等你信号出来,行情都走完了。我个人经验是:

  • 短线策略:N=3或5
  • 中线策略:N=10或20
  • 长线策略:N=60或120

当然,这没有标准答案。我建议你根据自己策略的持仓周期来定。

小技巧:如果你不确定选哪个N,可以跑一个「N值敏感性分析」。把N从1到30都试一遍,看看哪个N下模型表现最稳定。我常用的方法是看「夏普比率」的峰值。

4.2 分类标签的生成:从连续值到离散类别

收益率是连续值,但我们做的是分类任务。所以需要把连续值映射成离散类别。

最常见的做法是设一个阈值

def generate_label(future_return, threshold=0.02):
    if future_return > threshold:
        return 1  # 涨
    elif future_return < -threshold:
        return -1  # 跌
    else:
        return 0  # 平

这里threshold取2%。为什么是2%?其实没有魔法数字。我一般会看历史收益率的分布,取一个能平衡样本量的值。

举个例子,如果历史数据显示:

  • 收益率 > 2% 的样本占30%
  • 收益率 < -2% 的样本占30%
  • 中间占40%

那这个阈值就挺合理。三类样本不至于太偏。

注意:千万不要把threshold设得太小。比如设成0.1%,那几乎所有样本都是「涨」或「跌」,模型学到的全是噪声。我曾经犯过这个错,模型在训练集上准确率90%,一上实盘就崩。后来发现,它学到的根本不是趋势,而是随机波动。

4.3 平衡性检查:别让模型变成「偏科生」

标签生成完了,第一件事就是检查类别分布

为什么?因为不平衡的数据会让模型偷懒。比如90%的样本都是「涨」,模型只要永远预测「涨」,准确率就有90%。但它什么也没学到。

我一般用这个函数来检查:

def check_balance(labels):
    unique, counts = np.unique(labels, return_counts=True)
    total = len(labels)
    for cls, cnt in zip(unique, counts):
        print(f"类别 {cls}: {cnt} 样本, 占比 {cnt/total:.2%}")
    return counts / total

输出结果大概长这样:

类别 样本数 占比
-1(跌) 1200 28.6%
0(平) 1800 42.9%
1(涨) 1200 28.6%

这个分布就挺健康。三类样本差不多。

但如果出现这种情况:

类别 样本数 占比
-1(跌) 300 7.1%
0(平) 3600 85.7%
1(涨) 300 7.1%

那就要警惕了。模型大概率会学成「永远预测平」的废物。

核心原则:任何一类的占比不要低于10%。如果低于10%,建议调整阈值,或者改用二分类(只预测涨和跌,去掉「平」)。

4.4 核心逻辑流程图

下面这张图,把整个标签构建的流程串起来了。你可以把它当作本章的「知识地图」:

原始价格数据 收盘价序列 计算未来N日收益率 (未来收盘价/当前)-1 设定阈值 如 ±2% 生成分类标签 1(涨) / 0(平) / -1(跌) 或 1(涨) / 0(不涨) 平衡性检查 统计各类别占比 确保每类 > 10% 是否平衡? 是 → 进入模型训练 | 否 → 调整阈值或重采样 进入模型训练 调整阈值 图:标签构建核心流程

4.5 处理不平衡的几种方法

如果检查后发现不平衡,怎么办?别慌,有办法。我按推荐程度排个序:

  1. 调整阈值:这是最简单的方法。把threshold调大或调小,让三类样本更均衡。
  2. 重采样:对少数类做上采样(复制样本),或对多数类做下采样(随机丢弃)。我一般用上采样,因为下采样会丢失信息。
  3. 改用二分类:如果「平」的样本太多,干脆去掉中间类别,只预测「涨」和「跌」。这样问题简化了,模型也更容易学。
  4. 加权损失函数:给少数类更高的权重,让模型更关注它们。这个方法在深度学习里很常用。
我的经验:如果数据量足够大(比如超过1万条),我优先用「调整阈值」。如果数据量小,我会用「重采样+加权损失」的组合。记住,没有银弹,多试几种方法,看验证集上的表现。

4.6 一个完整的代码示例

最后,给你一个完整的标签构建函数。这是我项目里常用的模板:

import numpy as np
import pandas as pd

def build_labels(df, n_days=5, threshold=0.02, method='three_class'):
    """
    构建涨跌分类标签
    
    参数:
        df: DataFrame,必须包含 'close' 列
        n_days: 未来N日
        threshold: 涨跌阈值
        method: 'three_class' 或 'two_class'
    
    返回:
        df: 添加了 'label' 列的DataFrame
    """
    # 计算未来N日收益率
    df['future_return'] = df['close'].shift(-n_days) / df['close'] - 1
    
    # 生成标签
    if method == 'three_class':
        conditions = [
            df['future_return'] > threshold,
            df['future_return'] < -threshold
        ]
        choices = [1, -1]
        df['label'] = np.select(conditions, choices, default=0)
    else:  # two_class
        df['label'] = (df['future_return'] > threshold).astype(int)
    
    # 删除未来数据不可用的行
    df = df.dropna(subset=['label'])
    
    # 打印平衡性检查
    print("标签分布:")
    print(df['label'].value_counts(normalize=True))
    
    return df

# 使用示例
# df = build_labels(data, n_days=5, threshold=0.02, method='three_class')

这个函数会帮你自动完成:计算收益率 → 生成标签 → 删除无效行 → 打印平衡性检查。一步到位。

好了,标签构建就讲到这里。记住,标签的质量直接决定了模型的上限。花时间把这一步做好,后面会省很多事。


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