Overall Statistics
Total Trades
4282
Average Win
0.09%
Average Loss
-0.07%
Compounding Annual Return
65.909%
Drawdown
6.300%
Expectancy
0.231
Net Profit
44.597%
Sharpe Ratio
4.313
Sortino Ratio
6.063
Probabilistic Sharpe Ratio
99.961%
Loss Rate
44%
Win Rate
56%
Profit-Loss Ratio
1.18
Alpha
0
Beta
0
Annual Standard Deviation
0.09
Annual Variance
0.008
Information Ratio
4.915
Tracking Error
0.09
Treynor Ratio
0
Total Fees
$5840.69
Estimated Strategy Capacity
$3900000.00
Lowest Capacity Asset
HLT VMCPV8L4GJTX
Portfolio Turnover
108.55%
# import python libraries
from AlgorithmImports import *
import random

################################################################################
class CustomFillModel(ImmediateFillModel):
    def __init__(self, algorithm):
        super().__init__()
        self.algorithm = algorithm
        self.absoluteRemainingByOrderId = {}
        self.random = Random(387510346)

    # def MarketFill(self, asset: Security, order: MarketOrder) -> OrderEvent:
    #     absoluteRemaining = order.AbsoluteQuantity
    #     if order.Id in self.absoluteRemainingByOrderId.keys():
    #         absoluteRemaining = self.absoluteRemainingByOrderId[order.Id]

    #     fill = super().MarketFill(asset, order)
    #     absoluteFillQuantity = int(
    #         min(absoluteRemaining, 
    #             self.random.Next(0, 2*int(order.AbsoluteQuantity)))
    #         )
    #     fill.FillQuantity = np.sign(order.Quantity) * absoluteFillQuantity
        
    #     if absoluteRemaining == absoluteFillQuantity:
    #         fill.Status = OrderStatus.Filled
    #         if self.absoluteRemainingByOrderId.get(order.Id):
    #             self.absoluteRemainingByOrderId.pop(order.Id)
    #     else:
    #         absoluteRemaining = absoluteRemaining - absoluteFillQuantity
    #         self.absoluteRemainingByOrderId[order.Id] = absoluteRemaining
    #         fill.Status = OrderStatus.PartiallyFilled
    #     self.algorithm.MyLog(f"CustomFillModel: {fill}")
    #     return fill

    def LimitFill(self, asset: Security, order: LimitOrder) -> OrderEvent:
        fill = super().LimitFill(asset, order)
        # Set the fill price to the be limit order price
        limit_price = order.LimitPrice
        fill.FillPrice = limit_price
        return super().LimitFill(asset, order)

    def MarketOnOpenFill(self, asset: Security, order: MarketOnOpenOrder) -> OrderEvent:
        return super().MarketOnOpenFill(asset, order)

    def MarketOnCloseFill(self, asset: Security, order: MarketOnCloseOrder) -> OrderEvent:
        return super().MarketOnCloseFill(asset, order)
"""
Alternative Data Strategy
Version 1.0.3
Platform: QuantConnect
By: Aaron Eller
www.excelintrading.com
aaron@excelintrading.com

Revision Notes:
    1.0.0 (04/18/2023) - Initial rewrite from 'Alpha V3'.
                       - Added scheduled function to get daily signal prior
                          to the premarket open up until the regular session
                          market open.
                       - Added logic to verify orders were not invalid.
                       - Added option for initial orders to be market on open
                       - Added slippage and fill models.
                       - Added max intraday leverage tracking and plot.
    1.0.1 (04/20/2023) - Changed log in self.GetSignal() when signal not found 
                          to be more clear.
                       - Added debugging logs in self.GetSignal() to see what
                          the contents of the Google Sheet are for each check.
    1.0.2 (04/21/2023) - Explicitly put to allow orders outside of regular trading
                          hours.
                       - Changed self.file to be filename and not download.
    1.0.3 (04/25/2023) - Added self.OnOrderEvent built-in function.
                       - Added self.AfterMarketClose scheduled event.
                       - Added tag to HLT end of day exit order.
                       - Added tag to all other end of day exit orders.
                       - Cancel any open entry orders still open at liquidation times.
                       - Updated TaggedTrailingStop to skip after daily liquidations.
                       - Added entry fill stat variables and logs at end of backtest.
                       - Added logs to TaggedTrailingStop when triggered.
    1.0.4 (04/28/2023) - Kash - Updated code to track premarket and rth fills hourly
                              - Added daily profit and loss plot 

Consider ignoring TaggedTrailingStop during premarket?
Make sure TaggedTrailingStop doesn't trigger multiple times in premarket

References:
-QC (Lean) Class List
  https://lean-api-docs.netlify.app/annotated.html
"""
# import python libraries
from AlgorithmImports import *
from io import StringIO
import pandas as pd
import datetime as dt

# Import from files
from reality_models import CustomFillModel
from TaggedTrailingStop import TaggedTrailingStop

################################################################################
# User inputs / global variables
PRINT_MESSAGES = True
EXTENDED_MARKET_HOURS = True

