CET1 vs Requirements and AT1 Trigger levels¶
© 2025 Marek Ozana
This notebook is a small end-to-end example of using polars-bloomberg to pull regulatory capital metrics from BQL and chart them for a universe of European banks.
We compare three CET1-related levels:
- CET1 (reported): the bank’s current Common Equity Tier 1 ratio (what it has).
- CET1 requirement (“review” / SREP): the supervisory CET1 requirement level (what it must meet under review-driven requirements).
- AT1 trigger: the contractual CET1 trigger level associated with Additional Tier 1 (CoCo) instruments (the “oh no” line where conversion/write-down mechanics may be activated).
Interpretation:
- The vertical gap CET1 − requirement is “headroom” against supervisory requirements.
- The gap CET1 − AT1 trigger is distance to the contractual AT1 trigger (not a regulatory buffer).
- Field definitions vary by jurisdiction and data source. Treat these as indicative and sanity-check against bank disclosures when precision matters.
Out[1]:
In [2]:
Copied!
from polars_bloomberg import BQuery
query = """
let(
#cet1 = value(bs_tier1_com_equity_ratio, fundamentalTicker, mapby=LINEAGE);
#trigger = CAPITAL_TYPE_COCO_TRIGGER_LEVEL();
#req = value(bs_ce_tier_1_review(), fundamentalTicker, mapby=LINEAGE);
)
get(ticker, #cet1, #trigger, #req)
for(
filter(
members('I31095 Index'),
value(bs_tot_asset(currency='EUR') > 105B, fundamentalTicker, mapby=LINEAGE)
)
)
"""
with BQuery() as bq:
res = bq.bql(query)
# Combine on 'ID' column
df = res[0]
for idx, d in enumerate(res[1:], start=1):
suffix = f"_{idx}"
df = df.join(d, on="ID", how="inner", suffix=suffix)
df = df.drop_nulls()
df.head(3)
from polars_bloomberg import BQuery
query = """
let(
#cet1 = value(bs_tier1_com_equity_ratio, fundamentalTicker, mapby=LINEAGE);
#trigger = CAPITAL_TYPE_COCO_TRIGGER_LEVEL();
#req = value(bs_ce_tier_1_review(), fundamentalTicker, mapby=LINEAGE);
)
get(ticker, #cet1, #trigger, #req)
for(
filter(
members('I31095 Index'),
value(bs_tot_asset(currency='EUR') > 105B, fundamentalTicker, mapby=LINEAGE)
)
)
"""
with BQuery() as bq:
res = bq.bql(query)
# Combine on 'ID' column
df = res[0]
for idx, d in enumerate(res[1:], start=1):
suffix = f"_{idx}"
df = df.join(d, on="ID", how="inner", suffix=suffix)
df = df.drop_nulls()
df.head(3)
Out[2]:
shape: (3, 14)
| ID | ticker | #cet1 | REVISION_DATE | AS_OF_DATE | PERIOD_END_DATE | SOURCE_ID | #trigger | MULTIPLIER | #req | REVISION_DATE_3 | AS_OF_DATE_3 | PERIOD_END_DATE_3 | SOURCE_ID_3 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| str | str | f64 | date | date | date | str | f64 | f64 | f64 | date | date | date | str |
| "ZM918892 Corp" | "INTNED" | 13.4 | 2025-10-30 | 2025-12-18 | 2025-09-30 | "INGA NA Equity" | 7.0 | 1.0 | 10.83 | 2025-10-30 | 2025-12-18 | 2025-09-30 | "INGA NA Equity" |
| "YV921889 Corp" | "NDAFH" | 15.9 | 2025-10-16 | 2025-12-18 | 2025-09-30 | "NDA FH Equity" | 5.125 | 1.0 | 13.6 | 2025-10-16 | 2025-12-18 | 2025-09-30 | "NDA FH Equity" |
| "ZM198062 Corp" | "ACAFP" | 11.65 | 2025-10-30 | 2025-12-18 | 2025-09-30 | "ACA FP Equity" | 5.125 | 1.0 | 8.2 | 2025-10-30 | 2025-12-18 | 2025-09-30 | "ACA FP Equity" |