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