Producer price inflation#

This category group contains real-time information states of output price growth in the economy. In particular, it contains the following important concepts:

  • Real-time measures of internationally comparable GDP price deflators based on vintages which are either taken from national sources or by JPMaQS.

  • Estimated economy-wide output price growth based on standard econometric (“technical”) estimates that use GDP deflators as target but deploy higher-frequency price information. Historic and current models are based on the simplest conventions and are recurrently reconstructed based on learning (view basic principles here)

  • Real-time measures of standard producer price (PPI) index trends of selected sectors in accordance with local convention (typically focused on industry), based on sequential estimation of seasonal factors and/or original data vintages.

Standard PPI inflation#

Ticker: PPIH_NSA_P1M1ML12 / _P1M1ML12_3MMA

Label: Main producer price index: %oya / %oya, 3mma

Definition: Main producer price index: % over a year ago / % over a year ago, 3-month moving average

Notes:

  • In general, the PPIs used for this category are the most commonly watched local measures. Definitions are not equal across currency areas. In some countries, prices refer only to manufacturing output. In others, they include construction, mining and utilities. Some also include agriculture and services. See the country-specific notes in Appendix 1.

  • India and Taiwan do not report PPI and instead the wholesale price index has been used.

  • Most underlying vintages have a monthly frequency. Australia, New Zealand and Indonesia only publish quarterly production price indices. For trend growth calculations, these indices are assumed to be stable over the quarter.

  • USA changed how it calculated PPI from the processing stage to the final intermediate demand. The final demand version is used from January 2016 onwards.

  • Hungary only reported manufacturing prices until 2010.

  • South Africa only reported manufacturing prices until 2012.

  • For Brazil, WPI was used in calculations until 2019.

Imports#

Only the standard Python data science packages and the specialized macrosynergy package are needed.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import math
import os

import json
import yaml

import macrosynergy.management as msm
import macrosynergy.panel as msp
import macrosynergy.signal as mss
import macrosynergy.pnl as msn


from macrosynergy.download import JPMaQSDownload

from timeit import default_timer as timer
from datetime import timedelta, date, datetime

import requests
import warnings

warnings.simplefilter("ignore")

The JPMaQS indicators we consider are downloaded using the J.P. Morgan Dataquery API interface within the macrosynergy package. This is done by specifying ticker strings, formed by appending an indicator category code <category> to a currency area code <cross_section>. These constitute the main part of a full quantamental indicator ticker, taking the form DB(JPMAQS,<cross_section>_<category>,<info>), where <info> denotes the time series of information for the given cross-section and category. The following types of information are available:

  • value giving the latest available values for the indicator

  • eop_lag referring to days elapsed since the end of the observation period

  • mop_lag referring to the number of days elapsed since the mean observation period

  • grade denoting a grade of the observation, giving a metric of real time information quality.

After instantiating the JPMaQSDownload class within the macrosynergy.download module, one can use the download(tickers,start_date,metrics) method to easily download the necessary data, where tickers is an array of ticker strings, start_date is the first collection date to be considered and metrics is an array comprising the times series information to be downloaded.

# Cross-sections of interest


cids_dmca = [
    "AUD",
    "CAD",
    "CHF",
    "EUR",
    "GBP",
    "JPY",
    "NOK",
    "NZD",
    "SEK",
    "USD",
]  # DM currency areas
cids_dmec = ["DEM", "ESP", "FRF", "ITL", "NLG"]  # DM euro area countries
cids_latm = ["BRL", "COP", "CLP", "MXN", "PEN"]  # Latam countries
cids_emea = ["CZK", "HUF", "ILS", "PLN", "RON", "RUB", "TRY", "ZAR"]  # EMEA countries
cids_emas = [
    "CNY",
    # "HKD",
    "IDR",
    "INR",
    "KRW",
    "MYR",
    "PHP",
    "SGD",
    "THB",
    "TWD",
]  # EM Asia countries

cids_dm = cids_dmca + cids_dmec
cids_em = cids_latm + cids_emea + cids_emas

cids = sorted(cids_dm + cids_em)

# for FX

