Распределение доходностей и тест Шапиро-Уилка на Python

Распределение доходностей и тест Шапиро-Уилка на Python Анализ данных и Бэктесты
Пошаговое руководство по построению диаграммы распределения доходностей финансовых активов и проверке на нормальность с помощью теста Шапиро-Уилка в Python.
Суть: В финансовом анализе предположение о нормальности доходностей часто оказывается ложным. В этой инструкции мы загрузим исторические данные акций с помощью yfinance, рассчитаем логарифмические доходности, построим гистограмму распределения с наложенной теоретической кривой Гаусса и проведем строгий статистический тест Шапиро-Уилка (scipy.stats.shapiro) для проверки гипотезы о нормальности.

Введение в анализ распределения

В количественном анализе (Quantitative Finance) предположение о нормальном распределении доходностей активов лежит в основе многих классических моделей, включая портфельную теорию Марковица и расчет стоимости под риском (VaR). Однако реальные финансовые рынки характеризуются «тяжелыми хвостами» (fat tails) и асимметрией.

Перед тем как анализировать структуру временного ряда с помощью сложных методов, таких как Поиск ценовых паттернов с помощью Dynamic Time Warping (DTW) в Pandas или проводить Расчет показателя Херста (Hurst Exponent) для BTC/USDT на Python, кванту необходимо оценить базовые статистические свойства активов. Это критически важно при оценке рисков и расчете таких метрик, как в статье про Расчет Information Ratio, Alpha и Beta к Bitcoin на Python, где классические формулы часто неявно предполагают нормальность распределения. В этом руководстве мы разберем, как построить диаграмму распределения доходностей и провести строгий статистический тест Шапиро-Уилка на Python.

Исходный код

Ниже представлен готовый скрипт на Python, который загружает исторические данные индекса S&P 500 (SPY), рассчитывает логарифмические доходности, выполняет тест Шапиро-Уилка и строит визуализацию распределения в сравнении с идеальной нормальной кривой.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.stats as stats
import yfinance as yf

# 1. Загрузка исторических данных
ticker = 'SPY'
data = yf.download(ticker, start='2020-01-01', end='2023-12-31')

# Вычисляем логарифмические доходности
data['Log_Returns'] = np.log(data['Adj Close'] / data['Adj Close'].shift(1))
returns = data['Log_Returns'].dropna()

# 2. Тест Шапиро-Уилка на нормальность
# Нулевая гипотеза (H0): данные распределены нормально
shapiro_stat, p_value = stats.shapiro(returns)

print(f"Статистика теста Шапиро-Уилка: {shapiro_stat:.6f}")
print(f"p-value: {p_value:.6e}")

alpha = 0.05
if p_value > alpha:
    print("Вывод: Распределение близко к нормальному (не отклоняем H0)")
else:
    print("Вывод: Распределение НЕ является нормальным (отклоняем H0 на уровне значимости 5%)")

# 3. Визуализация распределения доходностей
plt.figure(figsize=(10, 6))

# Построение гистограммы фактических доходностей
count, bins, ignored = plt.hist(returns, bins=50, density=True, alpha=0.6, color='royalblue', edgecolor='black', label='Фактические доходности')

# Построение теоретической кривой нормального распределения
mu, std = stats.norm.fit(returns)
xmin, xmax = plt.xlim()
x = np.linspace(xmin, xmax, 100)
p = stats.norm.pdf(x, mu, std)
plt.plot(x, p, 'r-', linewidth=2, label=f'Нормальное распределение\n(mu={mu:.4f}, std={std:.4f})')

# Оформление графика
plt.title(f'Распределение доходностей {ticker} vs Нормальное распределение', fontsize=14)
plt.xlabel('Логарифмическая доходность', fontsize=12)
plt.ylabel('Плотность вероятности', fontsize=12)
plt.legend(loc='upper right')
plt.grid(True, linestyle='--', alpha=0.5)

# Добавление результатов теста на график
test_results_text = f'Тест Шапиро-Уилка:\nStat = {shapiro_stat:.4f}\np-value = {p_value:.2e}\nНормальность: {"Да" if p_value > alpha else "Нет"}'
plt.text(0.05, 0.95, test_results_text, transform=plt.gca().transAxes, fontsize=10,
         verticalalignment='top', bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))

plt.tight_layout()
plt.show()

Разбор параметров

  • stats.shapiro(returns): Функция из библиотеки scipy.stats, которая вычисляет статистику теста Шапиро-Уилка и соответствующее значение p-value. Это один из наиболее мощных критериев проверки на нормальность для непрерывных распределений.
  • p_value: Вероятность получить наблюдаемые (или еще более экстремальные) результаты при условии, что нулевая гипотеза о нормальности верна. Если p_value < 0.05, мы отвергаем гипотезу о нормальности.
  • stats.norm.fit(returns): Метод максимального правдоподобия, который оценивает параметры среднего значения (mu) и стандартного отклонения (std) для построения теоретической кривой Гаусса поверх нашей гистограммы.
  • np.log(data['Adj Close'] / data['Adj Close'].shift(1)): Расчет логарифмических доходностей. В отличие от простых доходностей, логарифмические доходности обладают свойством аддитивности во времени и лучше подходят для статистического моделирования.

Как запустить

Для запуска данного скрипта вам понадобятся установленные библиотеки для анализа данных и визуализации. Вы можете установить их одной командой через терминал:

pip install numpy pandas matplotlib scipy yfinance

После установки запустите скрипт в вашей среде разработки (например, VS Code, PyCharm или Jupyter Notebook). Скрипт автоматически загрузит актуальные котировки, проведет расчеты, выведет результаты теста в консоль и отобразит интерактивный график распределения.

Оцените статью
FinFluct