Overall Statistics
Total Orders
2328
Average Win
0.18%
Average Loss
-0.12%
Compounding Annual Return
11.245%
Drawdown
16.600%
Expectancy
0.437
Start Equity
540000
End Equity
1079016.28
Net Profit
99.818%
Sharpe Ratio
0.592
Sortino Ratio
0.594
Probabilistic Sharpe Ratio
28.390%
Loss Rate
42%
Win Rate
58%
Profit-Loss Ratio
1.48
Alpha
0.025
Beta
0.393
Annual Standard Deviation
0.096
Annual Variance
0.009
Information Ratio
-0.2
Tracking Error
0.123
Treynor Ratio
0.145
Total Fees
$2402.23
Estimated Strategy Capacity
$24000000.00
Lowest Capacity Asset
NOB R735QTJ8XC9X
Portfolio Turnover
2.52%
# 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, 6, 30)
        # 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"))