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.

Clone Algorithm
111
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
import math 
import talib
import numpy
import pandas 
import zipline
import datetime
import collections

EntryTrigger    = 50
EnableLogging   = True
KeltnerPeriods  = 11
ChopPeriods     = 21
MFIPeriods      = 11
ROCPeriods      = 11
MACDPeriodsSlow = 63;   MACDPeriodsFast = 21;   MACDPeriodsSignal= 11
HighHighPeriods = 10
LowLowPeriods   = 10
HMAPeriods      = 63
RSIPeriods      = 21
LongMAPeriods   = 84
VariancePeriods = 11;   VarianceCutoff  = .5
ExitPeriods     = 100
TrailingStopPct = 10.0
VolBiasPeriods  = 21;  VolBiasThreshold = .70
MaxSecuritiesToTrade = 80
TROPeriods      = 20
TROMultiplier   = 5

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def initialize(context):
    set_symbol_lookup_date("2016-01-01")
    context.REF = symbol("IWM")
    set_benchmark(context.REF)
    context.Expired = []    
    context.S = {}  
    
    # Set execution cost assumptions. For live trading with Interactive Brokers we will assume a $1.00 minimum
    # per trade fee, with a per share cost of $0.0075. 
    set_commission(commission.PerShare(cost=0.0075, min_trade_cost=1.00))
    # Set market impact assumptions. We will limit our simulation to allow us to be up to 2.5% of 
    # the traded volume for any one minute, and that our price impact constant is 0.1. 
    set_slippage(slippage.VolumeShareSlippage(volume_limit=0.025, price_impact=0.10))
    
    statusRule = date_rules.every_day()
    entryRule  = date_rules.week_start(0)
    exitRule   = date_rules.week_end(0)
    
    # Establish state
    schedule_function(EstablishState, statusRule)
    schedule_function(ExpiredStop, statusRule)
    
    # Calculate indicators
    schedule_function(CalculateKeltnerChannels, entryRule)
    schedule_function(CalculateChoppiness, entryRule)
    schedule_function(CalculateMFI, entryRule)
    schedule_function(CalculateHighestHigh, entryRule)
    schedule_function(CalculateLowestLow, entryRule)
    schedule_function(CalculateOBV, entryRule)
    schedule_function(CalculateROC, entryRule)
    schedule_function(CalculateMACD, entryRule)
    schedule_function(CalculateLongMA, entryRule)
    schedule_function(CalculateRSI, entryRule)
    schedule_function(CalculateHMA, entryRule)
    schedule_function(CalculateVolatilityVariance, entryRule)
    schedule_function(CalculateTRO, entryRule)
    
    # Handle exits
    schedule_function(PeriodStop, exitRule)
    schedule_function(TrailingStop, exitRule)
    # TODO: fix for short positions
    #schedule_function(VolBiasProfitStop, exitRule)
    #schedule_function(VolatilityStop, exitRule)
    
    # Handle entries
    schedule_function(HandleEntry, entryRule)
    schedule_function(ExitAllPositions, exitRule)        
    schedule_function(RecordStatus, entryRule)
    
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def handle_data(context, data):
    pass
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def RecordStatus(context, data):
    positions    = context.portfolio.positions
    #record(Leveage       = context.account.leverage)
    #record(DataCount     = len(data))
    #record(PositionCount = sum([1 for stock in positions if positions[stock].amount != 0]))
    if (not context.S[context.REF].TRO is None):
        record(TRO       = context.S[context.REF].TRO)
        record(Close     = context.S[context.REF].Close)
    
    #if (context.REF in context.S and not context.S[context.REF].MACD is None):
    #    record(MACD    = context.S[context.REF].MACD)
    #    record(Trigger = context.S[context.REF].Trigger)
    #    record(OBV     = context.S[context.REF].OBV)
    #    record(MFI     = context.S[context.REF].MFI)
    #if (context.REF in context.S and not context.S[context.REF].Chop is None):
    #    record(chop     = context.S[context.REF].Chop)
    #    record(KeltHigh = context.S[context.REF].KeltnerHigh)
    #    record(KeltLow  = context.S[context.REF].KeltnerLow)
    #    #record(Close    = context.S[context.REF].Close)
    #    record(Weight    = context.S[context.REF].Weight)
        
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def HandleEntry(context, data):
    eligibleLong = []; eligibleShort = []
    weightSortedStocks = collections.OrderedDict(sorted(context.S.items(), key=lambda tpl: tpl[1].Weight, reverse=False))
    meanWeight = sum([stock.Weight for stock in context.S.values()]) / float(len(context.S))
    #record(MeanWeight = meanWeight)
    eligible = weightSortedStocks.keys()
    if (meanWeight > 0):
        halfWay = int(len(eligible) * .75)
    else:
        halfWay = int(len(eligible) * .25)
    
    # eligible[0] has a larger weight than eligible[-1]
    
    # Lowest of Weights are buys
    if (meanWeight < 0):
        eligibleLong  = eligible[-MaxSecuritiesToTrade / 4:]
    # Highest of Weights are sells
    #if (meanWeight > 0):
    #    eligibleShort = eligible[0:MaxSecuritiesToTrade / 4]
    # Mid upper quartile are sells
    eligibleShort += eligible[0:halfWay][-MaxSecuritiesToTrade / 4:]
    # Mid lower quartile are buys
    eligibleLong += eligible[(len(eligible) - halfWay):][0:MaxSecuritiesToTrade / 4]
    
    eligibleLongCount = float(len(eligibleLong))
    eligibleShortCount = float(len(eligibleShort))
    
    for stock in eligibleLong:
        if (stock in context.Expired or not data.can_trade(stock)):
            continue
        order_target_percent(stock, -1.0 / eligibleLongCount)
        PrintEntry(context.S[stock], "ThresholdEntryLong", stock.symbol)
    for stock in eligibleShort:
        if (stock in context.Expired or not data.can_trade(stock)):
            continue
        order_target_percent(stock, 1.0 / eligibleShortCount)
        PrintEntry(context.S[stock], "ThresholdEntryShort", stock.symbol)        
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateKeltnerChannels(context, data):
    highDeck  = data.history(context.S.keys(), "high" , KeltnerPeriods + 1, "1d").dropna(axis=1)
    lowDeck   = data.history(context.S.keys(), "low"  , KeltnerPeriods + 1, "1d").dropna(axis=1)    
    closeDeck = data.history(context.S.keys(), "close", KeltnerPeriods + 1, "1d").dropna(axis=1)    
    #valid     = [sid for sid in highDeck if sid in data]
    #highDeck  = highDeck[valid]
    #lowDeck   = lowDeck[valid]
    #closeDeck = closeDeck[valid]
    sma       = closeDeck.apply(talib.MA, timeperiod = KeltnerPeriods, matype = MAType.SMA).dropna()
    for stock in context.S:
        try:
            atr = talib.ATR(highDeck[stock], lowDeck[stock], closeDeck[stock], timeperiod=KeltnerPeriods)
        except: continue
        context.S[stock].KeltnerLow  = sma[stock][-1] - atr[-1]
        context.S[stock].KeltnerHigh = sma[stock][-1] + atr[-1]
        context.S[stock].Weight += 10 if context.S[stock].Close > context.S[stock].KeltnerHigh else 0
        context.S[stock].Weight += -10 if context.S[stock].Close < context.S[stock].KeltnerLow else 0        
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateChoppiness(context, data): #Courtesy of Peter Bakker
    # 100 * LOG10( SUM(ATR(1), x) / ( MaxHi(x) - MinLo(x) ) ) / LOG10(x)
    highDeck  = data.history(context.S.keys(), "high" , ChopPeriods * 2, "1d").dropna(axis=1)
    lowDeck   = data.history(context.S.keys(), "low"  , ChopPeriods * 2, "1d").dropna(axis=1)    
    closeDeck = data.history(context.S.keys(), "close", ChopPeriods * 2, "1d").dropna(axis=1)    
    #valid     = [sid for sid in highDeck if sid in data]
    #highDeck  = highDeck[valid]
    #lowDeck   = lowDeck[valid]
    #closeDeck = closeDeck[valid]
    for stock in context.S:
        try:
            ATR = talib.ATR(highDeck[stock], lowDeck[stock], closeDeck[stock], timeperiod=ChopPeriods)
        except: continue
        total = sum(ATR[-ChopPeriods:])  
        lowest = min(lowDeck[stock][-ChopPeriods:])  
        highest = max(highDeck[stock][-ChopPeriods:])  
        diff = highest - lowest  
        temp = (total/diff)  
        chop = 100 * math.log10(temp) / math.log10(ChopPeriods)  
        context.S[stock].Chop = chop
        context.S[stock].Weight += GetScaledWeighting(0, 100, context.S[stock].Chop)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateMFI(context, data):
    highDeck = data.history(context.S.keys(), "high"  , MFIPeriods * 2, "1d").dropna(axis=1)
    lowsDeck = data.history(context.S.keys(), "low"   , MFIPeriods * 2, "1d").dropna(axis=1)
    closDeck = data.history(context.S.keys(), "close" , MFIPeriods * 2, "1d").dropna(axis=1)
    voluDeck = data.history(context.S.keys(), "volume", MFIPeriods * 2, "1d").dropna(axis=1)
    for stock in context.S:
        try:
            mfi = talib.MFI(highDeck[stock], lowsDeck[stock], closDeck[stock], voluDeck[stock], timeperiod=MFIPeriods)
            context.S[stock].MFI = mfi[-1]
            context.S[stock].Weight += GetScaledWeighting(0, 100, context.S[stock].MFI)
        except:
            continue
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateOBV(context, data):
    closeDeck  = data.history(context.S.keys(), "close", 200, "1d").dropna(axis=1)
    #closeDeck  = closeDeck[[sid for sid in closeDeck if sid in data]]
    volumeDeck = data.history(context.S.keys(), "volume", 200, "1d").dropna(axis=1)
    #volumeDeck = volumeDeck[[sid for sid in volumeDeck if sid in data]]
    for stock in closeDeck:
        context.S[stock].OBV = talib.OBV(closeDeck[stock], volumeDeck[stock])[-1]
        context.S[stock].Weight += GetScaledWeighting(-1000000000, 1000000000, context.S[stock].OBV)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateROC(context, data):
    closeDeck = data.history(context.S.keys(), "close", ROCPeriods, "1d").dropna(axis=1)
    #closeDeck = closeDeck[[sid for sid in closeDeck if sid in data]]
    for stock in closeDeck:
        context.S[stock].ROC = (closeDeck[stock][-1] - closeDeck[stock][0]) / closeDeck[stock][0] * 100.0
        context.S[stock].Weight += GetScaledWeighting(-100, 100, context.S[stock].ROC)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateMACD(context, data):
    closeDeck = data.history(context.S.keys(), "close", MACDPeriodsSlow + MACDPeriodsFast, "1d").dropna(axis=1)
    #closeDeck = closeDeck[[sid for sid in closeDeck if sid in data]]
    for stock in closeDeck:
        macd, signal, hist = talib.MACD(closeDeck[stock], MACDPeriodsFast, MACDPeriodsSlow, MACDPeriodsSignal)        
        context.S[stock].MACD = (macd[-1] - signal[-1]) / signal[-1] * 100.0
        context.S[stock].Weight += GetScaledWeighting(-100, 100, context.S[stock].MACD)
        context.S[stock].Weight += 10 if hist[-1] > 0.0 else -10
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~            
def CalculateHighestHigh(context, data):
    highDeck = data.history(context.S.keys(), "high", HighHighPeriods, "1d").dropna(axis=1)
    #highDeck = highDeck[[sid for sid in highDeck if sid in data]]
    for stock in highDeck:
        context.S[stock].Weight += 10 if context.S[stock].High == max(highDeck[stock]) else 0
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateLowestLow(context, data):
    lowDeck = data.history(context.S.keys(), "low", LowLowPeriods, "1d").dropna(axis=1)
    #lowDeck = lowDeck[[sid for sid in lowDeck if sid in data]]
    for stock in lowDeck:
        context.S[stock].Weight += -10 if context.S[stock].Low == min(lowDeck[stock]) else 0    
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateLongMA(context, data):
    closeDeck = data.history(context.S.keys(), "close", LongMAPeriods, "1d").dropna(axis=1)
    #closeDeck = closeDeck[[sid for sid in closeDeck if sid in data]]
    sma       = closeDeck.apply(talib.MA, timeperiod = LongMAPeriods, matype = MAType.SMA).dropna()
    for stock in sma:
        context.S[stock].SMA = (context.S[stock].Close - sma[stock][-1]) / sma[stock][-1] * 100.0
        context.S[stock].Weight += GetScaledWeighting(-100, 100, context.S[stock].SMA)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~    
