Quantlandian

Mean Reversion Algorithm - recently IPO'd companies

I have seen this algo in the forum before, not much description from the author but seems to be good algo.

Any thought about this?



"""
This is a template algorithm on Quantopian for you to adapt and fill in.
"""
import quantopian.algorithm as algo
import quantopian.optimize as opt
from quantopian.optimize import TargetWeights
from quantopian.pipeline import Pipeline,CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data import Fundamentals
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.filters.morningstar import IsPrimaryShare
from quantopian.pipeline.factors import SimpleMovingAverage, AverageDollarVolume, Returns,DailyReturns,ExponentialWeightedMovingAverage, AnnualizedVolatility,RSI,FastStochasticOscillator
from quantopian.pipeline.filters import QTradableStocksUS
from sklearn import covariance
from datetime import datetime  
from datetime import timedelta 

import pandas as pd
import numpy as np

short_window = 6

age_upper_limit = 50
age_lower_limit = 20

momentum_check_window = 30

returns_window = 12

MAX_GROSS_LEVERAGE = 1.0
MAX_SHORT_POSITION_SIZE = 0.4
MAX_LONG_POSITION_SIZE = 0.4


class StockAge(CustomFactor):
    inputs = [Fundamentals.ipo_date]
    window_length = 1
    def compute(self, today, assets, out, ipo_dates):
        today = np.array(today, dtype='datetime64[D]')
        ipo_dates = np.array(ipo_dates, dtype='datetime64[D]')
        out[:] = today - ipo_dates

def initialize(context):
    """
    Called once at the start of the algorithm.
    """
    context.isNew = True
    # Rebalance every day, 1 hour after market open.
    algo.schedule_function(BuildTrades, date_rules.week_start(), time_rules.market_close(minutes = 10))
    
    algo.schedule_function(
        rebalance,
        algo.date_rules.week_start(),
        algo.time_rules.market_open(minutes = 10),
    )
   

    # Record tracking variables at the end of each day.
    algo.schedule_function(
        record_vars,
        algo.date_rules.every_day(),
        algo.time_rules.market_close(),
    )
   
    
    algo.schedule_function(print_positions, date_rules.every_day(), time_rules.market_close())
    # Create our dynamic stock selector.
    algo.attach_pipeline(make_pipeline(), 'pipeline')

    context.hold = {}
    context.reachedMinima = {}
    context.holdLength = {}
    context.exclude = {}
    context.excludeLength = {}
    context.weights = {}
    context.security_list = {}
    context.old_security_list = {}
    context.i = 0
    for i in range(1, 391):  
        schedule_function(track_orders, date_rules.every_day(), time_rules.market_open(minutes=i))

    
