统计特征(上):均值、方差、偏度、峰度、最大值、最小值、极差

各位同学,咱们今天聊聊时间序列里最基础、也最容易被忽视的东西——统计特征。

说实话,我刚开始做量化那会儿,觉得这些统计量太简单了。均值方差嘛,大学就学过。结果呢?第一次实盘回测,策略在震荡市里表现挺好,一遇到极端行情直接崩了。后来复盘才发现,我根本没关注序列的偏度和峰度。嗯,这就是典型的「基础不牢,地动山摇」。

所以今天咱们把这几个特征掰开揉碎了讲。你想想看,一个时间序列,你拿到手第一件事是什么?肯定是看它长什么样。均值告诉你中心在哪,方差告诉你波动多大,偏度和峰度告诉你分布的形状。这些信息,是后续所有因子合成的基石。

1. 均值与方差:最熟悉的陌生人

均值,说白了就是「平均价格」。但在时间序列里,均值有个坑——它可能随时间变化。我习惯用滚动均值来捕捉这种变化,而不是算一个全局平均。

方差呢,衡量的是离散程度。金融里我们更常用标准差(方差的平方根),因为它和原始数据量纲一致。举个例子,某股票日收益率的标准差是0.02,意味着每天涨跌1-2个百分点是常态。

核心公式:
均值 μ = (1/N) * Σx_i
方差 σ² = (1/N) * Σ(x_i - μ)²
import numpy as np
import pandas as pd

# 生成模拟数据
np.random.seed(42)
returns = np.random.normal(0.001, 0.02, 1000)

# 计算统计量
mean_val = np.mean(returns)
var_val = np.var(returns, ddof=1)  # 样本方差,ddof=1
std_val = np.std(returns, ddof=1)

print(f"均值: {mean_val:.6f}")
print(f"方差: {var_val:.6f}")
print(f"标准差: {std_val:.6f}")
我的经验: 计算方差时,ddof参数很关键。ddof=0是总体方差,ddof=1是样本方差。金融数据通常用样本方差,因为我们是拿历史数据推断总体。

3. 最大值、最小值与极差:边界的力量

这三个特征,我经常用在风控因子里。最大值告诉你历史上最疯狂的时刻,最小值告诉你最绝望的时刻,极差就是两者之差,衡量了整个波动范围。

我曾经做过一个CTA策略,专门捕捉突破行情。核心逻辑就是:当价格突破过去20天的最大值时开多,跌破最小值时开空。这个策略在趋势行情里赚得盆满钵满,但在震荡市里被反复打脸。后来我加了极差条件——极差太小说明市场在窄幅震荡,这时候就不交易。

# 滚动窗口计算
window = 20
df = pd.DataFrame({'price': np.cumprod(1 + returns) * 100})

df['max_20'] = df['price'].rolling(window).max()
df['min_20'] = df['price'].rolling(window).min()
df['range_20'] = df['max_20'] - df['min_20']

print(df.head(25))
避坑指南: 我曾经犯过一个错误——直接用全局最大值做因子。结果呢?数据更新后,新的最大值出现,整个因子序列都要重算。正确的做法是用滚动窗口,或者用分位数代替极值,这样更稳健。

4. 偏度与峰度:分布的「形状」

这两个特征,很多初学者会跳过。但我告诉你,在因子合成里,它们往往是最有信息量的。

偏度衡量分布的不对称性。正偏度意味着右尾更长,也就是有更多极端正收益。负偏度则相反。在股票里,个股收益率的偏度通常为正,因为上涨的幅度可以很大(比如涨停),但下跌有跌停限制。

峰度衡量分布的「尖峭」程度。高峰度意味着分布有厚尾——极端值出现的概率比正态分布高。金融数据几乎都是高峰度的,这就是为什么「黑天鹅」事件频发。

计算公式:
偏度 = (1/N) * Σ((x_i - μ)/σ)³
峰度 = (1/N) * Σ((x_i - μ)/σ)⁴ - 3
(减3是为了让正态分布的峰度为0)
from scipy.stats import skew, kurtosis

skew_val = skew(returns)
kurt_val = kurtosis(returns, fisher=True)  # Fisher=True 表示减3

print(f"偏度: {skew_val:.4f}")
print(f"峰度: {kurt_val:.4f}")

# 判断分布形态
if skew_val > 0.5:
    print("右偏分布,正极端值较多")
elif skew_val < -0.5:
    print("左偏分布,负极端值较多")
else:
    print("近似对称分布")

if kurt_val > 1:
    print("高峰度,厚尾风险大")
elif kurt_val < -1:
    print("低峰度,分布较平坦")
else:
    print("峰度接近正态")
我的习惯: 在因子合成前,我会先看每个因子的偏度和峰度。如果某个因子的偏度绝对值大于2,或者峰度大于10,我会考虑做一下变换(比如取对数或Box-Cox变换),否则模型容易被极端值带偏。

5. 知识体系总览

下面这张图,是我自己整理的知识框架。你可以把它当作一个检查清单——每次拿到新数据,按这个顺序过一遍,基本不会漏掉重要信息。

时间序列统计特征知识体系 中心趋势 均值 (Mean) 离散程度 方差 / 标准差 最大值 / 最小值 / 极差 分布形状 偏度 (Skewness) 峰度 (Kurtosis) 应用场景:因子合成 / 风险控制 / 异常检测 提示:滚动窗口计算比全局计算更实用,能捕捉时序变化 偏度和峰度在金融数据中往往比均值方差更有信息量

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

最后,咱们来个实战。假设我们要构造一个「极端波动因子」——当价格出现极端波动时,这个因子值会变大。

def extreme_volatility_factor(price_series, window=20):
    """
    构造极端波动因子:
    结合极差、偏度、峰度的信息
    """
    # 计算收益率
    returns = price_series.pct_change().dropna()
    
    # 滚动窗口计算
    rolling_range = price_series.rolling(window).max() - price_series.rolling(window).min()
    rolling_skew = returns.rolling(window).skew()
    rolling_kurt = returns.rolling(window).kurt()
    
    # 标准化
    range_norm = (rolling_range - rolling_range.mean()) / rolling_range.std()
    skew_norm = (rolling_skew - rolling_skew.mean()) / rolling_skew.std()
    kurt_norm = (rolling_kurt - rolling_kurt.mean()) / rolling_kurt.std()
    
    # 合成因子:极差大 + 偏度极端 + 峰度高 = 极端波动
    factor = (range_norm.abs() + skew_norm.abs() + kurt_norm.abs()) / 3
    
    return factor

# 测试
price = pd.Series(np.cumprod(1 + returns) * 100)
factor = extreme_volatility_factor(price)
print(f"因子均值: {factor.mean():.4f}")
print(f"因子标准差: {factor.std():.4f}")
避坑指南: 合成因子时,一定要先标准化。我曾经直接把原始值加起来,结果极差因为量级大,主导了整个因子,偏度和峰度的信息完全被淹没了。标准化后,每个特征的贡献才均衡。

好了,今天的内容就到这。这些统计特征,看似简单,但用好了能解决很多实际问题。下次你拿到一个时间序列,不妨先算算这些量,心里就有底了。


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