Overall Statistics
Total Trades
73
Average Win
0.99%
Average Loss
-0.49%
Compounding Annual Return
22.640%
Drawdown
3.300%
Expectancy
0.678
Net Profit
13.208%
Sharpe Ratio
1.825
Probabilistic Sharpe Ratio
94.953%
Loss Rate
44%
Win Rate
56%
Profit-Loss Ratio
2.02
Alpha
0.075
Beta
0.33
Annual Standard Deviation
0.055
Annual Variance
0.003
Information Ratio
0.275
Tracking Error
0.079
Treynor Ratio
0.305
Total Fees
$0.00
Estimated Strategy Capacity
$40000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
Portfolio Turnover
30.51%
from System import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Indicators import *

import tweepy, statistics
from datetime import datetime, timedelta, date
import numpy as np
from scipy import stats
from AlgorithmImports import *
from sklearn.linear_model import LinearRegression, LogisticRegression
from sklearn.tree import ExtraTreeRegressor, ExtraTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
from sklearn import preprocessing

class DualMomentumWithOutDaysAlphaModel(AlphaModel):

    def __init__(self, algorithm, VOLA = 126, BASE_RET = 83, resolution = Resolution.Daily, *args, **kwargs):
        super().__init__()
        self.VOLA = VOLA
        self.BASE_RET = BASE_RET
        self.resolution = Resolution.Daily # resolution
        self.MKT = algorithm.AddEquity('SPY', resolution).Symbol
        self.SLV = algorithm.AddEquity('SLV', resolution).Symbol
        self.GLD = algorithm.AddEquity('GLD', resolution).Symbol
        self.XLI = algorithm.AddEquity('XLI', resolution).Symbol
        self.XLU = algorithm.AddEquity('XLU', resolution).Symbol
        self.DBB = algorithm.AddEquity('DBB', resolution).Symbol
        self.UUP = algorithm.AddEquity('UUP', resolution).Symbol

        self.count = self.BASE_RET
        self.outday = 5

        pairs = [self.MKT, self.SLV, self.GLD, self.XLI, self.XLU, self.DBB, self.UUP]
        for symbol in pairs:
            self.consolidator = TradeBarConsolidator(timedelta(days=1))
            self.consolidator.DataConsolidated += self.consolidation_handler
            algorithm.SubscriptionManager.AddConsolidator(symbol, self.consolidator)

        self.history = np.log(algorithm.History(pairs, self.VOLA + 1, self.resolution))
        #self.history = self.history['close'].unstack(level=0).dropna()

        self.predictionInterval = Time.Multiply(Extensions.ToTimeSpan(self.resolution), 1)
        resolutionString = Extensions.GetEnumString(resolution, Resolution)
        self.Name = f"{self.__class__.__name__}({resolutionString})"

        # Force alpha to only produce insights Daily at 11.10am
        self.set_flag = False
        algorithm.Schedule.On(algorithm.DateRules.EveryDay(),
                              algorithm.TimeRules.AfterMarketOpen('SPY', 100),
                              self.SetFlag)
                              
    def SetFlag(self):
        self.set_flag = True

    def consolidation_handler(self, sender, consolidated):
        self.history.loc[consolidated.EndTime, consolidated.Symbol] = consolidated.Close
        self.history = self.history.iloc[-(self.VOLA + 1):]
        
    def Update(self, algorithm, _data):
        if algorithm.IsWarmingUp or not self.set_flag:
            return []

        self.set_flag = False
        insights = []
        
        # Volatility
        vola = self.history[self.MKT].pct_change().std() * np.sqrt(252)
        wait_days = int(vola * self.BASE_RET)
        period = int((1.0 - vola) * self.BASE_RET)
        r = self.history.pct_change(period).iloc[-1]

        exit_market = r[self.SLV] < r[self.GLD] and r[self.XLI] < r[self.XLU] and r[self.DBB] < r[self.UUP]

        direction = InsightDirection.Down
            
        if (exit_market):
            #algorithm.Plot("In vs Out", "Market", -1)
            direction = InsightDirection.Down
            self.outday = self.count
        elif (self.count >= wait_days + self.outday):
                #algorithm.Plot("In vs Out", "Market", 1)
                direction = InsightDirection.Up
        else:
            direciton = InsightDirection.Flat
        self.count += 1
                                                                    
        insights.append(Insight.Price(self.MKT, self.predictionInterval, direction))

        return insights
#region imports
from AlgorithmImports import *
#endregion
import math 

class SupportResistance:
    
    # Get the S&R's for the last 2 weeks.
    def __init__(self, algorithm, ticker):
        self.Ticker = ticker
        self.Algorithm = algorithm
        self.Daily = RollingWindow[TradeBar](7);
        self.Min    = algorithm.MIN(self.Ticker, 390, Resolution.Minute)     # Range of today; breaking out of new highs/lows
        self.Max    = algorithm.MAX(self.Ticker, 390, Resolution.Minute)
        algorithm.Consolidate(self.Ticker, Resolution.Daily, self.SaveDailyBars)
        
    def NextSupport(self):
        
        support = []
        
        price = self.Algorithm.Securities[self.Ticker].Price
        
        # Rounded Price to $1
        support.append( round(price) )
        
        # Rounded Price to $10
        support.append( int(math.floor(price / 10.0)) * 10 )
        
        # Yesterday's OHLC Price
        support.append( self.Daily[0].Close )
        support.append( self.Daily[0].Low )
        
        # Append 7 day Low:
        support.append( min([bar.Low for bar in self.Daily]) )
        
        support = sorted(support, reverse=True)
        return support[0]
        
        
    def NextResistance(self):
        
        resistance = []
        price = self.Algorithm.Securities[self.Ticker].Price
        
        # Round Price Up to $1
        resistance.append( math.ceil(price) )
        
        # Rounded Price Up to $10
        resistance.append( int(math.ceil(price / 10.0)) * 10 )
            
        # Yesterday's Close, High
        resistance.append( self.Daily[0].Close )
        resistance.append( self.Daily[0].High )
        
        # Append 7 Day High
        resistance.append( max([bar.High for bar in self.Daily]) )
        
        # Take the lowest value on the list
        resistance = sorted(resistance)
        return resistance[0]
    
    # Build a 14 day historical rolling window of underlying prices.
    def SaveDailyBars(self, bar):
        self.Daily.Add(bar)
        
    # Reset any "daily" indicators
    def Reset(self):
        self.Min.Reset()
        self.Max.Reset()
#region imports
from AlgorithmImports import *
#endregion
from trade import *
from levels import *

from dual_momentum_with_out_days_alpha import DualMomentumWithOutDaysAlphaModel
from symbol_data import SymbolData
from ml_model import ML_Model

