// Average Directional Index with +DI / -DI (J. Welles Wilder).
///|
/// ADX decomposition: the ADX trend-strength line plus the +DI and -DI
/// directional indicators. All three arrays have the input length.
pub struct AdxResult {
adx : Array[Double]
plus_di : Array[Double]
minus_di : Array[Double]
}
///|
fn dm_plus(candles : @base.Candles, i : Int32) -> Double {
let up = candles.high[i] - candles.high[i - 1]
let down = candles.low[i - 1] - candles.low[i]
if @base.is_nan(up) || @base.is_nan(down) {
return @base.nan()
}
if up > down && up > 0.0 {
up
} else {
0.0
}
}
///|
fn dm_minus(candles : @base.Candles, i : Int32) -> Double {
let up = candles.high[i] - candles.high[i - 1]
let down = candles.low[i - 1] - candles.low[i]
if @base.is_nan(up) || @base.is_nan(down) {
return @base.nan()
}
if down > up && down > 0.0 {
down
} else {
0.0
}
}
///|
/// Number of leading NaN values produced by `adx` for the given period:
/// `2 * period - 1` (the DI lines themselves are valid from `period`).
pub fn adx_lookback(period : Int32) -> Int32 {
2 * period - 1
}
///|
/// Average Directional Index.
///
/// True range and directional movement are accumulated with Wilder's
/// smoothing (`s = s - s / period + current`, seeded by a simple sum over
/// the first `period` changes). +DI/-DI are valid from index `period`, and
/// ADX (a Wilder average of the DX) from index `2 * period - 1`. A zero
/// smoothed true range (perfectly flat window) maps the DI lines to 0.
pub fn adx(
candles : @base.Candles,
period : Int32,
) -> AdxResult raise @base.TaError {
@base.validate_candles(candles)
if period < 2 {
raise @base.InvalidPeriod("ADX period must be at least 2, got \{period}")
}
let n = candles.close.length()
if period > n {
raise @base.NotEnoughData("period \{period} exceeds input length \{n}")
}
let pd = period.to_double()
let plus_di : Array[Double] = []
let minus_di : Array[Double] = []
for _ in 0.. period
if seeded {
for i in 1..<(period + 1) {
let t = @base.true_range(candles, i)
let p = dm_plus(candles, i)
let m = dm_minus(candles, i)
if @base.is_nan(t) || @base.is_nan(p) || @base.is_nan(m) {
seeded = false
break
}
s_tr += t
s_p += p
s_m += m
}
}
if seeded {
plus_di[period] = if s_tr == 0.0 { 0.0 } else { 100.0 * s_p / s_tr }
minus_di[period] = if s_tr == 0.0 { 0.0 } else { 100.0 * s_m / s_tr }
for i in (period + 1).. first {
let mut sum = 0.0
let mut ok = true
for i in period..<(first + 1) {
if @base.is_nan(dx[i]) {
ok = false
break
}
sum += dx[i]
}
if ok {
let mut a = sum / pd
adx_out[first] = a
for i in (first + 1)..