Overall Statistics
Total Trades
41
Average Win
46.76%
Average Loss
-5.46%
Compounding Annual Return
276.127%
Drawdown
39.500%
Expectancy
3.307
Net Profit
1657.579%
Sharpe Ratio
2.057
Loss Rate
55%
Win Rate
45%
Profit-Loss Ratio
8.57
Alpha
0.507
Beta
0.448
Annual Standard Deviation
0.504
Annual Variance
0.254
Information Ratio
-0.265
Tracking Error
0.547
Treynor Ratio
2.315
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 *
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
        self.follow_factor = 1
        

    def Initialize(self):
        '''Initialise algorithm with data, resolution, cash and start-end dates'''

        self.SetStartDate(2016,1,1)  # Set Start Date
        self.SetEndDate(2018,2,28)  # 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 = decimal.Decimal(self.previousTrigger * adjustedRatio)
                
                updateOrderFields = UpdateOrderFields()
                updateOrderFields.StopPrice = trigger
                updateOrderFields.LimitPrice = trigger * 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.Log("{0}: {1}: {2}".format(self.Time, order.Type, event))
            
            ## BUY ORDER FILLED ##
            if event.OrderId == self.buyTicket.OrderId:
                self.Debug("Buy ticket event detected")
                # If buy order is filled, create stop loss
                if self.buyTicket.Status == OrderStatus.Filled:
                    self.Debug("Buy order filled")
                    quantity = self.buyTicket.Quantity
                    # limit price is set below the trigger to maximise the chances of catching a price decrease
                    trigger = (1 - self.initial_stop_percent_delta) * self.buyTicket.AverageFillPrice
                    limit = trigger * 0.99
                    self.stopTicket = self.StopLimitOrder(self.symbol, -quantity, stopPrice=trigger, limitPrice=limit)
                    self.previousTrigger = trigger
                    self.buyTicket = None
            
            ## SELL ORDER FILLED ##
            elif event.OrderId == self.sellTicket.OrderId:
                self.Debug("Sell ticket event detected")
                # If sell order is filled, cancel stop loss
                if self.sellTicket.Status == OrderStatus.Filled:
                    self.Debug("Sell order filled")
                    self.stopTicket.Cancel()

            ## STOP ORDER FILLED ##          
            elif event.OrderId == self.stopTicket.OrderId:
                self.Debug("Stop ticket event detected")
                # If stop order is filled, cancel the sell order, if any:
                if self.stopTicket.Status == OrderStatus.Filled:
                    self.Debug("Stop order filled")
                    self.stopTicket = None
                    if self.sellTicket is not None:
                        self.sellTicket.Cancel()
                        self.sellTicket = None