class OptionsOvernightContrarian(QCAlgorithm):

    def Initialize(self):
        
        # Settings
        self.SetStartDate(2023, 5, 1)
        #self.SetEndDate(2020, 6, 1)
        self.SetCash(10000)
        self.SetWarmup(timedelta(10))
        self.EnableAutomaticIndicatorWarmUp = True 
        self.orderDate = None
        
        # Apply Robinhood Fees
        self.SetSecurityInitializer(lambda security: security.SetFeeModel(ConstantFeeModel(0)))
        
        # Select Underlying and Configure
        self.ticker = "SPY" # ES	CME	Futures.Indices.SP500EMini
        self.equity = self.AddEquity(self.ticker, Resolution.Minute, extendedMarketHours=True)
        self.equity.SetDataNormalizationMode(DataNormalizationMode.Raw)

        self.Data = dict()
        self.Data_ = dict()
        self.DataT = dict()
        self.DataHour = dict()
        self.DataDaily = dict()
        # initialize our equity data
        BarPeriod = timedelta(minutes=5)
        BarPeriod_ = timedelta(minutes=15)
        TTPeriod = timedelta(minutes=30)
        HourPeriod = timedelta(minutes=60)
        DailyPeriod = timedelta(minutes=60*24)
        RollingWindowSize = 5
        for symbol in [self.ticker]:
            equity = self.AddEquity(symbol, Resolution.Minute)
            equity.SetDataNormalizationMode(DataNormalizationMode.Raw)
            self.Data[symbol] = SymbolData(equity, BarPeriod, RollingWindowSize * (390/5))
            self.Data_[symbol] = SymbolData(equity, BarPeriod_, RollingWindowSize * (390/15))
            self.DataT[symbol] = SymbolData(equity, TTPeriod, RollingWindowSize * (390/10))
            self.DataHour[symbol] = SymbolData(equity, HourPeriod, RollingWindowSize)
            self.DataDaily[symbol] = SymbolData(equity, HourPeriod, RollingWindowSize*5)

        for symbol, symbolData in self.Data.items():
            consolidator = TradeBarConsolidator(BarPeriod)
            consolidator.DataConsolidated += self.OnDataConsolidated
            self.SubscriptionManager.AddConsolidator(symbol, consolidator)
            quoteconsolidator = QuoteBarConsolidator(BarPeriod)
            quoteconsolidator.DataConsolidated += self.OnQuoteDataConsolidated
            self.SubscriptionManager.AddConsolidator(symbol, quoteconsolidator)

        for symbol, symbolData in self.Data_.items():
            consolidator = TradeBarConsolidator(BarPeriod_)
            consolidator.DataConsolidated += self.OnDataConsolidated
            self.SubscriptionManager.AddConsolidator(symbol, consolidator)
            quoteconsolidator = QuoteBarConsolidator(BarPeriod_)
            quoteconsolidator.DataConsolidated += self.OnQuoteDataConsolidated
            self.SubscriptionManager.AddConsolidator(symbol, quoteconsolidator)

        for symbol, symbolData in self.DataT.items():
            consolidator = TradeBarConsolidator(TTPeriod)
            consolidator.DataConsolidated += self.OnDataConsolidated
            self.SubscriptionManager.AddConsolidator(symbol, consolidator)
            quoteconsolidator = QuoteBarConsolidator(TTPeriod)
            quoteconsolidator.DataConsolidated += self.OnQuoteDataConsolidated
            self.SubscriptionManager.AddConsolidator(symbol, quoteconsolidator)

        for symbol, symbolData in self.DataHour.items():
            consolidator = TradeBarConsolidator(HourPeriod)
            consolidator.DataConsolidated += self.OnDataConsolidated
            self.SubscriptionManager.AddConsolidator(symbol, consolidator)
            quoteconsolidator = QuoteBarConsolidator(HourPeriod)
            quoteconsolidator.DataConsolidated += self.OnQuoteDataConsolidated
            self.SubscriptionManager.AddConsolidator(symbol, quoteconsolidator)


        for symbol, symbolData in self.DataDaily.items():
            consolidator = TradeBarConsolidator(DailyPeriod)
            consolidator.DataConsolidated += self.OnDataConsolidated
            self.SubscriptionManager.AddConsolidator(symbol, consolidator)
            quoteconsolidator = QuoteBarConsolidator(DailyPeriod)
            quoteconsolidator.DataConsolidated += self.OnQuoteDataConsolidated
            self.SubscriptionManager.AddConsolidator(symbol, quoteconsolidator)


        # Select Target Asset to Trade
        self.option = self.AddOption(self.ticker)
        #self.option.SetFilter(self.OptionFilterUniverse)
        self.option.SetFilter(-3, 10, 0, 1)

        # # Set TrainingMethod to be executed immediately
        self.Train(self.TrainingMethod)

        # Set TrainingMethod to be executed at 8:00 am every Sunday
        self.Train(self.DateRules.EveryDay(self.ticker), self.TimeRules.At(9 , 0), self.TrainingMethod)

        # Exit before any assignments
        self.Schedule.On(self.DateRules.EveryDay(self.ticker), self.TimeRules.AfterMarketOpen(self.ticker, 1), self.MarketOpen)
        self.Schedule.On(self.DateRules.EveryDay(self.ticker), self.TimeRules.BeforeMarketClose(self.ticker, 5), self.BeforeMarketClose)
        self.Schedule.On(self.DateRules.EveryDay(self.ticker), self.TimeRules.BeforeMarketClose(self.ticker, -1), self.MarketClose)

        # Trade Tracker
        self.Trade = TradeManagement(self, self.ticker, self.option.Symbol)
        
        # Support Resistance Detection:
        self.SupportResistance = SupportResistance(self, self.ticker)
        self.mean = self.EMA(self.ticker, 60, Resolution.Minute)

    def MarketClose(self):
        self.SupportResistance.Reset()
        
    def CloseTo(self, x, y, delta):
        return abs(x-y) < delta

    def OnData(self, data):
        
        # Ignore dividends, splits etc
        if not self.equity.Exchange.ExchangeOpen or self.IsWarmingUp: 
            return 

        # Manage existing positions:
        self.Trade.ManageOpenPositions()
        
        price = self.equity.Price
        support = self.SupportResistance.NextSupport()
        resistance = self.SupportResistance.NextResistance()
        mean = self.mean.Current.Value

        #self.Plot("SPY", "Minute", self.equity.Price)
        #self.Plot("Volume", "Minute", self.equity.Volume)
        # self.Plot('SPY', 'Support', support)
        # self.Plot('SPY', 'Resistance', resistance)

        # for symbol in self.DataHour.keys():
        #     symbolData = self.DataHour[symbol]
        #     if symbolData.IsReady() and symbolData.WasJustUpdated(self.Time):
        #         bars = symbolData.Bars
        #         curr = bars[0]
        #         prev = bars[1]
        #         # self.Plot('SPY', 'Current', float(curr.Close))
        #         # self.Plot('SPY', 'Previous', float(prev.Close))

        #         self.model = ML_Model(self, self.Data, symbol, timeframe = 5)
        #         price_obs, price_pred, duration_obs, duration_pred, direction_obs, direction_pred, p_val = self.model.BuildModel()

        #         if (direction_obs == 1) and (p_val < 0.025):
        #             if duration_obs > 1:
        #                 #self.SetHoldings(symbol, 0.95)
        #                 self.Long = True
        #             if duration_obs <= 1:
        #                 #self.SetHoldings(symbol, 0.0)
        #                 self.Long = False
        #         if (direction_obs == 0) and (p_val < 0.025):
        #             #self.SetHoldings(symbol, 0.0)
        #             self.Long = False


        for symbol in self.Data_.keys():
            symbolData = self.Data_[symbol]
            if symbolData.IsReady() and symbolData.WasJustUpdated(self.Time):
                bars = symbolData.Bars
                curr = bars[0]
                prev = bars[1]
                # self.Plot('SPY', 'Current', float(curr.Close))
                # self.Plot('SPY', 'Previous', float(prev.Close))

                self.model = ML_Model(self, self.Data_, symbol, timeframe = 15)
                price_obs, price_pred, duration_obs, duration_pred, direction_obs, direction_pred, p_val = self.model.BuildModel()
                self.Plot("SPY", 'Actual_15', float(price_obs))
                self.Plot("SPY", 'Pred_15', float(price_pred))
                self.Plot("SPY_Duration", 'Actual_15', float(duration_obs))
                self.Plot("SPY_Duration", 'Pred_15', float(duration_pred))
                self.Plot("SPY_Direction", 'Actual_15', float(direction_obs))
                self.Plot("SPY_Direction", 'Pred_15', float(direction_pred))
                self.Plot("PVal", 'p-val', float(p_val))


                # chain = data.OptionChains.get(option.Symbol)
                # if not chain:
                #     return

                # # Find ATM call with the farthest expiry
                # expiry = max([x.Expiry for x in chain])
                # call_contracts = sorted([x for x in chain
                #     if x.Right == OptionRight.Call and x.Expiry == expiry],
                #     key=lambda x: abs(chain.Underlying.Price - x.Strike))

                # if not call_contracts:
                #     return

                # atm_call = call_contracts[0]

                # naked_call = OptionStrategies.NakedCall(symbol, atm_call.Strike, expiry)

                if (direction_obs == 2) and (p_val < 0.025):
                    if duration_obs > 1:
                        self.SetHoldings(symbol, 0.95)
                    if duration_obs <= 1:
                        self.SetHoldings(symbol, 0.0)
                if (direction_obs == 1) and (p_val < 0.025):
                    self.SetHoldings(symbol, 0.0)

        # for symbol in self.DataHour.keys():
        #     symbolData = self.DataHour[symbol]
        #     if symbolData.IsReady() and symbolData.WasJustUpdated(self.Time):
        #         bars = symbolData.Bars
        #         curr = bars[0]
        #         prev = bars[1]
        #         # self.Plot('SPY', 'Current_H', float(curr.Close))
        #         # self.Plot('SPY', 'Previous_H', float(prev.Close))

        #         self.model = ML_Model(self, self.DataHour, symbol)
        #         data, y_pred = self.model.BuildModel()
        #         self.Plot("SPY", 'Actual_H', float(data))
        #         self.Plot("SPY", 'Pred_H', float(y_pred))
        
                # if self.CloseTo(price, support, 0.15) and (curr.Open > prev.Close):#(mean > support):
                #     t = self.Trade.Create(OrderDirection.Buy)
                #     self.Log(f"{self.Time} LONG: Price {price} Support {support}")
                #     #self.Plot('SPY', 'Buy', price)
                #     #self.Plot('SPY', 'Support', support)
                    
                # if self.CloseTo(price, resistance, 0.15) and (curr.Open < prev.Close): #(mean < resistance):
                #     t = self.Trade.Create(OrderDirection.Sell)
                #     self.Log(f"{self.Time} SHORT: Price {price} Resistance {resistance}")
                    #self.Plot('SPY', 'Sell', price)
                    #self.Plot('SPY', 'Resistance', resistance)

    def TrainingMethod(self):

        self.Log(f'Start training at {self.Time}')
        # Use the historical data to train the machine learning model
        history = self.History(self.ticker, 200, Resolution.Minute)

        # ML code:
        pass

    def OnDataConsolidated(self, sender, bar):
        self.Data[bar.Symbol.Value].Bars.Add(bar)
        self.Data_[bar.Symbol.Value].Bars.Add(bar)
        self.DataT[bar.Symbol.Value].Bars.Add(bar)
        self.DataHour[bar.Symbol.Value].Bars.Add(bar)
        self.DataDaily[bar.Symbol.Value].Bars.Add(bar)

    def OnQuoteDataConsolidated(self, sender, quotebar: QuoteBar):
        self.Data[quotebar.Symbol.Value].QuoteBars.Add(quotebar)
        self.Data_[quotebar.Symbol.Value].QuoteBars.Add(quotebar)
        self.DataT[quotebar.Symbol.Value].QuoteBars.Add(quotebar)
        self.DataHour[quotebar.Symbol.Value].QuoteBars.Add(quotebar)      
        self.DataDaily[quotebar.Symbol.Value].QuoteBars.Add(quotebar)  
    # def UpdateVolumeProfileWindow(self, sender: object, updated: IndicatorDataPoint) -> None:
    #     indicator = sender
    #     self.Data[updated.Symbol.Value].MarketProfile['time'].Add(updated.EndTime)
    #     self.Data[updated.Symbol.Value].MarketProfile["vp"].Add(updated.Value)
    #     self.Data[updated.Symbol.Value].MarketProfile["vp_profilehigh"].Add(indicator.ProfileHigh)
    #     self.Data[updated.Symbol.Value].MarketProfile["vp_profilelow"].Add(indicator.ProfileLow)
    #     self.Data[updated.Symbol.Value].MarketProfile["vp_pocprice"].Add(indicator.POCPrice)
    #     self.Data[updated.Symbol.Value].MarketProfile["vp_pocvolume"].Add(indicator.POCVolume)
    #     self.Data[updated.Symbol.Value].MarketProfile["vp_valueareavolume"].Add(indicator.ValueAreaVolume)
    #     self.Data[updated.Symbol.Value].MarketProfile["vp_valueareahigh"].Add(indicator.ValueAreaHigh)
    #     self.Data[updated.Symbol.Value].MarketProfile["vp_valuearealow"].Add(indicator.ValueAreaLow)

#################################################################################################
# SCRATCH #######################################################################################
#################################################################################################

    def MarketOpen(self): 
        pass
    
    def BeforeMarketClose(self):
        pass

    def OptionFilterUniverse(self, universe):
        # Select puts 2-3 strikes OOM, expiring at least 2 days out; but no more than 10
        return universe.IncludeWeeklys().Strikes(-3, 3).Expiration(2, 10)
        
    def StrikeVisualization(self, contracts):
        strikes = f"{self.Time} - {self.equity.Price} :: [["
        for x in self.contracts:
            strikes = strikes + f"{x.Strike}-{x.Expiry} ]] [["
        self.Log(strikes)
        self.Log(f"{self.Time} - Contract: {self.contracts[0]} | Strike: {self.contracts[0].Strike} | Underlying: {self.equity.Price} | Delta: {self.equity.Price - self.contracts[0].Strike}")
#region imports
from AlgorithmImports import *
#endregion


# Your New Python File
import math
import tweepy, statistics, json
from datetime import datetime, timedelta, date
import numpy as np
from scipy import stats
from AlgorithmImports import *
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LinearRegression, LogisticRegression
from sklearn.tree import ExtraTreeRegressor, ExtraTreeClassifier
from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
from sklearn.model_selection import train_test_split, GridSearchCV, StratifiedKFold, KFold
from sklearn.feature_selection import SelectKBest, f_regression, f_classif
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error, r2_score, explained_variance_score, accuracy_score
from sklearn import preprocessing
from sklearn.preprocessing import MinMaxScaler, StandardScaler, LabelEncoder
import statistics
import ta
from scipy.stats import ttest_rel, ttest_ind, ttest_1samp
import pickle 

import inspect


