CDS index carry#

This category group features types of carry that are simply based on the implied spreads of credit derivative swaps indices, particularly CDX and iTraxx investment-grade and high-yield indices for the U.S. and the euro area.

CDS index carry in % of notional#

Ticker: CRCRY_NSA

Label: CDS index carry, in % of notional.

Definition: Annualized carry (weighted spread) on the CDS index, % of notional of the on-the-run contract.

Notes:

  • The source of the underlying market quotes is J.P. Morgan/DataQuery.

  • A new index series is determined on the basis of liquidity every six months. Positions are rolled accordingly.

  • Unlike FX forward or equity carry, the economic value of the CDS index carry is not affected by inflation, since there is no implied exposure to a nominal principal. Hence, there is no separate “real” carry calculated by JPMaQs.

Vol-targeted CDS index carry#

Ticker: CRCRY_VT10

Label: CDS index carry for 10% vol target.

Definition: Annualized carry (weighted spread) of the CDS index, % of risk capital on position scaled to 10% (annualized) volatility target.

Notes:

  • The source of the underlying market quotes is J.P. Morgan/DataQuery.

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

  • See further the notes in the section above for “CDS index carry in % of notional” (CRCRY_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.

# Define cross sections (currency tickers)

cids_g2 = ["EUR", "USD"]
cids_cr = ["EIG", "EHY", "UIG", "UHY"]
cids = sorted(cids_g2 + cids_cr)
# Define quantamental indicators (category tickers)

main = ["CRCRY_NSA", "CRCRY_VT10"]
econ = ["PCREDITBN_SJA_P1M1ML12", "PCREDITGDP_SJA_D1M1ML12"]  # economic context
mark = ["CRXR_NSA", "CRXR_VT10"]  # market links

xcats = main + econ + mark
# Download series from J.P. Morgan DataQuery by tickers

start_date = "2002-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_1 = df[df["cid"].isin(cids_cr)]
dfd_x = df[df["cid"].isin(["EUR", "USD"])]
dfd_2 = df.replace({"^EUR": "EIG", "^USD": "UIG"}, regex=True)
dfd_3 = df.replace({"^EUR": "EHY", "^USD": "UHY"}, regex=True)

dfd = pd.concat([dfd_1, dfd_2, dfd_3])

print("Download time from DQ: " + str(timedelta(seconds=end - start)))
Maximum number of tickers is 36
Downloading data from JPMaQS.
Timestamp UTC:  2023-06-02 16:00:41
Connection successful!
Number of expressions requested: 144
Download time from DQ: 0:00:15.658244

Availability#

cids_exp = cids_cr
msm.missing_in_df(dfd, xcats=main, cids=cids_exp)
Missing xcats across df:  set()
Missing cids for CRCRY_NSA:  set()
Missing cids for CRCRY_VT10:  set()

JPMaQS contains four cross sections for CDS index performance data:

  • EHY: European high yield

  • EIG: European investment grade

  • UHY: U.S. high yield

  • UIG: U.S. investment grade

CDS index carries are available from 2002-2004.

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, 1))

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

plot = msm.check_availability(
    dfd, xcats=xcatx, cids=cidx, start_size=(18, 1), start_years=False
)
../_images/CDS index carry_17_0.png
xcatx = main
cidx = cids_exp

plot = msp.heatmap_grades(
    dfd,
    xcats=xcatx,
    cids=cidx,
    size=(18, 1),
    title=f"Average vintage grades from {start_date} onwards",
)
../_images/CDS index 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/CDS index carry_19_0.png ../_images/CDS index carry_19_1.png

History#

CDS index carry in % of notional#

Average carry on high-yield indices has been above 4.5% in the U.S. and just below 4% in Europe. The average carry on the investment grade indices has naturally been much lower, at less than 1%.

xcatx = ["CRCRY_NSA"]
cidx = cids_exp

msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    sort_cids_by="mean",
    start=start_date,
    kind="bar",
    title="Means and standard deviations of CDS index carry, % of notional, since 2002",
    xcat_labels=["CDS index carry"],
    size=(16, 8),
)
../_images/CDS index carry_23_0.png
xcatx = ["CRCRY_NSA"]
cidx = cids_exp

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start=start_date,
    title="EUR, USD: CDS index carry across key market segments",
    title_adj=1.02,
    cumsum=False,
    ncol=2,
    same_y=True,
    size=(12, 7),
    aspect=2,
)
../_images/CDS index carry_24_0.png

Vol-targeted CDS index carry#

Vol-targeted spreads or carry contain financial uncertainty in both the numerator (spread) and the denominator (return volatility). This means that they are not fluctuating in line with overall financial market uncertainty, but rather represent the ratio of specific default insurance to short-term market volatility.

On average, vol-targeted carry has been a little higher for the high-yield segment than the investment grade segment. Vol-targeted carry has been quite volatile and evidently stationary.

xcatx = ["CRCRY_VT10"]
cidx = cids_exp

msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    sort_cids_by="mean",
    start=start_date,
    kind="bar",
    title="Means and standard deviations of CDS index carry, 10% vol-target, since 2002",
    xcat_labels=["CDS index carry"],
    size=(16, 8),
)
../_images/CDS index carry_27_0.png
xcatx = ["CRCRY_VT10"]
cidx = cids_exp

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start=start_date,
    title="EUR, USD: CDS index carry, 10% annualized vol target",
    title_adj=1.02,
    cumsum=False,
    ncol=2,
    same_y=True,
    size=(12, 7),
    aspect=2,
)
../_images/CDS index carry_28_0.png

Importance#

Empirical Clues#

There is some evidence to suggest that CDS index carry is a promising predictor of CDS index returns. However, greater carry coincides with greater variability in the returns. The relationship is consistent before and after 2020, where each of the carry series’ suffer from jumps related to the effects of the COVID-19 pandemic.

xcatx = ["CRCRY_NSA", "CRXR_NSA"]
cidx = cids_exp

cr = msp.CategoryRelations(
    dfd,
    xcats=xcatx,
    cids=cidx,
    freq="M",
    lag=1,
    xcat_aggs=["mean", "sum"],
    fwin=1,
    start=start_date,
)
cr.reg_scatter(
    title="CDS Carry and subsequent monthly vol-adjusted returns",
    labels=False,
    coef_box="lower right",
    xlab="CDS index carry",
    ylab="CDS index return",
    separator=2020,
    prob_est="map",
)
../_images/CDS index carry_35_0.png