Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
Dealing with stock splits

How about this for dealing with stock splits (and maybe other extreme jumps).
A portable self-contained single function. Tailor the two variables offset and _days_out as you wish.

The attached backtest contains a test mode that simulates a split and this is the output:

2015-09-22 split:75 INFO TSLA price jump 130.50 to 230.50, possible split, excluding 63 days  
2015-12-21 split:79 INFO Clearing TSLA possible split exclusion after 63 days, now 232.56  
def handle_data(context, data):  
    for stock in data:  
        if split(stock, context, data): continue

        # Your other code, ordering etc ...

def split(s, c, data):  
    ''' Try to detect a stock split or reverse split and quarantine the stock for x days.  
        Especially important in the live/real environment where any indicators involving prices  
          could possibly lead to an erroneous (disastrous) buy or sell,  
          also for the rare backtest splits mistakenly not adjusted for awhile.  
        Input:  c is context, s is the stock object  
        Output: 0 if not excluded, 1 if excluded  
    '''  
    if 'excludes' not in c:  
        c.excludes = {  
            '_days_out': 63,                          # Number of days to keep them out of trading  
            '_date_prv': get_datetime().date()  
        }  
    offset  = .4  # Ratio above or below previous price to consider too much of a jump,  
                  #   and therefore a possible split (or reverse split).  
    exclude = 0  
    sym     = s.symbol  
    price   = data[s].price  
    if sym not in c.excludes:  
        c.excludes[sym] = {  
            'price_prv': price,  
            'days_out' : -1    # Not out, initialization  
        }  
        return 0  
    price_prv = c.excludes[sym]['price_prv']  
    if   price > price_prv + price_prv * offset \  
      or price < price_prv - price_prv * offset:      # If price change beyond offset up or down  
        c.excludes[sym] = {  
            'price_prv': price,  
            'days_out' : 0                            # Begin exclusion  
        }  
        exclude = 1  
        log.info('{} price jump {} to {}, possible split, excluding {} days'.format(  
            sym, '%.2f' % c.excludes[sym]['price_prv'], '%.2f' % price_prv, c.excludes['_days_out']))  
    if c.excludes[sym]['days_out'] >= c.excludes['_days_out']:  
        c.excludes[sym]['days_out'] = -1                   # Reset, no longer excluded  
        log.info('Clearing {} possible split exclusion after {} days, now {}'.format(  
            sym, c.excludes['_days_out'], '%.2f' % price))  
    if get_datetime().date() != c.excludes['_date_prv']:   # New day  
        c.excludes['_date_prv'] = get_datetime().date()    # Save the new date as previous  
        if c.excludes[sym]['days_out'] != -1:  
            c.excludes[sym]['days_out'] += 1               # Is excluded, increment days  
            exclude = 1  
    c.excludes[sym]['price_prv'] = price_prv               # New previous price  
    if exclude:  
        return 1  
    return 0  
1 response

This is nice. Couple suggestions:

  • if 'excludes' not in c: I'd remove this and move the initialization into initialize() so you don't have to check for it over and over again
  • price > price_prv + price_prv * offset is the same as price > price_prv * (1 + offset)
  • I am not familiar with classes in python but it feels like if excludes were modeled as an object so you could hide those details of checking and setting values in it