Parabolic SAR (SAR)

Parameters: acceleration = 0.02 (0.005–0.2) | maximum = 0.2 (0.1–1)

Overview

The Parabolic SAR tracks trends by plotting accelerating stop points that trail price action and flip sides when penetrated. Developed by J. Welles Wilder, the indicator begins with an initial acceleration factor that increases each period the trend continues, causing the SAR points to accelerate toward price until reaching a maximum value. During an uptrend, SAR points appear below price and rise progressively faster with each new high, while downtrends place SAR points above price that fall increasingly rapidly with each new low. When price crosses the SAR level, the indicator immediately reverses direction and starts tracking the new trend from the opposite side. The parabolic nature creates tightening stops as trends mature, helping traders exit positions before major reversals while staying in strong moves. The acceleration parameter controls initial sensitivity, while the maximum parameter caps how aggressive the stop becomes, balancing between early exits and adequate profit protection.

Defaults: acceleration = 0.02, maximum = 0.2.

Implementation Examples

Get started with SAR using highs and lows:

use vectorta::indicators::sar::{sar, SarInput, SarParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};

// Using high/low slices
let high = vec![101.0, 103.0, 102.5, 104.0, 105.5];
let low  = vec![ 99.0, 100.5, 100.0, 101.0, 102.0];
let params = SarParams { acceleration: Some(0.02), maximum: Some(0.2) };
let input = SarInput::from_slices(&high, &low, params);
let result = sar(&input)?;

// Using Candles with defaults (acceleration=0.02, maximum=0.2)
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = SarInput::with_default_candles(&candles);
let result = sar(&input)?;

// Access SAR values
for v in result.values { println!("SAR: {}", v); }

API Reference

Input Methods
// From candles (uses high/low)
SarInput::from_candles(&Candles, SarParams) -> SarInput

// From high/low slices
SarInput::from_slices(&[f64], &[f64], SarParams) -> SarInput

// From candles with default parameters (acc=0.02, max=0.2)
SarInput::with_default_candles(&Candles) -> SarInput
Parameters Structure
pub struct SarParams {
    pub acceleration: Option<f64>, // Default: 0.02
    pub maximum: Option<f64>,      // Default: 0.2
}
Output Structure
pub struct SarOutput {
    pub values: Vec<f64>, // SAR values
}
Validation, Warmup & NaNs
  • Inputs require highs and lows. Candles path uses candles.high and candles.low.
  • At least 2 valid points after the first finite pair are required; otherwise SarError::NotEnoughValidData.
  • acceleration > 0.0 and maximum > 0.0; invalid values return InvalidAcceleration/InvalidMaximum.
  • Before the first computed SAR, outputs are NaN. First value is written at index first_valid + 1.
  • Slice-based APIs trim to the minimum of high.len() and low.len().
  • sar_into_slice requires output length to match inputs; otherwise SarError::LengthMismatch.
  • Streaming returns None on the first update; first Some value appears on the second update.
Error Handling
use vectorta::indicators::sar::{sar, SarError};

match sar(&input) {
    Ok(output) => process(output.values),
    Err(SarError::EmptyData) => eprintln!("empty input"),
    Err(SarError::AllValuesNaN) => eprintln!("all values are NaN"),
    Err(SarError::NotEnoughValidData { needed, valid }) => {
        eprintln!("need {needed} valid points, got {valid}");
    }
    Err(SarError::InvalidAcceleration { acceleration }) => {
        eprintln!("invalid acceleration: {acceleration}");
    }
    Err(SarError::InvalidMaximum { maximum }) => {
        eprintln!("invalid maximum: {maximum}");
    }
    Err(SarError::LengthMismatch { got, expected }) => {
        eprintln!("output length {got} != {expected}");
    }
}

Python Bindings

Basic Usage

Calculate SAR from NumPy arrays of highs and lows:

import numpy as np
from vectorta import sar

high = np.array([101.0, 103.0, 102.5, 104.0, 105.5])
low  = np.array([ 99.0, 100.5, 100.0, 101.0, 102.0])

# Defaults: acceleration=0.02, maximum=0.2
values = sar(high, low)

# Or specify parameters and kernel
values = sar(high, low, acceleration=0.02, maximum=0.2, kernel="auto")

print(values)  # NumPy array
Streaming Real-time Updates

Update from high/low ticks:

from vectorta import SarStream

stream = SarStream(acceleration=0.02, maximum=0.2)

for high_tick, low_tick in tick_stream:
    value = stream.update(high_tick, low_tick)
    if value is not None:
        print("SAR:", value)
Batch Parameter Optimization

Test multiple acceleration/maximum combinations:

import numpy as np
from vectorta import sar_batch

high = np.array([...], dtype=float)
low  = np.array([...], dtype=float)

results = sar_batch(
    high,
    low,
    acceleration_range=(0.01, 0.05, 0.01),
    maximum_range=(0.2, 0.5, 0.1),
    kernel="auto"
)

# results is a dict with:
# - values: 2D array [rows x cols]
# - accelerations: tested acceleration values
# - maximums: tested maximum values
print(results["values"].shape, results["accelerations"], results["maximums"])
CUDA Acceleration

CUDA support for SAR is currently under development. The API will follow the same pattern as other CUDA-enabled indicators.

# Coming soon: CUDA-accelerated SAR calculations
# Expected patterns: sar_cuda_batch(...), sar_cuda_many_series_one_param(...)

JavaScript/WASM Bindings

Basic Usage

Calculate SAR in JavaScript/TypeScript:

import { sar_js } from 'vectorta-wasm';

const high = new Float64Array([101.0, 103.0, 102.5, 104.0, 105.5]);
const low  = new Float64Array([ 99.0, 100.5, 100.0, 101.0, 102.0]);

// Args: high, low, acceleration, maximum
const sarValues = sar_js(high, low, 0.02, 0.2);
console.log('SAR values:', sarValues);
Memory-Efficient Operations

Use zero-copy operations for large datasets:

import { sar_alloc, sar_free, sar_into, memory } from 'vectorta-wasm';

const len = 1024;
const high = new Float64Array(len);
const low  = new Float64Array(len);
// ... fill high/low ...

const highPtr = sar_alloc(len);
const lowPtr  = sar_alloc(len);
const outPtr  = sar_alloc(len);

new Float64Array(memory.buffer, highPtr, len).set(high);
new Float64Array(memory.buffer, lowPtr, len).set(low);

// Calculate into pre-allocated memory
sar_into(highPtr, lowPtr, outPtr, len, 0.02, 0.2);

const out = new Float64Array(memory.buffer, outPtr, len).slice();

sar_free(highPtr, len);
sar_free(lowPtr, len);
sar_free(outPtr, len);
Batch Processing

Run a sweep and get combinations with outputs:

import { sar_batch as sar_batch_js } from 'vectorta-wasm';

const high = new Float64Array([...]);
const low  = new Float64Array([...]);

// Unified batch API returns an object: { values, combos, rows, cols }
const cfg = {
  acceleration_range: [0.01, 0.05, 0.01],
  maximum_range: [0.2, 0.5, 0.1],
};
const { values, combos, rows, cols } = sar_batch_js(high, low, cfg);

// values is flat: rows*cols; reshape per row if needed
const rowsArr = [] as number[][];
for (let r = 0; r < rows; r++) {
  const start = r * cols;
  rowsArr.push(Array.from(values.slice(start, start + cols)));
}

// combos is an array of { acceleration: number | null, maximum: number | null }
console.log(combos);

Performance Analysis

Comparison:
View:

Across sizes, Rust CPU runs about 1.24× slower than Tulip C in this benchmark.

Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05

Related Indicators