class ML_Model:
    
    def __init__(self, algorithm, _dict, ticker, timeframe):
        self.Ticker = ticker
        self.Algorithm = algorithm
        self.DataBars = _dict[self.Ticker]
        self.timeframe = str(timeframe) + '-' + 'minute' + '_'
        self.regressor_model1 = pickle.loads(bytes(self.Algorithm.ObjectStore.ReadBytes(self.timeframe + 'Price_Change')))
        self.regressor_model2 = pickle.loads(bytes(self.Algorithm.ObjectStore.ReadBytes(self.timeframe + 'Price_Movement_Duration')))
        self.classifier_model = pickle.loads(bytes(self.Algorithm.ObjectStore.ReadBytes(self.timeframe + 'Price_Direction')))
        # self.regressor1 = RandomForestRegressor(random_state = 42)
        # self.regressor2 = RandomForestRegressor(random_state = 42)
        # self.classifier = RandomForestClassifier(random_state = 42)
        # self.pipeline_reg = Pipeline([
        #         ('selector', SelectKBest(f_regression)),
        #         ('regressor', self.regressor)
        #     ])
        # self.pipeline_clf = Pipeline([
        #         ('selector', SelectKBest(f_classif)),
        #         ('classifier', classifier)
        #     ])
        # self.hyperparameters = {
        #     'selector__k': [2, 4, 6, 8, 'all'],
        #     'regressor__n_estimators' : [10, 50, 150, 200],
        #     'regressor__max_depth': [None, 5, 10, 15],
        #     'regressor__min_samples_split': [2, 5, 25]
        # }
        # self.cv = KFold(n_splits=10, shuffle = True, random_state = 42)
        # self.regression_grid_search = GridSearchCV(self.pipeline_reg, self.hyperparameters, scoring = 'neg_mean_squared_error', cv = self.cv)
        # self.classifier_grid_search = GridSearchCV(self.pipeline_clf, self.hyperparameters, scoring = 'accuracy', cv = self.cv)

        self.best_model = None

    def BuildModel(self):
        if self.DataBars.IsReady():
            bars = self.DataBars.Bars
            quotebars = self.DataBars.QuoteBars

            # TradeBar
            _time = np.flip(np.array([bar.Time for bar in bars])).reshape(-1,1).flatten()
            _open = np.flip(np.array([bar.Open for bar in bars])).reshape(-1,1).flatten()
            _high = np.flip(np.array([bar.High for bar in bars])).reshape(-1,1).flatten()
            _low = np.flip(np.array([bar.Low for bar in bars])).reshape(-1,1).flatten()
            _close = np.flip(np.array([bar.Close for bar in bars])).reshape(-1,1).flatten()
            _volume = np.flip(np.array([bar.Volume for bar in bars])).reshape(-1,1).flatten()

            #_close = pd.DataFrame(_close, columns = ['close'])

            df = pd.DataFrame({'time': _time,
                                'open': _open,
                                'high': _high,
                                'low': _low,
                                'close': _close,
                                'volume': _volume
            })

            df = compute_indicators(df)

            df = add_features(df)


            # close_above_previous_close = _close.close > _close.close.shift()
            # close_above_current_open = _close > _open
            # close_pct_change = _close.close.pct_change()

            # def multi_condition(x):
            #     if all(((i is True) or (i > 0)) for i in x):
            #         return 'Up'
            #     elif all(((i is False) or (i < 0)) for i in x):
            #         return 'Down'
            #     else:
            #         return 'Chop'
            
            # features = close_above_previous_close + close_above_current_open + close_pct_change
            # features = pd.DataFrame(features)
            # features['Price_Movement'] = features.apply(lambda x: multi_condition(x), axis = 1)
            
            # QuoteBar
            # q_askopen = np.flip(np.array([bar.Ask.Open for bar in quotebars])).reshape(-1,1)
            # q_askhigh = np.flip(np.array([bar.Ask.High for bar in quotebars])).reshape(-1,1)
            # q_asklow = np.flip(np.array([bar.Ask.Low for bar in quotebars])).reshape(-1,1)
            # q_asksize = np.flip(np.array([bar.LastAskSize for bar in quotebars])).reshape(-1,1)

            # q_bidopen = np.flip(np.array([bar.Bid.Open for bar in quotebars])).reshape(-1,1)
            # q_bidhigh = np.flip(np.array([bar.Bid.High for bar in quotebars])).reshape(-1,1)
            # q_bidlow = np.flip(np.array([bar.Bid.Low for bar in quotebars])).reshape(-1,1)
            # q_bidsize = np.flip(np.array([bar.LastBidSize for bar in quotebars])).reshape(-1,1)

            # X = _open + _high + _low + q_askopen + q_askhigh + q_asklow + q_bidopen + q_bidhigh + q_bidlow
            # y = np.flip(np.array([bar.Close for bar in bars])).reshape(-1,1)

            df = df.dropna()
            X = df.drop(['close', 'time', 'date', 'volume', 'Price_Direction'], axis=1)

            le = LabelEncoder()
            y_clf = le.fit_transform(df['Price_Direction'])
            #y_clf = (y_clf == 2).astype(int)
            y1 = df['close']
            y2 = df['Price_Movement_Duration']

            X_train, X_test, y1_train, y1_test, y2_train, y2_test = train_test_split(X, y1, y2, test_size = 0.2, random_state = 1990)
            X_train_clf, X_test_clf, y_train_clf, y_test_clf = train_test_split(X, y_clf, test_size = 0.2, random_state = 1990)

            self.regressor_model1.fit(X_train, y1_train) # self.regressor1.fit(X_train, y_train)
            self.regressor_model2.fit(X_train, y2_train) # self.regressor2.fit(X_train, y_train)
            self.classifier_model.fit(X_train, y_train_clf) # self.classifier.fit(X_train, y_train_clf)

            y_pred1 = self.regressor_model1.predict(X_test)
            y_pred2 = self.regressor_model2.predict(X_test)
            y_pred_clf = self.classifier_model.predict(X_test_clf)

            # self.Algorithm.Plot("Metrics", 'r2-price', r2_score(y1_test, y_pred1))
            # self.Algorithm.Plot("Metrics", 'r2-movement-duration', r2_score(y2_test, y_pred2))
            # self.Algorithm.Plot("Metrics", 'accuracy-direction', accuracy_score(y_test_clf, y_pred_clf))

            actual = str(le.inverse_transform(y_test_clf.reshape(-1,1)[-1])[0])
            pred = str(le.inverse_transform(y_pred_clf.reshape(-1,1)[-1])[0])

            # def movement_score(i):
            #     if i == 'Up':
            #         return 1.0
            #     elif i == 'Down':
            #         return -1.0
            #     else:
            #         return 0.0

            #self.Algorithm.Plot("Movement", 'Actual', float(movement_score(actual)))
            #self.Algorithm.Plot("Movement", 'Predicted', float(movement_score(pred)))
            # self.Algorithm.Plot("Error", 'Mean Absolute Error', metrics.mean_absolute_error(y_test, y_pred))
            # self.Algorithm.Plot("Error", 'Mean Squared Error', metrics.mean_squared_error(y_test, y_pred, squared=True))
            # self.Algorithm.Plot("Error", 'Root Mean Squared Error', np.sqrt(metrics.mean_squared_error(y_test, y_pred, squared=False)))

            # self.Algorithm.Plot("Differences", "Actual", round(float(y_test[-1] - y_test[-2]), 3))
            # self.Algorithm.Plot("Differences", "Predicted", round(float(y_pred[-1] - y_pred[-2]), 3))

            # self.Algorithm.Plot("SPY", 'Actual_15', float(y1_test.iloc[-1]))
            # self.Algorithm.Plot("SPY", 'Pred_15', float(y_pred1[-1]))
            # self.Algorithm.Plot("SPY_Duration", 'Actual_15', float(y2_test.iloc[-1]))
            # self.Algorithm.Plot("SPY_Duration", 'Pred_15', float(y_pred2[-1]))
            # self.Algorithm.Plot("SPY_Direction", 'Actual_15', float(y_test_clf[-1]))
            # self.Algorithm.Plot("SPY_Direction", 'Pred_15', float(y_pred_clf[-1]))
            return y1_test.iloc[-1], y_pred1[-1], y2_test.iloc[-1], y_pred2[-1], y_test_clf[-1], y_pred_clf[-1], X['close_pct_change_p_value'].iloc[-1]

def compute_indicators(df):
    indicator_kama = ta.momentum.KAMAIndicator(df.close, fillna=False)
    indicator_trix = ta.trend.TRIXIndicator(df.close, fillna=False)
    indicator_macd = ta.trend.MACD(df.close, fillna=False)
    indicator_bollinger = ta.volatility.BollingerBands(df.close, fillna=False)
    indicator_kelter = ta.volatility.KeltnerChannel(df.high, df.low, df.close, fillna=False)
    indicator_kst = ta.trend.KSTIndicator(df.close, fillna=False)
    indicator_aweosc = ta.momentum.AwesomeOscillatorIndicator(low=df.low, high=df.high, fillna=False)
    indicator_williams = ta.momentum.WilliamsRIndicator(close=df.close, low=df.low, high=df.high, fillna=False)
    indicator_ichimoku = ta.trend.IchimokuIndicator(low=df.low, high=df.high, fillna=False)
    indicator_truetrange = ta.volatility.AverageTrueRange(close=df.close, low=df.low, high=df.high, fillna=False)
    indicator_rsi = ta.momentum.RSIIndicator(close=df.close, fillna=False)
    indicator_vwap = ta.volume.VolumeWeightedAveragePrice(high=df.high, low=df.low, close=df.close, volume=df.volume, fillna=False)

    indicator_list = [indicator_kama,
                        indicator_trix,
                        indicator_macd,
                        indicator_bollinger,
                        indicator_kelter,
                        indicator_kst,
                        indicator_aweosc,
                        indicator_williams,
                        indicator_ichimoku,
                        indicator_truetrange,
                        indicator_rsi,
                        indicator_vwap]

    for indicator in indicator_list:
        for _subfunction in inspect.getmembers(indicator):
            if (str(type(_subfunction[1])) == "<class 'method'>") & (str(_subfunction[0]) not in ['__init__', '_check_fillna', '_run']):
                col_name = str(_subfunction[0])
                if col_name not in df.columns:
                    vals = _subfunction[1]().values
                    df[col_name] = vals
    
    return df

def compare_with_previous(x):
    if x > 0:
        return 'Up'
    elif x < 0:
        return 'Down'
    else:
        return 'Chop'

# Define a function to calculate the slope
def calculate_slope(series):
    x = np.array(series.index)
    y = series.values
    slope = (len(series) * np.sum(x * y) - np.sum(x) * np.sum(y)) / (len(series) * np.sum(x**2) - np.sum(x)**2)
    return slope

def calc_fib_levels(x, level):
    height = x.max() - x.min()
    fib_level = x.min() + (height * level)
    return fib_level

def add_features(df_x):
    df_x = df_x.sort_values('time', ascending = True)

    df_x['time'] = pd.to_datetime(df_x['time'])
    df_x['date'] = pd.to_datetime(df_x['time']).dt.date

    df_x['close_diff'] = df_x['close'].diff(periods=2)
    df_x['close_pct_change'] = df_x.close.pct_change() * 100

    df_x['close_pct_change_slope'] = df_x['close_pct_change'].rolling(window=2).apply(calculate_slope, raw=False)

    df_x['close_pct_change_p_value'] = df_x['close_pct_change'].rolling(window=20).apply(lambda x: ttest_1samp(x.iloc[:-1], x.iloc[-1])[-1])

    df_x['Price_Direction'] = df_x['close_pct_change'].apply(lambda x: compare_with_previous(x))#df_x['close'].combine(df_x['close'].shift(-1), compare_with_previous)

    for col in ['open', 'high', 'low']:
        df_x[col+'_Fib_Level_786'] = df_x[col].rolling(window = 20).apply(lambda x: calc_fib_levels(x, level = 0.786))
        df_x[col+'_Fib_Level_618'] = df_x[col].rolling(window = 20).apply(lambda x: calc_fib_levels(x, level = 0.618))
        df_x[col+'_Fib_Level_500'] = df_x[col].rolling(window = 20).apply(lambda x: calc_fib_levels(x, level = 0.5))
        df_x[col+'_Fib_Level_382'] = df_x[col].rolling(window = 20).apply(lambda x: calc_fib_levels(x, level = 0.382))
        df_x[col+'_Fib_Level_236'] = df_x[col].rolling(window = 20).apply(lambda x: calc_fib_levels(x, level = 0.236))

    curr = None
    count = 0

    count_list = list()

    for x, y in zip(df_x['Price_Direction'], df_x['close_pct_change']):

        y = abs(y)

        if curr is None:
            curr = x
        
        if (curr == x) & (y >= 0.00005):
            count += 1
        else:
            curr = x
            count = 0
            
        count_list.append(count)

    def reverse_sublists_with_values_greater_than_zero(input_list):
        start_idx = None
        for i in range(len(input_list)):
            if input_list[i] > 0 and start_idx is None:
                start_idx = i
            elif input_list[i] == 0 and start_idx is not None:
                input_list[start_idx:i] = input_list[start_idx:i][::-1]
                start_idx = None

        # Handle the case where the last sublist extends to the end
        if start_idx is not None:
            input_list[start_idx:] = input_list[start_idx:][::-1]

        return input_list

    df_x['Price_Movement_Duration'] = reverse_sublists_with_values_greater_than_zero(count_list)
    df_x['Price_Movement_Duration'] = df_x['Price_Movement_Duration'].astype(int)

    percentile_075 = df_x['close_pct_change'].quantile(0.075)
    percentile_925 = df_x['close_pct_change'].quantile(0.925)

    def mod_direction(row):

        if (row[0] >= percentile_075) and (row[0] <= percentile_925) and (row[1] <= 1):
            return 'Chop'
        else:
            return row[2]
        

    df_x['Price_Direction'] = df_x[['close_pct_change', 'Price_Movement_Duration', 'Price_Direction']].apply(lambda x: mod_direction(x), axis = 1)

    return df_x

def mod_duration(_df):

    # Iterate over rows using itertuples and compare current row with the next row
    for i, (current_row, prev_row) in enumerate(zip(_df[::-1].itertuples(index=False), _df[::-1].shift(-1).itertuples(index=False))):
        if i == 0:
            continue

        current_value, current_dur = getattr(current_row, 'Price_Direction'), getattr(current_row, 'Price_Movement_Duration')
        prev_value, prev_dur = getattr(prev_row, 'Price_Direction'), getattr(prev_row, 'Price_Movement_Duration')


        # if direction is the same and previous movement duration is 0, change previous movement duration to current movement duration to + 1
        if (current_value == prev_value) & (prev_dur == 0):
            new_value = current_dur + 1
            _df.at[_df.index[-(i+2)], 'Price_Movement_Duration'] = new_value
    
    return _df
#region imports
from AlgorithmImports import *
#endregion


# Your New Python File
class SymbolData(object):
    
    def __init__(self, symbol, barPeriod, windowSize):
        self.Symbol = symbol
        # The period used when population the Bars rolling window
        self.BarPeriod = barPeriod
        # A rolling window of data, data needs to be pumped into Bars by using Bars.Update( tradeBar ) and can be accessed like:
        # mySymbolData.Bars[0] - most first recent piece of data
        # mySymbolData.Bars[5] - the sixth most recent piece of data (zero based indexing)
        self.Bars = RollingWindow[IBaseDataBar](windowSize)
        self.QuoteBars = RollingWindow[QuoteBar](windowSize)

        self.MarketProfile = dict()
        self.MarketProfile['time'] = RollingWindow[DateTime](windowSize)
        self.MarketProfile["vp"] = RollingWindow[float](windowSize)
        self.MarketProfile["vp_profilehigh"] = RollingWindow[float](windowSize)
        self.MarketProfile["vp_profilelow"] = RollingWindow[float](windowSize)
        self.MarketProfile["vp_pocprice"] = RollingWindow[float](windowSize)
        self.MarketProfile["vp_pocvolume"] = RollingWindow[float](windowSize)
        self.MarketProfile["vp_valueareavolume"] = RollingWindow[float](windowSize)
        self.MarketProfile["vp_valueareahigh"] = RollingWindow[float](windowSize)
        self.MarketProfile["vp_valuearealow"] = RollingWindow[float](windowSize)
  
    # Returns true if all the data in this instance is ready (indicators, rolling windows, ect...)
    def IsReady(self):
        return self.Bars.IsReady

    # Returns true if the most recent trade bar time matches the current time minus the bar's period, this
    # indicates that update was just called on this instance
    def WasJustUpdated(self, current):
        return self.Bars.Count > 0 and self.Bars[0].Time == current - self.BarPeriod                                               
 
from AlgorithmImports import *

from System import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Indicators import *
from QuantConnect.Data.Market import TradeBar
from QuantConnect.Indicators.CandlestickPatterns import *

