This is the second strategy in my series testing whether backtested strategies hold up over extended periods. After finding that the January Effect strategy fell apart over 27 years, I wanted to test a more sophisticated approach.

What This Strategy Does:
The Drawdown Regime Gold Hedge uses a Hidden Markov Model (HMM) to detect market drawdown regimes in SPY. When the model detects a high-drawdown regime, it rotates the portfolio from SPY into GLD as a hedge. When the regime shifts back to normal, it returns to SPY. The strategy rebalances weekly and uses minute-resolution data for both assets.
Key parameters include a 50-week history lookback for HMM fitting and a 20-week drawdown lookback window.
6-Year Backtest (2019-2025) - "Dancing Violet Badger":
- Sharpe Ratio: 0.823
- PSR: 43.781%
- Sortino: 0.881
- CAGR: 19.787%
- Net Profit: 195.39%
- Max Drawdown: 27.1%
- Win Rate: 79%
- Total Orders: 414
20-Year Backtest (2005-2025) - "Adaptable Brown Owl":
- Sharpe Ratio: 0.469
- PSR: 1.337%
- Sortino: 0.495
- CAGR: 12.197%
- Net Profit: 1015.99%
- Max Drawdown: 41%
- Win Rate: 70%
- Total Orders: 1450
Key Observations:
1. Unlike the January Effect, this strategy actually survives the extended test. A 12.2% CAGR over 20 years turning $1M into $11.1M is meaningful.
2. However, the Sharpe dropped from 0.823 to 0.469 and max drawdown increased from 27% to 41% - the 6-year window was clearly a favorable period.
3. The PSR dropped dramatically from 43.8% to 1.3%, suggesting the statistical significance of the Sharpe is weak over the longer period.
4. Win rate decreased from 79% to 70%, and expectancy dropped from 0.533 to 0.378.
5. QC flags this as "Likely Overfitting" due to 16 parameters, which is a valid concern for an HMM-based approach.
Lessons Learned:
- A strategy that still makes money over 20 years is far more trustworthy than one that only works in a short window
- The degradation in metrics from short to long periods tells you how much of the performance was regime-specific vs structural
- HMM-based regime detection adds value but is sensitive to parameter choices
- Always test the longest period your data allows before trusting any strategy
Full code attached below. Would love to hear thoughts on improving the robustness of regime-detection approaches.
Here is the full Python code:
# Drawdown Regime Gold Hedge
# Defensive hedge strategy that monitors SPY drawdown using rolling windows to detect market stress periods.
# When significant drawdowns are detected, the strategy shifts allocation from SPY to GLD (gold ETF) as a safe haven hedge.
# Weekly rebalancing adjusts positions based on current drawdown regime analysis, aiming to protect capital during market downturns
# while maintaining exposure during normal conditions.
# region imports
from AlgorithmImports import *
from scipy.optimize import minimize
from hmmlearn.hmm import GMMHMM
# endregion
np.random.seed(70)
class DrawdownRegimeGoldHedgeAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2005, 1, 1)
# self.set_end_date(2025, 1, 1) # Commented out to backtest to present
self.set_cash(1000000)
self.set_security_initializer(BrokerageModelSecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices)))
# Determine the lookback window (in weeks).
self.history_lookback = self.get_parameter("history_lookback", 50)
self.drawdown_lookback = self.get_parameter("drawdown_lookback", 20)
# Request SPY as market representative for trading and HMM fitting.
self.spy = self.add_equity("SPY", Resolution.MINUTE).symbol
# Request GLD as hedge asset for trading.
self.gold = self.add_equity("GLD", Resolution.MINUTE).symbol
self.set_benchmark(self.spy)
# Schdeuled a weekly rebalance.
self.schedule.on(self.date_rules.week_start(self.spy), self.time_rules.after_market_open(self.spy, 1), self.rebalance)
def rebalance(self) -> None:
# Get the drawdown as the input to the drawdown regime. Since we're rebalancing weekly, we resample to study weekly drawdown.
history = self.history(self.spy, self.history_lookback*5, Resolution.DAILY).unstack(0).close.resample('W').last()
drawdown = history.rolling(self.drawdown_lookback).apply(lambda a: (a.iloc[-1] - a.max()) / a.max()).dropna()
try:
# Initialize the HMM, then fit by the drawdown data, as we're interested in the downside risk regime.
# McLachlan & Peel (2000) suggested 2-3 components are used in GMMs to capture the main distribution and the tail to balance between complexity and characteristics capture.
# By studying the ACF and PACF plots, the 1-lag drawdown series is suitable to supplement as exogenous variable.
inputs = np.concatenate([drawdown[[self.spy]].iloc[1:].values, drawdown[[self.spy]].diff().iloc[1:].values], axis=1)
model = GMMHMM(n_components=2, n_mix=3, covariance_type='tied', n_iter=100, random_state=0).fit(inputs)
# Obtain the current market regime.
regime_probs = model.predict_proba(inputs)
current_regime_prob = regime_probs[-1]
regime = 0 if current_regime_prob[0] > current_regime_prob[1] else 1
# Determine the regime number: the higher the coefficient, the larger the drawdown in this state.
high_regime = 1 if model.means_[0][1][0] < model.means_[1][1][0] else 0
# Check the transitional probability of the next regime being the high volatility regime.
# Calculated by the probability of the current regime being 1/0, then multiplied by the posterior probabilities of each scenario.
next_prob_zero = current_regime_prob @ model.transmat_[:, 0]
next_prob_high = round(next_prob_zero if high_regime == 0 else 1 - next_prob_zero, 2)
# Buy more Gold and less SPY if the current regime is easier to have large drawdown.
# Fund will shift to hedge asset like gold to drive up its price.
# Weighted by the posterior probabilities.
self.set_holdings([PortfolioTarget(self.gold, next_prob_high), PortfolioTarget(self.spy, 1 - next_prob_high)])
except:
pass
Ney Torres
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!