Parabolic Time Price System
The Parabolic Time Price System, also known as Parabolic SAR (Stop and Reverse), is a trend-following indicator developed by J. Welles Wilder. It provides potential entry and exit points by plotting dots above or below price bars, creating a parabolic curve that follows price movements and accelerates with the trend.
What It Measures
The Parabolic SAR measures the direction and potential reversal points of an asset's price trend. The indicator uses an acceleration factor that increases over time as the trend develops, causing the SAR to accelerate toward the price and eventually trigger a reversal signal when price crosses the SAR level.
When to Use
Use the Parabolic SAR when you want to identify trending markets and potential exit points for existing positions. It works best in strongly trending markets where prices make sustained moves in one direction. The indicator is particularly effective for setting trailing stops that tighten as trends develop and mature.
Interpretation
When the SAR dots appear below the price, it indicates an uptrend and suggests holding long positions. When the dots flip above the price, it signals a downtrend and suggests holding short positions. The crossover from one side to the other generates trading signals: price crossing above the SAR is a buy signal, while price crossing below the SAR is a sell signal. The acceleration factor causes the SAR to move faster as the trend develops, making it increasingly sensitive to reversals as positions age.
Default Usage
use rust_ti::trend_indicators::bulk::parabolic_time_price_system;
pub fn main() {
// fetch the data in your preferred way
// let high = vec![...]; // high prices
// let low = vec![...]; // low prices
let para = parabolic_time_price_system(&high, &low, 0.02, 0.2, 0.02, Position::Long, 0.0);
println!("{:?}", para);
}
import pytechnicalindicators as pti
# fetch the data in your preferred way
# high = [...] # high prices
# low = [...] # low prices
para = pti.trend_indicators.bulk.parabolic_time_price_system(high, low, 0.02, 0.2, 0.02, "Long", 0.0);
print(para)
// WASM import
import init, { trend_bulk_parabolicTimePriceSystem } from 'https://cdn.jsdelivr.net/npm/ti-engine@latest/dist/web/ti_engine.js';
await init();
// fetch the data in your preferred way
// const high = [...]; // high prices
// const low = [...]; // low prices
const para = trend_bulk_parabolicTimePriceSystem(high, low, 0.02, 0.2, 0.02, "Long", 0.0);
console.log(para);
Optimization
The best way to determine what the best parameters for your indicator are is to build a simple optimization loop that tests all possible parameter combinations between a defined min and max value, and rate the output.
Below is an example of how to do this in Rust.
use rust_ti::trend_indicators::bulk::parabolic_time_price_system;
use rust_ti::chart_trends::{peaks, valleys};
fn proximity_rating(fuzzed_location: &usize, price_location: &usize) -> f64 {
1.0 / (*fuzzed_location as f64 - *price_location as f64).abs()
}
pub fn main() {
// fetch the data in your preferred way
// let high = vec![...]; // high prices
// let low = vec![...]; // low prices
// let close = vec![...]; // closing prices
// get buy and sell points
let sell_points = peaks(&close, 20, 5)
.into_iter()
.map(|(_, i)| i)
.collect::<Vec<usize>>();
let buy_points = valleys(&close, 20, 5)
.into_iter()
.map(|(_, i)| i)
.collect::<Vec<usize>>();
// Define the ranges for optimization
let min_af_start = 0.01;
let max_af_start = 0.05;
let af_start_step = 0.01;
let min_af_max = 0.15;
let max_af_max = 0.25;
let af_max_step = 0.01;
let min_af_step = 0.01;
let max_af_step = 0.15;
let af_step_increment = 0.01;
let fuzz_parameter = 5;
// Store the best parameters found
let mut best_rating = 0.0;
let mut best_af_start = 0.0;
let mut best_af_max = 0.0;
let mut best_af_step = 0.0;
let mut best_position = Position::Long;
let mut best_indicators = vec![];
let positions = vec![Position::Long, Position::Short];
for &position in &positions {
let mut af_start = min_af_start;
while af_start <= max_af_start {
let mut af_max = min_af_max;
while af_max <= max_af_max {
let mut af_step = min_af_step;
while af_step <= max_af_step {
let indicators = parabolic_time_price_system(
&high,
&low,
af_start,
af_max,
af_step,
position,
0.0,
);
let mut rating = 0.0;
// Rate sell points
for &sell_point in &sell_points {
for fuzz in 0..=fuzz_parameter {
if sell_point + fuzz < indicators.len() && sell_point >= fuzz {
let fuzzed_location = sell_point + fuzz;
let indicator_value = indicators[fuzzed_location];
let price_value = close[sell_point];
// For selling, we want SAR below price (bullish becomes bearish)
if indicator_value < price_value {
rating += proximity_rating(&fuzzed_location, &sell_point);
}
if fuzz > 0 {
let fuzzed_location = sell_point - fuzz;
let indicator_value = indicators[fuzzed_location];
if indicator_value < price_value {
rating += proximity_rating(&fuzzed_location, &sell_point);
}
}
}
}
}
// Rate buy points
for &buy_point in &buy_points {
for fuzz in 0..=fuzz_parameter {
if buy_point + fuzz < indicators.len() && buy_point >= fuzz {
let fuzzed_location = buy_point + fuzz;
let indicator_value = indicators[fuzzed_location];
let price_value = close[buy_point];
// For buying, we want SAR above price (bearish becomes bullish)
if indicator_value > price_value {
rating += proximity_rating(&fuzzed_location, &buy_point);
}
if fuzz > 0 {
let fuzzed_location = buy_point - fuzz;
let indicator_value = indicators[fuzzed_location];
if indicator_value > price_value {
rating += proximity_rating(&fuzzed_location, &buy_point);
}
}
}
}
}
if rating > best_rating {
best_rating = rating;
best_af_start = af_start;
best_af_max = af_max;
best_af_step = af_step;
best_position = position;
best_indicators = indicators;
}
af_step += af_step_increment;
}
af_max += af_max_step;
}
af_start += af_start_step;
}
}
println!("Best Parabolic SAR parameters found:");
println!("acceleration_factor_start = {}", best_af_start);
println!("acceleration_factor_max = {}", best_af_max);
println!("acceleration_factor_step = {}", best_af_step);
println!("start_position = {:?}", best_position);
println!("Rating: {}", best_rating);
println!("Best Indicator values: {:?}", &best_indicators[0..3.min(best_indicators.len())]);
}
Optimization Output
Below is an example output from the optimization code above run on a year of S&P data.
Best Parabolic SAR parameters found:
acceleration_factor_start = 0.01
acceleration_factor_max = 0.22
acceleration_factor_step = 0.1
start_position = Short
Rating: 0.10524691358024692
Best Indicator values: [5176.85, 5176.85, 5175.6, ... ]
Interactive Chart
To better illustrate how the indicator performs with different parameters, an interactive chart is provided below comparing default parameters (blue) with optimized parameters (green).
Analysis
The optimized Parabolic SAR parameters (AF start=0.01, AF max=0.22, AF step=0.1, starting Short) generated significantly better results compared to the default parameters (AF start=0.02, AF max=0.2, AF step=0.02, starting Long). The optimized strategy achieved a profit of $3.20 with 50 trades, while the default strategy resulted in a loss of $-10.97 with 34 trades. The lower initial acceleration factor in the optimized parameters allows the SAR to be more patient before accelerating, which reduces false signals and whipsaws, leading to better overall performance across the trading period.
Optimized trading simulation
- SideLONG
- Shares0.0356
- Entry$5638.94
- Value$5638.94
Default trading simulation
- SideSHORT
- Shares0.0327
- Entry$5983.25
- Value$5638.94
Trading simulation code
For those you want to run their own simulation to compare results
fn simulate_trading(best_indicator: &[f64], high: &[f64], low: &[f64], close: &[f64]) {
println!("
--- Trading Simulation ---");
let initial_capital = 1000.0;
let mut capital = initial_capital;
let investment_pct = 0.20;
struct Trade {
entry_price: f64,
shares: f64,
position_type: Position,
}
let mut open_position: Option<Trade> = None;
// Print table header
println!(
"{:<5} | {:<19} | {:<10} | {:<10} | {:<12} | {:<15} | {:<10}",
"Day", "Event", "SAR", "Price", "Shares", "Capital", "P/L"
);
println!("{}", "-".repeat(95));
for i in 1..best_indicator.len() {
if i >= close.len() {
break;
}
let sar = best_indicator[i];
let prev_sar = best_indicator[i - 1];
let current_price = close[i];
let prev_price = close[i - 1];
let day = i;
// Detect position changes based on SAR crossovers
if let Some(trade) = open_position.take() {
// Check if we need to close and reverse position
let should_reverse = match trade.position_type {
Position::Long => current_price < sar && prev_price >= prev_sar,
Position::Short => current_price > sar && prev_price <= prev_sar,
};
if should_reverse {
// Close current position
let exit_value = match trade.position_type {
Position::Long => {
let sale_value = trade.shares * current_price;
let profit = sale_value - (trade.shares * trade.entry_price);
capital += sale_value;
println!(
"{:<5} | {:<19} | {:<10.2} | ${:<9.2} | {:<12.4} | ${:<14.2} | ${:<9.2}",
day,
"Close Long",
sar,
current_price,
trade.shares,
capital,
profit
);
profit
}
Position::Short => {
let cost_to_cover = trade.shares * current_price;
let profit = (trade.shares * trade.entry_price) - cost_to_cover;
capital += profit;
println!(
"{:<5} | {:<19} | {:<10.2} | ${:<9.2} | {:<12.4} | ${:<14.2} | ${:<9.2}",
day,
"Close Short",
sar,
current_price,
trade.shares,
capital,
profit
);
profit
}
};
// Open reverse position
let investment = capital * investment_pct;
let shares = investment / current_price;
let new_position_type = match trade.position_type {
Position::Long => {
println!(
"{:<5} | {:<19} | {:<10.2} | ${:<9.2} | {:<12.4} | ${:<14.2} | {}",
day,
"Open Short (Reverse)",
sar,
current_price,
shares,
capital,
"-"
);
Position::Short
}
Position::Short => {
capital -= investment;
println!(
"{:<5} | {:<19} | {:<10.2} | ${:<9.2} | {:<12.4} | ${:<14.2} | {}",
day,
"Open Long (Reverse)",
sar,
current_price,
shares,
capital,
"-"
);
Position::Long
}
};
open_position = Some(Trade {
entry_price: current_price,
shares,
position_type: new_position_type,
});
} else {
// Keep position open
open_position = Some(trade);
}
} else {
// No position open, check for entry signal
if prev_price <= prev_sar && current_price > sar {
// Bullish crossover - go long
let investment = capital * investment_pct;
let shares = investment / current_price;
capital -= investment;
println!(
"{:<5} | {:<19} | {:<10.2} | ${:<9.2} | {:<12.4} | ${:<14.2} | {}",
day,
"Open Long",
sar,
current_price,
shares,
capital,
"-"
);
open_position = Some(Trade {
entry_price: current_price,
shares,
position_type: Position::Long,
});
} else if prev_price >= prev_sar && current_price < sar {
// Bearish crossover - go short
let investment = capital * investment_pct;
let shares = investment / current_price;
println!(
"{:<5} | {:<19} | {:<10.2} | ${:<9.2} | {:<12.4} | ${:<14.2} | {}",
day,
"Open Short",
sar,
current_price,
shares,
capital,
"-"
);
open_position = Some(Trade {
entry_price: current_price,
shares,
position_type: Position::Short,
});
}
}
}
println!("
--- Final Results ---");
if let Some(trade) = open_position {
let last_price = close.last().unwrap_or(&0.0);
match trade.position_type {
Position::Long => {
println!("Simulation ended with an OPEN LONG position:");
println!(" - Shares: {:.4}", trade.shares);
println!(" - Entry Price: ${:.2}", trade.entry_price);
let current_value = trade.shares * last_price;
capital += current_value;
println!(
" - Position value at last price (${:.2}): ${:.2}",
last_price, current_value
);
}
Position::Short => {
println!("Simulation ended with an OPEN SHORT position:");
println!(" - Shares: {:.4}", trade.shares);
println!(" - Entry Price: ${:.2}", trade.entry_price);
let cost_to_cover = trade.shares * last_price;
let pnl = (trade.shares * trade.entry_price) - cost_to_cover;
capital += pnl;
println!(
" - Unrealized P/L at last price (${:.2}): ${:.2}",
last_price, pnl
);
}
}
}
let final_pnl = capital - initial_capital;
println!("
Initial Capital: ${:.2}", initial_capital);
println!("Final Capital: ${:.2}", capital);
println!("Total P/L: ${:.2}", final_pnl);
}
fn main() {
// Fetch data and perform optimization as shown in the optimization code above
// Run trading simulation with optimized parameters
simulate_trading(&best_indicators, &high, &low, &close);
// Compare with default parameters (typically 0.02, 0.2, 0.02, Long)
println!("
Default Indicator values for comparison:");
let default_sar = parabolic_time_price_system(&high, &low, 0.02, 0.2, 0.02, Position::Long, 0.0);
println!("{:?}", default_sar);
simulate_trading(&default_sar, &high, &low, &close);
}