So in my continuing series of C# implementations of common indicators, I spent all of yesterday working out the ADX. The ADX was originally developed by J. Welles Wilder and published in his book New Concepts in Technical Trading Systems, June 1978.
Various sites report implementations, but always define it in terms of indicators which have varying definitions. Namely FxStreet defines the general concept here, but only in terms of DI+/-, TR and DM+/-, each of which has varying definitions. ForexReal defines ADX here, but uses a completely different concept of TR. Stockwiz has a good overview of all of the variables used (at least they define all of their variables) here. I will be defining the ADX all the way down to price action (what you need when you are coding this…) as best I can to the original definition by J Welles Wilder.
Lets start by defining the Directional Move indicators
Directional Move Indicator
= the Positive Direction Movement Indicator
= the Negative Direction Movement Indicator
We use two helper variables to track the delta extreme price changes from yesterday
If today’s range is entirely within yesterday’s range, or the range is the same, then there has been no directional movement.
If the range moved up, then there has been a positive directional move.
If the range moved down, then there has been a negative directional move.
Average Directional Move Indicator
= the Average Positive Direction Movement Indicator for N periods
= the Average Negative Direction Movement Indicator for N periods
The Welles Wilder Average is used here to stay true to form, but this average introduces a lot of latency, so I will also implement an exponential moving average.
Alternatively, the formula for the ADM with an exponential moving average is shown below.
True Range
is the true range for a given period. It is a measure of volatility and takes into account any overnight gap in the period’s price.
Average True Range
is the average true range for N periods.
The Welles Wilder Average is used here again to stay true to form, but for my C# implementation I will be using the exponential moving average.
Alternatively the ATR can be calculated with the exponential moving average as shown below.
Directional Index
is the average positive directional movement indicator normalized by the average true range.
is the average negative directional movement indicator normalized by the average true range.
The directional index calculation is very straight forward and shown below.
Directional Movement Index
- the directional movement index is the difference of the directional indices over the sum directional indices.
Average Directional Movement Index
is the average directional movement calculated over N periods.
Staying true to form again, the Welles Wilder Average is used below.
Again, you can alternatively use the exponential moving average as shown below.
C# implementation
So after the laborious research to determine exactly what the ADX is and should be (exponential vs Welles Wilder Average), here is the C# implementation of this indicator.
//The Average Directional Indicator
if(barHistory[timePeriod].Count > 1)
{
//Directional Movement Indicator
double deltaHigh = cBar.High - barHistory[timePeriod][barHistory[timePeriod].Count - 2].High;
double deltaLow = barHistory[timePeriod][barHistory[timePeriod].Count - 2].Low - cBar.Low;
if((deltaHigh < 0 && deltaLow < 0) || (deltaHigh == deltaLow))
{
sig.DmPlus = 0;
sig.DmMinus = 0;
}
else
if(deltaHigh > deltaLow)
{
sig.DmPlus = deltaHigh;
sig.DmMinus = 0;
}
else
if (deltaHigh < deltaLow)
{
sig.DmPlus = 0;
sig.DmMinus = deltaLow;
}
//Average Directional Movement Indicator
if(barHistory[timePeriod].Count <= 2)
{
sig.AdmPlus = sig.DmPlus;
sig.AdmMinus = sig.DmMinus;
}
else
{
double a = (double) 2/(double) (calc.DMIPeriod + 1);
sig.AdmPlus = sig.DmPlus * a +
symbolHistory[timePeriod][symbolHistory[timePeriod].Count-2].AdmPlus * (1-a);
sig.AdmMinus = sig.DmMinus * a +
symbolHistory[timePeriod][symbolHistory[timePeriod].Count - 2].AdmMinus * (1 - a);
}
//True Range Indicator
sig.TrueRange = Math.Max(Math.Abs(cBar.High - cBar.Low),
Math.Max(
Math.Abs(cBar.High - barHistory[timePeriod][barHistory[timePeriod].Count - 2].Close),
Math.Abs(barHistory[timePeriod][barHistory[timePeriod].Count - 2].Close - cBar.Low)));
//Average True Range Indicator
if(barHistory[timePeriod].Count <= 2)
{
sig.AverageTrueRange = sig.TrueRange;
}
else
{
double a = (double)2 / (double)(calc.ADXPeriod + 1);
sig.AverageTrueRange = sig.TrueRange * a +
symbolHistory[timePeriod][symbolHistory[timePeriod].Count - 2].AverageTrueRange * (1 - a);
}
//Directional index Indicator
if (sig.TrueRange != 0)
{
sig.DiPlus = 100 * sig.AdmPlus/sig.TrueRange;
sig.DiMinus = 100 * sig.AdmMinus/sig.TrueRange;
}
else
{
sig.DiPlus = symbolHistory[timePeriod][symbolHistory[timePeriod].Count - 2].DiPlus;
sig.DiMinus = symbolHistory[timePeriod][symbolHistory[timePeriod].Count - 2].DiMinus;
}
//Directional Movement Index Indicator
if ((sig.DiPlus + sig.DiMinus) != 0)
sig.Dx = Math.Abs((sig.DiPlus - sig.DiMinus)) / (sig.DiPlus + sig.DiMinus) * 100;
else
sig.Dx = symbolHistory[timePeriod][symbolHistory[timePeriod].Count - 2].Dx;
//Average Directional Movement Indicator
if (barHistory[timePeriod].Count <= 2)
{
sig.Adx = sig.Dx;
}
else
{
double a = (double)2 / (double)(calc.DMIPeriod + 1);
sig.Adx = sig.Dx * a +
symbolHistory[timePeriod][symbolHistory[timePeriod].Count - 2].Adx * (1 - a);
}
}
Sample Data
This is a sample data set consisting of the NYSE A/D 1-min bar data (”Close”) from August 28, 2007 and the 14 period ADX in red.
Recent Comments