Overall Statistics
Total Orders
393
Average Win
1.27%
Average Loss
-0.64%
Compounding Annual Return
19.172%
Drawdown
12.100%
Expectancy
0.702
Start Equity
100000
End Equity
240330.96
Net Profit
140.331%
Sharpe Ratio
0.925
Sortino Ratio
0.955
Probabilistic Sharpe Ratio
69.415%
Loss Rate
43%
Win Rate
57%
Profit-Loss Ratio
2.00
Alpha
0.073
Beta
0.362
Annual Standard Deviation
0.107
Annual Variance
0.011
Information Ratio
0.209
Tracking Error
0.13
Treynor Ratio
0.272
Total Fees
$1412.59
Estimated Strategy Capacity
$160000000.00
Lowest Capacity Asset
SGOV XEVHFSIAYBFP
Portfolio Turnover
5.84%
Drawdown Recovery
188
# region imports
from AlgorithmImports import *
import numpy as np
from datetime import datetime
# endregion

class SharpeGodTier_V4_1_Production(QCAlgorithm):
    """
    SharpeGodTier V4.1 - SGOV Toggle Build
    
    Changelog v4.1:
    - Added USE_CASH_PROXY parameter to toggle SGOV/IBTU yield sweep on or off.
      Set to False for live deployment if LSE market data subscription is unavailable.
      Set to True for backtesting to capture T-bill yield on idle cash.
      When disabled, idle cash earns IBKR's native interest rate automatically.
    """

    # ── SGOV TOGGLE ───────────────────────────────────────────────────────
    # True  = deploy idle cash into SGOV (backtest) or IBTU (live)
    # False = leave idle cash with IBKR earning native interest (live fallback)
    USE_CASH_PROXY = True

    def Initialize(self):
        # ── 1. Backtest & Brokerage Settings ──────────────────────────────
        #self.SetStartDate(2021, 1, 1)
        self.SetEndDate(2026, 1, 1)
        self.set_start_date(self.end_date - timedelta(5*365)) 
        self.SetCash(100_000)
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
        self.SetBenchmark("SPY")

        # ── 2. Yield Engine Asset (SGOV) ───────────────────────────────────
        # Only added if USE_CASH_PROXY is True
        # For live UK deployment replace "SGOV" with "IBTU" and add Market.LSE
        if self.USE_CASH_PROXY:
            self.cash_proxy = self.AddEquity("SGOV", Resolution.Daily).Symbol
        else:
            self.cash_proxy = None
            self.Log("SGOV/IBTU cash proxy DISABLED — idle cash earning IBKR native interest")

        # ── 3. Strategy Parameters (WFO-validated) ─────────────────────────
        self.max_positions  = 5
        self.rsi_entry      = 41
        self.max_trade_days = 50
        self.sgov_tolerance = 0.02

        # ── 4. Hybrid Crypto Setup ─────────────────────────────────────────
        self.ibit_launch = datetime(2024, 1, 11)
        self.gbtc = self.AddEquity("GBTC", Resolution.Daily).Symbol
        self.ibit = self.AddEquity("IBIT", Resolution.Daily).Symbol

        # ── 5. Asset Universe ──────────────────────────────────────────────
        self.assets = {
            "QQQ"  : "Tech",
            "NVDA" : "Tech",
            "XLF"  : "Finance",
            "XLV"  : "Health",
            "GLD"  : "Gold",
            "TLT"  : "Treasury",
            "USO"  : "Commodity",
            "XLE"  : "Commodity",
            "XLP"  : "Equity",
        }

        self.symbols = []
        self.data    = {}

        for ticker, asset_type in self.assets.items():
            symbol = self.AddEquity(ticker, Resolution.Daily).Symbol
            self.symbols.append(symbol)
            self.data[symbol] = self._MakeIndicatorBundle(symbol, asset_type)

        for symbol in [self.gbtc, self.ibit]:
            self.data[symbol] = self._MakeIndicatorBundle(symbol, "Crypto")

        # ── 6. Market Regime Filter ────────────────────────────────────────
        self.spy     = self.AddEquity("SPY", Resolution.Daily).Symbol
        self.spy_std = self.STD(self.spy, 10, Resolution.Daily)

        self.SetWarmUp(200)

    # ── Helpers ────────────────────────────────────────────────────────────

    def _MakeIndicatorBundle(self, symbol, asset_type):
        return {
            "rsi"       : self.RSI(symbol, 14, MovingAverageType.Wilders, Resolution.Daily),
            "sma_200"   : self.SMA(symbol, 200, Resolution.Daily),
            "atr"       : self.ATR(symbol, 14, MovingAverageType.Wilders, Resolution.Daily),
            "mom"       : self.ROC(symbol, 20, Resolution.Daily),
            "hwm"       : 0.0,
            "entry_date": None,
            "type"      : asset_type,
        }

    def GetActiveCryptoSymbol(self):
        return self.ibit if self.Time >= self.ibit_launch else self.gbtc

    # ── Main Event ─────────────────────────────────────────────────────────

    def OnData(self, data: Slice):
        if self.IsWarmingUp or not self.spy_std.IsReady:
            return

        active_crypto   = self.GetActiveCryptoSymbol()
        inactive_crypto = self.ibit if active_crypto == self.gbtc else self.gbtc

        # ── SWITCHOVER GUARD ───────────────────────────────────────────────
        if self.Portfolio[inactive_crypto].Invested:
            self.Liquidate(inactive_crypto, "Crypto proxy switchover")
            self.data[inactive_crypto]["hwm"]        = 0.0
            self.data[inactive_crypto]["entry_date"] = None

        active_symbols = self.symbols + [active_crypto]

        # ── STEP 1: EXIT LOGIC ─────────────────────────────────────────────
        for s in [s for s in active_symbols if self.Portfolio[s].Invested]:
            if not data.Bars.ContainsKey(s):
                continue

            d     = self.data[s]
            price = self.Securities[s].Price
            d["hwm"] = max(d["hwm"], price)

            if d["type"] == "Crypto":
                exit_rsi, atr_mult = 85, 3.2
            elif d["type"] == "Commodity":
                exit_rsi, atr_mult = 80, 3.0
            else:
                exit_rsi, atr_mult = 75, 2.5

            days_held     = (self.Time - d["entry_date"]).days
            trailing_stop = d["hwm"] - (d["atr"].Current.Value * atr_mult)

            if (d["rsi"].Current.Value > exit_rsi or
                    price < trailing_stop or
                    days_held > self.max_trade_days):

                self.Liquidate(s, "V4.1 Sniper Exit")
                d["hwm"]        = 0.0
                d["entry_date"] = None

        # ── STEP 2: ENTRY LOGIC ────────────────────────────────────────────
        is_high_vol    = self.spy_std.Current.Value > (self.Securities[self.spy].Price * 0.015)
        invested_count = sum(1 for s in active_symbols if self.Portfolio[s].Invested)

        if invested_count < self.max_positions:
            candidates = []
            for s in active_symbols:
                if self.Portfolio[s].Invested or not data.Bars.ContainsKey(s):
                    continue

                d = self.data[s]
                if not d["rsi"].IsReady or not d["sma_200"].IsReady:
                    continue

                if (self.Securities[s].Price > d["sma_200"].Current.Value and
                        d["rsi"].Current.Value < self.rsi_entry):
                    candidates.append(s)

            candidates.sort(key=lambda x: self.data[x]["mom"].Current.Value, reverse=True)

            for s in candidates:
                if sum(1 for sym in active_symbols if self.Portfolio[sym].Invested) >= self.max_positions:
                    break

                d        = self.data[s]
                risk_pct = 0.01 if is_high_vol else 0.03

                atr_val = d["atr"].Current.Value
                if atr_val <= 0:
                    continue

                qty_factor = (self.Portfolio.TotalPortfolioValue * risk_pct) / (atr_val * 3)

                if d["type"] == "Crypto":
                    max_w = 0.30
                elif d["type"] == "Commodity":
                    max_w = 0.25
                else:
                    max_w = 0.33

                target_weight = min(
                    max_w,
                    (qty_factor * self.Securities[s].Price) / self.Portfolio.TotalPortfolioValue
                )

                if target_weight > 0.02:
                    self.SetHoldings(s, target_weight)
                    d["hwm"]        = self.Securities[s].Price
                    d["entry_date"] = self.Time

        # ── STEP 3: CASH MANAGEMENT (SGOV YIELD SWEEP) ────────────────────
        # Skipped entirely if USE_CASH_PROXY is False
        # Idle cash automatically earns IBKR native interest in that case
        if not self.USE_CASH_PROXY:
            return

        # Guard: skip if no price data yet for cash proxy
        if not data.Bars.ContainsKey(self.cash_proxy):
            return

        tactical_value = sum(
            self.Portfolio[s].HoldingsValue
            for s in active_symbols
        )
        tactical_weight     = tactical_value / self.Portfolio.TotalPortfolioValue
        sgov_target_weight  = max(0, 0.95 - tactical_weight)
        sgov_current_weight = self.Portfolio[self.cash_proxy].HoldingsValue / self.Portfolio.TotalPortfolioValue
        sgov_deviation      = abs(sgov_current_weight - sgov_target_weight)

        if sgov_deviation > self.sgov_tolerance:
            if sgov_target_weight > 0.05:
                self.SetHoldings(self.cash_proxy, sgov_target_weight)
            elif tactical_weight > 0.92:
                self.Liquidate(self.cash_proxy, "Clearing Space for Tactical Signal")