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天窗口
当然,具体还要看你的交易频率和数据频率。
知识体系总览
下面这张图把本章的核心逻辑串起来了:
实战:把这些特征合成一个因子
光说不练假把式。咱们把这些特征组合起来,构建一个简单的波动率因子:
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),二是捕捉时序的动态变化(滚动窗口)。把这两件事做好了,你的因子质量会上一个台阶。