Overall Statistics
from AlgorithmImports import *
import numpy as np

class MyExecution(ExecutionModel):

    def __init__(self,algo):

        self.algo = algo  # create the algo instance
        self.orderCounter = 1

    def Execute(self, algorithm: QCAlgorithm, targets: List[PortfolioTarget]) -> None:

        # update the complete set of portfolio targets with the new targets
        self.algo.targetsCollection.AddRange(targets)

        if not self.algo.targetsCollection.IsEmpty:
            
            for target in self.algo.targetsCollection.Values:
                
                # check order entry conditions
                if target.Quantity != 0: # Is this neccessary?
                    self.algo.Debug(f"target.Quantity:{target.Quantity}")

                    for _ in range(abs(int(target.Quantity))*self.algo.orderMultiplier):
                        orderPrice = self.GetOrderPrice(target, algorithm)
                        orderProperties = self.GetOrderProperties(target, algorithm)

                        # Only Place Order as Long as Some conditions are met - Here only time should be before say 1 hour after market opens & 
                        #initiate Trade class
                        trade = Trade(algorithm)
                        ticket = trade.placeMainOrder(target.Symbol, np.sign(target.Quantity)*1, orderPrice, orderProperties)
                        self.algo.tradesCollection[str(ticket.OrderId)] = trade
                    # ticket.UpdateTag('LumberCopyNasdaq' + '_ID_' + str(ticket.OrderId))                    
                    
            self.algo.targetsCollection.Clear()
            


    def GetOrderPrice(self, target, algorithm):
        orderPrice = None
        bid = algorithm.Securities[target.Symbol].BidPrice
        ask = algorithm.Securities[target.Symbol].AskPrice      
        minOrderPrice = min(bid,ask)
        maxOrderPrice = max(bid,ask)

        if target.Quantity > 0:
            orderPrice = round(minOrderPrice*(1+self.algo.premiumDiscount),1)

        elif target.Quantity < 0:
            if self.orderCounter == 1:  # This is temporary so we can see 2 different Asks for 2 positions
                orderPrice = round(maxOrderPrice*(1-self.algo.premiumDiscount),1)
            else:
                orderPrice = round(maxOrderPrice*(1-self.algo.premiumDiscount+0.01),1) # Less Competitive            

        self.orderCounter +=1
        return orderPrice

    
    def GetOrderProperties(self, target, algorithm):
        orderProperties = None
       
        # Set a Limit Order (MainOrder) to be good until Lumber Close
        self.algo.order_properties.TimeInForce = TimeInForce.GoodTilDate(algorithm.Time.replace(hour=16, minute=0, second=  0, microsecond=0))
        orderProperties = self.algo.order_properties
        return orderProperties


