Overall Statistics
Total Trades
430
Average Win
0.64%
Average Loss
-0.36%
Compounding Annual Return
-33.818%
Drawdown
20.100%
Expectancy
0.025
Net Profit
-3.445%
Sharpe Ratio
-0.203
Probabilistic Sharpe Ratio
34.078%
Loss Rate
63%
Win Rate
37%
Profit-Loss Ratio
1.77
Alpha
-0.377
Beta
1.065
Annual Standard Deviation
0.591
Annual Variance
0.349
Information Ratio
-0.616
Tracking Error
0.586
Treynor Ratio
-0.112
Total Fees
$1118.78
Estimated Strategy Capacity
$23000000.00
Lowest Capacity Asset
GME SC72NCBXXAHX
class WellDressedSkyBlueSardine(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 12, 1)
        self.SetEndDate(2020, 12, 31)
        self.SetCash(100000)
        
        self.AddUniverse(self.CoarseFilter, self.FineFilter)
        self.UniverseSettings.Resolution = Resolution.Hour
        
        self.Data = {}
        
    def CoarseFilter(self, coarse):
        sortedByDollarVolume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)
        return [x.Symbol for x in sortedByDollarVolume if x.Price > 10
                                                and x.HasFundamentalData][:200]

    def FineFilter(self, fine):
        sortedByPE = sorted(fine, key=lambda x: x.MarketCap)
        return [x.Symbol for x in sortedByPE if x.MarketCap > 0][:10]

    def OnSecuritiesChanged(self, changes):
        # close positions in removed securities
        for x in changes.RemovedSecurities:
            self.Liquidate()
            if x.Symbol in self.Data:
                del self.Data[x.Symbol]
        
        # can't open positions here since data might not be added correctly yet
        for x in changes.AddedSecurities:
            self.Data[x.Symbol] = SCTR("SCTR",x.Symbol)
            self.RegisterIndicator(x.Symbol,self.Data[x.Symbol],Resolution.Daily)
            history = self.History(x.Symbol, 200, Resolution.Daily)
            if history.empty or 'close' not in history.columns:
                    continue
            for bar in history.loc[x.Symbol, :].itertuples():
                    tradebar = TradeBar(bar.Index, x.Symbol, bar.open, bar.high, bar.low, bar.close, bar.volume)
                    self.Data[x.Symbol].Update(tradebar)
    def OnData(self, data): 
        
        SCTR = {}
        
        for symboldata in self.Data.values():
            if symboldata.IsReady:
                self.Debug(f'{symboldata.symbol}\'s SCTR = {symboldata.Value} in {self.Time}')
                SCTR[symboldata.symbol] = symboldata.Value
                
        [self.SetHoldings(symbol, sctr/sum(list(SCTR.values()))) for symbol, sctr in SCTR.items()]
        
class SCTR(PythonIndicator):
    def __init__(self, name,symbol):
        self.Name = name
        self.symbol = symbol
        
        
        self.Value = 0
        self.EMA200 = ExponentialMovingAverage(200)
        self.EMA50 = ExponentialMovingAverage(50)
        self.ROC125 = RateOfChange(125)
        self.ROC20 = RateOfChange(20)
        self.PPO = PercentagePriceOscillator(12,26,MovingAverageType.Exponential)
        self.RSI14 = RelativeStrengthIndex(14)
        self.PPOWindow = RollingWindow[float](3)

    def Update(self, bar):
        self.EMA200.Update(bar.Time, bar.Close)
        self.EMA50.Update(bar.Time, bar.Close)
        self.ROC125.Update(bar.Time, bar.Close)
        self.ROC20.Update(bar.Time, bar.Close)
        self.PPO.Update(bar.Time, bar.Close)
        self.RSI14.Update(bar.Time, bar.Close)
            
        self.PPOWindow.Add(self.PPO.Current.Value)
        if self.isReady():
            self.Value = self.SCTR()
            return True
        self.Value = 0
        return False

    def SCTR(self):
        return self.EMA200.Current.Value*0.3 + self.EMA50.Current.Value*0.15 + self.ROC125.Current.Value*0.3 + self.ROC20.Current.Value*0.15 \
                + (self.PPOWindow[0] - self.PPOWindow[2])/(3*self.PPOWindow[2]) *0.05 + self.RSI14.Current.Value*0.05
    def isReady(self):
        return self.EMA200.IsReady and self.EMA50.IsReady \
                    and self.ROC125.IsReady and self.ROC20.IsReady\
                    and self.PPO.IsReady and self.RSI14.IsReady
    

        
