Статистические распределения формируют математический фундамент финансового анализа. Выбор адекватной модели распределения влияет на точность оценки рисков, корректность ценообразования активов и надежность прогнозных моделей. Понимание свойств различных распределений позволяет избежать систематических ошибок при принятии инвестиционных решений.
Нормальное распределение: базовая модель и ее ограничения
Нормальное (гауссово) распределение — наиболее популярное в финансовом моделировании. Распределение задается двумя параметрами:
- μ (математическое ожидание);
- σ (стандартное отклонение).
Плотность вероятности симметрична относительно среднего, 68% значений лежат в пределах одного стандартного отклонения, 95% — в пределах двух.
Центральная предельная теорема (ЦПТ) объясняет широкое применение нормального распределения: сумма большого числа независимых случайных величин стремится к нормальному распределению. Это делает его естественным выбором для моделирования агрегированных эффектов множества факторов. Модель Блэка-Шоулза, современная портфельная теория Марковица, CAPM — все базируются на предположении нормальности доходностей.
Между тем, реальные финансовые данные систематически нарушают предположения нормальности. Эмпирические распределения доходностей демонстрируют три ключевых отклонения: избыточный эксцесс (leptokurtosis), асимметрия (skewness) и тяжелые хвосты (fat tails). Эксцесс нормального распределения равен 3, для финансовых временных рядов типичны значения 5-10 и выше. Это означает, что экстремальные события происходят чаще, чем предсказывает гауссова модель.
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
# Генерация синтетических финансовых доходностей
np.random.seed(42)
n_samples = 2000
# Создаем данные с характеристиками реальных финансовых рядов
# Используем смесь нормального и t-распределения для имитации толстых хвостов
normal_component = np.random.normal(0.0005, 0.015, int(n_samples * 0.9))
extreme_component = np.random.standard_t(df=3, size=int(n_samples * 0.1)) * 0.04
returns = np.concatenate([normal_component, extreme_component])
np.random.shuffle(returns)
# Статистики распределения
mean_ret = np.mean(returns)
std_ret = np.std(returns)
skew = stats.skew(returns)
kurt = stats.kurtosis(returns, fisher=True)
# Тест Харке-Бера на нормальность
jb_stat, jb_pvalue = stats.jarque_bera(returns)
# Тест Колмогорова-Смирнова
ks_stat, ks_pvalue = stats.kstest(returns, 'norm', args=(mean_ret, std_ret))
print(f"Статистики распределения:")
print(f"Среднее: {mean_ret:.6f}")
print(f"Ст. отклонение: {std_ret:.6f}")
print(f"Асимметрия: {skew:.4f}")
print(f"Эксцесс: {kurt:.4f}")
print(f"\nТест Харке-Бера: статистика={jb_stat:.2f}, p-value={jb_pvalue:.6f}")
print(f"Тест Колмогорова-Смирнова: статистика={ks_stat:.4f}, p-value={ks_pvalue:.6f}")
# Визуализация
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# График 1: Временной ряд доходностей
axes[0, 0].plot(range(len(returns)), returns, color='black', linewidth=0.5, alpha=0.7)
axes[0, 0].set_title('Временной ряд доходностей', fontsize=11, fontweight='bold')
axes[0, 0].set_xlabel('Период')
axes[0, 0].set_ylabel('Доходность')
axes[0, 0].axhline(y=0, color='gray', linestyle='--', linewidth=1)
axes[0, 0].grid(alpha=0.3)
# График 2: Гистограмма с наложением нормального распределения
axes[0, 1].hist(returns, bins=60, density=True, alpha=0.6, color='gray', edgecolor='black')
x_range = np.linspace(returns.min(), returns.max(), 200)
axes[0, 1].plot(x_range, stats.norm.pdf(x_range, mean_ret, std_ret),
'r-', linewidth=2, label='Нормальное')
axes[0, 1].set_title('Распределение доходностей', fontsize=11, fontweight='bold')
axes[0, 1].set_xlabel('Доходность')
axes[0, 1].set_ylabel('Плотность')
axes[0, 1].legend()
axes[0, 1].grid(alpha=0.3)
# График 3: Q-Q plot
stats.probplot(returns, dist="norm", plot=axes[1, 0])
axes[1, 0].set_title('Q-Q график (Normal)', fontsize=11, fontweight='bold')
axes[1, 0].grid(alpha=0.3)
# График 4: Сравнение хвостов
quantiles = np.arange(0.01, 0.1, 0.01)
empirical_left = np.quantile(returns, quantiles)
theoretical_left = stats.norm.ppf(quantiles, mean_ret, std_ret)
quantiles_right = np.arange(0.9, 1.0, 0.01)
empirical_right = np.quantile(returns, quantiles_right)
theoretical_right = stats.norm.ppf(quantiles_right, mean_ret, std_ret)
axes[1, 1].scatter(theoretical_left, empirical_left, color='red', alpha=0.7, s=30, label='Левый хвост (1-10%)')
axes[1, 1].scatter(theoretical_right, empirical_right, color='blue', alpha=0.7, s=30, label='Правый хвост (90-99%)')
axes[1, 1].plot([returns.min(), returns.max()], [returns.min(), returns.max()],
'k--', linewidth=1, label='Идеальное соответствие')
axes[1, 1].set_title('Сравнение квантилей', fontsize=11, fontweight='bold')
axes[1, 1].set_xlabel('Теоретические квантили (Normal)')
axes[1, 1].set_ylabel('Эмпирические квантили')
axes[1, 1].legend()
axes[1, 1].grid(alpha=0.3)
plt.tight_layout()
plt.show()
# Анализ хвостов
print(f"\nАнализ хвостов:")
print(f"5% квантиль (эмпирический): {np.quantile(returns, 0.05):.6f}")
print(f"5% квантиль (теоретический): {stats.norm.ppf(0.05, mean_ret, std_ret):.6f}")
print(f"95% квантиль (эмпирический): {np.quantile(returns, 0.95):.6f}")
print(f"95% квантиль (теоретический): {stats.norm.ppf(0.95, mean_ret, std_ret):.6f}")
Статистики распределения:
Среднее: 0.000689
Ст. отклонение: 0.025977
Асимметрия: -1.0466
Эксцесс: 39.7292
Тест Харке-Бера: статистика=131899.05, p-value=0.000000
Тест Колмогорова-Смирнова: статистика=0.1188, p-value=0.000000
Анализ хвостов:
5% квантиль (эмпирический): -0.026578
5% квантиль (теоретический): -0.042040
95% квантиль (эмпирический): 0.030146
95% квантиль (теоретический): 0.043417

