Анализ статистических свойств распределения: асимметрия, эксцесс и нормальность

Когда я впервые столкнулся с реальными рыночными данными, меня поразило, насколько они отличаются от теоретических предположений классической финансовой теории. Распределения доходностей акций демонстрировали толстые хвосты, асимметрию и эксцесс, которые полностью противоречили предположениям о нормальности. Именно тогда я понял, что глубокое понимание статистических свойств распределений — это не академическая роскошь, а практическая необходимость для любой серьезной биржевой аналитики.

Асимметрия: скрытая опасность рыночных движений

Асимметрия (skewness) — это мера отклонения распределения от симметричности относительно среднего значения. В контексте финансовых рынков этот показатель приобретает особую важность, поскольку он показывает, насколько часто мы можем ожидать экстремальные движения в одном направлении по сравнению с другим.

  • Положительная асимметрия указывает на то, что распределение имеет длинный правый хвост — это означает, что крупные положительные отклонения встречаются чаще, чем крупные отрицательные;
  • Отрицательная асимметрия говорит об обратном: длинный левый хвост свидетельствует о более частых крупных отрицательных движениях.

Для трейдера это критически важная информация, поскольку она помогает понять, в какую сторону чаще происходят резкие движения цены.

Я заметил интересную закономерность: большинство индексов акций демонстрируют отрицательную асимметрию, что означает более частые и резкие падения по сравнению с ростом. Это объясняется психологическими особенностями рынка — паника продаж обычно происходит быстрее и интенсивнее, чем постепенный рост. Эта особенность имеет прямые практические последствия для управления рисками и построения торговых стратегий.

Давайте взглянем как выглядит ассиметрия распределения дневных доходностей индекса SP500.

import numpy as np
import pandas as pd
import yfinance as yf
from scipy import stats
import matplotlib.pyplot as plt
from scipy.stats import jarque_bera, normaltest
import warnings
warnings.filterwarnings('ignore')

# Получение данных по индексу S&P 500
ticker = "^GSPC"
data = yf.download(ticker, start="2020-01-01", end="2025-01-01")

# Сброс индекса и обработка мультииндекса
data = data.reset_index()
if isinstance(data.columns, pd.MultiIndex):
    data.columns = [col[0] if col[0] != '' else col[1] for col in data.columns]
data['Date'] = pd.to_datetime(data['Date'])
print("Длина данных:", len(data))
print("Колонки после загрузки из yfinance:", data.columns.tolist())

returns = data['Close'].pct_change().dropna()

# Расчет асимметрии
skewness = stats.skew(returns)
print(f"Асимметрия S&P 500: {skewness:.4f}")

# Визуализация распределения с показом асимметрии
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))

# Гистограмма доходностей
ax1.hist(returns, bins=50, density=True, alpha=0.7, color='darkgray', edgecolor='black')
ax1.axvline(returns.mean(), color='red', linestyle='--', linewidth=2, label=f'Среднее: {returns.mean():.4f}')
ax1.axvline(returns.median(), color='blue', linestyle='--', linewidth=2, label=f'Медиана: {returns.median():.4f}')
ax1.set_title('Распределение доходностей S&P 500')
ax1.set_xlabel('Доходность')
ax1.set_ylabel('Плотность')
ax1.legend()
ax1.grid(True, alpha=0.3)

# Q-Q plot для проверки нормальности
stats.probplot(returns, dist="norm", plot=ax2)
ax2.set_title('Q-Q plot: проверка нормальности')
ax2.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Сравнение с нормальным распределением
normal_sample = np.random.normal(returns.mean(), returns.std(), len(returns))
print(f"Асимметрия нормального распределения: {stats.skew(normal_sample):.4f}")
print(f"Разница в асимметрии: {abs(skewness - stats.skew(normal_sample)):.4f}")
Асимметрия S&P 500: -0.4794
Асимметрия нормального распределения: -0.1214
Разница в асимметрии: 0.3581

Распределение доходностей SP500 и визуализация ее нормальности через Q-Q plot

Рис. 1: Распределение доходностей SP500 и визуализация ее нормальности через Q-Q plot

Этот код демонстрирует фундаментальный подход к анализу асимметрии реальных финансовых данных. Мы видим, что асимметрия S&P 500 значительно отличается от нормального распределения, что подтверждает мою гипотезу о неприменимости классических статистических предположений к финансовым рынкам. Разница между средним и медианным значениями также указывает на наличие асимметрии — в нормальном распределении эти значения должны совпадать.

Практическое применение этого анализа заключается в корректировке стратегий управления рисками. Если мы знаем, что распределение имеет отрицательную асимметрию, мы можем адаптировать размеры позиций и уровни stop-loss для учета более высокой вероятности крупных потерь. Это особенно важно при использовании алгоритмических торговых систем, где автоматическое управление рисками должно учитывать реальные статистические свойства данных.

Эксцесс: мера экстремальности рыночных событий

Эксцесс (kurtosis) измеряет «тяжесть хвостов» распределения по сравнению с нормальным распределением. Этот показатель критически важен для понимания частоты экстремальных событий на рынке. Высокий эксцесс указывает на то, что экстремальные события происходят чаще, чем предсказывает нормальное распределение.

В финансовой практике различают несколько типов эксцесса:

  • Мезокуртическое распределение имеет эксцесс равный 3 (как у нормального распределения);
  • Лептокуртическое распределение характеризуется эксцессом больше 3, что означает более толстые хвосты и острый пик;
  • Платикуртическое распределение имеет эксцесс меньше 3, что соответствует более плоскому распределению с тонкими хвостами.

Мой опыт показывает, что большинство финансовых инструментов демонстрируют лептокуртическое поведение, что означает более частые экстремальные движения, чем предсказывает нормальная модель. Это имеет прямые последствия для оценки рисков и построения портфелей. Использование Value at Risk (VaR) моделей, основанных на предположении нормальности, может привести к существенной недооценке рисков.

Давайте рассмотрим как можно реализовать сравнение эксцессов различных активов с помощью Python. В качестве примера это будут акции Apple, Tesla, Bitcoin, Золото, индекс волатильности VIX, а анализировать мы будем эксцесс распределения их доходностей за последние 3 года.

# Расширенный анализ эксцесса для различных активов
tickers = ['AAPL', 'TSLA', 'BTC-USD', 'GLD', '^VIX']
kurtosis_data = {}

for ticker in tickers:
    try:
        data = yf.download(ticker, start="2022-06-01", end="2025-06-01", progress=False)
        # Сброс индекса и обработка мультииндекса
        data = data.reset_index()
        if isinstance(data.columns, pd.MultiIndex):
           data.columns = [col[0] if col[0] != '' else col[1] for col in data.columns] 
        data['Date'] = pd.to_datetime(data['Date']) 
        returns = data['Close'].pct_change().dropna()
        
        # Расчет различных мер эксцесса
        kurt_raw = stats.kurtosis(returns, fisher=False)  # Обычный эксцесс
        kurt_excess = stats.kurtosis(returns, fisher=True)  # Избыточный эксцесс
        
        kurtosis_data[ticker] = {
            'returns': returns,
            'kurtosis': kurt_raw,
            'excess_kurtosis': kurt_excess,
            'std': returns.std(),
            'mean': returns.mean()
        }
        
        print(f"{ticker}:")
        print(f"  Эксцесс: {kurt_raw:.4f}")
        print(f"  Избыточный эксцесс: {kurt_excess:.4f}")
        print(f"  Стандартное отклонение: {returns.std():.4f}")
        print(f"  Интерпретация: {'Лептокуртик' if kurt_raw > 3 else 'Платикуртик' if kurt_raw < 3 else 'Мезокуртик'}")
        print()
    except Exception as e:
        print(f"Ошибка при обработке {ticker}: {e}")

# Визуализация сравнения распределений
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
axes = axes.flatten()

