import re
import pandas as pd
from IPython.display import Markdown, HTML
from tulip.data.bloomberg import BloombergClient as bb
from tulip.plots import plot_line, plot_lines, plot_bar
from tulip.utils.string_manipulation import c_titleize as titleize
from tulip.core import TulipLibrary
from tulip.libraries.identifiers import KATE_KEYS
from tulip_mania.notebook_related import display_chart
from arcticdb import QueryBuilder # or: from arcticdb import QueryBuilder as QB
market_data = TulipLibrary.market_data()
# todos;
# - add how we get the FV
dashboards = TulipLibrary.dashboards()
dashboard = dashboards.read("rates_dashboard").data
last_mask = dashboard.date == dashboard.date.max()
dashboard = dashboard[last_mask].drop(columns=["date"])
def style_dashboard(df):
"""
Formats floats, centers all text, widens columns,
adds white+dark-grey separator rows, and applies a bold 'card' header style.
"""
separator_labels = [
"policy_rate",
"inflation_expectation_1y",
"real_yield_10y",
"output_gap",
"nom_yield_10y-inflation_expectation_1y",
"nom_yield_10y",
"nom_yield_5y",
"private_credit_growth_1y",
"govt_balance_pgdp",
"govt_balance_pgdp_prior_year",
"monetary_base_pgdp",
]
def _norm(s: str) -> str:
return re.sub(r"[ \t\-_–—]+", "_", str(s).strip().lower())
display_index = df.index.map(titleize)
sep_titleized = {titleize(x) for x in separator_labels}
sep_norm = {_norm(x) for x in sep_titleized}
TOP_SPACER = "border-top: 18px solid white"
TOP_GREY_RULE = "box-shadow: inset 0 1px 0 0 #444"
TOP_STYLE = f"{TOP_SPACER}; {TOP_GREY_RULE}"
df.columns.name = None # "Metric"
df.index.name = None
column_width = "50px"
styler = (
df.style.format("{:.3f}", na_rep="")
.set_properties(
**{
"text-align": "center",
"font-family": "'Montserrat', sans-serif",
"min-width": column_width,
}
)
.set_table_styles(
[
{
"selector": "th.row_heading",
"props": [
("width", "300px"),
("min-width", "300px"),
("text-align", "center"),
],
},
{
"selector": "th.index_name",
"props": [
("text-align", "center"),
("font-weight", "bold"),
("font-size", "1.1em"),
("position", "static"), # override sticky
],
},
{"selector": "td", "props": [("text-align", "center")]},
{
"selector": "th.col_heading",
"props": [
("text-align", "center"),
("text-transform", "uppercase"),
("letter-spacing", "1.2px"),
("font-weight", "700"),
("color", "#FFFFFF"),
("background-color", "#0F0F10"),
("box-shadow", "inset 0 -2px 0 0 #2A2A2D"),
("padding", "12px 10px"),
("min-width", column_width),
("border-right", "1px solid #FFFFFF"),
],
},
{
"selector": "thead tr",
"props": [("border-bottom", "3px solid #2A2A2D")],
},
{
"selector": "thead th.col_heading", # only column headers sticky
"props": [("position", "sticky"), ("top", "0"), ("z-index", "2")],
},
{
"selector": "tbody tr:nth-child(odd)",
"props": [("background-color", "white")],
},
{
"selector": "tbody tr:nth-child(even)",
"props": [("background-color", "white")],
},
],
overwrite=False,
)
)
styler = styler.relabel_index(list(display_index), axis=0)
def index_top_border(s):
return [TOP_STYLE if _norm(v) in sep_norm else "" for v in s]
def row_top_border(row):
top = TOP_STYLE if _norm(row.name) in sep_norm else ""
return [top] * len(row)
try:
styler = styler.apply_index(index_top_border, axis=0)
except Exception:
pass
styler = styler.apply(row_top_border, axis=1)
return styler
HTML(f"""
<div style="max-height: 400px; overflow-y: auto;">
{style_dashboard(dashboard).to_html()}
</div>
<style>
div[style*="overflow-y"] table thead th.col_heading {{
position: sticky;
top: 0;
z-index: 2;
}}
</style>
""")Methodological Notes
Fiscal position data is sourced mainly from the IMF, with adjustments for national budget laws. These are estimates that refer to the the current year, therefore might be very different from a trailing calculation using the national accounts.
Govt Balance PGDP (overall balance) includes interest payments.
The Primary Balance (line below) excludes interest payments. Primary Debt Neutral is the level of the primary balance consistent with stabilizing the debt-to-GDP ratio. It accounts for a medium-term GDP growth rate.
Excess Primary is the gap between the actual primary balance and the debt-neutral level (i.e., the fiscal buffer or shortfall).
You can crosscheck these numbers with the IMF with their datamapper tool. For example: New Zealand
gov_columns = [
"govt_balance_pgdp",
"govt_balance_pgdp_primary",
"govt_balance_pgdp_primary_debt_neutral",
"govt_balance_pgdp_excess_primary",
]
gov_finances = dashboard.loc[gov_columns, :].rename(
{k: titleize(k) for k in gov_columns}, axis=0
)
fig = plot_bar(
gov_finances.T,
colors=[
"#1f77b4",
"#a5c9e1",
"#c5c5c5",
"#5a5a5a",
],
title="<b>Government Balance as % of GDP</b>",
figsize=(1000, 500),
boundaries=True,
logo=False,
tick_suffix="%",
)
display_chart(fig)govt_balance = market_data.read("govt_balance_pgdp", columns=KATE_KEYS).data
fig = plot_lines(
govt_balance,
years_limit=5,
title="<b>Govt Balance (surplus/deficit) (% of GDP)</b>",
ylabel="% of GDP",
show_0=True,
figsize=(1000, 500),
logo=False,
tick_suffix="%",
)
visible = {"EUR", "GBP", "JPY", "USD"}
for tr in fig.data:
if getattr(tr, "name", None) not in visible:
tr.visible = "legendonly"
fig.update_layout(
yaxis_title="% of GDP",
)
display_chart(fig)Private Credit Growth Detail¶
Methodology detail: Private Credit Expansion
credit_creation = market_data.read("private_credit_growth_1y_pgdp").data
fig = plot_lines(
credit_creation,
years_limit=10,
title="<b>Private Credit Growth (% of GDP)</b>",
ylabel="% of GDP",
show_0=True,
figsize=(1200, 500),
top_margin=120,
logo=False,
tick_suffix="%",
)
visible = {"EUR", "GBP", "JPY", "USD"}
for tr in fig.data:
if getattr(tr, "name", None) not in visible:
tr.visible = "legendonly"
fig.update_layout(
yaxis_title="% of GDP",
)
display_chart(fig)Central Bank Intervention Detail (QE/QT and FX Purchases/Sales)¶
Methodology detail: Intervention liquidity
mp3_like_measures = market_data.read(
"intervention_liquidity_pgdp_diff_6m", columns=KATE_KEYS
).data
fig = plot_lines(
mp3_like_measures,
years_limit=10,
title="<b>Intervention Liquidity (% of GDP)</b>",
ylabel="% of GDP",
show_0=True,
figsize=(1000, 500),
logo=False,
tick_suffix="%",
)
visible = {"EUR", "GBP", "JPY", "USD"}
for tr in fig.data:
if getattr(tr, "name", None) not in visible:
tr.visible = "legendonly"
fig.update_layout(
yaxis_title="% of GDP",
)
display_chart(fig)Appendix:¶
Updated times of each data point
try:
dashboard_metadata = dashboards.read_metadata("rates_dashboard").metadata
last_dates_df = dashboard_metadata["last_date"]
last_dates_df.index = last_dates_df.index.map(titleize)
last_dates_df.index.name = "Metric"
# Style to match the dashboard above
last_dates_df.style.format(
lambda x: pd.to_datetime(x).strftime("%Y-%m-%d") if pd.notna(x) else ""
).set_properties(
**{
"text-align": "center",
"font-family": "'Montserrat', sans-serif",
}
).set_table_styles(
[
{
"selector": "th.col_heading",
"props": [
("text-align", "center"),
("text-transform", "uppercase"),
("letter-spacing", "1.2px"),
("font-weight", "700"),
("color", "#FFFFFF"),
("background-color", "#0F0F10"),
("padding", "12px 10px"),
],
},
{
"selector": "th.row_heading",
"props": [
("text-align", "center"),
("width", "300px"),
],
},
{"selector": "td", "props": [("text-align", "center")]},
]
)
display(
Markdown(
"Calculated as of " + dashboard_metadata["calculated"].strftime("%Y-%m-%d")
)
)
display(Markdown(""))
display(Markdown("### Last Available Dates for Underlying Data Sources"))
display(last_dates_df)
except Exception as e:
print(f"Could not load last dates metadata: {e}")Private credit indicators for each country
data = {
"Indicator": [
"AUD_PCREDITBN_SA",
"BRL_PCREDITBN_NSA",
"CAD_PCREDITBN_NSA",
"CHF_PCREDITBN_NSA",
"CLP_PCREDITBN_NSA",
"CNY_PCREDITBN_NSA",
"COP_PCREDITBN_NSA",
"CZK_PCREDITBN_NSA",
"DEM_PCREDITBN_NSA",
"ESP_PCREDITBN_NSA",
"EUR_PCREDITBN_NSA",
"FRF_PCREDITBN_NSA",
"GBP_PCREDITBN_NSA",
"HUF_PCREDITBN_NSA",
"IDR_PCREDITBN_NSA",
"ILS_PCREDITBN_NSA",
"INR_PCREDITBN_NSA",
"ITL_PCREDITBN_NSA",
"JPY_PCREDITBN_NSA",
"KRW_PCREDITBN_NSA",
"MXN_PCREDITBN_NSA",
"MYR_PCREDITBN_NSA",
"NLG_PCREDITBN_NSA",
"NOK_PCREDITBN_NSA",
"NZD_PCREDITBN_NSA",
"PEN_PCREDITBN_NSA",
"PHP_PCREDITBN_NSA",
"PLN_PCREDITBN_NSA",
"RON_PCREDITBN_NSA",
"RUB_PCREDITBN_NSA",
"SEK_PCREDITBN_NSA",
"SGD_PCREDITBN_NSA",
"THB_PCREDITBN_NSA",
"TRY_PCREDITBN_NSA",
"TWD_PCREDITBN_NSA",
"USD_PCREDITBN_SA",
"ZAR_PCREDITBN_NSA",
],
"Underlying data": [
"Total narrow credit lending excluding financial businesses",
"Total credit operation outstanding",
"Total credit liabilities of households and private non-financial corporations",
"Total domestic loans",
"Total domestic credit for the private sector from the banking sector",
"Depository Corporations Survey - Domestic credit for non-financial sectors",
"Gross domestic credit to the private sector",
"Credit to other residents (non-government) (loans)",
"Loans to domestic non-banks",
"Domestic loans to other resident sectors",
"Loans to other EA residents (excl. governments and MFIs)",
"Domestic loans to the private sector",
"Domestic loans to the private sector",
"Loans to other residents",
"Loans to the private sector",
"Domestic private sector credit",
"Domestic credit from commercial banks",
"Other MFIs (excl. Central Bank) loans to other sectors",
"Outstanding loans from domestic banks (excl. Shinkin Banks)",
"Domestic loans to non-financial corporations and households",
"Domestic loans to the private sector",
"Total claims on the private sector",
"Loan to EA residents (private sector)",
"Domestic credit to non-financial corporations and households",
"Loans to Housing & Personal Consumption and Businesses",
"Total credit to the private sector",
"Domestic claims to the private sector",
"Total loans to domestic residents (non-MFI and non-government)",
"Loans to the private sector",
"Loans in local currency to individuals and organizations",
"MFIs lending to households and non-financial corporations",
"Domestic credit to the private sector",
"Depository Corporations Survey - Domestic credit",
"Loans to the private sector",
"All banks loans outstanding",
"Loan and leases in bank credit, all commercial banks",
"Domestic private sector claims",
],
"Source": [
"Reserve Bank of Australia",
"Central Bank of Brazil",
"Statistics Canada",
"National Bank of Switzerland",
"Central Bank of Chile",
"The People's Bank of China",
"Central Bank of Colombia",
"Czech National Bank",
"Deutsche Bundesbank",
"Bank of Spain",
"ECB",
"Banque de France",
"Bank of England",
"Central Bank of Hungary",
"Bank Indonesia",
"Bank of Israel",
"Reserve Bank of India",
"Banca d'Italia",
"Bank of Japan",
"Bank of Korea",
"Bank of Mexico",
"Central Bank of Malaysia",
"Central Bank of Netherlands",
"Statistics Norway",
"Reserve Bank of New Zealand",
"Central Reserve Bank of Peru",
"Central Bank of the Philippines",
"National Bank of Poland",
"National Bank of Romania",
"Central Bank of the Russian Federation",
"Statistics Sweden",
"Monetary Authority of Singapore",
"Bank of Thailand",
"Central Bank of the Republic of Türkiye",
"Central Bank of Taiwan",
"Federal Reserve",
"Reserve Bank of South Africa",
],
}
summary = pd.DataFrame(data)
HTML(summary.to_html(index=False))from tulip_mania.notebook_related import notebook_updated
notebook_updated()