# Turn on/off using market on open orders #
#  If set to True, it instead enters the initial positions via market on open orders rather than limit orders in the premarket session.
#  -> test impact of premarket order fills that are not as realistic
USE_MARKET_ON_OPEN = False

# Set the limit order offset amount
LIMIT_ORDER_OFFSET_AMOUNT = 0.05

# Turn on/off using a custom fill model
# Will fill limit orders at the limit price
USE_CUSTOM_FILL = False

# Constant percentage slippage model
USE_CONSTANT_SLIPPAGE = False
CONSTANT_SLIPPAGE_PERCENT = 0.0003
# default = 0.0001 or 0.01%, which is pretty low, e.g. $0.01 for $100 price

# Volume share slippage model
USE_VOLUME_SHARE_SLIPPAGE = False
VS_VOLUME_LIMIT = 0.05 # default #0.025
VS_PRICE_IMPACT = 0.2 # default 0.1

################################################################################
class AlternativeDataAlgorithm(QCAlgorithm):
    def Initialize(self):
        # Set backtest details
        self.SetBacktestDetails()
        # Add strategy variables required for the algo
        self.AddStrategyVariables()
        # Add instrument data to the algo
        self.AddInstrumentData()
        # Schedule functions
        self.ScheduleFunctions()

        # Add risk management model
        #self.AddRiskManagement(TaggedTrailingStop(self, 0.05))
        # Set benchmark
        # self.SetBenchmark("SPY")
        # self.SetWarmUp(timedelta(60))

        #Add entry check directory
        self.entry_orders_filled_premarket = {}
        self.entry_orders_filled_rth = {}
#------------------------------------------------------------------------------
    def SetBacktestDetails(self):
        """Set the backtest details."""
        self.SetStartDate(2023, 4, 26)
        self.SetCash(250000)
        self.SetTimeZone("US/Eastern")

        # Setup trading framework
        # Transaction and submit/execution rules will use IB models
        self.SetBrokerageModel(
            BrokerageName.InteractiveBrokersBrokerage, 
            AccountType.Margin
        )
        # Make sure that orders are allowed to fill in the premarket hours
        self.DefaultOrderProperties = InteractiveBrokersOrderProperties()
        self.DefaultOrderProperties.OutsideRegularTradingHours = True

        # Set the percentage of the portfolio to free up for position sizing
        #  calculations to avoid buying power issues
        self.Settings.FreePortfolioValuePercentage = 0.05

        # Configure all universe securities
        # This sets the data normalization mode
        # You can also set custom fee, slippage, fill, and buying power models
        self.SetSecurityInitializer(
            CustomSecurityInitializer(
                self.BrokerageModel, 
                FuncSecuritySeeder(self.GetLastKnownPrices),
                self
            )
        )

#------------------------------------------------------------------------------
    def AddStrategyVariables(self):
        """Create required strategy variables."""
        # Save link to the Alternative Data Stream link
        self.file = 'https://docs.google.com/spreadsheets/d/19yCql9Ts95qIF' + \
           'i5kDT-F7_h_ZIqqHN3WQ0y9RVgRzuA/export?format=csv' #ALPHA 2

        # self.file = 'https://docs.google.com/spreadsheets/d/1zSmq9U8IoqXojgXVIOwUJ' + \
        #    'BLdwosTKNfKGSWvPQx8fxY/export?format=csv' #ALPHA 4


        # self.file = 'https://docs.google.com/spreadsheets/d/1PWnU6QCP7hQN1ONSpbLBE6bSWu8' + \
        #     'XjvdArUEd8HbJ7KQ/export?format=csv' #ALPHA 1

        # Keep track of the daily signal
        self.signal = None
        # Set the hedge percentage amount
        self.hedgeAmount = 0.10 # 20% hedge 
        # Define the list of tickers and hedges to trade
        # self.tickers=[
        #     "HLT","H","MAR","CHH","WH","IHG","SHO","MGM", #HOSPITALITY
        #     "EXR",'SELF',"NSA","PSA","CUBE","LSI" #STORAGE
        # ]
        self.tickers=[
            "HLT","NSA","PSA","CUBE","MLM","H","MAR",'WH','CHH','EXR','LSI'
        ]
        self.hedges = ["AMED","SAM","SMG"]
        # Calculate the traded ticker's weight
        self.weight = (1-self.hedgeAmount) / len(self.tickers)
        # Calculate the desired weight per hedge instrument
        self.hedge_weight = (1.0 / len(self.hedges)) * self.hedgeAmount
        # Create dictionary for pointer to the SymbolData instances
        self.symbolData = {}
        # Keep track of max leverage for the day
        self.max_leverage = 0
        # Keep track when HLT is liquidated each day
        self.hlt_liquidated = False
        # Keep track when the remaining symbols are liquidated each day
        self.liquidated = False
        # Track entry orders filled premarket vs rth
        self.entry_orders_filled_premarket = 0
        self.entry_orders_filled_rth = 0
        self.entry_orders_canceled = 0

        #For profit and loss
        self.yesterday_total_profit = 0
        self.yesterday_total_fees = 0

