Overall Statistics
Total Trades
2744
Average Win
0.17%
Average Loss
-0.14%
Compounding Annual Return
7.144%
Drawdown
12.400%
Expectancy
0.100
Net Profit
19.446%
Sharpe Ratio
0.724
Probabilistic Sharpe Ratio
27.753%
Loss Rate
51%
Win Rate
49%
Profit-Loss Ratio
1.26
Alpha
0.049
Beta
0.022
Annual Standard Deviation
0.071
Annual Variance
0.005
Information Ratio
-0.207
Tracking Error
0.217
Treynor Ratio
2.358
Total Fees
$4540.00
Estimated Strategy Capacity
$6200000.00
Lowest Capacity Asset
NOX R735QTJ8XC9X
"""
Basic Liquidation System Strategy
@version: 0.5
@creation date: 10/06/2022
- At Open, do 15min VWAP/TWAP entry for a total position of $15,000.
- At 9:45 set stop at HOD
- At 10:05 if P/L > 0, exit 50%. If P/L < 0, exit 100% (Adjust stop size accordingly)
- At 10:30 exit all.
"""

import pandas as pd
from io import StringIO
from AlgorithmImports import *

from ast import literal_eval

TICKERS_CSV = "https://drive.google.com/uc?export=download&id=1did0Sk3F9Sn5Il_nUX252jOB_n0UFqat"

AGG_OPS = {"open": "first", "close": "last",
           "high": "max", "low": "min",
           "volume": "sum"}


class LiquidationBasic(QCAlgorithm):
    def Initialize(self):
        self.capital = literal_eval(self.GetParameter("capital"))
        self.entry_size = literal_eval(self.GetParameter("entry_size"))  # Negative value for shorts
        self.wap_type = literal_eval(self.GetParameter("wap_type"))  # VWAP or TWAP
        self.wap_res = literal_eval(self.GetParameter("wap_resolution"))  # Resolution, in seconds, for WAP calculation
        self.wap_fract = self.wap_res/(15*60)

        self.SetCash(self.capital)  # Set Strategy Cash
        self.SetStartDate(2019, 5, 1)
        self.SetEndDate(2022, 6, 1)

        csv = StringIO(self.Download(TICKERS_CSV))
        self.overhang = pd.read_csv(csv, parse_dates=["Agreement Start Date"],
                                    dayfirst=True)
        self.overhang["Date"] = self.overhang["Agreement Start Date"].dt.date
        self.AddUniverse(self.coarse_filter)
        self.resolution = Resolution.Second
        self.UniverseSettings.Resolution = self.resolution
        self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))

        every_day = self.DateRules.EveryDay()
        every_second = self.TimeRules.Every(TimeSpan.FromSeconds(self.wap_res))
        at = self.TimeRules.At
        self.Schedule.On(every_day, every_second, self.open_trade)
        self.Schedule.On(every_day, at(9, 45), self.set_stop)
        self.Schedule.On(every_day, at(10, 5), self.adjust_position)
        self.Schedule.On(every_day, at(10, 30), self.close_trade)

    def open_trade(self):
        if time(9, 30) < self.Time.time() < time(9, 45):
            symbols = list(self.ActiveSecurities.Keys)
            history = self.History(symbols, self.Time.date(), self.Time,
                                   resolution=self.resolution)
            if len(history) > 0:
                self.Transactions.CancelOpenOrders()
                for symbol in symbols:
                    order_value = self.entry_size*self.wap_fract
                    price = self.Securities[symbol].Price
                    quantity = int(order_value / price)
                    self.LimitOrder(symbol, quantity, price)

    def set_stop(self):
        symbols = self.get_owned_stocks()
        history = self.History(symbols, self.Time.date(), self.Time,
                               resolution=self.resolution)
        if len(history) > 0:
            self.Debug(f"{self.Time} - Set Stop")
            self.Transactions.CancelOpenOrders()
            today_bar = history.groupby("symbol").agg(AGG_OPS)
            limits = today_bar.eval("high + (high - low)*0.5")  # Intra range 1.05 as limit price
            for s in symbols:
                self.StopLimitOrder(s, -self.Portfolio[s].Quantity,
                                    today_bar["high"][s], limits[s])

    def adjust_position(self):
        symbols = self.get_owned_stocks()
        history = self.History(symbols, self.Time.date(), self.Time,
                               resolution=self.resolution)
        if len(history) > 0:
            self.Debug(f"{self.Time} - Adjust Position")
            self.Transactions.CancelOpenOrders()
            today_bar = history.groupby("symbol").agg(AGG_OPS)
            limits = today_bar.eval("high + (high - low)*0.5")  # Intra range 1.05 as limit price
            for s in symbols:
                pl = self.Portfolio[s].get_Profit() \
                     + self.Portfolio[s].get_UnrealizedProfit()
                price = self.Securities[s].Price
                qty = self.Portfolio[s].Quantity
                if pl > 0:
                    self.LimitOrder(s, -int(qty/2), price)
                    self.StopLimitOrder(s, int(qty/2)-qty,
                                        today_bar["high"][s], limits[s])
                else:
                    self.LimitOrder(s, -int(qty), price)

    def get_owned_stocks(self):
        return [s for s in self.ActiveSecurities.Keys
                if self.Portfolio[s].Quantity != 0]

    def close_trade(self):
        if len(list(self.ActiveSecurities.Keys)) > 0:
            self.Debug(f"{self.Time} - Close Trade")
            self.Transactions.CancelOpenOrders()
            self.Liquidate()

    def coarse_filter(self, coarse):
        tickers = self.overhang.query("Date == @self.Time.date()")
        universe = [] if len(tickers) == 0 else \
            [x.Symbol for x in coarse if
             (x.Symbol.Value == tickers["ticker"]).any()]
        self.Debug(f"{self.Time} - Universe {len(tickers)} tickers")
        return universe
