Overall Statistics
Total Orders
101081
Average Win
0.01%
Average Loss
-0.01%
Compounding Annual Return
1.574%
Drawdown
34.600%
Expectancy
-0.011
Start Equity
10000000
End Equity
11051319.63
Net Profit
10.513%
Sharpe Ratio
-0.089
Sortino Ratio
-0.106
Probabilistic Sharpe Ratio
0.531%
Loss Rate
42%
Win Rate
58%
Profit-Loss Ratio
0.71
Alpha
-0.029
Beta
0.253
Annual Standard Deviation
0.095
Annual Variance
0.009
Information Ratio
-0.588
Tracking Error
0.151
Treynor Ratio
-0.034
Total Fees
$54307.79
Estimated Strategy Capacity
$65000000.00
Lowest Capacity Asset
ABT R735QTJ8XC9X
Portfolio Turnover
1.13%
# from AlgorithmImports import *
# from datetime import datetime, timedelta

# class OptimalStopStrategyAlgorithm(QCAlgorithm):
#     def Initialize(self):
#         self.SetStartDate(2018, 1, 1)  # Set the start date for the algorithm
#         self.SetEndDate(datetime.today())  # Set the end date to today
#         self.SetCash(10000000)  # Set the initial cash for the algorithm
#         self.UniverseSettings.Resolution = Resolution.Daily  # Set the universe selection resolution to daily

#         # Add universe selection based on the constituents of SPY ETF
#         self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)

#         # Dictionary to store stock data including the rolling window
#         self.data = {}
#         self.SetBenchmark("SPY")


#         # Lists to track long and short positions
#         self.long_positions = []
#         self.short_positions = []

#     def CoarseSelectionFunction(self, coarse):
#         sorted_by_dollar_volume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)
#         return [x.Symbol for x in sorted_by_dollar_volume[:500]]

#     def FineSelectionFunction(self, fine):
#         # Filter fine universe to include only stocks with market cap > 10 billion
#         return [x.Symbol for x in fine if x.MarketCap > 1e10]

#     def OnSecuritiesChanged(self, changes):
#         # Remove data for securities that are no longer in the universe
#         for security in changes.RemovedSecurities:
#             if security.Symbol in self.data:
#                 del self.data[security.Symbol]

#         # Add data for new securities in the universe
#         for security in changes.AddedSecurities:
#             symbol = security.Symbol
#             self.data[symbol] = {
#                 'high_window': RollingWindow[Decimal](30) ,
#                 'low_window': RollingWindow[Decimal](30) ,
#                 'atr': self.ATR(symbol, 14, Resolution.Daily),
#                 'stop_price': None,
#                 'long_position': False,
#                 'short_position': False
#             }
#             self.AddEquity(symbol, Resolution.Daily)

#     def OnData(self, data):
#         for symbol, stock_data in list(self.data.items()):
#             if symbol in data.Bars:
#                 bar = data.Bars[symbol]
#                 stock_data['high_window'].Add(bar.High)
#                 stock_data['low_window'].Add(bar.Low)
#                 atr = stock_data['atr'].Current.Value

#                 if stock_data['high_window'].IsReady and stock_data['low_window'].IsReady:
#                     current_price = bar.Close
#                     highest_high = max(stock_data['high_window'])
#                     lowest_low = min(stock_data['low_window'])

#                     # Long Entry Condition
#                     if current_price >= highest_high and not stock_data['long_position'] and not stock_data['short_position']:
#                         stock_data['long_position'] = True
#                         stock_data['short_position'] = False
#                         stock_data['stop_price'] = current_price - 2 * atr
#                         self.long_positions.append(symbol)
#                         self.Debug(f"Long Entry: {symbol} at {current_price} with stop at {stock_data['stop_price']}")

#                     # Short Entry Condition
#                     if current_price <= lowest_low and not stock_data['short_position'] and not stock_data['long_position']:
#                         stock_data['short_position'] = True
#                         stock_data['long_position'] = False
#                         stock_data['stop_price'] = current_price + (current_price * 0.05)
#                         self.short_positions.append(symbol)
#                         self.Debug(f"Short Entry: {symbol} at {current_price} with stop at {stock_data['stop_price']}")