class StockChartsTechnicalRank:
    """
    Custom indicator class for the StockCharts Technical Rank indicator.
    REF: https://school.stockcharts.com/doku.php?id=technical_indicators:sctr
    By: Aaron Eller
    aaron@excelintrading.com
    """
    # Can customize each input, but will default to the settings from link
    def __init__(
        self,
        name,
        lt_ema_period=200, 
        lt_ema_weight=0.30, 
        lt_roc_period=125, 
        lt_roc_weight=0.30,
        mt_ema_period=50,
        mt_ema_weight=0.15,
        mt_roc_period=20,
        mt_roc_weight=0.15,
        ppo_fast=12,
        ppo_slow=26,
        ppo_signal=9,
        ppo_histogram_slope_period=3,
        ppo_histogram_slope_weight=0.05,
        rsi_period=14,
        rsi_weight=0.05,
        ):
        """Initialize the indicator."""
        self.Name = name
        self.Time = datetime.min
        self.Value = 0
        self.IsReady = False
        
        # Create required indicators
        self.lt_ema = ExponentialMovingAverage(lt_ema_period)
        self.lt_roc = RateOfChange(lt_roc_period)
        self.mt_ema = ExponentialMovingAverage(mt_ema_period)
        self.mt_roc = RateOfChange(mt_roc_period)
        
        self.ppo = PercentagePriceOscillator(
            ppo_fast,
            ppo_slow,
            MovingAverageType.Exponential
            )
        self.ppo_signal = ExponentialMovingAverage(ppo_signal)
        # ppo_histogram = self.ppo-self.ppo_signal
        self.ppo_histogram_window = \
            RollingWindow[float](ppo_histogram_slope_period)
        
        self.rsi = RelativeStrengthIndex(rsi_period, MovingAverageType.Wilders)
        
        # Constants for the indicator
        self.lt_ema_weight = lt_ema_weight
        self.lt_roc_weight = lt_roc_weight
        self.mt_ema_weight = mt_ema_weight
        self.mt_roc_weight = mt_roc_weight
        self.ppo_histogram_slope_weight = ppo_histogram_slope_weight
        self.rsi_weight = rsi_weight
        
        # Let's verify that the weights total 1.0
        if sum(
            [lt_ema_weight, lt_roc_weight, mt_ema_weight, mt_roc_weight,
            ppo_histogram_slope_weight, rsi_weight]
            ) != 1.0:
            raise ValueError("Invalid weights for StockChartsTechnicalRank "
                "class. Sum must equal 1.0")
                
        # Let's calculate the minimum number of bars required to initialize
        self.min_bars = max([
            lt_ema_period, 
            lt_roc_period,
            mt_ema_period,
            mt_roc_period,
            ppo_slow + ppo_signal + ppo_histogram_slope_period,
            rsi_period
            ])
                
#-------------------------------------------------------------------------------
    def __repr__(self):
        """
        Returns the object representation in string format.
        Called via repr() on the object.
        """
        return "{0} -> IsReady: {1}. Time: {2}. Value: {3}".format(
            self.Name, self.IsReady, self.Time, self.Value)
            
#-------------------------------------------------------------------------------
    def is_ready(self):
        """
        Check if the indicator is warmed up and ready to be calculated.
        """
        return self.lt_ema.IsReady and self.mt_ema.IsReady and \
            self.lt_roc.IsReady and self.mt_roc.IsReady and \
            self.rsi.IsReady and self.ppo_histogram_window.IsReady
        
#-------------------------------------------------------------------------------
    def Update(self, input):
        """
        Update the indicator with the input. 
        This is a required function for custom indicators!
        """
        # Update EMAs
        self.lt_ema.Update(input.Time, input.Close)
        # Check if long-term EMA is ready
        if self.lt_ema.IsReady:
            # Calculate the percent above/below the current EMA
            lt_ema = self.lt_ema.Current.Value
            self.lt_ema_pct = (input.Close-lt_ema)/lt_ema
        self.mt_ema.Update(input.Time, input.Close)
        # Check if medium-term EMA is ready
        if self.mt_ema.IsReady:
            # Calculate the percent above/below the current EMA
            mt_ema = self.mt_ema.Current.Value
            self.mt_ema_pct = (input.Close-mt_ema)/mt_ema
        
        # Update ROCs
        self.lt_roc.Update(input.Time, input.Close)
        self.mt_roc.Update(input.Time, input.Close)
        
        # Update the RSI
        self.rsi.Update(input.Time, input.Close)
        
        # Update the PPO
        self.ppo.Update(input.Time, input.Close)
        # Check if ready
        if self.ppo.IsReady:
            # Update the PPO signal
            ppo = self.ppo.Current.Value
            self.ppo_signal.Update(input.Time, ppo)
            # Check if PPO signal is ready
            if self.ppo_signal.IsReady:
                # Update the PPO Histogram
                ppo_signal = self.ppo_signal.Current.Value
                ppo_histogram = ppo-ppo_signal
                # Update the PPO Histogram RollingWindow 
                self.ppo_histogram_window.Add(ppo_histogram)
                # Calculate the PPO Histogram Slope
                if self.ppo_histogram_window.IsReady:
                    ppo_hist_current = self.ppo_histogram_window[0]
                    ppo_hist_last = self.ppo_histogram_window[
                        self.ppo_histogram_window.Count-1]
                    # Slope is degree of change in PPO histogram over x days
                    #  = rise/run
                    slope = (ppo_hist_current-ppo_hist_last) \
                        /self.ppo_histogram_window.Count
                    # Get slope component value
                    # Slope greater than +1 (ie +45degrees), set value to 100
                    if slope >= 1:
                        self.ppo_histogram_slope_value = 100
                    # Slope less than -1 (ie -45degrees), set value to 0
                    elif slope <= -1:
                        self.ppo_histogram_slope_value = 0
                    # Otherwise value is (slope+1)*50
                    else:
                        self.ppo_histogram_slope_value = (slope+1)*50.0
                        # e.g. slope=0.9 returns 1.9*50=95
                        
        # Update IsReady
        if not self.IsReady:
            if self.is_ready():
                self.IsReady = True
                    
        # Update self.Value when ready
        if self.IsReady:
            self.Value = \
                100.0*self.lt_ema_pct*self.lt_ema_weight + \
                100.0*self.lt_roc.Current.Value*self.lt_roc_weight + \
                100.0*self.mt_ema_pct*self.mt_ema_weight + \
                100.0*self.mt_roc.Current.Value*self.mt_roc_weight + \
                self.rsi.Current.Value*self.rsi_weight + \
                self.ppo_histogram_slope_value*self.ppo_histogram_slope_weight
                
                # NOTE: use 100.0* for values that are decimal floats
                # e.g. if a stock is 5% above it's long-term ema, lt_ema_pct=0.05
                # RSI value is 0-100