"""
Basic Liquidation System Strategy
@version: 0.7
@creation date: 10/06/2022
- At Open, do 15min VWAP/TWAP entry for a total position of $15,000.
- At 9:45 set stop at HOD
- At 10:05 if P/L > 0, exit 50%. If P/L < 0, exit 100% (Adjust stop size accordingly)
- At 10:30 exit all.
"""

import pandas as pd
from io import StringIO
from AlgorithmImports import *

from ast import literal_eval

TICKERS_CSV = "https://drive.google.com/uc?export=download&id=1did0Sk3F9Sn5Il_nUX252jOB_n0UFqat"

AGG_OPS = {"open": "first", "close": "last",
           "high": "max", "low": "min",
           "volume": "sum"}


class LiquidationBasic(QCAlgorithm):
    def Initialize(self):
        self.capital = literal_eval(self.GetParameter("capital"))
        self.entry_size = literal_eval(self.GetParameter("entry_size"))  # Negative value for shorts
        self.wap_type = literal_eval(self.GetParameter("wap_type"))  # VWAP or TWAP
        self.wap_res = literal_eval(self.GetParameter("wap_resolution"))  # Resolution, in seconds, for WAP calculation
        self.wap_fract = self.wap_res/(15*60)

        self.SetCash(self.capital)  # Set Strategy Cash
        self.SetStartDate(2019, 5, 1)
        self.SetEndDate(2022, 6, 1)

        csv = StringIO(self.Download(TICKERS_CSV))
        self.overhang = pd.read_csv(csv, parse_dates=["Agreement Start Date"],
                                    dayfirst=True)
        self.overhang["Date"] = self.overhang["Agreement Start Date"].dt.date
        self.AddUniverse(self.coarse_filter)
        self.resolution = Resolution.Second
        self.UniverseSettings.Resolution = self.resolution
        self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))

        every_day = self.DateRules.EveryDay()
        every_second = self.TimeRules.Every(TimeSpan.FromSeconds(self.wap_res))
        at = self.TimeRules.At
        self.Schedule.On(every_day, every_second, self.open_trade)
        self.Schedule.On(every_day, at(9, 45), self.set_stop)
        self.Schedule.On(every_day, at(10, 5), self.adjust_position)
        self.Schedule.On(every_day, at(10, 30), self.close_trade)

    def open_trade(self):
        if time(9, 30) < self.Time.time() < time(9, 45):
            symbols = list(self.ActiveSecurities.Keys)
            history = self.History(symbols, self.Time.date(), self.Time,
                                   resolution=self.resolution)
            if len(history) > 0:
                self.Transactions.CancelOpenOrders()
                for symbol in symbols:
                    order_value = self.entry_size*self.wap_fract
                    price = self.Securities[symbol].Price
                    quantity = int(order_value / price)
                    self.LimitOrder(symbol, quantity, price)

    def set_stop(self):
        symbols = self.get_owned_stocks()
        history = self.History(symbols, self.Time.date(), self.Time,
                               resolution=self.resolution)
        if len(history) > 0:
            self.Debug(f"{self.Time} - Set Stop")
            self.Transactions.CancelOpenOrders()
            today_bar = history.groupby("symbol").agg(AGG_OPS)
            limits = today_bar.eval("high + (high - low)*0.05")  # Intra range 1.05 as limit price
            for s in symbols:
                self.StopLimitOrder(s, -self.Portfolio[s].Quantity,
                                    today_bar["high"][s], limits[s])

    def adjust_position(self):
        symbols = self.get_owned_stocks()
        history = self.History(symbols, self.Time.date(), self.Time,
                               resolution=self.resolution)
        if len(history) > 0:
            self.Debug(f"{self.Time} - Adjust Position")
            self.Transactions.CancelOpenOrders()
            today_bar = history.groupby("symbol").agg(AGG_OPS)
            limits = today_bar.eval("high + (high - low)*0.05")  # Intra range 1.05 as limit price
            for s in symbols:
                pl = self.Portfolio[s].get_Profit() \
                     + self.Portfolio[s].get_UnrealizedProfit()
                price = self.Securities[s].Price
                qty = self.Portfolio[s].Quantity
                if pl > 0:
                    self.LimitOrder(s, -int(qty/2), price)
                    self.StopLimitOrder(s, int(qty/2)-qty,
                                        today_bar["high"][s], limits[s])
                else:
                    self.LimitOrder(s, -int(qty), price)

    def get_owned_stocks(self):
        return [s for s in self.ActiveSecurities.Keys
                if self.Portfolio[s].Quantity != 0]

    def close_trade(self):
        if len(list(self.ActiveSecurities.Keys)) > 0:
            self.Debug(f"{self.Time} - Close Trade")
            self.Transactions.CancelOpenOrders()
            self.Liquidate()

    def coarse_filter(self, coarse):
        tickers = self.overhang.query("Date == @self.Time.date()")
        universe = [] if len(tickers) == 0 else \
            [x.Symbol for x in coarse if
             (x.Symbol.Value == tickers["ticker"]).any()]
        self.Debug(f"{self.Time} - Universe {len(tickers)} tickers")
        return universe
