Hi members

I was attempting to create a Long/Short Equity with mean reversion tilt, meaning, go long on equities that were close or hit their 52Lows or go short on equities that just recently hit their 52Highs, but also incorporating certain value investing factors. also, id like to do trades in the account every quarter, not every day or every month. for some reason, when manually checking the data if it does what i want it o do, the data seem to be slightly off, not sure what's going on here. can someone help me fix this?

 

thank you so much!

 

 

#region imports
from AlgorithmImports import *
#endregion
#This is a Template of dynamic stock selection.
#You can try your own fundamental factor and ranking method by editing the CoarseSelectionFunction and FineSelectionFunction
import statistics
from QuantConnect.Data.UniverseSelection import *

class BasicTemplateAlgorithm(QCAlgorithm):
    
    def __init__(self):
    # set the flag for rebalance
        self.reb = 1
    # Number of stocks to pass CoarseSelection process
        self.num_coarse = 250
    # Number of stocks to long/short
        self.num_fine = 20
        self.symbols = None
        self.first_month = 0
        self.topFine = None



    def Initialize(self):
        self.SetCash(100000)
        self.SetStartDate(2015,2,1)
    # if not specified, the Backtesting EndDate would be today 
        self.SetEndDate(2023,4,25)
        
        
        self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol
        
        self.UniverseSettings.Resolution = Resolution.Daily
        
        self.AddUniverse(self.CoarseSelectionFunction,self.FineSelectionFunction)
        
    # Schedule the rebalance function to execute at the begining of each month
        self.Schedule.On
        self.Schedule.On(self.DateRules.MonthStart(self.spy), 
        self.TimeRules.AfterMarketOpen(self.spy,5), Action(self.rebalance))
        
    
    def CoarseSelectionFunction(self, coarse):
    # if the rebalance flag is not 1, return null list to save time.
        
        if self.reb != 1:
            return self.topFine if self.topFine is not None else []

    # make universe selection once a month
    # drop stocks which have no fundamental data or have too low prices
        selected = [x for x in coarse if (x.HasFundamentalData) 
                    and (float(x.Price) > 5)]
        
        sortedByDollarVolume = sorted(selected, key=lambda x: x.DollarVolume, reverse=True)
        top = sortedByDollarVolume[:self.num_coarse]

        return [i.Symbol for i in top]

    def FineSelectionFunction(self, fine):
    # return null list if it's not time to rebalance
        if self.reb != 1:
            return self.topFine if self.topFine is not None else []

        self.reb = 0

        # Calculate 52-week high and low prices
        high_low_prices = {}
        for x in fine:
            symbol = x.Symbol
            trade_bars_df = self.History(TradeBar, [symbol], 252, Resolution.Daily)
            try:
                high = max(trade_bars_df.high)
                low = min(trade_bars_df.low)
                high_low_prices[symbol] = {'high': high, 'low': low}
                # self.Debug(f"high_low_prices: {high_low_prices[symbol]}")
            except:
                high_low_prices[symbol] = {'high': None, 'low': None}
                pass
 

        # Filter long and short candidates
        long_candidates = [x for x in fine if high_low_prices[x.Symbol]['low'] is not None and float(x.Price) <= 1.25 * high_low_prices[x.Symbol]['low']]
        short_candidates = [x for x in fine if high_low_prices[x.Symbol]['high'] is not None and float(x.Price) >= 0.97 * high_low_prices[x.Symbol]['high']]


        # Rank candidates using the existing factors
        long_ranked = self.rank_stocks(long_candidates)
        short_ranked = self.rank_stocks(short_candidates)

        self.long = [x[0] for x in long_ranked[:self.num_fine]]
        self.short = [x[0] for x in short_ranked[-self.num_fine:]]

        self.topFine = [i.Symbol for i in self.long + self.short]

        return self.topFine

    def rank_stocks(self, stock_list):
        sortedByfactor1 = sorted(stock_list, key=lambda x: x.ValuationRatios.CFOPerShare, reverse=True)
        sortedByfactor2 = sorted(stock_list, key=lambda x: x.ValuationRatios.PCFRatio, reverse=True)
        sortedByfactor3 = sorted(stock_list, key=lambda x: x.ValuationRatios.EVToEBITDA, reverse=False)

        stock_dict = {}

        for i, ele in enumerate(sortedByfactor1):
            rank1 = i
            rank2 = sortedByfactor2.index(ele)
            rank3 = sortedByfactor3.index(ele)

            score = sum([rank1 * 0.333333, rank2 * 0.333333, rank3 * 0.333333])
            stock_dict[ele] = score

        return sorted(stock_dict.items(), key=lambda d: d[1], reverse=True)



    def OnData(self, data):
        pass
    
    def rebalance(self):
        if self.first_month == 0:
            self.first_month += 1
            return
        current_month = self.Time.month
        if current_month not in [12]:
            return
    # if this month the stock are not going to be long/short, liquidate it.
        long_short_list = self.topFine
        for i in self.Portfolio.Values:
            if (i.Invested) and (i.Symbol not in long_short_list):
                self.Liquidate(i.Symbol)
        
                
    # Alternatively, you can liquidate all the stocks at the end of each month.
    # Which method to choose depends on your investment philosiphy 
    # if you prefer to realized the gain/loss each month, you can choose this method.
    
        #self.Liquidate()
        
    # Assign each stock equally. Alternatively you can design your own portfolio construction method
        

        for i in self.long:
            self.SetHoldings(i.Symbol, 1.2/self.num_fine)
        
        for i in self.short:
            self.SetHoldings(i.Symbol, -0.2/self.num_fine)

        self.reb = 1