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())
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
)
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",
)
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),
)
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),
)
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,
)
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,
)
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,
)
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),
)
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,
)
Importance #
Research links #
The importance of carry trades in the bond market has been most popular in periods characterised by a lower concentration of market-changing events and lower inflation. This is due to the fact that in such instances, the probability of losing one’s return due to adverse yield movements or higher inflation is lower. For instance, after covid, especially in the summer months, carry trades - in the italian market in particular - were extremely popular. See Fitch Ratings .
Of course bonds return and the expected carry depends largely on the general level of interest rates and in particular on the funding levels present in the market. For instance, after the Italian debt crisis in 2011, shortly after the arrival of the new Italian PM Mario Monti, government bond yields were still high however funding rates at the ECB were low. This spurred a resurgence of the carry trade, as detailed at the time on Reuters .
Bond carry has not observed large popularity in the literature however indeed it is among the main drivers of portfolio allocation. A strategy whereby portfolio construction was based on carry by selecting bonds with the highest expected bond risk premium per unit duration showed to have a low correlation with other strategies, thus “resulting in a global curve carry factor that has a significant positive risk-adjusted performance” Duyvesteyn et al.
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",
)
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",
)
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:
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:
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).