import inspect
import pandas as pd
import numpy as np
from scipy import stats
from scipy.signal import argrelextrema
import statistics
from operator import itemgetter
from functools import reduce

from FilterIndicators import *
from SmartRollingWindow import *
from pykalman import KalmanFilter

from levels import *
from market_profile import Market_Profile

class SymbolData(object):
    def __init__(self, algorithm, symbol, *args, **kwargs):
        super().__init__()
        self.Symbol = symbol
        self.resolution = Resolution.Daily
        self.lookback = 20
        self.ceiling = 30
        self.floor = 10
        self.hh_all = False
        self.ll_all = False
        self.hh_sum = False
        self.ll_sum = False
        self.breakout = False
        self.breakdown = False
        self.fir = 0.00
        self.EXCL = 21
        self.scale = 0.00
        self.tol = 0.98
        self.is_uptrend = False
        self.is_downtrend = False
        self.volatility = 0.00
        self.tolerance = 0.98
        self.vwap = IntradayVwap()
        self.vol_slope = 0.00
        self.roc_slope = 0.00
        self.median_roc = 0.00
        self.median_vol = 0.00
        # Support Resistance
        self.long = False
        self.short = False
        self.Daily = RollingWindow[TradeBar](21)
        ### Market Profile
        self.market_profile = Market_Profile()
        self.mp_uptrend = False
        self.mp_downtrend = False
        self.mp_entry_signal = False
        self.mp_exit_signal = False
        self.poc_Win =  RollingWindow[float](5)
        self.pocWin = SmartRollingWindow("float", 5)
        self.val_Win =  RollingWindow[float](5)
        self.valWin = SmartRollingWindow("float", 5)
        self.vah_Win =  RollingWindow[float](5)
        self.vahWin = SmartRollingWindow("float", 5)
        self.price_Win =  RollingWindow[float](5)
        self.priceWin = SmartRollingWindow("float", 5)
        self.medpri_Win =  RollingWindow[float](5)
        self.medpriWin = SmartRollingWindow("float", 5)
        self.vwap_fst = VolumeWeightedAveragePriceIndicator(50)
        self.vwap_med = VolumeWeightedAveragePriceIndicator(100)
        self.vwap_mlng = VolumeWeightedAveragePriceIndicator(150)
        self.vwap_lng = VolumeWeightedAveragePriceIndicator(200)
        self.vwap_fst_Win =  RollingWindow[float](5)
        self.vwap_fstWin = SmartRollingWindow("float", 5)
        self.vwap_med_Win =  RollingWindow[float](5)
        self.vwap_medWin = SmartRollingWindow("float", 5)
        self.vwap_lng_Win =  RollingWindow[float](5)
        self.vwap_lngWin = SmartRollingWindow("float", 5)
        self.dont_buy = False

        self.fast = RateOfChange(int(8*1.0))
        self.fast_Win = RollingWindow[float](41)
        self.slow = RateOfChange(int(14*1.0))
        self.slow_Win = RollingWindow[float](41)
        self.roc = RateOfChange(int(5*1.0))
        self.roc_fast = RateOfChange(int(3*1.0))
        self.roc_med = RateOfChange(int(8*1.0))
        self.roc_long = RateOfChange(int(14*1.0))
        self.vol_roc = RateOfChange(int(5*1.0))
        self.vol_roc_fast = RateOfChange(int(3*1.0))
        self.vol_roc_med = RateOfChange(int(8*1.0))
        self.vol_roc_long = RateOfChange(int(14*1.0))
        self.roc_Win = RollingWindow[float](5)
        self.roclen_Win = RollingWindow[float](int(5))
        self.rocSum_Win = RollingWindow[float](int(5))
        self.vol_Win = RollingWindow[float](5)
        self.prices_Win = RollingWindow[float](41)
        self.low_Win = RollingWindow[float](41)
        self.high_Win = RollingWindow[float](41)
        self.roc_prices_Win = RollingWindow[float](41)
        self.roc_prices_lev_Win = RollingWindow[float](10)
        self.roc_volume_Win = RollingWindow[float](41)
        self.stochasticMACD = Stochastic(30, 3, 3)
        self.macd = MovingAverageConvergenceDivergence(12, 26, 9, MovingAverageType.Exponential)
        self.macd_stochfast_Win = RollingWindow[float](8)
        self.macdStochFastWin = SmartRollingWindow("float", 5)
        self.macd_stochk_Win = RollingWindow[float](8)
        self.macdStochKWin = SmartRollingWindow("float", 5)
        self.macd_stochd_Win = RollingWindow[float](8)
        self.macdStochDWin = SmartRollingWindow("float", 5)      
        self.macd_Win = RollingWindow[float](8)
        self.macd_stoch_Win = RollingWindow[float](8)
        self.macdHist_Win = RollingWindow[float](8)
        self.macdFast_Win = RollingWindow[float](8)
        self.macdSlow_Win = RollingWindow[float](8)
        self.macdSignal_Win = RollingWindow[float](8)
        self.macdDelta_Win = RollingWindow[float](8)
        self.macd_uptrend = False
        self.macd_downtrend = False
        self.stochasticRSI = Stochastic(21, 3, 3)
        self.rsi = RelativeStrengthIndex(14, MovingAverageType.Wilders)
        self.rsi_Win = RollingWindow[float](8)
        self.rsiWin = SmartRollingWindow("float", 5)
        self.rsiFastStoch_Win = RollingWindow[float](8)
        self.rsiFastStochWin = SmartRollingWindow("float", 5)
        self.rsiStochD_Win = RollingWindow[float](5)
        self.rsiStochKWin = SmartRollingWindow("float", 5)
        self.rsiStochK_Win = RollingWindow[float](5)
        self.rsiStochDWin = SmartRollingWindow("float", 5)
        self.rsi_uptrend = False
        self.rsi_downtrend = False
        self.williamsPR = WilliamsPercentR(14)
        self.williamsPR_slow = WilliamsPercentR(21)
        self.williamsWindow = RollingWindow[float](5)
        self.williams_median_roc = 0.00
        self.williams_median = 0.00
        self.vpnIndicator = False
        self.vpnScale = 0.00
        self.vpn_period = 10
        self.atr = AverageTrueRange(self.vpn_period, MovingAverageType.Exponential)
        self.vpn_vol_Win = RollingWindow[float](self.vpn_period)
        self.vpn_hlc_Win = RollingWindow[float](self.vpn_period)
        self.vpn_list = RollingWindow[float](6)
        self.stochasticTrix = Stochastic(21, 3, 3)
        self.trix = Trix(9)
        self.trix_slow = Trix(18)
        self.trixFastStoch_Win = RollingWindow[float](int(5))
        self.trixStochFastWin = SmartRollingWindow("float", 5)
        self.trixStochK_Win = RollingWindow[float](int(5))
        self.trixStochKWin = SmartRollingWindow("float", 5)
        self.trixStochD_Win = RollingWindow[float](int(5))
        self.trixStochDWin = SmartRollingWindow("float", 5)
        self.trix_uptrend = False
        self.trix_downtrend = False
        self.rocSignal_quick = IndicatorExtensions.Over(self.roc_fast, self.roc_med)
        self.volSignal_quick = IndicatorExtensions.Over(self.vol_roc_fast, self.vol_roc_med)
        self.rocSignal_fast = IndicatorExtensions.Over(self.roc_fast, self.roc)
        self.volSignal_fast = IndicatorExtensions.Over(self.vol_roc_fast, self.vol_roc)
        self.rocSignal_med = IndicatorExtensions.Over(self.roc, self.roc_med)
        self.volSignal_med = IndicatorExtensions.Over(self.vol_roc, self.vol_roc_med)
        self.rocSignal_long = IndicatorExtensions.Over(self.roc_med, self.roc_long)
        self.volSignal_long = IndicatorExtensions.Over(self.vol_roc_med, self.vol_roc_long)
        self.rocvolSignal_Win = RollingWindow[float](5)
        self.rocvolSignal_median = 0.00
        self.roc_vol_signal_up = False
        self.roc_vol_signal_down = False
        self.kalFilter  = KalmanFilterIndicator(name='Kalman', period=5, selector=Field.Close)
        self.kalFilterLow  = KalmanFilterIndicator(name='Kalman', period=5, selector=Field.Low)
        self.kalFilterHigh  = KalmanFilterIndicator(name='Kalman', period=5, selector=Field.High)
        self.kalWindow    = SmartRollingWindow("float", 2)
        self.kalWindowLow    = SmartRollingWindow("float", 2)
        self.kalWindowHigh    = SmartRollingWindow("float", 2)
        self.priceWindow  = SmartRollingWindow("float", 2)
        self.priceWindowLow  = SmartRollingWindow("float", 2)
        self.priceWindowHigh  = SmartRollingWindow("float", 2)
        self.exit_signal = False
        self.entry_signal = False
        # Candles
        self.cndl_abandonedbaby = AbandonedBaby()
        self.cndl_advanceblock = AdvanceBlock()
        self.cndl_belthold = BeltHold()
        self.cndl_breakway = Breakaway()
        self.cndl_closingmarubozu = ClosingMarubozu()
        self.cndl_concealedbabyswallow = ConcealedBabySwallow()
        self.cndl_counterattack = Counterattack()
        self.cndl_darkcloudcover = DarkCloudCover()
        self.cndl_doji = Doji()
        self.cndl_dojistar = DojiStar()
        self.cndl_dragonflydoji = DragonflyDoji()
        self.cndl_engulfing = Engulfing()
        self.cndl_eveningdojistar = EveningDojiStar()
        self.cndl_eveningstar = EveningStar()
        self.cndl_gapsidebysidewhite = GapSideBySideWhite()
        self.cndl_gravestonedoji = GravestoneDoji()
        self.cndl_hammer = Hammer()
        self.cndl_hangingman = HangingMan()
        self.cndl_harami = Harami()
        self.cndl_haramicross = HaramiCross()
        self.cndl_highwavecandle = HighWaveCandle()
        self.cndl_hikkake = Hikkake()
        self.cndl_hikkakemodified = HikkakeModified()
        self.cndl_homingpigeon = HomingPigeon()
        self.cndl_identicalthreecrows = IdenticalThreeCrows()
        self.cndl_inneck = InNeck()
        self.cndl_invertedhammer = InvertedHammer()
        self.cndl_kicking = Kicking()
        self.cndl_kickingbylength = KickingByLength()
        self.cndl_ladderbottom = LadderBottom()
        self.cndl_longleggeddoji = LongLeggedDoji()
        self.cndl_longlinecandle = LongLineCandle()
        self.cndl_marubozu = Marubozu()
        self.cndl_mathold = MatHold()
        self.cndl_matchinglow = MatchingLow()
        self.cndl_morningdojistar = MorningDojiStar()
        self.cndl_morningstar = MorningStar()
        self.cndl_onneck = OnNeck()
        self.cndl_pierce = Piercing()
        self.cndl_rickshawman = RickshawMan()
        self.cndl_risefallthreemethods = RiseFallThreeMethods()
        self.cndl_separatinglines = SeparatingLines()
        self.cndl_shootingstar = ShootingStar()
        self.cndl_shortlinecandle = ShortLineCandle()
        self.cndl_spinningtop = SpinningTop()
        self.cndl_stalledpattern = StalledPattern()
        self.cndl_sticksandwich = StickSandwich()
        self.cndl_takuri = Takuri()
        self.cndl_tasukigap = TasukiGap()
        self.cndl_threeblackcrows = ThreeBlackCrows()
        self.cndl_threeinside = ThreeInside()
        self.cndl_threelinest = ThreeLineStrike()
        self.cndl_threeoutside = ThreeOutside()
        self.cndl_threestarsinsouth = ThreeStarsInSouth()
        self.cndl_threewhitesoldiers = ThreeWhiteSoldiers()
        self.cndl_thrusting = Thrusting()
        self.cndl_tristar = Tristar()
        self.cndl_twocrows = TwoCrows()
        self.cndl_uniquethreeriver = UniqueThreeRiver()
        self.cndl_updowngapthreemethods = UpDownGapThreeMethods()
        self.cndl_upsidegaptwocrows = UpsideGapTwoCrows()
        self.candleWin = SmartRollingWindow("float", 5)
        self.candleavgWindow  = SmartRollingWindow("float", 2)
        self.candleContainer = RollingWindow[float](2)
        self.cndl_uptrend = False
        self.cndl_downtrend = False
        self.candlescore = 0.00
        
        self.indicators = [self.roc, self.roc_fast, self.roc_med, self.roc_long, 
                            self.vol_roc, self.vol_roc_fast, self.vol_roc_med, self.vol_roc_long, 
                            self.vwap, self.fast, self.slow,
                            self.macd, self.stochasticMACD, self.rsi, self.stochasticRSI, self.williamsPR, self.williamsPR_slow, self.atr, 
                            self.stochasticTrix, self.trix, self.trix_slow, self.kalFilter, self.kalFilterLow, self.kalFilterHigh]
        self.candles = [self.cndl_abandonedbaby, self.cndl_advanceblock, self.cndl_belthold, self.cndl_breakway, self.cndl_closingmarubozu, 
                            self.cndl_concealedbabyswallow, self.cndl_counterattack, self.cndl_darkcloudcover, self.cndl_doji, self.cndl_dojistar, 
                            self.cndl_dragonflydoji, self.cndl_engulfing, self.cndl_eveningdojistar, self.cndl_eveningstar, self.cndl_gapsidebysidewhite,
                            self.cndl_gravestonedoji, self.cndl_hammer, self.cndl_hangingman, self.cndl_harami, self.cndl_haramicross, 
                            self.cndl_highwavecandle, self.cndl_hikkake, self.cndl_hikkakemodified, self.cndl_homingpigeon, self.cndl_identicalthreecrows,
                            self.cndl_inneck, self.cndl_invertedhammer, self.cndl_kicking, self.cndl_kickingbylength, self.cndl_ladderbottom, 
                            self.cndl_longleggeddoji, self.cndl_longlinecandle, self.cndl_marubozu, self.cndl_mathold, self.cndl_matchinglow, 
                            self.cndl_morningdojistar, self.cndl_morningstar, self.cndl_onneck, self.cndl_pierce, self.cndl_rickshawman, 
                            self.cndl_risefallthreemethods, self.cndl_separatinglines, self.cndl_shootingstar, self.cndl_shortlinecandle, 
                            self.cndl_spinningtop, self.cndl_stalledpattern, self.cndl_sticksandwich, self.cndl_takuri, self.cndl_tasukigap, 
                            self.cndl_threeblackcrows, self.cndl_threeinside, self.cndl_threelinest, self.cndl_threeoutside, self.cndl_threestarsinsouth, 
                            self.cndl_threewhitesoldiers, self.cndl_thrusting, self.cndl_tristar, self.cndl_twocrows, self.cndl_uniquethreeriver, 
                            self.cndl_updowngapthreemethods, self.cndl_upsidegaptwocrows]
        self.indicators = self.indicators + self.candles
        for indicator in self.indicators:
            consolidator = algorithm.ResolveConsolidator(symbol, self.resolution)
            algorithm.RegisterIndicator(symbol, indicator, consolidator)
        
        # Warm up
        history = algorithm.History(self.Symbol, 126, self.resolution)
        if history.empty or 'close' not in history.columns:
            return

        for tuple in history.loc[symbol].itertuples():
            tradeBar = TradeBar(tuple.Index, symbol, tuple.open, tuple.high, tuple.low, tuple.close, tuple.volume)
            median_price = round(float(statistics.median([tuple.open, tuple.high, tuple.low, tuple.close])), 4)
            self.roc.Update(tuple.Index, median_price)
            self.roc_fast.Update(tuple.Index,median_price)
            self.roc_med.Update(tuple.Index,median_price)
            self.roc_long.Update(tuple.Index,median_price)
            self.rocSignal_quick.Update(tuple.Index,median_price)
            self.rocSignal_fast.Update(tuple.Index,median_price)
            self.rocSignal_med.Update(tuple.Index,median_price)
            self.rocSignal_long.Update(tuple.Index,median_price)
            self.vol_roc.Update(tuple.Index, tuple.volume)
            self.vol_roc_fast.Update(tuple.Index, tuple.volume)
            self.vol_roc_med.Update(tuple.Index, tuple.volume)
            self.vol_roc_long.Update(tuple.Index, tuple.volume)
            self.volSignal_fast.Update(tuple.Index, tuple.volume)
            self.volSignal_med.Update(tuple.Index, tuple.volume)
            self.volSignal_long.Update(tuple.Index, tuple.volume)
            self.atr.Update(tradeBar)
            self.macd.Update(tuple.Index, tuple.close)
            self.fast.Update(tuple.Index, median_price)
            self.slow.Update(tuple.Index, median_price)
            self.williamsPR.Update(tradeBar)
            self.williamsPR_slow.Update(tradeBar)
            self.rsi.Update(tuple.Index, median_price)
            # candles
            self.cndl_abandonedbaby.Update(tradeBar)
            self.cndl_advanceblock.Update(tradeBar)
            self.cndl_belthold.Update(tradeBar)
            self.cndl_breakway.Update(tradeBar)
            self.cndl_closingmarubozu.Update(tradeBar)
            self.cndl_concealedbabyswallow.Update(tradeBar)
            self.cndl_counterattack.Update(tradeBar)
            self.cndl_darkcloudcover.Update(tradeBar)
            self.cndl_doji.Update(tradeBar)
            self.cndl_dojistar.Update(tradeBar)
            self.cndl_dragonflydoji.Update(tradeBar)
            self.cndl_engulfing.Update(tradeBar)
            self.cndl_eveningdojistar.Update(tradeBar)
            self.cndl_eveningstar.Update(tradeBar)
            self.cndl_gapsidebysidewhite.Update(tradeBar)
            self.cndl_gravestonedoji.Update(tradeBar)
            self.cndl_hammer.Update(tradeBar)
            self.cndl_hangingman.Update(tradeBar)
            self.cndl_harami.Update(tradeBar)
            self.cndl_haramicross.Update(tradeBar)
            self.cndl_highwavecandle.Update(tradeBar)
            self.cndl_hikkake.Update(tradeBar)
            self.cndl_hikkakemodified.Update(tradeBar)
            self.cndl_homingpigeon.Update(tradeBar)
            self.cndl_identicalthreecrows.Update(tradeBar)
            self.cndl_inneck.Update(tradeBar)
            self.cndl_invertedhammer.Update(tradeBar)
            self.cndl_kicking.Update(tradeBar)
            self.cndl_kickingbylength.Update(tradeBar)
            self.cndl_ladderbottom.Update(tradeBar)
            self.cndl_longleggeddoji.Update(tradeBar)
            self.cndl_longlinecandle.Update(tradeBar)
            self.cndl_marubozu.Update(tradeBar)
            self.cndl_mathold.Update(tradeBar)
            self.cndl_matchinglow.Update(tradeBar)
            self.cndl_morningdojistar.Update(tradeBar)
            self.cndl_morningstar.Update(tradeBar)
            self.cndl_onneck.Update(tradeBar)
            self.cndl_pierce.Update(tradeBar)
            self.cndl_rickshawman.Update(tradeBar)
            self.cndl_risefallthreemethods.Update(tradeBar)
            self.cndl_separatinglines.Update(tradeBar)
            self.cndl_shootingstar.Update(tradeBar)
            self.cndl_shortlinecandle.Update(tradeBar)
            self.cndl_spinningtop.Update(tradeBar)
            self.cndl_stalledpattern.Update(tradeBar)
            self.cndl_sticksandwich.Update(tradeBar)
            self.cndl_takuri.Update(tradeBar)
            self.cndl_tasukigap.Update(tradeBar)
            self.cndl_threeblackcrows.Update(tradeBar)
            self.cndl_threeinside.Update(tradeBar)
            self.cndl_threelinest.Update(tradeBar)
            self.cndl_threeoutside.Update(tradeBar)
            self.cndl_threestarsinsouth.Update(tradeBar)
            self.cndl_threewhitesoldiers.Update(tradeBar)
            self.cndl_thrusting.Update(tradeBar)
            self.cndl_tristar.Update(tradeBar)
            self.cndl_twocrows.Update(tradeBar)
            self.cndl_uniquethreeriver.Update(tradeBar)
            self.cndl_updowngapthreemethods.Update(tradeBar)
            self.cndl_upsidegaptwocrows.Update(tradeBar)

           # Support Resistance
            def CloseTo(x, y, delta):
                return abs(x-y) < delta

            def Next_RS(self, algorithm, symbol):
                s, r = [], []
                price = algorithm.Securities[symbol].Price
                # Rounded Price to $1
                s.append(round(price))
                r.append(math.ceil(price))

                # Rounded Price to $10
                s.append(int(math.floor(price / 10.0)) * 10)
                # Rounded Price Up to $10
                r.append(int(math.ceil(price / 10.0)) * 10)

                # Yesterday's OHLC Price
                s.append( self.Daily[0].Close)
                s.append( self.Daily[0].Low )
                r.append( self.Daily[0].Close)
                r.append( self.Daily[0].High)

                # Append 7 day Low:
                s.append( min([bar.Low for bar in self.Daily]) )
                r.append( max([bar.High for bar in self.Daily]) )

                s = sorted(s, reverse=True)
                r = sorted(r)
                return s[0], r[0]

            self.Daily.Add(tradeBar)
            if self.Daily.IsReady:
                support, resistance = Next_RS(self, algorithm, symbol)

                price = algorithm.Securities[symbol].Price
                mean = self.slow.Current.Value

                if CloseTo(price, support, 0.10) and mean > support*self.tol:
                    self.long = True
                if CloseTo(price, resistance, 0.10) and mean < resistance*self.tol:
                    self.short = True


            def calc_divergence(obj):
                x = np.array(list(obj))
                local_maxima = argrelextrema(x, np.greater)[0]
                local_minima = argrelextrema(x, np.less)[0]
                if x[-1] > x[-2]:
                    x = np.append(x, len(x) - 1)
                elif x[-1] > x[-2]:
                    x = np.append(x, len(x) - 1)
                hh_all = all(x[local_maxima][i] < x[local_maxima][i+1] for i in range(len(local_maxima)-1))
                ll_all = all(x[local_minima][i] > x[local_minima][i+1] for i in range(len(local_minima)-1))
                return hh_all, ll_all

            # Stochastic RSI
            if self.rsi.IsReady:
                rsi = self.rsi.Current.Value
                trade_bar = TradeBar(tuple.Index, symbol, rsi, rsi, rsi, rsi, 0)
                self.stochasticRSI.Update(trade_bar)
                if self.stochasticRSI.IsReady:
                    self.rsi_Win.Add(rsi)
                    self.rsiFastStoch_Win.Add(self.stochasticRSI.FastStoch.Current.Value)
                    self.rsiStochK_Win.Add(self.stochasticRSI.StochK.Current.Value)
                    self.rsiStochD_Win.Add(self.stochasticRSI.StochD.Current.Value)
                    self.rsiWin.Add(rsi)
                    self.rsiFastStochWin.Add(self.stochasticRSI.FastStoch.Current.Value)
                    self.rsiStochKWin.Add(self.stochasticRSI.StochK.Current.Value)
                    self.rsiStochDWin.Add(self.stochasticRSI.StochD.Current.Value)
                    if self.rsi_Win.IsReady:
                        rsi_list = list(self.rsi_Win)
                        rsifast_list = list(self.rsiFastStoch_Win)
                        rsistochk_list = list(self.rsiStochK_Win)
                        rsistochd_list = list(self.rsiStochD_Win)
                        curr_rsi, prev_rsi, last_rsi, min_rsi, max_rsi = rsi_list[-1], rsi_list[-2], rsi_list[-3], min(rsi_list), max(rsi_list)
                        curr_fast, prev_fast, last_fast, min_fast, max_fast = rsifast_list[-1], rsifast_list[-2], rsifast_list[-3], min(rsifast_list), max(rsifast_list)
                        curr_stochk, prev_stochk, last_stochk, min_stochk, max_stochk = rsistochk_list[-1], rsistochk_list[-2], rsistochk_list[-3], min(rsistochk_list), max(rsistochk_list)
                        curr_stochd, prev_stochd, last_stochd, min_stochd, max_stochd = rsistochd_list[-1], rsistochd_list[-2], rsistochd_list[-3], min(rsistochd_list), max(rsistochd_list)
                        cond1 = (curr_fast >= curr_stochk*self.tolerance) and (curr_stochk >= curr_stochd*self.tolerance)
                        cond11 = (curr_rsi >= max_rsi*self.tolerance) and (curr_fast >= max_fast*self.tolerance) and (curr_stochk >= max_stochk*self.tolerance) and (curr_stochd >= max_stochd*self.tolerance)
                        cond2 = (curr_rsi >= prev_rsi*self.tolerance) and (curr_fast >= prev_fast*self.tolerance) and (curr_stochk >= prev_stochk*self.tolerance) and (curr_stochd >= prev_stochd*self.tolerance)
                        cond3 = (prev_rsi >= last_rsi*self.tolerance) and (prev_fast >= last_fast*self.tolerance) and (prev_stochk >= last_stochk*self.tolerance) and (prev_stochd >= last_stochd*self.tolerance)

                        cond4 = (curr_fast*self.tolerance <= curr_stochk) and (curr_stochk*self.tolerance <= curr_stochd)
                        cond41 = (min_rsi*self.tolerance <= curr_rsi) and (min_fast*self.tolerance <= curr_fast) and (min_stochk*self.tolerance <= curr_stochk) and (min_stochd*self.tolerance <= curr_stochd)
                        cond5 = (curr_rsi*self.tolerance <= prev_rsi) and (curr_fast*self.tolerance <= prev_fast) and (curr_stochk*self.tolerance <= prev_stochk) and (curr_stochd*self.tolerance <= prev_stochd)
                        cond6 = (prev_rsi*self.tolerance <= last_rsi) and (prev_fast*self.tolerance <= last_fast) and (prev_stochk*self.tolerance <= last_stochk) and (prev_stochd*self.tolerance <= last_stochd)

                        hh_rsi, ll_rsi = calc_divergence(rsi_list)
                        hh_rsifast, ll_rsifast = calc_divergence(rsifast_list)
                        hh_rsik, ll_rsik = calc_divergence(rsistochk_list)
                        hh_rsid, ll_rsid = calc_divergence(rsistochd_list)
                        cond7 = hh_rsi, hh_rsifast, hh_rsik, hh_rsid
                        cond8 = ll_rsi, ll_rsifast, ll_rsik, ll_rsid

                        if (cond1 and cond7) and (cond2 and cond3):
                            self.rsi_uptrend = True
                        else:
                            self.rsi_uptrend = False
                        if (cond4 and cond8) and (cond5 and cond6):
                            self.rsi_downtrend = True
                        else:
                            self.rsi_downtrend = False

                        exit = self.rsiFastStochWin.crossedBelow(self.rsiWin) and self.rsiFastStochWin.crossedBelow(self.rsiStochKWin) and self.rsiStochKWin.crossedBelow(self.rsiStochDWin)
                        entry = self.rsiFastStochWin.crossedAbove(self.rsiWin) and self.rsiFastStochWin.crossedAbove(self.rsiStochKWin) and self.rsiStochKWin.crossedAbove(self.rsiStochDWin)
                        if algorithm.Portfolio[symbol].Invested:
                            if exit and self.short:
                                self.rsi_exit_signal = True
                            else:
                                self.rsi_exit_signal = False
                        if not algorithm.Portfolio[symbol].Invested:
                            if entry and self.long:
                                self.rsi_entry_signal = True
                            else:
                                self.rsi_entry_signal = False

            # MACD Trend
            if self.macd.IsReady:
                macd = self.macd.Current.Value
                trade_bar = TradeBar(tuple.Index, symbol, macd, macd, macd, macd, 0)
                self.stochasticMACD.Update(trade_bar)
                if self.stochasticMACD.IsReady:
                    macd = self.macd.Current.Value
                    macd_fast = self.macd.Fast.Current.Value
                    macd_slow = self.macd.Slow.Current.Value
                    macd_hist = self.macd.Histogram.Current.Value
                    signal = self.macd.Signal.Current.Value
                    delta = (macd - signal)/macd_fast
                    macd_stoch = self.stochasticMACD.Current.Value
                    macd_stochfast = self.stochasticMACD.FastStoch.Current.Value
                    macd_stochk = self.stochasticMACD.StochK.Current.Value
                    macd_stochd = self.stochasticMACD.StochD.Current.Value
                    self.macd_Win.Add(macd)
                    self.macdHist_Win.Add(macd_fast)
                    self.macdFast_Win.Add(macd_hist)
                    self.macdSlow_Win.Add(macd_slow)
                    self.macdSignal_Win.Add(signal)
                    self.macdDelta_Win.Add(delta)
                    self.macd_stoch_Win.Add(macd_stoch)
                    self.macd_stochfast_Win.Add(macd_stochfast)
                    self.macd_stochk_Win.Add(macd_stochk)
                    self.macd_stochd_Win.Add(macd_stochd)
                    self.macdStochFastWin.Add(macd_stochfast)
                    self.macdStochKWin.Add(macd_stochk)
                    self.macdStochDWin.Add(macd_stochd)
                    if self.macd_Win.IsReady:
                        macd_list = list(self.macd_Win)
                        macdhist_list = list(self.macdHist_Win)
                        macdfast_list = list(self.macdFast_Win)
                        macdslow_list = list(self.macdSlow_Win)
                        macdsignal_list = list(self.macdSignal_Win)
                        macddelta_list = list(self.macdDelta_Win)
                        macdstoch_list = list(self.macd_stoch_Win)
                        macdstochfast_list = list(self.macd_stochfast_Win)
                        macdstochk_list = list(self.macd_stochk_Win)
                        macdstochd_list = list(self.macd_stochd_Win)
                        curr_macd, prev_macd, last_macd, min_macd, max_macd = macd_list[-1], macd_list[-2], macd_list[-3], min(macd_list), max(macd_list)
                        curr_macd_fast, prev_macd_fast, last_macd_fast, min_macd_fast, max_macd_fast = macdfast_list[-1], macdfast_list[-2], macdfast_list[-3], min(macdfast_list), max(macdfast_list)
                        curr_macd_slow, prev_macd_slow, last_macd_slow, min_macd_slow, max_macd_slow = macdslow_list[-1], macdslow_list[-2], macdslow_list[-3], min(macdslow_list), max(macdslow_list)
                        curr_macd_hist, prev_macd_hist, last_macd_hist, min_macd_hist, max_macd_hist = macdhist_list[-1], macdhist_list[-2], macdhist_list[-3], min(macdhist_list), max(macdhist_list)
                        curr_signal, prev_signal, last_signal, min_signal, max_signal = macdsignal_list[-1], macdsignal_list[-2], macdsignal_list[-3], min(macdsignal_list), max(macdsignal_list)
                        curr_delta, prev_delta, last_delta, min_delta, max_delta = macddelta_list[-1], macddelta_list[-2], macddelta_list[-3], min(macddelta_list), max(macddelta_list)
                        curr_macdstoch, prev_macdstoch, last_macdstoch, min_macdstoch, max_macdstoch = macdstoch_list[-1], macdstoch_list[-2], macdstoch_list[3], min(macdstoch_list), max(macdstoch_list)
                        curr_macdstochfast, prev_macdstochfast, last_macdstochfast, min_macdstochfast, max_macdstochfast = macdstochfast_list[-1], macdstochfast_list[-2], macdstochfast_list[-3], min(macdstochfast_list), max(macdstochfast_list)
                        curr_macdstochk, prev_macdstochk, last_macdstochk, min_macdstochk, max_macdstochk = macdstochk_list[-1], macdstochk_list[-2], macdstochk_list[-3], min(macdstochk_list), max(macdstochk_list)
                        curr_macdstochd, prev_macdstochd, last_macdstochd, min_macdstochd, max_macdstochd = macdstochd_list[-1], macdstochd_list[-2], macdstochd_list[-3], min(macdstochd_list), max(macdstochd_list)
                        
                        cond1 = ((curr_macd_hist-curr_delta)>=0.0025) and (curr_macd >= curr_signal*self.tolerance) and (curr_macdstochfast >= curr_macdstochk*self.tolerance) and (curr_macdstochk >= curr_macdstochd*self.tolerance)
                        cond11 = (curr_macd >= max_macd*self.tolerance) and (curr_macdstochfast >= max_macdstochfast*self.tolerance) and (curr_macdstochk >= max_macdstochk*self.tolerance) and (curr_macdstochd >= max_macdstochd*self.tolerance)
                        cond2 = (curr_macd >= prev_macd*self.tolerance) and (curr_macd_fast >= prev_macd_fast*self.tolerance) and (curr_macd_hist >= prev_macd_hist*self.tolerance) and (curr_signal >= prev_signal*self.tolerance)
                        cond3 = (prev_macd >= last_macd*self.tolerance) and (prev_macd_fast >= last_macd_fast*self.tolerance) and (prev_macd_hist >= last_macd_hist*self.tolerance) and (prev_signal >= last_signal*self.tolerance)
                        cond4 = ((curr_macd_hist-curr_delta) <= -0.0025) and (curr_macd*self.tolerance <= curr_signal) and (curr_macdstochfast*self.tolerance <= curr_macdstochk) and (curr_macdstochk*self.tolerance <= curr_macdstochd)
                        cond41 = (min_macd*self.tolerance <= curr_macd) and (min_macdstochfast*self.tolerance <= curr_macdstochfast) and (min_macdstochk*self.tolerance <= curr_macdstochk) and (min_macdstochd*self.tolerance <= curr_macdstochd)
                        cond5 = (curr_macd*self.tolerance <= prev_macd) and (curr_macd_fast*self.tolerance <= prev_macd_fast) and (curr_macd_hist*self.tolerance <= prev_macd_hist) and (curr_signal*self.tolerance <= prev_signal)
                        cond6 = (prev_macd*self.tolerance <= last_macd) and (prev_macd_fast*self.tolerance <= last_macd_fast) and (prev_macd_hist*self.tolerance <= last_macd_hist) and (prev_signal*self.tolerance <= last_signal)

                        hh_macd, ll_macd = calc_divergence(macd_list)
                        hh_macdhist, ll_macdhist = calc_divergence(macdhist_list)
                        hh_macdfast, ll_macdfast = calc_divergence(macdfast_list)
                        hh_macdslow, ll_macdslow = calc_divergence(macdslow_list)
                        hh_macdsignal, ll_macdsignal = calc_divergence(macdsignal_list)
                        hh_macdstochfast, ll_macdstochfast = calc_divergence(macdstochfast_list)
                        hh_macdstochk, ll_macdstochk = calc_divergence(macdstochk_list)
                        hh_macdstochd, ll_macdstochd = calc_divergence(macdstochd_list)
                        cond7 = hh_macd, hh_macdhist, hh_macdfast, hh_macdslow, hh_macdsignal, hh_macdstochfast, hh_macdstochk, hh_macdstochd
                        cond8 = ll_macd, ll_macdhist, ll_macdfast, ll_macdslow, ll_macdsignal, ll_macdstochfast, ll_macdstochk, ll_macdstochd

                        if (cond1 and cond7) and (cond2 and cond3):
                            self.macd_uptrend = True
                        else:
                            self.macd_uptrend = False
                        if (cond4 and cond8) and (cond5 and cond6):
                            self.macd_downtrend = True
                        else:
                            self.macd_downtrend = False

                        exit = self.macdStochFastWin.crossedBelow(self.macdStochKWin) and self.macdStochKWin.crossedBelow(self.macdStochDWin)
                        entry = self.macdStochFastWin.crossedAbove(self.macdStochKWin) and self.macdStochKWin.crossedAbove(self.macdStochDWin)                        
                        if algorithm.Portfolio[symbol].Invested:
                            if exit and self.short:
                                self.macd_exit_signal = True
                            else:
                                self.macd_exit_signal = False
                        if not algorithm.Portfolio[symbol].Invested:
                            if entry and self.long:
                                self.macd_entry_signal = True
                            else:
                                self.macd_entry_signal = False

            def roc_calc(obj):
                obj_list = list(obj)
                output_list = list()
                for i in range(-1, -len(obj_list)+1, -1):
                    if obj_list[i-1] != 0:
                        val = round((obj_list[i] - obj_list[i-1])/obj_list[i-1], 4)
                    else:
                        val = 0
                    output_list.append(val)
                return output_list
        
            self.roc_Win.Add(round(float(statistics.median([self.roc.Current.Value, self.roc_fast.Current.Value, self.roc_med.Current.Value, self.roc_long.Current.Value])), 4))
            if self.roc_Win.IsReady:
                val = statistics.median(roc_calc(self.roc_Win))
                roc_sum, roc_len = sum(list(self.roc_Win)), len(list(self.roc_Win))
                self.roc_slope = round(float(roc_sum)/roc_len, 4)

            def calc_divergence(obj):
                x = np.array(list(obj))
                local_maxima = argrelextrema(x, np.greater)[0]
                local_minima = argrelextrema(x, np.less)[0]
                if x[-1] > x[-2]:
                    x = np.append(x, len(x) - 1)
                elif x[-1] > x[-2]:
                    x = np.append(x, len(x) - 1)
                hh_all = all(x[local_maxima][i] < x[local_maxima][i+1] for i in range(len(local_maxima)-1))
                ll_all = all(x[local_minima][i] > x[local_minima][i+1] for i in range(len(local_minima)-1))
                return hh_all, ll_all

            self.rocSum_Win.Add(round(float(sum([self.roc.Current.Value, self.roc_fast.Current.Value, self.roc_med.Current.Value, self.roc_long.Current.Value])), 4))
            self.roclen_Win.Add(round(float(statistics.median([self.roc.Current.Value, self.roc_fast.Current.Value, self.roc_med.Current.Value, self.roc_long.Current.Value])), 4))
            if self.rocSum_Win.IsReady:
                self.hh_all, self.ll_all = calc_divergence(self.roclen_Win)
                self.hh_sum, self.ll_sum = calc_divergence(self.rocSum_Win)    
            self.vol_Win.Add(round(float(statistics.median([self.vol_roc.Current.Value, self.vol_roc_fast.Current.Value, self.vol_roc_med.Current.Value, self.vol_roc_long.Current.Value])), 4))
            if self.vol_Win.IsReady:
                val = statistics.median(roc_calc(self.vol_Win))
                vol_sum, vol_len = sum(list(self.vol_Win)), len(list(self.vol_Win))
                self.vol_slope = round(float(vol_sum)/vol_len, 4)
            
            self.roc_prices_Win.Add(round(statistics.median([self.roc.Current.Value, self.roc_fast.Current.Value, self.roc_med.Current.Value, self.roc_long.Current.Value]), 4))
            self.roc_prices_lev_Win.Add(round(statistics.median([self.roc.Current.Value, self.roc_fast.Current.Value, self.roc_med.Current.Value]), 4))
            self.roc_volume_Win.Add(round(statistics.median([self.vol_roc.Current.Value, self.vol_roc_fast.Current.Value, self.vol_roc_med.Current.Value, self.vol_roc_long.Current.Value]), 4))
            self.prices_Win.Add(tuple.close)
            if self.prices_Win.IsReady and self.roc_prices_Win.IsReady:
                prices = list(self.roc_prices_Win)
                volumes = list(self.roc_volume_Win)
                frames = [i for i in range(-2, -21, -2)]
                frames_ = [i for i in range(-1, -21, -1)]
                prices_lev = list(self.roc_prices_lev_Win)
                _frames = [i for i in range(-1, -3, -1)]
                
                v1 = round(statistics.median([round(float(prices[i] - prices[i-5]/ prices[i-5]), 4) if prices[i-5] != 0 else 0 for i in frames]), 4)
                v11 = round(statistics.median([round(float(prices[i] - prices[i-5]/ prices[i-5]), 4) if prices[i-5] != 0 else 0 for i in frames_]), 4)
                v1_mom = round(statistics.median([round(float(prices_lev[i] - prices_lev[i-1]/ abs(abs(i)+(i-1))), 4) if abs(abs(i)+(i-1)) != 0 else 0 for i in _frames]), 4)
                
                self.median_roc = v1 if (v1 > v11) else -1
                self.median_roc_momentum = v1_mom
                self.median_vol = round(statistics.median([round(float(volumes[i] - volumes[i-5]/ volumes[i-5]), 4) if volumes[i-5] != 0 else 0 for i in frames]), 4)
                
                C = list(self.prices_Win)
                avg = sum(list(self.prices_Win))/len(list(self.prices_Win))
                self.volatility = float(np.sqrt(252)*reduce(lambda a,b:a+abs(avg-b),C,0)/len(C))/C[-1]

            rocSignal_lst = [self.rocSignal_quick.Current.Value, self.rocSignal_fast.Current.Value, self.rocSignal_med.Current.Value, self.rocSignal_long.Current.Value]
            volSignal_lst = [self.volSignal_quick.Current.Value, self.volSignal_fast.Current.Value, self.volSignal_med.Current.Value, self.volSignal_long.Current.Value]
            rocsignal_up = all(x > 0.0 for x in rocSignal_lst)
            rocsignal_down = all(x < 0.0 for x in rocSignal_lst)
            volsignal = all(x > 0.0 for x in volSignal_lst)
            self.roc_vol_signal_up = rocsignal_up and volsignal
            self.roc_vol_signal_down = rocsignal_down and volsignal

            self.williamsWindow.Add(round(statistics.median([self.williamsPR.Current.Value, self.williamsPR_slow.Current.Value]), 4))
            if self.williamsWindow.IsReady:
                williams = list(self.williamsWindow)
                w_length = len(williams)
                frames = [i for i in range(-1, (w_length*-1)+1, -1)]
                self.williams_median_roc = round(statistics.median([round(float(williams[i] - williams[i-1]/ williams[i-1]), 4) if williams[i-1] != 0 else 0 for i in frames]), 4)
                self.williams_median = round(statistics.median(williams), 4)
            
            self.high_Win.Add(tuple.high)
            self.low_Win.Add(tuple.low)
            if self.high_Win.IsReady and self.low_Win.IsReady and self.prices_Win.IsReady:
                close = list(self.prices_Win)
                todayvol = np.std(close[1:31])
                yesterdayvol = np.std(close[0:30])
                deltavol = (todayvol - yesterdayvol) / todayvol
                self.lookback = round(self.lookback * (1 + deltavol))
                
                # Account for upper/lower limit of lockback length
                if self.lookback > self.ceiling:
                    self.lookback = self.ceiling
                elif self.lookback < self.floor:
                    self.lookback = self.floor
                    
                high = list(self.high_Win)
                low = list(self.low_Win)

                # Buy in case of breakout
                breakout_condition1 = (algorithm.Securities[symbol].Close >= max(high[:-1]))
                if breakout_condition1:
                    self.breakout = True
                breakdown_condition1 = (algorithm.Securities[symbol].Close >= min(low[:-1]))
                if breakdown_condition1:
                    self.breakdown = True
            
            fast = self.fast.Current.Value
            slow = self.slow.Current.Value
            median_roc = round(statistics.median([self.roc.Current.Value, self.roc_fast.Current.Value, self.roc_med.Current.Value, self.roc_long.Current.Value]), 4)
            self.fast_Win.Add(fast)
            self.slow_Win.Add(slow)
            self.is_uptrend = (((slow) >= (median_roc*self.tolerance)) and (tuple.close >= (median_roc*self.tolerance)))
            self.is_downtrend = (((slow*self.tolerance) <= (median_roc)) and (tuple.close*self.tolerance <= (median_roc)))
            
        if self.is_uptrend or self.fast_Win.IsReady:
            # triangle formula
            # base * height * 0.5
            curr_fast, curr_slow = fast, slow
            prev_fast, prev_slow = min(list(self.fast_Win)), min(list(self.slow_Win))
            self.scale = round(float(fast - slow) / ((fast+slow)/2.0), 4)
        else:
            self.scale = 0.00
        
        self.kalWindow.Add(self.kalFilter.Value)
        self.kalWindowLow.Add(self.kalFilterLow.Value)
        self.kalWindowHigh.Add(self.kalFilterHigh.Value)
        self.priceWindow.Add(statistics.median([algorithm.Securities[symbol].Open, algorithm.Securities[symbol].High, algorithm.Securities[symbol].Low, algorithm.Securities[symbol].Close]))
        self.priceWindowLow.Add(algorithm.Securities[symbol].Low)
        self.priceWindowHigh.Add(algorithm.Securities[symbol].High)
        
        if self.kalFilterLow.IsReady:
            exit = self.priceWindowLow.crossedBelow(self.kalWindowLow) and self.priceWindowHigh.crossedBelow(self.kalWindowHigh) and self.priceWindow.crossedBelow(self.kalWindow)
            entry = self.priceWindowLow.crossedAbove(self.kalWindowLow) and self.priceWindowHigh.crossedAbove(self.kalWindowHigh) and self.priceWindow.crossedAbove(self.kalWindow)
            if algorithm.Portfolio[symbol].Invested:
                # exit 
                if exit is True:
                    self.exit_signal = True
                else:
                    self.exit_signal = False
            elif not algorithm.Portfolio[symbol].Invested:
                # entry
                if entry is True:
                    self.entry_signal = True
                else:
                    self.entry_signal = False

        # VPN Indicator
        iATR = 0.1
        ema_smooth = 3
        vp = 0.0
        vn = 0.0
        vtot = 0.0
        dist = self.atr.Current.Value * iATR
        self.vpn_vol_Win.Add(tuple.volume)
        self.vpn_hlc_Win.Add(round(statistics.median([tuple.high, tuple.low, tuple.close]), 4))
        if self.vpn_vol_Win.IsReady and self.vpn_hlc_Win.IsReady:
            vpn_vol_Win = list(self.vpn_vol_Win)
            vpn_hlc_Win = list(self.vpn_hlc_Win)
            for i in range(-1, -self.vpn_period, -1):
                if (vpn_hlc_Win[i] >= vpn_hlc_Win[i-1] + dist):
                    vp += vpn_vol_Win[i]
                elif (vpn_hlc_Win[i] <= vpn_hlc_Win[i-1] - dist):
                    vn += vpn_vol_Win[i]
                vtot += vpn_vol_Win[i]
                
            vpn_val = (((vp - vn) / (vtot/self.vpn_period)) / self.vpn_period) * 100
                
            self.vpn_list.Add(vpn_val)

        if self.vpn_list.IsReady:
            vpn_ema = pd.DataFrame(list(self.vpn_list)).ewm(span=ema_smooth, adjust=False).mean().iloc[-1][0]
            vpn_scale = self.vpn_list[-1]
            vpnIndicator = ((vpn_scale) >= (vpn_ema*self.tolerance)) and ((vpn_scale) >= (self.vpn_list[-2]*self.tolerance))
            self.vpnIndicator = vpnIndicator
        if self.vpnIndicator:
            curr_vpn, curr_vpn_ema = vpn_scale, vpn_ema
            low_vpn, low_vpn_ema = min(self.vpn_list), min(pd.DataFrame(list(self.vpn_list)).ewm(span=ema_smooth, adjust=False).mean().iloc[-1])
            vpnScale = round(float(curr_vpn - curr_vpn_ema) / ((low_vpn + low_vpn_ema) / 2.0), 4)
            self.vpnScale = vpnScale
        else:
            self.vpnScale = 0.00
            
        # Candles
        cndl_coef =  float(sum([x.Current.Value for x in self.candles]))
        
        self.candleContainer.Add(cndl_coef)
        self.candleWin.Add(cndl_coef)
        if self.candleContainer.IsReady and self.candleWin.IsReady:
            cndl_avg = statistics.median(list(self.candleContainer))
            self.candleavgWin.Add(cndl_avg)
        
            if self.candleWin.IsReady and self.candleavgWin.IsReady:
                exit = self.candleWin.crossedBelow(self.candleavgWin)
                entry = self.candleWin.crossedAbove(self.candleavgWin)
                if algorithm.Portfolio[symbol].Invested:
                    if exit and self.short:
                        self.cndl_downtrend = True
                if not algorithm.Portfolio[symbol].Invested:
                    if entry and self.long:
                        self.cndl_uptrend = True

        # Price is Favorable
        if (algorithm.Securities[symbol].BidPrice < self.VWAP):
            self.PriceIsFavorable = True
        elif (algorithm.Securities[symbol].AskPrice > self.VWAP):
            self.PriceIsFavorable = True
        else:
            self.PriceIsFavorable = False
        
        # Market Profile
        if self.vwap_lng.IsReady:
            self.market_profile.CalcMarketProfile(algorithm, 21, [symbol])
            a, val, vah, d = self.market_profile.GetMarketProfile(algorithm, symbol)
            poc = float(max(a, key=a.get))
            
            self.poc_Win.Add(poc)
            self.pocWin.Add(poc)
            self.val_Win.Add(val)
            self.valWin.Add(val)
            self.vah_Win.Add(vah)
            self.vahWin.Add(vah)
            self.price_Win.Add(algorithm.Securities[symbol].Price)
            self.priceWin.Add(algorithm.Securities[symbol].Price)
            self.medpri_Win.Add(median_price)
            self.medpriWin.Add(median_price)
            mp_fst = self.vwap_fst.Current.Value
            mp_med = self.vwap_med.Current.Value    
            mp_lng = self.vwap_lng.Current.Value
            self.vwap_fst_Win.Add(mp_fst)
            self.vwap_fstWin.Add(mp_fst)
            self.vwap_med_Win.Add(mp_med) 
            self.vwap_medWin.Add(mp_med)    
            self.vwap_lng_Win.Add(mp_lng)
            self.vwap_lngWin.Add(mp_lng)
            
            if self.poc_Win.IsReady:
                poc_lst = list(self.poc_Win)
                val_lst = list(self.val_Win)
                vah_lst = list(self.vah_Win)
                pri_lst = list(self.price_Win)
                medp_lst = list(self.medpri_Win)
                fst_lst = list(self.vwap_fst_Win)
                med_lst = list(self.vwap_med_Win)
                lng_lst = list(self.vwap_lng_Win)
                cur_poc, pre_poc, lst_poc = poc_lst[-1], poc_lst[-2], poc_lst[-3]
                cur_val, pre_val, lst_val = val_lst[-1], val_lst[-2], val_lst[-3]
                cur_vah, pre_vah, lst_vah = vah_lst[-1], vah_lst[-2], vah_lst[-3]
                cur_pri, pre_pri, lst_pri = pri_lst[-1], pri_lst[-2], pri_lst[-3]
                cur_medp, pre_medp, lst_medp = medp_lst[-1], medp_lst[-2], medp_lst[-3]
                cur_fst, pre_fst, lst_fst = fst_lst[-1], fst_lst[-2], fst_lst[-3]    
                cur_med, pre_med, lst_med = med_lst[-1], med_lst[-2], med_lst[-3]
                cur_lng, pre_lng, lst_lng = lng_lst[-1], lng_lst[-2], lng_lst[-3]
    
                cond1 = (cur_pri >= cur_poc*self.tol) and (cur_pri >= cur_medp*self.tol) and (cur_fst >= cur_med*self.tol) and (cur_med >= cur_lng*self.tol)    
                cond2 = (cur_pri >= pre_pri*self.tol) and (cur_medp >= pre_medp*self.tol) and (cur_poc >= pre_poc*self.tol) and (cur_val >= pre_val*self.tol) and (cur_vah >= pre_vah*self.tol) and (cur_fst >= pre_fst*self.tol) and (cur_med >= pre_med*self.tol) and (cur_lng >= pre_lng*self.tol)
                cond3 = (pre_pri >= lst_pri*self.tol) and (pre_medp >= lst_medp*self.tol) and (pre_poc >= lst_poc*self.tol) and (pre_val >= lst_val*self.tol) and (pre_vah >= lst_vah*self.tol) and (pre_fst >= lst_fst*self.tol) and (pre_med >= lst_med*self.tol) and (pre_lng >= lst_lng*self.tol)    
                cond4 = (cur_pri <= cur_poc*self.tol) and (cur_pri <= cur_medp*self.tol) and (cur_fst <= cur_med*self.tol) and (cur_med <= cur_lng*self.tol) 
                cond5 = (cur_pri <= pre_pri*self.tol) and (cur_medp <= pre_medp*self.tol) and (cur_poc <= pre_poc*self.tol) and (cur_val <= pre_val*self.tol) and (cur_vah <= pre_vah*self.tol) and (cur_fst <= pre_fst*self.tol) and (cur_med <= pre_med*self.tol) and (cur_lng <= pre_lng*self.tol)    
                cond6 = (pre_pri <= lst_pri*self.tol) and (pre_medp <= lst_medp*self.tol) and (pre_poc <= lst_poc*self.tol) and (pre_val <= lst_val*self.tol) and (pre_vah <= lst_vah*self.tol) and (pre_fst <= lst_fst*self.tol) and (pre_med <= lst_med*self.tol) and (pre_lng <= lst_lng*self.tol)     
                
                hh_poc, ll_poc = calc_divergence(poc_lst)
                hh_val, ll_val = calc_divergence(val_lst)
                hh_vah, ll_vah = calc_divergence(vah_lst)
                hh_pri, ll_pri = calc_divergence(pri_lst)
                hh_mpr, ll_mpr = calc_divergence(medp_lst)
                hh_fst, ll_fst = calc_divergence(fst_lng) 
                hh_med, ll_med = calc_divergence(med_lng)    
                hh_long, ll_long = calc_divergence(lng_lst)    
                cond7 = hh_poc, hh_val, hh_vah, hh_pri, hh_mpr, hh_fst, hh_med, hh_long
                cond8 = ll_poc, ll_val, ll_vah, ll_pri, hh_mpr, ll_fst, ll_med, ll_long    
                if (cond1 and cond7) and (cond2 and cond3):
                    self.mp_uptrend = True
                if (cond4 and cond8) and (cond5 and cond6):
                    self.mp_downtrend = True
                    
                exit = self.valWin.crossedBelow(self.vwap_medWin) and self.valWin.crossedBelow(self.vwap_mlngWin) and self.valWin.crossedBelow(self.medpriWin)
                entry = self.valWin.crossedAbove(self.vwap_medWin) and self.valWin.crossedAbove(self.vwap_mlngWin) and self.valWin.crossedAbove(self.medpriWin)
                if algorithm.Portfolio[symbol].Invested:
                    if exit and self.short:
                        self.mp_exit_signal = True
                    else:
                        self.mp_exit_signal = False
                if not algorithm.Portfolio[symbol].Invested:
                    if entry and self.long:
                        self.mp_entry_signal = True
                    else:
                        self.mp_entry_signal = False
                
                # if algorithm.Portfolio[symbol].Invested:
                #     if self.pocWin.crossedBelow(self.vwap_lngWin): #and self.medpriWin.crossedBelow(self.vwap_medWin) and self.valWin.crossedBelow(self.vwap_mlngWin) and self.valWin.crossedBelow(self.vwap_lngWin):
                #         self.dont_buy = True
                #     else:
                #         self.dont_buy = False
                # if not algorithm.Portfolio[symbol].Invested:
                #     if all(self.medpriWin[0] < x[0] for x in [self.vwap_lngWin]):
                #         self.dont_buy = True
                #     else:
                #         self.dont_buy = False               
                behavior = poc < mp_lng
                if behavior:
                    self.dont_buy = True
                else:
                    self.dont_buy = False     

    @property
    def VWAP(self):
       return self.vwap.Value
       
