Back to Community
Trade Like A Warrior (S&C V35:06 page 34)

June 2017's Stocks and Commodities magazine has a very interesting interview with Ross Cameron of Warrior Trading. He publicly converted $583 into $100k in 45 days at the beginning of 2017. The results are on YouTube.

I decided to code what he said in the interview.

Basic Philosophy

Every day there is a stock that moves 20-30%. Find it when it is up only 5-10%. 80% of time they are gapping up pre-market. Trade 9:30-11:30am, exit all trades. Have the rest of the day free.

S&C Interview check list of factors/strategy:

  1. At open buy stocks that gap up 10%+
  2. float <100M shares (approximated)
  3. strong daily chart: price > exponential ema200
  4. $1.00 < price < $20 (user configurable).
  5. history of big moves (user configurable). 1 previous 60% within last 3 months.
  6. sector-wide catalyst or technical breakout (not implemented)

Additional factors/strategy
7. Liquid so exit orders don't fail. Setting below 30 can't get out of trades.
8. In the interview he did NOT give an exit strategy. I coded sell at 11:30am if not stopped out.

Observations

  1. With the simple entry/exit rules, the entire 2017 success is due to catching HTGM on 3/24.
  2. If you run this for 10 years you go bust

Additions / Optional settings

  1. Sell at open
  2. Trailing stops (for both long and short strategy)
  3. Wait for pull back of n% before buying

Closing Thoughts

You can play with the parameters to see if any produce a return over a 10+ year horizon. Likely better entry/exit rules and additional factors are needed to make it tradeable.

I didn't test all possible combinations of input parameters.

License is MIT. Use at your own risk.

Cheers,
John Glossner

Clone Algorithm
69
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
'''
Stocks and Commodities V35:06 page 34 (June 2017)
Trade like a Warrior. Interview with Ross Cameron

Philosophy:
  Every day there is a stock that moves 20-30%
  Find it when it is up only 5-10%
  80% of time they are gapping up pre-market
  Trade 9:30-11:30am then enjoy the rest of the day


Algorithm:
Find checklist of indicators
   1) At open buy stocks gaping up 10%+
   2) float <100M shares (approximated)
   3) strong daily chart: price > exponential ema200
   4) $1.0 < price < $20  (user configurable). 
   5) history of big moves (user configurable). 60% within last 3 months.
   6) sector-wide catalyst or technical breakout (not implemented)
   7) Exit all positions by 11:30am
Additional indicators
   8) Liquid so exit orders don't fail

Optional trailing stop
Optional Sell gap up. 
Optional wait for pullback

NOTE: Entry / Exit rules are not described in the interview. This code
buys gap up stocks at open and sells at 11:30am unless stopped out.

John Glossner
[email protected]
http://Linkedin.com/in/glossner

MIT License

2017 06 01 Initial version
'''
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.factors import AverageDollarVolume, SimpleMovingAverage, ExponentialWeightedMovingAverage
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.filters.morningstar import Q500US, Q1500US
from quantopian.pipeline.filters.morningstar import IsPrimaryShare
from zipline.api import get_environment
from collections import OrderedDict

import datetime
import numpy as np
                                       
global_min_prev_gap_up_percent = 0.60  #FIXME: Get rid of global in PreviousMoves CustomFactor
 
def initialize(context):
    """
    Called once at the start of the algorithm.
    """ 
    #Stock Selection User Defined Variables
    context.min_stock_price    = 1.00        #min stock trading price. Avoid penny stocks
    context.max_stock_price    = 20.0        #max stock trading price
    context.min_gap_up_percent = 0.10        #Used to select stocks at open
    context.max_gap_up_percent = 2.0         #10% gap up = 0.1
    context.float_shares_min   = 1000000.0   #minimum number of float shares 1M
    context.float_shares_max   = 100000000.0 #max number of float shares 100M
    context.min_avg_dollar_vol = 30          #70 or above is highly liquid
    context.gap_up_day_window  = 63          # 252 = 1 year, 63=3 months
    context.min_gap_up_days    = 1.0         #Min number of gap ups in window

    context.sell_at_open       = False       #Reverse strategy
    context.wait_for_pull_back = False       #Can wait for gapping stock to pull back
    context.pull_back_percent  = 0.02        #0.02 = 2%
    
    
    #Account Management User Defined Variables
    context.use_trailing_stop = False
    context.stop_pct = 0.02                 #0.01 = 1% stop

    context.desired_leverage = 1.0           #Change up or down as desired
    context.leverage_reduction = 0.95        #When overlevered tries to reduce number of trades
    context.number_securities_to_reduce = 3  #maximum number of securities to delever. Must be integer.    
    
    
    #Should not have to change anything below here except perhaps close_positions time
    schedule_function(open_positions_at_beginning_of_day, date_rules.every_day(), time_rules.market_open())
    schedule_function(close_positions, date_rules.every_day(), time_rules.market_open(hours=2, minutes=0))
    
    if get_environment('arena') == 'backtest':    #Called every minute in live trading
        schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close(minutes=1))

    attach_pipeline(make_pipeline(context), 'my_pipeline')
    
    #Wouldn't be needed with GTC orders
    context.exit_order_list = {}           #Equity():info optional. Make sure position is exited
    context.failed_order_list = {}         #Equity():portfolio_percent. To replace orders
    context.stop_list = {}                 #Equity():stop. ASSUMES single order per equity
    context.pull_back_list = {}            #Equity():portfolio_percent
    context.pnl_list = {}                  #Equity(): pnl.
    
    return


 
def before_trading_start(context, data):
    """
    Called every day before market open.
    Any positions not exited the previous day are added to exit list
    New pipeline stocks are retrieved
    """
    #If any positions didn't exit the previous day, add them to the exit list
    for pos in context.portfolio.positions:
        context.exit_order_list[pos] = 'previous day order not exited'
    
    context.output = pipeline_output('my_pipeline').dropna()
  
    log.info('Number of stocks from pipeline= ' + str(len(context.output.index)))
    return     


def handle_data(context,data):
    """
    Called first before any function scheduled at Open
    Called every minute in order of importance of actions
    Checks: exit orders, leverage, failed orders, trailing stops, pull backs
    """
    if len(context.exit_order_list) != 0: handle_failed_exit_orders(context, data)
    if context.account.leverage > context.desired_leverage: handle_leverage_too_high(context, data)
    if len(context.failed_order_list) != 0: handle_failed_orders(context, data)
    if context.use_trailing_stop: handle_trailing_stops(context, data)
    if context.wait_for_pull_back: trade_on_pull_back(context, data)
     
    return

def get_gap_up_stocks(context, data):
    '''
    At market open get stocks that gapped up more than a user defined min/max
    '''
    context.output['open_price'] = data.current(context.output.index.tolist(), 'price')
    #Trim stocks outside of gap up range. 
    context.output = context.output[ context.output['open_price']/context.output['close'] - 1.0 >  context.min_gap_up_percent ]   
    context.output = context.output[ context.output['open_price']/context.output['close'] - 1.0 <  context.max_gap_up_percent ]   

    log.info('Number of gap up stocks= ' + str(len(context.output.index)))
    log.info(context.output.head())
    
    return
    

def open_positions_at_beginning_of_day(context, data):
    '''
    Determine gap up stocks
    Place orders for them
    This should only execute once at/near open
       if pull back is being used the stock goes into the pull back list
       otherwise an order is placed for the stock
    '''
    get_gap_up_stocks(context, data)
    
    for stock in context.output.index:
        if allow_trade(context, data, stock):
            portfolio_percent = 0.98 / len(context.output.index)
            if context.sell_at_open: portfolio_percent = - portfolio_percent
            if context.wait_for_pull_back:
                context.pull_back_list[stock] = portfolio_percent 
            else:
                ord = order_target_percent(stock, portfolio_percent )
                check_order(context, data, ord, stock, portfolio_percent, 'open_positions_at_beginning_of_day:(): open entry')
                if ord and context.use_trailing_stop:
                    context.stop_list[stock] = data.current(stock, 'price') *(1.0 - context.stop_pct)
    return

def trade_on_pull_back(context, data):
    '''
    For all stocks in the gap up list, wait until a pull back
    pull back amount is user defined context.pull_back_percent
    '''
    if not context.wait_for_pull_back or len(context.pull_back_list) == 0: return
    for stock in context.pull_back_list.copy():
        if data.history(stock, bar_count=1, fields='open', frequency='1m') / data.current(stock, 'price') > context.pull_back_percent:
            percent = context.pull_back_list[stock]
            ord = order_target_percent(stock, percent )
            check_order(context, data, ord, stock, percent, 'trade_on_pull_back:(): open entry')
            if ord: del context.pull_back_list[stock]
            if ord and context.use_trailing_stop:
                context.stop_list[stock] = data.current(stock, 'price') *(1.0 - context.stop_pct)
    return

def handle_failed_exit_orders(context, data):
    '''
    Make sure a stock in an exit list goes to zero shares held
    Sometimes can't completely exit a position in 1 trading day
    This removes residual shares across days
    '''
    for security in context.exit_order_list.copy():    #Copy b/c can't iterate through a dictionary and delete keys 
        #If security is in other lists, delete them
        if security in context.stop_list: del context.stop_list[security]
        if security in context.failed_order_list: del context.failed_order_list[security]
        if security in context.pull_back_list: del context.pull_back_list[security]
            
        #no positions in portfolio so remove it from exit list
        if context.portfolio.positions[security].amount == 0:    
            if security in context.exit_order_list: del context.exit_order_list[security]
               
        #Have position and need to get rid of them
        elif data.can_trade(security) and not get_open_orders(security):
            ord = order_target(security, 0.0)
            check_exit_order(context, data, ord, security, 'handle_data(): Handle Exit orders that failed')
    return

def handle_leverage_too_high(context, data):
    '''
    Rank by worst performers
    Divest user defined number of ranked stocks
    '''
    # - Find worst performers and start divesting
    #
    #Find profit of all positions
    portfolio_positions = {}
    for pos in context.portfolio.positions:    #pos is an equity_object
        position_object = context.portfolio.positions[pos]
        portfolio_positions[pos] = abs( (position_object.last_sale_price - position_object.cost_basis)/position_object.amount )
            
    #rank positions
    ranks = {}
    if len(portfolio_positions) <= context.number_securities_to_reduce:
        ranks = portfolio_positions
    else:
        ranks = OrderedDict(sorted(portfolio_positions.items(), key=lambda x: x[1], reverse=False)[:context.number_securities_to_reduce]) 
        # for k,v in ranks.items(): 
        #     print(k)
        #     print(v)
        
    #Reduce positions
    for pos in ranks:
        portfolio_percent = context.leverage_reduction*context.desired_leverage/len(ranks)    #reduce out additional shares
        if context.portfolio.positions[pos].amount < 0: portfolio_percent = -1.0 * portfolio_percent    #short            
        if allow_trade(context, data, pos):                
            ord=order_target_percent(pos, portfolio_percent)
            check_order(context, data, ord, pos, portfolio_percent, "handle_data() - leverage too high")
    return    

def handle_failed_orders(context, data):
    '''
    process failed order list
    try to re-place failed orders
    if successful remove stock from failed order list
    '''
    for security in context.failed_order_list.copy():    #Copy b/c can't iterate through a dictionary and delete keys
        if data.can_trade(security) and not get_open_orders(security):
            percent = context.failed_order_list[security]
            ord = order_target(security, percent)   
            check_order(context, data, ord, security, percent, "handle_data() - handle failed orders")
            if ord: 
                del context.failed_order_list[security]
                if security in context.pull_back_list: del context.pull_back_list[security]
                if context.use_trailing_stop: context.stop_list[security] = data.current(security, 'price')*(1.0 - context.stop_pct)
    return

def close_positions(context, data):
    '''
    Close any open positions at end of trading
    '''
    for pos in context.portfolio.positions:    #pos is an equity_object
        if allow_trade(context, data, pos):
            ord = order_target_percent(pos, 0.0)
            check_exit_order(context, data, ord, pos, 'close_positions()')
    return

