
The investment universe consists of 2 United States (US) Treasury Bill market bonds: 10-year and one-month bonds.
ASSET CLASS: CFDs, ETFs, forwards, funds, futures, swaps | REGION: United States | FREQUENCY:
Monthly | MARKET: bonds | KEYWORD: 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 Return | 8.08% |
| Volatility | 7.45% |
| Beta | -0.053 |
| Sharpe Ratio | 1.08 |
| Sortino Ratio | -0.067 |
| Maximum Drawdown | -17.8% |
| Win Rate | 45% |
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