class Trade():

    def __init__(self,algo):
        self.mainOrderTicket=None
        self.stopLossTicket=None
        self.takeProfitTicket=None
        self.algo=algo

    def placeMainOrder(self,symbol,quantity,orderPrice, orderProperties=None,tag=None):      
        self.mainOrderTicket=self.algo.LimitOrder(symbol=symbol, quantity=quantity, limitPrice=orderPrice, orderProperties = orderProperties,tag=tag) # , orderProperties= self.algo.order_properties
        return self.mainOrderTicket

    def updateMainOrder(self, price):      
        response = self.mainOrderTicket.UpdateLimitPrice(price)
        if response.IsSuccess:
            self.algo.Debug("MainOrder with Order_id {} Updated Successfully".format(self.mainOrderTicket.OrderId))
            return True
   
    def getMainOrderId(self):
        self.mainOrderTicket.OrderId

    def placeStopLossOrder(self,quantity,stopPrice,limitPrice,orderProperties=None):
        self.stopLossTicket=self.algo.StopLimitOrder(symbol=self.mainOrderTicket.Symbol, quantity=quantity, stopPrice=stopPrice, limitPrice=limitPrice,tag=str(self.mainOrderTicket.OrderId)) #, orderProperties= self.algo.order_properties
        return self.stopLossTicket

    def placeTakeProfitOrder(self,quantity,limitPrice,orderProperties=None):
        self.takeProfitTicket=self.algo.LimitOrder( symbol=self.mainOrderTicket.Symbol, quantity=quantity, limitPrice=limitPrice,tag=str(self.mainOrderTicket.OrderId)) #, orderProperties= self.algo.order_properties
        return self.takeProfitTicket
  
    def cancelStopLoss(self):
        response=self.stopLossTicket.Cancel()
        if response.IsSuccess:
            self.algo.Debug("StopLossOrder with Order_id {} connected to main order {} Cancelled Successfully".format(self.stopLossTicket.OrderId,self.stopLossTicket.Tag))
            self.stopLossTicket=None
            return True

    def cancelTakeProfit(self):
        response=self.takeProfitTicket.Cancel()
        if response.IsSuccess:
            self.algo.Debug("TakeProfit Order with Order_id {} connected to main order {} Cancelled Successfully".format(self.takeProfitTicket.OrderId,self.takeProfitTicket.Tag))
            self.takeProfitTicket = None
            return True

    def cancelMainOrder(self):
        response=self.MainOrderTicket.Cancel()
        if response.IsSuccess:
            self.algo.Debug("MainOrder with Order_id {}  Cancelled Successfully".format(self.mainOrderTicket.OrderId))           
            # del self.algo.tradesCollection[main_id]
            return True
    #return main order ticket
    def getMainOrderTicket(self):
        return self.algo.Transactions.GetOrderTicket(self.mainOrderTicket.OrderId)

    #return stop loss ticket
    def getStopLossOrderTicket(self):
        return self.algo.Transactions.GetOrderTicket(self.stopLossOrderTicket.OrderId)
    


    #return take profit ticket
    def getTakeProfitTicket(self):
        self.algo.Transactions.GetOrderTicket(self.takeProfitTicket.OrderId)

    #cancel order if it voilates condition of fill

    def cancelIfVoilates(self,percent):

        #get main order ticket

        ticket=self.getMainOrderTicket()


        #get open price of security 

        openPrice=self.algo.Securities[ticket.Symbol].Open


        # calculate the percentage change between the fill and open price

        changeInFillOpen= ((ticket.AverageFillPrice-openPrice)/openPrice)*100


        if changeInFillOpen > abs(percent):

            # cancel main order

            response=self.cancelMainOrder()
            return True


        else:
            return  False



from AlgorithmImports import *