def update_trailing_stop(context, data):
    '''
    If trailing stops are being used update based on price action
    '''
    if not context.use_trailing_stop: return
    if len(context.stop_list) == 0: return
    
    for stock in context.stop_list:
        price = data.current(stock, 'price')
    
        #Long
        if context.portfolio.positions[stock].amount > 0:
            stop_pct = 1.0 - context.stop_pct
            context.stop_list[stock] = max(context.stop_list[stock], stop_pct * price)

        #Short
        elif context.portfolio.positions[stock].amount < 0:
            stop_pct = 1.0 + context.stop_pct
            context.stop_list[stock] = min(context.stop_list[stock], stop_pct * price)
    return

def handle_trailing_stops(context, data):
    '''
    update trailing stop
    if trailing stop hit exit position
    '''
    update_trailing_stop(context, data)
    if len( context.stop_list ) > 0:
        for stock in context.stop_list.copy():
            price = data.current(stock, 'price')
            if  price < context.stop_list[stock] and context.portfolio.positions[stock].amount > 0  or  \
                price > context.stop_list[stock] and context.portfolio.positions[stock].amount < 0:    #long or short
                log.info('Stop hit. Exiting Long Position. stop= ' + str(context.stop_list[stock]) + '  price= ' + str(price))
                ord = order_target(stock, 0.0)
                check_exit_order(context, data, ord, stock, 'handle_data(): Stop hit. Exiting Position. stop= ' + \
                             str(context.stop_list[stock]) + '  price= ' + str(price))
                if stock in context.stop_list: del context.stop_list[stock]
                if stock in context.failed_order_list: del context.failed_order_list[stock]
    return

def my_record_vars(context, data):
    """
    Plot variables at the end of each day.
    """
    record(leverage=context.account.leverage)
    return


def allow_trade(context, data, security):
    '''
    Logic for placing trades
    - can trade, no open orders, not in exit list, not a failed order
    '''
    if data.can_trade(security) and \
       not get_open_orders(security) and \
       security not in context.failed_order_list and \
       security not in context.exit_order_list: return True
    return False

def check_order(context, data, order, security, percent, info):
    '''
    Check to see if the order was placed
    If not add it to the failed order list
    TODO: Handle partial fills
    '''
    if ord: 
        log_order(context, data, order, security, info)
    else:
        if security not in context.failed_order_list: context.failed_order_list[security] = percent    
    return

def check_exit_order(context, data, order, security, info):
    '''
    Check if exit order succeeded
    '''
    if security not in context.exit_order_list: context.exit_order_list[security] = info
    if ord: 
        log_order(context, data, order, security, info)
        profit_and_loss(context, data)
    return          


def log_order(context, data, order, security, info):
    '''
    Log an order to the console
    info: Any string to print out
    '''
    if order:
        if get_order(order).amount > 0:
            log.info(str(info) + '  ' + '++++ placing LONG order for ' + str( get_order(order).amount) + '  ' + str(security))
        else:
            log.info(str(info) + '  ' + '---- placing SHORT Order for ' + str( get_order(order).amount) + '  ' + str(security))  
    else:    #empty order 
         log.info(str(info) + '  ' +  '!!!!! WARNING: Order FAILED for ' + str(security))
            
    return

def profit_and_loss(context, data):
    '''
    Log/print profit and loss of all positions
    FIXME: Not fully accurate but close enough
    '''
    positions = context.portfolio.positions
    if len(positions) == 0: return
    
    for pos in positions:
        if pos in context.exit_order_list:
            pnl = positions[pos].amount * (positions[pos].last_sale_price - positions[pos].cost_basis)
            if pos in context.pnl_list: pnl = context.pnl_list[pos] + pnl
            context.pnl_list[pos] = pnl

    for stock in context.pnl_list:
        log.info('PnL ' + stock.symbol + ':  ' + str( round(context.pnl_list[stock], 2) ))
    return

class FloatShares(CustomFactor):
    ''' 
    FIXME: This is an estimate based on available data
    Float = shares available for trading =
            total_outstanding_shares - restricted_stock - closely_held_shares    
    '''
    # Pre-declare inputs and window_length
    inputs = [morningstar.valuation.shares_outstanding, 
              morningstar.balance_sheet.preferred_shares_number,
              morningstar.balance_sheet.restricted_common_stock,
              morningstar.balance_sheet.treasury_shares_number]
    window_length = 1

    # Compute float shares
    def compute(self, today, assets, out, shares_outstanding, pref_shares, restricted_stock, treasury_stock):
        out[:] = np.nan_to_num(shares_outstanding[-1]) - \
                 np.nan_to_num(pref_shares[-1]) - \
                 np.nan_to_num(restricted_stock[-1]) - \
                 np.nan_to_num(treasury_stock[-1])                    
                    
class PreviousMoves(CustomFactor):
    ''' 
    Compute number of times in the past window_length a gap up input_percent occured
    '''
    # Default inputs and window_length
    inputs = [USEquityPricing.open, USEquityPricing.close]
    window_length = 252
    gap_percent = global_min_prev_gap_up_percent + 1.0    #FIXME: Shouldn't have to use global here
                                                          # +1.0 because looks at ratio open/prev_close
    
    # def __init__(self, inputs, window_length, mask, gap_percent, *args, **kwargs):
    #     if 'gap_percent' in kwargs:
    #         PreviousMoves.gap_percent = kwargs.pop('gap_percent')
    #     CustomFactor.__init__(self, inputs=inputs, window_length=window_length, mask=mask)

    # Compute historical gap ups
    def compute(self, today, assets, out, open, close):
        #Values come in as window_length rows x num_stocks in columns
                
        close_shift = np.roll(close, -1, axis=0) #align previous day close to current day open
        ratio = open / close_shift

        #Format data and remove nans
        ratio[ ratio <  PreviousMoves.gap_percent ] = 0.0  #zero out days below gap_percent
        ratio[ ratio >= PreviousMoves.gap_percent ] = 1.0  #Above gap_percent change it to 1
        ratio[ np.isnan(ratio) ] = 0.0    #Set nan entries to 0
        
        num_up_days = ratio.sum(axis=0)    #Sum up number of 1's = gap days in window_length
        out[:] = num_up_days
        

def make_pipeline(context):
    """
    A function to create our dynamic stock selector (pipeline). Documentation on
    pipeline can be found here: https://www.quantopian.com/help#pipeline-title
    """

    #Q1500 limits to 30% per sector so not used as base universe
    """
    9 Filters:
        1. common stock
        2 & 3. not limited partnership - name and database check
        4. database has fundamental data
        5. not over the counter
        6. not when issued
        7. not depository receipts
        8. primary share
        9. high dollar volume
    See: https://www.quantopian.com/tutorials/pipeline#lesson11
    """
    common_stock = morningstar.share_class_reference.security_type.latest.eq('ST00000001')
    not_lp_name = ~morningstar.company_reference.standard_name.latest.matches('.* L[\\. ]?P\.?$')
    not_lp_balance_sheet = morningstar.balance_sheet.limited_partnership.latest.isnull()
    have_data = morningstar.valuation.market_cap.latest.notnull()
    not_otc = ~morningstar.share_class_reference.exchange_id.latest.startswith('OTC')
    not_wi = ~morningstar.share_class_reference.symbol.latest.endswith('.WI')
    not_depository = ~morningstar.share_class_reference.is_depositary_receipt.latest
    primary_share = IsPrimaryShare()
    
    # Combine the above filters.
    tradable_filter = (common_stock & not_lp_name & not_lp_balance_sheet &
                       have_data & not_otc & not_wi & not_depository & primary_share)
    
    high_volume_tradable = (AverageDollarVolume(window_length=21,
                                                mask=tradable_filter)
                            .percentile_between(context.min_avg_dollar_vol, 100))
    

    # Factors and/or Filters
    yesterday_close = USEquityPricing.close.latest
    ema200 = ExponentialWeightedMovingAverage(inputs=[USEquityPricing.close],
                                              window_length=200,
                                              decay_rate=np.exp(np.log(0.5) / 15))
    
    float_shares = FloatShares(mask=high_volume_tradable)
    prev_moves = PreviousMoves(mask=high_volume_tradable,
                               window_length=context.gap_up_day_window)
    
    #Trade like a warrior filter by
    #  1) close > $4 to week out penny stocks (user configurable)
    #  2) close < $50  (means 5000 per trade) (user configurable)
    #  3) price > ema200 to signal strong stocks
    #  4) float <100M shares (user configurable) 
    #  5) history of big moves (user configurable)
    #  6) sector-wide catalyst or technical breakout (not implemented)

    pipe_screen =  high_volume_tradable  \
                  &( yesterday_close > context.min_stock_price ) \
                  &( yesterday_close < context.max_stock_price ) \
                  &( yesterday_close > ema200 ) \
                  &( float_shares > context.float_shares_min ) \
                  &( float_shares < context.float_shares_max ) \
                  &( prev_moves >= context.min_gap_up_days )
        
    pipe = Pipeline(
        screen = pipe_screen,
        columns = {
            'close': yesterday_close          
        }
    )
    return pipe
There was a runtime error.
7 responses

Gonna go out on a limb here and say that only works of the small time series you tested it in, in fact, nearly all of the positive returns come from 1 day. it has been my observation that stocks peak go up, and at ~1.5 to 2 hours after market open, they begin to correct. so a stock will say jump to 10% over the first 2 hours then, for the rest of the day it will fall down to 6.5%. Infact, the results shown above show that if a stock goes up 5% you should short it.... You say that if you run this over a ten year period you will go broke... lets see why.

Same algorithm. 10 year time series

Clone Algorithm
1
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
'''
Stocks and Commodities V35:06 page 34 (June 2017)
Trade like a Warrior. Interview with Ross Cameron

Philosophy:
  Every day there is a stock that moves 20-30%
  Find it when it is up only 5-10%
  80% of time they are gapping up pre-market
  Trade 9:30-11:30am then enjoy the rest of the day


Algorithm:
Find checklist of indicators
   1) At open buy stocks gaping up 10%+
   2) float <100M shares (approximated)
   3) strong daily chart: price > exponential ema200
   4) $1.0 < price < $20  (user configurable). 
   5) history of big moves (user configurable). 60% within last 3 months.
   6) sector-wide catalyst or technical breakout (not implemented)
   7) Exit all positions by 11:30am
Additional indicators
   8) Liquid so exit orders don't fail

Optional trailing stop
Optional Sell gap up. 
Optional wait for pullback

NOTE: Entry / Exit rules are not described in the interview. This code
buys gap up stocks at open and sells at 11:30am unless stopped out.

John Glossner
[email protected]
http://Linkedin.com/in/glossner

MIT License

2017 06 01 Initial version
'''
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.factors import AverageDollarVolume, SimpleMovingAverage, ExponentialWeightedMovingAverage
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.filters.morningstar import Q500US, Q1500US
from quantopian.pipeline.filters.morningstar import IsPrimaryShare
from zipline.api import get_environment
from collections import OrderedDict

import datetime
import numpy as np
                                       
global_min_prev_gap_up_percent = 0.60  #FIXME: Get rid of global in PreviousMoves CustomFactor
 
