RiskAnalytics

Regime-conditional VaR, portfolio Greeks, stress testing

RiskAnalytics is v1’s advanced risk measurement module. It computes regime-conditional VaR (VaR that changes by market regime), portfolio Greeks aggregated across option positions, stress test scenarios, and correlation structure decomposition.

Import

python
from horizon.fund._risk_analytics import RiskAnalytics, VaRResult, StressTestResult

What it measures

Value at Risk (VaR) Regime-conditional: different regimes (trending, mean-reverting, crisis) get different VaR estimates. Historical, parametric, and Monte Carlo methods available.
Expected Shortfall (CVaR) Conditional tail expectation. "given you exceed VaR, what's the expected loss?" More conservative than VaR.
Portfolio Greeks Aggregate delta, gamma, vega, theta across all option positions. Essential for options overlays.
Correlation shock "What if correlations all go to 1?". stress test for risk-off regimes where normally-diversified positions move together.
Beta exposure Portfolio beta to SPY (or any benchmark), and net directional exposure.
Scenario analysis Apply specific shocks (market -10%, vol spike to 40, sector rotation) and see P&L impact.

Regime-conditional VaR

The key insight: a single VaR computed from all history averages across regimes and systematically underestimates crisis losses. RiskAnalytics detects the current regime and reports the VaR conditional on that regime.

API

python
class RiskAnalytics:
    def __init__(
        self,
        confidence_level: float = 0.95,
        regime_lookback: int = 252,
    ) -> None:
        ...

    def compute_var(
        self,
        returns: list[float],
        method: str = "historical",       # "historical" | "parametric" | "monte_carlo"
        regime: str | None = None,
    ) -> VaRResult:
        ...

    def compute_portfolio_greeks(
        self,
        positions: list[OptionPosition],
        spot_prices: dict[str, float],
    ) -> PortfolioGreeks:
        ...

    def stress_test(
        self,
        positions: list[Position],
        scenario: Scenario,
    ) -> StressTestResult:
        ...

    def correlation_shock(
        self,
        positions: list[Position],
        shock_correlation: float = 1.0,
    ) -> float:
        """Return portfolio vol if all correlations went to `shock_correlation`."""

Result types

python
@dataclass(frozen=True)
class VaRResult:
    var_95: float
    var_99: float
    cvar_95: float                    # Expected shortfall
    cvar_99: float
    regime: str                        # "trending" | "mean_reverting" | "crisis"
    method: str
    n_observations: int

@dataclass(frozen=True)
class PortfolioGreeks:
    delta: float                       # $ delta (directional)
    gamma: float                       # $ gamma
    vega: float                        # $ vega (vol exposure)
    theta: float                       # $ theta / day
    dollar_delta: float                # delta * underlying price
    dollar_gamma: float

@dataclass(frozen=True)
class StressTestResult:
    scenario_name: str
    pnl_impact_usd: float
    pnl_impact_pct: float
    positions_affected: list[str]
    worst_position: str

Usage

python
from horizon.fund._risk_analytics import RiskAnalytics

analytics = RiskAnalytics(confidence_level=0.95, regime_lookback=252)

# Compute VaR from a return series
returns = [0.001, -0.002, 0.003, ...]
var = analytics.compute_var(returns, method="historical")

print(f"Regime: {var.regime}")
print(f"VaR 95%: {var.var_95:.3%}")
print(f"CVaR 95%: {var.cvar_95:.3%}")
print(f"VaR 99%: {var.var_99:.3%}")

# Portfolio Greeks (requires option positions)
positions = [
    OptionPosition(symbol="AAPL", strike=180, expiry="2025-01-17", right="call", qty=10, delta=0.5, gamma=0.02, vega=0.3, theta=-0.05),
    # ...
]
greeks = analytics.compute_portfolio_greeks(positions, spot_prices={"AAPL": 185})
print(f"Portfolio delta: ${greeks.dollar_delta:,.0f}")
print(f"Portfolio vega: ${greeks.vega:,.0f}")
print(f"Portfolio theta: ${greeks.theta:,.0f}/day")

# Correlation shock. "what if everything moves together"
shocked_vol = analytics.correlation_shock(positions, shock_correlation=1.0)
normal_vol = analytics.correlation_shock(positions, shock_correlation=0.3)
print(f"Normal vol: {normal_vol:.2%}")
print(f"Shocked vol: {shocked_vol:.2%}")
print(f"Shock amplification: {shocked_vol / normal_vol:.1f}×")

Integration with Horizon

Wire RiskAnalytics into your backtest to get regime-conditional risk metrics alongside the standard MetricsCollector:

python
import horizon as hz
from horizon.fund._risk_analytics import RiskAnalytics

analytics = RiskAnalytics()

result = hz.run(mode="backtest", strategies=[...], ...)

# Compute returns from equity curve
equities = [e for _, e in result.equity_curve]
returns = [equities[i] / equities[i-1] - 1 for i in range(1, len(equities))]

var = analytics.compute_var(returns)
print(f"Backtest regime: {var.regime}")
print(f"Historical VaR 95%: {var.var_95:.3%}")
print(f"Historical CVaR 95%: {var.cvar_95:.3%}")

For a risk overlay that automatically reduces exposure when VaR exceeds a threshold, wrap in a custom risk check (see Risk Management).

Stress scenarios

The v1 module ships with predefined scenarios you can apply to any position list:

When to use

Options-heavy portfolios If you have any option exposure, you need portfolio Greeks. Net delta and net vega are the key headline numbers.
Multi-regime strategies When your backtest spans multiple regimes, a single VaR number lies. Regime-conditional VaR gives you truthful tail estimates per regime.
Pre-trade stress tests Before a large trade, run the scenario set to check whether it puts the portfolio over a tail-risk threshold.

Pitfalls

Source

python/horizon/fund/_risk_analytics.py. ~400 lines.

Next