for i, (ticker, data_dict) in enumerate(kurtosis_data.items()):
    if i < len(axes):
        returns = data_dict['returns']
        
        # Гистограмма с наложением нормального распределения
        axes[i].hist(returns, bins=50, density=True, alpha=0.7, color='darkgray', edgecolor='black')
        
        # Наложение нормального распределения для сравнения
        x = np.linspace(returns.min(), returns.max(), 100)
        normal_dist = stats.norm.pdf(x, returns.mean(), returns.std())
        axes[i].plot(x, normal_dist, 'r-', linewidth=2, label='Нормальное распределение')
        
        axes[i].set_title(f'{ticker}\nЭксцесс: {data_dict["kurtosis"]:.2f}')
        axes[i].set_xlabel('Доходность')
        axes[i].set_ylabel('Плотность')
        axes[i].legend()
        axes[i].grid(True, alpha=0.3)

# Удаление лишних осей
for i in range(len(kurtosis_data), len(axes)):
    fig.delaxes(axes[i])

plt.tight_layout()
plt.show()

# Статистический тест на нормальность
print("Тесты на нормальность:")
print("=" * 50)
for ticker, data_dict in kurtosis_data.items():
    returns = data_dict['returns']
    
    # Тест Харке-Бера
    jb_stat, jb_pvalue = jarque_bera(returns)
    
    # Тест Д'Агостино
    da_stat, da_pvalue = normaltest(returns)
    
    print(f"{ticker}:")
    print(f"  Тест Харке-Бера: статистика = {jb_stat:.4f}, p-value = {jb_pvalue:.6f}")
    print(f"  Тест Д'Агостино: статистика = {da_stat:.4f}, p-value = {da_pvalue:.6f}")
    print(f"  Вывод: {'Отклоняем гипотезу о нормальности' if jb_pvalue < 0.05 else 'Не отклоняем гипотезу о нормальности'}")
    print()
AAPL:
  Эксцесс: 12.0108
  Избыточный эксцесс: 9.0108
  Стандартное отклонение: 0.0181
  Интерпретация: Лептокуртик

TSLA:
  Эксцесс: 6.3353
  Избыточный эксцесс: 3.3353
  Стандартное отклонение: 0.0394
  Интерпретация: Лептокуртик

BTC-USD:
  Эксцесс: 7.0746
  Избыточный эксцесс: 4.0746
  Стандартное отклонение: 0.0270
  Интерпретация: Лептокуртик

GLD:
  Эксцесс: 3.9793
  Избыточный эксцесс: 0.9793
  Стандартное отклонение: 0.0097
  Интерпретация: Лептокуртик

^VIX:
  Эксцесс: 23.2235
  Избыточный эксцесс: 20.2235
  Стандартное отклонение: 0.0783
  Интерпретация: Лептокуртик

Диаграммы эксцессов распределений доходностей акций Apple, Tesla, Bitcoin, котировок Золота и индекса VIX

Рис. 2: Диаграммы эксцессов распределений доходностей акций Apple, Tesla, Bitcoin, котировок Золота и индекса VIX

Тесты на нормальность:
==================================================
AAPL:
  Тест Харке-Бера: статистика = 2594.2788, p-value = 0.000000
  Тест Д'Агостино: статистика = 180.6804, p-value = 0.000000
  Вывод: Отклоняем гипотезу о нормальности

TSLA:
  Тест Харке-Бера: статистика = 377.0205, p-value = 0.000000
  Тест Д'Агостино: статистика = 90.7879, p-value = 0.000000
  Вывод: Отклоняем гипотезу о нормальности

BTC-USD:
  Тест Харке-Бера: статистика = 758.7421, p-value = 0.000000
  Тест Д'Агостино: статистика = 109.6331, p-value = 0.000000
  Вывод: Отклоняем гипотезу о нормальности

GLD:
  Тест Харке-Бера: статистика = 30.8520, p-value = 0.000000
  Тест Д'Агостино: статистика = 16.6143, p-value = 0.000247
  Вывод: Отклоняем гипотезу о нормальности

^VIX:
  Тест Харке-Бера: статистика = 13618.0692, p-value = 0.000000
  Тест Д'Агостино: статистика = 500.0521, p-value = 0.000000
  Вывод: Отклоняем гипотезу о нормальности

Этот расширенный анализ показывает, насколько сильно различаются статистические свойства разных активов. Особенно интересно сравнение между традиционными активами (акции, золото) и криптовалютами или волатильностью (VIX). Криптовалюты обычно демонстрируют экстремально высокий эксцесс, что означает значительно более частые резкие движения цен.

Практическое значение этого анализа трудно переоценить. При построении портфелей и разработке торговых алгоритмов необходимо учитывать реальные статистические свойства активов. Модели, основанные на предположении нормальности, будут систематически недооценивать риски для активов с высоким эксцессом. Это особенно критично при использовании кредитного плеча или деривативов, где недооценка рисков может привести к катастрофическим потерям.

Глубокий анализ нормальности распределений

Проверка нормальности распределения — это краеугольный камень статистического анализа в финансах. Однако большинство аналитиков ограничиваются простыми визуальными методами или базовыми тестами, что может привести к неправильным выводам. В своей практике я использую комплексный подход, включающий несколько статистических тестов и продвинутые методы визуализации.

  • Тест Шапиро-Уилка считается одним из наиболее мощных тестов нормальности для небольших выборок, но он имеет ограничения при работе с большими датасетами;
  • Тест Харке-Бера основан на асимметрии и эксцессе и особенно полезен для финансовых данных;
  • Тест Колмогорова-Смирнова сравнивает эмпирическую функцию распределения с теоретической нормальной, но может быть менее чувствительным к отклонениям в хвостах.
Читайте также:  Хедж-фонды: Как они работают и за счет чего обгоняют индексы и классические инвестфонды

Критически важно понимать, что статистическая значимость не всегда означает практическую значимость. При работе с большими выборками даже незначительные отклонения от нормальности могут быть статистически значимыми, но не иметь практического значения для торговых решений. Наоборот, при малых выборках серьезные отклонения могут не достигать статистической значимости.

from scipy.stats import shapiro, kstest, anderson
import seaborn as sns
from scipy.optimize import minimize
from scipy.stats import t, genpareto, skewnorm

def comprehensive_normality_test(data, name="Dataset"):
    """
    Комплексный тест нормальности с множественными статистическими тестами
    """
    print(f"\n{'='*60}")
    print(f"КОМПЛЕКСНЫЙ АНАЛИЗ НОРМАЛЬНОСТИ: {name}")
    print(f"{'='*60}")
    
    # Базовые статистики
    print(f"Размер выборки: {len(data)}")
    print(f"Среднее: {np.mean(data):.6f}")
    print(f"Стандартное отклонение: {np.std(data):.6f}")
    print(f"Асимметрия: {stats.skew(data):.6f}")
    print(f"Эксцесс: {stats.kurtosis(data, fisher=False):.6f}")
    print(f"Избыточный эксцесс: {stats.kurtosis(data, fisher=True):.6f}")
    
    # Тесты нормальности
    print(f"\nТЕСТЫ НОРМАЛЬНОСТИ:")
    print(f"{'-'*40}")
    
    # Тест Шапиро-Уилка (для выборок < 5000)
    if len(data) < 5000:
        sw_stat, sw_pvalue = shapiro(data)
        print(f"Шапиро-Уилка: статистика = {sw_stat:.6f}, p-value = {sw_pvalue:.6f}")
    
    # Тест Харке-Бера
    jb_stat, jb_pvalue = jarque_bera(data)
    print(f"Харке-Бера: статистика = {jb_stat:.6f}, p-value = {jb_pvalue:.6f}")
    
    # Тест Д'Агостино
    da_stat, da_pvalue = normaltest(data)
    print(f"Д'Агостино: статистика = {da_stat:.6f}, p-value = {da_pvalue:.6f}")
    
    # Тест Колмогорова-Смирнова
    ks_stat, ks_pvalue = kstest(data, 'norm', args=(np.mean(data), np.std(data)))
    print(f"Колмогоров-Смирнов: статистика = {ks_stat:.6f}, p-value = {ks_pvalue:.6f}")
    
    # Тест Андерсона-Дарлинга
    ad_stat, ad_critical, ad_significance = anderson(data, dist='norm')
    print(f"Андерсон-Дарлинг: статистика = {ad_stat:.6f}")
    
    # Оценка отклонения от нормальности
    significance_level = 0.05
    tests_rejecting_normality = 0
    total_tests = 0
    
    if len(data) < 5000 and sw_pvalue < significance_level:
        tests_rejecting_normality += 1
    if len(data) < 5000:
        total_tests += 1
    
    if jb_pvalue < significance_level:
        tests_rejecting_normality += 1
    total_tests += 1
    
    if da_pvalue < significance_level:
        tests_rejecting_normality += 1
    total_tests += 1
    
    if ks_pvalue < significance_level: tests_rejecting_normality += 1 total_tests += 1 print(f"\nРЕЗУЛЬТАТ: {tests_rejecting_normality}/{total_tests} тестов отклоняют нормальность") if tests_rejecting_normality >= total_tests * 0.5:
        conclusion = "РАСПРЕДЕЛЕНИЕ НЕ НОРМАЛЬНОЕ"
        color = "red"
    else:
        conclusion = "РАСПРЕДЕЛЕНИЕ БЛИЗКО К НОРМАЛЬНОМУ"
        color = "green"
    
    print(f"ЗАКЛЮЧЕНИЕ: {conclusion}")
    
    return {
        'conclusion': conclusion,
        'tests_rejecting': tests_rejecting_normality,
        'total_tests': total_tests,
        'statistics': {
            'mean': np.mean(data),
            'std': np.std(data),
            'skewness': stats.skew(data),
            'kurtosis': stats.kurtosis(data, fisher=False),
            'excess_kurtosis': stats.kurtosis(data, fisher=True)
        }
    }

