The investment universe consists of 2 United States (US) Treasury Bill market bonds: 10-year and one-month bonds.

I. STRATEGY IN A NUTSHELL

Monthly bond strategy on US Treasuries: invest in 10-year T-bonds when term premium is below its 40-month moving average; otherwise, invest in 1-month T-bills. Fully weighted; rebalanced monthly.

II. ECONOMIC RATIONALE

The term premium predicts regimes with higher risk-adjusted returns. Using moving-average signals, the strategy times duration risk effectively, improving returns relative to constant-duration exposure in US and SA markets.

III. SOURCE PAPER

Trading the Term Premium [Click to Open PDF]

Luke van Schaik, Peresec ; Emlyn Flint, University of Cape Town ; Florence Chikurunhe, Peregrine Securities

<Abstract>

The proliferation of factor investing strategies in recent years has highlighted the idea that a portfolio can harvest improved risk-adjusted returns through timed exposure to risk factors during times of elevated risk premia. While there is a large body of research on such risk factors and risk premia in equity markets, there has been relatively little research on the topic in fixed income markets.

One such fixed income risk factor that has begun to receive interest from practitioners and academics is the term premium, the risk premium associated with duration risk in bonds. Adrian et al. (ACM) (2013) introduced a sophisticated affine term structure model which is able to efficiently estimate the term premium using linear regressions. While the model appears to align with economic theory, little work has been done on investigating practical applications of the term premium in timing exposure to duration risk in a bond portfolio.

This report investigates the practical applicability of the ACM model in the South African and United States sovereign bond markets, finding that signals generated from the model are able to capture regimes of increased risk-adjusted returns. Using these signals in systematic strategies also generates promising results.

IV. BACKTEST PERFORMANCE

Annualised Return8.08%
Volatility7.45%
Beta-0.053
Sharpe Ratio1.08
Sortino Ratio-0.067
Maximum Drawdown-17.8%
Win Rate45%

V. FULL PYTHON CODE

from AlgorithmImports import *
from pandas.core.frame import dataframe
# endregion

class TermSpreadandTermPremiumPredictUSGovernmentBondsReturns(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2007, 6, 1) # BIL inception
        self.SetCash(100000)

        self.long_duration_bond:Symbol = self.AddEquity('IEF', Resolution.Daily).Symbol
        self.short_duration_bond:Symbol = self.AddEquity('BIL', Resolution.Daily).Symbol
        self.term_spread:Symbol = self.AddData(TermSpread, 'T10Y3M', Resolution.Daily).Symbol

        self.period:int = 40 * 21
        self.rebalance_flag:bool = False

        self.term_spread_sma = self.SMA(self.term_spread, self.period, Resolution.Daily)
        self.SetWarmup(self.period, Resolution.Daily)

        self.recent_month:int = -1

    def OnData(self, data: Slice) -> None:
        if self.IsWarmingUp:
            return

        t10y3m_last_update_date:datetime.date = TermSpread._last_update_date

        # check if custom data is still arriving
        if self.Securities[self.term_spread].GetLastData() and self.Time.date() >= t10y3m_last_update_date:
            self.Liquidate()
            return

        # rebalance monthly
        if self.Time.month == self.recent_month:
            return
        self.recent_month = self.Time.month

        # compare latest value with 40-month moving average
        traded_asset:Symbol|None = None
        risk_flag:bool = False

        if self.term_spread in data and data[self.term_spread]:
            if data[self.term_spread].Price < self.term_spread_sma.Current.Value:
                traded_asset = self.long_duration_bond
            else:
                traded_asset = self.short_duration_bond

        # trade execution
        if all(x in data and data[x] for x in [self.long_duration_bond, self.short_duration_bond]):
            if not self.Portfolio[traded_asset].Invested:
                self.Liquidate()
                self.SetHoldings(traded_asset, 1)

# Source: https://fred.stlouisfed.org/series/T10Y3M
class TermSpread(PythonData):
    def GetSource(self, config, date, isLiveMode):
        return SubscriptionDataSource('data.quantpedia.com/backtesting_data/economic/T10Y3M.csv', SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)

    _last_update_date:datetime.date = datetime(1,1,1).date()

    @staticmethod
    def get_last_update_date() -> Dict[Symbol, datetime.date]:
       return TermSpread._last_update_date

    def Reader(self, config, line, date, isLiveMode):
        data = TermSpread()
        data.Symbol = config.Symbol

        if not line[0].isdigit(): return None
        split = line.split(';')
        
        # Parse the CSV file's columns into the custom data class
        data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
        if split[1] != '.':
            data.Value = float(split[1])

        if data.Time.date() > TermSpread._last_update_date:
            TermSpread._last_update_date = data.Time.date()
        
        return data

Leave a Reply

Discover more from Quant Buffet

Subscribe now to keep reading and get access to the full archive.

Continue reading