4、统计特征(下):分位数、四分位距、变异系数、滚动窗口统计量

好,咱们接着聊统计特征。上一节我们把均值、方差这些基础统计量讲透了,这一节要深入一点——分位数、四分位距、变异系数,还有滚动窗口统计量。

说实话,这些特征在量化金融里比均值方差更常用。为什么?因为金融数据不听话,它不服从正态分布。你想想看,股票收益率经常出现极端值,均值很容易被带偏。这时候分位数就派上用场了。

分位数:数据分布的"切片"

分位数,说白了就是把数据从小到大排好,然后切成几段。第p分位数表示有p%的数据小于等于这个值。

我个人习惯用这几个分位数:

  • 0.25分位数(Q1):下四分位数,25%的数据比它小
  • 0.50分位数(Q2):中位数,数据的中点
  • 0.75分位数(Q3):上四分位数,75%的数据比它小

中位数比均值稳健得多。我在项目中遇到过一只股票,某天因为乌龙指暴跌了30%,均值直接被拉偏了5个点,但中位数几乎没变。这就是分位数的魅力——抗噪能力强。

核心观点:在金融时间序列中,中位数往往比均值更能反映"正常水平"。

四分位距(IQR):稳健的离散度度量

四分位距就是Q3 - Q1。它衡量的是中间50%数据的分布范围。

为什么不用标准差?因为标准差对异常值太敏感了。你想想看,一个极端值就能让标准差翻倍。但IQR不一样,只要极端值没超过Q1或Q3,它根本不受影响。

我曾经用IQR做过一个异常检测系统。规则很简单:

# 异常值判定:超出 [Q1 - 1.5*IQR, Q3 + 1.5*IQR] 视为异常
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
is_outlier = (x < lower_bound) | (x > upper_bound)

这个1.5倍IQR的规则是统计学里的经典做法。嗯,这里要注意:如果数据分布特别偏,这个阈值可能需要调整。我在做期权数据清洗时就遇到过,有些品种的价差分布极不对称,1.5倍不够用,得改成2倍甚至3倍。

变异系数:标准化后的离散度

变异系数(CV) = 标准差 / 均值。它解决了一个实际问题:两只股票,一个价格100块,标准差5块;另一个价格10块,标准差2块。谁的波动更大?

看标准差的话,第一个是5,第二个是2,好像第一个波动大。但算一下变异系数:

  • 股票A:5 / 100 = 0.05
  • 股票B:2 / 10 = 0.20

实际上股票B的波动比例更大。变异系数就是用来做这种"去量纲"比较的。

实用技巧:当均值接近0时,变异系数会变得不稳定甚至无穷大。这时候我建议改用"四分位距/中位数"作为替代。

滚动窗口统计量:捕捉时序动态特征

前面讲的分位数、IQR、变异系数都是全局统计量。但金融数据是时变的——今天的波动率和昨天的波动率可能完全不同。所以我们需要滚动窗口统计量。

滚动窗口,就是固定一个窗口大小(比如20天),然后逐日滑动,在每个窗口内计算统计量。这样我们就得到了一个随时间变化的特征序列。

我常用的滚动窗口统计量包括:

import pandas as pd
import numpy as np

# 假设df是日收益率数据,index是日期
window = 20

# 滚动均值
df['rolling_mean'] = df['return'].rolling(window).mean()

# 滚动标准差(波动率)
df['rolling_std'] = df['return'].rolling(window).std()

# 滚动中位数
df['rolling_median'] = df['return'].rolling(window).median()

# 滚动IQR
def rolling_iqr(x):
    return x.quantile(0.75) - x.quantile(0.25)

df['rolling_iqr'] = df['return'].rolling(window).apply(rolling_iqr)

# 滚动变异系数
df['rolling_cv'] = df['rolling_std'] / df['rolling_mean'].abs()

这里有个坑:滚动窗口大小怎么选?

我踩过这个坑。一开始做因子回测时,我用了60天的滚动窗口计算波动率,结果发现这个波动率太"平滑"了,根本抓不住短期波动突变。后来改成20天,效果好了很多。

避坑指南:窗口太小(比如5天)噪声太大,窗口太大(比如120天)反应太慢。我个人建议:

  • 高频交易:5-10天窗口
  • 中频交易:20-30天窗口
  • 低频配置:60-90天窗口

当然,具体还要看你的交易频率和数据频率。

知识体系总览

下面这张图把本章的核心逻辑串起来了:

统计特征(下)知识体系 统计特征(下) 分位数 四分位距 (IQR) 变异系数 (CV) 滚动窗口统计量 Q1 / Q2 / Q3 中位数抗噪 Q3 - Q1 异常检测 标准差/均值 去量纲比较 滚动均值/标准差 窗口大小选择 核心:稳健性 + 时变性 → 更好的因子特征

实战:把这些特征合成一个因子

光说不练假把式。咱们把这些特征组合起来,构建一个简单的波动率因子:

def build_volatility_factor(price_series, window=20):
    """
    构建综合波动率因子
    输入:价格序列
    输出:因子值序列
    """
    # 计算对数收益率
    log_ret = np.log(price_series / price_series.shift(1))
    
    # 滚动标准差(传统波动率)
    vol_std = log_ret.rolling(window).std()
    
    # 滚动IQR(稳健波动率)
    vol_iqr = log_ret.rolling(window).apply(
        lambda x: x.quantile(0.75) - x.quantile(0.25)
    )
    
    # 滚动变异系数(相对波动率)
    vol_cv = vol_std / log_ret.rolling(window).mean().abs()
    
    # 合成因子:三种波动率的等权组合
    # 先标准化
    vol_std_z = (vol_std - vol_std.mean()) / vol_std.std()
    vol_iqr_z = (vol_iqr - vol_iqr.mean()) / vol_iqr.std()
    vol_cv_z = (vol_cv - vol_cv.mean()) / vol_cv.std()
    
    # 等权合成
    factor = (vol_std_z + vol_iqr_z + vol_cv_z) / 3
    
    return factor

这个因子我曾在股指期货的跨期套利策略里用过。传统只用滚动标准差,信号噪声很大。加入IQR和CV后,因子更稳健了,回撤明显降低。

小提示:合成因子时,记得先做标准化。不同统计量的量纲不同,直接相加会出问题。我习惯用Z-score标准化,简单有效。

好了,这一节的内容就到这儿。统计特征这块,说白了就是两件事:一是找到稳健的度量方式(分位数、IQR),二是捕捉时序的动态变化(滚动窗口)。把这两件事做好了,你的因子质量会上一个台阶。

专注资料整理