Overall Statistics |
Total Trades 41 Average Win 46.76% Average Loss -5.46% Compounding Annual Return 273.624% Drawdown 39.500% Expectancy 3.307 Net Profit 1657.579% Sharpe Ratio 2.052 Loss Rate 55% Win Rate 45% Profit-Loss Ratio 8.57 Alpha 0.505 Beta 0.448 Annual Standard Deviation 0.503 Annual Variance 0.253 Information Ratio -0.265 Tracking Error 0.546 Treynor Ratio 2.303 Total Fees $0.00 |
import clr clr.AddReference("System") clr.AddReference("QuantConnect.Algorithm") clr.AddReference("QuantConnect.Indicators") clr.AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Indicators import * from QuantConnect.Orders import * import decimal as d class MovingAverageAlgorithm(QCAlgorithm): def __init__(self): # Market parameters self.previousTime = None self.previousPrice = None self.previousTrigger = None self.buyTicket = None self.sellTicket = None self.stopTicket = None self.symbol = "BTCEUR" self.base = "BTC" self.quote = "EUR" self.market = Market.GDAX self.brokerage = BrokerageName.GDAX self.resolution = Resolution.Daily self.conversionRate = 1.23 # Strategy parameters self.length_fast_ema = 5 self.length_slow_ema = 14 self.initial_stop_percent_delta = 0.1 # e.g. 0.1 means stop price = 0.9 * price self.follow_factor = 1 # 0 means stop price follows price 1 for 1, def Initialize(self): '''Initialise algorithm with data, resolution, cash and start-end dates''' # Add string correspondance to each Orders.OrderType: self.orderTypeDict = {OrderType.Market: "Market", OrderType.Limit: "Limit", OrderType.StopMarket: "StopMarket", OrderType.StopLimit: "StopLimit", OrderType.MarketOnOpen: "MarketOnOpen", OrderType.MarketOnClose: "MarketOnClose"} # Add string correspondance to each Orders.Direction: self.orderDirectionDict = {OrderDirection.Buy: "Buy", OrderDirection.Sell: "Sell", OrderDirection.Hold: "Hold"} self.SetStartDate(2016,1,1) # Set Start Date self.SetEndDate(2018,3,4) # Set End Date self.Portfolio.SetCash(0) # Set USD to 0 since we only have EUR in our account self.Portfolio.SetCash("EUR", 1000, self.conversionRate) # Set EUR strategy cash with static EURUSD conversion rate self.AddCrypto(self.symbol, self.resolution, self.market) # symbol to trade self.SetBrokerageModel(self.brokerage, AccountType.Cash) # crypto brokerage # Indicators self.ema_fast = self.EMA(self.symbol, self.length_fast_ema, self.resolution) # fast moving average self.ema_slow = self.EMA(self.symbol, self.length_slow_ema, self.resolution) # slow moving average # Benchmark is buy & hold of the traded security self.SetBenchmark(self.symbol) # Note - use single quotation marks: ' instead of double " # Chart - Master Container for the Chart: coinPlot = Chart('Strategy Equity') # On the Trade Plotter Chart we want 3 series: trades and price: coinPlot.AddSeries(Series('Benchmark', SeriesType.Line, 0)) coinPlot.AddSeries(Series('Fast EMA', SeriesType.Line, 0)) coinPlot.AddSeries(Series('Slow EMA', SeriesType.Line, 0)) def OnData(self, data): '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here. Arguments: data: Slice object keyed by symbol containing the security data ''' # Notice in this method: # 1. We never need to 'update' our indicators with the data, the engine takes care of this for us # 2. We can use indicators directly in math expressions # 3. We can easily plot many indicators at the same time # Wait for indicators to fully initialize if not self.ema_slow.IsReady: return # Allow one trade maximum per day if self.previousTime is not None and self.previousTime.date() == self.Time.date(): return # Retrieve quote asset. CashBook is a dictionary of currencies (including crypto assets) availableQuote = self.Portfolio.CashBook[self.quote].Amount # Retrieve current holdings holdings = self.Portfolio.CashBook[self.base].Amount # Retrieve current price currentPrice = self.Securities[self.symbol].Close ## UPDATE TRAILING STOP LIMIT ORDER ## # Increase trailing stop limit if it exist and if necessary: if self.stopTicket is not None: if currentPrice > self.previousPrice: ratio = currentPrice / self.previousPrice adjustedRatio = (ratio + self.follow_factor) / (1 + self.follow_factor) trigger = self.previousTrigger * d.Decimal(adjustedRatio) updateOrderFields = UpdateOrderFields() updateOrderFields.StopPrice = trigger updateOrderFields.LimitPrice = trigger * d.Decimal(0.99) self.stopTicket.Update(updateOrderFields) self.previousTrigger = trigger # Define a small tolerance to avoid bouncing when comparing indicators: tolerance = 0.00015 ## BUY ORDER ## # 1. Go long if we're currently short or flat # Note that we cannot short crypto assets at the moment with GDAX API # 2. If fast ema is greater than slow ema, then go long # 3. Wait for price confirmation: price must exceed a recent high if holdings <= 0: if self.ema_fast.Current.Value > self.ema_slow.Current.Value * d.Decimal(1 + tolerance): limitPrice = currentPrice * d.Decimal(1+0.001) # use all cash on long, 0.999 is here to avoid buying power issues quantity = (availableQuote / limitPrice) * d.Decimal(0.999) lot = self.Securities[self.symbol].SymbolProperties.LotSize roundedQuantity = round(quantity/lot-d.Decimal(0.5))*lot # -0.5 is present to round down the quantity self.buyTicket = self.LimitOrder(self.symbol, roundedQuantity, round(limitPrice, 2)) ## SELL ORDER ## # 1. Liquidate if we're currently long # 2. If fast ema is less than slow ema then liquidate the long if holdings > 0: if self.ema_fast.Current.Value < self.ema_slow.Current.Value: limitPrice = currentPrice * d.Decimal(1-0.001) self.sellTicket = self.LimitOrder(self.symbol, -holdings, round(limitPrice, 2)) # sell entire position # Store in memory the time and the price of the last execution self.previousTime = self.Time self.previousPrice = currentPrice # Plot indicators and benchma self.Plot('Strategy Equity', 'Benchmark', self.Securities[self.symbol].Price) self.Plot('Strategy Equity', 'Fast EMA', self.ema_fast.Current.Value) self.Plot('Strategy Equity', 'Slow EMA', self.ema_slow.Current.Value) def OnOrderEvent(self, event): # Handle filling of buy & sell & orders: # Determine if order is the buy or the sell or the stop order = self.Transactions.GetOrderById(event.OrderId) self.Debug("Event detected: {0} {1}".format(self.orderTypeDict[order.Type], self.orderDirectionDict[order.Direction])) self.Debug("{0}".format(event)) ## BUY ORDER ## if self.buyTicket is not None: if event.OrderId == self.buyTicket.OrderId: # If buy order is filled, create stop loss if self.buyTicket.Status == OrderStatus.Filled: quantity = self.buyTicket.Quantity # limit price is set below the trigger to maximise the chances of catching a price decrease trigger = d.Decimal(1 - self.initial_stop_percent_delta) * self.buyTicket.AverageFillPrice limit = trigger * d.Decimal(0.99) # Use round(price, precision) to meet brokerage price precision requirement self.stopTicket = self.StopLimitOrder(self.symbol, -quantity, round(trigger, 2), round(limit, 2)) self.previousTrigger = trigger self.buyTicket = None return ## SELL ORDER ## if self.sellTicket is not None: if event.OrderId == self.sellTicket.OrderId: # If sell order is filled, cancel stop loss if self.sellTicket.Status == OrderStatus.Filled: self.stopTicket.Cancel() return ## STOP ORDER ## if self.stopTicket is not None: if event.OrderId == self.stopTicket.OrderId: # If stop order is filled, cancel the sell order, if any: if self.stopTicket.Status == OrderStatus.Filled: self.stopTicket = None if self.sellTicket is not None: self.sellTicket.Cancel() self.sellTicket = None return