Overall Statistics
Total Trades
190
Average Win
3.71%
Average Loss
-2.97%
Compounding Annual Return
33.179%
Drawdown
54.700%
Expectancy
0.100
Net Profit
16.077%
Sharpe Ratio
0.904
Probabilistic Sharpe Ratio
38.774%
Loss Rate
51%
Win Rate
49%
Profit-Loss Ratio
1.25
Alpha
0.868
Beta
-0.151
Annual Standard Deviation
0.867
Annual Variance
0.751
Information Ratio
0.18
Tracking Error
1.238
Treynor Ratio
-5.177
Total Fees
$34475.06
Estimated Strategy Capacity
$5700000.00
Lowest Capacity Asset
ETHUSD XJ
from arch import arch_model
import numpy as np

class StrategyManagerUtility:
    def __init__(self, quantScenario):
        self.quantScenario = quantScenario
    
    def setupModel(self, preference):
        self.preference = preference
        self.preference.algorithmReadings
        
    # GJR GARCH mode

        # Attempt to Fit the model
        try:    
            #Tweak the garch parameters to prevent the model from crashing on certain data ----------------------------------------------------------------------
            egarch_gm = arch_model(preference.garchData, p = 1, q = 1, o = 1, vol = 'GARCH', dist = 't')
            #egarch_gm = arch_model(preference.garchData, p = 1, q = 1, o = 1, vol = 'GARCH', dist = 't', rescale = "FALSE")
            
            egarch_result = egarch_gm.fit()
                
            # Make 1-period ahead forecast
            gm_forecast = egarch_result.forecast(horizon = 1)
            preference.egarchMean = gm_forecast.mean.values[-1]
            #if preference.egarchMean:
            #    preference.egarchMeanArray = np.append( preference.egarchMeanArray, preference.egarchMean)
            #else:
            #    pass
            
            garchForcastNextDay = gm_forecast.variance[-1:].values
            
            #printing the logs
            self.genericGarchLog(preference, gm_forecast, garchForcastNextDay[0])
            
            if preference.quantScenario.IsWarmingUp:
                return
            else:
                self.setHoldingsBasedOnEGarch(garchForcastNextDay[0], preference)
        except Exception:
            return
        
        #self.Debug("----------> " + str(self.gm_result.summary()))
    
    def setHoldingsBasedOnEGarch(self, garchForcastNextDay, preference):
        
        self.sellMultipler = 2
        self.buyMultipler = 1
        
        if garchForcastNextDay[0] < (preference.egarchMean * self.buyMultipler):
            preference.quantScenario.SetHoldings(preference.tradedETFSymbol, 1)
        elif garchForcastNextDay[0] > preference.egarchMean*self.sellMultipler and  garchForcastNextDay[0] > 0:
            #self.SetHoldings(self.tradedETFSymbol, (self.egarchMean)/garchForcastNextDay[0])
            sdVariance = (preference.egarchMean)/garchForcastNextDay[0]
            
            if sdVariance > 1:
                sdVariance = 1
                
            preference.quantScenario.SetHoldings(preference.tradedETFSymbol, 0) # The algorithm has been changed, instead of holding a variable percentage of SOXL, it holds 100% or 0%, but to hold nothing when the volitility grows
            #self.SetHoldings([PortfolioTarget("TQQQ", 0), PortfolioTarget("ETF", int(sdVariance))], True)
            
    def isnan(self, value):
        try:
            import math
            return math.isnan(float(value))
        except:
            return False
    
    def genericGarchLog(self, preference, inputGm_forecast, garchForcastNextDay):
        
        sdVariance = (preference.egarchMean)/garchForcastNextDay[0]
        adjustedSdVariance = (preference.egarchMean)/garchForcastNextDay[0]
        
        #formatting the data for logging below: removing Nan values, rounding very small numbers up to zero, and very large numbers down to 100%
        if self.isnan(sdVariance):
            return
        
        if "e+" in str(sdVariance):
            sdVariance = 0
        
        if garchForcastNextDay[0] < (preference.egarchMean * self.buyMultipler):
            adjustedSdVariance = 100
        else:
            adjustedSdVariance = 0 # ----------------------------------------------------------------------------------------This number has been created to align to the 100% or 0% holding following the algrithm update

        sdVarianceNumpyConversion = round(float(sdVariance * 100), 4)
        self.preference.algorithmReadings.append(adjustedSdVariance)
        
        #--- price input ------------ preference.quantScenario.Portfolio["TQQQ"].Price
        preference.quantScenario.Debug(str(preference.quantScenario.Time) + " - " + str(preference.garchData[-1]))
        preference.quantScenario.Debug("SOXL Allocation(raw): " + str(sdVariance) + ", Adjusted Allocation: "+ str(adjustedSdVariance) + "%, Percentage Conversion: " + str(sdVarianceNumpyConversion) + "%")
        #preference.quantScenario.Debug("Mean, based on Garch Data: "+ str(preference.egarchMean) + ", garchForcastNextDay: "+str(garchForcastNextDay[0]))
        #preference.quantScenario.Debug("Forecast Variance, based on Garch Data: "+ str(inputGm_forecast.variance[-1:].values))
        #preference.quantScenario.Debug("Yesterday's Standard Deviation: "+str(preference.garchData[-1]))
        #preference.quantScenario.Debug("Element in Garch Data Array: "+ str(preference.garchData[:5]))
        #preference.quantScenario.Debug("length ---> " + str(len(self.preference.garchData)))
