Duration volatility risk premia #

The category group contains simple estimates of duration volatility risk premia based on interest rate swaps (IRS). They are calculated as the difference between implied and expected realized volatility divided by expected realized volatility. Conceptually, a positive volatility risk premium means that market participants demand a surcharge for exposure to rates volatility. Expected realized volatility is estimated by recent realized volatility, which is assumed to be an unbiased estimator over the short term.

Duration volatility risk premia (2-year duration) #

Ticker : IRVRP03M02Y_NSA / IRVRP06M02Y_NSA / IRVRP01Y02Y_NSA

Label : Volatility risk premia for 2-year IRS receivers with swaption maturity of: 3 months / 6 months / 1 year

Definition : IRS volatility risk premia for 2-year duration of the underlying receiver swap and swaption maturity of: 3 months / 6 months / 1 year

Notes :

  • Duration volatility risk premia are defined to be the difference between implied and realized volatility of forward-starting swap rates’ changes divided by the realized volatility of these rate changes. The metrics are unitless and refer to a fractional deviation.

  • Implied volatility is the parameter that yields the market price of a swaption when substituted into the option pricing formula under the assumption of an arithmetic Brownian motion.

  • Realised volatility is the standard deviation of an exponential moving average of daily forward-starting IRS yield changes with a half-life of 11 days, a convention frequently used for risk management.

  • The source of the underlying quotes is J.P. Morgan/Dataquery. Note that for the calculation of duration volatility risk premia, the use of IRS yields and returns are equivalent.

  • For more information on the calculations, see Appendix 1 .

Duration volatility risk premia (3-year duration) #

Ticker : IRVRP03M03Y_NSA / IRVRP06M03Y_NSA / IRVRP01Y03Y_NSA

Label : Volatility risk premia for 3-year IRS receivers with swaption maturity of: 3 months / 6 months / 1 year

Definition : IRS volatility risk premia for 3-year duration of the underlying receiver swap and swaption maturity of: 3 months / 6 months / 1 year

Notes :

  • See the important notes for ‘Duration volatility risk premia (2-year duration)’.

  • For further information on the volatility risk premia calculations, see Appendix 1 .

IRS volatility risk premia (5-year duration) #

Ticker : IRVRP03M05Y_NSA / IRVRP06M05Y_NSA / IRVRP01Y05Y_NSA

Label : Volatility risk premia for 5-year IRS receivers with swaption maturity of: 3 months / 6 months / 1 year

Definition : IRS volatility risk premia for 5-year duration of the underlying receiver swap and swaption maturity of: 3 months / 6 months / 1 year

Notes :

  • See the important notes for ‘Duration volatility risk premia (2-year duration)’.

  • For further information on the volatility risk premia calculations, see Appendix 1 .

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 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 = [
    "USD",
    "EUR",
    "GBP",
    "NOK",
    "PLN",
    "SEK",
    "CHF",
    "JPY",
    "ILS",
    "ZAR",
]
tails = ["02Y", "03Y", "05Y"]
expiries = ["03M", "06M", "01Y"]
main = []
for e in expiries:
    for t in tails:
        main.append(f"IRVRP{e}{t}_NSA")

econ = ["FXXRxEASD_NSA", "BXBGDPRATIO_NSA_12MMA"]  # economic context
mark = [
    "EQXR_NSA",
    "EQXR_VT10",
    "FXXR_NSA",
    "FXXR_VT10",
    "DU05YXR_NSA",
    "DU05YXR_VT10",
    "DU02YXR_NSA",
    "DU02YXR_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 190
Downloading data from JPMaQS.
Timestamp UTC:  2023-10-23 14:13:06
Connection successful!
Number of expressions requested: 760
Requesting data: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 38/38 [00:11<00:00,  3.18it/s]
Downloading data: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 38/38 [00:16<00:00,  2.31it/s]
Download time from DQ: 0:00:39.168976

Availability #

cids_exp = cids  # cids expected in category panels
msm.missing_in_df(dfd, xcats=main, cids=cids_exp)
Missing xcats across df:  []
Missing cids for IRVRP01Y02Y_NSA:  []
Missing cids for IRVRP01Y03Y_NSA:  []
Missing cids for IRVRP01Y05Y_NSA:  []
Missing cids for IRVRP03M02Y_NSA:  []
Missing cids for IRVRP03M03Y_NSA:  []
Missing cids for IRVRP03M05Y_NSA:  []
Missing cids for IRVRP06M02Y_NSA:  []
Missing cids for IRVRP06M03Y_NSA:  []
Missing cids for IRVRP06M05Y_NSA:  []

Start dates for quantamental indicators of duration volatility risk premia are mixed, even for developed markets.

Disclaimer : JPMaQS has discovered an anomaly with the quality of swaption implied yield volatility data sources used in a number of emerging markets Interest Rate Variance Risk Premia indicators. Our sources are reviewing these datasets. While this process takes place, we have decided to suspend the following cross-sections: HKD, HUF and KRW.

Once the data sources complete their review, JPMaQS would revisit the data quality and are planning to resume the availability of these indicators in Q1 2024.

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=(12, 4))

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

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

