Сначала важная развилка. Не путайте VaR (Value at Risk) — метрику рыночного риска — с VAR (векторной авторегрессией), эконометрической моделью временных рядов. Это разные вещи с похожим написанием; о второй у нас отдельная статья. Здесь речь только о Value at Risk.
Что такое VaR и чего он не показывает
VaR — это квантиль распределения убытков портфеля на заданном горизонте при выбранном уровне доверия. Формулировка «однодневный VaR при уровне доверия 99% равен 1,5 млн ₽» означает: с вероятностью 99% убыток за один день не превысит 1,5 млн ₽, либо, что то же самое, лишь в 1% дней убыток окажется больше этой величины. Три параметра задают VaR полностью: горизонт (1 день, 10 дней), уровень доверия (95%, 99%) и валюта/единица измерения позиции. Менять любой из них без оговорки нельзя — VaR 95% и VaR 99% это разные числа, и сравнивать их напрямую бессмысленно.
Выбор уровня доверия и горизонта — не технический, а управленческий вопрос. Уровень 99% и горизонт 10 дней исторически закреплены в банковском регулировании (надзорные требования к рыночному риску), тогда как для внутреннего управления портфелем чаще удобнее 95% и один день: на коротком горизонте у вас просто больше превышений, а значит больше данных для проверки модели. Чем выше уровень доверия, тем дальше в хвост распределения уходит оценка и тем меньше наблюдений её подкрепляют — оценка 99,9%-квантиля по году дневных данных опирается буквально на единицы точек, поэтому крайние уровни доверия требуют либо очень длинной истории, либо явной модели хвоста.
Ключевое ограничение: VaR — это порог, а не ожидание убытка за этим порогом. Он ничего не говорит о том, насколько тяжёлым окажется убыток в тех самых «плохих» процентах случаев. Два портфеля могут иметь одинаковый VaR, но совершенно разный размер катастрофического хвоста. Поэтому VaR дополняют метрикой Expected Shortfall (ES, она же CVaR) — средним убытком при условии, что порог VaR превышен. У ES есть важное теоретическое преимущество: это когерентная мера риска (в частности, субаддитивная), то есть риск диверсифицированного портфеля у неё не превышает суммы рисков компонентов. VaR в общем случае субаддитивностью не обладает, поэтому регуляторная практика (например, FRTB Базельского комитета) сместилась в сторону ES. Считать VaR по-прежнему полезно, но трактовать его в отрыве от ES не стоит.
Ещё одна общая для всех методов величина — это знак. По соглашению VaR приводят как
положительное число, обозначающее размер убытка, хотя считается он по левому хвосту
распределения доходностей (то есть по отрицательным значениям). В коде ниже мы
последовательно берём квантиль доходностей и переводим его в положительный убыток
умножением на −1; путаница в знаке — одна из
самых частых причин расхождений между двумя реализациями одного и того же метода.
Ниже все три метода используют одни и те же синтетические данные — это иллюстративный пример, числа условны и не относятся к какому-либо реальному портфелю. На практике вместо генератора подставляются исторические доходности ваших инструментов. Мы сознательно генерируем данные из нормального распределения: на таких данных три метода должны давать близкие оценки, и это удобный самопроверочный признак корректности реализации. На реальных рядах с тяжёлыми хвостами оценки начнут расходиться — и именно характер расхождения подскажет, какое допущение нарушено.
import numpy as np
import pandas as pd
from scipy.stats import norm
rng = np.random.default_rng(42)
# Синтетический портфель: 3 актива, 1000 дневных доходностей (иллюстративно)
n_days, n_assets = 1000, 3
mu = np.array([0.0004, 0.0002, 0.0006])
vols = np.array([0.012, 0.018, 0.025])
corr = np.array([[1.0, 0.3, 0.2],
[0.3, 1.0, 0.5],
[0.2, 0.5, 1.0]])
cov = np.outer(vols, vols) * corr
returns = pd.DataFrame(rng.multivariate_normal(mu, cov, size=n_days),
columns=["A", "B", "C"])
# Веса и стоимость портфеля
weights = np.array([0.4, 0.35, 0.25])
portfolio_value = 10_000_000 # условная стоимость портфеля, ₽
port_returns = returns.values @ weights # дневные доходности портфеля
alpha = 0.99 # уровень доверия
Метод 1. Исторический VaR
Самый прямой подход: берём фактические доходности портфеля за окно наблюдений и находим эмпирический квантиль распределения убытков. Если доверие 99%, то VaR — это 1-й процентиль доходностей (умноженный на стоимость портфеля и взятый по модулю как величина убытка). Никаких предположений о форме распределения здесь не делается — хвост берётся таким, каким он был в данных.
def historical_var(port_returns, alpha, value):
# Квантиль доходностей на уровне (1 - alpha): левый хвост
q = np.quantile(port_returns, 1 - alpha)
var = -q * value # убыток как положительное число
# Expected Shortfall: средний убыток за порогом
tail = port_returns[port_returns <= q]
es = -tail.mean() * value
return var, es
var_h, es_h = historical_var(port_returns, alpha, portfolio_value)
print(f"Исторический VaR 99%: {var_h:,.0f} ₽")
print(f"Исторический ES 99%: {es_h:,.0f} ₽")
Плюсы: не требует допущения о распределении, естественно захватывает асимметрию и тяжёлые хвосты, если они есть в выборке. Прозрачен и легко объясним. Минусы: полностью зависит от выбранного окна — короткое окно даёт шумную оценку, длинное смешивает разные режимы рынка. Метод не экстраполирует хвост: VaR не может оказаться больше худшего наблюдённого убытка, поэтому события, которых ещё не было в окне, он по построению недооценивает. Ещё одна тонкость — реакция на смену режима: в простой версии все наблюдения в окне равноправны, и после периода спокойствия модель медленно повышает оценку риска. Чтобы это исправить, применяют взвешенный исторический метод (age-weighting по Boudoukh — Richardson — Whitelaw), где недавним наблюдениям присваивается больший вес, или фильтрованный (filtered historical simulation), где доходности предварительно нормируют на текущую оценку волатильности. Это уже не одна строка кода, а методологическое решение.
Метод 2. Параметрический (дельта-нормальный) VaR
Параметрический, или variance-covariance, метод предполагает, что доходности
портфеля подчиняются нормальному распределению, и тогда VaR выражается аналитически
через волатильность. Для портфеля стоимостью V
со стандартным отклонением дневной доходности
σ и средней
μ формула такова:
VaR = (z_α · σ − μ) · V, где
z_α — квантиль стандартного нормального
распределения. Для 99% это z ≈ 2.326, для 95% —
z ≈ 1.645. На коротком (дневном) горизонте
слагаемым μ часто пренебрегают, считая
VaR ≈ z_α · σ · V.
Волатильность портфеля считается через ковариационную матрицу активов:
σ_p = √(wᵀ Σ w), где
w — веса, а
Σ — ковариационная матрица доходностей.
def parametric_var(returns, weights, alpha, value):
cov = np.cov(returns.values, rowvar=False) # ковариационная матрица
sigma_p = np.sqrt(weights @ cov @ weights) # волатильность портфеля
mu_p = returns.values @ weights
mu_p = mu_p.mean()
z = norm.ppf(alpha) # z ≈ 2.326 при alpha = 0.99
var = (z * sigma_p - mu_p) * value
# ES для нормального распределения: pdf(z)/(1-alpha) * sigma
es = (norm.pdf(z) / (1 - alpha) * sigma_p - mu_p) * value
return var, es
var_p, es_p = parametric_var(returns, weights, alpha, portfolio_value)
print(f"Параметрический VaR 99%: {var_p:,.0f} ₽")
print(f"Параметрический ES 99%: {es_p:,.0f} ₽")
Плюсы: быстрый, аналитический, не требует симуляций; удобен, когда позиций много, а связи между ними линейны. Минусы: предположение нормальности почти всегда недооценивает хвосты — реальные финансовые ряды имеют эксцесс выше нормального (тяжёлые хвосты) и часто асимметрию, поэтому настоящая вероятность крупных убытков больше, чем даёт нормальная модель. Метод также плохо работает с нелинейными инструментами (опционы): дельта-нормальное приближение игнорирует выпуклость (гамму).
Метод 3. Монте-Карло VaR
Здесь мы не опираемся ни на историческую выборку напрямую, ни на замкнутую формулу, а симулируем большое число сценариев доходностей из заданной модели, считаем для каждого PnL портфеля и берём эмпирический квантиль симулированного распределения убытков. В простом варианте сценарии генерируются из многомерного нормального распределения с оценённой ковариационной матрицей; модель легко заменить на более реалистичную (t-распределение с тяжёлыми хвостами, копулы, стохастическую волатильность).
def monte_carlo_var(returns, weights, alpha, value, n_sims=100_000, seed=7):
rng = np.random.default_rng(seed)
mu = returns.mean().values
cov = np.cov(returns.values, rowvar=False)
# Симуляция сценариев доходностей активов
sims = rng.multivariate_normal(mu, cov, size=n_sims)
pnl = (sims @ weights) * value # PnL по каждому сценарию
q = np.quantile(pnl, 1 - alpha)
var = -q
es = -pnl[pnl <= q].mean()
return var, es
var_mc, es_mc = monte_carlo_var(returns, weights, alpha, portfolio_value)
print(f"Монте-Карло VaR 99%: {var_mc:,.0f} ₽")
print(f"Монте-Карло ES 99%: {es_mc:,.0f} ₽")
Плюсы: максимально гибкий — позволяет заложить любое распределение, нелинейные инструменты, переоценку позиций по сценариям. Минусы: вычислительно дорогой (особенно с полной переоценкой деривативов), а результат ровно настолько хорош, насколько корректна заложенная модель: гарантия «как в реальности» отсутствует — это симуляция ваших предположений, а не рынка.
Отдельно стоит держать в голове ошибку симуляции. Квантиль, оценённый по конечному числу сценариев, сам по себе случаен: при малом числе симуляций оценка 99%-VaR заметно «дрожит» от запуска к запуску. Зафиксированный seed делает результат воспроизводимым, но не устраняет смещение — его уменьшает только увеличение числа сценариев или специальные приёмы снижения дисперсии. Поэтому Монте-Карло VaR разумно сопровождать оценкой его собственной погрешности, а не приводить как точное число.
Сравнение трёх методов
| Метод | Допущения о распределении | Хвосты | Стоимость вычислений | Когда применять |
|---|---|---|---|---|
| Исторический | Нет; берётся эмпирическое распределение | Только то, что было в окне; не экстраполирует | Низкая | Достаточно данных и стабильный режим; нужен прозрачный метод |
| Параметрический | Нормальность (или иное замкнутое распределение) | Систематически недооценивает тяжёлые хвосты | Минимальная (формула) | Много линейных позиций; нужна скорость и грубая оценка |
| Монте-Карло | Любая заданная модель доходностей | Зависит от модели; можно заложить тяжёлые хвосты | Высокая | Нелинейные инструменты, сложные зависимости, сценарный анализ |
Какое окно и какие данные брать
До выбора метода стоит решить вопрос о входных данных, потому что он влияет на результат сильнее, чем различие между тремя методами. Длина окна — это компромисс: слишком короткое даёт неустойчивую, шумную оценку и не видит редких событий; слишком длинное растворяет текущий режим в усреднении по разным эпохам рынка. Типичные значения — от одного до нескольких лет дневных наблюдений, но универсального ответа нет, и окно стоит фиксировать осознанно, а не по умолчанию.
Не менее важна качественная сторона данных. Доходности должны быть согласованы по частоте и часовому поясу, очищены от технических артефактов (нулевые доходности в нерабочие дни, ошибочные котировки), а ряды по разным инструментам — выровнены по датам, иначе ковариационная матрица окажется смещённой. Для портфеля важно считать VaR по доходности именно портфеля при текущих весах, а не агрегировать VaR отдельных позиций — последнее игнорирует диверсификацию и систематически завышает риск.
Бэктестинг VaR: проверяете ли вы то, что считаете
Любая оценка VaR требует проверки на данных: совпадает ли заявленный уровень
доверия с фактической частотой превышений (exceptions). Профессиональный стандарт —
тест Купика на долю превышений (Kupiec POF, proportion-of-failures). Идея проста:
если VaR при 99% корректен, превышения должны происходить примерно в 1% дней; тест
сравнивает наблюдённую долю с ожидаемой через статистику отношения
правдоподобия, которая асимптотически распределена как
χ² с одной степенью свободы. Слишком много
превышений — модель недооценивает риск; слишком мало — переоценивает и связывает
лишний капитал.
from scipy.stats import chi2
def kupiec_pof(returns, var_threshold, alpha):
# var_threshold — доходность-порог (отрицательная) для уровня alpha
exceptions = returns < var_threshold
n = len(returns)
x = exceptions.sum() # число превышений
p = 1 - alpha # ожидаемая доля, напр. 0.01
pi_hat = x / n
# Статистика отношения правдоподобия Купика
lr = -2 * (np.log((1 - p)**(n - x) * p**x)
- np.log((1 - pi_hat)**(n - x) * pi_hat**x))
p_value = 1 - chi2.cdf(lr, df=1)
return x, n * p, lr, p_value
thr = np.quantile(port_returns, 1 - alpha)
x, expected, lr, pval = kupiec_pof(port_returns, thr, alpha)
print(f"Превышений: {x}, ожидалось ≈ {expected:.1f}, p-value Купика: {pval:.3f}")
На практике одного POF-теста мало: его дополняют тестом на независимость превышений (Christoffersen) — важно, чтобы превышения не группировались в кластеры, что указывало бы на неучтённую динамику волатильности. Стоит помнить и об обратной стороне: Expected Shortfall, в отличие от VaR, тестировать на превышениях напрямую нельзя — он не является элиситируемым в одиночку, поэтому его бэктест устроен сложнее (например, совместная проверка пары VaR-ES). Это ещё одна причина докладывать обе метрики, а не выбирать одну.
Типовые ошибки
-
Масштабирование √t без оснований. Перевод дневного VaR в
10-дневный по правилу
VaR_10 = VaR_1 · √10корректен только при независимых одинаково распределённых доходностях. При автокорреляции или кластеризации волатильности это правило искажает оценку. - Слепая нормальность. Параметрический VaR удобен, но предположение нормальности почти всегда занижает хвост. Проверяйте эксцесс и асимметрию; при тяжёлых хвостах используйте t-распределение или историю.
- Слишком короткое окно. Окно в несколько десятков наблюдений даёт неустойчивый квантиль, который скачет от пересчёта к пересчёту и не захватывает редкие события.
- Игнорирование ES. VaR молчит о размере убытка за порогом. Без Expected Shortfall вы не видите, насколько тяжёлым может быть «плохой» сценарий.
Как с этим работает StatGazer
VaR — это не одна строчка кода, а методологический выбор: горизонт, окно, модель хвоста, способ бэктеста. Мы строим риск-модели под конкретный портфель и регламент на странице финансовой аналитики, а если у вас уже есть своя модель VaR или ES — проводим её независимую проверку и валидацию: воспроизводим расчёт, проверяем допущения и бэктест, фиксируем границы применимости. Описать задачу можно в контактах.