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 math

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 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 obtain the data. Here tickers is an array of ticker strings, start_date is the first release date to be considered and metrics denotes the types of information requested.

# 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 = ["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)
# Quantamental categories of interest

exps = [
    "EXPORTS_SA_P1M1ML12",
    "EXPORTS_SA_P1M1ML12_3MMA",
    "EXPORTS_SA_P3M3ML3AR",
    "EXPORTS_SA_P6M6ML6AR",
]  # exports
imps = [
    "IMPORTS_SA_P1M1ML12",
    "IMPORTS_SA_P1M1ML12_3MMA",
    "IMPORTS_SA_P3M3ML3AR",
    "IMPORTS_SA_P6M6ML6AR",
]  # imports
mtbs = ["MTBGDPRATIO_SA_3MMA"]  # merchandise trade balances


main = exps + imps + mtbs

econ = ["RIR_NSA"]  # economic context
mark = ["EQXR_NSA", "FXXR_VT10", "DU05YXR_NSA", "DU05YXR_VT10"]  # market links

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

start_date = "1995-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")

with JPMaQSDownload(client_id=client_id, client_secret=client_secret) as downloader:
    start = timer()
    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 518
Downloading data from JPMaQS.
Timestamp UTC:  2024-02-23 12:10:04
Connection successful!
Requesting data: 100%|██████████| 104/104 [00:25<00:00,  4.14it/s]
Downloading data: 100%|██████████| 104/104 [00:24<00:00,  4.17it/s]
Some expressions are missing from the downloaded data. Check logger output for complete list.
384 out of 2072 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 7607 dates are missing.
Download time from DQ: 0:00:57.120816

Availability#

cids_exp = sorted(
    list(set(cids) - set(cids_dmec + ["ARS", "HKD"]))
)  # cids expected in category panels
msm.missing_in_df(dfd, xcats=main, cids=cids_exp)
Missing xcats across df:  []
Missing cids for EXPORTS_SA_P1M1ML12:  []
Missing cids for EXPORTS_SA_P1M1ML12_3MMA:  []
Missing cids for EXPORTS_SA_P3M3ML3AR:  []
Missing cids for EXPORTS_SA_P6M6ML6AR:  []
Missing cids for IMPORTS_SA_P1M1ML12:  []
Missing cids for IMPORTS_SA_P1M1ML12_3MMA:  []
Missing cids for IMPORTS_SA_P3M3ML3AR:  []
Missing cids for IMPORTS_SA_P6M6ML6AR:  []
Missing cids for MTBGDPRATIO_SA_3MMA:  []

Real-time quantamental indicators of foreign trade trends are available by the mid-1990s for most developed countries and many emerging economies. Some countries’ indicators start only in the early 2000s.

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

xcatx = main
cidx = cids_exp

dfx = msm.reduce_df(dfd, xcats=xcatx, cids=cidx)
dfs = msm.check_startyears(dfx)
msm.visual_paneldates(dfs, size=(18, 6))

print("Last updated:", date.today())
../_images/Foreign trade trends_16_0.png
Last updated: 2024-02-23
xcatx = main
cidx = cids_exp

plot = msm.check_availability(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start_size=(18, 6),
    start_years=False,
)
../_images/Foreign trade trends_17_0.png

Vintages are consistently high-graded, with Singapore the notable exception.

xcatx = main
cidx = cids_exp