class IntradayVwap:
    '''Defines the canonical intraday VWAP indicator'''
    def __init__(self):
        self.Value = 0.0
        self.lastDate = datetime.min
        self.sumOfVolume = 0.0
        self.sumOfPriceTimesVolume = 0.0

    @property
    def IsReady(self):
        return self.sumOfVolume > 0.0

    def Update(self, input):
        '''Computes the new VWAP'''
        success, volume, averagePrice = self.GetVolumeAndAveragePrice(input)
        if not success:
            return self.IsReady

        # reset vwap on daily boundaries
        if self.lastDate != input.EndTime.date():
            self.sumOfVolume = 0.0
            self.sumOfPriceTimesVolume = 0.0
            self.lastDate = input.EndTime.date()

        # running totals for Σ PiVi / Σ Vi
        self.sumOfVolume += volume
        self.sumOfPriceTimesVolume += averagePrice * volume

        if self.sumOfVolume == 0.0:
           # if we have no trade volume then use the current price as VWAP
           self.Value = input.Value
           return self.IsReady

        self.Value = self.sumOfPriceTimesVolume / self.sumOfVolume
        return self.IsReady

    def GetVolumeAndAveragePrice(self, input):
        '''Determines the volume and price to be used for the current input in the VWAP computation'''

        if type(input) is Tick:
            if input.TickType == TickType.Trade:
                return True, float(input.Quantity), float(input.LastPrice)

        if type(input) is TradeBar:
            if not input.IsFillForward:
                averagePrice = round(float(statistics.median([input.Open, input.High, input.Low, input.Close])), 4)
                return True, float(input.Volume), averagePrice

        return False, 0.0, 0.0
