Skip to content
AI360Xpert
Beta

Conditional Value at Risk (CVaR) Optimization

CVaR optimization trains a policy for its bad days, not its average day. It maximizes the average return of the worst alpha fraction of episodes, so rare crashes count more than lucky wins.

CVaR averages the worst alpha tail of the return distribution while VaR only marks where that tail starts.
CVaR averages the worst alpha tail of the return distribution while VaR only marks where that tail starts.

Why Does This Exist?

Standard RL chases the mean. It picks the policy with the highest expected return and calls it done. That works until the mean hides a cliff edge.

Picture two delivery robot policies. Both average +4.3 per shift. One never drops below -2. The other scores +22 on lucky shifts and -20 when it tips down stairs. A mean-only optimizer calls them tied. Your hardware budget does not. You want the policy whose worst fifth of shifts still pays, even if its highlight reel looks plainer.

That is what this page covers. You will see how VaR marks the edge of the bad tail and how CVaR averages everything past that edge, with a 10-episode worked example you can check by hand. It then sketches how a policy gradient lifts that tail.

Two neighbours do the adjacent jobs. Quantile Regression DQN teaches how to estimate the return distribution that VaR and CVaR read from. Safe RL teaches how to add hard constraints during training and deployment. This page teaches the tail objective itself, the workhorse of risk-sensitive RL. For the fixed-grid distribution that started this family, see Categorical DQN.

You only need one prerequisite idea. Each episode produces a total discounted return RR. Run the same policy ten times and you get ten different RR values because starts, noise, and slips differ. VaR and CVaR are just two ways of summarizing the low end of those numbers.

Think of It Like This

The ferry captain judged on stormy crossings

You run a harbor ferry. Each crossing earns a score: punctual arrivals score high, late or rough crossings score low.

The owner offers two bonus schemes. Scheme A pays on your average over all 100 crossings. Scheme B pays on your average over your worst 20 crossings. Under Scheme A you chase calm-day speed records. Under Scheme B you practice docking in wind and rain, add fenders, and slow down when chop builds, because one -20 day drags your bonus more than three +22 days lift it.

Map it point for point: crossings are episodes, scores are returns RR, the cutoff for your worst 20 is VaR, and the average of those 20 is CVaR. Optimizing CVaR is choosing Scheme B.

Where the analogy stops: ferry crossings are close to independent, while RL episodes come from one policy that you keep changing. Push the tail up and the whole distribution shifts, which is why CVaR training needs fresh rollouts instead of one fixed logbook.

How It Actually Works

Let RR be the random episodic return under policy πθ\pi_\theta, where θ\theta holds the policy weights. Let F(r)=P(R≤r)F(r) = P(R \le r) be its cumulative distribution, so F(r)F(r) is the chance a return lands at or below rr. Let α\alpha in (0,1)(0, 1) be the tail fraction you care about, here 0.20.2 for the worst fifth.

VaR marks the edge, CVaR averages past it

VaR (Value at Risk) is the quantile at α\alpha:

VaRα=inf⁡{r:F(r)≥α}VaR_\alpha = \inf \{ r : F(r) \ge \alpha \}

In plain words, VaRαVaR_\alpha is the cutoff where the worst α\alpha fraction begins. With α=0.2\alpha = 0.2, about 20 percent of episodes score at or below VaR.

CVaR (Conditional Value at Risk) is the mean inside that tail:

CVaRα=E[R∣R≤VaRα]=1α∫0αVaRu duCVaR_\alpha = E[R \mid R \le VaR_\alpha] = \frac{1}{\alpha} \int_0^\alpha VaR_u \, du

So VaR is one number on the edge, while CVaR averages the whole bad slice. CVaR is always at or below VaR for returns, which is why traders say VaR tells you where the cliff starts and CVaR tells you how deep the drop is.

One convention trap to fix now: finance often defines CVaR on losses with α=0.95\alpha = 0.95 and averages the worst 5 percent above a high quantile. Here we define it on returns and average the worst α\alpha below a low quantile. Same idea, mirrored axis. Always check which side the author averages.

Optimization uses a handy variational form from Rockafellar and Uryasev. For returns:

CVaRα=max⁡η{η−1αE[(η−R)+]}CVaR_\alpha = \max_\eta \left\{ \eta - \frac{1}{\alpha} E[(\eta - R)_+] \right\}

Here η\eta is a free threshold guess and (η−R)+=max⁡(0,η−R)(\eta - R)_+ = \max(0, \eta - R) is the shortfall below it. The best η\eta lands exactly on VaRαVaR_\alpha. That form matters because it turns a conditional average into an expectation you can sample.

Worked example with 10 sampled returns

Say one policy produces these 10 episode returns, in the order they happened:

[12,5,−8,22,3,−20,9,15,−2,7][12, 5, -8, 22, 3, -20, 9, 15, -2, 7]

Sort them lowest first:

[−20,−8,−2,3,5,7,9,12,15,22][-20, -8, -2, 3, 5, 7, 9, 12, 15, 22]

The plain mean is:

(43)/10=4.3(43) / 10 = 4.3

since the values sum to 4343. A mean-only reader sees +4.3 and moves on.

