The power of macro trends in rates markets #
This notebook serves as an illustration of the points discussed in the post “The power of macro trends in rates markets” available on the Macrosynergy website. The post highlights the importance of broad macroeconomic trends, such as inflation , economic growth , and credit creation , in influencing shifts in monetary policy. These trends play a crucial role in determining whether monetary policy will lean towards tightening or easing.
The post emphasizes that markets may not always fully anticipate policy shifts that follow macro trends due to a possible lack of attention or conviction. In such cases, macro trends can serve as predictors of returns in fixed-income markets. Even a simple point-in-time macro pressure indicator, which is an average of excess inflation, economic growth, and private credit trends, has exhibited a significant correlation with subsequent interest rate swap returns for 2-year fixed rate receivers in both large and small currency areas.
The post also highlights that considering the gap between real rates and macro trend pressure provides an even higher forward correlation and remarkable directional accuracy in predicting fixed-income returns.
The notebook covers the three main parts:
-
Get Packages and JPMaQS Data: This section is responsible for installing and importing the necessary Python packages used throughout the analysis.
-
Transformations and Checks: In this part, the notebook performs calculations and transformations on the data to derive the relevant signals and targets used for the analysis, including the normalization of feature variables using z-score or building simple linear composite indicators.
-
Value Checks: This is the most critical section, where the notebook calculates and implements the trading strategies based on the hypotheses tested in the post. This section involves backtesting selected trading strategies targeting rates returns. In particular, the post investigates the predictive power of economic growth, inflation and private credit growth on subsequent rates returns.
It is important to note that while the notebook covers a selection of indicators and strategies used for the post’s main findings, users can explore countless other possible indicators and approaches. Users can modify the code to test different hypotheses and strategies based on their research and ideas. Best of luck with your research!
Get packages and JPMaQS data #
This notebook primarily relies on the standard packages available in the Python data science stack. However, there is an additional package,
macrosynergy
that is required for two purposes:
-
Downloading JPMaQS data: The
macrosynergy
package facilitates the retrieval of JPMaQS data, which is used in the notebook. -
For the analysis of quantamental data and value propositions: The
macrosynergy
package provides functionality for performing quick analyses of quantamental data and exploring value propositions.
For detailed information and a comprehensive understanding of the
macrosynergy
package and its functionalities, please refer to the
“Introduction to Macrosynergy package”
notebook on the Macrosynergy Quantamental Academy or visit the following link on
Kaggle
.
# Run only if needed!
"""
%%capture
! pip install macrosynergy --upgrade
"""
'\n%%capture\n! pip install macrosynergy --upgrade\n'
import numpy as np
import pandas as pd
from pandas import Timestamp
import matplotlib.pyplot as plt
import seaborn as sns
import os
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
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
DB(JPMAQS,<cross_section>_<category>,<info>)
, where
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. For more information see
here
The notebook uses the
macrosynergy
package, which supports financial market research and the development of trading strategies based on formats and conventions of the J.P. Morgan Macrosynergy Quantamental System (JPMaQS). For full documentation on
macrosynergy
package check out https://github.com/macrosynergy/macrosynergy or view the notebook on
Kaggle
for examples.
# Quantamental categories of interest
ecos = [
"CPIC_SA_P1M1ML12",
"CPIC_SJA_P3M3ML3AR",
"CPIC_SJA_P6M6ML6AR",
"CPIH_SA_P1M1ML12",
"CPIH_SJA_P3M3ML3AR",
"CPIH_SJA_P6M6ML6AR",
"INFTEFF_NSA",
"INTRGDP_NSA_P1M1ML12_3MMA",
"INTRGDPv5Y_NSA_P1M1ML12_3MMA",
"RGDP_SA_P1Q1QL4_20QMA",
"RYLDIRS02Y_NSA",
"PCREDITBN_SJA_P1M1ML12",
]
mkts = [
"DU02YXR_NSA",
"DU02YXR_VT10",
]
xcats = ecos + mkts
The description of each JPMaQS category is available either under Macro Quantamental Academy , JPMorgan Markets (password protected), or on Kaggle (limited set of tickers used in this notebook). In particular, the set used for this notebook is using Consumer price inflation trends , Inflation targets , Intuitive growth estimates , Domestic credit ratios , Long-term GDP growth , Real interest rates , Private credit expansion , Duration returns
# Cross-sections of interest
cids_dm = ["AUD", "CAD", "CHF", "EUR", "GBP", "JPY", "NOK", "SEK", "USD"]
cids_em = [
"CLP",
"COP",
"CZK",
"HUF",
"ILS",
"INR",
"KRW",
"MXN",
"PLN",
"THB",
"TRY",
"TWD",
"ZAR",
]
cids = cids_dm + cids_em
# Download series from J.P. Morgan DataQuery by tickers
tickers = [cid + "_" + xcat for cid in cids for xcat in xcats]
print(f"Maximum number of tickers is {len(tickers)}")
# Download series from J.P. Morgan DataQuery by tickers
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 dq:
df = dq.download(
tickers=tickers,
start_date="2000-01-01",
suppress_warning=True,
metrics=[
"value",
],
show_progress=True,
)
Maximum number of tickers is 308
Downloading data from JPMaQS.
Timestamp UTC: 2024-03-21 15:06:57
Connection successful!
Requesting data: 100%|██████████| 16/16 [00:03<00:00, 4.94it/s]
Downloading data: 100%|██████████| 16/16 [00:08<00:00, 1.98it/s]
Some dates are missing from the downloaded data.
2 out of 6321 dates are missing.
# uncomment if running on Kaggle
"""for dirname, _, filenames in os.walk('/kaggle/input'):
for filename in filenames:
print(os.path.join(dirname, filename))
df = pd.read_csv('../input/fixed-income-returns-and-macro-trends/JPMaQS_Quantamental_Indicators.csv', index_col=0, parse_dates=['real_date'])"""
"for dirname, _, filenames in os.walk('/kaggle/input'):\n for filename in filenames:\n print(os.path.join(dirname, filename))\n \ndf = pd.read_csv('../input/fixed-income-returns-and-macro-trends/JPMaQS_Quantamental_Indicators.csv', index_col=0, parse_dates=['real_date'])"
This example notebook contains a few select categories for a subset of developed and emerging markets: AUD (Australian dollar), CAD (Canadian dollar), CHF (Swiss franc), CLP (Chilean peso), COP (Colombian peso), CZK (Czech Republic koruna), EUR (euro), GBP (British pound), HUF (Hungarian forint), IDR (Indonesian rupiah), ILS (Israeli shekel), INR (Indian rupee), JPY (Japanese yen), KRW (Korean won), MXN (Mexican peso), NOK (Norwegian krone), NZD (New Zealand dollar), PLN (Polish zloty), SEK (Swedish krona), TRY (Turkish lira), TWD (Taiwanese dollar), USD (U.S. dollar) and ZAR (South African rand). The following cell displays the lists of downloaded categories, cross-sections, and displays the first 3 rows of downloaded dataframe.
display(df["xcat"].unique())
display(df["cid"].unique())
df["ticker"] = df["cid"] + "_" + df["xcat"]
df.head(3)
array(['CPIC_SA_P1M1ML12', 'CPIC_SJA_P3M3ML3AR', 'CPIC_SJA_P6M6ML6AR',
'CPIH_SA_P1M1ML12', 'CPIH_SJA_P3M3ML3AR', 'CPIH_SJA_P6M6ML6AR',
'INFTEFF_NSA', 'INTRGDP_NSA_P1M1ML12_3MMA',
'INTRGDPv5Y_NSA_P1M1ML12_3MMA', 'PCREDITBN_SJA_P1M1ML12',
'RGDP_SA_P1Q1QL4_20QMA', 'RYLDIRS02Y_NSA', 'DU02YXR_NSA',
'DU02YXR_VT10'], dtype=object)
array(['AUD', 'CAD', 'CHF', 'CLP', 'COP', 'CZK', 'EUR', 'GBP', 'HUF',
'ILS', 'INR', 'JPY', 'KRW', 'MXN', 'NOK', 'PLN', 'SEK', 'THB',
'TRY', 'TWD', 'USD', 'ZAR'], dtype=object)
real_date | cid | xcat | value | ticker | |
---|---|---|---|---|---|
0 | 2000-01-03 | AUD | CPIC_SA_P1M1ML12 | 1.244168 | AUD_CPIC_SA_P1M1ML12 |
1 | 2000-01-03 | AUD | CPIC_SJA_P3M3ML3AR | 3.006383 | AUD_CPIC_SJA_P3M3ML3AR |
2 | 2000-01-03 | AUD | CPIC_SJA_P6M6ML6AR | 1.428580 | AUD_CPIC_SJA_P6M6ML6AR |
Availability #
It is important to assess data availability before conducting any analysis. It allows for the identification of any potential gaps or limitations in the dataset, which can impact the validity and reliability of analysis and ensure that a sufficient number of observations for each selected category and cross-section is available and determining the appropriate time periods for analysis. The function
missing_in_df()
simply displays (1) categories that are missing across all expected cross-sections for a given category name list and (2) cross-sections that are missing within a category.
msm.missing_in_df(df, xcats=xcats, cids=cids)
Missing xcats across df: []
Missing cids for CPIC_SA_P1M1ML12: []
Missing cids for CPIC_SJA_P3M3ML3AR: []
Missing cids for CPIC_SJA_P6M6ML6AR: []
Missing cids for CPIH_SA_P1M1ML12: []
Missing cids for CPIH_SJA_P3M3ML3AR: []
Missing cids for CPIH_SJA_P6M6ML6AR: []
Missing cids for DU02YXR_NSA: []
Missing cids for DU02YXR_VT10: []
Missing cids for INFTEFF_NSA: []
Missing cids for INTRGDP_NSA_P1M1ML12_3MMA: []
Missing cids for INTRGDPv5Y_NSA_P1M1ML12_3MMA: []
Missing cids for PCREDITBN_SJA_P1M1ML12: []
Missing cids for RGDP_SA_P1Q1QL4_20QMA: []
Missing cids for RYLDIRS02Y_NSA: []
Blacklist dictionary #
Before conducting any analysis, we take out bad data return periods for fixed-income markets. To check the rationality behind this removal, please see the period of non-tradability for TRY. We create a dictionary of the non-tradable period
dublack
for
TRY
and pass this dictionary to
macrosynergy
functions that exclude the blacklisted periods from related analyses.
msp.view_timelines(
df,
xcats=["DU02YXR_VT10"],
cids=["TRY"],
cumsum=False,
start="2020-01-01",
same_y=False,
all_xticks=True,
title="Duration return for 10% vol target: 2-year maturity, TRY",
)
dublack = {"TRY": (Timestamp("2020-01-01 00:00:00"), Timestamp("2100-01-01 00:00:00"))}
Transformations and checks #
Features (explanatory variables) of the analysis #
The basic hypothesis is that excess growth and inflation significantly shape the trend in real and nominal interest rates at all maturities. Positive excesses put upward pressure on rates while negative excesses (shortfalls) exert downward pressure. We call this pressure abstractly “macro trend pressure”. This pressure is unlikely to be fully priced in the market for lack of attention or conviction. In practice, financial markets often neglect the fundamental gravity of rates for the sake of abstract factors, such as carry, and risk management. Below example is a snippet of simple predictions that can be done with selected JPMaQs indicators.
Excess growth #
Excess growth is a ready-made category available in JPMaQS dataset. It is defined as Latest estimated “intuitive” GDP growth trend, % over a year ago, 3-month moving average minus a long-term median of that country’s actual GDP growth rate at that time: based on 5 year lookback of the latter. Please see full documentation
here
. For a description of the estimation of intuitive GDP growth see
here
. Here we simply plot ranges and timelines of the Excess Growth indicator with the help of
view_ranges()
and
view_timelines()
from the
macrosynergy
package:
xcatx = ["INTRGDPv5Y_NSA_P1M1ML12_3MMA"]
msp.view_ranges(
df,
cids=cids,
xcats=xcatx,
size=(12, 6),
kind="bar",
sort_cids_by="mean",
ylab="% daily rate",
start="2000-01-01",
)
msp.view_timelines(
df,
xcats=xcatx,
cids=cids,
ncol=4,
cumsum=False,
start="2000-01-01",
same_y=False,
all_xticks=False,
title="Latest economic growth trend (intuitive quantamental measure) in excess of 5-year median, % oya, 3-month average",
)
Excess inflation #
In this notebook, excess inflation is defined as the difference between the recorded seasonally and jump-adjusted inflation trend and the effective inflation target (
INFTEFF_NSA
). The resulting indicator receives postfix
vIET
. This operation is performed with the help of
panel_calculator()
function from the
macrosynergy.panel
module
See below for calculate of plausible metrics of excess inflation versus a country’s effective inflation target.
# Preparation: for relative target deviations, we need denominator bases that should never be less than 2
dfa = msp.panel_calculator(df, ["INFTEBASIS = INFTEFF_NSA.clip(lower=2)"], cids=cids)
df = msm.update_df(df, dfa)
# Calculate absolute and relative target deviations for a range of CPI inflation metrics
infs = [
"CPIH_SA_P1M1ML12",
"CPIH_SJA_P6M6ML6AR",
"CPIH_SJA_P3M3ML3AR",
"CPIC_SA_P1M1ML12",
"CPIC_SJA_P6M6ML6AR",
"CPIC_SJA_P3M3ML3AR",
]
for inf in infs:
calcs = [
f"{inf}vIET = ( {inf} - INFTEFF_NSA )",
f"{inf}vIETR = ( {inf} - INFTEFF_NSA ) / INFTEBASIS",
]
dfa = msp.panel_calculator(df, calcs=calcs, cids=cids)
df = msm.update_df(df, dfa)
# Average excess inflation metrics across three different standard horizons
calcs = []
for cp in ["CPIH", "CPIC"]:
for v in ["vIET", "vIETR"]:
calc = f"{cp}_SA_PALL{v} = ( {cp}_SA_P1M1ML12{v} + {cp}_SJA_P6M6ML6AR{v} + {cp}_SJA_P3M3ML3AR{v} ) / 3"
calcs += [calc]
dfa = msp.panel_calculator(df, calcs, cids=cids)
df = msm.update_df(df, dfa)
Here we simply plot newly created excess inflation indicators with the help of
view_timelines()
from the
macrosynergy
package:
xcatx = ["CPIH_SA_P1M1ML12vIET",
"CPIH_SJA_P6M6ML6ARvIET", "CPIH_SJA_P3M3ML3ARvIET"]
msp.view_timelines(
df,
xcats=xcatx,
cids=cids,
ncol=4,
cumsum=False,
start="2000-01-01",
same_y=False,
all_xticks=False,
title="CPI inflation rates, %ar, versus effective inflation target, market information state",
xcat_labels=["% over a year ago", "% 6m/6m, saar", "% 3m/3m, saar"],
)
Excess credit growth #
Similar to excess inflation, excess credit growth metrics require transformations and a neutral benchmark. A neutral benchmark here serves as a medium-term nominal GDP growth estimate, calculated as the sum of the past 5-years’ growth and the effective estimated inflation target. As before,
panel_calculator()
function from the
macrosynergy.panel
module is used to create new time series
dfa = msp.panel_calculator(
df,
["PCBASIS = INFTEFF_NSA + RGDP_SA_P1Q1QL4_20QMA"],
cids=cids,
)
df = msm.update_df(df, dfa)
pcgs = [
"PCREDITBN_SJA_P1M1ML12",
]
for pcg in pcgs:
calc_pcx = f"{pcg}vLTB = {pcg} - PCBASIS "
dfa = msp.panel_calculator(df, calcs=[calc_pcx], cids=cids)
df = msm.update_df(df, dfa)
Excess ratios based on expansion relative to GDP and a nominal GDP benchmark are not plausible metrics for the central bank because the initial leverage of the economy strongly affects the expansion rate and must plausibly be considered for the benchmark. Put economically, countries with bank leverage will always produce low rates versus a nominal GDP growth benchmark and part of that shortfall may reflect other channels of leverage outside the banking system, just as the credit markets in the U.S. The macrosynergy package provides two useful functions,
view_ranges()
and
view_timelines()
, which assist in plotting means, standard deviations, and time series of the chosen indicators.
xcatx = ["PCREDITBN_SJA_P1M1ML12vLTB"]
msp.view_timelines(
df,
xcats=xcatx,
cids=cids,
ncol=4,
cumsum=False,
start="2000-01-01",
same_y=False,
all_xticks=True,
title="Private credit growth, %oya, relative to the sum of inflation target and long-term growth, market information state",
)
Composite macro trend pressures and rate-pressure gaps #
For the empirical analysis, we add excess inflation, economic growth, and private credit to arrive at a primitive composite macro trend pressure indicator. This is undoubtedly not the optimal or most plausible way to combine the three components, as most central banks would put greater emphasis on inflation than on credit growth. However, simplicity trumps sophistication to deliver a proof of concept that is not suspect of data mining. This indicator measures the pressure for monetary tightening (positive values) or easing (negative values).
Using the
macrosynergy
package, we can visualize the newly created indicator.
calcs = [
"XGHI = ( INTRGDPv5Y_NSA_P1M1ML12_3MMA + CPIH_SJA_P6M6ML6ARvIET ) / 2",
"XGHIPC = ( INTRGDPv5Y_NSA_P1M1ML12_3MMA + CPIH_SJA_P6M6ML6ARvIET + PCREDITBN_SJA_P1M1ML12vLTB ) / 3",
]
dfa = msp.panel_calculator(df, calcs, cids=cids)
df = msm.update_df(df, dfa)
xcatx = ["XGHIPC", "XGHI"]
msp.view_timelines(
df,
xcats=xcatx,
cids=cids,
ncol=4,
cumsum=False,
start="2000-01-01",
same_y=False,
all_xticks=False,
title="Composite macro trend pressure, % ar, in excess of benchmarks",
)
Rate-pressure gaps #
For the analysis below, we merely subtract the composite macro trend pressure from the real interest rate, as both are denominated in % annualized. A large positive gap means that the real rate is high and the macro trend pressure is small or negative. This should bias policy towards easing and be positive for subsequent receiver returns. Over the past 20 years, rate pressure gaps show both cyclical and longer-term dynamics. Time series for some EM countries start later, due to the limited availability of swap yield data.
calcs = [
"RPG = RYLDIRS02Y_NSA - XGHI"
]
dfa = msp.panel_calculator(df, calcs, cids=cids)
df = msm.update_df(df, dfa)
xcatx = ["RPG"]
msp.view_timelines(
df,
xcats=xcatx,
cids=cids,
ncol=4,
cumsum=False,
start="2000-01-01",
same_y=False,
all_xticks=False,
title="Composite rate pressure gap, (real 2-year yield minus macro trend pressure)",
)
Target #
The target returns of the analysis below are fixed receiver positions in 2-year interest swaps, targeted at 10% volatility to allow comparable risk-taking across all currency areas. Outperformance or underperformance of countries has often been persistent over months or even years.
xcats_sel = ["DU02YXR_VT10"]
msp.view_timelines(
df,
xcats=xcats_sel,
cids=cids,
ncol=4,
cumsum=True,
start="2000-01-01",
same_y=False,
all_xticks=False,
title="Duration return, in % of notional: 2-year maturity ",
xcat_labels=None,
)
Value checks #
In this part of the analysis, the notebook calculates the naive PnLs (Profit and Loss) for macro demand-based rates strategies using the previously derived demand indicators. The PnLs are calculated based on simple trading strategies that utilize the yields as signals (no regression is involved). The strategies involve going long (buying) or short (selling) of rates positions based purely on the direction of the score signals.
To evaluate the performance of these strategies, the notebook computes various metrics and ratios, including:
-
Correlation: Measures the relationship between the macro pressure and rate-pressure gaps on consequent bond returns. Positive correlations indicate that the strategy moves in the same direction as the market, while negative correlations indicate an opposite movement.
-
Accuracy Metrics: These metrics assess the accuracy of the macro and rate gap indicaror based strategies in predicting market movements. Common accuracy metrics include accuracy rate, balanced accuracy, precision, etc.
-
Performance Ratios: Various performance ratios, such as Sharpe ratio, Sortino ratio, Max draws, etc.
This notebook investigates two hypotheses:
-
Predictive power of simple macro trend pressure on subsequent IRS receiver returns, and
-
Predictive power of rate-pressure gaps on subsequent IRS receiver returns.
The post investigates these relationships separately for the large currency areas (EUR and USD) and for the rest of the available currencies. The rationale behind this distinction is that the idea that the local markets depend disproportionately on returns in the U.S. and the Euro area, and that we would expect that the predictive power of country-specific macro trends for smaller countries should be less, cet. par., , for smaller countries than for larger countries.
It is important to note that the analysis deliberately disregards transaction costs and risk management considerations. This is done to provide a more straightforward comparison of the strategies’ raw performance without the additional complexity introduced by transaction costs and risk management, which can vary based on trading size, institutional rules, and regulations.
Simple macro trend pressure and IRS receiver returns. U.S. and the euro area #
In the large currency areas, there has been significant predictive power of macro trend pressure on subsequent interest rate swap receiver returns. A sizeable negative correlation of 10% or more prevailed in both countries and across the decades of the sample. The negative correlation has been driven mainly by excess growth and inflation, in accordance with the intuition of the popular
Taylor
rule
. With the help of
CategoryRelations()
we can quickly visualize and analyze two categories (signal and target). We define the lag and frequency (monthly in our case)
crm = msp.CategoryRelations(
df,
xcats=["XGHIPC", "DU02YXR_VT10"],
cids=["USD", "EUR"],
freq="M",
lag=1,
xcat_aggs=["last", "sum"],
start="2000-01-01",
xcat_trims=[None, None],
)
crm.reg_scatter(
labels=False,
coef_box="lower right",
title="US and Euro area: macro pressure and subsequent monthly IRS returns",
xlab="Average of excess inflation, economic growth, and excess credit growth, %ar, end-of-month information state",
ylab="2-year interest rate swap receiver returns over the next month, %",
)
It is useful to check if the diagnosed relation has been stable over time. Relevant subperiods are decades in our case. Indeed, the correlation has been negative in both the 2000s and the 2010s and early 2020s.
crm.reg_scatter(
labels=False,
coef_box="upper left",
title="Main markets: Fixed Income trend-adjusted macro trend and subsequent monthly IRS returns, 2002-2021",
xlab=None,
ylab=None,
size=(12, 8),
separator=2010,
)
The
SignalReturnRelations
class of the
macrosynergy
package facilitates a quick assessment of the power of a signal category in predicting the direction of subsequent returns for data in JPMaQS format.
Accuracy (rate of correctly predicted market return direction) has been very high by the standards of macro trading signals over the past 22 years, at 56-58% at a monthly frequency. The same is true for balanced accuracy, which reflects that the macro trend signal predicted both positive and negative returns well. In fact, the ratio of correctly predicted positive returns was over 60% and the ratio of positively predicted negative returns was over 52%.
xcats_sel = ["XGHIPC", "DU02YXR_VT10"]
srr = mss.SignalReturnRelations(
df,
cids=["USD", "EUR"],
sigs=xcats_sel[0],
sig_neg=[True], # use the negative of signal category
rets=xcats_sel[1],
freqs="M",
start="2000-01-01",
)
srr.cross_section_table()
accuracy | bal_accuracy | pos_sigr | pos_retr | pos_prec | neg_prec | pearson | pearson_pval | kendall | kendall_pval | auc | |
---|---|---|---|---|---|---|---|---|---|---|---|
Panel | 0.567 | 0.573 | 0.432 | 0.534 | 0.617 | 0.528 | 0.155 | 0.000 | 0.122 | 0.000 | 0.572 |
Mean | 0.567 | 0.576 | 0.437 | 0.534 | 0.620 | 0.532 | 0.165 | 0.050 | 0.113 | 0.017 | 0.571 |
PosRatio | 1.000 | 1.000 | 0.500 | 1.000 | 1.000 | 1.000 | 1.000 | 1.000 | 1.000 | 1.000 | 1.000 |
EUR | 0.579 | 0.576 | 0.553 | 0.538 | 0.605 | 0.546 | 0.233 | 0.000 | 0.143 | 0.001 | 0.575 |
USD | 0.555 | 0.576 | 0.321 | 0.531 | 0.634 | 0.518 | 0.097 | 0.101 | 0.084 | 0.033 | 0.567 |
Here is a brief explanations of the
.summary_table()
outputs of the
SignalReturnRelations
:
srr.accuracy_bars()
start_date = "2000-01-01"
end_date = "2023-01-01"
sigs = ["XGHIPC", "XGHI"]
naive_pnl = msn.NaivePnL(
df,
ret="DU02YXR_VT10",
sigs=sigs,
cids=["USD", "EUR"],
start=start_date,
end=end_date,
)
for sig in sigs:
naive_pnl.make_pnl(
sig,
sig_neg=True,
sig_op="zn_score_pan",
thresh=2,
rebal_freq="monthly",
vol_scale=10,
rebal_slip=1,
pnl_name=sig + "_PZN",
)
The
signal_heatmap()
method is used to create a heatmap of signals for a specific PnL across time and sections.
naive_pnl.signal_heatmap(pnl_name=sigs[0] + "_PZN")
Simple macro trend pressure and IRS receiver returns. Other currency areas #
The correlation between macro trend pressure on rates and subsequent swap receiver returns has also been negative in the 16 smaller currency areas. The Pearson correlation coefficient has been a bit smaller but owing to the abundance of data, its conventional significance statistic has been near 100%.
The predictive power of country-specific macro trends should, all other things equal, be less for small countries, as their local markets depend disproportionately on returns in the U.S. and the euro area. Efficient prediction of small countries’ fixed income returns based on macro trends should use both local macro trends and those of the big two countries.
cids_small=list(set(cids)-set(["USD", "EUR"]))
crm = msp.CategoryRelations(
df,
xcats=["XGHIPC", "DU02YXR_VT10"],
cids=cids_small,
freq="M",
lag=1,
xcat_aggs=["last", "sum"],
blacklist=dublack,
start="2000-01-01",
xcat_trims=[None, None],
)
crm.reg_scatter(
labels=False,
coef_box="lower right",
title="Smaller countries: macro pressure and subsequent monthly IRS returns",
xlab="Average of excess inflation, economic growth, and private credit growth, %ar, end-of-month information state",
ylab="2-year interest rate swap receiver returns over the next month, %",
)
Accuracy has been materially lower when predicting local swap returns with local macro trend pressures alone in the smaller currency areas. This plausibly reflects two effects. First, as mentioned above, returns in smaller countries are disproportionately affected by the macro trends and other return factors of the large currency areas. Thus, U.S. markets strongly influence Australia, but not the other way round, Second, benchmarks for excess inflation and growth are much harder to estimate for some of the smaller countries for lack of data, particularly in the early part of the 2000s.
xcats_sel = ["XGHIPC", "DU02YXR_NSA"]
srr = mss.SignalReturnRelations(
df,
cids=cids_small,
sigs=xcats_sel[0],
sig_neg=[True], # use the negative of signal category
rets=xcats_sel[1],
blacklist=dublack,
freqs="M",
start="2000-01-01",
)
srr.cross_section_table()
accuracy | bal_accuracy | pos_sigr | pos_retr | pos_prec | neg_prec | pearson | pearson_pval | kendall | kendall_pval | auc | |
---|---|---|---|---|---|---|---|---|---|---|---|
Panel | 0.524 | 0.532 | 0.399 | 0.536 | 0.575 | 0.490 | 0.076 | 0.000 | 0.063 | 0.000 | 0.531 |
Mean | 0.522 | 0.534 | 0.392 | 0.536 | 0.578 | 0.491 | 0.084 | 0.303 | 0.070 | 0.194 | 0.529 |
PosRatio | 0.800 | 0.850 | 0.250 | 0.950 | 0.950 | 0.300 | 0.950 | 0.800 | 0.950 | 0.900 | 0.850 |
AUD | 0.484 | 0.507 | 0.304 | 0.557 | 0.566 | 0.447 | 0.015 | 0.800 | 0.055 | 0.174 | 0.506 |
CAD | 0.510 | 0.521 | 0.297 | 0.517 | 0.547 | 0.495 | 0.115 | 0.051 | 0.060 | 0.130 | 0.517 |
CHF | 0.548 | 0.548 | 0.503 | 0.541 | 0.589 | 0.507 | 0.188 | 0.001 | 0.125 | 0.002 | 0.548 |
CLP | 0.478 | 0.488 | 0.266 | 0.527 | 0.509 | 0.467 | 0.014 | 0.837 | 0.027 | 0.568 | 0.491 |
COP | 0.503 | 0.521 | 0.297 | 0.535 | 0.565 | 0.477 | 0.103 | 0.201 | 0.066 | 0.225 | 0.518 |
CZK | 0.522 | 0.536 | 0.304 | 0.522 | 0.571 | 0.500 | 0.103 | 0.119 | 0.069 | 0.119 | 0.530 |
GBP | 0.553 | 0.555 | 0.444 | 0.513 | 0.574 | 0.536 | 0.063 | 0.298 | 0.081 | 0.044 | 0.554 |
HUF | 0.583 | 0.589 | 0.457 | 0.561 | 0.657 | 0.520 | 0.123 | 0.063 | 0.121 | 0.006 | 0.589 |
ILS | 0.502 | 0.493 | 0.553 | 0.584 | 0.579 | 0.408 | 0.136 | 0.045 | 0.041 | 0.372 | 0.493 |
INR | 0.484 | 0.476 | 0.405 | 0.465 | 0.437 | 0.516 | -0.020 | 0.768 | -0.018 | 0.687 | 0.477 |
JPY | 0.572 | 0.548 | 0.728 | 0.576 | 0.602 | 0.494 | 0.046 | 0.438 | 0.060 | 0.126 | 0.539 |
KRW | 0.526 | 0.535 | 0.381 | 0.530 | 0.573 | 0.496 | 0.082 | 0.233 | 0.132 | 0.004 | 0.533 |
MXN | 0.498 | 0.554 | 0.164 | 0.548 | 0.639 | 0.470 | 0.015 | 0.830 | 0.034 | 0.449 | 0.530 |
NOK | 0.510 | 0.522 | 0.234 | 0.510 | 0.544 | 0.500 | 0.114 | 0.053 | 0.085 | 0.031 | 0.516 |
PLN | 0.543 | 0.543 | 0.496 | 0.504 | 0.548 | 0.539 | 0.050 | 0.431 | 0.068 | 0.109 | 0.543 |
SEK | 0.534 | 0.573 | 0.307 | 0.572 | 0.674 | 0.473 | 0.191 | 0.001 | 0.135 | 0.001 | 0.564 |
THB | 0.562 | 0.561 | 0.530 | 0.511 | 0.569 | 0.553 | 0.062 | 0.357 | 0.093 | 0.040 | 0.561 |
TRY | 0.506 | 0.590 | 0.148 | 0.556 | 0.708 | 0.471 | 0.131 | 0.097 | 0.057 | 0.286 | 0.546 |
TWD | 0.512 | 0.506 | 0.567 | 0.544 | 0.549 | 0.462 | 0.088 | 0.201 | 0.041 | 0.367 | 0.506 |
ZAR | 0.515 | 0.518 | 0.453 | 0.536 | 0.556 | 0.480 | 0.070 | 0.245 | 0.059 | 0.145 | 0.518 |
srr.accuracy_bars()
Rate-pressure gaps and IRS receiver returns. U.S. and the euro area #
The correlation of rate-pressure gaps and subsequent returns in the two large currency areas has been positive and significantly stronger than the correlation for macro trend pressure alone.
crm = msp.CategoryRelations(
df,
xcats=["RPG", "DU02YXR_VT10"],
cids=["USD", "EUR"],
freq="M",
lag=1,
xcat_aggs=["last", "sum"],
start="2000-01-01",
xcat_trims=[None, None],
)
crm.reg_scatter(
labels=False,
coef_box="lower right",
title="US and Euro area: macro pressure and subsequent monthly IRS returns",
xlab="Average of excess inflation and economic growth, %ar, end-of-month information state",
ylab="2-year interest rate swap receiver returns over the next month, %",
)
xcats_sel = ["RPG", "DU02YXR_VT10"]
srr = mss.SignalReturnRelations(
df,
cids=["USD", "EUR"],
sigs=xcats_sel[0],
rets=xcats_sel[1],
freqs="M",
start="2000-01-01",
end="2023-01-01"
)
srr.accuracy_bars()
Rate-pressure gaps and IRS receiver returns. Other currency areas #
The predictive power of local rate-pressure gaps has been significantly lower for the 16 smaller currency areas. This difference plausibly reflects the dominant influence of the U.S. and – to a smaller extent – the euro area, as well as differences in data quality.
crm = msp.CategoryRelations(
df,
xcats=["RPG", "DU02YXR_VT10"],
cids=cids_small,
freq="M",
lag=1,
blacklist=dublack,
xcat_aggs=["last", "sum"],
start="2000-01-01",
xcat_trims=[None, None],
)
crm.reg_scatter(
labels=False,
coef_box="lower right",
title="Smaller countries: rate-pressure gap and subsequent monthly IRS returns",
xlab="Real 2-year yield minus average of excess inflation, economic and private credit growth, %ar, end-of-month information state",
ylab="2-year interest rate swap receiver returns over the next month, %",
)
xcats_sel = ["RPG", "DU02YXR_NSA"]
srr = mss.SignalReturnRelations(
df,
cids=cids_small,
sigs=xcats_sel[0],
blacklist=dublack,
rets=xcats_sel[1],
freqs="M",
start="2000-01-01",
)
srr.accuracy_bars()