Reversal – Yield Change Factor in Fixed Income
Log in to collectAcademic paper
Beyond Carry and Momentum in Government Bonds
Jérôme Gava; William Lefebvre; Julien Turc
- École Polytechnique
- BNP Paribas (France)
- ?BNP Paribas
- ?Ecole Polytechnique
- ?Laboratoire de Probabilités, Statistique et Modélisation
- ?Laboratoire de Probabilités, Statistique et Modélisation (LPSM)
Strategy in a nutshell
The strategy trades 10-year government bond futures from Australia, Canada, Germany, the UK, and the US using a reversal factor based on yield changes over 3 months, 6 months, and 6 years. Allocations follow bottom, median, or top approaches. Portfolios buy/sell proportionally based on cross-sectional scores relative to the average, adjusted for dispersion, with equal-weighted allocations across countries to neutralize directional bias. Rebalanced monthly, the strategy systematically leverages reversal factors for bond decisions.
Economic rationale
Yield changes, typically a value indicator, serve here as a reversal factor for predicting bond futures. While no fundamental theory explains this, statistical and machine learning analyses confirm its reliability. Reversal is uncorrelated with carry, a traditional bond driver, making it a distinct and effective factor for portfolio construction and enhancing bond investment outcomes.
Backtest performance
Full Python code
import numpy as np
from AlgorithmImports import *
import data_tools
class ReversalYieldChangeFactor(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbols = {
"ASX_XT1" : "AU10YT", # 10 Year Commonwealth Treasury Bond Futures, Continuous Contract #1 (Australia)
"MX_CGB1" : "CA10YT", # Ten-Year Government of Canada Bond Futures, Continuous Contract #1 (Canada)
"EUREX_FGBL1" : "DE10YT", # Euro-Bund (10Y) Futures, Continuous Contract #1 (Germany)
"LIFFE_R1" : "GB10YT", # Long Gilt Futures, Continuous Contract #1 (U.K.)
"CME_TY1" : "US10YT" # 10 Yr Note Futures, Continuous Contract #1 (USA)
}
self.bond_yield = {} # Bond yield data
self.period = 6*21
self.SetWarmUp(self.period)
for symbol in self.symbols:
data = self.AddData(data_tools.QuantpediaFutures, symbol, Resolution.Daily)
data.SetFeeModel(data_tools.CustomFeeModel())
bond_yield_symbol = self.symbols[symbol]
self.AddData(data_tools.QuantpediaBondYield, bond_yield_symbol, Resolution.Daily)
self.bond_yield[symbol] = RollingWindow[float](self.period)
first_key = [x for x in self.symbols.keys()][0]
self.rebalance_flag: bool = False
self.Schedule.On(self.DateRules.MonthStart(first_key), self.TimeRules.At(0, 0), self.Rebalance)
self.settings.minimum_order_margin_portfolio_percentage = 0.
def OnData(self, data):
# store daily bond yield values
for symbol in self.symbols:
yield_symbol = self.symbols[symbol]
if yield_symbol in data and data[yield_symbol]:
bond_yield = data[yield_symbol].Value
self.bond_yield[symbol].Add(bond_yield)
if not self.rebalance_flag:
return
self.rebalance_flag = False
yield_change = {
symbol : self.bond_yield[symbol][self.period-1] - self.bond_yield[symbol][0]
for symbol, bond in self.symbols.items()
if self.bond_yield[symbol].IsReady
and all([self.Securities[x].GetLastData() and self.Time.date() < data_tools.LastDateHandler.get_last_update_date()[x]] for x in [symbol, bond])
}
if len(yield_change) <= 2: return
avg_change = np.average([x[1] for x in yield_change.items()])
diff = {
symbol : avg_change - yield_change[symbol] for symbol in yield_change
}
total_diff = sum([abs(x[1]) for x in diff.items()])
targets: List[PortfolioTarget] = []
for symbol in diff:
if symbol in data and data[symbol]:
targets.append(PortfolioTarget(symbol, diff[symbol]/total_diff))
self.SetHoldings(targets, True)
def Rebalance(self):
self.rebalance_flag = True