Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor · · Updated 2026-08-21

Come si calcola il Value at Risk: 3 metodi

VaR storico, parametrico e Monte Carlo sulla stessa serie di rendimenti SPY, con expected shortfall per capire cosa il VaR non mostra e confrontare i risultati.

Il value at risk, o VaR, si calcola fissando un orizzonte temporale e un livello di confidenza, quindi ricavando un percentile da una serie di rendimenti. Un VaR a un giorno, con un livello di confidenza del 99%, pari al 2% significa che la perdita resta inferiore al 2% in 99 sessioni su 100 e lo supera nella centesima. Tre metodi standard rispondono a questa domanda utilizzando gli stessi input, ma producono risultati più divergenti di quanto la maggior parte delle persone si aspetti.

Cosa misura realmente il value at risk

Il VaR è un quantile della distribuzione delle perdite. Si ordinano tutti i rendimenti giornalieri di un campione dal peggiore al migliore, si percorre l’1% partendo dall’estremo negativo e il rendimento individuato, espresso come perdita positiva, è il VaR storico a un giorno con livello di confidenza del 99%. Questa costruzione non garantisce in alcun modo uno scenario peggiore. Indica il limite della regione che la stima non descrive più.

È questa la proprietà che i lettori interpretano più spesso in modo errato. Un VaR al 99% indica che l’1% peggiore delle giornate si trova oltre la soglia, ma non dice di quanto. Massimo drawdown risponde a una domanda diversa: misura la perdita dal picco al minimo effettivamente subita da un portafoglio. I due indicatori possono quindi ordinare la stessa coppia di portafogli in senso opposto.

Tutti i dati riportati di seguito si basano su un’unica serie: le chiusure giornaliere di SPY dall’inizio del 2010 alla fine del 2025, convertite in variazioni percentuali da chiusura a chiusura. La finestra è fissa, non mobile, e ogni pannello ricostruisce la serie utilizzando le stesse date. I valori non cambiano quindi tra un’elaborazione e l’altra.

QuerySerie di rendimenti fissata: rendimenti giornalieri di SPY per anno solare, dal 2010 al 2025
L'esatto SQL dietro ogni numero
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 year
Run this yourself

La serie copre 16 anni solari, di cui 252 sedute nel solo 2010. Due caratteristiche sono rilevanti per tutto ciò che segue. Su questo orizzonte la media giornaliera è quasi irrilevante: 0.054% in 2010, a fronte di una deviazione standard giornaliera, sigma, pari a 1.13%. Inoltre, sigma non è costante. Nel periodo 2020 era pari a 2.11% al giorno, mentre nel periodo 2017 era pari a 0.43%. Un’unica sigma non può descrivere entrambi.

Come si calcola il value at risk, in tre modi

VaR storico: si legge il percentile dai dati osservati

Si ordinano i rendimenti effettivi e si prende il percentile. Non si assume alcuna distribuzione, ed è questo il vantaggio del metodo. L’ipotesi implicita è che il campione contenga già il tipo di giornata che la stima deve rappresentare. Spingendo il livello di confidenza abbastanza lontano, il risultato dipende soltanto dalle poche sedute peggiori dell’intera finestra.

VaR parametrico: media meno z per sigma

Si riassume la serie con la sua media e il suo sigma, quindi si assume che i rendimenti seguano una distribuzione normale. Il VaR è pari a z per sigma meno la media, dove z è il quantile della normale standard: 1.645 al 95%, 2.326 al 99%, 3.090 al 99,9%. Il calcolo è immediato, ma l’ipotesi fallisce in una direzione precisa. I rendimenti azionari giornalieri sono più concentrati rispetto a una curva normale nella parte centrale e si spingono molto più lontano agli estremi. Qui sigma è anche il denominatore del lo Sharpe ratio e porta con sé lo stesso punto cieco quando i dati hanno code pesanti.

VaR Monte Carlo: si simula, con il seed fissato

