import pandas as pd
from IPython.display import Markdown, display
from tulip.core.collection import TulipCollection
from tulip.data.bloomberg import BloombergClient as bb
from tulip.data.haver import (
get_series as get_series_from_haver,
get_collection as get_collection_from_haver,
)
from tulip.data.gs import GSClient as gs
from tulip.data.haver import get_urate
from tulip.plots import plot_line, plot_lines, 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
Sweden¶
# ========================================
# PARAMETERS & DATA FETCHING
# ========================================
# Country identifiers
ISO_2 = "SE"
haver_code = 144
ctry_name = "Sweden"
bloomberg_ctry_prefix = "SE"
goldman_ctry_id = ISO_2
pyctry = pycountry.countries.get(alpha_2=ISO_2)
# ========================================
# DATA FETCHING - All Haver Series
# ========================================
# Core economic indicators
core_series = {
"Economic Tendency": "SENVET@NORDIC",
"PMI": "SESVPTG@NORDIC",
"Confidence Indicator": "SESVXJ@NORDIC",
"Unemployment Rate": "SESELUR@NORDIC",
"Unemployment Rate 2": "SESELUCR@NORDIC",
"NAIRU": "SEAVELUR@NORDIC",
"Job Starts": "E144TVNS@INTWKLY",
}
core_collection = get_collection_from_haver(core_series)
# GDP and growth data
growth_series = {
"Real GDP": "SESNGDP@NORDIC",
"Fixed Investment": "SESNI@NORDIC",
"Inventories": "SESNB@NORDIC",
"Gov Consumption": "SESNCP@NORDIC",
"HH Consumption": "SESNCV@NORDIC",
"Exports": "SESNX@NORDIC",
"Imports": "SESNM@NORDIC",
}
growth_collection = get_collection_from_haver(growth_series)
growth_collection["SESNM@NORDIC"].good_is = -1
# Nominal GDP
ngdp = get_series_from_haver(f"H{haver_code}NGDP@G10")
ngdp_ann = ngdp.ts.rolling(4).sum()
# Flash GDP
flash_gdp = get_series_from_haver("SEWNGCY@NORDIC")
# Trade data
trade_balance = get_series_from_haver("SETIB@NORDIC").ts.rename(
"Trade Balance (SEK bn)"
)
sek_usd = get_series_from_haver("SENXUSV@NORDIC").ts.rename("SEK/USD")
# Riksbank sentiment indicators
riksbank_series = {
"Business Price Expectations": "SESVBSPM@NORDIC",
"Consumer Confidence": "SESCCI@NORDIC",
"Consumer Confidence Macro": "SESCCIMA@NORDIC",
"Consumer Confidence Micro": "SESCCIMI@NORDIC",
"Business Demand Assessment": "SESVBODM@NORDIC",
"1Y CPIF Expectations": "SENJ1FA@NORDIC",
"2Y CPIF Expectations": "SENJ2FA@NORDIC",
"5Y CPIF Expectations": "SENJ5FA@NORDIC",
}
riksbank_collection = get_collection_from_haver(riksbank_series)
# Inflation
cpi = get_series_from_haver("SENPCY@NORDIC")
# Unemployment data
unemployment_rate = get_series_from_haver("SESELUR@NORDIC")
labor_force = get_series_from_haver("SESELLF@NORDIC")
unemployed_weekly = get_series_from_haver("E144TVAR@INTWKLY")
nairu_cb = get_series_from_haver("SEAVELUR@NORDIC")
# Fiscal data
fiscal_series = {
"Budget Deficit": "SENFGB@NORDIC",
"General Govt Budget Deficit": "SEAFTNL@NORDIC",
"Defense Expenditure": "SENFEND@NORDIC",
"Central Govt Debt": "SENFPD@NORDIC",
}
fiscal_collection = get_collection_from_haver(fiscal_series)
# Debt levels
debt_series = {
"Domestic Debt": "SESZLDDG@NORDIC",
"Household Debt": "SESZDHP@NORDIC",
"Nonfinancial Corporations Debt": "SESZDNP@NORDIC",
"Financial Corporations Debt": "SESZDFP@NORDIC",
"Government Debt": "SESZDGP@NORDIC",
"Nonprofit Institutions Serving Households' Debt": "SESZLSP@NORDIC",
}
debt_collection = get_collection_from_haver(debt_series)
# Debt to income
debt_income_series = {
"Outstanding Debt to Disposable Income": "SESZLHDI@NORDIC",
"Interest Rate": "SENRREPV@NORDIC",
}
debt_income_collection = get_collection_from_haver(debt_income_series)
# Real estate
real_estate_series = {
"Policy Rate": "SENRREPV@NORDIC",
"2Y Mortgage Yield": "SENRM2@NORDIC",
"5Y Mortgage Yield": "SENRM5@NORDIC",
"Housing Prices": "SEAPH001@NORDIC",
}
real_estate_collection = get_collection_from_haver(real_estate_series)
# Stock indices
omx = get_series_from_haver("S144O30@INTWKLY").ts.rename("OMXS30")
spx = get_series_from_haver("S111SP5@INTWKLY").ts.rename("S&P500")# IRIS SUMMARY
iris.summarize(
core_collection, "What trends do you see in Sweden's economic indicators?"
).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
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 = 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 = cai_series.set_index("metricName", append=True)["metricValue"].unstack(
"metricName"
)
cai_series.columns = ["Hard", "Soft", "Headline"]
cai_plot = plot_lines(
cai_series,
show_0=True,
title=f"<b>{ctry_name} Current Activity Indicator</b> Updated: {pd.Timestamp.today().strftime('%Y-%m-%d')}",
years_limit=4,
tick_suffix="%",
source="Goldman Sachs",
)
cai_plot.show()
except Exception as e:
display(Markdown(f"*CAI not available for {ctry_name}: {e}*"))# Bloomberg Nowcast (if available)
try:
nowcast_ticker = f"BENW{bloomberg_ctry_prefix}GC Index"
bloomberg_nowcast = bb.get_series(nowcast_ticker)
bloomberg_nowcast.plot(
years_limit=3, tick_suffix="%", title=f"<b>{ctry_name} Bloomberg Nowcast</b>"
)
except Exception as e:
display(Markdown(f"*Bloomberg nowcast not available for {ctry_name}*"))1.3 GDP Growth & Components¶
# Flash GDP
flash_gdp.plot(tick_suffix="%", show_0=True, title="<b>Sweden Flash GDP</b>")# GDP Components Dashboard
growth_collection.dashboard.table()growth_collection.dashboard.plots(show_0=True)# Nominal GDP Growth
plot_line(
ngdp_ann.pct_change(4),
title="<b>Nominal GDP Growth (%)</b>",
tick_format="0.0%",
show_0=True,
)1.4 Business Activity Surveys¶
# Economic Tendency
economic_tendency = core_collection["SENVET@NORDIC"]
fig = economic_tendency.plot(title="<b>Sweden Economic Tendency</b>")
fig.add_hline(y=100, line_width=1, line_dash="dash", line_color="black")
fig.show()# PMI
pmi = core_collection["SESVPTG@NORDIC"]
fig = pmi.plot(title="<b>Sweden PMI</b>")
fig.add_hline(y=50, line_width=1, line_dash="dash", line_color="black")
fig.show()# Confidence Indicator
confidence_indicator = core_collection["SESVXJ@NORDIC"]
fig = confidence_indicator.plot(title="<b>Sweden Confidence Indicator</b>")
fig.add_hline(y=100, line_width=1, line_dash="dash", line_color="black")
fig.show()# Riksbank Metrics
riksbank_collection.dashboard.table()1.5 Retail Sales & Consumer Activity¶
1.6 Industrial Production & Manufacturing¶
1.7 Trade¶
# Trade Balance vs SEK/USD
fig = plot_line(
blue=trade_balance,
red=sek_usd,
title="<b>Sweden: Trade Balance vs SEK/USD Exchange Rate</b>",
tick_suffix="",
years_limit=10,
watermark=False,
source="Haver",
)
fig.data[1].update(yaxis="y2")
fig.update_layout(
yaxis=dict(
title=dict(text="Trade Balance (SEK bn)", font=dict(color="blue")),
tickfont=dict(color="blue"),
),
yaxis2=dict(
title=dict(text="SEK/USD", font=dict(color="red")),
tickfont=dict(color="red"),
overlaying="y",
side="right",
),
)
fig.show()1.8 Growth Summary (AI-Generated)¶
2. Prices: Inflation & Wages¶
2.1 Headline & Core Inflation¶
# CPI Inflation
cpi.plot(title="<b>Sweden CPI Inflation</b>", tick_suffix="%")2.2 Inflation Expectations & Surveys¶
# CPIF Expectations from Riksbank collection
cpif_expectations = riksbank_collection.df[
["SENJ1FA@NORDIC", "SENJ2FA@NORDIC", "SENJ5FA@NORDIC"]
]
cpif_expectations.columns = ["1Y CPIF", "2Y CPIF", "5Y CPIF"]
plot_lines(
cpif_expectations,
title="<b>Sweden CPIF Inflation Expectations</b>",
tick_suffix="%",
years_limit=5,
)2.3 Inflation Nowcasts & Forecasts¶
2.4 Wage Growth & Labor Costs¶
2.5 Inflation Components (Goods vs Services)¶
3. Slack: Output Gap & Capacity Utilization¶
3.1 Output Gap Estimates¶
3.2 Capacity Utilization¶
4. Employment: Labor Market Conditions¶
4.1 Unemployment Rate & Labor Force¶
# Labor Force Survey Unemployment Rate
plot_line(
unemployment_rate.ts,
title="<b>Sweden: Labor Force Survey Unemployment Rate (SA, %)</b>",
tick_suffix="%",
)# Unemployment vs NAIRU
urate = get_urate("SWE")
plot_lines(
[urate.ts.rename("Unemployment"), nairu_cb.ts.rename("Nairu Estimate")],
default_x_range=("2010-01-01", "2025-12-31"),
title="<b>Swedish Unemployment vs NAIRU</b>",
tick_suffix="%",
)4.2 Weekly Unemployment (SPES-Based)¶
Sweden publishes a weekly figure on unemployment. We normalize by the labor force to create a faster measure.
# Weekly unemployment calculation
unemployed = unemployed_weekly.ts.rename("All Registered Unemployed")
lf = labor_force.ts.rename("Labor Force")
lf = lf.mul(100).resample("W-MON").bfill()
weekly_unemployment = (
pd.concat(
[
unemployed,
lf,
unemployment_rate.ts.div(100)
.resample("W-MON")
.bfill()
.rename("Labor Force Survey Unemployment"),
],
axis=1,
)
.ffill()
.dropna()
)
weekly_unemployment["Fast Unemployment"] = (
weekly_unemployment["All Registered Unemployed"]
/ weekly_unemployment["Labor Force"]
)
fig = plot_lines(
weekly_unemployment[["Labor Force Survey Unemployment", "Fast Unemployment"]],
title="<b>SPES-Based Unemployment Rate (Weekly figure)</b>",
axis_title="Unemployment Rate (%)",
years_limit=2,
source="Haver",
watermark=False,
tick_format="0.1%",
)
fig.show()4.3 Employment Growth & Payrolls¶
4.4 Jobless Claims & Leading Indicators¶
4.5 Job Openings & Labor Turnover¶
4.6 Alternative Employment Data¶
5. Financial Conditions¶
5.1 Credit Growth & Debt Levels¶
# Outstanding Debt Levels
combined_df = debt_collection.df
combined_df.columns = debt_collection.titles.values()
fig = plot_lines(
combined_df,
title="<b>Sweden Debt Outstanding as % of GDP</b>",
tick_suffix="%",
y_axis_label="Debt Outstanding as a % of SA GDP",
x_axis_label="Date",
watermark=False,
years_limit=10,
logo=False,
figsize=(1200, 500),
)
fig.show()# Debt Creation (YoY Change)
yoy_df = combined_df.diff(6) * 2
yoy_df = yoy_df.dropna()
fig = plot_lines(
yoy_df,
title="<b>Sweden Debt Creation</b>",
tick_suffix="%",
y_axis_label="Debt Creation (Ann. Chg in % of GDP)",
x_axis_label="Date",
watermark=False,
years_limit=10,
source="Haver",
logo=False,
figsize=(1200, 500),
)
fig.show()5.2 Housing, Consumption and Debt¶
# Debt to Income vs Interest Rate
df_od = debt_income_collection.df
df_od.columns = debt_income_collection.titles.values()
df_od.dropna(inplace=True)
fig = plot_line(
blue=df_od["Outstanding Debt to Disposable Income"],
red=df_od["Interest Rate"],
title="<b>% of Outstanding Debt to Disposable Income vs Interest Rate</b>",
tick_suffix="%",
y_axis_label="Debt to Disposable Income (%)",
y2_axis_label="Interest Rate (%)",
source="Haver",
watermark=False,
years_limit=10,
figsize=(1200, 500),
)
fig.show()5.3 Financial Conditions Indices¶
5.4 Credit Spreads & Market Stress¶
5.5 Foreign Exchange Rate¶
# SEK/USD Exchange Rate
get_series_from_haver("SENXUSV@NORDIC").plot(title="<b>SEK/USD Exchange Rate</b>")5.6 External Balance (Current Account, Trade)¶
6. Fiscal Conditions¶
6.1 Budget Balance & Deficit¶
# Deficit as % of GDP
deficit_as_PGDP = (
fiscal_collection[0].ts.rolling(12).sum() / ngdp_ann.resample("ME").ffill()
)
plot_lines(
deficit_as_PGDP.rename("Deficit as percentage of GDP"),
tick_format="0.0%",
title="<b>Sweden Deficit as percentage of GDP</b>",
years_limit=3,
show_0=True,
)6.2 Public Debt Levels¶
6.3 Fiscal Policy Outlook¶
7. Assets & Markets¶
7.1 Real Estate¶
# Swedish Mortgage Yields, Policy Rate, and Housing Prices
df_all = real_estate_collection.df
df_all.columns = real_estate_collection.titles.values()
df_all = df_all.dropna()
fig = plot_lines(
df_all,
title="<b>Swedish Mortgage Yields, Policy Rate, and Housing Prices</b>",
width=1000,
height=600,
tick_suffix="%",
watermark=False,
default_y_range=(-10, 15),
)
switch_trace_to_secondary_axis(
fig,
trace_names="Housing Prices",
secondary_axis_title="Housing Price Index (1981 = 100)",
tick_suffix="",
)
fig.update_layout(yaxis=dict(title="Mortgage Yields & Policy Rate (%)"))
fig.show()7.2 Equities¶
# OMXS30 vs S&P 500
df = pd.concat([omx, spx], axis=1).dropna()
df = df[df.index >= "2020-01-01"]
df_rebased = df / df.iloc[0] * 100
fig = plot_lines(
df_rebased,
title="<b>Rebased Index Comparison: OMXS30 vs. S&P 500 (Start = 100)</b>",
axis_title="Index Level (Rebased)",
source="Haver",
watermark=False,
width=1000,
height=600,
)
fig.show()8. Riksbank Sentiment Indicator¶
Riksbank Sentiment Index see
Source: J.P. Morgan, SCB, Riksbank, NIER, Origo. *Z scores of NIER industry demand exp., NIER industry price exp., HH conf., Origo 5-year CPI exp. Four variables have equal weights
riksbank_collection.dashboard.plots()# Riksbank Sentiment Index (Leads Interest Rate Cuts)
sentiment_indicator = pd.DataFrame(
{
"industry_demands": riksbank_collection["SESVBODM@NORDIC"].ts,
"industry_price_expectations": riksbank_collection["SESVBSPM@NORDIC"].ts,
"confidence_indicator": riksbank_collection["SESCCI@NORDIC"].ts,
"cpi_expectations": riksbank_collection["SENJ5FA@NORDIC"].ts,
}
)
ssi = (
sentiment_indicator.sub(sentiment_indicator.mean())
.div(sentiment_indicator.std())
.mean(axis=1)
)
policy_rate_target = bb.get_series("SWRRATEI Index")
ssi = ssi.rename("Riksbank Sentiment Index").to_frame()
ssi["Policy Rate Target"] = policy_rate_target.ts.resample("ME").last().ffill()
ssi["Policy Rate Change"] = ssi["Policy Rate Target"].diff()
fig = plot_line(
blue=ssi.loc["2003":, "Policy Rate Change"],
red=ssi.loc["2003":, "Riksbank Sentiment Index"],
show_0=True,
align_0=True,
title="<b>Riksbank Sentiment Index vs Policy Rate Change</b>",
)
fig.show()from tulip_mania.notebook_related import notebook_updated
notebook_updated()