Риск-менеджмент

Как рассчитать VaR портфеля: три метода с кодом

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

Сначала важная развилка. Не путайте VaR (Value at Risk) — метрику рыночного риска — с VAR (векторной авторегрессией), эконометрической моделью временных рядов. Это разные вещи с похожим написанием; о второй у нас отдельная статья. Здесь речь только о Value at Risk.

Что такое VaR и чего он не показывает

VaR — это квантиль распределения убытков портфеля1 на заданном горизонте при выбранном уровне доверия. Формулировка «однодневный VaR при уровне доверия 99% равен 1,5 млн ₽» означает: с вероятностью 99% убыток за один день не превысит 1,5 млн ₽, либо, что то же самое, лишь в 1% дней убыток окажется больше этой величины. Три параметра задают VaR полностью: горизонт (1 день, 10 дней), уровень доверия (95%, 99%) и валюта/единица измерения позиции. Менять любой из них без оговорки нельзя — VaR 95% и VaR 99% это разные числа, и сравнивать их напрямую бессмысленно.

Выбор уровня доверия и горизонта — не технический, а управленческий вопрос. Уровень 99% и горизонт 10 дней исторически закреплены в банковском регулировании (надзорные требования к рыночному риску), тогда как для внутреннего управления портфелем чаще удобнее 95% и один день: на коротком горизонте у вас просто больше превышений, а значит больше данных для проверки модели. Чем выше уровень доверия, тем дальше в хвост распределения уходит оценка и тем меньше наблюдений её подкрепляют — оценка 99,9%-квантиля по году дневных данных опирается буквально на единицы точек, поэтому крайние уровни доверия требуют либо очень длинной истории, либо явной модели хвоста2.

Ключевое ограничение: VaR — это порог, а не ожидание убытка за этим порогом. Он ничего не говорит о том, насколько тяжёлым окажется убыток в тех самых «плохих» процентах случаев. Два портфеля могут иметь одинаковый VaR, но совершенно разный размер катастрофического хвоста. Поэтому VaR дополняют метрикой Expected Shortfall (ES, она же CVaR)34 — средним убытком при условии, что порог VaR превышен. У ES есть важное теоретическое преимущество: это когерентная мера риска5 (в частности, субаддитивная), то есть риск диверсифицированного портфеля у неё не превышает суммы рисков компонентов. VaR в общем случае субаддитивностью не обладает, поэтому регуляторная практика (например, FRTB Базельского комитета6) сместилась в сторону ES. Считать VaR по-прежнему полезно, но трактовать его в отрыве от ES не стоит.

Главное: VaR — это квантиль убытка («порог потерь»), а не их среднее за порогом. Всегда смотрите его вместе с Expected Shortfall.
-6 -4 -2 0 2 4 6 μ VaR₉₉ = 3.49% порог убытка ES₉₉ = 4.00% среднее за порогом 1% «плохих» дней дневная доходность портфеля, % (левый хвост = убыток)
Рис. 1. VaR и Expected Shortfall на распределении доходностей. VaR₉₉ — это порог: левее него лежит 1% худших дней. Expected Shortfall (ES) — среднее по этому хвосту, оно всегда дальше VaR. Два портфеля с одинаковым VaR могут иметь разный ES: именно 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)7, где недавним наблюдениям присваивается больший вес, или фильтрованный (filtered historical simulation)8, где доходности предварительно нормируют на текущую оценку волатильности. Это уже не одна строка кода, а методологическое решение.

Метод 2. Параметрический (дельта-нормальный) VaR

Параметрический, или variance-covariance9, метод предполагает, что доходности портфеля подчиняются нормальному распределению, и тогда VaR выражается аналитически через волатильность. Для портфеля стоимостью V со стандартным отклонением дневной доходности σ и средней μ формула такова: VaR = (z_α · σ − μ) · V, где z_α — квантиль стандартного нормального распределения. Для 99% это z ≈ 2.326, для 95% — z ≈ 1.645. На коротком (дневном) горизонте слагаемым μ часто пренебрегают, считая VaR ≈ z_α · σ · V.

Волатильность портфеля считается через ковариационную матрицу активов10: σ_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} ₽")

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