"""
Full Liquidation System Strategy
@version: 0.1
@creation date: 13/7/2022

"""

import pandas as pd
from io import StringIO
from AlgorithmImports import *

from ast import literal_eval
from datetime import datetime, timedelta

AGG_OPS = {"open": "first", "high": "max", "low": "min",
           "close": "last", "volume": "sum"}
TICKERS_CSV = "https://drive.google.com/uc?export=download&id=1bzOypNRbhLMRsQzS5DJUxG0OaIRi7hI8"
#"https://drive.google.com/uc?export=download&id=1cReDW0EPToXFmOfIWtdK9-bR5doecw0-"


class LiquidationFulll(QCAlgorithm):
    def Initialize(self):
        self.capital = literal_eval(self.GetParameter("capital"))
        self.entry_size = literal_eval(self.GetParameter("entry_size"))  # Negative value for shorts

        self.SetCash(self.capital)  # Set Strategy Cash
        self.SetStartDate(2021, 10, 1)
        self.SetEndDate(2022, 7, 1)

        csv = StringIO(self.Download(TICKERS_CSV))
        self.overhang = pd.read_csv(csv, parse_dates=["Date"], dayfirst=False)
        self.overhang["Date"] = self.overhang["Date"].dt.date
        self.AddUniverse(self.coarse_filter)
        self.resolution = Resolution.Second
        self.UniverseSettings.Resolution = self.resolution

        every_day = self.DateRules.EveryDay()
        every_second = self.TimeRules.Every(TimeSpan.FromSeconds(30))
        every_minute = self.TimeRules.Every(TimeSpan.FromMinutes(15))
        at = self.TimeRules.At
        self.Schedule.On(every_day, every_second, self.open_trade)
        self.Schedule.On(every_day, every_second, self.adding_trade)
        self.Schedule.On(every_day, at(7, 5), self.PM_entry)
        self.Schedule.On(every_day, at(7, 15), self.PM_stop)
        self.Schedule.On(every_day, at(8, 10), self.PM_second_entry)
        self.Schedule.On(every_day, at(8, 15), self.PM_second_stop)
        self.Schedule.On(every_day, at(9, 45), self.set_stop)
        self.Schedule.On(every_day, at(10, 5), self.adjust_position)
        self.Schedule.On(every_day, at(10, 30), self.adjust_second_position)
        self.Schedule.On(every_day, every_minute, self.set_second_stop)
        self.Schedule.On(every_day, at(15, 55), self.close_trade)
        self.Schedule.On(every_day, at(15, 58), self.liquidate_trade)
        self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))


    def PM_entry(self):
        
        symbols = list(self.ActiveSecurities.Keys)
        history = self.History(symbols, self.Time.date(), self.Time,
                                   resolution=self.resolution)
        if len(history) > 0:
            self.Transactions.CancelOpenOrders()
            self.Debug(f"{self.Time} - PM entry1")
            hod = history["high"].groupby("symbol").max()
            lod = history["low"].groupby("symbol").min()
            intra_range = hod - lod
            intra_range1 = (hod - lod) * .1
            vwap_bars = history.eval("volume*(high+low+close)/3")
            total_vwap = vwap_bars.groupby("symbol").sum()
            vwap = total_vwap/history["volume"].groupby("symbol").sum()
            limit_price = vwap - intra_range1

            for s in symbols:
                lasts = self.Securities[s].Price
                order_val = self.entry_size
                quantity = int(order_val/vwap[s]/8)
                self.LimitOrder(s, quantity, lasts)
    
    def PM_stop(self):
        symbols = self.get_owned_stocks()
        history = self.History(symbols, self.Time.replace(hour=7,minute=00,second=00), self.Time,
                                   resolution=self.resolution)
        if len(history) > 0:
            self.Transactions.CancelOpenOrders()
            self.Debug(f"{self.Time} - PM stop1")
            hod = history["high"].groupby("symbol").max()
            lod = history["low"].groupby("symbol").min()
            intra_range = hod - lod
            intra_range1 = (hod - lod) * .1
            vwap_bars = history.eval("volume*(high+low+close)/3")
            total_vwap = vwap_bars.groupby("symbol").sum()
            vwap = total_vwap/history["volume"].groupby("symbol").sum()
            limit_price = hod + intra_range1

            for s in symbols:
                lasts = self.Securities[s].Price
                order_val = self.entry_size
                qty = self.Portfolio[s].Quantity
                self.StopLimitOrder(s, -qty, hod[s],limit_price[s])    
    
    def PM_second_entry(self):
        symbols = self.get_owned_stocks()
        history = self.History(symbols, self.Time.replace(hour=7,minute=00,second=00), self.Time,
                                   resolution=self.resolution)
        if len(history) > 0:
            self.Transactions.CancelOpenOrders()
            self.Debug(f"{self.Time} - PM entry2")
            hod = history["high"].groupby("symbol").max()
            lod = history["low"].groupby("symbol").min()
            intra_range = hod - lod
            intra_range1 = (hod - lod) * .1
            vwap_bars = history.eval("volume*(high+low+close)/3")
            total_vwap = vwap_bars.groupby("symbol").sum()
            vwap = total_vwap/history["volume"].groupby("symbol").sum()
            limit_price = hod + intra_range1

            for s in symbols:
                lasts = self.Securities[s].Price
                order_val = self.entry_size
                holding = self.Portfolio[s]
                pos_avg = holding.Price
                stop_limit = pos_avg + intra_range1[s]
                #_current = data.Bars[self.symbol.Symbol]
                pl = self.Portfolio[s].get_UnrealizedProfit()
                if pl >= 0:
                    qty = self.Portfolio[s].Quantity
                    #self.MarketOrder(s, -int(qty/2))
                    #self.LimitOrder(s, -int(qty/2),target_price[s])
                    self.LimitOrder(s, qty,lasts)
                    #self.StopLimitOrder(s, int(qty/2)-qty, hod[s],limit_price[s])
                    self.StopLimitOrder(s, -qty, hod[s],limit_price[s])
                else:
                    qty = self.Portfolio[s].Quantity
                    self.LimitOrder(s, -int(qty),lasts)
                
    def PM_second_stop(self):
        symbols = self.get_owned_stocks()
        history = self.History(symbols, self.Time.replace(hour=7,minute=00,second=00), self.Time,
                                   resolution=self.resolution)
        if len(history) > 0:
            self.Transactions.CancelOpenOrders()
            self.Debug(f"{self.Time} - PM stop2")
            hod = history["high"].groupby("symbol").max()
            lod = history["low"].groupby("symbol").min()
            intra_range = hod - lod
            intra_range1 = (hod - lod) * .1
            vwap_bars = history.eval("volume*(high+low+close)/3")
            total_vwap = vwap_bars.groupby("symbol").sum()
            vwap = total_vwap/history["volume"].groupby("symbol").sum()
            limit_price = hod + intra_range1

            for s in symbols:
                lasts = self.Securities[s].Price
                order_val = self.entry_size
                qty = self.Portfolio[s].Quantity
                self.StopLimitOrder(s, -self.Portfolio[s].Quantity, hod[s],limit_price[s])

    def open_trade(self):
        if time(9, 30) < self.Time.time() < time(9, 35):
            symbols = self.get_owned_stocks()
            history = self.History(symbols, self.Time.replace(hour=9,minute=30,second=00), self.Time,
                                   resolution=self.resolution)

            if len(history) > 0:
                self.Transactions.CancelOpenOrders()
                
                hod = history["high"].groupby("symbol").max()
                lod = history["low"].groupby("symbol").min()
                intra_range = hod - lod
                intra_range1 = (hod - lod) * .1
                vwap_bars = history.eval("volume*(high+low+close)/3")
                total_vwap = vwap_bars.groupby("symbol").sum()
                vwap = total_vwap/history["volume"].groupby("symbol").sum()
                limit_price = vwap - intra_range1
                
                for s in symbols:
                    lasts = self.Securities[s].Price
                    holding = self.Portfolio[s]
                    order_val = self.entry_size
                    pl = self.Portfolio[s].get_UnrealizedProfit()
                    
                    if pl >= 0:
                        quantity = int(order_val/vwap[s]/20)
                        #limit_price = vwap[symbol] - intra_range1[symbol]
                        self.LimitOrder(s, quantity, lasts)
                        #self.LimitOrder(symbol,quantity,self.lasts_close[symbol])
                    else:
                        qty = self.Portfolio[s].Quantity
                        self.LimitOrder(s, -int(qty), lasts)

    def adding_trade(self):
        if time(9, 35) < self.Time.time() < time(9, 45):
            symbols = self.get_owned_stocks()
            history = self.History(symbols, self.Time.replace(hour=9,minute=30,second=00), self.Time,
                                   resolution=self.resolution)
           
            if len(history) > 0:
                self.Transactions.CancelOpenOrders()
                
                hod = history["high"].groupby("symbol").max()
                lod = history["low"].groupby("symbol").min()
                intra_range = hod - lod
                intra_range1 = (hod - lod) * .1
                vwap_bars = history.eval("volume*(high+low+close)/3")
                total_vwap = vwap_bars.groupby("symbol").sum()
                vwap = total_vwap/history["volume"].groupby("symbol").sum()
                limit_price = vwap - intra_range1
                
                for s in symbols:
                    lasts = self.Securities[s].Price
                    holding = self.Portfolio[s]
                    order_val = self.entry_size
                    pl = self.Portfolio[s].get_UnrealizedProfit()
                    quantity = int(order_val/vwap[s]/80)
                    #limit_price = vwap[symbol] - intra_range1[symbol]
                    self.LimitOrder(s, quantity, lasts)
                    #self.LimitOrder(symbol,quantity,self.lasts_close[symbol])

    def set_stop(self):
        symbols = self.get_owned_stocks()
        history = self.History(symbols, self.Time.replace(hour=9,minute=30,second=00), self.Time,
                               resolution=self.resolution)
        if len(history) > 0:
            self.Debug(f"{self.Time} - Set Stop")
            self.Transactions.CancelOpenOrders()
            hod = history["high"].groupby("symbol").max()
            lod = history["low"].groupby("symbol").min()
            intra_range = hod - lod
            intra_range1 = (hod - lod) *.05
            intra_range2 = (hod - lod) *.15
            stop_price = hod + intra_range1
            limit_price = hod + intra_range2
            target_price = lod + intra_range1
            
            for s in symbols:
                    holding = self.Portfolio[s]
                    pos_avg = holding.Price
                    lasts = self.Securities[s].Price
                    qty = self.Portfolio[s].Quantity
                    #self.StopLimitOrder(s, -self.Portfolio[s].Quantity, self.Portfolio[s].Price, hod[s])
                    self.StopLimitOrder(s, -self.Portfolio[s].Quantity, stop_price[s],limit_price[s])
                    #self.LimitOrder(s, -int(qty/4),target_price[s])
            
    def adjust_position(self):
        symbols = self.get_owned_stocks()
        history = self.History(symbols, self.Time.replace(hour=9,minute=30,second=00), self.Time,
                               resolution=self.resolution)
        if len(history) > 0:
            self.Debug(f"{self.Time} - Adjust Position")
            self.Transactions.CancelOpenOrders()
            hod = history["high"].groupby("symbol").max()
            lod = history["low"].groupby("symbol").min()
            intra_range = hod - lod
            intra_range1 = (hod - lod) * .05
            limit_price = hod + intra_range1
            target_price = lod + intra_range1
            
            for s in symbols:
                lasts = self.Securities[s].Price
                holding = self.Portfolio[s]
                pos_avg = holding.Price
                stop_limit = pos_avg + intra_range1[s]
                pl = self.Portfolio[s].get_UnrealizedProfit()
               # pl = self.Portfolio[s].get_Profit() \
                #     + self.Portfolio[s].get_UnrealizedProfit()
                if pl > 0:
                    qty = self.Portfolio[s].Quantity
                    #self.MarketOrder(s, -int(qty/2))
                    #self.LimitOrder(s, -int(qty/2),target_price[s])
                    #self.LimitOrder(s, -int(qty/4),lasts)
                    #self.StopLimitOrder(s, int(qty/2)-qty, hod[s],limit_price[s])
                    self.StopLimitOrder(s, -int(qty), pos_avg, stop_limit)
                else:
                    qty = self.Portfolio[s].Quantity
                    self.LimitOrder(s, -int(qty),lasts)
                    #self.Liquidate(s)
    
    def adjust_second_position(self):
        symbols = self.get_owned_stocks()
        history = self.History(symbols, self.Time.replace(hour=9,minute=30,second=00), self.Time,
                               resolution=self.resolution)
        if len(history) > 0:
            self.Debug(f"{self.Time} - Adjust Position_2")
            self.Transactions.CancelOpenOrders()
            hod = history["high"].groupby("symbol").max()
            lod = history["low"].groupby("symbol").min()
            intra_range = hod - lod
            intra_range1 = (hod - lod) * .05
            intra_range2 = (hod - lod) * .15
            limit_price = hod + intra_range1
            target_price = lod + intra_range1
            
            for s in symbols:
                lasts = self.Securities[s].Price
                holding = self.Portfolio[s]
                pos_avg = holding.Price
                stop_limit = pos_avg + intra_range2[s]
                pl = self.Portfolio[s].get_UnrealizedProfit()
                
                if pl > 0:
                    qty = self.Portfolio[s].Quantity
                    #self.MarketOrder(s, -int(qty/2))
                    self.LimitOrder(s, int(qty/2),target_price[s])
                    #self.StopLimitOrder(s, int(qty/2)-qty, hod[s],limit_price[s])
                    self.StopLimitOrder(s, -int(qty), pos_avg, stop_limit)
                else:
                    qty = self.Portfolio[s].Quantity
                    self.LimitOrder(s, -int(qty),lasts)
                    #self.Liquidate(s)

    def set_second_stop(self):
        if time(10, 35) < self.Time.time() < time(15, 45):
            symbols = self.get_owned_stocks()
            history = self.History(symbols, self.Time.replace(hour=9,minute=30,second=00), self.Time,
                               resolution=self.resolution)
            if len(history) > 0:
                self.Debug(f"{self.Time} - Set Stop2")
                #self.Transactions.CancelOpenOrders()
                hod = history["high"].groupby("symbol").max()
                lod = history["low"].groupby("symbol").min()
                intra_range = hod - lod
                intra_range1 = (hod - lod) *.05
                intra_range2 = (hod - lod) *.15
                stop_price = hod + intra_range1
                limit_price = hod + intra_range2
                target_price = lod + intra_range1
            
                for s in symbols:
                    holding = self.Portfolio[s]
                    pos_avg = holding.Price
                    lasts = self.Securities[s].Price
                    qty = self.Portfolio[s].Quantity
                    r_pl = self.Portfolio[s].get_Profit() 
                    stop_limit = pos_avg + intra_range2[s]
                    #self.StopLimitOrder(s, -self.Portfolio[s].Quantity, self.Portfolio[s].Price, hod[s])
                    if r_pl > 0:
                        self.Transactions.CancelOpenOrders()
                        self.StopLimitOrder(s, -int(qty), pos_avg, stop_limit[s])
                   
                    #self.LimitOrder(s, -int(qty/4),target_price[s])

    def get_owned_stocks(self):
        return [s for s in self.ActiveSecurities.Keys
                if self.Portfolio[s].Quantity != 0]

    def close_trade(self):
        #if len(list(self.ActiveSecurities.Keys)) > 0:
        symbols = self.get_owned_stocks()
        history = self.History(symbols, self.Time.replace(hour=9,minute=30,second=00), self.Time,
                               resolution=self.resolution)
        if len(history) > 0:
            self.Debug(f"{self.Time} - Close Trade")
            self.Transactions.CancelOpenOrders()
            hod = history["high"].groupby("symbol").max()
            lod = history["low"].groupby("symbol").min()
            intra_range = hod - lod
            intra_range1 = (hod - lod) * .05
            
            for s in symbols:
                lasts = self.Securities[s].Price
                qty = self.Portfolio[s].Quantity
                limit_price = lasts + intra_range1[s]
                self.LimitOrder(s, -int(qty), limit_price)
            #self.Liquidate()

    def liquidate_trade(self):
        if len(list(self.ActiveSecurities.Keys)) > 0:
            self.Debug(f"{self.Time} - Liquidate Trade")
            self.Transactions.CancelOpenOrders()
            self.Liquidate()

    def coarse_filter(self, coarse):
        tickers = self.overhang.query("Date == @self.Time.date()")
        universe = [] if len(tickers) == 0 else \
            [x.Symbol for x in coarse if
             (x.Symbol.Value == tickers["Symbol"]).any()]
        self.Debug(f"{self.Time} - Universe {len(tickers)} tickers")
        return universe
    
    #def OnData(self, data: Slice):
    #    if self.aapl_symbol in data.Bars:
    #        aapl_current_trade = data.Bars[self.aapl_symbol]
