Parabolic SAR (Stop and Reverse, abbreviated PSAR) is a classic trend-following technical indicator created by J. Welles Wilder Jr., the inventor of RSI and ADX. Unlike position-sizing recovery systems such as Martingale, PSAR focuses purely on tracking market momentum and identifying trend reversal points, delivering mechanical entry/exit signals without emotional bias.
The core logic of PSAR lies in dynamic trailing stop dots plotted on price charts:
- Dots below price = Bullish uptrend, valid long holding signal
- Dots above price = Bearish downtrend, exit all long positions
- An acceleration factor (AF) tightens SAR levels as trends strengthen, locking in floating profits automatically
This article walks through a long-only PSAR daily trading strategy built for Tesla (TSLA) stock, complete with full ALGOGENE Python API script, calculation breakdown, execution rules, and key limitations for live deployment.

Core Mathematical Mechanism of Parabolic SAR
PSAR relies on three fixed core parameters:
- Initial Acceleration Factor (AF): 0.02
- AF increment per new extreme price high/low: +0.02
- AF maximum cap: 0.2 (AF stops rising once hit)
Standard PSAR Formula
- EP (Extreme Point): Highest high in bull trend, lowest low in bear trend
- Reversal rule: If price crosses PSAR dot, reset AF back to 0.02 and flip trend state
- Bull trend: Track new higher highs to raise EP and lift AF
- Bear trend: Track new lower lows to lower EP and lift AF
Strategy Trading Rules (Long-Only Variant)
This script is strictly long-only, no short positions:
- Long Entry Trigger: Calculated PSAR sits below the latest daily close (bull trend confirmed), net position ≤ 0
- If existing short positions exist (edge case), fully close before opening long
- Long Exit Trigger: Calculated PSAR sits above the latest daily close (bear trend flip), close all open long lots
- Execution Frequency: Run signal calculation once per trading day (24-hour cooldown to avoid duplicate trades)
- Data Requirement: Minimum 3 completed daily bars to initialize PSAR trend, EP and AF state
ALGOGENE Strategy Script Walkthrough
We implement the PSAR logic via ALGOGENE’s official Python backtest API, targeting TSLA daily bars. Below is the full annotated script, split into functional modules for easy customization.
Backtest Setting
You can configure the below parameters to test this PSAR long-only strategy:
- Instrument: TSLA (US Stock)
- Data Interval: Daily (1D)
- Backtest Period: Customizable (e.g., 2023–2025)
- Initial Capital: US$100,000
- Leverage: 1x (cash equity trading)
- Base Trade Volume: 1.0 contract (adjust self.trade_volume in code for position sizing)
- Indicator Fixed Params: AF Start=0.02, AF Step=0.02, AF Max=0.2
Full Complete Strategy Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | from AlgoAPI import AlgoAPIUtil, AlgoAPI_Backtest from datetime import datetime, timedelta class AlgoEvent: def __init__(self): # Target instrument self.instrument = "TSLA" # Strategy state self.first_day = True self.pre_acc = None self.pre_trend = None self.pre_EP = None self.pre_PSAR = None # Trading control self.lasttradetime = datetime(2000, 1, 1) # Position/order tracking self.open_orders = {} # User-adjustable trade size # In ALGOGENE, volume means number of contracts. # Adjust this according to your capital, leverage, and TSLA contract specification. self.trade_volume = 1.0 def start(self, mEvt): self.evt = AlgoAPI_Backtest.AlgoEvtHandler(self, mEvt) self.evt.start() def on_bulkdatafeed(self, isSync, bd, ab): """ Main strategy logic. This strategy is designed for daily-bar execution. """ if self.instrument not in bd: return current_time = bd[self.instrument]["timestamp"] # Run once per day if current_time < self.lasttradetime + timedelta(hours=24): return self.lasttradetime = current_time # Get the latest 3 daily bars price = self.evt.getHistoricalBar( contract={"instrument": self.instrument}, numOfBar=3, interval="D" ) # Need at least 3 bars to reproduce the original logic if price is None or len(price) < 3: self.evt.consoleLog("Not enough historical data for PSAR calculation.") return # Convert returned dictionary into sorted list of bars timestamps = sorted(price.keys()) bars = [price[t] for t in timestamps] # Previous day bar: bars[-2] # Latest completed daily bar: bars[-1] prev_bar = bars[-2] latest_bar = bars[-1] prev_open = prev_bar["o"] prev_high = prev_bar["h"] prev_low = prev_bar["l"] prev_close = prev_bar["c"] latest_high = latest_bar["h"] latest_low = latest_bar["l"] latest_close = latest_bar["c"] # Initialize PSAR state on the first valid day if self.first_day: self.pre_acc = 0.02 if prev_close > prev_open: # Previous day was bullish self.pre_trend = "bull" self.pre_EP = prev_high self.pre_PSAR = prev_low else: # Previous day was bearish self.pre_trend = "bear" self.pre_EP = prev_low self.pre_PSAR = prev_high self.first_day = False self.evt.consoleLog( "PSAR initialized:", "trend =", self.pre_trend, "EP =", self.pre_EP, "PSAR =", self.pre_PSAR, "ACC =", self.pre_acc ) return # Calculate current PSAR value # PSAR = previous PSAR - previous acceleration factor * (previous PSAR - previous EP) PSAR = self.pre_PSAR - self.pre_acc * (self.pre_PSAR - self.pre_EP) # SAR reversal adjustment if self.pre_trend == "bear" and latest_high > PSAR: PSAR = self.pre_EP if self.pre_trend == "bull" and latest_low < PSAR: PSAR = self.pre_EP # Determine current trend if PSAR > latest_close: trend = "bear" else: trend = "bull" # Calculate current extreme point if trend == "bear": EP = min(self.pre_EP, latest_low) else: EP = max(self.pre_EP, latest_high) # Calculate acceleration factor acc = self.pre_acc if trend == self.pre_trend and EP != self.pre_EP: acc += 0.02 acc = min(acc, 0.2) if trend != self.pre_trend: acc = 0.02 # Save current PSAR state for next calculation self.pre_EP = EP self.pre_PSAR = PSAR self.pre_acc = acc self.pre_trend = trend self.evt.consoleLog( current_time, "Close =", latest_close, "PSAR =", PSAR, "Trend =", trend, "EP =", EP, "ACC =", acc ) # Execute trading logic net_volume = self.get_net_position() if trend == "bull": # Open long only if no existing long position if net_volume <= 0: # If there is any short position, close it first. # In the default long-only version, this normally should not happen. if net_volume < 0: self.close_all_positions() self.open_long_position() elif trend == "bear": # Close long positions when bearish if net_volume > 0: self.close_all_positions() def open_long_position(self): """ Open a long market order. """ order = AlgoAPIUtil.OrderObject() order.instrument = self.instrument order.openclose = "open" order.buysell = 1 order.ordertype = 0 order.volume = self.trade_volume order.orderRef = "SAR_LONG" self.evt.sendOrder(order) self.evt.consoleLog("Send long order for", self.instrument, "volume =", self.trade_volume) def close_all_positions(self): """ Close all outstanding opened orders for the target instrument. """ pos, osOrder, pendOrder = self.evt.getSystemOrders() for tradeID in osOrder: order_info = osOrder[tradeID] if order_info["instrument"] == self.instrument: close_order = AlgoAPIUtil.OrderObject() close_order.tradeID = tradeID close_order.openclose = "close" self.evt.sendOrder(close_order) self.evt.consoleLog("Close order sent. tradeID =", tradeID) def get_net_position(self): """ Get current net position for TSLA. Positive value means net long. Negative value means net short. Zero means no position. """ pos, osOrder, pendOrder = self.evt.getSystemOrders() if self.instrument in pos: return pos[self.instrument]["netVolume"] return 0 def on_openPositionfeed(self, op, oo, uo): """ Update internal open order tracking. """ self.open_orders = oo |
Key Code Module Breakdown
- Initialization (__init__):
- Stores persistent PSAR calculation state (AF, EP, trend, PSAR level), trade volume and instrument ticker. All parameters are fully editable for backtesting different assets.
- Bulk Market Data Handler (on_bulkdatafeed):
Core strategy logic triggered on daily bar updates:
- Restrict one trade signal per day to avoid overtrading
- Pull 3 historical daily bars to initialize PSAR math
- Iterative PSAR value, trend, EP and acceleration factor recalculation
- Execute entry/exit based on bull/bear PSAR trend signals
-
Order Execution Helpers
- open_long_position: Submit market buy orders with fixed contract volume
- close_all_positions: Batch close all existing open trades for TSLA
- get_net_position: Helper to read current net exposure for position logic control
Core Advantages of the PSAR Trend Strategy
- Fully Mechanical & Rule-Based: No subjective price interpretation; all entry/exit decisions are computed mathematically, eliminating trader emotional bias.
- Built-In Dynamic Trailing Stop Function: PSAR dots automatically trail price and tighten as trends accelerate, locking incremental profits without manual stop-loss adjustments.
- Simple Maintenance & Low Computational Cost: Only requires OHLC daily price data, no complex multi-indicator stack; lightweight for ALGOGENE real-time and backtest engines.
- Clear Risk Boundaries: Strategy exits all long exposure immediately once a bearish PSAR flip triggers, avoiding deep drawdowns during trend reversals.
Critical Limitations & Risk Notes
PSAR has distinct market environment weaknesses that all quant traders must account for:
- Poor Performance in Sideways/Choppy Markets: In range-bound stocks, PSAR generates frequent false reversal signals, creating repeated small losing trades and eroding cumulative returns.
- Lagging Indicator Nature: PSAR confirms trend shifts only after price has already moved, meaning entries and exits occur slightly after market turning points.
- Long-Only Bias Restricts Market Exposure: This implementation excludes short selling; during extended bear markets, the strategy will hold zero positions and miss downside trading opportunities.
- No Built-In Volatility Filter: Raw PSAR signals do not measure trend strength. Pair with ADX (>25 threshold) as a filter to skip low-momentum ranging markets for improved results.
- Sensitive to Parameter Tuning: Default AF 0.02/0.2 works for large-cap stocks like TSLA; crypto, forex and small-cap equities require re-calibrated acceleration settings.
Conclusion
Parabolic SAR is a foundational trend-following tool for systematic retail and institutional traders, offering clear, automated entry and exit signals with native trailing stop functionality. Unlike Martingale’s high-risk capital recovery mechanics, PSAR prioritizes trend capture and risk control as its core design goal.
The provided ALGOGENE Python script delivers a ready-to-deploy long-only PSAR daily trading system for TSLA, with fully editable instrument, position size and core indicator parameters. For robust live performance, we recommend adding a trend strength filter (ADX) and out-of-sample backtesting across multiple market regimes to reduce false range-bound signals.
This code is built for educational demonstration of ALGOGENE’s market data and order execution APIs. All live trading deployments require additional risk management layers (maximum drawdown limits, position sizing caps, volatility filters).
