Value at Risk and Expected Shortfall
At its core, financial risk management is concerned with quantifying potential losses to assess the downside of financial endeavors and prepare for worst-case scenarios. Perhaps the two most salient risk measures in the field of risk management are value at risk (VaR) and expected shortfall, also known as conditional value at risk (CVaR). Using statistical underpinnings, these measures estimate the probabilistic maximal loss of a portfolio over a given time period.
VaR¶
For a given confidence level $\alpha\in(0,1)$ and timeframe, VaR measures the maximum loss such that $(1-\alpha)\%$ of the time, the loss will be greater than the VaR. Mathematically, VaR is the $(1-\alpha)$-quantile loss, or: $$ \text{VaR}_\alpha=F_X^{-1}(1-\alpha), $$ where $F_X^{-1}(\cdot)$ is the inverse cumulative distribution function of the random variable $X$ of returns. Under the assumption that returns (equivalently, negative losses) are normally distributed under $X\sim\mathcal{N}(\mu,\sigma^2)$, the VaR is: $$ \text{VaR}_\alpha = \mu + \sigma\cdot F^{-1}(1-\alpha), $$ where $F^{-1}(\cdot)$ is the inverse cumulative distribution function of the standard normal.
Calculating historical VaR is therefore as simple as using the empirical distribution to find the value at which $(1-\alpha)\%$ of the returns lie below:
returns = raw.Close["^GSPC"].diff().dropna()
conf = .95
fig, ax = plt.subplots(figsize=(5,15/4))
var = np.quantile(returns, 1 - conf)
ax.axvline(var, color="#B03845", label=r"$\mathrm{VaR}_{\alpha=95\%}$")
_, bins, patches = ax.hist(returns, bins=200, density=True, color="#0A1E3D")
for patch, l_edge in zip(patches, bins[:-1]):
if l_edge < var:
patch.set_facecolor("#B03845")
plt.xlabel("Daily returns (USD)")
plt.ylabel("Frequency (normalised)")
plt.title("Daily S&P 500 returns from 1980 to 2025")
ax.spines[['right', 'top']].set_visible(False)
plt.xlim(-100, 100)
plt.legend(frameon=False)
print("VaR: {:.2f}; VaR under normality: {:.2f}".format(var,
np.mean(returns) + np.std(returns) * scipy.stats.norm.ppf(.05)))
plt.show()
Expressed in English, from 1980 to 2025, the S&P 500 index had a 1-day 95% VaR of \$25.21; or there was a 95% probability that losses would not exceed \$25.21 in one day. This is quite different to the VaR under the normality assumption, which has a 1-day 95% VaR of \$32.21.
Although relatively easy to calculate, VaR is sensitive to the assumed underlying probability model: A light-tail distribution (e.g., the normal) used in place of a true fat-tailed distribution can lead to underestimation in VaR as $\alpha\to1$:
CVaR¶
The greatest drawback of VaR is the information lost by summarizing the tail of the distribution with just its upper value—VaR does not quantify the shape of the left tail. The conditional VaR (CVaR) or expected shortfall addresses this by calculating the average loss given the worst $(1-\alpha)\%$ of returns. In other words, it is the conditional expectation: $$ \begin{align*} \text{CVaR}_\alpha&=\mathbb{E}\left[X\mid X\le \text{VaR}_\alpha\right]\\ &= \frac{1}{1-\alpha}\int_{-\infty}^{\text{VaR}_\alpha} x\cdot f(x) dx, \end{align*} $$ where $f(\cdot)$ is the probability density function of $X$. Under the normality assumption, this simplifies to: $$ \text{CVaR}_\alpha=\mu - \sigma\frac{\phi(F^{-1}(\alpha))}{1-\alpha}, $$
where $\phi(\cdot)$ is the probability density function of the standard normal.
CVaR is often considered an enhancement of VaR as it quantifies tail risk on top of answering the question of how often portfolio losses will exceed the worst $(1-\alpha)\%$ of cases. Reusing the same example above:
returns = raw.Close["^GSPC"].diff().dropna()
conf = .95
fig, ax = plt.subplots(figsize=(5,15/4))
var = np.quantile(returns, 1 - conf)
ax.axvline(var, color="#B03845", label=r"$\mathrm{VaR}_{\alpha=95\%}$")
_, bins, patches = ax.hist(returns, bins=200, density=True, color="#0A1E3D")
for patch, l_edge in zip(patches, bins[:-1]):
if l_edge < var:
patch.set_facecolor("#B03845")
cvar = np.mean(returns[returns <= var])
ax.axvline(cvar, color="#94D2F6", label=r"$\mathrm{CVaR}_{\alpha=95\%}$")
plt.xlabel("Daily returns (USD)")
plt.ylabel("Frequency (normalised)")
plt.title("Daily S&P 500 returns from 1980 to 2025")
ax.spines[['right', 'top']].set_visible(False)
plt.xlim(-100, 100)
plt.legend(frameon=False)
print(f"VaR: {var:.2f}; VaR under normality: {normalVaR(np.mean(returns), np.std(returns), .95)}")
print(f"CVaR: {cvar:.2f}; CVaR under normality: {normalCVaR(np.mean(returns), np.std(returns), .95)}")
plt.show()
From 1980 to 2025, the S&P 500 index had a 1-day 95% CVaR of \$50.47, meaning that in the worst 5% of 1-day S&P 500 returns, the average loss would be \$50.47. This last example presents the common case where the CVaR under the normality assumption underestimates the true CVaR due to the presence of fat-tailed returns.
References¶
- Daniel P Palomar (2024). Portfolio optimization. MSc in Financial Mathematics, HKUST, [Online]. Available: https://www.danielppalomar.com/mafs6010r---portfolio-optimization-with-r.html.
- Martin Haugh (2016). IEOR E4602 Quantitative Risk Management, Columbia University, [Online]. Available: http://www.columbia.edu/~mh2078/QuantRiskManagement.html
- Yue Shi, Chi Tim Ng, Ka-Fai Cedric Yiu (2018). Portfolio selection based on asymmetric Laplace distribution, coherent risk measure, and expectation-maximization estimation. Quantitative Finance and Economics.