Introduction

During the initial outbreak of the COVID March 2020 the safety that the 60-40 stock-bonds portfolio offered seemed to break down, leading investors to seek new uncorrelated assets to hedge portfolios in times of crisis. This micro-study aims to determine the new 60-40 portfolio, as the interest from idle cash start to diminish. It uses machine learning to select and weight portfolio assets based on the magnitude of the predicted returns. 

The trading strategy in this research post uses machine learning and economic factors to manage a portfolio of risk-on and risk-off assets. The algorithm rebalances the portfolio at the start of every month. During each rebalance, it allocates a portion of the portfolio to each asset the regression model predicts will have a positive return over the following month, scaling the positions based on the magnitude of the predicted returns.  

Background

A risk-on environment occurs when investors are optimistic about market conditions, so they invest in assets that are riskier but offer the potential for higher returns, like the S&P 500 and Bitcoin. A risk-off environment occurs when investors are cautious about market conditions, so they invest in more stable assets, like gold and bonds. Investors can try to rotate their entire portfolio between these assets when they believe the environment has changed, but an approach that offers more diversification and less timing risk is to incrementally scale in and out of each asset over time.

This research scales the position size of each asset based on what a decision tree regression model predicts will be its total return over the next month. Total return is the return of holding an asset and reinvesting dividends into the asset. For instance, the Vanguard Total Bond Market Index Fund (BND) holds a wide spectrum of bonds and asset-backed securities. The interest from these bonds, minus the fund’s expenses, is what provides the dividend payments. Many factors influence the bond yields, including interest rates, credit quality, and economic conditions. The market price of BND typically hovers around its net asset value, which is the per-share value of the fund's underlying assets minus its liabilities.

To predict the future total return of each asset, the algorithm feeds the following factors into a decision tree regression model:

  • VIX: The Cboe Volatility Index (VIX) is a 30-day forward projection of volatility for the S&P 500 Index. High VIX levels might indicate fear in the market, potentially favoring risk-off assets.
  • Yield Spread: The yield spread is the difference between the yields of two different maturities on the US Treasury Yield Curve. When the 3-month yield is lower than the 10-year yield, it signals stable economic conditions, which typically suggests stock growth. When the 3-month yield is higher than the 10-year yield, the yield curve is inverted, which has historically been a predictor of economic recessions.
  • Interest Rate: The US Interest Rate is the primary credit rate from the Federal Open Market Committee (FOMC). As the interest rate decreases, investors have more capital to invest in risky assets. As the interest rate increases, the risk-free rate increases so investors typically favor risk-off assets.

Implementation

To implement this strategy, we start by adding the assets in the initialize method.

self._bitcoin = self.add_crypto("BTCUSD", market=Market.BITFINEX, leverage=2).symbol
self._equities = [self.add_equity(ticker).symbol for ticker in ['SPY', 'GLD', 'BND']]

We also add the factors. These were all sourced from Federal Reserve Economic Data (FRED) to consolidate code.

self._factors = [self.add_data(Fred, ticker, Resolution.DAILY).symbol for ticker in ['VIXCLS', 'T10Y3M', 'DFF']]

Next, we instantiate the decision tree regression model and a StandardScaler to standardize the factors.

self._model = DecisionTreeRegressor(max_depth=12, random_state=1) 
self._scaler = StandardScaler()

At the bottom of the initialize method, we add a monthly Scheduled Event to handle the rebalances.

self.schedule.on(self.date_rules.month_start(self._equities[0]), self.time_rules.after_market_open(self._equities[0], 1), self._rebalance)

In the _rebalance method, we get the factor history. We chose 4 years of history because Bitcoin’s halving cycle is about 4 years long.

factors = self.history(self._factors, timedelta(4*365), Resolution.DAILY)['value'].unstack(0).dropna()

We also get the label history.

label = self.history(self._equities + [self._bitcoin], timedelta(4*365), Resolution.DAILY, data_normalization_mode=DataNormalizationMode.TOTAL_RETURN)['close'].unstack(0).dropna().pct_change(21).shift(-21).dropna()

Next, we predict the future total return of each asset.

prediction_by_symbol = pd.Series()
for symbol in self._equities + [self._bitcoin]:
    asset_labels = label[symbol].dropna()
    idx = factors.index.intersection(asset_labels.index)
    self._model.fit(self._scaler.fit_transform(factors.loc[idx]), asset_labels.loc[idx])
    prediction = self._model.predict(self._scaler.transform([factors.iloc[-1]]))[0]
    if prediction > 0:
        prediction_by_symbol.loc[symbol] = prediction

At this point, we set the target weight of each asset to be based on the magnitude of the prediction.

weight_by_symbol = 1.5 * prediction_by_symbol / prediction_by_symbol.sum()

Bitcoin is very volatile, so let’s limit its target weight to 10%.

if self._bitcoin in weight_by_symbol and weight_by_symbol.loc[self._bitcoin] > 0.1:
    weight_by_symbol.loc[self._bitcoin] = 0.1
    if len(weight_by_symbol) > 1:
        equities = [symbol for symbol in self._equities if symbol in weight_by_symbol]
        weight_by_symbol.loc[equities] = 1.5 * weight_by_symbol.loc[equities] / weight_by_symbol.loc[equities].sum()

At the end of the _rebalance method, we place the trades.

self.set_holdings([PortfolioTarget(symbol, weight) for symbol, weight in weight_by_symbol.items()], True)

Conclusion

The trading strategy in this research post uses a decision tree regression model to manage a portfolio of risk-on and risk-off assets based on the VIX, yield spread, and interest rate. We backtested the strategy from January 2017 to September 2024. The results show that the strategy achieved a 0.794 Sharpe ratio. In contrast, buying and holding the SPY achieves a 0.564 Sharpe ratio over the same time period. Therefore, the strategy outperforms the benchmark.

To test the robustness of the strategy, we ran an optimization job. We varied the lookback period from 2 years to 8 years in steps of 2 and we varied the maximum Bitcoin weight from 2% to 14% in steps of 2%. Of the 49 strategy variants, 32/49 (65%) outperformed buying and holding the SPY. All variants with a lookback period of 4 years outperformed the benchmark. Moreover, as the maximum Bitcoin weight increased, the Sharpe ratio increased, regardless of the lookback period.

derek-melchin_1726679489.jpg