#                     # Long Stop Condition
#                     if stock_data['long_position'] and current_price <= stock_data['stop_price']:
#                         self.long_positions.remove(symbol)
#                         stock_data['long_position'] = False
#                         self.Liquidate(symbol)
#                         self.Debug(f"Long Exit: {symbol} at {current_price}")

#                     # Short Stop Condition
#                     if stock_data['short_position'] and current_price >= stock_data['stop_price']:
#                         self.short_positions.remove(symbol)
#                         stock_data['short_position'] = False
#                         self.Liquidate(symbol)
#                         self.Debug(f"Short Exit: {symbol} at {current_price}")

#         self.Rebalance()

#     def Rebalance(self):
#         num_longs = len(self.long_positions)
#         num_shorts = len(self.short_positions)
#         max_allocation = 0.05

#         # Adjust long positions
#         if num_longs > 0:
#             long_allocation = min(max_allocation, 1.0 / num_longs)
#             for symbol in self.long_positions:
#                 if symbol in self.Securities:
#                     price = self.Securities[symbol].Price
#                     if price > 0:
#                         target_quantity = round(self.Portfolio.TotalPortfolioValue * long_allocation / price)
#                         current_quantity = self.Portfolio[symbol].Quantity
#                         quantity_difference = target_quantity - current_quantity
#                         if quantity_difference != 0:
#                             self.MarketOrder(symbol, quantity_difference)

#         # Adjust short positions
#         if num_shorts > 0:
#             short_allocation = -min(max_allocation, 1.0 / num_shorts)
#             for symbol in self.short_positions:
#                 if symbol in self.Securities:
#                     price = self.Securities[symbol].Price
#                     if price > 0:
#                         target_quantity = round(self.Portfolio.TotalPortfolioValue * short_allocation / price)
#                         current_quantity = self.Portfolio[symbol].Quantity
#                         quantity_difference = target_quantity - current_quantity
#                         if quantity_difference != 0:
#                             self.MarketOrder(symbol, quantity_difference)

#     def OnEndOfAlgorithm(self):
#         self.Liquidate()
#         self.Debug("Liquidated all positions at the end of the algorithm.")

#     def LiquidateMostProfitable(self):
#         max_profit = 0
#         max_symbol = None
#         for symbol, holding in self.Portfolio.items():
#             profit = holding.UnrealizedProfit
#             if profit > max_profit:
#                 max_profit = profit
#                 max_symbol = symbol

#         if max_symbol is not None:
#             self.Liquidate(max_symbol)
#             self.Debug(f"Liquidated {max_symbol} to free up funds")


##########################################################################

# from AlgorithmImports import *
# from datetime import datetime, timedelta

# class OptimalStopStrategyAlgorithm(QCAlgorithm):
#     def Initialize(self):
#         self.SetStartDate(2018, 1, 1)  # Set the start date for the algorithm
#         self.SetEndDate(datetime.today())  # Set the end date to today
#         self.SetCash(10000000)  # Set the initial cash for the algorithm
#         self.UniverseSettings.Resolution = Resolution.Daily  # Set the universe selection resolution to daily

#         # Add universe selection based on the constituents of SPY ETF
#         self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)

#         # Dictionary to store stock data including the rolling window
#         self.data = {}
#         self.SetBenchmark("SPY")

#         # Define thresholds
#         self.buy_threshold = 9
#         self.sell_threshold = 9

#         # Lists to track long and short positions
#         self.long_positions = []
#         self.short_positions = []

#     def CoarseSelectionFunction(self, coarse):
#         sorted_by_dollar_volume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)
#         return [x.Symbol for x in sorted_by_dollar_volume[:500]]

#     def FineSelectionFunction(self, fine):
#         # Filter fine universe to include only stocks with market cap > 10 billion
#         return [x.Symbol for x in fine if x.MarketCap > 1e10]

#     def OnSecuritiesChanged(self, changes):
#         # Remove data for securities that are no longer in the universe
#         for security in changes.RemovedSecurities:
#             if security.Symbol in self.data:
#                 del self.data[security.Symbol]

