Private credit expansion #

This category group includes metrics of the expansion of private credit (local convention) with statistical adjustment for jumps and spikes in the series’, based on concurrently available vintages. Adjustments for outliers are particularly important for these series’, due to the occasional distortionary influences of legal and regulatory changes.

Nominal private credit growth #

Ticker : PCREDITBN_SJA_P1M1ML12

Label : Private bank credit, jump-adjusted, % over a year ago.

Definition : Private bank credit at the end of the latest reported month, % change over a year ago, seasonally and jump-adjusted.

Notes :

  • For most countries, the indicator is limited to bank loans to private households and companies.

  • The stock of credit is denominated in local currency.

  • Jump adjustment means that two types of outliers are adjusted for. Firstly, large “spikes” (i.e. two subsequent large moves of the seasonal index in opposite directions) are averaged. Holiday patterns or tax effects can be responsible for these spikes. Secondly, large one-off jumps in the index are replaced by the local trend. The causes of one-off jumps can be balance sheet restructurings in the banking systems and tax-related asset shifts. The criteria for adjustments are statistical, based on pattern recognition.

Private credit expansion as % of GDP #

Ticker : PCREDITGDP_SJA_D1M1ML12

Label : Banks’ private credit expansion, jump-adjusted, as % of GDP over 1 year.

Definition : Change of private credit over 1 year ago, seasonally and jump-adjusted, as % of nominal GDP (1-year moving average) in the base period.

Notes :

  • For most countries, the indicator is limited to bank loans to private households and companies.

  • The expansion is calculated as the change in the stock of bank credit over the latest reported 12 months as a % of the annual nominal GDP at the beginning of this 12 month period.

  • Jump adjustment means that two types of outliers are adjusted for. Firstly, large “spikes” (i.e. two subsequent large moves of the seasonal index in opposite directions) are averaged. Holiday patterns or tax effects can be responsible for these spikes. Secondly, large one-off jumps in the index are replaced by the local trend. The causes of one-off jumps can be balance sheet restructurings in the banking systems and tax-related asset shifts. The criteria for adjustments are statistical, based on pattern recognition.

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

main = ["PCREDITBN_SJA_P1M1ML12", "PCREDITGDP_SJA_D1M1ML12"]
econ = ["NIR_NSA", "RIR_NSA"]  # economic context
mark = [
    "EQXR_NSA",
    "EQXR_VT10",
    "FXXR_NSA",
    "DU05YXR_NSA",
    "DU05YXR_VT10",
    "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()
    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 418
Downloading data from JPMaQS.
Timestamp UTC:  2023-07-20 08:53:42
Connection successful!
Number of expressions requested: 1672
Requesting data: 100%|█████████████████████████████████████████████████████████████████| 84/84 [00:26<00:00,  3.11it/s]
Downloading data: 100%|████████████████████████████████████████████████████████████████| 84/84 [00:45<00:00,  1.87it/s]
Download time from DQ: 0:01:45.784066

Availability #

cids_exp = sorted(
    list(set(cids) - set(cids_dmec + ["ARS", "HKD"]))
)  # cids expected in category panels

Real-time quantamental indicators of private credit expansion are available from the 1990s for most developed markets and some emerging markets. Most EM indicators, however, start 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 1 .

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, 2))

print("Last updated:", date.today())
https://macrosynergy.com/notebooks.build/themes/financial-conditions/_images/4469a1acf494b4e9bb9bfa11f0f4ed7fefe7b3e452b40b0a8db89e04413c9693.png
Last updated: 2023-07-20
plot = msm.check_availability(
    dfd, xcats=xcatx, cids=cidx, start_size=(18, 2), start_years=False, start=start_date
)
https://macrosynergy.com/notebooks.build/themes/financial-conditions/_images/b5291ce6b76e2c3c1ad160987af26ab3fff340ff37a9e56a1ab13f5fef5ffc9b.png

Average grades are currently quite mixed across countries and times, with USD the only cross-section with high grade vintages consistently available across indicators.

