Duration returns #
This category group contains daily returns of the most liquid fixed-for-floating interest rate swaps of the currency area for various tenors. For most markets and periods, returns have been calculated based on interest rate swaps. However, non-deliverable swaps have been used for some parts of the history for emerging markets.
We approximate the excess returns of a duration contract of tenor \(m\) (in years) using the following equation:
where \(y_{t,m}^{fix}\) is the fixed rate, \(y_{t}^{float}\) is the underlying floating rate of the contract, and \({DUR}_{t,m}\) is the modified duration of the contract of tenor \(m\) .
Duration return in % of notional #
Ticker : DU02YXR_NSA / DU05YXR_NSA / DU10YXR_NSA
Label : Duration return, in % of notional: 2-year maturity / 5-year maturity / 10-year maturity.
Definition : Return on fixed receiver position in main interest rate swaps contract traded in the currency area, % of notional of the contract, daily roll: 2-year maturity / 5-year maturity / 10-year maturity.
Notes :
-
The interest rate derivative for most currency areas is an interest rate swap. For some emerging market countries (China, India, South Korea, Thailand, Taiwan), non-deliverable swaps have been used.
-
The returns have been approximated as the sums of the yield spread between the fixed and floating leg and the daily change in yield times duration.
-
For the return formula, see the introduction above.
Vol-targeted duration return #
Ticker : DU02YXR_VT10 / DU05YXR_VT10 / DU10YXR_VT10
Label : Duration return for 10% vol target: 2-year maturity / 5-year maturity / 10-year maturity.
Definition : Return on fixed receiver position, % of risk capital on position scaled to 10% (annualized) volatility target, assuming monthly roll: 2-year maturity / 5-year maturity / 10-year maturity
Notes :
-
Positions are scaled to a 10% volatility target based on historic standard deviations for an exponential moving average with a half-life of 11 days. Positions are rebalanced at the end of each month with a constrained maximum leverage (notional to risk capital) of 20.
-
See further the above notes on “Duration return in % of notional” (
DU02YXR_NSA
/DU05YXR_NSA
/DU10YXR_NSA
).
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 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.
# 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)
main = [
"DU02YXR_NSA",
"DU02YXR_VT10",
"DU05YXR_NSA",
"DU05YXR_VT10",
"DU10YXR_NSA",
"DU10YXR_VT10",
]
econ = []
mark = ["DU05YCRY_NSA", "DU05YCRY_VT10", "EQXR_NSA", "EQXR_V10"] # 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,
show_progress=True,
)
end = timer()
dfd = df
print("Download time from DQ: " + str(timedelta(seconds=end - start)))
Maximum number of tickers is 380
Downloading data from JPMaQS.
Timestamp UTC: 2023-09-06 13:13:30
Connection successful!
Number of expressions requested: 1520
Requesting data: 100%|█████████████████████████████████████████████████████████████████| 76/76 [00:24<00:00, 3.15it/s]
Downloading data: 100%|████████████████████████████████████████████████████████████████| 76/76 [00:40<00:00, 1.87it/s]
Download time from DQ: 0:01:22.430663
Availability #
cids_exp = sorted(
list(set(cids) - set(cids_dmec + ["ARS", "PEN", "PHP", "TRY"]))
) # cids expected in category panels
msm.missing_in_df(dfd, xcats=main, cids=cids_exp)
Missing xcats across df: []
Missing cids for DU02YXR_NSA: ['RON', 'BRL']
Missing cids for DU02YXR_VT10: ['RON', 'BRL']
Missing cids for DU05YXR_NSA: ['RON', 'BRL']
Missing cids for DU05YXR_VT10: ['RON', 'BRL']
Missing cids for DU10YXR_NSA: ['RON', 'BRL']
Missing cids for DU10YXR_VT10: ['RON', 'BRL']
Duration returns are available from 2000 for most developed market currencies and some emerging markets. Typically, most emerging market duration return series are available from the mid-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, 4))
print("Last updated:", date.today())
Last updated: 2023-09-06
xcatx = main
cidx = cids_exp
plot = msm.check_availability(
dfd, xcats=xcatx, cids=cidx, start_size=(18, 4), start_years=False
)
xcatx = main
cidx = cids_exp
plot = msp.heatmap_grades(
dfd,
xcats=xcatx,
cids=cidx,
size=(18, 4),
title=f"Average vintage grades from {start_date} onwards",
)
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),
)
History #
2-year duration returns #
Short-term duration returns have displayed very different variances across countries, as well as a pronounced proclivity towards outliers.
xcatx = ["DU02YXR_NSA"]
cidx = cids_exp
msp.view_ranges(
dfd,
xcats=xcatx,
cids=cidx,
sort_cids_by="std",
start="2000-01-01",
kind="box",
title="Boxplots of 2-year duration returns since 2000",
xcat_labels=["2-year duration returns"],
size=(16, 8),
)
Most currency areas’ IRS fixed receiver positions have recorded positive cumulative returns over the past 20 years.
xcatx = ["DU02YXR_NSA"]
cidx = cids_exp
msp.view_timelines(
dfd,
xcats=xcatx,
cids=cidx,
start="2000-01-01",
title="Cumulative duration returns for receivers with 2-year fixed legs",
title_adj=1.03,
title_xadj=0.52,
title_fontsize=27,
legend_fontsize=17,
cumsum=True,
ncol=4,
same_y=False,
size=(12, 7),
aspect=1.7,
all_xticks=True,
)
Correlation coefficients of short-term duration returns have been strongly positive across developed countries. Correlations have been much weaker across EM countries.
xcatx = "DU02YXR_NSA"
cidx = cids_exp
msp.correl_matrix(
dfd,
xcats=xcatx,
cids=cidx,
title="Cross-sectional correlations for 2-year duration returns, since 2000",
size=(20, 14),
cluster=True,
)
5-year and 10-year duration returns #
Differences in return variation have been substantial for longer durations and dwarf the respective mean returns.
xcatx = ["DU05YXR_NSA"]
cidx = cids_exp
msp.view_ranges(
dfd,
xcats=xcatx,
cids=cidx,
sort_cids_by="std",
start="2000-01-01",
kind="box",
title="Boxplots of 5-year duration returns since 2000",
size=(16, 8),
)
xcatx = ["DU10YXR_NSA"]
cidx = cids_exp
msp.view_ranges(
dfd,
xcats=xcatx,
cids=cidx,
sort_cids_by="std",
start="2000-01-01",
kind="box",
title="Boxplots of 10-year duration returns since 2000",
size=(16, 8),
)
Almost all countries posted positive long-term returns over the past 20 years. The exceptions were India, Russia and China.
xcatx = ["DU05YXR_NSA", "DU10YXR_NSA"]
cidx = cids_exp
msp.view_timelines(
dfd,
xcats=xcatx,
cids=cidx,
start="2000-01-01",
title="Cumulative duration returns for receivers with 5-year and 10-year fixed legs",
xcat_labels=["5-year", "10-year"],
title_adj=1.02,
title_xadj=0.48,
title_fontsize=27,
legend_fontsize=17,
label_adj=0.075,
cumsum=True,
ncol=4,
same_y=False,
size=(12, 7),
aspect=1.7,
all_xticks=True,
)
Vol-targeted duration returns #
Volatility targeting evens out differences in return variation across cross-sections, but cannot remove differences in historic kurtosis: some currency areas’ returns have posted fatter tails and, hence, larger outliers.
xcatx = ["DU02YXR_VT10"]
cidx = cids_exp
msp.view_ranges(
dfd,
xcats=xcatx,
cids=cidx,
sort_cids_by="std",
start="2000-01-01",
kind="box",
title="Boxplots of 2-year duration returns, 10% vol-target, since 2000",
xcat_labels=["2-year duration returns, 10% vol-target"],
size=(16, 8),
)
xcatx = ["DU10YXR_VT10"]
cidx = cids_exp
msp.view_ranges(
dfd,
xcats=xcatx,
cids=cidx,
sort_cids_by="std",
start="2000-01-01",
kind="box",
title="Boxplots of 10-year duration returns, 10% vol-target, since 2000",
xcat_labels=["10-year duration returns, 10% vol-target"],
size=(16, 8),
)
For most countries, long-term vol-targeted returns in long durations have outperformed those of shorter durations. This implies that Sharpe ratios of longer-duration positions have been superior over the past 20 years.
xcatx = ["DU02YXR_VT10", "DU10YXR_VT10"]
cidx = cids_exp
msp.view_timelines(
dfd,
xcats=xcatx,
cids=cidx,
start="2000-01-01",
title="Cumulative duration returns for receivers with 2-year and 10-year fixed legs, 10% vol-target",
xcat_labels=["2-year", "10-year"],
cumsum=True,
title_adj=1.03,
title_xadj=0.47,
label_adj=0.075,
title_fontsize=27,
legend_fontsize=17,
ncol=4,
same_y=False,
size=(12, 7),
aspect=1.7,
)
Importance #
Research Links #
“Interest rate swaps trade duration risk across developed and emerging markets. Since 2000 fixed rate receivers have posted positive returns in 26 of 27 markets. Returns have been positively correlated across virtually all countries, even though low yield swaps correlated negatively with global equities and high-yield swaps positively. IRS returns have posted fat tails in all markets, i.e. a greater proclivity to outliers than would be expected from a normal distribution. Active volatility management failed to contain extreme returns. Relative IRS positions across countries can be calibrated based on estimated relative standard deviations and allow setting up more country-specific trades. However, such relative IRS positions have even fatter tails and carry more directional risk. Regression-based hedging goes a long way in reducing directionality, even if risk correlations are circumstantial rather than structural.” Macrosynergy
“Inflation risk premia in the U.S. and the euro area have disappeared or even turned negative since the great financial crisis, according to various studies. There is also evidence that this is not because inflation uncertainty has declined but because the balance of risk has shifted from high inflation problems to deflationary recessions. Put simply, markets pay a premium for bonds and interest rate swap receivers as a hedge against deflation risk rather than demanding a discount for exposure to high inflation risk. This can hold for as long as the expected correlation between economic-financial performance and inflation remains broadly positive.” Macrosynergy
Empirical Clues #
Developed markets have posted negative correlations between cumulative duration and equity returns, possibly due to the dominant influence of economic cycles (which push bond and equity prices in different directions).
xcatx = ["EQXR_NSA", "DU05YXR_NSA"]
cidx = cids_dm
cr = msp.CategoryRelations(
dfd,
xcats=xcatx,
cids=cidx,
freq="M",
lag=0,
xcat_aggs=["sum", "sum"],
start="2000-01-01",
years=None,
)
EQXR_NSA misses: ['NOK', 'NZD'].
DU05YXR_NSA misses: ['DEM', 'ESP', 'FRF', 'ITL', 'NLG'].
cr.reg_scatter(
title="Monthly cumulative duration and equity returns in developed countries since 2000",
labels=False,
coef_box="lower right",
xlab="Monthly equity index future returns",
ylab="Monthly 5-year duration (IRS) returns",
reg_robust=True,
)
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).