# Тестирование различных финансовых инструментов
test_instruments = {
    'S&P 500': '^GSPC',
    'Bitcoin': 'BTC-USD',
    'Apple': 'AAPL',
    'Tesla': 'TSLA',
    'Gold': 'GLD'
}

results = {}

for name, ticker in test_instruments.items():
    try:
        data = yf.download(ticker, start="2022-06-01", end="2025-06-01", progress=False)
        # Сброс индекса и обработка мультииндекса
        data = data.reset_index()
        if isinstance(data.columns, pd.MultiIndex):
           data.columns = [col[0] if col[0] != '' else col[1] for col in data.columns] 
        data['Date'] = pd.to_datetime(data['Date']) 
        returns = data['Close'].pct_change().dropna()
                
        result = comprehensive_normality_test(returns, name)
        results[name] = result
        
    except Exception as e:
        print(f"Ошибка при анализе {name}: {e}")

# Создание сводной таблицы результатов
print(f"\n{'='*80}")
print("СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ ТЕСТИРОВАНИЯ НОРМАЛЬНОСТИ")
print(f"{'='*80}")

summary_df = pd.DataFrame({
    'Инструмент': list(results.keys()),
    'Отклонение от нормальности': [f"{r['tests_rejecting']}/{r['total_tests']}" for r in results.values()],
    'Асимметрия': [f"{r['statistics']['skewness']:.4f}" for r in results.values()],
    'Избыточный эксцесс': [f"{r['statistics']['excess_kurtosis']:.4f}" for r in results.values()],
    'Заключение': [r['conclusion'] for r in results.values()]
})

print(summary_df.to_string(index=False))
============================================================
КОМПЛЕКСНЫЙ АНАЛИЗ НОРМАЛЬНОСТИ: S&P 500
============================================================
Размер выборки: 751
Среднее: 0.000550
Стандартное отклонение: 0.011261
Асимметрия: 0.421887
Эксцесс: 11.812320
Избыточный эксцесс: 8.812320

ТЕСТЫ НОРМАЛЬНОСТИ:
----------------------------------------
Шапиро-Уилка: статистика = 0.929503, p-value = 0.000000
Харке-Бера: статистика = 2452.294680, p-value = 0.000000
Д'Агостино: статистика = 153.885829, p-value = 0.000000
Колмогоров-Смирнов: статистика = 0.069042, p-value = 0.001476
Андерсон-Дарлинг: статистика = 7.009048

РЕЗУЛЬТАТ: 4/4 тестов отклоняют нормальность
ЗАКЛЮЧЕНИЕ: РАСПРЕДЕЛЕНИЕ НЕ НОРМАЛЬНОЕ

============================================================
КОМПЛЕКСНЫЙ АНАЛИЗ НОРМАЛЬНОСТИ: Bitcoin
============================================================
Размер выборки: 1095
Среднее: 0.001513
Стандартное отклонение: 0.027033
Асимметрия: 0.082924
Эксцесс: 7.074611
Избыточный эксцесс: 4.074611

ТЕСТЫ НОРМАЛЬНОСТИ:
----------------------------------------
Шапиро-Уилка: статистика = 0.938204, p-value = 0.000000
Харке-Бера: статистика = 758.742089, p-value = 0.000000
Д'Агостино: статистика = 109.633090, p-value = 0.000000
Колмогоров-Смирнов: статистика = 0.085032, p-value = 0.000000
Андерсон-Дарлинг: статистика = 17.918420

РЕЗУЛЬТАТ: 4/4 тестов отклоняют нормальность
ЗАКЛЮЧЕНИЕ: РАСПРЕДЕЛЕНИЕ НЕ НОРМАЛЬНОЕ

============================================================
КОМПЛЕКСНЫЙ АНАЛИЗ НОРМАЛЬНОСТИ: Apple
============================================================
Размер выборки: 751
Среднее: 0.000585
Стандартное отклонение: 0.018124
Асимметрия: 0.654278
Эксцесс: 12.010775
Избыточный эксцесс: 9.010775

ТЕСТЫ НОРМАЛЬНОСТИ:
----------------------------------------
Шапиро-Уилка: статистика = 0.921855, p-value = 0.000000
Харке-Бера: статистика = 2594.278792, p-value = 0.000000
Д'Агостино: статистика = 180.680403, p-value = 0.000000
Колмогоров-Смирнов: статистика = 0.076712, p-value = 0.000273
Андерсон-Дарлинг: статистика = 8.839323

РЕЗУЛЬТАТ: 4/4 тестов отклоняют нормальность
ЗАКЛЮЧЕНИЕ: РАСПРЕДЕЛЕНИЕ НЕ НОРМАЛЬНОЕ

============================================================
КОМПЛЕКСНЫЙ АНАЛИЗ НОРМАЛЬНОСТИ: Tesla
============================================================
Размер выборки: 751
Среднее: 0.001221
Стандартное отклонение: 0.039412
Асимметрия: 0.480725
Эксцесс: 6.335297
Избыточный эксцесс: 3.335297

ТЕСТЫ НОРМАЛЬНОСТИ:
----------------------------------------
Шапиро-Уилка: статистика = 0.965999, p-value = 0.000000
Харке-Бера: статистика = 377.020482, p-value = 0.000000
Д'Агостино: статистика = 90.787913, p-value = 0.000000
Колмогоров-Смирнов: статистика = 0.057032, p-value = 0.014517
Андерсон-Дарлинг: статистика = 4.161480

РЕЗУЛЬТАТ: 4/4 тестов отклоняют нормальность
ЗАКЛЮЧЕНИЕ: РАСПРЕДЕЛЕНИЕ НЕ НОРМАЛЬНОЕ

============================================================
КОМПЛЕКСНЫЙ АНАЛИЗ НОРМАЛЬНОСТИ: Gold
============================================================
Размер выборки: 751
Среднее: 0.000802
Стандартное отклонение: 0.009734
Асимметрия: 0.082089
Эксцесс: 3.979282
Избыточный эксцесс: 0.979282

ТЕСТЫ НОРМАЛЬНОСТИ:
----------------------------------------
Шапиро-Уилка: статистика = 0.990453, p-value = 0.000086
Харке-Бера: статистика = 30.851959, p-value = 0.000000
Д'Агостино: статистика = 16.614290, p-value = 0.000247
Колмогоров-Смирнов: статистика = 0.039438, p-value = 0.188198
Андерсон-Дарлинг: статистика = 1.852611

РЕЗУЛЬТАТ: 3/4 тестов отклоняют нормальность
ЗАКЛЮЧЕНИЕ: РАСПРЕДЕЛЕНИЕ НЕ НОРМАЛЬНОЕ

