Overall Statistics
Total Trades
43
Average Win
49.73%
Average Loss
-4.99%
Compounding Annual Return
304.381%
Drawdown
37.800%
Expectancy
3.697
Net Profit
1995.534%
Sharpe Ratio
2.181
Loss Rate
57%
Win Rate
43%
Profit-Loss Ratio
9.96
Alpha
0.567
Beta
0.44
Annual Standard Deviation
0.497
Annual Variance
0.247
Information Ratio
-0.167
Tracking Error
0.547
Treynor Ratio
2.465
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.stopMonitor = False
        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
        self.follow_factor = 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.stopMonitor:
            if currentPrice > self.previousPrice: 
                ratio = currentPrice / self.previousPrice
                adjustedRatio = (ratio + self.follow_factor) / (1 + self.follow_factor)
                trigger = self.previousTrigger * d.Decimal(adjustedRatio)
                self.previousTrigger = trigger
                
                if self.stopTicket is not None:
                    self.stopTicket.cancel()
                    self.stopTicket = None
                
            elif currentPrice <= self.previousTrigger:
                # limit price is set below the trigger to maximise the chances of catching a price decrease
                stopLimitPrice = self.previousTrigger * d.Decimal(0.99)
                # Use round(price, precision) to meet brokerage price precision requirement
                self.stopTicket = self.LimitOrder(self.symbol, -holdings, round(stopLimitPrice, 2))
                
        # 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 and self.stopTicket is None:
            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)
        
        ## BUY ORDER ##
        if self.buyTicket is not None:
            self.Debug("Event detected: {0} {1}".format(self.orderTypeDict[order.Type], self.orderDirectionDict[order.Direction]))
            self.Debug("{0}".format(event))
            if event.OrderId == self.buyTicket.OrderId:
                # If buy order is filled, monitor price to create a simulated stop limit order if needed
                if self.buyTicket.Status == OrderStatus.Filled:
                    self.previousTrigger = d.Decimal(1 - self.initial_stop_percent_delta) * self.buyTicket.AverageFillPrice
                    self.buyTicket = None
                    self.stopMonitor = True
                    
                return
        
        ## SELL ORDER ##
        if self.sellTicket is not None:
            self.Debug("Event detected: {0} {1} ==> Normal Sell Order".format(self.orderTypeDict[order.Type], self.orderDirectionDict[order.Direction]))
            self.Debug("{0}".format(event))
            if event.OrderId == self.sellTicket.OrderId:
                # If sell order is filled, cancel stop loss
                if self.sellTicket.Status == OrderStatus.Filled:
                    self.sellTicket = None
                    self.stopMonitor = False
                    if self.stopTicket is not None:
                        self.stopTicket.Cancel()
                        self.stopTicket = None
                    
                return

        ## STOP ORDER ##  
        if self.stopTicket is not None:
            self.Debug("Event detected: {0} {1} ==> Simulated Stop Limit Order".format(self.orderTypeDict[order.Type], self.orderDirectionDict[order.Direction]))
            self.Debug("{0}".format(event))
            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
                    self.stopMonitor = False
                    if self.sellTicket is not None:
                        self.sellTicket.Cancel()
                        self.sellTicket = None
                        
                return