Term premia estimates#

The category group contains term premia estimates for fixed income contracts, based on macro-quantamental indicators.

Interest rate swap term premia (Macrosynergy method)#

Ticker: DU02YETP_NSA / DU05YETP_NSA / DU10YETP_NSA

Label: Estimated IRS term premia, % ar, Macrosynergy method: 2-year maturity / 5-year maturity / 10-year maturity.

Definition: Estimated IRS term premia, % ar, Macrosynergy method: 2-year maturity / 5-year maturity / 10-year maturity.

Notes:

  • Conceptually the fixed income term premium refers to the gap between the yield of a longer-maturity bond and the average expected risk-free short-term rate for the same maturity. Essentially, it represents the cost of commitment. Although the term premium itself cannot be directly observed, it can be estimated by employing a term structure model that differentiates between expected short-term rates and risk premia.

  • The simple Macrosynergy term premium model mainly makes an assumption about the expected path of the short-term interest rate. It posits a long-term “convergence point” of the short rate at the estimated long-term rate of nominal GDP growth, which, in turn, is estimated as the sum of the 5-year moving median of past GDP growth (documentation here) and the effective inflation targeted (documentation here). This reflects the idea that the long-term real interest rate should be near the real return on the existing capital stock. The assumption is that the short-term nominal interest rate drifts towards a long-term convergence rate linearly over 5 years. The term premium is then the difference between the long-term yield on a swap and the geometric average of the forward short-term rates implied by that drift.

  • The main calculations are summarized below in Appendix 1 and further details are given in Appendix 2.

  • This version of the estimated term premium is an off-the-shelf simple benchmark based on macroeconomics without complicated modelling. It is open to modification using other quantamental indicators. For example, the current state of inflation and “slack” in the economy will plausibly influence the path of a short-term rate.

  • Term premia for Turkey have been rather volatile win the last two years due to a non-updating short-term interest rate.

  • Term premia for Taiwan have been negative throughout the observation period. This is due to our approximation of the trend in real rates (r*) and growth rates (g*). The growth rate of Taiwan has been very high and this has resulted in a very high steady-state rate which in turn has pushed the resulting term premia in negative territory.

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.

# Cross-sections of interest

cids_dm = [
    "AUD",
    "CAD",
    "CHF",
    "EUR",
    "GBP",
    "JPY",
    "NOK",
    "NZD",
    "SEK",
    "USD",
]  # DM currency areas
cids_latm = ["COP", "CLP", "MXN"]  # Latam countries
cids_emea = ["CZK", "HUF", "ILS", "PLN", "RUB", "TRY", "ZAR"]  # EMEA countries
cids_emas = [
    "CNY",
    "IDR",
    "INR",
    "KRW",
    "SGD",
    "THB",
    "TWD",
]  # EM Asia countries
cids_em = cids_latm + cids_emea + cids_emas

cids = sorted(cids_dm + cids_em)
tenors = ["02", "05", "10"]

main = ["DU02YETP_NSA", "DU05YETP_NSA", "DU10YETP_NSA"]

xtra = [
    "DU02YYLD_NSA",
    "DU05YYLD_NSA",
    "DU10YYLD_NSA",
    "DU02YCRY_NSA",
    "DU05YCRY_NSA",
    "DU10YCRY_NSA",
    "INFTEFF_NSA",
    "RGDP_SA_P1Q1QL4_20QMM",
]
rets = [
    "DU02YXR_NSA",
    "DU02YXR_VT10",
    "DU05YXR_NSA",
    "DU05YXR_VT10",
    "DU10YXR_NSA",
    "DU10YXR_VT10",
]  # market links

xcats = main + xtra + rets
# Download series from J.P. Morgan DataQuery by tickers

start_date = "1996-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()
    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 459