Рис. 1: Анализ распределения финансовых доходностей. Верхняя левая панель показывает временной ряд с видимыми экстремальными выбросами. Верхняя правая демонстрирует гистограмму с наложением теоретической нормальной кривой: эмпирическое распределение имеет более острый пик и толстые хвосты. Q-Q график подтверждает систематическое отклонение от нормальности в хвостах. Нижняя правая панель показывает, что эмпирические квантили в обоих хвостах лежат дальше от центра, чем предсказывает нормальная модель
Представленный выше код генерирует синтетические данные, имитирующие характеристики реальных финансовых доходностей. Смесь нормального распределения (90% данных) и t-распределения с малыми степенями свободы (10% данных) создает распределение с толстыми хвостами и избыточным эксцессом. Такой подход отражает феномен, наблюдаемый на финансовых рынках: большую часть времени доходности ведут себя относительно предсказуемо, но периодически происходят резкие скачки.
Тест Харке-Бера проверяет гипотезу о нормальности на основе асимметрии и эксцесса. Низкое p-value (обычно < 0.05) указывает на отклонение от нормального распределения. Тест Колмогорова-Смирнова сравнивает эмпирическую функцию распределения с теоретической, измеряя максимальное расстояние между ними.
Q-Q график (quantile-quantile plot) визуализирует соответствие квантилей данных теоретическим квантилям нормального распределения. Если точки лежат на прямой линии, распределение близко к нормальному. Отклонения в хвостах (концах графика) указывают на избыточную частоту экстремальных событий. Для финансовых данных типично наблюдать S-образную форму: левый хвост выше линии (больше экстремальных падений), правый хвост также выше (больше резких ростов).
Альтернативные распределения для финансов
Распределения с тяжелыми хвостами
Распределение Стьюдента (t-распределение) добавляет параметр степеней свободы ν, контролирующий толщину хвостов. При ν → ∞ распределение сходится к нормальному, при малых ν (3-7) хвосты существенно тяжелее. Четвертый момент (эксцесс) определяется как 3 + 6/(ν-4) для ν > 4, что дает значения 6-9 при типичных для финансов ν = 5-10.
T-распределение используется в моделировании доходностей на коротких временных интервалах (дневные, недельные данные), где влияние редких событий максимально. Модель позволяет корректно оценить вероятность движений в 3-5 стандартных отклонений, которые в нормальной модели считались бы практически невозможными.
from scipy.stats import t as student_t, norm, gennorm
# Генерация данных из различных распределений
np.random.seed(42)
n = 5000
# Нормальное распределение
normal_data = np.random.normal(0, 1, n)
# t-распределение с разными степенями свободы
t3_data = student_t.rvs(df=3, loc=0, scale=1, size=n)
t5_data = student_t.rvs(df=5, loc=0, scale=1, size=n)
t10_data = student_t.rvs(df=10, loc=0, scale=1, size=n)
# GED с разными параметрами формы
ged_beta1 = gennorm.rvs(beta=1, loc=0, scale=1, size=n) # Laplace
ged_beta15 = gennorm.rvs(beta=1.5, loc=0, scale=1, size=n)
# Визуализация
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# График 1: Сравнение t-распределений с нормальным
x = np.linspace(-5, 5, 500)
axes[0, 0].plot(x, norm.pdf(x, 0, 1), 'k-', linewidth=2, label='Normal')
axes[0, 0].plot(x, student_t.pdf(x, df=3, loc=0, scale=1), 'r-', linewidth=2, label='t (ν=3)')
axes[0, 0].plot(x, student_t.pdf(x, df=5, loc=0, scale=1), 'b-', linewidth=2, label='t (ν=5)')
axes[0, 0].plot(x, student_t.pdf(x, df=10, loc=0, scale=1), 'g-', linewidth=2, label='t (ν=10)')
axes[0, 0].set_title('t-распределение: влияние степеней свободы', fontsize=11, fontweight='bold')
axes[0, 0].set_xlabel('Значение')
axes[0, 0].set_ylabel('Плотность вероятности')
axes[0, 0].legend()
axes[0, 0].grid(alpha=0.3)
axes[0, 0].set_ylim(0, 0.45)
# График 2: Фокус на хвостах (логарифмическая шкала)
axes[0, 1].semilogy(x, norm.pdf(x, 0, 1), 'k-', linewidth=2, label='Normal')
axes[0, 1].semilogy(x, student_t.pdf(x, df=3, loc=0, scale=1), 'r-', linewidth=2, label='t (ν=3)')
axes[0, 1].semilogy(x, student_t.pdf(x, df=5, loc=0, scale=1), 'b-', linewidth=2, label='t (ν=5)')
axes[0, 1].set_title('Сравнение хвостов (лог-шкала)', fontsize=11, fontweight='bold')
axes[0, 1].set_xlabel('Значение')
axes[0, 1].set_ylabel('Плотность вероятности (log)')
axes[0, 1].legend()
axes[0, 1].grid(alpha=0.3)
axes[0, 1].set_xlim(2, 5)
# График 3: GED с различными параметрами формы
axes[1, 0].plot(x, norm.pdf(x, 0, 1), 'k-', linewidth=2, label='Normal (β=2)')
axes[1, 0].plot(x, gennorm.pdf(x, beta=1, loc=0, scale=1), 'r-', linewidth=2, label='Laplace (β=1)')
axes[1, 0].plot(x, gennorm.pdf(x, beta=1.5, loc=0, scale=1), 'b-', linewidth=2, label='GED (β=1.5)')
axes[1, 0].plot(x, gennorm.pdf(x, beta=3, loc=0, scale=1), 'g-', linewidth=2, label='GED (β=3)')
axes[1, 0].set_title('GED: влияние параметра формы β', fontsize=11, fontweight='bold')
axes[1, 0].set_xlabel('Значение')
axes[1, 0].set_ylabel('Плотность вероятности')
axes[1, 0].legend()
axes[1, 0].grid(alpha=0.3)
axes[1, 0].set_ylim(0, 0.6)
# График 4: Сравнение эксцесса
datasets = {
'Normal': normal_data,
't (ν=3)': t3_data,
't (ν=5)': t5_data,
't (ν=10)': t10_data,
'GED (β=1)': ged_beta1,
'GED (β=1.5)': ged_beta15
}
kurtosis_values = [stats.kurtosis(data, fisher=True) for data in datasets.values()]
names = list(datasets.keys())
axes[1, 1].bar(range(len(names)), kurtosis_values, color=['black', 'red', 'blue', 'green', 'orange', 'purple'])
axes[1, 1].set_xticks(range(len(names)))
axes[1, 1].set_xticklabels(names, rotation=45, ha='right')
axes[1, 1].set_title('Эксцесс различных распределений', fontsize=11, fontweight='bold')
axes[1, 1].set_ylabel('Excess Kurtosis')
axes[1, 1].axhline(y=0, color='gray', linestyle='--', linewidth=1)
axes[1, 1].grid(alpha=0.3, axis='y')
plt.tight_layout()
plt.show()
# Численное сравнение вероятностей в хвостах
print("\nВероятность события |X| > 3σ:")
print(f"Normal: {2 * (1 - norm.cdf(3)):.6f}")
print(f"t (ν=3): {2 * (1 - student_t.cdf(3, df=3)):.6f}")
print(f"t (ν=5): {2 * (1 - student_t.cdf(3, df=5)):.6f}")
print(f"t (ν=10): {2 * (1 - student_t.cdf(3, df=10)):.6f}")
print("\nВероятность события |X| > 4σ:")
print(f"Normal: {2 * (1 - norm.cdf(4)):.8f}")
print(f"t (ν=3): {2 * (1 - student_t.cdf(4, df=3)):.8f}")
print(f"t (ν=5): {2 * (1 - student_t.cdf(4, df=5)):.8f}")

