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