Downloading data from JPMaQS.
Timestamp UTC:  2023-08-23 09:56:56
Connection successful!
Number of expressions requested: 1836
Requesting data: 100%|█████████████████████████████████████████████████████████████████| 92/92 [00:27<00:00,  3.30it/s]
Downloading data: 100%|████████████████████████████████████████████████████████████████| 92/92 [03:59<00:00,  2.60s/it]
Download time from DQ: 0:04:59.668652

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 DU02YETP_NSA:  ['SGD', 'NOK', 'AUD', 'EUR', 'IDR']
Missing cids for DU05YETP_NSA:  []
Missing cids for DU10YETP_NSA:  ['MXN', 'JPY', 'CZK']
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, 2))

print("Last updated:", date.today())
../_images/Term premia estimates_13_0.png
Last updated: 2023-08-23
xcatx = main
cidx = cids_exp

plot = msm.check_availability(
    dfd, xcats=xcatx, cids=cids_exp, start_size=(14, 3), start_years=False
)
../_images/Term premia estimates_14_0.png
xcatx = main
cidx = cids_exp

plot = msp.heatmap_grades(
    dfd,
    xcats=xcatx,
    cids=cidx,
    size=(14, 3),
    title=f"Average vintage grades from {start_date} onwards",
)
../_images/Term premia estimates_15_0.png

History#

IRS term premia according to the Macrosynergy method#

Estimated IRS term premia have shown the following stylized features since 2000:

  • 10-year term premia have been mostly positive, but the those for shorter maturities have been mixed and predominantly negative for many developed and emerging markets.

  • In most countries term premia decined after the great financial crisis.

  • The volatility of term premia in emerging markets has been higher than that in developed markets.

xcatx = main
cidx = cids

msp.view_ranges(
    dfd,
    xcats=xcatx,
    cids=cidx,
    sort_cids_by="std",
    start="2000-01-01",
    kind="box",
    size=(16, 8),
)

msp.view_timelines(
    dfd,
    xcats=xcatx,
    cids=cidx,
    start="2000-01-01",
    title="Estimated term premia according to the Macrosynergy methodology",
    xcat_labels=["2-year", "5-year", "10-year"],
    title_adj=1.01,
    title_fontsize=22,
    label_adj=0.05,
    ncol=4,
    cumsum=False,
    same_y=False,
    size=(12, 7),
    aspect=2,
    all_xticks=True,
)
../_images/Term premia estimates_19_0.png ../_images/Term premia estimates_19_1.png

Estimated term-premia correlations have been high, above 50%, across the panel. Correlation between 2-year and 5-year estimated term premia has been larger than between the other maturities.

xcats_sel = main
msp.correl_matrix(
    df,
    xcats=xcats_sel,
    cids=cids,
    freq="M",
    start="2003-01-01",
    size=(6, 3),
)
../_images/Term premia estimates_21_0.png

Importance#

Empirical clues#

There has been strong and significant correlation of estimated term premia and subsequent monthly or quarterly returns. This is consistent with the notion of term premia indicating risk premia being paid for duration exposure.

Remember that the JPMaQS definition of duration returns is for going long the duration exposure by receiving the fixed rate and paying the floating rate. Hence when duration increases, by failing short-term interest rates, we see positive returns (and wise versa).

cr = msp.CategoryRelations(
    dfd,
    xcats=["DU05YETP_NSA", "DU05YXR_NSA"],
    cids=cids,
    freq="Q",
    lag=1,
    xcat_aggs=["last", "sum"],
    start="2000-01-01",
)


cr.reg_scatter(
    title="Estimated term premia and subsequent IRS returns, all markets, since 2000",
    labels=False,
    prob_est="map",
    coef_box="lower right",
    ylab="5-year IRS return, next quarter",
    xlab="Estimated 5-year IRS term premium",
)
DU05YXR_NSA misses: ['ZAR'].
../_images/Term premia estimates_30_1.png

Appendices#

Appendix 1: Calculation of estimated term premia according to the macrosynergy method#

Here a term premium is the difference between a longer maturity yield and an average of estimated expected short-term yields over the maturity. In order to proxy the average short-term rate, we use the following method.

