
Employ ten sector ETFs, selecting the top 3 based on 12-month momentum, evenly weighting them in your portfolio. Rebalance monthly for optimal performance.
ASSET CLASS: ETFs, funds, stocks | REGION: United States | FREQUENCY: Monthly | MARKET: Equity | KEYWORD: Momentum
STRATEGY IN A NUTSHELL
Employ a diverse portfolio comprising ten sector ETFs. Identify the top-performing 3 ETFs based on their robust 12-month momentum, allocating equal weights to each. Maintain this allocation for one month before rebalancing. This strategy capitalizes on sectors exhibiting strong performance trends while ensuring risk mitigation through diversification. By regularly reassessing and adjusting the portfolio, investors can adapt to market changes effectively. The approach leverages momentum indicators to guide investment decisions, aligning with prevailing market trends. Through disciplined rebalancing, the portfolio remains agile, potentially enhancing returns by capitalizing on evolving market dynamics.
ECONOMIC RATIONALE
The momentum anomaly stems from behavioral biases like herding and confirmation bias. Moskowitz and Grinblatt find industry momentum strategies profitable even after accounting for various factors. Chen, Jiang, and Zhu note the profitability of stock momentum but highlight transaction costs. They also find momentum in sector indexes, which remain profitable after adjusting for costs. Andreu, Swinkels, and Tjong-A-Tjoe demonstrate the effectiveness of ETFs in exploiting country and industry momentum, yielding significant excess returns. Sector rotation based on business cycle sensitivity further enhances profitability by focusing on sectors with high return probabilities and low loss probabilities. This integrated approach underscores the viability of momentum strategies in capturing market inefficiencies and generating substantial returns.
SOURCE PAPER
Relative Strength Strategies for Investing [Click to Open PDF]
”Meb Faber”, ”Cambria Investment Management”
<Abstract>
The purpose of this paper is to present simple quantitative methods that improve risk-adjusted returns for investing in US equity sectors and global asset class portfolios. A relative strength model is tested on the French-Fama US equity sector data back to the 1920s that results in increased absolute returns with equity-like risk. The relative strength portfolios outperform the buy and hold benchmark in approximately 70% of all years and returns are persistent across time. The addition of a trend-following parameter to dynamically hedge the portfolio decreases both volatility and drawdown. The relative strength model is then tested across a portfolio of global asset classes with supporting results.

BACKTEST PERFORMANCE
| Annualised Return | 11.30% |
| Volatility | 6.90% |
| Beta | 0.680 |
| Sharpe Ratio | 1.37 |
| Sortino Ratio | 1.32 |
| Maximum Drawdown | 29.40% |
| Win Rate | 71% |
FULL PYTHON CODE
from AlgoLib import *
class SectorMomentumAlgorithm(XXX):
'''
A sector momentum trading algorithm that selects sectors based on their momentum,
rebalances monthly, and leverages the AlgoLib for executing trades and managing portfolio.
'''
def Initialize(self):
'''
Initializes the trading algorithm settings, including start date, initial cash,
selected sectors, momentum period, and warm-up period.
'''
self.SetStartDate(2015, 1, 1) # Set the start date of the algorithm
self.SetCash(100000) # Set the initial cash amount
# Initialize dictionary to store monthly momentum data for each sector
self.data:Dict[str, Momentum] = {}
# Set the period (in trading days) over which to measure momentum
self.momentum_period:int = 252
# Set the algorithm to warm up with the specified number of days of data
self.SetWarmUp(self.momentum_period, Resolution.Daily)
# Number of top sectors to select based on momentum
self.selected_symbol_count:int = 3
# Define the list of sectors to be considered for momentum trading
self.selected_sectors:List[str] = [
"XLK", # Technology
"XLE", # Energy
"XLV", # Health Care
"XLF", # Financial
"XLI", # Industrial
"XLB", # Materials
"XLY", # Consumer Discretionary
"XLP", # Consumer Staples
"XLU" # Utilities
]
# Loop through the selected sectors, add them to the algorithm, set leverage, and calculate momentum
for sector in self.selected_sectors:
data = self.AddEquity(sector, Resolution.Daily)
data.SetLeverage(2) # Set leverage for each sector
# Calculate momentum for each sector and store it
self.data[sector] = self.MOMP(sector, self.momentum_period, Resolution.Daily)
# Subscribe to the momentum update event for the first sector
self.data[self.selected_sectors[0]].Updated += self.OnMomentumUpdated
# Variable to store the most recent month processed
self.recent_month:int = -1
# Flag to indicate when rebalancing should occur
self.rebalance_flag:bool = False
def OnMomentumUpdated(self, sender, updated) -> None:
'''
Event handler for momentum updates. Sets a flag to trigger rebalancing
if the current month has changed since the last update.
'''
# Set rebalance flag if the month has changed
if self.recent_month != self.Time.month:
self.recent_month = self.Time.month
self.rebalance_flag = True
def OnData(self, data: Slice) -> None:
'''
Called on new data. Rebalances the portfolio monthly based on momentum,
liquidating positions not in the top momentum sectors and investing in new top sectors.
'''
if self.IsWarmingUp: return # Skip if the algorithm is still warming up
# Check if it's time to rebalance
if self.rebalance_flag:
self.rebalance_flag = False # Reset the rebalance flag
# Sort sectors by momentum and filter out those not ready or not present in the latest data
sorted_by_momentum:List = sorted([x for x in self.data.items() if x[1].IsReady and \
x[0] in self.selected_sectors and \
x[0] in data and data[x[0]]], \
key = lambda x: x[1].Current.Value, reverse = True)
# Liquidate if there are not enough sectors to select
if len(sorted_by_momentum) < self.selected_symbol_count:
self.Liquidate()
return
# Select top sectors based on momentum
selected_sectors:List[str] = [x[0] for x in sorted_by_momentum[:self.selected_symbol_count]]
# Liquidate positions not in the selected sectors
invested_sectors:List[str] = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
for sector in invested_sectors:
if sector not in selected_sectors:
self.Liquidate(sector)
# Allocate equal weights to selected sectors
for sector in selected_sectors:
self.SetHoldings(sector, 1 / len(selected_sectors))
# Custom fee model
class CustomFeeModel(FeeModel):
'''
Custom fee model that calculates trading fees based on the price and quantity of the order,
set to 0% for demonstration purposes.
'''
def GetOrderFee(self, parameters):
'''
Calculates the order fee based on trade parameters.
'''
# Calculate fee as 0% of the trade value
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.0
return OrderFee(CashAmount(fee, "USD"))