================================================================================
СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ ТЕСТИРОВАНИЯ НОРМАЛЬНОСТИ
================================================================================
Инструмент Отклонение от нормальности Асимметрия Избыточный эксцесс                  Заключение
   S&P 500                        4/4     0.4219             8.8123 РАСПРЕДЕЛЕНИЕ НЕ НОРМАЛЬНОЕ
   Bitcoin                        4/4     0.0829             4.0746 РАСПРЕДЕЛЕНИЕ НЕ НОРМАЛЬНОЕ
     Apple                        4/4     0.6543             9.0108 РАСПРЕДЕЛЕНИЕ НЕ НОРМАЛЬНОЕ
     Tesla                        4/4     0.4807             3.3353 РАСПРЕДЕЛЕНИЕ НЕ НОРМАЛЬНОЕ
      Gold                        3/4     0.0821             0.9793 РАСПРЕДЕЛЕНИЕ НЕ НОРМАЛЬНОЕ

Этот комплексный подход к тестированию нормальности предоставляет гораздо более надежную основу для принятия решений. Использование множественных тестов помогает избежать ложных выводов, которые могут возникнуть при использовании только одного теста. Каждый тест имеет свои сильные и слабые стороны, и только их комбинация дает полную картину.

Практическое применение результатов тестирования нормальности критически важно для выбора правильных статистических методов и моделей. Если данные не следуют нормальному распределению, использование параметрических тестов может привести к неверным выводам. В таких случаях необходимо применять непараметрические методы или трансформировать данные для приближения к нормальности.

Особенно важно понимать, что отклонение от нормальности в финансовых данных — это скорее правило, чем исключение. Это означает, что стандартные финансовые модели, основанные на предположении нормальности, требуют серьезных корректировок для адекватного отражения реальности рынков.

Альтернативные распределения для финансовых данных

Когда становится очевидно, что данные не следуют нормальному распределению, критически важно найти подходящую альтернативу. В финансовой практике наиболее часто используются t-распределение Стьюдента для учета толстых хвостов, скошенное нормальное распределение для асимметричных данных, и различные смешанные модели для сложных случаев.

T-распределение особенно полезно для моделирования доходностей активов с толстыми хвостами. Оно имеет дополнительный параметр — количество степеней свободы, который контролирует «толщину» хвостов. При увеличении степеней свободы t-распределение приближается к нормальному, а при их уменьшении хвосты становятся толще.

Скошенное t-распределение комбинирует преимущества обоих подходов — оно может моделировать как асимметрию, так и толстые хвосты одновременно. Это делает его особенно привлекательным для финансовых приложений, где оба эффекта присутствуют одновременно.

from scipy.stats import t, skewnorm, genextreme
from scipy.optimize import minimize
import warnings
warnings.filterwarnings('ignore')

class DistributionFitter:
    """
    Класс для подбора оптимального распределения к финансовым данным
    """
    
    def __init__(self, data):
        self.data = np.array(data)
        self.fitted_distributions = {}
        
    def fit_normal(self):
        """Подгонка нормального распределения"""
        mu, sigma = stats.norm.fit(self.data)
        aic = self._calculate_aic(stats.norm, (mu, sigma))
        bic = self._calculate_bic(stats.norm, (mu, sigma))
        
        self.fitted_distributions['normal'] = {
            'params': (mu, sigma),
            'aic': aic,
            'bic': bic,
            'distribution': stats.norm
        }
        
    def fit_t_distribution(self):
        """Подгонка t-распределения"""
        df, loc, scale = stats.t.fit(self.data)
        aic = self._calculate_aic(stats.t, (df, loc, scale))
        bic = self._calculate_bic(stats.t, (df, loc, scale))
        
        self.fitted_distributions['t'] = {
            'params': (df, loc, scale),
            'aic': aic,
            'bic': bic,
            'distribution': stats.t
        }
        
    def fit_skewed_normal(self):
        """Подгонка скошенного нормального распределения"""
        a, loc, scale = stats.skewnorm.fit(self.data)
        aic = self._calculate_aic(stats.skewnorm, (a, loc, scale))
        bic = self._calculate_bic(stats.skewnorm, (a, loc, scale))
        
        self.fitted_distributions['skewnorm'] = {
            'params': (a, loc, scale),
            'aic': aic,
            'bic': bic,
            'distribution': stats.skewnorm
        }
        
    def fit_generalized_extreme_value(self):
        """Подгонка обобщенного распределения экстремальных значений"""
        c, loc, scale = stats.genextreme.fit(self.data)
        aic = self._calculate_aic(stats.genextreme, (c, loc, scale))
        bic = self._calculate_bic(stats.genextreme, (c, loc, scale))
        
        self.fitted_distributions['genextreme'] = {
            'params': (c, loc, scale),
            'aic': aic,
            'bic': bic,
            'distribution': stats.genextreme
        }
        
    def _calculate_aic(self, distribution, params):
        """Расчет информационного критерия Акаикэ"""
        try:
            log_likelihood = np.sum(distribution.logpdf(self.data, *params))
            k = len(params)
            n = len(self.data)
            return 2 * k - 2 * log_likelihood
        except:
            return np.inf
            
    def _calculate_bic(self, distribution, params):
        """Расчет байесовского информационного критерия"""
        try:
            log_likelihood = np.sum(distribution.logpdf(self.data, *params))
            k = len(params)
            n = len(self.data)
            return k * np.log(n) - 2 * log_likelihood
        except:
            return np.inf
            
    def fit_all_distributions(self):
        """Подгонка всех распределений"""
        self.fit_normal()
        self.fit_t_distribution()
        self.fit_skewed_normal()
        self.fit_generalized_extreme_value()
        
    def get_best_distribution(self, criterion='aic'):
        """Получение лучшего распределения по выбранному критерию"""
        if not self.fitted_distributions:
            self.fit_all_distributions()
            
        best_dist = min(self.fitted_distributions.items(), 
                       key=lambda x: x[1][criterion])
        return best_dist
        
    def compare_distributions(self):
        """Сравнение всех подогнанных распределений"""
        if not self.fitted_distributions:
            self.fit_all_distributions()
            
        comparison = pd.DataFrame({
            'Распределение': list(self.fitted_distributions.keys()),
            'AIC': [d['aic'] for d in self.fitted_distributions.values()],
            'BIC': [d['bic'] for d in self.fitted_distributions.values()],
            'Параметры': [str(d['params']) for d in self.fitted_distributions.values()]
        })
        
        comparison = comparison.sort_values('AIC')
        return comparison

# Применение к реальным данным
ticker = "BTC-USD"
data = yf.download(ticker, start="2022-01-01", end="2024-01-01", progress=False)
returns = data['Close'].pct_change().dropna()

# Подгонка распределений
fitter = DistributionFitter(returns)
comparison_table = fitter.compare_distributions()

print("СРАВНЕНИЕ РАСПРЕДЕЛЕНИЙ ДЛЯ BITCOIN:")
print("="*60)
print(comparison_table.to_string(index=False))

# Получение лучшего распределения
best_name, best_info = fitter.get_best_distribution('aic')
print(f"\nЛучшее распределение по AIC: {best_name}")
print(f"Параметры: {best_info['params']}")

# Визуализация сравнения
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
axes = axes.flatten()

colors = ['red', 'blue', 'green', 'orange']
dist_names = ['normal', 't', 'skewnorm', 'genextreme']
dist_labels = ['Нормальное', 'T-распределение', 'Скошенное нормальное', 'Обобщ. экстремальное']

for i, (dist_name, label, color) in enumerate(zip(dist_names, dist_labels, colors)):
    if dist_name in fitter.fitted_distributions:
        ax = axes[i]
        
        # Гистограмма данных
        ax.hist(returns, bins=50, density=True, alpha=0.7, color='lightgray', 
               edgecolor='black', label='Данные')
        
        # Подогнанное распределение
        dist_info = fitter.fitted_distributions[dist_name]
        x = np.linspace(returns.min(), returns.max(), 1000)
        pdf = dist_info['distribution'].pdf(x, *dist_info['params'])
        ax.plot(x, pdf, color=color, linewidth=3, label=f'{label}\nAIC: {dist_info["aic"]:.2f}')
       
        ax.set_title(f'{label}')
        ax.set_xlabel('Доходность')
        ax.set_ylabel('Плотность')
        ax.legend()
        ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Детальный анализ лучшего распределения
