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
- 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).
- 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). - 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.
- 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 itsSYSTEM_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_TICKERScollector 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 quantileq = 0.05. method = rolling_quantile_reg. Both the CoVaR level and Delta-CoVaR are stored; the level uses the engine'scompute_covar()inside a loop whose window slicing is identical tocompute_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, enginecompute_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 = 0marks 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) andwindow_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
| Setting | Value | Source |
|---|---|---|
| System index | KBE | argus SYSTEM_INDEX |
| Tail quantile q (CoVaR, MES) | 0.05 | argus + engine default |
| Absorption eigenvalue fraction | 0.20 | argus + engine default |
| Primary rolling window | 252 trading days | engine rolling-fn default |
| Absorption second window | 63 trading days | argus SVB case study |
| AR-shift short / long MA | 21 / 252 | engine compute_ar_shift default |
| LRMES systemic-event threshold C | -0.40 | engine compute_lrmes default |
| GARCH | (1,1), constant mean | engine _fit_garch |
| DCC | (1,1), Engle two-step MLE | engine _fit_dcc_params |
Limitations (read before using)
- 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.
- 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.
- 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.
- 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.
- 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.
- No network/contagion layer. The Eisenberg-Noe interbank network measure
is not included (argus's
network_metricswas 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):
| Measure | Point | Stored | Independent | Diff |
|---|---|---|---|---|
| Absorption 63d | 2023-03-17 | 0.877166 | 0.877166 | 0.00e+00 |
| MES 252d, JPM | 2026-07-06 | -0.014760 | -0.014760 | 0.00e+00 |
| Delta-CoVaR 252d, BAC | 2026-07-06 | -0.036847 | -0.036847 | 0.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 indata/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/banksexplorer). 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.parquetbysrc/lib/gsib.ts.