Awesome Oscillator Indicator: Formula, Settings and How to Read It

The Awesome Oscillator is Bill Williams' momentum histogram: the 5-period simple moving average of the bar's median price minus the 34-period one. It is drawn in a separate pane and is positive when the short-horizon centre of the range sits above the long-horizon one.

Senzoukria · Indicators · Updated September 2026


Awesome Oscillator ships with the Senzoukria desktop app, in the Momentum group of the indicator catalogue. It is drawn in its own panel below the chart.

What Awesome Oscillator measures

The input is the median price, (high + low) / 2, rather than the close: Williams wanted the movement of the centre of the auction across two horizons, and the close is a single end-of-bar snapshot that is noisy on intraday futures. Both terms are plain simple moving averages — there is no exponential or Wilder smoothing in the original definition, and none here. The 5/34 defaults come from Williams' daily work, roughly a week against a month, and are kept unchanged for parity with other platforms. No histogram point is emitted until the slow average is complete, so early bars are blank rather than approximated.

The formula, as implemented

This is not a description of how the indicator is usually defined elsewhere — it is what the shipped code computes, documented next to the implementation:

Période entière ≥ `min` depuis les params (garde-fou UI, même helper que momentum.ts / stats.ts). */ function intParam( params: Record<string, unknown>, key: string, fallback: number, min = 1, ): number { return Math.max(min, Math.floor(numParam(params, key, fallback))); } function clamp(v: number, lo: number, hi: number): number { return Math.min(hi, Math.max(lo, v)); } function toPoints( bars: readonly FootprintBar[], values: ReadonlyArray<number | null>, ): SeriesPoint[] { return bars.map((b, i) => ({ time: b.bucketTsNs, value: values[i] ?? null })); } /** Histogramme : une barre sans valeur n'émet PAS de point (contrat `HistogramPoint`, pas de trou représentable). */ function histPoints( bars: readonly FootprintBar[], values: ReadonlyArray<number | null>, ): HistogramPoint[] { const out: HistogramPoint[] = []; for (let i = 0; i < bars.length; i++) { const v = values[i]; if (v !== null && v !== undefined) { out.push({ time: bars[i].bucketTsNs, value: v }); } } return out; } /** SMA N sur série À TROUS : null dès qu'UNE valeur de la fenêtre est null — moyenner autour d'un trou mentirait sur la période effective. */ function smaNullable( values: ReadonlyArray<number | null>, period: number, ): Array<number | null> { const n = Math.max(1, Math.floor(period)); const out: Array<number | null> = new Array<number | null>(values.length).fill(null); for (let i = n - 1; i < values.length; i++) { let sum = 0; let ok = true; for (let j = i - n + 1; j <= i; j++) { const v = values[j]; if (v === null) { ok = false; break; } sum += v; } if (ok) out[i] = sum / n; } return out; } /** WMA N (poids linéaires 1..N, le plus récent pèse N) tolérante aux trous : null dès qu'une valeur manque dans la fenêtre. Même formule que `helpers.wmaSeries`, qui n'accepte que des `number[]`. */ function wmaNullable( values: ReadonlyArray<number | null>, period: number, ): Array<number | null> { const n = Math.max(1, Math.floor(period)); const denom = (n * (n + 1)) / 2; const out: Array<number | null> = new Array<number | null>(values.length).fill(null); for (let i = n - 1; i < values.length; i++) { let acc = 0; let ok = true; for (let j = 0; j < n; j++) { const v = values[i - n + 1 + j]; if (v === null) { ok = false; break; } acc += v * (j + 1); } if (ok) out[i] = acc / denom; } return out; } /** EMA N (graine SMA) sur une série dont les valeurs définies forment un SUFFIXE contigu — sortie d'un lissage précédent. Le préfixe reste null ; série toute-null ou trou interne → tout-null (dégradation propre, on ne devine pas). Copie locale de la primitive de momentum.ts (non exportée). */ function suffixEma( values: ReadonlyArray<number | null>, period: number, ): Array<number | null> { const out: Array<number | null> = new Array<number | null>(values.length).fill(null); const start = values.findIndex((v) => v !== null); if (start < 0) return out; const defined: number[] = []; for (let i = start; i < values.length; i++) { const v = values[i]; if (v === null) return out; defined.push(v); } const ema = emaSeries(defined, period); for (let j = 0; j < ema.length; j++) out[start + j] = ema[j]; return out; } /** ROC en POURCENT : 100·(x[i] − x[i−N])/x[i−N]. Référence NULLE → null (pas de division par zéro déguisée) — même règle que `roc` (momentum.ts). */ function rocPercentSeries( values: readonly number[], period: number, ): Array<number | null> { const n = Math.max(1, Math.floor(period)); const out: Array<number | null> = new Array<number | null>(values.length).fill(null); for (let i = n; i < values.length; i++) { const ref = values[i - n]; if (ref !== 0) out[i] = (100 * (values[i] - ref)) / ref; } return out; } /** Prix médian de la barre — l'entrée de Bill Williams et d'Ehlers. */ function hl2(b: FootprintBar): number { return (b.high + b.low) / 2; } /** Égalités RSI, STRICTEMENT celles de `rsi` (momentum.ts) : marché PLAT (les deux moyennes nulles) → null (pas d'information directionnelle) ; que des gains → 100 ; que des pertes → 0. */ function rsiValue(avgGain: number, avgLoss: number): number | null { if (avgLoss === 0 && avgGain === 0) return null; if (avgLoss === 0) return 100; if (avgGain === 0) return 0; return 100 - 100 / (1 + avgGain / avgLoss); } /** RSI de WILDER sur une série quelconque (closes, ou série de streaks pour Connors). Graine = moyenne SIMPLE des N premières variations, puis lissage Wilder avg = (avg·(N−1) + x)/N ; premier point à l'indice N. Copie locale de la primitive de momentum.ts (non exportée) — même formule, mêmes égalités, donc `connors-rsi` et `rsi` restent cohérents à l'écran. */ function wilderRsiSeries( values: readonly number[], period: number, ): Array<number | null> { const n = Math.max(1, Math.floor(period)); const out: Array<number | null> = new Array<number | null>(values.length).fill(null); if (values.length < n + 1) return out; let avgGain = 0; let avgLoss = 0; for (let i = 1; i <= n; i++) { const ch = values[i] - values[i - 1]; if (ch > 0) avgGain += ch; else avgLoss -= ch; } avgGain /= n; avgLoss /= n; out[n] = rsiValue(avgGain, avgLoss); for (let i = n + 1; i < values.length; i++) { const ch = values[i] - values[i - 1]; avgGain = (avgGain * (n - 1) + Math.max(0, ch)) / n; avgLoss = (avgLoss * (n - 1) + Math.max(0, -ch)) / n; out[i] = rsiValue(avgGain, avgLoss); } return out; } /** AO de Bill Williams — factorisé parce que l'Accelerator le consomme : SMA_fast(hl2) − SMA_slow(hl2), null tant que la LENTE n'est pas amorcée. */ function awesomeSeries( bars: readonly FootprintBar[], fast: number, slow: number, ): Array<number | null> { const mid = bars.map(hl2); const f = smaSeries(mid, fast); const s = smaSeries(mid, slow); return mid.map((_, i) => { const a = f[i]; const b = s[i]; return a !== null && b !== null ? a - b : null; }); } /** Étage « stochastique + lissage exponentiel à facteur fixe » de Schaff — factorisé parce que STC l'applique DEUX FOIS (cf. sa déf) : %K = 100·(x − min(x, cycle))/(max(x, cycle) − min(x, cycle)) état ← (premier %K défini) puis état + f·(%K − état) Fenêtre PLATE (max == min) ou incomplète → sortie null ET ÉTAT FIGÉ : on ne nourrit pas la récurrence avec un %K inventé, et on ne reconduit pas la dernière valeur (le `nz(f1[1])` des portages Pine peindrait un plateau faux). Un trou ainsi créé se propage aux fenêtres suivantes qui le contiennent — c'est le comportement voulu. / function schaffStage( src: ReadonlyArray<number | null>, cycle: number, factor: number, ): Array<number | null> { const n = Math.max(2, Math.floor(cycle)); const out: Array<number | null> = new Array<number | null>(src.length).fill(null); let state: number | null = null; for (let i = n - 1; i < src.length; i++) { let lo = Number.POSITIVE_INFINITY; let hi = Number.NEGATIVE_INFINITY; let ok = true; for (let j = i - n + 1; j <= i; j++) { const v = src[j]; if (v === null) { ok = false; break; } if (v < lo) lo = v; if (v > hi) hi = v; } const cur = src[i]; if (!ok || cur === null) continue; // trou → état figé, sortie null const range = hi - lo; if (!(range > 0)) continue; // fenêtre plate → null, jamais 0 const k = (100 * (cur - lo)) / range; state = state === null ? k : state + factor * (k - state); out[i] = state; } return out; } // ── Défs — Bill Williams ──────────────────────────────────────────────────── /** Awesome Oscillator (Bill Williams) — lissage SMA SIMPLE (pas d'EMA, pas de Wilder ; c'est la définition originale) sur le PRIX MÉDIAN : hl2 = (high + low)/2 AO = SMA_fast(hl2) − SMA_slow(hl2) POURQUOI hl2 et pas le close : Williams mesure le déplacement du CENTRE de l'enchère sur deux horizons ; le close n'est qu'un instantané de fin de barre, très bruité en intraday futures. Défauts 5/34 (Williams) — la paire « une semaine / un mois » de son analyse quotidienne, gardée telle quelle pour la parité avec ATAS/TradingView. HISTOGRAMME : warm-up (< slow barres) → AUCUN point. Le renderer colore par SIGNE (contrat `SeriesOutput.histogram`) ; Bill Williams colore par SENS DE VARIATION barre à barre — écart assumé et documenté, le shape du moteur ne transporte pas de couleur par point.

How to read it

  • Above zero, the short-horizon centre of the range is above the long-horizon one; below zero, the reverse. The zero line is the only level the formula defines.
  • Distance from zero measures how far the two horizons have separated: that is momentum magnitude, not conviction.
  • Shrinking bars while price still advances say the two averages are converging and the move is decelerating.
  • A higher price high against a lower AO high is a momentum divergence — a description of what has already happened, not a forecast.
  • In a balanced market the bars oscillate tightly around zero and sign changes carry little meaning.

Parameters and defaults

fast (5) and slow (34) set the two averaging horizons; it is the gap between them, more than their absolute size, that drives the amplitude. Bringing them closer gives a quieter histogram with frequent sign changes, moving them apart gives larger, slower swings and a longer warm-up. The two colors only affect the display.

Awesome Oscillator — parameters exposed in the app, with the values it ships with.
ParameterTypeDefaultRange
Fast SMAnumber51 – 200
Slow SMAnumber342 – 500

What it does not show

The Awesome Oscillator is price-only: no volume, delta or book information reaches it, so it cannot separate a rise that was met by aggressive selling from one that was met by nothing at all. It lags by construction, since both terms are trailing averages. The median price also ignores where the bar closed, so a bar that reversed hard into its close reads the same as one that held its highs. One display note: bars here are colored by the sign of the value, while Williams colored each bar by its change from the previous one, so the same data can look different from platform to platform.

Using it in Senzoukria

Add it from the Indicators panel of any footprint chart or candle chart. It runs on futures data from Rithmic or Databento and on crypto pairs from Binance and Bybit, on the same engine — the calculation does not change with the venue, only the data feeding it does. Market data subscriptions are billed by the provider, separately from the app.

See the full indicator library, or start with the order flow guide if you are new to reading aggression, delta and absorption.

Frequently asked questions

Why does the Awesome Oscillator use the median price instead of the close?
Williams designed it to track the centre of the auction across two horizons, and the median price, (high + low) / 2, summarises where the bar traded rather than where it happened to stop. The close is a single instant at the end of the bar and is noisy on short intraday bars. As a consequence the oscillator ignores a strong close inside an otherwise unchanged range.
What does an Awesome Oscillator zero-line cross mean?
It means the 5-period average of the median price has crossed the 34-period average, so the short horizon has moved from one side of the long horizon to the other. It is a statement about two trailing averages, and both have already moved before the cross prints. In ranging conditions the two averages sit close together and crossings occur on very small differences.
Do the 5 and 34 periods have to stay at their defaults?
No — both are editable, from 1 to 200 for the fast average and 2 to 500 for the slow one. The defaults are Williams' own and are kept so readings match other platforms using the same pair. Changing them changes the amplitude and the warm-up, so comparisons with charts on default settings no longer hold.

Keep reading