Awesome Oscillator 指标:公式、参数与读法

动量震荡指标(Awesome Oscillator,AO)是 Bill Williams 的动量柱状图:K线中间价的 5 周期简单移动平均减去 34 周期简单移动平均。它绘制在独立面板中,当短期区间中心位于长期区间中心之上时为正。

Senzoukria · 指标 · Updated September 2026


Awesome Oscillator 动量, 显示在图表下方的独立面板中.

Awesome Oscillator 测量的是什么

输入是中间价 (high + low) / 2,而不是收盘价:Williams 想要的是拍卖中心在两个时间尺度上的移动,而收盘价只是K线结束时的一个快照,在日内期货上噪音很大。两项都是普通的简单移动平均——原始定义中没有指数平滑或 Wilder 平滑,这里也没有。5/34 的默认值来自 Williams 在日线上的研究,大约是一周对一个月,为了与其他平台保持一致而原样保留。在慢速平均完整之前不输出任何柱,因此最初的K线是空白,而不是近似值。

公式:代码中的实现

这不是别处常见的定义,而是所发布代码实际计算的内容,记录在实现旁边:

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.

如何解读

  • 在零轴上方,短期区间中心高于长期区间中心;在零轴下方则相反。零轴是公式定义的唯一水平。
  • 与零轴的距离衡量两个时间尺度分离的程度:这是动量的大小,而不是信念的强弱。
  • 价格仍在推进而柱子缩短,说明两条平均线正在靠拢,行情在减速。
  • 价格高点更高而 AO 高点更低,属于动量背离——这是对已发生情况的描述,而不是预测。
  • 在平衡市场中,柱子在零轴附近小幅摆动,正负号的变化意义不大。

参数与默认值

fast(5)和 slow(34)设定两个平均的时间尺度;决定振幅的主要是两者之间的差距,而不是它们各自的大小。让两者接近会得到更平静、正负号频繁变化的柱状图;让两者拉开会得到更大、更慢的摆动和更长的预热期。两种颜色只影响显示。

Awesome Oscillator — 应用中可调的参数及其默认值。
参数类型默认值取值范围
Fast SMAnumber51 – 200
Slow SMAnumber342 – 500

它无法显示的内容

动量震荡指标只使用价格:成交量、delta 或订单簿信息都不会进入其中,因此它无法区分遭遇主动卖盘的上涨和毫无阻力的上涨。它在构造上就是滞后的,因为两项都是跟随性的平均。中间价还忽略了K线的收盘位置,因此一根在收盘前大幅反转的K线与一根守住高点的K线读数相同。关于显示的一点说明:这里的柱子按数值的正负着色,而 Williams 是按每根柱相对前一根的变化着色,因此同样的数据在不同平台上可能看起来不同。

浏览全部指标

其他语言版本

Frequently asked questions

为什么动量震荡指标用中间价而不是收盘价?
Williams 设计它是为了跟踪拍卖中心在两个时间尺度上的变化,而中间价 (high + low) / 2 概括的是K线的成交范围,而不是它恰好停在哪里。收盘价只是K线结束时的一个瞬间,在较短的日内K线上噪音很大。因此,如果区间本身没有变化,该震荡指标会忽略一个强势收盘。
动量震荡指标穿越零轴意味着什么?
这意味着中间价的 5 周期平均穿越了 34 周期平均,短期尺度从长期尺度的一侧移到了另一侧。这是关于两条跟随性平均线的陈述,而两者在交叉出现之前都已经移动过了。在震荡行情中,两条平均线靠得很近,极小的差异就会产生交叉。
5 和 34 这两个周期必须保持默认吗?
不必——两者都可以修改,快速平均为 1 到 200,慢速平均为 2 到 500。默认值是 Williams 本人的取值,保留它们是为了让读数与使用同样参数的其他平台一致。修改后振幅和预热期都会变化,与默认设置图表的比较也就不再成立。

Keep reading