plot = msp.heatmap_grades(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start=start_date,
    size=(18, 6),
    title=f"Average vintage grades, from {start_date} onwards",
)
../_images/Foreign trade trends_19_0.png
xcatx = [s for s in main if s.startswith("EXPORTS")]
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) for exports",
    start=start_date,
    kind="box",
    size=(16, 4),
)
msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    val="mop_lag",
    title="Median of observation period lags (ranges of time elapsed since middle of observation period in days) for exports",
    start=start_date,
    kind="box",
    size=(16, 4),
)
../_images/Foreign trade trends_20_0.png ../_images/Foreign trade trends_20_1.png
xcatx = [s for s in main if s.startswith("IMPORTS")]
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) for imports",
    start=start_date,
    kind="box",
    size=(16, 4),
)
msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    val="mop_lag",
    title="Median of observation period lags (ranges of time elapsed since middle of observation period in days) for imports",
    start=start_date,
    kind="box",
    size=(16, 4),
)
../_images/Foreign trade trends_21_0.png ../_images/Foreign trade trends_21_1.png
xcatx = [s for s in main if s.startswith("MTB")]
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) for merchandise trade balances",
    start=start_date,
    kind="box",
    size=(16, 4),
)
msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    val="mop_lag",
    title="Median of observation period lags (ranges of time elapsed since middle of observation period in days) for merchandise trade balances",
    start=start_date,
    kind="box",
    size=(16, 4),
)
../_images/Foreign trade trends_22_0.png ../_images/Foreign trade trends_22_1.png

History#

Annual export growth#

Average annual export growth has ranged from less than 5% (for a range of developed markets) to near 40% (for some emerging economies) since 1995. Since the growth rate is recorded in local currency, high-inflation countries tend to record higher long-term average growth rates.

xcatx = ["EXPORTS_SA_P1M1ML12", "EXPORTS_SA_P1M1ML12_3MMA"]
cidx = cids_exp
start_date = "1995-01-01"

msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    sort_cids_by="mean",
    start="1995-01-01",
    title="Means and standard deviations of annual merchandise export trends since 1995",
    xcat_labels=[
        "% over a year ago, seasonally-adjusted",
        "3-month moving average, % over a year ago, seasonally-adjusted",
    ],
    kind="bar",
    size=(16, 8),
)
../_images/Foreign trade trends_26_0.png

Periods of very high local-currency export growth have often been associated with currency depreciation, such as in Brazil and Indonesia in the 1990s.

xcatx = ["EXPORTS_SA_P1M1ML12", "EXPORTS_SA_P1M1ML12_3MMA"]
cidx = cids_exp
start_date = "1995-01-01"

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start=start_date,
    title="Exports growth, seasonally-adjusted, % over a year ago and 3-month moving average thereof",
    title_fontsize=27,
    legend_fontsize=17,
    label_adj=0.075,
    xcat_labels=["% over a year ago", "3-month moving average, % over a year ago"],
    ncol=4,
    same_y=False,
    size=(12, 7),
    aspect=1.7,
    all_xticks=True,
)
../_images/Foreign trade trends_28_0.png

For many countries, the disruptions caused by the COVID-19 pandemic led to unprecedented outliers in 2020/21, with asymmetric effects on the growth rates, because rebounds were calculated based on an extraordinarily low base.

xcatx = ["EXPORTS_SA_P1M1ML12", "EXPORTS_SA_P1M1ML12_3MMA"]
cidx = ["ZAR"]
start_date = "1995-01-01"

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start=start_date,
    title=f"{cidx[0]}: Exports growth, seasonally-adjusted, % over a year ago and 3-month moving average thereof",
    title_adj=1.03,
    xcat_labels=["% over a year ago", "3-month moving average, % over a year ago"],
    size=(12, 7),
    aspect=1.7,
)
../_images/Foreign trade trends_30_0.png

The real-time indicators of annual export growth have been positively correlated across almost all country pairs.

xcatx = ["EXPORTS_SA_P1M1ML12"]
cidx = cids_exp
start_date = "1995-01-01"

msp.correl_matrix(
    dfd,
    xcats=xcatx,
    cids=cidx,
    size=(20, 14),
    start=start_date,
    title="Cross-sectional correlation of annual exports growth since 1995",
)
../_images/Foreign trade trends_32_0.png

Annual import growth#

Long-term average import growth in local currency has naturally been highest in EM and higher-inflation economies. As for exports, the local currency value had a great bearing on import value growth over time and across countries.

xcatx = ["IMPORTS_SA_P1M1ML12", "IMPORTS_SA_P1M1ML12_3MMA"]
cidx = cids_exp
start_date = "1995-01-01"

msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    sort_cids_by="mean",
    start=start_date,
    kind="bar",
    size=(16, 8),
    title="Means and standard deviations of annual merchandise import trends since 1995",
    xcat_labels=[
        "% over a year ago, seasonally-adjusted",
        "3-month moving average, % over a year ago, seasonally-adjusted",
    ],
)
../_images/Foreign trade trends_39_0.png
xcatx = ["IMPORTS_SA_P1M1ML12", "IMPORTS_SA_P1M1ML12_3MMA"]
cidx = cids_exp
start_date = "1995-01-01"

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start=start_date,
    title="Imports growth, seasonally-adjusted, % over a year ago and 3-month moving average thereof",
    title_fontsize=27,
    legend_fontsize=17,
    label_adj=0.075,
    xcat_labels=["% over a year ago", "3-month moving average, % over a year ago"],
    ncol=4,
    same_y=False,
    size=(12, 7),
    aspect=1.7,
    all_xticks=True,
)
../_images/Foreign trade trends_40_0.png

Importance#

Empirical clues#

Stronger short-term local currency export trends have on average been associated with lower subsequent local equity index future returns. This may reflect that high export value growth typically bodes for tighter monetary conditions. The relationship is strongest for developed markets.

xcatx = ["EXPORTS_SA_P6M6ML6AR", "EQXR_NSA"]
cidx = list(set(cids_dm) - set(["DEM", "ESP", "FRF", "ITL", "NLG", "NOK", "NZD"]))
cr = msp.CategoryRelations(
    dfd,
    xcats=xcatx,
    cids=cidx,
    freq="Q",
    lag=1,
    xcat_aggs=["last", "sum"],
    start="2000-01-01",
)

cr.reg_scatter(
    title="Short-term local-currency export trends and subsequent equity index returns, developed markets, since 2000",
    labels=True,
    coef_box="lower right",
    xlab="Exports in local currency, seasonally and calendar-adjusted, %6m/6m ar",
    ylab="Local equity index future returns (next quarter)",
    reg_robust=False,
)
../_images/Foreign trade trends_50_0.png

Strong short-term import trends, which are indicative of stronger nominal local demand, have on average been associated with weaker subsequent duration returns.

xcatx = ["IMPORTS_SA_P6M6ML6AR", "DU05YXR_VT10"]
cidx = cids

cr = msp.CategoryRelations(
    dfd,
    xcats=xcatx,
    cids=list(
        set(cidx)
        - set(["BRL", "DEM", "ESP", "FRF", "HKD", "ITL", "NLG", "PEN", "PHP", "RON"])
    ),
    freq="Q",
    lag=1,
    xcat_aggs=["last", "sum"],
    fwin=1,
    start="2000-01-01",
    xcat_trims=[100, 30],
)

cr.reg_scatter(
    title="Local-currency import trends and subsequent quarterly duration returns since 2000, all liquid markets",
    labels=False,
    coef_box="upper right",
    xlab="Local-currency imports, seasonally and calender-adjusted, %6m/6m ar",
    ylab="5-year IRS return, next quarter",
    reg_robust=True,
)
../_images/Foreign trade trends_52_0.png

Appendices#

Appendix 1: Notes on OECD data integration#

National sources series’ used to construct indicators in this category group are complemented by vintages provided by OECD’s ‘Revision Analysis’ dataset. The integration of the OECD datasets follows the following rules:

  1. The following priority order is applied for combining vintages. First, JPMaQS uses seasonally and calendar adjusted original vintages from national sources. Beyond that JPMaQS uses OECD vintages.

  2. OECD vintages inform on the month of release but not the exact date. Actual release dates for these vintages are estimated based on release days of subsequent vintages.

  3. Inconsistencies, data errors and missing values in the OECD vintages have been corrected for JPMaQS.

OECD data is seasonally adjusted and denominated in native currency. No such consistency is found in national sources. Often figures are stated in a foreign denomination (EUR or USD) and no seasonal adjustment has been applied. To integrate these two data sources we first currency convert national sources into their native currency and then apply seasonal adjustment.

Appendix 2: 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).