Anyone interested in coding this strategy?
12 responses

How do you calculate the SCTR and the Money Wave?

Hi André!

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?

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).

151
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

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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 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.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 for tpl in weightSortedStocks.items() if tpl.Weight > 50.0 and tpl.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]) / closeDeck[stock] * 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]) / closeDeck[stock] * 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]

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

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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())
)
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.

151
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

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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 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.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 for tpl in weightSortedStocks.items() if tpl.Weight > 50.0 and tpl.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]) / closeDeck[stock] * 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]) / closeDeck[stock] * 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]

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

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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())
)
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