#------------------------------------------------------------------------------
    def AddInstrumentData(self):
        """Add instrument data to the algo."""
        # Save the data resolution that we want to use
        if self.LiveMode:
            self.data_resolution = Resolution.Second
        else:
            self.data_resolution = Resolution.Minute
        # Loop through the list of tickers
        self.symbols = {}
        for ticker in self.tickers:
            # Add data and save the QC Symbol object
            symbol = self.AddEquity(
                ticker, self.data_resolution, Market.USA, leverage=0.00, 
                extendedMarketHours=EXTENDED_MARKET_HOURS
            ).Symbol
            # Save link to symbols
            self.symbols[ticker] = symbol
            # Create and save link to the SymbolData instance for the ticker
            self.symbolData[symbol] = SymbolData(self, symbol, hedge=False)
        # Loop through the list of hedges        
        for ticker in self.hedges:
            # Add data and save the QC Symbol object
            symbol = self.AddEquity(
                ticker, self.data_resolution, Market.USA, leverage=0.00, 
                extendedMarketHours=EXTENDED_MARKET_HOURS
            ).Symbol
            # Save link to symbols
            self.symbols[ticker] = symbol
            # Create and save link to the SymbolData instance for the ticker
            self.symbolData[symbol] = SymbolData(self, symbol, hedge=True)

#------------------------------------------------------------------------------
    def ScheduleFunctions(self):
        """Schedule the functions required by the algo."""
        # Get the daily signal
        # Premarket session is 4am-930am ET
        t = dt.datetime(2023,1,1,3,55)
        times_to_check = []
        while True:
            # Schedule event to get the signal
            self.Schedule.On(
                self.DateRules.EveryDay("HLT"), 
                self.TimeRules.At(t.hour, t.minute), 
                self.GetSignal
            )
            # Immediately break for backtesting
            if not self.LiveMode:
                # Download and save the file as a df
                f = self.Download(self.file) # Alpha Signal 2
                df = pd.read_csv(StringIO(f))
                df['Date'] = pd.to_datetime(df['Date']).dt.date
                self.df = df
                break
            times_to_check.append(t.time())
            # Increment t by 1 minute
            t += dt.timedelta(minutes=1)
            # Break at 930am
            if t.time() >= dt.time(9,30):
                break

        # Check for Trade Entry
        self.Schedule.On(
            self.DateRules.EveryDay("HLT"), 
            self.TimeRules.Every(TimeSpan.FromMinutes(1)), 
            self.TradeEntryCheck
        )
        # Liquidate HLT 180 minutes before the close
        self.Schedule.On(
            self.DateRules.EveryDay("HLT"), 
            self.TimeRules.BeforeMarketClose(self.tickers[0], 180), 
            self.HLTLiquidation
        )
        # Liquidate the positions 30 minutes prior to the close
        self.Schedule.On(
            self.DateRules.EveryDay("HLT"), 
            self.TimeRules.BeforeMarketClose(self.tickers[0], 30), 
            self.DailyLiquidation
        )
        # Reset variables after the close
        self.Schedule.On(
            self.DateRules.EveryDay("HLT"), 
            self.TimeRules.BeforeMarketClose(self.tickers[0], -5), 
            self.AfterMarketClose
        )

#------------------------------------------------------------------------------
    def MyLog(self, message):
        """Add algo time to log if live trading. Otherwise just log message."""
        if self.LiveMode:
            self.Log(f'{self.Time}: {message}')
        elif PRINT_MESSAGES:
            self.Log(message)

#------------------------------------------------------------------------------
    def GetSignal(self):
        """Read the Google doc's alternative data to get today's signal."""
        # Skip if we already have the signal for the day
        if self.signal != None:
            return
        # Read as a df
        if self.LiveMode:
            try:
                f = self.Download(self.file) # Alpha Signal 2
                df = pd.read_csv(StringIO(f))
                df['Date'] = pd.to_datetime(df['Date']).dt.date
                # For debugging
                self.MyLog(f"Google Sheet last row: {df.tail(1)}")
            except:
                self.MyLog("Error reading Google doc!")
                self.signal = None
                return 
        else: 
            # Backtesting: we've already downloaded the contents of the file
            df = self.df
        # Get and save the alternative data signal for today
        alternative_data = df[df['Date'] == self.Time.date()]
        # Set signal to None if empty dataframe
        if alternative_data.empty:
            self.signal = None
            self.MyLog(f"Do not have today's ({self.Time.date()}) signal")
        else:
            self.signal = alternative_data['Signal'].iloc[0]
            self.MyLog(f"Today's signal: {self.signal}")

#------------------------------------------------------------------------------
    def TradeEntryCheck(self):
        # Skip if warming up the algo
        if self.IsWarmingUp:
            return
        # Get a list of the open orders
        openOrders = self.Transactions.GetOpenOrders()
        # Get a list of tickers we are invested in
        invested = [x.Symbol.Value for x in self.Portfolio.Values if x.Invested]
        # Skip if we have open orders or positions for each ticker
        if (len(openOrders)+len(invested))==(len(self.tickers)+len(self.hedges)):
            return
        # Skip if <= 4am or after 10am
        if self.Time.time() <= dt.time(4, 0) or self.Time.time() > dt.time(10, 0):
            return
        # Enter trades based on today's signal
        self.ExecuteTrade()

