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)