Overall Statistics
Total Trades
78
Average Win
2.03%
Average Loss
-6.06%
Compounding Annual Return
-2.591%
Drawdown
18.700%
Expectancy
-0.007
Net Profit
-1.277%
Sharpe Ratio
0.183
Probabilistic Sharpe Ratio
25.213%
Loss Rate
26%
Win Rate
74%
Profit-Loss Ratio
0.34
Alpha
-0.577
Beta
0.115
Annual Standard Deviation
0.298
Annual Variance
0.089
Information Ratio
-8.946
Tracking Error
0.606
Treynor Ratio
0.471
Total Fees
$13.80
Estimated Strategy Capacity
$160000.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.MarginRemaining * 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)