print(f"\nДЕТАЛЬНЫЙ АНАЛИЗ ЛУЧШЕГО РАСПРЕДЕЛЕНИЯ ({best_name.upper()}):")
print("="*60)

best_dist = best_info['distribution']
best_params = best_info['params']

# Расчет квантилей для оценки рисков
quantiles = [0.01, 0.05, 0.1, 0.9, 0.95, 0.99]
print("Квантили распределения:")
for q in quantiles:
   value = best_dist.ppf(q, *best_params)
   print(f"  {q*100:4.1f}%: {value:.6f}")

# Сравнение с эмпирическими квантилями
print("\nСравнение с эмпирическими квантилями:")
for q in quantiles:
   theoretical = best_dist.ppf(q, *best_params)
   empirical = np.percentile(returns, q*100)
   diff = abs(theoretical - empirical)
   print(f"  {q*100:4.1f}%: теор={theoretical:.6f}, эмпир={empirical:.6f}, разность={diff:.6f}")
СРАВНЕНИЕ РАСПРЕДЕЛЕНИЙ ДЛЯ BITCOIN:
============================================================
Распределение          AIC          BIC                                                                                               Параметры
            t -3309.375885 -3295.600863 (np.float64(2.2407027936597563), np.float64(-0.00016071984767432836), np.float64(0.015396198240798808))
     skewnorm -3109.541649 -3095.766627   (np.float64(-0.8290644596240175), np.float64(0.017063899532344627), np.float64(0.033203049800901635))
       normal -3107.946080 -3098.762733                                  (np.float64(0.00024623794924590416), np.float64(0.028629080466488592))
   genextreme -1044.348452 -1030.573430    (np.float64(1.2527768866732307), np.float64(-0.010311235401929957), np.float64(0.19508626869404289))

Лучшее распределение по AIC: t
Параметры: (np.float64(2.2407027936597563), np.float64(-0.00016071984767432836), np.float64(0.015396198240798808))

Сравнение распределений доходностей Bitcoin: нормального, T-Стьюдента, скошенного нормального, обобщенного экстремального

Рис. 3: Сравнение распределений доходностей Bitcoin: нормального, T-Стьюдента, скошенного нормального, обобщенного экстремального

ДЕТАЛЬНЫЙ АНАЛИЗ ЛУЧШЕГО РАСПРЕДЕЛЕНИЯ (T):
============================================================
Квантили распределения:
   1.0%: -0.093042
   5.0%: -0.041991
  10.0%: -0.027867
  90.0%: 0.027546
  95.0%: 0.041670
  99.0%: 0.092720

Сравнение с эмпирическими квантилями:
   1.0%: теор=-0.093042, эмпир=-0.088773, разность=0.004269
   5.0%: теор=-0.041991, эмпир=-0.042452, разность=0.000461
  10.0%: теор=-0.027867, эмпир=-0.028265, разность=0.000398
  90.0%: теор=0.027546, эмпир=0.029159, разность=0.001614
  95.0%: теор=0.041670, эмпир=0.046502, разность=0.004832
  99.0%: теор=0.092720, эмпир=0.089522, разность=0.003199

Этот комплексный анализ подбора распределений демонстрирует принципиально важный подход к моделированию финансовых данных. Использование информационных критериев AIC и BIC позволяет объективно сравнивать модели с разным количеством параметров, избегая переобучения. Криптовалюты, как правило, лучше описываются t-распределением или обобщенным распределением экстремальных значений, что отражает их высокую волатильность и частые экстремальные движения.

Читайте также:  Основы диверсификации биржевых портфелей

Практическое значение правильного выбора распределения невозможно переоценить. Неправильная модель может привести к существенным ошибкам в оценке рисков, особенно в хвостах распределения. Это критически важно для расчета Value at Risk, Expected Shortfall и других мер риска, которые активно используются в риск-менеджменте.

Сравнение теоретических и эмпирических квантилей позволяет оценить качество подгонки в наиболее важных областях распределения — в хвостах, где сосредоточены экстремальные риски. Хорошая модель должна точно воспроизводить эмпирические квантили, особенно в области малых вероятностей.

Продвинутые методы анализа статистических свойств

Временная изменчивость статистических характеристик

Одна из ключевых ошибок, которую я наблюдаю в анализе финансовых данных, — это предположение о стационарности статистических свойств. В реальности асимметрия, эксцесс и другие характеристики распределений могут значительно изменяться во времени. Эта нестационарность имеет критическое значение для построения адаптивных торговых систем и динамического управления рисками.

Концепция скользящих окон позволяет отслеживать эволюцию статистических свойств во времени. Однако выбор размера окна представляет компромисс между стабильностью оценок (большие окна) и чувствительностью к изменениям (малые окна). В своей практике я использую адаптивные методы, которые автоматически корректируют размер окна в зависимости от рыночных условий.

Особенно интересным является анализ режимных переключений в статистических свойствах. Периоды высокой волатильности часто сопровождаются изменением асимметрии и эксцесса, что требует адаптации торговых стратегий. Модели скрытых марковских процессов и переключения режимов позволяют формализовать этот подход.

import pandas as pd
from scipy.stats import jarque_bera
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
import matplotlib.dates as mdates

class TimeVaryingDistributionAnalyzer:
    """
    Анализатор временной изменчивости статистических свойств
    """
    
    def __init__(self, data, window_sizes=[30, 60, 120, 252]):
        self.data = data
        self.window_sizes = window_sizes
        self.results = {}
        
    def calculate_rolling_statistics(self, window_size):
        """Расчет скользящих статистических характеристик"""
        rolling_stats = pd.DataFrame(index=self.data.index)
        
        # Скользящие характеристики
        rolling_stats['mean'] = self.data.rolling(window=window_size).mean()
        rolling_stats['std'] = self.data.rolling(window=window_size).std()
        rolling_stats['skewness'] = self.data.rolling(window=window_size).skew()
        rolling_stats['kurtosis'] = self.data.rolling(window=window_size).kurt()
        
        # Скользящий тест на нормальность
        rolling_stats['jb_statistic'] = np.nan
        rolling_stats['jb_pvalue'] = np.nan
        
        for i in range(window_size, len(self.data)):
            window_data = self.data.iloc[i-window_size:i]
            if len(window_data.dropna()) >= 10:  # Минимум данных для теста
                jb_stat, jb_pval = jarque_bera(window_data.dropna())
                rolling_stats.iloc[i, rolling_stats.columns.get_loc('jb_statistic')] = jb_stat
                rolling_stats.iloc[i, rolling_stats.columns.get_loc('jb_pvalue')] = jb_pval
        
        return rolling_stats.dropna()
    
    def analyze_regime_changes(self, window_size=60):
        """Анализ изменений режимов в статистических свойствах"""
        rolling_stats = self.calculate_rolling_statistics(window_size)
        
        # Подготовка данных для кластеризации
        features = rolling_stats[['std', 'skewness', 'kurtosis']].dropna()
        scaler = StandardScaler()
        features_scaled = scaler.fit_transform(features)
        
        # Кластеризация для выделения режимов
        n_clusters = 3  # Низкая, средняя, высокая волатильность
        kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)
        regimes = kmeans.fit_predict(features_scaled)
        
        # Добавление режимов к данным
        regime_df = pd.DataFrame({
            'regime': regimes,
            'volatility': features['std'],
            'skewness': features['skewness'],
            'kurtosis': features['kurtosis']
        }, index=features.index)
        
        return regime_df, kmeans.cluster_centers_
    
    def detect_statistical_breaks(self, window_size=60, threshold=2.0):
        """Обнаружение структурных сдвигов в статистических свойствах"""
        rolling_stats = self.calculate_rolling_statistics(window_size)
        
        breaks = pd.DataFrame(index=rolling_stats.index)
        
        # Обнаружение сдвигов в различных характеристиках
        for column in ['std', 'skewness', 'kurtosis']:
            series = rolling_stats[column]
            
            # Расчет z-score изменений
            changes = series.diff().abs()
            z_scores = (changes - changes.mean()) / changes.std()
            
            # Маркировка значительных изменений
            breaks[f'{column}_break'] = z_scores > threshold
            breaks[f'{column}_z_score'] = z_scores
        
        return breaks
    
    def comprehensive_analysis(self):
        """Комплексный анализ временной изменчивости"""
        results = {}
        
        for window_size in self.window_sizes:
            print(f"Анализ для окна {window_size} дней...")
            
            # Скользящие статистики
            rolling_stats = self.calculate_rolling_statistics(window_size)
            
            # Статистики изменчивости
            variability_stats = {
                'std_variability': rolling_stats['std'].std(),
                'skewness_variability': rolling_stats['skewness'].std(),
                'kurtosis_variability': rolling_stats['kurtosis'].std(),
                'mean_jb_pvalue': rolling_stats['jb_pvalue'].mean(),
                'normality_rejection_rate': (rolling_stats['jb_pvalue'] < 0.05).mean()
            }
            
            results[window_size] = {
                'rolling_stats': rolling_stats,
                'variability': variability_stats
            }
        
        return results

