Parabolic Sar Volume Strategy
Strategy that combines Parabolic SAR with volume confirmation. Enters trades when price crosses the Parabolic SAR with above-average volume.
Testing indicates an average annual return of about 151%. It performs best in the stocks market.
Parabolic SAR identifies trend shifts, and higher volume validates the signal. Trades commence when the SAR flip comes with expanding volume.
Useful for traders who track volume-based moves. The SAR trail and an ATR factor guard against big losses.
Details
- Entry Criteria:
- Long:
Close > SAR && Volume > AvgVolume - Short:
Close < SAR && Volume > AvgVolume
- Long:
- Long/Short: Both
- Exit Criteria: SAR flip
- Stops: Uses Parabolic SAR as trailing stop
- Default Values:
Acceleration= 0.02mMaxAcceleration= 0.2mVolumePeriod= 20CandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Breakout
- Direction: Both
- Indicators: Parabolic SAR, Parabolic SAR, Volume
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Mid-term
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that combines Parabolic SAR with volume confirmation.
/// Enters trades when price crosses the Parabolic SAR with above-average volume.
/// </summary>
public class ParabolicSarVolumeStrategy : Strategy
{
private readonly StrategyParam<decimal> _acceleration;
private readonly StrategyParam<decimal> _maxAcceleration;
private readonly StrategyParam<int> _volumePeriod;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private ParabolicSar _parabolicSar;
private VolumeIndicator _volumeIndicator;
private SimpleMovingAverage _volumeAverage;
private decimal _prevSar;
private decimal _currentAvgVolume;
private bool _prevPriceAboveSar;
private int _cooldown;
/// <summary>
/// Parabolic SAR acceleration factor.
/// </summary>
public decimal Acceleration
{
get => _acceleration.Value;
set => _acceleration.Value = value;
}
/// <summary>
/// Parabolic SAR maximum acceleration factor.
/// </summary>
public decimal MaxAcceleration
{
get => _maxAcceleration.Value;
set => _maxAcceleration.Value = value;
}
/// <summary>
/// Period for volume moving average.
/// </summary>
public int VolumePeriod
{
get => _volumePeriod.Value;
set => _volumePeriod.Value = value;
}
/// <summary>
/// Bars to wait between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type for strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ParabolicSarVolumeStrategy"/>.
/// </summary>
public ParabolicSarVolumeStrategy()
{
_acceleration = Param(nameof(Acceleration), 0.02m)
.SetRange(0.01m, 0.1m)
.SetDisplay("SAR Acceleration", "Starting acceleration factor", "Indicators");
_maxAcceleration = Param(nameof(MaxAcceleration), 0.2m)
.SetRange(0.1m, 0.5m)
.SetDisplay("SAR Max Acceleration", "Maximum acceleration factor", "Indicators");
_volumePeriod = Param(nameof(VolumePeriod), 20)
.SetRange(10, 50)
.SetDisplay("Volume Period", "Period for volume moving average", "Indicators");
_cooldownBars = Param(nameof(CooldownBars), 30)
.SetRange(1, 100)
.SetDisplay("Cooldown Bars", "Bars between entries", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevSar = 0;
_currentAvgVolume = 0;
_prevPriceAboveSar = false;
_cooldown = 0;
_parabolicSar = null;
_volumeIndicator = null;
_volumeAverage = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Initialize indicators
_parabolicSar = new ParabolicSar
{
Acceleration = Acceleration,
AccelerationMax = MaxAcceleration
};
_volumeIndicator = new VolumeIndicator();
_volumeAverage = new SMA
{
Length = VolumePeriod
};
// Create candle subscription
var subscription = SubscribeCandles(CandleType);
// Binding for Parabolic SAR indicator
subscription
.Bind(_parabolicSar, _volumeIndicator, ProcessIndicators)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _parabolicSar);
var volumeArea = CreateChartArea();
if (volumeArea != null)
{
DrawIndicator(volumeArea, _volumeIndicator);
DrawIndicator(volumeArea, _volumeAverage);
}
DrawOwnTrades(area);
}
}
private void ProcessIndicators(ICandleMessage candle, decimal sarValue, decimal volumeValue)
{
var avgValue = _volumeAverage.Process(new DecimalIndicatorValue(_volumeAverage, volumeValue, candle.ServerTime));
if (avgValue == null)
return;
_currentAvgVolume = avgValue.ToDecimal();
if (_currentAvgVolume <= 0)
return;
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Wait until strategy and indicators are ready
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Get current price and volume
var currentPrice = candle.ClosePrice;
var currentVolume = candle.TotalVolume;
var isPriceAboveSar = currentPrice > sarValue;
// Determine if volume is above average
var isHighVolume = currentVolume > _currentAvgVolume * 1.5m;
if (_cooldown > 0)
{
_cooldown--;
_prevSar = sarValue;
_prevPriceAboveSar = isPriceAboveSar;
return;
}
// Check for SAR crossover with volume confirmation
// Bullish crossover: Price crosses above SAR with high volume
if (isPriceAboveSar && !_prevPriceAboveSar && isHighVolume && Position <= 0)
{
CancelActiveOrders();
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_cooldown = CooldownBars;
}
// Bearish crossover: Price crosses below SAR with high volume
else if (!isPriceAboveSar && _prevPriceAboveSar && isHighVolume && Position >= 0)
{
CancelActiveOrders();
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
_cooldown = CooldownBars;
}
// Exit signals based on SAR crossover (without volume confirmation)
else if ((Position > 0 && !isPriceAboveSar) || (Position < 0 && isPriceAboveSar))
{
ClosePosition();
_cooldown = CooldownBars;
}
// Update previous values for next candle
_prevSar = sarValue;
_prevPriceAboveSar = isPriceAboveSar;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ParabolicSar, VolumeIndicator, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class parabolic_sar_volume_strategy(Strategy):
"""
Strategy that combines Parabolic SAR with volume confirmation.
"""
def __init__(self):
super(parabolic_sar_volume_strategy, self).__init__()
self._acceleration = self.Param("Acceleration", 0.02) \
.SetRange(0.01, 0.1) \
.SetDisplay("SAR Acceleration", "Starting acceleration factor", "Indicators")
self._maxAcceleration = self.Param("MaxAcceleration", 0.2) \
.SetRange(0.1, 0.5) \
.SetDisplay("SAR Max Acceleration", "Maximum acceleration factor", "Indicators")
self._volumePeriod = self.Param("VolumePeriod", 20) \
.SetRange(10, 50) \
.SetDisplay("Volume Period", "Period for volume moving average", "Indicators")
self._cooldownBars = self.Param("CooldownBars", 30) \
.SetRange(1, 100) \
.SetDisplay("Cooldown Bars", "Bars between entries", "General")
self._candleType = self.Param("CandleType", tf(15)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._parabolicSar = None
self._volumeIndicator = None
self._volumeAverage = None
self._prevSar = 0.0
self._currentAvgVolume = 0.0
self._prevPriceAboveSar = False
self._cooldown = 0
@property
def CandleType(self):
return self._candleType.Value
def OnStarted2(self, time):
super(parabolic_sar_volume_strategy, self).OnStarted2(time)
self._prevSar = 0.0
self._currentAvgVolume = 0.0
self._prevPriceAboveSar = False
self._cooldown = 0
self._parabolicSar = ParabolicSar()
self._parabolicSar.Acceleration = self._acceleration.Value
self._parabolicSar.AccelerationMax = self._maxAcceleration.Value
self._volumeIndicator = VolumeIndicator()
self._volumeAverage = SimpleMovingAverage()
self._volumeAverage.Length = self._volumePeriod.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._parabolicSar, self._volumeIndicator, self.ProcessIndicators).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._parabolicSar)
volumeArea = self.CreateChartArea()
if volumeArea is not None:
self.DrawIndicator(volumeArea, self._volumeIndicator)
self.DrawIndicator(volumeArea, self._volumeAverage)
self.DrawOwnTrades(area)
def OnReseted(self):
super(parabolic_sar_volume_strategy, self).OnReseted()
self._prevSar = 0.0
self._currentAvgVolume = 0.0
self._prevPriceAboveSar = False
self._cooldown = 0
self._parabolicSar = None
self._volumeIndicator = None
self._volumeAverage = None
def ProcessIndicators(self, candle, sarValue, volumeValue):
# Process volume average
avgResult = process_float(self._volumeAverage, volumeValue, candle.ServerTime, True)
if avgResult is None:
return
self._currentAvgVolume = float(avgResult)
if self._currentAvgVolume <= 0:
return
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
currentPrice = float(candle.ClosePrice)
currentVolume = float(candle.TotalVolume)
isPriceAboveSar = currentPrice > float(sarValue)
# Volume must be 1.5x above average
isHighVolume = currentVolume > self._currentAvgVolume * 1.5
if self._cooldown > 0:
self._cooldown -= 1
self._prevSar = float(sarValue)
self._prevPriceAboveSar = isPriceAboveSar
return
cooldown = int(self._cooldownBars.Value)
# Bullish crossover
if isPriceAboveSar and not self._prevPriceAboveSar and isHighVolume and self.Position <= 0:
self.CancelActiveOrders()
volume = self.Volume + abs(self.Position)
self.BuyMarket(volume)
self._cooldown = cooldown
# Bearish crossover
elif not isPriceAboveSar and self._prevPriceAboveSar and isHighVolume and self.Position >= 0:
self.CancelActiveOrders()
volume = self.Volume + abs(self.Position)
self.SellMarket(volume)
self._cooldown = cooldown
# Exit signals
elif (self.Position > 0 and not isPriceAboveSar) or (self.Position < 0 and isPriceAboveSar):
self.ClosePosition()
self._cooldown = cooldown
self._prevSar = float(sarValue)
self._prevPriceAboveSar = isPriceAboveSar
def CreateClone(self):
return parabolic_sar_volume_strategy()