Overall Statistics
Total Trades
35
Average Win
3.04%
Average Loss
-0.66%
Compounding Annual Return
37.327%
Drawdown
6.600%
Expectancy
1.629
Net Profit
21.877%
Sharpe Ratio
1.652
Probabilistic Sharpe Ratio
68.965%
Loss Rate
53%
Win Rate
47%
Profit-Loss Ratio
4.59
Alpha
0.253
Beta
0.058
Annual Standard Deviation
0.158
Annual Variance
0.025
Information Ratio
0.371
Tracking Error
0.358
Treynor Ratio
4.496
Total Fees
$2510.41
'''
**
 * Author:    Joseph A Bastulli
 * Created:   2.02.2020
 * 
 * (c) Copyright Joseph A Bastulli
**
'''

import numpy as np
import pandas as pd
from collections import deque

class QuantumOptimizedPrism(QCAlgorithm):

    def Initialize(self):
        
        # Max window size
        self.Lookback = 168 # 1 week in hours
    
        # list of symbols we want to trade
        self.symbolList = ["BTCUSD","ETHUSD","LTCUSD","BCHUSD"]
        #self.symbolList = ["BTCUSD","ETHUSD","LTCUSD","BCHUSD","XRPUSD","XLMUSD","EOSUSD","REPUSD","XTZUSD","ETCUSD","ZRXUSD"]
        
        # dictionary to store info per symbol
        self.rollingWindow = {}
        
        # init insights for plotting alpha
        self.insightPeriod = Time.Multiply(Extensions.ToTimeSpan(Resolution.Hour), self.Lookback)
        self.insightTemp = None
        
        # create plot and consolidator for each symbol we want to trade
        for name in self.symbolList:
            cryptoSymbol = self.AddCrypto(name, Resolution.Minute, Market.GDAX).Symbol
            self.Debug('{}'.format(cryptoSymbol))
            fiveConsolidator = TradeBarConsolidator(timedelta(hours=1))
            fiveConsolidator.DataConsolidated += self.FiveConsolidator
            self.SubscriptionManager.AddConsolidator(cryptoSymbol, fiveConsolidator)
            self.rollingWindow["close_{0}".format(cryptoSymbol)] = deque(maxlen=self.Lookback)
            
            stockPlot = Chart(str(cryptoSymbol))
            stockPlot.AddSeries(Series('Price', SeriesType.Line, 0))
            stockPlot.AddSeries(Series('Top', SeriesType.Line, 0))
            stockPlot.AddSeries(Series('Mid', SeriesType.Line, 0))
            stockPlot.AddSeries(Series('Bot', SeriesType.Line, 0))
            self.AddChart(stockPlot)
        
        # max buy sell amount
        self.weight = 1 / len(self.symbolList)
        
        self.SetCash(100000)
        self.SetStartDate(2020, 1, 1)
        self.SetBrokerageModel(BrokerageName.GDAX, AccountType.Cash)
        self.SetWarmUp(self.Lookback)

    def GetInsight(self, bar, price, top, mid, bot):
        '''Updates this alpha model with the latest data from the consolidator'''
        
        insights = []
        if price > top:
            direction = InsightDirection.Up
        elif price < top and price > mid:
            direction = InsightDirection.Flat
        else:
            direction = InsightDirection.Down
        
        if self.insightTemp is not None and direction == self.insightTemp:
            return insights
        
        self.insightTemp = direction
            
        insight = Insight.Price(bar.Symbol, self.insightPeriod, direction)
        insights.append(insight)
        return insights
        
    def Indicator(self, symbol):
        '''Rolling quantile for upper and lower bounds'''
        data = np.array(self.rollingWindow["close_"+str(symbol)])
        top = np.quantile(data,0.99, interpolation='higher')
        bot = np.quantile(data,0.01, interpolation='higher')
        mid = (top+bot)/2
        return top, bot, mid
            
    def FiveConsolidator(self, sender, bar):
        
        stringSym = str(bar.Symbol)
        sym_price = bar.Close
        
        # Append price data
        self.rollingWindow["close_{0}".format(bar.Symbol)].append(sym_price)
        
        if not self.IsWarmingUp:
            if len(self.rollingWindow["close_{0}".format(bar.Symbol)]) < self.Lookback:
                return
            
            # Get indicators
            top, bot, mid = self.Indicator(bar.Symbol)
            
            # buy
            if sym_price > top and not self.Portfolio.Invested:
                self.SetHoldings(bar.Symbol, self.weight)
                
            # sell
            elif sym_price < mid and self.Portfolio.Invested:
                self.SetHoldings(bar.Symbol, 0)
                
            else:
                pass
            
            # plot insights (not needed for trading).  Disable for faster backtesting
            self.EmitInsights(self.GetInsight(bar, sym_price, top, mid, bot))
            
            # plot every interval defined plot!
            if self.Time.hour % 24 == 0 and self.Time.minute == 0:
                self.Plot(str(bar.Symbol), 'Price', sym_price)
                self.Plot(str(bar.Symbol), 'Top', top)
                self.Plot(str(bar.Symbol), 'Mid', mid)
                self.Plot(str(bar.Symbol), 'Bot', bot)

        
    def OnData(self, data):
        pass

    def OnOrderEvent(self, orderEvent):
        self.Debug("{} {}".format(self.Time, orderEvent.ToString()))

    def OnEndOfAlgorithm(self):
        self.Log("{} - TotalPortfolioValue: {}".format(self.Time, self.Portfolio.TotalPortfolioValue))
        self.Log("{} - CashBook: {}".format(self.Time, self.Portfolio.CashBook))