| Overall Statistics |
|
Total Trades 54 Average Win 4.00% Average Loss -1.22% Compounding Annual Return -5.536% Drawdown 19.500% Expectancy -0.367 Net Profit -11.951% Sharpe Ratio -0.358 Probabilistic Sharpe Ratio 0.939% Loss Rate 85% Win Rate 15% Profit-Loss Ratio 3.27 Alpha 0.05 Beta -0.073 Annual Standard Deviation 0.095 Annual Variance 0.009 Information Ratio -1.729 Tracking Error 0.685 Treynor Ratio 0.464 Total Fees $6506.96 Estimated Strategy Capacity $74000000.00 Lowest Capacity Asset BTCUSDT 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"]
self.UniverseTickers = ["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)