Back to Community
Day trading within whole numbers

This month Stocks and Commodities had an article by Ken Calhoun on day trading using whole numbers.

Algorithm

  1. Find stock getting ready to take out their 2-day high
    • just above a whole number
    • priced $15 to $50 (configurable)
    • >15k shares per minute traded (configurable)
  2. Set buy-stop order 0.20 above whole number (configurable)
  3. On entry set stop at whole number
    1. Exit trade on 0.80 gain (configurable)

Extensions

  1. You can set global_tolerance_to_whole_number to allow stocks at 13.98, 14.02, etc.
  2. You can cease entering new positions at end of day (configurable)
  3. You can set the time at end of day to close all positions (configurable)

Comments

Uses pipeline with custom filters and custom factors
Uses order_optimal_portfolio

Clone Algorithm
27
Loading...
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 Within Whole Numbers
by Ken Calhoun

Algorithm:
  1) Find stock getting ready to take out their 2-day high
     * just above a whole number
     * priced $15 to $50 (configurable) 
     * >15k shares per minute traded (configurable)
  2) Set buy-stop order 0.20 above whole number (configurable)
  3) On entry set stop at whole number
  4) Exit trade on 0.80 gain (configurable)
  
Note:
1) Could run a volume check every minute for 15k shares but
   to use it in pipeline we approximate it with daily
   15k*390 minutes = 5.85M shares per day
   
Extensions:
	1) You can set global_tolerance_to_whole_number to allow stocks at 13.98, 14.02, etc.
    2) You can cease entering new positions at end of day (configurable)
    3) You can set the time at end of day to close all positions (configurable)

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

MIT License
https://opensource.org/licenses/MIT 