#region imports
from AlgorithmImports import *
#endregion
import random
import string
import math

class TradeManagement:
    
    def __init__(self, algorithm, ticker, optionSymbol):
        self.Trades     = {}
        self.Algorithm  = algorithm
        self.Ticker = ticker
        self.OptionSymbol = optionSymbol
        
        # Volatility indicators for position sizing
        self.atr    = self.Algorithm.ATR(self.Ticker, 6, MovingAverageType.Exponential, Resolution.Daily)
        self.stddev = self.Algorithm.STD(self.Ticker, 6, Resolution.Daily)
    
    # Manage Open Positions
    def ManageOpenPositions(self):
        
        for t in self.OpenTrades():
            
            # Scan for Profit-Loss
            if t.UnrealizedProfit() > t.Target:
                self.Close(t, "Profit")
                
            elif t.UnrealizedProfit() < -t.Target:
                self.Close(t, "Loss")
                
            # Stop Assignment
            if t.ExpiringSoon():
                self.Close(t, "Expiring")
    
    
    # Base target contract count on the number of contracts to hit the profit assuming 2SD move.
    def ContractSizing(self, targetProfit): 
        expectedDollarMovement = 1 * self.stddev.Current.Value * 100
        # At least 1 contract
        contracts = min(10, math.ceil(targetProfit / expectedDollarMovement))
        return int(contracts)
        
    # Base target profit per position on the volatility of the last few days.
    def TargetProfitEstimate(self):
        return round(self.atr.Current.Value * self.stddev.Current.Value * 100) # if abs(self.atr.Current.Value) > 0 else round(self.atr.Current.Value * 100)
        
    def OpenTrades(self):
        return [t for t in self.Trades.values() if t.IsOpen()]
    
    # Place a trade in the direction signalled
    def Create(self, direction):
        
        symbol = self.SelectOptionContract(direction)
        if symbol is None:
            return
        
        # If we already hold; skip
        alreadyOpen = [c for c in self.OpenTrades() if c.Symbol == symbol]
        if len(alreadyOpen) > 0:
            return
        
        # If today's the expiry don't trade
        # if (symbol.ID.Date < self.Algorithm.Time):
        #     return
        
        targetProfit = self.TargetProfitEstimate()
        size = self.ContractSizing(targetProfit)
        price = self.Algorithm.Securities[symbol].Price
        asset = self.Algorithm.Securities[self.Ticker].Price
        
        tag = f"Asset: {asset} | Opened | Target of ${targetProfit} at ${price}"
        ticket  = self.Algorithm.MarketOrder(symbol, size, False, tag)
        trade   = Trade(self.Algorithm, self.Algorithm.Securities[symbol], ticket, targetProfit)
        #self.Algorithm.Log(str(self.Algorithm.Portfolio.Keys))
        
        #self.Algorithm.Plot("Risk Management", "Unrealized Profit Percent", float(trade.UnrealizedProfit()))
        #self.Algorithm.Plot("Risk Management", "Unrealized Profit Percent (Portfolio)", float(self.Algorithm.Portfolio[trade.Contract].UnrealizedProfitPercent))

        self.Trades[trade.Id] = trade
        return trade
        
    def Close(self, trade, reason):
        trade.Close(reason)
        if trade.Id in self.Trades:
            del self.Trades[trade.Id]
        
    # Select OOM Option Contract with Given Bias
    def SelectOptionContract(self, direction):
        
        if direction == OrderDirection.Buy:
            right = OptionRight.Call
        else:
            right = OptionRight.Put
        
        chain = self.Algorithm.CurrentSlice.OptionChains.GetValue(self.OptionSymbol)
        if chain is None: 
            self.Algorithm.Log(f"{self.Algorithm.Time} - No option chains with {self.OptionSymbol}. Missing data for Mar-2nd.")
            return None
        
        # Select contracts expirying tomorrow at least.
        chain = [x for x in chain if (x.Right == right and x.Expiry > self.Algorithm.Time)]
        reverseSort = right == OptionRight.Call         # Reverse sort of a call
        contracts = sorted(sorted(chain, key = lambda x: abs(x.Strike), reverse=reverseSort), key = lambda x: x.Expiry)
        contracts = [x for x in contracts if x.OpenInterest > 20000]

        #contracts_IV = sorted(sorted(chain, key = lambda x: x.ImpliedVolatility, reverse=reverseSort), key = lambda x: x.Expiry)[:2]
        #contracts_OI = sorted(sorted(chain, key = lambda x: x.OpenInterest, reverse=reverseSort), key = lambda x: x.Expiry)[:5]
        #contracts = list(set.intersection(*map(set, [contracts, contracts_IV])))

        try:
            #self.Algorithm.Plot("Options", "Implied Volatility", round(float(contracts[0].ImpliedVolatility), 5))
            #self.Algorithm.Plot("Options", "Open Interest", float(contracts[0].OpenInterest))
            #self.Algorithm.Plot("Contract", "Delta", abs(float(self.Algorithm.Securities[self.Ticker].Price - contracts[0].Strike)))
            self.Algorithm.Plot("Options", "Relative Volume ROCP", float(self.rdv_roc.Current.Value))
            self.Algorithm.Plot("Options", "ATR", float(self.atr.Current.Value))
            self.Algorithm.Plot("Options", "STDDEV", float(self.stddev.Current.Value))
        except:
            pass
        
        # No contracts found
        if len(contracts) == 0:
            return None
            
        return contracts[0].Symbol