#         # Add data for new securities in the universe
#         for security in changes.AddedSecurities:
#             symbol = security.Symbol
#             self.data[symbol] = {
#                 'high_window': RollingWindow[Decimal](30)  ,
#                 'low_window': RollingWindow[Decimal](30)  ,
#                 'stop_price': None,
#                 'long_position': False,
#                 'short_position': False
#             }
#             self.AddEquity(symbol, Resolution.Daily)

#     def OnData(self, data):
#         for symbol, stock_data in list(self.data.items()):
#             if symbol in data.Bars:
#                 bar = data.Bars[symbol]
#                 stock_data['high_window'].Add(bar.High)
#                 stock_data['low_window'].Add(bar.Low)

#                 if stock_data['high_window'].IsReady and stock_data['low_window'].IsReady:
#                     current_price = bar.Close
#                     highest_high = max(stock_data['high_window'])
#                     lowest_low = min(stock_data['low_window'])

#                     # Long Entry Condition
#                     if current_price >= highest_high and not stock_data['long_position'] and not stock_data['short_position']:
#                         stock_data['long_position'] = True
#                         stock_data['short_position'] = False
#                         stock_data['stop_price'] = current_price * 0.80  # 20% below the entry price
#                         self.long_positions.append(symbol)
#                         self.Debug(f"Long Entry: {symbol} at {current_price} with stop at {stock_data['stop_price']}")

#                     # Short Entry Condition
#                     if current_price <= lowest_low and not stock_data['short_position'] and not stock_data['long_position']:
#                         stock_data['short_position'] = True
#                         stock_data['long_position'] = False
#                         stock_data['stop_price'] = current_price * 1.20  # 20% above the entry price
#                         self.short_positions.append(symbol)
#                         self.Debug(f"Short Entry: {symbol} at {current_price} with stop at {stock_data['stop_price']}")

#                     # Long Stop Condition
#                     if stock_data['long_position'] and current_price <= stock_data['stop_price']:
#                         self.long_positions.remove(symbol)
#                         stock_data['long_position'] = False
#                         self.Liquidate(symbol)
#                         self.Debug(f"Long Exit: {symbol} at {current_price}")

#                     # Short Stop Condition
#                     if stock_data['short_position'] and current_price >= stock_data['stop_price']:
#                         self.short_positions.remove(symbol)
#                         stock_data['short_position'] = False
#                         self.Liquidate(symbol)
#                         self.Debug(f"Short Exit: {symbol} at {current_price}")

#         self.Rebalance()

#     def Rebalance(self):
#         num_longs = len(self.long_positions)
#         num_shorts = len(self.short_positions)
#         max_allocation = 0.05

#         # Adjust long positions
#         if num_longs > 0:
#             long_allocation = min(max_allocation, 1.0 / num_longs)
#             for symbol in self.long_positions:
#                 if symbol in self.Securities:
#                     price = self.Securities[symbol].Price
#                     if price > 0:
#                         target_quantity = round(self.Portfolio.TotalPortfolioValue * long_allocation / price)
#                         current_quantity = self.Portfolio[symbol].Quantity
#                         quantity_difference = target_quantity - current_quantity
#                         if quantity_difference != 0:
#                             self.MarketOrder(symbol, quantity_difference)

#         # Adjust short positions
#         if num_shorts > 0:
#             short_allocation = -min(max_allocation, 1.0 / num_shorts)
#             for symbol in self.short_positions:
#                 if symbol in self.Securities:
#                     price = self.Securities[symbol].Price
#                     if price > 0:
#                         target_quantity = round(self.Portfolio.TotalPortfolioValue * short_allocation / price)
#                         current_quantity = self.Portfolio[symbol].Quantity
#                         quantity_difference = target_quantity - current_quantity
#                         if quantity_difference != 0:
#                             self.MarketOrder(symbol, quantity_difference)

#     def OnEndOfAlgorithm(self):
#         self.Liquidate()
#         self.Debug("Liquidated all positions at the end of the algorithm.")

