Duration volatility #

This category contains basic generic estimates of annualized standard deviations of IRS positions and related leverage measures. The main purpose of these indicators in JPMaQS is to facilitate the adjustment of other quantamental indicators, factors and returns for volatility.

For most markets and periods, return volatility has been calculated based on interest rate swaps. However, non-deliverable swaps have been used for some of the history of emerging markets.

Duration volatility #

Ticker : DU02YXRxEASD_NSA / DU05YXRxEASD_NSA / DU10YXRxEASD_NSA

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

Definition : Annualized return standard deviation of fixed receiver position in main interest rate swaps contract traded in the currency area, % of notional of the contract: 2-year maturity / 5-year maturity / 10-year maturity.

Notes :

  • The standard deviation has been calculated based on an exponential moving average of daily returns with a half-life of 11 active trading days.

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

Leverage ratio of vol-targeted duration position #

Ticker : DU02YXRxLEV10_NSA / DU05YXRxLEV10_NSA / DU10YXRxLEV10_NSA

Label : Leverage ratio of duration position for 10% annualized vol target: 2-year maturity / 5-year maturity / 10-year maturity.

Definition : IRS leverage ratio for a 10% annualized vol target, as ratio of contract notional relative to risk capital on which the return is calculated (and subject to maximum leverage of 20): 2-year maturity / 5-year maturity / 10-year maturity

Notes :

  • This serves as the leverage ratio for a 10% annualized vol target and is inversely proportional to the estimated annualized standard deviation of the return on a USD1 notional position.

  • An intuitive leverage ratio of 20 (for notional to risk capital) has been applied to simulate capital and risk management limits to leverage.

  • See further the notes above on “Duration volatility” ( DU02YXRxEASD_NSA / DU05YXRxEASD_NSA / DU10YXRxEASD_NSA ).

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

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", "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 = [
    "DU02YXRxEASD_NSA",
    "DU02YXRxLEV10_NSA",
    "DU05YXRxEASD_NSA",
    "DU05YXRxLEV10_NSA",
    "DU10YXRxEASD_NSA",
    "DU10YXRxLEV10_NSA",
]

econ = [
    "RYLDIRS05Y_NSA",
    "BXBGDPRATIO_NSA_12MMA",
    "PCREDITGDP_SJA_D1M1ML12",
]  # economic context
mark = [
    "DU02YCRY_NSA",
    "DU02YCRY_VT10",
    "DU05YCRY_NSA",
    "DU05YCRY_VT10",
    "DU10YCRY_NSA",
    "DU10YCRY_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,
    )
    end = timer()

dfd = df

print("Download time from DQ: " + str(timedelta(seconds=end - start)))
Maximum number of tickers is 555
Downloading data from JPMaQS.
Timestamp UTC:  2024-10-17 16:22:41
Connection successful!
Some expressions are missing from the downloaded data. Check logger output for complete list.
420 out of 2220 expressions are missing. To download the catalogue of all available expressions and filter the unavailable expressions, set `get_catalogue=True` in the call to `JPMaQSDownload.download()`.
Some dates are missing from the downloaded data. 
2 out of 6471 dates are missing.
Download time from DQ: 0:02:17.472400

Availability #

cids_exp = sorted(
    list(set(cids) - set(cids_dmec + ["ARS", "PEN", "PHP"]))
)  # cids expected in category panels
msm.missing_in_df(dfd, xcats=main, cids=cids_exp)
No missing XCATs across DataFrame.
Missing cids for DU02YXRxEASD_NSA:   []
Missing cids for DU02YXRxLEV10_NSA:  []
Missing cids for DU05YXRxEASD_NSA:   []
Missing cids for DU05YXRxLEV10_NSA:  []
Missing cids for DU10YXRxEASD_NSA:   []
Missing cids for DU10YXRxLEV10_NSA:  []

Quantamental indicators of duration volatility are available from the early 2000s for most developed market currencies and some emerging markets. Typically, most emerging market duration volatility 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())
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/d4e460215aa774da55ea53bf81ded4c2aa6af15666d8f66e847e1f52e8e293cb.png
Last updated: 2024-10-17
xcatx = main
cidx = cids_exp

