Parabolic SAR: Acceleration, Reversals and Pine Script
This indicator plots TradingView’s ta.sar(0.02, 0.02, 0.2) as blue crosses on the chart. SAR maintains a rising or falling stop-and-reverse calculation, but the plotted crosses do not place orders or prove a trend will continue. The snippet has fixed parameters and no strategy logic.
Inspect the Pine ScriptRead the complete update, not just the recurrence
TradingView’s SAR documentation attributes the indicator to Wilder’s 1978 work. The recurrence moves the prior SAR toward an extreme point using an acceleration factor: candidate = prior SAR + prior AF × (prior EP − prior SAR).
That recurrence is only part of the algorithm. Current highs or lows can trigger a reversal; the extreme point and AF then reset for the new direction. The published ta.sar reference example also constrains the result against the preceding two lows in the rising state or highs in the falling state. These checks affect the final plotted value.
AF increases when a new directional extreme is reached, not simply because another bar elapsed. Without a new extreme it stays unchanged. A reversal resets it to Start. The two initial closes establish the starting direction in the reference implementation; current highs and lows then participate in reversal checks. A close crossing the previous dot is not an exact substitute for that algorithm.
A five-bar synthetic calculation
Use Start = 0.02, Increment = 0.02 and Max = 0.2. The table follows the published reference update order and uses hypothetical high/low/close values. AF is shown after each bar’s update.
| Bar | High / Low / Close | Final SAR | EP / AF | State |
|---|---|---|---|---|
| 0 | 10 / 8 / 9 | Unavailable | Not initialized | — |
| 1 | 11 / 9 / 10 | 8 | 11 / 0.02 | Rising |
| 2 | 12 / 10 / 11 | 8 | 12 / 0.04 | Rising |
| 3 | 13 / 11 / 12 | 8.16 | 13 / 0.06 | Rising |
| 4 | 12 / 7 / 11 | 13 | 7 / 0.02 | Falling |
At bar 2 the raw candidate is 8.06, but the two-bar low constraint keeps the plotted result at 8. At bar 3 the candidate is 8 + 0.04 × (12 − 8) = 8.16; the new high raises AF for the next update.
At bar 4 the candidate is 8.16 + 0.06 × (13 − 8.16) = 8.4504. Its low of 7 penetrates that candidate, causing a reversal and a plotted SAR of 13. The close is 11, still above the previous SAR of 8.16. Thus a rule requiring close to fall below the previous dot would miss this reversal. None of these numbers is a trade fill or a backtest result.
Parameter changes are local arithmetic, not performance rules
Start initializes AF; Increment increases it at qualifying new extremes; Max caps those increments. The copied call fixes them at 0.02, 0.02 and 0.2. The separate Visual Editor configuration exposes these three fields, but the copied script has no input declarations.
For an otherwise identical state with SAR = 100 and EP = 110, AF = 0.02 produces a raw candidate of 100.2, while AF = 0.04 produces 100.4. Doubling AF doubles this one increment before constraints and reversal checks. It does not prove twice the final responsiveness or a doubled flip count across a path.
Increasing Max from 0.2 to 0.3 allows greater acceleration when later extreme updates reach that range. It does not loosen an already tight stop. Increasing Start likewise is not a general way to avoid small-pullback reversals. Inspect parameter sweeps on the same data rather than prescribing a market-specific optimum without evidence.
A close dot does not prove AF reached its cap: a small EP-to-SAR distance can also produce proximity at a low AF. Nor does elapsed trend duration force a reversal. New extremes, prior-bar constraints and the path of prices all matter.
A plotted stop is not an executed stop order
The script is declared as an indicator and plots one series. It does not hold a long or short position, submit a stop, reverse an account, declare alerts, or request another timeframe. The unused time conversion helpers do not create a session filter.
A strategy must separately define which known SAR value becomes an order, when that order is submitted and updated, whether it may reverse exposure, and how gaps and fees are handled. Do not use a completed bar’s newly calculated stop as though it had been available before that bar’s high and low occurred.
The live bar’s high and low can change, so the current plotted result can update before close. If a closing-bar rule is intended, specify it explicitly. A gap beyond a stop can execute differently from the plotted stop price; neither a cross marker nor a historical reversal establishes a guaranteed fill.
Compare mechanisms rather than unsupported rankings
Supertrend builds ATR-based bands and carries them according to its state rules. Its active line is not simply a constant distance from every close. SAR instead tracks an extreme point and acceleration state. Neither formula establishes that one always exits earlier or avoids more whipsaws.
A Donchian channel tracks rolling highest highs and lowest lows. Its width changes as extremes enter or leave the window, and a chosen channel boundary can be used in an explicitly specified stop rule. Calling its width fixed or dismissing it as incapable of trailing-stop use is incorrect.
- Verify the five-bar example and test equality, gaps and initialization against the exact implementation.
- Define SAR state changes separately from close-crossing signals and trading entries.
- If evaluating EMA, ADX or another timeframe as a filter, keep execution, data, costs and test periods comparable.
- Report actual trade counts, return and drawdown. A visually orderly set of dots is not a reliability percentage.
Pine Script v6: implementation and limits
The full source calls ta.sar with fixed parameters and plots crosses. It contains no trade, alert or multi-timeframe logic. The unused helper definitions do not change that scope.
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Pineify
//======================================================================//
// ____ _ _ __ //
// | _ (_)_ __ ___(_)/ _|_ _ //
// | |_) | | '_ \ / _ \ | |_| | | | //
// | __/| | | | | __/ | _| |_| | //
// |_| |_|_| |_|\___|_|_| \__, | //
// |___/ //
//======================================================================//
//@version=6
indicator(title="Parabolic SAR", overlay=true, max_labels_count=500)
//#region --------------------------------------------------- Custom Code
//#endregion
//#region --------------------------------------------------- Common Dependence
p_comm_time_range_to_unix_time(string time_range, int date_time = time, string timezone = syminfo.timezone) =>
int start_unix_time = na
int end_unix_time = na
int start_time_hour = na
int start_time_minute = na
int end_time_hour = na
int end_time_minute = na
if str.length(time_range) == 11
start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
start_time_minute := math.floor(str.tonumber(str.substring(time_range, 3, 5)))
end_time_hour := math.floor(str.tonumber(str.substring(time_range, 6, 8)))
end_time_minute := math.floor(str.tonumber(str.substring(time_range, 9, 11)))
else if str.length(time_range) == 9
start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
start_time_minute := math.floor(str.tonumber(str.substring(time_range, 2, 4)))
end_time_hour := math.floor(str.tonumber(str.substring(time_range, 5, 7)))
end_time_minute := math.floor(str.tonumber(str.substring(time_range, 7, 9)))
start_unix_time := timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), start_time_hour, start_time_minute, 0)
end_unix_time := timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), end_time_hour, end_time_minute, 0)
[start_unix_time, end_unix_time]
p_comm_time_range_to_start_unix_time(string time_range, int date_time = time, string timezone = syminfo.timezone) =>
int start_time_hour = na
int start_time_minute = na
if str.length(time_range) == 11
start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
start_time_minute := math.floor(str.tonumber(str.substring(time_range, 3, 5)))
else if str.length(time_range) == 9
start_time_hour := math.floor(str.tonumber(str.substring(time_range, 0, 2)))
start_time_minute := math.floor(str.tonumber(str.substring(time_range, 2, 4)))
timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), start_time_hour, start_time_minute, 0)
p_comm_time_range_to_end_unix_time(string time_range, int date_time = time, string timezone = syminfo.timezone) =>
int end_time_hour = na
int end_time_minute = na
if str.length(time_range) == 11
end_time_hour := math.floor(str.tonumber(str.substring(time_range, 6, 8)))
end_time_minute := math.floor(str.tonumber(str.substring(time_range, 9, 11)))
else if str.length(time_range) == 9
end_time_hour := math.floor(str.tonumber(str.substring(time_range, 5, 7)))
end_time_minute := math.floor(str.tonumber(str.substring(time_range, 7, 9)))
timestamp(timezone, year(date_time, timezone), month(date_time, timezone), dayofmonth(date_time, timezone), end_time_hour, end_time_minute, 0)
p_comm_timeframe_to_seconds(simple string tf) =>
float seconds = 0
tf_lower = str.lower(tf)
value = str.tonumber(str.substring(tf_lower, 0, str.length(tf_lower) - 1))
if str.endswith(tf_lower, 's')
seconds := value
else if str.endswith(tf_lower, 'd')
seconds := value * 86400
else if str.endswith(tf_lower, 'w')
seconds := value * 604800
else if str.endswith(tf_lower, 'm')
seconds := value * 2592000
else
seconds := str.tonumber(tf_lower) * 60
seconds
p_custom_sources() =>
[open, high, low, close, volume]
//#endregion
//#region --------------------------------------------------- Ta Dependence
//#endregion
//#region --------------------------------------------------- Constants
// Input Groups
string P_GP_1 = ""
//#endregion
//#region --------------------------------------------------- Inputs
//#endregion
//#region --------------------------------------------------- Price Data
//#endregion
//#region --------------------------------------------------- Indicators
p_ind_1 = ta.sar(0.02, 0.02, 0.2) // SAR
//#endregion
//#region --------------------------------------------------- Conditions
//#endregion
//#region --------------------------------------------------- Indicator Plots
// SAR
plot(p_ind_1, "SAR", color.rgb(41, 98, 255, 0), 1, style=plot.style_cross)
//#endregion
//#region --------------------------------------------------- Custom Plots
//#endregion
//#region --------------------------------------------------- Alert
//#endregion
Specify a checkable adaptation in Pineify
Ask for explicit SAR inputs and, if needed, clearly defined confirmed-bar state alerts. For a strategy adaptation, state when orders become active, how stops update and whether reversals are allowed. Check final syntax and reproduce the reference calculation before assessing results in TradingView.
Open Pine Script AI Coding AgentEducational indicator analysis. No measured trading performance is established here. Syntax validation does not prove formula equivalence, execution assumptions or profitability.