Quant BuffetRelax, Not Over Thinking

Global 5-Asset Trend-Following Strategy Using 10-Month SMA Filter

Log in to collect

Academic paper

AuthorsMeb Faber; Cambria Investment Management

Teaser

Investment universe: 5 ETFs (SPY, EFA, BND, VNQ, GSG). Portfolio equally weighted. Hold if over 10-month SMA, else cash.

Strategy in a nutshell

The investment universe comprises five ETFs: SPY (US stocks), EFA (foreign stocks), BND (bonds), VNQ (REITs), and GSG (commodities). The portfolio maintains equal weighting across these ETFs. Each asset class ETF is held only when its value exceeds its ten-month Simple Moving Average (SMA); otherwise, the portfolio remains in cash. This strategy aims to capitalize on favourable market conditions indicated by the SMA while avoiding exposure to asset classes experiencing downward trends, thereby optimizing investment performance.

Economic rationale

The strategy operates on a simple premise: momentum or trend-following filters identify periods of lower performance coupled with higher volatility, as well as periods of higher performance with lower volatility. Successful implementation naturally leads to outperforming a passive buy-and-hold approach. This strategy consistently outperforms passive holding with lower drawdowns, as demonstrated in research findings. By timing each asset class appropriately, it surpasses passive strategies while minimizing risks. Diversification benefits from low asset correlation are leveraged in a multi-asset portfolio with momentum filters, enabling investment in favorable asset classes while avoiding unfavorable ones. Practically, since 1973, adopting this strategy would have enhanced risk-adjusted returns through diversified assets and market timing, avoiding significant losses during bear markets. This approach offers equity-like returns with bond-like volatility and drawdowns, as highlighted by Collie, Sylvanus, and Thomas. They emphasize the volatility of market volatility, advocating for adaptable asset allocation to navigate market fluctuations effectively.

Backtest performance

Annualised return11.50%
Volatility6.90%
Beta0.302
Sharpe ratio1.40
Sortino ratio1.23
Maximum drawdown- 29.4%
Win rate83%

Full Python code

from AlgoLib import *

class AssetClassTrendFollowing(QCAlgorithm):
'''
A quant trading algorithm that implements a trend-following strategy across multiple asset classes.
It rebalances the portfolio monthly based on the assets' prices relative to their moving averages.
'''

def Initialize(self):
'''
Initializes the algorithm settings, including start date, initial cash, selected assets, and moving average period.
It also configures the moving averages for each asset and sets up the algorithm warm-up period.
'''

self.SetStartDate(2000, 1, 1)  # Sets the start date of the backtest
self.SetCash(100000)  # Sets the initial cash for the backtest

assets = ["SPY", "EFA", "IEF", "VNQ", "GSG"]  # List of asset symbols to include in the strategy
moving_avg_period = 10 * 21  # Moving average period (10 months, assuming 21 trading days per month)

# Dictionary to store the moving average indicator for each asset
self.asset_moving_avgs = { 
    self.AddEquity(asset, Resolution.Minute).Symbol: self.SMA(asset, moving_avg_period, Resolution.Daily) for asset in assets 
}

self.last_rebalance_month = -1  # Tracks the last month the portfolio was rebalanced
self.SetWarmUp(moving_avg_period, Resolution.Daily)  # Sets the warm-up period for the algorithm
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.  # Sets the minimum margin for order portfolio percentage

def OnData(self, data: Slice) -> None:
'''
The event handler for new data. It rebalances the portfolio once a month based on the defined trend-following strategy.
'''

if self.IsWarmingUp: 
    return  # Returns immediately if the algorithm is still warming up

if not (self.Time.hour == 9 and self.Time.minute == 31):
    return  # Ensures rebalancing happens at the beginning of the trading day (9:31 AM)

# Checks if it's time to rebalance (once a month)
if self.Time.month == self.last_rebalance_month:
    return  # If already rebalanced this month, do nothing
self.last_rebalance_month = self.Time.month  # Updates the last rebalance month

# Identifies long assets based on their current price vs moving average
long_assets = [symbol for symbol, moving_avg in self.asset_moving_avgs.items() 
    if symbol in data 
    and data[symbol] 
    and moving_avg.IsReady 
    and data[symbol].Value > moving_avg.Current.Value
]

# Calculates the target portfolio weights for the assets to go long
portfolio_targets = [PortfolioTarget(asset, 1. / len(long_assets)) for asset in long_assets]
self.SetHoldings(portfolio_targets, True)  # Rebalances the portfolio based on the target weights