cids_nofx = ["EUR", "USD", "SGD"] + cids_dmec
cids_fx = list(set(cids) - set(cids_nofx))
cids_eur = ["CHF", "CZK", "HUF", "NOK", "PLN", "RON", "SEK"]  # trading against EUR
cids_eud = ["GBP", "RUB", "TRY"]  # trading against EUR and USD
cids_usd = list(set(cids_fx) - set(cids_eur + cids_eud))  # trading against USD
# Quantamental categories of interest

main = [
    "PGDP_SA_P1Q1QL4",
    "PGDP_SA_P1Q1QL1AR",
    "PGDP_SA_P2Q2QL2AR",
    "PGDPTECH_SA_P1M1ML12_3MMA",
    "PGDPTECH_SA_P1M1ML12",
    "PGDPTECH_SA_P1M1ML12_D3M3ML3",
    "PGDPTECHX_SA_P1M1ML12_3MMA",
    "PGDPTECHX_SA_P1M1ML12",
    "PGDPTECHX_SA_P1M1ML12_D3M3ML3",
    "PPIH_NSA_P1M1ML12",
    "PPIH_NSA_P1M1ML12_3MMA",
    "PPIH_SA_P3M3ML3AR",
    "PPIH_SA_P6M6ML6AR",
]
econ = ["CPIH_SA_P1M1ML12", "INFTEFF_NSA"]  # economic context
mark = [
    "FXXR_NSA",
    "FXXR_VT10",
    "EQXR_NSA",
    "DU05YXR_NSA",
    "DU02YXR_NSA",
    "FXTARGETED_NSA",
    "FXUNTRADABLE_NSA",
]  # market links

xcats = main + econ + mark
# Download series from J.P. Morgan DataQuery by tickers

start_date = "1990-01-01"
tickers = [cid + "_" + xcat for cid in cids for xcat in xcats]
print(f"Maximum number of tickers is {len(tickers)}")

# Retrieve credentials

client_id: str = os.getenv("DQ_CLIENT_ID")
client_secret: str = os.getenv("DQ_CLIENT_SECRET")

# Download from DataQuery

with JPMaQSDownload(client_id=client_id, client_secret=client_secret) as downloader:
    start = timer()
    assert downloader.check_connection()
    df = downloader.download(
        tickers=tickers,
        start_date=start_date,
        metrics=["value", "eop_lag", "mop_lag", "grading"],
        suppress_warning=True,
        show_progress=True,
    )
    end = timer()

dfd = df

print("Download time from DQ: " + str(timedelta(seconds=end - start)))
Maximum number of tickers is 814
Downloading data from JPMaQS.
Timestamp UTC:  2024-01-09 16:06:23
Connection successful!
Number of expressions requested: 3256
Requesting data: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 163/163 [00:57<00:00,  2.81it/s]
Downloading data: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 163/163 [00:49<00:00,  3.26it/s]
Download time from DQ: 0:03:43.823323

Availability#