from InvestorPreferenceUtility import InvestorPreferenceUtility

class SOXLStandardDeviation(QCAlgorithm):
    
    #Next Steps:
    # Create a simple Crypto DCA program and share the bug with the tutor for DCA.
    #Don't STORE the mean values (funding rate data), create a standard deviation based on the mean values.
    # Refactor the function that updates the Garch data with daily funding rate data (previosuly used for daily standard deviationd data)
    # Refactor the strategy manager class to buy and sell according to the funding rate and not the standard deviation
    
    
    def Initialize(self):
        
        self.investorPrefernce = InvestorPreferenceUtility(self)    
    
    def OnData(self, data):
        '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
            Arguments:
                data: Slice object keyed by symbol containing the stock data
        '''
            
       # if "2021-04-30 13:" in str(self.Time):
       #    self.investorPrefernce.injectTodaysStandardDeviationValue(1.8)
       #if "2021-04-30 14:" in str(self.Time):
       #     self.investorPrefernce.injectTodaysStandardDeviationValue(1.58)
        
        #if "2020-04-22 12:" in str(self.Time):
        #    self.Debug("======================Settlement Violations:" + str(self.investorPrefernce.settlementViolations))
from StrategyManagerUtility import StrategyManagerUtility
import numpy as np
    
class InvestorPreferenceUtility:
    
    def __init__(self, quantconnectObject):
        self.quantScenario = quantconnectObject
        self.strategyManager = StrategyManagerUtility(self)
        self.jsonFundingRates = self.downloadJSON()
        
        self.initalizeAttributes()
        self.setupIndicators()
         #The scheduler is basically the main method of the algorithm. The schedulers create break points that stops the program for computations.
        self.setupScheduler()
        
        
    def initalizeAttributes(self):
        self.quantScenario.SetStartDate(2021, 1, 1) #stock split is 2015,5,20 - 50%
        self.quantScenario.SetEndDate(2021, 12, 1)
        self.quantScenario.SetCash(100000)  # Set Strategy Cash
        self.leveragedETFSymbol = "BTCUSD"
        self.tradedETFSymbol = "ETHUSD"
        self.minutesBeforeMarketClose = 1
        self.counter = 0
        self.investmentInterval = 14
        self.esppInterval = 90
        self.indicatorPeriod = 7 #Default is 7 for std Indicator
        self.garchData = []
        self.egarchMean = 0
        list = []
        self.egarchMeanArray = np.array(list)
        self.resolution = Resolution.Daily
        self.stockSplitMapping = {
        29.72511: 4.75, 
        38.55771: 5.36, 
        41.87474: 5.24,
        42.2561: 3.83,
        41.22449: 2.99,
        34.39242: 4.47 }
        self.settlementViolations = 0
        self.algorithmReadings = []
        
    def setupIndicators(self):
        #Warming up the function
        self.warmUpTimeForBacktestScenario = self.indicatorPeriod
        self.quantScenario.SetWarmUp(timedelta(days=self.indicatorPeriod))
        
        self.leveragedETF = self.quantScenario.AddCrypto(self.leveragedETFSymbol, Resolution.Daily, Market.GDAX)
        self.leveragedETF.SetDataNormalizationMode(DataNormalizationMode.Raw)
        
        self.tradedETF = self.quantScenario.AddCrypto(self.tradedETFSymbol, Resolution.Daily, Market.GDAX)
        self.tradedETF.SetDataNormalizationMode(DataNormalizationMode.Raw)
        self.quantScenario.SetBrokerageModel(BrokerageName.GDAX, AccountType.Cash)
        self.tradedETF.SetLeverage(1)
        
        #without this code, injecting into the algorithm will not work. It makes the hourly calls that make it possible to inject at a particular hour.
        self.forceQuantToCallOnDataEachHour = self.quantScenario.AddCrypto("LTCUSD", Resolution.Hour) 
        
        #Setup standard deviation indicator
        self.quantScenario.stdIndicator = self.quantScenario.STD(self.leveragedETFSymbol, self.indicatorPeriod, Resolution.Daily)
    
    def setupScheduler(self):
        self.quantScenario.Schedule.On(self.quantScenario.DateRules.EveryDay(self.leveragedETFSymbol), self.quantScenario.TimeRules.AfterMarketOpen(self.leveragedETFSymbol, 1), self.dailyComputation)
        
        #self.quantScenario.Schedule.On(self.quantScenario.DateRules.EveryDay(self.leveragedETFSymbol), self.quantScenario.TimeRules.BeforeMarketClose(self.leveragedETFSymbol, self.minutesBeforeMarketClose), self.dollarCostAverage)

    def dollarCostAverage(self):
        self.counter += 1
        
        if self.counter % self.investmentInterval == 0:
            investment = 1230
            #1230 - indiviudal
            #9859 - group - 3000 Justin, 1230
            #Rigby's yearly contribution: 115000
            self.quantScenario.Portfolio.SetCash(self.quantScenario.Portfolio.Cash + investment)
        
        if self.counter % self.esppInterval == 0:
            investment = 4250
            #4250
            self.quantScenario.Portfolio.SetCash(self.quantScenario.Portfolio.Cash + investment)
        
    def downloadJSON(self):
        if self.quantScenario.ObjectStore.ContainsKey("OurJson"):
            curObj = self.quantScenario.ObjectStore.Read("OurJson")
            final = json.loads(curObj)
            self.quantScenario.Debug(final["mean"])
        return final
    
    #Updates to this function would. Get the current date and time, and find the funding rate for that corresponding day and append it to the garch array
    def updateGarchDataArrayAndAccountingForAtockSplits(self):
        #This is a temporary conditional statement to account for the price split on 1/22/2021, please remove this conditional statement after 60 days from 1/22/2021 -------------------------------------------------------------
        roundedStdIndicatorValue = round(self.quantScenario.stdIndicator.Current.Value, 5) #replace this with funding rate STANDARD DEVIATION data from current day
        if roundedStdIndicatorValue in self.stockSplitMapping.keys(): #maybe decpuple this functionality 
            adjustedStockSplitValue = self.stockSplitMapping[roundedStdIndicatorValue]
            self.quantScenario.stdIndicator.Current.Value = adjustedStockSplitValue
            self.garchData.append(adjustedStockSplitValue)
        else:
            self.garchData.append(roundedStdIndicatorValue)
        
    def dailyComputation(self):
        
        self.updateGarchDataArrayAndAccountingForAtockSplits() #--------------------------------remove this line with a conditional
            
        self.counter += 1
        
        #maintaining the 60 days STD for Garch in tact.
        self.rebalanceGarchArray()
            
        #self.strategyManager.setupModel(self)
        if self.counter % 2 == 0:
             self.quantScenario.SetHoldings("ETHUSD", 1.0)
        else:
             self.quantScenario.SetHoldings("ETHUSD", 0.5)
    
    def rebalanceGarchArray(self):
        if len(self.garchData) > 60:
            self.garchData.pop(0)
    
    def injectTodaysStandardDeviationValue(self, inputStandardDeviation):
        
        self.quantScenario.Debug("----------->Injecting Stardard Deviation Data for the next day")
        self.garchData.append(inputStandardDeviation)
        self.rebalanceGarchArray()
        self.strategyManager.setupModel(self)
        #mean =  np.mean(preference.egarchMeanArray)
        #preference.quantScenario.Debug("egarch mean: " + str(mean))