#------------------------------------------------------------------------------
    def ExecuteTrade(self):
        """Execute today's trades based on the signal and desired weights."""
        # Return if signal not 1 or 2
        if self.signal != 1 and self.signal != 2:
            return
        # Otherwise loop through the traded tickers
        for ticker in self.tickers:
            # Get the SymbolData instance for the ticker and it's symbol
            symbol = self.symbols[ticker]
            sd = self.symbolData[symbol]
            symbol = sd.symbol
            # Check if not traded for today
            if not sd.traded_flag:
                # Get the desired weight and qty to order
                if self.signal == 1:
                    weight = self.weight
                else:
                    weight = -self.weight
                quantity = self.CalculateOrderQuantity(symbol, weight)
                # Skip if quantity is 0
                if quantity == 0:
                    continue
                # Log message
                price = self.Securities[symbol].Price
                bid = self.Securities[symbol].BidPrice
                ask = self.Securities[symbol].AskPrice
                self.MyLog(
                    f"{ticker} desired weight={weight}, price={price}, "
                    f"and desired qty={quantity} / bid={bid}, ask={ask}"
                )
                # Use a market on open order when desired
                if USE_MARKET_ON_OPEN:
                    order = self.MarketOnOpenOrder(
                        symbol, quantity, tag="MOO entry order"
                    )
                    # Set traded_flag if order is accepted
                    if order.Status != OrderStatus.Invalid:
                        sd.traded_flag = True
                        sd.entry_order = order
                # Use a limit order if before the market open
                elif self.Time.time() < dt.time(9, 30):
                    # Get the limit price
                    limit_price = None
                    if price is not None and bid is not None and ask is not None:
                        if price != 0 and bid != 0 and ask != 0:
                            # Add buffer to buy price
                            if weight > 0:
                                limit_price = \
                                    round(price+LIMIT_ORDER_OFFSET_AMOUNT,2)
                            # Or subtract buffer from sell price
                            else:
                                limit_price = \
                                    round(price-LIMIT_ORDER_OFFSET_AMOUNT,2)
                    # Create limit order if we have a valid price
                    if limit_price:
                        order = self.LimitOrder(
                            symbol, quantity, limit_price, tag='entry limit order'
                        ) 
                        # Set traded_flag if order is accepted
                        if order.Status != OrderStatus.Invalid:
                            sd.traded_flag = True
                            sd.entry_order = order
                # Otherwise use a market order
                else:
                    order = self.MarketOrder(
                        symbol, quantity, tag='entry market order'
                    )
                    # Set traded_flag if order is accepted
                    if order.Status != OrderStatus.Invalid:
                        sd.traded_flag = True
                        sd.entry_order = order
        # Always rebalance the hedges
        self.HedgeRebalance()

#------------------------------------------------------------------------------
    def HedgeRebalance(self):
        """Execute today's hedge trades."""
        # Loop through the list of hedges        
        for ticker in self.hedges:
            # Get the SymbolData instance for the ticker and it's symbol
            symbol = self.symbols[ticker]
            sd = self.symbolData[symbol]
            symbol = sd.symbol
            # Check if not traded for today
            if not sd.traded_flag:
                # Get the desired weight and qty to order
                if self.signal == 1:
                    weight = -self.hedge_weight
                else:
                    weight = self.hedge_weight
                quantity = self.CalculateOrderQuantity(symbol, weight)
                # Skip if quantity is 0
                if quantity == 0:
                    continue
                # Log message
                price = self.Securities[symbol].Price
                bid = self.Securities[symbol].BidPrice
                ask = self.Securities[symbol].AskPrice
                self.MyLog(
                    f"{ticker} desired weight={weight}, price={price}, "
                    f"and desired qty={quantity} / bid={bid}, ask={ask}"
                )
                # Use a limit order if before the market open
                if self.Time.time() < dt.time(9, 30):
                    # Get the limit price
                    limit_price = None
                    if price is not None and bid is not None and ask is not None:
                        if price != 0 and bid != 0 and ask != 0:
# Is this correct or backwards?
                            # Subtract buffer from buy price
                            if weight > 0:
                                limit_price = \
                                    round(price-LIMIT_ORDER_OFFSET_AMOUNT,2)
                            # Or add buffer to sell price
                            else:
                                limit_price = \
                                    round(price+LIMIT_ORDER_OFFSET_AMOUNT,2)
                    # Create limit order if we have a valid price
                    if limit_price:
                        order = self.LimitOrder(
                            symbol, quantity, limit_price, tag='entry limit order'
                        ) 
                        # Set traded_flag if order is accepted
                        if order.Status != OrderStatus.Invalid:
                            sd.traded_flag = True
                            sd.entry_order = order
                # Otherwise use a market order
                else:
                    order = self.MarketOrder(
                        symbol, quantity, tag='entry market order'
                    )
                    # Set traded_flag if order is accepted
                    if order.Status != OrderStatus.Invalid:
                        sd.traded_flag = True
                        sd.entry_order = order