As local-currency short-term interest rate we use the main 1-month money market rate or closest proxy. For many countries the short-term interest rate has been the 1-month local-currency LIBOR rate until 2021. As of the end of 2021 LIBOR are being replaced in many countries by overnight index swap (OIS) rates. These rates have different economic meaning (as they imply almost no credit risk) but have become the new standard. For more details see the documentation on JPMaQS short-term interest rates here.

We estimate a natural short-term interest rate as the sum of expected long-term real GDP growth and expected inflation. The long-term growth rate is estimated as the last 5-year moving median of the real GDP growth rate, as documented here. Long-term expected inflation is assumed to be equal to the effective inflation target of the central bank here.

The estimated natural rate is given by the sum of the long-term real GDP growth and the effective inflation target like so:

(5)#\[\begin{equation} i^{*} = (g^{*} + \pi^{*}) \end{equation}\]

Where \(g^{*}\) is the 5-year GDP growth rate and \(\pi^{*}\) represents the effective inflation target.

We assume that the short-term interest rate converges to the natural rate linearly over a 5-year period. Accordingly, we calculate a naive curve of forward rates by computing the simple yield increment starting from the short-term yield and leading up to our proxy of the long-term GDP growth.

The expected drift in the short-term interest rate per month is given by two stages. Firstly, the increment is calculated by taking the difference between the estimated natural rate and the current short-term rate and dividing by the number of months to the 5-year convergence date. This drift applies to the first 60 months of the contract.

(6)#\[\begin{equation} di = \frac{(i^{*} - i_{0})}{N} \end{equation}\]

where \(di\) is the monthly drift or increment in the short rate, and \(i_{0}\) is the initial (current) short term rate and \(N=60\) months (5 years).

The second stage is a zero drift after the 60 months have passed, whereby we expect the steady state natural rate to prevail.

The average short-term interest rate is the mean of a vector of the short-term interest rates that results from applying the two drifts to the months to maturity of the contract. The following formula applies:

(7)#\[\begin{equation} E(i_{n}) = \begin{cases} i_{0} + di*n &\forall n \le 60 \\ i^{*} &\forall n > 60 \end{cases} \end{equation}\]

Where \(n\) represents the tenor of the contract.

We can then calculate the mean interest rate towards convergence of a 2-year, 5-year and 10-year yield as:

(8)#\[\begin{equation} 1 + \hat{y}_{n} = \left(\prod_{j=0}^{n-1}(1+E(i_{j}))\right)^{\frac{1}{n}} \end{equation}\]

Where \(\hat{y}_{n}\) is the convergence towards the long-term steady state of the IRS yield with tenor \(n\).

The term premium is the difference between the interest rate swap yield and the average short-term interest rate.

(9)#\[\begin{equation} \text{tp}_{n} = y^{irs}_{n} - \hat{y}_{n} \end{equation}\]

Appendix 2: Convergence of short-term interest rates and the term-premia#

To simplify notation in the following, we use the arithmetic term-structure decomposition as in Bauer and Rudebusch (2020). With this assumption we can write the decomposition of the fixed yields as:

(10)#\[\begin{equation} y_{t}^{(n)} = \frac{1}{n} \sum_{j=0}^{n-1} \mathbb{E}_{t}(i_{t+j}) + {TP}_{t}^{(n)} \end{equation}\]

