import pandas as pd
from IPython.display import Markdown, display
from tulip.core.collection import TulipCollection
from tulip.core import TulipLibrary, merge_collections
from tulip.data.haver import HaverClient as hc
from tulip.data.gs import GSClient as gs
from tulip.data.haver import (
get_series as get_series_from_haver,
get_collection as get_collection_from_haver,
)
from tulip.plots import (
plot_line,
plot_lines,
plot_area,
plot_bar,
switch_trace_to_secondary_axis,
)
from tulip.genai import iris
from tulip.analysis.country_related.analytics import summarize_gs_eco_fct
from tulip_mania.notebook_related import notebook_updated
import pycountry
Mexico¶
# ========================================
# PARAMETERS & DATA FETCHING
# ========================================
# Country identifiers
ISO_2 = "MX"
haver_code = 273
ctry_name = "Mexico"
goldman_ctry_id = ISO_2
pyctry = pycountry.countries.get(alpha_2=ISO_2)
# Market data library
market_data = TulipLibrary.market_data()
# ========================================
# DATA FETCHING - All data retrieval consolidated here
# ========================================
# Nominal GDP (for ratios)
nom_gdp = get_series_from_haver(f"S{haver_code}NGDP@EMERGE") # Millions of New Pesos
nom_gdp_ts = nom_gdp.ts.rolling(4).sum().rename(f"MEX_NGDP")
QoQ_nom_gdp = (
nom_gdp.df.pct_change()
.mul(4)
.rename(columns={nom_gdp.id.split("@")[0].lower(): "QoQ Annualized Nominal GDP"})
)
YoY_nom_gdp = nom_gdp.df.pct_change(4).rename(
columns={nom_gdp.id.split("@")[0].lower(): "YoY Annualized Nominal GDP"}
)
# IGAE Nowcasts
igae_nowcasts_dict = {
"IGAE Nowcast Low": "S273GVLI@EMERGELA",
"IGAE Nowcast Center": "S273GVFI@EMERGELA",
"IGAE Nowcast High": "S273GVHI@EMERGELA",
}
# GDP Detail
gdp_detail_dict = {
"Real GDP": f"S{haver_code}NGPC@EMERGE",
"Fixed Investment": f"S{haver_code}NFC@EMERGE",
"Private Consumption": f"S{haver_code}NCPC@EMERGE",
"Public Consumption": f"S{haver_code}NCGC@EMERGE",
"Exports": f"S{haver_code}NXC@EMERGE",
"Imports": f"S{haver_code}NMC@EMERGE",
}
# Surveys
surveys_dict = {
"Consumer Confidence Index": "N273VPCC@EMERGELA",
"Producer Confidence Index": "N273VCP@EMERGELA",
"Global Trend Business Opinion Indicator": "S273VBT@EMERGELA",
"Global Indicator of Business Opinion of Confidence": "S273VBGC@EMERGELA",
"PMI": "S273VPMI@EMERGELA",
"News Based Economic Policy Uncertainty Index": "N273VIUC@EMERGELA",
}
# Prices
prices_dict = {
"CPI YoY": f"H273PJ@EMERGELA",
"Core CPI YoY": f"H273PJXQ@EMERGELA",
"Non-Core CPI YoY": f"H273PJ3@EMERGELA",
"PPI YoY ex-Petroleum": f"N273PPFX@EMERGELA",
"Target Inflation": f"A{haver_code}VUCP@EMERGELA",
}
# Real Wages
real_wages_dict = {
"Global Index of Avg Real Remuneration All Sectors": "S273E7@EMERGELA",
}
# Output Gap
output_gap_dict = {
"Economic Output Gap (IGAE)": "S273GAPM@EMERGELA",
"Economic Output Gap ex-Services (IGAE)": "S273GAPX@EMERGELA",
"GDP Gap": "S273NGAP@EMERGELA",
"GDP Gap ex-Services": "S273XGAP@EMERGELA",
}
# Employment
employment_dict = {
"Unemployment Rate": f"H{haver_code}ELUR@EMERGELA",
"IGPOSE (NFP Equivalent)": "S273ELEI@EMERGELA",
"Labor Force Participation": f"N{haver_code}ELPR@EMERGE",
"Underemployment Rate": f"H273ELDR@EMERGELA",
"Manufacturing Man-hours": "S273E0M@EMERGELA",
"Persons employed in Construction": "N273HET@EMERGELA",
}
# Trade
trade_dict = {
"Current Account Balance % GDP": f"H{haver_code}BCPG@EMERGE",
"Worker Remittances": f"N273BW@EMERGELA",
}
# Credit Levels
credit_levels_dict = {
"Domestic Currency Credit": "N273FCB@EMERGELA",
"Domestic Currency Credit Non-fin Corp": "N273FCBV@EMERGELA",
"Domestic Currency Credit Households": "N273FCBH@EMERGELA",
"Foreign Currency Credit": "I273FCB@EMERGELA",
"Foreign Currency Credit Non-fin Corp": "I273FCBV@EMERGELA",
"Foreign Currency Credit Households": "I273FCBH@EMERGELA",
}
# Credit YoY Change
credit_yoy_dict = {
"Domestic Currency Credit": "yryr%(N273FCB@EMERGELA)",
"Domestic Currency Credit Non-fin Corp": "yryr%(N273FCBV@EMERGELA)",
"Domestic Currency Credit Households": "yryr%(N273FCBH@EMERGELA)",
"Foreign Currency Credit": "yryr%(I273FCB@EMERGELA)",
"Foreign Currency Credit Non-fin Corp": "yryr%(I273FCBV@EMERGELA)",
"Foreign Currency Credit Households": "yryr%(I273FCBH@EMERGELA)",
}
# Interest Rate Differential
int_rate_dict = {
"Banxico Policy Rate": "N273RTAR@EMERGE",
"FFR": "FFEDTAR@USECON",
}
# Government Finance
govt_finance_dict = {
"Public Debt": "C273GDT@EMERGELA",
"Public Debt W/Central Bank": "C273GCT@EMERGELA",
}# ========================================
# MAIN SUMMARY - Data Collection Instantiation
# ========================================
# Instantiate all collections here for the main summary
# Growth collections
igae_nowcasts = get_collection_from_haver(igae_nowcasts_dict)
gdp_detail = get_collection_from_haver(gdp_detail_dict).apply("yoy")
gdp_detail[-1].good_is = -1
surveys_collection = get_collection_from_haver(surveys_dict)
surveys_collection[-1].good_is = -1
# Inflation collections
prices_collection = get_collection_from_haver(prices_dict).apply("yoy")
prices_collection.good_is = -1
real_wages_collection = get_collection_from_haver(["S273E7@EMERGELA"]).apply("yoy")
# Employment and slack collections
employment_collection = get_collection_from_haver(employment_dict)
output_gap_collection = get_collection_from_haver(output_gap_dict)
# Financial collections
credit_levels_collection = get_collection_from_haver(credit_levels_dict)
int_rate_collection = get_collection_from_haver(int_rate_dict)
# Fiscal collections
govt_finance_collection = get_collection_from_haver(govt_finance_dict)
govt_finance_collection[-1].good_is = -1
# ========================================
# MERGE INTO THEMATIC COLLECTIONS
# ========================================
growth_collection = merge_collections([igae_nowcasts, gdp_detail, surveys_collection])
inflation_collection = merge_collections([prices_collection, real_wages_collection])
unemployment_and_slack_collection = merge_collections(
[employment_collection, output_gap_collection]
)
fiscal_collection = govt_finance_collection
financial_collection = merge_collections(
[credit_levels_collection, int_rate_collection]
)
# Main country collection
country_collection = merge_collections(
[
growth_collection,
inflation_collection,
unemployment_and_slack_collection,
financial_collection,
fiscal_collection,
]
)
print(f"✓ All data instantiated successfully")
print(f"✓ Main collection contains {len(country_collection)} key economic indicators")# ========================================
# AI SUMMARY OF MEXICAN ECONOMY
# ========================================
overall_summary = iris.summarize(
country_collection,
"Highlight recent developments in the Mexican economy that could matter for markets. "
"Focus on growth momentum, inflation trajectory, labor market conditions, and policy outlook.",
model="claude-sonnet-4-5-20250929",
)
overall_summary.html()1. Growth: Activity & Output¶
1.1 Economic Forecasts (Consensus)¶
# Goldman Sachs Economic Forecasts
try:
gs_eco_fct = gs.get_eco_forecast(geographyId=goldman_ctry_id)
gs_summary = summarize_gs_eco_fct(gs_eco_fct)
gs_summary = gs_summary[~gs_summary.index.str.contains("ngdp")].to_frame().T
display(
gs_summary.style.set_caption(
f"Goldman Sachs {ctry_name} Economic Forecasts"
).format(precision=2)
)
except Exception as e:
display(Markdown(f"*Goldman Sachs forecasts not available for {ctry_name}: {e}*"))1.2 Growth Nowcasts & Real-Time Indicators¶
# Goldman Sachs Current Activity Indicator (CAI)
try:
cai_series_soft_vs_hard = gs.get_CAI_series(
geographyId=goldman_ctry_id,
metricName=[
"CAI_HEADLINE",
"CAI_CONTRIBUTION_TYPE_HARD",
"CAI_CONTRIBUTION_TYPE_SOFT",
],
startDate="1980-01-01",
)
cai_series_soft_vs_hard = cai_series_soft_vs_hard.set_index(
"metricName", append=True
)["metricValue"].unstack("metricName")
cai_series_soft_vs_hard.columns = ["Hard", "Soft", "Headline"]
cai_plot = plot_lines(
cai_series_soft_vs_hard,
show_0=True,
title=f"<b>{ctry_name} Current Activity Indicator</b> Updated: {pd.Timestamp.today().strftime('%Y-%m-%d')}",
years_limit=4,
)
cai_plot.show()
except Exception as e:
display(Markdown(f"*CAI not available for {ctry_name}: {e}*"))Local Stats Agency Nowcast (IGAE)¶
Link: Indicador Global de la Actividad Económica (IGAE). Año base 2018
# IGAE Nowcasts (already instantiated above)
igae_nowcasts.dashboard.table()plot_lines(
igae_nowcasts.df.rename(
columns={x.split("@")[0].lower(): v for x, v in igae_nowcasts.titles.items()}
),
show_0=True,
title=f"<b>{ctry_name} Est Economic Activity</b> Updated: {pd.Timestamp.today().strftime('%Y-%m-%d')}",
years_limit=4,
source="IGAE",
)1.3 GDP Growth & Components¶
real_gdp_yoy = get_series_from_haver(f"S{haver_code}NGPC@EMERGE")
real_gdp_yoy.ts = real_gdp_yoy.ts.pct_change(4)
real_gdp_yoy.plot(show_0=True, tick_format="0.0%")GDP Detail¶
# GDP Detail (already instantiated above)
gdp_detail.dashboard.table()gdp_detail.dashboard.plots()1.4 Business Activity Surveys¶
# Surveys (already instantiated above)
surveys_collection.dashboard.table()surveys_collection.dashboard.plots(hlines=50)1.5 Retail Sales & Consumer Activity¶
1.6 Industrial Production & Manufacturing¶
1.7 Growth Summary (AI-Generated)¶
2. Prices: Inflation & Wages¶
2.1 Headline & Core Inflation¶
# Prices (already instantiated above)
prices_collection.dashboard.table()cpi_df = prices_collection.df
cpi_df.columns = prices_collection.titles.values()
plot_lines(
cpi_df,
years_limit=10,
title="<b>Mexico Price Indices</b>",
show_0=True,
top_margin=140,
tick_suffix="%",
logo=False,
)2.2 Inflation Expectations & Surveys¶
2.3 Inflation Nowcasts & Forecasts¶
2.4 Wage Growth & Labor Costs¶
# Real Wages (already instantiated above)
real_wages_collection.dashboard.plots(
show_0=True, logo=False, tick_suffix="%", years_limit=10
)2.5 Inflation Components (Goods vs Services)¶
3. Slack: Output Gap & Capacity Utilization¶
3.1 Output Gap Estimates¶
# Output Gap (already instantiated above)
output_gap_collection.dashboard.table()output_gap_collection.dashboard.plots(show_0=True)3.2 Capacity Utilization¶
4. Employment: Labor Market Conditions¶
4.1 Unemployment Rate & Labor Force¶
# Employment (already instantiated above)
employment_collection.dashboard.table()employment_collection.dashboard.plots(years_limit=4, show_0=True)4.2 Employment Growth & Payrolls¶
4.3 Jobless Claims & Leading Indicators¶
4.4 Job Openings & Labor Turnover¶
4.5 Alternative Employment Data¶
5. Financial Conditions¶
5.1 Credit Growth & Debt Levels¶
# Credit Levels (already instantiated above)
cred_levels = credit_levels_collection.df
cred_levels.columns = credit_levels_collection.titles.values()
credit_levels_collection.dashboard.table()Credit Creation (YoY)¶
credit_yoy_change_collection = hc.create_collection(credit_yoy_dict)
credit_yoy_change_collection.dashboard.table()credit_yoy_df = cred_levels[
["Domestic Currency Credit", "Foreign Currency Credit"]
].copy()
credit_yoy_df.loc[:, "Total"] = credit_yoy_df.sum(axis=1)
credit_yoy_df = credit_yoy_df.pct_change(12)
plot_lines(
credit_yoy_df,
show_0=True,
title="<b>Mexico Credit YoY Change</b>",
years_limit=10,
tick_format="0.0%",
)cred_levels_gdp_change = cred_levels.diff(12).div(
nom_gdp_ts.reindex(cred_levels.index, method="ffill"), axis=0
)
fig = plot_lines(
cred_levels_gdp_change,
years_limit=10,
title="<b>Mexico Credit 12M change / GDP Change</b>",
show_0=True,
tick_suffix="",
logo=False,
tick_format="0.0%",
top_margin=120,
figsize=(900, 500),
)
# Customize trace colors
fig.data[0].line.color = "#00008B" # Dark blue
fig.data[1].line.color = "#4169E1" # Medium blue
fig.data[2].line.color = "#87CEEB" # Light blue
fig.data[3].line.color = "#8B4513" # Dark brown
fig.data[4].line.color = "#A0522D" # Medium brown
fig.data[5].line.color = "#D2B48C" # Light brown
fig.show()Credit Creation According to Macrosynergy¶
private_credit_msn = pd.concat(
[
market_data.read("private_credit_growth_1y", columns=["MXN"])
.data.squeeze()
.rename("Credit Growth 1Y"),
market_data.read("private_credit_growth_1y_pgdp", columns=["MXN"])
.data.squeeze()
.rename("Credit Growth 1Y PGDP"),
],
axis=1,
)
plot_lines(
private_credit_msn,
title="Mexico: Private Sector Credit Growth",
tick_suffix="%",
years_limit=10,
show_0=True,
)5.2 Financial Conditions Indices¶
5.3 Credit Spreads & Market Stress¶
5.4 Interest Rate Differentials¶
# Interest Rate Differential (already instantiated above)
int_rate_diff_df = int_rate_collection.df.copy()
int_rate_diff_df.columns = ["Banxico Policy Rate", "Fed Funds Rate"]
int_rate_diff_df["Rate Diff (Banxico - Fed)"] = (
int_rate_diff_df["Banxico Policy Rate"] - int_rate_diff_df["Fed Funds Rate"]
)int_rate_collection.dashboard.table()int_rate_diff_plot = plot_lines(
int_rate_diff_df,
title="Policy Rate Differential (Banxico - Fed)",
years_limit=10,
tick_suffix="%",
)
switch_trace_to_secondary_axis(
int_rate_diff_plot,
"Rate Diff (Banxico - Fed)",
secondary_axis_title="Delta",
default_y2_range=(0, int_rate_diff_df["Rate Diff (Banxico - Fed)"].max()),
as_area=True,
tick_suffix="%",
)
int_rate_diff_plot.show()5.5 External Balance (Current Account, Trade)¶
trade_collection = []
for k, v in trade_dict.items():
trade_collection.append(get_series_from_haver(v))
trade_collection = TulipCollection(trade_collection)
trade_collection.dashboard.table()trade_collection.dashboard.plots(show_0=True)6. Fiscal Conditions¶
6.1 Budget Balance & Deficit¶
6.2 Public Debt Levels¶
# Government Finance (already instantiated above)
govt_finance_collection.dashboard.table()govt_debt = govt_finance_collection.df
govt_debt.columns = govt_finance_collection.titles.values()
plot_lines(
govt_debt.mul(1000).div(
nom_gdp_ts.reindex(govt_debt.index, method="ffill"), axis=0
),
show_0=True,
title="<b>Mexico Government Debt as percentage of GDP</b>",
years_limit=10,
tick_format="0.0%",
)6.3 Fiscal Policy Outlook¶
from tulip_mania.notebook_related import notebook_updated
notebook_updated()