class NasdaqAlpha(AlphaModel):
    def __init__(self, algo):

        # Save an Instance of our Main Algorithm
        self.algo = algo 

        # variable to hold nasdaq percentage change
        self.algo.percentageChange=None

        # Check Entry Condition only 30 seconds prior to Market Open
        self.entryTimeStart = self.algo.Time.replace(hour=9, minute=59, second=30, microsecond=0)
        self.entryTimeEnd   = self.algo.Time.replace(hour=10, minute=0, second=  1, microsecond=0)

        # Rollin windows for yesterday's close
        self.nasdaqYesterdayClose = RollingWindow[float](1)
        self.lumberYesterdayClose = RollingWindow[float](1)

        #insight arguments  
        self.insightPeriod = timedelta(hours = 1)
        self.insightDailyCount = 0

        # DICT TO HOLD INSIGHT TIME
        self.insightsTimeBySymbol = {}

        #rolling window to handle latest bid and ask
        self.latestBidRolling = RollingWindow[float](1)
        self.latestAskRolling = RollingWindow[float](1)
        self.latestBidSizeRolling = RollingWindow[float](1)
        self.latestAskSizeRolling = RollingWindow[float](1)

        # LIST AND COLLECTION FOR INSIGHTS
        self.insights = []
        self.insightCollection = InsightCollection()

        
    def OnLumberOpen(self):        

        #Resetting Latest Bid And Ask Rolling Window
        self.latestAskRolling.Reset()
        self.latestBidRolling.Reset()

        # GET LUMBER YESTERDAY'S CLOSE
        lumber_history = self.algo.History(self.algo.lumberSymbol,7,Resolution.Daily)      
        for bar in lumber_history.itertuples():
            self.lumberYesterdayClose.Add(bar.close)

        # GET NASDAQ'S YESTERDAY CLOSE
        nasdaq_history = self.algo.History(self.algo.nasdaqSymbol,7,Resolution.Daily)       
        for bar in nasdaq_history.itertuples():
            self.nasdaqYesterdayClose.Add(bar.close)

        self.insightDailyCount = 0 # Reset it to 0 every morning 
        # self.algo.Debug(f"Yesterday's Date:{self.algo.Time} Nasdaq Close:{self.nasdaqYesterdayClose[0]}, Lumber Close: {self.lumberYesterdayClose[0]}")

    def OnLumberClose(self):
        # Ideally this code should be inside EndOfDay in our Main class
        # GeneratedTimeUtc : Gets the utc time this insight was generated
        # CloseTimeUtc : Gets the insight's prediction end time. This is the time when this insight prediction 
        # is expected to be fulfilled. This time takes into account market hours, weekends, as well as the symbol's data resolution

        insight_properties = ['Symbol','GeneratedTimeUtc','CloseTimeUtc','Direction','EstimatedValue','Weight','Id','Magnitude','Confidence','Period','Score','IsActive','IsExpired']      

        for insight in self.insights:
            for prop in insight_properties:
                if prop in ['GeneratedTimeUtc','CloseTimeUtc']:
                    value = getattr(insight, prop) - timedelta(hours = 5) # Converting UTC to EST
                else:
                    value = getattr(insight, prop)
                # self.algo.Debug(f"{prop}:{value}")

 
    def OnSecuritiesChanged(self, algorithm,changes):       
        for security in changes.AddedSecurities:
            if security.Symbol == self.algo.lumberSymbol:
                # schedule an event to trigger before lumber market opens to get yesterday close for both nasdaq and lumber            
                algorithm.Schedule.On(algorithm.DateRules.EveryDay(security.Symbol), algorithm.TimeRules.AfterMarketOpen(security.Symbol, -1), self.OnLumberOpen)
                # algorithm.Schedule.On(algorithm.DateRules.EveryDay(security.Symbol), algorithm.TimeRules.BeforeMarketClose(security.Symbol, 2), self.OnLumberClose)

        for security in changes.RemovedSecurities:
            if security.Symbol in self.insightsTimeBySymbol:
                # REMOVE SECURITY FROM DICT
                self.insightsTimeBySymbol.pop(security.Symbol)

    def Update(self, algorithm, data): 
        
        # Return empty list if outside our CheckTime (just 30 seconds prior to MarketOpen)
        if algorithm.Time < self.entryTimeStart or algorithm.Time > self.entryTimeEnd:
            return []

        insight = None

        # Check if yesterday's Lumber & Nasdaq close if ready
        if self.lumberYesterdayClose.IsReady and self.nasdaqYesterdayClose.IsReady:
             for security in data.Keys:
                # algorithm.Debug(f'{security}')
                ticks=data.Ticks[security]                
                if security == self.algo.nasdaqSymbol:
                    for tick in ticks:
                        if tick.TickType == TickType.Trade:
                            self.algo.percentageChange=((tick.Price - self.nasdaqYesterdayClose[0])/self.nasdaqYesterdayClose[0])*100
                            
                            # if int(self.algo.nasdaqROCP.Current.Value) != 0:
                            #     algorithm.Debug(f"self.algo.percentageChange:{self.algo.percentageChange}, \
                            #     self.algo.nasdaqROCP:{self.algo.nasdaqROCP.Current.Value}, self.algo.nasdaqDailyChange52High:{self.algo.nasdaqDailyChange52High.Current.Value}, self.algo.nasdaqDailyChange52Low:{self.algo.nasdaqDailyChange52Low.Current.Value}  ")

                if security == self.algo.lumberSymbol:
                    for tick in ticks:
                        if tick.TickType == TickType.Quote:
                            # algorithm.Debug(f"bid is {tick.BidPrice} bidVolume {tick.BidSize} ask is {tick.AskPrice} askVolume:{tick.AskSize}")                     

                            # check if tick ask is  equal to 0 and there is no ask price in rolling window
                            if int(tick.AskPrice) == 0 and not self.latestAskRolling.IsReady:
                                ask = algorithm.Securities[security].AskPrice

                                # askVol = algorithm.Securities[security].AskVolume
                                # algorithm.Debug(f"ask was {tick.AskPrice} so using last ask {ask}")                     
                            
                            # check if tick ask is  equal to 0 & there is  ask price in rolling window
                            elif int(tick.AskPrice) == 0 and self.latestAskRolling.IsReady:
                                ask = self.latestAskRolling[0]
                                # algorithm.Debug(f"ask was {tick.AskPrice} so using last ask from rolling window {ask}")                                                 
                            else:
                                ask = tick.AskPrice
                                askVol = tick.AskSize
                                self.latestAskRolling.Add(ask) # adding the ask to rolling window
                                self.latestAskSizeRolling.Add(askVol)

                            # check if tick bid is  equal to 0 & there is no bid price in rolling window
                            if int(tick.BidPrice) == 0 and not self.latestBidRolling.IsReady:
                                bid = algorithm.Securities[security].BidPrice
                                # algorithm.Debug(f"bid was {tick.BidPrice} so using last bid {bid}")                     
                            
                            # check if tick bid is  equal to 0 & there is  Bid price in rolling window
                            elif int(tick.BidPrice) == 0 and self.latestBidRolling.IsReady:
                                bid = self.latestBidRolling[0]
                                # algorithm.Debug(f"bid was {tick.BidPrice} so using last bid from rolling window {bid}")                                                 
                            
                            else:
                                bid = tick.BidPrice
                                bidVol = tick.BidSize
                                self.latestBidSizeRolling.Add(bidVol)
                                self.latestBidRolling.Add(bid) # adding the bid to rolling window

                            # algorithm.Debug(f'ask:{tick.AskPrice} bid:{tick.BidPrice} time:{algorithm.Time}')

                            # insightFlag = self.ShouldEmitInsight(algorithm.Time,security)
                            # conditionLong = ask > self.lumberYesterdayClose[0] and bid > self.lumberYesterdayClose[0] and self.algo.percentageChange and self.algo.percentageChange > 1 and insightFlag
                            # conditionShort = bid < self.lumberYesterdayClose[0] and ask < self.lumberYesterdayClose[0] and self.algo.percentageChange and self.algo.percentageChange < -1 and insightFlag
                            
                            # Weighed Average Price
                            
                            # WAP 

                            if ask > self.lumberYesterdayClose[0] and bid > self.lumberYesterdayClose[0] and self.algo.percentageChange and self.algo.percentageChange > 1 and self.ShouldEmitInsight(algorithm.Time,security):
                                algorithm.Debug(f"Position Up: Date{algorithm.Time} Nasdaq ∆:{self.algo.percentageChange},Lumber Yesterday Close:{self.lumberYesterdayClose[0]}, Lumber Ask:{ask}, Lumber Bid:{bid}")
                                insight = Insight(self.algo._lumberContract.Mapped,self.insightPeriod, InsightType.Price, InsightDirection.Up)
                                algorithm.Debug(f"Up Insight generated at: {algorithm.Time}")
                                self.insights.append(insight)
                            
                            elif bid < self.lumberYesterdayClose[0] and ask < self.lumberYesterdayClose[0] and self.algo.percentageChange and self.algo.percentageChange < -1 and self.ShouldEmitInsight(algorithm.Time,security):
                                algorithm.Debug(f"Position Down: Date{algorithm.Time} Nasdaq ∆:{self.algo.percentageChange},Lumber Yesterday Close:{self.lumberYesterdayClose[0]}, Lumber Ask:{ask}, Lumber Bid:{bid}")
                                # Insight(symbol, period, type, direction, magnitude=None, confidence=None, sourceModel=None, weight=None)
                                insight = Insight(self.algo._lumberContract.Mapped,self.insightPeriod, InsightType.Price, InsightDirection.Down)
                                algorithm.Debug(f"Down Insight generated at: {algorithm.Time}")
                                self.insights.append(insight)
                            
                            else:
                                # algorithm.Debug(f"No Condition Met, Date:{algorithm.Time}")
                                pass
                               
        if insight is not None: self.insightCollection.Add(insight)

        return self.insights


    def ShouldEmitInsight(self, utcTime, symbol):
        generatedTimeUtc = self.insightsTimeBySymbol.get(symbol)
        self.insightDailyCount +=1
        if generatedTimeUtc is not None:
            # we previously emitted a insight for this symbol, check it's period to see if we should emit another insight        
            if utcTime - generatedTimeUtc < self.insightPeriod and self.insightDailyCount > 1:
                return False

        # we either haven't emitted a insight for this symbol or the previous insight's period has expired, so emit a new insight now for this symbol
        self.insightsTimeBySymbol[symbol] = utcTime
        return True