2017 09 10 Initial version
'''
from quantopian.algorithm import attach_pipeline, pipeline_output, order_optimal_portfolio
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters.morningstar import Q500US, Q1500US
from quantopian.pipeline import CustomFactor, CustomFilter
from collections import OrderedDict
import quantopian.optimize as opt

import numpy as np
import math

global_tolerance_to_whole_number = 0.05		#How close to whole number does close need to be
											#Must be less than open_position_delta
 
def initialize(context):
    """
    Called once at the start of the algorithm.
    """         
    #Stock Selection User Defined Variables
    context.open_position_delta  = 0.20      #0.2 would be price plus 20 cents to enter position
    context.close_position_delta = 0.80      #0.8 take profit at 80 cents
    context.desired_leverage     = 0.98      #Change up or down as desired

    context.min_stock_price      = 15.00     #min stock trading price. 
    context.max_stock_price      = 50.0      #max stock trading price
    context.min_volume_per_day	 = 5850000

    
    #Stop new positions near end of day
    schedule_function(cease_new_positions, date_rules.every_day(), time_rules.market_close(minutes=120))
    #Close at end of day no matter what
    schedule_function(close_all_positions, date_rules.every_day(), time_rules.market_close(minutes=30))
        
    ################################################################################

    context.max_leverage = [0]    	#for plotting
    context.portfolio_weights = {}	#Equity():weight
    context.stop_list = {}			#Equity():Stop class. All the securities and their stops
        
    attach_pipeline(make_pipeline(context), 'my_pipeline') 
    
    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
    """    
    context.end_of_day = False
    context.allow_opening_positions = True
    
    #If any positions didn't exit the previous day set them up to exit
    context.portfolio_weights = {}
    for pos in context.portfolio.positions:
        context.portfolio_weights[pos] = 0.0
        log.info("Did NOT EXIT at close. Stock= " + str(pos))
    
    context.output = pipeline_output('my_pipeline').dropna()

    #Print pipeline output 
    log.info('Number of stocks from pipeline= ' + str(len(context.output.index)))
    if len(context.output.index):
    	log.info(' \n'.join( sorted([stock_.symbol + ' prev close=  ' \
            + str(context.output['close'][stock_]) \
            +'  2day high= ' + str(context.output['2d_high'][stock_]) \
            for stock_ in context.output.index] )))    
    
    #Set stops for stocks identified in pipeline
    context.stop_list = {}    #Reset each day
    for stock in context.output.index:
        close_loss_stop = math.floor( context.output['2d_high'][stock] + global_tolerance_to_whole_number )
        close_profit_stop = close_loss_stop + context.close_position_delta
        open_stop = close_loss_stop + context.open_position_delta
        context.stop_list[stock] = Stop(context, data, stock, open_stop, close_profit_stop, close_loss_stop)
        
    return     

def handle_data(context,data):
    """
    Called first before any function scheduled at Open
    Called every minute in order of importance of actions
    """
    
    #Check stops and optimize portfolio
    # trade_fixed_stops() returns true if portfolio weights are updated
    if context.allow_opening_positions and trade_fixed_stops(context, data):
        optimize_portfolio(context, data)
        
    my_record_vars(context, data)
    return

    

def optimize_portfolio(context, data):
    '''
    Update positions based on weights
    '''
    if len(context.portfolio_weights) == 0: return
    objective = opt.TargetWeights(context.portfolio_weights)
    constraints=[opt.MaxGrossExposure(context.desired_leverage)]
    order_optimal_portfolio(objective, constraints) 
    print_orders(context, data)
    return

def cease_new_positions(context, data):
    context.allow_opening_positions = False
    return
    

def close_all_positions(context, data):
    '''
    Set target weights and call optimizer
    '''
    cancel_all_orders(context, data)
    if len(context.portfolio.positions) == 0: return
    
    context.portfolio_weights = {}
    for pos in context.portfolio.positions:
        context.portfolio_weights[pos] = 0.0
        if pos in context.stop_list: del context.stop_list[pos]
            
    objective = opt.TargetWeights(context.portfolio_weights)
    constraints=[opt.MaxGrossExposure(0.0)]
    order_optimal_portfolio(objective, constraints)
    
    print_orders(context, data)
        
    return


def trade_fixed_stops(context, data):
    '''
    Check if any stops have triggered and add them to a list
    Update weights and return True if portfolio needs rebalancing
    '''
    if len(context.stop_list) == 0: return
    
    openList = []
    closeList = []
    
    #keep any positions with 0 weights in the closeList
    for pos in context.portfolio.positions:
        if pos in context.portfolio_weights and context.portfolio_weights[pos] == 0.0:
            closeList.append(pos)
            
    for stock in context.stop_list:
        #New positions
        if allow_trade(context, data, stock) and not in_portfolio(context, data, stock) and context.stop_list[stock].open_stop_hit(): 
            openList.append(stock)
            
        #Closing positions
        if in_portfolio(context, data, stock) and context.stop_list[stock].close_stop_hit(): 
            closeList.append(stock)
        
    #Determine number of stocks to set portfolio weights
    if len(openList) or len(closeList) :	#have adjustments to make
        number_of_stocks = len(openList) + len(context.portfolio.positions) - len(closeList)

        #It is possible that the net change in stocks is 0
        if number_of_stocks == 0: number_of_stocks = len(context.portfolio.positions)            
        if number_of_stocks == 0: return False
                
        log.info('TOTAL NUMBER OF STOCKS FOR WEIGHTS= ' + str(number_of_stocks))
        
        #Make changes
        for stock in context.portfolio.positions:
            context.portfolio_weights[stock] = 1.0 / number_of_stocks
        
        for stock in openList:
            context.portfolio_weights[stock] = 1.0 / number_of_stocks
            
        for stock in closeList:
            context.portfolio_weights[stock] = 0.0
            if stock in context.stop_list: del context.stop_list[stock]
            
        return True	#adjustments were made
    return False
        

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 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):
        return True
    else:
        return False

def print_orders(context, data):
    open_orders = get_open_orders()
    if len(open_orders) == 0: 
        log.info('check_orders(): no active orders')
    
    else:
        for stock, orders in open_orders.iteritems():
            for ord in orders:
                if context.portfolio.positions[stock].amount == 0:
                	log.info('OPENING order for ' + str( ord.amount) + ' for ' + str(stock))
            	else:
            		log.info('CLOSING order for ' + str( ord.amount) + ' for ' + str(stock))
    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 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

class Stop(object):
    '''
    Class to handle fixed stops
    
    open_stop is a stop to buy
    close_profit_stop is a stop for when profit target is met
    close_loss_stop is a stop for max loss on a position
    '''
    def __init__(self, context, data, asset, open_stop=None, close_profit_stop=None, close_loss_stop=None):
        self.context = context
        self.data = data
        self.asset = asset
        self.open_stop = open_stop
        self.close_profit_stop = close_profit_stop
        self.close_loss_stop = close_loss_stop

    def close_stop_hit(self):
        price = self.data.current(self.asset, 'price')
    
        #Long
        if self.context.portfolio.positions[self.asset].amount > 0:
            #Loss
            if price <= self.close_loss_stop:
                log.info(str(self.asset) + '  close_loss_stop LONG hit at stop= ' \
                         + str(self.close_loss_stop) + '  price= ' + str(price))
                return True
            #Profit
            if price >= self.close_profit_stop:
                log.info(str(self.asset) +'  close_profit_stop LONG hit at stop=' \
                         +  str(self.close_profit_stop) + '  price= ' + str(price)) 
                return True
        #Short
        elif self.context.portfolio.positions[self.asset].amount < 0:
            #Loss
            if price >= self.close_loss_stop:
                log.info(str(self.asset) + '  close_loss_stop SHORT hit at stop= ' \
                         + str(self.close_loss_stop) + '  price= ' + str(price))
                return True
            #Profit
            if price <= self.close_profit_stop:
                log.info(str(self.asset) +'  close_profit_stop SHORT hit at stop=' \
                         +  str(self.close_profit_stop) + '  price= ' + str(price)) 
                return True        
        return False

    def open_stop_hit(self, short=False):
        price = self.data.current(self.asset, 'price')
        if short and price >= self.open_stop: return False
        if not short and price <= self.open_stop: return False
        log.info(str(self.asset) + ' open_stop hit at stop= ' + str(self.open_stop) \
                 +  '  price= ' + str(price))
        return True


class under_two_day_high(CustomFilter):
    '''
    Check if a stock is nearing its 2-day high
    close[-2] > close[-1]
    '''
    inputs = [USEquityPricing.close, USEquityPricing.high]
    window_length = 2

    def compute(self, today, assets, out, close, high):
        out[:] =  high[-2] > close[-1]
        
class two_day_high(CustomFactor):
    '''
    2-day high = high[-2]
    '''
    inputs = [USEquityPricing.high]
    window_length = 2

    def compute(self, today, assets, out, high):
        out[:] =  high[-2]
        
class whole_number(CustomFilter):
    '''
    Previous 2 day high should be a whole number
    global: tolerance within variable cents of whole number
    '''
    inputs = [USEquityPricing.high]
    window_length = 2
    
    def compute(self, today, assets, out, high ):
        frac, whole = np.modf(high)
        out[:] = np.logical_or(frac[-2] <= global_tolerance_to_whole_number, (1.0 - frac[-2]) <= global_tolerance_to_whole_number)

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
    """

    # Factors and/or Filters
    base_universe = Q1500US()

    yesterday_close = USEquityPricing.close.latest
    
    pipe_screen =  base_universe \
                  &( yesterday_close > context.min_stock_price ) \
                  &( yesterday_close < context.max_stock_price ) \
            	  &( USEquityPricing.volume.latest > context.min_volume_per_day) \
                  &( under_two_day_high(mask=base_universe ) ) \
                  &( whole_number(mask=base_universe) )
        
    pipe = Pipeline(
        screen = pipe_screen,
        columns = {
            'close'            : yesterday_close,
            '2d_high'		   : two_day_high(mask=base_universe)
        }
    )
    return pipe

There was a runtime error.