Overall Statistics
Total Trades
6897
Average Win
0.12%
Average Loss
-0.19%
Compounding Annual Return
-87.036%
Drawdown
87.700%
Expectancy
-0.313
Net Profit
-87.106%
Sharpe Ratio
-2.54
Probabilistic Sharpe Ratio
0.000%
Loss Rate
58%
Win Rate
42%
Profit-Loss Ratio
0.65
Alpha
-0.794
Beta
1.066
Annual Standard Deviation
0.339
Annual Variance
0.115
Information Ratio
-3.298
Tracking Error
0.242
Treynor Ratio
-0.808
Total Fees
$79624.36
Estimated Strategy Capacity
$560000.00
Lowest Capacity Asset
AAPL R735QTJ8XC9X
dev_mode = False # for Shile's use, keep False
if dev_mode:
    from AlgorithmImports import *
resolution = Resolution.Hour

# ---indicies---
market = 'SPY'
silver = 'SLV'
gold = 'GLD'
utility = 'XLU'
industrial = 'XLI'
safe = 'FXF'  # safe currency 
risk = 'FXA'  # risk currency
debt_short = 'SHY'
debt_inflation = 'TIP'
metal = 'DBB'
inp = 'IGE' # input
cash = 'UUP'

# ---equities CHANGEABLE
equities = ['SPY', 'QQQ', 'AAPL']

# ---safeties CHANGEABLE
safeties = ['GLD', 'FXF']

# ---in and out parameters
# parameters found from file from you
bull = True # set False for bear
inOutLookbackBull = 252
inOutLookbackBear = 126

waitDaysConstant = 80 # WAITD_CONSTANT from your file
iniWaitDays = 15 # INI_WAIT_DAYS from your file
minWaitDays = 60 # 60 from the `min(60, self.WDadjvar)` from your file

# ---supertrend parameters CHANGEABLE
superTrendPeriod = 10
superTrendMultiple = 3

# ---squeeze parameters CHANGEABLE
squeezeTrendPeriod = 20
squeezeBBMultiple = 2 # BollingerBands
squeezeKeltMultiple = 1.5 # Kelter Channel

# ---portfolio parameters
# for the returns based portfolio allocation
max_drawdown = .1 # max drawdown allowed before liquidation is signaled
max_alloc = .4 # max allocation to any given stock
returns_lookback = 100 # lookback for returns/drawdown calculations 

#from configs import *
if dev_mode:
    from AlgorithmImports import *
    
from collections import deque
from datetime import datetime 

class AdaptableRedSnake(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2000, 1, 11)
        self.SetCash(100000) 
        
        self.supertrends = {}
        self.squeezes = {}

        for equity in equities:
            sym = self.AddEquity(equity, resolution).Symbol

            superTrend = MySuperTrend(self, superTrendPeriod, superTrendMultiple)
            self.RegisterIndicator(sym, superTrend, resolution)
            self.supertrends[sym] = superTrend

            squeeze = Squeeze(squeezeTrendPeriod, bollinger_multiple=squeezeBBMultiple,  kelt_multiple=squeezeKeltMultiple)
            self.RegisterIndicator(sym, squeeze, resolution)
            self.squeezes[sym] = squeeze

            self.SetWarmUp(20)
        
        self.safeties = [self.AddEquity(symbol, resolution) for symbol in safeties]

    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 self.IsWarmingUp:
            return

        for symbol in self.supertrends:
            if not data.Bars.ContainsKey(symbol):
                return
            if self.supertrends[symbol].Value < data[symbol].Close and self.squeezes[symbol]:
                self.SetHoldings(symbol, 1 / len(self.supertrends)) 
            else:
                self.Liquidate(symbol)
                for safety in self.safeties:
                    self.SetHoldings(symbol, 1 / len(self.supertrends) / len(self.safeties)) 
           
class MySuperTrend:
    def __init__(self, algorithm, period, multiple, movingAverageType=MovingAverageType.Simple):
        
        self.Name = "Custom Indicator"
        self.Time = datetime.min
        self.Value = 0
        
        self.multiplier = multiple
        self.atr = AverageTrueRange(period, movingAverageType)
        self.values = deque(maxlen=period)

        self.previousTrailingLowerBand = 0
        self.previousTrailingUpperBand = 0
        self.previousClose = 0
        self.previousTrend = 0

        
    def __repr__(self):
        return "{0} -> IsReady: {1}. Time: {2}. Value: {3}".format(self.Name, self.IsReady, self.Time, self.Value)

    def Update(self, input:TradeBar):
        self.Time = input.EndTime
        self.atr.Update(input)
        superTrend = 0

        currentClose = input.Close
        currentBasicLowerBand = (input.Low + input.High) / 2 - self.multiplier * self.atr.Current.Value
        currentBasicUpperBand = (input.Low + input.High) / 2 + self.multiplier * self.atr.Current.Value

        if self.previousClose > self.previousTrailingLowerBand:
            currentTrailingLowerBand = max(currentBasicLowerBand, self.previousTrailingLowerBand)
        else:
            currentTrailingLowerBand = currentBasicLowerBand

        if self.previousClose < self.previousTrailingUpperBand:
            currentTrailingUpperBand = min(currentBasicUpperBand, self.previousTrailingUpperBand)
        else:
            currentTrailingUpperBand = currentBasicUpperBand

        if currentClose > currentTrailingUpperBand:
            currentTrend = 1
        elif currentClose < currentTrailingLowerBand:
            currentTrend = -1
        else:
            currentTrend = self.previousTrend

        if currentTrend == 1:
            superTrend = currentTrailingLowerBand
        elif currentTrend == -1:
            superTrend = currentTrailingUpperBand
        
        self.previousTrailingLowerBand = currentTrailingLowerBand
        self.previousTrailingUpperBand = currentTrailingUpperBand
        self.previousClose = currentClose
        self.previousTrend = currentTrend

        if not self.atr.IsReady:
            return 0
        
        self.Value = superTrend
        return self.IsReady
    
    @property
    def IsReady(self):
        return self.atr.IsReady and self.Value != 0

class Squeeze:
    '''
    .Value = 1 iff "squeezed" else .Value = 0
    
    Tells us if we are in or out of squeeze

    Is Squeeze: lower BB > lower Keltner and upper BB < upper Keltner
    '''

    def __init__(self, period, bollinger_multiple=2, kelt_multiple=1.5, movingAverageType=MovingAverageType.Simple):
        '''
        .Value = 1 iff "squeezed" else .Value = 0
        '''
        self.Name = "SuperTrend"
        self.Time = datetime.min
        self.Value = 0
        
        self.bb = BollingerBands(period, bollinger_multiple, movingAverageType)
        self.kelt = KeltnerChannels(period, kelt_multiple, movingAverageType)

    def __repr__(self):
        return "{0} -> IsReady: {1}. Time: {2}. Value: {3}".format(self.Name, self.IsReady, self.Time, self.Value)

    def Update(self, input:TradeBar):
        self.Time = input.EndTime
        self.kelt.Update(input)
        self.bb.Update(input.EndTime, input.Close)

        isSqueeze = self.bb.LowerBand.Current.Value > self.kelt.LowerBand.Current.Value and self.bb.UpperBand.Current.Value <  self.kelt.UpperBand.Current.Value
        self.Value = int(isSqueeze)
        return self.IsReady 

    @property
    def IsReady(self):
        return self.kelt.IsReady and self.bb.IsReady