Python | Source and method
In 2020, bank returns fell 44% while bad loans rose 17%.
An interactive scorecard comparing the twenty largest US banks on the same six measures, across eleven years of quarterly regulatory filings. One pipeline produces both the dashboard and the spreadsheet a risk team would actually circulate.
The question
Bank health is public information, and it is almost unreadable. Every US bank files a quarterly call report; the FDIC serves those filings through an open API. But a filing is a single institution at a single moment, and the number that matters is almost always relative - is this bank earning more or less than its peers, and is the gap widening?
This dashboard asks how the twenty largest US banks compare on the same terms, over the same window, so that an outlier is visible without reading a single filing. Every KPI is shown against the peer median rather than in isolation.
How it is structured
A single self-contained page, laid out as four zones. There is no server behind it: the entire dashboard, including the plotting library, is one HTML file, which is why it can be embedded directly in this site and still work if you save it to disk.
| KPI row | Five headline ratios plus a badge for the largest bank. Each card shows the selected bank and its gap to the peer median, coloured for direction - which is not the same as up or down |
|---|---|
| Filter rail | Bank selector, metric selector and a year range. Selecting a bank redraws every zone at once |
| Ranking and trend | Who is who by total assets, and the selected bank's chosen metric against the peer median through time, with the other nineteen behind it as ghost lines |
| Scatter and heatmap | Efficiency against return on equity, bubble-sized by assets - who runs lean and earns - and a bank-by-year map of bad loans, where 2020 lights up |
The six measures
- ROA - net income over average assets. Profitability of the balance sheet.
- ROE - net income over average equity. Return to shareholders.
- NIM - net interest income over average earning assets. The core lending spread.
- Efficiency ratio - cost to produce a dollar of revenue. Lower is better, which the delta colouring respects.
- NPL ratio - loans 90 days late or on nonaccrual, over total loans. Credit quality.
- Coverage ratio - loan loss allowance over those bad loans. The reserve cushion behind them.
What it shows
- The 2020 shock hit earnings far harder than credit. Between 2019 Q4 and 2020 Q4 the peer median return on equity fell from 11.7% to 6.6% - down 44% - while the median bad-loan ratio rose only from 0.82% to 0.96%. Banks took the provision before the losses arrived.
- The rate cycle is legible in the lending spread. Median net interest margin ran 2.94%, fell to 2.18%, and recovered to 3.06% by 2026 Q1 - a full round trip visible in one line.
- Efficiency varies more than profitability does. Charles Schwab sits near an 18% efficiency ratio, an outlier no universal bank comes close to, because its cost base is a different business.
- Credit stress is bank-specific, not systemic. Capital One peaks at a 1.95% bad-loan ratio in 2024 Q4 while the peer median stays under 1% - a card book behaving like a card book.
- Peer medians at 2026 Q1, for reference: ROA 1.16%, ROE 11.6%, NIM 3.06%, efficiency 55.1%, NPL 0.84%.
Technical note
Why the published ratios were used, and how they were checked anyway
The FDIC serves most of these ratios pre-computed. It would have been easy to ignore them and derive all six from raw dollar components - it looks more rigorous, and it is the wrong call. The published ratios handle the things that quietly go wrong in a hand-rolled version: averaging a balance over the quarter rather than snapping it at quarter end, and the edge cases around merger periods and restated filings.
Trusting a source is only defensible if it is tested. So one ratio is recomputed from its own components and compared: the bad-loan ratio, rebuilt from noncurrent loans over total loans, matches the FDIC's own field to 0.000000 percentage points. Two further anchors run on every build - JPMorgan Chase must come out first by assets, and the 2020 credit shock must be visible in the peer median. If any of them fail, the numbers on this page are not to be trusted, and the pipeline says so.
One convention had to be picked and stated. The headline ratios are year-to-date annualised, which is the standard the FDIC uses in its own Quarterly Banking Profile, so these figures can be compared against the FDIC's published aggregates without translation. The single-quarter variants are computed too and kept as extra columns rather than discarded.
And one number is deliberately absent. When a bank has zero bad loans, the coverage ratio divides by zero. Reporting infinity is meaningless and reporting zero is a lie, so the division is guarded and the value shows as not available - 15 bank-quarters in total, mostly State Street. An honest gap beats a confident wrong number.
One pipeline, two outputs
The same run that builds the dashboard also writes a styled Excel scorecard - twenty banks by KPI columns for the latest quarter, conditionally filled against the peer median, print-ready. That is the point rather than a leftover: Python automating the spreadsheet instead of arguing with it. The audience for a dashboard and the audience for a workbook that gets emailed around are not the same people, and both come out of one script.
- Every API response is cached to disk, so the repository rebuilds offline and a reviewer gets the same numbers as this page.
- Refresh is a rerun. New quarter, same script, new dashboard and new workbook.
- Colours live in exactly two places - one CSS block and one Python dictionary - so the palette was swapped in a single pass after the design was validated for contrast and colour-vision deficiency.
Caveats, stated plainly
- These are insured bank charters, not holding companies. The set contains Goldman Sachs Bank USA, not the Goldman Sachs Group. Charter-level and group-level figures legitimately differ.
- The top twenty is fixed as of the latest quarter and history follows the surviving charter through mergers - so Truist's series carries BB&T's pre-2019 history.
- Coverage is unavailable for 15 bank-quarters, by design, as described above.
- Ratios are year-to-date annualised; quarterly variants exist in the data but are not what the cards show.