Como calcular o Value at Risk: 3 métodos
Veja como calcular o VaR histórico, paramétrico e de Monte Carlo na mesma série de retornos do SPY, além do expected shortfall que o VaR não captura.
O value at risk, ou VaR, é calculado definindo um horizonte, fixando um nível de confiança e, em seguida, obtendo um percentil de uma série de retornos. Um VaR de 2% para um dia e com nível de confiança de 99% significa que a perda fica abaixo de 2% em 99 de cada 100 sessões e supera esse valor na centésima. Três métodos padrão respondem a essa pergunta com os mesmos dados de entrada, mas divergem mais do que a maioria das pessoas espera.
O que o value at risk realmente mede
O VaR é um quantil de uma distribuição de perdas. Ordene todos os retornos diários de uma amostra, do pior ao melhor. Avance 1% a partir da extremidade negativa. O retorno encontrado, expresso como uma perda positiva, é o VaR histórico de 1 dia a 99%. Nada nessa construção promete um pior caso. Ela apenas marca o limite da região que a estimativa deixa de descrever.
Essa é a propriedade que os leitores mais costumam interpretar mal. Um VaR de 99% indica que os piores 1% dos dias estão além do limite. Ele não informa até onde chegam. Drawdown máximo responde a uma pergunta diferente: qual foi a perda do pico ao vale que uma carteira efetivamente sofreu. As duas métricas podem ordenar o mesmo par de carteiras em posições opostas.
Todos os números abaixo usam uma única série: os fechamentos diários do SPY do início de 2010 ao fim de 2025, convertidos em variações percentuais de fechamento a fechamento. A janela é fixa, não móvel. Cada painel reconstrói a série com essas mesmas datas. Assim, os números não mudam entre as execuções.
O SQL exato por trás de cada número
WITH
px AS
(
SELECT date, max(toFloat64(close)) AS close_px
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2009-12-01'
AND date < '2026-01-01'
GROUP BY date
),
rets AS
(
SELECT date, 100 * (close_px / prev_close - 1) AS ret_pct
FROM
(
SELECT date, close_px,
lagInFrame(close_px, 1) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_close
FROM px
)
WHERE date >= '2010-01-01' AND prev_close > 0
)
SELECT
toString(toYear(date)) AS year,
count() AS sessions,
round(avg(ret_pct), 3) AS mean_return_pct,
round(stddevSamp(ret_pct), 2) AS daily_sigma_pct
FROM rets
GROUP BY year
ORDER BY yearA série abrange 16 anos-calendário e 252 sessões apenas em 2010. Duas características são relevantes para tudo o que vem a seguir. A média diária é quase irrelevante neste horizonte: 0.054% em 2010, contra um desvio-padrão diário, sigma, de 1.13%. E sigma não é constante. 2020 registrou 2.11% por dia, enquanto 2017 registrou 0.43%. Um único sigma não consegue descrever os dois períodos.
Como é calculado o value at risk, de três formas
VaR histórico: ler o percentil com base no que aconteceu
Ordene os retornos observados e obtenha o percentil. Nenhuma distribuição é pressuposta, o que é a principal vantagem do método. A premissa é que a amostra já contenha o tipo de dia que a estimativa pretende cobrir. Quando o nível de confiança se afasta o suficiente, apenas as poucas sessões mais negativas de toda a janela determinam o resultado.
VaR paramétrico: média menos z vezes sigma
Resuma a série pela média e pelo sigma e, em seguida, pressuponha que os retornos seguem uma distribuição normal. O VaR é z vezes sigma menos a média, em que z é o quantil da distribuição normal padrão: 1,645 a 95%, 2,326 a 99% e 3,090 a 99,9%. O cálculo é imediato, mas a premissa falha em uma direção específica. Os retornos diários de ações concentram-se mais no centro do que uma curva normal e alcançam extremos muito mais distantes. Aqui, sigma é o denominador que também compõe o índice de Sharpe e traz o mesmo ponto cego para dados de cauda pesada.
VaR de Monte Carlo: simular com a seed fixada
Gere uma grande amostra sintética a partir de um processo pressuposto e obtenha o percentil dos resultados simulados. O painel abaixo usa 40.000 observações normais padrão geradas pela transformação de Box-Muller a partir de uma sequência uniforme inicializada por hash, redimensionada para a média e o sigma da série. A seed está no SQL, portanto as observações são idênticas em todas as execuções. A simulação oferece flexibilidade, dependência do caminho e correlação entre múltiplos ativos, mas não oferece realismo: se receber uma distribuição normal, devolverá a resposta paramétrica acrescida do ruído da amostragem. Reamostrar os retornos observados, a técnica por trás de intervalos de confiança bootstrap, mantém a cauda real em consideração.
O SQL exato por trás de cada número
WITH
px AS
(
SELECT date, max(toFloat64(close)) AS close_px
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2009-12-01'
AND date < '2026-01-01'
GROUP BY date
),
rets AS
(
SELECT date, 100 * (close_px / prev_close - 1) AS ret_pct
FROM
(
SELECT date, close_px,
lagInFrame(close_px, 1) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_close
FROM px
)
WHERE date >= '2010-01-01' AND prev_close > 0
),
emp AS
(
SELECT
avg(ret_pct) AS mu,
stddevSamp(ret_pct) AS sd,
quantileExact(0.100)(ret_pct) AS h90,
quantileExact(0.050)(ret_pct) AS h95,
quantileExact(0.025)(ret_pct) AS h975,
quantileExact(0.010)(ret_pct) AS h99,
quantileExact(0.005)(ret_pct) AS h995,
quantileExact(0.001)(ret_pct) AS h999
FROM rets
),
draws AS
(
SELECT
quantileExact(0.100)(z) AS z90,
quantileExact(0.050)(z) AS z95,
quantileExact(0.025)(z) AS z975,
quantileExact(0.010)(z) AS z99,
quantileExact(0.005)(z) AS z995,
quantileExact(0.001)(z) AS z999
FROM
(
SELECT sqrt(-2 * log(u1)) * cos(2 * pi() * u2) AS z
FROM
(
SELECT
(cityHash64('var-seed-u1', i) % 999999937 + 1) / 999999938.0 AS u1,
(cityHash64('var-seed-u2', i) % 999999937 + 1) / 999999938.0 AS u2
FROM (SELECT arrayJoin(range(40000)) AS i)
)
)
)
SELECT
tupleElement(lvl, 1) AS confidence,
round(-1 * tupleElement(lvl, 2), 2) AS historical_var_pct,
round(tupleElement(lvl, 3) * sd - mu, 2) AS parametric_var_pct,
round(-1 * (mu + sd * tupleElement(lvl, 4)), 2) AS monte_carlo_var_pct,
round(-1 * tupleElement(lvl, 2) - (tupleElement(lvl, 3) * sd - mu), 2) AS method_spread
FROM
(
SELECT
mu,
sd,
arrayJoin([
('90.0%', h90, 1.281552, z90, 1),
('95.0%', h95, 1.644854, z95, 2),
('97.5%', h975, 1.959964, z975, 3),
('99.0%', h99, 2.326348, z99, 4),
('99.5%', h995, 2.575829, z995, 5),
('99.9%', h999, 3.090232, z999, 6)
]) AS lvl
FROM emp
CROSS JOIN draws
)
ORDER BY tupleElement(lvl, 5)No nível 95.0%, os três métodos ficam a um ponto de distância entre si: 1.66% histórico, 1.74% paramétrico e 1.71% simulado. Em 99.9%, eles se separam: 5.85% histórico contra 3.31% paramétrico, uma diferença de 2.54 pontos percentuais com dados idênticos. A coluna simulada fica ao lado da coluna paramétrica em todos os níveis, e essa é a conclusão, não uma falha. A simulação reproduz qualquer distribuição que lhe seja fornecida.
Leia o painel de cima para baixo em uma coluna, e cada aumento da confiança amplia o limite. Leia-o na horizontal, por linha, e a escolha do método quase não importa no centro da distribuição, mas domina na cauda. Um limite de VaR informado sem o método e a janela utilizados não é um número que outra pessoa consiga reproduzir.
O que o VaR não mostra: expected shortfall
O expected shortfall, também chamado de VaR condicional, calcula a média das perdas nos dias que ultrapassam o VaR. O VaR indica onde começa a cauda da distribuição. O expected shortfall mede o que ocorre dentro dela.
O SQL exato por trás de cada número
WITH
px AS
(
SELECT date, max(toFloat64(close)) AS close_px
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2009-12-01'
AND date < '2026-01-01'
GROUP BY date
),
rets AS
(
SELECT date, 100 * (close_px / prev_close - 1) AS ret_pct
FROM
(
SELECT date, close_px,
lagInFrame(close_px, 1) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_close
FROM px
)
WHERE date >= '2010-01-01' AND prev_close > 0
),
qs AS
(
SELECT
quantileExact(0.100)(ret_pct) AS q90,
quantileExact(0.050)(ret_pct) AS q95,
quantileExact(0.025)(ret_pct) AS q975,
quantileExact(0.010)(ret_pct) AS q99,
quantileExact(0.005)(ret_pct) AS q995,
quantileExact(0.001)(ret_pct) AS q999
FROM rets
),
tails AS
(
SELECT
any(q90) AS var90,
any(q95) AS var95,
any(q975) AS var975,
any(q99) AS var99,
any(q995) AS var995,
any(q999) AS var999,
avgIf(ret_pct, ret_pct <= q90) AS es90,
avgIf(ret_pct, ret_pct <= q95) AS es95,
avgIf(ret_pct, ret_pct <= q975) AS es975,
avgIf(ret_pct, ret_pct <= q99) AS es99,
avgIf(ret_pct, ret_pct <= q995) AS es995,
avgIf(ret_pct, ret_pct <= q999) AS es999
FROM rets
CROSS JOIN qs
HAVING countIf(ret_pct <= q999) > 0
)
SELECT
tupleElement(lvl, 1) AS confidence,
round(-1 * tupleElement(lvl, 2), 2) AS historical_var_pct,
round(-1 * tupleElement(lvl, 3), 2) AS expected_shortfall_pct,
round(tupleElement(lvl, 3) / tupleElement(lvl, 2), 2) AS es_to_var_ratio
FROM
(
SELECT
arrayJoin([
('90.0%', var90, es90, 1),
('95.0%', var95, es95, 2),
('97.5%', var975, es975, 3),
('99.0%', var99, es99, 4),
('99.5%', var995, es995, 5),
('99.9%', var999, es999, 6)
]) AS lvl
FROM tails
)
ORDER BY tupleElement(lvl, 4)Em 99.0%, o VaR desta série é 3.09% e o expected shortfall é 4.43%, ou 1.44 vezes o limite. Em uma distribuição normal, essa razão ficaria perto de 1,15 no mesmo nível. Mesmo em 99.9%, quando o limite já chegou a 5.85%, a média dos dias que ultrapassam o limite é 8.14%. Um limite baseado apenas no VaR trata todos os excessos como se fossem o mesmo evento. A coluna da razão mostra como essa suposição está distante da realidade.
O VaR de 1 dia a 99% significa a mesma coisa para uma instituição e para um trader?
Não. A diferença está sobretudo no horizonte. Um trader que encerra o dia sem posição mantém risco durante algumas horas. Por isso, um limite de uma sessão corresponde ao período de manutenção da posição, embora a trajetória intradiária possa ultrapassar bastante o resultado entre fechamentos. Uma instituição que financia passivos de longo prazo mantém posições durante anos. A sua exposição estende-se por trimestres. Nesse caso, o VaR de 1 dia a 99% funciona como instrumento de capital e monitorização, e não como descrição do risco efetivamente assumido. Durante anos, as regras de capital bancário basearam-se num VaR de 1 dia a 99%. Mais tarde, o enquadramento de risco de mercado de Basileia passou a usar um expected shortfall de 97,5%.
A forma padrão de ligar horizontes é escalar pela raiz quadrada do tempo: multiplicar o valor de uma sessão pela raiz quadrada do número de sessões no horizonte. Esse procedimento pressupõe retornos independentes e um sigma constante. A coluna de sigma ano a ano acima já mostra a segunda metade desse pressuposto a falhar.
O SQL exato por trás de cada número
WITH
px AS
(
SELECT date, max(toFloat64(close)) AS close_px
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2009-11-01'
AND date < '2026-01-01'
GROUP BY date
),
multi AS
(
SELECT
date,
100 * (close_px / p1 - 1) AS r1,
100 * (close_px / p5 - 1) AS r5,
100 * (close_px / p10 - 1) AS r10,
100 * (close_px / p20 - 1) AS r20
FROM
(
SELECT
date,
close_px,
lagInFrame(close_px, 1) OVER (ORDER BY date ROWS BETWEEN 20 PRECEDING AND CURRENT ROW) AS p1,
lagInFrame(close_px, 5) OVER (ORDER BY date ROWS BETWEEN 20 PRECEDING AND CURRENT ROW) AS p5,
lagInFrame(close_px, 10) OVER (ORDER BY date ROWS BETWEEN 20 PRECEDING AND CURRENT ROW) AS p10,
lagInFrame(close_px, 20) OVER (ORDER BY date ROWS BETWEEN 20 PRECEDING AND CURRENT ROW) AS p20
FROM px
)
WHERE date >= '2010-01-01' AND p20 > 0
),
q AS
(
SELECT
quantileExact(0.01)(r1) AS q1,
quantileExact(0.01)(r5) AS q5,
quantileExact(0.01)(r10) AS q10,
quantileExact(0.01)(r20) AS q20
FROM multi
)
SELECT
tupleElement(h, 1) AS horizon,
round(-1 * tupleElement(h, 3), 2) AS actual_var_pct,
round(-1 * q1 * sqrt(tupleElement(h, 2)), 2) AS sqrt_scaled_var_pct,
round(tupleElement(h, 3) / (q1 * sqrt(tupleElement(h, 2))), 2) AS actual_to_scaled_ratio
FROM
(
SELECT
q1,
arrayJoin([
('1 session', 1.0, q1, 1),
('5 sessions', 5.0, q5, 2),
('10 sessions', 10.0, q10, 3),
('20 sessions', 20.0, q20, 4)
]) AS h
FROM q
)
ORDER BY tupleElement(h, 4)A primeira linha é uma verificação de identidade: em 1 session, o valor escalado é igual ao valor medido, com um rácio de 1. Em 20 sessions, os dois valores divergem, e não na direção normalmente apontada como risco do método simplificado: escalar o valor de uma sessão produz 13.8%, enquanto os retornos medidos nesse horizonte registam 10.7%, com um rácio de 0.78. Nesta janela, o valor escalado fica acima da cauda medida para várias sessões. Dois padrões atuam em sentidos opostos. Uma distribuição de uma sessão com caudas pesadas torna-se menos extrema quando os retornos são agregados. Assim, o quantil de 99% agregado aumenta mais lentamente do que a raiz quadrada do horizonte. O agrupamento da volatilidade atua no sentido contrário, concentrando sessões de forte oscilação dentro da mesma janela. Nesta série, o primeiro padrão é mais forte. Nenhuma das duas direções é garantida noutra janela ou noutro ativo. Esse é o ponto: o multiplicador é uma hipótese, não uma medição. Esse agrupamento também é a propriedade em que o volatility targeting se apoia para redimensionar posições.
Há ainda uma limitação, que vale enunciar de forma direta. Tudo acima é um VaR de ativo único sobre um fundo de índice amplo. Uma carteira com dez ativos correlacionados apresenta risco de concentração, que um VaR de carteira trata por meio de uma estimativa de correlação. As correlações, porém, variam mais precisamente quando a estimativa é mais importante.
Notas metodológicas e convenções
- Os percentis vêm de um quantil exato, e as simulações de Monte Carlo usam uma sequência uniforme inicializada por hash e escrita no SQL. Assim, todos os valores podem ser recalculados e reproduzem o mesmo resultado.
- Os retornos são variações de preço entre fechamentos, sem reinvestimento de dividendos, que é a convenção usual para um VaR de uma sessão.
- O painel de horizontes usa janelas sobrepostas: retornos consecutivos de 20 sessões partilham 19 dias. Por isso, a cauda baseia-se em muito menos observações independentes do que o número de linhas sugere.
Perguntas frequentes
O que é um VaR de 1 dia a 99%?
É o nível de perda que é ultrapassado pelo pior 1 em cada 100 dias de negociação, medido ao longo de uma única sessão. Na série acima, a estimativa histórica é 3.09%. O valor identifica um limiar, mas não informa o tamanho das perdas que o ultrapassam.
Qual método de cálculo do VaR é mais preciso?
Nenhum dos três é preciso em termos absolutos, pois cada um responde à pergunta sob premissas diferentes. O VaR histórico é fiel à amostra recebida, mas não diz nada sobre eventos que ficaram fora dela. O VaR paramétrico é barato e subestima as caudas das ações. O método de Monte Carlo só é tão bom quanto a distribuição usada como entrada.
Qual é a diferença entre VaR e expected shortfall?
O VaR é o limiar correspondente a um determinado nível de confiança. O expected shortfall calcula a média das perdas nos dias que ultrapassam esse limiar. Nesta série, o expected shortfall ao nível de 99% equivale a 1.44 vezes o VaR, contra aproximadamente 1,15 vez sob uma distribuição normal.
É possível converter um VaR de 1 dia em um VaR de 10 dias?
Multiplicar pela raiz quadrada de 10 é o atalho padrão. Essa abordagem pressupõe que os retornos são independentes e que a volatilidade é constante. O painel de horizontes mede essa diferença: ao longo de 20 sessões, o valor escalado foi 13.8%, contra 10.7% no valor medido.
Cada painel acima contém o SQL que gerou os dados. Para executar os mesmos três cálculos com outro ticker ou outra janela, faça o pedido em inglês simples no terminal Strasmore.