def initialize(context):
    """
    Called once at the start of the algorithm.
    """ 
    #Stock Selection User Defined Variables
    context.min_stock_price    = 1.00        #min stock trading price. Avoid penny stocks
    context.max_stock_price    = 20.0        #max stock trading price
    context.min_gap_up_percent = 0.10        #Used to select stocks at open
    context.max_gap_up_percent = 2.0         #10% gap up = 0.1
    context.float_shares_min   = 1000000.0   #minimum number of float shares 1M
    context.float_shares_max   = 100000000.0 #max number of float shares 100M
    context.min_avg_dollar_vol = 30          #70 or above is highly liquid
    context.gap_up_day_window  = 63          # 252 = 1 year, 63=3 months
    context.min_gap_up_days    = 1.0         #Min number of gap ups in window

    context.sell_at_open       = False       #Reverse strategy
    context.wait_for_pull_back = False       #Can wait for gapping stock to pull back
    context.pull_back_percent  = 0.02        #0.02 = 2%
    
    
    #Account Management User Defined Variables
    context.use_trailing_stop = False
    context.stop_pct = 0.02                 #0.01 = 1% stop

    context.desired_leverage = 1.0           #Change up or down as desired
    context.leverage_reduction = 0.95        #When overlevered tries to reduce number of trades
    context.number_securities_to_reduce = 3  #maximum number of securities to delever. Must be integer.    
    
    
    #Should not have to change anything below here except perhaps close_positions time
    schedule_function(open_positions_at_beginning_of_day, date_rules.every_day(), time_rules.market_open())
    schedule_function(close_positions, date_rules.every_day(), time_rules.market_open(hours=2, minutes=0))
    
    if get_environment('arena') == 'backtest':    #Called every minute in live trading
        schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close(minutes=1))

    attach_pipeline(make_pipeline(context), 'my_pipeline')
    
    #Wouldn't be needed with GTC orders
    context.exit_order_list = {}           #Equity():info optional. Make sure position is exited
    context.failed_order_list = {}         #Equity():portfolio_percent. To replace orders
    context.stop_list = {}                 #Equity():stop. ASSUMES single order per equity
    context.pull_back_list = {}            #Equity():portfolio_percent
    context.pnl_list = {}                  #Equity(): pnl.
    
    return


 
def before_trading_start(context, data):
    """
    Called every day before market open.
    Any positions not exited the previous day are added to exit list
    New pipeline stocks are retrieved
    """
    #If any positions didn't exit the previous day, add them to the exit list
    for pos in context.portfolio.positions:
        context.exit_order_list[pos] = 'previous day order not exited'
    
    context.output = pipeline_output('my_pipeline').dropna()
  
    log.info('Number of stocks from pipeline= ' + str(len(context.output.index)))
    return     


def handle_data(context,data):
    """
    Called first before any function scheduled at Open
    Called every minute in order of importance of actions
    Checks: exit orders, leverage, failed orders, trailing stops, pull backs
    """
    if len(context.exit_order_list) != 0: handle_failed_exit_orders(context, data)
    if context.account.leverage > context.desired_leverage: handle_leverage_too_high(context, data)
    if len(context.failed_order_list) != 0: handle_failed_orders(context, data)
    if context.use_trailing_stop: handle_trailing_stops(context, data)
    if context.wait_for_pull_back: trade_on_pull_back(context, data)
     
    return

def get_gap_up_stocks(context, data):
    '''
    At market open get stocks that gapped up more than a user defined min/max
    '''
    context.output['open_price'] = data.current(context.output.index.tolist(), 'price')
    #Trim stocks outside of gap up range. 
    context.output = context.output[ context.output['open_price']/context.output['close'] - 1.0 >  context.min_gap_up_percent ]   
    context.output = context.output[ context.output['open_price']/context.output['close'] - 1.0 <  context.max_gap_up_percent ]   

    log.info('Number of gap up stocks= ' + str(len(context.output.index)))
    log.info(context.output.head())
    
    return
    

def open_positions_at_beginning_of_day(context, data):
    '''
    Determine gap up stocks
    Place orders for them
    This should only execute once at/near open
       if pull back is being used the stock goes into the pull back list
       otherwise an order is placed for the stock
    '''
    get_gap_up_stocks(context, data)
    
    for stock in context.output.index:
        if allow_trade(context, data, stock):
            portfolio_percent = 0.98 / len(context.output.index)
            if context.sell_at_open: portfolio_percent = - portfolio_percent
            if context.wait_for_pull_back:
                context.pull_back_list[stock] = portfolio_percent 
            else:
                ord = order_target_percent(stock, portfolio_percent )
                check_order(context, data, ord, stock, portfolio_percent, 'open_positions_at_beginning_of_day:(): open entry')
                if ord and context.use_trailing_stop:
                    context.stop_list[stock] = data.current(stock, 'price') *(1.0 - context.stop_pct)
    return

def trade_on_pull_back(context, data):
    '''
    For all stocks in the gap up list, wait until a pull back
    pull back amount is user defined context.pull_back_percent
    '''
    if not context.wait_for_pull_back or len(context.pull_back_list) == 0: return
    for stock in context.pull_back_list.copy():
        if data.history(stock, bar_count=1, fields='open', frequency='1m') / data.current(stock, 'price') > context.pull_back_percent:
            percent = context.pull_back_list[stock]
            ord = order_target_percent(stock, percent )
            check_order(context, data, ord, stock, percent, 'trade_on_pull_back:(): open entry')
            if ord: del context.pull_back_list[stock]
            if ord and context.use_trailing_stop:
                context.stop_list[stock] = data.current(stock, 'price') *(1.0 - context.stop_pct)
    return

def handle_failed_exit_orders(context, data):
    '''
    Make sure a stock in an exit list goes to zero shares held
    Sometimes can't completely exit a position in 1 trading day
    This removes residual shares across days
    '''
    for security in context.exit_order_list.copy():    #Copy b/c can't iterate through a dictionary and delete keys 
        #If security is in other lists, delete them
        if security in context.stop_list: del context.stop_list[security]
        if security in context.failed_order_list: del context.failed_order_list[security]
        if security in context.pull_back_list: del context.pull_back_list[security]
            
        #no positions in portfolio so remove it from exit list
        if context.portfolio.positions[security].amount == 0:    
            if security in context.exit_order_list: del context.exit_order_list[security]
               
        #Have position and need to get rid of them
        elif data.can_trade(security) and not get_open_orders(security):
            ord = order_target(security, 0.0)
            check_exit_order(context, data, ord, security, 'handle_data(): Handle Exit orders that failed')
    return

def handle_leverage_too_high(context, data):
    '''
    Rank by worst performers
    Divest user defined number of ranked stocks
    '''
    # - Find worst performers and start divesting
    #
    #Find profit of all positions
    portfolio_positions = {}
    for pos in context.portfolio.positions:    #pos is an equity_object
        position_object = context.portfolio.positions[pos]
        portfolio_positions[pos] = abs( (position_object.last_sale_price - position_object.cost_basis)/position_object.amount )
            
    #rank positions
    ranks = {}
    if len(portfolio_positions) <= context.number_securities_to_reduce:
        ranks = portfolio_positions
    else:
        ranks = OrderedDict(sorted(portfolio_positions.items(), key=lambda x: x[1], reverse=False)[:context.number_securities_to_reduce]) 
        # for k,v in ranks.items(): 
        #     print(k)
        #     print(v)
        
    #Reduce positions
    for pos in ranks:
        portfolio_percent = context.leverage_reduction*context.desired_leverage/len(ranks)    #reduce out additional shares
        if context.portfolio.positions[pos].amount < 0: portfolio_percent = -1.0 * portfolio_percent    #short            
        if allow_trade(context, data, pos):                
            ord=order_target_percent(pos, portfolio_percent)
            check_order(context, data, ord, pos, portfolio_percent, "handle_data() - leverage too high")
    return    

def handle_failed_orders(context, data):
    '''
    process failed order list
    try to re-place failed orders
    if successful remove stock from failed order list
    '''
    for security in context.failed_order_list.copy():    #Copy b/c can't iterate through a dictionary and delete keys
        if data.can_trade(security) and not get_open_orders(security):
            percent = context.failed_order_list[security]
            ord = order_target(security, percent)   
            check_order(context, data, ord, security, percent, "handle_data() - handle failed orders")
            if ord: 
                del context.failed_order_list[security]
                if security in context.pull_back_list: del context.pull_back_list[security]
                if context.use_trailing_stop: context.stop_list[security] = data.current(security, 'price')*(1.0 - context.stop_pct)
    return

def close_positions(context, data):
    '''
    Close any open positions at end of trading
    '''
    for pos in context.portfolio.positions:    #pos is an equity_object
        if allow_trade(context, data, pos):
            ord = order_target_percent(pos, 0.0)
            check_exit_order(context, data, ord, pos, 'close_positions()')
    return

def update_trailing_stop(context, data):
    '''
    If trailing stops are being used update based on price action
    '''
    if not context.use_trailing_stop: return
    if len(context.stop_list) == 0: return
    
    for stock in context.stop_list:
        price = data.current(stock, 'price')
    
        #Long
        if context.portfolio.positions[stock].amount > 0:
            stop_pct = 1.0 - context.stop_pct
            context.stop_list[stock] = max(context.stop_list[stock], stop_pct * price)

        #Short
        elif context.portfolio.positions[stock].amount < 0:
            stop_pct = 1.0 + context.stop_pct
            context.stop_list[stock] = min(context.stop_list[stock], stop_pct * price)
    return

def handle_trailing_stops(context, data):
    '''
    update trailing stop
    if trailing stop hit exit position
    '''
    update_trailing_stop(context, data)
    if len( context.stop_list ) > 0:
        for stock in context.stop_list.copy():
            price = data.current(stock, 'price')
            if  price < context.stop_list[stock] and context.portfolio.positions[stock].amount > 0  or  \
                price > context.stop_list[stock] and context.portfolio.positions[stock].amount < 0:    #long or short
                log.info('Stop hit. Exiting Long Position. stop= ' + str(context.stop_list[stock]) + '  price= ' + str(price))
                ord = order_target(stock, 0.0)
                check_exit_order(context, data, ord, stock, 'handle_data(): Stop hit. Exiting Position. stop= ' + \
                             str(context.stop_list[stock]) + '  price= ' + str(price))
                if stock in context.stop_list: del context.stop_list[stock]
                if stock in context.failed_order_list: del context.failed_order_list[stock]
    return

def my_record_vars(context, data):
    """
    Plot variables at the end of each day.
    """
    record(leverage=context.account.leverage)
    return


def allow_trade(context, data, security):
    '''
    Logic for placing trades
    - can trade, no open orders, not in exit list, not a failed order
    '''
    if data.can_trade(security) and \
       not get_open_orders(security) and \
       security not in context.failed_order_list and \
       security not in context.exit_order_list: return True
    return False

def check_order(context, data, order, security, percent, info):
    '''
    Check to see if the order was placed
    If not add it to the failed order list
    TODO: Handle partial fills
    '''
    if ord: 
        log_order(context, data, order, security, info)
    else:
        if security not in context.failed_order_list: context.failed_order_list[security] = percent    
    return

def check_exit_order(context, data, order, security, info):
    '''
    Check if exit order succeeded
    '''
    if security not in context.exit_order_list: context.exit_order_list[security] = info
    if ord: 
        log_order(context, data, order, security, info)
        profit_and_loss(context, data)
    return          


def log_order(context, data, order, security, info):
    '''
    Log an order to the console
    info: Any string to print out
    '''
    if order:
        if get_order(order).amount > 0:
            log.info(str(info) + '  ' + '++++ placing LONG order for ' + str( get_order(order).amount) + '  ' + str(security))
        else:
            log.info(str(info) + '  ' + '---- placing SHORT Order for ' + str( get_order(order).amount) + '  ' + str(security))  
    else:    #empty order 
         log.info(str(info) + '  ' +  '!!!!! WARNING: Order FAILED for ' + str(security))
            
    return

def profit_and_loss(context, data):
    '''
    Log/print profit and loss of all positions
    FIXME: Not fully accurate but close enough
    '''
    positions = context.portfolio.positions
    if len(positions) == 0: return
    
    for pos in positions:
        if pos in context.exit_order_list:
            pnl = positions[pos].amount * (positions[pos].last_sale_price - positions[pos].cost_basis)
            if pos in context.pnl_list: pnl = context.pnl_list[pos] + pnl
            context.pnl_list[pos] = pnl

    for stock in context.pnl_list:
        log.info('PnL ' + stock.symbol + ':  ' + str( round(context.pnl_list[stock], 2) ))
    return