plot = msp.heatmap_grades(
    dfd,
    xcats=xcatx,
    cids=cidx,
    size=(12, 4),
    title=f"Average vintage grades from {start_date} onwards",
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/5a5d99234883b817e562a41e96af98bb6a788221123e1093a51df5f60510d61e.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=(12, 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=(12, 4),
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/5202cdc7761b4702a5e1d2ac2c5bc986e4dc2fe413a38e4d35d91601dc971ec5.png https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/76ef9e334eb2bd0cb01b59e05c74c774244c725e664e8397782dc2bc2be76c35.png

History #

Premia for swaptions with 2-year duration #

Volatility risk premia, in most countries, have been positive since 2000. They have been highest for Switzerland and lowest for the United States.

xcatx = ["IRVRP03M02Y_NSA", "IRVRP06M02Y_NSA", "IRVRP01Y02Y_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 volatility risk premia for IRS options, 2-year duration, since 2000",
    xcat_labels=["3-month maturity", "6-month maturity", "1-year maturity"],
    kind="bar",
    size=(16, 8),
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/5789cfb945a2ec10df5f7f485ab749840f1b800cc97b9e2f6a7ec12e32cfa08c.png
xcatx = ["IRVRP03M02Y_NSA", "IRVRP06M02Y_NSA", "IRVRP01Y02Y_NSA"]
cidx = cids_exp

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    title="Volatility risk premia for IRS swap options, 2-year duration",
    title_adj=0.92,
    xcat_labels=["3-month maturity", "6-month maturity", "1-year maturity"],
    ncol=3,
    same_y=False,
    # label_adj=0.05,
    aspect=1.7,
    all_xticks=False,
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/98e74667a3958ddd703605fefccd2799168d91d6479b10b93dcdfad450d191e9.png

With exception of South Africa, volatility risk premia have been positively correlated across countries.

xcatx = "IRVRP06M02Y_NSA"
cidx = cids_exp

dfd_vrp7 = dfd[dfd["xcat"] == "IRVRP06M02Y_NSA"][
    ["real_date", "cid", "xcat", "value"]
].set_index("real_date")
dfw_vrp7 = dfd_vrp7.groupby(["cid", "xcat"]).resample("W").mean().reset_index()
# cidx = list(set(cids_exp) - set(["ZAR"]))
msp.correl_matrix(
    dfw_vrp7,
    xcats=xcatx,
    cids=cidx,
    title="Cross-sectional correlations of volatility risk premia for IRS swap options, 6-month maturity, 2-year duration, since 2000",
    size=(20, 14),
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/a8d1b4070e52b56f557d1cb4c8d233e7220980695d38b99f0d7c88ccac95db79.png

Premia for swaptions with 3-year duration #

Volatility risk premia for 3-year duration swaps had a similar history to those with 2-year duration, albeit with slightly lower peaks in some episodes of high uncertainty.

xcatx = ["IRVRP06M02Y_NSA", "IRVRP06M03Y_NSA"]
cidx = cids_exp

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    title="Volatility risk premia for IRS swap options, 6-month maturity",
    title_adj=0.92,
    xcat_labels=["2-year duration", "3-year duration"],
    ncol=4,
    same_y=False,
    label_adj=0.05,
    aspect=1.7,
    all_xticks=False,
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/536b74c426cf7a40a7fd69f70b2e31570e1a8ad88a23c1e83323dbc3f9c1b8f3.png

Premia for swaptions with 5-year duration #

For 5-year duration, peaks have not been as pronounced as for shorter durations.

xcatx = ["IRVRP06M02Y_NSA", "IRVRP06M05Y_NSA"]
cidx = cids_exp

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    title="Volatility risk premia for IRS swap options, 6-month maturity",
    title_adj=0.95,
    xcat_labels=["2-year duration", "5-year duration"],
    ncol=4,
    same_y=False,
    label_adj=0.05,
    aspect=1.7,
    all_xticks=False,
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/98723773144cf0e7f01bfa90aa4cef752a6e3add5e29bf855070c29d9eba30d1.png

Importance #

Empirical Clues #

Over the last 20 years, volatility risk premia have been negatively correlated with subsequent quarterly IRS receiver returns. The negative correlation was particularly prevalent in the largest, most liquid markets.

xcatx = ["IRVRP03M02Y_NSA", "DU02YXR_VT10"]
cidx = ["EUR", "USD"]

cr = msp.CategoryRelations(
    dfd,
    xcats=xcatx,
    cids=cidx,
    freq="Q",
    lag=1,
    xcat_aggs=["last", "sum"],
    fwin=1,
    start="2002-01-01",
)
cr.reg_scatter(
    title="Volatility risk premia and subsequent duration returns (only USD and EUR)",
    labels=True,
    coef_box="upper right",
    xlab="Volatility risk premium (3-m swaption, 2-y duration), end of quarter",
    ylab="Vol-targeted 2-year IRS returns, next quarter",
)
https://macrosynergy.com/notebooks.build/themes/shock-and-risk-measures/_images/85592017e7ec717ce42bac6c945c46b358a5e7e107418acbf4478b80e47ec1d6.png

Appendices #

Appendix 1: Calculation methodology #

The category group contains estimates of duration volatility risk premia , which are defined as the difference between implied and realized volatility of forward-starting interest rate swap rate differentials divided by the realized volatility of the same. Thus, the basic formula is simply:

(1) # \[\begin{equation} \text{VRP}(\tau,\delta,H) \equiv \frac{\sigma_i(\tau,\delta)-\sigma_r(H)}{\sigma_r(H)}, \end{equation}\]

where \(\sigma_i(\tau, \delta)\) is the volatility implied by the market for at-the-money swap options with time to maturity \(\tau\) and duration (“tail”) of the underlrying swap \(\delta\) , and \(\sigma_r(H)\) is the realised (historical) volatility, extracted using an exponentially weighted moving average filter of half-life \(H\) . The volatility risk premium is unitless and refers to the fractional deviation of implied volatility from realised volatility.

Notes :

  • Implied volatility is the paramter \(\sigma_i\) which when substituted into the option pricing formula for arithmetic Brownian motion yields the market price for swap options. It codifies the standard deviation of the underlying forward starting swap rate differential, and will generally be a function of both the strike of the option ( \(K\) ), the time to maturity of the option ( \(\tau\) ), and the duration of the underlying swap ( \(\delta\) ). This trivariate mapping \(\sigma_i: K \times \tau \times \delta \mapsto \mathbb{R}^+\) is known as the volatility cube (in contrast to e.g. the FX or equity market where we speak of volatility surfaces ).

  • Swap rates can go negative, which suggests that the standard Black-Scholes formula with log-normal returns is not appropriate. We define the forward starting swap rate \(F_t^{\tau, \delta}\) as the expected interest rate prevailing over the time interval \([\tau, \tau+\delta]\) . Formally we model the dynamics thereof as \begin{equation} dF_t^{\tau, \delta} = \sigma_i dW_t \end{equation} where \(dW_t\) is a Brownian increment. When we speak of the differential of a forward starting swap rate we mean the daily difference \begin{equation} r_t = F_t^{\tau, \delta} - F_{t-1}^{\tau, \delta}. \end{equation}

  • Realised volatility is here defined as the running standard deviation of forward starting swap rate differentials, exponentially weighted through time: \begin{equation} \sigma_{r,t} = \sqrt{252 \cdot \sum_{k=0}^\infty \lambda^k r_{t-k}^2}, \end{equation} where \(\lambda\) is a decay parameter. Following the RiskMetrics convention we set \(\lambda = 0.94\) , or identically, the half-life \(H\) equal to ~11 days (the value for \(k\) which renders \(\lambda^k \approx 0.5\) ). The \(\sqrt{252}\) is an annualisation factor reflecting the fact that swaps typically have 252 observation periods in a year. Assuming that returns are i.i.d.: \(\text{Var}[r_{\text{yearly}}]=\text{Var}[r_1+r_2+\cdots+r_{252}]=\text{Var}[r_1]+\text{Var}[r_2]+\cdots+\text{Var}[r_{252}]=252 \cdot\text{Var}[r_{\text{daily}}]\) .

  • Implied volatility codifies the market’s expectations of future uncertainty. Meanwhile realised volatility looks backwards in time. Empirical evidence suggests implied volatility tends to exceed realised volatility (the so-called premium).

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