Overall Statistics |
Total Orders 2373 Average Win 0.18% Average Loss -0.12% Compounding Annual Return 11.440% Drawdown 16.400% Expectancy 0.435 Start Equity 540000 End Equity 1111942.40 Net Profit 105.915% Sharpe Ratio 0.598 Sortino Ratio 0.601 Probabilistic Sharpe Ratio 29.669% Loss Rate 42% Win Rate 58% Profit-Loss Ratio 1.46 Alpha 0.025 Beta 0.396 Annual Standard Deviation 0.096 Annual Variance 0.009 Information Ratio -0.203 Tracking Error 0.122 Treynor Ratio 0.145 Total Fees $2457.29 Estimated Strategy Capacity $19000000.00 Lowest Capacity Asset BLK ROIDIDJRNXID Portfolio Turnover 2.49% |
# https://quantpedia.com/strategies/lottery-effect-in-stocks/ # # The investment universe contains all stocks on NYSE, AMEX, and NASDAQ. Stocks are then sorted into deciles based on maximum daily returns during # the past month. The investor goes long on stocks with the lowest maximum daily returns in the previous month and goes short on stocks with the # highest maximum daily returns. Portfolios are value-weighted and rebalanced monthly. # # QC implementation changes: # - Universe consists of 500 most liquid stocks traded on NYSE, AMEX, or NASDAQ. import numpy as np from AlgorithmImports import * class LotteryEffectinStocks(QCAlgorithm): def Initialize(self): # self.SetStartDate(2000, 1, 1) self.SetStartDate(2016, 1, 1) self.SetCash(100000) self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol self.coarse_count = 500 # Daily close data. self.data = {} self.period = 21 self.weight = {} self.selection_flag = False self.UniverseSettings.Resolution = Resolution.Daily self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction) self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.BeforeMarketClose(self.symbol), self.Selection) def OnSecuritiesChanged(self, changes): for security in changes.AddedSecurities: security.SetFeeModel(CustomFeeModel()) security.SetLeverage(10) def CoarseSelectionFunction(self, coarse): # Update the rolling window every day. for stock in coarse: symbol = stock.Symbol # Store monthly price. if symbol in self.data: self.data[symbol].update(stock.AdjustedPrice) if not self.selection_flag: return Universe.Unchanged # selected = [x.Symbol for x in coarse if x.HasFundamentalData and x.Market == 'usa'] selected = [x.Symbol for x in sorted([x for x in coarse if x.HasFundamentalData and x.Market == 'usa'], key = lambda x: x.DollarVolume, reverse = True)[:self.coarse_count]] # Warmup price rolling windows. for symbol in selected: if symbol in self.data: continue self.data[symbol] = SymbolData(self.period) history = self.History(symbol, self.period, Resolution.Daily) if history.empty: self.Log(f"Not enough data for {symbol} yet.") continue closes = history.loc[symbol].close for time, close in closes.iteritems(): self.data[symbol].update(close) return [x for x in selected if self.data[x].is_ready()] def FineSelectionFunction(self, fine): fine = [x for x in fine if x.MarketCap != 0 and ((x.SecurityReference.ExchangeId == "NYS") or (x.SecurityReference.ExchangeId == "NAS") or (x.SecurityReference.ExchangeId == "ASE"))] max_daily_performance = { x : self.data[x.Symbol].max_daily_performance() for x in fine } long = [] short = [] if len(max_daily_performance) >= 10: # Maximum return sorting. sorted_by_max_ret = sorted(max_daily_performance.items(), key = lambda x:x[1], reverse = True) decile = int(len(sorted_by_max_ret) / 10) long = [x[0] for x in sorted_by_max_ret[-decile:]] short = [x[0] for x in sorted_by_max_ret[:decile]] # Market cap weighting. total_market_cap_long = sum([x.MarketCap for x in long]) for stock in long: self.weight[stock.Symbol] = stock.MarketCap / total_market_cap_long total_market_cap_short = sum([x.MarketCap for x in short]) for stock in short: self.weight[stock.Symbol] = -stock.MarketCap / total_market_cap_long return [x[0] for x in self.weight.items()] def OnData(self, data): if not self.selection_flag: return self.selection_flag = False # Trade execution. stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested] for symbol in stocks_invested: if symbol not in self.weight: self.Liquidate(symbol) for symbol, w in self.weight.items(): self.SetHoldings(symbol, w) self.weight.clear() def Selection(self): self.selection_flag = True class SymbolData(): def __init__(self, period) -> None: self.prices = RollingWindow[float](period) def update(self, value) -> None: self.prices.Add(value) def is_ready(self) -> bool: return self.prices.IsReady def max_daily_performance(self) -> float: closes = np.array([x for x in self.prices]) daily_returns = (closes[:-1] - closes[1:]) / closes[1:] return max(daily_returns) # Custom fee model. class CustomFeeModel(FeeModel): def GetOrderFee(self, parameters): fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005 return OrderFee(CashAmount(fee, "USD"))
# https://quantpedia.com/strategies/lottery-effect-in-stocks/ # # The investment universe contains all stocks on NYSE, AMEX, and NASDAQ. Stocks are then sorted into deciles based on maximum daily returns during # the past month. The investor goes long on stocks with the lowest maximum daily returns in the previous month and goes short on stocks with the # highest maximum daily returns. Portfolios are value-weighted and rebalanced monthly. # # QC implementation changes: # - Universe consists of 500 most liquid stocks traded on NYSE, AMEX, or NASDAQ. from AlgorithmImports import * # from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel # from Risk import MaximumDrawdownPercentPerSecurity import numpy as np from dateutil.relativedelta import relativedelta from math import ceil from itertools import chain class LotteryEffectinStocks(QCAlgorithm): def Initialize(self): # self.SetStartDate(2000, 1, 1) # self.SetStartDate(2011, 1, 1) self.SetStartDate(2018, 1, 1) # self.SetEndDate(2016, 7, 1) self.SetEndDate(2024, 8, 31) # self.SetCash(100000) self.SetCash(540000) self.saltare_allocation = 0.25 self.max_short_size = 0.05 # self.copycat_cash_weight = 0 self.copycat_cash_weight = 0.5 # self.copycat_cash_weight = 1 # self.lottery_cash_weight = 0 self.lottery_cash_weight = 0.5 # self.lottery_cash_weight = 1 # self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol self.symbol = self.AddEquity('SPY', Resolution.Minute).Symbol ############################################ # Crisis Alpha Portfolio self.symbols = ['TLT', 'IEF', 'IEI', 'SHY'] self.treasuries_1year = 'BIL' crisis_tickers = ['TLT', 'IEF', 'IEI', 'SHY', 'BIL'] self.crisis_symbols = [ Symbol.Create(crisis_ticker, SecurityType.Equity, Market.USA) for crisis_ticker in crisis_tickers] # period = 10 # self.SetWarmUp(period, Resolution.Daily) copycat_period = 300 # For Minute Resolution self.SetWarmUp(copycat_period) # Monthly etf price. self.copycat_data = {} for symbol in self.symbols + [self.treasuries_1year]: data = self.AddEquity(symbol, Resolution.Minute) self.copycat_data[symbol] = CopycatSymbolData(symbol, copycat_period) self.last_month = -1 ############################################ self.copycat_weight = {} self.investors_preferences = {} # self.symbol = self.AddEquity('SPY', Resolution.Minute).Symbol # # Use to Plot Benchmark # # self.AddEquity("SPY", Resolution.Daily) # self.benchmarkTicker = 'SPY' # self.SetBenchmark(self.benchmarkTicker) # # self.initBenchmarkPrice = None # # Use if Plotting Long or Short Position of Benchmark # self.initBenchmarkPrice = 0 # # self.benchmarkExposure = -0.5 # # self.benchmarkExposure = -1 # self.benchmarkExposure = 1 self.hedge_symbol = self.AddEquity('VIXM', Resolution.Minute).Symbol self.hedge_weight = 0 self.hedge_two_symbol = self.AddEquity('GDX', Resolution.Minute).Symbol self.hedge_two_weight = 0 self.months_lag = 2 # Lag for getting investors preferences report # csv_string_file = self.Download('https://www.dropbox.com/s/1lfjrmly07lf0u3/full_investors_preferences%20copy%2015.csv?dl=1') # csv_string_file = self.Download('https://www.dropbox.com/s/l7qxq9hiivsonag/full_investors_preferences%20copy%2016.csv?dl=1') # csv_string_file = self.Download('https://www.dropbox.com/s/8qkm5ywnm8vs837/full_investors_preferences_16_copy.csv?dl=1') csv_string_file = self.Download('https://www.dropbox.com/s/pgk4patc3zwajgp/full_investors_preferences_17.csv?dl=1') lines = csv_string_file.split('\n') # This splittig works with the MarketWatch Scrape dates = [] # Skip csv header in loop for line in lines[1:]: line_split = line.split(';') date = datetime.strptime(line_split[0], "%d.%m.%Y").date() self.investors_preferences[date] = {} for ticker in line_split[1:]: if ticker not in self.investors_preferences[date]: self.investors_preferences[date][ticker] = 0 self.investors_preferences[date][ticker] += 1 self.month_counter = 0 self.copycat_selection_flag = False self.copycat_quarterly_selection_flag = False # self.copycat_selection_flag = True self.copycat_crisis_flag = False # self.copycat_data_selection_flag = True # Number of stocks in Coarse Universe self.NumberOfSymbolsCoarse = 500 # Number of sorted stocks in the fine selection subset using the valuation ratio, EV to EBITDA (EV/EBITDA) self.NumberOfSymbolsFine = 20 # Final number of stocks in security list, after sorted by the valuation ratio, Return on Assets (ROA) self.NumberOfSymbolsInPortfolio = 10 self.lastMonth = -1 self.dollarVolumeBySymbol = {} ############################################ self.coarse_count = 500 # Daily close data. self.data = {} self.period = 21 self.weight = {} self.selection_flag = False # self.UniverseSettings.Resolution = Resolution.Daily self.UniverseSettings.Resolution = Resolution.Minute # self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction) self.universeCopycat = self.AddUniverse(self.CopycatCoarseSelectionFunction, self.CopycatSelectFine) self.universeLottery = self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction) # Only needed if storing crisis symbols monthly prices in OnData # self.universeCrisis = self.AddUniverse(self.CoarseCrisisFunction, self.FineCrisisFunction) # Copycat Portfolio self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol,2), self.CopycatSelection) # self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol,3), self.CopycatSelection) # Crisis Alpha Portfolio self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.CopycatRebalance) # self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol,1), self.CopycatRebalance) # Lottery Portfolio self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.BeforeMarketClose(self.symbol), self.Selection) def OnSecuritiesChanged(self, changes): for security in changes.AddedSecurities: security.SetFeeModel(CustomFeeModel()) security.SetLeverage(10) def CopycatCoarseSelectionFunction(self, coarse): if not self.copycat_selection_flag and not self.copycat_quarterly_selection_flag: return Universe.Unchanged self.Log(str(self.Time) + "CoarseSelectionFunction") # # Used to filter for the most common holdings only # selected_report = {k: v for k, v in selected_report.items() if int(v) > 4} # My Addition # # selected_report = {k: v for k, v in selected_report.items() if int(v) < 2} # My Addition # Select universe based on report top = sorted([x for x in coarse if x.HasFundamentalData], key=lambda x: x.DollarVolume, reverse=True)[:self.NumberOfSymbolsCoarse] self.dollarVolumeBySymbol = { i.Symbol: i.DollarVolume for i in top } # self.copycat_quarterly_selection_flag = False return list(self.dollarVolumeBySymbol.keys()) def CopycatSelectFine(self, fine): selected_report = None min_date = self.Time.date() - relativedelta(months=self.months_lag+1) # quarterly data max_date = self.Time.date() self.Log(str(self.Time) + "SelectFine") for date in self.investors_preferences: # Get latest report if date >= min_date and date <= max_date: selected_report = self.investors_preferences[date] # Report might not be selected, because there are no data for that date if selected_report is None: return Universe.Unchanged # Used to filter for the most common holdings only selected_report = {k: v for k, v in selected_report.items() if int(v) > 20} # My Addition filteredFine = [x for x in fine if x.Symbol.Value in selected_report and x.CompanyReference.CountryId == "USA" and (x.CompanyReference.PrimaryExchangeID == "NYS" or x.CompanyReference.PrimaryExchangeID == "NAS") and (self.Time - x.SecurityReference.IPODate).days > 180 and x.EarningReports.BasicAverageShares.ThreeMonths * x.EarningReports.BasicEPS.TwelveMonths * x.ValuationRatios.PERatio > 5e8] count = len(filteredFine) if count == 0: return [] myDict = dict() percent = self.NumberOfSymbolsFine / count # select stocks with top dollar volume in every single sector for key in ["N", "M", "U", "T", "B", "I"]: value = [x for x in filteredFine if x.CompanyReference.IndustryTemplateCode == key] value = sorted(value, key=lambda x: self.dollarVolumeBySymbol[x.Symbol], reverse = True) myDict[key] = value[:ceil(len(value) * percent)] # myDict[key] = value[:ceil(len(value) * 1)] # stocks in QC500 universe topFine = chain.from_iterable(myDict.values()) # Magic Formula: ## Rank stocks by Enterprise Value to EBITDA (EV/EBITDA) ## Rank subset of previously ranked stocks (EV/EBITDA), using the valuation ratio Return on Assets (ROA) # sort stocks in the security universe of QC500 based on Enterprise Value to EBITDA valuation ratio sortedByEVToEBITDA = sorted(topFine, key=lambda x: x.ValuationRatios.EVToEBITDA , reverse=True) # sortedByEVToEBITDA = sorted(filteredFine, key=lambda x: x.ValuationRatios.EVToEBITDA , reverse=True) # sort subset of stocks that have been sorted by Enterprise Value to EBITDA, based on the valuation ratio Return on Assets (ROA) # sortedByROA = sorted(sortedByEVToEBITDA[:self.NumberOfSymbolsFine], key=lambda x: x.ValuationRatios.ForwardROA, reverse=False) # If using it must match fine_selected variable sortedByROA = sorted(sortedByEVToEBITDA[:self.NumberOfSymbolsInPortfolio], key=lambda x: x.ValuationRatios.ForwardROA, reverse=False) selected = [x.Symbol for x in sortedByROA] symbol_selected = [x.Symbol.Value for x in sortedByROA] # self.Debug(str(symbol_selected)) # self.Log(str(symbol_selected)) # new_selected_report = dict(((key, selected_report[key]) for key in symbol_selected)) new_selected_report = {} for k in set(selected_report).intersection(symbol_selected): new_selected_report[k]= selected_report[k] # self.Debug(str(new_selected_report)) # self.Log(str(new_selected_report)) # Calculate total preferences votes for selected report # total_preferences_votes = sum([x[1] for x in selected_report.items()]) total_preferences_votes = sum([x[1] for x in new_selected_report.items()]) # self.Debug(str(total_preferences_votes)) # Calculate weight for each stock in selected universe for symbol in selected: # for symbol in coarse_selected: # weight = total stock preferences votes / total investor votes in selected report # self.copycat_weight[symbol] = selected_report[symbol.Value] / total_preferences_votes # self.copycat_weight[symbol] = new_selected_report[symbol.Value] / total_preferences_votes self.copycat_weight[symbol] = 1 / len(selected) # self.hedge_weight = (1 - sum([x[1] for x in self.copycat_weight.items()])) / 2 self.hedge_weight = 1.1 - sum([x[1] for x in self.copycat_weight.items()]) self.hedge_two_weight = (1 - sum([x[1] for x in self.copycat_weight.items()])) / 2 # self.hedge_two_weight = 1.1 - sum([x[1] for x in self.copycat_weight.items()]) # retrieve list of securites in portfolio # fine_selected = [f.Symbol for f in sortedByROA[:self.NumberOfSymbolsFine]] # If using it must match sortedByROA variable fine_selected = [f.Symbol for f in sortedByROA[:self.NumberOfSymbolsInPortfolio]] return fine_selected def CoarseSelectionFunction(self, coarse): # Update the rolling window every day. for stock in coarse: symbol = stock.Symbol # Store monthly price. if symbol in self.data: self.data[symbol].update(stock.AdjustedPrice) # IMPORTANT: Update Crisis Symbols here instead of in OnData to avoid a race condition if symbol.Value in self.symbols + [self.treasuries_1year]: # self.data[symbol].update(stock.AdjustedPrice) self.copycat_data[symbol.Value].update(stock.AdjustedPrice) # self.Debug(f"{str(self.Time)} Crisis Symbol: {symbol.Value} Price: {stock.AdjustedPrice}") # if not self.selection_flag: # return Universe.Unchanged if self.selection_flag: # selected = [x.Symbol for x in coarse if x.HasFundamentalData and x.Market == 'usa'] selected = [x.Symbol for x in sorted([x for x in coarse if x.HasFundamentalData and x.Market == 'usa'], key = lambda x: x.DollarVolume, reverse = True)[:self.coarse_count]] # Warmup price rolling windows. for symbol in selected: if symbol in self.data: continue self.data[symbol] = SymbolData(self.period) history = self.History(symbol, self.period, Resolution.Daily) if history.empty: self.Log(f"Not enough data for {symbol} yet.") continue closes = history.loc[symbol].close # for time, close in closes.iteritems(): for time, close in closes.items(): self.data[symbol].update(close) return [x for x in selected if self.data[x].is_ready()] else: return self.crisis_symbols # return Universe.Unchanged def FineSelectionFunction(self, fine): fine = [x for x in fine if x.MarketCap != 0 and ((x.SecurityReference.ExchangeId == "NYS") or (x.SecurityReference.ExchangeId == "NAS") or (x.SecurityReference.ExchangeId == "ASE"))] max_daily_performance = { x : self.data[x.Symbol].max_daily_performance() for x in fine } long = [] short = [] if len(max_daily_performance) >= 10: # Maximum return sorting. sorted_by_max_ret = sorted(max_daily_performance.items(), key = lambda x:x[1], reverse = True) decile = int(len(sorted_by_max_ret) / 10) long = [x[0] for x in sorted_by_max_ret[-decile:]] short = [x[0] for x in sorted_by_max_ret[:decile]] # Market cap weighting. total_market_cap_long = sum([x.MarketCap for x in long]) for stock in long: self.weight[stock.Symbol] = stock.MarketCap / total_market_cap_long total_market_cap_short = sum([x.MarketCap for x in short]) for stock in short: # self.weight[stock.Symbol] = -stock.MarketCap / total_market_cap_long # self.weight[stock.Symbol] = max(-0.285, -stock.MarketCap / total_market_cap_long) self.weight[stock.Symbol] = max(-(self.max_short_size/(self.saltare_allocation * self.lottery_cash_weight)), -stock.MarketCap / total_market_cap_long) return [x[0] for x in self.weight.items()] # def CoarseCrisisFunction(self, coarse): # # Only needed if storing crisis symbols monthly prices in OnData # return self.crisis_symbols # def FineCrisisFunction(self, fine): # # Only needed if storing crisis symbols monthly prices in OnData # return self.crisis_symbols def OnData(self, data): # if not self.selection_flag: # return # self.selection_flag = False current_time = self.Time # For Minute Resolution if (current_time.hour == 9 and current_time.minute == 35): # For Minute Resolution if self.selection_flag: # Trade execution. already_invested = [x.Key for x in self.Portfolio if x.Value.Invested] for symbol in already_invested: # if symbol not in self.weight: if symbol not in self.weight and symbol.Value not in [x.Value for x in self.universeCopycat.Members.Keys]: # if symbol not in self.weight and symbol not in self.copycat_weight: if symbol != self.treasuries_1year: # self.Liquidate(symbol) self.SetHoldings(symbol, 0, False, "1Liquidated") if symbol not in self.weight and self.Portfolio[symbol].IsShort: # This prevnts the Shorts from growing too large over time # self.Liquidate(symbol) self.SetHoldings(symbol, 0, False, "2Liquidated") for symbol, w in self.weight.items(): # self.SetHoldings(symbol, w) # if symbol not in stocks_invested: if symbol.Value not in [x.Value for x in self.universeCopycat.Members.Keys]: # if symbol not in self.copycat_weight: # self.SetHoldings(symbol, w) quantity = self.CalculateOrderQuantity(symbol, self.lottery_cash_weight * w) # quantity = self.CalculateOrderQuantity(symbol, 0 * w) if quantity: self.MarketOrder(symbol, quantity) # continue self.weight.clear() self.selection_flag = False ############################################ # Crisis Alpha Portfolio # if self.last_month == self.Time.month: return # if self.last_month != self.Time.month: # self.last_month = self.Time.month if self.last_month == self.Time.day: return if self.last_month != self.Time.day: # For Minute Resolution self.last_month = self.Time.day # self.Log(str(self.Time.year)) # self.Debug(str(self.Time.year)) # Store monthly prices. # for symbol in self.symbols + [self.treasuries_1year]: # symbol_obj = self.Symbol(symbol) # if symbol_obj in data.Keys: # if data[symbol_obj]: # price = data[symbol_obj].Value # if price != 0: # self.copycat_data[symbol].update(price) # # self.Debug(f"{str(self.Time)} Crisis Symbol: {symbol} Price: {price}") ############################################ if (current_time.hour == 9 and current_time.minute == 31): # For Minute Resolution # if (current_time.hour == 9 and current_time.minute == 32): # For Minute Resolution # self.Log(f"Copycat universe includes: {sorted([x.Value for x in self.universeCopycat.Members.Keys])}") # self.Log(f"Copycat universe size: {len([x.Value for x in self.universeCopycat.Members.Keys])}") # self.Log(f"Lottery universe includes: {[x.Value for x in self.universeLottery.Members.Keys]}") # self.Log(f"Lottery universe size: {len([x.Value for x in self.universeLottery.Members.Keys])}") ############################################ # This is a Crisis Flag is Override that will nuke all Long positions # if self.Time.year == 2022 or self.Time.year == 2023: # self.copycat_crisis_flag = True # self.Log(str(self.Time) + "Crisis Flag is Override to On") if self.Time.year == 2022: self.copycat_crisis_flag = True crisis_months = [1,2,3,4,5,6,8,9,10,11] if self.Time.year == 2023: if self.Time.month in crisis_months: self.copycat_crisis_flag = True ############################################ if self.copycat_crisis_flag: self.Log(str(self.Time) + "Crisis Flag is True") current_invest = [x.Key for x in self.Portfolio if x.Value.Invested] for symbol in current_invest: ## This will nuke all positions in a Crisis ## if symbol not in self.weight and symbol not in [symbol.symbol.Value for symbol in self.managed_symbols]: # if symbol != self.treasuries_1year: if symbol != self.treasuries_1year and self.Portfolio[symbol].IsLong: # # self.SetHoldings(symbol, 0) self.SetHoldings(symbol, 0, False, "3Liquidated") # if symbol.Value not in [x.Value for x in self.universeLottery.Members.Keys]: # This will leave some Copycat universe positions that are also part of Lottery universe # So it does not nuke all positions # if symbol != self.treasuries_1year: # self.SetHoldings(symbol, 0, False, "3Liquidated") # else: # if symbol.Value in [x.Value for x in self.universeLottery.Members.Keys]: # if symbol.Value not in [x.Value for x in self.universeCopycat.Members.Keys]: # if symbol != self.treasuries_1year and self.Portfolio[symbol].IsLong: # self.SetHoldings(symbol, 0, False, "Reduced") if self.treasuries_1year not in current_invest: # self.SetHoldings(self.treasuries_1year, 1) self.SetHoldings(self.treasuries_1year, self.copycat_cash_weight) self.copycat_crisis_flag = False # This gets 307% self.copycat_selection_flag = False else: if not self.copycat_selection_flag and not self.copycat_quarterly_selection_flag: return self.copycat_selection_flag = False self.copycat_quarterly_selection_flag = False # if self.Time.year == 2021 or self.Time.year == 2022 or self.Time.year == 2023: # self.copycat_crisis_flag = True # self.Log(str(self.Time) + "Crisis Flag is Override to On") # Trade Execution stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested] for symbol in stocks_invested: if symbol not in self.copycat_weight: ## This will nuke all positions not in self.copycat_weight if str(symbol.Value) == str(self.hedge_symbol) or str(symbol.Value) == str(self.hedge_two_symbol): ## This will leave some Copycat universe positions that are also part of Lottery universe ## So it does not nuke all positions # if symbol.Value in [x.Value for x in self.universeLottery.Members.Keys] or str(symbol.Value) == str(self.hedge_symbol) or str(symbol.Value) == str(self.hedge_two_symbol): continue elif symbol.Value in [x.Value for x in self.universeLottery.Members.Keys] and self.Portfolio[symbol].IsShort: continue elif symbol.Value in [x.Value for x in self.universeLottery.Members.Keys] and self.Portfolio[symbol].IsLong: # self.SetHoldings(symbol, 0.01, False, "2Reduced") self.SetHoldings(symbol, 0.005, False, "2Reduced") else: self.SetHoldings(symbol, 0, False, "4Liquidated") # if self.copycat_crisis_flag: # if symbol.Value == self.treasuries_1year: # continue # else: # self.SetHoldings(symbol, 0, False, "4Liquidated") for symbol, w in self.copycat_weight.items(): quantity = self.CalculateOrderQuantity(symbol, self.copycat_cash_weight * w) if quantity: self.MarketOrder(symbol, quantity) self.copycat_weight.clear() def CopycatRebalance(self): # self.Liquidate() self.Log(str(self.Time) + "Rebalancing") # Etfs with positive momentum. - symbol -> (performance, volatility) positive_mom = {x : (self.copycat_data[x].performance(), self.copycat_data[x].volatility()) for x in self.symbols if self.copycat_data[x].is_ready() and self.copycat_data[x].performance() > 0} # self.Log(str(self.Time) + "Is BIL Ready?") if not self.copycat_data[self.treasuries_1year].is_ready(): return # self.Log(str(self.Time) + "YES! BIL is Ready!") if len(positive_mom) > 0: self.Log(str(self.Time) + "Positive IS mom > 0!") if self.Time.month % 3 == 2: self.copycat_crisis_flag = False self.Log(str(self.Time) + "This is Not the Month!") else: self.Log(str(self.Time) + "BIL Invested Check") current_invest = [x.Key for x in self.Portfolio if x.Value.Invested] for symbol in current_invest: if symbol == self.treasuries_1year: self.copycat_selection_flag = True self.copycat_crisis_flag = False self.Log(str(self.Time) + "BIL Already Invested!") else: self.copycat_crisis_flag = True self.Log(str(self.Time) + "Crisis Flag is On") # if self.Time.year == 2021 or self.Time.year == 2022 or self.Time.year == 2023: # self.copycat_crisis_flag = True # self.Log(str(self.Time) + "Crisis Flag is Override to On") def CopycatSelection(self): self.Log(str(self.Time) + "Selection") if self.Time.month % 3 == 2: # if self.Time.month % 3 == 0: # For Minute Resolution # self.copycat_selection_flag = True self.copycat_quarterly_selection_flag = True self.Log(str(self.Time) + "Selection Flag is True") # else: # self.copycat_quarterly_selection_flag = False def Selection(self): self.selection_flag = True class CopycatSymbolData(): def __init__(self, symbol, period): self.Symbol = symbol self.Price = RollingWindow[float](period) def update(self, value): self.Price.Add(value) def is_ready(self) -> bool: return self.Price.IsReady def performance(self, values_to_skip = 0) -> float: closes = [x for x in self.Price][values_to_skip:] return (closes[0] / closes[-1] - 1) def volatility(self): prices = np.array([x for x in self.Price]) daily_returns = prices[:-1] / prices[1:] - 1 return np.std(daily_returns) class SymbolData(): def __init__(self, period) -> None: self.prices = RollingWindow[float](period) def update(self, value) -> None: self.prices.Add(value) def is_ready(self) -> bool: return self.prices.IsReady def max_daily_performance(self) -> float: closes = np.array([x for x in self.prices]) daily_returns = (closes[:-1] - closes[1:]) / closes[1:] return max(daily_returns) # Custom fee model. class CustomFeeModel(FeeModel): def GetOrderFee(self, parameters): fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005 return OrderFee(CashAmount(fee, "USD"))