4、标签构造:如何定义“好股票”?未来N日收益率排序。相对排名与分组标签。

好,咱们进入实战环节最核心的一步——标签构造

说白了,就是回答一个问题:到底什么样的股票,才算“好股票”?

你想想看,如果连好坏都定义不清楚,模型学出来的东西肯定跑偏。我在早期做量化的时候,就犯过这个错。直接用未来N日的绝对收益率当标签,结果模型学了一堆“垃圾股”的特征——因为那些小盘妖股短期涨得猛,但风险也大。后来我痛定思痛,改用排序分组的思路,效果才稳定下来。

4.1 为什么不用绝对收益率?

绝对收益率,比如“未来5天涨了10%”,看起来直观。但问题在于:

  • 市场环境不同:牛市里大部分股票都涨,熊市里大部分都跌。模型如果只学绝对收益,会把“市场Beta”当成“个股Alpha”。
  • 极端值干扰:一只股票因为重组连续涨停,收益率100%,另一只正常涨了5%。模型会拼命去拟合那个100%的异常点,反而忽略了常态。
  • 不可比性:不同时间点、不同板块的股票,收益率分布差异巨大。直接拿数值去训练,模型会“晕头转向”。

所以,我个人习惯的做法是:放弃绝对数值,改用相对排序

4.2 核心思路:未来N日收益率排序

具体怎么做?很简单:

  1. 在每一个调仓日(比如每月第一个交易日),计算所有候选股票的未来N日收益率(比如未来20个交易日)。
  2. 然后,按收益率从高到低排序
  3. 排名越靠前,说明这只股票在未来一段时间内,相对其他股票表现更好。

这样做的好处是:去掉了市场整体的影响。不管大盘涨跌,我们只看股票之间的相对强弱。模型学到的,是“哪些特征的股票更容易跑赢同行”,而不是“哪些股票容易跟风大盘”。

核心公式

标签 = rank(未来N日收益率) / 总样本数

这样标签值在0到1之间,0代表最差,1代表最好。方便模型统一处理。

4.3 相对排名 vs 分组标签

有了排序之后,我们有两种常见的标签构造方式:

方式 说明 适用场景
相对排名 直接用排序后的百分位数作为标签(0~1连续值) 回归模型(如LightGBM回归、神经网络)
分组标签 将股票分成若干组(如5组、10组),每组赋予离散标签 分类模型(如逻辑回归、分类树)或排序学习模型(LambdaRank)

我个人更推荐分组标签。为什么?

因为连续排名虽然信息量大,但模型容易过拟合到细微的排名差异上。比如排名0.51和0.52,其实差别不大,但模型会拼命去区分它们。分组之后,我们只关心“这只股票属于前20%还是后20%”,模型学到的模式更鲁棒。

我的经验:分组数量一般取5组(Quintile)或10组(Decile)。5组比较粗,适合样本量不大的情况;10组更精细,适合股票数量多的市场(比如A股4000+只)。

4.4 代码实现:从收益率到分组标签

嗯,这里我直接给一段核心代码。假设你已经有了一个DataFrame,包含每只股票在每个调仓日的未来20日收益率。

import pandas as pd
import numpy as np

def create_rank_labels(df, date_col='date', code_col='code', ret_col='fwd_20d_ret', n_groups=5):
    """
    构造分组标签
    df: 包含日期、股票代码、未来收益率的DataFrame
    n_groups: 分组数量,默认5组
    """
    df = df.copy()
    
    # 按日期分组,在每个调仓日内部排序
    df['rank'] = df.groupby(date_col)[ret_col].rank(method='first', ascending=True)
    df['group'] = df.groupby(date_col)['rank'].transform(
        lambda x: pd.qcut(x, q=n_groups, labels=False, duplicates='drop')
    )
    
    # 注意:pd.qcut的标签是0,1,2,...,n_groups-1
    # 0代表最差组,n_groups-1代表最好组
    # 我们通常希望标签越大代表越好,所以可以反转
    df['label'] = n_groups - 1 - df['group']
    
    return df[['date', 'code', 'ret', 'rank', 'group', 'label']]

# 使用示例
# result = create_rank_labels(raw_data, n_groups=5)
# result['label'] 就是我们要的标签,0最差,4最好

避坑指南

我曾经遇到过一个问题:某一天所有股票的收益率都一样(比如停牌日),导致pd.qcut报错。所以建议在分组前,先检查一下该日是否有足够的收益率差异。如果差异太小,可以跳过该日,或者用随机排序打破平局。

4.5 标签构造的完整流程

为了让你更直观地理解,我画了一张流程图。这张图展示了从原始数据到最终标签的完整链路。

标签构造流程图 原始数据 日期、股票、未来收益率 按日期分组 每个调仓日内部排序 计算相对排名 rank(收益率) / 总样本数 分组标签 pd.qcut分成5组或10组 最终标签 0=最差组, 4=最好组 关键要点 • 每个调仓日独立排序 • 去掉市场整体影响 • 分组比连续排名更鲁棒 • 5组或10组是常见选择 • 标签越大代表越好 --- 避坑 --- • 注意收益率相同的情况 • 检查分组是否均匀 • 避免未来信息泄露

4.6 分组标签的进阶技巧

有了基础的分组标签,我们还可以做一些优化:

  • 加权分组:不是简单等分,而是根据收益率的大小赋予不同权重。比如收益率最高的1%单独成一组,剩下的再等分。这样可以捕捉极端收益。
  • 动态分组:市场波动大的时候,分组可以多一些(比如10组);市场平稳的时候,分组少一些(比如5组)。
  • 多周期标签:同时构造未来5日、20日、60日的分组标签,让模型学习不同时间尺度下的选股能力。

一个小技巧:如果你用的是排序学习模型(比如LambdaRank),可以直接用pairwise的排序对作为标签。也就是说,不关心具体分组,只关心“股票A是否比股票B好”。这样模型更专注于相对关系,效果往往更好。

好了,标签构造这部分就讲到这里。记住一句话:好的标签,是成功模型的一半。花时间把标签定义清楚,后面模型训练会省很多力气。


蓝海数据掘金营,专注资料整理