Рис. 2: Сравнение распределений с тяжелыми хвостами. Верхняя левая панель показывает, как уменьшение степеней свободы ν делает t-распределение более островершинным с толстыми хвостами. Верхняя правая панель в логарифмической шкале демонстрирует, что хвосты t-распределения убывают медленнее нормального. Нижняя левая показывает семейство GED: меньшие значения β создают более острый пик и тяжелые хвосты. Нижняя правая панель количественно сравнивает эксцесс: t-распределение с малыми ν имеет существенно больший эксцесс, чем нормальное
Вероятность события |X| > 3σ:
Normal: 0.002700
t (ν=3): 0.057669
t (ν=5): 0.030099
t (ν=10): 0.013344
Вероятность события |X| > 4σ:
Normal: 0.00006334
t (ν=3): 0.02800846
t (ν=5): 0.01032342
Визуализация демонстрирует ключевые различия между распределениями:
- При малых степенях свободы t-распределение имеет более острый пик в центре и существенно более толстые хвосты;
- График в логарифмической шкале показывает, что при удалении от центра (|x| > 3) вероятности t-распределения убывают медленнее, чем у нормального. Это важный момент для финансового анализа: событие в 4 стандартных отклонения в нормальной модели имеет вероятность 0.006%, в t-распределении с ν=5 — около 0.08%, то есть в 13 раз выше;
- Обобщенное распределение ошибок (Generalized Error Distribution, GED) вводит параметр формы β, регулирующий как хвосты, так и пик распределения. При β = 2 получается нормальное распределение, β < 2 дает более тяжелые хвосты, β > 2 — более легкие;
- Распределение Лапласа соответствует β = 1 и демонстрирует острый пик с экспоненциальными хвостами. GED активно применяется в GARCH-моделях для описания условного распределения остатков. Эмпирически для финансовых данных оптимальное значение β лежит в диапазоне 1.2-1.8.
Асимметричные распределения
Логнормальное распределение возникает естественным образом, когда логарифм случайной величины имеет нормальное распределение. Если цена актива следует геометрическому броуновскому движению с постоянными параметрами, ее распределение в любой момент времени логнормально. Распределение ограничено снизу нулем и имеет правостороннюю асимметрию.
Плотность вероятности логнормального распределения для переменной X рассчитывается по формуле:
f(x) = (1 / (x σ √(2π))) exp(-(ln(x) — μ)² / (2σ²))
где:
- x > 0 — значение случайной величины;
- μ — математическое ожидание логарифма X;
- σ — стандартное отклонение логарифма X.
Среднее значение равно exp(μ + σ²/2).
Медиана: exp(μ)
Мода: exp(μ — σ²).
Разница между средним и медианой отражает правостороннюю асимметрию: большинство значений сосредоточено ниже среднего, но редкие высокие значения сдвигают среднее вправо.
from scipy.stats import lognorm, skewnorm
# Параметры для различных распределений
np.random.seed(42)
n = 10000
# Логнормальное с разными параметрами
mu1, sigma1 = 0, 0.3
mu2, sigma2 = 0, 0.6
lognorm1 = lognorm.rvs(s=sigma1, scale=np.exp(mu1), size=n)
lognorm2 = lognorm.rvs(s=sigma2, scale=np.exp(mu2), size=n)
# Скошенное нормальное (Skewed normal) распределение
skewnorm_data_neg = skewnorm.rvs(a=-5, loc=0, scale=1, size=n) # левая асимметрия
skewnorm_data_pos = skewnorm.rvs(a=5, loc=0, scale=1, size=n) # правая асимметрия
# Визуализация
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# График 1: Логнормальное распределение
x_log = np.linspace(0.01, 5, 500)
axes[0, 0].plot(x_log, lognorm.pdf(x_log, s=sigma1, scale=np.exp(mu1)),
'b-', linewidth=2, label=f'σ={sigma1}')
axes[0, 0].plot(x_log, lognorm.pdf(x_log, s=sigma2, scale=np.exp(mu2)),
'r-', linewidth=2, label=f'σ={sigma2}')
axes[0, 0].set_title('Логнормальное распределение', fontsize=11, fontweight='bold')
axes[0, 0].set_xlabel('Значение')
axes[0, 0].set_ylabel('Плотность вероятности')
axes[0, 0].legend()
axes[0, 0].grid(alpha=0.3)
# График 2: Гистограмма логнормального
axes[0, 1].hist(lognorm1, bins=100, density=True, alpha=0.6, color='blue',
edgecolor='black', range=(0, 5))
axes[0, 1].set_title('Гистограмма: Логнормальное (σ=0.3)', fontsize=11, fontweight='bold')
axes[0, 1].set_xlabel('Значение')
axes[0, 1].set_ylabel('Плотность')
axes[0, 1].axvline(np.mean(lognorm1), color='red', linestyle='--', linewidth=2, label='Среднее')
axes[0, 1].axvline(np.median(lognorm1), color='green', linestyle='--', linewidth=2, label='Медиана')
axes[0, 1].legend()
axes[0, 1].grid(alpha=0.3)
# График 3: Skewed normal распределение
x_skew = np.linspace(-4, 4, 500)
axes[1, 0].plot(x_skew, norm.pdf(x_skew, 0, 1), 'k-', linewidth=2, label='Normal')
axes[1, 0].plot(x_skew, skewnorm.pdf(x_skew, a=-5, loc=0, scale=1),
'r-', linewidth=2, label='Skew α=-5 (левая)')
axes[1, 0].plot(x_skew, skewnorm.pdf(x_skew, a=5, loc=0, scale=1),
'b-', linewidth=2, label='Skew α=5 (правая)')
axes[1, 0].set_title('Skewed Normal распределение', fontsize=11, fontweight='bold')
axes[1, 0].set_xlabel('Значение')
axes[1, 0].set_ylabel('Плотность вероятности')
axes[1, 0].legend()
axes[1, 0].grid(alpha=0.3)
# График 4: Сравнение асимметрии
datasets_skew = {
'Normal': np.random.normal(0, 1, n),
'Lognormal\n(σ=0.3)': np.log(lognorm1[lognorm1 > 0]), # логарифм для центрирования
'Skew Normal\n(α=-5)': skewnorm_data_neg,
'Skew Normal\n(α=5)': skewnorm_data_pos
}
skewness_values = [stats.skew(data) for data in datasets_skew.values()]
names_skew = list(datasets_skew.keys())
colors = ['black', 'blue', 'red', 'green']
axes[1, 1].bar(range(len(names_skew)), skewness_values, color=colors)
axes[1, 1].set_xticks(range(len(names_skew)))
axes[1, 1].set_xticklabels(names_skew)
axes[1, 1].set_title('Асимметрия различных распределений', fontsize=11, fontweight='bold')
axes[1, 1].set_ylabel('Skewness')
axes[1, 1].axhline(y=0, color='gray', linestyle='--', linewidth=1)
axes[1, 1].grid(alpha=0.3, axis='y')
plt.tight_layout()
plt.show()
# Численные характеристики логнормального распределения
print("Логнормальное распределение (σ=0.3):")
print(f"Среднее: {np.mean(lognorm1):.4f}")
print(f"Медиана: {np.median(lognorm1):.4f}")
print(f"Мода (приблиз): {np.exp(mu1 - sigma1**2):.4f}")
print(f"Асимметрия: {stats.skew(lognorm1):.4f}")
print(f"Эксцесс: {stats.kurtosis(lognorm1, fisher=True):.4f}")
print("\nSkewed Normal (α=-5, левая асимметрия):")
print(f"Асимметрия: {stats.skew(skewnorm_data_neg):.4f}")
print(f"Эксцесс: {stats.kurtosis(skewnorm_data_neg, fisher=True):.4f}")