cids_exp = cids  # cids expected in category panels
msm.missing_in_df(dfd, xcats=xcats, cids=cids_exp)
Missing xcats across df:  []
Missing cids for CPIH_SA_P1M1ML12:  []
Missing cids for DU02YXR_NSA:  ['RON', 'DEM', 'PHP', 'ITL', 'FRF', 'BRL', 'PEN', 'NLG', 'ESP']
Missing cids for DU05YXR_NSA:  ['RON', 'DEM', 'PHP', 'ITL', 'FRF', 'BRL', 'PEN', 'NLG', 'ESP']
Missing cids for EQXR_NSA:  ['COP', 'ILS', 'NZD', 'RON', 'RUB', 'IDR', 'PHP', 'CLP', 'HUF', 'NOK', 'PEN', 'CZK']
Missing cids for FXTARGETED_NSA:  ['DEM', 'USD', 'ITL', 'FRF', 'NLG', 'ESP']
Missing cids for FXUNTRADABLE_NSA:  ['DEM', 'USD', 'ITL', 'FRF', 'NLG', 'ESP']
Missing cids for FXXR_NSA:  ['DEM', 'USD', 'ITL', 'FRF', 'NLG', 'ESP']
Missing cids for FXXR_VT10:  ['DEM', 'USD', 'ITL', 'FRF', 'NLG', 'ESP']
Missing cids for INFTEFF_NSA:  ['DEM', 'ITL', 'FRF', 'NLG', 'ESP']
Missing cids for PGDPTECHX_SA_P1M1ML12:  ['AUD', 'NZD']
Missing cids for PGDPTECHX_SA_P1M1ML12_3MMA:  ['AUD', 'NZD']
Missing cids for PGDPTECHX_SA_P1M1ML12_D3M3ML3:  ['AUD', 'NZD']
Missing cids for PGDPTECH_SA_P1M1ML12:  []
Missing cids for PGDPTECH_SA_P1M1ML12_3MMA:  []
Missing cids for PGDPTECH_SA_P1M1ML12_D3M3ML3:  []
Missing cids for PGDP_SA_P1Q1QL1AR:  []
Missing cids for PGDP_SA_P1Q1QL4:  []
Missing cids for PGDP_SA_P2Q2QL2AR:  []
Missing cids for PPIH_NSA_P1M1ML12:  []
Missing cids for PPIH_NSA_P1M1ML12_3MMA:  []
Missing cids for PPIH_SA_P3M3ML3AR:  ['CNY']
Missing cids for PPIH_SA_P6M6ML6AR:  ['CNY']
xcatx = xcats
cidx = cids_exp

dfx = msm.reduce_df(dfd, xcats=main, cids=cidx)
dfs = msm.check_startyears(
    dfx,
)
msm.visual_paneldates(dfs, size=(20, 5))

print("Last updated:", date.today())
../_images/Producer price inflation_21_0.png
Last updated: 2024-01-09
xcatx = main
cidx = cids_exp

plot = msm.check_availability(
    dfd, xcats=xcatx, cids=cidx, start_size=(12, 4), start_years=False
)
../_images/Producer price inflation_22_0.png
xcatx = main
cidx = cids_exp

plot = msp.heatmap_grades(
    dfd,
    xcats=xcatx,
    cids=cidx,
    size=(12, 4),
    title=f"Average vintage grades from {start_date} onwards",
)
../_images/Producer price inflation_23_0.png

Standard PPI inflation data have the shortest observation period lags, following by economy-wide estimated output price trends. The GDP deflatos lake longest to release.

xcatx = ["PGDPTECH_SA_P1M1ML12_3MMA", "PGDP_SA_P1Q1QL4", "PPIH_NSA_P1M1ML12"]
cidx = cids_exp

msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    val="eop_lag",
    title="End of observation period lags (ranges of time elapsed since end of observation period in days)",
    start=start_date,
    kind="box",
    size=(16, 7),
)
../_images/Producer price inflation_25_0.png

History#

GDP price deflators inflation#

Averages and variation of GDP deflators have been very different across countries. Quarterly deflator changes are very volatile for most countries. The annual growth rates and the 2-quarter growth rates have empirically been more plausible indicators of trends.

xcatx = ["PGDP_SA_P1Q1QL4", "PGDP_SA_P1Q1QL1AR","PGDP_SA_P2Q2QL2AR"]
cidx = cids_exp

msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    sort_cids_by="mean",
    start="2000-01-01",
    title="Means and standard deviations of GDP price deflators inflation, since 2000",
    kind="bar",
    size=(16, 8),
)
../_images/Producer price inflation_29_0.png

Some countries such as Great Britain and China can have highly volatile GDP price deflator, which can be explained by methodology changes. For example, in the UK during the height of COVID, the Office for National Statistics reduced public sector output based on physical indicators, such as in-person classes taught. Since at the same time costs for such services remained stable the inputted deflator surged. which affected the GDP deflator during the covid disruption. China has historically used undisclosed methods for their real growth rate and there have been implausible patterns in their data. There is evidence to suggest that China overstates real GDP growth in downturns, as there has been a pattern of decreasing price growth rather than production growth.