#region imports
from AlgorithmImports import *
#endregion
"""
Library of indicators
@version: 0.9
"""

import pandas as pd


def filter_bars(bars, start, end):
    output = bars.unstack("symbol").between_time(start, end).stack("symbol")
    output.index = output.index.reorder_levels(["symbol", "time"])
    return output.sort_index()


def rename(bars, name):
    return bars.rename(name) if isinstance(bars, pd.Series) \
        else bars.add_prefix(f"{name}_")


# Daily indicators
# define daily indicators
def roll_max(bars, window, groupby="symbol"):
    groups = bars.groupby(groupby)
    output = groups.apply(lambda x: x.rolling(window, min_periods=1).max())
    return output


def roll_min(bars, window, groupby="symbol"):
    groups = bars.groupby(groupby)
    return groups.apply(lambda x: x.rolling(window).min())


def roll_average(bars, window, groupby="symbol", mean_type="arit"):
    mean_func = (lambda x: x.ewm(span=window).mean()) if mean_type=="exp" \
        else (lambda x: x.rolling(window).mean())
    return bars.groupby(groupby).apply(mean_func)


def roll_range(bars, window):
    max_high = roll_max(bars["high"], window).squeeze()
    min_low = roll_min(bars["low"], window).squeeze()
    avg_close = roll_average(bars["close"], window).squeeze()
    return (avg_close-min_low)/(max_high-min_low)


