Overall Statistics
Total Trades
19
Average Win
1.81%
Average Loss
-1.82%
Compounding Annual Return
-5.323%
Drawdown
5.700%
Expectancy
0.141
Net Profit
-1.176%
Sharpe Ratio
-0.318
Probabilistic Sharpe Ratio
25.136%
Loss Rate
43%
Win Rate
57%
Profit-Loss Ratio
1.00
Alpha
-0.011
Beta
-0.057
Annual Standard Deviation
0.12
Annual Variance
0.014
Information Ratio
-2.942
Tracking Error
0.175
Treynor Ratio
0.673
Total Fees
$143.67
from orderTypes import OrderTypeCodes
from datetime import timedelta



class MeanReversion(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2019,1,1)  # Set Start Date
        self.SetEndDate(2019,3,20)
        self.SetCash(100000)  # Set Strategy Cash
        # self.AddEquity("SPY", Resolution.Minute)

        # what resolution should the data *added* to the universe be?
        self.UniverseSettings.Resolution = Resolution.Minute

      
        # Number of stock selected for check long and short conditions. 
        # This is used in the Fine Fundamental Filter to select the top 200
        # stocks ordered by Price To Sales and the bottom 200 stock by PS.
        self.long = 200
        self.short = 200
        
        self.longStocks = 0
        self.shortStocks = 0 
        
        self.tradeBarWindow = {}
        
        self.BarPeriod = timedelta(days=1)
        
        # This is the period of our sma indicators
        self.SimpleMovingAveragePeriod = 20
       
        # This is the number of consolidated bars we'll hold in symbol data for reference
        self.RollingWindowSize = 2
        
        # Holds all of our data keyed by each symbol
        self.Data = {}
        
        # List to use to add stocks every time an entry point is triggered.
        self.stocksPurchased = []
        
        self.nextRebalance = self.Time      # Initialize next rebalance time
        self.rebalance_days = 90           # Rebalance every 90 days
        
      
        # The dictionaries BollingBand and SMA20 are used to store the values for each symbol
        # of these two technical indicators. At first these dictionaries are updated with the
        # required window values and then are used in the technical setup for entry points.
        self.BollingerBand = {}
        self.SMA20 = {}
        
        # The three dictionaries below, are used to store order tickets for the market orders
        # entry positions, the stop orders as well as the limit orders for take profits
        # Using the order tickets, we can track orders and get important properties of the 
        # order object such as date, price, quantity, and also use the ticket for cancelling
        # the order for the symbol when the opposite order is filled.
        self.orders = {}
        self.stopOrders = {}
        self.profitOrders = {}
        
        self.stateData = {}
        
        self.AddUniverse(self.MyCoarseFilterFunction, self.FineSelectionFunction)
        
        # The functions technicalState and exitPositions are scheduled functions at specific time 
        # of the day.
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(15, 59), self.technicalState) 
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(10, 0), self.exitPositions) 
        

      # sort the data by daily dollar volume and take the top 'NumberOfSymbols'
    def MyCoarseFilterFunction(self, coarse):
        '''
        This is the first filter of the universe. In this filter we filtered out stocks with 
        price below to 10 and dollar Volume less than 10 millions dollars. Using this filter 
        we can get stocks with considerable volume
        '''
        
        
        if self.Time < self.nextRebalance:
            return Universe.Unchanged
        
        firstUniverse = [x for x in coarse  if x.HasFundamentalData and x.Price > 5 and x.DollarVolume > 1000000] 

        
        return [x.Symbol for x in firstUniverse]
        
    def FineSelectionFunction(self, fine):
        '''
        This function narrow the universe by ordering the first universe based on the Price
        To Sales ratio. Then it creates the longUniverse list with the 100 stocks with 
        the lowest Price to Sales ratio and the shortUniverse list with the 100 stocks with
        the greatest price to sales ratio
        '''
        
        if self.Time < self.nextRebalance:
            return Universe.Unchanged
            
        # Within this line we ordered the universe by Price To Sales ratio. The 
        # variable sortedByPsRatio will be a list where the first items are those
        # items with the lowest Price To Sales ratio(default order is ascending, so
        # lowest values are at first) and the bottom items will have the higest 
        # Price to Sales ratio
        sortedByPsRatio = sorted(fine, key= lambda x: x.ValuationRatios.PSRatio)
        
        # self.Debug(self.Time)
        #self.Debug('length %s' % len(sortedByPsRatio))
        
        # We get the first items of the sortedByPsRatio list by slicing the list 
        # using self.long value. Basically, if self.long is equal to 200, we take
        # the first 200 items of the sortedByPsRatio list, which are the 200 lowest
        # price to sales ratio. Then we call the Symbol method of the item to get
        # the symbol value
        self.longUniverse = [x.Symbol for x in sortedByPsRatio[:self.long]]
        
         # We get the last items for the sortedByPsRatio list by slicing the list
         # wit the last self.short value. So if self.short is 200 the self.shortUniverse 
         # will contain the latest 200 items from the sortedByPsRatio list.
         # Last we call the Symbol method of the item to get the symbol value
        self.shortUniverse = [x.Symbol for x in sortedByPsRatio[-self.short:]]
        
        
        return [x for x in (self.longUniverse + self.shortUniverse)] 
    
            
    def technicalState(self):
        '''
        This function scan for entry points for the stocks selected to go long or 
        go short. Once the technical indicators are ready for each symbol, the 
        function tested out the correspondent condition for go long or go short 
        for the stock selected in the universe, and send a market Order as well
        as a stopOrder and limit Order to stop position or take profit. 
        '''
        
        if self.Time < self.nextRebalance:
            return
        
        self.nextRebalance = self.Time + timedelta(self.rebalance_days)
        
    def exitPositions(self,):
        '''
        This function runs when the portfolio has one ore more position. The function
        will send order to exit positions (long or short) if the current date is 7 days
        greater than the order date. When the order is triggered the two open orders
        for stop loss and take profit are cancelled.
        '''
        
        if not self.Portfolio.Invested:
            return
        
        for symbol, order in self.orders.items():
       
                
            if order.Time.date() + timedelta(days= 7) < self.Time.date() and order.Quantity > 0 and self.Securities[symbol].Invested and symbol in self.stocksPurchased and ((self.Time.date().weekday !=5) or (self.Time.date().weekday !=6)):
                quantity = order.Quantity
                
                self.MarketOrder(symbol,-quantity,self.Time,'LongExitTime')
                self.stocksPurchased.remove(symbol)
                
                self.Debug('Exit after 7 days order for symbol {}'.format(symbol))
                
                # Cancel open orders
                cancelStop = self.stopOrders[symbol].Cancel()
                cancelProfit = self.profitOrders[symbol].Cancel()
                self.Debug("Cancel stop order for symbol {}".format(symbol))
                self.Debug("Cancel profit order for symbol {}".format(symbol))
                
              
                
            if order.Time.date() + timedelta(days=7) < self.Time.date() and order.Quantity < 0 and self.Securities[symbol].Invested and symbol in self.stocksPurchased and ((self.Time.date().weekday !=5) or (self.Time.date().weekday !=6)):
                quantity = order.Quantity
                self.MarketOrder(symbol,-quantity,self.Time,'ShortExitTime')
                self.stocksPurchased.remove(symbol)
                
                openOrders = self.Transactions.GetOpenOrders(symbol)
                
                # Cancel open orders
                #if self.exitByTime !=True:
                    
                cancelStop = self.stopOrders[symbol].Cancel()
                cancelProfit = self.profitOrders[symbol].Cancel()
                self.Debug("Cancel stop order for symbol {}".format(symbol))
                self.Debug("Cancel profit order for symbol {}".format(symbol))
               
                
    def OnSecuritiesChanged(self, changes):
        '''
        Show new securities added to the Universe as well as removed securities
        that dont belong any more to the Universe. At the moment where a security
        is added to the portfolio, we create the technical indicators objects.
        These objects will be uptated on each day in the technicalState function
        '''
        
       
        # Securities that are added to the universe based on the Fundamental Filters
        for security in changes.AddedSecurities:
            symbol = security.Symbol    
            self.Data[symbol] = SymbolData(self, symbol, self.BarPeriod, self.RollingWindowSize)
           
            
          
        
        # Securities that are removed from the universe because dont meet any more
        # fundamental filters.
        for removed in changes.RemovedSecurities:
            
            if removed.Symbol in self.longUniverse:
                self.longUniverse.remove(removed.Symbol)
        #       self.Debug('symbol removed is %s' % removed.Symbol)
               
                
            elif removed.Symbol in self.shortUniverse:
                self.shortUniverse.remove(removed.Symbol)
               
    
               
               
    def OnData(self, data):
        '''OnData event is the primary entry point for your algorithm.
        In this function we calculate the amount of days for the next rebalance
        date. The variable self.rebalance_days is a variable that is defined
        in the Initailization part of the strategy, and means the number of days
        needed for the next rebalance.
        
        Each new data point will be pumped in here.
            Arguments:
                data: Slice object keyed by symbol containing the stock data
            
        '''
        
        universe = self.longUniverse + self.shortUniverse
            
        for symbol in self.Data.keys():
            symbolData = self.Data[symbol]
            #self.Debug('On date {} symbolData is {}'.format(self.Time.date(),symbolData.IsReady()))
            if symbolData.IsReady() and symbolData.WasJustUpdated(self.Time):
                
                
                
                #self.Debug('barsCount %s' % symbolData.Bars.Count)
                if symbolData.Bars.Count == self.RollingWindowSize:
                
                    yesterdayBar = symbolData.Bars[1]
                    todayBar = symbolData.Bars[0]
                        
                    #
                    self.Debug('yesterdayclose %s' % yesterdayBar.Close)
                    #self.Debug('yesterday Open %s' % yesterdayBar.Open)
                    self.Debug('today close %s' % todayBar.Close)
                    #self.Debug('today Open %s' % todayBar.Open)
                    
                    #self.Debug('current Time %s' % self.Time)
                    #self.Debug('Time of Bars[0] %s' % symbolData.Bars[0].Time)
                    #self.Debug('Time of Bars[1] %s' % symbolData.Bars[1].Time)
                    
                    
                    #self.Debug('EndTime of Bars[0] %s' % symbolData.Bars[0].EndTime)
                    #self.Debug('EndTime of Bars[1] %s' % symbolData.Bars[1].EndTime)
                    
                    #if yesterdayBar and todayBar:
                    yesterdayClose = round(yesterdayBar.Close,2)
                    yesterdayOpen = round(yesterdayBar.Open,2)
                    todayClose = round(todayBar.Close,2)
                    todayOpen = round(todayBar.Open,2)
                
                    
                    upperBB = symbolData.BollingerBand.UpperBand.Current.Value
                    lowerBB = symbolData.BollingerBand.LowerBand.Current.Value
                        
                    sma_20 = round(symbolData.SMA20.Current.Value,2)
                    
                    if symbol in self.shortUniverse:
                        #self.Debug('On {} Symbol {} Yesterday Close {} BBUpper {} Yesterday Open {} Today Close {} Today Open {} SMA20 {}'.format(self.Time.date(),symbol,yesterdayClose,upperBB,yesterdayOpen,todayClose,todayOpen,sma_20))

                        if (yesterdayClose > upperBB) and (yesterdayClose > yesterdayOpen) and (todayClose < upperBB) and (todayClose < todayOpen) and (todayClose > yesterdayOpen) and (todayClose > (sma_20 * 1.1))  and symbol not in self.stocksPurchased:
                            self.Debug('Condition to sell for {}'.format(symbol))
                            self.Debug('On {} Symbol {} Yesterday Close {} BBUpper {} Yesterday Open {} Today Close {} Today Open {} SMA20 {}'.format(self.Time.date(),symbol,yesterdayClose,upperBB,yesterdayOpen,todayClose,todayOpen,sma_20))
                        
                        
                            # Define the tradeSize as 15% of the portfolio. Orders are sended by one stock
                            # so, most of the time the total position is at this level or below.
                            tradeSize = self.Portfolio.Cash * 0.15
                            quantity = int(round(tradeSize / todayClose))
                            stopPrice = round(todayClose * 1.20,2)
                        
                            self.Debug('Holdings Value %s' % self.Portfolio.TotalHoldingsValue)
                    
                            # Place the entry order to go short and inmediately place the stop Order at 5%
                            # of loss and the take profit at the sma_20 level.
                        
                            self.orders[symbol] = self.MarketOrder(symbol, -quantity,self.Time,'ShortTrade')
                        
                            #if self.exitByTime != True:
                            self.stopOrders[symbol] = self.StopMarketOrder(symbol, quantity, stopPrice,'StopShort') 
                            self.profitOrders[symbol] = self.LimitOrder(symbol,quantity,sma_20,'TakeProfitShort')
                                
                            # Every symbol that is purchased, is joined to the stocksPurchased list to avoid
                            # multiple orders for the same symbol on the same moment of time.
                            self.stocksPurchased.append(symbol)
                        
                    if symbol in self.longUniverse:
                        #self.Debug('On {} Symbol {} Yesterday Close {} BBLow {} Yesterday Open {} Today Close {} Today Open {} SMA20 {}'.format(self.Time.date(),symbol,yesterdayClose,lowerBB,yesterdayOpen,todayClose,todayOpen,sma_20))

                        if (yesterdayClose < lowerBB) and (yesterdayClose < yesterdayOpen) and (todayClose > lowerBB) and (todayClose > todayOpen) and (todayClose < yesterdayOpen) and (todayClose < (sma_20 * 0.9)) and symbol not in self.stocksPurchased:
                            self.Debug('Condition to buy for {}'.format(symbol))
                            self.Debug('On {} Symbol {} Yesterday Close {} BBLow {} Yesterday Open {} Today Close {} Today Open {} SMA20 {}'.format(self.Time.date(),symbol,yesterdayClose,lowerBB,yesterdayOpen,todayClose,todayOpen,sma_20))
                        
                            # Define the tradeSize as 15% of the portfolio. Orders are sended by one stock
                            # so, most of the time the total position is at this level or below.
                            tradeSize = self.Portfolio.Cash * 0.15
                            quantity = int(round(tradeSize / todayClose))
                            stopPrice = round(todayClose * 0.80,2)
                        
                            #self.Debug('Holdings Value %s' % self.Portfolio.TotalHoldingsValue)
                    
                            # Place the entry order to go short and inmediately place the stop Order at 5%
                            # of loss and the take profit at the sma_20 level.
                        
                            self.orders[symbol] = self.MarketOrder(symbol, quantity,self.Time,'LongTrade')
                            #if self.exitByTime !=True:
                            
                            self.stopOrders[symbol] = self.StopMarketOrder(symbol, -quantity, stopPrice,'StopLong')
                            self.profitOrders[symbol] = self.LimitOrder(symbol,-quantity,sma_20,'TakeProfitLong')
                    
                            #if self.exitByTime == True:
                            #self.stopOrders[symbol] = self.StopMarketOrder(symbol, -quantity, stopPrice,'StopLong')

                            self.stocksPurchased.append(symbol)
                        
                
 # Override the base class event handler for order events
    def OnOrderEvent(self, orderEvent):
        '''
        Each time an order is submitted or filled, this function is triggered. We use
        this function to cancel opposite function for each symbol. For example, when
        a profit order is filled, we should cancel the stop market order for that symbol.
        
        '''
        
        order = self.Transactions.GetOrderById(orderEvent.OrderId)
        #self.Debug("{0}: {1}: {2}: {3}: {4}".format(self.Time, OrderTypeCodes[order.Type], order.Quantity,order.Price,orderEvent))
        
        # OrderTypeCodes[order.Type] == 'StopMarket'  and order.Quantity < 0
        # If statetement to cancel profit limit order when the stop market order for loss is filled
        if order.Status == 3 and self.stopOrders[order.Symbol].OrderId == order.Id  and order.Quantity >0: 
        
            self.stocksPurchased.remove(order.Symbol)
           
            del self.orders[order.Symbol] 
            
            #if self.exitByTime != True:
            self.profitOrders[order.Symbol].Cancel()
            
            if order.Quantity > 0:
                self.Debug('Cancel short profit order for symbol {} because stop is triggered'.format(order.Symbol))
        
        if order.Status == 3 and self.stopOrders[order.Symbol].OrderId == order.Id  and order.Quantity < 0:
            self.stocksPurchased.remove(order.Symbol)
           
            del self.orders[order.Symbol] 
            
            self.profitOrders[order.Symbol].Cancel()
            
            self.Debug('Cancel long profit order for symbol {} because stop is triggered'.format(order.Symbol))
       
        if order.Status == 3 and  order.Id == self.profitOrders[order.Symbol].OrderId and order.Quantity >0: 
            
            self.stocksPurchased.remove(order.Symbol)
            
            self.stopOrders[order.Symbol].Cancel()
            del self.orders[order.Symbol] 
            
            #if order.Quantity > 0:
            self.Debug('At {} Cancel short loss order for symbol {} because profit is triggered'.format(self.Time.date(),order.Symbol))
       
        if order.Status == 3 and  order.Id == self.profitOrders[order.Symbol].OrderId and order.Quantity <0:
            
            self.stocksPurchased.remove(order.Symbol)
            self.stopOrders[order.Symbol].Cancel()
            del self.orders[order.Symbol] 
            
            self.Debug('At {} Cancel long loss order for symbol {} because profit is triggered'.format(self.Time.date(),order.Symbol))
                
     