class Trade:
    
    def __init__(self, algorithm, contract, ticket, target): 
        self.Id         = ''.join(random.choice(string.ascii_lowercase) for i in range(10))
        self.Ticket     = ticket 
        self.Contract   = contract
        self.Symbol     = contract.Symbol
        self.Algorithm  = algorithm
        self.Open       = True
        self.Target     = target
        
    def ExpiringSoon(self): 
        expiry = self.Symbol.ID.Date + timedelta(hours=16)
        if ( (expiry - self.Algorithm.Time) < timedelta(minutes=10)):
            self.Algorithm.Log(f"{self.Symbol} Close to Expiry: {expiry} - {self.Algorithm.Time} < 10min" )
            return True
        else:
            return False
        
    def UnrealizedProfitPercent(self):
        return (self.Contract.Holdings.Price - self.Ticket.AverageFillPrice) / self.Contract.Holdings.Price
    
    def UnrealizedProfit(self):
        return 100*(self.Contract.Holdings.Price - self.Ticket.AverageFillPrice) * self.Ticket.Quantity
        
    def IsOpen(self):
        return self.Open
        
    def Close(self, reason):
        self.Open = False
        tag = f"Close | {reason} | {self.UnrealizedProfit()} Net"
        self.Algorithm.Liquidate(self.Symbol, tag)