Overall Statistics
Total Orders
1444
Average Win
0.79%
Average Loss
-0.23%
Compounding Annual Return
21.654%
Drawdown
32.500%
Expectancy
0.616
Start Equity
100000.00
End Equity
278121.36
Net Profit
178.121%
Sharpe Ratio
0.7
Sortino Ratio
0.816
Probabilistic Sharpe Ratio
24.099%
Loss Rate
64%
Win Rate
36%
Profit-Loss Ratio
3.50
Alpha
0.035
Beta
0.168
Annual Standard Deviation
0.213
Annual Variance
0.045
Information Ratio
-1.045
Tracking Error
0.502
Treynor Ratio
0.883
Total Fees
$0.00
Estimated Strategy Capacity
$3000.00
Lowest Capacity Asset
DAIUSD E3
Portfolio Turnover
1.32%
#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.rsiEntryThreshold = 60  # Lowering entry threshold to encourage more trades
        self.rsiExitThreshold = 35   # Raising exit threshold
        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("BTCUSD") 
        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()
            self.Debug(f"Processing {symbol}: RSI={rsi}, Investable={investable}, Invested={self.Portfolio[symbol].Invested}")
            
            # Selling
            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.rsiExitThreshold:
                    self.Debug(f"Liquidating {symbol} due to RSI below threshold: RSI={rsi}")
                    self.Liquidate(symbol, "RSI below threshold")
                continue
            
            if not investable:
                self.Debug(f"{symbol} is not investable due to volume constraint")
                continue
            
            # Buying
            if rsi > self.rsiEntryThreshold and self.Portfolio.MarginRemaining > self.positionSizeUSD:
                self.Debug(f"Buying {symbol}: RSI={rsi}, Price={self.Securities[symbol].Price}")
                self.Buy(symbol, self.positionSizeUSD / self.Securities[symbol].Price)
            else:
                if rsi <= self.rsiEntryThreshold:
                    self.Debug(f"RSI for {symbol} is not above entry threshold: RSI={rsi}")
                if self.Portfolio.MarginRemaining <= self.positionSizeUSD:
                    self.Debug(f"Not enough margin remaining to buy {symbol}")

class Pair:
    def __init__(self, algorithm, ticker, minimumVolume): 
        self.symbol = algorithm.AddCrypto(ticker, Resolution.Daily, Market.Bitfinex).Symbol
        self.rsi    = algorithm.RSI(self.symbol, 14,  MovingAverageType.Simple, Resolution.Daily)
        self.volume = IndicatorExtensions.Times(algorithm.SMA(self.symbol, 30, Resolution.Daily, Field.Volume), 
                                                algorithm.SMA(self.symbol, 30, Resolution.Daily, Field.Close))
        self.minimumVolume = minimumVolume
    
    def Investable(self):
        return (self.volume.Current.Value > self.minimumVolume)