#     def LiquidateMostProfitable(self):
#         max_profit = 0
#         max_symbol = None
#         for symbol, holding in self.Portfolio.items():
#             profit = holding.UnrealizedProfit
#             if profit > max_profit:
#                 max_profit = profit
#                 max_symbol = symbol

#         if max_symbol is not None:
#             self.Liquidate(max_symbol)
#             self.Debug(f"Liquidated {max_symbol} to free up funds")

##############################################################################

### Trailing stops Try 1


# from AlgorithmImports import *
# from datetime import datetime, timedelta

# class OptimalStopStrategyAlgorithm(QCAlgorithm):
#     def Initialize(self):
#         self.SetStartDate(2018, 1, 1)  # Set the start date for the algorithm
#         self.SetEndDate(datetime.today())  # Set the end date to today
#         self.SetCash(10000000)  # Set the initial cash for the algorithm
#         self.UniverseSettings.Resolution = Resolution.Daily  # Set the universe selection resolution to daily

#         # Add universe selection based on the constituents of SPY ETF
#         self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)

#         # Dictionary to store stock data including the rolling window
#         self.data = {}
#         self.SetBenchmark("SPY")

#         # Lists to track long and short positions
#         self.long_positions = []
#         self.short_positions = []

#     def CoarseSelectionFunction(self, coarse):
#         sorted_by_dollar_volume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)
#         return [x.Symbol for x in sorted_by_dollar_volume[:500]]

#     def FineSelectionFunction(self, fine):
#         # Filter fine universe to include only stocks with market cap > 10 billion
#         return [x.Symbol for x in fine if x.MarketCap > 1e10]

#     def OnSecuritiesChanged(self, changes):
#         # Remove data for securities that are no longer in the universe
#         for security in changes.RemovedSecurities:
#             if security.Symbol in self.data:
#                 del self.data[security.Symbol]

#         # Add data for new securities in the universe
#         for security in changes.AddedSecurities:
#             symbol = security.Symbol
#             self.data[symbol] = {
#                 'high_window': RollingWindow[Decimal](30) ,
#                 'low_window': RollingWindow[Decimal](30) ,
#                 'atr': self.ATR(symbol, 14, Resolution.Daily),
#                 'stop_price': None,
#                 'long_position': False,
#                 'short_position': False
#             }
#             self.AddEquity(symbol, Resolution.Daily)

#     def OnData(self, data):
#         for symbol, stock_data in list(self.data.items()):
#             if symbol in data.Bars:
#                 bar = data.Bars[symbol]
#                 stock_data['high_window'].Add(bar.High)
#                 stock_data['low_window'].Add(bar.Low)

#                 if stock_data['high_window'].IsReady and stock_data['low_window'].IsReady:
#                     current_price = bar.Close
#                     highest_high = max(stock_data['high_window'])
#                     lowest_low = min(stock_data['low_window'])
#                     atr = stock_data['atr'].Current.Value

#                     # Long Entry Condition
#                     if current_price >= highest_high and not stock_data['long_position'] and not stock_data['short_position']:
#                         stock_data['long_position'] = True
#                         stock_data['short_position'] = False
#                         stock_data['stop_price'] = current_price - 2 * atr  # Set trailing stop
#                         self.long_positions.append(symbol)
#                         self.Debug(f"Long Entry: {symbol} at {current_price} with initial stop at {stock_data['stop_price']}")

#                     # Short Entry Condition
#                     if current_price <= lowest_low and not stock_data['short_position'] and not stock_data['long_position']:
#                         stock_data['short_position'] = True
#                         stock_data['long_position'] = False
#                         stock_data['stop_price'] = current_price + 2 * atr  # Set trailing stop
#                         self.short_positions.append(symbol)
#                         self.Debug(f"Short Entry: {symbol} at {current_price} with initial stop at {stock_data['stop_price']}")

#                     # Update Long Trailing Stop
#                     if stock_data['long_position']:
#                         new_stop_price = current_price - 2 * atr
#                         if new_stop_price > stock_data['stop_price']:
#                             stock_data['stop_price'] = new_stop_price
#                             self.Debug(f"Updated Long Stop: {symbol} to {stock_data['stop_price']} at {current_price}")

