3. 描述性统计与可视化:收益率分布特征

好,咱们进入第三章。说实话,这一章是我个人觉得最有意思的部分之一。为什么?因为数据不会骗人,但数据会“说话”。你光看价格曲线,可能觉得市场挺温和的。但一旦你开始做描述性统计,画收益率分布图,很多隐藏的“脾气”就全暴露出来了。

我记得刚入行那会儿,带我的老大哥跟我说过一句话:“做量化,先学会看数据长什么样,再谈建模。” 这句话我一直记着。今天咱们就来聊聊,怎么用Python把收益率数据“扒光”,看看它到底长啥样。

3.1 收益率的基本计算

咱们先明确一个概念:我们分析的是收益率,不是价格。价格序列通常是非平稳的,直接拿来分析会出大问题。收益率分两种:简单收益率和对数收益率。我个人习惯用对数收益率,因为它具有可加性,而且更接近正态分布(虽然实际中差得远)。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats

# 假设 df 是包含价格数据的 DataFrame,列名为 'Close'
df['log_return'] = np.log(df['Close'] / df['Close'].shift(1))
df['simple_return'] = df['Close'].pct_change()

# 去掉缺失值
df = df.dropna()

嗯,这里要注意:shift(1) 会产生一个 NaN 值,记得用 dropna() 处理掉。我曾经在项目里忘了这步,结果后面画图时多了一个诡异的点,排查了半天才发现是缺失值搞的鬼。

3.2 收益率分布特征:偏度、峰度与JB检验

金融时间序列的收益率,说白了,很少是正态分布的。你想想看,如果收益率真的服从正态分布,那像2008年金融危机、2020年疫情这种极端行情,发生的概率几乎为零。但现实呢?它们就是发生了。

所以我们需要三个指标来量化这种“非正态性”:

  • 偏度(Skewness):衡量分布的不对称性。负偏度意味着左尾更长,也就是更容易出现极端亏损。
  • 峰度(Kurtosis):衡量分布的“尖胖”程度。金融数据的峰度通常大于3(正态分布的峰度为3),这叫“尖峰厚尾”。
  • JB检验(Jarque-Bera Test):综合检验分布是否符合正态分布。p值越小,越拒绝正态假设。

核心结论:绝大多数金融资产的收益率分布,都是负偏、尖峰、厚尾的。这意味着:极端亏损比极端盈利更常见,而且极端事件的发生概率远高于正态分布的预测。

# 计算偏度和峰度
skewness = df['log_return'].skew()
kurtosis = df['log_return'].kurtosis()  # 这是超额峰度,即峰度-3

print(f"偏度 (Skewness): {skewness:.4f}")
print(f"超额峰度 (Excess Kurtosis): {kurtosis:.4f}")

# JB检验
jb_stat, jb_pvalue = stats.jarque_bera(df['log_return'].dropna())
print(f"JB统计量: {jb_stat:.4f}")
print(f"JB p值: {jb_pvalue:.4e}")

我在项目中遇到过好几次这样的情况:明明策略回测表现很好,但一上实盘就崩。后来一查,发现回测期间的收益率偏度是正的,而实盘期间变成了负的。说白了,市场环境变了,但策略没变。所以,我建议你在做任何模型之前,先把偏度和峰度算清楚,心里有个底。

3.3 波动率聚集现象

波动率聚集,这是金融时间序列最经典的特征之一。什么意思呢?就是大的波动后面往往跟着大的波动,小的波动后面跟着小的波动。你可以把它理解成市场的“情绪惯性”——恐慌会传染,平静也会延续。

为什么会这样?因为信息是集群到达的。比如一个突发利空消息,市场需要一段时间来消化,这段时间内波动率就会持续偏高。等消息消化完了,波动率才会慢慢降下来。

个人经验:判断一个波动率模型好不好,最简单的办法就是看它能不能捕捉到波动率聚集现象。如果模型预测的波动率是均匀的,那基本可以扔掉了。

# 计算绝对收益率和平方收益率,用于观察波动率聚集
df['abs_return'] = np.abs(df['log_return'])
df['sq_return'] = df['log_return'] ** 2

# 绘制收益率与绝对收益率
fig, axes = plt.subplots(2, 1, figsize=(12, 8))

axes[0].plot(df.index, df['log_return'], color='steelblue', linewidth=0.8)
axes[0].set_title('日收益率序列', fontsize=14)
axes[0].set_ylabel('收益率')
axes[0].axhline(y=0, color='gray', linestyle='--', linewidth=0.5)

axes[1].plot(df.index, df['abs_return'], color='coral', linewidth=0.8)
axes[1].set_title('绝对收益率序列(波动率代理指标)', fontsize=14)
axes[1].set_ylabel('绝对收益率')
axes[1].set_xlabel('时间')

plt.tight_layout()
plt.show()

你看,绝对收益率序列有明显的“成簇”现象。高波动时期,绝对收益率的值普遍偏高;低波动时期,则普遍偏低。这就是波动率聚集的直观体现。

3.4 使用matplotlib绘制价格与收益率图

可视化是理解数据最直接的方式。我个人习惯把价格图和收益率图放在一起看,这样能快速判断当前市场处于什么状态。

fig, axes = plt.subplots(2, 1, figsize=(14, 10), sharex=True)

# 价格图
axes[0].plot(df.index, df['Close'], color='darkgreen', linewidth=1.2)
axes[0].set_title('价格走势', fontsize=15, fontweight='bold')
axes[0].set_ylabel('价格')
axes[0].grid(True, alpha=0.3)

# 收益率图
axes[1].plot(df.index, df['log_return'], color='navy', linewidth=0.7, alpha=0.8)
axes[1].set_title('对数收益率', fontsize=15, fontweight='bold')
axes[1].set_ylabel('收益率')
axes[1].set_xlabel('时间')
axes[1].axhline(y=0, color='red', linestyle='--', linewidth=0.8)
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

避坑指南:我曾经在画收益率图时,直接用默认的线宽和颜色,结果图一出来,密密麻麻的线条根本看不清。后来我养成了两个习惯:一是把线宽调细(0.5-0.8),二是把透明度调低(alpha=0.7左右)。这样既能看清整体趋势,又不会因为线条重叠而显得杂乱。

3.5 本章知识体系总览

下面这张图,是我自己总结的本章知识结构。你可以把它当成一个“地图”,随时回来对照。

第三章:描述性统计与可视化 收益率计算 简单收益率 对数收益率(推荐) 缺失值处理 分布特征分析 偏度(Skewness) 峰度(Kurtosis) JB检验 → 负偏、尖峰、厚尾 波动率聚集 大波动→大波动 小波动→小波动 绝对收益率作为代理 可视化工具:matplotlib + seaborn 价格走势图 收益率时序图 绝对收益率图 核心目标:理解数据特征,为建模打基础

3.6 小结

这一章咱们干了三件事:算收益率、看分布特征、画图。说白了,就是在正式建模之前,先跟数据“混个脸熟”。

我个人觉得,描述性统计这一步,再怎么强调都不过分。很多新手一上来就想着跑模型、调参数,结果模型跑出来一塌糊涂,回头一看,数据本身就有问题。嗯,这种坑我踩过不止一次。

你想想看,如果你连收益率是左偏还是右偏都不知道,连波动率有没有聚集效应都不清楚,你怎么敢把几百万的资金交给一个模型?所以,慢下来,先把数据看清楚。

一句话总结:金融收益率不是正态分布,它有偏、有峰、有聚集。承认这一点,是构建波动率模型的第一步。


专注资料整理