class FloatShares(CustomFactor):
    ''' 
    FIXME: This is an estimate based on available data
    Float = shares available for trading =
            total_outstanding_shares - restricted_stock - closely_held_shares    
    '''
    # Pre-declare inputs and window_length
    inputs = [morningstar.valuation.shares_outstanding, 
              morningstar.balance_sheet.preferred_shares_number,
              morningstar.balance_sheet.restricted_common_stock,
              morningstar.balance_sheet.treasury_shares_number]
    window_length = 1

    # Compute float shares
    def compute(self, today, assets, out, shares_outstanding, pref_shares, restricted_stock, treasury_stock):
        out[:] = np.nan_to_num(shares_outstanding[-1]) - \
                 np.nan_to_num(pref_shares[-1]) - \
                 np.nan_to_num(restricted_stock[-1]) - \
                 np.nan_to_num(treasury_stock[-1])                    
                    
class PreviousMoves(CustomFactor):
    ''' 
    Compute number of times in the past window_length a gap up input_percent occured
    '''
    # Default inputs and window_length
    inputs = [USEquityPricing.open, USEquityPricing.close]
    window_length = 252
    gap_percent = global_min_prev_gap_up_percent + 1.0    #FIXME: Shouldn't have to use global here
                                                          # +1.0 because looks at ratio open/prev_close
    
    # def __init__(self, inputs, window_length, mask, gap_percent, *args, **kwargs):
    #     if 'gap_percent' in kwargs:
    #         PreviousMoves.gap_percent = kwargs.pop('gap_percent')
    #     CustomFactor.__init__(self, inputs=inputs, window_length=window_length, mask=mask)

    # Compute historical gap ups
    def compute(self, today, assets, out, open, close):
        #Values come in as window_length rows x num_stocks in columns
                
        close_shift = np.roll(close, -1, axis=0) #align previous day close to current day open
        ratio = open / close_shift

        #Format data and remove nans
        ratio[ ratio <  PreviousMoves.gap_percent ] = 0.0  #zero out days below gap_percent
        ratio[ ratio >= PreviousMoves.gap_percent ] = 1.0  #Above gap_percent change it to 1
        ratio[ np.isnan(ratio) ] = 0.0    #Set nan entries to 0
        
        num_up_days = ratio.sum(axis=0)    #Sum up number of 1's = gap days in window_length
        out[:] = num_up_days
        

def make_pipeline(context):
    """
    A function to create our dynamic stock selector (pipeline). Documentation on
    pipeline can be found here: https://www.quantopian.com/help#pipeline-title
    """

    #Q1500 limits to 30% per sector so not used as base universe
    """
    9 Filters:
        1. common stock
        2 & 3. not limited partnership - name and database check
        4. database has fundamental data
        5. not over the counter
        6. not when issued
        7. not depository receipts
        8. primary share
        9. high dollar volume
    See: https://www.quantopian.com/tutorials/pipeline#lesson11
    """
    common_stock = morningstar.share_class_reference.security_type.latest.eq('ST00000001')
    not_lp_name = ~morningstar.company_reference.standard_name.latest.matches('.* L[\\. ]?P\.?$')
    not_lp_balance_sheet = morningstar.balance_sheet.limited_partnership.latest.isnull()
    have_data = morningstar.valuation.market_cap.latest.notnull()
    not_otc = ~morningstar.share_class_reference.exchange_id.latest.startswith('OTC')
    not_wi = ~morningstar.share_class_reference.symbol.latest.endswith('.WI')
    not_depository = ~morningstar.share_class_reference.is_depositary_receipt.latest
    primary_share = IsPrimaryShare()
    
    # Combine the above filters.
    tradable_filter = (common_stock & not_lp_name & not_lp_balance_sheet &
                       have_data & not_otc & not_wi & not_depository & primary_share)
    
    high_volume_tradable = (AverageDollarVolume(window_length=21,
                                                mask=tradable_filter)
                            .percentile_between(context.min_avg_dollar_vol, 100))
    

    # Factors and/or Filters
    yesterday_close = USEquityPricing.close.latest
    ema200 = ExponentialWeightedMovingAverage(inputs=[USEquityPricing.close],
                                              window_length=200,
                                              decay_rate=np.exp(np.log(0.5) / 15))
    
    float_shares = FloatShares(mask=high_volume_tradable)
    prev_moves = PreviousMoves(mask=high_volume_tradable,
                               window_length=context.gap_up_day_window)
    
    #Trade like a warrior filter by
    #  1) close > $4 to week out penny stocks (user configurable)
    #  2) close < $50  (means 5000 per trade) (user configurable)
    #  3) price > ema200 to signal strong stocks
    #  4) float <100M shares (user configurable) 
    #  5) history of big moves (user configurable)
    #  6) sector-wide catalyst or technical breakout (not implemented)

    pipe_screen =  high_volume_tradable  \
                  &( yesterday_close > context.min_stock_price ) \
                  &( yesterday_close < context.max_stock_price ) \
                  &( yesterday_close > ema200 ) \
                  &( float_shares > context.float_shares_min ) \
                  &( float_shares < context.float_shares_max ) \
                  &( prev_moves >= context.min_gap_up_days )
        
    pipe = Pipeline(
        screen = pipe_screen,
        columns = {
            'close': yesterday_close          
        }
    )
    return pipe
There was a runtime error.

FULL BACKTEST NOTEBOOK

45% profitable trades.

Loading notebook preview...
Notebook previews are currently unavailable.

Same algorithm, 10 year time series, changed the first 2 occurrences of

order_target_percent(blah, blah blah)  

To:
order_target_percent(blah, -1* blah blah)

Clone Algorithm
1
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
'''
Stocks and Commodities V35:06 page 34 (June 2017)
Trade like a Warrior. Interview with Ross Cameron

Philosophy:
  Every day there is a stock that moves 20-30%
  Find it when it is up only 5-10%
  80% of time they are gapping up pre-market
  Trade 9:30-11:30am then enjoy the rest of the day


Algorithm:
Find checklist of indicators
   1) At open buy stocks gaping up 10%+
   2) float <100M shares (approximated)
   3) strong daily chart: price > exponential ema200
   4) $1.0 < price < $20  (user configurable). 
   5) history of big moves (user configurable). 60% within last 3 months.
   6) sector-wide catalyst or technical breakout (not implemented)
   7) Exit all positions by 11:30am
Additional indicators
   8) Liquid so exit orders don't fail

Optional trailing stop
Optional Sell gap up. 
Optional wait for pullback

NOTE: Entry / Exit rules are not described in the interview. This code
buys gap up stocks at open and sells at 11:30am unless stopped out.

John Glossner
[email protected]
http://Linkedin.com/in/glossner

MIT License

2017 06 01 Initial version
'''
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.factors import AverageDollarVolume, SimpleMovingAverage, ExponentialWeightedMovingAverage
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.filters.morningstar import Q500US, Q1500US
from quantopian.pipeline.filters.morningstar import IsPrimaryShare
from zipline.api import get_environment
from collections import OrderedDict

import datetime
import numpy as np
                                       
global_min_prev_gap_up_percent = 0.60  #FIXME: Get rid of global in PreviousMoves CustomFactor
 
def initialize(context):
    """
    Called once at the start of the algorithm.
    """ 
    #Stock Selection User Defined Variables
    context.min_stock_price    = 1.00        #min stock trading price. Avoid penny stocks
    context.max_stock_price    = 20.0        #max stock trading price
    context.min_gap_up_percent = 0.10        #Used to select stocks at open
    context.max_gap_up_percent = 2.0         #10% gap up = 0.1
    context.float_shares_min   = 1000000.0   #minimum number of float shares 1M
    context.float_shares_max   = 100000000.0 #max number of float shares 100M
    context.min_avg_dollar_vol = 30          #70 or above is highly liquid
    context.gap_up_day_window  = 63          # 252 = 1 year, 63=3 months
    context.min_gap_up_days    = 1.0         #Min number of gap ups in window

    context.sell_at_open       = False       #Reverse strategy
    context.wait_for_pull_back = False       #Can wait for gapping stock to pull back
    context.pull_back_percent  = 0.02        #0.02 = 2%
    
    
    #Account Management User Defined Variables
    context.use_trailing_stop = False
    context.stop_pct = 0.02                 #0.01 = 1% stop

    context.desired_leverage = 1.0           #Change up or down as desired
    context.leverage_reduction = 0.95        #When overlevered tries to reduce number of trades
    context.number_securities_to_reduce = 3  #maximum number of securities to delever. Must be integer.    
    
    
    #Should not have to change anything below here except perhaps close_positions time
    schedule_function(open_positions_at_beginning_of_day, date_rules.every_day(), time_rules.market_open())
    schedule_function(close_positions, date_rules.every_day(), time_rules.market_open(hours=2, minutes=0))
    
    if get_environment('arena') == 'backtest':    #Called every minute in live trading
        schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close(minutes=1))

    attach_pipeline(make_pipeline(context), 'my_pipeline')
    
    #Wouldn't be needed with GTC orders
    context.exit_order_list = {}           #Equity():info optional. Make sure position is exited
    context.failed_order_list = {}         #Equity():portfolio_percent. To replace orders
    context.stop_list = {}                 #Equity():stop. ASSUMES single order per equity
    context.pull_back_list = {}            #Equity():portfolio_percent
    context.pnl_list = {}                  #Equity(): pnl.
    
    return


 
def before_trading_start(context, data):
    """
    Called every day before market open.
    Any positions not exited the previous day are added to exit list
    New pipeline stocks are retrieved
    """
    #If any positions didn't exit the previous day, add them to the exit list
    for pos in context.portfolio.positions:
        context.exit_order_list[pos] = 'previous day order not exited'
    
    context.output = pipeline_output('my_pipeline').dropna()
  
    log.info('Number of stocks from pipeline= ' + str(len(context.output.index)))
    return     


def handle_data(context,data):
    """
    Called first before any function scheduled at Open
    Called every minute in order of importance of actions
    Checks: exit orders, leverage, failed orders, trailing stops, pull backs
    """
    if len(context.exit_order_list) != 0: handle_failed_exit_orders(context, data)
    if context.account.leverage > context.desired_leverage: handle_leverage_too_high(context, data)
    if len(context.failed_order_list) != 0: handle_failed_orders(context, data)
    if context.use_trailing_stop: handle_trailing_stops(context, data)
    if context.wait_for_pull_back: trade_on_pull_back(context, data)
     
    return

def get_gap_up_stocks(context, data):
    '''
    At market open get stocks that gapped up more than a user defined min/max
    '''
    context.output['open_price'] = data.current(context.output.index.tolist(), 'price')
    #Trim stocks outside of gap up range. 
    context.output = context.output[ context.output['open_price']/context.output['close'] - 1.0 >  context.min_gap_up_percent ]   
    context.output = context.output[ context.output['open_price']/context.output['close'] - 1.0 <  context.max_gap_up_percent ]   

    log.info('Number of gap up stocks= ' + str(len(context.output.index)))
    log.info(context.output.head())
    
    return
    

def open_positions_at_beginning_of_day(context, data):
    '''
    Determine gap up stocks
    Place orders for them
    This should only execute once at/near open
       if pull back is being used the stock goes into the pull back list
       otherwise an order is placed for the stock
    '''
    get_gap_up_stocks(context, data)
    
    for stock in context.output.index:
        if allow_trade(context, data, stock):
            portfolio_percent = 0.98 / len(context.output.index)
            if context.sell_at_open: portfolio_percent = - portfolio_percent
            if context.wait_for_pull_back:
                context.pull_back_list[stock] = portfolio_percent 
            else:
                ord = order_target_percent(stock, -1*portfolio_percent )
                check_order(context, data, ord, stock, portfolio_percent, 'open_positions_at_beginning_of_day:(): open entry')
                if ord and context.use_trailing_stop:
                    context.stop_list[stock] = data.current(stock, 'price') *(1.0 - context.stop_pct)
    return

