Overall Statistics
Total Trades
499
Average Win
0.05%
Average Loss
-0.09%
Compounding Annual Return
-3.759%
Drawdown
33.400%
Expectancy
-0.608
Net Profit
-7.367%
Sharpe Ratio
0.026
Loss Rate
75%
Win Rate
25%
Profit-Loss Ratio
0.56
Alpha
0.124
Beta
-7.042
Annual Standard Deviation
0.28
Annual Variance
0.078
Information Ratio
-0.033
Tracking Error
0.28
Treynor Ratio
-0.001
Total Fees
$1706.35
from QuantConnect.Data.UniverseSelection import *
import pandas as pd
from decimal import Decimal


class RSISignalAlgorithm(QCAlgorithm):
    
    def Initialize(context):
        # // Set parameters for RSI Indicator and Signals, thereof.
        context.rsiPeriod = 21
        context.lowerThreshold = 40
        context.upperThreshold = 80
        
        # // Other parameters required for backtesting/project
        context.MinDollarVolume = 10000000          # 10M ADV
        context.PerTradeEquity = Decimal(0.01)      # Percent of Equity per Trade
        context.MinCashHoldings = Decimal(0.02)
        context.StopLossPercent = Decimal(0.1)      # 10% Stop Loss
        context.TrailingLeadPercent = Decimal(0.1)  # Trailing Stop after 10%
        context.TrailingStopPercent = Decimal(0.05) # at 5%
        
        # // Set the initial cash, start, and end dates for backtesting
        context.SetCash(1000000)         # 1M starting capital
        context.SetStartDate(2016, 7, 1) # Backtest start date
        context.SetEndDate(2018, 7, 1)   # Backtest end date
        
        # // Subscribe to data for securities returned in our selected universe
        # // We are deliberately setting Leverage to 1, as leverage was not
        # // specified for this project.
        context.UniverseSettings.Resolution = Resolution.Daily
        context.UniverseSettings.MinimumTimeInUniverse = 0
        context.UniverseSettings.Leverage = 1
        context.UniverseSettings.FillForward = False
        context.AddUniverse(context.universe_filter_coarse)
        # // Use raw prices for all securities.
        # context.SetSecurityInitializer(
        #     lambda x: x.SetDataNormalizationMode(DataNormalizationMode.Raw))        
        
        # // Placeholder to collect indicator and other data for securities
        context.Data = {}
        
        # // Placeholder to collect various stats for securities for later use
        context.TrailingLeads = {}
        context.StopLossOrders = {}
        context.TrailingLeadReached = {}
        
        # // Finally, set a warm up period for the entire universe
        context.SetWarmUp(context.rsiPeriod)
        
    def universe_filter_coarse(context, coarse_data):
        # // Select all securities with Dollar Volume above specified threshold
        # // We make sure to arrange them according to decreasing ADV.
        selected = filter(lambda x: x.DollarVolume > context.MinDollarVolume, coarse_data)
        selected = sorted(list(selected), key=lambda x: x.DollarVolume, reverse=True)
        
        # // Initialize indicator and other data for all selected securities
        for cf in selected:
            if cf.Symbol not in context.Data:
                context.Data[cf.Symbol] = SymbolData(cf.Symbol, context.rsiPeriod,
                    context.upperThreshold, context.lowerThreshold)
            context.Data[cf.Symbol].update(cf)
            
        # // Since, we are going to buy securities with 1% of our equity on each
        # // trade, and we will be only doing this when we have enough cash
        # // available, we can return all available securities for our universe.
        return [ x.Symbol for x in selected ]
        
    # // Whenever new securities are added or removed in our universe,
    # // this function will be called with corresponding changes.
    def OnSecuritiesChanged(context, changes):
        for security in changes.RemovedSecurities:
            data = context.Data[security.Symbol]
            # // Sell a security, if we are invested in it and indicator
            # // value crossed the specified threshold for selling.
            if security.Invested and data.aboveThreshold:
                # context.Debug("Selling Ticker = %s, with Indicator Value = %s" % (data.symbol, data.rsi))
                context.Liquidate(security.Symbol, "SELL Threshold")
                context.mark_security_sold(security.Symbol)
                
        for security in changes.AddedSecurities:
            data = context.Data[security.Symbol]
            # // We will buy a security with 1% percent of our equity, when:
            # // - we have enough cash available (say, 2% of our equity)
            # // - indicator value crossed below the specified threshold for buy
            # // - we are not already holding the security (not specified in project)
            cash = context.Portfolio.Cash
            equity = context.Portfolio.TotalPortfolioValue
            cash_threshold = context.MinCashHoldings * equity
            if not security.Invested and data.belowThreshold and cash > cash_threshold:
                quantity = int(context.PerTradeEquity * equity/data.price)
                # // buy with a LimitOrder? or MarketOnOpen order? - Not sure.
                # // Going with MarketOnOpen order at the moment.
                context.MarketOnOpenOrder(security.Symbol, quantity, "BUY Threshold")
                # context.Debug("Buying Ticker = %s, with Indicator Value = %s" % (data.symbol, data.rsi))
                
                # // Also, since we need stop loss - place a StopMarketOrder right away.
                # // We, also, need to store these stop market orders in a dict,
                # // so that we can cancel them later, if required.
                stop_price = data.price * (1 - context.StopLossPercent)
                trailing_lead = data.price * (1 + context.TrailingLeadPercent)
                context.StopLossOrders[data.symbol] = context.StopMarketOrder(
                    data.symbol, -quantity, stop_price, "STOP LOSS")
                context.TrailingLeadReached[data.symbol] = False
                context.TrailingLeads[data.symbol] = trailing_lead

    # // This function is called on each tick, and is a good place to add logic for
    # // trailing stop loss functionality. At each tick, for each security, we
    # // check if we have reached a new trailing lead price, otherwise, we check
    # // if we have hit trailing stop price and sell the security as well as cancel
    # // any stop loss we may have (this is very important).
    def OnData(context, _data):
        for symbol, data in context.Data.items():
            # // check if we are holding the security?
            # // At every buy, we issue a stop loss order and cache this ticket.
            if context.Portfolio[symbol].HoldStock:
                if symbol in context.StopLossOrders:
                    sl_ticket = context.StopLossOrders[symbol]
                    tp_reached = context.TrailingLeadReached[symbol]
                    trailing_lead = context.TrailingLeads[symbol]
                    trailing_stop = trailing_lead * (1 - context.TrailingStopPercent)
                    # // stop loss was filled, we should remove any tracking
                    # // information for this symbol.
                    if sl_ticket.Status == "Filled":
                        context.mark_security_sold(symbol)
                    # // Trailing Lead was hit earlier and now price is below the
                    # // trailing stop price - we need to sell and cancel our
                    # // stop limit order.
                    elif tp_reached and data.price < trailing_stop:
                        context.Liquidate(symbol, "TRAIL STOP")
                        context.mark_security_sold(symbol)
                    # // Trailing Stop has not yet been hit. If price exceeds
                    # // trailing lead, we update it.
                    elif data.price >= trailing_lead:
                        trailing_lead = data.price
                        context.TrailingLeadReached[symbol] = True
                # else:
                    # context.Debug("Security in Portfolio without a StopLoss: %s" % symbol)
                
    # // when a security is marked as sold, we should ensure that we delete any
    # // remnants of it from the cached data/dicts we preserved for later use.
    # // Also, cancel StopLoss order created for this security when buying.
    def mark_security_sold(context, symbol):
        sl_ticket = context.StopLossOrders[symbol]
        if sl_ticket.Status != "Filled":
            sl_ticket.Cancel()
        context.StopLossOrders.pop(symbol)
        context.TrailingLeads.pop(symbol)
        context.TrailingLeadReached.pop(symbol)


class SymbolData(object):
    def __init__(self, symbol, rsiPeriod, upperThreshold, lowerThreshold):
        self.symbol = symbol
        self.price = 0
        self.rsi = RelativeStrengthIndex(rsiPeriod)
        self.upperThreshold = upperThreshold
        self.lowerThreshold = lowerThreshold
        self.aboveThreshold = False
        self.belowThreshold = False
        
    def update(self, data):
        self.price = data.Price
        if self.rsi.Update(data.EndTime, data.Price):
            rsi = self.rsi.Current.Value
            self.aboveThreshold = rsi > self.upperThreshold
            self.belowThreshold = rsi < self.lowerThreshold