plot = msp.heatmap_grades(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start=start_date,
    size=(18, 2),
    title=f"Average vintage grades, from {start_date} onwards",
)
https://macrosynergy.com/notebooks.build/themes/financial-conditions/_images/c4580771368bd99cdc7712780d8a54b1b5aa8951cdff118b2e9ba68cc93208f9.png
xcatx = main
msp.view_ranges(
    dfd,
    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)",
    start="2000-01-01",
    kind="box",
    size=(16, 4),
)
msp.view_ranges(
    dfd,
    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)",
    start="2000-01-01",
    kind="box",
    size=(16, 4),
)
https://macrosynergy.com/notebooks.build/themes/financial-conditions/_images/ba32430751ba98c05d38bd68952d509a1d951b73a05571b5b54ebf1e486976c4.png https://macrosynergy.com/notebooks.build/themes/financial-conditions/_images/82b5e5962d400ea4cdeec605fe86702756de34f19ee92b8ac9a07027218e9e8e.png

History #

Nominal private credit growth #

Long-term credit growth has been in a range of 5-15% for most countries. Across time, most countries have experienced pronounced fluctuations in private credit expansion, illustrating the importance of boom-bust patterns in credit cycles.

xcatx = ["PCREDITBN_SJA_P1M1ML12"]
cidx = cids_exp

msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    sort_cids_by="mean",
    title="Means and standard deviations of nominal private credit growth since 2000",
    kind="bar",
    start="2000-01-01",
    size=(16, 8),
)
https://macrosynergy.com/notebooks.build/themes/financial-conditions/_images/39cfc483a7019551519a7f1c7631d84403dd94ef80061850dd5392905c8d4139.png
xcatx = ["PCREDITBN_SJA_P1M1ML12"]
cidx = cids_exp

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    title="Private bank credit, % change over a year ago, seasonally and jump-adjusted",
    title_fontsize=27,
    title_xadj=0.5,
    title_adj=1.02,
    ncol=4,
    same_y=False,
    size=(12, 7),
    aspect=1.7,
    all_xticks=True,
)
https://macrosynergy.com/notebooks.build/themes/financial-conditions/_images/42e881f531894333316fc0e17db53d5cbbcc42bd6d264daf392524e3be36dd98.png

Correlations across currency areas have been mostly positive, with a small group of outliers. The outliers have been countries that experienced pronounced idiosyncratic credit cycles: China, Japan, the Philippines, Thailand, Taiwan, and Turkey.

msp.correl_matrix(
    dfd,
    xcats="PCREDITBN_SJA_P1M1ML12",
    cids=cids_exp,
    size=(20, 14),
    title="Cross-sectional correlation of private credit growth, since 2000",
)
https://macrosynergy.com/notebooks.build/themes/financial-conditions/_images/3bb52a2709048e32930b2543f8f5c96ac2abf166a28caa3e58c6f6a768f6cf13.png

Private credit expansion as % of GDP #

Unlike percent growth, credit growth in relation to GDP is strongly influenced by existing leverage. All other things equal, countries with larger financial systems post higher values and greater fluctuations. The order of countries in terms of magnitude of expansion is quite different from the order according to simple nominal credit growth.

xcatx = ["PCREDITGDP_SJA_D1M1ML12"]
cidx = cids_exp

msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    sort_cids_by="mean",
    start="2000-01-01",
    kind="bar",
    size=(16, 8),
    title="Means and standard deviations of private credit expansion, % of GDP, since 2000",
)
https://macrosynergy.com/notebooks.build/themes/financial-conditions/_images/eb8d69cd7b4d77bb44ac8f7bd6db0c306ee17b424ceabfae651422171b53c051.png
xcatx = ["PCREDITGDP_SJA_D1M1ML12"]
cidx = cids_exp

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    title="Private credit expansion over a year ago, as % of GDP",
    title_fontsize=27,
    title_xadj=0.5,
    title_adj=1.02,
    ncol=4,
    same_y=False,
    size=(12, 7),
    aspect=1.7,
    all_xticks=True,
)
https://macrosynergy.com/notebooks.build/themes/financial-conditions/_images/2af3e77bf77fd4127d71e2c6a709e6f7869fab6d4c848408b7dc50cc6bfc811a.png

