Government bond yields and carry #
The category group contains carry and yield data for generic zerocoupon 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: 1year maturity / 2year maturity / 3year maturity / 5year maturity / 7year maturity / 10year maturity / 30year maturity.
Definition : Generic zerocoupon government bond yield, % annual yield: 1year maturity / 2year maturity / 3year maturity / 5year maturity / 7year maturity / 10year maturity / 30year maturity.
Notes :

The zerocoupon yields are taken from zerocoupon curves that are available on J.P. Morgan DataQuery.

Not all maturities have readily available zerocoupon bonds. Hence, the resulting zerocoupon 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: 1year maturity / 2year maturity / 5year maturity.
Definition : Generic zerocoupon government bond yield minus the inflation expectation for that period, % annual yield: 1year maturity / 2year maturity / 5year 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 voltargeted government bond yields #
Ticker : GB01YRYLD_VT10 / GB02YRYLD_VT10 / GB05YRYLD_VT10
Label : Voltargeted real government bond yield: 1year maturity / 2year maturity / 5year maturity.
Definition : Voltargeted real generic government bond yield: 1year maturity / 2year maturity / 5year maturity.
Notes :

Positions are scaled to a 10% voltarget based on historic standard deviations for an exponential moving average with a halflife 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: 1year maturity / 2year maturity / 3year maturity / 5year maturity / 7year maturity / 10year maturity / 30year maturity.
Definition : Generic government bond carry measure including both pull to par and roll effects, % ar: 1year maturity / 2year maturity / 3year maturity / 5year maturity / 7year maturity / 10year maturity / 30year 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 riskfree rate, which is calculated with our internal shortterm 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 shortterm interest rate indicators. For more information on the specific short term nominal interest rates used, please see here .
Generic government bond voltargeted 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: 1year maturity / 2year maturity / 3year maturity / 5year maturity / 7year maturity / 10year maturity / 30year maturity.
Definition : Generic government bond carry for 10% vol target, % ar: 1year maturity / 2year maturity / 3year maturity / 5year maturity / 7year maturity / 10year maturity / 30year maturity.
Notes :

Positions are scaled to a 10% voltarget based on historic standard deviations for an exponential moving average with a halflife 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 crosssection 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 = "20000101"
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: 20230530 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: 20230530
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="20000101",
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="20000101",
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="20000101",
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="20000101",
kind="box",
size=(16, 4),
)
History #
Nominal bond yields #
Nominal bond yields have displayed pronounced mediumterm 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="20000101",
title="Generic government bond spot rates for 1year, 5year and 10year tenors",
xcat_labels=["1year", "5year", "10year"],
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 forwardlooking 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="20000101",
title="Generic government bond real yields for 1year, 2year and 5year tenors",
xcat_labels=["1year", "2year", "5year"],
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 longerduration 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="20000101",
title="Government bond nominal carry for 1year, 5year and 10year tenors",
title_adj=1.05,
title_xadj=0.4,
xcat_labels=["1year carry", "5year carry", "10year 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 crosssection pairs. Australia is the notable exception.
xcatx = "GB05YCRY_NSA"
cidx = cids
msp.correl_matrix(
dfd,
xcats=xcatx,
cids=cidx,
title="Crosssectional correlations for government bond nominal carry, 5year tenor",
size=(20, 14),
)
Voltargeted 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="20000101",
title="Government bond nominal carry, 5year tenor, outright and 10% voltarget",
title_adj=1.05,
title_xadj=0.43,
xcat_labels=["Voltargeted", "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 marketchanging 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 riskadjusted performance” Duyvesteyn et al.
Empirical clues #
Carry has been strongly correlated with excess returns across countries and multiyear periods.
cr = msp.CategoryRelations(
dfd,
xcats=["GB05YCRY_NSA", "GB05YXR_NSA"],
cids=cids,
freq="A",
lag=0,
xcat_aggs=["mean", "sum"],
start="20000101",
years=2,
)
cr.reg_scatter(
title="Government bond carry and concurrent excess returns over 2year periods, all available history",
labels=True,
prob_est="map",
coef_box="lower right",
ylab="Cumulative 5year bond return",
xlab="Average 5year bond carry",
)
Similarly, real yields have been strongly correlated with cash returns across countries and multiyear periods.
xcatx = ["GB05YRYLD_NSA", "GB05YR_NSA"]
cidx = cids
cr = msp.CategoryRelations(
dfd,
xcats=xcatx,
cids=cidx,
xcat_aggs=["mean", "sum"],
start="20000101",
years=2,
)
cr.reg_scatter(
title="Government bond real 5year yields and concurrent returns over 2year periods, all available periods",
labels=True,
prob_est="map",
coef_box="lower right",
ylab="Cumulative 5year bond return",
xlab="Average 5year 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 zerocoupon bond carry \(C_{m,t}\) for maturity \(m\) at the end of day \(t\) is simply the relative 1day 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 1period 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 firsttoexpire 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 zerocoupon rates and apply the same carry definition.
For a given term structure encompassing the funding rate and the relevant zerocoupon bond maturities, we have:
Appendix 2: Currency symbols #
The word ‘crosssection’ 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).