// Stochastic oscillator family and Williams %R.
///|
/// Stochastic decomposition: the %K and %D lines, both of input length.
pub struct Stoch {
k : Array[Double]
d : Array[Double]
}
///|
/// Raw %K series: position of each close within its `period`-bar high/low
/// range, scaled to 0..100. A flat window (highest high equals lowest low)
/// reads 0; a window containing NaN or a NaN close yields NaN. Computed in
/// O(n) via the base rolling extremes.
fn fast_k_series(
candles : @base.Candles,
period : Int32,
) -> Array[Double] raise @base.TaError {
let hh = @base.rolling_max(candles.high, period)
let ll = @base.rolling_min(candles.low, period)
let n = candles.close.length()
let out : Array[Double] = []
for i in 0.. Stoch raise @base.TaError {
@base.validate_candles(candles)
if period <= 0 || d_period <= 0 {
raise @base.InvalidPeriod("all periods must be positive")
}
let k = fast_k_series(candles, period)
let d = @overlap.sma(k, d_period)
{ k, d, }
}
///|
/// Number of leading NaN values produced by `stoch`:
/// `(period - 1) + (k_slowing - 1) + (d_period - 1)`.
pub fn stoch_lookback(
period : Int32,
k_slowing : Int32,
d_period : Int32,
) -> Int32 {
period - 1 + (k_slowing - 1) + (d_period - 1)
}
///|
/// Stochastic Oscillator (TA-Lib `STOCH`): raw %K is smoothed by an SMA over
/// `k_slowing` bars (slow %K), and %D is an SMA of slow %K over `d_period`
/// bars.
pub fn stoch(
candles : @base.Candles,
period : Int32,
k_slowing~ : Int32,
d_period~ : Int32,
) -> Stoch raise @base.TaError {
@base.validate_candles(candles)
if period <= 0 || k_slowing <= 0 || d_period <= 0 {
raise @base.InvalidPeriod("all periods must be positive")
}
let fast = fast_k_series(candles, period)
let k = @overlap.sma(fast, k_slowing)
let d = @overlap.sma(k, d_period)
{ k, d, }
}
///|
/// Number of leading NaN values produced by `willr` for the given period.
pub fn willr_lookback(period : Int32) -> Int32 {
period - 1
}
///|
/// Williams %R: `-100 * (highest_high - close) / (highest_high - lowest_low)`
/// over the window, computed through the identity `%R = %K - 100`. A flat
/// window therefore reads -100, mirroring the raw %K rule (reads 0).
pub fn willr(
candles : @base.Candles,
period : Int32,
) -> Array[Double] raise @base.TaError {
@base.validate_candles(candles)
let k = fast_k_series(candles, period)
let n = candles.close.length()
let out : Array[Double] = []
for i in 0..