# Применение к данным S&P 500
sp500_data = yf.download("^GSPC", start="2020-01-01", end="2024-01-01", progress=False)
sp500_returns = sp500_data['Close'].pct_change().dropna()

analyzer = TimeVaryingDistributionAnalyzer(sp500_returns)
results = analyzer.comprehensive_analysis()

# Анализ режимов
regime_data, cluster_centers = analyzer.analyze_regime_changes(window_size=60)

print("АНАЛИЗ ВРЕМЕННОЙ ИЗМЕНЧИВОСТИ СТАТИСТИЧЕСКИХ СВОЙСТВ S&P 500:")
print("="*70)

# Вывод результатов для разных размеров окон
for window_size, data in results.items():
    variability = data['variability']
    print(f"\nОкно {window_size} дней:")
    print(f"  Изменчивость волатильности: {variability['std_variability']:.6f}")
    print(f"  Изменчивость асимметрии: {variability['skewness_variability']:.6f}")
    print(f"  Изменчивость эксцесса: {variability['kurtosis_variability']:.6f}")
    print(f"  Средний p-value теста Харке-Бера: {variability['mean_jb_pvalue']:.6f}")
    print(f"  Доля отклонений от нормальности: {variability['normality_rejection_rate']:.2%}")

# Визуализация временной эволюции
fig, axes = plt.subplots(4, 1, figsize=(15, 16))

window_size = 60
rolling_stats = results[window_size]['rolling_stats']

# График волатильности
axes[0].plot(rolling_stats.index, rolling_stats['std'], color='black', linewidth=1.5)
axes[0].set_title('Скользящая волатильность (60 дней)')
axes[0].set_ylabel('Стандартное отклонение')
axes[0].grid(True, alpha=0.3)

# График асимметрии
axes[1].plot(rolling_stats.index, rolling_stats['skewness'], color='darkblue', linewidth=1.5)
axes[1].axhline(y=0, color='red', linestyle='--', alpha=0.7)
axes[1].set_title('Скользящая асимметрия (60 дней)')
axes[1].set_ylabel('Асимметрия')
axes[1].grid(True, alpha=0.3)

# График эксцесса
axes[2].plot(rolling_stats.index, rolling_stats['kurtosis'], color='darkgreen', linewidth=1.5)
axes[2].axhline(y=0, color='red', linestyle='--', alpha=0.7, label='Нормальное распределение')
axes[2].set_title('Скользящий избыточный эксцесс (60 дней)')
axes[2].set_ylabel('Избыточный эксцесс')
axes[2].legend()
axes[2].grid(True, alpha=0.3)

# График режимов
regime_colors = ['red', 'blue', 'green']
for regime in range(3):
    mask = regime_data['regime'] == regime
    axes[3].scatter(regime_data.index[mask], regime_data['volatility'][mask], 
                   c=regime_colors[regime], alpha=0.6, s=20, 
                   label=f'Режим {regime + 1}')

axes[3].set_title('Режимы рыночной волатильности')
axes[3].set_ylabel('Волатильность')
axes[3].set_xlabel('Дата')
axes[3].legend()
axes[3].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Анализ структурных сдвигов
breaks = analyzer.detect_statistical_breaks(window_size=60, threshold=2.0)
significant_breaks = breaks[
    (breaks['std_break']) | 
    (breaks['skewness_break']) | 
    (breaks['kurtosis_break'])
]

print(f"\nОБНАРУЖЕНО СТРУКТУРНЫХ СДВИГОВ: {len(significant_breaks)}")
print("Даты значительных изменений:")
for date in significant_breaks.index[:10]:  # Показать первые 10
    print(f"  {date.strftime('%Y-%m-%d')}")
Анализ для окна 30 дней...
Анализ для окна 60 дней...
Анализ для окна 120 дней...
Анализ для окна 252 дней...
АНАЛИЗ ВРЕМЕННОЙ ИЗМЕНЧИВОСТИ СТАТИСТИЧЕСКИХ СВОЙСТВ S&P 500:
======================================================================

Окно 30 дней:
  Изменчивость волатильности: 0.008058
  Изменчивость асимметрии: 0.578185
  Изменчивость эксцесса: 1.463374
  Средний p-value теста Харке-Бера: 0.534141
  Доля отклонений от нормальности: 10.15%

Окно 60 дней:
  Изменчивость волатильности: 0.007125
  Изменчивость асимметрии: 0.523779
  Изменчивость эксцесса: 1.628048
  Средний p-value теста Харке-Бера: 0.379342
  Доля отклонений от нормальности: 21.48%

Окно 120 дней:
  Изменчивость волатильности: 0.005222
  Изменчивость асимметрии: 0.430377
  Изменчивость эксцесса: 1.344152
  Средний p-value теста Харке-Бера: 0.278983
  Доля отклонений от нормальности: 42.26%

Окно 252 дней:
  Изменчивость волатильности: 0.003414
  Изменчивость асимметрии: 0.405722
  Изменчивость эксцесса: 2.079970
  Средний p-value теста Харке-Бера: 0.113898
  Доля отклонений от нормальности: 68.26%

Динамика скользящей волатильности SP500 в сравнении со скользящей асимметрией, скользящим избыточным эксцессом и режимами рыночной волатильности

Рис. 4: Динамика скользящей волатильности SP500 в сравнении со скользящей асимметрией, скользящим избыточным эксцессом и режимами рыночной волатильности

ОБНАРУЖЕНО СТРУКТУРНЫХ СДВИГОВ: 35
Даты значительных изменений:
  2020-04-06
  2020-06-03
  2020-06-08
  2020-06-09
  2020-06-10
  2020-06-12
  2020-06-18
  2020-06-22
  2020-07-01
  2020-07-16

Этот анализ временной изменчивости выявляет критически важные закономерности, которые часто игнорируются в стандартных подходах. Мы видим, что статистические свойства распределений действительно изменяются во времени, и эти изменения часто совпадают с крупными рыночными событиями. Периоды кризисов (в данном случае COVID-ный 2020 год) характеризуются не только повышенной волатильностью, но и изменением асимметрии и эксцесса.

Практическое применение этого анализа включает создание адаптивных систем управления рисками, которые автоматически корректируют параметры в зависимости от текущего рыночного режима. Обнаружение структурных сдвигов может служить сигналом для пересмотра торговых стратегий или портфельных весов.

Особенно важно понимать, что модели, основанные на исторических данных, могут быть неприменимы в периоды режимных переключений. Это подчеркивает необходимость использования адаптивных подходов и постоянного мониторинга статистических свойств данных.

Многомерный анализ статистических зависимостей

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

Копулы представляют собой мощный инструмент для моделирования зависимостей между переменными независимо от их маргинальных распределений. Это особенно важно в финансах, где различные активы могут иметь различные распределения, но при этом демонстрировать сложные зависимости в хвостах распределений.

Анализ хвостовых зависимостей критически важен для понимания поведения портфеля в экстремальных условиях. Многие активы демонстрируют асимметричные зависимости — более сильную корреляцию во время падений рынка по сравнению с ростом. Это явление, известное как «contagion effect», имеет прямые последствия для диверсификации портфеля.

from scipy.stats import kendalltau, spearmanr
from scipy.optimize import minimize
import numpy as np
from scipy.stats import multivariate_normal
import seaborn as sns