def track_orders(context, data):  
    '''  Show orders when made and filled.  
           Info: https://www.quantopian.com/posts/track-orders  
    '''  
    c = context  
    try: c.trac  
    except:  
        c.t_opts = {        # __________    O P T I O N S    __________  
            'symbols'     : [],   # List of symbols to filter for, like ['TSLA', 'SPY']  
            'log_neg_cash': 1,    # Show cash only when negative.  
            'log_cash'    : 1,    # Show cash values in logging window or not.  
            'log_ids'     : 1,    # Include order id's in logging window or not.  
            'log_unfilled': 1,    # When orders are unfilled. (stop & limit excluded).  
            'log_cancels' : 0,    # When orders are canceled.  
        }    # Move these to initialize() for better efficiency.  
        c.t_dates  = {  # To not overwhelm the log window, start/stop dates can be entered.  
            'active': 0,  
            'start' : [],   # Start dates, option like ['2007-05-07', '2010-04-26']  
            'stop'  : []    # Stop  dates, option like ['2008-02-13', '2010-11-15']  
        }  
        c.trac = {}  
      #  log.info('track_orders active. Headers ...')  
      #  log.info('             Shares     Shares')  
      #  log.info('Min   Action Order  Sym  Now   at Price   PnL   Stop or Limit   Cash  Id')  
    from pytz import timezone as _tz  # Python only does once, makes this portable.  
                                      #   Move to top of algo for better efficiency.  
    # If 'start' or 'stop' lists have something in them, triggers ...  
    if c.t_dates['start'] or c.t_dates['stop']:  
        _date = str(get_datetime().date())  
        if   _date in c.t_dates['start']:    # See if there's a match to start  
            c.t_dates['active'] = 1  
        elif _date in c.t_dates['stop']:     #   ... or to stop  
            c.t_dates['active'] = 0  
    else: c.t_dates['active'] = 1           # Set to active b/c no conditions.  
    if c.t_dates['active'] == 0: return     # Skip if not active.  
    def _minute():   # To preface each line with the minute of the day.  
        bar_dt = get_datetime().astimezone(_tz('US/Eastern'))  
        return (bar_dt.hour * 60) + bar_dt.minute - 570 # (-570 = 9:31a)  
    def _trac(to_log):      # So all logging comes from the same line number,  
        print (' {}'.format( to_log)) # for vertical alignment in the logging window.
    for oid in c.trac.copy():               # Existing known orders  
      o = get_order(oid)  
      if c.t_opts['symbols'] and (o.sid.symbol not in c.t_opts['symbols']): continue  
      if o.dt == o.created: continue        # No chance of fill yet.  
      cash = ''  
      prc  = data.current(o.sid, 'price') if data.can_trade(o.sid) else c.portfolio.positions[o.sid].last_sale_price  
      if (c.t_opts['log_neg_cash'] and c.portfolio.cash < 0) or c.t_opts['log_cash']:  
        cash = str(int(c.portfolio.cash))  
      if o.status == 2:                     # Canceled  
        do = 'Buy' if o.amount > 0 else 'Sell' ; style = ''  
        if o.stop:  
          style = ' stop {}'.format(o.stop)  
          if o.limit: style = ' stop {} limit {}'.format(o.stop, o.limit)  
        elif o.limit: style = ' limit {}'.format(o.limit)  
        if c.t_opts['log_cancels']:  
          _trac('  Canceled {} {} {}{} at {}   {}  {}'.format(do, o.amount,  
             o.sid.symbol, style, prc, cash, o.id[-4:] if c.t_opts['log_ids'] else ''))  
        del c.trac[o.id]  
      elif o.filled:                        # Filled at least some.  
        filled = '{}'.format(o.amount)  
        filled_amt = 0  
        #if o.status == 1:                   # Nope, is either partial or complete  
        if o.filled == o.amount:             # Complete  
          if 0 < c.trac[o.id] < o.amount:  
            filled   = '{}'.format(o.filled)# - c.trac[o.id], o.amount)  
          filled_amt = o.filled  
        else:                                    # c.trac[o.id] value is previously filled total  
          filled_amt = o.filled - c.trac[o.id]   # filled this time, can be 0  
          c.trac[o.id] = o.filled                # save fill value for increments math  
          filled = '{}'.format(filled_amt)#, o.amount)  
        if filled_amt:  
          now = ' ({})'.format(c.portfolio.positions[o.sid].amount) if c.portfolio.positions[o.sid].amount else ' _'  
          pnl = ''  # for the trade only  
          amt = c.portfolio.positions[o.sid].amount ; style = ''  
          if (amt - o.filled) * o.filled < 0:    # Profit-taking scenario including short-buyback  
            cb = c.portfolio.positions[o.sid].cost_basis  
            if cb:  
              pnl  = -filled_amt * (prc - cb)  
              sign = '+' if pnl > 0 else '-'  
              pnl  = '  ({}{})'.format(sign, '%.0f' % abs(pnl))  
          if o.stop:  
            style = ' stop {}'.format(o.stop)  
            if o.limit: style = ' stop () limit {}'.format(o.stop, o.limit)  
          elif o.limit: style = ' limit {}'.format(o.limit)  
          if o.filled == o.amount: del c.trac[o.id]  
       #   _trac('   {} {} {}'.format(  
        #    'Bott' if o.amount > 0 else 'Sold', filled, o.sid.symbol))  
      elif c.t_opts['log_unfilled'] and not (o.stop or o.limit):  
        _trac('      {} {}{} unfilled  {}'.format(o.sid.symbol, o.amount,  
         ' limit' if o.limit else '', o.id[-4:] if c.t_opts['log_ids'] else ''))

    oo = get_open_orders().values()  
    if not oo: return                       # Handle new orders  
    cash = ''  
    if (c.t_opts['log_neg_cash'] and c.portfolio.cash < 0) or c.t_opts['log_cash']:  
      cash = str(int(c.portfolio.cash))  
    for oo_list in oo:  
      for o in oo_list:  
        if c.t_opts['symbols'] and (o.sid.symbol not in c.t_opts['symbols']): continue  
        if o.id in c.trac: continue         # Only new orders beyond this point  
        prc = data.current(o.sid, 'price') if data.can_trade(o.sid) else c.portfolio.positions[o.sid].last_sale_price  
        c.trac[o.id] = 0 ; style = ''  
        now  = ' ({})'.format(c.portfolio.positions[o.sid].amount) if c.portfolio.positions[o.sid].amount else ' _'  
        if o.stop:  
         style = ' stop {}'.format(o.stop)  
         if o.limit: style = ' stop {} limit {}'.format(o.stop, o.limit)  
        elif o.limit: style = ' limit {}'.format(o.limit)  
        _trac('{}, {}, {}'.format( 
         o.sid.symbol, o.amount, get_datetime('US/Eastern')+timedelta(minutes=17*60+32)))
        
