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 guards 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() AvgVolume is the average volume of the previous VolumePeriod candles. A long closes when the SAR flips above price and a short when it flips below. An entry signal against an open position reverses it.
- 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.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Parabolic SAR Volume strategy.
/// A close above the Parabolic SAR on volume above the average of the previous VolumePeriod candles goes long and a close below it on such
/// volume goes short, reversing an opposite position. The SAR is the trailing stop: a long closes when the SAR flips above price and a short
/// when it flips below.
/// </summary>
public class ParabolicSarVolumeStrategy : Strategy
{
private readonly StrategyParam<decimal> _acceleration;
private readonly StrategyParam<decimal> _maxAcceleration;
private readonly StrategyParam<int> _volumePeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _volumes = [];
/// <summary>
/// Initial acceleration factor of the SAR.
/// </summary>
public decimal Acceleration
{
get => _acceleration.Value;
set => _acceleration.Value = value;
}
/// <summary>
/// Maximum acceleration factor of the SAR.
/// </summary>
public decimal MaxAcceleration
{
get => _maxAcceleration.Value;
set => _maxAcceleration.Value = value;
}
/// <summary>
/// Previous candles the volume is averaged over.
/// </summary>
public int VolumePeriod
{
get => _volumePeriod.Value;
set => _volumePeriod.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public ParabolicSarVolumeStrategy()
{
_acceleration = Param(nameof(Acceleration), 0.02m)
.SetGreaterThanZero()
.SetDisplay("SAR Acceleration", "Initial acceleration factor of the SAR", "SAR");
_maxAcceleration = Param(nameof(MaxAcceleration), 0.2m)
.SetGreaterThanZero()
.SetDisplay("SAR Max Acceleration", "Maximum acceleration factor of the SAR", "SAR");
_volumePeriod = Param(nameof(VolumePeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Volume Period", "Previous candles the volume is averaged over", "Volume");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
_volumes.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_volumes.Clear();
var sar = new ParabolicSar
{
Acceleration = Acceleration,
AccelerationMax = MaxAcceleration,
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(sar, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sar);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue sarValue)
{
if (candle.State != CandleStates.Finished)
return;
// Volume is compared with the candles before this one.
var average = _volumes.Count == VolumePeriod ? _volumes.Average() : (decimal?)null;
_volumes.Add(candle.TotalVolume);
if (_volumes.Count > VolumePeriod)
_volumes.RemoveAt(0);
// The first SAR value is formed but empty.
if (!sarValue.IsFormed || sarValue.IsEmpty || average is not decimal avgVolume)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var sar = sarValue.GetValue<decimal>();
var close = candle.ClosePrice;
var surge = candle.TotalVolume > avgVolume;
if (close > sar && surge && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (close < sar && surge && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && close < sar)
SellMarket(Position);
else if (Position < 0 && close > sar)
BuyMarket(-Position);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ParabolicSar
from StockSharp.Algo.Strategies import Strategy
class parabolic_sar_volume_strategy(Strategy):
"""
Parabolic SAR Volume strategy.
A close above the Parabolic SAR on volume above the average of the previous VolumePeriod candles goes long and a close below it on such
volume goes short, reversing an opposite position. The SAR is the trailing stop: a long closes when the SAR flips above price and a short
when it flips below.
"""
def __init__(self):
super(parabolic_sar_volume_strategy, self).__init__()
self._acceleration = self.Param("Acceleration", 0.02).SetGreaterThanZero().SetDisplay("SAR Acceleration", "Initial acceleration factor of the SAR", "SAR")
self._max_acceleration = self.Param("MaxAcceleration", 0.2).SetGreaterThanZero().SetDisplay("SAR Max Acceleration", "Maximum acceleration factor of the SAR", "SAR")
self._volume_period = self.Param("VolumePeriod", 20).SetGreaterThanZero().SetDisplay("Volume Period", "Previous candles the volume is averaged over", "Volume")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._volumes = []
def OnReseted(self):
super(parabolic_sar_volume_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(parabolic_sar_volume_strategy, self).OnStarted2(time)
self._reset_state()
sar = ParabolicSar()
sar.Acceleration = Decimal(self._acceleration.Value)
sar.AccelerationMax = Decimal(self._max_acceleration.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(sar, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sar)
self.DrawOwnTrades(area)
def _process_candle(self, candle, sar_value):
if candle.State != CandleStates.Finished:
return
# Volume is compared with the candles before this one.
period = self._volume_period.Value
average = None
if len(self._volumes) == period:
total = Decimal(0)
for volume in self._volumes:
total += volume
average = total / Decimal(period)
self._volumes.append(candle.TotalVolume)
if len(self._volumes) > period:
self._volumes.pop(0)
# The first SAR value is formed but empty.
if not sar_value.IsFormed or sar_value.IsEmpty or average is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
sar = sar_value.GetValue[Decimal](None)
close = candle.ClosePrice
surge = candle.TotalVolume > average
if close > sar and surge and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif close < sar and surge and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and close < sar:
self.SellMarket(self.Position)
elif self.Position < 0 and close > sar:
self.BuyMarket(-self.Position)
def CreateClone(self):
return parabolic_sar_volume_strategy()