Overall Statistics
Total Trades
388
Average Win
0.34%
Average Loss
-0.30%
Compounding Annual Return
15.598%
Drawdown
1.900%
Expectancy
0.332
Net Profit
21.286%
Sharpe Ratio
2.408
Probabilistic Sharpe Ratio
97.855%
Loss Rate
37%
Win Rate
63%
Profit-Loss Ratio
1.12
Alpha
0
Beta
0
Annual Standard Deviation
0.044
Annual Variance
0.002
Information Ratio
2.408
Tracking Error
0.044
Treynor Ratio
0
Total Fees
$717.80
Estimated Strategy Capacity
$20000000.00
Lowest Capacity Asset
MES XZDYPWUWC7I9
# https://www.quantconnect.com/tutorials/introduction-to-options/stochastic-processes-and-monte-carlo-method 

# *** https://towardsdatascience.com/improving-your-algo-trading-by-using-monte-carlo-simulation-and-probability-cones-abacde033adf *** 
    # https://kjtradingsystems.com/calculators.html 

# Additional Stats we can use after completing a Monte Carlo: 
# Risk of Ruin - https://en.wikipedia.org/wiki/Risk_of_ruin 
# Max and median drawdowns
# Annual rates of return
# return / drawdown ratios 


# Next steps:
# 1) write out steps to use QC backtests on the MC excel sheet. 
# 2) create a copy of the excel sheet that includes position sizing and/or max risk per trade
    # will need to research how to add those 

""" 
Disadvantages of using Monte Carlo: 
1) he first disadvantage is that the historical results need to be stable. By “stable” I am not referring to the actual trade results, but rather the trading method. 
    A trader who consistently trades the same system or approach has a stable method. 

2) if the market changes significantly. When a “step” change in the market occurs, Monte Carlo analysis is no longer valid. 
    - structural changes in the market could invalidate monte carlo results. 
    - do we want/need a different monte carlo for different regimes? (e.g. quantitative tightening vs easing, inflationary vs deflationary, deflation/reflation/inflation/growth)

3) serial correlation of trades: Monte Carlo assumes each trade is independent of the previous trade, but for many systems the current trade is dependent (or at least correlated) to the previous trade. 
    - there is a modified Monte Carlo approach that detect and deal with serial correlation. 

4) Monte Carlo does not fix or detect curvefitting, over optimization, hindsight bias, etc. 
    - If the backtest is overfit, the Monte carlo analysis will be as well and give false confidence. 
"""



