joellewis/forward-risk
Estimate potential future losses using VaR, Expected Shortfall, Monte Carlo simulation, and stress testing. Use when the user asks about Value-at-Risk, CVaR, Expected Shortfall, scenario analysis, stress testing, or factor-based risk decomposition. Also trigger when users mention 'how much could I lose', 'worst-case scenario', 'tail risk', 'risk budget', 'component VaR', 'marginal VaR', '99% confidence loss', 'Monte Carlo simulation', or ask how to project portfolio risk forward.
npx skills add https://github.com/JoelLewis/finance_skills --skill forward-risk
Assumes returns are normally distributed. For a single asset or portfolio in dollar terms (assuming zero expected return over short horizons):
VaR = W * z_alpha * sigma_p
where:
More generally, including expected return:
VaR_alpha = mu - z_alpha * sigma
To convert from 1-day VaR to h-day VaR (assuming i.i.d. returns):
VaR_h = VaR_1 * sqrt(h)
For a portfolio with weight vector w and covariance matrix Sigma:
sigma_p = sqrt(w' * Sigma * w)
VaR_p = W * z_alpha * sqrt(w' * Sigma * w)
The covariance matrix captures both individual volatilities and correlations between assets.
Simulate a large number of portfolio return scenarios (e.g., 10,000+), then take the alpha-percentile of the simulated loss distribution.
Steps:
Monte Carlo VaR can accommodate non-normal distributions, fat tails, path-dependent instruments, and nonlinear payoffs (e.g., options).
CVaR answers: "Given that losses exceed VaR, what is the expected loss?"
ES_alpha = E[Loss | Loss > VaR_alpha]
For a normal distribution:
ES_alpha = mu + sigma * phi(z_alpha) / (1 - alpha)
where phi is the standard normal PDF.
CVaR is a coherent risk measure (unlike VaR) because it satisfies subadditivity: CVaR(A+B) <= CVaR(A) + CVaR(B). This means diversification always reduces or maintains CVaR, which is not guaranteed for VaR.
Decomposes total portfolio VaR into contributions from each position. Component VaRs sum to total VaR.
CVaR_i = w_i * beta_i * VaR_p
where beta_i = Cov(R_i, R_p) / Var(R_p) is the asset's beta to the portfolio.
Equivalently:
CVaR_i = w_i * (partial VaR / partial w_i)
sum(CVaR_i) = VaR_p
This decomposition identifies which positions are the largest contributors to portfolio risk.
Measures the rate of change of portfolio VaR with respect to a small increase in a position's weight.
MVaR_i = partial(VaR_p) / partial(w_i) = z_alpha * (Sigma * w)_i / sigma_p
Marginal VaR is used for position sizing: adding to a position with low marginal VaR reduces portfolio risk more efficiently.
Apply specific historical or hypothetical market moves to the current portfolio to estimate P&L impact.
Scenario P&L is computed by applying the scenario returns to current position exposures and revaluing.
A structured framework for assessing portfolio resilience under extreme but plausible conditions.
Common stress scenarios:
Stress tests should include second-order effects: margin calls, liquidity demands, correlation spikes, counterparty risk.
Separate total portfolio risk into systematic factor risk and idiosyncratic (security-specific) risk.
sigma^2_p = b' * Sigma_f * b + sum(w_i^2 * sigma^2_epsilon_i)
where:
Common factor models: Fama-French (market, size, value, momentum), Barra risk models, PCA-based statistical factors.
| Formula | Expression | Use Case |
|---------|-----------|----------|
| Parametric VaR (single) | W * z_alpha * sigma | Simple position VaR |
| Portfolio VaR | W * z_alpha * sqrt(w' * Sigma * w) | Multi-asset VaR |
| Multi-day VaR | VaR_1 * sqrt(h) | Scale to h-day horizon |
| CVaR (normal) | mu + sigma * phi(z_alpha) / (1 - alpha) | Expected tail loss |
| Component VaR | w_i * beta_i * VaR_p | Risk contribution per position |
| Marginal VaR | z_alpha * (Sigma * w)_i / sigma_p | Sensitivity to weight change |
| Factor Risk | b' * Sigma_f * b | Systematic risk component |
| Idiosyncratic Risk | sum(w_i^2 * sigma^2_epsilon_i) | Security-specific risk |
Given: A $1,000,000 equity portfolio with an annualized volatility of 15%.
Calculate: 1-day 95% parametric VaR (assuming 252 trading days and zero expected daily return).
Solution:
Daily volatility:
sigma_daily = 0.15 / sqrt(252) = 0.15 / 15.875 = 0.00945
1-day 95% VaR:
VaR = $1,000,000 * 1.645 * 0.00945 = $15,545
Alternatively, computing directly from annual figures:
VaR_annual = $1,000,000 * 1.645 * 0.15 = $246,750
VaR_1day = $246,750 / sqrt(252) = $15,545
Interpretation: There is a 5% chance of losing more than $15,545 in a single day under normal market conditions.
Given: A two-asset portfolio (60% equities, 40% bonds). Equities: mu = 10%, sigma = 18%. Bonds: mu = 4%, sigma = 5%. Correlation rho = -0.2. Portfolio value = $1,000,000.
Calculate: 95% annual VaR via Monte Carlo simulation (conceptual steps).
Solution:
Sigma = | 0.0324 -0.0018 |
| -0.0018 0.0025 |
For this portfolio, the analytical answer provides a benchmark:
sigma_p = sqrt(0.6^2 * 0.0324 + 0.4^2 * 0.0025 + 2 * 0.6 * 0.4 * (-0.0018))
= sqrt(0.011664 + 0.0004 - 0.000864)
= sqrt(0.0112)
= 10.58%
VaR_95% = $1,000,000 * 1.645 * 0.1058 = $174,090
The Monte Carlo result should converge to approximately this value for a multivariate normal assumption.
Given: From the Monte Carlo simulation above, the losses exceeding VaR (the worst 500 out of 10,000 scenarios) have an average loss of $225,000.
Calculate: 95% CVaR.
Solution:
CVaR_95% = $225,000
Interpretation: When losses exceed the 95% VaR threshold, the average loss is $225,000. This is roughly 29% worse than the $174,090 VaR figure, highlighting the severity of tail events.
uv run scripts/forward_risk.py # run the demo (uses PEP 723 inline deps)
uv run scripts/forward_risk.py --verify # check demo outputs against the worked examples (exit 1 on mismatch)
python3 scripts/forward_risk.py # alternative (requires: pip install numpy scipy)
The demo prints the calculations covered above; its values match the worked examples in this skill. Run --help for a list of the classes and functions. For programmatic use, import the module rather than running it — the demo only executes under python forward_risk.py.
Take joellewis/forward-risk from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference pip.
Without those the skill loads but fails at the first command.