Overall Statistics
Total Trades
26
Average Win
0.24%
Average Loss
-0.18%
Compounding Annual Return
-6.786%
Drawdown
1.200%
Expectancy
-0.268
Net Profit
-0.614%
Sharpe Ratio
-1.754
Probabilistic Sharpe Ratio
18.345%
Loss Rate
69%
Win Rate
31%
Profit-Loss Ratio
1.38
Alpha
-0.05
Beta
-0.016
Annual Standard Deviation
0.033
Annual Variance
0.001
Information Ratio
-4.786
Tracking Error
0.108
Treynor Ratio
3.488
Total Fees
$26.00
class RSIBasedUniverseSelection(QCAlgorithm):

    def Initialize(self):
        # Set Start Date, End Date, and Cash
        #-------------------------------------------------------
        self.SetTimeZone(TimeZones.NewYork)     #EDIT: Added Timezon
        self.SetStartDate(2011, 1, 1)   # Set Start Date
        self.SetEndDate(2011, 2, 1)      # Set End Date
        self.SetCash(10000)            # Set Strategy Cash
        #-------------------------------------------------------
        
        self.SetBenchmark("SPY")
        self.AddUniverse(self.CoarseSelectionFilter, self.FineSelectionFilter)
        self.UniverseSettings.Resolution = Resolution.Daily    #Needs to change to Resolution.Minute once code works, leaving Daily for now to minimize data
        self.UniverseSettings.SetDataNormalizationMode = DataNormalizationMode.SplitAdjusted
        self.UniverseSettings.FeeModel = ConstantFeeModel(0.0)
        self.UniverseSettings.Leverage = 1
        
        self.__numberOfSymbols = 100
        self.__numberOfSymbolsFine = 10
        self.indicators = {}
        self.rsi_period = 14
        self.percentagebuy = 0.1
        
        # 1) Setting a Benchmark to plot with equity
        self.benchmarkTicker = 'SPY'
        self.SetBenchmark(self.benchmarkTicker)
        self.initBenchmarkPrice = None


    def CoarseSelectionFilter(self, coarse):
        sortedByDollarVolume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)   # sort descending by daily dollar volume
        return [ x.Symbol for x in sortedByDollarVolume[:self.__numberOfSymbols] ]  # return the symbol objects of the top entries from our sorted collection
        
    def FineSelectionFilter(self, fine):  # sort the data by P/E ratio and take the top 'NumberOfSymbolsFine'
        sortedByPeRatio = sorted(fine, key=lambda x: x.OperationRatios.OperationMargin.Value, reverse=False)    # sort descending by P/E ratio
        self.universe = [ x.Symbol for x in sortedByPeRatio[:self.__numberOfSymbolsFine] ]  # take the top entries from our sorted collection
        return self.universe
        
    def OnSecuritiesChanged(self, changes):
        for security in changes.AddedSecurities:
            self.indicators[security.Symbol] = SymbolData(security.Symbol, self, self.rsi_period)
            
        # for security in changes.RemovedSecurities:
        #     if security.Invested:
        #         self.Liquidate(security.Symbol, "Universe Removed Security")
            
        #     if security in self.indicators:
        #         self.indicators.pop(security.Symbol, None)
        
        
    def OnData(self, data):
        self.UpdateBenchmarkValue()
        self.Plot('Strategy Equity', self.benchmarkTicker, self.benchmarkValue)
        
        if (data.Time > self.EndDate - timedelta(1)):
            self.Debug("Liquidating!")
            self.Liquidate()
            return
        
        for symbol in self.universe:
            if not data.ContainsKey(symbol):    #Tested and Valid/Necessary
                continue
            
            if data[symbol] is None:            #Tested and Valid/Necessary
                continue
            
            if not symbol in self.indicators:    #Tested and Valid/Necessary
                continue
            
            # Ensure indicators are ready to update rolling windows
            if not self.indicators[symbol].is_rsi_value_ready():
                continue
            
            self.indicators[symbol].rsi_window.Add(self.indicators[symbol].get_rsi_value())
            
            if not self.indicators[symbol].is_rsi_window_ready():
                continue
            
            if self.indicators[symbol].is_valid_buy_order():
                if self.Portfolio.MarginRemaining > 0.9 * self.percentagebuy * self.Portfolio.TotalPortfolioValue:
                    self.buyquantity = round((self.percentagebuy*self.Portfolio.TotalPortfolioValue)/data[symbol].Close)
                    self.MarketOrder(symbol, self.buyquantity)
            elif self.indicators[symbol].is_valid_sell_order():
                if self.Portfolio.MarginRemaining > 0.9 * self.percentagebuy * self.Portfolio.TotalPortfolioValue:
                    self.sellQuantity = round((self.percentagebuy*self.Portfolio.TotalPortfolioValue)/data[symbol].Close)
                    self.MarketOrder(symbol, -1*self.sellQuantity)
            elif self.Portfolio[symbol].Invested:
                if self.Portfolio[symbol].IsLong and self.indicators[symbol].is_valid_buy_order_liquidate():
                    self.Liquidate(symbol)
                if not self.Portfolio[symbol].IsLong and self.indicators[symbol].is_valid_sell_order_liquidate():
                    self.Liquidate(symbol)
                if self.Securities[symbol].Holdings.UnrealizedProfitPercent < 0.1:
                    self.Debug('Liquidate after 10% loss on trade' + str(symbol))
                    self.Liquidate(symbol)
                    
    def UpdateBenchmarkValue(self):
        ''' Simulate buy and hold the Benchmark '''
        if self.initBenchmarkPrice is None:
            self.initBenchmarkCash = self.Portfolio.Cash
            self.initBenchmarkPrice = self.Benchmark.Evaluate(self.Time)
            self.benchmarkValue = self.initBenchmarkCash
        else:
            currentBenchmarkPrice = self.Benchmark.Evaluate(self.Time)
            self.benchmarkValue = (currentBenchmarkPrice / self.initBenchmarkPrice) * self.initBenchmarkCash    

class SymbolData(object):

    rolling_window_length = 3
    
    def __init__(self, symbol, context, rsi_period):
        
        self.symbol = symbol
        self.rsi_period = rsi_period
        self.rsi_value = context.RSI(self.symbol, rsi_period, MovingAverageType.Exponential)
        self.rsi_window = RollingWindow[float](self.rolling_window_length)
        
        # Warm up EMA indicators
        history = context.History([symbol], rsi_period + self.rolling_window_length, Resolution.Daily)
        for time, row in history.loc[symbol].iterrows():
            self.rsi_value.Update(time, row["close"])
            
            # Warm up rolling windows
            if self.rsi_value.IsReady:
                self.rsi_window.Add(self.rsi_value.Current.Value)
    
    def get_rsi_value(self):
        return self.rsi_value.Current.Value
        
        
    def is_rsi_value_ready(self):
        return self.rsi_value.IsReady
        
    def is_rsi_window_ready(self):
        return self.rsi_window.IsReady
    
    def is_valid_buy_order(self):
        return self.rsi_window[0] > 25 and self.rsi_window[1] < 25
        
    def is_valid_sell_order(self):
        return self.rsi_window[0] <70 and self.rsi_window[1] > 70
        
    def is_valid_buy_order_liquidate(self):
        return self.rsi_window[0] > 50
    
    def is_valid_sell_order_liquidate(self):
        return self.rsi_window[0] < 50