Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
Code to capture profit on price jump

Often when a stock spikes upward it quickly drops back down again as people and programs take profit. This is an effort to be one of those programs.

Looking for a price jump over 3.0x, adding to watch_list, and processing anything on that list every 25 minutes, looking for whether the slope of minute prices is downward and close the position if so, taking that profit hopefully before the price drops too far. Upward movement is not hampered.

A comparison of before and after this code. This is a Robinhood algorithm of mine based on an early Charles Witt example and changed quite a bit.

I haven't tried this with other code, could just be overfit. Would like this to evolve to be more dynamic. Ideas.
You can just drop all of the code below at the end of initialize. There's some useful information in the logging window that will tell you if it catches anything. Would be great to hear if it works for you.

The relevant excerpts:

def initialize(context):

    context.watch_list = []  
    for i in range(25, 385, 25):   # (start, end, every i minutes)  
        schedule_function(watch, date_rules.every_day(), time_rules.market_open(minutes=i))

def watch(context, data):          # On schedule, process any sids in watch_list  
    prices = data.history(context.watch_list, 'close', 2, '1d') # Daily, couple days  
    for s in context.watch_list:  
        if s not in context.portfolio.positions:    # If sold elsewhere, drop it from watch  
            context.watch_list.remove(s)  
            continue

        # Slope of prices, minute  
        slp = slope(data.history(s, 'close', 60, '1m').dropna())    # Minutes, note dropna(), important  
        log.info('{} at {}  prv {}  {}%  slp {}'.format(s.symbol, data.current(s, 'price'), '%.2f' % prices[s][0], '%.0f' % (100 * data.current(s, 'price') / prices[s][0]), '%4f' % slp ))

        if slp < 0:     # Close if downward  
            log.info('sell {} at {}  prv {}  {}%'.format(s.symbol, data.current(s, 'price'), '%.2f' % prices[s][0], '%.0f' % (100 * data.current(s, 'price') / prices[s][0]) ))  
            order_target(s, 0)    # Close/sell  
            context.watch_list.remove(s)

    # Any new for price jump watch_list  
    prices = data.history(context.portfolio.positions.keys(), 'close', 2, '1d') # Couple days  
    for s in context.portfolio.positions:    # Add to watch_list with price jump  
        if not data.can_trade(s):   continue  
        if data.current(s, 'price') > 3.0 * prices[s][0]:   # If price has jumped upward  
            if s in context.watch_list: continue  
            log.info('{} to watch_list at {}  prv {}  {}%'.format(s.symbol, data.current(s, 'price'), '%.2f' % prices[s][0], '%.0f' % (100 * data.current(s, 'price') / prices[s][0]) ))  
            context.watch_list.append(s)

import statsmodels.api as sm  
def slope(in_list):     # Return slope of regression line. [Make sure this list contains no nans]  
    return sm.OLS(in_list, sm.add_constant(range(-len(in_list) + 1, 1))).fit().params[-1]  # slope

As always, use PvR to see clearly.

9 responses

Hello Quants,
I was wondering i any of you have had experience trading shares under $10. Some of these can tend to trade on low volume. I'm interested on hearing how others deal with low volume stocks, what slippage they tend to use when doing this/what daily volume they are comfortable with for a given $ traded amount to trade. I guess this comes back to the old "what slippage do you find works best in what situations" question. I have had great backtests in the past where returns were severely reduced due to slippage. It is amazing how much returns can be compromised through a small but steady loss of 1-3 cents per share per trade.

Blue - As an idea you may want to also try shorting into that spike and taking profits after the quick fall. Might be something to gain there. I am currently working the bugs out of an algo similar to this, but it is reactive to large spikes, not necessarily over 3x, just large spikes in general.

As usual, all comments and thoughts from others are welcome.
Have a good day.

@Blue, can you please provide more information on how to use your code? I tried to initialize your watch list with pipeline output, but that didnĀ“t work. I then realized the for loops check portfolio positions...

Thanks for sharing.

Blue,

Many thanks for sharing what seems to be an amazingly profiable idea and algorithm. Allow me just on question, please: where does the actual buying take place in the code snippet that you quote?

@Roberto Sure, just copy everything except the def initialize line above and drop all of that at the end of your own initialize function. So it's real easy.

@TV It is only the part that does sell after a price jump with succeeding dip.

            order_target(s, 0)    # Close/sell  

@Blue Can you explain this condition? What is that "3"?
if data.current(s, 'price') > 3.0 * prices[s][0]:

If the current price is 3 times higher than yesterday's close (set by the 2, '1d'). So that's plenty adjustable. In my own code lately I think I have that set to 2.5. Couple of things one can do, this logs high, low, avg for any value/variable you'd like to watch. And/or click in margin to set a breakpoint and examine variable values, also do math, even run functions from there et al. For example on that line in the debugger console window that appears type prices and hit [enter]. Then prices[s].

Isn't that looking for stock that spiked 300% over night ? I feel this is unrealistic, not sure if I am missing anything

I knew somebody would say that. Unfortunately didn't have a plan for dealing with it but thanks to a triple espresso just now lol here's an answer.

Make it automatic based on the particular type of stocks one is dealing with. This might be a typical volatility class used with pipeline. Others.

class Volatility(CustomFactor):  
    inputs = [USEquityPricing.close]  
    window_length = 20  
    def compute(self, today, assets, out, close):  
        # [0:-1] is needed to remove last close since diff is one element shorter  
        daily_returns = np.diff(close, axis = 0) / close[0:-1]  
        out[:] = daily_returns.std(axis = 0) * math.sqrt(252)  

It should be possible to use those values and decide how much of an outlier a price jump needs to be, for you to want to act on it by closing the position and capturing that profit.

The more manual route I mentioned is https://www.quantopian.com/posts/track-values

Using track_values() in some tests on Q500US(), as expected it is a set of pretty stable stocks so the highest jump over the last year is just 1.24. In 2010 on the other hand, 1.58, and I didn't look at other years. My stocks are volatile enough that 2.5 catches 17 jumps over 4 years.

People can use their ingenuity to modify and improve on the example.

Another simple solution to make it more dynamic--though I don't know if it's any better--would be to use standard deviation. Something like:
if data.current(s, 'price') > 3.0 * std + prices[s][0]: If you're looking for extreme price swings, maybe you'll set it to more than three standard deviations.

You can get std from talib.