@pond-ts/financial API Reference
    Preparing search index...

    Function ultimateOscillator

    • Ultimate Oscillator (Larry Williams, 1976) — buying pressure as a fraction of true range, averaged over three horizons at once and weighted toward the shortest, bounded 0..100:

      BP  = close − min(low, prevClose)          "buying pressure"
      TR = max(high, prevClose) − min(low, prevClose)
      A_n = Σ BP over n bars / Σ TR over n bars
      uo = 100 · (4·A_short + 2·A_medium + A_long) / 7

      Appends one column; undefined for the first longPeriod rows.

      Williams built it against the complaint that a single-horizon oscillator gives false divergences whenever the trader's horizon and the indicator's disagree, so it reads three at once. The weights halve as the horizon doubles, which is what keeps the three legs' contributions comparable rather than letting the longest window dominate.

      Reads high, low and close, each named by an option defaulting to its DEFAULT_OHLCV column — the atr shape.

      The three look-backs are shortPeriod / mediumPeriod / longPeriod, not a periods: [7, 14, 28] array. A tuple would be shorter to type and worse to read: the weights are positional (4 / 2 / 1), so periods: [28, 14, 7] is a silently different indicator, while longPeriod: 7 is obviously wrong at the call site. It also matches how every other multi-horizon study here names its lengths (macd's fastPeriod/slowPeriod, coppock's longPeriod/shortPeriod/wmaPeriod). The three are validated as strictly increasing for the same reason.

      The weights themselves are not options: 4 / 2 / 1 is the definition, and a weight vector nobody publishes an alternative for is a speculative knob.

      TA-Lib's ULTOSC, matched bar-for-bar — the oracle asserts identical null masks and agreement to 7.1e-15 at both (7, 14, 28) and (3, 5, 9). The true range is the package's own trueRangeValues, which the generator separately confirms is talib.TRANGE exactly (0.0), so the ATR family and this study measure range the same way by construction rather than by coincidence.

      • Warm-up is longPeriod rows, not longPeriod − 1: both BP and TR read the previous close, so bar 0 has neither and the first full window of longPeriod defined bars ends on bar longPeriod. The two shorter legs are ready earlier and wait for the longest.
      • Bounded 0..100 on real bars, because 0 ≤ BP ≤ TR there: BP is non-negative since close ≥ low, and TR ≥ close − min(low, prevClose) since TR's upper term is max(high, prevClose) ≥ close. Point a close option at a column that is not inside its bar (a smoothed line, say) and both bounds can be left — reported honestly rather than clamped, as the input is the thing that is inconsistent.
      • A window whose true range sums to zeroundefined, guarded explicitly. On consistent bars it is the 0/0 of a completely flat window; on a redirected close the numerator can be non-zero over a zero range, and an ±Infinity in a chart's y-domain is worse than a gap. Any one of the three legs being undefined makes the reading undefined — a weighted mean of an unknown is unknown.
      • Scale- and shift-invariant: BP and TR are both homogeneous of degree one in price and both unchanged by adding a constant to every price, so their ratio is invariant to both. Pinned by property tests.
      • A leading gap shifts the start; an interior gap costs the windows containing it — the gap bar and the longPeriod bars after it — after which the study recovers. Note which bar a missing close costs: BP and TR read the previous close, so a bar with no close removes both its own reading and the next bar's, which is why the hole reaches one bar further than the window alone would (trueRangeValues states the same asymmetry).

      Type Parameters

      • S extends SeriesSchema
      • const Output extends string = "uo"

      Parameters

      Returns TimeSeries<
          readonly [S[0], ValueColumnsForSchema<S>, OptionalNumberColumn<Output>],
      >