Overall Statistics
Total Orders
151
Average Win
10.02%
Average Loss
-7.75%
Compounding Annual Return
100.985%
Drawdown
43.200%
Expectancy
0.467
Start Equity
100000
End Equity
812912.89
Net Profit
712.913%
Sharpe Ratio
1.616
Sortino Ratio
2.001
Probabilistic Sharpe Ratio
69.902%
Loss Rate
36%
Win Rate
64%
Profit-Loss Ratio
1.29
Alpha
0.707
Beta
2.181
Annual Standard Deviation
0.478
Annual Variance
0.228
Information Ratio
1.871
Tracking Error
0.397
Treynor Ratio
0.354
Total Fees
$6383.82
Estimated Strategy Capacity
$0
Lowest Capacity Asset
TQQQ UK280CGTCB51
Portfolio Turnover
13.68%
from AlgorithmImports import *

class CrazyHolyGrailSimplified(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2022, 1, 1)  # Set a relevant start date
        self.SetEndDate(2024, 12, 31)  # Set a relevant end date
        self.SetCash(100000)

        # Define the symbols used in the Composer strategy
        self.tqqq = self.AddEquity("TQQQ", Resolution.Daily).Symbol
        self.qqq = self.AddEquity("QQQ", Resolution.Daily).Symbol
        self.uvxy = self.AddEquity("UVXY", Resolution.Daily).Symbol
        self.tecl = self.AddEquity("TECL", Resolution.Daily).Symbol
        self.upro = self.AddEquity("UPRO", Resolution.Daily).Symbol
        self.tlt = self.AddEquity("TLT", Resolution.Daily).Symbol
        self.sqqq = self.AddEquity("SQQQ", Resolution.Daily).Symbol

        # Initialize indicators
        self.tqqq_sma200 = self.SMA(self.tqqq, 200, Resolution.Daily)
        self.tqqq_sma20 = self.SMA(self.tqqq, 20, Resolution.Daily)

        self.qqq_rsi10 = self.RSI(self.qqq, 10, MovingAverageType.Simple, Resolution.Daily)
        self.tqqq_rsi10 = self.RSI(self.tqqq, 10, MovingAverageType.Simple, Resolution.Daily)
        self.upro_rsi10 = self.RSI(self.upro, 10, MovingAverageType.Simple, Resolution.Daily)

        # Set warm-up period. Max warm-up is 200 for SMA200.
        self.SetWarmUp(TimeSpan.FromDays(250)) 

        # Schedule the trading logic to run daily, 5 minutes after market open
        self.Schedule.On(self.DateRules.EveryDay(), 
                         self.TimeRules.AfterMarketOpen(self.tqqq, 5), # Use one symbol for scheduling
                         self.TradeLogic)

    def TradeLogic(self):
        # Ensure all required indicators are ready
        if self.IsWarmingUp or \
           not self.tqqq_sma200.IsReady or \
           not self.tqqq_sma20.IsReady or \
           not self.qqq_rsi10.IsReady or \
           not self.tqqq_rsi10.IsReady or \
           not self.upro_rsi10.IsReady:
            self.Debug("Indicators are not ready yet. Skipping trade logic.")
            return

        # Get current prices for the relevant assets
        tqqq_price = self.Securities[self.tqqq].Price
        qqq_price = self.Securities[self.qqq].Price
        uvxy_price = self.Securities[self.uvxy].Price
        tecl_price = self.Securities[self.tecl].Price
        upro_price = self.Securities[self.upro].Price
        tlt_price = self.Securities[self.tlt].Price
        sqqq_price = self.Securities[self.sqqq].Price

        # Initialize a list to hold the symbols we want to invest in for this period
        target_symbols = []

        # Start translating the Composer logic:
        # (if (> (current-price "EQUITIES::TQQQ//USD") (moving-average-price "EQUITIES::TQQQ//USD" {:window 200}))
        if tqqq_price > self.tqqq_sma200.Current.Value:
            #   [(if (> (rsi "EQUITIES::QQQ//USD" {:window 10}) 80)
            if self.qqq_rsi10.Current.Value > 80:
                #     [(asset "EQUITIES::UVXY//USD" "ProShares Ultra VIX Short-Term Futures ETF")]
                target_symbols.append(self.uvxy)
            else:
                #     [(asset "EQUITIES::TQQQ//USD" "ProShares UltraPro QQQ")])]
                target_symbols.append(self.tqqq)
        else:
            # [(if (< (rsi "EQUITIES::TQQQ//USD" {:window 10}) 31)
            if self.tqqq_rsi10.Current.Value < 31:
                #     [(asset "EQUITIES::TECL//USD" "Direxion Daily Technology Bull 3x Shares")]
                target_symbols.append(self.tecl)
            else:
                #     [(if (< (rsi "EQUITIES::UPRO//USD" {:window 10}) 31)
                if self.upro_rsi10.Current.Value < 31:
                    #       [(asset "EQUITIES::UPRO//USD" "ProShares UltraPro S&P500")]
                    target_symbols.append(self.upro)
                else:
                    #       [(if (> (current-price "EQUITIES::TQQQ//USD") (moving-average-price "EQUITIES::TQQQ//USD" {:window 20}))
                    if tqqq_price > self.tqqq_sma20.Current.Value:
                        #         [(asset "EQUITIES::TQQQ//USD" "ProShares UltraPro QQQ")]
                        target_symbols.append(self.tqqq)
                    else:
                        #         [(filter (rsi {:window 10}) (select-top 1)
                        #           [(asset "EQUITIES::TLT//USD" "iShares 20+ Year Treasury Bond ETF")
                        #           (asset "EQUITIES::SQQQ//USD" "ProShares UltraPro Short QQQ")])])])])])
                        
                        # Calculate RSI for TLT and SQQQ to select the one with the lowest RSI (most oversold)
                        tlt_rsi = self.RSI(self.tlt, 10).Current.Value if self.RSI(self.tlt, 10).IsReady else float('inf')
                        sqqq_rsi = self.RSI(self.sqqq, 10).Current.Value if self.RSI(self.sqqq, 10).IsReady else float('inf')
                        
                        if tlt_rsi <= sqqq_rsi and tlt_rsi != float('inf'):
                            target_symbols.append(self.tlt)
                        elif sqqq_rsi != float('inf'):
                            target_symbols.append(self.sqqq)
                        # If neither RSI is ready, no asset is added from this branch.
                        # This should ideally be handled by ensuring these RSIs are ready during warm-up.
                        # For simplicity, we assume they become ready eventually.

        # === Execute Trades based on target_symbols ===
        # Liquidate positions not in the target_symbols list
        for holding in self.Portfolio.Values:
            if holding.Invested and holding.Symbol not in target_symbols:
                self.Liquidate(holding.Symbol)
                self.Debug(f"Liquidating {holding.Symbol.Value}")

        # Invest in target_symbols with equal weight
        if target_symbols:
            allocation_per_asset = 1.0 / len(target_symbols)
            for symbol in target_symbols:
                # Only set holdings if the asset currently has data
                if self.Securities[symbol].HasData and self.Securities[symbol].Price > 0:
                    self.SetHoldings(symbol, allocation_per_asset)
                    self.Debug(f"Setting holdings for {symbol.Value} to {allocation_per_asset*100:.2f}%")
                else:
                    self.Debug(f"Skipping SetHoldings for {symbol.Value} due to no data or zero price.")
        else:
            # If no target symbols are selected by the logic, liquidate everything
            if self.Portfolio.Invested:
                self.Liquidate()
                self.Debug("No assets selected by logic. Liquidating all positions.")