#------------------------------------------------------------------------------
    def HLTLiquidation(self):
        """Liquidate open HLT position at the end of the day."""
        self.MyLog("Time for HLTLiquidation")
        # Keep track when HLT is liquidated each day
        self.hlt_liquidated = True
        # Reset the traded_flag variable
        symbol = self.symbols["HLT"]
        sd = self.symbolData[symbol]
        sd.traded_flag = False
        # Cancel any open entry orders
        if sd.entry_order:
            response = sd.entry_order.Cancel()
            if response.IsSuccess:
                sd.entry_order = None
                self.entry_orders_canceled += 1
        # Exit any open position immediately with a market order
        quantity = self.Portfolio["HLT"].Quantity
        if quantity != 0:
            self.MarketOrder("HLT", -quantity, tag="HLT EOD exit")
            self.Log(f"HLT end of day exit for {quantity} shares")

#------------------------------------------------------------------------------
    def DailyLiquidation(self):
        """Liquidate any open positions at the end of the day."""
        self.MyLog("Time for DailyLiquidation")
        # Keep track when the remaining symbols are liquidated each day
        self.liquidated = True
        # Loop through SymbolData instances
        for symbol, sd in self.symbolData.items():
            # Reset the traded_flag variable
            sd.traded_flag = False
            # Cancel any open entry orders
            if sd.entry_order:
                response = sd.entry_order.Cancel()
                if response.IsSuccess:
                    sd.entry_order = None
                    self.entry_orders_canceled += 1
            # Exit any open position with a market on close order
            quantity = self.Portfolio[symbol].Quantity
            if quantity != 0:
                self.MarketOnCloseOrder(symbol, -quantity, tag="EOD MOC exit")
                self.Log(f"End of day exit for {quantity} {symbol} shares")
        # Set the signal back to None
        # Will get reset the next market day
        self.signal = None
        # Plot the account leverage
        self.Plot('Leverage', 'Leverge', self.max_leverage)
        # Keep track of max leverage for the day
        self.max_leverage = 0

#------------------------------------------------------------------------------
    # def AfterMarketClose(self):
    #     """Function called after the end of the day."""
    #     # Reset liquidated variables
    #     self.hlt_liquidated = False
    #     self.liquidated = False
    #     # Track leverage end of day
    #     leverage = self.Portfolio.TotalHoldingsValue \
    #         / self.Portfolio.TotalPortfolioValue
    #     for symbol, sd in self.symbolData.items():
    #         qty = self.Portfolio[symbol].Quantity
    #         if qty != 0:
    #             self.Log(f"Open position at end of day! {qty} of {symbol}")
    #             if not self.LiveMode:
    #                 raise
    #     self.Plot('End of Day Leverage', 'End of Day Leverge', leverage)

    def AfterMarketClose(self):
        """Function called after the end of the day."""
        # Reset liquidated variables
        self.hlt_liquidated = False
        self.liquidated = False
        # Track leverage end of day
        leverage = self.Portfolio.TotalHoldingsValue \
            / self.Portfolio.TotalPortfolioValue
        for symbol, sd in self.symbolData.items():
            qty = self.Portfolio[symbol].Quantity
            if qty != 0:
                self.Log(f"Open position at end of day! {qty} of {symbol}")
                if not self.LiveMode:
                    self.Debug(f"Open position at end of day! {qty} of {symbol}")
                else:
                    # Handle the exception in live mode
                    self.Log("Open position at end of day! Exception handled.")

        self.Plot('End of Day Leverage', 'End of Day Leverage', leverage)

#-------------------------------------------------------------------------------
    def OnData(self, data):
        """Built-in event handler for new data."""
        # Track max intraday leverage
        leverage = self.Portfolio.TotalHoldingsValue \
            / self.Portfolio.TotalPortfolioValue
        if leverage > self.max_leverage:
            self.max_leverage = leverage
        
    # Added Profit and Loss Visual
       # self.Plot("Daily Realized Pnl", "Value", self.get_daily_realized_pnl())
        
    def OnEndOfDay(self):
        self.yesterday_total_profit = self.Portfolio.TotalProfit
        self.yesterday_total_fees = self.Portfolio.TotalFees
    
    # def get_daily_realized_pnl(self):
    #     daily_gross_profit = self.Portfolio.TotalProfit - self.yesterday_total_profit
    #     daily_fees = self.Portfolio.TotalFees - self.yesterday_total_fees
    #     return daily_gross_profit - daily_fees            

#-------------------------------------------------------------------------------  
    def OnEndOfAlgorithm(self):
        """Built-in event handler for end of backtest."""
    # Log entry orders filled premarket by hour
        for hour, count in self.entry_orders_filled_premarket.items():
            self.Debug(f"{count} entry orders filled premarket in hour {hour}."
        )
    # Log entry orders filled in regular trading session by hour
        for hour, count in self.entry_orders_filled_rth.items():
            self.Debug(f"{count} entry orders filled in regular trading session for hour {hour}."

        )
        self.Debug(f"{self.entry_orders_canceled} entry orders not filled.")

