FX forward betas #

The category group includes generic estimates of the sensitivity of FX forward returns across currencies to other global returns, particularly to a “global directional risk basket”. The sensitivity is measured by a regression coefficient and often called a “beta”.

FX forward beta with respect to global risk basket #

Ticker : FXXRBETAvGDRB_NSA

Label : FX forward return beta with respect to a global directional risk basket.

Definition : Regression coefficient of FX forward return with respect to a global directional risk basket.

Notes :

  • The benchmark global directional risk basket contains equal volatility-weights in equity index futures, CDS indices and FX forwards.

  • Hedge ratios are calculated based on historical “beta”, i.e. weighted least-squares regression coefficients of past forward returns with respect to global directional risk basket returns. The estimate uses two regressions. The first is based on monthly returns with a 24-month half-life exponentially-weighted lookback. The other is based on daily returns with an exponentially-weighted lookback of 63 trading days. The usage of the two lookbacks seeks to strike a balance between timeliness of information and structural relations.

  • The underlying FX forward returns are calculated for a contract that is long the local currency of the cross section against its dominant traded benchmark. For most currencies, the benchmark is the dollar. For some European currencies (Switzerland, the Czech Republic, Hungary, Norway, Poland, Romania, Sweden) the benchmark is the euro. For Great Britain, Turkey, and Russia, an equally weighted basket of dollars and euros are used.

  • For some currencies, returns include periods of low liquidity and FX targeting. If one wishes to ‘blacklist’ such periods, one should use the non-tradability and FX-target dummmies, which have category ticker codes FXUNTRADABLE_NSA and FXTARGETED_NSA .

  • For more information on the global directional risk basket, see the notes for ‘directional risk basket returns’ ( DRBXR_NSA ).

  • For more information on FX targeting, see the notes for FX tradeability and flexibility .

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 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 os

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_dmec
cids_em = cids_latm + cids_emea + cids_emas
cids = sorted(cids_dm + cids_em)
main = ["FXXRBETAvGDRB_NSA"]
econ = ["FXCRR_NSA", "FXCRR_V10"]  # economic context
mark = [
    "EQXR_NSA",
    "FXXR_NSA",
    "FXXR_VT10",
    "FXXRHvGDRB_NSA",
    "FXTARGETED_NSA",
    "FXUNTRADABLE_NSA",
]  # market links

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

start_date = "2000-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,
    )
    end = timer()

dfd = df

print("Download time from DQ: " + str(timedelta(seconds=end - start)))
Maximum number of tickers is 342
2023-03-27T15:24:49.482912  UTC
Number of expressions requested : 1368
Number of expressions returned  : 980
(Number of unavailable expressions  : 388)
Some expressions were unavailable, and were not returned.
Check logger output for more details.
Number of missing time-series from the Database: 0.
Download time from DQ: 0:01:14.976191

Availability #

cids_exp = sorted(list(set(cids) - set(cids_dmec)))  # cids expected in category panels
msm.missing_in_df(dfd, xcats=main, cids=cids_exp)
Missing xcats across df:  set()
Missing cids for FXXRBETAvGDRB_NSA:  {'USD', 'HKD'}

Quantamental indicators for FX forward betas are available from 2000 for most currency areas. The notable exceptions are Indonesia, Romania and Russia.

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

print("Last updated:", date.today())
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/eff69bf7abd04e3c22e8d6489f5070c528fcc769ef27da1ae600163438e65971.png
Last updated: 2023-03-27
xcatx = main
cidx = cids_exp