def trade_on_pull_back(context, data):
    '''
    For all stocks in the gap up list, wait until a pull back
    pull back amount is user defined context.pull_back_percent
    '''
    if not context.wait_for_pull_back or len(context.pull_back_list) == 0: return
    for stock in context.pull_back_list.copy():
        if data.history(stock, bar_count=1, fields='open', frequency='1m') / data.current(stock, 'price') > context.pull_back_percent:
            percent = context.pull_back_list[stock]
            ord = order_target_percent(stock, -1*percent )
            check_order(context, data, ord, stock, percent, 'trade_on_pull_back:(): open entry')
            if ord: del context.pull_back_list[stock]
            if ord and context.use_trailing_stop:
                context.stop_list[stock] = data.current(stock, 'price') *(1.0 - context.stop_pct)
    return

def handle_failed_exit_orders(context, data):
    '''
    Make sure a stock in an exit list goes to zero shares held
    Sometimes can't completely exit a position in 1 trading day
    This removes residual shares across days
    '''
    for security in context.exit_order_list.copy():    #Copy b/c can't iterate through a dictionary and delete keys 
        #If security is in other lists, delete them
        if security in context.stop_list: del context.stop_list[security]
        if security in context.failed_order_list: del context.failed_order_list[security]
        if security in context.pull_back_list: del context.pull_back_list[security]
            
        #no positions in portfolio so remove it from exit list
        if context.portfolio.positions[security].amount == 0:    
            if security in context.exit_order_list: del context.exit_order_list[security]
               
        #Have position and need to get rid of them
        elif data.can_trade(security) and not get_open_orders(security):
            ord = order_target(security, 0.0)
            check_exit_order(context, data, ord, security, 'handle_data(): Handle Exit orders that failed')
    return

def handle_leverage_too_high(context, data):
    '''
    Rank by worst performers
    Divest user defined number of ranked stocks
    '''
    # - Find worst performers and start divesting
    #
    #Find profit of all positions
    portfolio_positions = {}
    for pos in context.portfolio.positions:    #pos is an equity_object
        position_object = context.portfolio.positions[pos]
        portfolio_positions[pos] = abs( (position_object.last_sale_price - position_object.cost_basis)/position_object.amount )
            
    #rank positions
    ranks = {}
    if len(portfolio_positions) <= context.number_securities_to_reduce:
        ranks = portfolio_positions
    else:
        ranks = OrderedDict(sorted(portfolio_positions.items(), key=lambda x: x[1], reverse=False)[:context.number_securities_to_reduce]) 
        # for k,v in ranks.items(): 
        #     print(k)
        #     print(v)
        
    #Reduce positions
    for pos in ranks:
        portfolio_percent = context.leverage_reduction*context.desired_leverage/len(ranks)    #reduce out additional shares
        if context.portfolio.positions[pos].amount < 0: portfolio_percent = -1.0 * portfolio_percent    #short            
        if allow_trade(context, data, pos):                
            ord=order_target_percent(pos, portfolio_percent)
            check_order(context, data, ord, pos, portfolio_percent, "handle_data() - leverage too high")
    return    

def handle_failed_orders(context, data):
    '''
    process failed order list
    try to re-place failed orders
    if successful remove stock from failed order list
    '''
    for security in context.failed_order_list.copy():    #Copy b/c can't iterate through a dictionary and delete keys
        if data.can_trade(security) and not get_open_orders(security):
            percent = context.failed_order_list[security]
            ord = order_target(security, percent)   
            check_order(context, data, ord, security, percent, "handle_data() - handle failed orders")
            if ord: 
                del context.failed_order_list[security]
                if security in context.pull_back_list: del context.pull_back_list[security]
                if context.use_trailing_stop: context.stop_list[security] = data.current(security, 'price')*(1.0 - context.stop_pct)
    return

def close_positions(context, data):
    '''
    Close any open positions at end of trading
    '''
    for pos in context.portfolio.positions:    #pos is an equity_object
        if allow_trade(context, data, pos):
            ord = order_target_percent(pos, 0.0)
            check_exit_order(context, data, ord, pos, 'close_positions()')
    return

def update_trailing_stop(context, data):
    '''
    If trailing stops are being used update based on price action
    '''
    if not context.use_trailing_stop: return
    if len(context.stop_list) == 0: return
    
    for stock in context.stop_list:
        price = data.current(stock, 'price')
    
        #Long
        if context.portfolio.positions[stock].amount > 0:
            stop_pct = 1.0 - context.stop_pct
            context.stop_list[stock] = max(context.stop_list[stock], stop_pct * price)

        #Short
        elif context.portfolio.positions[stock].amount < 0:
            stop_pct = 1.0 + context.stop_pct
            context.stop_list[stock] = min(context.stop_list[stock], stop_pct * price)
    return

def handle_trailing_stops(context, data):
    '''
    update trailing stop
    if trailing stop hit exit position
    '''
    update_trailing_stop(context, data)
    if len( context.stop_list ) > 0:
        for stock in context.stop_list.copy():
            price = data.current(stock, 'price')
            if  price < context.stop_list[stock] and context.portfolio.positions[stock].amount > 0  or  \
                price > context.stop_list[stock] and context.portfolio.positions[stock].amount < 0:    #long or short
                log.info('Stop hit. Exiting Long Position. stop= ' + str(context.stop_list[stock]) + '  price= ' + str(price))
                ord = order_target(stock, 0.0)
                check_exit_order(context, data, ord, stock, 'handle_data(): Stop hit. Exiting Position. stop= ' + \
                             str(context.stop_list[stock]) + '  price= ' + str(price))
                if stock in context.stop_list: del context.stop_list[stock]
                if stock in context.failed_order_list: del context.failed_order_list[stock]
    return

def my_record_vars(context, data):
    """
    Plot variables at the end of each day.
    """
    record(leverage=context.account.leverage)
    return


def allow_trade(context, data, security):
    '''
    Logic for placing trades
    - can trade, no open orders, not in exit list, not a failed order
    '''
    if data.can_trade(security) and \
       not get_open_orders(security) and \
       security not in context.failed_order_list and \
       security not in context.exit_order_list: return True
    return False

def check_order(context, data, order, security, percent, info):
    '''
    Check to see if the order was placed
    If not add it to the failed order list
    TODO: Handle partial fills
    '''
    if ord: 
        log_order(context, data, order, security, info)
    else:
        if security not in context.failed_order_list: context.failed_order_list[security] = percent    
    return

def check_exit_order(context, data, order, security, info):
    '''
    Check if exit order succeeded
    '''
    if security not in context.exit_order_list: context.exit_order_list[security] = info
    if ord: 
        log_order(context, data, order, security, info)
        profit_and_loss(context, data)
    return          


def log_order(context, data, order, security, info):
    '''
    Log an order to the console
    info: Any string to print out
    '''
    if order:
        if get_order(order).amount > 0:
            log.info(str(info) + '  ' + '++++ placing LONG order for ' + str( get_order(order).amount) + '  ' + str(security))
        else:
            log.info(str(info) + '  ' + '---- placing SHORT Order for ' + str( get_order(order).amount) + '  ' + str(security))  
    else:    #empty order 
         log.info(str(info) + '  ' +  '!!!!! WARNING: Order FAILED for ' + str(security))
            
    return

def profit_and_loss(context, data):
    '''
    Log/print profit and loss of all positions
    FIXME: Not fully accurate but close enough
    '''
    positions = context.portfolio.positions
    if len(positions) == 0: return
    
    for pos in positions:
        if pos in context.exit_order_list:
            pnl = positions[pos].amount * (positions[pos].last_sale_price - positions[pos].cost_basis)
            if pos in context.pnl_list: pnl = context.pnl_list[pos] + pnl
            context.pnl_list[pos] = pnl

    for stock in context.pnl_list:
        log.info('PnL ' + stock.symbol + ':  ' + str( round(context.pnl_list[stock], 2) ))
    return

class FloatShares(CustomFactor):
    ''' 
    FIXME: This is an estimate based on available data
    Float = shares available for trading =
            total_outstanding_shares - restricted_stock - closely_held_shares    
    '''
    # Pre-declare inputs and window_length
    inputs = [morningstar.valuation.shares_outstanding, 
              morningstar.balance_sheet.preferred_shares_number,
              morningstar.balance_sheet.restricted_common_stock,
              morningstar.balance_sheet.treasury_shares_number]
    window_length = 1

    # Compute float shares
    def compute(self, today, assets, out, shares_outstanding, pref_shares, restricted_stock, treasury_stock):
        out[:] = np.nan_to_num(shares_outstanding[-1]) - \
                 np.nan_to_num(pref_shares[-1]) - \
                 np.nan_to_num(restricted_stock[-1]) - \
                 np.nan_to_num(treasury_stock[-1])                    
                    
class PreviousMoves(CustomFactor):
    ''' 
    Compute number of times in the past window_length a gap up input_percent occured
    '''
    # Default inputs and window_length
    inputs = [USEquityPricing.open, USEquityPricing.close]
    window_length = 252
    gap_percent = global_min_prev_gap_up_percent + 1.0    #FIXME: Shouldn't have to use global here
                                                          # +1.0 because looks at ratio open/prev_close
    
    # def __init__(self, inputs, window_length, mask, gap_percent, *args, **kwargs):
    #     if 'gap_percent' in kwargs:
    #         PreviousMoves.gap_percent = kwargs.pop('gap_percent')
    #     CustomFactor.__init__(self, inputs=inputs, window_length=window_length, mask=mask)

    # Compute historical gap ups
    def compute(self, today, assets, out, open, close):
        #Values come in as window_length rows x num_stocks in columns
                
        close_shift = np.roll(close, -1, axis=0) #align previous day close to current day open
        ratio = open / close_shift

        #Format data and remove nans
        ratio[ ratio <  PreviousMoves.gap_percent ] = 0.0  #zero out days below gap_percent
        ratio[ ratio >= PreviousMoves.gap_percent ] = 1.0  #Above gap_percent change it to 1
        ratio[ np.isnan(ratio) ] = 0.0    #Set nan entries to 0
        
        num_up_days = ratio.sum(axis=0)    #Sum up number of 1's = gap days in window_length
        out[:] = num_up_days
        

def make_pipeline(context):
    """
    A function to create our dynamic stock selector (pipeline). Documentation on
    pipeline can be found here: https://www.quantopian.com/help#pipeline-title
    """

    #Q1500 limits to 30% per sector so not used as base universe
    """
    9 Filters:
        1. common stock
        2 & 3. not limited partnership - name and database check
        4. database has fundamental data
        5. not over the counter
        6. not when issued
        7. not depository receipts
        8. primary share
        9. high dollar volume
    See: https://www.quantopian.com/tutorials/pipeline#lesson11
    """
    common_stock = morningstar.share_class_reference.security_type.latest.eq('ST00000001')
    not_lp_name = ~morningstar.company_reference.standard_name.latest.matches('.* L[\\. ]?P\.?$')
    not_lp_balance_sheet = morningstar.balance_sheet.limited_partnership.latest.isnull()
    have_data = morningstar.valuation.market_cap.latest.notnull()
    not_otc = ~morningstar.share_class_reference.exchange_id.latest.startswith('OTC')
    not_wi = ~morningstar.share_class_reference.symbol.latest.endswith('.WI')
    not_depository = ~morningstar.share_class_reference.is_depositary_receipt.latest
    primary_share = IsPrimaryShare()
    
    # Combine the above filters.
    tradable_filter = (common_stock & not_lp_name & not_lp_balance_sheet &
                       have_data & not_otc & not_wi & not_depository & primary_share)
    
    high_volume_tradable = (AverageDollarVolume(window_length=21,
                                                mask=tradable_filter)
                            .percentile_between(context.min_avg_dollar_vol, 100))
    

    # Factors and/or Filters
    yesterday_close = USEquityPricing.close.latest
    ema200 = ExponentialWeightedMovingAverage(inputs=[USEquityPricing.close],
                                              window_length=200,
                                              decay_rate=np.exp(np.log(0.5) / 15))
    
    float_shares = FloatShares(mask=high_volume_tradable)
    prev_moves = PreviousMoves(mask=high_volume_tradable,
                               window_length=context.gap_up_day_window)
    
    #Trade like a warrior filter by
    #  1) close > $4 to week out penny stocks (user configurable)
    #  2) close < $50  (means 5000 per trade) (user configurable)
    #  3) price > ema200 to signal strong stocks
    #  4) float <100M shares (user configurable) 
    #  5) history of big moves (user configurable)
    #  6) sector-wide catalyst or technical breakout (not implemented)

    pipe_screen =  high_volume_tradable  \
                  &( yesterday_close > context.min_stock_price ) \
                  &( yesterday_close < context.max_stock_price ) \
                  &( yesterday_close > ema200 ) \
                  &( float_shares > context.float_shares_min ) \
                  &( float_shares < context.float_shares_max ) \
                  &( prev_moves >= context.min_gap_up_days )
        
    pipe = Pipeline(
        screen = pipe_screen,
        columns = {
            'close': yesterday_close          
        }
    )
    return pipe