#                     # Update Short Trailing Stop
#                     if stock_data['short_position']:
#                         new_stop_price = current_price + 2 * atr
#                         if new_stop_price < stock_data['stop_price']:
#                             stock_data['stop_price'] = new_stop_price
#                             self.Debug(f"Updated Short Stop: {symbol} to {stock_data['stop_price']} at {current_price}")

#                     # Long Stop Condition
#                     if stock_data['long_position'] and current_price <= stock_data['stop_price']:
#                         self.long_positions.remove(symbol)
#                         stock_data['long_position'] = False
#                         self.Liquidate(symbol)
#                         self.Debug(f"Long Exit: {symbol} at {current_price}")

#                     # Short Stop Condition
#                     if stock_data['short_position'] and current_price >= stock_data['stop_price']:
#                         self.short_positions.remove(symbol)
#                         stock_data['short_position'] = False
#                         self.Liquidate(symbol)
#                         self.Debug(f"Short Exit: {symbol} at {current_price}")

#         self.Rebalance()

#     def Rebalance(self):
#         num_longs = len(self.long_positions)
#         num_shorts = len(self.short_positions)
#         max_allocation = 0.05

#         # Adjust long positions
#         if num_longs > 0:
#             long_allocation = min(max_allocation, 1.0 / num_longs)
#             for symbol in self.long_positions:
#                 if symbol in self.Securities:
#                     price = self.Securities[symbol].Price
#                     if price > 0:
#                         target_quantity = round(self.Portfolio.TotalPortfolioValue * long_allocation / price)
#                         current_quantity = self.Portfolio[symbol].Quantity
#                         quantity_difference = target_quantity - current_quantity
#                         if quantity_difference != 0:
#                             self.MarketOrder(symbol, quantity_difference)

#         # Adjust short positions
#         if num_shorts > 0:
#             short_allocation = -min(max_allocation, 1.0 / num_shorts)
#             for symbol in self.short_positions:
#                 if symbol in self.Securities:
#                     price = self.Securities[symbol].Price
#                     if price > 0:
#                         target_quantity = round(self.Portfolio.TotalPortfolioValue * short_allocation / price)
#                         current_quantity = self.Portfolio[symbol].Quantity
#                         quantity_difference = target_quantity - current_quantity
#                         if quantity_difference != 0:
#                             self.MarketOrder(symbol, quantity_difference)

#     def OnEndOfAlgorithm(self):
#         self.Liquidate()
#         self.Debug("Liquidated all positions at the end of the algorithm.")

#     def LiquidateMostProfitable(self):
#         max_profit = 0
#         max_symbol = None
#         for symbol, holding in self.Portfolio.items():
#             profit = holding.UnrealizedProfit
#             if profit > max_profit:
#                 max_profit = profit
#                 max_symbol = symbol

#         if max_symbol is not None:
#             self.Liquidate(max_symbol)
#             self.Debug(f"Liquidated {max_symbol} to free up funds")



########################################################################################


# Trailing Stop v2   RollingWindow[Decimal](30)

# from AlgorithmImports import *
# from datetime import datetime, timedelta

# class OptimalStopStrategyAlgorithm(QCAlgorithm):
#     def Initialize(self):
#         self.SetStartDate(2018, 1, 1)  # Set the start date for the algorithm
#         self.SetEndDate(datetime.today())  # Set the end date to today
#         self.SetCash(10000000)  # Set the initial cash for the algorithm
#         self.UniverseSettings.Resolution = Resolution.Daily  # Set the universe selection resolution to daily

#         # Add universe selection based on the constituents of SPY ETF
#         self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)

#         # Dictionary to store stock data including the rolling window
#         self.data = {}
#         self.SetBenchmark("SPY")

#         # Lists to track long and short positions
#         self.long_positions = []
#         self.short_positions = []

#     def CoarseSelectionFunction(self, coarse):
#         sorted_by_dollar_volume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)
#         return [x.Symbol for x in sorted_by_dollar_volume[:500]]

#     def FineSelectionFunction(self, fine):
#         # Filter fine universe to include only stocks with market cap > 10 billion
#         return [x.Symbol for x in fine if x.MarketCap > 1e10]

