The strategy invests in 10-year bonds from 54 countries, longing top-return quintiles, shorting bottom-return quintiles, with equal weighting and monthly rebalancing based on past month’s performance.

I. STRATEGY IN A NUTSHELL

Trades 10-year government bonds from 54 countries, ranking by past month’s returns. Goes long on the top quintile and short on the bottom quintile, with equally weighted portfolios rebalanced monthly.

II. ECONOMIC RATIONALE

Last month’s bond returns predict future short-term performance across asset classes. This momentum is independent of traditional factors, robust across periods, and reflects a common, persistent short-term return pattern.

III. SOURCE PAPER

Short-Term Momentum (Almost) Everywhere [Click to Open PDF]

Adam Zaremba, Andreas Karathanasopoulos, Huaigang Long.Montpellier Business School; Poznan University of Economics and Business; University of Cape Town (UCT).University of Dubai.Zhejiang University; Zhejiang University of Finance and Economics (ZUFE)

<Abstract>

Is there a short-term reversal effect outside the universe of individual stocks? To answer this, we investigate a comprehensive dataset of more than two centuries of returns on five major asset classes: equity indices, government bonds, treasury bills, commodities, and currencies. Contrary to stock-level evidence, we find a striking short-term momentum pattern: the most recent month’s return positively predicts future performance. The effect is not explained by established return predictors — including the standard momentum — and is robust to many considerations. The short-term momentum is strongest among assets of high idiosyncratic volatility and in periods of elevated return dispersion. Also, the strategy payoffs display partial commonality across different asset classes.

IV. BACKTEST PERFORMANCE

Annualised Return6.04%
Volatility9.69%
Beta-0.037
Sharpe Ratio0.62
Sortino Ratio-0.305
Maximum DrawdownN/A
Win Rate49%

V. FULL PYTHON CODE

from AlgorithmImports import *
class OneMonthMomentum(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)
        self.symbols = [
            "EUREX_FGBL1",    # Euro-Bund (10Y) Futures, Continuous Contract #1 (Germany)
            "CME_TY1",        # 10 Yr Note Futures, Continuous Contract #1 (USA)
            "MX_CGB1",        # Ten-Year Government of Canada Bond Futures, Continuous Contract #1 (Canada)
            "ASX_XT1",        # 10 Year Commonwealth Treasury Bond Futures, Continuous Contract #1 (Australia)
            "SGX_JB1",        # SGX 10-Year Mini Japanese Government Bond Futures, Continuous Contract #1 (Japan)
            "LIFFE_R1",       # Long Gilt Futures, Continuous Contract #1 (U.K.)
            "EUREX_FBTP1"     # Long-Term Euro-BTP Futures, Continuous Contract #1 (Italy)
        ]
        
        self.period = 21
        self.quantile = 5
        self.SetWarmUp(self.period)
        
        # Daily ROC data.
        self.data = {}
        
        for symbol in self.symbols:
            data = self.AddData(QuantpediaFutures, symbol, Resolution.Daily)
            data.SetFeeModel(CustomFeeModel())
            data.SetLeverage(5)
            
            self.data[symbol] = self.ROC(symbol, self.period, Resolution.Daily)
        
        self.rebalance_flag: bool = False
        self.Schedule.On(self.DateRules.MonthStart(self.symbols[0]), self.TimeRules.At(0, 0), self.Rebalance)
        self.settings.daily_precise_end_time = False
        self.settings.minimum_order_margin_portfolio_percentage = 0.
    
    def on_data(self, slice: Slice) -> None:
        if not self.rebalance_flag:
            return
        self.rebalance_flag = False
        # Return sorting.
        sorted_by_return = sorted([x for x in self.data.items() if x[1].IsReady and self.Securities[x[0]].GetLastData() and self.Time.date() < QuantpediaFutures.get_last_update_date()[x[0]] and slice.contains_key(x[0]) and slice[x[0]]], key = lambda x: x[1].Current.Value, reverse = True)
        long = []
        short = []
        if len(sorted_by_return) >= self.quantile:
            quintile = int(len(sorted_by_return) / self.quantile)
            long = [x[0] for x in sorted_by_return[:quintile]]
            short = [x[0] for x in sorted_by_return[-quintile:]]
        # Trade execution.
        targets: List[PortfolioTarget] = []
        for i, portfolio in enumerate([long, short]):
            for symbol in portfolio:
                if slice.contains_key(symbol) and slice[symbol]:
                    targets.append(PortfolioTarget(symbol, ((-1) ** i) / len(portfolio)))
        
        self.SetHoldings(targets, True)
    def Rebalance(self):
        self.rebalance_flag = True
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
    _last_update_date:Dict[Symbol, datetime.date] = {}
    @staticmethod
    def get_last_update_date() -> Dict[Symbol, datetime.date]:
       return QuantpediaFutures._last_update_date
    def GetSource(self, config, date, isLiveMode):
        return SubscriptionDataSource("data.quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
    def Reader(self, config, line, date, isLiveMode):
        data = QuantpediaFutures()
        data.Symbol = config.Symbol
        
        if not line[0].isdigit(): return None
        split = line.split(';')
        
        data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
        data['back_adjusted'] = float(split[1])
        data['spliced'] = float(split[2])
        data.Value = float(split[1])
        if config.Symbol.Value not in QuantpediaFutures._last_update_date:
            QuantpediaFutures._last_update_date[config.Symbol.Value] = datetime(1,1,1).date()
        if data.Time.date() > QuantpediaFutures._last_update_date[config.Symbol.Value]:
            QuantpediaFutures._last_update_date[config.Symbol.Value] = data.Time.date()
        return data
# Custom fee model.
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))

VI. Backtest Performance

Leave a Reply

Discover more from Quant Buffet

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

Continue reading