Overall Statistics |
Total Trades 261 Average Win 1.83% Average Loss -6.15% Compounding Annual Return -58.070% Drawdown 42.800% Expectancy -0.052 Net Profit -35.156% Sharpe Ratio -0.703 Probabilistic Sharpe Ratio 4.563% Loss Rate 27% Win Rate 73% Profit-Loss Ratio 0.30 Alpha -2.539 Beta 0.487 Annual Standard Deviation 0.508 Annual Variance 0.258 Information Ratio -9.355 Tracking Error 0.517 Treynor Ratio -0.732 Total Fees $148.26 Estimated Strategy Capacity $290000.00 |
from clr import AddReference AddReference("System") AddReference("QuantConnect.Algorithm") AddReference("QuantConnect.Indicators") AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Indicators import * from datetime import datetime, timedelta import numpy as np from System.Drawing import Color from decimal import Decimal #####REFERENCES: #crypto code #https://github.com/QuantConnect/Lean/blob/master/Algorithm.Python/BasicTemplateCryptoAlgorithm.py #stochastic #https://www.quantconnect.com/docs/algorithm-reference/indicators#Indicators-Reference-Table #consolidating periods #https://www.quantconnect.com/docs/algorithm-reference/consolidating-data#Consolidating-Data-Consolidating-Periods #setting stop loss / tp in orderevent #https://www.quantconnect.com/forum/discussion/3522/onorderevent-self-event-not-working/p1 #https://www.quantconnect.com/forum/discussion/3523/gdax-stop-limit-order-quot-brokeragemodel-declared-unable-to-submit-order-quot/p1 #updating orders #https://www.quantconnect.com/docs/algorithm-reference/trading-and-orders#Trading-and-Orders-Updating-Orders class SmoothBlueElephant(QCAlgorithm): def __init__(self): #Market Parameters self.base = "ETH" self.quote = "USD" self.symbol = self.base + self.quote # Set cryptocurrency symbol to trade self.startcash = 500 # starting cash self.market = Market.GDAX self.brokerage = BrokerageName.GDAX self.buyTicket = None self.tpTicket = None #backtest parameters self.startmonth = 1 self.btlenmonth = 6 #Strategy Parameters self.sl = 0.05 #set stop loss self.tp = 0.02 #set take profit #Indicator Parameters self.partfillIndicator = None #prep for partfill management self.longIndicator = 0 #prep long indicator (if =0, not holding, if =1, long position open) self.buyOrderIndicator = 0 #prep order indicator (if =0 no open buy orders, if = 1 open buy orders) ################## def Initialize(self): #Backtest Parameters self.SetStartDate(2019, self.startmonth, 15) # Set Start Date self.SetEndDate(2019, (self.startmonth+self.btlenmonth), 15) # Set End Date for Backtest self.SetCash(self.startcash) # Set Strategy Cash self.SetBrokerageModel(self.brokerage, AccountType.Cash) # set crypto brokerage self.cur = self.AddCrypto(self.symbol, Resolution.Minute, self.market) #get data ##### Indicators #Calculate stochastic self._sto = self.STO(self.symbol, 30, 14, 3, Resolution.Minute) #calculate 30min stochastic (mid level) #get Stochastic D value self.D = self._sto.StochD self._rsi = self.RSI(self.symbol, 1, MovingAverageType.Simple, Resolution.Hour) #calculate Hourly RSI self.SetWarmUp(1, Resolution.Hour) #warm up period for indicators ##### Charts #create chart plot = Chart("indicators") plot.AddSeries(Series('stoch', SeriesType.Line, 0)) plot.AddSeries(Series('rsi', SeriesType.Line, 0)) signalPlot = Chart("signal") signalPlot.AddSeries(Series('buy', SeriesType.Line, 0)) #chartdata arrays self.s = None self.r = None self.sigs = [self.D, self._rsi] self.inds = [self.s, self.r] self.sers = ['stoch', 'rsi'] self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.Every(timedelta(minutes=30)), self.chartHandler) ################## def OnData(self, data): if not self._sto.IsReady: return #wait for indicators to be ready ##### Handle open orders / positions if self.buyTicket != None: if self.buyTicket.Status == OrderStatus.Invalid: self.Debug("Buy order invalid") self.buyOrderIndicator = 0 #cancel open buy orders if stochastic gets out of oversold before buy fills if self.buyOrderIndicator == 1 and self.D.Current.Value > 20: self.Debug("Stoch left oversold") if self.buyTicket != None: self.buyTicket.Cancel("Stoch left oversold") #stop loss #if long indicator on, check for sl hit if self.longIndicator == 1: if self.Securities[self.symbol].Price <= (self.limitPrice * (1-self.sl)): self.Transactions.CancelOpenOrders(self.symbol) #cancel tp limit order self.Liquidate(self.symbol) #sell all holdings self.longIndicator = 0 #reset long indicator self.buyOrderIndicator = 0 #reset buy order indicator self.partfillIndicator = 0 #reset part fill indicator #wait until positions close or orders cancel if self.longIndicator == 1 or self.buyOrderIndicator == 1: self.Debug("waiting for positions to close") return ##### Buy signalling & ordering #if stochastic and rsi are oversold, buy limit at 0.5% below current price if self.D.Current.Value < 20 and self._rsi.Current.Value < 30: self.limitPrice = self.Securities[self.symbol].Price * 0.995 self.quantity = (self.Portfolio.CashBook['USD'].Amount * 0.99) / self.limitPrice # "*0.99" to avoid issues with buying power ... can we also use self.Portfolio.CashBook["USD"].Amount for available buying power? self.buyTicket = self.LimitOrder(self.symbol, self.quantity, self.limitPrice) self.buyOrderIndicator = 1 ################## def OnOrderEvent(self, orderEvent): self.Debug("Order Event") #identify order type & log order # order = self.Transactions.GetOrderById(orderEvent.OrderId) # self.Log("{0}: {1}: {2}".format(self.Time, order.Type, orderEvent)) ##### Buy Order Handling if self.buyTicket == None: return #if order is buy order if orderEvent.OrderId == self.buyTicket.OrderId: self.Debug("Buy ticket event detected") #if buy order partially filled, set take profit and set part-filled indicator to 1 if self.buyTicket.Status == OrderStatus.PartiallyFilled: self.Debug("Buy order partially filled") self.longIndicator = 1 self.partfillIndicator = 1 partfillQuantity = self.Portfolio.CashBook[self.base].Amount tpPrice = self.limitPrice * (1 + self.tp) self.tpTicket = self.LimitOrder(self.symbol, -partfillQuantity, tpPrice) #if buy order filled, set tp elif self.buyTicket.Status == OrderStatus.Filled: self.Debug("Buy order filled") self.longIndicator = 1 self.buyOrderIndicator = 0 quantity = self.Portfolio.CashBook[self.base].Amount #if part filled indicator is 1, update tp quantity and reset partfillIndicator if self.partfillIndicator == 1: updateSettings = UpdateOrderFields() updateSettings.Quantity = quantity updateSettings.Tag = "SL & TP quantities updated" ticket = self.tpTicket response = ticket.Update(updateSettings) # Validate the response is OK if response.IsSuccessful: self.Debug("Order updated successfully") else: self.Debug("Order not updated") self.partfillIndicator = 0 #otherwise just set tp else: tpPrice = self.limitPrice * (1 + self.tp) self.tpTicket = self.LimitOrder(self.symbol, -quantity, tpPrice) #if buy order cancelled reset buy order and partfill indicators elif self.buyTicket.Status == OrderStatus.Canceled: self.Debug("Buy order cancelled") self.buyOrderIndicator = 0 self.partfillIndicator = 0 ##### Take Profit Handling if self.tpTicket == None: return #if order is tp order = filled, reset long indicator if orderEvent.OrderId == self.tpTicket.OrderId: if self.tpTicket.Status == OrderStatus.Filled: self.Debug("Take Profit filled") self.longIndicator = 0 #if buy order only part filled, cancel open buy orders and reset buy order indicator if self.partfillIndicator == 1: self.Transactions.CancelOpenOrders(self.symbol) self.buyOrderIndicator = 0 self.partfillIndicator = 0 ################## def chartHandler(self): #chartdata for i in range(2): if self.sigs[i].Current.Value < 20: self.inds[i] = 1 else: self.inds[i] = 0 for i in range(2): self.Plot("indicators", self.sers[i], self.inds[i]) sum = 0 for i in self.inds: sum = sum + i if sum == 2: self.Plot("signal", 'buy', 1) else: self.Plot("signal", 'buy', 0)