Si genera un ampio campione sintetico da un processo ipotizzato e si legge il percentile dai valori estratti. Il pannello seguente utilizza 40.000 estrazioni da una normale standard, generate con la trasformazione di Box-Muller a partire da una sequenza uniforme con seed determinato da un hash, poi scalate sulla media e sul sigma della serie. Il seed è presente nella query SQL, quindi le estrazioni sono identiche a ogni nuova esecuzione. La simulazione offre flessibilità, dipendenza dal percorso e correlazione tra più asset, ma non maggiore realismo: se le si fornisce una distribuzione normale, restituisce la risposta parametrica con l’aggiunta del rumore di campionamento. Il ricampionamento dei rendimenti osservati, la tecnica alla base degli intervalli di confidenza bootstrap, mantiene invece in gioco la coda reale.

QueryUna serie, tre metodi: VaR a 1 giorno su sei livelli di confidenza
L'esatto SQL dietro ogni numero
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)
Run this yourself

Al livello 95.0% i tre metodi producono risultati entro un punto l’uno dall’altro: 1.66% storico, 1.74% parametrico, 1.71% simulato. A 99.9% si separano: 5.85% storico contro 3.31% parametrico, con una differenza di 2.54 punti percentuali sugli stessi dati. La colonna simulata resta accanto a quella parametrica a ogni livello. È questo il punto, non un difetto. Una simulazione riproduce la distribuzione che le è stata fornita.

Leggendo il pannello per colonna, ogni aumento della confidenza amplia la soglia. Leggendolo per riga, la scelta del metodo incide poco nella parte centrale della distribuzione, ma diventa determinante nella coda. Un limite di VaR indicato senza specificare il metodo e la finestra temporale non è un numero che altri possano riprodurre.

Cosa non indica il VaR: expected shortfall

L’expected shortfall, chiamato anche VaR condizionato, calcola la perdita media nei giorni in cui viene superata la soglia del VaR. Il VaR indica dove inizia la coda della distribuzione. L’expected shortfall misura ciò che si trova al suo interno.

QueryVaR rispetto all’expected shortfall, la perdita media oltre la soglia
L'esatto SQL dietro ogni numero
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)
Run this yourself

A 99.0%, il VaR su questa serie è pari a 3.09% e l’expected shortfall a 4.43%, ossia 1.44 volte la soglia. Con una distribuzione normale, allo stesso livello, quel rapporto sarebbe vicino a 1,15. Anche a 99.9%, quando la soglia ha già raggiunto 5.85%, la perdita media nei giorni di superamento è pari a 8.14%. Un limite basato soltanto sul VaR tratta ogni superamento come se fosse lo stesso evento; la colonna del rapporto misura quanto questa ipotesi sia lontana dalla realtà.

Il VaR a un giorno e al 99% ha lo stesso significato per un’istituzione e per un trader?

No. La differenza riguarda soprattutto l’orizzonte temporale. Un trader che chiude tutte le posizioni prima della notte mantiene il rischio per alcune ore. Una soglia su una sessione è quindi coerente con il periodo di detenzione, anche se il percorso intraday può superare ampiamente il valore calcolato tra una chiusura e l’altra. Un’istituzione che finanzia passività a lunga scadenza mantiene le posizioni per anni e la sua esposizione si sviluppa nell’arco di trimestri. Il suo VaR a un giorno e al 99% è quindi uno strumento per il capitale e il monitoraggio, non una descrizione del rischio effettivamente assunto. Per anni le regole sul capitale bancario si sono basate su un VaR a un giorno e al 99%. In seguito, il quadro di Basilea per il rischio di mercato ha spostato la misura sull’expected shortfall al 97,5%.

Il collegamento standard tra gli orizzonti consiste nel riscalare il dato in base alla radice quadrata del tempo: si moltiplica il valore riferito a una sessione per la radice quadrata dell’orizzonte espresso in sessioni. Questo passaggio presuppone rendimenti indipendenti e una sigma costante. La colonna della sigma anno per anno riportata sopra mostra già perché questa ipotesi non regge.

QueryScala radice del tempo rispetto a perdite misurate su più sessioni, livello 99%
L'esatto SQL dietro ogni numero
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)
Run this yourself

