Ultimate Oscillator in Python — Coding It from Scratch (pandas)

Buying pressure, true range, and the 4/2/1 blend

Ultimate Oscillator computed in Python
Ultimate Oscillator computed in Python

The Ultimate Oscillator has more moving parts than most, but each is simple: buying pressure, true range, three summed ratios, and a weighted blend, as the explainer covered. Here it is in pandas.

What you’ll need

  • Python 3.9+, pandas/numpy
  • high, low, close series.

From scratch

import numpy as np
import pandas as pd


def ultimate_oscillator(high, low, close, s1=7, s2=14, s3=28):
    prior_close = close.shift(1)
    low_or_pc = pd.concat([low, prior_close], axis=1).min(axis=1)

    bp = close - low_or_pc                                       # buying pressure
    tr = pd.concat([high, prior_close], axis=1).max(axis=1) - low_or_pc

    avg1 = bp.rolling(s1).sum() / tr.rolling(s1).sum()
    avg2 = bp.rolling(s2).sum() / tr.rolling(s2).sum()
    avg3 = bp.rolling(s3).sum() / tr.rolling(s3).sum()

    return (100 * (4 * avg1 + 2 * avg2 + avg3) / 7).rename("uo")

bp is how far the close finished above the true low; tr is the true range; the three ratios are summed-BP over summed-TR at each horizon; and the 4/2/1 weighting blends them into the 0–100 line from the output chart.

Williams’ divergence signal (the real one)

uo = ultimate_oscillator(df["high"], df["low"], df["close"])
# Bullish: price lower low, UO higher low from below 30, then UO breaks the div high.
# (Sketch — a full implementation tracks the two swing points.)
oversold_div = (df["close"] < df["close"].shift(10)) & (uo > uo.shift(10)) & (uo.shift(10) < 30)

The complete rule tracks the divergence’s swing high and requires a breakout past it; even this sketch is a better filter than a plain 30-cross to test in AlgoGen.

Gotchas

  • Sum, then divide. Each average is sum(BP)/sum(TR) over the window — not the mean of per-bar ratios.
  • True low uses the prior close. min(low, prior_close) and max(high, prior_close) are the same true-range endpoints as ATR.
  • The weights matter. 4/2/1 (divided by 7) is the definition; changing them makes a different indicator.

A note on the full signal

Williams’ complete rule is worth stating in full, because shortcutting it loses the edge. A valid buy needs three things: (1) a bullish divergence — price makes a lower low while the UO makes a higher low; (2) the UO’s low during that divergence was below 30; and (3) the UO then rises above the high it made between the two divergence lows. The sell is the mirror (bearish divergence with a peak above 70, then a break below the intervening low). Coding it properly means tracking the two swing points and the level between them — more work than a 30-cross, but that specificity is exactly what the indicator was built to provide.

The lazy one-liner

import pandas_ta as ta
df["uo"] = ta.uo(df["high"], df["low"], df["close"])   # 7/14/28 defaults

Same indicator elsewhere: MQL5, Pine Script, EasyLanguage, NinjaScript.


This post is educational, not financial advice. Indicators describe the past; they don’t predict the future. Backtest anything before you risk real money on it.

Sources and further reading

  1. Windowing operations (pandas documentation)

Historical research from the Algogen archive. Not investment advice.

Blog

Have a strategy idea?

Describe it in plain English, preview where your rules fire, then decide whether the evidence is worth a backtest.

Keep me postedHow it works