Duration carry#

This category contains daily carry of the dominant liquid fixed-for-floating swaps of the currency area for various tenors. Carry has two components: (a) the yield to maturity in excess of the financing cost (“term premium”) and (b) the “roll-down” of the bond across the yield curve as time to maturity is shortening. Unlike for other asset types there is no distinction between real and nominal carry because both the long position and the funding lag are in nominal terms.

We approximate carry of a duration contract of tenor \(m\) (in years) using the following equation:

(1)#\[\begin{equation} {CRY}_{t,m} = y^{fix}_{t, m} - y_{t}^{float} - {DUR}_{t,m} (y_{t,m-1}^{fix} - y_{t,m}^{fix}) \end{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 carry in % of notional#

Ticker: DU02YCRY_NSA / DU05YCRY_NSA / DU10YCRY_NSA

Label: Duration carry, in % of notional: 2-year maturity / 5-year maturity / 10-year maturity.

Definition: Carry on a fixed receiver position in the 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:

  • Duration carry has two components: the term premium and the roll-down of the bond across the yield curve per time.

  • The interest rate derivative for most currency areas is an interest rate swap. For some emerging market countries (China, India, Korea, Thailand, Taiwan) non-deliverable swaps have been used.

  • For the carry formula, see Appendix 1.

Vol-targeted duration carry#

Ticker: DU02YCRY_VT10 / DU05YCRY_VT10 / DU10YCRY_VT10

Label: Duration carry for 10% vol target: 2-year maturity / 5-year maturity / 10-year maturity.

Definition: Carry on fixed receiver position, % of risk capital on position scaled to 10% annualized volatility target, assuming daily roll: 2-year maturity / 5-year maturity / 10-year maturity

Notes:

  • Duration carry has two components: the term premium and the roll-down of the bond across the yield curve per time.

  • The interest rate derivative for most currency areas is an interest rate swap. For some emerging market countries (China, India, Korea, Thailand, TWD), non-deliverable swaps have been used.

  • 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. Leverage (ratio of contract notional to risk capital) is subject to a maximum of 20.

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 = [
    "DU02YCRY_NSA",
    "DU02YCRY_VT10",
    "DU05YCRY_NSA",
    "DU05YCRY_VT10",
    "DU10YCRY_NSA",
    "DU10YCRY_VT10",
]

econ = [
    "RYLDIRS05Y_NSA",
    "BXBGDPRATIO_NSA_12MMA",
    "PCREDITGDP_SJA_D1M1ML12",
]  # economic context
mark = [
    "DU02YXR_NSA",
    "DU02YXR_VT10",
    "DU05YXR_NSA",
    "DU05YXR_VT10",
    "DU10YXR_NSA",
    "DU10YXR_VT10",
]  # 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 570
Downloading data from JPMaQS.
Timestamp UTC:  2023-06-14 10:31:53
Connection successful!
Number of expressions requested: 2280
Requesting data: 100%|███████████████████████████████████████████████████████████████| 114/114 [00:34<00:00,  3.26it/s]
Downloading data: 100%|██████████████████████████████████████████████████████████████| 114/114 [01:08<00:00,  1.66it/s]
Download time from DQ: 0:02:09.018754

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:  set()
Missing cids for DU02YCRY_NSA:  {'RON', 'BRL'}
Missing cids for DU02YCRY_VT10:  {'RON', 'BRL'}
Missing cids for DU05YCRY_NSA:  {'RON', 'BRL'}
Missing cids for DU05YCRY_VT10:  {'RON', 'BRL'}
Missing cids for DU10YCRY_NSA:  {'RON', 'BRL'}
Missing cids for DU10YCRY_VT10:  {'RON', 'BRL'}

Quantamental indicators of duration carry are available from the early 2000s for most developed market currencies and some emerging markets. Typically, most emerging market duration carry 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 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, 4))

print("Last updated:", date.today())
../_images/Duration carry_16_0.png
Last updated: 2023-06-14
xcatx = main
cidx = cids_exp

plot = msm.check_availability(
    dfd, xcats=xcatx, cids=cidx, start_size=(18, 4), start_years=False
)
../_images/Duration carry_17_0.png
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",
)
../_images/Duration carry_18_0.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),
)
../_images/Duration carry_19_0.png ../_images/Duration carry_19_1.png

History#

2-year duration carry#

Short-term carry has predominantly been positive but the standard deviations have been large relative to the absolute averages.

xcatx = ["DU02YCRY_NSA"]
cidx = cids_exp

msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    sort_cids_by="mean",
    start="2000-01-01",
    title="Means and standard deviations of 2-year duration carries since 2000",
    kind="bar",
    size=(16, 8),
)
../_images/Duration carry_23_0.png
xcatx = ["DU02YCRY_NSA"]
cidx = cids_exp

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    title="Duration carry for receivers with 2-year fixed legs",
    title_adj=1.02,
    title_xadj=0.5,
    title_fontsize=27,
    legend_fontsize=17,
    cumsum=False,
    ncol=4,
    same_y=False,
    size=(12, 7),
    aspect=1.7,
    all_xticks=True,
)
../_images/Duration carry_24_0.png

For most countries, cross-sectional correlations of short-term carry have been positive over the last 20 years. However China, Hungary, Indonesia and Russia have displayed pronounced idiosyncratic fluctuations.

xcatx = "DU02YCRY_NSA"
cidx = cids_exp

msp.correl_matrix(
    dfd,
    xcats=xcatx,
    cids=cidx,
    title="Cross-sectional correlations for 2-year duration carries, since 2000",
    size=(20, 14),
)
../_images/Duration carry_26_0.png

5 and 10-year duration carry#

Standard deviations for longer-term carry have typically exceeded absolute carry averages, particularly for higher-yield emerging markets.

xcatx = ["DU05YCRY_NSA", "DU10YCRY_NSA"]
cidx = cids_exp

msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    sort_cids_by="mean",
    start="2000-01-01",
    title="Means and standard deviations of 5-year and 10-year duration carries since 2000",
    xcat_labels=["5-year", "10-year"],
    kind="bar",
    size=(16, 8),
)
../_images/Duration carry_29_0.png
xcatx = ["DU05YCRY_NSA", "DU10YCRY_NSA"]
cidx = cids_exp

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    title="Duration carry for receivers with 5-year and 10-year fixed legs",
    title_adj=1.02,
    title_xadj=0.45,
    title_fontsize=27,
    legend_fontsize=17,
    xcat_labels=["5-year", "10-year"],
    cumsum=False,
    ncol=4,
    same_y=True,
    size=(12, 7),
    aspect=1.7,
    all_xticks=True,
)
../_images/Duration carry_30_0.png

Vol-targeted duration carry#

Duration carry for 10% annualized volatility targets has been predominantly positive and ranged from a negative 50 basis points to a positive 100 basis points.

xcatx = ["DU02YCRY_VT10", "DU05YCRY_VT10"]
msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    title="Duration carry for receivers with 2-year and 5-year fixed legs, 10% vol-target",
    title_adj=1.02,
    title_fontsize=27,
    legend_fontsize=17,
    xcat_labels=["2-year", "5-year"],
    cumsum=False,
    ncol=4,
    same_y=False,
    size=(12, 7),
    aspect=1.7,
    all_xticks=True,
)
../_images/Duration carry_33_0.png

Importance#

Empirical Clues#

On average, periods and countries with higher duration carry have witnessed slightly higher duration returns in the long-term.

xcatx = ["DU05YCRY_NSA", "DU05YXR_NSA"]
cidx = cids_exp

cr = msp.CategoryRelations(
    dfd,
    xcats=xcatx,
    cids=cidx,
    freq="A",
    lag=0,
    xcat_aggs=["mean", "sum"],
    start="2000-01-01",
    years=3,
    xcat_trims=[5, 50],
)
cr.reg_scatter(
    title="Duration carry and returns (5-year tenor) over 3-year periods and countries since 2002",
    labels=True,
    coef_box="lower right",
    xlab="5-year duration carry",
    ylab="quarterly 5-year duration returns",
)
DU05YCRY_NSA misses: ['BRL', 'RON'].
DU05YXR_NSA misses: ['BRL', 'RON'].
../_images/Duration carry_39_1.png

Appendices#

Appendix 1: Calculations#

Given an arbitrary day \(t\) and tenor \(h\), the basic formula for the ‘duration’ carry on a fixed receiver position in the main interest rate swap contract traded in the respective currency area is

\( \text{DU\{h\}CRY_NSA}(t) = y_{\text{fix}}(t, h) - y_{\text{float}}(t) - \text{DUR}(t, h) \times \left [y_{\text{fix}}(t,h-1) - y_{\text{fix}}(t,h) \right ] \)

where

  • \(y_{\text{float}}(t,h)\) is the floating interest rate

  • \(y_{\text{fix}}(t,h)\) is the daily fixing of the contract

  • \(\text{DUR}(t, h)\) is the duration of the underlying contract.

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