def roll_change(bars, window):
    return bars.groupby("symbol").pct_change(window)


def position_range(bars, window):
    yesterday_bars = bars.groupby("symbol").shift(1)  # Not including trading date
    max_high = roll_max(yesterday_bars["high"], window).squeeze()
    min_low = roll_min(yesterday_bars["low"], window).squeeze()
    return (bars["open"]-min_low)/(max_high-min_low)


def gap(bars):
    yesterday_bars = bars.groupby("symbol").shift(1)  # Not including trading date
    return bars["open"]/yesterday_bars["close"]-1


def extension(bars, window):
    max_high = roll_max(bars["high"], window).squeeze()
    min_low = roll_max(bars["low"], window).squeeze()
    return (bars["high"]-max_high)/(max_high-min_low)


def retracement(bars, window):
    max_high = roll_max(bars["high"], window).squeeze()
    min_low = roll_max(bars["low"], window).squeeze()
    return (max_high-bars["low"])/(max_high-min_low)


def gap_extension(bars):
    yesterday_bars = bars.groupby("symbol").shift(1)  # Not including trading date
    return (yesterday_bars["high"]-bars["open"])/(bars["open"]-yesterday_bars["close"])


def day_range(bars):
    return bars.eval("(open-low)/(high-low)")


def gap_retracement(bars):
    yesterday_bars = bars.groupby("symbol").shift(1)  # Not including trading date
    return (bars["open"]-yesterday_bars["low"])/(bars["open"]-yesterday_bars["close"])