Importance #

Empirical clues #

Historically, currency areas with high credit growth have delivered higher FX forward returns. The connection plausibly arises from the positive effect of credit growth on local real interest rates.

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 = ["PCREDITBN_SJA_P1M1ML12", "FXXR_NSA"]
cidx = cids_exp

cr_crr = msp.CategoryRelations(
    dfd,
    xcats=xcatx,
    cids=cidx,
    blacklist=fxblack,
    freq="M",
    lag=0,
    xcat_aggs=["mean", "sum"],
    start=start_date,
    years=5,
)

cr_crr.reg_scatter(
    title="Private credit growth and FX returns across 30 countries and 5-year episodes",
    labels=True,
    coef_box="upper right",
    ylab="1-month FX forward return",
    xlab="Domestic private credit, % over a year ago",
    # reg_robust=True,
)
FXXR_NSA misses: ['USD'].
https://macrosynergy.com/notebooks.build/themes/financial-conditions/_images/c93303cce14024a87cc80bce95d51d947d4d5e63c15306eaded120990cac845f.png

There is corresponding evidence of a positive correlation between private credit growth and real interest rates.

xcatx = ["PCREDITBN_SJA_P1M1ML12", "RIR_NSA"]
cidx = cids_exp

cr_crr = msp.CategoryRelations(
    dfd,
    xcats=xcatx,
    cids=cidx,
    blacklist=fxblack,
    freq="M",
    lag=0,
    xcat_aggs=["mean", "mean"],
    start=start_date,
    years=5,
)

cr_crr.reg_scatter(
    title="Private credit growth and real interest rates across 30 countries and 5-year episodes",
    labels=True,
    ylab="1-month real interest rate",
    xlab="Domestic private credit, % over a year ago",
    reg_robust=True,
)
https://macrosynergy.com/notebooks.build/themes/financial-conditions/_images/66db1f23cbd04becf4bc20299b962d02c09cb2299074db0cb3b6ee4854bc153a.png

Strong credit growth increases the probability of policy tightening and thus - all other things equal - bodes ill for long duration positions. Indeed, historically there has been a negative and signficant predictive relation between credit growth z-scores (normalized by country-specific means and standard deviation) and subsequent IRS receiever returns.

# Compute Zn scores
xcatx = ["PCREDITGDP_SJA_D1M1ML12"]
cidx = cids_exp

for xcat in xcatx:
    dfa = msp.make_zn_scores(
        df=dfd,
        cids=list(cidx),
        xcat=xcat,
        start="2000-01-01",
        est_freq="m",
        neutral="mean",
        # thresh=0,
        pan_weight=0,
    )
    dfd = msm.update_df(dfd, dfa)
xcatx = ["PCREDITGDP_SJA_D1M1ML12ZN", "DU05YXR_VT10"]
cidx = list(set(cids_exp) - set(["BRL", "PEN", "PHP", "RON"]))

cr = msp.CategoryRelations(
    dfd,
    xcats=xcatx,
    cids=cidx,
    freq="Q",
    lag=1,
    xcat_aggs=["last", "sum"],
    start="2000-01-01",
    years=None,
    xcat_trims=[100, 100],  # remove invalid outliers
)

cr.reg_scatter(
    title="Private credit growth, z-scores, and next quarter duration returns across all currency areas",
    coef_box="upper left",
    ylab="5-year IRS return, next quarter sum",
    xlab="Domestic private credit expansion, as % of GDP, z-scores based on cross-section, quarter last value",
    prob_est="map",
)
https://macrosynergy.com/notebooks.build/themes/financial-conditions/_images/45cf7e0adfae7dadbde78908ecf078ab918abcc6fd6478053a1a78313f06ba7d.png

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), 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).