xcatx = ["PGDP_SA_P1Q1QL4","PGDP_SA_P2Q2QL2AR"]
cidx = cids_exp

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    title="GDP deflator trends",
    # label_adj=0.05,
    title_fontsize=27,
    legend_fontsize=17,
    title_adj=1.02,
    title_xadj=0.42,
    ncol=4,
    same_y=False,
    size=(12, 7),
    aspect=1.7,
    all_xticks=True,
)
../_images/Producer price inflation_31_0.png

GDP price deflators have generally not been strongly correlated across countries. Clusters of strong correlation can be found, however, among European economies and other specific country pairs. Japan’s and Taiwan’s deflator trends have been negatively correlated with many EM countries.

xcatx = ["PGDP_SA_P1Q1QL4"]
cidx = cids_exp

msp.correl_matrix(
    dfd,
    xcats=xcatx,
    cids=cidx,
    size=(20, 14),
    title="Cross-sectional correlations of annual GDP price deflators inflation since 1990",
    cluster=True,

)
../_images/Producer price inflation_33_0.png

Annual PPI inflation#

Average rates and standard deviations of PPI inflation have been diverse across countries, with exchange rates being a key differentiating factor. Long-term average inflation has ranged from near-zero in Switzerland to near 20% in Turkey.

xcatx = ["PPIH_NSA_P1M1ML12", "PPIH_NSA_P1M1ML12_3MMA"]
cidx = cids_exp

msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    sort_cids_by="mean",
    start="2000-01-01",
    title="Means and standard deviations of annual PPI inflation",
    xcat_labels=["%oya", "%oya, 3mma"],
    kind="bar",
    size=(16, 8),
)
../_images/Producer price inflation_44_0.png
xcatx = ["PPIH_NSA_P1M1ML12", "PPIH_NSA_P1M1ML12_3MMA"]
cidx = cids_exp

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    title="Headline PPI inflation and three month moving average",
    title_fontsize=27,
    legend_fontsize=17,
    title_adj=1.02,
    title_xadj=0.42,
    ncol=4,
    same_y=False,
    size=(12, 7),
    aspect=1.7,
    all_xticks=True,
)
../_images/Producer price inflation_45_0.png

Quantamental series of PPI inflation rates are positively correlated across almost all country pairs and often strongly so. This mainly reflects the impact of various raw materials on PPI dynamics.

msp.correl_matrix(dfd, xcats="PPIH_NSA_P1M1ML12", cids=cids_exp, size=(20, 14))
../_images/Producer price inflation_47_0.png

Seasonally-adjusted PPI trend#

The 6-months-over-6months trends have been a lot more stable than the 3-months-over-3-months trends and seem to be a useful basis for measuring shorter-term producer price inflation trend differentials across countries.

xcatx = ["PPIH_SA_P3M3ML3AR", "PPIH_SA_P6M6ML6AR"]
cidx = cids_exp

msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    sort_cids_by="mean",
    start="2000-01-01",
    title="Means and standard deviations of seasonally-adjusted PPI trends",
    xcat_labels=["3-month/3-month trend", "6-month/6-month trend"],
    kind="bar",
    size=(16, 8),
)
../_images/Producer price inflation_50_0.png
xcatx = ["PPIH_SA_P3M3ML3AR", "PPIH_SA_P6M6ML6AR"]
cidx = cids_exp

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    xcat_labels=[
        "% 3m/3m, seasonally-adjusted annualized rate",
        "% 6m/6m, seasonally-adjusted annualized rate",
    ],
    title="Seasonally adjusted trends of headline PPI",
    title_fontsize=27,
    legend_fontsize=17,
    label_adj=0.075,
    title_adj=1.02,
    title_xadj=0.4,
    ncol=4,
    same_y=False,
    size=(12, 7),
    aspect=1.7,
    all_xticks=True,
)
../_images/Producer price inflation_51_0.png

Importance#

Empirical clues#

calcs = ["XPGDPTECH_SA_P1M1ML12 = PGDPTECH_SA_P1M1ML12 - INFTEFF_NSA ",
         "XPGDPTECHX_SA_P1M1ML12 = PGDPTECHX_SA_P1M1ML12 - INFTEFF_NSA ",]