#-------------------------------------------------------------------------------  
    def OnOrderEvent(self, orderEvent):
        """Built-in event handler for orders."""
        # Skip if not filled
        if orderEvent.Status != OrderStatus.Filled:
            if self.LiveMode:
                self.Log(f"New order event: {orderEvent}")
            return
        # Get the order details
        order = self.Transactions.GetOrderById(orderEvent.OrderId)
        order_qty = int(order.Quantity)
        avg_fill = orderEvent.FillPrice
        tag = order.Tag
        symbol = order.Symbol
        # Log message for order
        self.Log(f"{symbol} {tag} order filled: {order_qty} @ {avg_fill}")

        # Get the SymbolData instance for the symbol
        if self.symbolData.get(symbol) is None:
            # Catch for backtesting to handle before live trading
            if not self.LiveMode:
                raise ValueError(
                    f"SymbolData instance not found for {symbol}"
                )
            self.Log(f"SymbolData instance not found for {symbol}")
            return
        else:
            sd = self.symbolData.get(symbol)

        # Check for type of order filled
        if 'entry' in tag:
            sd.entry_order = None
            # Update counter
            if self.Time.time() < dt.time(9, 30):
                hour = self.Time.hour
                if hour in self.entry_orders_filled_premarket:
                    self.entry_orders_filled_premarket[hour] += 1
                else:
                    self.entry_orders_filled_premarket[hour] = 1
            else:
                hour = self.Time.hour
                if hour in self.entry_orders_filled_rth:
                    self.entry_orders_filled_rth[hour] += 1
                else:
                    self.entry_orders_filled_rth[hour] = 1

################################################################################
class SymbolData(object):
    """Class to store data for a specific symbol."""
    def __init__(self, algo, symbol_object, hedge=False):
        """Initialize SymbolData object."""
        # Save a reference to the QCAlgorithm class
        self.algo = algo
        # Save the string symbol as ticker and the .Symbol object as symbol
        self.ticker = symbol_object.Value
        self.symbol = symbol_object
        # Save if the symbol is a hedge
        self.hedge = hedge
        # Keep track if the symbol has been traded today
        self.traded_flag = None
        # Keep track of the entry order
        self.entry_order = None

###############################################################################
class CustomSecurityInitializer(BrokerageModelSecurityInitializer):
    def __init__(
        self, 
        brokerage_model: IBrokerageModel, 
        security_seeder: ISecuritySeeder,
        algorithm
        ) -> None:
        self.algorithm = algorithm
        super().__init__(brokerage_model, security_seeder)

    def Initialize(self, security: Security) -> None:
        """
        Define models to be used for securities as they are added to the 
        algorithm's universe.
        """
        # First, call the superclass definition
        # This method sets the reality models of each security using the 
        #  default reality models of the brokerage model
        super().Initialize(security)
        # Define the data normalization mode
        security.SetDataNormalizationMode(DataNormalizationMode.Raw)
        # Define the fee model to use for the security
        # security.SetFeeModel()

        # Define the slippage model to use for the security
        if USE_VOLUME_SHARE_SLIPPAGE:
            security.SetSlippageModel(
                VolumeShareSlippageModel(VS_VOLUME_LIMIT, VS_PRICE_IMPACT)
            )
        if USE_CONSTANT_SLIPPAGE:
            security.SetSlippageModel(
                ConstantSlippageModel(CONSTANT_SLIPPAGE_PERCENT)
            )
        # security.SetSlippageModel(AlphaStreamsSlippageModel())

        # Define the fill model to use for the security
        if USE_CUSTOM_FILL:
            security.SetFillModel(CustomFillModel(self.algorithm))
        # Define the buying power model to use for the security
        # security.SetBuyingPowerModel()
#region imports
from AlgorithmImports import *
#endregion


# Your New Python File
from AlgorithmImports import *
import datetime as dt