#CURRENT_CAPITAL = 1000
CURRENT_CAPITAL = 100000
def print_positions(context, data):
    print("____________ IPOS Positions Portfolio _____________")
   
    differential = 1
    for ea in context.portfolio.positions:
        symbol = context.portfolio.positions[ea].asset.symbol
        
        costbasis = round(context.portfolio.positions[ea].cost_basis,2)
        total = context.portfolio.portfolio_value
        shares = context.portfolio.positions[ea].amount
        differential = costbasis*shares/total  + differential
        percent = costbasis*shares/total      
       # shares = round(percent/differential,2)*CURRENT_CAPITAL/costbasis
       
        print(str(symbol)+": "+str(round(percent/differential*100))+"% Shares: "+str(shares)+", Basis: $"+str(costbasis)+",  Total: $"+str(round(shares*costbasis,2)))
    print("____________ ------------------------- _____________")   
def make_pipeline():
    """
    A function to create our dynamic stock selector (pipeline). Documentation
    on pipeline can be found here:
    https://www.quantopian.com/help#pipeline-title
    """

    primary_share = IsPrimaryShare()
    common_stock = morningstar.share_class_reference.security_type.latest.eq('ST00000001')
    not_depositary = ~morningstar.share_class_reference.is_depositary_receipt.latest
    not_otc = ~morningstar.share_class_reference.exchange_id.latest.startswith('OTC')
    not_wi = ~morningstar.share_class_reference.symbol.latest.endswith('.WI')
    not_lp_name = ~morningstar.company_reference.standard_name.latest.matches('.* L[. ]?P.?$')
    not_lp_balance_sheet = morningstar.balance_sheet.limited_partnership.latest.isnull()
    have_market_cap = morningstar.valuation.market_cap.latest.notnull()
    
    
    symbols = morningstar.share_class_reference.symbol
    object_methods = [method_name for method_name in dir(symbols)]
                  #if callable(getattr(symbols, method_name))]
   
    tradeable_stocks = (primary_share                    
                        & common_stock
                        & not_depositary
                        & not_otc
                        & not_wi
                        & not_lp_name
                   
                        & not_lp_balance_sheet
                        & have_market_cap
                       )

    # Base universe set to the QTradableStocksUS

    age = StockAge()
    
    has_age = (age>=0)
    
    rsi = RSI(window_length = 12, mask = has_age).percentile_between(0,50)
    
    age2 = StockAge(mask = rsi)
    
    youngest = age2.bottom(50)
    
    returns = Returns(window_length = 12,mask = youngest)

    lowest_returns = returns.bottom(5)
    
    pipe = Pipeline(
        columns = {
            'returns' : returns,
            },
        screen = lowest_returns & tradeable_stocks
    )
    return pipe

def pnl(context, data, s):  
    pos = context.portfolio.positions[s]  
    return pos.last_sale_price  / pos.cost_basis
def BuildTrades(context,data):
    context.pipeline_data = algo.pipeline_output('pipeline')
    context.isNew = False
def rebalance(context, data):
   if context.isNew:
        return
   else:
    
    pipeline_data = context.pipeline_data
    todays_universe = pipeline_data.index
    
    alpha = {}
    
    for security in todays_universe:
        if security in context.portfolio.positions:
            alpha[security] = pnl(context,data,security)
        else:
            alpha[security] = 1
    
    objective = opt.MaximizeAlpha(alpha)
    
    constrain_gross_leverage = opt.MaxGrossExposure(.5)
    
    constrain_pos_size = opt.PositionConcentration.with_equal_bounds(
        -MAX_SHORT_POSITION_SIZE,
        MAX_LONG_POSITION_SIZE,
    )
 
    # Run the optimization. This will calculate new portfolio weights and
    # manage moving our portfolio toward the target.
    algo.order_optimal_portfolio(
        objective=objective,
        constraints=[
            constrain_gross_leverage,
            constrain_pos_size,
        ]
    )
    #context.i+=1


def record_vars(context, data):
    record(positions = len(context.portfolio.positions))
    record(leverage = context.account.leverage)

``` (ending line)

@WongFocus

Hi WongFocus,

Thanks for the contribution to Quantlandian!

For better presentation of python code, please quote the code using below syntax:

```python (beginning line)
your code here
``` (ending line)

Would you also be able to identify who is the original author, and describe more about the strategy?

Thank you! :smiley:

@alexchan

Thank you Alex. I fixed it.
Look forward to see more useful algo.