Overall Statistics |
Total Trades 169 Average Win 0% Average Loss 0% Compounding Annual Return 0% Drawdown 0% Expectancy 0 Net Profit 0% Sharpe Ratio 0 Probabilistic Sharpe Ratio 0% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0 Annual Variance 0 Information Ratio 0 Tracking Error 0 Treynor Ratio 0 Total Fees BUSD0.00 Estimated Strategy Capacity BUSD13000.00 Lowest Capacity Asset BAKEBUSD 18N |
from AlgorithmImports import * import datetime import math from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor # GUIDE DOCS # Core Concepts # Portfolio object https://www.quantconnect.com/docs/v2/writing-algorithms/portfolio/key-concepts # Order algo https://github.com/QuantConnect/Lean/blob/master/Algorithm.Python/OrderTicketDemoAlgorithm.py # BInance Price Data https://www.quantconnect.com/datasets/binance-crypto-price-data # Quote bars https://www.quantconnect.com/docs/v2/writing-algorithms/securities/asset-classes?ref=v1#Handling-Data-QuoteBars # Indicators https://www.quantconnect.com/docs/v2/writing-algorithms/indicators/supported-indicators class ZEdge43(QCAlgorithm): def Initialize(self): self.SetStartDate(2022, 1, 1) self.SetEndDate(2022,1,7) self.SetAccountCurrency('BUSD') self.SetCash('BUSD', 10000) self.SetWarmup(datetime.timedelta(days=2)) self.UniverseSettings.Resolution = Resolution.Second self.SetTimeZone(TimeZones.Utc) self.thread_executor = ThreadPoolExecutor(max_workers=5) # Account types: https://www.quantconnect.com/docs/v2/cloud-platform/live-trading/brokerages/binance self.SetBrokerageModel(BrokerageName.Binance, AccountType.Margin) # Set default order properties self.DefaultOrderProperties = BinanceOrderProperties() self.DefaultOrderProperties.TimeInForce = TimeInForce.GoodTilCanceled self.DefaultOrderProperties.PostOnly = True self.min_invested = 3 #BUSD . If less than this considered not invested (needed for min order sizes/gas fees etc) # Variables self.SMA_period = 60 self.RSI_period = 2 self.LongSlopeThresh = float(self.GetParameter('LongSlopeThresh')) self.LongRSIThresh = float(self.GetParameter('LongRSIThresh')) self.LongSellWait_mins = float(self.GetParameter('LongSellWait_mins')) self.ShortSlopeThresh = float(self.GetParameter('ShortSlopeThresh')) self.ShortRSIThresh = float(self.GetParameter('ShortRSIThresh')) self.ShortCoverWait_mins = float(self.GetParameter('ShortCoverWait_mins')) # Indicator periods self.sma_numerator = 1*60 # mins self.sma_denominator = 6*60 # mins self.rsi_range = 2*60 # mins self.account_equity_multiplier = 3 self.account_buffer = 0.95 self.OrderUpdateWait_secs = 5 # how long before closing maker positions or updating closing positions #Coins self.coins = ["ADABUSD","ALGOBUSD","ALICEBUSD","ANCBUSD","ANKRBUSD","APEBUSD","ATOMBUSD","AUCTIONBUSD","AUDIOBUSD","AVAXBUSD", \ "AXSBUSD","BAKEBUSD","BELBUSD","BNBBUSD","BNXBUSD","BONDBUSD","BTCBUSD","BURGERBUSD","C98BUSD","CAKEBUSD","CELOBUSD","CHRBUSD", \ "COMPBUSD","CRVBUSD","DARBUSD","DOGEBUSD","DOTBUSD","DYDXBUSD","EGLDBUSD","ENJBUSD","ENSBUSD","EOSBUSD","ETCBUSD","ETHBUSD", \ "FILBUSD","FLOWBUSD","FLUXBUSD","FRONTBUSD","FTMBUSD","FTTBUSD","GALABUSD","GALBUSD","GMTBUSD","HBARBUSD","HIVEBUSD","HNTBUSD", \ "HOTBUSD","ICPBUSD","IDEXBUSD","IMXBUSD","IOTXBUSD","JASMYBUSD","KAVABUSD","KDABUSD","KLAYBUSD","LDOBUSD","LEVERBUSD","LINKBUSD", \ "LRCBUSD","LTCBUSD","MANABUSD","MATICBUSD","MINABUSD","NEARBUSD","NEXOBUSD","ONEBUSD","OPBUSD","PEOPLEBUSD","PONDBUSD","PYRBUSD", \ "QNTBUSD","RAREBUSD","REEFBUSD","REIBUSD","RNDRBUSD","ROSEBUSD","RUNEBUSD","SANDBUSD","SFPBUSD","SHIBBUSD","SLPBUSD","SOLBUSD", \ "SPELLBUSD","STGBUSD","TLMBUSD","TRBBUSD","TRIBEBUSD","TRXBUSD","UNIBUSD","VETBUSD","VOXELBUSD","WINBUSD","WINGBUSD","XLMBUSD", \ "XRPBUSD","XTZBUSD","YGGBUSD","ZILBUSD"] # Dictionaries for coins data self.securities = {} self.sma_windows = {} self.sma = {} self.rsi_windows = {} self.rsi = {} self.tickets_maker = {} self.tickets_closing = {} self.positions = {} self.tickets_maker_cancellation = {} self.order_qty = {} self.total_closed = {} self.qty_filled = {} self.qty_filled_closing = {} self.closing_qty = {} self.first_close = {} # Filling the dictionaries for coin in self.coins: self.securities[coin] = self.AddCrypto(coin, Resolution.Second) symbol = self.securities[coin].Symbol self.securities[coin].SetFeeModel(ConstantFeeModel(0)) self.sma_windows[coin] = RollingWindow[float](7*60+1) self.rsi_windows[coin] = RollingWindow[float](2*60+1) self.sma[coin] = self.SMA(symbol,self.SMA_period, resolution=Resolution.Minute) self.rsi[coin] = self.RSI(symbol, self.RSI_period, resolution=Resolution.Minute) self.tickets_maker[coin] = None self.tickets_closing[coin] = None self.positions[coin] = None self.tickets_maker_cancellation[coin] = None self.order_qty[coin] = 0.0 self.total_closed[coin] = 0.0 self.qty_filled[coin] = 0.0 self.qty_filled_closing[coin] = 0.0 self.closing_qty[coin] = 0.0 self.first_close[coin] = False self.Debug(f'Number of coins! {len(self.coins)}') def process_coin(self, coin: str, data: Slice) -> bool: portfolio = self.Portfolio security = self.securities[coin] symbol = security.Symbol symbol_value = symbol.Value # Add sma values to windows if self.sma[coin].IsReady: self.sma_windows[coin].Add(self.sma[coin].Current.Value) if self.rsi[coin].IsReady: self.rsi_windows[coin].Add(self.rsi[coin].Current.Value) # calculating invested amount quote_currency = security.QuoteCurrency.Symbol base_currency = symbol_value.replace(quote_currency,'') invested = portfolio[symbol].Invested amount_invested = portfolio.CashBook[base_currency].Amount amount_invested_accy = portfolio.CashBook[base_currency].ValueInAccountCurrency # Check if indicators are ready if not self.sma_windows[coin].IsReady or not self.rsi_windows[coin].IsReady: return True #STRATEGY if not invested or (amount_invested_accy <= self.min_invested and amount_invested_accy >= -self.min_invested): if self.tickets_maker[coin]: if self.UtcTime >= self.tickets_maker[coin].Time + datetime.timedelta(seconds=self.OrderUpdateWait_secs) \ and self.tickets_maker[coin].Status == OrderStatus.Submitted: # Check if submitted order exists, then cancel it self.tickets_maker[coin].Cancel('Closing maker order as it hasnt been filled') self.Log(f'{self.UtcTime} - {symbol} - Cancel - Cancelling maker order as {self.OrderUpdateWait_secs} seconds have passed.') self.positions[coin] = None if self.UtcTime.second == 0: # RUN EVERY MINUTE if (self.sma_windows[coin][self.sma_numerator]/self.sma_windows[coin][self.sma_denominator]) >= self.LongSlopeThresh \ and self.rsi_windows[coin][self.rsi_range] < self.LongRSIThresh \ and self.positions[coin] == None: # GO LONG! # QTY and Price self.bid_price = round(data.QuoteBars[symbol].Bid.High,2) if self.bid_price == 0: # in case the bid price cannot be retrieved. return True portfolio_value = portfolio.TotalPortfolioValue # In case quant connect cannot retrieve the price self.order_qty[coin] = math.floor(((portfolio_value * self.account_equity_multiplier * self.account_buffer) / len(self.coins)) / self.bid_price) # Round down. # Limit order self.tickets_maker[coin] = self.LimitOrder(symbol, self.order_qty[coin], self.bid_price) self.Debug(f'{self.UtcTime} - {symbol} - LONG ! - Creating bid maker order {self.order_qty[coin]}@{self.bid_price} Current bid price: {data.QuoteBars[symbol].Bid}') # Reset closing order parameters self.total_closed[coin] = 0.0 self.qty_filled[coin] = 0.0 self.qty_filled_closing[coin] = 0.0 self.positions[coin] = 'long' self.first_close[coin] = False if (self.sma_windows[coin][self.sma_numerator]/self.sma_windows[coin][self.sma_denominator]) < self.ShortSlopeThresh \ and self.rsi_windows[coin][self.rsi_range] > self.ShortRSIThresh \ and self.positions[coin] == None: # GO SHORT! # QTY and Price self.ask_price = round(data.QuoteBars[symbol].Ask.Low,2) if self.ask_price == 0: # in case the ask price cannot be retrieved. return True portfolio_value = portfolio.TotalPortfolioValue self.order_qty[coin] = -math.floor(((portfolio_value * self.account_equity_multiplier * self.account_buffer) / len(self.coins)) / self.ask_price)# Round down. # Limit order self.tickets_maker[coin] = self.LimitOrder(symbol, self.order_qty[coin], self.ask_price) self.Debug(f'{self.UtcTime} - {symbol} - SHORT! - Creating ask maker order {self.order_qty[coin]}@{self.ask_price} Current bid price: {data.QuoteBars[symbol].Ask}') # Reset closing order parameters self.total_closed[coin] = 0.0 self.qty_filled[coin] = 0.0 self.qty_filled_closing[coin] = 0.0 self.closing_qty[coin] = 0.0 self.positions[coin] = 'short' self.first_close[coin] = False if invested and (amount_invested_accy > self.min_invested or amount_invested_accy < -self.min_invested): # We have holdings. if (self.UtcTime >= self.tickets_maker[coin].Time + datetime.timedelta(seconds=self.OrderUpdateWait_secs)) and self.qty_filled[coin] == 0: #We have waited 5 seconds since making order. We cancel order incase partial filled. self.qty_filled[coin] = self.tickets_maker[coin].QuantityFilled self.tickets_maker_cancellation[coin] = self.tickets_maker[coin].Cancel(f'{self.OrderUpdateWait_secs} seconds passed. \ Cancelled order') self.Log(f'{self.UtcTime} - {symbol} - Cancelled - {self.OrderUpdateWait_secs} seconds passed. \ #Cancelled {self.positions[coin]} order. \ #Filled {self.qty_filled[coin]} / {self.order_qty[coin]}.') self.first_close[coin] = True if (self.UtcTime >= self.tickets_maker[coin].Time + datetime.timedelta(minutes=self.LongSellWait_mins) and self.positions[coin] == 'long')\ or (self.UtcTime >= self.tickets_maker[coin].Time + datetime.timedelta(minutes=self.ShortCoverWait_mins) and self.positions[coin] == 'short'): #We have waited 15 minutes since making order and its filled. if self.first_close[coin]: # >=15 mins after maker order and we have position. Place closing order self.closing_qty[coin] = -amount_invested if self.positions[coin] == 'long': self.price = data.QuoteBars[symbol].Ask.Low else: self.price = data.QuoteBars[symbol].Bid.High self.tickets_closing[coin] = self.LimitOrder(symbol, self.closing_qty[coin], self.price) self.Log(f'{self.UtcTime} - {symbol} - Order - closing {self.positions[coin]} maker order {self.closing_qty[coin]}@{self.price}.') self.first_close[coin] = False if not self.first_close[coin]: # >=15. mins after make order and closing ticket exist. Placing new closing order if self.UtcTime >= self.tickets_closing[coin].Time + datetime.timedelta(seconds=self.OrderUpdateWait_secs): # 5 seconds after closing order and we still have a position. # Close ticket self.qty_filled_closing[coin] = self.tickets_closing[coin].QuantityFilled self.tickets_closing[coin].Cancel('closing order') self.total_closed[coin] += self.qty_filled_closing[coin] self.Log(f'{self.UtcTime} - {symbol} - {self.OrderUpdateWait_secs} seconds passed closing order. \ # Filled {self.qty_filled_closing[coin]}. Total closed {self.total_closed[coin]}/{self.qty_filled[coin]}') # Create New Ticket self.closing_qty[coin] = -amount_invested if self.positions[coin] == 'long': self.price = data.QuoteBars[symbol].Ask.Low else: self.price = data.QuoteBars[symbol].Bid.High self.tickets_closing[coin] = self.LimitOrder(symbol, self.closing_qty[coin], self.price) self.Log(f'{self.UtcTime} - {symbol} - Creating new closing {self.positions[coin]} maker order {self.closing_qty[coin]}@{self.price}.') return True def OnData(self, data: Slice) -> None: if self.IsWarmingUp: return # load C# variables into Python for faster loading # REF: https://www.quantconnect.com/docs/v2/writing-algorithms/key-concepts/algorithm-engine for coin in self.coins: # self.thread_executor.submit(self.process_coin, coin, data) self.process_coin(coin, data) def OnOrderEvent(self, orderEvent: OrderEvent) -> None: order = self.Transactions.GetOrderById(orderEvent.OrderId) if orderEvent.Status == OrderStatus.Filled: symbol = orderEvent.Symbol symbol_value = symbol.Value self.Log(f"{self.UtcTime} - {symbol} - ORDER EVENT: {order.Type} : {orderEvent}") quote_currency = self.securities[symbol_value].QuoteCurrency.Symbol base_currency = symbol_value.replace(quote_currency,'') cb_amount = self.Portfolio.CashBook[base_currency].ValueInAccountCurrency if not self.Portfolio[symbol].Invested or (cb_amount < self.min_invested and cb_amount > - self.min_invested): # If symbol is now neutral, we reset position to None self.Log(f'resetting for {symbol_value}') self.positions[symbol_value] = None def OnEndOfAlgorithm(self) -> None: self.thread_executor.shutdown()