class TaggedTrailingStop(RiskManagementModel):
    def __init__(self, algo, trailingStopPercent=0.05, trailingStopActivationPercent=0.03, partialLiquidationPercent=0.00):
        self.algo = algo
        self.trailingStopPercent = abs(trailingStopPercent)
        self.trailingStopActivationPercent = abs(trailingStopActivationPercent)
        self.partialLiquidationPercent = abs(partialLiquidationPercent)
        self.trailingAbsoluteHoldingsState = dict()
        self.highestProfit = dict()
        self.marketVolatility = None  # Placeholder for market volatility

    def ManageRisk(self, algorithm, targets):
        # Update market volatility (e.g., using VIX or a custom metric)
        self.marketVolatility = self.calculateMarketVolatility()

        riskAdjustedTargets = list()
        if self.algo.Time.time() < dt.time(9, 31):
            return riskAdjustedTargets

        for kvp in algorithm.Securities:
            symbol = kvp.Key
            security = kvp.Value

            # Initialize the symbol in dictionaries if not present
            if symbol not in self.trailingAbsoluteHoldingsState:
                self.trailingAbsoluteHoldingsState[symbol] = HoldingsState(None, 0)
            if symbol not in self.highestProfit:
                self.highestProfit[symbol] = 0

            if not security.HasData or not security.Invested:
                self.resetState(symbol)
                continue

            self.updateHoldingsState(symbol, security)

            trailingAbsoluteHoldingsState = self.trailingAbsoluteHoldingsState[symbol]
            unrealizedProfitPercent = self.calculateUnrealizedProfitPercent(symbol, security)

            # Incorporate fundamental data in trailing stop logic
            fundamentalMultiplier = self.getFundamentalMultiplier(symbol, security)

             # Use dict.get() method to safely access the value with a default if key doesn't exist
            highestProfitForSymbol = self.highestProfit.get(symbol, 0)  # Default to 0 if not present


            # Dynamic adjustment of trailing stop level
            trailingStopLevel = max(self.trailingStopPercent * fundamentalMultiplier * (1 - self.highestProfit[symbol]),
                                    self.trailingStopActivationPercent * self.marketVolatility)

            trailingStopPrice = trailingAbsoluteHoldingsState.absoluteHoldingsValue * (1 - trailingStopLevel)

            if self.shouldTriggerStop(unrealizedProfitPercent, security, trailingStopPrice):
                self.executeTrailingStop(symbol, security, trailingStopPrice, riskAdjustedTargets)

        return riskAdjustedTargets

    # Additional methods to be implemented
    def calculateMarketVolatility(self):
        # Example: Use a proxy like VIX to represent market volatility
        # This requires VIX data to be added to the algorithm's universe
        vixSecurity = self.algo.Securities['VIX']
        if vixSecurity.Price > 0:
            return vixSecurity.Price / 100  # Normalize the VIX value
        else:
            return 1  # Default value if VIX data is not available

    def getFundamentalMultiplier(self, symbol, security):
        # Example: Use P/E ratio as a fundamental factor
        # This requires fundamental data for the securities
        if security.Fundamentals.ValuationRatios.PERatio is not None:
            peRatio = security.Fundamentals.ValuationRatios.PERatio
            # Example logic: Lower P/E ratio might imply a stronger fundamental position
            return 1 / max(peRatio, 1)  # Ensuring the multiplier is never zero
        else:
            return 1  # Default multiplier if P/E ratio is not available

    def calculateUnrealizedProfitPercent(self, symbol, security):
        holdingsState = self.trailingAbsoluteHoldingsState.get(symbol, None)
        if holdingsState is not None:
            return (security.Holdings.UnrealizedProfit / holdingsState.absoluteHoldingsValue)
        return 0

    def shouldTriggerStop(self, unrealizedProfitPercent, security, trailingStopPrice):
        # Trigger stop if unrealized profit is beyond a certain threshold and price is below trailing stop price
        return unrealizedProfitPercent >= self.trailingStopActivationPercent and security.Price <= trailingStopPrice

    def executeTrailingStop(self, symbol, security, trailingStopPrice, riskAdjustedTargets):
        price = security.Price
        partialLiquidationValue = self.partialLiquidationPercent * security.Holdings.AbsoluteHoldingsValue
        sharesToSell = partialLiquidationValue / price
        self.algo.Log(f"Trailing stop triggered for {symbol}. Selling {sharesToSell} shares.")
        riskAdjustedTargets.append(PortfolioTarget(symbol, security.Holdings.Quantity - sharesToSell))

    def resetState(self, symbol):
        self.trailingAbsoluteHoldingsState.pop(symbol, None)
        self.highestProfit.pop(symbol, None)

    def updateHoldingsState(self, symbol, security):
        position = PositionSide.Long if security.Holdings.IsLong else PositionSide.Short
        absoluteHoldingsValue = security.Holdings.AbsoluteHoldingsValue

        if symbol not in self.trailingAbsoluteHoldingsState:
            # If the symbol is not already in the state dictionary, add it
            self.trailingAbsoluteHoldingsState[symbol] = HoldingsState(position, absoluteHoldingsValue)
        else:
            # If the symbol is already in the state dictionary, update it
            trailingState = self.trailingAbsoluteHoldingsState[symbol]
            if trailingState.position != position:
                # If the position type (long/short) has changed, update the holdings state
                self.trailingAbsoluteHoldingsState[symbol] = HoldingsState(position, absoluteHoldingsValue)
            else:
                # If the position type is the same, update the holdings value if it's higher than the recorded value
                trailingState.absoluteHoldingsValue = max(trailingState.absoluteHoldingsValue, absoluteHoldingsValue)


class HoldingsState:
    def __init__(self, position, absoluteHoldingsValue):
        self.position = position
        self.absoluteHoldingsValue = absoluteHoldingsValue
#region imports
from AlgorithmImports import *
#endregion


# Your New Python File
from AlgorithmImports import *
import datetime as dt

