
Investment universe: 5 ETFs (SPY, EFA, BND, VNQ, GSG). Portfolio equally weighted. Hold if over 10-month SMA, else cash.
ASSET CLASS: CFDs, ETFs, funds, futures | REGION: Global | FREQUENCY: Monthly | MARKET: bonds, commodities, equities, REITs | KEYWORD: Trend-following
I. 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.
II. 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.
III. SOURCE PAPER
A Quantitative Approach to Tactical Asset Allocation [Click to Open PDF]
”Meb Faber”, ”Cambria Investment Management”
<Abstract>
In this paper we update our 2006 white paper “A Quantitative Approach to Tactical Asset Allocation” with new data from the 2008-2012 period. How well did the purpose of the original paper – to present a simple quantitative method that improves the risk-adjusted returns across various asset classes – hold up since publication? Overall, we find that the models have performed well in real-time, achieving equity like returns with bond like volatility and drawdowns. We also examine the effects of departures from the original system including adding more asset classes, introducing various portfolio allocations, and implementing alternative cash management strategies.

IV. BACKTEST PERFORMANCE
| Annualised Return | 11.50% |
| Volatility | 6.90% |
| Beta | 0.302 |
| Sharpe Ratio | 1.40 |
| Sortino Ratio | 1.23 |
| Maximum Drawdown | –29.4% |
| Win Rate | 83% |
V. 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