Back to Community
Anyone interested in coding this strategy?
12 responses

How do you calculate the SCTR and the Money Wave?

Hi André!

Please check these two links for SCTR
http://stockcharts.com/freecharts/sctr.html
http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:sctr

The money wave is a simple Slow Stochastics (5,1). The most difficult thing is here is to code the SCTR indicator. This tools is very good to use in many strategies. So if someone like to code it, the community could reuse usit many times in rebalancing strategies.

its already a function in talib.

Hello @Stian Andreassen,
Here do find it in the list?
https://github.com/mrjbq7/ta-lib/blob/master/README.md

Looks a bit involved, but doable, if you have the inclination.

"It takes two steps to calculate the StockCharts Technical Rank (SCTR). First, each stock is “scored” based on six different technical indicators. These six indicators can be subdivided into three groups: long-term, medium-term and short-term. The box below details these indicators, the relevant timeframe and the weightings."

"After this first calculation round, StockCharts.com then ranks these stocks by their indicator score."

Long-Term Indicators (weighting)

  * Percent above/below 200-day EMA (30%)  
  * 125-Day Rate-of-Change (30%)

Medium-Term Indicators (weighting)

  * Percent above/below 50-day EMA (15%)  
  * 20-day Rate-of-Change (15%)

Short-Term Indicators (weighting)

  * 3-day slope of PPO-Histogram (5%)  
  * 14-day RSI (5%)  
Percentage Price Oscillator (PPO): {(12-day EMA - 26-day EMA)/26-day EMA} x 100

Signal Line: 9-day EMA of PPO

PPO Histogram: PPO - Signal Line  

@Isak Engdahl, sorry I really thought I'd seen it somewhere in talib, apperently I was mistaken.

@Market Tech,
Do you have a clue if this can be coded within Quantopian framework?
"3-day slope of PPO-Histogram"

My Python programming skills are to poor. Or maybe this can not be programmed in Quantopian?

Are you interested in taking a stab on it?

@Isak E., Too many tasks on my ticket, totally tapped... But,

The PPO looks to be the MACD (26,12,9 sound familiar?) and the TALib.MACD will give you the third value which would be the histogram. Three of those will give you a delta. Have to turn that into a range between 0 : 100 (see next sentence) which doesn't make much sense, but whatever.

The four price percent deltas (whether price / old price or price / EMA) have a problem as they have to be normalized into 0 : 100, meaning, they need to be turned into percents and then adjusted ( Add 100, divide by 2) to get them into the 0 : 100 range (out of the -100 : +100 range).

Not a problem but (I think) that's the way to do it.

The STO comes with TALib.

Once loaded, and ranked by this fabricated % change technical composite then seeking out the dips using the STO and trading the top N of the ranked sounds like a proper swing strategy. Again, my time is not my own right now. But if nobody takes it on in the next few days I may hack a version from the "pluggable framework" published here on this forum.

Ah, what the hell... Here's a start for you. Don't know how to exit the positions (PeriodExit included) And don't know how to select the base universe (default added).

Clone Algorithm
151
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

