Imports #

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

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

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

from macrosynergy.download import JPMaQSDownload

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.

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_em = cids_latm + cids_emea + cids_emas
cids = sorted(cids_dm + cids_em)

cids_g3 = ["EUR", "JPY", "USD"]  # DM large currency areas

# For Equity analyses
cids_dmes = ["AUD", "CAD", "CHF", "GBP", "SEK"]  # Smaller DM equity countries
cids_dmeq = cids_g3 + cids_dmes  # DM equity countries

cids_exp = sorted(list(set(cids)))
main = [
    "POP_NSA_P1Y1YL1",
    "POP_NSA_P1Y1YL1_5YMA",
    "POP_NSA_P1Y1YL1_5YMM",
    "POP_NSA_P1Q1QL4_20QMA",
    "POP_NSA_P1Q1QL4_20QMM",
    "POP_NSA_P1Q1QL4",
    "WFORCE_NSA_P1Y1YL1",
    "WFORCE_NSA_P1Q1QL4",
    "WFORCE_NSA_P1Y1YL1_5YMA",
    "WFORCE_NSA_P1Y1YL1_5YMM",
    "WFORCE_NSA_P1Q1QL4_20QMA",
    "WFORCE_NSA_P1Q1QL4_20QMM",
]
econ = [
    "EMPL_NSA_P1M1ML12_3MMA",
    "EMPL_NSA_P1Q1QL4",
    "WAGES_NSA_P1M1ML12_3MMA",
    "WAGES_NSA_P1Q1QL4",
]  # economic context
mark = [
    "EQXR_NSA",
    "FXTARGETED_NSA",
    "FXUNTRADABLE_NSA",
    "DU02YXR_VT10",
    "DU05YXR_VT10",
    "DU02YXR_NSA",
    "DU05YXR_NSA",
]
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:

    df = downloader.download(
        tickers=tickers,
        start_date=start_date,
        metrics=["all"],
        suppress_warning=True,
        show_progress=True,
    )
Maximum number of tickers is 759
Downloading data from JPMaQS.
Timestamp UTC:  2024-04-29 08:57:33
Connection successful!
Requesting data: 100%|██████████| 152/152 [00:32<00:00,  4.68it/s]
Downloading data: 100%|██████████| 152/152 [00:36<00:00,  4.14it/s]
Some expressions are missing from the downloaded data. Check logger output for complete list.
1196 out of 3036 expressions are missing. To download the catalogue of all available expressions and filter the unavailable expressions, set `get_catalogue=True` in the call to `JPMaQSDownload.download()`.
Some dates are missing from the downloaded data. 
2 out of 8958 dates are missing.

Availability #

Most population trend indicators are available by the early 1990s. Notable exceptions are Hungary and South Korea. In comparison, the work force indicators are generally available from the mid-1990s onwards. China is the only cross-section for which work force statistics are available by 1990.

For the explanation of currency symbols, which are related to currency areas or countries for which categories are available, please view Appendix 1 .

xcatx = main

dfd = msm.reduce_df(df, xcats=xcatx, cids=cids_exp)
dfs = msm.check_startyears(
    dfd,
)
msm.visual_paneldates(dfs, size=(20, 5))
https://macrosynergy.com/notebooks.build/themes/economic-trends/_images/337d448830d9d0573b5b7d2fb191281aaff5600a73d9ca207bd657837fe5a02a.png
plot = msm.check_availability(
    df, xcats=main, cids=cids_exp, start_size=(18, 6), start_years=False
)
https://macrosynergy.com/notebooks.build/themes/economic-trends/_images/c8f60c9d82f20b9123224e75ed5308e184d41ee7707b5ffbccaf7bbe3f164741.png

The vintage grading of employment and work force data is low, as original vintages are not easily accessible and may need to be restored from printed material. These data are not usually watched by markets.

plot = msp.heatmap_grades(
    df,
    xcats=main,
    cids=cids_exp,
    size=(18, 6),
    title=f"Average vintage grades from {start_date} onwards",
)
https://macrosynergy.com/notebooks.build/themes/economic-trends/_images/7246b00b80a1707a1743f925f3aeaa529e784cad39ed668b4737b9b265dc1284.png
xcatx = ["POP_NSA_P1Y1YL1", "WFORCE_NSA_P1Y1YL1"]

