Government bond yields and carry#

The category group contains carry and yield data for generic zero-coupon bonds which span 9 countries and 7 tenors.

Generic government bond yields#

Ticker: GB01YYLD_NSA / GB02YYLD_NSA / GB03YYLD_NSA / GB05YYLD_NSA / GB07YYLD_NSA / GB10YYLD_NSA / GB30YYLD_NSA

Label: Generic government bond yield: 1-year maturity / 2-year maturity / 3-year maturity / 5-year maturity / 7-year maturity / 10-year maturity / 30-year maturity.

Definition: Generic zero-coupon government bond yield, % annual yield: 1-year maturity / 2-year maturity / 3-year maturity / 5-year maturity / 7-year maturity / 10-year maturity / 30-year maturity.

Notes:

  • The zero-coupon yields are taken from zero-coupon curves that are available on J.P. Morgan DataQuery.

  • Not all maturities have readily available zero-coupon bonds. Hence, the resulting zero-coupon yields are derived from an interpolation operated by DataQuery.

Real government bond yields#

Ticker: GB01YRYLD_NSA / GB02YRYLD_NSA / GB05YRYLD_NSA

Label: Generic government bond real yield: 1-year maturity / 2-year maturity / 5-year maturity.

Definition: Generic zero-coupon government bond yield minus the inflation expectation for that period, % annual yield: 1-year maturity / 2-year maturity / 5-year maturity.

Notes:

  • Real yields are calculated by subtracting inflation expectations from the spot nominal yield.

  • Inflation expectations are formulaic estimates according to Macrosynergy methodology. The estimate assumes that market participants form their inflation expectations based on the recent inflation target and the effective inflation target. For more information on the calculations, see here.

Real vol-targeted government bond yields#

Ticker: GB01YRYLD_VT10 / GB02YRYLD_VT10 / GB05YRYLD_VT10

Label: Vol-targeted real government bond yield: 1-year maturity / 2-year maturity / 5-year maturity.

Definition: Vol-targeted real generic government bond yield: 1-year maturity / 2-year maturity / 5-year maturity.

Notes:

  • Positions are scaled to a 10% vol-target based on historic standard deviations for an exponential moving average with a half-life of 11 days. Leverage is constrained to a maximum value of 5 due to typical leverage limitations.

Generic government bond carry#

Ticker: GB01YCRY_NSA / GB02YCRY_NSA / GB03YCRY_NSA / GB05YCRY_NSA / GB07YCRY_NSA / GB10YCRY_NSA / GB30YCRY_NSA

Label: Generic government bond carry, % ar: 1-year maturity / 2-year maturity / 3-year maturity / 5-year maturity / 7-year maturity / 10-year maturity / 30-year maturity.

Definition: Generic government bond carry measure including both pull to par and roll effects, % ar: 1-year maturity / 2-year maturity / 3-year maturity / 5-year maturity / 7-year maturity / 10-year maturity / 30-year maturity.

Notes:

  • We define an asset’s “carry” as its futures return, assuming that the yield curve stays the same. For basic formulas, see Appendix 1.

  • The carry presented is yearly carry: the carry one would earn over a year.

  • By including the risk-free rate, which is calculated with our internal short-term interest rate series, we can also appreciate carry as a measure of the return on a bond when taking into account its cost of funding.

  • The funding rate used comes from our internal short-term interest rate indicators. For more information on the specific short term nominal interest rates used, please see here.

Generic government bond vol-targeted nominal carry#

Ticker: GB01YCRY_VT10 / GB02YCRY_VT10 / GB03YCRY_VT10 / GB05YCRY_VT10 / GB07YCRY_VT10 / GB10YCRY_VT10 / GB30YCRY_VT10

Label: Generic government bond carry for 10% vol target, % ar: 1-year maturity / 2-year maturity / 3-year maturity / 5-year maturity / 7-year maturity / 10-year maturity / 30-year maturity.

Definition: Generic government bond carry for 10% vol target, % ar: 1-year maturity / 2-year maturity / 3-year maturity / 5-year maturity / 7-year maturity / 10-year maturity / 30-year maturity.

Notes:

  • Positions are scaled to a 10% vol-target based on historic standard deviations for an exponential moving average with a half-life of 11 days. Leverage is subject to a maximum of 5.

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

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.