class MultivariateDistributionAnalyzer:
    """
    Анализатор многомерных статистических свойств финансовых данных
    """
    
    def __init__(self, data):
        self.data = data
        self.assets = data.columns.tolist()
        
    def calculate_correlation_matrix(self, method='pearson'):
        """Расчет корреляционной матрицы различными методами"""
        if method == 'pearson':
            return self.data.corr(method='pearson')
        elif method == 'spearman':
            return self.data.corr(method='spearman')
        elif method == 'kendall':
            return self.data.corr(method='kendall')
        else:
            raise ValueError("Метод должен быть 'pearson', 'spearman' или 'kendall'")
    
    def analyze_tail_dependence(self, quantile=0.05):
        """Анализ хвостовых зависимостей"""
        n_assets = len(self.assets)
        lower_tail_dep = np.zeros((n_assets, n_assets))
        upper_tail_dep = np.zeros((n_assets, n_assets))
        
        for i in range(n_assets):
            for j in range(i, n_assets):
                if i == j:
                    lower_tail_dep[i, j] = 1.0
                    upper_tail_dep[i, j] = 1.0
                else:
                    asset1 = self.data.iloc[:, i]
                    asset2 = self.data.iloc[:, j]
                    
                    # Нижняя хвостовая зависимость
                    threshold1_low = asset1.quantile(quantile)
                    threshold2_low = asset2.quantile(quantile)
                    
                    both_low = ((asset1 <= threshold1_low) & (asset2 <= threshold2_low)).sum()
                    either_low = ((asset1 <= threshold1_low) | (asset2 <= threshold2_low)).sum() if either_low > 0:
                        lower_tail_dep[i, j] = both_low / either_low
                        lower_tail_dep[j, i] = lower_tail_dep[i, j]
                    
                    # Верхняя хвостовая зависимость
                    threshold1_high = asset1.quantile(1 - quantile)
                    threshold2_high = asset2.quantile(1 - quantile)
                    
                    both_high = ((asset1 >= threshold1_high) & (asset2 >= threshold2_high)).sum()
                    either_high = ((asset1 >= threshold1_high) | (asset2 >= threshold2_high)).sum()
                    
                    if either_high > 0:
                        upper_tail_dep[i, j] = both_high / either_high
                        upper_tail_dep[j, i] = upper_tail_dep[i, j]
        
        return lower_tail_dep, upper_tail_dep
    
    def test_multivariate_normality(self):
        """Тест многомерной нормальности"""
        from scipy.stats import chi2
        
        # Стандартизация данных
        standardized_data = (self.data - self.data.mean()) / self.data.std()
        
        # Расчет статистики Марди для асимметрии
        n, p = standardized_data.shape
        
        # Статистика для эксцесса
        sample_cov = np.cov(standardized_data.T)
        inv_cov = np.linalg.inv(sample_cov)
        
        mahalanobis_distances = []
        for i in range(n):
            diff = standardized_data.iloc[i].values - standardized_data.mean().values
            md = np.sqrt(np.dot(np.dot(diff, inv_cov), diff.T))
            mahalanobis_distances.append(md)
        
        mahalanobis_distances = np.array(mahalanobis_distances)
        
        # Тест на многомерную нормальность (упрощенный)
        # Расстояния Махаланобиса должны следовать распределению хи-квадрат
        expected_quantiles = chi2.ppf(np.linspace(0.01, 0.99, 99), df=p)
        observed_quantiles = np.percentile(mahalanobis_distances**2, np.linspace(1, 99, 99))
        
        # Корреляция между ожидаемыми и наблюдаемыми квантилями
        correlation = np.corrcoef(expected_quantiles, observed_quantiles)[0, 1]
        
        return {
            'mahalanobis_distances': mahalanobis_distances,
            'qq_correlation': correlation,
            'is_multivariate_normal': correlation > 0.95  # Пороговое значение
        }
    
    def analyze_regime_dependent_correlations(self, volatility_threshold=0.02):
        """Анализ корреляций в зависимости от рыночных режимов"""
        # Расчет средней волатильности
        volatilities = self.data.rolling(window=20).std().mean(axis=1)
        
        # Разделение на режимы высокой и низкой волатильности
        high_vol_mask = volatilities > volatilities.quantile(0.7)
        low_vol_mask = volatilities < volatilities.quantile(0.3)
        
        correlations = {}
        correlations['all_periods'] = self.calculate_correlation_matrix('pearson')
        correlations['high_volatility'] = self.data[high_vol_mask].corr(method='pearson')
        correlations['low_volatility'] = self.data[low_vol_mask].corr(method='pearson')
        
        # Расчет разности корреляций
        correlations['difference'] = (correlations['high_volatility'] - 
                                    correlations['low_volatility'])
        
        return correlations

# Загрузка данных для нескольких активов
tickers = ['AAPL', 'MSFT', 'GOOGL', 'TSLA', 'NVDA']
portfolio_data = {}

for ticker in tickers:
    data = yf.download(ticker, start="2022-06-01", end="2025-06-01", progress=False)
    # Сброс индекса и обработка мультииндекса
    data = data.reset_index()
    if isinstance(data.columns, pd.MultiIndex):
        data.columns = [col[0] if col[0] != '' else col[1] for col in data.columns]
    data['Date'] = pd.to_datetime(data['Date'])
    portfolio_data[ticker] = data['Close'].pct_change().dropna()

# Создание DataFrame с доходностями
returns_df = pd.DataFrame(portfolio_data).dropna()

# Инициализация анализатора
analyzer = MultivariateDistributionAnalyzer(returns_df)

print("МНОГОМЕРНЫЙ АНАЛИЗ СТАТИСТИЧЕСКИХ СВОЙСТВ ПОРТФЕЛЯ:")
print("="*60)

# Анализ корреляций разными методами
correlation_methods = ['pearson', 'spearman', 'kendall']
correlations = {}

for method in correlation_methods:
    correlations[method] = analyzer.calculate_correlation_matrix(method)
    print(f"\nКорреляционная матрица ({method.capitalize()}):")
    print(correlations[method].round(3))

# Анализ хвостовых зависимостей
lower_tail, upper_tail = analyzer.analyze_tail_dependence(quantile=0.05)

print(f"\nХВОСТОВЫЕ ЗАВИСИМОСТИ:")
print(f"Нижняя хвостовая зависимость (5% квантиль):")
lower_tail_df = pd.DataFrame(lower_tail, index=tickers, columns=tickers)
print(lower_tail_df.round(3))

print(f"\nВерхняя хвостовая зависимость (95% квантиль):")
upper_tail_df = pd.DataFrame(upper_tail, index=tickers, columns=tickers)
print(upper_tail_df.round(3))

# Тест многомерной нормальности
mv_normality = analyzer.test_multivariate_normality()
print(f"\nТЕСТ МНОГОМЕРНОЙ НОРМАЛЬНОСТИ:")
print(f"Корреляция Q-Q графика: {mv_normality['qq_correlation']:.4f}")
print(f"Многомерная нормальность: {'ДА' if mv_normality['is_multivariate_normal'] else 'НЕТ'}")

# Анализ корреляций в разных режимах
regime_correlations = analyzer.analyze_regime_dependent_correlations()

print(f"\nАНАЛИЗ КОРРЕЛЯЦИЙ ПО РЕЖИМАМ:")
print(f"Средняя корреляция в периоды низкой волатильности: {regime_correlations['low_volatility'].values[np.triu_indices_from(regime_correlations['low_volatility'], k=1)].mean():.3f}")
print(f"Средняя корреляция в периоды высокой волатильности: {regime_correlations['high_volatility'].values[np.triu_indices_from(regime_correlations['high_volatility'], k=1)].mean():.3f}")

# Визуализация результатов
fig, axes = plt.subplots(2, 3, figsize=(18, 12))

# Тепловые карты корреляций
correlation_titles = ['Пирсон', 'Спирмен', 'Кендалл']
for i, (method, title) in enumerate(zip(correlation_methods, correlation_titles)):
    sns.heatmap(correlations[method], annot=True, cmap='RdBu_r', center=0,
                square=True, ax=axes[0, i], cbar_kws={'shrink': 0.8})
    axes[0, i].set_title(f'Корреляция {title}')

# Хвостовые зависимости и разности корреляций
sns.heatmap(lower_tail_df, annot=True, cmap='Reds', square=True, 
            ax=axes[1, 0], cbar_kws={'shrink': 0.8})