#     def OnSecuritiesChanged(self, changes):
#         # Remove data for securities that are no longer in the universe
#         for security in changes.RemovedSecurities:
#             if security.Symbol in self.data:
#                 del self.data[security.Symbol]

#         # Add data for new securities in the universe
#         for security in changes.AddedSecurities:
#             symbol = security.Symbol
#             self.data[symbol] = {
#                 'high_window': RollingWindow[Decimal](30) ,
#                 'low_window': RollingWindow[Decimal](30) ,
#                 'atr': self.ATR(symbol, 14, Resolution.Daily),
#                 'stop_price': None,
#                 'long_position': False,
#                 'short_position': False,
#                 'stop_order_ticket': None
#             }
#             self.AddEquity(symbol, Resolution.Daily)

#     def OnData(self, data):
#         for symbol, stock_data in list(self.data.items()):
#             if symbol in data.Bars:
#                 bar = data.Bars[symbol]
#                 stock_data['high_window'].Add(bar.High)
#                 stock_data['low_window'].Add(bar.Low)

#                 if stock_data['high_window'].IsReady and stock_data['low_window'].IsReady:
#                     current_price = bar.Close
#                     highest_high = max(stock_data['high_window'])
#                     lowest_low = min(stock_data['low_window'])
#                     atr = stock_data['atr'].Current.Value

#                     # Long Entry Condition
#                     if current_price >= highest_high and not stock_data['long_position'] and not stock_data['short_position']:
#                         stock_data['long_position'] = True
#                         stock_data['short_position'] = False
#                         stock_data['stop_price'] = current_price - 2 * atr  # Set initial stop price
#                         self.long_positions.append(symbol)
#                         self.Debug(f"Long Entry: {symbol} at {current_price} with initial stop at {stock_data['stop_price']}")

#                     # Short Entry Condition
#                     if current_price <= lowest_low and not stock_data['short_position'] and not stock_data['long_position']:
#                         stock_data['short_position'] = True
#                         stock_data['long_position'] = False
#                         stock_data['stop_price'] = current_price + 2 * atr  # Set initial stop price
#                         self.short_positions.append(symbol)
#                         self.Debug(f"Short Entry: {symbol} at {current_price} with initial stop at {stock_data['stop_price']}")

#         self.Rebalance()

#     def Rebalance(self):
#         num_longs = len(self.long_positions)
#         num_shorts = len(self.short_positions)
#         max_allocation = 0.05

#         # Adjust long positions
#         if num_longs > 0:
#             long_allocation = min(max_allocation, 1.0 / num_longs)
#             for symbol in self.long_positions:
#                 if symbol in self.Securities and symbol in self.data:
#                     price = self.Securities[symbol].Price
#                     if price > 0:
#                         target_quantity = round(self.Portfolio.TotalPortfolioValue * long_allocation / price)
#                         current_quantity = self.Portfolio[symbol].Quantity
#                         quantity_difference = target_quantity - current_quantity
#                         if quantity_difference != 0:
#                             self.MarketOrder(symbol, quantity_difference)
                        
#                         # Update or create trailing stop order
#                         atr = self.data[symbol]['atr'].Current.Value
#                         new_stop_price = price - 2 * atr
#                         if self.data[symbol]['stop_order_ticket'] is None:
#                             self.data[symbol]['stop_order_ticket'] = self.StopMarketOrder(symbol, -target_quantity, new_stop_price)
#                             self.data[symbol]['stop_price'] = new_stop_price
#                         elif new_stop_price > self.data[symbol]['stop_price']:
#                             self.data[symbol]['stop_price'] = new_stop_price
#                             self.data[symbol]['stop_order_ticket'].UpdateStopPrice(new_stop_price)
#                             self.Debug(f"Updated Long Stop: {symbol} to {new_stop_price}")