Рис. 3: Асимметричные распределения. Верхняя левая панель демонстрирует логнормальное распределение с разными параметрами волатильности: больший σ создает более выраженную правостороннюю асимметрию и длинный правый хвост. Верхняя правая показывает гистограмму логнормального распределения с явным разрывом между средним и медианой. Нижняя левая сравнивает скошенное нормальное распределение с различными параметрами асимметрии: отрицательный α создает толстый левый хвост, положительный — правый. Нижняя правая количественно показывает коэффициенты асимметрии для разных моделей
Логнормальное распределение (σ=0.3):
Среднее: 1.0457
Медиана: 0.9992
Мода (приблиз): 0.9139
Асимметрия: 0.9680
Эксцесс: 1.7289
Skewed Normal (α=-5, левая асимметрия):
Асимметрия: -0.7959
Эксцесс: 0.5585
Логнормальное распределение применяется для моделирования:
- Цен активов (не доходностей);
- Стоимости опционов в модели Блэка-Шоулза;
- Размеров убытков в страховании.
Ограничение положительными значениями делает его естественным выбором для величин, которые не могут быть отрицательными. График показывает, что с ростом параметра σ распределение становится более асимметричным, правый хвост удлиняется. Разница между средним и медианой увеличивается — большинство значений остаются близко к нулю, но среднее растет за счет редких высоких выбросов.
Скошенное (ассиметричное) нормальное распределение расширяет нормальное, добавляя параметр асимметрии α:
- При α = 0 получается обычное нормальное распределение;
- Положительные значения α создают правостороннюю асимметрию;
- Отрицательные — левостороннюю.
Для финансовых доходностей характерна отрицательная асимметрия: резкие падения происходят чаще и быстрее, чем резкие росты.
Экстремальные распределения
Теория экстремальных значений (Extreme Value Theory, EVT) фокусируется на моделировании хвостов распределения, игнорируя центральную часть.
Обобщенное распределение экстремальных значений (Generalized Extreme Value, GEV) описывает распределение максимумов (или минимумов) в блоках данных фиксированного размера. Распределение содержит три параметра: расположение μ, масштаб σ и форма ξ.
Параметр формы ξ определяет тип распределения:
- ξ = 0: распределение Гумбеля (экспоненциальные хвосты);
- ξ > 0: распределение Фреше (степенные хвосты, heavy-tailed);
- ξ < 0: распределение Вейбулла (ограниченные хвосты).
Для финансовых данных обычно ξ > 0, что указывает на тяжелые хвосты. GEV применяется для оценки максимальных просадок портфеля за период, экстремальных дневных убытков, стресс-тестирования.
from scipy.stats import genextreme, genpareto
# Генерация экстремальных данных
np.random.seed(42)
n_blocks = 500
block_size = 20
# Симуляция доходностей с экстремальными событиями
base_returns = np.random.normal(0, 0.01, n_blocks * block_size)
extreme_events = np.random.choice(len(base_returns), size=50, replace=False)
base_returns[extreme_events] += np.random.normal(-0.05, 0.02, 50)
# Извлечение блочных минимумов для GEV
block_minima = []
for i in range(n_blocks):
block = base_returns[i * block_size:(i + 1) * block_size]
block_minima.append(np.min(block))
block_minima = np.array(block_minima)
# Peaks-over-threshold для GPD
threshold = np.percentile(base_returns, 5) # 5% нижний квантиль
exceedances = threshold - base_returns[base_returns < threshold]
# Подгонка GEV
gev_params = genextreme.fit(block_minima)
print("GEV параметры (block minima):")
print(f"Shape (ξ): {gev_params[0]:.4f}")
print(f"Location (μ): {gev_params[1]:.4f}")
print(f"Scale (σ): {gev_params[2]:.4f}")
# Подгонка GPD
gpd_params = genpareto.fit(exceedances)
print(f"\nGPD параметры (exceedances):")
print(f"Shape (ξ): {gpd_params[0]:.4f}")
print(f"Location: {gpd_params[1]:.4f}")
print(f"Scale (σ): {gpd_params[2]:.4f}")
# Визуализация
fig, axes = plt.subplots(2, 3, figsize=(16, 10))
# График 1: Временной ряд с порогом
axes[0, 0].plot(base_returns, color='black', linewidth=0.5, alpha=0.7)
axes[0, 0].axhline(y=threshold, color='red', linestyle='--', linewidth=2, label=f'Threshold (5%)')
axes[0, 0].scatter(np.where(base_returns < threshold)[0],
base_returns[base_returns < threshold], color='red', s=10, alpha=0.6, label='Exceedances') axes[0, 0].set_title('Временной ряд с порогом POT', fontsize=11, fontweight='bold') axes[0, 0].set_xlabel('Наблюдение') axes[0, 0].set_ylabel('Доходность') axes[0, 0].legend() axes[0, 0].grid(alpha=0.3) # График 2: Распределение блочных минимумов axes[0, 1].hist(block_minima, bins=40, density=True, alpha=0.6, color='gray', edgecolor='black', label='Данные') x_gev = np.linspace(block_minima.min(), block_minima.max(), 200) axes[0, 1].plot(x_gev, genextreme.pdf(x_gev, *gev_params), 'r-', linewidth=2, label='GEV fit') axes[0, 1].set_title('GEV: блочные минимумы', fontsize=11, fontweight='bold') axes[0, 1].set_xlabel('Минимальная доходность в блоке') axes[0, 1].set_ylabel('Плотность') axes[0, 1].legend() axes[0, 1].grid(alpha=0.3) # График 3: Распределение превышений (GPD) axes[0, 2].hist(exceedances, bins=40, density=True, alpha=0.6, color='gray', edgecolor='black', label='Данные') x_gpd = np.linspace(0, exceedances.max(), 200) axes[0, 2].plot(x_gpd, genpareto.pdf(x_gpd, *gpd_params), 'b-', linewidth=2, label='GPD fit') axes[0, 2].set_title('GPD: превышения порога', fontsize=11, fontweight='bold') axes[0, 2].set_xlabel('Величина превышения') axes[0, 2].set_ylabel('Плотность') axes[0, 2].legend() axes[0, 2].grid(alpha=0.3) # График 4: Q-Q plot для GEV theoretical_quantiles_gev = genextreme.ppf(np.linspace(0.01, 0.99, len(block_minima)), *gev_params) empirical_quantiles_gev = np.sort(block_minima) axes[1, 0].scatter(theoretical_quantiles_gev, empirical_quantiles_gev, alpha=0.6, color='red') axes[1, 0].plot([block_minima.min(), block_minima.max()], [block_minima.min(), block_minima.max()], 'k--', linewidth=1) axes[1, 0].set_title('Q-Q plot: GEV', fontsize=11, fontweight='bold') axes[1, 0].set_xlabel('Теоретические квантили') axes[1, 0].set_ylabel('Эмпирические квантили') axes[1, 0].grid(alpha=0.3) # График 5: Q-Q plot для GPD theoretical_quantiles_gpd = genpareto.ppf(np.linspace(0.01, 0.99, len(exceedances)), *gpd_params) empirical_quantiles_gpd = np.sort(exceedances) axes[1, 1].scatter(theoretical_quantiles_gpd, empirical_quantiles_gpd, alpha=0.6, color='blue') axes[1, 1].plot([0, exceedances.max()], [0, exceedances.max()], 'k--', linewidth=1) axes[1, 1].set_title('Q-Q plot: GPD', fontsize=11, fontweight='bold') axes[1, 1].set_xlabel('Теоретические квантили') axes[1, 1].set_ylabel('Эмпирические квантили') axes[1, 1].grid(alpha=0.3) # График 6: Сравнение GEV с разными параметрами формы x_comparison = np.linspace(-0.15, 0.02, 300) axes[1, 2].plot(x_comparison, genextreme.pdf(x_comparison, 0, loc=-0.03, scale=0.02), 'g-', linewidth=2, label='Gumbel (ξ=0)') axes[1, 2].plot(x_comparison, genextreme.pdf(x_comparison, 0.2, loc=-0.03, scale=0.02), 'r-', linewidth=2, label='Frechet (ξ=0.2)') axes[1, 2].plot(x_comparison, genextreme.pdf(x_comparison, -0.2, loc=-0.03, scale=0.02), 'b-', linewidth=2, label='Weibull (ξ=-0.2)') axes[1, 2].set_title('Типы GEV распределений', fontsize=11, fontweight='bold') axes[1, 2].set_xlabel('Значение') axes[1, 2].set_ylabel('Плотность вероятности') axes[1, 2].legend() axes[1, 2].grid(alpha=0.3) plt.tight_layout() plt.show() # Оценка экстремальных квантилей print("\nОценка экстремальных квантилей:") for p in [0.01, 0.005, 0.001]: gev_quantile = genextreme.ppf(p, *gev_params) print(f"GEV {p*100:.1f}% квантиль: {gev_quantile:.6f}") print("\nВероятность превышения через GPD:") for threshold_level in [0.05, 0.07, 0.10]: prob = 1 - genpareto.cdf(threshold_level, *gpd_params) print(f"P(превышение > {threshold_level:.2f}): {prob:.6f}")

