FinObservatory

Systemic risk / Methodology

FinWeave systemic-risk series: methodology

Version 1.0, computed 2026-07-10 by scripts/build_systemic.py, validated by scripts/validate_systemic.py. Input: data/parquet/equity_returns.parquet (daily bank equity log returns, Yahoo Finance via argus's collector, 2020-01-03 to 2026-07-06; provenance in docs/data_provenance.md). Output: data/parquet/systemic_series.parquet (165,381 rows, long format). Engine: the systemic layer absorbed from argus, engine/finweave_engine/layers/systemic/{covar,mes,absorption,srisk}.py, driven unmodified.

What this is (and what it is NOT)

This is a market-based systemic-risk series for the large US banking sector: four academic measures of how much each listed bank co-moves with, and contributes to, system-wide tail risk, plus one system-level measure of how unified bank co-movement is. Everything here is computed from equity returns only, the one systemic-risk input the repo actually holds.

It is NOT a complete SRISK dollar-capital-shortfall estimate. SRISK (Brownlees-Engle 2017) converts a market-based loss expectation into a dollar capital shortfall using each bank's market capitalization and book debt (total liabilities). Neither of those exists anywhere in this repo: the equity-returns source stores log returns and volume only, no price level or shares outstanding, so market cap cannot be reconstructed; and the FDIC data this repo carries is subsidiary-bank call-report data, not the consolidated bank-holding-company balance sheet SRISK requires. Rather than fabricate or substitute the wrong balance sheet, this build produces LRMES, the returns-based systemic-vulnerability core of SRISK, and stops there. The dollar SRISK level is a documented open gap (see Limitations, and the ledger 2026-07-10 entry). Do not present LRMES as SRISK.

It is not investment advice. These are historical statistical measures of co-movement, not forecasts of failure.

Design principles

  1. Engine driven unmodified. Every value comes from the absorbed engine's own functions. Where a rolling driver exists it is used; where the atomic function is called in a loop, the window slicing replicates the engine's own rolling function exactly (verified: Part 3 of the validation reproduces the stored series to zero difference from a from-scratch recomputation).
  2. Match argus where argus decides; use the engine default otherwise. argus itself computes only absorption and MES, and only at a 63-day window in its SVB case study (app/main.py). For a full historical series the primary window is 252 trading days (1 year), which is the coded default of every rolling function in the engine (compute_covar_rolling, compute_mes_rolling, compute_absorption_ratio_rolling) and the academic standard. The absorption ratio is additionally published at argus's 63-day window because the shorter window is what resolves the March 2020 co-movement spike (a 252-day trailing window cannot end before 2021).
  3. Real inputs only; refuse the wrong substitute. No market cap or book debt was invented to force a SRISK number. LRMES is shipped; SRISK dollars wait for a real consolidated-BHC source.
  4. Honest anchors. Each measure is checked against known crisis episodes and the result is reported as found, including where a naive anchor does not hold and why (see Validation).

Universe

  • System index: KBE, the SPDR S&P Regional Banking ETF, used by argus as its SYSTEM_INDEX (app/collectors/yahoo.py). It is the market/system proxy in every measure, never treated as a bank.
  • Banks: 28. The 27 current bank-holding-company tickers from argus's BHC_TICKERS collector list (JPM, BAC, C, WFC, GS, MS, BK, STT, USB, PNC, TFC, COF, SCHW, AXP, FITB, MTB, HBAN, RF, CFG, KEY, ZION, WAL, FHN, EWBC, FCNCA, ALLY, NTRS) plus CMA (Comerica, a distinct regional bank present in the data).
  • BNY is excluded (not counted among the 28). BNY and BK are the same entity, Bank of New York Mellon: the ticker changed from BK to BNY. Including both would double-count it. BK is kept because it is the fresher series (to 2026-07-02 vs BNY's 2026-02-09).
  • CMA is kept for its own per-bank measures (its CoVaR / MES / LRMES series end naturally at its last observation, 2026-01-30) but excluded from the system absorption panel. The absorption ratio uses listwise deletion, so one stale ticker would force every post-January-2026 date onto a stale correlation subset; the absorption panel therefore keeps only the 27 tickers fresh to within 10 trading days of the panel end.

The measures

Long-format output columns: measure, entity, date, value, window_days, method. entity is the ticker for bank-level measures and SYSTEM for system-level measures. window_days = 0 on lrmes marks the full-sample GARCH filter (see below).

Delta-CoVaR and CoVaR (per bank)

Adrian, Tobias, and Markus K. Brunnermeier. 2016. "CoVaR." American Economic Review 106(7): 1705-1741.

CoVaR is the Value-at-Risk of the system conditional on an institution being in distress. The engine estimates it by quantile regression of system returns on bank returns (statsmodels QuantReg): CoVaR is the fitted system return when the bank is at its own q-quantile return; Delta-CoVaR is that value minus the fitted system return when the bank is at its median. A more negative Delta-CoVaR means the system's tail is worse when that bank is in distress, i.e. the bank contributes more to systemic risk.

  • Estimation: rolling, window_days = 252, tail quantile q = 0.05.
  • method = rolling_quantile_reg. Both the CoVaR level and Delta-CoVaR are stored; the level uses the engine's compute_covar() inside a loop whose window slicing is identical to compute_covar_rolling().
  • Series span: 2021-01-04 to 2026-07-06 (a 252-day trailing window cannot end before the first year of data closes).

Marginal Expected Shortfall, MES (per bank)

Acharya, Viral V., Lasse H. Pedersen, Thomas Philippon, and Matthew Richardson. 2017. "Measuring Systemic Risk." Review of Financial Studies 30(1): 2-47.

MES is the average return of a bank on the days when the system return is in its own left tail (below its q-quantile). A more negative MES means the bank loses more precisely when the system is under stress.

  • Estimation: rolling, window_days = 252, q = 0.05, engine compute_mes_rolling(). method = rolling_tail_mean.
  • Series span: 2021-01-04 to 2026-07-06.

LRMES, the SRISK core (per bank)

Brownlees, Christian, and Robert F. Engle. 2017. "SRISK: A Conditional Capital Shortfall Measure of Systemic Risk." Review of Financial Studies 30(1): 48-79. Conditional-correlation step: Engle, Robert. 2002. "Dynamic Conditional Correlation." Journal of Business & Economic Statistics 20(3): 339-350.

Long-Run MES is the expected fractional decline in a bank's equity given a systemic event, defined as a fixed multi-period market decline (the engine uses C = -0.40). The engine estimates it from a GARCH(1,1) fit on the bank and on the system returns (conditional volatilities) and a DCC(1,1) fit on their standardized residuals (time-varying conditional correlation), then LRMES = 1 - exp(log(1 + C) * beta) with beta = rho_t * sigma_bank_t / sigma_system_t.

  • Estimation: one GARCH(1,1) + DCC(1,1) fit per bank over its full aligned history produces conditional volatility and correlation at every date; LRMES is evaluated at each date's filtered (rho_t, sigma_bank_t, sigma_system_t). This is in-sample GARCH filtering (parameters estimated on the full sample, conditional paths are the filtered values). window_days = 0 marks this (not a trailing window). method = garch_dcc_filter.
  • DCC parameters (alpha, beta) are MLE-estimated per pair (Engle 2002 two-step SLSQP), not hardcoded; the engine falls back to (0.05, 0.90) only if the optimizer fails.
  • Series span: 2020-01-03 to 2026-07-06 (LRMES needs no trailing window, so it starts at the first date).

Engine bug found and worked around, without modifying the engine. The engine's compute_srisk_all() passes NumPy arrays into _fit_garch(), whose final line calls .values and therefore only works on a pandas Series. Inside compute_srisk_all() the GARCH branch always raises and is silently swallowed into a simple-correlation-beta fallback, so its GARCH/DCC path never actually runs. To exercise the intended GARCH(1,1)+DCC(1,1) estimation, this build calls the engine's own unmodified _fit_garch / _dcc_correlation / compute_lrmes directly with Series input (their designed type). The engine source is untouched; the fix is in how the build feeds it. Recorded in the ledger.

SRISK dollar level (per bank): NOT PRODUCED

SRISK = max(0, k * (book_debt + market_cap) - (1 - LRMES) * market_cap) with prudential ratio k = 0.08. This is deliberately not in the output: neither book_debt (consolidated-BHC total liabilities, FR Y-9C) nor market_cap (price times shares) exists in the repo, and the correct source (FR Y-9C via the Fed's National Information Center, or 10-K/10-Q XBRL via SEC EDGAR, for liabilities; a market source for cap) is a deliberate future decision reserved in the ledger. LRMES above is the returns-based core; converting it to a dollar shortfall is the open step.

Absorption ratio and AR shift (system level)

Kritzman, Mark, Yuanzhen Li, Sebastien Page, and Roberto Rigobon. 2011. "Principal Components as a Measure of Systemic Risk." Journal of Portfolio Management 37(4): 112-126.

The absorption ratio is the fraction of the banking cross-section's total return variance captured by its top eigenvalues (here the top 20% of eigenvalues, via PCA on the return correlation matrix). A high ratio means bank returns are tightly unified (few factors explain most variance), which Kritzman et al. associate with a fragile, tightly-coupled system; a low ratio means dispersed, independent movement.

  • Estimation: rolling PCA on the fresh 27-bank panel (KBE excluded, matching argus's bank_tickers), frac = 0.20. Published at two windows: window_days = 252 (primary) and window_days = 63 (argus's SVB case-study window, which resolves the 2020 spike). method = rolling_pca.
  • AR shift = 21-day moving average of the 252-day absorption ratio minus its 252-day moving average (method = ma_shift, window_days = 252). Kritzman et al. propose a positive shift (short-run coupling tightening above its long-run level) as a turbulence lead indicator.
  • Spans: absorption 63d 2020-04-03 to 2026-07-06; absorption 252d and AR shift from 2021-01-04 / 2021-12-31 to 2026-07-06.

Parameter summary

SettingValueSource
System indexKBEargus SYSTEM_INDEX
Tail quantile q (CoVaR, MES)0.05argus + engine default
Absorption eigenvalue fraction0.20argus + engine default
Primary rolling window252 trading daysengine rolling-fn default
Absorption second window63 trading daysargus SVB case study
AR-shift short / long MA21 / 252engine compute_ar_shift default
LRMES systemic-event threshold C-0.40engine compute_lrmes default
GARCH(1,1), constant meanengine _fit_garch
DCC(1,1), Engle two-step MLEengine _fit_dcc_params

Limitations (read before using)

  1. Listed banks only. The universe is 28 publicly-traded large US bank holding companies plus a bank-sector ETF. Private banks, credit unions, the thousands of small FDIC banks, and non-bank financials are absent. These are market-based measures; they exist only for entities with a traded equity.
  2. SRISK dollar levels are not produced. No consolidated-BHC market cap or book debt in the repo (see above). LRMES is the returns-based core; the dollar shortfall, and therefore the classic March-2020 SRISK spike that is driven by the market-cap/leverage channel, is out of reach until a real balance-sheet source is wired in. This is why the "SRISK spikes in March 2020" anchor is reported as not-applicable rather than passed.
  3. Book-value liabilities vintage (applies once SRISK is built). When SRISK is eventually computed, book debt is a point-in-time filing (quarterly FR Y-9C / 10-Q) that lags the daily equity series; the standard SRISK convention holds the most recent reported liabilities fixed between filings, so the dollar level inherits a reporting-lag vintage. Noted now so it is not a surprise later.
  4. Yahoo input caveat. The equity returns originate from Yahoo Finance via an unofficial API wrapper (argus's collector), not an exchange feed or a licensed vendor. Yahoo's Terms of Service restrict redistribution to personal/non-commercial use, so this series is an internal engine output, not a redistributable raw dataset (unlike the FDIC/FRED spine). Yahoo adjusted returns can carry dividend/split-adjustment and thin-day artifacts; two tickers (BNY, CMA) also stopped updating in early 2026.
  5. In-sample GARCH filtering. LRMES uses full-sample GARCH/DCC parameters, so a given historical date's conditional volatility reflects parameters estimated with later data. This is the standard filtering convention (e.g. NYU V-Lab re-estimates periodically); it is appropriate for a descriptive historical series but is not a real-time, point-in-time forecast.
  6. No network/contagion layer. The Eisenberg-Noe interbank network measure is not included (argus's network_metrics was never populated with real data; this build adds no substitute).

Validation

Printed evidence: scripts/validate_systemic.py. Three parts.

1. Reference comparison: not possible, and why

The task specified comparing the computed series against argus_systemic_reference.parquet. That reference does not exist. Argus's systemic_indicators table has 0 rows and network_metrics has 0 rows (confirmed read-only against argus.db, and consistent with data/raw/argus_export/SOURCE.md): argus has never run these computations against real data and stored the result. There is nothing to compute a correlation or maximum deviation against. Validation therefore rests on crisis anchors and independent recomputation below, not on an argus reference.

2. Crisis sanity anchors

  • MES at the SVB week (strong pass). Ranking the 28 banks by their 252-day MES as of 2023-03-17, the most systemically-exposed name is WAL (Western Alliance, MES -0.1102), the regional bank that came closest to failing in the March 2023 run, by a wide margin over the next names. Five of argus's seven SVB-affected regionals (WAL, EWBC, KEY, CMA, ZION) are in the eight most-negative MES banks. MES correctly localizes the episode to the SVB-vulnerable regionals.
  • Absorption spikes in the 2020 COVID co-movement crash (pass). The 63-day absorption ratio over March-May 2020 averages 0.9550, the 95th percentile of its full-sample distribution, versus 0.9198 in calm mid-2021. The broad-market crash unified bank co-movement, exactly what the absorption ratio is built to detect.
  • The full-sample maximum is NOT 2020. The 63-day absorption ratio peaks at 0.9696 on 2025-06-20, above the 2020 peak of 0.9580: the April-July 2025 co-movement spike (coinciding with the April 2025 tariff-driven market selloff) unified bank returns even more tightly than COVID did. The narrative above discusses 2020 and 2023 because they are the classic anchor episodes; the 2025 window is a larger co-movement event by this measure and is visible in the dashboard chart.
  • Absorption does NOT rise in the SVB or 2022-2023 episodes (reported honestly, not forced). The 63-day absorption over March-April 2023 averages 0.8571 (18th percentile, below median), and the 252-day absorption falls across the rate cycle (2021-H2 0.8911, 2022-H2 0.8723, 2023-Q1 0.8595). This is economically coherent: 2022-2023 was a dispersion episode, where rate-sensitivity and regional-versus-money-center fortunes diverged sharply (deposit flight to the largest banks), which reduces the concentration of variance in the top eigenvalues. The absorption ratio measures system-wide unification, which characterizes the 2020 all-asset crash but not a sector- splitting event like SVB. The naive anchor "absorption jumps in every crisis" holds for COVID and not for SVB, and the reason is a real property of the measure, not a bug.
  • LRMES is a conditional-beta measure and is near-flat by design (reported honestly). System-mean LRMES sits in a narrow 0.37-0.45 band across all regimes (COVID monthly mean 0.394, calm 2021 0.380, recent 0.366). This is correct: LRMES is the expected equity loss given a fixed -40% systemic shock, so it tracks each bank's systematic exposure, not the current level of market stress. Its cross-section is economically right (WAL is consistently the most vulnerable: LRMES 0.576 at the 2020-03-23 COVID trough versus JPM 0.355, GS 0.389), and it does rise modestly in stress, but it does not and should not spike the way a dollar SRISK level would. The dramatic SRISK spike lives in the market-cap/leverage channel that is the documented missing input.

3. Independent recomputation (pipeline math verified)

Three stored values were recomputed from the raw returns without the engine and matched exactly (scripts/validate_systemic.py Part 3):

MeasurePointStoredIndependentDiff
Absorption 63d2023-03-170.8771660.8771660.00e+00
MES 252d, JPM2026-07-06-0.014760-0.0147600.00e+00
Delta-CoVaR 252d, BAC2026-07-06-0.036847-0.0368470.00e+00

The pipeline reproduces the engine's arithmetic to zero difference on all three measure families (PCA absorption, tail-mean MES, quantile-regression Delta-CoVaR).

Verdict

The returns-based measures are computed correctly (Part 3, exact), and behave as theory predicts on the one anchor each is built to detect (MES localizes SVB; absorption spikes in the 2020 co-movement crash). Where an anchor does not hold (absorption in dispersion crises; the LRMES/SRISK March-2020 spike), the reason is understood and documented rather than papered over. SRISK dollar levels remain an honest, ledger-tracked open gap pending a real consolidated-BHC balance-sheet source.

Long-run academic benchmarks (added 2026-07-10)

Three independent academic datasets, wired 2026-07-10 from the data-expansion sweep (docs/data_expansion_2026_07.md, wiring-queue items 3 and 7/10), shown as context, not systemic-risk measures. None feed the LRMES, MES, CoVaR, or absorption-ratio computations above; they are separate series composed on the page purely for scale and historical framing. Full provenance for each: docs/data_provenance.md.

Market-wide volatility regime (Ken French Data Library)

Rolling 12-month annualized volatility of the Fama-French US market excess return (Mkt-RF, french_ff3_monthly.parquet, monthly, 1926-07 to the current vintage month): stddev_samp(Mkt-RF) over a trailing 12-observation window, annualized by x sqrt(12). This is the whole US equity market back to 1926, a much longer and broader backdrop than the absorption ratio above (28 banks, 2020-2026 only). Computed anchors (2026-07-10): all-time peak ~71% (1933, the Depression); GFC peak ~31% (2009); COVID-year peak ~28% (Dec 2020, a trailing-12-month window that still contains the March 2020 crash); the single calendar month of October 1987 (Black Monday) returned Mkt-RF = -23.19% on its own (shown as a separate one-month anchor, not part of the rolling series). License: free for academic research and teaching use with citation (Fama and French; not public domain).

Banking-industry cost of capital and beta (Damodaran Online)

A cross-sectional snapshot (not a time series), Damodaran's US industry averages, January 2026 vintage: money-center banks, regional banks, and the Total Market baseline, each with levered beta, unlevered beta, cost of equity, and WACC (damodaran_cost_of_capital.parquet joined to damodaran_betas.parquet on (industry_name, region)). A textbook CAPM/WACC yardstick placed next to the market-implied LRMES/MES/CoVaR measures above; the two are not the same thing (one is a forward-looking valuation input, the other a realized-return-based tail-risk statistic), and the page does not conflate them. License: free, acknowledgement welcomed but not required.

Eight centuries of global real interest rates (Schmelzing, BoE SWP 845)

Schmelzing's 7-year-averaged global real interest rate, schmelzing_headline.parquet, 1310-2018 (702 non-null years; the paper's own window, not extended to today). Shown as the deep-history backdrop for the low- and negative-real-rate environment of the 2010s that shaped bank risk-taking and reach-for-yield behavior generally (the page does not claim a specific causal link to any named bank episode). All-time high and low and the 2018 endpoint are computed at render time from the parquet, not hardcoded. License: Bank of England copyright; non-commercial re-use permitted (kept under that allowance for this free, non-commercial research repository).

G-SIB designations (FSB, added 2026-07-11)

The official supervisory counterpart to the market-based measures above: the Financial Stability Board's annual list of global systemically important banks (G-SIBs) and their capital-surcharge buckets, gsib_history.parquet (442 rows, 2011-2025, 37 distinct banks). Shown on /systemic as (1) the latest year's list grouped by bucket with home jurisdiction, and (2) a per-bank bucket-history heat matrix 2011-2025. This is official designation data, not a FinWeave computation; it does not feed LRMES, MES, CoVaR, or the absorption ratio.

  • Source and method. FSB annual G-SIB PDFs (fsb.org), transcribed verbatim from each year's Annex bucket table; every (year, bank, bucket) triple read directly off the primary PDF, with each year's row count hard-asserted against the FSB's own stated list size. Full retrieval, the 15 PDF URLs, the verbatim transcription note, the cosmetic name-normalization table, and the two task-specified spot-checks (JPMorgan in the top bucket; Credit Suisse exits after 2022) are in data/raw/gsib/SOURCE.md.
  • Buckets. FSB buckets 1-5, higher = larger capital surcharge; bucket 5 has never been populated in any year. 2011 predates the bucket system (the 4 Nov 2011 methodology paper listed 29 G-SIFIs alphabetically with no bucket), so the 2011 column carries a null bucket for every bank and the matrix renders it as designation-only (a . marker), not a zero.
  • Honest absence. A blank matrix cell means the bank was not on that year's list (rendered blank, never zero). The matrix tracks presence separately from bucket so a 2011 listed-but-null cell is distinguished from an absent year.
  • Jurisdiction. ISO3 of the bank's headquarters/home-regulator domicile, assigned here from uncontested public facts (the FSB lists do not print a country); the assignment table and the Nordea/HSBC edge cases are in the SOURCE.md.
  • No forced links to /banks. A G-SIB is a consolidated holding company; the /banks/[cert] scorecards key on FDIC insured-subsidiary CERTs, a different legal entity that does not map one-to-one onto a holding company. To avoid asserting an identity that does not hold, G-SIB bank names are rendered as plain text, not links (the page links only the section-level pointer to the /banks explorer). This mirrors the same holding-company-vs-subsidiary caveat already stated for the equity-ticker universe on this page.
  • License. FSB publications are freely available; cite "FSB, [Year] List of Global Systemically Important Banks (G-SIBs)". Every value is recomputable from data/parquet/gsib_history.parquet by src/lib/gsib.ts.