risk-report
You hand over a book — either inline weights or a positions JSON —
and the skill returns the empirical risk picture: how volatile this
book has been, how it co-moves with the benchmark, how bad the tail
gets (VaR + Expected Shortfall), how deep the recent drawdown was,
which historical days hurt most and which names did the damage, and
which positions are doing the heavy lifting in the variance budget.
This is descriptive risk math on a current book. The script does NOT
predict future returns. It tells you what the last N days of history
say about how a book like the one you have just handed over has
behaved.
When to invoke
- PM needs the daily risk snapshot on a current book
- Risk officer running a tail-risk review
- Researcher comparing book risk vs a benchmark
- Pre-trade check: "what does this proposed book look like?"
- Post-portfolio-mark follow-up: "we know what the book is worth;
what could it lose?"
What you need
Optional:
- Benchmark ticker (default
SPY) for beta + tracking error + R²
- Lookback window in trading days (default 252)
- VaR confidence levels (default
0.95,0.99)
- Number of worst historical days to surface (default 5)
- Vol estimator (default
realized; ewma for RiskMetrics EWMA with
configurable λ, default 0.94, that responds faster to recent regime
shifts)
What you get back
Two output layers from one analysis.
Layer 1: canonical JSON matching output-schema.json.
Per-book stats (vol, return, Sharpe, beta, alpha, tracking error,
correlation, R²). A full VaR block keyed by each requested confidence
level with historical VaR, parametric VaR, historical ES, parametric
ES. Max drawdown with peak/trough/duration/recovery. Worst-N stress
days with per-name loss attribution. Per-position variance
contribution and per-position beta to the benchmark.
Concentration: top-1/3/5 weights, Herfindahl, effective N.
tier_caveats for excluded names and the always-on methodology
warnings.
Layer 2: rendered PM report. Header line with the book and
lookback, a Portfolio statistics block, a VaR table (rows: historical,
parametric, ES — columns: each confidence), one-liner max drawdown,
the worst-N stress days with per-name attribution and the
benchmark's return on the same date, a Position contribution table
sorted by variance share, a Concentration line, then the adaptive
Take. The Take reads what's actually true about the book (high beta,
concentrated, big drawdown, low tracking error) and surfaces 2-3 of
the most striking facts in plain English. See references/
for the full methodology.
How it works
- Parse the book. Inline
--positions (weights sum to ≤ 1.0; the
residual is implicit cash) or --book JSON. The JSON format supports
either weight per position or shares + price, in which case
weights are computed from the value share.
- Pull daily aggs per position and per benchmark over
--lookback-days (default 252). Massive's
/v2/aggs/ticker/{T}/range/1/day/{from}/{to}?adjusted=true so
dividends and splits don't contaminate the vol estimate.
- Compute log returns close-to-close. Align all series to the
intersection of date indices so every metric reads from the same
panel.
- Drop short series. A position with fewer than 60 aligned
trading days is excluded (surfaced in
tier_caveats and
positions_excluded); the equivalent weight folds into the cash
bucket so the math stays consistent.
- Per-name annualized vol via
np.std(daily_returns, ddof=1) * sqrt(252).
- Pairwise Pearson correlation matrix, 5%-shrunk toward identity
for numerical safety (same pattern as
position-sizer). Covariance
from per-name vols × the shrunk correlation.
- Portfolio daily returns as the weighted sum across names per
day. This is the single time series every metric reads from.
- Stats: annualized vol, annualized mean return, naïve Sharpe;
beta + alpha + tracking error + correlation + R² vs the benchmark.
- VaR + ES at each confidence:
- Historical VaR = -percentile(returns, 100 × (1 - confidence))
- Parametric VaR = z × σ - μ, where z = Φ⁻¹(confidence)
- Historical ES = -mean(returns ≤ VaR threshold)
- Parametric ES = -(μ - σ × φ(z) / (1 - confidence)) (Gaussian)
See
references/var-and-es.md.
- Max drawdown on the cumulative NAV (reconstructed from log
returns). Returns peak, trough, duration, and whether the series
recovered to the peak. See
references/max-drawdown.md.
- Worst-N stress picks the N most-negative days in the
portfolio return series and attributes each day's loss to
individual names via
w_i × r_i_on_that_day. See
references/stress-scenarios.md.
- Position variance contributions via the
MRC_i = w_i × (Σw)_i decomposition normalized to sum to 1.
- Concentration stats: top-1/3/5, Herfindahl (Σw²), effective
N (1 / HHI). See
references/concentration.md.
- Adaptive Take. Reads beta, top variance contributor share,
Herfindahl, drawdown, and tracking error. Surfaces the 2-3 that
actually matter for this book, in plain English.
Foundations used
Output mode: report
The render is a top-to-bottom report — header, stats block, VaR table,
drawdown one-liner, stress days, position table, concentration line,
take, caveats. Closer to a daily risk note than a pure data dump,
because the PM/risk audience needs the take alongside the numbers.
See references/ for the per-section methodology.
MC mode
N/A. risk-report uses empirical historical distributions; it does not
run Monte Carlo. For distribution-of-outcomes sweeps see
valuation-sanity-check --mc.
Endpoints used
GET /v2/aggs/ticker/{ticker}/range/1/day/{from}/{to}?adjusted=true
Daily aggregates per name + per benchmark. One call per ticker per
run; results cached per ticker.
Verify endpoint paths against current docs at massive.com/docs before
shipping; field names and versions shift.
Doesn't handle (yet)
- Single-regime lookback. All math reads from one window. A
multi-window comparison (60-day vs 252-day VaR) would show regime
sensitivity; queued.
- No factor decomposition. Variance contribution is at the
position level, not the factor level. A factor-attributed
decomposition (size, value, momentum, quality) needs the
factor-research machinery wired in; obvious follow-up.
- Cornish-Fisher / Student-t parametric VaR. Parametric VaR
assumes normality; surfaces in caveats. A higher-moment variant is
a clean PR.
- No conditional / stressed-period VaR. Worst-N is the closest
thing; a regime-conditional VaR (e.g., compute VaR only on days
when SPY was down) is a follow-up.
- No correlation-shock stress. "What if every correlation goes to
0.9" is queued; the worst-N empirical stress carries the day for v1.
- Single benchmark. Beta is vs one ticker. Multi-benchmark (SPY +
IWM + EFA + AGG) needs a multi-regression; queued.
These are clean PR extensions. The output schema reserves space for
each so adding them later doesn't break consumers.
1---2name: risk-report3description: VaR (historical + parametric), Expected Shortfall, max drawdown, beta, tracking error, position variance contributions, concentration metrics, and worst-N historical stress scenarios for a portfolio. Pairs with portfolio-mark (which marks the book — risk-report tells you what could happen to those marks). Use when a PM, risk officer, or quant needs the full risk picture on a current book.4---56# risk-report78You hand over a book — either inline weights or a positions JSON —9and the skill returns the empirical risk picture: how volatile this10book has been, how it co-moves with the benchmark, how bad the tail11gets (VaR + Expected Shortfall), how deep the recent drawdown was,12which historical days hurt most and which names did the damage, and13which positions are doing the heavy lifting in the variance budget.1415This is descriptive risk math on a current book. The script does NOT16predict future returns. It tells you what the last N days of history17say about how a book like the one you have just handed over has18behaved.1920## When to invoke2122- PM needs the daily risk snapshot on a current book23- Risk officer running a tail-risk review24- Researcher comparing book risk vs a benchmark25- Pre-trade check: "what does this proposed book look like?"26- Post-portfolio-mark follow-up: "we know what the book is worth;27 what could it lose?"2829## What you need3031- A book: either inline `--positions T=w,T=w,...` or a `--book book.json`32 (see [`examples/sample-book.json`](./examples/sample-book.json))33- `MASSIVE_API_KEY` exported3435Optional:3637- Benchmark ticker (default `SPY`) for beta + tracking error + R²38- Lookback window in trading days (default 252)39- VaR confidence levels (default `0.95,0.99`)40- Number of worst historical days to surface (default 5)41- Vol estimator (default `realized`; `ewma` for RiskMetrics EWMA with42 configurable λ, default 0.94, that responds faster to recent regime43 shifts)4445## What you get back4647Two output layers from one analysis.4849**Layer 1: canonical JSON** matching [`output-schema.json`](./output-schema.json).50Per-book stats (vol, return, Sharpe, beta, alpha, tracking error,51correlation, R²). A full VaR block keyed by each requested confidence52level with historical VaR, parametric VaR, historical ES, parametric53ES. Max drawdown with peak/trough/duration/recovery. Worst-N stress54days with per-name loss attribution. Per-position variance55contribution and per-position beta to the benchmark.56Concentration: top-1/3/5 weights, Herfindahl, effective N.57`tier_caveats` for excluded names and the always-on methodology58warnings.5960**Layer 2: rendered PM report.** Header line with the book and61lookback, a Portfolio statistics block, a VaR table (rows: historical,62parametric, ES — columns: each confidence), one-liner max drawdown,63the worst-N stress days with per-name attribution and the64benchmark's return on the same date, a Position contribution table65sorted by variance share, a Concentration line, then the adaptive66Take. The Take reads what's actually true about the book (high beta,67concentrated, big drawdown, low tracking error) and surfaces 2-3 of68the most striking facts in plain English. See [`references/`](./references/)69for the full methodology.7071## How it works72731. **Parse the book.** Inline `--positions` (weights sum to ≤ 1.0; the74 residual is implicit cash) or `--book` JSON. The JSON format supports75 either `weight` per position or `shares` + `price`, in which case76 weights are computed from the value share.772. **Pull daily aggs** per position and per benchmark over78 `--lookback-days` (default 252). Massive's79 `/v2/aggs/ticker/{T}/range/1/day/{from}/{to}?adjusted=true` so80 dividends and splits don't contaminate the vol estimate.813. **Compute log returns** close-to-close. Align all series to the82 intersection of date indices so every metric reads from the same83 panel.844. **Drop short series.** A position with fewer than 60 aligned85 trading days is excluded (surfaced in `tier_caveats` and86 `positions_excluded`); the equivalent weight folds into the cash87 bucket so the math stays consistent.885. **Per-name annualized vol** via `np.std(daily_returns, ddof=1) *89 sqrt(252)`.906. **Pairwise Pearson correlation matrix**, 5%-shrunk toward identity91 for numerical safety (same pattern as `position-sizer`). Covariance92 from per-name vols × the shrunk correlation.937. **Portfolio daily returns** as the weighted sum across names per94 day. This is the single time series every metric reads from.958. **Stats:** annualized vol, annualized mean return, naïve Sharpe;96 beta + alpha + tracking error + correlation + R² vs the benchmark.979. **VaR + ES at each confidence:**98 - Historical VaR = -percentile(returns, 100 × (1 - confidence))99 - Parametric VaR = z × σ - μ, where z = Φ⁻¹(confidence)100 - Historical ES = -mean(returns ≤ VaR threshold)101 - Parametric ES = -(μ - σ × φ(z) / (1 - confidence)) (Gaussian)102 See [`references/var-and-es.md`](./references/var-and-es.md).10310. **Max drawdown** on the cumulative NAV (reconstructed from log104 returns). Returns peak, trough, duration, and whether the series105 recovered to the peak. See [`references/max-drawdown.md`](./references/max-drawdown.md).10611. **Worst-N stress** picks the N most-negative days in the107 portfolio return series and attributes each day's loss to108 individual names via `w_i × r_i_on_that_day`. See109 [`references/stress-scenarios.md`](./references/stress-scenarios.md).11012. **Position variance contributions** via the111 `MRC_i = w_i × (Σw)_i` decomposition normalized to sum to 1.11213. **Concentration** stats: top-1/3/5, Herfindahl (Σw²), effective113 N (1 / HHI). See [`references/concentration.md`](./references/concentration.md).11414. **Adaptive Take.** Reads beta, top variance contributor share,115 Herfindahl, drawdown, and tracking error. Surfaces the 2-3 that116 actually matter for this book, in plain English.117118## Foundations used119120- [`massive-api-patterns`](../massive-api-patterns) for REST auth and121 rate limiting on the daily aggs pull.122123## Output mode: report124125The render is a top-to-bottom report — header, stats block, VaR table,126drawdown one-liner, stress days, position table, concentration line,127take, caveats. Closer to a daily risk note than a pure data dump,128because the PM/risk audience needs the take alongside the numbers.129See [`references/`](./references/) for the per-section methodology.130131## MC mode132133N/A. risk-report uses empirical historical distributions; it does not134run Monte Carlo. For distribution-of-outcomes sweeps see135`valuation-sanity-check --mc`.136137## Endpoints used138139- `GET /v2/aggs/ticker/{ticker}/range/1/day/{from}/{to}?adjusted=true`140 Daily aggregates per name + per benchmark. One call per ticker per141 run; results cached per ticker.142143Verify endpoint paths against current docs at massive.com/docs before144shipping; field names and versions shift.145146## Doesn't handle (yet)147148- **Single-regime lookback.** All math reads from one window. A149 multi-window comparison (60-day vs 252-day VaR) would show regime150 sensitivity; queued.151- **No factor decomposition.** Variance contribution is at the152 position level, not the factor level. A factor-attributed153 decomposition (size, value, momentum, quality) needs the154 `factor-research` machinery wired in; obvious follow-up.155- **Cornish-Fisher / Student-t parametric VaR.** Parametric VaR156 assumes normality; surfaces in caveats. A higher-moment variant is157 a clean PR.158- **No conditional / stressed-period VaR.** Worst-N is the closest159 thing; a regime-conditional VaR (e.g., compute VaR only on days160 when SPY was down) is a follow-up.161- **No correlation-shock stress.** "What if every correlation goes to162 0.9" is queued; the worst-N empirical stress carries the day for v1.163- **Single benchmark.** Beta is vs one ticker. Multi-benchmark (SPY +164 IWM + EFA + AGG) needs a multi-regression; queued.165166These are clean PR extensions. The output schema reserves space for167each so adding them later doesn't break consumers.