| 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)