edgekit

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.

!Sign convention: VaR and CVaR are positive loss numbers

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
ParamTypeDefaultMeaning
returnsarray-likePer-period return series.
alphafloat0.05Tail probability (0.05 = 95% VaR).
methodstr"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
ParamTypeDefaultMeaning
returnsarray-likePer-period return series.
alphafloat0.05Tail probability.
methodstr"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 thing

var_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
ParamTypeDefaultMeaning
returnsarray-likePer-period return series.
alphafloat0.05Tail probability.
methodstr"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"]
var_cvar chart
The return distribution with the VaR quantile and CVaR tail-average marked.

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.ndarray
  • equity — 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) -> float
  • equity — 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
ParamTypeDefaultMeaning
returnsarray-likePer-period return series.
qfloat0.05Tail 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 have

See also#