edgekit.risk
Value-at-Risk & tail risk — how bad a normal-ish bad day looks (VaR), how bad the days beyond it look (CVaR / expected shortfall), and the drawdown-shape diagnostics that a single loss number misses. Three VaR methods, two ES methods, and the ulcer / tail-ratio pair.
What's inside. value_at_risk and expected_shortfall are the two headline estimators (with cvar as an alias for the latter); var_cvar returns both in one call. Below the quantile, drawdown_series reconstructs the full underwater curve, ulcer_index summarises its depth-and-duration pain, and tail_ratio compares the right tail to the left.
Both are reported as positive magnitudes of loss — a 5% VaR of 0.021 means “on the worst 5% of days you lose about 2.1%”, not -0.021. Because CVaR averages the losses beyond the VaR threshold, it is always at least as large: cvar >= var. If either comes back negative, your input is a gain at that quantile.
Value-at-Risk & expected shortfall#
value_at_risk#
The loss you should not exceed on all but the worst alpha fraction of periods — the standard one-number risk budget. Three methods trade off distributional assumptions: historical (empirical quantile, no assumption), gaussian (normal closed form), and cornish_fisher (normal adjusted for skew and kurtosis — better for fat tails).
value_at_risk(returns, alpha=0.05, method="historical") -> float| Param | Type | Default | Meaning |
|---|---|---|---|
returns | array-like | — | Per-period return series. |
alpha | float | 0.05 | Tail probability (0.05 = 95% VaR). |
method | str | "historical" | "historical", "gaussian", or "cornish_fisher". |
Returns: a float — the VaR as a positive loss fraction.
import edgekit as ek
ek.risk.value_at_risk(rets, alpha=0.05) # historical 95% VaR
ek.risk.value_at_risk(rets, alpha=0.01, method="cornish_fisher") # fat-tail-aware 99%expected_shortfall#
Expected shortfall (a.k.a. CVaR) — the averageloss on the days worse than the VaR threshold. Answers the question VaR ducks: “when it does break, how bad is it?”. Two methods: historical and gaussian. cvar is an exported alias for this function.
expected_shortfall(returns, alpha=0.05, method="historical") -> float
cvar(returns, alpha=0.05, method="historical") -> float # alias| Param | Type | Default | Meaning |
|---|---|---|---|
returns | array-like | — | Per-period return series. |
alpha | float | 0.05 | Tail probability. |
method | str | "historical" | "historical" or "gaussian". |
Returns: a float — the average tail loss as a positive fraction (always >= value_at_risk).
ek.risk.expected_shortfall(rets, alpha=0.05)
ek.risk.cvar(rets, alpha=0.05) # same thingvar_cvar#
Both risk numbers in a single call, sharing the same method and alpha — the convenience form when you report them together (which you should).
var_cvar(returns, alpha=0.05, method="historical") -> dict| Param | Type | Default | Meaning |
|---|---|---|---|
returns | array-like | — | Per-period return series. |
alpha | float | 0.05 | Tail probability. |
method | str | "historical" | VaR method ("historical" / "gaussian" / "cornish_fisher"; ES falls back to "gaussian" for cornish_fisher). |
Returns: a dict with keys "var" and "cvar", both positive loss numbers with cvar >= var.
r = ek.risk.var_cvar(rets, alpha=0.05)
r["var"], r["cvar"]
Drawdown shape#
drawdown_series#
The full underwater curve — the fractional distance below the running peak at every point. Where max_drawdown gives you the single worst gap, this gives you the whole shape: how often, how deep, and how long you were underwater.
drawdown_series(equity) -> np.ndarrayequity— a cumulative equity / wealth curve.
Returns: a numpy array of drawdowns (0 at new highs, negative or positive-magnitude below — same length as equity).
dd = ek.risk.drawdown_series(equity)ulcer_index#
The Ulcer Index — the root-mean-square of the drawdown series, so it penalises deep and prolonged drawdowns rather than just the single worst point. A pain metric that rewards curves which recover quickly.
ulcer_index(equity) -> floatequity— a cumulative equity curve.
Returns: a float — the RMS drawdown (lower is smoother).
tail_ratio#
The ratio of the right-tail quantile to the (absolute) left-tail quantile — how big the good extremes are relative to the bad ones. > 1 means the upside tail dominates (the positively-skewed profile trend-following wants); < 1 means losses tail harder than gains.
tail_ratio(returns, q=0.05) -> float # >1 upside tail dominates| Param | Type | Default | Meaning |
|---|---|---|---|
returns | array-like | — | Per-period return series. |
q | float | 0.05 | Tail fraction to compare on each side. |
Returns: a float — right-tail / |left-tail| ratio.
ek.risk.tail_ratio(rets, q=0.05) # >1 is the shape a trend edge should haveSee also#
- edgekit.metrics —
max_drawdown/sharpe/sortinoalongside these tail numbers. - edgekit.optimize — the portfolio whose tail you are measuring.
- edgekit.sizing — turn a drawdown budget into a position size.