msp.view_ranges(
    df,
    xcats=xcatx,
    cids=cids_exp,
    val="eop_lag",
    title="End of observation period lags (ranges of time elapsed since end of observation period in days), population trends",
    start=start_date,
    kind="box",
    size=(16, 8),
)
msp.view_ranges(
    df,
    xcats=xcatx,
    cids=cids_exp,
    val="mop_lag",
    title="Median of observation period lags (ranges of time elapsed since middle of observation period in days), population trends",
    start=start_date,
    kind="box",
    size=(16, 8),
)
https://macrosynergy.com/notebooks.build/themes/economic-trends/_images/c6f91e5c79c7219bc00a8626461409510229b17f3abdaa263564e9faef8120b4.png https://macrosynergy.com/notebooks.build/themes/economic-trends/_images/c5d4b4f81b8f80d064091aef79fd6f1bd3667540489bd658d03d14ebca1b5ac0.png

For the purpose of the below presentation, we have renamed the quarterly-frequency annual growth rates to yearly-frequency annual growth rates in order to have a full panel of similar measures across most countries.

olds = [
    "POP_NSA_P1Q1QL4",
    "POP_NSA_P1Q1QL4_20QMA",
    "WFORCE_NSA_P1Q1QL4",
    "WFORCE_NSA_P1Q1QL4_20QMA",
    "WAGES_NSA_P1Q1QL4",
    "EMPL_NSA_P1Q1QL4",
]
news = [
    "POP_NSA_P1Y1YL1",
    "POP_NSA_P1Y1YL1_5YMA",
    "WFORCE_NSA_P1Y1YL1",
    "WFORCE_NSA_P1Y1YL1_5YMA",
    "WAGES_NSA_P1M1ML12_3MMA",
    "EMPL_NSA_P1M1ML12_3MMA",
]

dfx = df.replace(to_replace=olds, value=news)

scols = ["cid", "xcat", "real_date", "value"]
dfx = dfx[scols].copy().sort_values(["cid", "xcat", "real_date"])
dfx["ticker"] = dfx["cid"] + "_" + dfx["xcat"]

History #

Population growth #

Average population growth has been between 2% and just below -0.5% across the set of relevant markets. Romania, in particular, displays a significant population decline.

xcatx = ["POP_NSA_P1Y1YL1"]

msp.view_ranges(
    df,
    xcats=xcatx,
    cids=cids_exp,
    sort_cids_by="mean",
    title="Means and standard deviations of population growth, %oya",
    start="2000-01-01",
)
https://macrosynergy.com/notebooks.build/themes/economic-trends/_images/330e093e341778948e8a602aa2880cc7323cf63fecafde9220d3f668ac59db64.png
xcatx = ["POP_NSA_P1Y1YL1", "POP_NSA_P1Y1YL1_5YMA"]

msp.view_timelines(
    df,
    xcats=xcatx,
    cids=cids_exp,
    start="2000-01-01",
    title="Latest report population growth trends, %oya, simple and 5-year averages",
    xcat_labels=["% over a year ago", "% over a year ago, 5-year moving average"],
    title_fontsize=27,
    legend_fontsize=17,
    ncol=5,
    same_y=False,
    all_xticks=True,
)
https://macrosynergy.com/notebooks.build/themes/economic-trends/_images/e4c2f7a80c358d7fd3975c2495ab9e74796ba6b071641302971a0722d680169d.png

Work force growth #

While there is an obvious and clear relation between population and workforce growth in the medium term, there are also notable differences. In relative terms, the country with the higher population growth does not always record a more rapid expansion of the workforce. Romania displays both considerable negative population and workforce growth. Majority of other countries on average achieved positive workforce growth since 2000.

xcatx = ["POP_NSA_P1Y1YL1", "WFORCE_NSA_P1Y1YL1"]

msp.view_ranges(
    dfx,
    xcats=xcatx,
    cids=cids_exp,
    sort_cids_by="mean",
    start="2000-01-01",
    title="Means and standard deviations of population and work force trends",
    xcat_labels=[
        "Reported annual population growth",
        "Reported annual workforce growth",
    ],
)
https://macrosynergy.com/notebooks.build/themes/economic-trends/_images/df9fa37f3e71a1de7d4eb655ad9cf392195733f2ef9dfbef3d2a836ba8fa81a4.png
cr_pop_wf = msp.CategoryRelations(
    dfx,
    xcats=["POP_NSA_P1Y1YL1", "WFORCE_NSA_P1Y1YL1"],
    cids=cids,
    freq="Q",
    lag=0,
    xcat_aggs=["last", "last"],
    start="2000-01-01",
)