cids = ["AUD", "DEM", "FRF", "ESP", "ITL", "JPY", "NZD", "GBP", "USD"]
tenors = ["01", "02", "03", "05", "07", "10", "30"]

ylds = [f"GB{t}YYLD_NSA" for t in tenors]
ryds = [f"GB{t}YRYLD_NSA" for t in tenors]
ryvs = [f"GB{t}YRYLD_VT10" for t in tenors]
crys = [f"GB{t}YCRY_NSA" for t in tenors]
crvs = [f"GB{t}YCRY_VT10" for t in tenors]
rets = [f"GB{t}YXR_NSA" for t in tenors] + [f"GB{t}YR_NSA" for t in tenors]

main = ylds + ryds + ryvs + crys + crvs
xcats = main + rets
# 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 441
Downloading data from JPMaQS.
Timestamp UTC:  2023-05-30 18:10:03
Connection successful!
Number of expressions requested: 1764
Download time from DQ: 0:01:34.181941

Availability#

cids_exp = cids  # cids expected in category panels
msm.missing_in_df(dfd, xcats=xcats, cids=cids_exp)
Missing xcats across df:  {'GB10YRYLD_VT10', 'GB30YRYLD_VT10', 'GB07YRYLD_NSA', 'GB30YRYLD_NSA', 'GB03YRYLD_NSA', 'GB07YRYLD_VT10', 'GB03YRYLD_VT10', 'GB10YRYLD_NSA'}
Missing cids for GB01YCRY_NSA:  set()
Missing cids for GB01YCRY_VT10:  set()
Missing cids for GB01YRYLD_NSA:  set()
Missing cids for GB01YRYLD_VT10:  set()
Missing cids for GB01YR_NSA:  set()
Missing cids for GB01YXR_NSA:  set()
Missing cids for GB01YYLD_NSA:  set()
Missing cids for GB02YCRY_NSA:  set()
Missing cids for GB02YCRY_VT10:  set()
Missing cids for GB02YRYLD_NSA:  set()
Missing cids for GB02YRYLD_VT10:  set()
Missing cids for GB02YR_NSA:  set()
Missing cids for GB02YXR_NSA:  set()
Missing cids for GB02YYLD_NSA:  set()
Missing cids for GB03YCRY_NSA:  set()
Missing cids for GB03YCRY_VT10:  set()
Missing cids for GB03YR_NSA:  set()
Missing cids for GB03YXR_NSA:  set()
Missing cids for GB03YYLD_NSA:  set()
Missing cids for GB05YCRY_NSA:  set()
Missing cids for GB05YCRY_VT10:  set()
Missing cids for GB05YRYLD_NSA:  set()
Missing cids for GB05YRYLD_VT10:  set()
Missing cids for GB05YR_NSA:  set()
Missing cids for GB05YXR_NSA:  set()
Missing cids for GB05YYLD_NSA:  set()
Missing cids for GB07YCRY_NSA:  set()
Missing cids for GB07YCRY_VT10:  set()
Missing cids for GB07YR_NSA:  set()
Missing cids for GB07YXR_NSA:  set()
Missing cids for GB07YYLD_NSA:  set()
Missing cids for GB10YCRY_NSA:  set()
Missing cids for GB10YCRY_VT10:  set()
Missing cids for GB10YR_NSA:  set()
Missing cids for GB10YXR_NSA:  set()
Missing cids for GB10YYLD_NSA:  set()
Missing cids for GB30YCRY_NSA:  set()
Missing cids for GB30YCRY_VT10:  set()
Missing cids for GB30YR_NSA:  set()
Missing cids for GB30YXR_NSA:  set()
Missing cids for GB30YYLD_NSA:  set()

Available history is very different across countries, with the U.S. providing the largest data set.

xcatx = main
cidx = cids_exp

dfx = msm.reduce_df(dfd, xcats=xcatx, cids=cidx)
dfs = msm.check_startyears(
    dfx,
)
msm.visual_paneldates(dfs, size=(14, 8))

print("Last updated:", date.today())
../_images/Government bonds yields and carry_22_0.png
Last updated: 2023-05-30
xcatx = main
cidx = cids_exp

