// Ultimate Oscillator (Larry Williams): a weighted blend of buying-pressure
///|
fn fmin(a : Double, b : Double) -> Double {
if a < b {
a
} else {
b
}
}
///|
fn fmax(a : Double, b : Double) -> Double {
if a > b {
a
} else {
b
}
}
// ratios over three periods.
///|
/// Number of leading NaN values produced by `ultosc`: the longest period
/// `p3` (each output needs `p3` buying-pressure / true-range values).
pub fn ultosc_lookback(p3 : Int32) -> Int32 {
p3
}
///|
/// Ultimate Oscillator.
///
/// Buying pressure is `close - min(low, prev_close)` and true range is
/// `max(high, prev_close) - min(low, prev_close)`. For each of the three
/// periods the ratio `sum(BP) / sum(TR)` is taken, then blended as
/// `100 * (4 * avg1 + 2 * avg2 + avg3) / 7` with `avg1 <= avg2 <= avg3` the
/// short / mid / long ratios. A window whose true-range sum is zero, or that
/// contains NaN, yields NaN. The periods must satisfy `p1 <= p2 <= p3`.
pub fn ultosc(
candles : @base.Candles,
p1~ : Int32,
p2~ : Int32,
p3~ : Int32,
) -> Array[Double] raise @base.TaError {
@base.validate_candles(candles)
if p1 <= 0 || p2 <= 0 || p3 <= 0 {
raise @base.InvalidPeriod("all periods must be positive")
}
if p1 > p2 || p2 > p3 {
raise @base.InvalidPeriod("periods must satisfy p1 <= p2 <= p3")
}
let n = candles.close.length()
// Buying pressure and true range per bar; index 0 has no previous close
// and is never read (outputs start at `p3`).
let bp : Array[Double] = []
let tr : Array[Double] = []
bp.push(@base.nan())
tr.push(@base.nan())
for i in 1.. (Double, Double, Bool) {
let mut sum_bp = 0.0
let mut sum_tr = 0.0
for j in (i - p + 1)..<(i + 1) {
let b = bp[j]
let t = tr[j]
if @base.is_nan(b) || @base.is_nan(t) {
return (0.0, 0.0, false)
}
sum_bp += b
sum_tr += t
}
(sum_bp, sum_tr, true)
}
let out : Array[Double] = []
for i in 0..