def CalculateRSI(context, data):
    closeDeck = data.history(context.S.keys(), "close", RSIPeriods + 2, "1d").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 CalculateHMA(context, data):
    closeDeck = data.history(context.S.keys(), "close", HMAPeriods * 2, "1d").dropna(axis=1)
    #closeDeck = closeDeck[[sid for sid in closeDeck if sid in data]]
    wmaA      = closeDeck.apply(talib.MA,   timeperiod = HMAPeriods / 2, matype = MAType.WMA).dropna() * 2.0
    wmaB      = closeDeck.apply(talib.MA,   timeperiod = HMAPeriods, matype = MAType.WMA).dropna()
    wmaDiffs  = wmaA - wmaB
    hma       = wmaDiffs.apply(talib.MA, timeperiod = math.sqrt(HMAPeriods), matype = MAType.WMA)
    for stock in closeDeck:
        context.S[stock].HMA = (context.S[stock].Close - hma[stock][-1]) / hma[stock][-1] * 100.0
        context.S[stock].Weight += GetScaledWeighting(-100, 100, context.S[stock].HMA)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateVolatilityVariance(context, data):
    closeDeck      = data.history(context.S.keys(), "close", VariancePeriods, "1d").dropna(axis=1)
    #closeDeck      = closeDeck[[sid for sid in closeDeck if sid in data]]
    returnsDeck    = closeDeck.pct_change()
    returnsLog     = numpy.log1p(returnsDeck)
    returnsLogMean = pandas.rolling_mean(returnsLog, 3)
    sqrdLogDiffs   = (returnsLog - returnsLogMean)**2.0
    sqrdLogDiffs  *= 10000.0
    sqrdLogDiffs   = pandas.rolling_mean(sqrdLogDiffs, 7)
    for stock in closeDeck:
        if (not sqrdLogDiffs[stock].any() or len(sqrdLogDiffs[stock]) == 0):
            continue
        context.S[stock].VolVar = 30 - sqrdLogDiffs[stock][-1] # Lower is better for VolVar
        context.S[stock].Weight += GetScaledWeighting(0, 30, context.S[stock].VolVar)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateTRO(context, data):
    highDeck  = data.history(context.S.keys(), "high" , TROPeriods, "1d").dropna(axis=1)
    lowDeck   = data.history(context.S.keys(), "low"  , TROPeriods, "1d").dropna(axis=1)
    closeDeck = data.history(context.S.keys(), "close", TROPeriods, "1d").dropna(axis=1)
    #closeDeck = closeDeck[[sid for sid in closeDeck if sid in data]]    
    for stock in closeDeck:
        try:
            highs  = highDeck[stock]
        except:
            continue
        lows   = lowDeck[stock]
        closes = closeDeck[stock]
        low    = lows[-1]
        high   = highs[-1]
        
        # Use the ATR as a channel offset
        try:
            atr = talib.ATR(highs, lows, closes, timeperiod = TROPeriods - 1)[-1]
            if (numpy.isnan(atr)):
                return
        except:
            return
        tro         = context.S[stock].TRO
        atrOffset   = atr * TROMultiplier;
        direction   = 0
        
        # Calculate the trailing range offset 
        #  based on the low if price is above
        #  or based on the high if price is below
        if (tro is None or tro == 0.0):
            tro = low - atrOffset;
        else:
            if (tro > low):
                if (tro > high + atrOffset):
                    tro = high + atrOffset
                    direction = -1
            else:
                if (tro < low - atrOffset):
                    tro = low - atrOffset
                    direction = 1
        
        # State saved to make it easy to use the TRO values in trading logic
        if (direction == 0 and context.S[stock].TRODirection == 0): 
            direction = 1 if closes[-1] > closes[-TROPeriods] else -1
        context.S[stock].TRO = tro
        context.S[stock].TRODirection = direction

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        
def ExitAllPositions(context, data):
    positions = context.portfolio.positions
    for stock in positions:
        if (stock not in data):
            continue
        order_target_percent(stock, 0.0)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def PeriodStop(context, data):
    for stock in data:
        if (not context.S[stock].NetQuantity or context.S[stock].HasOpenOrders):
            continue
        if (context.S[stock].PeriodStop == None):
            context.S[stock].PeriodStop = ExitPeriods
            continue
        context.S[stock].PeriodStop -= 1
        if (context.S[stock].PeriodStop > 0):
            continue
        context.S[stock].PeriodStop = None
        if (context.S[stock].NetQuantity != 0 and data.can_trade(stock)):
            order_target_percent(stock, 0.0)  
            PrintExit(context.S[stock], "PeriodStop", stock.symbol)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                
