Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
Trying on for size: a technical framework

Attached below (so that I may replace it with updates as I see fit) is a simple framework for technical type strategies.
The various parts are divided into discrete code blocks that are added to the logic tree using schedule_function.
The entry mechanism uses a simple additive trigger mechanism. If all of the technical metrics have signaled an entry, an entry will occur.
It's setup to expand security wise for set universe or update universe, and to switch to use minutely as desired.

Logic tree:

def initialize(context):  
    context.ZZZ = symbol('SPY','QQQ','EFA','AGG','VNQ','GLD')[0]  
    set_benchmark(context.ZZZ)

    # Establish state  
    schedule_function(EstablishState)

    # Calculate indicators  
    schedule_function(HighestHigh)  
    schedule_function(CalculateLongMA)  
    schedule_function(CalculateRSI)  
    schedule_function(CalculateHMA)  
    schedule_function(CalculateVolatilityVariance)

    # Handle exits  
    schedule_function(PeriodStop)  
    schedule_function(TrailingStop)  
    schedule_function(VolBiasProfitStop)  
    schedule_function(VolatilityStop)

    # Handle entries  
    schedule_function(HandleEntry)

Entry logic:

def HandleEntry(context, data):  
    eligible = []  
    positions = context.portfolio.positions  
    openPositions = [stock for stock in positions if positions[stock].amount != 0]  
    for stock in context.S:  
        if (context.S[stock].Trigger >= EntryTrigger and stock not in openPositions):  
            eligible.append(stock)

    eligible += openPositions  
    eligibleCount = float(len(eligible))  
    for stock in eligible:  
        if (get_open_orders(stock)):  
            continue  
        if (stock not in openPositions):  
            order_target_percent(stock, 1.0 / eligibleCount)  
            PrintEntry(data[stock], "ThresholdEntry", stock.symbol)  
14 responses

Thank you. This framework is very helpful. I wonder if you would post a cloneable working example of this code.

Market Tech, thanks for posting this. I've definitely borrowed code from you before as a significant hurdle in developing these algos is often not the idea's themselves but the idiosyncrasies of implementation. Of particular use is the filtering of empty data sets that causes the dreaded pink screen/algo crash and portfolio management to handle exits.

Market Tech,

Coding has always been a struggle for me. I only invest the time and effort when there is a large benefit. Creating algorithmic trading strategies with Quantopian is one of these cases. Because of my steep learning curve, I appreciate when competent coders provide a template that I can tweak and adjust to accomplish my goal. Thank you for this post, and your many other contributions!

With that said, I have some ideas to improve your algorithm template. Consider this to be psuedocode, since I don't fully understand the syntax required. When you find a mistake, please correct me.

1) Provide the ability to adjust the impact of each criteria, using weights. (The code below shows how the variables might be used, but doesn't provide the logic.)

# Criteria Weighting  
# Provides the ability to change the impact of each criteria when making a decision.  
# Range: 0 to 10  
# 0 = Prevents an indicator from having any impact in the decision process  
# 5 = Default setting  
# 10 = Gives an indicator twice as much impact compared to the default, and 10 times the impact of a criteria with a weight of 1.

weightPctPriceChange = 5  
weightRelativeVolume = 5  
weightStochastics = 5

weightKeltnerChannels = 5  
weightChoppiness = 5  
weightMFI = 5  
weightOBV = 5  
weightROC = 5  
weightMACD = 5  
weightHighestHigh = 5  
weightLongMA = 5  
weightRSI = 5  
weightHMA = 5  
weightVolatilityVariance = 5  

2) Due to the recently added low-beta requirement, many algorithms need to add the ability to short stocks to lower their beta. Do you think it would be a good idea to keep a LongScore and a ShortScore for each stock? Here is an example using two new functions:

 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
