Overall Statistics
Total Trades
949
Average Win
5.46%
Average Loss
-0.31%
Compounding Annual Return
612.643%
Drawdown
21.500%
Expectancy
3.257
Net Profit
6439.820%
Sharpe Ratio
3.308
Loss Rate
77%
Win Rate
23%
Profit-Loss Ratio
17.35
Alpha
-0.24
Beta
122.689
Annual Standard Deviation
0.439
Annual Variance
0.192
Information Ratio
3.277
Tracking Error
0.439
Treynor Ratio
0.012
Total Fees
$0.00
import numpy as np
import decimal as d
from datetime import timedelta

### <summary>
### Basic mean reversion of bitcoin, buy when above sma. Sell when below.
### </summary>
class BTCMeanReversion(QCAlgorithm):

    def Initialize(self):
        '''Initialise the data and resolution required, as well as the cash and start-end dates for the algorithm'''

        self.starting_capitol = 100000
        self.SetStartDate(2016, 1, 1) 
        self.SetEndDate(2018, 2, 15)    #Set End Date
        self.SetCash(self.starting_capitol)           #Set Strategy Cash

        self.SetBrokerageModel(BrokerageName.GDAX, AccountType.Cash)
        self.SetBenchmark("BTCUSD")
        self.AddCrypto("BTCUSD", Resolution.Minute)
        
        # This is used to calculate the profit if we were to buy and hold
        self.starting_quantity = 0
        
        # 19 sma 9 ema = 1,835% 2016/01/01 - 2018/02/15
        self.short_sma = self.SMA("BTCUSD", 20, Resolution.Daily)
        self.short_ema = self.EMA("BTCUSD", 7, Resolution.Daily)
        
        # self.long_minute_ema = self.EMA("BTCUSD", 150, Resolution.Minute)
        # self.short_minute_ema = self.EMA("BTCUSD", 600, Resolution.Minute)
    
        stockPlot = Chart('Trade Plot')
        stockPlot.AddSeries(Series('Equity', SeriesType.Line, 0))
        stockPlot.AddSeries(Series('Benchmark', SeriesType.Line, 0))
        
        indicatorPlot = Chart('Indicators')
        indicatorPlot.AddSeries(Series('Price', SeriesType.Line, 0))
        indicatorPlot.AddSeries(Series('Too Fast', SeriesType.Line, 0))
        indicatorPlot.AddSeries(Series('Short EMA', SeriesType.Line, 0))
        indicatorPlot.AddSeries(Series('Long EMA', SeriesType.Line, 0))
        # 18 day sma starting point 18 * 24 = 432
        # 9 day ema starting point 9 * 24 = 216
        
        # self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.Every(timedelta(minutes=3)), Action(self.RebalanceMinute))
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.Every(timedelta(minutes=15)), Action(self.RebalanceHour))
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.Every(timedelta(hours=12)), Action(self.PlotData))
        
        
    def OnData(self, data):
        # self.RebalanceHour()
        pass

                
    def isMovingTooFast(self):
        return self.Securities["BTCUSD"].Price > (self.long_minute_ema.Current.Value * d.Decimal(1.05))
        
    def RebalanceHour(self):
        btcPrice = self.Securities["BTCUSD"].Price
        # comparing of daily sma/ema against current price
        if btcPrice > self.short_sma.Current.Value:
            if not self.Portfolio.Invested:
                if btcPrice > self.short_ema.Current.Value:
                    self.SetHoldings("BTCUSD", 1)
            elif self.Portfolio.Invested:
                if btcPrice < self.short_ema.Current.Value:
                    self.SetHoldings("BTCUSD", 0)
        elif self.Portfolio.Invested:
            self.SetHoldings("BTCUSD", 0)

        
    def RebalanceMinute(self):
        btcPrice = self.Securities["BTCUSD"].Price
        # Comparing minute sma/ema 
        if btcPrice > self.short_minute_ema.Current.Value:
            if self.isMovingTooFast():
                if self.Portfolio.Invested:
                    self.SetHoldings("BTCUSD", 0)
            else: 
                if not self.Portfolio.Invested \
                and btcPrice > self.short_minute_ema.Current.Value \
                and btcPrice < (self.short_minute_ema.Current.Value* d.Decimal(1.02)):
                    self.SetHoldings("BTCUSD", 1)
        # This is the buy in for the hourly rebalance 
        else:
            if self.Portfolio.Invested:
                self.SetHoldings("BTCUSD", 0)
                
    def PlotData(self):
        price = self.Securities["BTCUSD"].Price
        if self.starting_quantity == 0:
           self.starting_quantity = self.starting_capitol / price
        
        self.Plot('Trade Plot', 'Equity', self.Portfolio.TotalPortfolioValue)
        self.Plot('Trade Plot', 'Benchmark', (price * self.starting_quantity))
        
        # self.Plot('Indicators', 'Price', price)
        # too_fast = float(self.long_minute_ema.Current.Value * d.Decimal(1.05))
        # self.Plot('Indicators', 'Too Fast', too_fast)
        # self.Plot('Indicators', 'Short EMA', self.short_minute_ema.Current.Value)
        # self.Plot('Indicators', 'Long EMA', self.long_minute_ema.Current.Value)
        
    # Override SetHoldings to use limit orders (ratio is of totalPortfolioValue.)
    def SetHoldings(self, symbol, ratio):
        
        security = self.Securities[symbol]
        if not security.IsTradable:
            self.Debug("{} is not tradable.".format(symbol))
            return    # passive fail
        ratio = d.Decimal(ratio)
        price, quantity = security.Price, security.Holdings.Quantity
        
        # Keep 2% Cash    (for the limit order, rounding errors, and safety)
        totalPortfolioValue = self.Portfolio.TotalPortfolioValue * d.Decimal(0.98)
        
        # +0.1% Limit Order
        # (to make sure it executes quickly and without much loss)
        # (if you set the limit large it will act like a market order)
        limit = 1.001
        desiredQuantity = totalPortfolioValue * ratio / price
        orderQuantity = desiredQuantity - quantity
        # limit needs to be inverse when selling
        limitPrice = price * d.Decimal(limit if orderQuantity >= 0 else 1/limit)
        # self.Log("Limit Order: {} coins @ ${} per coin".format(orderQuantity, limitPrice))
        self.LimitOrder(symbol, orderQuantity, limitPrice)