def VolBiasProfitStop(context, data):
    openDeck  = data.history(context.S.keys(), VolBiasPeriods, "open", "1d").dropna(axis=1)
    #valid     = [sid for sid in openDeck if sid in data]
    #openDeck  = openDeck[valid]
    #highDeck  = highDeck[valid]                                             
    #lowDeck   = lowDeck[valid]                                              
    #closeDeck = closeDeck[valid]    
    highDeck  = data.history(context.S.keys(), "high" , VolBiasPeriods, "1d").dropna(axis=1)
    lowDeck   = data.history(context.S.keys(), "low"  , VolBiasPeriods, "1d").dropna(axis=1)
    closeDeck = data.history(context.S.keys(), "close", VolBiasPeriods, "1d").dropna(axis=1)
    
    for stock in openDeck:
        if (not context.S[stock].NetQuantity or context.S[stock].HasOpenOrders):
            continue
        if (not context.S[stock].VolBiasDelay is None):
            context.S[stock].VolBiasDelay -= 1
            if (context.S[stock].VolBiasDelay > 0):
                continue
            context.S[stock].VolBiasDelay = None
         
        context.S[stock].VolBias = None
        upPortion = 0; dnPortion = 0
        span = len(openDeck[stock])
        for i in range(0, span):
            if (closeDeck[stock][i] > openDeck[stock][i]):
                upPortion += (i + 1) * (highDeck[stock][i] - lowDeck[stock][i])
            else:
                dnPortion += (i + 1) * (highDeck[stock][i] - lowDeck[stock][i])
        factor = upPortion / (upPortion + dnPortion)
        context.S[stock].VolBias = factor
        if (factor >= VolBiasThreshold and data.can_trade(stock)):
            context.S[stock].VolBiasDelay = VolBiasPeriods
            order_target_percent(stock, 0.0)  
            PrintExit(context.S[stock], "VolBiasProfitStop", stock.symbol)
            context.S[stock].VolBiasDelay = VolBiasPeriods
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def TrailingStop(context, data):
    for stock in context.S:
        if (not context.S[stock].NetQuantity or context.S[stock].HasOpenOrders):
            continue
        offset = (TrailingStopPct * context.S[stock].Close) / 100.0
        if (context.S[stock].TrailingStop is None):
            if (context.S[stock].NetQuantity > 0):
                context.S[stock].TrailingStop = context.S[stock].CostBasis - offset
            else:
                context.S[stock].TrailingStop = context.S[stock].CostBasis + offset
            continue
        if (context.S[stock].NetQuantity > 0):
            if (context.S[stock].Low < context.S[stock].TrailingStop and data.can_trade(stock)):
                order_target_percent(stock, 0.0)  
                PrintExit(context.S[stock], "TrailSellStop", stock.symbol)
                context.S[stock].TrailingStop = None
                continue
            else:  
                context.S[stock].TrailingStop = max(context.S[stock].TrailingStop, context.S[stock].Low - offset) 
        else:
            if (context.S[stock].High > context.S[stock].TrailingStop and data.can_trade(stock)):  
                order_target_percent(stock, 0.0)  
                PrintExit(context.S[stock], "TrailCoverStop", stock.symbol)
                context.S[stock].TrailingStop = None
                continue
            else:  
                context.S[stock].TrailingStop = max(context.S[stock].TrailingStop, context.S[stock].High + offset) 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def VolatilityStop(context, data):
    closeDeck      = data.history(context.S.keys(), "close", VariancePeriods, "1d").dropna(axis=1)
    #closeDeck      = closeDeck[[sid for sid in closeDeck if sid in data]]
    returnsDeck    = closeDeck.pct_change()
    returnsLog     = numpy.log1p(returnsDeck)
    returnsLogMean = pandas.rolling_mean(returnsLog, 3) #magic #s
    sqrdLogDiffs   = (returnsLog - returnsLogMean)**2.0
    sqrdLogDiffs  *= 1000.0
    sqrdLogDiffs   = pandas.rolling_mean(sqrdLogDiffs, 7) #magic #s
    for stock in closeDeck:
        if (not context.S[stock].NetQuantity or context.S[stock].HasOpenOrders):
            continue
        if (not sqrdLogDiffs[stock].any() or len(sqrdLogDiffs[stock]) == 0):
            continue
        if (sqrdLogDiffs[stock][-1] > VarianceCutoff and data.can_trade(stock)):
            order_target_percent(stock, 0.0)  
            PrintExit(context.S[stock], "VolatilityStop", stock.symbol)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def EstablishState(context, data):
    # Reconcile available stocks into context.S
    for stock in data:
        if (stock not in context.S):
            context.S[stock] = DataStock(stock, data[stock])
        else:
            context.S[stock].Update(data[stock])
            
    # Reconcile backwards for securities we can no longer trade
    removeThese = []
    for stock in context.S:
        if (stock not in data):
            removeThese.append(stock)
    for stock in removeThese:
        del context.S[stock]                    
        
    # Now setup up state on the SIDDAta object inside context.S        
    for stock in context.S:
        context.S[stock].Weight        = 0
        context.S[stock].NetQuantity   = context.portfolio.positions[stock].amount
        context.S[stock].CostBasis     = context.portfolio.positions[stock].cost_basis
        context.S[stock].HasOpenOrders = False
        context.S[stock].OpenLimit     = None
        context.S[stock].OpenStop      = None
        if (not get_open_orders(stock)):
            continue
        context.S[stock].HasOpenOrders = True
        for order in get_open_orders(stock):
            if order.limit:
                context.S[stock].OpenLimit   = order
                context.S[stock].LimitLeaves = order.amount - order.filled
            elif order.stop:
                context.S[stock].OpenStop    = order
                context.S[stock].StopLeaves  = order.amount - order.filled 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def PrintEntry(dataStock, entryName, symbol):
    if (not EnableLogging):
        return
    if (dataStock.NetQuantity == 0):
        print(">> {0:<20}{1:<5} @ {2:>7.2f}".format(
            entryName, symbol, dataStock.Close))
    else:
        print("** {0:<20}{1:<5} @ {2:>7.2f} # {3:>5}".format(
            entryName, symbol, dataStock.Close, dataStock.NetQuantity))
   
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def PrintExit(dataStock, exitName, symbol):
    if (not EnableLogging):
        return    
    pnl = 0.0
    if (dataStock.NetQuantity > 0):
        pnl = dataStock.Close - dataStock.CostBasis
    else:
        pnl = dataStock.CostBasis - dataStock.Close
        
    print("<< {0:<20}{1:<5} @ {2:>7.2f} delta {3:>6.2f} qty {4:>5}".format(
            exitName, symbol, dataStock.Close, pnl, dataStock.NetQuantity))
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def ExpiredStop(context, data):
    for stock in data:  
        if (stock.end_date > (get_datetime() + datetime.timedelta(days=5))):
            continue
        if (stock in context.Expired):
            continue
        if (data.can_trade(stock)):
            order_target_percent(stock, 0)
        print("Expired {0}".format(stock.symbol))
        context.Expired.append(stock)     
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def GetScaledWeighting(originalRangeStart, originalRangeEnd, valueToPlace):
    targetRangeStart = -10; targetRangeEnd = 10
    scaledRange      = (originalRangeEnd - originalRangeStart) / (targetRangeEnd - targetRangeStart)
    scaledResult     = targetRangeStart + (valueToPlace - originalRangeStart) // scaledRange # // = floor division
    return scaledResult
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def before_trading_start(context, data): 
    f = fundamentals
            
    # Standard fundamental filtering
    fundyDeck = get_fundamentals(
        query(
            f.valuation.market_cap,
            f.valuation_ratios.pe_ratio,
            f.asset_classification.morningstar_sector_code
        )
        .filter(f.valuation.market_cap >= 1e9)
        .filter(f.valuation.market_cap <= 9e10)
        .order_by(f.valuation_ratios.ps_ratio.desc())
        .limit(MaxSecuritiesToTrade * 5)
    )
    update_universe(fundyDeck.columns.values)    
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class DataStock(object): # zipline.protocol.SIDData
    def __init__(this, sid, dataSid):
        this.Update(dataSid)
        this.MACD = None
        this.Trigger = None
        this.HMA = None
        this.ROC = None
        this.SMA = None
        this.OBV = None
        this.MFI = None
        this.Chop = None
        this.VolVar = None
        this.KeltnerHigh = None
        this.KeltnerLow = None
        this.Weight = None
        this.PeriodStop = None
        this.TrailingStop = None
        this.VolBiasDelay = None
        this.TRO = None
        this.TRODirection = 0
        
    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
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class MAType():
    SMA   = 0; EMA   = 1; WMA   = 2; DEMA  = 3; TEMA  = 4;    
    TRIMA = 5; KAMA  = 6; MAMA  = 7; T3    = 8
There was a runtime error.