-5 -4 -3 -2 -1 0 1 2 3 4 5 −2.33σ −2.65σ тяжёлый хвост t нормальное t (ν=4) VaR₉₉ (в σ): норм 2.33t 2.65 ES₉₉ (в σ): норм 2.67t 3.66 та же дисперсия — но хвост t тяжелее доходность в стандартных отклонениях (σ)
Рис. 2. Почему нормальность недооценивает хвост. Оба распределения имеют одинаковую дисперсию, но у t-распределения (ν=4) тяжелее хвост — в зоне крупных убытков его плотность выше. Поэтому и VaR₉₉, и особенно ES₉₉ у t заметно больше, чем даёт нормальная модель. На реальных рядах с тяжёлыми хвостами параметрический (нормальный) VaR систематически занижает риск. Синтетические данные · для наглядности.

Метод 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 делает результат воспроизводимым, но не устраняет смещение — его уменьшает только увеличение числа сценариев или специальные приёмы снижения дисперсии11. Поэтому Монте-Карло VaR разумно сопровождать оценкой его собственной погрешности, а не приводить как точное число.

Сравнение трёх методов

Метод Допущения о распределении Хвосты Стоимость вычислений Когда применять
Исторический Нет; берётся эмпирическое распределение Только то, что было в окне; не экстраполирует Низкая Достаточно данных и стабильный режим; нужен прозрачный метод
Параметрический Нормальность (или иное замкнутое распределение) Систематически недооценивает тяжёлые хвосты Минимальная (формула) Много линейных позиций; нужна скорость и грубая оценка
Монте-Карло Любая заданная модель доходностей Зависит от модели; можно заложить тяжёлые хвосты Высокая Нелинейные инструменты, сложные зависимости, сценарный анализ

Какое окно и какие данные брать

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

Не менее важна качественная сторона данных. Доходности должны быть согласованы по частоте и часовому поясу, очищены от технических артефактов (нулевые доходности в нерабочие дни, ошибочные котировки), а ряды по разным инструментам — выровнены по датам, иначе ковариационная матрица окажется смещённой. Для портфеля важно считать VaR по доходности именно портфеля при текущих весах, а не агрегировать VaR отдельных позиций — последнее игнорирует диверсификацию и систематически завышает риск.

Бэктестинг VaR: проверяете ли вы то, что считаете

Любая оценка VaR требует проверки на данных: совпадает ли заявленный уровень доверия с фактической частотой превышений (exceptions). Профессиональный стандарт — тест Купика на долю превышений12 (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)13 — важно, чтобы превышения не группировались в кластеры, что указывало бы на неучтённую динамику волатильности. Стоит помнить и об обратной стороне: Expected Shortfall, в отличие от VaR, тестировать на превышениях напрямую нельзя — он не является элиситируемым в одиночку14, поэтому его бэктест устроен сложнее (например, совместная проверка пары VaR-ES15). Это ещё одна причина докладывать обе метрики, а не выбирать одну.

-6 -4 -2 0 2 4 всплеск волатильности порог VaR₉₉ = -2.79% Превышений: 5 · ожидалось ≈ 2.5 (1% × 250) 0 50 100 150 200 249 торговый день дневная доходность, %
Рис. 3. Бэктест VaR: превышения во времени. Дневная доходность портфеля против порога VaR₉₉; дни, где убыток превысил порог, отмечены точками. При корректной модели на горизонте 250 дней ожидается ≈ 2.5 превышения. Тест Купика (POF) сверяет наблюдённую долю с ожидаемой; группировка превышений в кластер (здесь — во всплеск волатильности) — сигнал для теста независимости Кристофферсена. Синтетические данные · для наглядности.

Типовые ошибки

  • Масштабирование √t без оснований. Перевод дневного VaR в 10-дневный по правилу VaR_10 = VaR_1 · √10 корректен только при независимых одинаково распределённых доходностях. При автокорреляции или кластеризации волатильности это правило искажает оценку.
  • Слепая нормальность. Параметрический VaR удобен, но предположение нормальности почти всегда занижает хвост. Проверяйте эксцесс и асимметрию; при тяжёлых хвостах используйте t-распределение или историю.
  • Слишком короткое окно. Окно в несколько десятков наблюдений даёт неустойчивый квантиль, который скачет от пересчёта к пересчёту и не захватывает редкие события.
  • Игнорирование ES. VaR молчит о размере убытка за порогом. Без Expected Shortfall вы не видите, насколько тяжёлым может быть «плохой» сценарий.

Как с этим работает StatGazer

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

Источники