Рис. 4: Экстремальные распределения. Верхняя левая панель показывает временной ряд доходностей с красной пунктирной линией порога POT и отмеченными превышениями. Верхняя средняя демонстрирует подгонку GEV к блочным минимумам. Верхняя правая показывает GPD для превышений порога. Нижние панели содержат Q-Q графики для проверки качества подгонки обеих моделей и сравнение трех типов GEV: Gumbel (экспоненциальный хвост), Frechet (тяжелый хвост) и Weibull (ограниченный хвост)
GEV параметры (block minima):
Shape (ξ): 0.7281
Location (μ): -0.0239
Scale (σ): 0.0120
GPD параметры (exceedances):
Shape (ξ): 0.4832
Location: 0.0000
Scale (σ): 0.0039
Оценка экстремальных квантилей:
GEV 1.0% квантиль: -0.057541
GEV 0.5% квантиль: -0.062932
GEV 0.1% квантиль: -0.074766
Вероятность превышения через GPD:
P(превышение > 0.05): 0.017139
P(превышение > 0.07): 0.009296
P(превышение > 0.10): 0.004746
Код демонстрирует два основных подхода EVT:
- Block maxima (GEV) или Метод блочных максимумов. Он разбивает данные на блоки фиксированного размера и извлекает минимум (или максимум) из каждого блока. Размер блока выбирается исходя из природы данных: для дневных доходностей типичен блок в 20-60 дней.
- Peaks-over-threshold (GPD). Метод GPD анализирует все наблюдения, превышающие заданный порог, что использует больше информации из хвоста. Как правило для него подбирается порог таким образом, чтобы осталось 5-10% наблюдений. Для этого используют специальные графики: Mean excess plot и Parameter stability plot.
Q-Q графики показывают качество подгонки модели к экстремальным данным. Хорошее соответствие точек диагональной линии указывает, что модель корректно описывает хвост распределения. Отклонения в крайних квантилях (самые экстремальные события) ожидаемы из-за малого числа наблюдений, однако систематические паттерны указывают на необходимость пересмотра модели или порога.
Обобщенное распределение Парето используется для расчета Value at Risk и Conditional Value at Risk на экстремальных квантилях (99%, 99.5%) там, где эмпирических данных недостаточно.
Экстраполяция хвоста через параметрическую модель GPD дает более стабильные оценки, чем исторический метод. Положительное значение параметра формы ξ подтверждает наличие тяжелых хвостов: вероятность экстремальных событий убывает степенным образом, а не экспоненциально.
Выбор и оценка распределений
Выбор распределения базируется на трех критериях:
- Соответствие эмпирическим данным;
- Теоретическая обоснованность;
- Вычислительная простота.
Визуальный анализ (гистограммы, Q-Q графики) дает первичное понимание, но требует формального статистического подтверждения.
Тест Колмогорова-Смирнова сравнивает эмпирическую функцию распределения Fn(x) с теоретической F(x):
D = sup|Fn(x) — F(x)|
где:
- sup — супремум (максимум) разности по всем x;
- Fn(x) — доля наблюдений, не превышающих x;
- F(x) — теоретическая вероятность P(X ≤ x).
Статистика D измеряет максимальное вертикальное расстояние между двумя функциями. Малые значения D (и высокое p-value) указывают на хорошее соответствие. Тест чувствителен к отклонениям в центре распределения, но менее чувствителен к хвостам.
Тест Андерсона-Дарлинга модифицирует Колмогорова-Смирнова, давая больший вес хвостам:
A² = n ∫[(Fn(x) — F(x))² / (F(x)(1 — F(x)))] dF(x)
где:
- n — размер выборки;
- весовая функция 1/(F(x)(1-F(x))) увеличивает значимость хвостов.
Для финансового анализа тест Андерсона-Дарлинга предпочтительнее, так как именно хвосты определяют риски крупных убытков.
Информационные критерии Akaike (AIC) и Bayesian (BIC) позволяют сравнивать модели с разным числом параметров:
AIC = 2k — 2ln(L)
BIC = k ln(n) — 2ln(L)
где:
- k — число параметров модели;
- L — максимальное значение функции правдоподобия;
- n — размер выборки.
Меньшее значение AIC или BIC указывает на лучшую модель. BIC сильнее штрафует сложность модели (коэффициент ln(n) vs 2 в AIC), предпочитая более простые распределения. Для финансовых данных разница в AIC более 10 считается существенной, указывая на явное преимущество одной модели над другой.
import pandas as pd
from scipy.optimize import minimize
# Генерация тестовых данных с известными свойствами
np.random.seed(42)
n = 3000
# Данные с характеристиками финансовых доходностей
true_data = student_t.rvs(df=5, loc=0.0003, scale=0.018, size=n)
# Функции для подбора параметров различных распределений
def fit_student_t(data):
def neg_log_likelihood(params):
df, loc, scale = params
if df <= 2 or scale <= 0:
return 1e10
return -np.sum(student_t.logpdf(data, df, loc, scale))
init_params = [5, np.mean(data), np.std(data)]
bounds = [(2.01, 50), (None, None), (1e-6, None)]
result = minimize(neg_log_likelihood, init_params, bounds=bounds, method='L-BFGS-B')
return result.x, -result.fun
def fit_ged(data):
def neg_log_likelihood(params):
beta, loc, scale = params
if beta <= 0 or scale <= 0:
return 1e10
return -np.sum(gennorm.logpdf(data, beta, loc, scale))
init_params = [1.5, np.mean(data), np.std(data)]
bounds = [(0.1, 5), (None, None), (1e-6, None)]
result = minimize(neg_log_likelihood, init_params, bounds=bounds, method='L-BFGS-B')
return result.x, -result.fun
def fit_skewed_t(data):
"""Упрощенная версия: skewed normal как приближение"""
def neg_log_likelihood(params):
a, loc, scale = params
if scale <= 0:
return 1e10
return -np.sum(skewnorm.logpdf(data, a, loc, scale))
init_params = [0, np.mean(data), np.std(data)]
bounds = [(-10, 10), (None, None), (1e-6, None)]
result = minimize(neg_log_likelihood, init_params, bounds=bounds, method='L-BFGS-B')
return result.x, -result.fun
# Подгонка различных моделей
params_norm = (np.mean(true_data), np.std(true_data))
log_lik_norm = np.sum(norm.logpdf(true_data, *params_norm))
params_t, log_lik_t = fit_student_t(true_data)
params_ged, log_lik_ged = fit_ged(true_data)
params_skew, log_lik_skew = fit_skewed_t(true_data)
# Расчет информационных критериев
def calculate_ic(log_lik, k, n):
aic = 2 * k - 2 * log_lik
bic = k * np.log(n) - 2 * log_lik
return aic, bic
n_obs = len(true_data)
aic_norm, bic_norm = calculate_ic(log_lik_norm, 2, n_obs)
aic_t, bic_t = calculate_ic(log_lik_t, 3, n_obs)
aic_ged, bic_ged = calculate_ic(log_lik_ged, 3, n_obs)
aic_skew, bic_skew = calculate_ic(log_lik_skew, 3, n_obs)
# Статистические тесты
ks_norm_stat, ks_norm_p = stats.kstest(true_data, 'norm', args=params_norm)
ks_t_stat, ks_t_p = stats.kstest(true_data, lambda x: student_t.cdf(x, *params_t))
# Тест Андерсона-Дарлинга (только для нормального)
ad_result = stats.anderson(true_data, dist='norm')
# Результаты в таблице
results_df = pd.DataFrame({
'Модель': ['Normal', 'Student-t', 'GED', 'Skewed-N'],
'Параметры': [2, 3, 3, 3],
'Log-Lik': [log_lik_norm, log_lik_t, log_lik_ged, log_lik_skew],
'AIC': [aic_norm, aic_t, aic_ged, aic_skew],
'BIC': [bic_norm, bic_t, bic_ged, bic_skew],
'Δ AIC': [
aic_norm - aic_t,
0,
aic_ged - aic_t,
aic_skew - aic_t
]
})
print("Сравнение моделей распределений:")
print(results_df.to_string(index=False))
print(f"\nТест Колмогорова-Смирнова:")
print(f"Normal: D={ks_norm_stat:.4f}, p-value={ks_norm_p:.6f}")
print(f"Student-t: D={ks_t_stat:.4f}, p-value={ks_t_p:.6f}")
print(f"\nТест Андерсона-Дарлинга (Normal):")
print(f"Статистика: {ad_result.statistic:.4f}")
print(f"Критические значения: {ad_result.critical_values}")
print(f"Уровни значимости: {ad_result.significance_level}")
# Подобранные параметры
print(f"\nПодобранные параметры:")
print(f"Student-t: ν={params_t[0]:.2f}, μ={params_t[1]:.6f}, σ={params_t[2]:.6f}")
print(f"GED: β={params_ged[0]:.2f}, μ={params_ged[1]:.6f}, σ={params_ged[2]:.6f}")
# Визуализация
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# График 1: Наложение всех распределений
x_range = np.linspace(true_data.min(), true_data.max(), 300)
axes[0, 0].hist(true_data, bins=60, density=True, alpha=0.3, color='gray',
edgecolor='black', label='Данные')
axes[0, 0].plot(x_range, norm.pdf(x_range, *params_norm),
'r-', linewidth=2, label='Normal')
axes[0, 0].plot(x_range, student_t.pdf(x_range, *params_t),
'b-', linewidth=2, label='Student-t')
axes[0, 0].plot(x_range, gennorm.pdf(x_range, *params_ged),
'g-', linewidth=2, label='GED')
axes[0, 0].plot(x_range, skewnorm.pdf(x_range, *params_skew),
'm-', linewidth=2, label='Skewed-N')
axes[0, 0].set_title('Сравнение подогнанных моделей', fontsize=11, fontweight='bold')
axes[0, 0].set_xlabel('Доходность')
axes[0, 0].set_ylabel('Плотность')
axes[0, 0].legend()
axes[0, 0].grid(alpha=0.3)
# График 2: Информационные критерии
models = ['Normal', 'Student-t', 'GED', 'Skewed-N']
aic_values = [aic_norm, aic_t, aic_ged, aic_skew]
bic_values = [bic_norm, bic_t, bic_ged, bic_skew]
x_pos = np.arange(len(models))
width = 0.35
axes[0, 1].bar(x_pos - width/2, aic_values, width, label='AIC', color='steelblue')
axes[0, 1].bar(x_pos + width/2, bic_values, width, label='BIC', color='darkorange')
axes[0, 1].set_xticks(x_pos)
axes[0, 1].set_xticklabels(models)
axes[0, 1].set_title('Информационные критерии', fontsize=11, fontweight='bold')
axes[0, 1].set_ylabel('Значение критерия')
axes[0, 1].legend()
axes[0, 1].grid(alpha=0.3, axis='y')
# График 3: Фокус на левом хвосте
left_quantile = np.percentile(true_data, 10)
mask_left = x_range < left_quantile
axes[1, 0].hist(true_data[true_data < left_quantile], bins=30, density=True,
alpha=0.3, color='gray', edgecolor='black', label='Данные')
axes[1, 0].plot(x_range[mask_left], norm.pdf(x_range[mask_left], *params_norm),
'r-', linewidth=2, label='Normal')
axes[1, 0].plot(x_range[mask_left], student_t.pdf(x_range[mask_left], *params_t),
'b-', linewidth=2, label='Student-t')
axes[1, 0].plot(x_range[mask_left], gennorm.pdf(x_range[mask_left], *params_ged),
'g-', linewidth=2, label='GED')
axes[1, 0].set_title('Левый хвост (10% квантиль)', fontsize=11, fontweight='bold')
axes[1, 0].set_xlabel('Доходность')
axes[1, 0].set_ylabel('Плотность')
axes[1, 0].legend()
axes[1, 0].grid(alpha=0.3)
# График 4: P-P plot для лучшей модели (Student-t)
theoretical_probs = student_t.cdf(np.sort(true_data), *params_t)
empirical_probs = np.arange(1, len(true_data) + 1) / len(true_data)
axes[1, 1].scatter(theoretical_probs, empirical_probs, alpha=0.5, s=10, color='blue')
axes[1, 1].plot([0, 1], [0, 1], 'k--', linewidth=1)
axes[1, 1].set_title('P-P plot: Student-t', fontsize=11, fontweight='bold')
axes[1, 1].set_xlabel('Теоретические вероятности')
axes[1, 1].set_ylabel('Эмпирические вероятности')
axes[1, 1].grid(alpha=0.3)
plt.tight_layout()
plt.show()