def calcRelativeVolumeScore(context, data):  
    # Calculate the relative volume based on the 50 day moving average of volume, and the volume of the previous day  
    volumes = history(50, '1d', field='volume')  
    # Calculate the 50-day average volume  
    AverageVolumes = volumes.mean()  
    for stock in data:  
        # Divide the previous day's volume by the average volume  
        RelativeVolume = volumes[stock][iloc[-1]].volume() / AverageVolumes[stock]  
        if RelativeVolume >= 48:  
            context.S[stock].LongScore =+ (5 * weightRelativeVolume)  
            context.S[stock].ShortScore =+ (5 * weightRelativeVolume)  
        elif RelativeVolume >= 24:  
            context.S[stock].LongScore =+ (4 * weightRelativeVolume)  
            context.S[stock].ShortScore =+ (4 * weightRelativeVolume)  
        elif RelativeVolume >= 12:  
            context.S[stock].LongScore =+ (3 * weightRelativeVolume)  
            context.S[stock].ShortScore =+ (3 * weightRelativeVolume)  
        elif RelativeVolume >= 6:  
            context.S[stock].LongScore =+ (2 * weightRelativeVolume)  
            context.S[stock].ShortScore =+ (2 * weightRelativeVolume)  
        elif RelativeVolume >= 3:  
            context.S[stock].LongScore =+ (1 * weightRelativeVolume)  
            context.S[stock].ShortScore =+ (1 * weightRelativeVolume)  
        else: # RelativeVolumes[stock] < 3  
           # Stock doesn't meet minimum requirements  
            context.S[stock].Eligible = No  
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
def calcPctPriceChangeScore(context, data):

    # Calculate the percent price change from the previous close to the current price  
    closing_prices = history(2, '1d', 'close_price')  
    PctPriceChange = closing_prices.iloc[-1, -2].pct_change()  
    for stock in data:  
        # Add weighted values to Long scores  
        if PctPriceChange[stock] >= 80:  
            context.S[stock].LongScore =+ (5 * weightPctPriceChange)  
        elif PctPriceChange[stock] >= 40:  
            context.S[stock].LongScore =+ (4 * weightPctPriceChange)  
        elif PctPriceChange[stock] >= 20:  
            context.S[stock].LongScore =+ (3 * weightPctPriceChange)  
        elif PctPriceChange[stock] >= 10:  
            context.S[stock].LongScore =+ (2 * weightPctPriceChange)  
        elif PctPriceChange[stock] >= 5:  
            context.S[stock].LongScore =+ (1 * weightPctPriceChange)  
        # Add weighted values to Short scores  
        if PctPriceChange[stock] <= -80:  
            context.S[stock].ShortScore =+ (5 * weightPctPriceChange)  
        elif PctPriceChange[stock] <= -40:  
            context.S[stock].ShortScore =+ (4 * weightPctPriceChange)  
        elif PctPriceChange[stock] <= -20:  
           context.S[stock].ShortScore =+ (3 * weightPctPriceChange)  
        elif PctPriceChange[stock] <= -10:  
           context.S[stock].ShortScore =+ (2 * weightPctPriceChange)  
        elif PctPriceChange[stock] >= -5:  
           context.S[stock].ShortScore =+ (1 * weightPctPriceChange)  
        else: # -5% < PctPriceChange > 5%  
            # Stock doesn't meet minimum requirements  
            context.S[stock].Eligible = No  

@Tristan R., An excellent extension of this framework (such as it is).

What you'll find as you try to expand your technique however, is that it quickly becomes unwieldy. So you might shoot for collapsing your range classification into a single method which can interpret the values, any values, that you feed it. It would figure out the range, find the slice where the current value falls and perform a scaled normalized approximation selection of the value. Here I'll show you:

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
def CalculateRSI(context, data):  
    closeDeck = history(RSIPeriods + 2, "1d", "close_price").dropna(axis=1)  
    closeDeck = closeDeck[[sid for sid in closeDeck if sid in data]]  
    rsi       = closeDeck.apply(talib.RSI, timeperiod = RSIPeriods).dropna()  
    for stock in rsi:  
        context.S[stock].RSI = rsi[stock][-1]  
        context.S[stock].Weight += GetScaledWeighting(0, 100, context.S[stock].RSI)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
def GetScaledWeighting(originalRangeStart, originalRangeEnd, valueToPlace):  
    targetRangeStart = -10; targetRangeEnd = 10  
    scaledRange      = (originalRangeEnd - originalRangeStart) / (targetRangeEnd - targetRangeStart)  
    scaledResult     = targetRangeStart + (valueToPlace - originalRangeStart) // scaledRange # // = floor division  
    return scaledResult

If you apply such a scaling, changing the originalRangeStart and end with values appropriate to the indicator (one would have to normalize those a bit, like the highest high or longMA would need some math applied). But then any Weight > 0 is a long and < 0 a short, and the weight itself becomes a position scaling weight. I'd have to spend some time refactoring the framework but you get the picture.
~~~
And if you wanted to apply special weighting per an indicator you'd add that to the method such that your scaled result would be tuned per the various indicator weights.

Hi,

Thank you for all the code!

I have a few questions regarding the Keltner Channels, and I want to apologize in advance if the answers are obvious as I am rather new to coding.

(1) I noticed that the KC code here use the SMA. I was under the impression that Keltner Channels use EMA. Is the code somehow modifying the SMA to turn it into an EMA?

(2A) KC traditionally has three variables: the EMA period, the ATR period, and the ATR multiplier. I see that the EMA (SMA?) period and the ATR period are both done through the code's "KeltnerPeriods". If I wanted to separate these, would it be as simple as dividing the code term up earlier, as in "KeltnerATRPeriod" and "KeltnerEMAPeriod"?

(2B) I didn't see the multiplier for the ATR. If I wanted to add this into the code could I do it as one of the following: "KeltnerLow = sma[stock][-1] - ( x * atr[-1] )", where "x" is the multipler; or "KeltnerLow = sma[stock][-1] - (KeltnerATRModifier * atr[-1])", where KeltnerATRModifier was earlier defined in the code.

