Welles Wilder's ADX - Average Directional Index (technical indicator implementation)

An oldie but a goodie, I thought I'd share my code for a popular technical indicator, the ADX.

Introduced by Welles Wilder in his 1978 book New Concepts in Technical Trading Systems, along with the Minus Directional Indicator and the Plus Directional Indicator, the ADX forms part of a family of technical indicators developed by Wilder to help classify trends and trend strength.

I've followed Wilder and used a rolling window of 14 days for the calculation window, which is used in various parts to allow for the smoothing Wilder implements. Changing the variable '''context.window_length''' should allow you to change this if you wish.

Finally, at the end of the code I implement a very crude trading strategy based on the indicator - am sure you guys can do better and be more inventive with this.

1132
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
# implementation of Welles Wilder's ADX as given in spreadsheet form here

from collections import deque

# Put any initialization logic here.  The context object will be passed to
# the other methods in your algorithm.
def initialize(context):
#using the NASDAQ100 etf
context.ndx = sid(19920)
#container to count the number of event windows we have cycled through
context.ticks = 0
#Wilder uses a rolling window of 14 days for various smoothing within
#the indicator calculation
context.window_length = 14
#a collection of data containers that will be used during steps of the calculation
context.highs = deque([0] * 2, 2)
context.lows = deque([0] * 2, 2)
context.closes = deque([0] * 2, 2)
context.true_range_bucket = deque([0] * context.window_length, context.window_length)
context.pDM_bucket = deque([0] * context.window_length, context.window_length)
context.mDM_bucket = deque([0] * context.window_length, context.window_length)
context.dx_bucket = deque([0] * context.window_length, context.window_length)
#not sure why I had to define these here, but to print them later when debuggin
#I found that I had to declare them here
context.av_true_range = 0
context.av_pDM = 0
context.av_mDM = 0
context.di_diff = 0
context.di_sum = 0
context.dx = 0
context.pDI = 0
context.mDI = 0
pass

# Will be called on every trade event for the securities you specify.
def handle_data(context, data):
#iterate event window counter
context.ticks += 1
#pass high, low, close prices to our rolling containers
context.highs.appendleft(data[sid(19920)].high)
context.lows.appendleft(data[sid(19920)].low)
context.closes.appendleft(data[sid(19920)].close_price)

#ensure no calculation on first window
if context.closes[0] == 0:
high_less_low = 0
high_less_prec_close = 0
low_less_prec_close = 0
high_less_prec_high = 0
prec_low_less_low = 0
pDM_one = 0
mDM_one = 0
else:
high_less_low = context.highs[0]-context.lows[0]
high_less_prec_close = abs(context.highs[0]-context.closes[1])
low_less_prec_close = abs(context.lows[0]-context.closes[1])
high_less_prec_high = context.highs[0]-context.highs[1]
prec_low_less_low = context.lows[1]-context.lows[0]

#calculate the Plus Directional Movement
if high_less_prec_high > prec_low_less_low:
pDM_one = max(high_less_prec_high,0)
else:
pDM_one = 0

#calculate the Minus Directional Movement
if prec_low_less_low > high_less_prec_high:
mDM_one = max(prec_low_less_low,0)
else:
mDM_one = 0

#add the current pDM and mDM to the bucket to aid calculation of the first point in the
#smoothed statistic
context.pDM_bucket.appendleft(pDM_one)
context.mDM_bucket.appendleft(mDM_one)

#calculate the True Range and add to bucket
true_range = max(high_less_low,high_less_prec_close,low_less_prec_close)
context.true_range_bucket.appendleft(true_range)

#once we have collected enough data to have populated the rolling windows adequately
#we can start the meat of the calculation
if context.ticks < (context.window_length + 1):
context.av_true_range = 1
context.av_pDM = 0
context.av_mDM = 0
elif context.ticks == (context.window_length + 1):
context.av_true_range = sum(context.true_range_bucket)
context.av_pDM = sum(context.pDM_bucket)
context.av_mDM = sum(context.mDM_bucket)
else:
context.av_true_range = context.av_true_range - (context.av_true_range/context.window_length) + true_range
context.av_pDM = context.av_pDM - (context.av_pDM/14) + pDM_one
context.av_mDM = context.av_mDM - (context.av_mDM/14) + mDM_one

if context.ticks > context.window_length:
context.pDI = 100 * context.av_pDM / context.av_true_range
context.mDI = 100 * context.av_mDM / context.av_true_range
context.di_diff = abs(context.pDI - context.mDI)
context.di_sum = context.pDI + context.mDI
context.dx = 100 * context.di_diff / context.di_sum

#add to bucket to provide an average of the DX figures that will
#act as a starting point for the rolling ADX calculation
context.dx_bucket.appendleft(context.dx)

if context.ticks == (context.window_length * 2):
elif context.ticks > (context.window_length * 2):

if (context.adx > 20) and (context.pDI > context.mDI):
order(sid(19920), 500)
elif (context.adx > 25) and (context.pDI < context.mDI):
order(sid(19920), -500)
log.debug('sell')

log.debug('pDI')
log.debug(context.pDI)
log.debug('mDI')
log.debug(context.mDI)

This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.
45 responses

I'm not familiar with how ADX is calculated but does line 97 and 98 need to have "context.window_length" instead of 14?

@James - yep, absolutely

I would like to code an ADX pattern that shows up on the weekly and/or the monthly charts. I have a few really terrific patterns that I would like to share how to implement and backtest. I am not overly technical, BUT I have done the research on the patterns and can explain them and share them in layman's terms. Is it possible to connect with an interested party who is interested in collaboration?

Hello Michael,

I would be interested. As a start I reproduced Tim's results using TA-Lib. There are differences in the timing of trades but the overall result is the same.

A data window of 28 days is the minimum required. Returns vary with the window length:

28 days = 170.8%
29 days = 180.2%
30 days = 193.1%
31 days = 200.9%

P.

217
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 zipline.transforms.ta as ztt

Window = 30

def initialize(context):
context.stock = sid(19920)

def handle_data(context, data):
Results = GetPrices(data)
if Results is None:
return
OpenPrices   = Results[0]
HighPrices   = Results[1]
LowPrices    = Results[2]
ClosePrices  = Results[3]
timeperiod=14)[-1:]
mDI  = ztt.talib.MINUS_DI(HighPrices[context.stock], LowPrices[context.stock], ClosePrices[context.stock], \
timeperiod=14)[-1:]
pDI  = ztt.talib.PLUS_DI(HighPrices[context.stock], LowPrices[context.stock], ClosePrices[context.stock], \
timeperiod=14)[-1:]

if (ADX > 20) and (pDI > mDI):
order(sid(19920), 500)
elif (ADX > 25) and (pDI < mDI):
order(sid(19920), -500)
log.debug('sell')

record(Cash=context.portfolio.cash)

@batch_transform(window_length=Window, refresh_period=0)
def GetPrices(DataPanel):
OpenPrices  = DataPanel['open_price']
HighPrices  = DataPanel['high']
LowPrices   = DataPanel['low']
ClosePrices = DataPanel['close_price']
return OpenPrices, HighPrices, LowPrices, ClosePrices
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

Hi Peter,

Pretty astounding returns. I have a few patterns that are specific ADX patterns. Perhaps we can discuss and develop and backtest our strategy on here? I love this site and would like to collaborate as I have been in the markets for 13 years and might be interested in even launching a fund or a prop firm. If it goes well, we could be a big success for this site! And they could be a big boon for us as well. My email is [email protected].

Thanks for these, after reading this book I have been trying to replicate this strategy as well but have very little programming experience. From my understanding of the text, the higher the ADX of any given security, the more profitable it is to trade this system. That being said, is there a way to limit the universe to whatever stocks have the highest ADX at any given point in time? In other words, instead of simply choosing one security (in this case sid(19920)) and limiting trades to when that security has an ADX higher than 20 or 25, could we add code that would loop through all stocks available, select whichever has the highest ADX and buy/short that stock (given that all the other conditions in the model are met)?

Hello Chris,

Please see attached. Note that there is no real position sizing or money management going on here. Also, it doesn't trade the highest ADX but just all ADX over a threshold.

P.

217
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 zipline.transforms.ta as ztt

Window = 31

def initialize(context):
#context.stock = sid(19920)
set_universe(universe.DollarVolumeUniverse(floor_percentile=99.5, ceiling_percentile=100.0))

def handle_data(context, data):
Results = GetPrices(data)
if Results is None:
return
OpenPrices   = Results[0]
HighPrices   = Results[1]
LowPrices    = Results[2]
ClosePrices  = Results[3]
for stock in data:
timeperiod=14)[-1:]
mDI  = ztt.talib.MINUS_DI(HighPrices[stock], LowPrices[stock], ClosePrices[stock], \
timeperiod=14)[-1:]
pDI  = ztt.talib.PLUS_DI(HighPrices[stock], LowPrices[stock], ClosePrices[stock], \
timeperiod=14)[-1:]

if (ADX > 20) and (pDI > mDI):
order(stock, 5)
elif (ADX > 25) and (pDI < mDI):
order(stock, -5)
log.debug('sell')

record(Cash=context.portfolio.cash)

@batch_transform(window_length=Window, refresh_period=0)
def GetPrices(DataPanel):
OpenPrices  = DataPanel['open_price']
HighPrices  = DataPanel['high']
LowPrices   = DataPanel['low']
ClosePrices = DataPanel['close_price']
return OpenPrices, HighPrices, LowPrices, ClosePrices
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

Peter,

I appreciate the quick response. If I'm interpreting your source code correctly, this would limit the universe to stocks in the top 0.5 percentile in terms of trade volume, and trade any stocks in that universe with and ADX above 20 or 25, right? I guess my question is whether we can further limit the universe to just the stock with the highest ADX? As I mentioned, I am just starting to experiment with coding, but might this be achieved by looping through all stocks in the universe you limited it to, creating a dict of each stock name and its respective ADX, then pulling the max of this dict to pass through the ADX and DI based trading logic?

Hello Chris,

Yes, you are correct on all points. The 0.5% universe is around 31 stocks but it is revised each quarter. I'll have a look at selecting the highest ADX with a view to having fewer, better trades.

P.

Hello Chris,

P.

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 numpy as np

talibMDI = ta.MINUS_DI(timeperiod=14)
talibPDI = ta.PLUS_DI(timeperiod=14)
topN           = 10
rotationPeriod = 30

def initialize(context):
set_universe(universe.DollarVolumeUniverse(floor_percentile=98.00, ceiling_percentile=100.0))
context.dayCount = 0

def handle_data(context, data):
trendingSecurities  = []
trendingValues      = []
trendingDirections  = []
mdi_data  = talibMDI(data)
pdi_data  = talibPDI(data)

if context.dayCount == 0:
#Close open position
for stock in context.portfolio.positions:
if context.portfolio.positions[stock]['amount'] <> 0:
order(stock, -context.portfolio.positions[stock]['amount'])

for stock in data:
# Check if a security is still trading
if data[stock].datetime < get_datetime():
continue
# Check for a pending order
if get_open_orders(stock):
#print get_open_orders(stock)
continue
# Set a limit on the price of a security
if data[stock].close_price > 500.00:
continue
mDI = mdi_data[stock]
pDI = pdi_data[stock]
if pDI > mDI and ADX > 45:
trendingSecurities.append(stock)
trendingDirections.append('Up')
elif pDI < mDI and ADX > 35:
trendingSecurities.append(stock)
trendingDirections.append('Down')
symbols = []
for i in range (0, min(topN, len(trendingValues))):
security = trendingSecurities[np.argsort(trendingValues)[i]]
position = 1800
symbols.append(str(security.symbol))
if trendingDirections[np.argsort(trendingValues)[i]] == 'Up':
order(security, position)
else:
order(security, -position)
print sorted(symbols)
context.dayCount += 1
if context.dayCount == rotationPeriod:
context.dayCount = 0

record(Cash=context.portfolio.cash)

This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

Hi all,
Quick question. I'm new to python, so please excuse what I imagine is a stupid question.

If I'm trying to reference a changing value from one bar to the next, how would I code that?

For example, (context.mDI > context.mDI????) if I'm setting the condition so that the current mDI is greater that the mDI from the previous bar, what would it look like?

Thanks for humoring a newbie.

Best,

Justin Davis

Thanks Peter, this is extremely helpful. Sorry for being a pain, but I have a few more questions that I would really appreciate if you could answer. They are related to the code itself, so shouldn't require much effort on your part.

1) Related to the checks you added (lines 28-37). Maybe I'm interpreting the first (lines 28-30) incorrectly, but my interpretation is that it is checking whether the date/time of the last trade in the given security is less than (and therefore before) the current time. Assuming that there has been at least one trade in the given security at any point in the past, wouldn't this condition always be true? For example, if 10 shares of XYZ were purchased in 2005, even if XYZ was taken off the market in 2006, this condition would still evaluate to true wouldn't it?

As for the second and third checks that follow (lines 31-37), I'm not sure I see their relevancy. The second, as far as I can tell, just prints any open orders, but doesn't do anything with the printed list, correct? The third (lines 35-37) limits the universe of stocks to only those with a current price over $500? I must be interpreting that wrong, as I can't contemplate how this would benefit the strategy. 2) Could you please just briefly explain what the code on line 41 does (if not np.isnan(ADX))? 3) In regard to the dayCount variable, is this just so any stock positions in the portfolio are automatically sold off (or purchased to close a short position) every 30 days? Thanks again for all of your help! I plan on adding a few other elements to this strategy that should help minimize downside risk, and would be happy to share when finished. Hello Justin, One way is to use a fixed-length queue. See: https://www.quantopian.com/posts/stock-price-30-days-ago from collections import deque # in initialize context.MDIvalues = deque(maxlen=2) # append to window context.MDIvalues.append(mDI) # access where index 0 is yesterday and index 1 is today if context.MDIvalues[1] > context.MDIvalues[0] P. Hello Chris, Thanks for taking the time to have a look. To try to answer: 1) 'if data[stock].datetime < get_datetime():' is checking if the price for a security is a 'new' price. Prices are timestamped and prices and their timestamps are carried forward so that the data/price object always has a entry for each security at each time interval. This is a bit of a grey area. Some algos I've written fail if this check is not made. Note that 'continue' means 'don't continue but instead go to the next iteration of the for loop'. With 'if get_open_orders(stock):' please ignore the print statement which is leftover from debugging. What I'm trying to do here is very inelegant but I want to avoid trading a security while I still have an open position caused by a partial fill in the last trade of that security. I've had algos get into real problems where a long position is sold at time t, remains partially filled at t+1, and a short trade in the same security is entered also at t+1. [This is where I have a major conceptual problem with Quantopian which seems to be synchronous with minutely events whereas trading with Interactive Brokers via Trader Workstation or their FIX gateway would be asynchronous and would (mostly) avoid this issue. I wonder if this is because each algo running in Quantopian is single-threaded but, as I say, I have conceptual problems in this area.] With 'if data[stock].close_price > 500.00:' I'm avoiding trading 'BRK 'A'' with a price per share sometimes around$170,000. Note that 'continue' means 'don't continue'.

2) I changed the algo after the recent security issue. When service was first resumed I had problems importing zipline in order to use the TA-Lib library so I switched to the 'supported' TA-Lib functions. For ADX based on 14 periods the first result is available at period 28 with the prior values all being NaNs i.e. Not A Number.

3) For some reason I thought the best results with ADX might come from holding positions for a few days but that is not what I found so maybe the algo is defective. The best results often came by generating a new portfolio every day or after a much larger number of days than I would have guessed i.e. 30 trading days (~ 6 weeks).

Please add some ideas. At the moment the algo does literally nothing with position sizing or handling margin. At times it goes so short that the $1 M starting cash becomes$4M.

P.

Peter - I appreciate the quick response. Thanks for your suggestion.
Best,
Justin

Can anyone take a look at this? I'm having some issues that I identified through comments. Any suggestions are appreciated.

# implementation of Welles Wilder's ADX as given in spreadsheet form here

from collections import deque

# the other methods in your algorithm.

def initialize(context):
#using the NASDAQ100 etf
context.ndx = sid(19920)
#container to count the number of event windows we have cycled through
context.ticks = 0
#Wilder uses a rolling window of 14 days for various smoothing within
#the indicator calculation
context.window_length = 14
#a collection of data containers that will be used during steps of the calculation
context.highs = deque([0] * 2, 2)
context.lows = deque([0] * 2, 2)
context.closes = deque([0] * 2, 2)
context.true_range_bucket = deque([0] * context.window_length, context.window_length)
context.pDM_bucket = deque([0] * context.window_length, context.window_length)
context.mDM_bucket = deque([0] * context.window_length, context.window_length)
context.dx_bucket = deque([0] * context.window_length, context.window_length)
#not sure why I had to define these here, but to print them later when debuggin
#I found that I had to declare them here
context.av_true_range = 0
context.av_pDM = 0
context.av_mDM = 0
context.di_diff = 0
context.di_sum = 0
context.dx = 0
context.pDI = 0
context.mDI = 0
context.mDIvalues = deque(maxlen=2)
context.pDIvalues = deque(maxlen=2)

# Having A problem here

context.mDIvalues.append(mDI)
context.pDIvalues.append(pDI)

pass

# Will be called on every trade event for the securities you specify.

def handle_data(context, data):
#iterate event window counter
context.ticks += 1
#pass high, low, close prices to our rolling containers
context.highs.appendleft(data[sid(19920)].high)
context.lows.appendleft(data[sid(19920)].low)
context.closes.appendleft(data[sid(19920)].close_price)

#ensure no calculation on first window
if context.closes[0] == 0:
high_less_low = 0
high_less_prec_close = 0
low_less_prec_close = 0
high_less_prec_high = 0
prec_low_less_low = 0
pDM_one = 0
mDM_one = 0
else:
high_less_low = context.highs[0]-context.lows[0]
high_less_prec_close = abs(context.highs[0]-context.closes[1])
low_less_prec_close = abs(context.lows[0]-context.closes[1])
high_less_prec_high = context.highs[0]-context.highs[1]
prec_low_less_low = context.lows[1]-context.lows[0]

#calculate the Plus Directional Movement
if high_less_prec_high > prec_low_less_low:
pDM_one = max(high_less_prec_high,0)
else:
pDM_one = 0

#calculate the Minus Directional Movement
if prec_low_less_low > high_less_prec_high:
mDM_one = max(prec_low_less_low,0)
else:
mDM_one = 0

#add the current pDM and mDM to the bucket to aid calculation of the first point in the
#smoothed statistic
context.pDM_bucket.appendleft(pDM_one)
context.mDM_bucket.appendleft(mDM_one)

#calculate the True Range and add to bucket
true_range = max(high_less_low,high_less_prec_close,low_less_prec_close)
context.true_range_bucket.appendleft(true_range)

#once we have collected enough data to have populated the rolling windows adequately
#we can start the meat of the calculation
if context.ticks < (context.window_length + 1):
context.av_true_range = 1
context.av_pDM = 0
context.av_mDM = 0
elif context.ticks == (context.window_length + 1):
context.av_true_range = sum(context.true_range_bucket)
context.av_pDM = sum(context.pDM_bucket)
context.av_mDM = sum(context.mDM_bucket)
else:
context.av_true_range = context.av_true_range - (context.av_true_range/context.window_length) + true_range
context.av_pDM = context.av_pDM - (context.av_pDM/14) + pDM_one
context.av_mDM = context.av_mDM - (context.av_mDM/14) + mDM_one

if context.ticks > context.window_length:
context.pDI = 100 * context.av_pDM / context.av_true_range
context.mDI = 100 * context.av_mDM / context.av_true_range
context.di_diff = abs(context.pDI - context.mDI)
context.di_sum = context.pDI + context.mDI
context.dx = 100 * context.di_diff / context.di_sum

#add to bucket to provide an average of the DX figures that will
#act as a starting point for the rolling ADX calculation
context.dx_bucket.appendleft(context.dx)

if context.ticks == (context.window_length * 2):
elif context.ticks > (context.window_length * 2):

# Having TROUBLE with this Trading Logic
# access where index 0 is yesterday and index 1 is today
# Notice I used -1 for the day before yesterday b/c I wasn't sure what to do but I would like to include that in the buy condition

if (context.mDIvalues[1] > context.pDIvalues[1]) and (context.mDIvalues[1] > context.adxvalues[1]) and (context.mDIvalues[1] < context.mDIvalues[0]) and (context.mDIvalues[0] < context.mDIvalues[-1]):
order(sid(19920), 500)

order(sid(19920), -500)
log.debug('sell')

log.debug('pDI')
log.debug(context.pDI)
log.debug('mDI')
log.debug(context.mDI)

Hello Justin,

I see you've put loads of work into this...but I thought it would be simpler to try your logic using the TA-Lib indicator library (see http://ta-lib.org/). To test current, day-1 and day-2 values I made the queue length 3.

P.

33
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
from collections import deque
import numpy as np

talibMDI = ta.MINUS_DI(timeperiod=14)
talibPDI = ta.PLUS_DI(timeperiod=14)

def initialize(context):
context.ndx = sid(19920)
context.mDIvalues = deque(maxlen=3)
context.pDIvalues = deque(maxlen=3)

def handle_data(context, data):
mdi_data  = talibMDI(data)
pdi_data  = talibPDI(data)

mDI = mdi_data[context.ndx]
pDI = pdi_data[context.ndx]

context.mDIvalues.append(mDI)
context.pDIvalues.append(pDI)

if len(context.mDIvalues) < 3:
return

if (context.mDIvalues[2] > context.pDIvalues[2]) and (context.mDIvalues[2] > context.adxvalues[2]) \
and (context.mDIvalues[2] < context.mDIvalues[1]) and (context.mDIvalues[1] < context.mDIvalues[0]):
order(sid(19920), 500)
elif (context.adxvalues[2] > context.mDIvalues[2]) and (context.pDIvalues[2] > context.mDIvalues[2]) \
order(sid(19920), -500)
log.debug('sell')

record(Cash=context.portfolio.cash)

This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

Ha! Peter, you're the best. I know I'm terrible at this, but I'm obviously working through the process.... I appreciate your feedback and the indicator library resource. Very helpful. Maybe with the support of this community there's hope for me yet.....

Cheers,

Justin

Hello:

I am new to Python, coding in general, and quant finance, but am finding this site really fascinating so far. I watch commodity markets, and would like to try and run this algorithm on the continuous front month corn contract. The data is located on quandl at:

http://www.quandl.com/OFDP-Open-Financial-Data-Project/FUTURE_C1-CBOT-Corn-Futures-Continuous-Contract-1-C1-Front-Month

I read the section on importing quandl data, but was unable to figure out how to do this. I am not sure how to run it without the sid code. I know there is a corn etf, but I would rather not use that. If anyone could provide some advice, I would appreciate it.

Thanks,

Josh

Hello Josh,

I struggle a bit with 'fetch_csv'. I've got as far as reading the data:

def initialize(context):
context.sid = sid(8554)
fetch_csv('http://www.quandl.com/api/v1/datasets/OFDP/FUTURE_C1.csv?&trim_start=1959-07-01&trim_end=2013-11-20&sort_order=desc',
symbol      = 'Corn',
date_column = 'Date',
date_format = '%y/%m/%d',
pre_func    =  show_df)

def show_df(df):
return df

def handle_data(context, data):
pass

P.

Thanks Peter, I will play around with this over the weekend.

Reviving an old post here... this code is fantastic and has been a huge help to me. Does the OP or anyone else know why the summing on lines 92-94 of the original code are not divided by context.window_length? I would imagine these averages need to be divided by an N rather than just be a sum.

Amending this produces a negligible difference in my backtest so I'm just wondering if this summation is intentional for the subsequent moving average calculation?

Hi, I have zero knowledge on algo program but I know well on DMI.

DMI has its problems too. For example, when stock price gap up, +DI cross above -DI at a stock price that cannot be filled and therefore traders must carefully use DMI in backtesting that may give wrong results. I wish I know how to read the algo program in collaborating a better DMI.

Is that really a problem though? Taking gaps into account is part of the advantage of DMI (and "true" range in general)

If a stock price is opened gap up, say closed 20 and open gap up 22, DMI may show buy signal at 20.80 but there is no 20.80 to buy since price already gap up at 22 opening. Thus, it will have a wrong result if not carefully track down the proper prices using DMI.

I think you're getting too granular if you're telling your algorithm to buy only at the exact price the DI lines cross (slippage would make that improbable in real trading anyway). The purpose of the ADX is to indicate a trend, thus if a price gaps up to 22 and the +DI line is now above the -DI line, I would take that as a valid entry signal because the trend has turned bullish (depending on ADX readings and other factors).

Is there a way we can add to print the ROC and ADX below the backtest?

has this strategy been ported to the Quantopian2 APIs? I tried cloning it but it won't build in its current form

Can any one please post a working code for the ADX here. None of the previous codes are compiling. I tried cloning and not able to build it.

Hi Vladimir: Thank you very much for so fast reply. I will try to use this code and probably will be asking you for more help as I am new to Python. Thanks

Ok well number one: The post above me is ummm interesting. Also, given how many times the OG algorithm has been copied, I'm surprised that no one has bothered to check the leverage of this algorithm... It's totally out of control.

37
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
# implementation of Welles Wilder's ADX as given in spreadsheet form here

from collections import deque

# Put any initialization logic here.  The context object will be passed to
# the other methods in your algorithm.
def initialize(context):
context.minLeverage = [0] #ADDED BY JACOB SHRUM
context.maxLeverage = [0] #ADDED BY JACOB SHRUM
#using the NASDAQ100 etf
context.ndx = sid(19920)
#container to count the number of event windows we have cycled through
context.ticks = 0
#Wilder uses a rolling window of 14 days for various smoothing within
#the indicator calculation
context.window_length = 14
#a collection of data containers that will be used during steps of the calculation
context.highs = deque([0] * 2, 2)
context.lows = deque([0] * 2, 2)
context.closes = deque([0] * 2, 2)
context.true_range_bucket = deque([0] * context.window_length, context.window_length)
context.pDM_bucket = deque([0] * context.window_length, context.window_length)
context.mDM_bucket = deque([0] * context.window_length, context.window_length)
context.dx_bucket = deque([0] * context.window_length, context.window_length)
#not sure why I had to define these here, but to print them later when debuggin
#I found that I had to declare them here
context.av_true_range = 0
context.av_pDM = 0
context.av_mDM = 0
context.di_diff = 0
context.di_sum = 0
context.dx = 0
context.pDI = 0
context.mDI = 0
pass

# Will be called on every trade event for the securities you specify.
def handle_data(context, data):
#iterate event window counter
context.ticks += 1
#pass high, low, close prices to our rolling containers
context.highs.appendleft(data[sid(19920)].high)
context.lows.appendleft(data[sid(19920)].low)
context.closes.appendleft(data[sid(19920)].close_price)

#ensure no calculation on first window
if context.closes[0] == 0:
high_less_low = 0
high_less_prec_close = 0
low_less_prec_close = 0
high_less_prec_high = 0
prec_low_less_low = 0
pDM_one = 0
mDM_one = 0
else:
high_less_low = context.highs[0]-context.lows[0]
high_less_prec_close = abs(context.highs[0]-context.closes[1])
low_less_prec_close = abs(context.lows[0]-context.closes[1])
high_less_prec_high = context.highs[0]-context.highs[1]
prec_low_less_low = context.lows[1]-context.lows[0]

#calculate the Plus Directional Movement
if high_less_prec_high > prec_low_less_low:
pDM_one = max(high_less_prec_high,0)
else:
pDM_one = 0

#calculate the Minus Directional Movement
if prec_low_less_low > high_less_prec_high:
mDM_one = max(prec_low_less_low,0)
else:
mDM_one = 0

#add the current pDM and mDM to the bucket to aid calculation of the first point in the
#smoothed statistic
context.pDM_bucket.appendleft(pDM_one)
context.mDM_bucket.appendleft(mDM_one)

#calculate the True Range and add to bucket
true_range = max(high_less_low,high_less_prec_close,low_less_prec_close)
context.true_range_bucket.appendleft(true_range)

#once we have collected enough data to have populated the rolling windows adequately
#we can start the meat of the calculation
if context.ticks < (context.window_length + 1):
context.av_true_range = 1
context.av_pDM = 0
context.av_mDM = 0
elif context.ticks == (context.window_length + 1):
context.av_true_range = sum(context.true_range_bucket)
context.av_pDM = sum(context.pDM_bucket)
context.av_mDM = sum(context.mDM_bucket)
else:
context.av_true_range = context.av_true_range - (context.av_true_range/context.window_length) + true_range
context.av_pDM = context.av_pDM - (context.av_pDM/14) + pDM_one
context.av_mDM = context.av_mDM - (context.av_mDM/14) + mDM_one

if context.ticks > context.window_length:
context.pDI = 100 * context.av_pDM / context.av_true_range
context.mDI = 100 * context.av_mDM / context.av_true_range
context.di_diff = abs(context.pDI - context.mDI)
context.di_sum = context.pDI + context.mDI
context.dx = 100 * context.di_diff / context.di_sum

#add to bucket to provide an average of the DX figures that will
#act as a starting point for the rolling ADX calculation
context.dx_bucket.appendleft(context.dx)

if context.ticks == (context.window_length * 2):
elif context.ticks > (context.window_length * 2):

if (context.adx > 20) and (context.pDI > context.mDI):
order(sid(19920), 500)
elif (context.adx > 25) and (context.pDI < context.mDI):
order(sid(19920), -500)
log.debug('sell')

log.debug('pDI')
log.debug(context.pDI)
log.debug('mDI')
log.debug(context.mDI)

#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#MASSIVE OVER LEVERAGE ISSUES: THESE CAN BE CAUGHT BY DOING THE FOLLOWING
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

#if the current leverage is greater than the value in MaxLeverage, clear max leverage then append the current leverage. a similar method is used for minLeverage

#max leverage is set up as an array so that one can more quickly log all leverages  find the mean leverage if they want.
for L in context.maxLeverage: #ADDED BY JACOB SHRUM
if context.account.leverage > L: #ADDED BY JACOB SHRUM
context.maxLeverage = [] #ADDED BY JACOB SHRUM
for L in context.minLeverage: #ADDED BY JACOB SHRUM
if context.account.leverage < L: #ADDED BY JACOB SHRUM
context.minLeverage = [] #ADDED BY JACOB SHRUM
record(Max_Leverage = context.maxLeverage[0]) #ADDED BY JACOB SHRUM
record(Min_Leverage = context.minLeverage[0]) #ADDED BY JACOB SHRUM
record(Instantaneous_Leverage = context.account.leverage)  #ADDED BY JACOB SHRUM

There was a runtime error.

@Jacob it might be because of a difference between the Q platform 2013 and now. The original gives 193% returns and yours 3347%.

Anyways, I've tried using ADX in my algos (the talib version, not this implementation) and was unimpressed with it. Anybody gotten anything useful out of it?

@atiredmachine
It is most likely due to the fact that the starting capital is 1,000,000 and it is trading every minute
even when using :

order_target_percent(stock, 1.0)

it still over exposes.
IMO Indicators are inherently pretty useless... I use them to determine when to decrease leverage and or pull out of the market entirely - thats it.

@Jacob I believe Quantopian used to have a dropdown for daily or minute mode. This algo was probably written for daily mode.

My results using Vladimir's code is nowhere near as good as the original post. Basically buy when adx>25 and p>n, sell when adx>25 and n>p

14
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 talib

def initialize(context):
context.qqq = sid(19920)

stk = symbol('qqq')
period = 14

H = data.history(stk,'high', 2*period, '1d').dropna()
L = data.history(stk,'low', 2*period, '1d').dropna()
C = data.history(stk,'price', 2*period, '1d').dropna()

ta_nDI = talib.MINUS_DI(H, L, C, period)
ta_pDI = talib.PLUS_DI(H, L, C, period)

nDI = ta_nDI[-1]
pDI = ta_pDI[-1]

record(close_price = data.current(context.qqq,'close'))

order_target_percent(context.qqq, 1)
order_target_percent(context.qqq, -1)

print context.portfolio.positions[context.qqq].amount
There was a runtime error.

Wow Vladimir, your version performed 2x better than the benchmark. I guess going 100% long or short on QQQ isn't the greatest approach (like I originally did), since it is such an uptrending stock. Definitely like your 80/20 split method more. Thank you for commenting

Can anyone help creating a sample source code for me to look into soybean oil futures 2nd continuous contract month using ADX technical trend indicator? If can, please include the stop and reverse trades with minimum 1 lot, a 15-minute high low close data with ADX lines -- +DI, -DI and ADX lines. Many thanks.

The ADX indicator is currently not 'built-in' as a factor. However, one can create a custom factor. Something like this

"""
Average Directional Movement Index
Momentum indicator. Smoothed DX
**Default Inputs:** USEquityPricing.high, USEquityPricing.low, USEquitypricing.close
**Default Window Length:** 29

"""

inputs = [USEquityPricing.high, USEquityPricing.low, USEquityPricing.close]
window_length = 29

def compute(self, today, assets, out, high, low, close):

# positive directional index
plus_di = 100 * np.cumsum(plus_dm_helper(high, low) / trange_helper(high, low, close), axis=0)

# negative directional index
minus_di = 100 * np.cumsum(minus_dm_helper(high, low) / trange_helper(high, low, close), axis=0)

# full dx with 15 day burn-in period
dx_frame = (np.abs(plus_di - minus_di) / (plus_di + minus_di) * 100.)[14:]

# 14-day EMA
span = 14.
decay_rate = 2. / (span + 1.)
weights = weights_long = np.full(span, decay_rate, float) ** np.arange(span + 1, 1, -1)

# return EMA
out[:] = np.average(dx_frame, axis=0, weights=weights)

The code above relies on several helper functions.

def plus_dm_helper(high, low):
"""
Returns positive directional movement. Abstracted for use with more complex factors

Parameters
----------
high : np.array
matrix of high prices
low : np.array
matrix of low prices

Returns
-------
np.array : matrix of positive directional movement

"""
# get daily differences between high prices
high_diff = (high - np.roll(high, 1, axis=0))[1:]

# get daily differences between low prices
low_diff = (np.roll(low, 1, axis=0) - low)[1:]

# matrix of positive directional movement
return np.where(((high_diff > 0) | (low_diff > 0)) & (high_diff > low_diff), high_diff, 0.)

def minus_dm_helper(high, low):
"""
Returns negative directional movement. Abstracted for use with more complex factors

Parameters
----------
high : np.array
matrix of high prices
low : np.array
matrix of low prices

Returns
-------
np.array : matrix of negative directional movement

"""
# get daily differences between high prices
high_diff = (high - np.roll(high, 1, axis=0))[1:]

# get daily differences between low prices
low_diff = (np.roll(low, 1, axis=0) - low)[1:]

# matrix of megative directional movement
return np.where(((high_diff > 0) | (low_diff > 0)) & (high_diff < low_diff), low_diff, 0.)

def trange_helper(high, low, close):
"""
Returns true range

http://www.macroption.com/true-range/

Parameters
----------
high : np.array
matrix of high prices
low : np.array
matrix of low prices
close: np.array
matrix of close prices

Returns
-------
np.array : matrix of true range

"""
# define matrices to be compared
close = close[:-1]
high = high[1:]
low = low[1:]

# matrices for comparison
high_less_close = high - close
close_less_low = close - low
high_less_low = high - low

# return maximum value for each cel
return np.maximum(high_less_close, close_less_low, high_less_low)

There is a great post which provides custom factors for many technical signals. Check it out https://www.quantopian.com/posts/ta-lib-for-pipeline.

Good luck.

Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

This is awesome. Thank you so much.

Where does the SID (19920) data map come from？ Thanks！

The order function used in the source code is from the zipline library. Why is there no import zipline in the source code? thanks