cr_pop_wf.reg_scatter(
    labels=False,
    coef_box="upper left",
    xlab="Population growth, %oya: latest year",
    ylab="Work force growth, % over a year ago: latest year",
    title="Population growth vs. work force growth, %oya: latest year, since 2000. All available countries",
    size=(8, 4),
    prob_est="map",
)
https://macrosynergy.com/notebooks.build/themes/economic-trends/_images/e5c93651f2f972b10eb9092ca39ac2303ad568427174be3618b261d104e67df7.png

There has been a clear linear relation between average population growth and its standard deviation, across currency areas and years. This suggests high absolute population growth comes with high uncertainty.

dfa = msp.historic_vol(
    dfx, xcat="POP_NSA_P1Y1YL1", cids=cids, lback_meth="xma", postfix="_ASD"
)
dfx = msm.update_df(dfx, dfa)

dfa = msp.historic_vol(
    dfx, xcat="WFORCE_NSA_P1Y1YL1", cids=cids, lback_meth="xma", postfix="_ASD"
)
dfx = msm.update_df(dfx, dfa)


cr_pop_vol = msp.CategoryRelations(
    dfx,
    xcats=["POP_NSA_P1Y1YL1", "POP_NSA_P1Y1YL1_ASD"],
    cids=cids,
    freq="A",
    lag=0,
    xcat_trims=[20, 20],
    xcat_aggs=["mean", "mean"],
    start="2000-01-01",
)

cr_wf_vol = msp.CategoryRelations(
    dfx,
    xcats=["WFORCE_NSA_P1Y1YL1", "WFORCE_NSA_P1Y1YL1_ASD"],
    cids=cids,
    freq="A",
    lag=0,
    xcat_aggs=["mean", "mean"],
    xcat_trims=[20, 20],
    start="2000-01-01",
)

msv.multiple_reg_scatter(
    [cr_pop_vol, cr_wf_vol],
    title="Population and work force growth and their standard deviations across countries and years since 2000",
    xlab="Population/work force growth, % over a year ago",
    ylab="Average annualized standard deviation of population/work force growth",
    figsize=(20, 7),
    prob_est="map",
    fit_reg=False,
    coef_box=None,
    subplot_titles=["Population growth", "Work force growth"],
)
https://macrosynergy.com/notebooks.build/themes/economic-trends/_images/f8d9c799282a468561e11ae36901106fb885b55a957ba0b81e5ef7caf12867ce.png

Annual work force growth can be very volatile from year-to-year. Meaningful trends require multi-year averaging.

xcatx = ["WFORCE_NSA_P1Y1YL1", "WFORCE_NSA_P1Y1YL1_5YMA"]

msp.view_timelines(
    dfx,
    xcats=xcatx,
    cids=cids_exp,
    start="2000-01-01",
    title="Latest reported work force growth trends, %oya, simple and 5-year averages",
    xcat_labels=["%oya", "%oya, 5-year moving average"],
    title_fontsize=27,
    legend_fontsize=17,
    ncol=5,
    same_y=False,
    all_xticks=True,
)
https://macrosynergy.com/notebooks.build/themes/economic-trends/_images/4332ddd7fb9dd0437004f60d080bb8f6a9b23b9f5b04e20c56146144958932dd.png

Since 2015, Thailand has stood out as the one country maintaining a negative correlation with global workforce growth.

msp.correl_matrix(
    dfx,
    xcats="WFORCE_NSA_P1Y1YL1",
    # xcats=["WFORCE_NSA_P1Y1YL1", "POP_NSA_P1Y1YL1"],
    cids=cids,
    title="Cross-sectional correlations of workforce trends, since 2015",
    size=(20, 14),
    start="2015-01-01",
)
https://macrosynergy.com/notebooks.build/themes/economic-trends/_images/cf0932056942eaedefdb49db347e10f7f5bbabb437c94f157c31ce70c6647d84.png

Importance #

Empirical clues #

Appendices #

Appendix 1: 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), INR (Indian rupee), IDR (Indonesian rupiah), ILS (Israeli shekel), 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).