Wilder's directional movement — the two legs of the Directional
Movement System (Wilder 1978), and the per-bar term every study in the
ADX family starts from:
up = high[i] − high[i−1] dn = low[i−1] − low[i] +DM= up when up > dn and up >0, else0 −DM= dn when dn > up and dn >0, else0
The rule is "how far did this bar move outside the previous bar's range,
on the side that moved further" — an inside bar (both extremes contained)
moves in neither direction, and an outside bar counts only its larger
excursion. At most one leg is non-zero on any bar, and a tie (up === dn, including the 0 === 0 of an inside bar) makes both zero: the two
strict comparisons are what makes the split exhaustive without needing a
third case.
Why a kernel and not a loop inside the study
The studies README asks for the loop to live here (a study is
options-validation plus kernel calls), and the split earns it on the same
grounds upDownLegValues did: the obvious one-liners are wrong in a
case each. Math.max(up, 0) ignores the comparison between the legs, so
an outside bar would count in both directions and +DI + −DI would no
longer be the total directional movement it is defined to be. And a
>= 0 anywhere would make a flat pair report movement.
Missing cells
NaN marks a gap ([PND-STUDYBOX]). A comparison against NaN is false,
so an unguarded version would map an unknown move to a flat one —
"zero movement" and "no answer" are exactly the two readings the family's
gap behaviour depends on. Both legs are therefore explicitly NaN
whenever either bar's extreme is missing, which costs two bars for one
missing cell: the bar itself and its successor, for which it is prev.
DM[0] is NaN: the first bar has no predecessor, so its directional
movement is undefined rather than zero. Callers smoothing this should pass
start = 1, as atrValues does with TR[0].
Wilder's directional movement — the two legs of the Directional Movement System (Wilder 1978), and the per-bar term every study in the
ADXfamily starts from:The rule is "how far did this bar move outside the previous bar's range, on the side that moved further" — an inside bar (both extremes contained) moves in neither direction, and an outside bar counts only its larger excursion. At most one leg is non-zero on any bar, and a tie (
up === dn, including the0 === 0of an inside bar) makes both zero: the two strict comparisons are what makes the split exhaustive without needing a third case.Why a kernel and not a loop inside the study
The studies README asks for the loop to live here (a study is options-validation plus kernel calls), and the split earns it on the same grounds upDownLegValues did: the obvious one-liners are wrong in a case each.
Math.max(up, 0)ignores the comparison between the legs, so an outside bar would count in both directions and+DI + −DIwould no longer be the total directional movement it is defined to be. And a>= 0anywhere would make a flat pair report movement.Missing cells
NaNmarks a gap ([PND-STUDYBOX]). A comparison againstNaNisfalse, so an unguarded version would map an unknown move to a flat one — "zero movement" and "no answer" are exactly the two readings the family's gap behaviour depends on. Both legs are therefore explicitlyNaNwhenever either bar's extreme is missing, which costs two bars for one missing cell: the bar itself and its successor, for which it isprev.DM[0]isNaN: the first bar has no predecessor, so its directional movement is undefined rather than zero. Callers smoothing this should passstart = 1, as atrValues does withTR[0].O(N), one pass, two allocations.