plot = msm.check_availability(
    dfd, xcats=xcatx, cids=cids_exp, start_size=(18, 6), start_years=False
)
../_images/Government bonds yields and carry_23_0.png
xcatx = main
cidx = cids_exp

plot = msp.heatmap_grades(
    dfd,
    xcats=xcatx,
    cids=cidx,
    size=(18, 15),
    title=f"Average vintage grades from {start_date} onwards",
)
../_images/Government bonds yields and carry_24_0.png
xcats_yield = ylds[:2]

msp.view_ranges(
    dfd,
    xcats=xcats_yield,
    cids=cids,
    val="eop_lag",
    title="End of observation period lags (ranges of time elapsed since end of observation period in days), yields",
    start="2000-01-01",
    kind="box",
    size=(16, 4),
)

msp.view_ranges(
    dfd,
    xcats=xcats_yield,
    cids=cids,
    val="mop_lag",
    title="Median of observation period lags (ranges of time elapsed since middle of observation period in days), yields",
    start="2000-01-01",
    kind="box",
    size=(16, 4),
)
../_images/Government bonds yields and carry_25_0.png ../_images/Government bonds yields and carry_25_1.png
xcats_yield = crys[:2]

msp.view_ranges(
    dfd,
    xcats=xcats_yield,
    cids=cids,
    val="eop_lag",
    title="End of observation period lags (ranges of time elapsed since end of observation period in days), carries",
    start="2000-01-01",
    kind="box",
    size=(16, 4),
)

msp.view_ranges(
    dfd,
    xcats=xcats_yield,
    cids=cids,
    val="mop_lag",
    title="Median of observation period lags (ranges of time elapsed since middle of observation period in days), carries",
    start="2000-01-01",
    kind="box",
    size=(16, 4),
)
../_images/Government bonds yields and carry_26_0.png ../_images/Government bonds yields and carry_26_1.png

History#

Nominal bond yields#

Nominal bond yields have displayed pronounced medium-term trends and cyclical fluctuations in past decades. Also, overlapping historical patterns have been similar across the the nine countries.

xcatx = ["GB01YYLD_NSA", "GB05YYLD_NSA", "GB10YYLD_NSA"]
cidx = cids

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    title="Generic government bond spot rates for 1-year, 5-year and 10-year tenors",
    xcat_labels=["1-year", "5-year", "10-year"],
    title_adj=1.05,
    title_xadj=0.42,
    label_adj=0.1,
    ncol=3,
    same_y=True,
    size=(12, 7),
    aspect=1.7,
    all_xticks=True,
)
../_images/Government bonds yields and carry_30_0.png

Real bond yields#

Real bond yields based on forward-looking formulaic estimates have become negative over time.

xcatx = ["GB01YRYLD_NSA", "GB02YRYLD_NSA", "GB05YRYLD_NSA"]
cidx = cids

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    title="Generic government bond real yields for 1-year, 2-year and 5-year tenors",
    xcat_labels=["1-year", "2-year", "5-year"],
    label_adj=0.1,
    ncol=3,
    title_adj=1.05,
    title_xadj=0.43,
    same_y=True,
    size=(12, 7),
    aspect=1.7,
    all_xticks=True,
)
../_images/Government bonds yields and carry_33_0.png

Bond carry#

Nominal carry metrics have been diverse across countries and of course reflect both the level of funding rates and the shape of the bond yield curve. Most of the time since 1992 the longer-duration tenors have paid higher carry. Like yields, carry has posted pronounced cyclical fluctuations.

xcatx = ["GB01YCRY_NSA", "GB05YCRY_NSA", "GB10YCRY_NSA"]
cidx = cids_exp

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    title="Government bond nominal carry for 1-year, 5-year and 10-year tenors",
    title_adj=1.05,
    title_xadj=0.4,
    xcat_labels=["1-year carry", "5-year carry", "10-year carry"],
    label_adj=0.1,
    ncol=3,
    same_y=True,
    size=(25, 7),
    aspect=1.7,
    all_xticks=True,
)
../_images/Government bonds yields and carry_36_0.png

Carry has been positively correlated across countries but not for all cross-section pairs. Australia is the notable exception.