Рис. 5: Сравнение и оценка распределений. Верхняя левая панель показывает наложение четырех подогнанных моделей на гистограмму данных: Student-t и GED лучше описывают острый пик и хвосты. Верхняя правая демонстрирует значения AIC и BIC: меньшие значения указывают на лучшую модель. Нижняя левая фокусируется на левом хвосте, где различия между моделями наиболее критичны для оценки рисков. P-P график показывает качество подгонки Student-t: точки близки к диагонали, что подтверждает хорошее соответствие модели данным
Сравнение моделей распределений:
Модель Параметры Log-Lik AIC BIC Δ AIC
Normal 2 6982.703753 -13961.407506 -13949.394771 335.788231
Student-t 3 7151.597868 -14297.195737 -14279.176634 0.000000
GED 3 7121.032526 -14236.065053 -14218.045950 61.130684
Skewed-N 3 6982.703753 -13959.407506 -13941.388403 337.788231
Тест Колмогорова-Смирнова:
Normal: D=0.0436, p-value=0.000022
Student-t: D=0.0106, p-value=0.888370
Тест Андерсона-Дарлинга (Normal):
Статистика: 13.7753
Критические значения: [0.575 0.655 0.786 0.917 1.091]
Уровни значимости: [15. 10. 5. 2.5 1. ]
Подобранные параметры:
Student-t: ν=5.00, μ=-0.000634, σ=0.018140
GED: β=1.25, μ=-0.000629, σ=0.022457
Давайте разберем что вычисляет этот код:
- Метод максимального правдоподобия (Maximum Likelihood Estimation, MLE) оптимизирует параметры распределения так, чтобы максимизировать вероятность наблюдения данных. Логарифм правдоподобия упрощает вычисления, превращая произведение вероятностей в сумму;
- Оптимизация методом L-BFGS-B минимизирует отрицательный логарифм правдоподобия с учетом ограничений на параметры: степени свободы t-распределения должны быть больше 2, параметр масштаба положителен;
- Сравнение AIC показывает относительное качество моделей. Разница Δ AIC интерпретируется следующим образом: 0-2 (модели эквивалентны), 4-7 (существенная разница), >10 (модель с большим AIC практически не имеет поддержки);
- Если Student-t дает AIC на 50 единиц ниже нормального распределения, оно явно предпочтительнее. BIC может выбрать более простую модель при больших выборках, так как сильнее штрафует дополнительные параметры.
P-P график (probability-probability plot) сравнивает теоретические и эмпирические вероятности. В отличие от Q-Q графика, который сравнивает квантили, P-P график лучше подходит для оценки общего соответствия распределения. Точки должны лежать близко к диагональной линии. Систематические отклонения указывают на несоответствие модели: S-образная кривая означает различие в дисперсии, вогнутость/выпуклость — различие в асимметрии.
Практическое применение
Выбор распределения определяет результаты финансового анализа на всех этапах: от оценки рисков до ценообразования деривативов. Неадекватная модель распределения приводит к систематическим ошибкам в расчете метрик риска и недооценке вероятности крупных убытков.
Value at Risk (VaR) определяет максимальный убыток портфеля на заданном доверительном уровне за период. VaR на уровне 95% отвечает на вопрос: какой убыток не будет превышен в 95% случаев. Conditional Value at Risk (CVaR), также известный как Expected Shortfall, измеряет средний убыток в хвосте распределения — при условии, что VaR был превышен.
Для нормального распределения VaR вычисляется через квантиль:
VaR_α = μ + σ · Φ^(-1)(α)
где:
- α — уровень доверия (например, 0.05 для 5% VaR);
- Φ^(-1) — обратная функция стандартного нормального распределения;
- μ — ожидаемая доходность;
- σ — волатильность.
Для t-распределения и других альтернативных моделей используются соответствующие обратные функции распределения. При этом важно помнить, что различия в VaR между нормальной и t-моделью могут достигать 30% на экстремальных квантилях (1%, 0.5%).
# Генерация портфельных доходностей с реалистичными характеристиками
np.random.seed(42)
n_days = 2500
portfolio_value = 1000000 # $1M портфель
# Имитация доходностей: смесь режимов (спокойный + волатильный)
regime_1 = np.random.normal(0.0004, 0.01, int(n_days * 0.85))
regime_2 = student_t.rvs(df=4, loc=-0.002, scale=0.03, size=int(n_days * 0.15))
returns = np.concatenate([regime_1, regime_2])
np.random.shuffle(returns)
# Подгонка различных распределений
params_norm = (np.mean(returns), np.std(returns))
def fit_t_dist(data):
def neg_ll(params):
df, loc, scale = params
if df <= 2 or scale <= 0:
return 1e10
return -np.sum(student_t.logpdf(data, df, loc, scale))
result = minimize(neg_ll, [5, np.mean(data), np.std(data)],
bounds=[(2.01, 50), (None, None), (1e-6, None)], method='L-BFGS-B')
return result.x
params_t = fit_t_dist(returns)
# Расчет VaR для различных уровней доверия
confidence_levels = [0.90, 0.95, 0.99, 0.995]
var_results = []
for conf in confidence_levels:
alpha = 1 - conf
# Исторический VaR
var_hist = np.percentile(returns, alpha * 100)
# Параметрический VaR (Normal)
var_norm = norm.ppf(alpha, *params_norm)
# Параметрический VaR (Student-t)
var_t = student_t.ppf(alpha, *params_t)
# CVaR (Expected Shortfall)
tail_returns_hist = returns[returns <= var_hist] cvar_hist = np.mean(tail_returns_hist) if len(tail_returns_hist) > 0 else var_hist
# CVaR для нормального (аналитический)
cvar_norm = params_norm[0] - params_norm[1] * norm.pdf(norm.ppf(alpha)) / alpha
var_results.append({
'Confidence': f"{conf*100:.1f}%",
'VaR_Historical': var_hist * portfolio_value,
'VaR_Normal': var_norm * portfolio_value,
'VaR_Student_t': var_t * portfolio_value,
'CVaR_Historical': cvar_hist * portfolio_value,
'CVaR_Normal': cvar_norm * portfolio_value
})
var_df = pd.DataFrame(var_results)
print("Сравнение VaR и CVaR (долларовая стоимость):")
print(var_df.to_string(index=False))
# Обратное тестирование (backtesting)
# Проверка сколько раз фактические убытки превышали VaR
print("\n\nОбратное тестирование VaR (95% уровень):")
var_95_norm = norm.ppf(0.05, *params_norm)
var_95_t = student_t.ppf(0.05, *params_t)
var_95_hist = np.percentile(returns, 5)
violations_norm = np.sum(returns < var_95_norm)
violations_t = np.sum(returns < var_95_t)
violations_hist = np.sum(returns < var_95_hist)
expected_violations = len(returns) * 0.05
print(f"Ожидаемое число нарушений (5%): {expected_violations:.0f}")
print(f"Normal VaR нарушений: {violations_norm} ({violations_norm/len(returns)*100:.2f}%)")
print(f"Student-t VaR нарушений: {violations_t} ({violations_t/len(returns)*100:.2f}%)")
print(f"Historical VaR нарушений: {violations_hist} ({violations_hist/len(returns)*100:.2f}%)")
# Визуализация
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# График 1: Временной ряд с VaR уровнями
cumulative_returns = (1 + returns).cumprod()
axes[0, 0].plot(cumulative_returns, color='black', linewidth=1, label='Портфель')
axes[0, 0].set_title('Динамика портфеля', fontsize=11, fontweight='bold')
axes[0, 0].set_xlabel('Торговый день')
axes[0, 0].set_ylabel('Кумулятивная доходность')
axes[0, 0].grid(alpha=0.3)
axes[0, 0].legend()
# График 2: Распределение с VaR линиями
x_range = np.linspace(returns.min(), returns.max(), 300)
axes[0, 1].hist(returns, bins=60, density=True, alpha=0.3, color='gray', edgecolor='black')
axes[0, 1].plot(x_range, norm.pdf(x_range, *params_norm), 'r-', linewidth=2, label='Normal')
axes[0, 1].plot(x_range, student_t.pdf(x_range, *params_t), 'b-', linewidth=2, label='Student-t')
# VaR линии для 95% уровня
axes[0, 1].axvline(var_95_norm, color='red', linestyle='--', linewidth=2, alpha=0.7, label='VaR 95% (N)')
axes[0, 1].axvline(var_95_t, color='blue', linestyle='--', linewidth=2, alpha=0.7, label='VaR 95% (t)')
axes[0, 1].set_title('Распределение с VaR уровнями', fontsize=11, fontweight='bold')
axes[0, 1].set_xlabel('Доходность')
axes[0, 1].set_ylabel('Плотность')
axes[0, 1].legend()
axes[0, 1].grid(alpha=0.3)
# График 3: Сравнение VaR по уровням доверия
conf_labels = ['90%', '95%', '99%', '99.5%']
var_hist_values = [-var_df['VaR_Historical'].iloc[i] for i in range(len(conf_labels))]
var_norm_values = [-var_df['VaR_Normal'].iloc[i] for i in range(len(conf_labels))]
var_t_values = [-var_df['VaR_Student_t'].iloc[i] for i in range(len(conf_labels))]
x_pos = np.arange(len(conf_labels))
width = 0.25
axes[1, 0].bar(x_pos - width, var_hist_values, width, label='Historical', color='gray')
axes[1, 0].bar(x_pos, var_norm_values, width, label='Normal', color='red')
axes[1, 0].bar(x_pos + width, var_t_values, width, label='Student-t', color='blue')
axes[1, 0].set_xticks(x_pos)
axes[1, 0].set_xticklabels(conf_labels)
axes[1, 0].set_title('VaR по уровням доверия (потери)', fontsize=11, fontweight='bold')
axes[1, 0].set_ylabel('VaR, $')
axes[1, 0].legend()
axes[1, 0].grid(alpha=0.3, axis='y')
# График 4: Разница между моделями (%)
var_diff_norm_vs_t = [(var_df['VaR_Normal'].iloc[i] - var_df['VaR_Student_t'].iloc[i]) /
abs(var_df['VaR_Student_t'].iloc[i]) * 100 for i in range(len(conf_labels))]
axes[1, 1].plot(conf_labels, var_diff_norm_vs_t, marker='o', linewidth=2,
markersize=8, color='darkred', label='(Normal - Student-t) / Student-t')
axes[1, 1].axhline(y=0, color='gray', linestyle='--', linewidth=1)
axes[1, 1].set_title('Недооценка риска Normal vs Student-t', fontsize=11, fontweight='bold')
axes[1, 1].set_xlabel('Уровень доверия')
axes[1, 1].set_ylabel('Разница, %')
axes[1, 1].grid(alpha=0.3)
axes[1, 1].legend()
plt.tight_layout()
plt.show()
# Симуляция Монте-Карло для оценки портфельного риска
print("\n\nСимуляция Монте-Карло (10,000 сценариев, 20 дней):")
n_simulations = 10000
n_days_forecast = 20
# Симуляция с нормальным распределением
sim_norm = np.random.normal(params_norm[0], params_norm[1], (n_simulations, n_days_forecast))
final_returns_norm = (1 + sim_norm).prod(axis=1) - 1
# Симуляция с t-распределением
sim_t = student_t.rvs(df=params_t[0], loc=params_t[1], scale=params_t[2],
size=(n_simulations, n_days_forecast))
final_returns_t = (1 + sim_t).prod(axis=1) - 1
# VaR на горизонте 20 дней
var_20d_norm_95 = np.percentile(final_returns_norm, 5) * portfolio_value
var_20d_t_95 = np.percentile(final_returns_t, 5) * portfolio_value
print(f"20-дневный VaR (95%):")
print(f" Normal: ${abs(var_20d_norm_95):,.0f}")
print(f" Student-t: ${abs(var_20d_t_95):,.0f}")
print(f" Разница: ${abs(var_20d_t_95 - var_20d_norm_95):,.0f} ({abs(var_20d_t_95 - var_20d_norm_95)/abs(var_20d_t_95)*100:.1f}%)")
# Вероятность потери более 10% капитала
prob_loss_10_norm = np.sum(final_returns_norm < -0.10) / n_simulations
prob_loss_10_t = np.sum(final_returns_t < -0.10) / n_simulations print(f"\nВероятность потери >10% за 20 дней:")
print(f" Normal: {prob_loss_10_norm*100:.2f}%")
print(f" Student-t: {prob_loss_10_t*100:.2f}%")

