| Overall Statistics |
|
Total Trades 7870 Average Win 0.20% Average Loss -0.45% Compounding Annual Return 10140.037% Drawdown 42.600% Expectancy 0.352 Net Profit 23826.111% Sharpe Ratio 4.054 Loss Rate 6% Win Rate 94% Profit-Loss Ratio 0.44 Alpha -0.004 Beta 259.69 Annual Standard Deviation 0.881 Annual Variance 0.776 Information Ratio 4.038 Tracking Error 0.881 Treynor Ratio 0.014 Total Fees $0.00 |
import numpy as np
import decimal as d
import math
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(2017, 1, 1)
# self.SetEndDate(2018, , 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)
self.AddCrypto("ETHUSD", Resolution.Minute)
# This is used to calculate the profit if we were to buy and hold
self.starting_quantity = 0
self.weights = {
"ETHUSD" : 0,
# "BTCUSD" : 0
}
self.wait_to_order = {
"ETHUSD" : False,
"BTCUSD" : False
}
self.short_sma = {
"ETHUSD" : self.SMA("ETHUSD", 20, Resolution.Daily),
"BTCUSD" : self.SMA("BTCUSD", 20, Resolution.Daily),
}
self.short_ema = {
"ETHUSD" : self.EMA("ETHUSD", 7, Resolution.Daily),
"BTCUSD" : self.EMA("BTCUSD", 7, Resolution.Daily),
}
# self.long_minute_ema = self.EMA("ETHUSD", 150, Resolution.Minute)
# self.short_minute_ema = self.EMA("ETHUSD", 600, Resolution.Minute)
stockPlot = Chart('Trade Plot')
stockPlot.AddSeries(Series('Equity', SeriesType.Line, 0))
stockPlot.AddSeries(Series('Benchmark', SeriesType.Line, 0))
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.Every(timedelta(minutes=15)), Action(self.Rebalance))
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.Every(timedelta(hours=12)), Action(self.PlotData))
def RebalanceCoin(self, symbol):
price = self.Securities[symbol].Price
sma = self.short_sma[symbol].Current.Value
ema = self.short_ema[symbol].Current.Value
invested = self.Portfolio[symbol].Invested
if price > sma and sma > 0:
if price > ema:
self.weights[symbol] = 1 #min(self.weights[symbol] + 0.2, 1)
elif price < ema:
self.weights[symbol] = 0 #max(self.weights[symbol] - 0.1, 0)
elif invested:
self.weights[symbol] = 0
def Rebalance(self):
# self.PrintUpdate()
self.RebalancePortfolio()
self.ReInvestPortfolio()
def RebalancePortfolio(self):
for symbol in self.weights:
self.RebalanceCoin(symbol)
amount = 1/len(self.weights)
total = sum(self.weights.values())
div = 1/total if total > 1 else 1
if total > 1:
for symbol, weight in self.weights.items():
self.weights[symbol] = self.weights[symbol] * div
# print('New Weight %s : %0.2f ' % (symbol, w))
# invest the leftover money in others
# for j in self.weights:
# if self.weights[j] > 0:
# self.weights[j] = self.weights[j] + amount
def ReInvestPortfolio(self):
# Always be fully invested
for symbol, weight in self.weights.items():
self.SetHoldings(symbol, min(weight, 1))
def PlotData(self):
price = self.Securities["BTCUSD"].Price
if self.starting_quantity == 0 and price > 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))
def PrintSignal(self, signal):
self.Plot('MA Plot', signal, self.Securities["BTCUSD"].Price)
print('| Signal %s | Porfolio $%0.2f |' % (signal, self.Portfolio.TotalPortfolioValue))
def PrintUpdate(self):
print('| %s Porfolio $%0.2f | Benchmark $%0.2f | %s weight %0.2f | %s weight %0.2f ' % (self.Transactions.UtcTime, self.Portfolio.TotalPortfolioValue, (self.Securities["BTCUSD"].Price * self.starting_quantity), "BTCUSD", self.Portfolio["BTCUSD"].Quantity, "ETHUSD", self.Portfolio["ETHUSD"].Quantity))
def OnOrderEvent(self, newEvent):
if newEvent.Status == OrderStatus.Canceled:
pass
# self.Rebalance()
elif newEvent.Status == OrderStatus.Filled:
s = 'BUY' if newEvent.Direction == OrderDirection.Buy else 'SELL'
# self.PrintSignal(s)
def roundDown(self, n):
return math.floor(n*100)/100
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 = security.Price
if price == 0:
self.Debug("{} price is 0".format(symbol))
return
quantity = security.Holdings.Quantity
# Keep 1% Cash (for the limit order, rounding errors, and safety)
totalPortfolioValue = self.Portfolio.TotalPortfolioValue * d.Decimal(0.99)
desiredQuantity = totalPortfolioValue * ratio / price
orderQuantity = desiredQuantity - quantity
if orderQuantity > 0.01 or orderQuantity < -0.01:
# limit needs to be inverse when selling
limitPrice = price - d.Decimal(0.01) if orderQuantity > 0 else price + d.Decimal(0.01)
limitPrice = self.roundDown(limitPrice)
open_orders = self.Transactions.GetOpenOrders(symbol)
if len(open_orders) > 0: # need to fill an order before placing the next\
# self.Log("Cancel Orders")
if not self.wait_to_order[symbol]:
self.Transactions.CancelOpenOrders(symbol)
self.wait_to_order[symbol] = True
else:
self.wait_to_order[symbol] = False
elif len(open_orders) == 0:
if orderQuantity > 0 and self.Portfolio.Cash > d.Decimal(limitPrice) * d.Decimal(orderQuantity):
# self.Log("Cash $%0.2f | Limit Order %s: %0.2f coins @ $%0.2f per coin | actual price $%0.2f" % (ratio, self.Portfolio.Cash, symbol, orderQuantity, limitPrice, price))
self.LimitOrder(symbol, orderQuantity, limitPrice)
elif orderQuantity < 0:
# self.Log("Cash $%0.2f | Limit Order %s: %0.2f coins @ $%0.2f per coin | actual price $%0.2f" % (ratio, self.Portfolio.Cash, symbol, orderQuantity, limitPrice, price))
self.LimitOrder(symbol, orderQuantity, limitPrice)