Ключевые работы по Value at Risk, Expected Shortfall, когерентным мерам риска и бэктесту, упомянутым в статье. Номера-сноски в тексте ссылаются на этот список.

  1. Jorion, P. (2007). Value at Risk: The New Benchmark for Managing Financial Risk (3rd ed.). New York: McGraw-Hill. books.google.com. — каноническая монография по VaR: определение меры, три метода расчёта и бэктестинг.
  2. McNeil, A. J., Frey, R., & Embrechts, P. (2015). Quantitative Risk Management: Concepts, Techniques and Tools (Revised ed.). Princeton, NJ: Princeton University Press. press.princeton.edu. — справочник по количественному риск-менеджменту: EVT, копулы, когерентные меры и моделирование хвостов.
  3. Rockafellar, R. T., & Uryasev, S. (2000). Optimization of Conditional Value-at-Risk. Journal of Risk, 2(3), 21–41. doi:10.21314/JOR.2000.038. — вводит CVaR (Expected Shortfall) и его линейную оптимизацию для портфеля.
  4. Acerbi, C., & Tasche, D. (2002). On the Coherence of Expected Shortfall. Journal of Banking & Finance, 26(7), 1487–1503. doi:10.1016/S0378-4266(02)00283-2. — доказывает когерентность (в т.ч. субаддитивность) Expected Shortfall.
  5. Artzner, P., Delbaen, F., Eber, J.-M., & Heath, D. (1999). Coherent Measures of Risk. Mathematical Finance, 9(3), 203–228. doi:10.1111/1467-9965.00068. — аксиоматика когерентных мер риска; показывает, что VaR в общем случае не субаддитивен.
  6. Basel Committee on Banking Supervision (2019). Minimum capital requirements for market risk. Bank for International Settlements (BCBS №457). bis.org. — финальный стандарт FRTB, закрепивший переход рыночного риска с VaR на Expected Shortfall.
  7. Boudoukh, J., Richardson, M., & Whitelaw, R. (1998). The Best of Both Worlds: A Hybrid Approach to Calculating Value at Risk. Risk, 11(5), 64–67. pages.stern.nyu.edu. — гибридный (age-weighted) исторический VaR: экспоненциально убывающие веса по возрасту наблюдений.
  8. Barone-Adesi, G., Giannopoulos, K., & Vosper, L. (1999). VaR without Correlations for Portfolios of Derivative Securities. Journal of Futures Markets, 19(5), 583–602. doi.org. — фильтрованная историческая симуляция (FHS): историческая симуляция, нормированная GARCH-волатильностью.
  9. J.P. Morgan/Reuters (1996). RiskMetrics — Technical Document (4th ed.). New York: Morgan Guaranty Trust / Reuters. msci.com. — первоисточник параметрической (variance-covariance) методологии VaR.
  10. Markowitz, H. (1952). Portfolio Selection. The Journal of Finance, 7(1), 77–91. doi:10.1111/j.1540-6261.1952.tb01525.x. — вводит дисперсию портфеля через ковариации активов (σ² = wᵀΣw) как меру риска.
  11. Glasserman, P. (2003). Monte Carlo Methods in Financial Engineering. New York: Springer. doi:10.1007/978-0-387-21617-1. — методы Монте-Карло и снижения дисперсии в финансовой инженерии.
  12. Kupiec, P. H. (1995). Techniques for Verifying the Accuracy of Risk Measurement Models. Journal of Derivatives, 3(2), 73–84. doi:10.3905/jod.1995.407942. — тест на долю превышений (POF) — стандарт бэктеста точности VaR.
  13. Christoffersen, P. F. (1998). Evaluating Interval Forecasts. International Economic Review, 39(4), 841–862. doi:10.2307/2527341. — тест на независимость / условное покрытие превышений VaR.
  14. Gneiting, T. (2011). Making and Evaluating Point Forecasts. Journal of the American Statistical Association, 106(494), 746–762. doi:10.1198/jasa.2011.r10138. — элиситируемость функционалов; обосновывает, почему ES нельзя бэктестировать в одиночку напрямую.
  15. Fissler, T., & Ziegel, J. F. (2016). Higher Order Elicitability and Osband's Principle. Annals of Statistics, 44(4), 1680–1707. doi:10.1214/16-AOS1439. — совместная (higher-order) элиситируемость пары (VaR, ES) — теоретическая основа их совместного бэктеста.

Нужна модель, а не статья?

Опишите задачу.
Ответим в течение 24 часов.

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

NDA до передачи данных · границы работ, KPI и сроки фиксируются до старта · hello@statgazer.ru