Рис. 6: Практическое применение распределений в оценке рисков. Верхняя левая панель показывает кумулятивную доходность портфеля с видимыми периодами просадок. Верхняя правая демонстрирует распределение доходностей с наложением VaR линий: Student-t модель дает более консервативную оценку риска. Нижняя левая сравнивает VaR трех методов на различных уровнях доверия: различия растут с увеличением уровня доверия. Нижняя правая показывает процентную недооценку риска нормальной моделью относительно Student-t: на уровне 99.5% разница достигает 20-30%
Сравнение VaR и CVaR (долларовая стоимость):
Confidence VaR_Historical VaR_Normal VaR_Student_t CVaR_Historical CVaR_Normal
90.0% -14761.067001 -22936.670666 -15435.393578 -31993.287823 -31518.092518
95.0% -21594.224242 -29521.882206 -23164.201023 -46466.808243 -37095.995844
99.0% -56891.666690 -41874.643882 -49322.216545 -92951.526038 -48016.933759
99.5% -83910.827163 -46396.743157 -66306.050098 -114796.447425 -52126.720209
Обратное тестирование VaR (95% уровень):
Ожидаемое число нарушений (5%): 125
Normal VaR нарушений: 85 (3.40%)
Student-t VaR нарушений: 118 (4.72%)
Historical VaR нарушений: 125 (5.00%)
Симуляция Монте-Карло (10,000 сценариев, 20 дней):
20-дневный VaR (95%):
Normal: $121,414
Student-t: $115,872
Разница: $5,542 (4.8%)
Вероятность потери >10% за 20 дней:
Normal: 9.13%
Student-t: 7.30%
Сравнение VaR между моделями показывает ключевую разницу в оценке рисков:
- На уровне 99% доверия нормальная модель может недооценивать VaR на 20-30% по сравнению с t-распределением. Для портфеля в $1M это означает разницу в несколько десятков тысяч долларов в оценке маржинальных требований.
- Обратное тестирование (backtesting) проверяет адекватность модели VaR. Для 95% VaR ожидается, что фактические убытки превысят VaR примерно в 5% случаев. Если нарушений существенно больше (7-8%), модель недооценивает риски. Если существенно меньше (2-3%), модель слишком консервативна и требует избыточного капитала. Биномиальный тест формально проверяет, соответствует ли частота нарушений заявленному уровню доверия.
- CVaR (Expected Shortfall) измеряет средний убыток при условии превышения VaR, что дает более полную картину хвостового риска. Для нормального распределения CVaR имеет аналитическую формулу, для других распределений вычисляется численно или через симуляции. CVaR всегда больше VaR по абсолютной величине и предпочтительнее как мера риска: он когерентен и учитывает не только квантиль, но и форму хвоста за ним.
Симуляции Монте-Карло позволяют оценить многопериодный риск портфеля, учитывая сложные зависимости и нелинейности. Генерация тысяч сценариев будущих доходностей из подобранного распределения создает эмпирическое распределение конечного капитала. По этому распределению вычисляются VaR, CVaR и вероятности различных событий. Ключевое преимущество данного подхода: он универсален и работает для портфелей с опционами, деривативами и нелинейными выплатами, где аналитические формулы недоступны.
Выбор между историческим, параметрическим и симуляционным методом расчета VaR зависит от доступных данных и характеристик портфеля:
- Исторический метод не требует предположений о распределении, но зависит от репрезентативности выборки и не экстраполирует за пределы наблюдаемых данных;
- Параметрический метод эффективен при достаточной выборке для подгонки распределения и позволяет экстраполировать в хвосты;
- Симуляционный подход универсален, но вычислительно затратен и требует корректной спецификации модели генерации сценариев.
Заключение
Статистические распределения формируют единый базис, с помощью которого в финансовом анализе описывается неопределенность и риск. Выбор между нормальным, t-распределением, GED или экстремальными моделями определяет не только численные значения метрик риска, но и качество управленческих решений. Недооценка хвостовых рисков из-за неадекватной модели приводит к недостаточным капитальным резервам и уязвимости перед кризисами.
Практическое применение требует баланса между точностью модели и ее сложностью:
- T-распределение с 3-4 параметрами часто достаточно для корректного описания финансовых доходностей, обеспечивая существенное улучшение над нормальной моделью без избыточного усложнения.
- Для анализа экстремальных событий EVT предоставляет теоретически обоснованный математический аппарат, фокусирующийся именно на хвостах — зоне максимального риска.
- Комбинация методов — параметрические модели для центральной части распределения, EVT для хвостов, обратное тестирование для валидации — создает устойчивую систему риск-менеджмента, адаптированную к реальности финансовых рынков с их асимметрией, кластеризацией волатильности и неожиданными скачками во временных рядах.