There was a runtime error.

Doesnt seem to be loading. the returns are +38% over 10 years. up 200% at one point

@Jacob, see observations. This strategy will definitely go bust. Also, setting the flag sell_at_open = True will switch to a short strategy.

Updated with a few more knobs. Fixed cancelling orders bug. Also includes a switch to set to typical TLAW settings based on public information (context.use_trade_like_a_warrior_values=True). Results are much worse with that knob (-12% with 40% drawdown).

As previously noted, this is not a tradeable strategy and if you have a large account it doesn't scale well. It's success rests on hitting 1 or 2 home runs per year and getting out of wrong bets quickly.

It also is only an approximation to what Ross Cameron actually does. He has published his broker statements and he is profitable.

Clone Algorithm
69
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
'''
Day Trade (modified warrior)

Algorithm:

Gap and Go: 
 * At open to within 30 minutes select gap up with high relative volume
 * 

Find checklist of indicators to select stock universe.
Optimized results can be set back to warrior's results using flag
   1) At open buy stocks gaping up 4%+ (based on mktg email)
   2) 10M < float < 100M shares (approximated)
   3) strong daily chart: price > exponential ema200
   4) $3.00 < price < $10  (user configurable). Based on website.
   5) history of big moves (user configurable). 
      - guess 60% once within last 3 months
   6) Max 5 trades per day
   7) Catalyst (earnings, news, etc.) NOT IMPLEMENTED

Order Management
   1) Buy 4%+ gap up stocks during 1st 30 minutes of trading
   2) Set stop at low of first 1 minute candle
   3) Sell half position at profit target
      - guess 20% gain
   4) Exit all positions at 11:30am

Options / Modifications to strategy:
   1) trailing stop and/or close by trailing stop
   2) Sell gap up instead of buy

Philosophy:
Every day there is a stock that moves 20-30%
Find it when it is up only 5-10%
80% of time they are gapping up pre-market
Trade 9:30-11:30am then Go play golf


John Glossner
[email protected]
http://Linkedin.com/in/glossner

MIT License

2017 07 07 Added percent to exit when profit target hit
2017 06 29 Changed gap and go parameters based on website/marketing emails
2017 06 20 Canceled open orders bug
2017 06 01 Initial version
'''
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.factors import AverageDollarVolume, SimpleMovingAverage, ExponentialWeightedMovingAverage
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.filters.morningstar import Q500US, Q1500US
from quantopian.pipeline.filters.morningstar import IsPrimaryShare
from zipline.api import get_environment
from collections import OrderedDict

import datetime
import numpy as np
import pandas as pd
import talib
                                       #FIXME: Get rid of global in PreviousMoves CustomFactor?
global_min_prev_gap_up_percent = 0.60  #Used to select gap up stocks at open
 
def initialize(context):
    """
    Called once at the start of the algorithm.
    """ 
    
    context.use_trade_like_a_warrior_values = False #Set to true to override optimized values below
    
    #strategy
    context.use_gap_up_at_open_strategy  = True
    context.gap_trading_time             = 30       #How long to check for gap ups in minutes. (min=1)
    context.gap_to_previous_bar          = True     #False = gap to previous days close
    
    #Stock Selection User Defined Variables
    context.min_stock_price          = 1.00         #min stock trading price. Avoid penny stocks
    context.max_stock_price          = 20.0         #max stock trading price
    context.min_gap_up_percent       = 0.1          #Used to select stocks at open
    context.max_gap_up_percent       = 2.0          #10% gap up = 0.1
    context.float_shares_min         = 1000000.0    #minimum number of float shares 1M
    context.float_shares_max         = 100000000.0  #max number of float shares 100M
    context.min_avg_dollar_vol       = 30           #70 or above is highly liquid
    context.gap_up_day_window        = 63           #252 = 1 year, 63=3 months
    context.min_gap_up_days          = 1.0          #Min number of gap ups in window
    context.volume_multiplier        = 2.0          #volume increase over previous 10 ticks avg volume

    #Account Management User Defined Variables
    context.use_fixed_stop                    = True   #Low of 1 minute bar on opening trade
    context.set_fixed_stop_at_buy_price       = False  #Low of open bar if False
    context.use_trailing_stop                 = False  #For opening trade only. Interferes with use_fixed_stop
    context.close_positions_by_trailing_stop  = False  #Set a trailing stop to close out the positions
                                                       #Note: all positions still close at end of day
    
    context.sell_part_at_profit_target             = False  #Exit a part of positions
    context.percent_to_sell_at_profit_target       = 0.50   #Sell half = 0.5
    context.target_profit_per_trade                = 0.20   #0.20 = 20%

    context.trailing_stop_pct                      = 0.005  #0.01 = 1% stop.
    context.max_daily_portfolio_loss               = 0.02   #0.02 = 2% loss
    context.max_position_percent                   = 1.00   #0.02 = 2% of portfolio on any trade
    context.max_positions_per_day                  = 5      #Interacts with max_position_percent
    context.max_absolute_position_shares           = 200000 #Never order more than this many shares per stock
    context.max_percent_of_average_10tick_volume   = 0.10   #0.1 means no more shares than 10% of avg being traded

    context.desired_leverage = 0.98          #Change up or down as desired
    context.leverage_reduction = 0.95        #When overlevered tries to reduce number of trades
    context.number_securities_to_reduce = 3  #maximum number of securities to delever. Must be integer.    
    
    #strategy modifiers
    context.sell_at_open             = False       #Reverse strategy    
                
        
    ################################################################################
    #Should not have to change anything below here except perhaps close_positions time
    schedule_function(do_at_open, date_rules.every_day(), time_rules.market_open())

    #Exit positions schedule
    if context.close_positions_by_trailing_stop:
        schedule_function(close_positions_by_trailing_stop, date_rules.every_day(), time_rules.market_open(hours=2, minutes=0))
    else:
        schedule_function(close_positions, date_rules.every_day(), time_rules.market_open(hours=2, minutes=0))

    #Close at end of day no matter what
    schedule_function(close_positions, date_rules.every_day(), time_rules.market_close(minutes=30))

    
    # if get_environment('arena') == 'backtest':    #Called every minute in live trading
    #     schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close(minutes=1))
        
    context.max_leverage = [0]    #for plotting

    #Wouldn't be needed with GTC orders
    context.exit_order_list = {}            #Equity():info optional. Make sure position is exited
    context.failed_order_list = {}          #Equity():portfolio_percent. To replace orders
    context.stop_list = {}                  #Equity():stop. ASSUMES single order per equity
    context.trailing_stop_list = {}         #Equity():stop. ASSUMES single order per equity
    context.pnl_list = {}                   #Equity(): pnl
    context.profitable_list = {}            #Equity(): any info

    context.PANIC = False
    context.portfolio_value_at_open = None
    context.time_at_open = None
        
    attach_pipeline(make_pipeline(context), 'my_pipeline')
    
    if context.use_trade_like_a_warrior_values:
        init_warrior_values(context)

    return


 
def before_trading_start(context, data):
    """
    Called every day before market open.
    Any positions not exited the previous day are added to exit list
    New pipeline stocks are retrieved
    """
    #If any positions didn't exit the previous day, add them to the exit list
    for pos in context.portfolio.positions:
        context.exit_order_list[pos] = 'previous day order not exited'
        
    context.portfolio_value_at_open = context.portfolio.portfolio_value
    context.PANIC = False
    context.time_at_open = None
    context.profitable_list = {}    #Reset at beginning of each day
    
    context.output = pipeline_output('my_pipeline').dropna()
  
    log.info('Number of stocks from pipeline= ' + str(len(context.output.index)))
    return     

def do_at_open(context,data):
    '''
    Called at start of trading every day
    '''
    context.time_at_open = get_datetime('US/Eastern')
    return

def handle_data(context,data):
    """
    Called first before any function scheduled at Open
    Called every minute in order of importance of actions
    Checks: exit orders, leverage, failed orders, stops
    """
    #PANIC - close everything and return
    if daily_portfolio_loss_exceeded(context, data) or context.PANIC: 
        close_all_positions_and_pending(context, data) #Divest all stocks
    
    #Check lists and handle
    if len(context.exit_order_list) != 0: handle_failed_exit_orders(context, data)
    if context.account.leverage > context.desired_leverage: handle_leverage_too_high(context, data)
    if len(context.failed_order_list) != 0: handle_failed_orders(context, data)
    
    if context.PANIC: return    #No more new position 
    
    #Check profit target
    if len(context.portfolio.positions) != 0: check_profit_targets(context, data)        
    
    #Check stops
    if len(context.stop_list) != 0: handle_fixed_stops(context, data)                #Fixed stops
    if len(context.trailing_stop_list) != 0: handle_trailing_stops(context, data)    #Trailing stops

    #Look for gap and go stocks
    if context.time_at_open == None: context.time_at_open = get_datetime('US/Eastern')
    if context.use_gap_up_at_open_strategy and \
                not context.PANIC and \
                get_datetime('US/Eastern') < context.time_at_open + datetime.timedelta(minutes=context.gap_trading_time):
        open_gap_up_positions(context, data)


    my_record_vars(context, data)
    return

def open_gap_up_positions(context, data):
    '''
    At market open get stocks that gapped up more than a user defined min/max
    '''
    if len( context.output.index ) == 0: return
    
    #Find gap up stocks
    current_data = data.current(context.output.index.tolist(), fields=['price', 'volume'])
    context.output['open_price']  = current_data['price']
    context.output['open_volume'] = current_data['volume']
    context.output['avg_volume'] = data.history(context.output.index.tolist(), fields="volume", bar_count=10, frequency="1m").mean(skipna=True)
 
    #gap is relative to previous bar or yesterday's closing price
    if context.gap_to_previous_bar:
        if get_datetime('US/Eastern') > context.time_at_open:
            #Transpose to make the columns the index
            prev_tick_close = data.history(context.output.index.tolist(), fields='close', bar_count=2, frequency='1m').T    
            prev_tick_close.columns = ['prev_close', 'close']
            context.output['close'] = prev_tick_close['prev_close']
            
    #Trim stocks outside of gap up range and too low in volume
    #current tick price gapping above yesterday's close
    gap_stocks = context.output[ context.output['open_price']/context.output['close'] - 1.0 >=  context.min_gap_up_percent ]    
    gap_stocks = gap_stocks[ gap_stocks['open_price']/gap_stocks['close']             - 1.0 <=  context.max_gap_up_percent ]
    
    #stocks with high volume
    gap_stocks = gap_stocks[ gap_stocks['open_volume']/gap_stocks['avg_volume'] >= context.volume_multiplier ]
    
    #just log first time through
    if get_datetime('US/Eastern') <= context.time_at_open + datetime.timedelta(minutes=context.gap_trading_time):
        log.info('Number of gap up buy at open stocks= ' + str(len(gap_stocks.index)))
        if len(gap_stocks.index) != 0: log.info(gap_stocks.head())
            
    #Place trades
    if len(gap_stocks) > 0: open_positions(context, data, gap_stocks, 'open_gap_up_positions(): ')
    return
    

