import plotly.graph_objects as go
import pandas as pd
from tulip.data.haver import HaverApiClientCanada Stimulus¶
Canadam¶
# --- CONFIGURATION ---
start_date = "2015-01-01"
end_date = "2025-12-31"
# ---------------------
# 1. Retrieve Data (FIXED: Using the client instead of undefined function)
with HaverApiClient() as haver_client:
gg_flow = haver_client.get_series("V3723446@CANADA").ts
nfc_stock = haver_client.get_series("V1C15703@CANADA").ts
hh_stock = haver_client.get_series("V1C15625@CANADA").ts
gdp_ts = haver_client.get_series("V6E05783@CANADA").ts
# 2. Calculate 6-month Rolling Sums
gg_6m_sum = (-gg_flow).rolling(window=6).sum()
nfc_6m_sum = nfc_stock.diff().rolling(window=6).sum()
hh_6m_sum = hh_stock.diff().rolling(window=6).sum()
# 3. Handle Frequency & Alignment
gdp_monthly = gdp_ts.resample("MS").ffill()
gdp_aligned = gdp_monthly.reindex(gg_6m_sum.index, method="ffill")
# 4. Normalize (as % of GDP)
gg_norm = (gg_6m_sum / gdp_aligned) * 100
nfc_norm = (nfc_6m_sum / gdp_aligned) * 100
hh_norm = (hh_6m_sum / gdp_aligned) * 100
# 5. Filter for Plotting
df_plot = (
pd.DataFrame({"GG": gg_norm, "NFC": nfc_norm, "HH": hh_norm})
.dropna()
.loc[start_date:end_date]
)
# 6. Create Stacked Area Chart
fig = go.Figure()
# Helper for adding traces
def add_sector_trace(fig, data, name, color, fillcolor):
fig.add_trace(
go.Scatter(
x=data.index,
y=data,
mode="lines",
line=dict(width=1, color=color),
stackgroup="one", # This is what makes it an area chart
name=name,
fillcolor=fillcolor,
)
)
add_sector_trace(
fig,
df_plot["HH"],
"Households (HH)",
"rgb(131, 201, 255)",
"rgba(131, 201, 255, 0.5)",
)
add_sector_trace(
fig,
df_plot["NFC"],
"Non-Financial Corps (NFC)",
"rgb(255, 188, 121)",
"rgba(255, 188, 121, 0.5)",
)
add_sector_trace(
fig,
df_plot["GG"],
"General Government (GG)",
"rgb(255, 102, 102)",
"rgba(255, 102, 102, 0.5)",
)
# 7. Layout Adjustments
fig.update_layout(
title="<b>Canada: Total 6-Month Credit Injection as % of GDP</b>",
xaxis_title="Date",
yaxis_title="Percent of Annual GDP (%)",
yaxis=dict(ticksuffix="%", zeroline=True, zerolinecolor="black"),
template="plotly_white",
hovermode="x unified",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
fig.show()
# Latest Print Logic (Added a check for empty DF to avoid errors)
if not df_plot.empty:
latest_total = df_plot.iloc[-1].sum()
print(f"Latest Total Credit Injection: {latest_total:,.2f}% of GDP")
print(f"Latest Month: {df_plot.index[-1].strftime('%B %Y')}")import pandas as pd
import plotly.graph_objects as go
from tulip.data.haver import HaverApiClient
# --- 1. DATA RETRIEVAL ---
with HaverApiClient() as haver_client:
cad_credit_liabs_hh = haver_client.get_series("V1C15625@CANADA").ts
cad_credit_liabs_nfc = haver_client.get_series("V1C15703@CANADA").ts
cad_ngdp_haver = haver_client.get_series("V6E05783@CANADA").ts
# --- 2. METHODOLOGY ---
cad_ngdp_smoothed = cad_ngdp_haver.rolling(4).mean().resample("ME").ffill()
hh_yoy_flow = cad_credit_liabs_hh.diff(12)
nfc_yoy_flow = cad_credit_liabs_nfc.diff(12)
gdp_aligned = cad_ngdp_smoothed.reindex(hh_yoy_flow.index).ffill()
hh_norm = (hh_yoy_flow.squeeze() / gdp_aligned) * 100
nfc_norm = (nfc_yoy_flow.squeeze() / gdp_aligned) * 100
total_private = hh_norm + nfc_norm
# --- 3. PLOTTING ---
fig = go.Figure()
# Sector 1: Households
fig.add_trace(
go.Scatter(
x=hh_norm.index,
y=hh_norm.values,
mode="lines",
name="Households (HH)",
stackgroup="one",
line=dict(width=0.5, color="rgb(31, 119, 180)"),
fillcolor="rgba(31, 119, 180, 0.5)",
)
)
# Sector 2: Corporations
fig.add_trace(
go.Scatter(
x=nfc_norm.index,
y=nfc_norm.values,
mode="lines",
name="Non-Financial Corps (NFC)",
stackgroup="one",
line=dict(width=0.5, color="rgb(255, 127, 14)"),
fillcolor="rgba(255, 127, 14, 0.5)",
)
)
# Total Line
fig.add_trace(
go.Scatter(
x=total_private.index,
y=total_private.values,
mode="lines",
name="Total Private Credit",
line=dict(color="black", width=2, dash="dot"),
)
)
# --- 4. ZOOM CONTROLS (Range Slider & Buttons) ---
fig.update_layout(
title="Canada: Annual Private Credit Flow as % of GDP",
xaxis=dict(
rangeselector=dict(
buttons=list(
[
dict(count=1, label="1y", step="year", stepmode="backward"),
dict(count=5, label="5y", step="year", stepmode="backward"),
dict(count=10, label="10y", step="year", stepmode="backward"),
dict(step="all"),
]
)
),
rangeslider=dict(visible=True), # This creates the zoom bar at the bottom
type="date",
),
yaxis=dict(title="Percent of GDP (%)", ticksuffix="%"),
template="plotly_white",
hovermode="x unified",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
fig.show()import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from tulip.data.haver import HaverApiClient
# --- 1. DATA RETRIEVAL ---
with HaverApiClient() as haver_client:
# Canada 10y Yield (Monthly)
cad_10y = haver_client.get_series("R156MA@CANADA").ts.rename("Cad_10y")
# Credit Stocks (Monthly, Mil C$)
nfc_stock = haver_client.get_series("V1C15703@CANADA").ts.rename(
"Cad_NFC_Credit_Stock"
)
hh_stock = haver_client.get_series("V1C15625@CANADA").ts.rename(
"Cad_HH_Credit_Stock"
)
# --- 2. YIELD CALCULATIONS ---
# 6-month difference in BPS (diff * 100)
cad_10y_6m_delta = (cad_10y.diff(6) * 100).dropna().rename("Cad_10y_6mDelta")
# --- 3. CREDIT IMPULSE CALCULATIONS ---
# Total Private Credit Stock
cad_private_credit_stock = (nfc_stock + hh_stock).dropna()
# Velocity: YoY % Change
cad_private_credit_yoy = (cad_private_credit_stock.pct_change(12) * 100).dropna()
# Acceleration: 12-month change in the YoY rate (in points)
accel_cad_credit = (
cad_private_credit_yoy.diff(12)
.dropna()
.rename("Acceleration_Cad_Private_Credit_Stock")
)
# --- 4. PLOTTING ---
fig = make_subplots(specs=[[{"secondary_y": True}]])
# Credit Acceleration (Left Axis)
fig.add_trace(
go.Scatter(
x=accel_cad_credit.index,
y=accel_cad_credit,
name="Credit Acceleration (pp)",
line=dict(color="darkblue", width=2.5),
),
secondary_y=False,
)
# 10y Yield 6m Delta (Right Axis)
fig.add_trace(
go.Scatter(
x=cad_10y_6m_delta.index,
y=cad_10y_6m_delta,
name="10y Yield 6m Δ (BPS)",
line=dict(color="crimson", width=1.5, dash="dot"),
),
secondary_y=True,
)
# --- 5. LAYOUT ---
fig.update_layout(
title="<b>CANADA: Yields vs Credit Impulse</b><br><sup>Credit Acceleration vs. 6-Month Change in 10Y Yields</sup>",
xaxis=dict(rangeslider=dict(visible=True), gridcolor="whitesmoke"),
yaxis=dict(
title="<b>Credit Accel (pp change in YoY)</b>",
gridcolor="whitesmoke",
zeroline=True,
zerolinecolor="black",
),
yaxis2=dict(
title="<b>10y Yield Change (BPS)</b>", zeroline=True, zerolinecolor="gray"
),
template="plotly_white",
hovermode="x unified",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
fig.show()import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from tulip.data.haver import HaverApiClient
# --- 1. DATA RETRIEVAL ---
with HaverApiClient() as haver_client:
# Canada 10y Yield (Monthly)
cad_10y = haver_client.get_series("R156MA@CANADA").ts.rename("Cad_10y")
# Credit Stocks (Monthly, Mil C$)
nfc_stock = haver_client.get_series("V1C15703@CANADA").ts.rename(
"Cad_NFC_Credit_Stock"
)
hh_stock = haver_client.get_series("V1C15625@CANADA").ts.rename(
"Cad_HH_Credit_Stock"
)
# --- 2. YIELD CALCULATIONS ---
# 12-month difference in BPS (diff * 100)
cad_10y_12m_delta = (cad_10y.diff(12) * 100).dropna().rename("Cad_10y_12mDelta")
# --- 3. CREDIT IMPULSE CALCULATIONS ---
# Total Private Credit Stock
cad_private_credit_stock = (nfc_stock + hh_stock).dropna()
# Velocity: YoY % Change
cad_private_credit_yoy = (cad_private_credit_stock.pct_change(12) * 100).dropna()
# Acceleration: 12-month change in the YoY rate (in points)
accel_cad_credit = (
cad_private_credit_yoy.diff(12)
.dropna()
.rename("Acceleration_Cad_Private_Credit_Stock")
)
# --- 4. PLOTTING ---
fig = make_subplots(specs=[[{"secondary_y": True}]])
# Credit Acceleration (Left Axis)
fig.add_trace(
go.Scatter(
x=accel_cad_credit.index,
y=accel_cad_credit,
name="Credit Acceleration (pp)",
line=dict(color="darkblue", width=2.5),
),
secondary_y=False,
)
# 10y Yield 12m Delta (Right Axis)
fig.add_trace(
go.Scatter(
x=cad_10y_6m_delta.index,
y=cad_10y_6m_delta,
name="10y Yield 12m Δ (BPS)",
line=dict(color="crimson", width=1.5, dash="dot"),
),
secondary_y=True,
)
# --- 5. LAYOUT ---
fig.update_layout(
title="<b>CANADA: Yields vs Credit Impulse</b><br><sup>Credit Acceleration vs. 12-Month Change in 10Y Yields</sup>",
xaxis=dict(rangeslider=dict(visible=True), gridcolor="whitesmoke"),
yaxis=dict(
title="<b>Credit Accel (pp change in YoY)</b>",
gridcolor="whitesmoke",
zeroline=True,
zerolinecolor="black",
),
yaxis2=dict(
title="<b>10y Yield Change (BPS)</b>", zeroline=True, zerolinecolor="gray"
),
template="plotly_white",
hovermode="x unified",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
fig.show()import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from tulip.data.haver import HaverApiClient
# --- 1. DATA RETRIEVAL ---
with HaverApiClient() as haver_client:
# BoC Overnight Target Rate (Monthly, %)
cad_on_rate = haver_client.get_series("B156RD@CANADA").ts.rename("Cad_ON_Rate")
# Re-pulling Credit Stocks for a self-contained block
nfc_stock = haver_client.get_series("V1C15703@CANADA").ts.rename("NFC")
hh_stock = haver_client.get_series("V1C15625@CANADA").ts.rename("HH")
# --- 2. CALCULATIONS ---
# A. Overnight Rate 6m Delta (BPS)
# We multiply by 100 to convert the percentage point change to Basis Points
cad_on_rate_6m_delta = (cad_on_rate.diff(6) * 100).dropna()
# B. Credit Acceleration (The Smooth Version)
cad_private_credit = (nfc_stock + hh_stock).dropna()
velocity = cad_private_credit.pct_change(12) * 100
# Acceleration = Current YoY growth minus growth 12 months ago
accel_cad_credit = velocity.diff(12).dropna()
# --- 3. PLOTTING ---
fig = make_subplots(specs=[[{"secondary_y": True}]])
# Credit Acceleration (Left Axis)
fig.add_trace(
go.Scatter(
x=accel_cad_credit.index,
y=accel_cad_credit,
name="Credit Acceleration (pp)",
line=dict(color="royalblue", width=2.5),
),
secondary_y=False,
)
# Policy Rate Change (Right Axis)
fig.add_trace(
go.Scatter(
x=cad_on_rate_6m_delta.index,
y=cad_on_rate_6m_delta,
name="BoC Rate 6m Δ (BPS)",
line=dict(color="firebrick", width=1.5, dash="dot"),
),
secondary_y=True,
)
# --- 4. LAYOUT ---
fig.update_layout(
title="<b>CANADA: Monetary Transmission</b><br><sup>Policy Rate Momentum (BPS) vs. Private Credit Acceleration (pp)</sup>",
xaxis=dict(rangeslider=dict(visible=True), gridcolor="whitesmoke"),
yaxis=dict(title="<b>Credit Accel (pp)</b>", zeroline=True, zerolinecolor="black"),
yaxis2=dict(
title="<b>BoC Rate Change (BPS)</b>", zeroline=True, zerolinecolor="gray"
),
template="plotly_white",
hovermode="x unified",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
fig.show()import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from tulip.data.haver import HaverApiClient
# --- 1. DATA RETRIEVAL ---
with HaverApiClient() as haver_client:
# Canada CPI Index (Monthly)
cad_cpi_index = haver_client.get_series("V4C90914@CANADA").ts.rename(
"Cad_CPI_Index"
)
# Credit Stocks for Impulse
nfc_stock = haver_client.get_series("V1C15703@CANADA").ts.rename("NFC")
hh_stock = haver_client.get_series("V1C15625@CANADA").ts.rename("HH")
# --- 2. CALCULATIONS ---
# A. Inflation Acceleration (6-month Change in YoY Rate)
cad_cpi_yoy = (cad_cpi_index.pct_change(12) * 100).dropna()
# We use BPS (diff * 100) to match the "vibe" of our previous rate charts
cad_cpi_yoy_6m_delta = (cad_cpi_yoy.diff(6) * 100).dropna()
# B. Credit Acceleration
cad_private_credit = (nfc_stock + hh_stock).dropna()
velocity = cad_private_credit.pct_change(12) * 100
accel_cad_credit = velocity.diff(12).dropna()
# --- 3. PLOTTING ---
fig = make_subplots(specs=[[{"secondary_y": True}]])
# Credit Acceleration (Left Axis)
fig.add_trace(
go.Scatter(
x=accel_cad_credit.index,
y=accel_cad_credit,
name="Credit Acceleration (pp)",
line=dict(color="royalblue", width=2.5),
),
secondary_y=False,
)
# Inflation Acceleration (Right Axis)
fig.add_trace(
go.Scatter(
x=cad_cpi_yoy_6m_delta.index,
y=cad_cpi_yoy_6m_delta,
name="6m Inflation Accel (BPS)",
line=dict(color="darkorange", width=2, dash="solid"),
),
secondary_y=True,
)
# --- 4. LAYOUT ---
fig.update_layout(
title="<b>CANADA: The Credit-Inflation push</b><br><sup>Private Credit Acceleration vs. 6-Month Change in YoY CPI</sup>",
xaxis=dict(rangeslider=dict(visible=True), gridcolor="whitesmoke"),
yaxis=dict(title="<b>Credit Accel (pp)</b>", zeroline=True, zerolinecolor="black"),
yaxis2=dict(
title="<b>Inflation Accel (BPS)</b>", zeroline=True, zerolinecolor="gray"
),
template="plotly_white",
hovermode="x unified",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
fig.show()import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from tulip.data.haver import HaverApiClient
# --- 1. DATA RETRIEVAL ---
with HaverApiClient() as haver_client:
# Real GDP (Quarterly, SAAR)
cad_real_gdp = haver_client.get_series("V6E05752@CANADA").ts.rename("Cad_Real_GDP")
# Credit Stocks (Monthly, Mil C$)
nfc_stock = haver_client.get_series("V1C15703@CANADA").ts.rename("NFC")
hh_stock = haver_client.get_series("V1C15625@CANADA").ts.rename("HH")
# --- 2. CALCULATIONS ---
# A. Real GDP YoY % (Quarterly)
cad_real_gdp_yoy = (cad_real_gdp.pct_change(4) * 100).dropna()
# B. Credit Acceleration (Monthly)
cad_private_credit = (nfc_stock + hh_stock).dropna()
velocity = cad_private_credit.pct_change(12) * 100
accel_cad_credit = velocity.diff(12).dropna().rename("accel_cad_credit")
# --- 3. PLOTTING ---
fig = make_subplots(specs=[[{"secondary_y": True}]])
# Credit Acceleration (Left Axis)
fig.add_trace(
go.Scatter(
x=accel_cad_credit.index,
y=accel_cad_credit,
name="Credit Acceleration (pp)",
line=dict(color="royalblue", width=2.5),
),
secondary_y=False,
)
# Real GDP YoY % (Right Axis)
fig.add_trace(
go.Scatter(
x=cad_real_gdp_yoy.index,
y=cad_real_gdp_yoy,
name="Real GDP YoY %",
line=dict(color="forestgreen", width=3),
),
secondary_y=True,
)
# --- 4. LAYOUT ---
fig.update_layout(
title="<b>CANADA: Credit Acceleration vs Real Growth</b><br><sup>The Private Sector Credit Cycle vs. Real GDP Output</sup>",
xaxis=dict(rangeslider=dict(visible=True), gridcolor="whitesmoke"),
yaxis=dict(title="<b>Credit Accel (pp)</b>", zeroline=True, zerolinecolor="black"),
yaxis2=dict(
title="<b>Real GDP YoY %</b>",
zeroline=True,
zerolinecolor="gray",
ticksuffix="%",
range=[-2, 5],
),
template="plotly_white",
hovermode="x unified",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
fig.show()import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from tulip.data.haver import HaverApiClient
# ==========================================================
# 1. CONFIGURATION - MODIFY THESE RANGES
# ==========================================================
# Time Range
CHART_START_DATE = "1995-01-01"
# Left Y-Axis: Mortgage Acceleration (Percentage Points)
ACCEL_Y_RANGE = [-10, 10]
# Right Y-Axis: Rate Change (Basis Points)
RATE_Y_RANGE = [-300, 300]
# ==========================================================
# --- 2. DATA RETRIEVAL ---
with HaverApiClient() as haver_client:
# NFC Mortgage Stock (Monthly, Mil C$)
mort_stock = haver_client.get_series("V1C15692@CANADA").ts.rename(
"Cad_Mortgage_Stock"
)
# Conventional 5yr Mortgage Rate (%)
mort_rate = haver_client.get_series("DP00001@CANADA").ts.rename("Cad_Mortgage_Rate")
# --- 3. CALCULATIONS ---
# A. Mortgage Acceleration
mort_yoy = (mort_stock.pct_change(12) * 100).dropna()
accel_mortgage = mort_yoy.diff(12).dropna()
# B. Mortgage Rate 6-Month Delta (BPS)
delta_mort_rate_6m = (mort_rate.diff(6) * 100).dropna()
# Align and Filter by Start Date
df_plot = pd.concat([accel_mortgage, delta_mort_rate_6m], axis=1).loc[CHART_START_DATE:]
# --- 4. PLOTTING ---
fig = make_subplots(specs=[[{"secondary_y": True}]])
# Acceleration (Left Axis)
fig.add_trace(
go.Scatter(
x=df_plot.index,
y=df_plot.iloc[:, 0],
name="Mortgage Acceleration (pp)",
line=dict(color="navy", width=2.5),
),
secondary_y=False,
)
# 6m Rate Delta (Right Axis)
fig.add_trace(
go.Scatter(
x=df_plot.index,
y=df_plot.iloc[:, 1],
name="6m Rate Delta (BPS)",
line=dict(color="crimson", width=1.5, dash="dot"),
),
secondary_y=True,
)
# --- 5. LAYOUT ---
fig.update_layout(
title="<b>CANADA: Mortgage Market Momentum</b><br><sup>Mortgage Impulse vs. 6-Month Change in 5Y Mortgage Rates</sup>",
xaxis=dict(
rangeslider=dict(visible=True),
gridcolor="whitesmoke",
range=[CHART_START_DATE, df_plot.index.max()], # Sets X-axis start
),
yaxis=dict(
title="<b>Mortgage Accel (pp)</b>",
zeroline=True,
zerolinecolor="black",
range=ACCEL_Y_RANGE, # Sets Left Y-axis range
),
yaxis2=dict(
title="<b>Rate Change (BPS)</b>",
zeroline=True,
zerolinecolor="gray",
range=RATE_Y_RANGE, # Sets Right Y-axis range
),
template="plotly_white",
hovermode="x unified",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
fig.show()import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from tulip.data.haver import HaverApiClient
# ==========================================================
# 1. CONFIGURATION
# ==========================================================
CHART_START_DATE = "2005-01-01"
MORT_YOY_RANGE = [0, 15]
RATE_LEVEL_RANGE = [8, 1]
# ==========================================================
# 2. DATA & CALCULATIONS
# ==========================================================
with HaverApiClient() as haver_client:
mort_stock = haver_client.get_series("V1C15692@CANADA").ts
mort_rate = haver_client.get_series("DP00001@CANADA").ts
# 1st Deriv: YoY % Change
mort_yoy = (mort_stock.pct_change(12) * 100).dropna()
# 12-Month Moving Average
mort_yoy_12mma = mort_yoy.rolling(window=12).mean().dropna()
df_plot = pd.concat([mort_yoy, mort_yoy_12mma, mort_rate], axis=1).loc[
CHART_START_DATE:
]
df_plot.columns = ["Raw", "MMA12", "Rate"]
# ==========================================================
# 3. PLOTTING
# ==========================================================
fig = make_subplots(specs=[[{"secondary_y": True}]])
# Mortgage Growth YoY (Secondary: Faint Raw Data)
fig.add_trace(
go.Scatter(
x=df_plot.index,
y=df_plot["Raw"],
name="Mortgage Growth (YoY)",
line=dict(color="rgba(0, 63, 92, 0.75)", width=1),
),
secondary_y=False,
)
# Mortgage Growth YoY (12MMA Trend)
fig.add_trace(
go.Scatter(
x=df_plot.index,
y=df_plot["MMA12"],
name="Mortgage Growth (12MMA)",
line=dict(color="#003f5c", width=3),
),
secondary_y=False,
)
# 5Y Mortgage Rate (Inverted Level)
fig.add_trace(
go.Scatter(
x=df_plot.index,
y=df_plot["Rate"],
name="5Y Mortgage Rate (Inverted)",
line=dict(color="#bc5090", width=2.5),
),
secondary_y=True,
)
# ==========================================================
# 4. INSTITUTIONAL LAYOUT
# ==========================================================
fig.update_layout(
title="<b>Canada: Mortgage Credit Growth vs. 5Y Mortgage Rates</b>",
template="plotly_white",
hovermode="x unified",
height=600,
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
xaxis=dict(showgrid=True, gridcolor="whitesmoke"),
)
# Axis 1: Growth
fig.update_yaxes(
title_text="YoY % Change",
range=MORT_YOY_RANGE,
gridcolor="whitesmoke",
secondary_y=False,
)
# Axis 2: Inverted Rates
fig.update_yaxes(
title_text="Interest Rate (%, Inverted)",
range=RATE_LEVEL_RANGE,
showgrid=False,
secondary_y=True,
)
fig.show()import pandas as pd
import plotly.graph_objects as go
from tulip.data.haver import HaverApiClient
# --- 1. DATA RETRIEVAL ---
with HaverApiClient() as haver_client:
cad_credit_liabs_hh = haver_client.get_series("V1C15625@CANADA").ts
cad_credit_liabs_nfc = haver_client.get_series("V1C15703@CANADA").ts
cad_ngdp_haver = haver_client.get_series("V6E05783@CANADA").ts
# --- 2. DATA PREP & NORMALIZATION ---
# Smooth and align GDP
cad_ngdp_smoothed = cad_ngdp_haver.rolling(4).mean().resample("ME").ffill()
# Calculate Annual Flows as % of GDP
# (Difference over 12 months / Current GDP) * 100
gdp_aligned = cad_ngdp_smoothed.reindex(cad_credit_liabs_hh.index).ffill()
hh_norm = (cad_credit_liabs_hh.diff(12).squeeze() / gdp_aligned) * 100
nfc_norm = (cad_credit_liabs_nfc.diff(12).squeeze() / gdp_aligned) * 100
# --- 3. PERIOD CALCULATIONS ---
def get_benchmarks(series):
latest_val = series.iloc[-1]
# 6 prior months average (excludes the current/latest month)
prior_6m_avg = series.iloc[-7:-1].mean()
# Same month last year
last_year_val = series.iloc[-13]
return latest_val, prior_6m_avg, last_year_val
hh_latest, hh_6m, hh_ly = get_benchmarks(hh_norm)
nfc_latest, nfc_6m, nfc_ly = get_benchmarks(nfc_norm)
# Define the categories and the values for the bar chart
categories = ["Households (HH)", "Corporations (NFC)"]
latest_month_str = hh_norm.index[-1].strftime("%B %Y")
# --- 4. PLOTTING ---
fig = go.Figure()
# Column 1: Last Print (Blue)
fig.add_trace(
go.Bar(
name=f"Last Print ({latest_month_str})",
x=categories,
y=[hh_latest, nfc_latest],
marker_color="rgb(31, 119, 180)",
)
)
# Column 2: 6 Month Prior Avg (Red)
fig.add_trace(
go.Bar(
name="6-Month Prior Avg",
x=categories,
y=[hh_6m, nfc_6m],
marker_color="rgb(214, 39, 40)",
)
)
# Column 3: Same Month Last Year (Green)
fig.add_trace(
go.Bar(
name="Same Month Last Year",
x=categories,
y=[hh_ly, nfc_ly],
marker_color="rgb(44, 160, 44)",
)
)
# --- 5. LAYOUT ---
fig.update_layout(
title=f"<b>Canada: Private Credit Flow Momentum</b><br><sup>Current Flow vs. Historical Benchmarks (% of GDP)</sup>",
yaxis=dict(title="Flow as % of GDP", ticksuffix="%"),
barmode="group",
template="plotly_white",
hovermode="y unified",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
fig.show()
# Quick print check
print(f"Data updated through: {latest_month_str}")import pandas as pd
import plotly.graph_objects as go
from tulip.data.haver import HaverApiClient
# --- 1. DATA RETRIEVAL ---
with HaverApiClient() as haver_client:
# Outstanding Debt Stocks (Quarterly, Mil C$)
nfc_stock = haver_client.get_series("CANZDDN@CANADA").ts.rename(
"NFC_Cad_Debt_Stock"
)
gg_stock = haver_client.get_series("CANZDDG@CANADA").ts.rename("GG_Cad_Debt_Stock")
hh_stock = haver_client.get_series("CANZDHT@CANADA").ts.rename("HH_Cad_Debt_Stock")
# Nominal GDP (Quarterly, Mil C$)
cad_ngdp = haver_client.get_series("V6E05783@CANADA").ts.rename("Cad_NGDP")
# --- 2. METHODOLOGY ---
# Smoothing GDP (4-quarter rolling mean)
cad_ngdp_smoothed = cad_ngdp.rolling(4).mean()
# Calculate Annual Flows (diff 4 quarters)
nfc_flow = nfc_stock.diff(4)
gg_flow = gg_stock.diff(4)
hh_flow = hh_stock.diff(4)
# Normalize by Smoothed GDP (%)
nfc_flow_norm = (nfc_flow / cad_ngdp_smoothed) * 100
gg_flow_norm = (gg_flow / cad_ngdp_smoothed) * 100
hh_flow_norm = (hh_flow / cad_ngdp_smoothed) * 100
# Total Flow Calculation
total_flow_norm = nfc_flow_norm + gg_flow_norm + hh_flow_norm
# --- 3. PLOTTING ---
fig = go.Figure()
# NFC Flow (Blue Bars)
fig.add_trace(
go.Bar(
x=nfc_flow_norm.index,
y=nfc_flow_norm,
name="Non-Financial Corps (NFC)",
marker_color="royalblue",
)
)
# GG Flow (Red Bars)
fig.add_trace(
go.Bar(
x=gg_flow_norm.index,
y=gg_flow_norm,
name="General Government (GG)",
marker_color="crimson",
)
)
# HH Flow (Green Bars)
fig.add_trace(
go.Bar(
x=hh_flow_norm.index,
y=hh_flow_norm,
name="Households (HH)",
marker_color="forestgreen",
)
)
# Total Flow (Black Line)
fig.add_trace(
go.Scatter(
x=total_flow_norm.index,
y=total_flow_norm,
name="Total Credit Flow",
line=dict(color="black", width=3, dash="solid"),
)
)
# --- 4. LAYOUT ---
fig.update_layout(
title="<b>CANADA: Annual Credit Flows by Sector</b><br><sup>Annual Change in Debt Stock as % of Smoothed NGDP</sup>",
xaxis=dict(rangeslider=dict(visible=True), type="date"),
yaxis=dict(title="Flow as % of GDP", ticksuffix="%"),
barmode="relative", # This allows bars to stack even if some are negative (deleveraging)
template="plotly_white",
hovermode="x unified",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
fig.show()# --- 4. UPDATED CLEAN LAYOUT ---
fig.update_layout(
# 1. Increase the top margin (t=100) to create clear space for titles
margin=dict(t=120, l=50, r=50, b=50),
# 2. Position the title higher within that new margin
title=dict(
text="<b>CANADA: Annual Credit Creation by Sector</b><br><sup>Flows as % of GDP</sup>",
y=0.95, # Higher value moves it toward the top edge
x=0.5, # Centers it
xanchor="center",
yanchor="top",
font=dict(size=18),
),
# 3. Adjust Legend to not collide with subtitles
legend=dict(
orientation="h",
yanchor="bottom",
y=1.02, # Sits right on the top border of the plot area
xanchor="center",
x=0.5,
),
xaxis=dict(rangeslider=dict(visible=True), type="date"),
yaxis=dict(title="Flow as % of GDP", ticksuffix="%"),
barmode="relative",
template="plotly_white",
hovermode="x unified",
)
fig.show()import pandas as pd
import plotly.graph_objects as go
from tulip.data.haver import HaverApiClient
# --- 1. DATA RETRIEVAL (Quarterly) ---
with HaverApiClient() as haver_client:
# Outstanding Debt Stocks (Mil C$)
nfc_stock = haver_client.get_series("CANZDDN@CANADA").ts.rename("NFC")
gg_stock = haver_client.get_series("CANZDDG@CANADA").ts.rename("GG")
hh_stock = haver_client.get_series("CANZDHT@CANADA").ts.rename("HH")
# Nominal GDP (Mil C$)
cad_ngdp = haver_client.get_series("V6E05783@CANADA").ts.rename("NGDP")
# --- 2. CALCULATIONS ---
# GDP Smoothed (4-quarter rolling mean)
cad_ngdp_smoothed = cad_ngdp.rolling(4).mean()
# Calculate Annual Flows normalized by GDP (%)
def get_norm_flow(stock_series, gdp_series):
flow = stock_series.diff(4) # Annual flow (change over 4 quarters)
return (flow / gdp_series) * 100
nfc_flow_norm = get_norm_flow(nfc_stock, cad_ngdp_smoothed).dropna()
gg_flow_norm = get_norm_flow(gg_stock, cad_ngdp_smoothed).dropna()
hh_flow_norm = get_norm_flow(hh_stock, cad_ngdp_smoothed).dropna()
# --- 3. EXTRACTING BENCHMARKS ---
def extract_benchmarks(series):
return [
series.iloc[-1], # Last Print (e.g. Q3 2025)
series.iloc[-2], # Previous Quarter (e.g. Q2 2025)
series.iloc[-5], # Same Quarter Last Year (e.g. Q3 2024)
]
nfc_data = extract_benchmarks(nfc_flow_norm)
hh_data = extract_benchmarks(hh_flow_norm)
gg_data = extract_benchmarks(gg_flow_norm)
# Grouping data for the bar chart
sectors = ["Corps (NFC)", "Households (HH)", "Govt (GG)"]
last_print = [nfc_data[0], hh_data[0], gg_data[0]]
prev_quarter = [nfc_data[1], hh_data[1], gg_data[1]]
year_ago = [nfc_data[2], hh_data[2], gg_data[2]]
# Get date labels for the legend
def get_q_label(idx):
q = (idx.month - 1) // 3 + 1
return f"Q{q} {idx.year}"
date_last = get_q_label(nfc_flow_norm.index[-1])
date_prev = get_q_label(nfc_flow_norm.index[-2])
date_ly = get_q_label(nfc_flow_norm.index[-5])
# --- 4. PLOTTING ---
fig = go.Figure()
# Column 1: Last Print (Blue)
fig.add_trace(
go.Bar(
name=f"Last Print ({date_last})",
x=sectors,
y=last_print,
marker_color="rgb(31, 119, 180)",
)
)
# Column 2: Previous Quarter (Red)
fig.add_trace(
go.Bar(
name=f"Prev Quarter ({date_prev})",
x=sectors,
y=prev_quarter,
marker_color="rgb(214, 39, 40)",
)
)
# Column 3: Same Quarter Last Year (Green)
fig.add_trace(
go.Bar(
name=f"Year Ago ({date_ly})",
x=sectors,
y=year_ago,
marker_color="rgb(44, 160, 44)",
)
)
# --- 5. CLEAN LAYOUT ---
fig.update_layout(
margin=dict(t=120, l=60, r=40, b=60),
title=dict(
text="<b>CANADA: Sector Credit Flow Comparison</b><br><sup>Annual Flow as % of GDP </sup>",
y=0.95,
x=0.5,
xanchor="center",
yanchor="top",
font=dict(size=18),
),
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="center", x=0.5),
yaxis=dict(
title="<b>Flow as % of GDP</b>",
ticksuffix="%",
zeroline=True,
zerolinecolor="black",
),
barmode="group",
template="plotly_white",
hovermode="y unified",
)
fig.show()import pandas as pd
import plotly.graph_objects as go
from tulip.data.haver import HaverApiClient
# --- 1. DATA RETRIEVAL ---
with HaverApiClient() as haver_client:
# Monthly Household Credit Stocks (Mil C$)
mort_stock = haver_client.get_series("V1C15620@CANADA").ts.rename(
"HH_Mortgage_Stock"
)
cons_stock = haver_client.get_series("V1C15611@CANADA").ts.rename(
"HH_Cons_Credit_Stock"
)
# Quarterly Nominal GDP (Mil C$)
cad_ngdp = haver_client.get_series("V6E05783@CANADA").ts.rename("GDP_Cad")
# --- 2. METHODOLOGY & SMOOTHING ---
# A. Smooth GDP with a 4-quarter rolling average
gdp_rolling = cad_ngdp.rolling(window=4).mean()
# B. Bridge the gap: Match Quarterly GDP to Monthly Credit Index
# We use ffill() so October 2025 uses the last available GDP print (Q3)
gdp_monthly = gdp_rolling.resample("ME").last().reindex(mort_stock.index).ffill()
# C. Calculate Annual Flows (12-month diff) and Normalize (% of GDP)
mort_flow_norm = (mort_stock.diff(12) / gdp_monthly) * 100
cons_flow_norm = (cons_stock.diff(12) / gdp_monthly) * 100
# D. Total HH Credit Flow (The Sum)
total_hh_flow = mort_flow_norm + cons_flow_norm
# --- 3. PLOTTING ---
fig = go.Figure()
# Component 1: Mortgages (Stacked Bar)
fig.add_trace(
go.Bar(
x=mort_flow_norm.index,
y=mort_flow_norm,
name="Mortgage Credit Flow",
marker_color="royalblue",
opacity=0.8,
)
)
# Component 2: Consumer Credit (Stacked Bar)
fig.add_trace(
go.Bar(
x=cons_flow_norm.index,
y=cons_flow_norm,
name="Consumer Credit Flow",
marker_color="orange",
opacity=0.8,
)
)
# Total HH Flow (Black Line)
fig.add_trace(
go.Scatter(
x=total_hh_flow.index,
y=total_hh_flow,
name="Total HH Credit Creation",
line=dict(color="black", width=3),
)
)
# --- 4. CLEAN LAYOUT ---
fig.update_layout(
margin=dict(t=120, l=60, r=40, b=60),
title=dict(
text="<b>CANADA: Household Credit Creation Breakdown</b><br><sup>Annual Flow by Category as % of GDP</sup>",
y=0.95,
x=0.5,
xanchor="center",
yanchor="top",
font=dict(size=18),
),
barmode="stack", # This stacks the Mortgage and Consumer bars
xaxis=dict(rangeslider=dict(visible=True), gridcolor="whitesmoke"),
yaxis=dict(
title="<b>% of GDP</b>", ticksuffix="%", zeroline=True, zerolinecolor="black"
),
template="plotly_white",
hovermode="x unified",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="center", x=0.5),
)
fig.show()import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from tulip.data.haver import HaverApiClient
# --- 1. DATA RETRIEVAL ---
with HaverApiClient() as haver_client:
# NFC Credit Stock (Monthly, Mil C$)
nfc_stock = haver_client.get_series("V1C15703@CANADA").ts.rename(
"Cad_NFC_Credit_Stock"
)
# Canada: Prime Business Loan Rate (%)
cad_corp_loan_rate = haver_client.get_series("V122495@CANADA").ts.rename(
"cad_corp_loan_rate"
)
# --- 2. CALCULATIONS ---
# Create YoY percentage change for NFC Stock
nfc_stock_yoy = (
nfc_stock.pct_change(12).dropna() * 100
) # Multiplied by 100 for percentage scale
# --- 3. PLOTTING ---
# CONFIGURATION: Play with these values to change the view
date_start = "1995-01-01"
date_end = "2024-12-31"
y1_range = [2, 9] # Range for Loan Rate (%)
y2_range = [-5, 20] # Range for Credit Growth (YoY %)
fig = make_subplots(specs=[[{"secondary_y": True}]])
# Add Prime Business Loan Rate (Left Axis)
fig.add_trace(
go.Scatter(
x=cad_corp_loan_rate.index,
y=cad_corp_loan_rate,
name="Prime Business Loan Rate (%)",
line=dict(color="rgba(0, 63, 92, 1.0)", width=2), # Made opaque for visibility
),
secondary_y=False,
)
# Add NFC Credit Stock YoY (Right Axis)
fig.add_trace(
go.Scatter(
x=nfc_stock_yoy.index,
y=nfc_stock_yoy,
name="NFC Credit Stock (YoY %)",
line=dict(color="rgba(239, 86, 117, 1.0)", width=2, dash="dot"),
),
secondary_y=True,
)
# Layout and Axis Management
fig.update_layout(
title_text="Canada: Corp Loan Rate vs. NFC Credit Growth",
template="plotly_white",
hovermode="x unified",
xaxis=dict(range=[date_start, date_end]), # Date Range Control
)
# Set y-axes titles and ranges
fig.update_yaxes(title_text="<b>Loan Rate</b> (%)", range=y1_range, secondary_y=False)
fig.update_yaxes(
title_text="<b>NFC Stock</b> (YoY %)", range=y2_range, secondary_y=True
)
fig.show()import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from tulip.data.haver import HaverApiClient
# --- 1. DATA RETRIEVAL ---
with HaverApiClient() as haver_client:
# NFC Credit Stock (Monthly, Mil C$)
nfc_stock = haver_client.get_series("V1C15703@CANADA").ts.rename(
"Cad_NFC_Credit_Stock"
)
# Canada: Prime Business Loan Rate (%)
cad_corp_loan_rate = haver_client.get_series("V122495@CANADA").ts.rename(
"cad_corp_loan_rate"
)
# --- 2. CALCULATIONS ---
# Create YoY percentage change
nfc_stock_yoy = nfc_stock.pct_change(12).dropna() * 100
# 3rd) Add 12MMA for NFC
nfc_stock_yoy_12mma = nfc_stock_yoy.rolling(window=12).mean()
# --- 3. PLOTTING ---
# CONFIGURATION
date_start = "1995-01-01" # 2nd) Start date 1995
date_end = pd.Timestamp.now().strftime("%Y-%m-%d")
# 1st) Loan rate caps: 2% to 9% (Note: we invert these in update_yaxes)
y1_range = [9, 2]
y2_range = [-5, 15] # Adjusted to accommodate NFC peaks
fig = make_subplots(specs=[[{"secondary_y": True}]])
# Add Prime Business Loan Rate (Left Axis - Inverted)
fig.add_trace(
go.Scatter(
x=cad_corp_loan_rate.index,
y=cad_corp_loan_rate,
name="Prime Business Loan Rate (Inverted)",
line=dict(color="rgba(0, 63, 92, 1.0)", width=2.5),
),
secondary_y=False,
)
# 4th) NFC Stock YoY in background (Lighter color)
fig.add_trace(
go.Scatter(
x=nfc_stock_yoy.index,
y=nfc_stock_yoy,
name="NFC Credit Stock YoY",
line=dict(color="rgba(239, 86, 117, 0.25)", width=1.5), # 25% opacity
),
secondary_y=True,
)
# 3rd) NFC Stock 12MMA (Bold / Visible)
fig.add_trace(
go.Scatter(
x=nfc_stock_yoy_12mma.index,
y=nfc_stock_yoy_12mma,
name="NFC Credit Stock YoY (12MMA)",
line=dict(color="rgba(239, 86, 117, 1.0)", width=3), # Solid 100% opacity
),
secondary_y=True,
)
# Layout and Axis Management
fig.update_layout(
title_text="Canada: Prime Loan Rate vs. NFC Credit Growth",
template="plotly_white",
hovermode="x unified",
xaxis=dict(range=[date_start, date_end]),
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
# 1st) Invert range for loan rate
fig.update_yaxes(
title_text="<b>Loan Rate</b> (%, Inverted)",
range=y1_range,
autorange=False,
secondary_y=False,
)
fig.update_yaxes(
title_text="<b>NFC Stock Growth</b> (YoY %)", range=y2_range, secondary_y=True
)
fig.show()import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from tulip.data.haver import HaverApiClient
# --- 1. DATA RETRIEVAL ---
with HaverApiClient() as haver_client:
prime = haver_client.get_series("V122495@CANADA").ts.rename("Prime_Rate")
policy = haver_client.get_series("B156RD@CANADA").ts.rename("Policy_Rate")
# --- 2. CALCULATIONS ---
df = pd.concat([prime, policy], axis=1).dropna()
df = df.loc["1995-01-01":"2026-01-31"]
# Calculate Spread in percentage points
df["Spread"] = df["Prime_Rate"] - df["Policy_Rate"]
hist_avg = df["Spread"].mean()
# --- 3. PLOTTING ---
fig = make_subplots(
rows=2,
cols=1,
shared_xaxes=True,
vertical_spacing=0.07,
row_heights=[0.65, 0.35],
subplot_titles=("Rates: Prime Business vs. Policy", "Spread (Prime minus Policy)"),
)
# TOP PANEL: Prime and Policy Lines
fig.add_trace(
go.Scatter(
x=df.index,
y=df["Prime_Rate"],
name="Prime Business Rate",
line=dict(color="#003f5c", width=2.5),
),
row=1,
col=1,
)
fig.add_trace(
go.Scatter(
x=df.index,
y=df["Policy_Rate"],
name="Policy Rate",
line=dict(color="#ffa600", width=2, dash="dot"),
),
row=1,
col=1,
)
# BOTTOM PANEL: Spread Line Chart
fig.add_trace(
go.Scatter(
x=df.index,
y=df["Spread"],
name="Spread (%)",
line=dict(color="#ef5675", width=2),
fill="tozeroy", # Optional: adds a light fill to the baseline
fillcolor="rgba(239, 86, 117, 0.1)",
),
row=2,
col=1,
)
# Historical Average Line
fig.add_hline(
y=hist_avg,
line_dash="dash",
line_color="#333",
row=2,
col=1,
annotation_text=f"Hist. Avg: {hist_avg:.2f}%",
annotation_position="bottom right",
)
# --- 4. LAYOUT & LABELLING ---
fig.update_layout(
height=800,
title_text="Canada: Interest Rate Transmission Analysis",
template="plotly_white",
hovermode="x unified",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
# Update Y-Axes
fig.update_yaxes(title_text="Rate (%)", row=1, col=1)
# Set floor to 1% as requested
fig.update_yaxes(
title_text="Spread (%)", range=[1.0, df["Spread"].max() + 0.2], row=2, col=1
)
fig.show()import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from tulip.data.haver import HaverApiClient
# --- 1. DATA RETRIEVAL ---
with HaverApiClient() as haver_client:
# Canada: Prime Business Loan Rate
prime = haver_client.get_series("V122495@CANADA").ts.rename("Prime_Rate")
# Canada: 10 Year Treasury Bond Mid Yield
bond_10y = haver_client.get_series("R156MAE@CANADA").ts.rename("Yield_10Y")
# --- 2. CALCULATIONS ---
df = pd.concat([prime, bond_10y], axis=1).dropna()
# Filter for the requested period: July 2006 to January 2026
df = df.loc["2006-07-01":]
# Calculate Spread in percentage points
df["Spread"] = df["Prime_Rate"] - df["Yield_10Y"]
hist_avg = df["Spread"].mean()
# --- 3. PLOTTING (Technical Analysis Style) ---
fig = make_subplots(
rows=2,
cols=1,
shared_xaxes=True,
vertical_spacing=0.07,
row_heights=[0.65, 0.35],
subplot_titles=(
"Rate Levels: Prime Rate vs. 10Y Yield",
"Transmission Spread (Prime minus 10Y)",
),
)
# TOP PANEL: Prime Rate and 10Y Bond Yield Lines
fig.add_trace(
go.Scatter(
x=df.index,
y=df["Prime_Rate"],
name="Prime Rate",
line=dict(color="#003f5c", width=2.5),
),
row=1,
col=1,
)
fig.add_trace(
go.Scatter(
x=df.index,
y=df["Yield_10Y"],
name="10Y Bond Yield",
line=dict(color="#bc5090", width=2, dash="dot"),
),
row=1,
col=1,
)
# BOTTOM PANEL: Spread Line Chart
fig.add_trace(
go.Scatter(
x=df.index,
y=df["Spread"],
name="Spread (%)",
line=dict(color="#ffa600", width=2),
fill="tozeroy",
fillcolor="rgba(255, 166, 0, 0.1)",
),
row=2,
col=1,
)
# Historical Average Line for the Spread
fig.add_hline(
y=hist_avg,
line_dash="dash",
line_color="#333",
row=2,
col=1,
annotation_text=f"Hist. Avg: {hist_avg:.2f}%",
annotation_position="bottom right",
)
# --- 4. LAYOUT & LABELLING ---
fig.update_layout(
height=800,
title_text="Canada: Interest Rate Transmission Analysis - Prime vs. 10Y ",
template="plotly_white",
hovermode="x unified",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
# Update Y-Axes
fig.update_yaxes(title_text="Rate (%)", row=1, col=1)
# Set floor
fig.update_yaxes(
title_text="Spread (%)", range=[-0.5, df["Spread"].max() + 0.5], row=2, col=1
)
fig.show()import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from tulip.data.haver import HaverApiClient
# --- 1. DATA RETRIEVAL ---
with HaverApiClient() as haver_client:
# 5Y Conventional Mortgage Rate
mortgage_5y = haver_client.get_series("DP00001@CANADA").ts.rename("Mortgage_5Y")
# Overnight Target Rate
policy = haver_client.get_series("B156RD@CANADA").ts.rename("Policy_Rate")
# --- 2. CALCULATIONS ---
df1 = pd.concat([mortgage_5y, policy], axis=1).dropna()
df1 = df1.loc["2006-07-01":]
df1["Spread"] = df1["Mortgage_5Y"] - df1["Policy_Rate"]
hist_avg1 = df1["Spread"].mean()
last_date1 = df1.index[-1].strftime("%B %Y")
# --- 3. PLOTTING: MORTGAGE VS POLICY ---
fig1 = make_subplots(
rows=2,
cols=1,
shared_xaxes=True,
vertical_spacing=0.07,
row_heights=[0.65, 0.35],
subplot_titles=("Mortgage Rate vs. Policy Rate", "Spread (Mortgage - Policy)"),
)
fig1.add_trace(
go.Scatter(
x=df1.index,
y=df1["Mortgage_5Y"],
name="5Y Mortgage",
line=dict(color="#003f5c", width=2.5),
),
row=1,
col=1,
)
fig1.add_trace(
go.Scatter(
x=df1.index,
y=df1["Policy_Rate"],
name="Policy Rate",
line=dict(color="#ffa600", width=2, dash="dot"),
),
row=1,
col=1,
)
fig1.add_trace(
go.Scatter(
x=df1.index,
y=df1["Spread"],
name="Spread (%)",
line=dict(color="#ef5675", width=2),
fill="tozeroy",
fillcolor="rgba(239, 86, 117, 0.1)",
),
row=2,
col=1,
)
fig1.add_hline(
y=hist_avg1,
line_dash="dash",
line_color="#333",
row=2,
col=1,
annotation_text=f"Hist. Avg: {hist_avg1:.2f}%",
annotation_position="bottom right",
)
fig1.update_layout(
height=800,
title_text=f"Mortgage vs. Policy (Through {last_date1})",
template="plotly_white",
hovermode="x unified",
)
fig1.update_yaxes(title_text="Rate (%)", row=1, col=1)
fig1.update_yaxes(
title_text="Spread (%)", range=[1.0, df1["Spread"].max() + 0.5], row=2, col=1
)
fig1.show()# --- 1. DATA RETRIEVAL ---
with HaverApiClient() as haver_client:
mortgage_5y = haver_client.get_series("DP00001@CANADA").ts.rename("Mortgage_5Y")
bond_10y = haver_client.get_series("R156MAE@CANADA").ts.rename("Yield_10Y")
# --- 2. CALCULATIONS ---
df2 = pd.concat([mortgage_5y, bond_10y], axis=1).dropna()
df2 = df2.loc["2006-07-01":]
df2["Spread"] = df2["Mortgage_5Y"] - df2["Yield_10Y"]
hist_avg2 = df2["Spread"].mean()
last_date2 = df2.index[-1].strftime("%B %Y")
# --- 3. PLOTTING: MORTGAGE VS 10Y ---
fig2 = make_subplots(
rows=2,
cols=1,
shared_xaxes=True,
vertical_spacing=0.07,
row_heights=[0.65, 0.35],
subplot_titles=("Mortgage Rate vs. 10Y Bond Yield", "Spread (Mortgage - 10Y)"),
)
fig2.add_trace(
go.Scatter(
x=df2.index,
y=df2["Mortgage_5Y"],
name="5Y Mortgage",
line=dict(color="#003f5c", width=2.5),
),
row=1,
col=1,
)
fig2.add_trace(
go.Scatter(
x=df2.index,
y=df2["Yield_10Y"],
name="10Y Yield",
line=dict(color="#bc5090", width=2, dash="dot"),
),
row=1,
col=1,
)
fig2.add_trace(
go.Scatter(
x=df2.index,
y=df2["Spread"],
name="Spread (%)",
line=dict(color="#ffa600", width=2),
fill="tozeroy",
fillcolor="rgba(255, 166, 0, 0.1)",
),
row=2,
col=1,
)
fig2.add_hline(
y=hist_avg2,
line_dash="dash",
line_color="#333",
row=2,
col=1,
annotation_text=f"Hist. Avg: {hist_avg2:.2f}%",
annotation_position="bottom right",
)
fig2.update_layout(
height=800,
title_text=f"Mortgage vs. 10Y (Through {last_date2})",
template="plotly_white",
hovermode="x unified",
)
fig2.update_yaxes(title_text="Rate (%)", row=1, col=1)
fig2.update_yaxes(
title_text="Spread (%)", range=[1.0, df2["Spread"].max() + 0.5], row=2, col=1
)
fig2.show()