Overall Statistics
Total Trades
261
Average Win
0.80%
Average Loss
-0.80%
Compounding Annual Return
-57.854%
Drawdown
48.600%
Expectancy
-0.156
Net Profit
-19.188%
Sharpe Ratio
-0.102
Probabilistic Sharpe Ratio
25.655%
Loss Rate
58%
Win Rate
42%
Profit-Loss Ratio
1.00
Alpha
0
Beta
0
Annual Standard Deviation
0.982
Annual Variance
0.964
Information Ratio
-0.102
Tracking Error
0.982
Treynor Ratio
0
Total Fees
$2330.49
Estimated Strategy Capacity
$150000.00
Lowest Capacity Asset
XLMUSDT 18N
# region imports
from AlgorithmImports import *
from io import StringIO
import pandas as pd
# endregion

class CryptoStrategy01(QCAlgorithm):

    def Initialize(self):
        #self.SetTimeZone("America/New_York")
        self.SetTimeZone("UTC")
        self.SetStartDate(2020, 1, 1)  # Set Start Date
        self.SetEndDate(2020, 3, 30) #Set End Date
        
        self.SetAccountCurrency("USDT")
        self.SetCash(10000)  # Set Strategy Cash
        
        self.bias = ''
        self.rands = {}
        self.tickers = []
        self.break_percentage = 0.50
        self.tpMultiplier = 1.5
        self.slMultiplier = 1
        
        self.timeframe = Resolution.Hour
        self.SetSecurityInitializer(self.CustomSecurityInitializer)
        self.UniverseSettings.Resolution = self.timeframe
        self.AddUniverseSelection(ScheduledUniverseSelectionModel(self.DateRules.EveryDay(), self.TimeRules.At(0, 0) ,self.CoarseSelectionFunction))
        
        #self.benchmark = self.AddCrypto("BTCUSDT", self.timeframe, Market.Binance).Symbol
        #self.SetBenchmark("BTCUSDT")
        
        self.invested = [] #list of on going invseted coins
        self.historyInvested = [] #list of the coins we invested within the day to avoid re-entry
        self.indicators = {} # Set dictionary of indicators
        self.doneWarmingUp = []
        self.marginModel = 0

        
    # start of self-defined functions
    def WarmUp(self, symbol):
        
        # customize margin model to allow shorting in crypto Binance
        if self.marginModel < len(self.ActiveSecurities.Values):
            symbol.BuyingPowerModel = SecurityMarginModel(3.3)
            self.marginModel += 1
            
        # Either create a new indicator, or update one we already have
        if symbol not in self.indicators:
            self.indicators[symbol] = SymbolData(symbol)
        
        history = self.History([symbol.Symbol], 14, self.timeframe)
        for index, row in history.loc[symbol.Symbol].iterrows():
            tradebar = TradeBar(index, symbol.Symbol, row["open"], row["high"], row["low"], row["close"], row["volume"], timedelta(1))
            self.indicators[symbol].update(index, row["close"], tradebar)
                
        self.doneWarmingUp.append(str(symbol))
        self.Log(str(symbol) + " : Done warming up...")
        
    
    def CustomSecurityInitializer(self, security):
        '''Initialize the security with raw prices'''
        security.SetDataNormalizationMode(DataNormalizationMode.Raw) 
        
        
    def CoarseSelectionFunction(self, coarse):
        
        ### EXIT CONDITION ### when tp or sl is not triggered by the end of day
        if not len(self.invested) == 0: #checks if the list in not empty
            #self.Log(self.invested)
            for symbol in self.ActiveSecurities.Values:
                Close = symbol.Close #recent bar closing price
                if str(symbol) in self.invested:
                    if self.Portfolio[symbol.Symbol].IsLong:
                        self.Liquidate(symbol.Symbol, "EOD Long Exit " + str(symbol) + " @ " + str(Close))
                        self.Log("EOD Long Exit " + str(symbol) + " @ " + str(Close))
                        self.invested.remove(str(symbol))
                    
                    elif self.Portfolio[symbol.Symbol].IsShort:
                        self.Liquidate(symbol.Symbol, "EOD Short Exit " + str(symbol) + " @ " + str(Close))
                        self.Log("EOD Short Exit " + str(symbol) + " @ " + str(Close))
                        self.invested.remove(str(symbol))
                        
        #self.Log(self.invested)
        ### END OF EXIT CONDITION ###        
        
        self.doneWarmingUp = [] # reset list of symbols warm-up everyday
        self.marginModel = 0 # reset marginModel everyday
        self.invested = [] #reset list of ongoing investment
        self.historyInvested = [] #reset history of invested coins everyday
        
        self.tickers = [] 
        self.rands = {}
        symbols = []
        csv = self.Download("https://www.dropbox.com/s/97qbgx9x3janqh6/rands.csv?dl=1")
        df = pd.read_csv(StringIO(csv))
        
        try:
            df1= df.loc[df['datetime']==str(self.Time - timedelta(1))]
            symbolsToday = df1["symbol"]
            self.bias = df1['bias'].iloc[0]
            
            for ticker in symbolsToday:
                x = ticker.upper() + "USDT"
                self.tickers.append(x)
                
                resistance = df1.loc[df1['symbol']==ticker, 'resistance']
                resistance = resistance.values
    
                support = df1.loc[df1['symbol']==ticker, 'support']
                support = support.values
    
                self.rands[x + " 18N"] = {'resistance':resistance[0] , 'support':support[0]}
                          
            tickers = self.tickers
            for x in tickers:
                symbols.append(Symbol.Create(x, SecurityType.Crypto, Market.Binance))
                
            return symbols
            
        except:
            self.Log("Error in getting symbols...Date not found.")

    # end of self-defined functions
        
        
        

    def OnData(self, data):
        
        self.Log(len(self.ActiveSecurities))
        self.Log(self.rands)
        #self.Log(self.bias)
        
        for symbol in self.ActiveSecurities.Values:
            
            try:
                
                if str(symbol) not in self.doneWarmingUp:
                    self.WarmUp(symbol)
                self.Log(self.doneWarmingUp)
                
                # Either create a new indicator, or update one we already have
                if symbol not in self.indicators:
                    self.indicators[symbol] = SymbolData(symbol)
                    
                tradebar = TradeBar(self.Time, symbol.Symbol, symbol.Open, symbol.High, symbol.Low, symbol.Close, symbol.Volume, timedelta(1))
                self.indicators[symbol].update(self.Time, symbol.Close, tradebar)
                
                #check if indicators are ready
                if not self.indicators[symbol].atrWin.IsReady: 
                    return
                # end of adding symbol to indicators dictionary
                
                Close = symbol.Close #recent bar closing price
                Open = symbol.Open #recent bar opening price
                High = symbol.High #recent bar highest price
                Low = symbol.Low #recent bar lowest price
                resistance = self.rands[str(symbol)]['resistance'] #previous day resistance
                support = self.rands[str(symbol)]['support'] #previous day support
                atr = self.indicators[symbol].atrWin[0].Value #recent bar atr value
                self.Log(str(symbol) + " Close:" + str(Close) + " Open:" + str(Open) + " High:" + str(High) + " Low:" + str(Low))
                self.Log("Resistance:" + str(resistance) + " Support:" + str(support))
                #self.Log("ATR :" + str(atr))
                
                balance = self.Portfolio.MarginRemaining / 3 #scaling position size
                quantity = (balance * 0.9) /Close
                
                ### ENTRY CONDITIONS ###
                #"""
                if (str(symbol) not in self.invested) and (str(symbol) not in self.historyInvested):
                    if self.bias == "long":
                        
                        cl = Open - Close 
                        bl = support - Close
                        if bl > 0 and cl > 0:
                            candle_points = bl/cl
                            if candle_points > self.break_percentage:
                                self.Buy(symbol.Symbol, quantity)
                                self.indicators[symbol].EntryPrice = Close
                                self.indicators[symbol].TP = (atr * self.tpMultiplier) + self.indicators[symbol].EntryPrice
                                self.indicators[symbol].SL = self.indicators[symbol].EntryPrice - (atr * self.slMultiplier)
                                self.Log("BUY " + str(symbol) + " @ " + str(self.indicators[symbol].EntryPrice) + " TP:" + str(self.indicators[symbol].TP) + " SL:" + str(self.indicators[symbol].SL))
                                self.invested.append(str(symbol))
                                self.historyInvested.append(str(symbol))
                                
                        
                        
                    else: #if bias is "short"
                        
                        cl = Close - Open
                        bl = Close - resistance
                        if bl > 0 and cl > 0:
                            candle_points = bl/cl
                            if candle_points > self.break_percentage:
                                self.Sell(symbol.Symbol, quantity)
                                self.indicators[symbol].EntryPrice = Close
                                self.indicators[symbol].TP = self.indicators[symbol].EntryPrice - (atr * self.tpMultiplier)
                                self.indicators[symbol].SL = (atr * self.slMultiplier) + self.indicators[symbol].EntryPrice
                                self.Log("SHORT " + str(symbol) + " @ " + str(self.indicators[symbol].EntryPrice) + " TP:" + str(self.indicators[symbol].TP) + " SL:" + str(self.indicators[symbol].SL))
                                self.invested.append(str(symbol))
                                self.historyInvested.append(str(symbol))
                    
                ### EXIT CONDITIONS ###
                
                elif str(symbol) in self.invested:
                    
                    if self.Portfolio[symbol.Symbol].IsLong:
                        #check if SL is hit
                        if  self.indicators[symbol].SL >= Low:
                            self.StopMarketOrder(symbol.Symbol, -1*quantity, self.indicators[symbol].SL)
                            self.Log("SL Long Exit " + str(symbol) + " @ " + str(self.indicators[symbol].SL))
                            self.invested.remove(str(symbol))
                    
                        #check if TP is hit
                        elif self.indicators[symbol].TP <= High:
                            self.LimitOrder(symbol.Symbol, -1*quantity, self.indicators[symbol].TP)
                            self.Log("TP Long Exit " + str(symbol) + " @ " + str(self.indicators[symbol].TP))
                            self.invested.remove(str(symbol))
        
                        
                    elif self.Portfolio[symbol.Symbol].IsShort:
                        #check if SL is hit
                        if self.indicators[symbol].SL <= High:
                            self.StopMarketOrder(symbol.Symbol, quantity, self.indicators[symbol].SL)
                            self.Log("SL Short Exit " + str(symbol) + " @ " + str(self.indicators[symbol].SL))
                            self.invested.remove(str(symbol))
                            
                        #check if TP is hit
                        elif self.indicators[symbol].TP >= Low:
                            self.LimitOrder(symbol.Symbol, quantity, self.indicators[symbol].TP)
                            self.Log("TP SHORT Exit " + str(symbol) + " @ " + str(self.indicators[symbol].TP))
                            self.invested.remove(str(symbol))
            
                #"""
            except:
                self.Log("Symbol is not yet available: " + str(symbol))
                
                
                
    def OnOrderEvent(self, orderEvent):
        
        order = self.Transactions.GetOrderById(orderEvent.OrderId)
        
        ### Cancel remaining order if limit order or stop loss order is executed
        if order.Status == OrderStatus.Filled:
            if order.Type == OrderType.Limit or order.Type == OrderType.StopLimit or order.Type == OrderType.StopMarket:
                self.Transactions.CancelOpenOrders(order.Symbol)
    
    
                
           
class SymbolData(object): #holds indicators initialization and update
    def __init__(self, symbol):
        
        self.EntryPrice = 0
        self.TP = 0
        self.SL = 0

        # Declare Indicators
        
        self.atr = AverageTrueRange(14)
        self.atr.Updated += self.AtrUpdated
        self.atrWin = RollingWindow[IndicatorDataPoint](2)
        
        
    def update(self, time, value, tradebar):
        self.atr.Update(tradebar)
        
    def AtrUpdated(self, sender, updated):
        self.atrWin.Add(updated)