def open_positions(context, data, stocks, info=''):
    '''
    Determine stocks to place orders for
    Place orders for them
    '''
    if len(context.portfolio.positions) >= int(context.max_positions_per_day): return
        
    for stock in stocks.index:
        if not in_portfolio(context, data, stock) and allow_trade(context, data, stock):
            shares_to_purchase = size_position(context, data, stock, stocks)
            order_id = order(stock, shares_to_purchase)    
            check_order(context, data, order_id, stock, shares_to_purchase, info + 'open entry')
            #Trailing stops
            if ord and context.use_trailing_stop:
                stop_value = data.current(stock, 'price') *(1.0 - context.trailing_stop_pct)
                if context.sell_at_open: data.current(stock, 'price') *(1.0 + context.trailing_stop_pct)
                context.trailing_stop_list[stock] = stop_value
            #Fixed stops
            if ord and context.use_fixed_stop:
                #Long
                if context.set_fixed_stop_at_buy_price: 
                    stop_value =  data.current(stock, 'price')    #Really should wait for order to fill and adjust stop
                else: 
                    stop_value = data.current(stock, 'low')
                #Short
                if context.sell_at_open and not context.set_fixed_stop_at_buy_price: 
                    stop_value = data.current(stock, 'high')
                
                context.stop_list[stock] = stop_value
    return

def size_position(context, data, stock, stocks):
    stocks_in_play = len( stocks.index ) + len(context.portfolio.positions) + len( get_open_orders() )
    if stocks_in_play == 0: return 0
    if context.account.leverage >= context.desired_leverage: return 0
    
    #limit shares based on leverage
    current_price = data.current(stock, 'price')
    shares_to_purchase = context.desired_leverage * context.portfolio.portfolio_value * context.max_position_percent /  \
                        ( current_price * stocks_in_play)
    
    #limit shares by volume. (don't move the market)
    max_shares_by_volume = context.max_percent_of_average_10tick_volume * context.output['avg_volume'][stock]
    if shares_to_purchase > max_shares_by_volume:
        shares_to_purchase = max_shares_by_volume        
    
    #limit shares by absolute number
    if shares_to_purchase > context.max_absolute_position_shares:
        shares_to_purchase = context.max_absolute_position_shares
    
    #Reverse value if shorting
    if context.sell_at_open: shares_to_purchase = - shares_to_purchase
    return shares_to_purchase

def check_profit_targets(context, data):
    '''
    exit part of position if profit target achieved
    '''
    if not context.sell_part_at_profit_target: return
    
    #Check profit target, if reached, add trailing stop
    for pos in context.portfolio.positions:
        if pos not in context.profitable_list and \
           pos not in context.exit_order_list and \
           pos not in context.failed_order_list:
            cost = context.portfolio.positions[pos].cost_basis
            #long
            if (data.current(pos, 'price') - cost) / cost >= context.target_profit_per_trade and \
                context.portfolio.positions[pos].amount > 0:
                #Sell half
                shares_to_liquidate = int( context.portfolio.positions[pos].amount * context.percent_to_sell_at_profit_target )
                order_id = order(pos, -1*shares_to_liquidate)    
                check_order(context, data, order_id, pos, shares_to_liquidate, 'closing half for a profit')
                log.info('************************ Profit target met *******************')
                #P&L
                context.profitable_list[pos] = '  '
                portfolio_info = context.portfolio.positions[pos]
                pnl = shares_to_liquidate * (portfolio_info.last_sale_price - portfolio_info.cost_basis)
                if pos in context.pnl_list: pnl = context.pnl_list[pos] + pnl
                context.pnl_list[pos] = pnl
                #Trailing Stop
                if context.close_positions_by_trailing_stop and pos not in context.trailing_stop_list:
                    context.trailing_stop_list[pos] = data.current(pos, 'price')
                    if pos in context.stop_list: del context.stop_list[pos]
            #short
            if (data.current(pos, 'price') - cost) / cost <= context.target_profit_per_trade and \
                context.portfolio.positions[pos].amount < 0:    #short
                #buy back half
                shares_to_liquidate = int( context.portfolio.positions[pos].amount * context.percent_to_sell_at_profit_target )
                order_id = order(pos, -1*shares_to_liquidate)    
                check_order(context, data, order_id, pos, shares_to_liquidate, 'closing half for a profit')
                log.info('************************ Profit target met *******************')
                #P&L
                context.profitable_list[pos] = '  '
                portfolio_info = context.portfolio.positions[pos]
                pnl = shares_to_liquidate * (portfolio_info.last_sale_price - portfolio_info.cost_basis)
                if pos in context.pnl_list: pnl = context.pnl_list[pos] + pnl
                context.pnl_list[pos] = pnl

                #Trailing stops
                if context.close_positions_by_trailing_stop and pos not in context.trailing_stop_list:
                    context.trailing_stop_list[pos] = data.current(pos, 'price')
                    if pos in context.stop_list: del context.stop_list[pos]
    return



def daily_portfolio_loss_exceeded(context, data):
    if context.portfolio.portfolio_value <= context.portfolio_value_at_open * (1.0 - context.max_daily_portfolio_loss):
        context.PANIC = True
        return True
    return False

def handle_failed_exit_orders(context, data):
    ''' 
    Make sure a stock in an exit list goes to zero shares held
    Sometimes can't completely exit a position in 1 trading day
    This removes residual shares across days
    '''
    for security in context.exit_order_list.copy():    #Copy b/c can't iterate through a dictionary and delete keys 
        #If security is in other lists, delete them
        if security in context.trailing_stop_list: del context.trailing_stop_list[security]
        if security in context.stop_list: del context.stop_list[security]
        if security in context.failed_order_list: del context.failed_order_list[security]
            
        #no positions in portfolio so remove it from exit list
        if context.portfolio.positions[security].amount == 0:    
            if security in context.exit_order_list: del context.exit_order_list[security]
               
        #Have position and need to get rid of them
        elif data.can_trade(security) and not get_open_orders(security):
            ord = order_target(security, 0.0)
            check_exit_order(context, data, ord, security, 'handle_data(): Handle Exit orders that failed')
    return

def handle_leverage_too_high(context, data):
    '''
    Rank by worst performers
    Divest user defined number of ranked stocks
    '''
    # - Find worst performers and start divesting
    #
    cancel_all_orders(context, data)
    #Find profit of all positions
    portfolio_positions = {}
    for pos in context.portfolio.positions:
        if pos not in context.exit_order_list:    #pos is an equity_object
            position_object = context.portfolio.positions[pos]
            portfolio_positions[pos] = abs( (position_object.last_sale_price - position_object.cost_basis)/position_object.amount )
            
    #rank positions
    ranks = {}
    if len(portfolio_positions) <= context.number_securities_to_reduce:
        ranks = portfolio_positions
    else:
        ranks = OrderedDict(sorted(portfolio_positions.items(), key=lambda x: x[1], reverse=False)[:context.number_securities_to_reduce]) 
        # for k,v in ranks.items(): 
        #     print(k)
        #     print(v)
        
    #Reduce positions
    for pos in ranks:
        portfolio_percent = context.leverage_reduction*context.desired_leverage/len(ranks)    #reduce out additional shares
        if context.portfolio.positions[pos].amount < 0: portfolio_percent = -1.0 * portfolio_percent    #short            
        if allow_trade(context, data, pos):                
            order_id = order_target_percent(pos, portfolio_percent)
            check_order(context, data, order_id, pos, portfolio_percent, "handle_data() - leverage too high")
    return    

def handle_failed_orders(context, data):
    '''
    process failed order list
    try to re-place failed orders
    if successful remove stock from failed order list
    '''
    for security in context.failed_order_list.copy():    #Copy b/c can't iterate through a dictionary and delete keys
        if data.can_trade(security) and not get_open_orders(security):
            amount = context.failed_order_list[security]
            order_id = order_target(security, amount)   
            check_order(context, data, order_id, security, amount, "handle_data() - handle failed orders")
            if ord: 
                del context.failed_order_list[security]
                if context.use_trailing_stop: context.trailing_stop_list[security] = data.current(security, 'price')*(1.0 - context.trailing_stop_pct)
    return

def close_positions(context, data):
    '''
    Set trailing stop
    Allow winners to run and fails to close out naturally
    '''
    cancel_all_orders(context, data)
    for pos in context.portfolio.positions:    #pos is an equity_object       
        if data.can_trade(pos) and pos not in context.exit_order_list:
            ord = order_target_percent(pos, 0.0)
            check_exit_order(context, data, ord, pos, 'close_positions()')
    return

def close_positions_by_trailing_stop(context, data):
    cancel_all_orders(context, data)
    for pos in context.portfolio.positions:
        if pos not in context.trailing_stop_list:
            context.trailing_stop_list[pos] = data.current(pos, 'price')
    return

def close_all_positions_and_pending(context, data):
    '''
    cancel any open orders
    delete every possible order that might be placed
    close open positions
    '''
    cancel_all_orders(context, data)
    #clear out queues
    for stock in context.failed_order_list.copy(): 
        if stock not in context.exit_order_list: 
            context.exit_order_list[stock] = 'close_all_positions_and_pending - failed order list'
        del context.failed_order_list[stock]
            
    for stock in context.trailing_stop_list.copy(): 
        if stock not in context.exit_order_list: 
            context.exit_order_list[stock] = 'close_all_positions_and_pending - stop list'
        del context.trailing_stop_list[stock]

    for stock in context.stop_list.copy(): 
        if stock not in context.exit_order_list: 
            context.exit_order_list[stock] = 'close_all_positions_and_pending - stop list'
        del context.stop_list[stock]

    #close open positions
    close_positions(context, data)
    return

def handle_trailing_stops(context, data):
    '''
    update trailing stop
    if trailing stop hit exit position
    '''
    if len(context.trailing_stop_list) == 0: return
        
    #Update trailing stop price
    for stock in context.trailing_stop_list.copy():
        price = data.current(stock, 'price')
    
        #Long
        if context.portfolio.positions[stock].amount > 0:
            trailing_stop_pct = 1.0 - context.trailing_stop_pct
            context.trailing_stop_list[stock] = max(context.trailing_stop_list[stock], trailing_stop_pct * price)
            if price < context.trailing_stop_list[stock]:
                #Cancel any open orders
                cancel_all_orders(context, data, stock)
                #Sell all positions
                #log.info('Stop hit. Exiting Long Position. stop= ' + str(context.trailing_stop_list[stock]) + '  price= ' + str(price))
                ord = order_target(stock, 0.0)
                check_exit_order(context, data, ord, stock, 'handle_trailing_stops(): Long stop hit. Exiting Position. stop= ' + \
                         str(context.trailing_stop_list[stock]) + '  price= ' + str(price))
                if stock in context.stop_list: del context.stop_list[stock]
                if stock in context.trailing_stop_list: del context.trailing_stop_list[stock]
                if stock in context.failed_order_list:  del context.failed_order_list[stock]

        #Short
        elif context.portfolio.positions[stock].amount < 0:
            trailing_stop_pct = 1.0 + context.trailing_stop_pct
            context.trailing_stop_list[stock] = min(context.trailing_stop_list[stock], trailing_stop_pct * price)
            if price > context.trailing_stop_list[stock]:
                #Cancel any open orders
                cancel_all_orders(context, data, stock)
                #Buy back all short position                 
                #log.info('Stop hit. Exiting Short Position. stop= ' + str(context.trailing_stop_list[stock]) + '  price= ' + str(price))
                ord = order_target(stock, 0.0)
                check_exit_order(context, data, ord, stock, 'handle_trailing_stops(): Short stop hit. Exiting Position. stop= ' + \
                         str(context.trailing_stop_list[stock]) + '  price= ' + str(price))
                if stock in context.stop_list: del context.stop_list[stock]
                if stock in context.trailing_stop_list: del context.trailing_stop_list[stock]
                if stock in context.failed_order_list:  del context.failed_order_list[stock]
    return

