峰度和偏度这两个统计指标,在统计学上是非常重要的指标。在金融市场上,我们并不需要对其有深入了解,本文只是科普一些相关知识,重点是让大家明白峰度、偏度是什么以及通过这两个指标如何做到数据的 正态性检验

之所以金融市场上正态性检验如此重要,这是因为很多模型假设就是数据服从正态分布,因此我们在使用模型前应该对数据进行正态性检验,否则前面假设都没有满足,模型预测结果有何意义?

In [2]:
import matplotlib.pyplot as plt
import numpy as np
import scipy.stats as stats
import dai

有时候,平均值和方差不足以描述数据分布。 当我们计算方差时,我们对平均值的偏差进行了平方。 在偏差很大的情况下,我们不知道他们是否可能是积极的或消极的。 这里涉及到了分布的偏斜度和对称性。如果一个分布中,均值的一侧的部分是另一侧的镜子,则分布是对称的。 例如,正态分布是对称的。 平均值$\mu$和标准差$\sigma$的正态分布定义为:

$$ f(x) = \frac{1}{\sigma \sqrt{2 \pi}} e^{-\frac{(x - \mu)^2}{2 \sigma^2}} $$

我们可以绘制它来确认它是对称的:

In [8]:
xs = np.linspace(-6,6, 300)
normal = stats.norm.pdf(xs)
plt.plot(xs, normal);

偏度

偏度是描述数据分布形态的一个常用统计量,其描述的是某总体取值分布的对称性。这个统计量同样需要与正态分布相比较,偏度为0表示其数据分布形态与正态分布的偏斜程度相同;偏度大于0表示其数据分布形态与正态分布相比为正偏或右偏,即有一条长尾巴拖在右边,数据右端有较多的极端值;偏度小于0表示其数据分布形态与正态分布相比为负偏或左偏,即有一条长尾拖在左边,数据左端有较多的极端值。偏度的绝对值数值越大表示其分布形态的偏斜程度越大。

例如,分布可以具有许多小的正数和数个大的负值,这种情况是偏度为负,但仍然具有0的平均值,反之亦然(正偏度)。 对称分布的偏度0。正偏度分布中,平均值>中值>众数 。 负偏度刚好相反,平均值<中位数<众数。 在一个完全对称的分布中,即偏度为0,此时平均值=中位数=众数。

偏度的计算公式为:

$$ S_K = \frac{n}{(n-1)(n-2)} \frac{\sum_{i=1}^n (X_i - \mu)^3}{\sigma^3} $$

这里$n$所有观测值的个数, $\mu$是平均值, $\sigma$ 是标准差.

偏度的正负符号描述了数据分布的偏斜方向。

我们可以绘制一个正偏度和负偏度的分布,看看他们的样子。

对于单峰分布,负偏度通常表示尾部在左侧较大(长尾巴拖在左边),而正偏度表示尾部在右侧较大(长尾巴拖在右边)。

In [3]:
# 产生数据
xs2 = np.linspace(stats.lognorm.ppf(0.01, .7, loc=-.1), stats.lognorm.ppf(0.99, .7, loc=-.1), 150)

# 偏度>0
lognormal = stats.lognorm.pdf(xs2, .7)
plt.plot(xs2, lognormal, label='Skew > 0')

# 偏度<0
plt.plot(xs2, lognormal[::-1], label='Skew < 0')
plt.legend();

虽然在绘制离散数据集时,偏度不太明显,但我们仍然可以计算它。 例如,下面是2012-2014年沪深300收益率的偏度,平均值和中位数。

In [13]:
import numpy as np
import pandas as pd
from scipy import stats
import matplotlib.pyplot as plt

# 定义股票代码、开始日期和结束日期
instrument = '600519.SH'
start_date = '2020-01-01'
end_date = '2020-12-31'

# 使用 dai.query 执行查询,获取所需的原始数据
query = f"""
    SELECT date, close
    FROM cn_stock_bar1d
    WHERE instrument = '{instrument}'
    AND date BETWEEN '{start_date}' AND '{end_date}'
    ORDER BY date
"""
pricing = dai.query(query).df()

# 确保 'date' 列是 datetime 类型
pricing['date'] = pd.to_datetime(pricing['date'])