plot = msm.check_availability(
    dfd, xcats=xcatx, cids=cidx, start_size=(18, 1), start_years=False
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/3a9363ec8d1d159adcfdbeaafe95cbfae4ece6c430a28629ee4aa075bcb67a67.png
xcatx = main
cidx = cids_exp

plot = msp.heatmap_grades(
    dfd,
    xcats=xcatx,
    cids=cidx,
    size=(18, 1),
    title=f"Average vintage grades from {start_date} onwards",
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/fe3aa1f27156f57045338f2b19fe70e6d635d67834baf1f6a762701dc8dc038a.png
xcatx = main
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, 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)",
    start=start_date,
    kind="box",
    size=(16, 4),
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/9c0b5c8957bc77dceac99261bda70510ded633c6aac1ee1bf39a340f6bbc55dc.png https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/6f265a2f6ebcb356eed465f001313d093db4e6322d29b19c408df42f66e373a4.png

History #

FX forward beta with respect to global risk basket #

Across countries, average FX betas with respect to the risk basket since 2000 have ranged from negative values (in China and Japan) to over 1 (in Brazil and South Africa). Variation has been sizable, but betas have predominantly been positive.

xcatx = ["FXXRBETAvGDRB_NSA"]
cidx = cids_exp

msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    sort_cids_by="mean",
    start=start_date,
    kind="bar",
    title="Means and standard deviations of FX forward betas, with respect to a global directional risk basket, since 2000",
    xcat_labels=["FX forward betas"],
    size=(16, 8),
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/487372fd7211680108b45c2b07688021b80dd0a9ea1a32bff91614e675ee645f.png
xcatx = ["FXXRBETAvGDRB_NSA"]
cidx = cids_exp

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start=start_date,
    title="FX forward betas with respect to a global directional risk basket",
    cumsum=False,
    ncol=4,
    same_y=False,
    size=(12, 7),
    aspect=1.7,
    all_xticks=True,
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/1034d53e1f337d3e2842117e5c386f48a6002c32c31ec275ed2f0d3d6fd0511c.png

Cross-country correlations of estimated betas have predominantly been positive, with the notable exceptions of CHF, JPY and TRY.

msp.correl_matrix(
    dfd,
    xcats="FXXRBETAvGDRB_NSA",
    cids=cids_exp,
    size=(20, 14),
    cluster=True,
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/46e2b940df67150209a399b6c2871b68b051b298b3d63a2fbdfa9a5c74ce8bc1.png

Importance #

Empirical Clues #

There has been a strong and clear positive correlation between FX beta and carry. This suggests that being long currencies with positive real carry typically comes at the price of risk market and that hedging against that directional risk with a basket of assets that is paying carry itself may be quite expensive.

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")
fxblack
{'BRL': (Timestamp('2012-12-03 00:00:00'), Timestamp('2013-09-30 00:00:00')),
 'CHF': (Timestamp('2011-05-02 00:00:00'), Timestamp('2016-06-30 00:00:00')),
 'CNY': (Timestamp('2000-01-03 00:00:00'), Timestamp('2023-03-24 00:00:00')),
 'CZK': (Timestamp('2014-01-01 00:00:00'), Timestamp('2017-07-31 00:00:00')),
 'HKD': (Timestamp('2000-01-03 00:00:00'), Timestamp('2023-03-24 00:00:00')),
 'ILS': (Timestamp('2000-01-03 00:00:00'), Timestamp('2005-12-30 00:00:00')),
 'INR': (Timestamp('2000-01-03 00:00:00'), Timestamp('2004-12-31 00:00:00')),
 'MYR_1': (Timestamp('2000-01-03 00:00:00'), Timestamp('2007-11-30 00:00:00')),
 'MYR_2': (Timestamp('2018-07-02 00:00:00'), Timestamp('2023-03-24 00:00:00')),
 'PEN': (Timestamp('2021-07-01 00:00:00'), Timestamp('2021-07-30 00:00:00')),
 'RON': (Timestamp('2000-01-03 00:00:00'), Timestamp('2005-11-30 00:00:00')),
 'RUB_1': (Timestamp('2000-01-03 00:00:00'), Timestamp('2005-11-30 00:00:00')),
 'RUB_2': (Timestamp('2022-02-01 00:00:00'), Timestamp('2023-03-24 00:00:00')),
 'SGD': (Timestamp('2000-01-03 00:00:00'), Timestamp('2023-03-24 00:00:00')),
 'THB': (Timestamp('2007-01-01 00:00:00'), Timestamp('2008-11-28 00:00:00')),
 'TRY_1': (Timestamp('2000-01-03 00:00:00'), Timestamp('2003-09-30 00:00:00')),
 'TRY_2': (Timestamp('2020-01-01 00:00:00'), Timestamp('2023-03-24 00:00:00'))}
xcatx = ["FXCRR_NSA", "FXXRBETAvGDRB_NSA"]
cidx = list(set(cids_exp) - set(["CNY", "HKD", "USD"]))
cr = msp.CategoryRelations(
    dfd,
    xcats=xcatx,
    cids=cidx,
    freq="M",
    lag=0,
    xcat_aggs=["mean", "mean"],
    blacklist=fxblack,
    start="2000-01-01",
    years=3,
)
cr.reg_scatter(
    title="Real FX carry and FX forward beta (3-year periods across all countries)",
    labels=True,
    xlab="Real FX forward carry",
    ylab="Sensitity of FX forward returns to a global directional risk basket",
    reg_robust=True,
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/3654cad486bda5f8fe16c7d5234b5aa1ff73ec9e72029c7d6a826dd036670057.png

Higher beta has historically been related to higher subsequent returns across currencies. This supports the argument that high sensitivity to global markets is an undesirable feature in investors’ portfolios and commands a risk premium.

xcatx = ["FXXRBETAvGDRB_NSA", "FXXR_NSA"]
cidx = list(set(cids_exp) - set(["CNY", "HKD", "USD", "SGD"]))

cr1 = msp.CategoryRelations(
    dfd,
    xcats=xcatx,
    cids=cidx,
    freq="Q",
    lag=1,
    xcat_aggs=["mean", "sum"],
    # xcat1_chg="diff",
    start="2000-01-01",
    blacklist=fxblack,
    years=None,
)
cr1.reg_scatter(
    title="FX beta (sensitivity to global market risk) and subsequent quarterly returns, global panel since 2000",
    labels=False,
    coef_box="lower right",
    xlab="FX beta, quarter average",
    ylab="Next quarter's 1-month FX forward return",
    prob_est="map",
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/ddbcf81c019101a3901d8ad451b151eda2b215bd7293d55cd2613718f0dfaef8.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).