| Overall Statistics |
|
Total Orders 493 Average Win 1.36% Average Loss -2.17% Compounding Annual Return 42.666% Drawdown 45.900% Expectancy 0.280 Start Equity 100000 End Equity 590256.08 Net Profit 490.256% Sharpe Ratio 0.898 Sortino Ratio 0.939 Probabilistic Sharpe Ratio 36.556% Loss Rate 21% Win Rate 79% Profit-Loss Ratio 0.63 Alpha 0.241 Beta 1.296 Annual Standard Deviation 0.35 Annual Variance 0.122 Information Ratio 0.86 Tracking Error 0.3 Treynor Ratio 0.242 Total Fees $1093.61 Estimated Strategy Capacity $100000000.00 Lowest Capacity Asset BIL TT1EBZ21QWKL Portfolio Turnover 3.72% Drawdown Recovery 794 |
# Strategy: Hybrid leveraged-equity + gold barbell (weekly).
# Two equal sleeves that smooth each other: a leveraged-equity sleeve (best 3x
# US equity ETF by 13612W momentum, else T-bills) and a gold sleeve (GLD when
# trending, else T-bills). Gold is uncorrelated with equities, so the barbell
# cuts drawdown sharply while keeping return high. 13612W momentum score
# (Keller & Keuning, 2017) reacts fast to downturns. Always invested.
# region imports
from AlgorithmImports import *
# endregion
class HybridLeveragedGold(QCAlgorithm):
def initialize(self):
self.set_start_date(self.end_date - timedelta(5 * 365))
self.set_cash(100_000)
self.settings.seed_initial_prices = True
self._equity_pairs = {"TQQQ": "QQQ", "UPRO": "SPY", "SOXL": "SOXX"}
self._gold = "GLD"
self._cash_asset = "BIL"
self._lookback = 252
self._equity_weight = 0.50
self._gold_weight = 0.50
self._security_by_ticker = {
ticker: self.add_equity(ticker, Resolution.DAILY)
for ticker in list(self._equity_pairs.keys()) + list(self._equity_pairs.values()) + [self._gold, self._cash_asset]
}
self.set_warm_up(self._lookback + 10)
self.schedule.on(self.date_rules.week_start("SPY"), self.time_rules.at(8, 0), self._rebalance)
def on_warmup_finished(self):
self._rebalance()
def _momentum(self, ticker):
history = self.history(self._security_by_ticker[ticker], self._lookback + 1, Resolution.DAILY)
if history.empty or len(history) < self._lookback:
return None
closes = history["close"]
p0 = float(closes.iloc[-1])
momentum_factors = (
(p0 / float(closes.iloc[-21]) - 1) +
4 * (p0 / float(closes.iloc[-63]) - 1) +
2 * (p0 / float(closes.iloc[-126]) - 1) + (p0 / float(closes.iloc[-252]) - 1)
)
return 12 * momentum_factors
def _rebalance(self):
if self.is_warming_up:
return
target = {}
# Equity sleeve: hold the best leveraged ETF whose underlying is trending up, else T-bills.
equity_scores = {}
for leveraged, underlying in self._equity_pairs.items():
score = self._momentum(underlying)
if score is not None and score > 0:
equity_scores[leveraged] = score
if equity_scores:
target[max(equity_scores, key=lambda t: equity_scores[t])] = self._equity_weight
else:
target[self._cash_asset] = target.get(self._cash_asset, 0.0) + self._equity_weight
# Gold sleeve: hold gold when trending, otherwise T-bills.
gold_score = self._momentum(self._gold)
if gold_score is not None and gold_score > 0:
target[self._gold] = self._gold_weight
else:
target[self._cash_asset] = target.get(self._cash_asset, 0.0) + self._gold_weight
# Liquidate anything not targeted this rebalance, then size the targets.
hold = [self._security_by_ticker[ticker] for ticker in target]
for security in self._security_by_ticker.values():
if security not in hold:
self.liquidate(security)
for ticker, weight in target.items():
self.set_holdings(self._security_by_ticker[ticker], weight)