axes[1, 0].set_title('Нижняя хвостовая зависимость')

sns.heatmap(upper_tail_df, annot=True, cmap='Blues', square=True, 
            ax=axes[1, 1], cbar_kws={'shrink': 0.8})
axes[1, 1].set_title('Верхняя хвостовая зависимость')

sns.heatmap(regime_correlations['difference'], annot=True, cmap='RdBu_r', center=0,
            square=True, ax=axes[1, 2], cbar_kws={'shrink': 0.8})
axes[1, 2].set_title('Разность корреляций\n(Высокая - Низкая волатильность)')

plt.tight_layout()
plt.show()

# Дополнительный анализ: динамика корреляций
rolling_corr = {}
window = 60

for i, asset1 in enumerate(tickers):
    for j, asset2 in enumerate(tickers[i+1:], i+1):
        pair_name = f"{asset1}-{asset2}"
        rolling_corr[pair_name] = returns_df[asset1].rolling(window).corr(returns_df[asset2])

# Визуализация динамики корреляций
fig, ax = plt.subplots(figsize=(15, 8))

colors = ['black', 'blue', 'red', 'green', 'orange', 'purple', 'brown', 'pink', 'gray', 'olive']
for i, (pair, corr_series) in enumerate(rolling_corr.items()):
    ax.plot(corr_series.index, corr_series, label=pair, color=colors[i % len(colors)], linewidth=1.5)

ax.set_title('Динамика скользящих корреляций (60 дней)')
ax.set_xlabel('Дата')
ax.set_ylabel('Корреляция')
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
МНОГОМЕРНЫЙ АНАЛИЗ СТАТИСТИЧЕСКИХ СВОЙСТВ ПОРТФЕЛЯ:
============================================================

Корреляционная матрица (Pearson):
        AAPL   MSFT  GOOGL   TSLA   NVDA
AAPL   1.000  0.635  0.577  0.497  0.489
MSFT   0.635  1.000  0.661  0.411  0.611
GOOGL  0.577  0.661  1.000  0.419  0.508
TSLA   0.497  0.411  0.419  1.000  0.443
NVDA   0.489  0.611  0.508  0.443  1.000

Корреляционная матрица (Spearman):
        AAPL   MSFT  GOOGL   TSLA   NVDA
AAPL   1.000  0.619  0.570  0.485  0.467
MSFT   0.619  1.000  0.694  0.440  0.625
GOOGL  0.570  0.694  1.000  0.441  0.513
TSLA   0.485  0.440  0.441  1.000  0.444
NVDA   0.467  0.625  0.513  0.444  1.000

Корреляционная матрица (Kendall):
        AAPL   MSFT  GOOGL   TSLA   NVDA
AAPL   1.000  0.450  0.410  0.338  0.329
MSFT   0.450  1.000  0.515  0.306  0.449
GOOGL  0.410  0.515  1.000  0.308  0.364
TSLA   0.338  0.306  0.308  1.000  0.309
NVDA   0.329  0.449  0.364  0.309  1.000

ХВОСТОВЫЕ ЗАВИСИМОСТИ:
Нижняя хвостовая зависимость (5% квантиль):
        AAPL   MSFT  GOOGL   TSLA   NVDA
AAPL   1.000  0.226  0.226  0.169  0.226
MSFT   0.226  1.000  0.310  0.101  0.169
GOOGL  0.226  0.310  1.000  0.118  0.226
TSLA   0.169  0.101  0.118  1.000  0.206
NVDA   0.226  0.169  0.226  0.206  1.000

Верхняя хвостовая зависимость (95% квантиль):
        AAPL   MSFT  GOOGL   TSLA   NVDA
AAPL   1.000  0.226  0.206  0.152  0.152
MSFT   0.226  1.000  0.288  0.086  0.226
GOOGL  0.206  0.288  1.000  0.152  0.226
TSLA   0.152  0.086  0.152  1.000  0.134
NVDA   0.152  0.226  0.226  0.134  1.000

ТЕСТ МНОГОМЕРНОЙ НОРМАЛЬНОСТИ:
Корреляция Q-Q графика: 0.8789
Многомерная нормальность: НЕТ

АНАЛИЗ КОРРЕЛЯЦИЙ ПО РЕЖИМАМ:
Средняя корреляция в периоды низкой волатильности: 0.353
Средняя корреляция в периоды высокой волатильности: 0.617

Матрицы корреляций, хвостовые зависимости, разность корреляций акций Apple, Microsoft, Google, Nvidia, Tesla

Рис. 5: Матрицы корреляций, хвостовые зависимости, разность корреляций акций Apple, Microsoft, Google, Nvidia, Tesla

Динамика скользящих 60-дневных корреляций акций Apple, Microsoft, Google, Nvidia, Tesla

Рис. 6: Динамика скользящих 60-дневных корреляций акций Apple, Microsoft, Google, Nvidia, Tesla

Этот многомерный анализ выявляет критически важные закономерности, которые невозможно обнаружить при анализе отдельных активов. Различия между корреляциями Пирсона, Спирмена и Кендалла указывают на наличие нелинейных зависимостей. Анализ хвостовых зависимостей показывает, что корреляции в экстремальных условиях могут значительно отличаться от средних значений.

Читайте также:  Модели ценообразования активов: CAPM и APT

Особенно важным является обнаружение асимметричных хвостовых зависимостей — когда активы сильнее коррелируют во время падений, чем во время роста. Это явление имеет прямые последствия для диверсификации портфеля, поскольку в периоды кризисов диверсификация может оказаться менее эффективной, чем ожидалось.

Анализ корреляций в разных рыночных режимах подтверждает гипотезу о том, что зависимости между активами изменяются в зависимости от рыночных условий. Это требует использования динамических моделей управления портфелем, которые могут адаптироваться к изменяющимся условиям рынка.

Практические применения в алгоритмической торговле

Построение адаптивных систем управления рисками

Понимание истинных статистических свойств финансовых данных открывает возможности для создания значительно более эффективных систем управления рисками. Традиционные подходы, основанные на предположении нормальности, систематически недооценивают риски, особенно в периоды рыночного стресса. Адаптивные системы, учитывающие реальные статистические свойства, могут существенно повысить стабильность торговых стратегий.

Ключевым элементом адаптивного управления рисками является динамическое изменение размеров позиций в зависимости от текущих статистических характеристик рынка. Когда мы обнаруживаем увеличение эксцесса или изменение асимметрии, система должна автоматически корректировать уровни риска. Это особенно важно для высокочастотных стратегий, где ручное вмешательство невозможно.

Особенно важным является использование корректировки Корниша-Фишера для расчета VaR, которая учитывает асимметрию и эксцесс распределения. Это дает более точную оценку рисков по сравнению с традиционными методами, основанными на предположении нормальности.

Интеграция в торговые алгоритмы

Практическое внедрение статистического анализа в торговые алгоритмы требует создания модульной архитектуры, которая может работать в реальном времени и адаптироваться к изменяющимся рыночным условиям. Ключевым элементом является создание системы мониторинга статистических свойств, которая постоянно отслеживает изменения и подает сигналы для корректировки стратегии.

Заключение

Глубокий анализ статистических свойств распределений представляет собой фундаментальную основу для успешной торговли на финансовых рынках. Мой многолетний опыт убедительно показывает, что игнорирование реальных статистических характеристик данных в пользу упрощенных предположений о нормальности является одной из главных причин провала торговых стратегий и неадекватного управления рисками.

Асимметрия и эксцесс финансовых распределений — это не просто академические концепции, а практические инструменты, которые должны быть интегрированы в каждый аспект торговой деятельности. Отрицательная асимметрия большинства фондовых индексов говорит нам о необходимости асимметричного управления рисками, где защита от потерь должна быть приоритетнее максимизации прибыли. Высокий эксцесс криптовалют и других волатильных активов требует использования более консервативных моделей оценки рисков и соответствующих размеров позиций.

Временная изменчивость статистических свойств подчеркивает важность адаптивных подходов. Статические модели, даже самые сложные, обречены на провал в динамично изменяющейся рыночной среде. Успешные торговые системы должны постоянно мониторить статистические характеристики и адаптироваться к режимным переключениям.