I'm very interested in this, so any help or advice would be greatly appreciated.

@Jedouard P.,
1) You can use any moving average that you feel is appropriate. No there is no translation going on.
2) and 3) -- spot on approach.

You'll find that most of these price interpretation technical indicator aids were built for the human visual cortex and its handy-dandy pattern matching engine. Our minds can see channel breakouts and instantly grok them AND the context in which they appear. Thinking that if you tune the Keltner channels "just right" that it will somehow divine golden signals is, in my opinion (and thousands of hours of writing this stuff), a misleading approach. Sure technical indicators can assist you in adding probability flavors to your spicy algorithms. But the meat of the dish cannot be found within them. But, hey, what do I know. I'm a failed strategy writer who has never written an algo he would trust.

Hi Market Tech,

Thanks for the help and advice.

I'm very bullish, and, on top of this, I stick with the high-volume ETFs with top ratings from professional firms (Morning Star, etc.). I'm hoping to never get an inflated idea, much less head about my trading skill.

I've been doing a lot of work on a particular strategy and I've had a lot of good backtesting with a partial version of this on another platform, but the limitations of either the coding language or my skill in using it prevented me from coding the whole strategy. The reason I'm saying this is that I don't want you to think that I think a Keltner based on EMA will be a magic bullet or anything or even that my strategy will be, but on a five-year back-test, that simplified strategy has been getting better than average returns most weeks and, I hope, it should be better when I can code the whole thing.

I appreciate all the work you did on the code above. It really helps.

Market Tech-

Thank you so much for offering up this code to the public!

I have a question regarding an error I encounter when running the algo: "Runtime exception: AttributeError: 'module' object has no attribute 'SIDData'"

This occurs in reference to this portion of the code:

class DataStock(zipline.protocol.SIDData):
def init(this, sid, dataSid):
this.Update(dataSid)

def Update(this, dataSid):  
    this.Open   = dataSid.open_price  
    this.High   = dataSid.high  
    this.Low    = dataSid.low  
    this.Close  = dataSid.close_price  
    this.Volume = dataSid.volume

Please bear with me as I'm new to Python, any help you (or others) could offer in correcting this would be greatly appreciated! :)

Have a good one.

Peter, I'm no longer active on the Q, more productive things to do with my time...

However, searching the github zipline repo for SIDData I see that they did not document the change that may have been done to SIDData (zipline.protocol.SIDData)

So, I have no idea what has happened to cause this "module" error.

Maybe you might get lucky and one of the zipline engineers can chime in regarding this undocumented change.

MT

MT-

Thanks for the timely response, especially since you're no longer active here.

I just wanted to make sure that it wasn't something with my setup (i.e., needed to input manual data into one of the fields). Appreciate the confirmation.

-Peter

this screams for a pipeline port ;)

Oh yeah? Blimey, but I do not relish having to learn YATSA (yet another trading system architecture).

But... seeing how futures may (or may not) be coming online here soon, and futures are much more entertaining than droll equities, I might be cajoled into learning the paraphernalia of pipeline processing, (pptttiiinnggg!)

Surely someone has created a developer friendly (that means non-pythonic) pipeline example that might be the springboard one might use to begin such a port...???

(Oh, and yeah, my year long project is over and I'm now out of hiding.)

Market Tech -

Welcome back (cue Welcome Back, Kotter theme song...).

See https://www.quantopian.com/tutorials/pipeline. Maybe Q could do an Esperanto translation for you, since you don't speak Python.

Note that get_fundamentals will eventually be deprecated, in favor of its pipeline manifestation (see https://www.quantopian.com/posts/fundamentals-fundamentally-broken).

See also https://www.quantopian.com/posts/pipeline-trading-universe-best-practice.

Plugable code block strategy

Note: this strategy is for reference only. Various extensions are added as they come to mind. Pick out what you find useful (if anything).

Update: 8/14/2016
Added TRO
Fixed the history deprecation warnings

Update: 6/30/2016
Swapped in an expando object instead of the lost SidData.
Still get all those noisome deprecation alerts. But it runs.

Update: 5/11/2015
Added Keltner Channels, Choppiness Index (Tangx Peter Bakker)

Update: 4/30/2015
Added OBV and MFI indicators

Update: 4/5/2015
Uses context.S[stock].DynamicProperty rather than data[stock].DynamicProperty now.

Still have these to fix...

Line 517: Function update_universe is deprecated.
Line 429: Iterating over the assets in data is deprecated.
Line 431: data[sid(N)] is deprecated. Use data.current.
Line 438: Checking whether an asset is in data is deprecated.
Line 486: Iterating over the assets in data is deprecated.
Line 433: data[sid(N)] is deprecated. Use data.current.
Line 331: Iterating over the assets in data is deprecated.
Line 326: Checking whether an asset is in data is deprecated.