import pandas as pd
import pycountry
from IPython.display import Markdown
from tulip.core import TulipSeries
from tulip.core.collection import TulipCollection
from tulip.data.bloomberg import BloombergClient as bb
from tulip.data.gs import GSClient as gs
from tulip.data.haver import HaverClient as hc
from tulip.plots import plot_lines
from tulip.utils.frequencies import infer_freq
pyctry = pycountry.countries.get(alpha_2="GB")
def calculate_yoy_change(time_series):
"""
Calculate year-over-year percentage change for a time series using vectorized operations.
Parameters:
-----------
time_series : pandas.Series or pandas.DataFrame
Time series with a datetime index
Returns:
--------
pandas.Series or pandas.DataFrame
Year-over-year percentage change
"""
# Check if we're dealing with a Series element (float) or a Series/DataFrame
if isinstance(time_series, (float, int)):
raise ValueError("Cannot calculate YoY for a single value")
# For quarterly data (common frequencies: 'Q', 'QE', 'QE-DEC', etc.)
if isinstance(time_series.index, pd.DatetimeIndex):
freq_str = infer_freq(time_series.index)
# Handle quarterly data
if "Q" in freq_str:
return time_series.pct_change(4) # 4 quarters = 1 year
# Handle monthly data
elif "M" in freq_str:
return time_series.pct_change(12) # 12 months = 1 year
# Handle daily or irregular data
else:
# Default to 12-period shift for any other frequency
raise NotImplementedError(
f"Cannot calculate YoY for this frequency: {freq_str}"
)
ctry_iso2 = "GB"
currency_ticker = "GBP Curncy"
haver_ctry_code = 112
# S112NGCI@G10
bloomberg_pulls = {}
haver_pulls = {}
weekly_indicators = {}
employment_indicators = {
"unemployment_rate": "MGSX@UK", # [UK: LFS: Unemployment Rate: Aged 16 and Over (SA, %)]
"unemployment_rate_16_64": "UKSEURT@UK", # [UK: LFS: Three Month Unemployment Rate: Aged 16 to 64 (SA, %)]
"unemployment_rate_NSA": "UKNELUR@UK", # [UK: LFS: All Aged 16 & Over: Unemployment Rate(NSA, %)]
}
activity_indicators = {
# "Real Consumption Activity Index": "JNCOREAL Index",
# "Industrial Production": "JNIP Index",
# "Industrial Production (MoM)": "JNIPMOM Index",
# "Real Exports": "JNEIEXPT Index",
# "Real Imports": "JNEIIMPT Index",
# "Housing Starts": "JNHSAN Index",
# "Inventories": "JIPIM&M Index"
}
activity_indicators_collection = []
# BLOOMBERG_NOWCAST = "JNBGNCI Index"
United Kingdom¶
CAI (GS Nowcast)¶
cai_series_soft_vs_hard = gs.get_CAI_series(
geographyId=ctry_iso2,
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_iso2} Current Activity Indicator</b> Updated: {pd.Timestamp.today().strftime('%Y-%m-%d')}",
years_limit=4,
)
cai_plotgs_forecasts = gs.get_eco_forecast(geographyId=ctry_iso2).dropna(
subset=["forecastValue"]
)
cpi_forecats = gs_forecasts.loc[gs_forecasts.metricName.isin(["core_cpi", "cpi_avg"])]# gs_forecasts.metricName.drop_duplicates()Employment¶
### Employment indicators
employment_stuff = {
"Unemployment Rate": "UKSEURT@UK", # UK: LFS: Three Month Unemployment Rate: Aged 16 to 64 (SA, %)
"Youth Unemployment": "AIXT@UK", # UK: LFS: Unemployment Rate: Not in Full-Time Education: Age 16-24 (SA, %)
"Job Vacancies": "AP2Y@UK", # UK: Job Vacancies excluding Agriculture, Forestry & Fishing (SA, Thous)
}
employment_stuff_collection = []
for k, v in employment_stuff.items():
employment_stuff_collection.append(hc.get_series(v))
employment_stuff_collection = TulipCollection(employment_stuff_collection)
employment_stuff_collection["UKSEURT@UK"].good_is = -1
employment_stuff_collection["AIXT@UK"].good_is = -1
employment_stuff_collection.dashboard.table()employment_stuff_collection.dashboard.plots()HMRC Pay As You Earn (PAYE) Real Time Information¶
PAYE_RTI = hc.create_collection(["UKSEPPFC@UK", "UKSEPPFI@UK", "UKSEPPFO@UK"])
PAYE_RTI.dashboard.table()PAYE_RTI.dashboard.plots(logo=False)Goldman also mentioned Workforce Jobs as something to track but it is quaterly and not really timely. It does provide a breakdown of jobs by industry. It is in Haver (e.g., EMDCN@UK)
Budget¶
# todo### Inflation indicators
inflation_stuff = {
"Deflator": f"S{haver_ctry_code}NGPJ@G10",
"CPI": f"H{haver_ctry_code}PHPC@G10",
"Core CPI": f"H{haver_ctry_code}PHXB@G10",
# "Import Deflator": "S112PFMI@G10",# "CTWK@UK", # S112PFMI@G10
"Target": "UKAVMCP@UK", # [United Kingdom: Inflation Target (%)]
}
inflation_stuff_collection = []
for k, v in inflation_stuff.items():
inflation_stuff_collection.append(hc.get_series(v))
inflation_stuff_collection = TulipCollection(inflation_stuff_collection)
inflation_stuff_collection.dashboard.table()
for key, tulip_series in zip(inflation_stuff.keys(), inflation_stuff_collection):
if key == "Target":
tulip_series.ts = tulip_series.ts.div(100).rename(key)
else:
tulip_series.ts = calculate_yoy_change(tulip_series.ts).rename(key)plot_lines(
inflation_stuff_collection.ts.ffill(limit=300, limit_area="inside"),
show_0=True,
title=f"<b>{pyctry.name} Inflation Indicators</b>",
source=f"Haver, Kate Capital Updated: {pd.Timestamp.today().strftime('%Y-%m-%d')}",
years_limit=4,
tick_format="0.0%",
)Inflation in Goods¶
### Inflation indicators
inflation_goods = {
"Overall Goods": "CHOF@UK", # [UK: Retail Price Index: All Goods (NSA, Jan-87=100)]
"Food": "CHBA@UK", # [UK: Retail Price Index: Food (NSA, Jan-87=100)]
"Import Deflator": "S112PFMI@G10",
}
inflation_goods_collection = []
for k, v in inflation_goods.items():
inflation_goods_collection.append(hc.get_series(v))
inflation_goods_collection = TulipCollection(inflation_goods_collection)
inflation_goods_collection.dashboard.table()
for key, tulip_series in zip(inflation_goods.keys(), inflation_goods_collection):
tulip_series.ts = calculate_yoy_change(tulip_series.ts).rename(key)
plot_lines(
inflation_goods_collection.ts.ffill(limit=3),
show_0=True,
title=f"<b>{pyctry.name} Inflation Goods</b> Updated: {pd.Timestamp.today().strftime('%Y-%m-%d')}",
years_limit=4,
tick_format="0.0%",
)
Inflation in Services¶
### Inflation indicators
inflation_services = {
"Overall Services": "CHOG@UK", # [UK: Retail Price Index: All Services (NSA, Jan-87=100)]
"Rent": "DOBP@UK", # [UK: Retail Price Index: Housing Rent (NSA, Jan-87=100)]
}
inflation_services_collection = []
for k, v in inflation_services.items():
inflation_services_collection.append(hc.get_series(v))
inflation_services_collection = TulipCollection(inflation_services_collection)
inflation_services_collection.dashboard.table()
for key, tulip_series in zip(inflation_services.keys(), inflation_services_collection):
tulip_series.ts = calculate_yoy_change(tulip_series.ts).rename(key)
plot_lines(
inflation_services_collection.ts.ffill(limit=3),
show_0=True,
title=f"<b>{pyctry.name} Inflation Indicators</b> Updated: {pd.Timestamp.today().strftime('%Y-%m-%d')}",
years_limit=4,
tick_format="0.0%",
)Wage Growth¶
nom_wage_growth_tulip = bb.get_series("UKAWK54U Index")
nom_wage_growth_tulip.name = "Nominal Wage Growth"
nom_wage_growth_tulip.ts = calculate_yoy_change(nom_wage_growth_tulip.ts.ffill(limit=3))
nom_wage_growth_tulip.plot(show_0=True, years_limit=4, tick_format="0.0%")# KAC3@UK
# KAC3@UK [Great Britain: AWE: Total Pay: Whole Economy [3Mo Mvg Avg vs Yr Ago] (SA, %)]
Expectations¶
# # This is Central Bank Survey, but is very dated
# inflation_expectations = {
# (1, 25): "UMP2A1Y1@UK",
# (2, 25): "UMP2A2Y1@UK",
# (3, 25): "UMP2A3Y1@UK",
# (5, 25): "UMP2A5Y1@UK",
# (1, 50): "UMP2A1Y2@UK",
# (2, 50): "UMP2A2Y2@UK", # UMP2A1Y2@UK [UK: BoE MPS: Inflation Expectations: 1 Year Ahead: 50th Percentile (%)]
# (3, 50): "UMP2A3Y2@UK",
# (5, 50): "UMP2A5Y2@UK",
# (1, 75): "UMP2A1Y3@UK",
# (2, 75): "UMP2A2Y3@UK",
# (3, 75): "UMP2A3Y3@UK",
# (5, 75): "UMP2A5Y3@UK"
# }
# inflation_expectations_collection = []
# for k, v in inflation_expectations.items():
# inflation_expectations_collection.append(hc.get_series(v))
# inflation_expectations_collection = TulipCollection(inflation_expectations_collection)
# # inflation_expectations_collection.dashboard.table()
# multi_index = pd.MultiIndex.from_product([[25,50,75],[1,2,3,5]] ,
# names=['Percentile', 'Years Ahead'])
# # Create a MultiIndex DataFrame from the inflation expectations data
# df = inflation_expectations_collection.ts
# df.columns = multi_index
# Bloomberg data for inflation expectations
inflation_expectations_bb = {
"UK CPI Inflation Projections (Market Rate)": "UKPJCMMN Index",
"Decision Maker Panel Survey CPI 1 Year Ahead": "UKDDCPI1 Index",
"GFK UK Consumer Confidence-Inflation expectation": "UKCCINFL Index",
}
inflation_expectations_collection = bb.create_collection(inflation_expectations_bb)
inflation_expectations_collection.dashboard.table()
# Create a collection for Bloomberg inflation expectations
# Note: This is just creating the dictionary structure
# Actual data fetching would require Bloomberg API integration
# UK CPI Inflation Projections based on Market Interest Rate Expectations Mean UKPJCMMN Index
# Decision Maker Panel Survey CPI Inflation Expections 1 Year Ahead YoY NSA » "UKDDCPI1 Index"
# GFK UK Consumer Confidence-Inflation expectation UKCCINFL Index
inflation_expectations_collection.dashboard.plots()For the time being, nominal wage growth probably remains too high for comfort, especially for those MPC members inclined to attach a lot of weight to this indicator. And finally, price expectations are not yet supportive of policy easing. Our latest Citi/YouGov survey indicated that household’s inflation expectations have increased, and the PMIs suggest that companies see it the same way. This development was to some extent anticipated because of the NIC/NLW increases in April, but nonetheless remain too high for comfort.
from tulip_mania.notebook_related import notebook_updated
notebook_updated()