class SymbolData(object):
    
    def __init__(self, algorithm, symbol, barPeriod, windowSize):
        self.symbol = symbol
        self.algorithm = algorithm
        self.BarPeriod = barPeriod
    
        self.SMA20 = SimpleMovingAverage(symbol,20) 
           
        self.Bars = RollingWindow[TradeBar](windowSize)
            
        self.BollingerBand = algorithm.BB(symbol ,20,2)
            
        consolidator = TradeBarConsolidator(barPeriod)
            
        consolidator.DataConsolidated += self.OnDataConsolidated
            
        algorithm.SubscriptionManager.AddConsolidator(symbol,consolidator)
    
    
    def OnDataConsolidated(self,sender,bar):
        self.SMA20.Update(bar.EndTime, bar.Close)
        self.BollingerBand.Update(bar.EndTime, bar.Close)
        self.Bars.Add(bar)
        
    # Returns true if all the data in this instance is ready (indicators, rolling windows, ect...)
    
    def IsReady(self):
    
        return self.Bars.IsReady and self.SMA20.IsReady and self.BollingerBand.IsReady
    
    # Returns true if the most recent trade bar time matches the current time minus the bar's period, this
    # indicates that update was just called on this instance
    
    def WasJustUpdated(self, current):
        return self.Bars.Count > 0 and self.Bars[0].EndTime == current - self.BarPeriod
OrderTypeKeys = [
    'Market', 'Limit', 'StopMarket', 'StopLimit', 'MarketOnOpen',
    'MarketOnClose', 'OptionExercise',
]

OrderTypeCodes = dict(zip(range(len(OrderTypeKeys)), OrderTypeKeys))