def roll_vwap(bars, window):
    price_volume = bars[["high","low","close"]].mean(axis=1)*bars["volume"]
    avg_price_volume = price_volume.groupby("symbol").apply(lambda x: x.rolling(window, min_periods=1).sum())
    avg_volume = bars["volume"].groupby("symbol").apply(lambda x: x.rolling(window, min_periods=1).sum())
    return avg_price_volume/avg_volume


def shift(bars, shift):
    return bars.groupby("symbol").shift(shift)


def divergence(num_bars, den_bars):
    return num_bars/den_bars-1


# Intra day indicators
day_grouper = [pd.Grouper(level="symbol"), pd.Grouper(level="time", freq="1D")]


def intra_change(bars):
    grouper = bars.groupby(day_grouper)
    return grouper.last()/grouper.first()-1


def intra_vwap(bars):
    price_volume = bars.eval("(high + low + close)/3 * volume")
    price_volume = price_volume.groupby("symbol").cumsum()
    volume = bars["volume"].groupby("symbol").cumsum()
    return price_volume/volume


def intra_average(bars):
    return bars.groupby(day_grouper).average()


def intra_max(bars):
    return bars.groupby(day_grouper).max()


def intra_min(bars):
    return bars.groupby(day_grouper).min()


def intra_gapext(daily_bars, intra_bars):  # Gap Extension
    numerator = intra_max(intra_bars["high"])-daily_bars["open"]
    denominator = daily_bars["open"] - daily_bars["close"].groupby("symbol").shift(1)
    return numerator.divide(denominator, axis="index")


