Overall Statistics
Total Orders
4102
Average Win
2.02%
Average Loss
-0.43%
Compounding Annual Return
38.544%
Drawdown
66.900%
Expectancy
0.539
Start Equity
100000.00
End Equity
660050.28
Net Profit
560.050%
Sharpe Ratio
0.879
Sortino Ratio
1.123
Probabilistic Sharpe Ratio
30.249%
Loss Rate
73%
Win Rate
27%
Profit-Loss Ratio
4.69
Alpha
0.166
Beta
0.227
Annual Standard Deviation
0.341
Annual Variance
0.117
Information Ratio
-0.546
Tracking Error
0.529
Treynor Ratio
1.321
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
SNTUSD E3
Portfolio Turnover
3.46%
#region imports
from AlgorithmImports import *
#endregion

class CreativeRedHornet(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2019, 1, 1)
        #self.SetEndDate(2024, 6, 12)
        self.SetCash(100000)
        
        self.Settings.FreePortfolioValuePercentage = 0.05
        self.positionSizeUSD = 3500
        self.momentum_entry = 60
        self.oversold_entry = 20
        self.momentum_exit = 40
        self.overbought_exit = 80
        self.minimumVolume = 50000  # Lowering volume constraint
        
        # Add data for all tickers
        universe = ['BTCUSD', 'LTCUSD', 'ETHUSD', 'ETCUSD', 'RRTUSD', 'ZECUSD', 'XMRUSD', 'XRPUSD', 'EOSUSD', 
                    'SANUSD', 'OMGUSD', 'NEOUSD', 'ETPUSD', 'BTGUSD', 'SNTUSD', 'BATUSD', 'FUNUSD', 'ZRXUSD', 
                    'TRXUSD', 'REQUSD', 'LRCUSD', 'WAXUSD', 'DAIUSD', 'BFTUSD', 'ODEUSD', 'ANTUSD', 'XLMUSD', 
                    'XVGUSD', 'MKRUSD', 'KNCUSD', 'LYMUSD', 'UTKUSD', 'VEEUSD', 'ESSUSD', 'IQXUSD', 'ZILUSD', 
                    'BNTUSD', 'XRAUSD', 'VETUSD', 'GOTUSD', 'XTZUSD', 'MLNUSD', 'PNKUSD', 'DGBUSD', 'BSVUSD', 
                    'ENJUSD', 'PAXUSD']
        self.pairs = [Pair(self, ticker, self.minimumVolume) for ticker in universe]

        self.SetBenchmark(self.AddCrypto('BTCUSD', Resolution.Daily, Market.Bitfinex).Symbol)  
        
        self.SetWarmup(30)
        self.Debug("Initialization complete")

    def OnData(self, data):
        if self.IsWarmingUp:
            return

        for pair in self.pairs:
            if not pair.rsi.IsReady:
                self.Debug(f"{pair.symbol} RSI not ready")
                continue

            symbol = pair.symbol
            rsi = pair.rsi.Current.Value
            investable = pair.Investable()

            # Ensure higher highs condition is met
            if not pair.higher_high:
                self.Debug(f"Skipping {symbol}: No higher high detected.")
                continue

            rsi_decreasing = pair.previous_rsi is not None and rsi < pair.previous_rsi
            rsi_increasing = pair.previous_rsi is not None and rsi > pair.previous_rsi
            pair.previous_rsi = rsi

            if not investable:
                self.Debug(f"{symbol} is not investable due to volume constraint")
                continue

            # Buying logic
            if rsi_increasing and rsi > self.momentum_entry and rsi < self.overbought_exit and self.Portfolio.MarginRemaining > self.positionSizeUSD:
                self.Debug(f"Buying {symbol}: RSI approaching momentum_entry (bullish). RSI={rsi}, Price={self.Securities[symbol].Price}")
                self.Buy(symbol, self.positionSizeUSD / self.Securities[symbol].Price)

            elif rsi_decreasing and rsi < self.oversold_entry and self.Portfolio.MarginRemaining > self.positionSizeUSD:
                self.Debug(f"Buying {symbol}: RSI approaching oversold_entry (bearish). RSI={rsi}, Price={self.Securities[symbol].Price}")
                self.Buy(symbol, self.positionSizeUSD / self.Securities[symbol].Price)

            # Liquidation logic
            if self.Portfolio[symbol].Invested:
                if not investable:
                    self.Debug(f"Liquidating {symbol} due to volume constraint")
                    self.Liquidate(symbol, "Not enough volume")
                elif rsi > self.overbought_exit and rsi_increasing:
                    self.Debug(f"Liquidating {symbol}: RSI above overbought_exit (bullish). RSI={rsi}")
                    self.Liquidate(symbol, "RSI above overbought")
                elif rsi < self.momentum_exit and rsi_decreasing:
                    self.Debug(f"Liquidating {symbol}: RSI below momentum_exit (bearish). RSI={rsi}")
                    self.Liquidate(symbol, "RSI below momentum_exit")


class Pair:
    def __init__(self, algorithm, ticker, minimumVolume): 
        self.algorithm = algorithm
        self.symbol = algorithm.AddCrypto(ticker, Resolution.Daily, Market.Bitfinex).Symbol

        # Ensure RSI is properly initialized
        self.rsi = algorithm.RSI(self.symbol, 14, MovingAverageType.Simple, Resolution.Daily)
        
        # Simplified volume calculation for stability
        self.volume = algorithm.SMA(self.symbol, 30, Resolution.Daily, Field.Volume)
        
        self.minimumVolume = minimumVolume
        self.previous_rsi = None

        # Biweekly consolidator setup
        self.biweekly_consolidator = TradeBarConsolidator(timedelta(days=14))
        algorithm.SubscriptionManager.AddConsolidator(self.symbol, self.biweekly_consolidator)
        self.biweekly_consolidator.DataConsolidated += self.OnBiweeklyBar
        self.current_biweek = {"high": None}
        self.previous_biweek = {"high": None}
        self.higher_high = False
    
    def OnBiweeklyBar(self, sender, bar):
        # Check for valid high prices
        if bar.High is not None:
            self.previous_biweek = self.current_biweek.copy()
            self.current_biweek["high"] = bar.High

        # Check for higher highs
        if self.previous_biweek["high"] is not None:
            self.higher_high = self.current_biweek["high"] > self.previous_biweek["high"]
            
            if self.higher_high:
                self.algorithm.Debug(f"Higher high detected for {self.symbol}: {self.current_biweek['high']}")
    
    def Investable(self):
        # Ensure indicators are ready
        if not self.volume.IsReady or not self.rsi.IsReady:
            return False
        return self.volume.Current.Value > self.minimumVolume