EnableLogging   = True
ROCLongPeriods  = 125
ROCMidPeriods   = 20
EMALongPeriods  = 200
EMAMidPeriods   = 50
MACDPeriodsSlow = 26; MACDPeriodsFast = 12; MACDPeriodsSignal = 9
RSIPeriods      = 14
STOPeriodsA     = 14; STOPeriodsB = 3; STOPeriodsC = 5
ExitPeriods     = 10
MaxSecuritiesToTrade = 100

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def initialize(context):
    set_symbol_lookup_date('2015-01-01')
    context.REF = symbol('SPY')
    set_benchmark(context.REF)
    context.Expired = []    
    context.S = {}
    
    # Set execution cost assumptions. For live trading with Interactive Brokers we will assume a $1.50 minimum
    # per trade fee, with a per share cost of $0.0075. 
    set_commission(commission.PerShare(cost=0.0075, min_trade_cost=1.50))
    # 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.every_day()
    exitRule   = date_rules.every_day()
    
    # Establish state
    schedule_function(EstablishState, statusRule)
    schedule_function(ExpiredStop, statusRule)
    
    # Calculate indicators
    schedule_function(CalculateEMA, entryRule)    
    schedule_function(CalculateROC, entryRule)
    schedule_function(CalculateMACD, entryRule)
    schedule_function(CalculateRSI, entryRule)
    schedule_function(CalculateSTO, entryRule)
    
    # Handle exits
    schedule_function(PeriodStop, exitRule)
    
    # Handle entries
    schedule_function(CalculateWeights, entryRule)
    schedule_function(HandleEntry, entryRule)
    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 (context.REF in context.S and 'Weight' in context.S[context.REF]):
        #record(Weight    = context.S[context.REF].Weight)
        record(STO       = context.S[context.REF].STO)
        
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def HandleEntry(context, data):
    eligible = []
    openOrders = get_open_orders()
    openPositions = [sid for sid in context.portfolio.positions if context.portfolio.positions[sid].amount != 0]
    weightSortedStocks = collections.OrderedDict(sorted(context.S.items(), key=lambda tpl: tpl[1].Weight, reverse=True))
    meanWeight = sum([stock.Weight for stock in context.S.values()]) / float(len(context.S))
    record(MeanWeight = meanWeight)
    
    #
    # Select high ranked weights and low STOs
    #
    eligible = [tpl[0] for tpl in weightSortedStocks.items() if tpl[1].Weight > 50.0 and tpl[1].STO < 30]
    eligible += openPositions
    eligible = [stock for stock in eligible if stock not in context.Expired]
    eligible = [stock for stock in eligible if stock not in openOrders]
    eligibleCount = float(len(eligible))
    
    for stock in eligible:
        order_target_percent(stock, 1.0 / eligibleCount)
        PrintEntry(context.S[stock], "ThresholdEntryLong", stock.symbol)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateWeights(context, data):
    for stock in context.S:
        sidData = context.S[stock]        
        if ('EMALong' not in sidData):
            continue
        weight = 0.0
        weight += sidData.EMALong * .30
        weight += sidData.EMAMid  * .30
        weight += sidData.ROCLong * .15
        weight += sidData.ROCMid  * .15
        weight += sidData.RSI     * .05
        weight += sidData.PPO     * .05
        sidData.Weight = weight
    
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~            
def CalculateEMA(context, data):
    closeDeck = history(EMALongPeriods, "1d", "close_price").dropna(axis=1)
    closeDeck = closeDeck[[sid for sid in closeDeck if sid in data]]
    
    ema = closeDeck.apply(talib.MA, timeperiod = EMALongPeriods, matype = MAType.EMA).dropna()
    for stock in ema:
        context.S[stock].EMALong = (context.S[stock].Close - ema[stock][-1]) / ema[stock][-1] * 100.0
        context.S[stock].EMALong += 100.0
        context.S[stock].EMALong /= 2.0
        
    ema = closeDeck.apply(talib.MA, timeperiod = EMAMidPeriods, matype = MAType.EMA).dropna()
    for stock in ema:
        context.S[stock].EMAMid = (context.S[stock].Close - ema[stock][-1]) / ema[stock][-1] * 100.0
        context.S[stock].EMAMid += 100.0
        context.S[stock].EMAMid /= 2.0
        
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateROC(context, data):
    closeDeck = history(ROCLongPeriods, "1d", "close_price").dropna(axis=1)
    closeDeck = closeDeck[[sid for sid in closeDeck if sid in data]]
    for stock in closeDeck:
        context.S[stock].ROCLong = (closeDeck[stock][-1] - closeDeck[stock][0]) / closeDeck[stock][0] * 100.0
        context.S[stock].ROCLong += 100.0
        context.S[stock].ROCLong /= 2.0
        
    closeDeck = history(ROCMidPeriods, "1d", "close_price").dropna(axis=1)
    closeDeck = closeDeck[[sid for sid in closeDeck if sid in data]]
    for stock in closeDeck:
        context.S[stock].ROCMid = (closeDeck[stock][-1] - closeDeck[stock][0]) / closeDeck[stock][0] * 100.0
        context.S[stock].ROCMid += 100.0
        context.S[stock].ROCMid /= 2.0

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateMACD(context, data):
    closeDeck = history(MACDPeriodsSlow + MACDPeriodsFast, "1d", "close_price").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].PPO = (hist[-1] - hist[-3]) / hist[-3] * 100.0
        context.S[stock].PPO += 100.0
        context.S[stock].PPO /= 2.0

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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]

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateSTO(context, data):
    stoPeriods = STOPeriodsA + STOPeriodsB + STOPeriodsC + 1
    highDeck  = history(stoPeriods, '1d', 'high').dropna(axis=1)
    lowDeck   = history(stoPeriods, '1d', 'low').dropna(axis=1)    
    closeDeck = history(stoPeriods, '1d', 'close_price').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 closeDeck:
        sto = talib.STOCH(highDeck[stock], lowDeck[stock], closeDeck[stock],
                          fastk_period = STOPeriodsA, slowk_period = STOPeriodsB, slowd_period = STOPeriodsC)
        context.S[stock].STO = sto[1][-1]
        
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def PeriodStop(context, data):
    for stock in data:
        if (not context.S[stock].NetQuantity or context.S[stock].HasOpenOrders):
            continue
        if ('PeriodStop' not in context.S[stock]):
            context.S[stock].PeriodStop = ExitPeriods
            continue
        context.S[stock].PeriodStop -= 1
        if (context.S[stock].PeriodStop > 0):
            continue
        del context.S[stock].PeriodStop
        if (context.S[stock].NetQuantity != 0):
            order_target_percent(stock, 0.0)  
            PrintExit(context.S[stock], "PeriodStop", 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
        order_target_percent(stock, 0)
        print("Expired {0}".format(stock.symbol))
        context.Expired.append(stock)     
        
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def before_trading_start(context): 
    f = fundamentals
    fundyDeck = get_fundamentals(
        query(
            f.valuation.market_cap,
            f.valuation_ratios.pe_ratio
        )
        .filter(f.valuation.market_cap >= 1e9)
        .filter(f.valuation.market_cap <= 9e10)
        .order_by(f.valuation_ratios.pe_ratio.desc())
        .limit(MaxSecuritiesToTrade)
    )
    update_universe(fundyDeck.columns.values)    
    
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class MAType():
    SMA   = 0; EMA   = 1; WMA   = 2; DEMA  = 3; TEMA  = 4;    
    TRIMA = 5; KAMA  = 6; MAMA  = 7; T3    = 8
    
'''
NOTES from forum

"It takes two steps to calculate the StockCharts Technical Rank (SCTR). First, each stock is “scored” based on six different technical indicators. These six indicators can be subdivided into three groups: long-term, medium-term and short-term. The box below details these indicators, the relevant timeframe and the weightings."

"After this first calculation round, StockCharts.com then ranks these stocks by their indicator score."

Long-Term Indicators (weighting)

  * Percent above/below 200-day EMA (30%)  
  * 125-Day Rate-of-Change (30%)

Medium-Term Indicators (weighting)

  * Percent above/below 50-day EMA (15%)  
  * 20-day Rate-of-Change (15%)

Short-Term Indicators (weighting)

  * 3-day slope of PPO-Histogram (5%)  
  * 14-day RSI (5%)  
Percentage Price Oscillator (PPO): {(12-day EMA - 26-day EMA)/26-day EMA} x 100

Signal Line: 9-day EMA of PPO

PPO Histogram: PPO - Signal Line  

The PPO looks to be the MACD (26,12,9 sound familiar?) and the TALib.MACD will give you the third value which would be the histogram. Three of those will give you a delta. Have to turn that into a range between 0 : 100 (see next sentence) which doesn't make much sense, but whatever.

The four price percent deltas (whether price / old price or price / EMA) have a problem as they have to be normalized into 0 : 100, meaning, they need to be turned into percents and then adjusted ( Add 100, divide by 2) to get them into the 0 : 100 range (out of the -100 : +100 range).

Not a problem but (I think) that's the way to do it.

The STO comes with TALib.

Once loaded, and ranked by this fabricated % change technical composite then seeking out the dips using the STO and trading the top N of the ranked sounds like a proper swing strategy. Again, my time is not my own right now. But if nobody takes it on in the next few days I may hack a version from the "pluggable framework" published here on this forum.
'''
There was a runtime error.

And, you know, because I have no idea how StockCharts thinks it can normalize all those numbers into a 0 - 100 range... I thought I'd make a version that uses the min/max of the metric of the running set of securities and uses those as a normalization boundary. I'd already done this in another strat so it was easy to add. You can turn it off with the switch at the top.

Clone Algorithm
151
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

EnableLogging     = True
IsRelativeWeights = True
ROCLongPeriods    = 125
ROCMidPeriods     = 20
EMALongPeriods    = 200
EMAMidPeriods     = 50
MACDPeriodsSlow   = 26; MACDPeriodsFast = 12; MACDPeriodsSignal = 9
RSIPeriods        = 14
STOPeriodsA       = 14; STOPeriodsB = 3; STOPeriodsC = 5
ExitPeriods       = 10
MaxSecuritiesToTrade = 100

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def initialize(context):
    set_symbol_lookup_date('2015-01-01')
    context.REF = symbol('SPY')
    set_benchmark(context.REF)
    context.Expired = []    
    context.S = {}
    
    # Set execution cost assumptions. For live trading with Interactive Brokers we will assume a $1.50 minimum
    # per trade fee, with a per share cost of $0.0075. 
    set_commission(commission.PerShare(cost=0.0075, min_trade_cost=1.50))
    # 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.every_day()
    exitRule   = date_rules.every_day()
    
    # Establish state
    schedule_function(EstablishState, statusRule)
    schedule_function(ExpiredStop, statusRule)
    
    # Calculate indicators
    schedule_function(CalculateEMA, entryRule)    
    schedule_function(CalculateROC, entryRule)
    schedule_function(CalculateMACD, entryRule)
    schedule_function(CalculateRSI, entryRule)
    schedule_function(CalculateSTO, entryRule)
    
    # Handle exits
    schedule_function(PeriodStop, exitRule)
    
    # Handle entries
    schedule_function(CalculateWeights, entryRule)
    schedule_function(HandleEntry, entryRule)
    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 (context.REF in context.S and 'Weight' in context.S[context.REF]):
        #record(Weight    = context.S[context.REF].Weight)
        record(STO       = context.S[context.REF].STO)
        
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def HandleEntry(context, data):
    eligible = []
    openOrders = get_open_orders()
    openPositions = [sid for sid in context.portfolio.positions if context.portfolio.positions[sid].amount != 0]
    weightSortedStocks = collections.OrderedDict(sorted(context.S.items(), key=lambda tpl: tpl[1].Weight, reverse=True))
    meanWeight = sum([stock.Weight for stock in context.S.values()]) / float(len(context.S))
    record(MeanWeight = meanWeight)
    
    #
    # Select high ranked weights and low STOs
    #
    eligible = [tpl[0] for tpl in weightSortedStocks.items() if tpl[1].Weight > 50.0 and tpl[1].STO < 30]
    eligible += openPositions
    eligible = [stock for stock in eligible if stock not in context.Expired]
    eligible = [stock for stock in eligible if stock not in openOrders]
    eligibleCount = float(len(eligible))
    
    for stock in eligible:
        order_target_percent(stock, 1.0 / eligibleCount)
        PrintEntry(context.S[stock], "ThresholdEntryLong", stock.symbol)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateWeights(context, data):
    for stock in context.S:
        sidData = context.S[stock]        
        if ('EMALong' not in sidData):
            continue
        weight = 0.0
        weight += sidData.EMALong * .30
        weight += sidData.EMAMid  * .30
        weight += sidData.ROCLong * .15
        weight += sidData.ROCMid  * .15
        weight += sidData.RSI     * .05
        weight += sidData.PPO     * .05
        sidData.Weight = weight
    
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~            
def CalculateEMA(context, data):
    closeDeck = history(EMALongPeriods, "1d", "close_price").dropna(axis=1)
    closeDeck = closeDeck[[sid for sid in closeDeck if sid in data]]
    
    ema = closeDeck.apply(talib.MA, timeperiod = EMALongPeriods, matype = MAType.EMA).dropna()
    for stock in ema:
        context.S[stock].EMALong = (context.S[stock].Close - ema[stock][-1]) / ema[stock][-1] * 100.0
        context.S[stock].EMALong += 100.0
        context.S[stock].EMALong /= 2.0
        
    ema = closeDeck.apply(talib.MA, timeperiod = EMAMidPeriods, matype = MAType.EMA).dropna()
    for stock in ema:
        context.S[stock].EMAMid = (context.S[stock].Close - ema[stock][-1]) / ema[stock][-1] * 100.0
        context.S[stock].EMAMid += 100.0
        context.S[stock].EMAMid /= 2.0

    if (IsRelativeWeights):
        metricMin = min([stock.EMALong for stock in context.S.values() if 'EMALong' in stock])
        metricMax = max([stock.EMALong for stock in context.S.values() if 'EMALong' in stock])
        for stock in context.S:
            if ('EMALong' not in context.S[stock]):
                continue
            context.S[stock].EMALong = GetScaledWeighting(metricMin, metricMax, context.S[stock].EMALong)
            
        metricMin = min([stock.EMAMid for stock in context.S.values() if 'EMAMid' in stock])
        metricMax = max([stock.EMAMid for stock in context.S.values() if 'EMAMid' in stock])
        for stock in context.S:
            if ('EMAMid' not in context.S[stock]):
                continue
            context.S[stock].EMAMid = GetScaledWeighting(metricMin, metricMax, context.S[stock].EMAMid)
        
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateROC(context, data):
    closeDeck = history(ROCLongPeriods, "1d", "close_price").dropna(axis=1)
    closeDeck = closeDeck[[sid for sid in closeDeck if sid in data]]
    for stock in closeDeck:
        context.S[stock].ROCLong = (closeDeck[stock][-1] - closeDeck[stock][0]) / closeDeck[stock][0] * 100.0
        context.S[stock].ROCLong += 100.0
        context.S[stock].ROCLong /= 2.0
        
    closeDeck = history(ROCMidPeriods, "1d", "close_price").dropna(axis=1)
    closeDeck = closeDeck[[sid for sid in closeDeck if sid in data]]
    for stock in closeDeck:
        context.S[stock].ROCMid = (closeDeck[stock][-1] - closeDeck[stock][0]) / closeDeck[stock][0] * 100.0
        context.S[stock].ROCMid += 100.0
        context.S[stock].ROCMid /= 2.0

    if (IsRelativeWeights):
        metricMin = min([stock.ROCLong for stock in context.S.values() if 'ROCLong' in stock])
        metricMax = max([stock.ROCLong for stock in context.S.values() if 'ROCLong' in stock])
        for stock in context.S:
            if ('ROCLong' not in context.S[stock]):
                continue
            context.S[stock].ROCLong = GetScaledWeighting(metricMin, metricMax, context.S[stock].ROCLong)
            
        metricMin = min([stock.ROCMid for stock in context.S.values() if 'ROCMid' in stock])
        metricMax = max([stock.ROCMid for stock in context.S.values() if 'ROCMid' in stock])
        for stock in context.S:
            if ('ROCMid' not in context.S[stock]):
                continue
            context.S[stock].ROCMid = GetScaledWeighting(metricMin, metricMax, context.S[stock].ROCMid)
        
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateMACD(context, data):
    closeDeck = history(MACDPeriodsSlow + MACDPeriodsFast, "1d", "close_price").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].PPO = (hist[-1] - hist[-3]) / hist[-3] * 100.0
        context.S[stock].PPO += 100.0
        context.S[stock].PPO /= 2.0

    if (IsRelativeWeights):
        metricMin = min([stock.PPO for stock in context.S.values() if 'PPO' in stock])
        metricMax = max([stock.PPO for stock in context.S.values() if 'PPO' in stock])
        for stock in context.S:
            if ('PPO' not in context.S[stock]):
                continue
            context.S[stock].PPO = GetScaledWeighting(metricMin, metricMax, context.S[stock].PPO)
            
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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]

    if (IsRelativeWeights):
        metricMin = min([stock.RSI for stock in context.S.values() if 'RSI' in stock])
        metricMax = max([stock.RSI for stock in context.S.values() if 'RSI' in stock])
        for stock in context.S:
            if ('RSI' not in context.S[stock]):
                continue
            context.S[stock].RSI = GetScaledWeighting(metricMin, metricMax, context.S[stock].RSI)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CalculateSTO(context, data):
    stoPeriods = STOPeriodsA + STOPeriodsB + STOPeriodsC + 1
    highDeck  = history(stoPeriods, '1d', 'high').dropna(axis=1)
    lowDeck   = history(stoPeriods, '1d', 'low').dropna(axis=1)    
    closeDeck = history(stoPeriods, '1d', 'close_price').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 closeDeck:
        sto = talib.STOCH(highDeck[stock], lowDeck[stock], closeDeck[stock],
                          fastk_period = STOPeriodsA, slowk_period = STOPeriodsB, slowd_period = STOPeriodsC)
        context.S[stock].STO = sto[1][-1]
        
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def PeriodStop(context, data):
    for stock in data:
        if (not context.S[stock].NetQuantity or context.S[stock].HasOpenOrders):
            continue
        if ('PeriodStop' not in context.S[stock]):
            context.S[stock].PeriodStop = ExitPeriods
            continue
        context.S[stock].PeriodStop -= 1
        if (context.S[stock].PeriodStop > 0):
            continue
        del context.S[stock].PeriodStop
        if (context.S[stock].NetQuantity != 0):
            order_target_percent(stock, 0.0)  
            PrintExit(context.S[stock], "PeriodStop", 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
        order_target_percent(stock, 0)
        print("Expired {0}".format(stock.symbol))
        context.Expired.append(stock)     

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

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def before_trading_start(context): 
    f = fundamentals
    fundyDeck = get_fundamentals(
        query(
            f.valuation.market_cap,
            f.valuation_ratios.pe_ratio
        )
        .filter(f.valuation.market_cap >= 1e9)
        .filter(f.valuation.market_cap <= 9e10)
        .order_by(f.valuation_ratios.pe_ratio.desc())
        .limit(MaxSecuritiesToTrade)
    )
    update_universe(fundyDeck.columns.values)    
    
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class MAType():
    SMA   = 0; EMA   = 1; WMA   = 2; DEMA  = 3; TEMA  = 4;    
    TRIMA = 5; KAMA  = 6; MAMA  = 7; T3    = 8
    
'''
NOTES from forum

"It takes two steps to calculate the StockCharts Technical Rank (SCTR). First, each stock is “scored” based on six different technical indicators. These six indicators can be subdivided into three groups: long-term, medium-term and short-term. The box below details these indicators, the relevant timeframe and the weightings."

"After this first calculation round, StockCharts.com then ranks these stocks by their indicator score."

Long-Term Indicators (weighting)

  * Percent above/below 200-day EMA (30%)  
  * 125-Day Rate-of-Change (30%)

Medium-Term Indicators (weighting)

  * Percent above/below 50-day EMA (15%)  
  * 20-day Rate-of-Change (15%)

Short-Term Indicators (weighting)

  * 3-day slope of PPO-Histogram (5%)  
  * 14-day RSI (5%)  
Percentage Price Oscillator (PPO): {(12-day EMA - 26-day EMA)/26-day EMA} x 100

Signal Line: 9-day EMA of PPO

PPO Histogram: PPO - Signal Line  

The PPO looks to be the MACD (26,12,9 sound familiar?) and the TALib.MACD will give you the third value which would be the histogram. Three of those will give you a delta. Have to turn that into a range between 0 : 100 (see next sentence) which doesn't make much sense, but whatever.

The four price percent deltas (whether price / old price or price / EMA) have a problem as they have to be normalized into 0 : 100, meaning, they need to be turned into percents and then adjusted ( Add 100, divide by 2) to get them into the 0 : 100 range (out of the -100 : +100 range).

Not a problem but (I think) that's the way to do it.

The STO comes with TALib.

Once loaded, and ranked by this fabricated % change technical composite then seeking out the dips using the STO and trading the top N of the ranked sounds like a proper swing strategy. Again, my time is not my own right now. But if nobody takes it on in the next few days I may hack a version from the "pluggable framework" published here on this forum.
'''
There was a runtime error.

Hello, I just now joined after looking for some help coding the SCTR in Ninjatrader.

Market Tech, thank you for sharing your work.

If you have time, can you add something to this algo?

Only enter new positions when VIX/VXV < .92

Optionally, exit all positions when VIX/VXV >1.0

I think you are missing a step here. You calculate on day X the SCTR value for each stock say in the S&P 500.
Then for that day you RANK from the highest to the lowest the 500 SCTR for day X. Then you plot the resuslt.
So you need to have all the S&P 500 universe available. Then you plot the RANK for that stock.
Hope this helps.

RR