Traders' Tip text
DIX seemed to correlate nearly perfectly with other oscillators like RSI and StochD at their extremes, largely providing the same overbought/oversold information. Also, since DIX doesn’t oscillate within a defined range, we found it difficult to use in backtesting without dynamically analyzing the indicator.
Not to give up on putting DIX to use, after adjusting the formula slightly to measure the percentage distance of price with respect to the moving average (the article’s formula is the reverse), we created a normalized indicator by dividing the modified DIX with ATRP. The normalization helps confine DIX to a range of values even during extreme movements. Finally, by tallying the number of stocks in a DataSet whose normalized DIX is above a given value, we can create a market breadth indicator that gives us the percentage of “bullish” issues. For the sake of a name we’ll call it
Bullish Disparity Percent. The formulation is inspired by the
Bullish Percent Index, which is based on Point and Figure signals.
Figure 1. As evidenced by the vertical lines, when Bullish Disparity Percent rises from under 5 it’s been a good time to be long the S&P 100.
WealthScript Code (C#)
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using WealthLab;
using WealthLab.Indicators;
namespace WealthLab.Strategies
{
public class BullishDisparityPercent : WealthScript
{
StrategyParameter _pctDix;
StrategyParameter _trigger;
public BullishDisparityPercent()
{
_pctDix = CreateParameter("Pct Disparity", 2.5, 0, 8, 0.5);
_trigger = CreateParameter("Trigger", 5, 1, 20, 1);
}
public DataSeries DIXN(Bars bars, int dixPeriod, int atrPeriod)
{
DataSeries ema = EMA.Series(bars.Close, dixPeriod, EMACalculation.Modern);
DataSeries atr = ATRP.Series(bars, atrPeriod);
DataSeries dixN = 100 * (bars.Close/ema - 1) / atr;
dixN.Description = "DIXN(" + dixPeriod + "," + atrPeriod + ")";
return dixN;
}
public DataSeries BullishDIX (int dixPeriod, int atrPeriod, double disparity)
{
DataSeries dixMO = new DataSeries(Bars, "dixMO");
DataSeries contribs = new DataSeries(Bars, "Contributing Symbol Count");
foreach(string symbol in DataSetSymbols)
{
SetContext(symbol, true);
PrintStatusBar("Processing: " + symbol);
DataSeries dixN = DIXN(Bars, dixPeriod, atrPeriod);
for (int bar = Bars.FirstActualBar; bar < Bars.Count; bar ++)
{
contribs[bar]++;
if( dixN[bar] > disparity )
dixMO[bar]++;
}
RestoreContext();
}
Community.Components.Utility u = new Community.Components.Utility(this);
dixMO = 100 * dixMO / contribs;
dixMO.Description = u.GetDataSetName() + " Bullish Disparity Percent(" + dixPeriod + "," + atrPeriod + "," + disparity + ")";
return dixMO;
}
protected override void Execute()
{
ChartPane dixPane = CreatePane(50, true, true);
DataSeries dixBull = BullishDIX(100, 10, _pctDix.Value);
PlotSeries(dixPane, dixBull, Color.Green, LineStyle.Solid, 1);
// Find an associated index
ChartPane idxPane = CreatePane( 100, true, true );
idxPane.LogScale = true;
Bars idxBars = null;
if (DataSetSymbols[0] == "AAPL")
idxBars = GetExternalSymbol(".NDX", true);
else if (DataSetSymbols[2] == "ABT")
idxBars = GetExternalSymbol(".SPX", true);
else
idxBars = GetExternalSymbol(".DJI", true);
PlotSymbol(idxPane, idxBars, Color.Green, Color.Red);
// Highlight buy zones
for (int bar = 100; bar < Bars.Count; bar++)
{
if( dixBull[bar - 1] < _trigger.Value && CrossOver(bar, dixBull, _trigger.Value) )
SetBackgroundColor(bar, Color.Aquamarine);
}
}
}
}