def intra_highext(daily_bars, intra_bars):  # Total High Extension
    intra_high = intra_max(intra_bars["high"])
    intra_low = intra_min(intra_bars["low"])
    return (daily_bars["high"]-intra_high).divide(intra_high-intra_low,
                                                  axis="index")


def intra_retrace(bars):  # Retrace
    grouper = bars.groupby(day_grouper)
    start_bars = grouper.first()
    end_bars = grouper.last()
    return (end_bars["high"]-start_bars["high"])/(start_bars["high"]-start_bars["low"])


def intra_divup(bars):  # Divergence Up
    vwap = intra_vwap(bars)
    return (bars["high"] - vwap) / vwap


def intra_divdown(bars):  # Divergence Down
    vwap = intra_vwap(bars)
    return (vwap - bars["low"]) / vwap


def intra_position_range(bars):  # Posin Range
    #grouper = bars.groupby(day_grouper)  TODO : Reuse when new version of Pandas is available in QC
    grouper = bars.groupby([pd.Grouper(level="symbol"),
                            pd.Grouper(level="time", freq="1D")])
    return (grouper["close"].last()-grouper["low"].min())/(grouper["high"].max()-grouper["low"].min())


def intra_relvolume(daily_bars, intra_bars, avg_days=10):
    grouper = intra_bars.groupby(day_grouper)
    intra_volume = grouper["volume"].sum()
    avg_volume = shift(roll_average(daily_bars["volume"], avg_days), 1)  # Shift 1 day later to match with intra-day data
    return intra_volume/avg_volume.squeeze()


def intra_volume_hod(bars):
    grouper = bars.groupby(day_grouper)
    index = grouper.apply(lambda x: x.idxmax()[1])
    return grouper["volume"].cumsum()[index].groupby(day_grouper).last()
"""
Big Bertha Strategy with Machine Learning
Done
- Offline data storage to avoid symbols limitation
- Trade execution on high probability trades

Todo
- Risk management with stop loss
@version: 0.9
@creation date: 05/07/2022
"""

from AlgorithmImports import *

import numpy as np
import pandas as pd
from ast import literal_eval
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import GradientBoostingClassifier

import indicators as idx

pd.set_option('mode.use_inf_as_na', True)
GROUPER = [pd.Grouper(level="symbol"), pd.Grouper(level="time", freq="1D")]
AGG_OPS = {"open": "first", "close": "last",
           "high": "max", "low": "min",
           "volume": "sum"}