xcatx = "GB05YCRY_NSA"
cidx = cids
msp.correl_matrix(
    dfd,
    xcats=xcatx,
    cids=cidx,
    title="Cross-sectional correlations for government bond nominal carry, 5-year tenor",
    size=(20, 14),
)
../_images/Government bonds yields and carry_38_0.png

Vol-targeted bond carry#

A 10% annualized volatility target increases carry for most countries and time periods.

xcatx = ["GB05YCRY_VT10", "GB05YCRY_NSA"]
cidx = cids

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    title="Government bond nominal carry, 5-year tenor, outright and 10% vol-target",
    title_adj=1.05,
    title_xadj=0.43,
    xcat_labels=["Vol-targeted", "Outright"],
    legend_fontsize=15,
    label_adj=0.1,
    ncol=3,
    same_y=True,
    size=(12, 7),
    aspect=1.7,
    all_xticks=True,
)
../_images/Government bonds yields and carry_41_0.png

Importance#

Empirical clues#

Carry has been strongly correlated with excess returns across countries and multi-year periods.

cr = msp.CategoryRelations(
    dfd,
    xcats=["GB05YCRY_NSA", "GB05YXR_NSA"],
    cids=cids,
    freq="A",
    lag=0,
    xcat_aggs=["mean", "sum"],
    start="2000-01-01",
    years=2,
)


cr.reg_scatter(
    title="Government bond carry and concurrent excess returns over 2-year periods, all available history",
    labels=True,
    prob_est="map",
    coef_box="lower right",
    ylab="Cumulative 5-year bond return",
    xlab="Average 5-year bond carry",
)
../_images/Government bonds yields and carry_49_0.png

Similarly, real yields have been strongly correlated with cash returns across countries and multi-year periods.

xcatx = ["GB05YRYLD_NSA", "GB05YR_NSA"]
cidx = cids

cr = msp.CategoryRelations(
    dfd,
    xcats=xcatx,
    cids=cidx,
    xcat_aggs=["mean", "sum"],
    start="2000-01-01",
    years=2,
)


cr.reg_scatter(
    title="Government bond real 5-year yields and concurrent returns over 2-year periods, all available periods",
    labels=True,
    prob_est="map",
    coef_box="lower right",
    ylab="Cumulative 5-year bond return",
    xlab="Average 5-year real carry",
)
../_images/Government bonds yields and carry_51_0.png

Appendices#

Appendix 1: Calculation of total and excess generic bond returns#

We define an asset’s “carry” as its futures return, assuming that prices of the underlying stay the same. This means that zero-coupon bond carry \(C_{m,t}\) for maturity \(m\) at the end of day \(t\) is simply the relative 1-day price change of the future if tomorrow’s future price is equal to today’s price of the bond with the same maturity:

(3)#\[\begin{equation} \text{carry}_{m,t} = \frac{S_{m-1,t}-F_{m,t}}{F_{m,t}} \end{equation}\]

where \(F_{m,t}\) is the 1-period futures price when the underlying security currently has m periods to maturity and delivery is next period, and \(S_{m−1,t}\) is the spot price of a security with \(m – 1\) periods to maturity. See the appendices here for more information on the formulae for the spot price and futures price.

The above carry definition can be directly applied to bond futures. However, liquid bond futures contracts are traded only in a few countries and, when they exist, typically only the first-to-expire contract is liquid. To create a broad global cross section of bonds, we therefore compute synthetic futures prices based on an extensive data set of zero-coupon rates and apply the same carry definition.

For a given term structure encompassing the funding rate and the relevant zero-coupon bond maturities, we have:

(4)#\[\begin{equation} \text{carry}_{m,t} = \frac{(1+y_{m-1,t})^{-\frac{m-1}{252}}-(1+r^{(f)}_{t})\times (1+y_{m,t})^{-\frac{m}{252}}}{(1+r^{(f)}_{t})\times (1+y_{m,t})^{-\frac{m}{252}}} = \left [ \frac{(1+y_{m,t})^{\frac{m}{252}}}{(1+r^{(f)}_{t})\times (1+y_{m-1,t})^{\frac{m-1}{252}}} \right ] -1 \end{equation}\]

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