class GeekyBlackCamel(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 11, 18)  # Set Start Date
        self.SetCash(100000)  # Set Strategy Cash
        self.AddEquity("SPY", Resolution.Minute)
        self.AddEquity("BND", Resolution.Minute)
        self.AddEquity("AAPL", Resolution.Minute)

    def OnData(self, data: Slice):
        '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
            Arguments:
                data: Slice object keyed by symbol containing the stock data
        '''

        if not self.Portfolio.Invested:
            self.SetHoldings("SPY", 0.33)
            self.SetHoldings("BND", 0.33)
            self.SetHoldings("AAPL", 0.33)
# https://www.quantconnect.com/tutorials/introduction-to-options/stochastic-processes-and-monte-carlo-method 

# *** https://towardsdatascience.com/improving-your-algo-trading-by-using-monte-carlo-simulation-and-probability-cones-abacde033adf *** 
    # https://kjtradingsystems.com/calculators.html 

# Additional Stats we can use after completing a Monte Carlo: 
# Risk of Ruin - https://en.wikipedia.org/wiki/Risk_of_ruin 
# Max and median drawdowns
# Annual rates of return
# return / drawdown ratios 


# Next steps:
# 1) write out steps to use QC backtests on the MC excel sheet. 
# 2) create a copy of the excel sheet that includes position sizing and/or max risk per trade
    # will need to research how to add those 

""" 
Disadvantages of using Monte Carlo: 
1) he first disadvantage is that the historical results need to be stable. By “stable” I am not referring to the actual trade results, but rather the trading method. 
    A trader who consistently trades the same system or approach has a stable method. 

2) if the market changes significantly. When a “step” change in the market occurs, Monte Carlo analysis is no longer valid. 
    - structural changes in the market could invalidate monte carlo results. 
    - do we want/need a different monte carlo for different regimes? (e.g. quantitative tightening vs easing, inflationary vs deflationary, deflation/reflation/inflation/growth)

3) serial correlation of trades: Monte Carlo assumes each trade is independent of the previous trade, but for many systems the current trade is dependent (or at least correlated) to the previous trade. 
    - there is a modified Monte Carlo approach that detect and deal with serial correlation. 

4) Monte Carlo does not fix or detect curvefitting, over optimization, hindsight bias, etc. 
    - If the backtest is overfit, the Monte carlo analysis will be as well and give false confidence. 
"""

import decimal
import pandas as pd
from datetime import datetime
from io import StringIO
from datetime import datetime, timedelta
import pickle
from math import floor
 
# page w/ all Micro symbols - https://www.quantconnect.com/datasets/algoseek-us-futures
# micros started trading 5/6/2019 

"""
Algo Overview

Intraday momentum strategy that uses the momentum gauge with the following chars.
- Uses the mg difference - postive or negative to decide whether to short or long
- Uses overnight price gap to further qualify that the above is a continuing trend

"""
# This DEV is to add another TP to lock in higher gains after significant moves in our direction. 

# Next Steps: 
# add new logic (done)
# qual check new logic (done) 
# optimize 
# compare against STG backtests to see if value is added or not (seems to help significantly)
# - will need to update to %s once that upgrade is used in production 



# FUTURES_CONTRACT = Futures.Indices.SP500EMini
FUTURES_CONTRACT = Futures.Indices.MicroSP500EMini

EQUITY = "SPXL"
BENCHMARK = "SPXL"

GAP_MIN = .002

START_TAKE_PROFIT_POINTS = 25
PROFIT_POINTS = 10
START_TAKE_PROFIT_POINTS_HIGHER = 50
PROFIT_POINTS_HIGHER = 30

LIVE = False

GO_LIVE_CLEAR = False
GO_LIVE_CLOSING_OVERWRITE = False
GO_LIVE_CLOSING_OVERWRITE_VALUE =  4569.50
GO_LIVE_CLOSING_VIEW = False

RISK_PERCENTAGE = .08 
MAX_LOSS_POINTS_AS_PERCENTAGE = .0087
MARGIN_REQ_PER_CONTRACT = 1800
# https://www.interactivebrokers.com/en/trading/margin-futures-fops.php
PRICE_PER_POINT = 5

class gapMomentum(QCAlgorithm):
    
    closeObjectStoreKey = "close_object"

    def Initialize(self):
        """ Initialize -- note SP500 MG start date data is 2018-11-28 """
        
        # -- Preset Times -- #
        
        # # Opto Set
        # self.SetStartDate(2021, 1, 1)
        # self.SetEndDate(2022, 3, 25)
        
        # # Long Set
        self.SetStartDate(2021, 1, 1)
        self.SetEndDate(2022, 5, 1)
        
        # 1 Year
        # self.SetStartDate(2021, 3, 25)
        # self.SetEndDate(2022, 3, 25)
        
        # Debug margin req
        # self.SetStartDate(2020, 3, 3)
        # self.SetEndDate(2020, 4, 25)
        
        # Current Set 
        # self.SetStartDate(2022, 1, 26)
        # self.SetEndDate(2022, 5, 3)
        
        # -- Optimizations Used -- #
        start_take_profit_points = self.GetParameter("start_take_profit_points")
        self.start_take_profit_points = START_TAKE_PROFIT_POINTS if start_take_profit_points is None else float(start_take_profit_points)
        
        profit_points = self.GetParameter("profit_points")
        self.profit_points = PROFIT_POINTS if profit_points is None else float(profit_points)
        
        gap_min = self.GetParameter("gap_min")
        self.gapMin = GAP_MIN if gap_min is None else float(gap_min)
        
        risk_percentage = self.GetParameter("risk_percentage")
        self.risk_percentage = RISK_PERCENTAGE if risk_percentage is None else float(risk_percentage) 
    
        max_loss_as_percentage = self.GetParameter("max_loss_as_percentage")
        self.max_loss_as_percentage = MAX_LOSS_POINTS_AS_PERCENTAGE if max_loss_as_percentage is None else float(max_loss_as_percentage) 
        
        start_take_profit_points_higher = self.GetParameter("start_take_profit_points_higher")
        self.start_take_profit_points_higher = START_TAKE_PROFIT_POINTS_HIGHER if start_take_profit_points_higher is None else float(start_take_profit_points_higher) 
        
        profit_points_higher = self.GetParameter("profit_points_higher")
        self.profit_points_higher = PROFIT_POINTS_HIGHER if profit_points_higher is None else float(profit_points_higher) 
    
    
        # -- Futures & Equity Setup & Go Live Clear -- #
        
        if GO_LIVE_CLEAR or GO_LIVE_CLOSING_OVERWRITE or GO_LIVE_CLOSING_VIEW:
            self.SetStartDate(2022, 2, 14)
            self.SetEndDate(2022, 2, 16)
        
        self.SetCash(30000)
        
        # Setup Futures Contract
        self.futuresContract = self.AddFuture(FUTURES_CONTRACT)
        self.futuresContract.SetFilter(5, 150)
        self.contracts = {}
        self.oi_contract = None
        
        # Brokerage Model
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
        
        
        # Add SPXL for market hours
        self.equity = self.AddEquity(EQUITY, Resolution.Minute).Symbol
        
        # Initial dwonload for back-test
        url = self.Download(address="https://www.dropbox.com/s/6w6ip6fvh2q7ciw/SP500_MG_Real_prod.csv?dl=1", headers={"Cache-Control": "no-cache", "Pragma": "no-cache"})
        self.MG_df = pd.read_csv(StringIO(url))
        self.MG_df['Date'] = self.MG_df['Date'].apply(Utils.dateModFromString)
        self.MG_df['Date'] = self.MG_df['Date'].apply(Utils.dateModToFormat)
    
        # Capture Closing price
        self.Schedule.On(self.DateRules.EveryDay(EQUITY), self.TimeRules.BeforeMarketClose(self.equity, 0), self.captureClosingPrice)
        
        # Scheduled Entry
        self.Schedule.On(self.DateRules.EveryDay(EQUITY), self.TimeRules.AfterMarketOpen(self.equity, 0), self.openPosition)
        
        # Initilization Vars
        self.closingPrice = None
        self.priceGap = None
        self.price = None
        self.positionType = None
        self.openingPrice = 0
        self.security = None
        self.gapDown = None
        self.gapUp = None
        self.closePrice = None
        self.positionType = None
        self.boughtIn = False
        self.sitOut = False
        self.textProfit = True
        
    def OnData(self, data):
        """ Sorts Futures Contracts by OpenInterest """
        
        self.data = data
        
        if GO_LIVE_CLOSING_VIEW:
            value = self.ObjectStore.Read(self.closeObjectStoreKey)
            deserialized_value = bytes(self.ObjectStore.ReadBytes(self.closeObjectStoreKey))
            self.closingPrice = pickle.loads(deserialized_value)
            self.Debug("Current closingPrice stored: " + str(self.closingPrice))
            return
            
        if GO_LIVE_CLOSING_OVERWRITE:
            self.ObjectStore.SaveBytes(self.closeObjectStoreKey, pickle.dumps(GO_LIVE_CLOSING_OVERWRITE_VALUE))
            self.Debug("Manually stored closingPrice: " + str(GO_LIVE_CLOSING_OVERWRITE_VALUE))
            return
        
        if GO_LIVE_CLEAR:
            self.ObjectStore.Delete(self.closeObjectStoreKey)
            return

        # Iterates through futures contracts
        for chain in data.FutureChains:
            contracts = sorted([k for k in chain.Value if k.OpenInterest > 0],
                key=lambda k : k.OpenInterest, reverse=True)

            if not contracts:
                continue

            # Chooses Futures Contract with most open interest
            self.oi_contract = contracts[0]
                
            # Uses Futures Contract with most Open Interest
            self.symbol = self.oi_contract.Symbol
            if self.symbol not in self.contracts:
                self.contracts[self.symbol] = SymbolData(self, self.symbol)
                self.contracts[self.symbol].consolidators['INTRADAY_CONSOLIDATOR'].DataConsolidated += self.IntradayBars
            
            # self.Debug(" Debug for chain ")    
            self.security = self.Securities[self.symbol]
            self.price = self.security.Price
            
            self.initialMargin = self.security.BuyingPowerModel.InitialOvernightMarginRequirement
            # add to duplicate in openPosition 
            
    def openPosition(self):
        """ Opens Position Short or Long """
        
        # Return for hardcoding / clearing
        if GO_LIVE_CLEAR == True or GO_LIVE_CLOSING_OVERWRITE == True or GO_LIVE_CLOSING_VIEW == True:
            return
        
        # Catch if OnData didn't run first, if it hasn't, set security
        if self.security is None:
            for chain in self.data.FutureChains:
                contracts = sorted([k for k in chain.Value if k.OpenInterest > 0],
                    key=lambda k : k.OpenInterest, reverse=True)
    
                if not contracts:
                    continue
    
                # Chooses Futures Contract with most open interest
                self.oi_contract = contracts[0]
                    
                # Uses Futures Contract with most Open Interest
                if self.symbol not in self.contracts:
                    self.contracts[self.symbol] = SymbolData(self, self.symbol)
                    self.contracts[self.symbol].consolidators['INTRADAY_CONSOLIDATOR'].DataConsolidated += self.IntradayBars
                self.security = self.Securities[self.symbol]
                self.price = self.security.Price
                
                # Debug log to indicate that OnData isn't running before open position
                self.Debug(str(self.Time) + " : Could not find existing futures info data from onData")
                
        # Read in stored value for closePrice to closingPrice
        if self.ObjectStore.ContainsKey(self.closeObjectStoreKey):
            value = self.ObjectStore.Read(self.closeObjectStoreKey)
            deserialized_value = bytes(self.ObjectStore.ReadBytes(self.closeObjectStoreKey))
            self.closingPrice = pickle.loads(deserialized_value)
        
        # Calculate price gap for MG
        if self.price is not None and self.closingPrice is not None and self.priceGap is None:
            
            self.priceGap = self.price - self.closingPrice
            self.gapUp = self.closingPrice * (1+self.gapMin)
            self.gapDown = self.closingPrice * (1-self.gapMin)
            # self.Debug(str(self.Time) + " Set at 'Open'- Old Closing Price: " + str(self.closingPrice) + " -- Open Price: " + str(self.price) + " -- Price Gap: " + str(self.priceGap) + " -- gap Up: " + str(self.gapUp) + " -- gap down: " + str(self.gapDown))
            self.Notify.Sms("+12035831419", "Set at Open- Old Closing Price: " + str(self.closingPrice) + " -- Open Price: " + str(self.price) + " -- Price Gap: " + str(self.priceGap) + " -- gap Up: " + str(self.gapUp) + " -- gap down: " + str(self.gapDown))
            self.Notify.Sms("+15059771023", "Set at Open- Old Closing Price: " + str(self.closingPrice) +  "-- Open Price: " + str(self.price) + " -- Price Gap: " + str(self.priceGap) + " -- gap Up: " + str(self.gapUp) + " -- gap down: " + str(self.gapDown))
            
            # Set start position at beginning of day
            self.startInvestment = self.Portfolio.TotalPortfolioValue
            self.boughtIn = False
            self.sitOut = False
            self.textProfit = True
            self.keepPrice = self.price
            self.openingPrice = self.price
        
            # Cause a reinitilization of the file download every open position if LIVE
            if LIVE == True:
                url = self.Download(address="https://www.dropbox.com/s/6w6ip6fvh2q7ciw/SP500_MG_Real_prod.csv?dl=1", headers={"Cache-Control": "no-cache", "Pragma": "no-cache"})
                self.MG_df = pd.read_csv(StringIO(url))
    
                # Grab last known entry, which should be yesterday
                self.dayUsed = self.MG_df.iloc[-1][0]
                self.MG_positive = float(self.MG_df.iloc[-1][1])
                self.MG_negative = float(self.MG_df.iloc[-1][2])
                self.MG_difference = float(self.MG_df.iloc[-1][3])
                self.MG_daily_change = float(self.MG_df.iloc[-1][4])
            
                # Send text at 7:30 mountain with MG info
                # self.Debug(str(self.Time) + " -- Using Day: " + str(self.dayUsed) + " -- MG Difference: " + str(self.MG_difference) + " -- MG Daily Change: " + str(self.MG_daily_change))
                self.Notify.Sms("+12035831419", str(self.Time) + " -- Using Day: " + str(self.dayUsed) + " -- MG Difference: " + str(self.MG_difference) + " -- MG Daily Change: " + str(self.MG_daily_change))
                self.Notify.Sms("+15059771023", str(self.Time) + " -- Using Day: " + str(self.dayUsed) + " -- MG Difference: " + str(self.MG_difference) + " -- MG Daily Change: " + str(self.MG_daily_change))
        
        # self.Debug(str(self.Time) + " opening Portfolio Margin: " + str(self.Portfolio.MarginRemaining)) 
        
    def captureClosingPrice(self):
        """ Capture closing price """
        
        # Closing Price information
        self.closePrice = self.price
        self.Notify.Sms("+12035831419", "Closing Price: " + str(self.closePrice))
        self.Notify.Sms("+15059771023", "Closing Price: " + str(self.closePrice))
        self.priceGap = None
            
        if self.closePrice is not None:
            self.ObjectStore.SaveBytes(self.closeObjectStoreKey, pickle.dumps(self.closePrice))
            # self.Debug(str(self.Time) + ' -  Use Close price of: ' + str(self.closePrice))

        
    def IntradayBars(self, sender, bar):
        """ Creates IntraDay Bars """
        
        if not self.oi_contract or self.oi_contract.Symbol != bar.Symbol:
            return
        
        # Check if Today's date in MG File for back-testing
        try:
            current_time = f'{self.Time.year}-{self.Time.month}-{self.Time.day}'
            todayidxList = self.MG_df.index[self.MG_df['Date']==current_time].tolist()
            todayIDX = todayidxList[0] 
            self.todaysValues = True
        except:
            self.todaysValues = False
            
        # Use Yesterday's numbers to prevent look-ahead bias
        if self.todaysValues == True:
            self.dayUsed = self.MG_df.loc[todayIDX-1][0]
            self.MG_positive = float(self.MG_df.loc[todayIDX-1][1])
            self.MG_negative = float(self.MG_df.loc[todayIDX-1][2])
            self.MG_difference = float(self.MG_df.loc[todayIDX-1][3])
            self.MG_daily_change = float(self.MG_df.loc[todayIDX-1][4])
 
        # Time Logic
        current_time = self.Time.strftime("%H:%M")
        current_time_as_date = datetime.strptime(current_time, '%H:%M').time()
        
        # This is a back-up if open position somehow doesn't get the priceGap, an OK use of a `==`
        if current_time_as_date == datetime.strptime('9:30', '%H:%M').time():
            
            if self.priceGap is None:
                if self.ObjectStore.ContainsKey(self.closeObjectStoreKey):
                    value = self.ObjectStore.Read(self.closeObjectStoreKey)
                    deserialized_value = bytes(self.ObjectStore.ReadBytes(self.closeObjectStoreKey))
                    self.closingPrice = pickle.loads(deserialized_value)
                    
                    self.priceGap = self.price - self.closingPrice
                    self.gapUp = self.closingPrice * (1+self.gapMin)
                    self.gapDown = self.closingPrice * (1-self.gapMin)
                    # self.Debug(str(self.Time) + "Set at '==' - Old Closing Price: " + str(self.closingPrice) + " -- Open Price: " + str(self.price) + " -- Price Gap: " + str(self.priceGap) + " -- gap Up: " + str(self.gapUp) + " -- gap down: " + str(self.gapDown))
                    self.Notify.Sms("+12035831419", "Set at == - Old Closing Price: " + str(self.closingPrice) + " -- Open Price: " + str(self.price) + " -- Price Gap: " + str(self.priceGap) + " -- gap Up: " + str(self.gapUp) + " -- gap down: " + str(self.gapDown))
                    self.Notify.Sms("+15059771023", "Set at == - Old Closing Price: " + str(self.closingPrice) +  "-- Open Price: " + str(self.price) + " -- Price Gap: " + str(self.priceGap) + " -- gap Up: " + str(self.gapUp) + " -- gap down: " + str(self.gapDown))
            
                    # Set start position at beginning of day
                    self.startInvestment = self.Portfolio.TotalPortfolioValue
                    self.boughtIn = False
                    self.sitOut = False
                    self.textProfit = True
                    self.keepPrice = self.price
                    self.openingPrice = self.price
                    
                if LIVE == True:
                    url = self.Download(address="https://www.dropbox.com/s/6w6ip6fvh2q7ciw/SP500_MG_Real_prod.csv?dl=1", headers={"Cache-Control": "no-cache", "Pragma": "no-cache"})
                    self.MG_df = pd.read_csv(StringIO(url))
        
                    # Grab last known entry, which should be yesterday
                    self.dayUsed = self.MG_df.iloc[-1][0]
                    self.MG_positive = float(self.MG_df.iloc[-1][1])
                    self.MG_negative = float(self.MG_df.iloc[-1][2])
                    self.MG_difference = float(self.MG_df.iloc[-1][3])
                    self.MG_daily_change = float(self.MG_df.iloc[-1][4])
                
                    # self.Debug(str(self.Time) + " -- Using Day: " + str(self.dayUsed) + " -- MG Difference: " + str(self.MG_difference) + " -- MG Daily Change: " + str(self.MG_daily_change))
                    self.Notify.Sms("+12035831419", str(self.Time) + " -- Using Day: " + str(self.dayUsed) + " -- MG Difference: " + str(self.MG_difference) + " -- MG Daily Change: " + str(self.MG_daily_change))
                    self.Notify.Sms("+15059771023", str(self.Time) + " -- Using Day: " + str(self.dayUsed) + " -- MG Difference: " + str(self.MG_difference) + " -- MG Daily Change: " + str(self.MG_daily_change))
                
        # Begin to allow buy in process when price gap is exceeded
        if current_time_as_date >= datetime.strptime('9:30', '%H:%M').time() and current_time_as_date <= datetime.strptime('16:55', '%H:%M').time() and self.sitOut == False:
            
            # For Initial BuyIn
            if not self.security.Invested and self.boughtIn == False and self.sitOut == False and self.priceGap:
                
                """ 
                It's not enough to wish, dream, hope. Even children know this. We must set sail into the sea of uncertainty. 
                We must meet fear face-to-face. We must take our dreams as maps for a greater journey. 
                Dreams, to come true, need a good story. So go live one.
                    — Vironika Tugaleva
                """
                
                 # -- Long -- #
                if float(self.MG_daily_change) > 0:
                    self.positionType = 'Long'
                    
                    
                    # Calculate cutOffPrice using percentage instead of static number 
                    self.max_loss_points = self.price * self.max_loss_as_percentage
                    
                    self.cutOffPrice = self.price - self.max_loss_points
                    
                    if self.price > self.gapUp:
                        
                        self.contractsToTradeMaxByRisk = floor( (self.Portfolio.TotalPortfolioValue * self.risk_percentage) / (self.max_loss_points * PRICE_PER_POINT)) 
                        self.contractsToTradeMaxByEquity = floor( self.Portfolio.TotalPortfolioValue / MARGIN_REQ_PER_CONTRACT )
                        
                        if self.contractsToTradeMaxByEquity < self.contractsToTradeMaxByRisk:
                            self.contractsToTrade = self.contractsToTradeMaxByEquity
                        else:
                            self.contractsToTrade = self.contractsToTradeMaxByRisk
                        
                        self.MarketOrder(self.symbol, 1)                                                                        # Changed to 1 for MC analysis__________________________________________________
                        
                        self.Debug(str(self.Time) + " Buy price: " + str(self.price) + " long contractsToTrade: " + str(self.contractsToTrade) + " portfolio value: " + str(self.Portfolio.TotalPortfolioValue) )
                        # self.Debug(str(self.Time) + " risk%: " + str(self.risk_percentage) + " risk $: " + str(self.contractsToTrade * self.max_loss_points * 5) + " max_loss_pts: " +str(self.max_loss_points))
                        
                        self.Notify.Sms("+15059771023", "IntraDay Gap Momentum Playing Long Today at algo price: " + str(self.price))
                        self.Notify.Sms("+12035831419", "IntraDay Gap Momentum Playing Long Today at algo price: " + str(self.price))
                        
                        self.boughtIn = True
                        self.stopLossTriggered = False
                        self.stopLossTriggeredHigher = False
                        self.keepPrice = self.price
                    
                        ## Global SL
                        self.stopMarketTicket = self.StopMarketOrder(self.symbol, -1, self.cutOffPrice)                         # Changed to 1 for MC analysis__________________________________________________
                        
                # -- Short -- #
                elif float(self.MG_daily_change) < 0:
                    self.positionType = 'Short'
                    
                    # Calculate cutOffPrice using percentage instead of static number
                    self.max_loss_points = self.price * self.max_loss_as_percentage
                    self.cutOffPrice = self.price + self.max_loss_points
                    
                    if self.price < self.gapDown:
                        
                        self.contractsToTradeMaxByRisk = floor( (self.Portfolio.TotalPortfolioValue * self.risk_percentage) / (self.max_loss_points * PRICE_PER_POINT)) 
                        self.contractsToTradeMaxByEquity = floor( self.Portfolio.TotalPortfolioValue / MARGIN_REQ_PER_CONTRACT )
                        
                        if self.contractsToTradeMaxByEquity < self.contractsToTradeMaxByRisk:
                            self.contractsToTrade = self.contractsToTradeMaxByEquity
                        else:
                            self.contractsToTrade = self.contractsToTradeMaxByRisk
                            
                        self.MarketOrder(self.symbol, -1)                                                                        # Changed to 1 for MC analysis__________________________________________________
                        
                        self.Debug(str(self.Time) + " Sell price: " + str(self.price) + " short contractsToTrade: " + str(self.contractsToTrade) + " portfolio value: " + str(self.Portfolio.TotalPortfolioValue))
                        # self.Debug(str(self.Time) + " risk%: " + str(self.risk_percentage) + " risk $: " + str(self.contractsToTrade * self.max_loss_points * 5) + " max_loss_pts: " +str(self.max_loss_points))
                        
                        self.Notify.Sms("+15059771023", "IntraDay Gap Momentum Playing Short Today at algo price: " + str(self.price))
                        self.Notify.Sms("+12035831419", "IntraDay Gap Momentum Playing Short Today at algo price: " + str(self.price))
                        
                        self.boughtIn = True
                        self.stopLossTriggered = False
                        self.stopLossTriggeredHigher = False
                        self.keepPrice = self.price
                        
                        ## Global SL
                        self.stopMarketTicket = self.StopMarketOrder(self.symbol, 1, self.cutOffPrice)                              # Changed to 1 for MC analysis_______________________________________________
                    
            # -- Dynamic Exit -- #
            if self.security.Invested and self.stopLossTriggered == False:
                
                # For Long, set a take profit after PROFIT_BUFFER_POINTS exceeded
                if self.positionType == 'Long':
                    if self.price >= (self.keepPrice + self.start_take_profit_points):
                        
                        # Update Global SL
                        self.Notify.Sms("+15059771023", "TP StopLoss Triggered At: " + str(self.price))
                        self.Notify.Sms("+12035831419", "TP StopLoss Triggered At: " + str(self.price))
                        
                        self.stopLossTriggered = True
                        self.keepPoints = self.keepPrice + self.profit_points 
                        updateSettings = UpdateOrderFields()
                        updateSettings.StopPrice =  decimal.Decimal(round(self.keepPoints, 2))
                        response = self.stopMarketTicket.Update(updateSettings)
                    
                    
                # For Short, set a take profit after PROFIT_BUFFER_POINTS exceeded
                if self.positionType == 'Short':
                    if self.price <= (self.keepPrice - self.start_take_profit_points):
                        
                        # Update Global SL
                        self.Notify.Sms("+15059771023", "TP StopLoss Triggered At: " + str(self.price))
                        self.Notify.Sms("+12035831419", "TP StopLoss Triggered At: " + str(self.price))
                        
                        self.stopLossTriggered = True
                        self.keepPoints = self.keepPrice - self.profit_points
                        updateSettings = UpdateOrderFields()
                        updateSettings.StopPrice =  decimal.Decimal(round(self.keepPoints, 2))
                        response = self.stopMarketTicket.Update(updateSettings)
                        
            # Check if higher take profit threshold is met & raise the dynamic Exit
            if self.security.Invested and self.stopLossTriggeredHigher == False:  
                if self.positionType == 'Long':
                    if self.price >= (self.keepPrice + self.start_take_profit_points_higher):
                        
                        # Update Global SL
                        self.Notify.Sms("+15059771023", "Higher TP StopLoss Triggered At: " + str(self.price))
                        self.Notify.Sms("+12035831419", "Higher TP StopLoss Triggered At: " + str(self.price))
                        
                        self.stopLossTriggeredHigher = True
                        self.keepPoints = self.keepPrice + self.profit_points_higher 
                        updateSettings = UpdateOrderFields()
                        updateSettings.StopPrice =  decimal.Decimal(round(self.keepPoints, 2))
                        response = self.stopMarketTicket.Update(updateSettings)
                        
                        self.Debug(str(self.Time) + " Higher TP start - short algo price: " + str(self.price) + " keepPoints higher: " + str(self.keepPoints) + " keepPrice: " +str(self.keepPrice))
                
                        
                if self.positionType == 'Short':
                    if self.price <= (self.keepPrice - self.start_take_profit_points_higher):
                        
                        # Update Global SL
                        self.Notify.Sms("+15059771023", "Higher TP StopLoss Triggered At: " + str(self.price))
                        self.Notify.Sms("+12035831419", "Higher TP StopLoss Triggered At: " + str(self.price))
                        
                        self.stopLossTriggeredHigher = True
                        self.keepPoints = self.keepPrice - self.profit_points_higher
                        updateSettings = UpdateOrderFields()
                        updateSettings.StopPrice =  decimal.Decimal(round(self.keepPoints, 2))
                        response = self.stopMarketTicket.Update(updateSettings)
                        
                        self.Debug(str(self.Time) + " Higher TP start - short algo price: " + str(self.price) + " keepPoints higher: " + str(self.keepPoints) + " keepPrice: " +str(self.keepPrice))
                

            # Check whether max_loss_stop is exceeded, if so sit out
            if self.positionType == 'Long':
                if self.price <= self.cutOffPrice:
                    self.sitOut = True
                    if self.security.Invested:
                        gapMomentum.closePosition(self)
                        
            if self.positionType == 'Short':
                if self.price >= self.cutOffPrice:
                    self.sitOut = True
                    if self.security.Invested:
                        gapMomentum.closePosition(self)
                    
        # Liquadate always before end of day          
        if self.security.Invested and current_time_as_date >= datetime.strptime('16:55', '%H:%M').time():
            # self.Debug('Liqudated Close of Day ' + str(self.positionType) + ': ' + str(self.Time))
            gapMomentum.closePosition(self)
            
        # Bought in, and have closed position
        if not self.security.Invested and self.boughtIn == True and self.textProfit == True:
            
            self.textProfit = False
            # Get Daily Profit
            profit = round((self.Portfolio.TotalPortfolioValue  - self.startInvestment), 2)
            # self.Debug('Daily Profit: '+str(profit))
            
            # Msg
            if self.positionType == 'Short':
                self.Notify.Sms("+12035831419", "IntraDay Gap Momentum Sold Short Today for profit: " + str(profit))
                self.Notify.Sms("+15059771023", "IntraDay Gap Momentum Sold Short Today for profit: " + str(profit))
                 
            if self.positionType == 'Long':
                self.Notify.Sms("+12035831419", "IntraDay Gap Momentum Sold Long Today for profit: " + str(profit))
                self.Notify.Sms("+15059771023", "IntraDay Gap Momentum Sold Long Today for profit: " + str(profit))
                
    def closePosition(self):
        """ close position """
        
        ### Liquadate and Text
        self.Liquidate()
        
        # -- Clean up Stop Losses -- #
        
        # Gets Open Tickets related to stop loss
        open_tickets = self.Transactions.GetOpenOrderTickets()
        
        # Parses through open tickets looking for stop loss
        stop_tickets = [ticket for ticket in open_tickets if ticket.OrderType == OrderType.StopMarket]

        # If More then one active stop loss ticket, cancel current stop losses, create ticket
        if len(stop_tickets) > 1 :
            for ticket in stop_tickets:
                ticket.Cancel('Canceled stop loss tickets')
                create_ticket = True
        
    def OnSecuritiesChanged(self, changes):
        """ QC function for when futures contract changes """
        
        for security in changes.RemovedSecurities:
            self.symbol = security.Symbol
            symbolData = self.contracts.get(self.symbol, None)     

class SymbolData:
    """ For data consolidation with 5 minute bars"""
    def __init__(self, algorithm, symbol):
        self.consolidators = {
            '1D': TradeBarConsolidator(timedelta(1)),
            'INTRADAY_CONSOLIDATOR': TradeBarConsolidator(timedelta(minutes=5))
        }
        
        # Used for Intraday Function
        self.atr = AverageTrueRange(14, MovingAverageType.Simple)
        algorithm.RegisterIndicator(symbol, self.atr, self.consolidators['INTRADAY_CONSOLIDATOR'])
        
class Utils:
    """ Utility Functions """
    
    @staticmethod
    def dateModFromString(x):
        return datetime.strptime(x, '%Y-%m-%d')
    
    @staticmethod
    def dateModToFormat(x):
        return x.strftime("%Y-%-m-%-d")