Overall Statistics |
Total Trades 285 Average Win 2.54% Average Loss -0.77% Compounding Annual Return 100.497% Drawdown 20.800% Expectancy 1.303 Net Profit 373.360% Sharpe Ratio 2.543 Probabilistic Sharpe Ratio 97.204% Loss Rate 46% Win Rate 54% Profit-Loss Ratio 3.29 Alpha 0.518 Beta 0.136 Annual Standard Deviation 0.265 Annual Variance 0.07 Information Ratio -0.79 Tracking Error 0.603 Treynor Ratio 4.975 Total Fees $39800.36 Estimated Strategy Capacity $81000000.00 Lowest Capacity Asset SOLUSDT 18N |
#$@$@ ---------------------------------------------------------------- #$@$@ ------------- Author; Emmanuel Azubuike --------------- #$@$@ ---------------------------------------------------------------- class ScanWin(): def __init__(self, wT, wL): self.window = None self.winLength = wL if (wT is "int"):self.window = RollingWindow[int](wL) elif (wT is "bool"):self.window = RollingWindow[bool](wL) elif (wT is "float"):self.window = RollingWindow[float](wL) elif (wT is "TradeBar"):self.window = RollingWindow[TradeBar](wL) def isAbove(self, series): return (self.window[0] > series[0]) def isBelow(self, series): return (self.window[0] < series[0]) def Add(self,value): self.window.Add(value) def IsReady(self): return (self.window is not None) and \ (self.window.Count >= self.winLength) def __getitem__(self, index): return self.window[index]
#$@$@ ---------------------------------------------------------------- #$@$@ ------------- Author; Emmanuel Azubuike --------------- #$@$@ ---------------------------------------------------------------- from AlgorithmImports import * import operator import math from ScanWin import * class EMACrossoverUniverse(QCAlgorithm): #$@$@ ---------------------------------------------------------------- def Initialize(self): self.InitA() self.InitB() self.Initc() self.Initd() #$@$@ ---------------------------------------------------------------- def Initd(self): self.SetStartDate(2020, 1, 1) self.SetCash(100000) self.SetBenchmark(Symbol.Create("BTCUSDT", SecurityType.Crypto, Market.Binance)) def Initc(self): self.UniverseSettings.Resolution = Resolution.Daily self.symDataDict = { } self.UniverseTickers = ["SOLUSDT", "LUNAUSDT", "ADAUSDT", "BTCUSDT"] universeSymbols = [] for symbol in self.UniverseTickers: universeSymbols.append(Symbol.Create(symbol, SecurityType.Crypto, Market.Binance)) self.SetUniverseSelection(ManualUniverseSelectionModel(universeSymbols)) #$@$@ -------------------- def InitA(self): self.emaSlowPeriod = int(self.GetParameter('emaSlowPeriod')) self.emaFastPeriod = int(self.GetParameter('emaFastPeriod')) self.warmupPeriod = self.emaSlowPeriod self.maxExposurePct = float(self.GetParameter("maxExposurePct"))/100 self.maxHoldings = int(self.GetParameter("maxHoldings")) self.minAmountToBeInvested = int(self.GetParameter("minAmountToBeInvested")) self.SetSecurityInitializer(self.CustomSecurityInitializer) #$@$@ -------------------- def InitB(self): #self.symbol = "BTCUSDT" self.SetBrokerageModel(BrokerageName.Binance, AccountType.Cash) self.SetAccountCurrency("USDT") self.EnableAutomaticIndicatorWarmUp = True self.SetWarmUp(timedelta(self.warmupPeriod)) self.SelectedSymbolsAndWeights = {} #$@$@ ------------------------------------------------------------ @property def PAC(self): numHoldings = len([x.Key for x in self.Portfolio if self.IsInvested(x.Key)]) return numHoldings >= self.maxHoldings def CustomSecurityInitializer(self, security): security.MarginModel = SecurityMarginModel(3.3) #$@$@ ------------------------------------------------ def OnData(self, dataSlice): #$@$@ loop through the symbols in the slice for symbol in dataSlice.Keys: if symbol in self.symDataDict: symbolData = self.symDataDict[symbol] symbolData.OnSymbolData(self.Securities[symbol].Price, dataSlice[symbol]) if self.Portfolio[symbol].IsLong: symbolData.MOP() elif self.Portfolio[symbol].IsShort: symbolData.ManageOpenSellPositions() else: if(not self.PAC): if( symbolData.BuySignalFired() ): self.ONP(symbolData.symbol) symbolData.OnNewPositionOpened() #else: #if( symbolData.SellSignalFired() ): #self.OpenNewSellPosition(symbolData.symbol) #symbolData.OnNewPositionOpened() #$@$@ ----------------------------------------------------- def ReHo(self, rebalanceCurrHoldings=False): for symbol in self.SelectedSymbolsAndWeights: symbolWeight = round(1/len(self.SelectedSymbolsAndWeights),4) self.SetWH(symbol,symbolWeight) sortedSymbolsAndWeights = {k: v for k, v in sorted(self.SelectedSymbolsAndWeights.items(), key=lambda item: item[1], reverse=True)} for symbol in sortedSymbolsAndWeights: self.SetSymbolHoldings(symbol) #$@$@ ---rebalance sells portfolio of holdings with equal weighting-------- #--------------- def RebalanceHoldings(self, rebalanceCurrHoldings=False): for symbol in self.SelectedSymbolsAndWeights: symbolWeight = round(1/len(self.SelectedSymbolsAndWeights),4) self.SetWH(symbol,symbolWeight) sortedSymbolsAndWeights = {k: v for k, v in sorted(self.SelectedSymbolsAndWeights.items(), key=lambda item: item[1], reverse=True)} for symbol in sortedSymbolsAndWeights: self.SetSymbolHoldingsSell(symbol) def SetSymbolHoldings(self, symbol): adjustedWeight = self.SelectedSymbolsAndWeights[symbol] cash = self.Portfolio.TotalPortfolioValue - self.Portfolio.TotalHoldingsValue percent = adjustedWeight * self.maxExposurePct cost = self.Portfolio.TotalPortfolioValue * percent orderMsg = f"{symbol} | alloc. ({round(adjustedWeight*100,2)}% adjusted) " if (cost > cash): percent = self.GetTruncatedValue(cash / self.Portfolio.TotalPortfolioValue, 3) if(self.Portfolio[symbol].Invested): orderMsg = f"[Re-Balancing Buys] {orderMsg}" else: orderMsg = f"[NEW Addition Buy] {orderMsg}" self.SetHoldings(symbol, percent, tag=orderMsg) def SetSymbolHoldingsSell(self, symbol): adjustedWeight = self.SelectedSymbolsAndWeights[symbol] cash = self.Portfolio.TotalPortfolioValue - self.Portfolio.TotalHoldingsValue percent = adjustedWeight * self.maxExposurePct cost = self.Portfolio.TotalPortfolioValue * percent orderMsg = f"{symbol} | alloc. ({round(adjustedWeight*100,2)}% adjusted) " if (cost > cash): percent = self.GetTruncatedValue(cash / self.Portfolio.TotalPortfolioValue, 3) if(self.Portfolio[symbol].Invested): orderMsg = f"[Re-Balancing Sells] {orderMsg}" else: orderMsg = f"[NEW Addition Sells] {orderMsg}" self.SetHoldings(symbol, -percent, tag=orderMsg) #$@$@ --------–--------–--------–--------–--------–--------–--------–--------– def SetWH(self,symbol,symbolWeight): if( self.Portfolio.Invested ): TCB = sum( [x.Value.HoldingsCost for x in self.Portfolio if x.Value.Invested] ) else: TCB = 0.0 CA = self.Portfolio.TotalPortfolioValue - self.Portfolio.TotalHoldingsValue # CA = self.Portfolio.CashBook["USDT"].Amount WB = TCB + CA ATI = WB * symbolWeight if(self.Portfolio[symbol].Invested): profitPct = self.Portfolio[symbol].UnrealizedProfitPercent adjustedATI = ATI * (1 + profitPct) adjustedWeight = adjustedATI / self.Portfolio.TotalPortfolioValue else: adjustedWeight = ATI / self.Portfolio.TotalPortfolioValue symbolWeight = self.GetTruncatedValue(symbolWeight,3) adjustedWeight = self.GetTruncatedValue(adjustedWeight,3) self.SelectedSymbolsAndWeights[symbol] = adjustedWeight #$@$@ ------------------------------------------------- def ONP(self, symbol): self.SelectedSymbolsAndWeights[symbol] = 0 self.ReHo() def OpenNewSellPosition(self, symbol): self.SelectedSymbolsAndWeights[symbol] = 0 self.RebalanceHoldings() #$@$@ ----------------------------------------------------- def ExitBuypositions(self, symbol, exitMsg=""): profitPct = round(self.Securities[symbol].Holdings.UnrealizedProfitPercent,2) self.Liquidate(symbol, tag=f"Closed buy Positions {symbol.Value} ({profitPct}% profit) [{exitMsg}]") self.SelectedSymbolsAndWeights.pop(symbol) return def ExitSellpositions(self, symbol, exitMsg=""): profitPct = round(self.Securities[symbol].Holdings.UnrealizedProfitPercent,2) self.Liquidate(symbol, tag=f"CLosed Sell Positions {symbol.Value} ({profitPct}% profit) [{exitMsg}]") self.SelectedSymbolsAndWeights.pop(symbol) return #$@$@ ------------------------------------------------------ def OnSecuritiesChanged(self, changes): for security in changes.AddedSecurities: symbol = security.Symbol if( symbol in self.UniverseTickers and \ symbol not in self.symDataDict.keys()): self.symDataDict[symbol] = SymbolData(symbol, self) def GetTruncatedValue(self, value, decPlaces): truncFactor = 10.0 ** decPlaces return math.trunc(value * truncFactor) / truncFactor def IsInvested(self, symbol): return self.Portfolio[symbol].Invested and self.Portfolio[symbol].Quantity * self.Securities[symbol].Price > self.minAmountToBeInvested class SymbolData(): def __init__(self, theSymbol, algo): #$@$@ Algo / Symbol / Price reference self.algo = algo self.symbol = theSymbol self.lastPrice = 0 self.price = 0 self.Init2() #$@$@ ---------------------------------------- def Init2(self): self.indicators = { 'EMA_FAST' : self.algo.EMA(self.symbol,self.algo.emaFastPeriod,Resolution.Daily), 'EMA_SLOW' : self.algo.EMA(self.symbol,self.algo.emaSlowPeriod,Resolution.Daily)} for key, indicator in self.indicators.items(): self.algo.WarmUpIndicator(self.symbol, indicator, Resolution.Minute) self.emaFastWindow = ScanWin("float", 2) self.emaSlowWindow = ScanWin("float", 2) self.lastPriceWindow = ScanWin("float", 2) #$@$@ ---------------------------------------- def OnSymbolData(self, lastKnownPrice, tradeBar): self.lastPrice = lastKnownPrice self.UpdateRW() #$@$@ ---------------------------------------- def UpdateRW(self): self.emaFastWindow.Add(self.indicators['EMA_FAST'].Current.Value) self.emaSlowWindow.Add(self.indicators['EMA_SLOW'].Current.Value) self.lastPriceWindow.Add(self.lastPrice) #$@$@ ---------------------------------------- def IsReady(self): return (self.indicators['EMA_FAST'].IsReady and self.indicators['EMA_SLOW'].IsReady) #$@$@ ---------------------------------------- def BuySignalFired(self): if( self.IsReady() ): if( self.emaFastWindow.isAbove(self.emaSlowWindow) and \ self.lastPriceWindow.isAbove(self.emaFastWindow) ): return True return False #$@$@ ---------------------------------------- def ExitBuySignals(self): if( self.IsReady() ): if ( self.lastPriceWindow.isBelow(self.emaSlowWindow) or \ self.emaSlowWindow.isAbove(self.emaFastWindow) ): return True return False #$@$@ -------------Entry signal for Sells--------------------------- def SellSignalFired(self): if( self.IsReady() ): if ( self.lastPriceWindow.isBelow(self.emaFastWindow) and \ self.emaFastWindow.isBelow(self.emaSlowWindow) ): return True return False def ExitSellSignals(self): if( self.IsReady() ): if( self.emaFastWindow.isAbove(self.emaSlowWindow) or \ self.lastPriceWindow.isAbove(self.emaSlowWindow) ): return True return False #$@$@ --------------------------------------------------------- def OnNewPositionOpened(self): return #$@$@ ----------------------------------------------------------------------------- def MOP(self): if (self.ExitBuySignals()): self.ExitBuypositions(exitMsg="Exit Signal Fired for Buys") #self.Debug(f"{self.Time} - [Exit Signal Fired for Buys] \t\t\tBTC: ${self.price:.2f}") #$@$@ ----------------------------------------------------------------------------- def ManageOpenSellPositions(self): if (self.ExitSellSignals()): self.ExitSellpositions(exitMsg="Exit Sell Signal Fired for Sells") #self.Debug(f"{self.Time} - [Exit Sell Signal fired for Sells] \t\t\tBTC: ${self.price:.2f}") #$@$@ ---------------------------------------- def ExitBuypositions(self, exitMsg): self.algo.ExitBuypositions(self.symbol, exitMsg) def ExitSellpositions(self, exitMsg): self.algo.ExitSellpositions(self.symbol, exitMsg)