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 vector_ta::indicators::sar::{sar, SarInput, SarParams};
use vector_ta::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 vector_ta::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 vector_ta 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 vector_ta 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 vector_ta 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 helpers are available when the Python package is built with CUDA support. Inputs must be float32; outputs are device arrays (DLPack / __cuda_array_interface__ compatible).

import numpy as np
from vector_ta import sar_cuda_batch_dev, sar_cuda_many_series_one_param_dev

# One series (float32)
high_f32 = np.asarray(load_high(), dtype=np.float32)
low_f32 = np.asarray(load_low(), dtype=np.float32)

dev = sar_cuda_batch_dev(
    high_f32=high_f32,
    low_f32=low_f32,
    acceleration_range=(0.5, 2.0, 0.5),
    maximum_range=(0.5, 2.0, 0.5),
    device_id=0,
)

# Many series (time-major)
high_tm_f32 = np.asarray(load_high_time_major_matrix(), dtype=np.float32)
rows, cols = high_tm_f32.shape
high_tm_f32 = high_tm_f32.ravel()
low_tm_f32 = np.asarray(load_low_time_major_matrix(), dtype=np.float32)
low_tm_f32 = low_tm_f32.ravel()

dev_tm = sar_cuda_many_series_one_param_dev(
    high_tm_f32=high_tm_f32,
    low_tm_f32=low_tm_f32,
    cols=cols,
    rows=rows,
    acceleration=1.0,
    maximum=1.0,
    device_id=0,
)

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);

CUDA Bindings (Rust)

use vector_ta::cuda::CudaSar;
use vector_ta::indicators::sar::SarBatchRange;

let cuda = CudaSar::new(0)?;

let high: [f32] = /* ... */;
let low: [f32] = /* ... */;
let sweep = SarBatchRange::default();

let out = cuda.sar_batch_dev(&high, &low, &sweep)?;
let _ = out;

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-08

Related Indicators