def handle_fixed_stops(context, data):
    '''
    Long: if price <  stop exit position
    Short: if price > stop exit position
    '''
    if len(context.stop_list) == 0: return
        
    for stock in context.stop_list.copy():
        price = data.current(stock, 'price')
    
        #Long
        if context.portfolio.positions[stock].amount > 0:
            if price < context.stop_list[stock]:
                #Cancel any open orders
                cancel_all_orders(context, data, stock)
                #Sell all positions
                #log.info('Fixed Stop hit. Exiting Long Position. stop= ' + str(context.stop_list[stock]) + '  price= ' + str(price))
                ord = order_target(stock, 0.0)
                check_exit_order(context, data, ord, stock, 'handle_fixed_stops(): Long stop hit. Exiting Position. stop= ' + \
                         str(context.stop_list[stock]) + '  price= ' + str(price))
                if stock in context.stop_list: del context.stop_list[stock]
                if stock in context.trailing_stop_list: del context.trailing_stop_list[stock]
                if stock in context.failed_order_list:  del context.failed_order_list[stock]

        #Short
        elif context.portfolio.positions[stock].amount < 0:
            if price > context.stop_list[stock]:
                #Cancel any open orders
                cancel_all_orders(context, data, stock)
                #Buy back all short position                 
                #log.info('Stop hit. Exiting Short Position. stop= ' + str(context.stop_list[stock]) + '  price= ' + str(price))
                ord = order_target(stock, 0.0)
                check_exit_order(context, data, ord, stock, 'handle_fixed_stops(): Short stop hit. Exiting Position. stop= ' + \
                         str(context.stop_list[stock]) + '  price= ' + str(price))
                if stock in context.stop_list: del context.stop_list[stock]
                if stock in context.trailing_stop_list: del context.trailing_stop_list[stock]
                if stock in context.failed_order_list:  del context.failed_order_list[stock]
    return

def cancel_all_orders(context, data, stock=None):
    '''
    If stock given cancels open orders for that Equity Object
    Otherwise closes all orders for every equity object
    '''
    if stock == None:
        orders = get_open_orders()
    else:
        orders = get_open_orders(stock)

    if len( orders ) > 0:
        for order in orders:
            cancel_order(order)
    return


def my_record_vars(context, data):
    """
    Plot variables at the end of each day.
    """
    record(leverage=context.account.leverage)
    leverage = context.account.leverage
    for num in context.max_leverage:
        if leverage > num:
            context.max_leverage.remove(num)
            context.max_leverage.append(leverage)

    record(Max_Leverage = context.max_leverage[-1], leverage=leverage)

    return


def allow_trade(context, data, security):
    '''
    Logic for placing trades
    - can trade, no open orders, not in exit list, not a failed order
    '''
    if data.can_trade(security) and \
       not get_open_orders(security) and \
       security not in context.failed_order_list and \
       security not in context.exit_order_list: return True
    return False

def check_order(context, data, order_id, security, amount, info):
    '''
    Check to see if the order was placed
    If not add it to the failed order list
    TODO: Handle partial fills
    '''
    if order_id:
        log_order(context, data, order_id, security, info)
    else:
        if security not in context.failed_order_list: context.failed_order_list[security] = amount    
    return

def check_exit_order(context, data, order, security, info):
    '''
    Check if exit order succeeded
    '''
    if security not in context.exit_order_list: context.exit_order_list[security] = info
    if ord: 
        log_order(context, data, order, security, info)
        profit_and_loss(context, data)
    return          

def in_portfolio(context, data, security):
    '''
    Determine if a specific security is in the portfolio
    '''
    for pos in context.portfolio.positions:
        if pos.sid == security.sid : return True
    return False


def log_order(context, data, order_id, security, info):
    '''
    Log an order to the console
    info: Any string to print out
    '''
    if context.PANIC:
        log.info("************** PANIC: Portfolio daily loss exceeded *********")
    
    if order_id:
        if get_order(order_id).amount > 0:
            log.info(str(info) + '  ' + '++++ placing LONG order for ' + str( get_order(order_id).amount) + '  ' + str(security))
        else:
            log.info(str(info) + '  ' + '---- placing SHORT Order for ' + str( get_order(order_id).amount) + '  ' + str(security))  
    else:    #empty order 
         log.info(str(info) + '  ' +  '!!!!! WARNING: Order FAILED for ' + str(security))
            
    return

def profit_and_loss(context, data):
    '''
    Log/print profit and loss of all positions
    FIXME: Not fully accurate but close enough
    '''
    positions = context.portfolio.positions
    if len(positions) == 0: return
    
    for pos in positions:
        if pos in context.exit_order_list:
            pnl = positions[pos].amount * (positions[pos].last_sale_price - positions[pos].cost_basis)
            if pos in context.pnl_list: pnl = context.pnl_list[pos] + pnl
            context.pnl_list[pos] = pnl

    for stock in context.pnl_list:
        log.info('PnL ' + stock.symbol + ':  ' + str( round(context.pnl_list[stock], 2) ))
    return

class FloatShares(CustomFactor):
    ''' 
    FIXME: This is an estimate based on available data
    Float = shares available for trading =
            total_outstanding_shares - restricted_stock - closely_held_shares    
    '''
    # Pre-declare inputs and window_length
    inputs = [morningstar.valuation.shares_outstanding, 
              morningstar.balance_sheet.preferred_shares_number,
              morningstar.balance_sheet.restricted_common_stock,
              morningstar.balance_sheet.treasury_shares_number]
    window_length = 1

    # Compute float shares
    def compute(self, today, assets, out, shares_outstanding, pref_shares, restricted_stock, treasury_stock):
        out[:] = np.nan_to_num(shares_outstanding[-1]) - \
                 np.nan_to_num(pref_shares[-1]) - \
                 np.nan_to_num(restricted_stock[-1]) - \
                 np.nan_to_num(treasury_stock[-1])                    
                    
class PreviousMoves(CustomFactor):
    ''' 
    Compute number of times in the past window_length a gap up input_percent occured
    '''
    # Default inputs and window_length
    inputs = [USEquityPricing.open, USEquityPricing.close]
    window_length = 252
    gap_percent = global_min_prev_gap_up_percent + 1.0    #FIXME: Shouldn't have to use global here
                                                          # +1.0 because looks at ratio open/prev_close
    
    # def __init__(self, inputs, window_length, mask, gap_percent, *args, **kwargs):
    #     if 'gap_percent' in kwargs:
    #         PreviousMoves.gap_percent = kwargs.pop('gap_percent')
    #     CustomFactor.__init__(self, inputs=inputs, window_length=window_length, mask=mask)

    # Compute historical gap ups
    def compute(self, today, assets, out, open, close):
        #Values come in as window_length rows x num_stocks in columns
                
        close_shift = np.roll(close, -1, axis=0) #align previous day close to current day open
        ratio = open / close_shift

        #Format data and remove nans
        ratio[ ratio <  PreviousMoves.gap_percent ] = 0.0  #zero out days below gap_percent
        ratio[ ratio >= PreviousMoves.gap_percent ] = 1.0  #Above gap_percent change it to 1
        ratio[ np.isnan(ratio) ] = 0.0    #Set nan entries to 0
        
        num_up_days = ratio.sum(axis=0)    #Sum up number of 1's = gap days in window_length
        out[:] = num_up_days
        

def make_pipeline(context):
    """
    A function to create our dynamic stock selector (pipeline). Documentation on
    pipeline can be found here: https://www.quantopian.com/help#pipeline-title
    """

    #Q1500 limits to 30% per sector so not used as base universe
    """
    9 Filters:
        1. common stock
        2 & 3. not limited partnership - name and database check
        4. database has fundamental data
        5. not over the counter
        6. not when issued
        7. not depository receipts
        8. primary share
        9. high dollar volume
    See: https://www.quantopian.com/tutorials/pipeline#lesson11
    """
    common_stock = morningstar.share_class_reference.security_type.latest.eq('ST00000001')
    not_lp_name = ~morningstar.company_reference.standard_name.latest.matches('.* L[\\. ]?P\.?$')
    not_lp_balance_sheet = morningstar.balance_sheet.limited_partnership.latest.isnull()
    have_data = morningstar.valuation.market_cap.latest.notnull()
    not_otc = ~morningstar.share_class_reference.exchange_id.latest.startswith('OTC')
    not_wi = ~morningstar.share_class_reference.symbol.latest.endswith('.WI')
    not_depository = ~morningstar.share_class_reference.is_depositary_receipt.latest
    primary_share = IsPrimaryShare()
    
    # Combine the above filters.
    tradable_filter = (common_stock & not_lp_name & not_lp_balance_sheet &
                       have_data & not_otc & not_wi & not_depository & primary_share)
    
    high_volume_tradable = (AverageDollarVolume(window_length=21,
                                                mask=tradable_filter)
                            .percentile_between(context.min_avg_dollar_vol, 100))
    

    # Factors and/or Filters
    yesterday_close = USEquityPricing.close.latest
    ema200 = ExponentialWeightedMovingAverage(inputs=[USEquityPricing.close],
                                              window_length=200,
                                              decay_rate=np.exp(np.log(0.5) / 15))
    
    float_shares = FloatShares(mask=high_volume_tradable)
    prev_moves = PreviousMoves(mask=high_volume_tradable,
                               window_length=context.gap_up_day_window)
    
    #Trade like a warrior filter by
    pipe_screen =  high_volume_tradable  \
                  &( yesterday_close > context.min_stock_price ) \
                  &( yesterday_close < context.max_stock_price ) \
                  &( yesterday_close > ema200 ) \
                  &( float_shares > context.float_shares_min ) \
                  &( float_shares < context.float_shares_max ) \
                  &( prev_moves >= context.min_gap_up_days )
        
    pipe = Pipeline(
        screen = pipe_screen,
        columns = {
            'close'            : yesterday_close
        }
    )
    return pipe

def init_warrior_values(context):
    #strategy
    context.use_gap_up_at_open_strategy  = True
    context.gap_trading_time             = 30       #How long to check for gap ups in minutes. (min=1)
    context.gap_to_previous_bar          = True     #False = gap to previous days close
    
    #Stock Selection User Defined Variables
    context.min_stock_price          = 3.00         #min stock trading price. Avoid penny stocks
    context.max_stock_price          = 10.0         #max stock trading price
    context.min_gap_up_percent       = 0.04         #Used to select stocks at open
    context.max_gap_up_percent       = 2.0          #10% gap up = 0.1
    context.float_shares_min         = 10000000.0   #minimum number of float shares 10M
    context.float_shares_max         = 100000000.0  #max number of float shares 100M
    context.min_avg_dollar_vol       = 1            #70 or above is highly liquid
    context.gap_up_day_window        = 63           #252 = 1 year, 63=3 months
    context.min_gap_up_days          = 1.0          #Min number of gap ups in window
    context.volume_multiplier        = 2.0          #volume increase over previous 10 ticks avg volume

    #Account Management User Defined Variables
    context.use_fixed_stop                    = True   #Low of 1 minute bar on opening trade
    context.use_trailing_stop                 = False  #For opening trade only. Interferes with use_fixed_stop
    context.close_positions_by_trailing_stop  = False  #Set a trailing stop to close out the positions
                                                       #Note: all positions still close at end of day
        
    context.sell_part_at_profit_target             = False  #Exit a part of positions
    context.percent_to_sell_at_profit_target       = 0.50   #Sell half = 0.5
    context.target_profit_per_trade                = 0.20   #0.20 = 20%

    context.trailing_stop_pct                      = 0.005  #0.01 = 1% stop.
    context.max_daily_portfolio_loss          = 1.00   #0.02 = 2% loss
    context.max_position_percent              = 1.00   #0.02 = 2% of portfolio on any trade
    context.max_positions_per_day             = 5      #Interacts with max_position_percent
    context.max_absolute_position_shares      = 200000 #Never order more than this many shares per stock
    context.max_percent_of_average_10tick_volume   = 100.0   #0.1 means no more shares than 10% of avg being traded

    context.desired_leverage = 0.98          #Change up or down as desired
    context.leverage_reduction = 0.95        #When overlevered tries to reduce number of trades
    context.number_securities_to_reduce = 3  #maximum number of securities to delever. Must be integer.    
    
    #strategy modifiers
    context.sell_at_open             = False       #Reverse strategy    
    return
There was a runtime error.