dfa = msp.panel_calculator(dfd, calcs=calcs, cids=cids)
dfd = msm.update_df(dfd, dfa)
dfb = dfd[dfd["xcat"].isin(["FXTARGETED_NSA", "FXUNTRADABLE_NSA"])].loc[
    :, ["cid", "xcat", "real_date", "value"]
]
dfba = (
    dfb.groupby(["cid", "real_date"])
    .aggregate(value=pd.NamedAgg(column="value", aggfunc="max"))
    .reset_index()
)
dfba["xcat"] = "FXBLACK"
fxblack = msp.make_blacklist(dfba, "FXBLACK")
xcatx = [
    "XPGDPTECHX_SA_P1M1ML12",
    "XPGDPTECH_SA_P1M1ML12",
    "PGDPTECHX_SA_P1M1ML12",
    "PGDPTECH_SA_P1M1ML12",
]

for xc in xcatx:
    calc_eur = [f"{xc}vBM = {xc} - iEUR_{xc}"]
    calc_usd = [f"{xc}vBM = {xc} - iUSD_{xc}"]
    calc_eud = [f"{xc}vBM = {xc} - 0.5 * ( iEUR_{xc} + iUSD_{xc} )"]

    dfa_eur = msp.panel_calculator(dfd, calcs=calc_eur, cids=cids_eur)
    dfa_usd = msp.panel_calculator(dfd, calcs=calc_usd, cids=cids_usd)
    dfa_eud = msp.panel_calculator(dfd, calcs=calc_eud, cids=cids_eud)

    dfa = pd.concat([dfa_eur, dfa_usd, dfa_eud])
    dfd = msm.update_df(dfd, dfa)

Relative output price growth trends across countries are plausible predictors of FX return trends. That is because they indicate improving terms-of-trade and a greater bias towards monetary tightening. A country whose production prices grow faster than in the U.S. or the euro area has a better chance to experience currency appreciation against either of these two benchmarks.

Empirically, i.e., since 2000 and for 29 DM and EM currency areas, output price trend differentials have been positively correlated with subsequent returns both at a monthly and quarterly frequency. For both broad and narrow output price trends this relationship has been highly significant.

xcatx = ["PGDPTECH_SA_P1M1ML12vBM", "FXXR_VT10"]
cidx = cids

cr_crr = msp.CategoryRelations(
    dfd,
    xcats=xcatx,
    cids=cidx,
    freq="Q",
    lag=1,
    xcat_aggs=["last", "sum"],
    start="2000-01-01",
    blacklist=fxblack,
)
cr_crr.reg_scatter(
    title="Estimated output price differentials and subsequent FX forward returns, 29 countries since 2000",
    labels=False,
    coef_box="lower left",
    xlab="Estimated broad output price growth of the economy, %oya, versus benchmark (USD or EUR)", 
    ylab="FX forward return, targeted to 10% ar vol, versus benchmark (USD or EUR), next quarter",
    prob_est="map"
    
)
PGDPTECH_SA_P1M1ML12vBM misses: ['DEM', 'ESP', 'EUR', 'FRF', 'ITL', 'NLG', 'SGD', 'USD'].
FXXR_VT10 misses: ['DEM', 'ESP', 'FRF', 'ITL', 'NLG', 'USD'].
../_images/Producer price inflation_60_1.png
xcatx = ["PGDPTECHX_SA_P1M1ML12vBM", "FXXR_VT10"]
cidx = cids

cr_crr = msp.CategoryRelations(
    dfd,
    xcats=xcatx,
    cids=cidx,
    freq="Q",
    lag=1,
    xcat_aggs=["last", "sum"],
    start="2000-01-01",
    blacklist=fxblack,
)
cr_crr.reg_scatter(
    title="Narrow output price differentials and subsequent FX forward returns, 29 countries since 2000",
    labels=False,
    coef_box="lower left",
    xlab="Estimated narrow output price growth of the economy, %oya, versus benchmark (USD or EUR)", 
    ylab="FX forward return, targeted to 10% ar vol, versus benchmark (USD or EUR), next quarter",
    prob_est="map"
    
)
PGDPTECHX_SA_P1M1ML12vBM misses: ['AUD', 'DEM', 'ESP', 'EUR', 'FRF', 'ITL', 'NLG', 'NZD', 'SGD', 'USD'].
FXXR_VT10 misses: ['DEM', 'ESP', 'FRF', 'ITL', 'NLG', 'USD'].
../_images/Producer price inflation_61_1.png