from AlgorithmImports import *

class MyPortfolio(PortfolioConstructionModel):

    def __init__(self,algo,rebalancingFunc=None,portfolioBias=PortfolioBias.LongShort):
        # rebalancingFunc=Resolution.Daily

        self.algo=algo  # create the algo instance
        self.portfolioBias=portfolioBias
        self.setTargets = False

    def CreateTargets(self, algorithm, insights):
        
        tempCounter = 1
        """ This method is used to analyze the insights and determine for which insight 
        we need to create portfolio target i.e based on weight magnitude confidence etc.."""

        targets = []
        for insight in insights:
            # check if insight respects the portifolio bias
            if self.RespectPortfolioBias(insight) :

                # Fills up self.algo.positionCount
                self.GetPositionCount()

                # self.algo.Debug(f"Count of positionCount in CreateTargets of PortfolioModel: {abs(self.algo.positionCount)}")
                # Append PortfolioTargets All Order Quantity
                if not self.setTargets:
                    # self.algo.Debug(f"tempCounter in CreateTargets of PortfolioModel: {tempCounter}")
                    targets.append(PortfolioTarget(insight.Symbol,self.algo.positionCount))
                    
                    # self.algo.Debug(f"Count of Insights in CreateTargets of PortfolioModel: {len(insights)} at Time: {self.algo.Time}")
                    self.setTargets = True

        # if targets and not self.setTargets:
        #     for target in targets:
        #         self.algo.Debug(f"Target in Portfolio Construction-:-{target}")
        #         pass
        
        # self.setTargets = True

        return targets

    def GetPositionCount(self):
        if np.sign(self.algo.percentageChange * 1) == -1:
            self.algo.positionCount = math.ceil(self.algo.percentageChange) * 1          
        elif np.sign(self.algo.percentageChange * 1) == 1:
            self.algo.positionCount = math.floor(self.algo.percentageChange) * 1


    def RespectPortfolioBias(self, insight):
        """method is used to check if the long,short or both position are allowed by portifolio"""
        return self.portfolioBias == PortfolioBias.LongShort or insight.Direction == PortfolioBias.Long or insight.Direction == PortfolioBias.Short

    def ShouldCreateTargetForInsight(self, insight: Insight) -> bool:
        """ This method is used to check for which insight we are supposed to generate the 
        portfolio target we can use magnitude, weight and other aspects for this"""
        return True


    def DetermineTargetPercent(self, activeInsights):     
        """This method is used to calculate the %age of portifolio or no of stocks we want to set"""        
        pass