where \(y_{t}^{(n)}\) is the fixed yield at tenor \(n\), \(i_{t}\) is the short-term (nominal) interest rate (\(\mathbb{E}_{t}(i_{t+j})\) is the expectation for period \(j\), and \({TP}_{t}^{(n)}\) is the unobserved term-premium.

We can split the above equation into it’s long-term convergence path of \(i_{t}^{*}\) and the short-term deviations:

(11)#\[\begin{equation} y_{t}^{(n)} = i_{t}^{*} + \frac{1}{n} \sum_{j=0}^{n-1} \mathbb{E}_{t}(i^{c}_{t+j}) + TP_{t}^{n} \end{equation}\]

where \(i_{t+j}^{c} = i_{t+j} - i_{t}^{*}\).

Bauer and Rudebusch who defines \(i_{t}^{*}\) as the Beveridge-Nelson trend in the short-term nominal interest rate,

(12)#\[\begin{equation} i_{t}^{∗} ≡ \lim_{j\to\infty} \mathbb{E}_{t} i_{t+j}. \end{equation}\]

In a similar spirit to Bauer and Rudebusch (2020) we use the Fisher equation of:

(13)#\[\begin{equation} i_{t} = r_{t} + E_{t}(\pi_{t+1}) \end{equation}\]

Assumign we are in the long-run (steady state) we have

(14)#\[\begin{equation} i_{t}^{*} = r^{*}_{t} + \pi_{t}^{*} \end{equation}\]

Our assumption is that the trend in real-rates (\(r_{t}^{*}\)) can be approximated by stochastic trends in real-growth rates (\(g_{t}^{*}\)), which in JPMaQS is captured by the 20-month (5-year) moving median (RGDP_SA_P1Q1QL4_20QMM).

We approximate the stochastic end-points for short-term rates (\(i_{t}^{*}\)), with long-term inflation trend (\(\pi_{t}^{*}\)) set by our effective inflation targets of each country (INFTEFF_NSA) and the equilibrium trend of real short-term rates (\(r_{t}^{*}\)) by the trend in real GDP (\(r_{t}^{*}=g_{t}^{*}\) i.e. 5-year moving median: RGDP_SA_P1Q1QL4_20QMM).

(15)#\[\begin{equation} i_{t}^{*} = g^{*}_{t} + \pi_{t}^{*}. \end{equation}\]

We assume convergence in 5-years (\(K=60\) months) with a linear path for short-term interests rates to the trend value of \(i_{t}^{*}\). We model a linear path to the convergence of the short-term interest rates over the next 5-years:

(16)#\[\begin{equation} \mathbb{E}_{t}(i_{t+j}) = w(j, K)i_{t}^{*} + (1 - w(j, K)) i_{t} \end{equation}\]

where \(w(j,K) = \min\left(\frac{j}{K}, 1\right) \;\forall j\ge 0\).

We define \(\hat{y}_{t}^{(n)}\) as the expectation component of the fixed yield (\(y_{t}^{(n)}\)):

(17)#\[\begin{equation} \hat{y}_{t}^{(n)} = \frac{1}{n} \sum_{j=0}^{n-1} \mathbb{E}_{t}(i_{t+j}) \end{equation}\]

which once we substitute in our path of convergence is equal to:

(18)#\[\begin{equation} \begin{aligned} \hat{y}_{t}^{(n)} &= \frac{1}{n} \sum_{j=0}^{n-1}\left( w(j, K)i_{t}^{*} + (1 - w(j, K)) i_{t}\right)\\ &= \frac{1}{n} \sum_{j=0}^{n-1} \min\left(\frac{j}{K}, 1\right) i_{t}^{*} + \frac{1}{n}\sum_{j=0}^{n-1} \max\left(1 - \frac{j}{K}, 0\right) i_{t} \end{aligned} \end{equation}\]

Subtracting our estimate of the expectations components from observed fixed IRS yields, we can estimate the term premiums as:

(19)#\[\begin{equation} {TP}_{t} = y_{t}^{(n)} - \hat{y}_{t}^{(n)}. \end{equation}\]
# Constants
K_CONVERGENCE: int = 60  # 60months = 5years

# Initial parameters
istar = 5.5  # i*(t)
i0_low = 3.5  # i(t) from a low starting point below i*(t)
i0_high = 7.5  # i(t) from a high starting point above i*(t)
months = np.array([mm for mm in range(120 + 1)])  # Set up of months


def expected_short_term_rate(i0, istar, n, k_converge: int = K_CONVERGENCE) -> float:
    weight = min(n / k_converge, 1)
    return weight * istar + (1 - weight) * i0


def yhat_fn(i0: float, istar: float, n: int, k_converge: int = 60) -> float:
    if n == 0:
        # Return short-term rate for no-tenor
        return i0

    # Calculate expected yield for convergence
    yhat: float = (
        sum(expected_short_term_rate(i0=i0, istar=istar, n=jj) for jj in range(n)) / n
    )
    return yhat


# Plot model
fig = plt.figure(1)

# Plot short-term rate convergence
ax = fig.add_subplot(211)
plt.scatter([0], [i0_low])
plt.scatter([0], [i0_high])
plt.plot(
    months / 12,
    [expected_short_term_rate(istar=istar, i0=i0_low, n=mm) for mm in months],
    ls="--",
    label=f"Initial low ($i_{{t}}$): {i0_low:0.1f}%",
)
plt.plot(
    months / 12,
    [expected_short_term_rate(istar=istar, i0=i0_high, n=mm) for mm in months],
    ls="--",
    label=f"Initial high ($i_{{t}}$): {i0_high:0.1f}%",
)
plt.plot(
    [min(months), max(months)],
    [istar, istar],
    ls="-",
    c="g",
    label=f"Trend short-term rate ($i_{{t}}^{{*}}$): {istar:0.1f}%",
)
plt.title("Convergence paths of short-term rates: $\mathbb{E}_{t}(i_{t+n})$")

plt.ylabel("Percent")
plt.xlabel("Tenor ($n$) in years")
plt.xlim(xmin=0, xmax=120 / 12)

# Plot convergence longer fixed yields paths
ax = fig.add_subplot(212)
plt.scatter([0], [i0_low])
plt.scatter([0], [i0_high])
plt.plot(
    months / 12,
    [yhat_fn(istar=istar, i0=i0_low, n=mm) for mm in months],
    ls="--",
    label=f"Initial low ($i_{{t}}$): {i0_low:0.1f}%",
)
plt.plot(
    months / 12,
    [yhat_fn(istar=istar, i0=i0_high, n=mm) for mm in months],
    ls="--",
    label=f"Initial high ($i_{{t}}$): {i0_high:0.1f}%",
)
plt.plot(
    [min(months), max(months)],
    [istar, istar],
    ls="-",
    c="g",
    label=f"Trend short-term rate ($i_{{t}}^{{*}}$): {istar:0.1f}%",
)
plt.title("Expectations component of fixed-yields: $\hat{y}_{t}^{(n)}$")

plt.ylabel("Percent")
plt.xlabel("Tenor ($n$) in years")
plt.xlim(xmin=0, xmax=120 / 12)

plt.grid(True)
for ax in fig.get_axes():
    ax.label_outer()

legend = plt.legend(
    loc="lower center",
    ncol=4,
    frameon=True,
    fontsize=9,
    bbox_to_anchor=(0.5, -0.03),
    bbox_transform=plt.gcf().transFigure,
)
legend.set_zorder(60)
fig.tight_layout()
plt.show()
../_images/Term premia estimates_35_0.png

Natural convergence point:#

From JPMaQS we have the convergence point of short-term rates (\(i_{t}^{*}\)) given by the two components of trend growth and effective inflation targets. Both are available as seperate indicators in JPMaQS, and hence for convenience we show them below.

Remember our assumption that we approximate the trend in real-rates (\(r_{t}^{*}\)) by growth rates (\(g_{t}^{*}\)). As can be seen both inflation trends and growth trends show persistenty changes to their trends throughout the periods. This is in line with findings such as Bauer and Rudebusch (2020).

cidx = cids
msp.view_timelines(
    dfd,
    xcats=["INFTEFF_NSA", "RGDP_SA_P1Q1QL4_20QMM"],
    cids=cidx,
    start="2000-01-01",
    title="Natural short-term rate estimate components",
    xcat_labels=[
        "Effective inflation target ($\pi^{*}_{t}$)",
        "Real GDP growth trend ($g_{t}^{*}$)",
    ],
    title_adj=1.01,
    title_fontsize=22,
    label_adj=0.05,
    ncol=4,
    cumsum=False,
    same_y=False,
    size=(12, 7),
    aspect=2,
    all_xticks=True,
)
../_images/Term premia estimates_37_0.png