Quantamental data of PPI inflation have been negatively related to subsequent swap returns across the major tenors.

xcatx = ["PPIH_SA_P6M6ML6AR", "DU02YXR_NSA"]
cidx = list(
    set(cids_exp)
    - set(["BRL", "CNY", "DEM", "ESP", "FRF", "ITL", "NLG", "PEN", "PHP", "RON"])
)  # missing cids

cr = msp.CategoryRelations(
    dfd,
    xcats=xcatx,
    cids=cidx,
    freq="M",
    lag=1,
    fwin=1,
    xcat_aggs=["last", "sum"],
    xcat_trims=[100, 10],
    start="2000-01-01",
    years=None,
)
cr.reg_scatter(
    title="PPI inflation and subsequent 2-year IRS returns since 2000 across all markets",
    labels=False,
    coef_box="upper right",
    xlab="PPI trend, %6m/6m, ar, seasonally-adjusted, end of month",
    ylab="Next month's 2-year IRS fixed receiver returns",
    prob_est="map",
)
../_images/Producer price inflation_63_0.png

Appendices#

Appendix 3: Feature selection#

def feat_dict_to_pd(feat_dict):
    new_dict={}
    for key in feat_dict:
        new_dict[key] = eval(feat_dict[key])
    feat_dict=new_dict
    features = set()
    for i in feat_dict.values():
        features = features.union(i)
    psf = {}
    order = {}
    feat_list = list(features)
    for key in feat_dict:
        order[key[0:4]]=[True if e in feat_dict[key] else False for e in feat_list]
    df = pd.DataFrame(order,index=feat_list)
    return df
   
features_broad=requests.get("https://feature-selection-broad-prod.s3.eu-west-2.amazonaws.com/feature_selection_broad.json").json()
features_narrow = requests.get("https://feature-selection-narrow-prod.s3.eu-west-2.amazonaws.com/feature_selection_narrow.json").json()

The above github gists have the feature selection information for each cross section and the function easily visualises it, download the notebook to look at each cross section.

The steps to do so are as follows, choose if you want narrow or broad output prices and which cross section you want to visualise. Create the data by calling the function feat_dict_to_pd, with the input ‘features_narrow[“CID”]’ or ‘features_broad[“CID”]’. Then finally use seaborn heatmap to visualise the dataframe.

df = feat_dict_to_pd(features_narrow["USD"])
fig=sns.heatmap(df,cbar=False,cmap=sns.light_palette("#79C"),linecolor='black',linewidths=1,vmin=0)
../_images/Producer price inflation_108_0.png

Appendix 4: Currency symbols#

The word ‘cross-section’ refers to currencies, currency areas or economic areas. In alphabetical order, these are AUD (Australian dollar), BRL (Brazilian real), CAD (Canadian dollar), CHF (Swiss franc), CLP (Chilean peso), CNY (Chinese yuan renminbi), COP (Colombian peso), CZK (Czech Republic koruna), DEM (German mark), ESP (Spanish peseta), EUR (Euro), FRF (French franc), GBP (British pound), HKD (Hong Kong dollar), HUF (Hungarian forint), IDR (Indonesian rupiah), ITL (Italian lira), JPY (Japanese yen), KRW (Korean won), MXN (Mexican peso), MYR (Malaysian ringgit), NLG (Dutch guilder), NOK (Norwegian krone), NZD (New Zealand dollar), PEN (Peruvian sol), PHP (Phillipine peso), PLN (Polish zloty), RON (Romanian leu), RUB (Russian ruble), SEK (Swedish krona), SGD (Singaporean dollar), THB (Thai baht), TRY (Turkish lira), TWD (Taiwanese dollar), USD (U.S. dollar), ZAR (South African rand).