Parabolic SAR Indicator: Formula, Settings and How to Read It
Parabolic SAR (Welles Wilder, 1978) prints one dot per bar at a stop-and-reverse level that moves toward price at an accelerating rate. Senzoukria uses Wilder's original factors: an acceleration starting at 0.02, a step of 0.02 and a ceiling of 0.2.
Senzoukria · Indicators · Updated September 2026
Parabolic SAR ships with the Senzoukria desktop app, in the Averages & volatility group of the indicator catalogue. It is drawn on the price chart.
What Parabolic SAR measures
On each bar the dot advances from its previous position by the acceleration factor times the distance to the extreme point, the highest high of the current uptrend or the lowest low of the current downtrend. Two rules give the implementation its exact behaviour. The dot is clamped so that it can never sit inside the range of the two preceding bars, which prevents a reversal triggered by the placement of the indicator itself. And the acceleration factor rises only when a new extreme point is made: a bar that does not extend the trend leaves the factor where it is, a detail frequently implemented incorrectly. On a flip the old extreme point becomes the new dot, the extreme is reset to the current bar's low or high, and the factor returns to its starting value.
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:
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.
How to read it
- A dot below the bars means the current state is up and the dot is the level that would reverse it; a dot above means the opposite.
- Dots that close in on price faster from bar to bar show that the acceleration factor has been raised repeatedly, which happens only while the trend keeps making new extremes.
- Evenly spaced dots that stop converging tell you the trend has stopped extending, even though the state has not changed yet.
- The bar on which the state flips already carries the colour of the new state, so read the colour change as the flip itself rather than as a warning before it.
Parameters and defaults
AF start and AF step default to 0.02, both adjustable from 0.001 to 0.5, and AF max defaults to 0.2 within a 0.01 to 1 range; these defaults are Wilder's published values. Raising the start or the step makes the dots close in on price sooner and shortens the average time before a flip, while raising the ceiling lets the acceleration keep growing in a long trend. At a factor of 1 the dot would jump straight onto the extreme point in a single bar, which is why the ceiling is normally left well below it. The first dot is emitted on the second bar of the series, seeded from the first bar's high or low.
| Parameter | Type | Default | Range |
|---|---|---|---|
| AF start | number | 0.02 | 0.001 – 0.5 |
| AF step | number | 0.02 | 0.001 – 0.5 |
| AF max | number | 0.2 | 0.01 – 1 |
What it does not show
Parabolic SAR always holds a state, so it is never neutral: in a sideways market it flips back and forth, producing a sequence of reversals that correspond to no trend. It has no volatility term beyond the recent highs and lows used for the clamp, so it does not adapt to a change of regime the way an ATR-based stop does. The initial state is decided by comparing the first two closes, with equality resolved as bullish, so a chart loaded from a different starting bar can begin in the other state. And the whole calculation is price geometry: highs, lows and closes decide everything, with no volume term of any kind.
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.
Related indicators
- SMA — Averages & volatility
- EMA — Averages & volatility
- WMA — Averages & volatility
- Hull MA — Averages & volatility
- ATR (Wilder) — Averages & volatility
- Bollinger — Averages & volatility
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
- What does a Parabolic SAR dot represent?
- It is the stop-and-reverse level for that bar. While the trend state is up the dot sits below price and rises toward it, and if a bar's low trades under the dot the state reverses and the dot moves above price. The dot is a level computed from past bars, not a forecast of where price will go.
- When does the acceleration factor increase?
- Only when the trend records a new extreme point, that is a higher high in an uptrend or a lower low in a downtrend. In that case the factor rises by the step, 0.02 by default, up to the ceiling of 0.2. A bar that fails to extend the trend leaves the factor unchanged, and the dots stop converging.
- Why does Parabolic SAR flip so often in a range?
- Because it is always in one state or the other and its level keeps closing in on price every bar. In a range price has no sustained direction, so the dot catches up and reverses repeatedly. The calculation is built to trail an existing trend and has no mechanism for reporting the absence of one.