Parabolic SAR 指标:公式、参数与读法

抛物线 SAR(Parabolic SAR,Welles Wilder,1978)每根K线打一个点,位于以加速度向价格逼近的停损反转水平上。Senzoukria 使用 Wilder 的原始参数:加速因子起始 0.02、步长 0.02、上限 0.2。

Senzoukria · 指标 · Updated September 2026


Parabolic SAR 均线与波动率, 叠加在价格图上.

Parabolic SAR 测量的是什么

每根K线上,点从上一位置前进「加速因子乘以到极值点的距离」,极值点是当前上升趋势的最高价或当前下降趋势的最低价。有两条规则决定了本实现的确切行为。点被限制为永远不能落在前两根K线的价格区间之内,以防因指标自身的位置而触发反转。加速因子只有在创出新极值点时才增加:没有延续趋势的K线会让因子保持不变,这一细节经常被实现错误。翻转时,旧的极值点成为新的点位,极值重置为当前K线的最低价或最高价,因子回到起始值。

公式:代码中的实现

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

Sortie `bands` avec TROU SYNCHRONISÉ : une seule des trois lignes indéfinie ⇒ les trois valent null à cette barre. Une bande à moitié définie (basis sans bord, bord sans basis) n'a aucun sens à l'écran et laisserait le renderer inventer un remplissage. */ function bandsOut( bars: readonly FootprintBar[], upperVals: ReadonlyArray<number | null>, basisVals: ReadonlyArray<number | null>, lowerVals: ReadonlyArray<number | null>, color: string, fillAlpha: number, ): SeriesOutput { const upper: SeriesPoint[] = []; const basis: SeriesPoint[] = []; const lower: SeriesPoint[] = []; for (let i = 0; i < bars.length; i++) { const t = bars[i].bucketTsNs; const u = upperVals[i] ?? null; const m = basisVals[i] ?? null; const l = lowerVals[i] ?? null; const ok = u !== null && m !== null && l !== null; upper.push({ time: t, value: ok ? u : null }); basis.push({ time: t, value: ok ? m : null }); lower.push({ time: t, value: ok ? l : null }); } return { shape: "bands", upper, basis, lower, fillAlpha, style: { color, width: 1 }, }; } /** Milieu (max high + min low)/2 de la fenêtre de N barres se terminant en i — la « ligne médiane » d'Ichimoku (Tenkan/Kijun/Senkou B en dérivent toutes). Warm-up (< N barres) → null. */ function donchianMidSeries( bars: readonly FootprintBar[], period: number, ): Array<number | null> { const n = Math.max(1, Math.floor(period)); const out: Array<number | null> = new Array(bars.length).fill(null); for (let i = n - 1; i < bars.length; i++) { let hi = Number.NEGATIVE_INFINITY; let lo = Number.POSITIVE_INFINITY; for (let j = i - n + 1; j <= i; j++) { if (bars[j].high > hi) hi = bars[j].high; if (bars[j].low < lo) lo = bars[j].low; } out[i] = (hi + lo) / 2; } return out; } /** Moindres carrés y = a + b·t sur la fenêtre de n valeurs se terminant en i (t = 0 pour la plus ancienne, t = n−1 pour la courante). Sommes fermées : Σt = n(n−1)/2, Σt² = n(n−1)(2n−1)/6. Même fit que le LSMA d'averages2.ts — recopié pour que la famille reste autonome (fonction privée là-bas). */ function linFit( values: readonly number[], i: number, n: number, ): { a: number; b: number } { const sumT = (n * (n - 1)) / 2; const sumT2 = (n * (n - 1) * (2 * n - 1)) / 6; let sumY = 0; let sumTY = 0; for (let t = 0; t < n; t++) { const y = values[i - n + 1 + t]; sumY += y; sumTY += t * y; } const denom = n * sumT2 - sumT * sumT; // > 0 dès n ≥ 2 const b = denom > 0 ? (n * sumTY - sumT * sumY) / denom : 0; const a = (sumY - b * sumT) / n; return { a, b }; } /** SMA_P appliquée au SUFFIXE défini d'une série à préfixe null (sortie d'un étage précédent — suffixe contigu par construction). P ≤ 1 → identité. Défensif : un null APRÈS le début du suffixe → tout null (on ne recolle pas deux morceaux séparés par un trou). */ function smoothSuffix( values: ReadonlyArray<number | null>, period: number, ): Array<number | null> { const p = Math.max(1, Math.floor(period)); if (p === 1) return values.slice(); const out: Array<number | null> = new Array(values.length).fill(null); let start = 0; while (start < values.length && values[start] === null) start++; if (start >= values.length) 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 s = smaSeries(defined, p); for (let j = 0; j < s.length; j++) out[start + j] = s[j]; return out; } // ── Sessions ──────────────────────────────────────────────────────────────── type SessionOHLC = { o: number; h: number; l: number; c: number }; /** O/H/L/C d'une session complète : open de la PREMIÈRE barre, close de la DERNIÈRE, extrêmes sur toutes les barres. Les tranches de `splitSessions` sont non vides par construction → extrêmes finis. */ function sessionOHLC( bars: readonly FootprintBar[], ses: SessionSlice, ): SessionOHLC { let h = Number.NEGATIVE_INFINITY; let l = Number.POSITIVE_INFINITY; for (let i = ses.start; i <= ses.end; i++) { if (bars[i].high > h) h = bars[i].high; if (bars[i].low < l) l = bars[i].low; } return { o: bars[ses.start].open, h, l, c: bars[ses.end].close }; } /** Projection « session N−1 → session N » commune aux trois familles de pivots : chaque session passée calcule ses niveaux et les trace sur la SUIVANTE (bord droit du chart pour la session courante). La première session chargée n'a pas de précédente → rien n'est inventé. */ function projectedPivots( bars: readonly FootprintBar[], color: string, levelsOf: (prev: SessionOHLC) => Array<[number, string]>, ctx: SeriesContext, ): SeriesOutput[] { const sessions = splitSessions(bars, ctx); const levels: SeriesLevel[] = []; for (let s = 1; s < sessions.length; s++) { const prev = sessionOHLC(bars, sessions[s - 1]); const cur = sessions[s]; const from = bars[cur.start].bucketTsNs; const to = s === sessions.length - 1 ? null : bars[cur.end].bucketTsNs; for (const [price, label] of levelsOf(prev)) { levels.push({ price, from, to, color, label }); } } return [{ shape: "levels", levels }]; } // ── Swing (Fibonacci) ─────────────────────────────────────────────────────── /** Jambe de swing des N dernières barres : plus haut, plus bas, et SENS. Sens = ordre d'apparition des deux extrêmes : le plus haut arrivé APRÈS le plus bas ⇒ jambe HAUSSIÈRE (on retrace vers le bas depuis le sommet) ; sinon jambe BAISSIÈRE. Égalité d'extrêmes (plusieurs barres au même prix) : on garde la DERNIÈRE occurrence (`>=` / `<=`) — c'est le retest le plus récent qui définit la jambe active, pas le premier contact. `null` si moins de N barres (une fenêtre partielle mentirait sur son lookback — règle du catalogue) ou si le range est nul (high = low : il n'y a aucune jambe à retracer, 7 niveaux confondus seraient du bruit). */ type SwingLeg = { hi: number; lo: number; up: boolean; from: number }; function lastSwingLeg( bars: readonly FootprintBar[], lookback: number, ): SwingLeg | null { const n = Math.max(1, Math.floor(lookback)); if (bars.length < n) return null; const start = bars.length - n; let hi = Number.NEGATIVE_INFINITY; let lo = Number.POSITIVE_INFINITY; let hiIdx = start; let loIdx = start; for (let i = start; i < bars.length; i++) { if (bars[i].high >= hi) { hi = bars[i].high; hiIdx = i; } if (bars[i].low <= lo) { lo = bars[i].low; loIdx = i; } } if (!(hi > lo)) return null; return { hi, lo, up: hiIdx >= loIdx, from: bars[start].bucketTsNs }; } // ── Défs ──────────────────────────────────────────────────────────────────── /** Parabolic SAR (Wilder, 1978) — points de stop-and-reverse. AMORÇAGE (barre 1 ; la barre 0 n'émet rien, elle sert de référence) : tendance = HAUSSIÈRE si close[1] ≥ close[0], baissière sinon (l'égalité part haussière — choix arbitraire mais DÉTERMINISTE, documenté) ; SAR[1] = low[0] en hausse / high[0] en baisse ; EP = high[1] en hausse / low[1] en baisse ; AF = afStart. PAS COURANT (i ≥ 2), dans cet ordre EXACT : 1. SAR[i] = SAR[i−1] + AF·(EP − SAR[i−1]) — l'accélération travaille sur la distance au point extrême, pas sur le prix ; 2. BRIDAGE sur les DEUX barres précédentes : en hausse SAR[i] ≤ min(low[i−1], low[i−2]) ; en baisse SAR[i] ≥ max(high[i−1], high[i−2]). Sans cette borne le SAR pourrait se placer à l'intérieur du range récent et déclencher un flip fantôme ; 3. BASCULE : en hausse, low[i] < SAR[i] ⇒ tendance BAISSIÈRE et SAR[i] = EP (l'ancien point extrême devient le stop), EP = low[i], AF remis à afStart. Symétriquement en baisse avec high[i] > SAR[i] ; 4. SANS bascule, MISE À JOUR DE L'AF : uniquement si un NOUVEAU point extrême est fait (high[i] > EP en hausse, low[i] < EP en baisse) → EP = ce nouvel extrême et AF = min(AF + afStep, afMax). Une barre qui n'étend pas la tendance ne fait PAS accélérer l'AF (erreur classique). Défauts de Wilder : afStart 0.02, afStep 0.02, afMax 0.2. Sortie : un dot par barre (à partir de la barre 1) au niveau du SAR, coloré par la tendance COURANTE — la barre de flip porte déjà la couleur de la nouvelle tendance.

如何解读

  • 点在K线下方表示当前状态向上,这个点就是会使其反转的水平;点在上方则相反。
  • 点逐根K线越来越快地逼近价格,说明加速因子已被多次提高,而这只会在趋势持续创出新极值时发生。
  • 点间距均匀、不再收敛,说明趋势已停止延伸,尽管状态尚未改变。
  • 状态翻转的那根K线已经带有新状态的颜色,因此应把颜色变化读作翻转本身,而不是翻转前的预警。

参数与默认值

AF 起始值和 AF 步长默认均为 0.02,可在 0.001 到 0.5 之间调整;AF 上限默认 0.2,范围 0.01 到 1;这些默认值都是 Wilder 发布的数值。提高起始值或步长会让点更早逼近价格,缩短翻转前的平均时间;提高上限则让加速度在长趋势中持续增大。因子为 1 时,点会在一根K线内直接跳到极值点上,这就是上限通常设得远低于 1 的原因。第一个点出现在序列的第二根K线上,以第一根K线的最高价或最低价为初始值。

Parabolic SAR — 应用中可调的参数及其默认值。
参数类型默认值取值范围
AF startnumber0.020.001 – 0.5
AF stepnumber0.020.001 – 0.5
AF maxnumber0.20.01 – 1

它无法显示的内容

抛物线 SAR 始终处于某一状态,从不中性:在横盘市场中它来回翻转,产生一连串与任何趋势都不对应的反转。除了用于限位的近期最高价和最低价外,它没有波动率项,因此不会像基于 ATR 的止损那样适应行情状态的变化。初始状态通过比较前两个收盘价决定,相等时判为看涨,因此从不同起始K线加载的图表可能以相反的状态开始。整个计算纯属价格几何:最高价、最低价和收盘价决定一切,不含任何成交量项。

浏览全部指标

其他语言版本

Frequently asked questions

抛物线 SAR 的点代表什么?
它是该K线的停损反转水平。趋势状态向上时,点位于价格下方并向价格上移;如果某根K线的最低价跌破该点,状态反转,点移到价格上方。这个点是根据过去K线计算的水平,而不是对价格去向的预测。
加速因子什么时候增加?
只有当趋势创出新的极值点时,即上升趋势中出现更高的高点、下降趋势中出现更低的低点。此时因子按步长(默认 0.02)增加,直至上限 0.2。未能延续趋势的K线会让因子保持不变,点也不再收敛。
为什么抛物线 SAR 在震荡区间里翻转这么频繁?
因为它总是处于这一状态或那一状态,而且每根K线都在向价格逼近。在震荡区间中价格没有持续方向,点追上价格后便反复反转。这个计算是为跟随既有趋势而设计的,没有任何机制来报告趋势的缺席。

Keep reading