plot = msm.check_availability(
    dfd, xcats=xcatx, cids=cidx, start_size=(18, 4), start_years=False
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/356b8cf3b586c00d765e1ca4168c9161b5c6b3e6cc6ffe19558067a8de1df525.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",
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/b36f5f900ac2bddccda59f0d2407519a5cb3e19ff872895935da606db2d5438d.png
current_date = pd.Timestamp('now').strftime('%Y-%m-%d')
try_na = ["2020-02-24", "2024-08-01"]
black = {"TRY": try_na}  # Restricted Tradability of Russia
dfd = msm.reduce_df(dfd, blacklist=black).reset_index(drop=True)

History #

2-year duration volatility #

Average return standard deviations per contract notional have been vastly different across currency areas. Variation in return volatility has been largely proportional to the volatility itself.

xcatx = ["DU02YXRxEASD_NSA"]
cidx = cids_exp

msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    sort_cids_by="mean",
    start="2000-01-01",
    kind="bar",
    title="Means and standard deviations of 2-year duration volatilities, since 2000",
    size=(16, 8),
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/802a9870df355fcdab6af20bb78ab6ac9d140a9d9719b8cfa0aeaef9816776d7.png
xcatx = ["DU02YXRxEASD_NSA"]
cidx = cids_exp

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    title="Duration volatility for receivers with 2-year fixed legs",
    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/46b0dbab25671f7ac28bcb87dade7bd151eb4405cd519879a22173f726a26994.png

Duration volatility has been positively correlated across all country pairs except for (mainly) Turkey and Russia. Turkey’s rates volatility has been the most idiosyncratic across liquid markets.

xcatx = "DU02YXRxEASD_NSA"
cidx = cids_exp

msp.correl_matrix(
    dfd,
    xcats="DU02YXRxEASD_NSA",
    title="Cross-sectional correlations for 2-year duration volatility, since 2000",
    cids=cidx,
    cluster=True,
    size=(20, 14),
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/141aaea083007c0c4190c62eb5465d8ed17d6fb8fab3edfc3702dbef4f44d5f2.png

5 and 10-year duration volatility #

Average volatility per unit notional for longer tenors’ returns has been higher, averaging between 300 and above 1500 basis points for the 10-year contracts.

xcatx = ["DU05YXRxEASD_NSA", "DU10YXRxEASD_NSA"]
cidx = cids_exp

msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    sort_cids_by="mean",
    start="2000-01-01",
    kind="bar",
    title="Means and standard deviations of long-term duration volatilities, since 2000",
    xcat_labels=["5-year", "10-year"],
    size=(16, 8),
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/54bcb03ea9f76dc15c72e3d8cc60c00bca46e6ffb7c100400f340650968c7f3d.png
xcatx = ["DU05YXRxEASD_NSA", "DU10YXRxEASD_NSA"]
cidx = cids_exp

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    title="Duration volatility for receivers with 5-year and 10-year fixed legs",
    xcat_labels=["5-year", "10-year"],
    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/bbd01bdc65c34b2df2c049b902f5ab7062f5c54ed1f3ca57c4ed5c09fbf2e0f3.png

Leverage ratio #

Required leverage ratios (contract notional to risk capital) for 10% volatility targets averaged between 3 and 19 across currency areas. The cap of 20 has been binding for a range of countries since the 2010s.

xcatx = ["DU02YXRxLEV10_NSA"]
cidx = cids_exp

msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    sort_cids_by="mean",
    start="2000-01-01",
    kind="bar",
    title="Means and standard deviations of leverage ratios, 2-year contract maturity, since 2000",
    size=(16, 8),
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/0884018ae2d81df7b9aa6bab0483ea090225eef52c443573a48bfc58d69b4610.png
xcatx = ["DU02YXRxLEV10_NSA"]
cidx = cids_exp

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    title="2-year IRS position leverage required for 10% annualized volatility target",
    cumsum=False,
    ncol=4,
    same_y=True,
    size=(12, 7),
    aspect=1.7,
    all_xticks=True,
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/a9dd52dde6e8576d22f2ae4c2c6b2314bc13b9ed12605184ea933219218cc237.png

Importance #

Empirical Clues #

In duration space, carry does not compensate for volatility. Indeed, there has been a mild tendency for markets with high volatility to pay lower duration carry. This relationship is consistent both before and after 2008 (and indeed 2020), when the volatility series’ were subject to spikes caused by the 2008 financial crisis and the aftermath of the COVID-19 pandemic respectively.

xcatx = ["DU02YCRY_VT10", "DU02YXRxEASD_NSA"]
cidx = list(set(cids_exp) - set(["TRY"]))

cr = msp.CategoryRelations(
    dfd,
    xcats=xcatx,
    cids=cidx,
    freq="M",
    lag=1,
    xcat_aggs=["mean", "mean"],
    start="2000-01-01",
    # years=2,
    xcat_trims=[80, 20],
)
cr.reg_scatter(
    title="Duration carry (2-year tenor) and subsequent volatility, global panel without Turkey, since 2000",
    labels=False,
    coef_box="upper left",
    ylab="Volatility of 2-year duration returns",
    xlab="Duration carry",
    separator=2008,
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/2d5ecc00795c71107886429fd261f07122cb7535c5cc3c6ac7755a7d4e758bcb.png
xcatx = ["DU02YCRY_VT10", "DU02YXRxEASD_NSA"]
cidx = list(set(cids_exp) - set(["TRY"]))

cr = msp.CategoryRelations(
    dfd,
    xcats=xcatx,
    cids=cidx,
    freq="M",
    lag=1,
    xcat_aggs=["mean", "mean"],
    start="2000-01-01",
    # years=2,
    xcat_trims=[80, 20],
)
cr.reg_scatter(
    title="Duration carry (2-year tenor) and subsequent volatility, global panel without Turkey, since 2000",
    labels=False,
    coef_box="upper left",
    ylab="Volatility of 2-year duration returns",
    xlab="Duration carry",
    separator=2020,
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/1a87c7537d40226e99b3437c230878d68542cc112cedb260ec8d6a3d00625b12.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).