Guides · risk
Historical VaR is the empirical percentile of your observed losses — no distribution assumed. Parametric VaR fits a normal on the sample mean and standard deviation and reads the quantile analytically. POST /v1/risk/var computes both on every call, so the comparison costs nothing.
| Method | Computation | Trusts | Breaks when |
|---|---|---|---|
| historical | Sorts observed losses, takes the linear-interpolated percentile at the confidence level. | Your sample as it is — fat tails included, but also its gaps. | The sample is short, or the regime that produced it is gone. |
| parametric_normal | Fits μ and σ (ddof=1), then VaR = (z·σ − μ)·√h with z the normal quantile. | The normality assumption. | Returns are skewed or fat-tailed — i.e. in crises, exactly when it matters. |
Both come with their Expected Shortfall (the average loss beyond VaR) in the same response — see the ES guide for why that number matters more than VaR itself.
The minimal call, body truncated for display (the real series has 252 values; the API rejects fewer than 30):
curl -s https://quantbox.dev/v1/risk/var \
-H "X-API-Key: $QB_KEY" -H "Content-Type: application/json" \
-d '{"returns": [0.004057, -0.01208, 0.009405, 0.011687, -0.023012, …],
"confidence": 0.99, "horizon_days": 10, "portfolio_value": 2500000}'
Runs on the free tier — 500 calls/month, no credit card. Get a key.
The complete version generates the series with a seeded numpy generator, so it is fully reproducible — the first three values are 0.004057, -0.01208, 0.009405, and the response comes back with exactly the numbers shown here:
import requests
import numpy as np
KEY = "YOUR_API_KEY" # free key at https://quantbox.dev
rng = np.random.default_rng(42)
returns = np.round(rng.normal(0.0004, 0.012, 252), 6).tolist() # 252 trading days
r = requests.post(
"https://quantbox.dev/v1/risk/var",
headers={"X-API-Key": KEY},
json={"returns": returns, "confidence": 0.99,
"horizon_days": 10, "portfolio_value": 2_500_000},
)
print(r.json())
{
"historical": {
"var": {"fraction_of_portfolio": 0.0746, "amount": 186535.64},
"expected_shortfall": {"fraction_of_portfolio": 0.0788, "amount": 196994.09}
},
"parametric_normal": {
"var": {"fraction_of_portfolio": 0.0833, "amount": 208357.15},
"expected_shortfall": {"fraction_of_portfolio": 0.0954, "amount": 238499.60}
},
"n_observations": 252,
"confidence": 0.99,
"horizon_days": 10,
"assumptions": {
"input": "portfolio return series (decimal per period)",
"horizon_scaling": "sqrt(horizon_days), assumes iid returns",
"parametric": "normal distribution fitted on sample mean/std (ddof=1)",
"historical": "linear-interpolated percentile of empirical losses",
"sign": "positive VaR = potential loss; can be negative if all observations are gains"
}
}
| 99% · 10-day · €2.5M | Historical | Parametric (normal) |
|---|---|---|
| VaR | €186,535.64 (7.46%) | €208,357.15 (8.33%) |
| Expected Shortfall | €196,994.09 (7.88%) | €238,499.60 (9.54%) |
Amounts rounded to cents for display; the API returns full precision.
Note what happened: the parametric VaR comes out about 12% higher than the historical one — the opposite of the usual textbook framing where the normal “underestimates risk”. Nothing is broken. This seeded sample happens to have a 1% empirical quantile milder than what a normal fitted on its own mean and standard deviation implies: its realized tail is thinner than normal.
That is the real lesson of running both methods on every call: the gap between them is a distribution diagnostic, not a rule that one method is always more prudent. Historical far above parametric → your sample has fat tails the normal is ignoring. Parametric above historical → your sample's extremes are mild relative to its volatility (or your window simply missed the last crisis). Either way, a large gap means “look at your data before trusting either number”.
Prefer historical when you have enough data (a few hundred observations) and the tails are the point — regulatory-style measurement, backtesting, any series with known skew or jump behavior. It reports what actually happened, including the crashes in your window.
Prefer parametric when the sample is thin, when you need smooth sensitivity to volatility changes (stress σ up 20% and the VaR moves analytically), or as a deliberately simple benchmark. It is stable and cheap — and wrong in a known direction when tails are fat.
In practice: use both. Since the endpoint returns the pair on every call, the comparison is free, and the disagreement is often more informative than either number alone. Running per-asset data instead of a portfolio series? See portfolio VaR from positions and asset returns.
Horizon scaling. The 10-day figures are the 1-day measures scaled by sqrt(horizon_days) = √10. As the response’s assumptions.horizon_scaling field states, that assumes iid returns — with autocorrelation or volatility clustering it is an approximation, labeled rather than hidden.
Estimation detail. The historical percentile is linear-interpolated; the normal fit uses the sample standard deviation with ddof=1. Both are spelled out verbatim in the assumptions block above.
Minimum 30 observations. Fewer returns HTTP 422. At 99% confidence, even 252 points put only ~2.5 observations beyond the VaR threshold — treat high-confidence historical numbers from short windows with suspicion.
Sign convention. Positive VaR = potential loss; a window where everything went up can produce a negative VaR, and the API returns it rather than clamping.
Compare both methods on your own series. Key in seconds, one POST, four numbers.
Get a free API key500 calls/month free · no credit card · full schemas in /docs
Related: Value-at-Risk and Expected Shortfall in one call · Expected Shortfall (CVaR) via API · Portfolio VaR from positions and asset returns · All guides