from AlgorithmImports import *

class MyRiskManagementModel(RiskManagementModel):
    
    def __init__(self, algo):
        self.algo = algo # Save an Instance of our Main Algorithm
        self.count = 0
        self.target_modified = []


    # Adjust the portfolio targets and return them. If no changes emit nothing.
    def ManageRisk(self, algorithm: QCAlgorithm, targets: List[PortfolioTarget]) -> List[PortfolioTarget]:
        for target in targets:
            # self.algo.Debug(f"target in RiskManagamenet-:-{target}")
            pass
        return targets

from AlgorithmImports import *
from QuantConnect.Statistics import *
from NasdaqAlpha import *
from PortfolioModel import *
from ExecutionModel import *
from RiskModel import *
from orderEnum import *
import numpy as np





class NasdaqStrategy(QCAlgorithm):

    def Initialize(self):
        # Order is valid until filled or the market closes
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
        self.DefaultOrderProperties = InteractiveBrokersOrderProperties()
        
        # Order is valid until filled or the market closes
        
        # self.DefaultOrderProperties.TimeInForce = TimeInForce.Day
        # self.DefaultOrderProperties.OutsideRegularTradingHours = True

        # Set a Limit Order to be good until Lumber Close
        
        self.order_properties = OrderProperties()

        # Set start and end time for backtest
       
       
        # self.SetEndDate(datetime.now() - timedelta(2))        
        self.SetCash(1000000)
        
        # TradeBuilder tracks the trades of your algorithm and calculates some statistics.
        # https://www.quantconnect.com/docs/v2/writing-algorithms/trading-and-orders/trade-statistics
        tradeBuilder = TradeBuilder(FillGroupingMethod.FillToFill,FillMatchingMethod.FIFO)    
        self.SetTradeBuilder(tradeBuilder)

        # Lumber security
        self._lumberContract=self.AddFuture(Futures.Forestry.RandomLengthLumber,
        Resolution.Tick,dataMappingMode=DataMappingMode.FirstDayMonth,contractDepthOffset=0,
        dataNormalizationMode=DataNormalizationMode.Raw,extendedMarketHours=True, fillDataForward = True)

        self.tickSize = self._lumberContract.SymbolProperties.MinimumPriceVariation

        # Nasdaq security
        self._nasdaqContract=self.AddFuture(Futures.Indices.MicroNASDAQ100EMini,
        Resolution.Tick,dataMappingMode = DataMappingMode.OpenInterest, contractDepthOffset=0,
        dataNormalizationMode = DataNormalizationMode.Raw, extendedMarketHours=True)
        
        # Set security initializer
        seeder = FuncSecuritySeeder(self.GetLastKnownPrices)
        self.SetSecurityInitializer(lambda security: seeder.SeedSecurity(security))

        # Symbols for securities:
        self.lumberSymbol = self._lumberContract.Symbol
        self.nasdaqSymbol = self._nasdaqContract.Symbol

        # Get 52 Week High and 52 Week Low of Nasdaq       
        self.nasdaqROCP = self.ROCP(self.nasdaqSymbol,1, Resolution.Daily)
        self.nasdaqDailyChange52High = IndicatorExtensions.MAX(self.nasdaqROCP, 252)
        self.RegisterIndicator(self.nasdaqSymbol, self.nasdaqDailyChange52High, Resolution.Daily)
        self.nasdaqDailyChange52Low = IndicatorExtensions.MIN(self.nasdaqROCP, 252)
        self.RegisterIndicator(self.nasdaqSymbol, self.nasdaqDailyChange52Low, Resolution.Daily)
        
        # Warm-Up the algorithm
        self.SetWarmup(timedelta(253),resolution=Resolution.Minute)
        self.positionCount = 0
        self.orderMultiplier = 1
        
        # "Order To Market Price" represents Order Price Competitiveness
        self.premiumDiscount = -0.01 # In decimals: +0.01 (More Competitive) represents 1% premium to Bid/ 1 % discount to Ask 
        
        # while -0.01 (Less Competitive) represents 1% discount to Bid/ 1% premium to Ask
        # example for buy orders
        self.TARGET_LIMIT_OFFSET = 10
        self.STOPLOSS_STOP_OFFSET = 5 # This will be the actual stop loss - this is called the stop price of the stop loss order
        self.STOPLOSS_LIMIT_OFFSET = 2 # Maximum allowed below stop loss so its called Limit price of the stop loss
        self.targetsCollection = PortfolioTargetCollection()
        self.tradesCollection = {}
        
        # Set portifolio consruction model
        self.SetPortfolioConstruction(MyPortfolio(self))
        
        # Set risk management model
        # self.AddRiskManagement(TrailingStopRiskManagementModel(maximumDrawdownPercent=0.05))
        # self.AddRiskManagement(MaximumUnrealizedProfitPercentPerSecurity(maximumUnrealizedProfitPercent	= 0.05))
        self.AddRiskManagement(NullRiskManagementModel()) # Default
        
        
        # Set execution model
        # self.SetExecution(ImmediateExecutionModel())    
        self.SetExecution(MyExecution(self))
        
        
        # Set  alpha model
        self.alpha= NasdaqAlpha(self)
        self.SetAlpha(self.alpha)


    def OnData(self, slice: Slice) -> None:
        
        for changed_event in slice.SymbolChangedEvents.Values:
            # self.Debug(f"Contract rollover from {changed_event.OldSymbol} to {changed_event.NewSymbol}")
            pass

        for key in self.tradesCollection.keys(): # These are ID's of only MainTicket

            ticket = self.Transactions.GetOrderTicket(key)
            trade = self.tradesCollection[str(key)]
            
            if ticket.Status == OrderStatus.Submitted:
                LimitPrice = ticket.Get(OrderField.LimitPrice)
                # To Check if this Ask & Bid Prices are consistent with Tick Data
                LatestAsk = self.Securities[ticket.Symbol].AskPrice 
                LatestBid = self.Securities[ticket.Symbol].BidPrice
                minOrderPrice = min(LatestBid,LatestAsk)
                maxOrderPrice = max(LatestBid,LatestAsk)

                # Update LimitPrice if we are Long and Best Bid has Changed    
                if ticket.Quantity > 0: # Its a buy order
                    orderPrice = round(minOrderPrice*(1+self.premiumDiscount),1)
                   
                # Update LimitPrice if we are Short and Best Ask has Changed    
                elif ticket.Quantity < 0: # Its a Sell order
                    orderPrice = round(maxOrderPrice*(1-self.premiumDiscount),1)

                else:                # No Order
                    orderPrice = 0
                    LimitPrice = 0
                    #  self.Debug(f"Not Updating order: LatestAsk:{LatestAsk}, LatestBid:{LatestBid} ,LimitPrice:{LimitPrice}")

                if LimitPrice != orderPrice:
                    self.Debug(f"Updating order since LatestAsk/LatestBid/orderPrice {LatestAsk},{LatestBid},{orderPrice} ≠ LimitPrice {LimitPrice}")
                    trade.updateMainOrder(round(orderPrice))

            # For Trailing Stop
            elif ticket.Status == OrderStatus.Filled:
                # mainOrderFillPrice = trade.mainOrderTicket.FillPrice
                # currentPrice = self.Securities[trade.mainOrderTicket.Symbol].Price
                # changeInPrice=((currentPrice-mainOrderFillPrice)/mainOrderFillPrice)*100
                pass
                
    # OnEndOfDay notifies when (Time) each security has finished trading for the day
    def OnEndOfDay(self, symbol: Symbol) -> None:
        # self.Debug(f"Finished Trading on {self.Time} for security {symbol}")
        pass
        
    # When your algorithm stops executing, LEAN calls the OnEndOfAlgorithm method.
    def OnEndOfAlgorithm(self) -> None:
        # self.Debug("Printing tradesCollection")
        # for key,value in self.tradesCollection.items():
        #     self.Debug(f"key:{key}, Value:{value}")

        for trade in self.TradeBuilder.ClosedTrades:
            self.Debug(self.trade_to_string(trade))
        
        self.Debug("Algorithm done")


    def trade_to_string(self, trade):
        return 'Symbol: {0} EntryTime: {1} EntryPrice {2} Direction: {3} Quantity: {4} ExitTime: {5} ExitPrice: {6} ProfitLoss: {7} TotalFees: {8} MAE: {9} MFE: {10} EndTradeDrawdown: {11}'.format(
            trade.Symbol, trade.EntryTime - timedelta(hours=5), trade.EntryPrice, get_order_direction_name(trade.Direction), 
            trade.Quantity, trade.ExitTime - timedelta(hours=5), trade.ExitPrice, trade.ProfitLoss, trade.TotalFees, 
            trade.MAE, trade.MFE, trade.EndTradeDrawdown)

    def OnOrderEvent(self, orderEvent):
        ticket=self.Transactions.GetOrderTicket(orderEvent.OrderId)

        self.Debug(f"OrderType:{get_order_type_name(ticket.OrderType)},OrderId:{ticket.OrderId},tradesCollection:{self.tradesCollection}")

        # If order is main
        if ticket.OrderType == OrderType.Limit and str(orderEvent.OrderId) in self.tradesCollection:
            
            # get trade  
            trade=self.tradesCollection[str(orderEvent.OrderId)]
              
            if ticket.Status == OrderStatus.Filled:

                # check if price of lumber since open and fill price
                # is more than counted percent down or up

                voilationCheck=trade.cancelIfVoilates(percent=5.0)

                if not voilationCheck:
                    self.Debug("Main Order Filled")
            
                    # Calculate TakeProfit and Stop - Limit & Stop price
                    fillPrice = orderEvent.FillPrice
                    stopLossStopPrice = fillPrice- np.sign(ticket.Quantity * 1) * self.STOPLOSS_STOP_OFFSET
                    stopLossLimijitPrice = stopLossStopPrice - np.sign(ticket.Quantity * 1) * self.STOPLOSS_LIMIT_OFFSET
                    takeProfitPrice = fillPrice+ np.sign(ticket.Quantity * 1) * self.TARGET_LIMIT_OFFSET
                    
                    # Place stoploss order
                    trade.placeStopLossOrder(-ticket.Quantity,stopLossStopPrice,stopLossLimitPrice,orderProperties = self.order_properties)

                    # Place Take Profit                
                    trade.placeTakeProfitOrder(-ticket.Quantity,takeProfitPrice,orderProperties = self.order_properties)
                
           
            elif ticket.Status == OrderStatus.UpdateSubmitted:
                self.Debug(f"Main Order UpdateSubmitted")
                
            
            elif ticket.Status == OrderStatus.Submitted:
                self.Debug(f"Main Order Submitted")
                

            
            elif ticket.Status == OrderStatus.Canceled:
                self.Debug(f"Main Order Canceled")
                


        # If order is Take Profit
        elif  ticket.OrderType == OrderType.Limit and ticket.Tag in self.tradesCollection:
            if ticket.Status == OrderStatus.Filled:
                # self.Debug(f"tradesCollection in TakeProfit:{self.tradesCollection}")
                self.Debug(f"take profit placed with order_id {ticket.OrderId}")
                
                # get Main trade & Cancel StopLoss as Take Profit Filled
                trade=self.tradesCollection[str(ticket.Tag)]
                trade.cancelStopLoss()
        
        # If order is StopLoss
        elif  ticket.OrderType == OrderType.StopLimit:           
            if ticket.Status == OrderStatus.Filled:
                # self.Debug(f"tradesCollection in StopLoss:{self.tradesCollection}")
                self.Debug(f"Stop loss placed with order_id={ticket.OrderId}")
                
                # get Main trade & Cancel Take Profit as StopLoss Filled
                trade=self.tradesCollection[str(ticket.Tag)]
                trade.cancelTakeProfit()





from AlgorithmImports import *
# https://github.com/QuantConnect/Lean/blob/master/Common/Orders/OrderTypes.cs#L87

def get_order_status_name(index):
   return { 
       0: 'New',
       1: 'Submitted',
       2: 'PartiallyFilled',
       3: 'Filled',
       4: 'None',
       5: 'Canceled',
       6: 'None',
       7: 'Invalid',
       8: 'CancelPending',
       9: 'UpdateSubmitted '
    }[index]


def get_order_direction_name(index):
    return {
        0: 'Buy',
        1: 'Sell',
        2: 'Hold',
    }[index]


def get_order_type_name(index):
    return {
        0: 'Market',
        1: 'Limit',
        2: 'StopMarket',
        3: 'StopLimit',
        4: 'MarketOnOpen',
        5: 'MarketOnClose',
        6: 'OptionExercise',
        7: 'LimitIfTouched'
    }[index]