// Moving averages: SMA, EMA, WMA, HMA, DEMA, TEMA and the shared
// `moving_average` dispatcher used by APO/PPO.
///|
/// Number of leading NaN values produced by `sma` for the given period.
pub fn sma_lookback(period : Int32) -> Int32 {
period - 1
}
///|
/// Simple moving average.
///
/// Output has the same length as `data`; positions before the first full
/// window are NaN, and a NaN anywhere inside a window makes that position
/// NaN.
pub fn sma(
data : Array[Double],
period : Int32,
) -> Array[Double] raise @base.TaError {
@base.validate_series(data, period)
let out = @base.rolling_sum(data, period)
let pd = period.to_double()
for i in 0.. Array[Double] {
let n = data.length()
let out : Array[Double] = []
let fv = if first_valid < 0 { 0 } else { first_valid }
let warm = fv + period - 1
if warm >= n {
for _ in 0.. Int32 {
period - 1
}
///|
/// Exponential moving average with smoothing factor `2 / (period + 1)`,
/// seeded by the SMA of the first `period` values (TA-Lib convention).
pub fn ema(
data : Array[Double],
period : Int32,
) -> Array[Double] raise @base.TaError {
@base.validate_series(data, period)
ema_from(data, period, 0)
}
///|
/// Number of leading NaN values produced by `wma` for the given period.
pub fn wma_lookback(period : Int32) -> Int32 {
period - 1
}
///|
/// Linearly weighted moving average: the newest bar of the window gets
/// weight `period`, the oldest gets weight 1.
pub fn wma(
data : Array[Double],
period : Int32,
) -> Array[Double] raise @base.TaError {
@base.validate_series(data, period)
let n = data.length()
let denom = (period * (period + 1) / 2).to_double()
let pd = period.to_double()
let rsum = @base.rolling_sum(data, period)
let out : Array[Double] = []
let mut acc = @base.nan()
for i in 0.. Int32 {
period + @base.isqrt(period) - 2
}
///|
/// Hull moving average: `WMA(2 * WMA(n/2) - WMA(n), sqrt(n))`, smoothing the
/// lag of the weighted average against the price series.
pub fn hma(
data : Array[Double],
period : Int32,
) -> Array[Double] raise @base.TaError {
@base.validate_series(data, period)
let half = if period / 2 < 1 { 1 } else { period / 2 }
let sq = @base.isqrt(period)
let w1 = wma(data, half)
let w2 = wma(data, period)
let n = data.length()
let diff : Array[Double] = []
for i in 0.. Int32 {
2 * (period - 1)
}
///|
/// Double exponential moving average: `2 * EMA - EMA(EMA)`.
pub fn dema(
data : Array[Double],
period : Int32,
) -> Array[Double] raise @base.TaError {
@base.validate_series(data, period)
let e1 = ema_from(data, period, 0)
let e2 = ema_from(e1, period, period - 1)
let n = data.length()
let out : Array[Double] = []
for i in 0.. Int32 {
3 * (period - 1)
}
///|
/// Triple exponential moving average:
/// `3 * EMA - 3 * EMA(EMA) + EMA(EMA(EMA))`.
pub fn tema(
data : Array[Double],
period : Int32,
) -> Array[Double] raise @base.TaError {
@base.validate_series(data, period)
let e1 = ema_from(data, period, 0)
let e2 = ema_from(e1, period, period - 1)
let e3 = ema_from(e2, period, 2 * (period - 1))
let n = data.length()
let out : Array[Double] = []
for i in 0.. Array[Double] raise @base.TaError {
match ma_type {
@base.Sma => sma(data, period)
@base.Ema => ema(data, period)
}
}
///|
/// Number of leading NaN values produced by `midpoint` for the given period.
pub fn midpoint_lookback(period : Int32) -> Int32 {
period - 1
}
///|
/// Midpoint over a rolling window: `(max + min) / 2` of the last `period`
/// values. A NaN inside the window yields NaN.
pub fn midpoint(
data : Array[Double],
period : Int32,
) -> Array[Double] raise @base.TaError {
@base.validate_series(data, period)
let rmax = @base.rolling_max(data, period)
let rmin = @base.rolling_min(data, period)
let n = rmax.length()
let out : Array[Double] = []
for i in 0..