#         # Adjust short positions
#         if num_shorts > 0:
#             short_allocation = -min(max_allocation, 1.0 / num_shorts)
#             for symbol in self.short_positions:
#                 if symbol in self.Securities and symbol in self.data:
#                     price = self.Securities[symbol].Price
#                     if price > 0:
#                         target_quantity = round(self.Portfolio.TotalPortfolioValue * short_allocation / price)
#                         current_quantity = self.Portfolio[symbol].Quantity
#                         quantity_difference = target_quantity - current_quantity
#                         if quantity_difference != 0:
#                             self.MarketOrder(symbol, quantity_difference)
                        
#                         # Update or create trailing stop order
#                         atr = self.data[symbol]['atr'].Current.Value
#                         new_stop_price = price + 2 * atr
#                         if self.data[symbol]['stop_order_ticket'] is None:
#                             self.data[symbol]['stop_order_ticket'] = self.StopMarketOrder(symbol, target_quantity, new_stop_price)
#                             self.data[symbol]['stop_price'] = new_stop_price
#                         elif new_stop_price < self.data[symbol]['stop_price']:
#                             self.data[symbol]['stop_price'] = new_stop_price
#                             self.data[symbol]['stop_order_ticket'].UpdateStopPrice(new_stop_price)
#                             self.Debug(f"Updated Short Stop: {symbol} to {new_stop_price}")

#     def OnEndOfAlgorithm(self):
#         self.Liquidate()
#         self.Debug("Liquidated all positions at the end of the algorithm.")

#     def LiquidateMostProfitable(self):
#         max_profit = 0
#         max_symbol = None
#         for symbol, holding in self.Portfolio.items():
#             profit = holding.UnrealizedProfit
#             if profit > max_profit:
#                 max_profit = profit
#                 max_symbol = symbol

#         if max_symbol is not None:
#             self.Liquidate(max_symbol)
#             self.Debug(f"Liquidated {max_symbol} to free up funds")



###########################################################################################################

##### Base Case


from AlgorithmImports import *
from datetime import datetime, timedelta