La prima riga è un controllo d’identità: a 1 session il valore riscalato coincide con quello misurato, con un rapporto pari a 1. A 20 sessions i due valori divergono, e non nella direzione indicata di solito dagli avvertimenti su questa scorciatoia: il valore di una sessione riscalato è 13.8%, mentre i rendimenti misurati su quell’orizzonte registrano 10.7%, con un rapporto pari a 0.78. In questa finestra il valore riscalato è superiore alla coda misurata su più sessioni. Qui agiscono due dinamiche contrapposte. Una distribuzione dei rendimenti su una singola sessione con code pesanti diventa meno estrema quando i rendimenti vengono aggregati. Di conseguenza, il quantile al 99% aggregato aumenta più lentamente della radice quadrata dell’orizzonte. Il volatility clustering agisce nella direzione opposta, concentrando sessioni molto turbolente all’interno della stessa finestra. In questa serie la prima dinamica prevale sulla seconda. Nessuna delle due direzioni è però garantita in un’altra finestra o su un altro asset. È questo il punto: il moltiplicatore è un’ipotesi, non una misurazione. Il volatility clustering è anche la proprietà su cui fa leva il volatility targeting quando ridimensiona le posizioni.

Resta un’ulteriore limitazione, da esprimere chiaramente. Tutto quanto sopra riguarda il VaR di un singolo asset, calcolato su un ampio fondo indicizzato. Un portafoglio composto da dieci titoli correlati incorpora rischio di concentrazione, che il VaR di portafoglio gestisce attraverso una stima della correlazione. Le correlazioni, però, tendono a muoversi maggiormente proprio quando la stima diventa importante.

Note metodologiche e convenzioni
  • I percentili derivano da un quantile esatto, non da una stima campionaria. Le estrazioni Monte Carlo provengono da una sequenza uniforme con seed ottenuto da hash e definita in SQL. Ogni dato riportato può quindi essere ricalcolato ottenendo lo stesso valore.
  • I rendimenti sono variazioni di prezzo tra una chiusura e l’altra, senza reinvestimento dei dividendi: è la convenzione usuale per il VaR su una sessione.
  • Il pannello per orizzonte utilizza finestre sovrapposte: rendimenti consecutivi su 20 sessioni condividono 19 giorni. La coda si basa quindi su un numero di osservazioni indipendenti molto inferiore a quello suggerito dal numero di righe.

Domande frequenti

Che cos’è un VaR a un giorno con livello di confidenza del 99%?

È il livello di perdita superato dalla peggiore di 1 seduta ogni 100, misurato su una singola sessione. Nella serie sopra, la stima storica è 3.09%. Il dato indica una soglia, ma non dice nulla sull’entità delle perdite oltre tale soglia.

Quale metodo di calcolo del VaR è più accurato?

Nessuno dei tre è accurato in assoluto, perché ciascuno risponde alla domanda sulla base di ipotesi diverse. Il VaR storico è fedele al campione utilizzato, ma non fornisce indicazioni su ciò che il campione non include. Il VaR parametrico è economico da calcolare e tende a sottostimare le code della distribuzione azionaria. Il Monte Carlo è valido solo quanto la distribuzione statistica utilizzata.

Qual è la differenza tra VaR e expected shortfall?

Il VaR è la soglia corrispondente a un determinato livello di confidenza. L’expected shortfall calcola la perdita media nelle sedute in cui tale soglia viene superata. Su questa serie, l’expected shortfall al 99% è pari a 1.44 volte il VaR, contro circa 1,15 volte nel caso di una distribuzione normale.

È possibile convertire un VaR a un giorno in un VaR a dieci giorni?

Moltiplicare per la radice quadrata di 10 è la scorciatoia standard. Il calcolo presuppone che i rendimenti siano indipendenti e che la volatilità resti costante. Il pannello sull’orizzonte temporale misura lo scostamento: su 20 sedute, il dato scalato è risultato pari a 13.8%, contro un valore misurato di 10.7%.


Ogni pannello precedente contiene il codice SQL che lo ha prodotto. Per eseguire gli stessi tre calcoli su un ticker o su una finestra temporale diversi, chiedilo in inglese semplice sul terminale Strasmore.

#risk#value at risk#expected shortfall#quant#position sizing