# 将 'date' 列设置为索引
pricing.set_index('date', inplace=True)

# 打印数据头部,检查数据是否正确
print(pricing.head())

# 计算每日收益率
returns = pricing['close'].pct_change().dropna()

# 打印收益率头部,检查是否有 NaN
print(returns.head())

# 移除所有可能的 NaN 值
returns = returns.dropna()

# 计算并打印 Skew, Mean 和 Median
print('Skew:', stats.skew(returns))
print('Mean:', np.mean(returns))
print('Median:', np.median(returns))

# 绘制收益率的直方图
plt.hist(returns, bins=30)
Out[13]:
(array([ 1.,  0.,  0.,  0.,  0.,  0.,  1.,  2.,  2.,  2.,  1.,  3.,  5.,
         8., 18., 15., 31., 30., 30., 16., 20., 17., 11., 10.,  3.,  7.,
         8.,  0.,  0.,  1.]),
 array([-0.07904572, -0.07457753, -0.07010934, -0.06564115, -0.06117296,
        -0.05670477, -0.05223658, -0.04776839, -0.0433002 , -0.03883201,
        -0.03436381, -0.02989562, -0.02542743, -0.02095924, -0.01649105,
        -0.01202286, -0.00755467, -0.00308648,  0.00138171,  0.0058499 ,
         0.01031809,  0.01478628,  0.01925447,  0.02372266,  0.02819086,
         0.03265905,  0.03712724,  0.04159543,  0.04606362,  0.05053181,
         0.055     ]),
 <BarContainer object of 30 artists>)

沪深300日收益率数据从图形上可以看出(但不是很明显),尾巴是拖在了右侧,因此有点右偏,这和计算的偏度值Skew=0.26为正刚好一致

峰度

峰度是描述总体中所有取值分布形态陡缓程度的统计量。这个统计量需要与正态分布相比较,峰度为3表示该总体数据分布与正态分布的陡缓程度相同;峰度大于3表示该总体数据分布与正态分布相比较为陡峭,为尖顶峰;峰度小于3表示该总体数据分布与正态分布相比较为平坦,为平顶峰。峰度的绝对值数值越大表示其分布形态的陡缓程度与正态分布的差异程度越大。

峰度的具体计算公式为:

$$ K = \frac{n(n+1)}{(n-1)(n-2)(n-3)} \frac{\sum_{i=1}^n (X_i - \mu)^4}{\sigma^4} $$

在Scipy中,使用峰度与正态分布峰度的差值来定义分布形态的陡缓程度——超额峰度,用$K_E$表示:

$$ K_E = \frac{n(n+1)}{(n-1)(n-2)(n-3)} \frac{\sum_{i=1}^n (X_i - \mu)^4}{\sigma^4} - \frac{3(n-1)^2}{(n-2)(n-3)} $$

如果数据量很大,那么 $$ K_E \approx \frac{1}{n} \frac{\sum_{i=1}^n (X_i - \mu)^4}{\sigma^4} - 3 $$

In [5]:
plt.plot(xs,stats.laplace.pdf(xs), label='Leptokurtic')
print('尖峰的超额峰度:', (stats.laplace.stats(moments='k')))
plt.plot(xs, normal, label='Mesokurtic (normal)')
print('正态分布超额峰度:', (stats.norm.stats(moments='k')))
plt.plot(xs,stats.cosine.pdf(xs), label='Platykurtic')
print('平峰超额峰度:', (stats.cosine.stats(moments='k')))
plt.legend();

接着沪深300的例子,我们可以使用Scipy包来计算沪深300日收益率的超额峰度

In [6]:
print("沪深300的超额峰度: ", stats.kurtosis(returns))

使用Jarque-Bera的正态检验

Jarque-Bera检验是一个通用的统计检验,可以比较样本数据是否具有与正态分布一样的偏度和峰度。 Jarque Bera检验的零假设是数据服从正态分布。 默认时p值为0.05。

接着上面沪深300的例子我们来检验沪深300收益率数据是否服从正态分布。

In [7]:
from statsmodels.stats.stattools import jarque_bera
_, pvalue, _, _ = jarque_bera(returns)

if pvalue > 0.05:
    print('沪深300收益率数据服从正态分布.')
else:
    print('沪深300收益率数据并不服从正态分布.')