Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
Ranking Stocks based on signal percentiles

Hello all,
I wrote this algorithm to demo a way to rank/sort a universe of stocks based on several different signals. It uses a few different momentum indicators and buys the stocks that are trading above their 30 day moving average if they also show up in the top 20th percentile of all three indicators. It does the same thing on the short side, sell the stocks below their 30 day average that show up in the bottom 20th percentile of the indicators. It weights the portfolio so that it remains close to market neutral.

The strategy actually works better when turned into a reversion algo where the top percentiles are considered overbought and the lower percentiles are oversold.

David

6 responses

Great stuff. I'm afraid I'm going to be putting you up to a bunch more work. This code seems highly useful/adaptable while slightly beyond my pay grade at the moment.
I have a longing for logging added b/c I think it can help educate on ways to access the information contained in the objects.
A few questions:
a) The reason to have opted for commenting out the mavg bit (btw you can trick Python into dropping the build warning with a line saying mavg = mavg, after mavg is assigned, I do that when I might be toggling some variable in/out of the mix)
b) Having shied away from the minute mode required for history(), in the resulting object, what does iloc stand for and what does dropna() do? I presume the latter skips non-useful records.
c) If you happen to have a favorite resource for info on talib and a list of its many capabilities, please consider sharing that link, I find talib (awesomeness) official documention (yeah right) mere crib note reminders for those who already know what's what rather than intended to convey understanding, it lacks examples. I envy those who can learn without examples, must have missed that gene. Talib, you there? Examples good.

And a question for anyone:
My main algo does best with stocks that have a particular personality, extroverts, i.e. volatile.
Is there any way this code could be adapted to filter for high volatility fairly easily? (Then I'll probably also want to add a volume filter).

Thanks!

PS There's an opportunity here for someone to don the superman suit in their mind and modify to a strategy with an impressive output.

Hey Gary,
I'll answer your questions first.

a) The reason to have opted for commenting out the mavg bit

No good reason for this, I was just toggling settings and that happened to be commented out in the version I shared.

b) Having shied away from the minute mode required for history(), in the resulting object, what does iloc stand for and what does dropna() do? I presume the latter skips non-useful records.

The iloc method is an indexing operation, it select by integer index location. DataFrame.iloc[0] is the first row and DataFrame.iloc.iloc[-1] is the last row. You are correct about dropna(), it drops any NAN values from the data. I went with daily data to keep from placing too many orders, the costs are brutal at the intraday timeframe if the universe is large.

c) Happen to have a favorite resource for info on talib

talib documentation is crap, I will usually use a help command in a python terminal to find out what the arguments are, then do some googling to learn what the indicator actually does. There are blogs out there with strategies for each one. So to answer your question, no, I don't know of any single resource for the talib functions. There's a list of them here, but the rest is all google for me.

talib functions return numpy arrays for the most part, some of them return a tuple of arrays. That link should give you all the info you need about the data structures talib uses. I have also experimented with some of the talib functions, and it looks like the calculations are done using a rolling window, but I would re-confirm this for each one.

Is there any way this code could be adapted to filter for high volatility fairly easily?

Yes, adding a volatility calculation should be pretty straight forward, depending on how you prefer to calculate it. Using the price standard deviations would looks something like this

prices = history(50, '1d', 'price')  
volatility = prices.std()  
upper_percentile = np.percentile(volatility, 90)  
most_volatile_stocks = volatility[volatility > upper_percentile]  

most_volatile_stocks will be a series containing the volatility of the most volatile stocks in the universe, the sids will be the index of the series.

Changes made for this backtest
I modified the original version to hopefully get some better results, this one also uses the accumulation distribution oscillator, and money flow index. The biggest change I made is that I turned it into a contrarian strategy instead of a momentum strategy. The thought process is that the stocks in the higher percentiles of the indicators are overbought and the lower percentile stocks are oversold, so there is more potential upside for the lower percentiles.

I split the percentiles down the middle and considered any stock in the upper half of every indicator to be overbought, and the stocks in lower half of every indicator as oversold. I selected new stocks on a weekly basis, and remained as close to market neutral as possible. I did the 50/50 split so that more stocks would get selected, the idea is that it is easier to hit a target with a shotgun than a rifle, and on average, the lower percentile stocks should have more potential upside.

I am not generally a technical analysis fan, but I think a composition of several indicators should be better than any one on its own. I would not recommend trading this as is, there are some bugs that need attention, and TA is very hit/miss to say the least. I really wanted to demo a method of sorting and ranking a universe of stocks based on several different metrics, whatever those metrics happen to be.

BTW, I forgot to add logging statements, I'll add some on the next iteration of the algo and share the result. The docstring at the top of the code should have been deleted as well

Any thoughts??

David

Thanks for sharing, David! I'm very interested to see where this one goes.

Hello!
I used the first algorithm as a template to draft up a quick practice code. But I keep getting errors. Any idea on how to fix it? Thanks in advance for the help.

Calvin

The code

If a stock is one of the top five ranked by momentum, long if the stocks 200 day SMA is below the 50 day SMA

Weekly rebalance

Template "Ranking Stocks based on signal percentiles"

import pandas as pd
import talib as ta
import numpy as np

def initialize(context):

#Stocks that will be traded  
context.stocks = [ sid(19662),  # XLY Consumer Discrectionary SPDR Fund  
                   sid(19656),  # XLF Financial SPDR Fund  
                   sid(19658),  # XLK Technology SPDR Fund  
                   sid(19655),  # XLE Energy SPDR Fund  
                   sid(19661),  # XLV Health Care SPRD Fund  
                   sid(19657),  # XLI Industrial SPDR Fund  
                   sid(19659),  # XLP Consumer Staples SPDR Fund  
                   sid(19654),  # XLB Materials SPDR Fund  
                   sid(19660),  # XLU Utilities SPRD Fund  
                   sid(33652)]  # BND Vanguard Total Bond Market ETF  

#Over simplified leverage control  
context.leverages = 2.0  

#Rebalance function, 30 minutes before market close on Mondays  
schedule_function(  
func=rebalance,  
date_rule=date_rules.week_start(),  
time_rule=time_rules.market_close(minutes=30),  
half_days=True  
)  

pass

def rebalance(context, data):

#Momentum calculation with talib  
hist = history(121, '1d', 'price')  #Needs the extra day  
mom = hist.apply(ta.MOM, timeperiod = 120).iloc[-1].dropna()  

#Percentile/Top 5  
cutoff = np.percentile(mom, 50)  #Ten ETFs total, 50% for top five  

#Momentum selection  
hist = hist.iloc[-1]  
selection = hist  
selection = selection[mom >= cutoff]  #Error on this line.  

#Buy if 200 SMA > 50 SMA  
for stock in selection.index:  
    slow = data[stock].mavg(200)  
    fast = data[stock].mavg(50)  

    if (fast > slow):  
        order_target_percent(stock, (context.leverages / len(selection)))  
    else:  
        order_target_percent(stock, 0.0)  

def handle_data(context, data):

#Track leverage  
leverage = context.account.leverage  
record('Leverage', leverage)  

Thanks for sharing! This gives me a great place to start.