Now take α=0.2\alpha = 0.2. The tail holds α×10=2\alpha \times 10 = 2 episodes. Using the order-statistic rule, VaR is the second smallest value:

VaR0.2=−8VaR_{0.2} = -8

CVaR averages the two tail values:

CVaR0.2=−20+(−8)2=−282=−14CVaR_{0.2} = \frac{-20 + (-8)}{2} = \frac{-28}{2} = -14

So the policy averages +4.3 overall but -14 across its worst fifth. That gap is the whole argument for CVaR: the mean says fine, the tail says fix the stairs fall first. The diagram at the top draws these same numbers, with VaR as the dashed edge and CVaR as the solid tail mean left of it.

How a policy gradient lifts the tail

A CVaR policy gradient keeps the tail and mostly ignores the rest. In each update:

  1. Roll out NN episodes and collect returns R1R_1 to RNR_N.
  2. Estimate VaRαVaR_\alpha as the empirical α\alpha-quantile, exactly as above.
  3. Keep only episodes with Ri≤VaRαR_i \le VaR_\alpha and push θ\theta in the direction that makes those episodes both likelier and higher scoring, weighted by their shortfall (Ri−VaRα)(R_i - VaR_\alpha) times the score gradient ∇θlog⁡Pθ\nabla_\theta \log P_\theta.

You will often see the update written with an indicator I(Ri≤VaRα)\mathbb{I}(R_i \le VaR_\alpha) that zeroes out non-tail episodes, which is why only the bad slice teaches. The threshold η\eta from the variational form acts as a built-in baseline, so episodes exactly at VaR contribute nothing.

This costs data. With α=0.2\alpha = 0.2 you throw away four of every five episodes per update, so gradients wobble and small test batches mislead. In practice teams pair the objective with a distributional critic such as Quantile Regression DQN, which reads quantiles straight off learned locations instead of re-sorting thousands of rollouts, and they often warm-start on the mean before tightening α\alpha. Pick CVaR when rare failures dominate your true cost, such as hardware damage or large trading drawdowns. Stick with the mean when failures are cheap and you need every sample to count.

Code

The snippet below computes the same VaR and CVaR from the worked example. It is short on purpose: sort, cut the tail, average it.

from typing import List, Tuple

def var_cvar(returns: List[float], alpha: float = 0.2) -> Tuple[float, float, float]:    """Return (mean, VaR_alpha, CVaR_alpha) for lower-tail returns."""    ordered = sorted(returns)    n = len(ordered)    mean = sum(ordered) / n    # Order-statistic quantile: first index whose CDF reaches alpha.    k = max(1, int(-(-alpha * n // 1)))  # ceil(alpha * n)    var = ordered[k - 1]    tail = ordered[:k]    cvar = sum(tail) / len(tail)    return mean, var, cvar

episode_returns = [12, 5, -8, 22, 3, -20, 9, 15, -2, 7]mean, var, cvar = var_cvar(episode_returns, alpha=0.2)print(f"sorted: {sorted(episode_returns)}")print(f"mean: {mean:.1f}")print(f"VaR_0.2: {var}")print(f"CVaR_0.2: {cvar:.1f}")# -> sorted: [-20, -8, -2, 3, 5, 7, 9, 12, 15, 22]# -> mean: 4.3# -> VaR_0.2: -8# -> CVaR_0.2: -14.0

Watch Out For

Alpha points opposite ways in finance and RL

Finance papers average losses above a high quantile, so conservative means α=0.95\alpha = 0.95 or 0.990.99. Return-based RL averages gains below a low quantile, so conservative means α=0.1\alpha = 0.1 or 0.20.2. Copy the 0.95 into RL code and your tail holds nearly everything, so CVaR collapses back to the mean and the extra machinery does nothing.

Symptom: CVaR training behaves exactly like mean training and the tail never improves. Fix: name the side in a comment, such as lower-tail alpha equals 0.2, and assert that the kept fraction matches, for example 2 of 10 episodes for alpha 0.2.

CVaR starves on small batches

CVaR learns only from its tail slice. With α=0.2\alpha = 0.2 and 50 episodes per update, just 10 episodes carry gradient and the rest are discarded. Small batches then swing wildly: one lucky batch says the tail is fixed, the next says it collapsed.

Symptom: CVaR loss zigzags while the mean looks steady, and evaluation on 20 episodes disagrees with training. Fix: raise episodes per update, warm-start on the mean, then tighten alpha in steps, and read quantiles off a distributional critic instead of raw sorts when rollouts cost a lot.

The Quick Version

  • CVaR optimization maximizes the average return of the worst alpha fraction of episodes instead of the overall mean.
  • VaR at level alpha is the cutoff where that worst slice starts, while CVaR is the average inside the slice.
  • With returns [12, 5, -8, 22, 3, -20, 9, 15, -2, 7], the mean is 4.3, VaR at 0.2 is minus 8, and CVaR at 0.2 is minus 14.
  • A CVaR policy gradient keeps only tail episodes and weights each by its shortfall below VaR.
  • Small alpha values give stronger protection but need larger batches because most episodes are discarded each update.