class OptimalStopStrategyAlgorithm(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2018, 1, 1)  # Set the start date for the algorithm
        self.SetEndDate(datetime.today())  # Set the end date to today
        self.SetCash(10000000)  # Set the initial cash for the algorithm
        self.UniverseSettings.Resolution = Resolution.Daily  # Set the universe selection resolution to daily

        # Add universe selection based on the constituents of SPY ETF
        self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)

        # Dictionary to store stock data including the rolling window
        self.data = {}
        self.SetBenchmark("SPY")

        # Lists to track long and short positions
        self.long_positions = []
        self.short_positions = []

    def CoarseSelectionFunction(self, coarse):
        sorted_by_dollar_volume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)
        return [x.Symbol for x in sorted_by_dollar_volume[:500]]

    def FineSelectionFunction(self, fine):
        # Filter fine universe to include only stocks with market cap > 10 billion
        return [x.Symbol for x in fine if x.MarketCap > 1e10]

    def OnSecuritiesChanged(self, changes):
        # Remove data for securities that are no longer in the universe
        for security in changes.RemovedSecurities:
            if security.Symbol in self.data:
                del self.data[security.Symbol]

        # Add data for new securities in the universe
        for security in changes.AddedSecurities:
            symbol = security.Symbol
            self.data[symbol] = {
                'high_window': RollingWindow[Decimal](30) ,
                'low_window': RollingWindow[Decimal](30) ,
                'atr': self.ATR(symbol, 14, Resolution.Daily),
                'stop_price': None,
                'long_position': False,
                'short_position': False,
                'stop_order_ticket': None
            }
            self.AddEquity(symbol, Resolution.Daily)

    def OnData(self, data):
        for symbol, stock_data in list(self.data.items()):
            if symbol in data.Bars:
                bar = data.Bars[symbol]
                stock_data['high_window'].Add(bar.High)
                stock_data['low_window'].Add(bar.Low)

                if stock_data['high_window'].IsReady and stock_data['low_window'].IsReady:
                    current_price = bar.Close
                    highest_high = max(stock_data['high_window'])
                    lowest_low = min(stock_data['low_window'])
                    atr = stock_data['atr'].Current.Value

                    # Long Entry Condition
                    if current_price >= highest_high and not stock_data['long_position'] and not stock_data['short_position']:
                        stock_data['long_position'] = True
                        stock_data['short_position'] = False
                        stock_data['stop_price'] = current_price - 2 * atr  # Set initial stop price
                        self.long_positions.append(symbol)
                        self.Debug(f"Long Entry: {symbol} at {current_price} with initial stop at {stock_data['stop_price']}")

                    # Short Entry Condition
                    if current_price <= lowest_low and not stock_data['short_position'] and not stock_data['long_position']:
                        stock_data['short_position'] = True
                        stock_data['long_position'] = False
                        stock_data['stop_price'] = current_price + 2 * atr  # Set initial stop price
                        self.short_positions.append(symbol)
                        self.Debug(f"Short Entry: {symbol} at {current_price} with initial stop at {stock_data['stop_price']}")

        self.Rebalance()

    def Rebalance(self):
        num_longs = len(self.long_positions)
        num_shorts = len(self.short_positions)
        max_allocation = 0.05

        # Adjust long positions
        if num_longs > 0:
            long_allocation = min(max_allocation, 1.0 / num_longs)
            for symbol in self.long_positions:
                if symbol in self.Securities and symbol in self.data:
                    price = self.Securities[symbol].Price
                    if price > 0:
                        target_quantity = round(self.Portfolio.TotalPortfolioValue * long_allocation / price)
                        current_quantity = self.Portfolio[symbol].Quantity
                        quantity_difference = target_quantity - current_quantity
                        if quantity_difference != 0:
                            self.MarketOrder(symbol, quantity_difference)
                        
                        # Update or create trailing stop order
                        atr = self.data[symbol]['atr'].Current.Value
                        new_stop_price = price - 2 * atr
                        # if self.data[symbol]['stop_order_ticket'] is None:
                        #     self.data[symbol]['stop_order_ticket'] = self.StopMarketOrder(symbol, -target_quantity, new_stop_price)
                        #     self.data[symbol]['stop_price'] = new_stop_price
                        # elif new_stop_price > self.data[symbol]['stop_price']:
                        #     self.data[symbol]['stop_price'] = new_stop_price
                        #     self.data[symbol]['stop_order_ticket'].UpdateStopPrice(new_stop_price)
                        #     self.Debug(f"Updated Long Stop: {symbol} to {new_stop_price}")

        # Adjust short positions
        if num_shorts > 0:
            short_allocation = -min(max_allocation, 1.0 / num_shorts)
            for symbol in self.short_positions:
                if symbol in self.Securities and symbol in self.data:
                    price = self.Securities[symbol].Price
                    if price > 0:
                        target_quantity = round(self.Portfolio.TotalPortfolioValue * short_allocation / price)
                        current_quantity = self.Portfolio[symbol].Quantity
                        quantity_difference = target_quantity - current_quantity
                        if quantity_difference != 0:
                            self.MarketOrder(symbol, quantity_difference)
                        
                        # Update or create trailing stop order
                        atr = self.data[symbol]['atr'].Current.Value
                        new_stop_price = price + 2 * atr
                        # if self.data[symbol]['stop_order_ticket'] is None:
                        #     self.data[symbol]['stop_order_ticket'] = self.StopMarketOrder(symbol, target_quantity, new_stop_price)
                        #     self.data[symbol]['stop_price'] = new_stop_price
                        # elif new_stop_price < self.data[symbol]['stop_price']:
                        #     self.data[symbol]['stop_price'] = new_stop_price
                        #     self.data[symbol]['stop_order_ticket'].UpdateStopPrice(new_stop_price)
                        #     self.Debug(f"Updated Short Stop: {symbol} to {new_stop_price}")

    def OnEndOfAlgorithm(self):
        self.Liquidate()
        self.Debug("Liquidated all positions at the end of the algorithm.")

    def LiquidateMostProfitable(self):
        max_profit = 0
        max_symbol = None
        for symbol, holding in self.Portfolio.items():
            profit = holding.UnrealizedProfit
            if profit > max_profit:
                max_profit = profit
                max_symbol = symbol

        if max_symbol is not None:
            self.Liquidate(max_symbol)
            self.Debug(f"Liquidated {max_symbol} to free up funds")