class TaggedTrailingStop(RiskManagementModel):
    def __init__(self, algo, trailingStopPercent=0.05, trailingStopActivationPercent=0.03, partialLiquidationPercent=0.00):
        self.algo = algo
        self.trailingStopPercent = abs(trailingStopPercent)
        self.trailingStopActivationPercent = abs(trailingStopActivationPercent)
        self.partialLiquidationPercent = abs(partialLiquidationPercent)
        self.trailingAbsoluteHoldingsState = dict()
        self.highestProfit = dict()
        self.marketVolatility = None  # Placeholder for market volatility

    def ManageRisk(self, algorithm, targets):
        # Update market volatility (e.g., using VIX or a custom metric)
        self.marketVolatility = self.calculateMarketVolatility()

        riskAdjustedTargets = list()
        if self.algo.Time.time() < dt.time(9, 31):
            return riskAdjustedTargets

        for kvp in algorithm.Securities:
            symbol = kvp.Key
            security = kvp.Value

            # Initialize the symbol in dictionaries if not present
            if symbol not in self.trailingAbsoluteHoldingsState:
                self.trailingAbsoluteHoldingsState[symbol] = HoldingsState(None, 0)
            if symbol not in self.highestProfit:
                self.highestProfit[symbol] = 0

            if not security.HasData or not security.Invested:
                self.resetState(symbol)
                continue

            self.updateHoldingsState(symbol, security)

            trailingAbsoluteHoldingsState = self.trailingAbsoluteHoldingsState[symbol]
            unrealizedProfitPercent = self.calculateUnrealizedProfitPercent(symbol, security)

            # Incorporate fundamental data in trailing stop logic
            fundamentalMultiplier = self.getFundamentalMultiplier(symbol, security)

             # Use dict.get() method to safely access the value with a default if key doesn't exist
            highestProfitForSymbol = self.highestProfit.get(symbol, 0)  # Default to 0 if not present


            # Dynamic adjustment of trailing stop level
            trailingStopLevel = max(self.trailingStopPercent * fundamentalMultiplier * (1 - self.highestProfit[symbol]),
                                    self.trailingStopActivationPercent * self.marketVolatility)

            trailingStopPrice = trailingAbsoluteHoldingsState.absoluteHoldingsValue * (1 - trailingStopLevel)

            if self.shouldTriggerStop(unrealizedProfitPercent, security, trailingStopPrice):
                self.executeTrailingStop(symbol, security, trailingStopPrice, riskAdjustedTargets)

        return riskAdjustedTargets

    # Additional methods to be implemented
    def calculateMarketVolatility(self):
        # Example: Use a proxy like VIX to represent market volatility
        # This requires VIX data to be added to the algorithm's universe
        vixSecurity = self.algo.Securities['VIX']
        if vixSecurity.Price > 0:
            return vixSecurity.Price / 100  # Normalize the VIX value
        else:
            return 1  # Default value if VIX data is not available

    def getFundamentalMultiplier(self, symbol, security):
        # Example: Use P/E ratio as a fundamental factor
        # This requires fundamental data for the securities
        if security.Fundamentals.ValuationRatios.PERatio is not None:
            peRatio = security.Fundamentals.ValuationRatios.PERatio
            # Example logic: Lower P/E ratio might imply a stronger fundamental position
            return 1 / max(peRatio, 1)  # Ensuring the multiplier is never zero
        else:
            return 1  # Default multiplier if P/E ratio is not available

    def calculateUnrealizedProfitPercent(self, symbol, security):
        holdingsState = self.trailingAbsoluteHoldingsState.get(symbol, None)
        if holdingsState is not None:
            return (security.Holdings.UnrealizedProfit / holdingsState.absoluteHoldingsValue)
        return 0

    def shouldTriggerStop(self, unrealizedProfitPercent, security, trailingStopPrice):
        # Trigger stop if unrealized profit is beyond a certain threshold and price is below trailing stop price
        return unrealizedProfitPercent >= self.trailingStopActivationPercent and security.Price <= trailingStopPrice

    def executeTrailingStop(self, symbol, security, trailingStopPrice, riskAdjustedTargets):
        price = security.Price
        partialLiquidationValue = self.partialLiquidationPercent * security.Holdings.AbsoluteHoldingsValue
        sharesToSell = partialLiquidationValue / price
        self.algo.Log(f"Trailing stop triggered for {symbol}. Selling {sharesToSell} shares.")
        riskAdjustedTargets.append(PortfolioTarget(symbol, security.Holdings.Quantity - sharesToSell))

    def resetState(self, symbol):
        self.trailingAbsoluteHoldingsState.pop(symbol, None)
        self.highestProfit.pop(symbol, None)

    def updateHoldingsState(self, symbol, security):
        position = PositionSide.Long if security.Holdings.IsLong else PositionSide.Short
        absoluteHoldingsValue = security.Holdings.AbsoluteHoldingsValue

        if symbol not in self.trailingAbsoluteHoldingsState:
            # If the symbol is not already in the state dictionary, add it
            self.trailingAbsoluteHoldingsState[symbol] = HoldingsState(position, absoluteHoldingsValue)
        else:
            # If the symbol is already in the state dictionary, update it
            trailingState = self.trailingAbsoluteHoldingsState[symbol]
            if trailingState.position != position:
                # If the position type (long/short) has changed, update the holdings state
                self.trailingAbsoluteHoldingsState[symbol] = HoldingsState(position, absoluteHoldingsValue)
            else:
                # If the position type is the same, update the holdings value if it's higher than the recorded value
                trailingState.absoluteHoldingsValue = max(trailingState.absoluteHoldingsValue, absoluteHoldingsValue)


class HoldingsState:
    def __init__(self, position, absoluteHoldingsValue):
        self.position = position
        self.absoluteHoldingsValue = absoluteHoldingsValue