class BigBerthaML(QCAlgorithm):

    def Initialize(self):
        self.min_usd_volume = literal_eval(self.GetParameter("min_usd_volume"))
        self.capital = literal_eval(self.GetParameter("capital"))
        self.benchmark = self.GetParameter("benchmark")

        self.SetStartDate(2020, 1, 1)
        self.SetCash(self.capital)
        self.UniverseSettings.Resolution = Resolution.Minute
        self.UniverseSettings.ExtendedMarketHours = True
        self.AddUniverse(self.coarse_filter)
        self.AddEquity(self.benchmark, Resolution.Minute)
        self.SetBenchmark(self.benchmark)

        self.confidence = 0
        self.features, self.targets = None, None
        self.train_days = 252  # Training on the last year of data
        self.model = GradientBoostingClassifier(warm_start=True,
                                                n_iter_no_change=3)

        at = self.TimeRules.At
        every_day = self.DateRules.EveryDay(self.benchmark)
        self.Train(self.DateRules.MonthStart(), at(0, 0), self.train_model)
        self.Schedule.On(every_day, at(9, 35), self.update_data)
        self.Schedule.On(every_day, at(9, 35), self.trade)
        self.Schedule.On(every_day, at(15, 55), self.stop_trading)

    def coarse_filter(self, coarse):
        return [x.Symbol for x in coarse if
                x.HasFundamentalData and
                x.DollarVolume > self.min_usd_volume]

    def train_model(self):
        if self.features is None: return
        
        self.Debug(f"{self.Time} Training")
        x, y = self.get_train_data()
        fit_params = dict(sample_weight=abs(y))
        cv_scores = cross_val_score(self.model, X=x, y=(y > 0).astype(float),
                                    scoring="accuracy", fit_params=fit_params)
        self.confidence = max(np.mean(cv_scores) - 0.5, 0) * 2  # 100% if accuracy 100%, 0% if below 50%
        self.model.fit(x, (y > 0).astype(float), **fit_params)
        self.Debug(f"{self.Time} Points:{len(x)} Accuracy:{self.confidence:.1%}")
        self.Plot("ML", "Test Accuracy", self.confidence)

    def trade(self):
        if self.confidence <= 0: return

        self.Debug(f"{self.Time} Trading")
        x_pred = self.get_pred_data()
        y_proba = pd.Series(self.model.predict_proba(x_pred)[:, 1],
                            index=x_pred.index).groupby("symbol").last()
        trades = y_proba[(y_proba < 0.25) | (y_proba > 0.75)]
        positions = (trades - 0.5) * 2 * self.confidence  # TODO: Fix risk management Max portfolio size 100% including shorts
        for symbol, position in positions.items():
            self.Debug(f"{self.Time} - Trading {symbol} at {position:.1%}")
            self.SetHoldings(symbol, position)

    def stop_trading(self):
        self.Transactions.CancelOpenOrders()
        self.Liquidate()

    def update_data(self):
        trade_days = self.TradingCalendar.GetTradingDays(self.Time - timedelta(7),
                                                         self.Time - timedelta(1))
        last_day = list(filter(lambda p: p.BusinessDay and not p.PublicHoliday,
                               trade_days))[-1].Date
        start = last_day.replace(hour=9, minute=30, second=0)
        end = self.Time.replace(hour=9, minute=35, second=0)
        tickers = [ticker for ticker in list(self.ActiveSecurities.Keys)
                   if str(ticker) not in self.benchmark]
        minute_bars = self.History(tickers, start, end, Resolution.Minute)

        features = self.calculate_features(minute_bars).dropna()
        self.features = pd.concat([self.features, features]).drop_duplicates()
        targets = self.calculate_targets(minute_bars).dropna()
        self.targets = pd.concat([self.targets, targets]).drop_duplicates()

        memory = self.features.memory_usage(deep=True).sum()
        memory += self.targets.memory_usage(deep=True)
        self.Debug(f"{self.Time} Data updated: {len(tickers)} tickers {memory/10**6:.1f} MB")

    def calculate_features(self, minute_bars):
        day_bars = idx.filter_bars(minute_bars, "09:31", "16:00")
        day_bar = day_bars.groupby(GROUPER).agg(AGG_OPS)
        pm_bars = idx.filter_bars(minute_bars, "00:01", "09:30")
        pm_bar = pm_bars.groupby(GROUPER).agg(AGG_OPS)
        min5_bars = idx.filter_bars(day_bars, "09:31", "09:35")
        min5_bar = min5_bars.groupby(GROUPER).agg(AGG_OPS)

        features = pd.DataFrame()
        features["big_bertha"] = min5_bar.eval("(high-low)/open")
        features["close_range"] = min5_bar.eval("(close-low)/(high-low)")
        features["open_range"] = min5_bar.eval("(open-low)/(high-low)")
        features["pm_volume_usd"] = pm_bar.eval("close*volume")
        yesterday_close = day_bar["close"].groupby("symbol").shift(1)
        features["gap"] = day_bar["open"] / yesterday_close
        return features

    def calculate_targets(self, minute_bars):
        trade_day_bars = idx.filter_bars(minute_bars, "09:36", "15:55")
        trade_day_bar = trade_day_bars.groupby(GROUPER).agg(AGG_OPS)
        return trade_day_bar.eval("close/open-1").apply(np.log1p)

    def get_train_data(self):
        common_index = self.targets.index.intersection(self.features.index)
        y = self.targets.loc[common_index].groupby("symbol").tail(self.train_days)
        x = self.features.loc[y.index]
        return x, y

    def get_pred_data(self):
        return self.features.query("time == @self.Time.date()")

    def get_dataset_days(self):
        return len(self.features.index.get_level_values("time").unique()) \
            if self.features is not None else 0