Parabolic SAR Hurst Filter
The Parabolic SAR Hurst Filter strategy is built around Parabolic SAR Hurst Filter.
Testing indicates an average annual return of about 82%. It performs best in the stocks market.
Signals trigger when Parabolic confirms filtered entries on intraday (5m) data. This makes the method suitable for active traders.
Stops rely on ATR multiples and factors like SarAccelerationFactor, SarMaxAccelerationFactor. Adjust these defaults to balance risk and reward.
Details
- Entry Criteria: see implementation for indicator conditions.
- Long/Short: Both directions.
- Exit Criteria: opposite signal or stop logic.
- Stops: Yes, using indicator-based calculations.
- Default Values:
SarAccelerationFactor = 0.02mSarMaxAccelerationFactor = 0.2mHurstPeriod = 100CandleType = TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: Parabolic, Hurst
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday (5m)
- 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>
/// Parabolic SAR with Hurst Filter Strategy.
/// Enters a position when price crosses SAR and Hurst exponent indicates a persistent trend.
/// </summary>
public class ParabolicSarHurstStrategy : Strategy
{
private readonly StrategyParam<decimal> _sarAccelerationFactor;
private readonly StrategyParam<decimal> _sarMaxAccelerationFactor;
private readonly StrategyParam<int> _hurstPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _signalCooldownBars;
private ParabolicSar _parabolicSar = null!;
private HurstExponent _hurstIndicator = null!;
private decimal _prevSarValue;
private decimal _hurstValue;
private bool? _prevPriceAboveSar;
private int _cooldownRemaining;
/// <summary>
/// Parabolic SAR acceleration factor.
/// </summary>
public decimal SarAccelerationFactor
{
get => _sarAccelerationFactor.Value;
set => _sarAccelerationFactor.Value = value;
}
/// <summary>
/// Parabolic SAR maximum acceleration factor.
/// </summary>
public decimal SarMaxAccelerationFactor
{
get => _sarMaxAccelerationFactor.Value;
set => _sarMaxAccelerationFactor.Value = value;
}
/// <summary>
/// Hurst exponent calculation period.
/// </summary>
public int HurstPeriod
{
get => _hurstPeriod.Value;
set => _hurstPeriod.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Number of closed candles to wait before a new SAR crossover entry.
/// </summary>
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
/// <summary>
/// Initialize strategy.
/// </summary>
public ParabolicSarHurstStrategy()
{
_sarAccelerationFactor = Param(nameof(SarAccelerationFactor), 0.02m)
.SetRange(0.01m, 0.2m)
.SetDisplay("SAR Acceleration Factor", "Initial acceleration factor for Parabolic SAR", "SAR Settings")
.SetOptimize(0.01m, 0.1m, 0.01m);
_sarMaxAccelerationFactor = Param(nameof(SarMaxAccelerationFactor), 0.2m)
.SetRange(0.05m, 0.5m)
.SetDisplay("SAR Max Acceleration Factor", "Maximum acceleration factor for Parabolic SAR", "SAR Settings")
.SetOptimize(0.1m, 0.3m, 0.05m);
_hurstPeriod = Param(nameof(HurstPeriod), 100)
.SetRange(20, 200)
.SetDisplay("Hurst Period", "Period for Hurst exponent calculation", "Hurst Settings")
.SetOptimize(50, 150, 25);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 4)
.SetNotNegative()
.SetDisplay("Signal Cooldown Bars", "Closed candles to wait before a new SAR crossover entry", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_parabolicSar = null!;
_hurstIndicator = null!;
_prevSarValue = 0;
_hurstValue = 0.5m;
_prevPriceAboveSar = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
_parabolicSar = new ParabolicSar
{
Acceleration = SarAccelerationFactor,
AccelerationMax = SarMaxAccelerationFactor
};
_hurstIndicator = new HurstExponent
{
Length = HurstPeriod
};
// Create subscription for candles
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
// Start position protection
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _parabolicSar);
DrawIndicator(area, _hurstIndicator);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var sarValue = _parabolicSar.Process(new CandleIndicatorValue(_parabolicSar, candle));
var hurstValue = _hurstIndicator.Process(new CandleIndicatorValue(_hurstIndicator, candle));
if (!_parabolicSar.IsFormed || !_hurstIndicator.IsFormed || sarValue.IsEmpty || hurstValue.IsEmpty)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var sarPrice = sarValue.ToDecimal();
_hurstValue = hurstValue.ToDecimal();
var currentSarValue = sarPrice;
var priceAboveSar = candle.ClosePrice > sarPrice;
if (_prevPriceAboveSar is null || _prevSarValue == 0)
{
_prevSarValue = currentSarValue;
_prevPriceAboveSar = priceAboveSar;
return;
}
if (_hurstValue > 0.55m)
{
var bullishCross = !_prevPriceAboveSar.Value && priceAboveSar;
var bearishCross = _prevPriceAboveSar.Value && !priceAboveSar;
if (_cooldownRemaining == 0 && bullishCross && Position <= 0)
{
BuyMarket(Volume + (Position < 0 ? Math.Abs(Position) : 0m));
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && bearishCross && Position >= 0)
{
SellMarket(Volume + (Position > 0 ? Math.Abs(Position) : 0m));
_cooldownRemaining = SignalCooldownBars;
}
}
else
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(Math.Abs(Position));
}
_prevSarValue = currentSarValue;
_prevPriceAboveSar = priceAboveSar;
}
}
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, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import ParabolicSar, HurstExponent, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class parabolic_sar_hurst_strategy(Strategy):
"""
Parabolic SAR with Hurst Filter Strategy.
Enters a position when price crosses SAR and Hurst exponent indicates a persistent trend.
"""
def __init__(self):
super(parabolic_sar_hurst_strategy, self).__init__()
self._sar_acceleration_factor = self.Param("SarAccelerationFactor", 0.02) \
.SetRange(0.01, 0.2) \
.SetDisplay("SAR Acceleration Factor", "Initial acceleration factor for Parabolic SAR", "SAR Settings") \
.SetCanOptimize(True) \
.SetOptimize(0.01, 0.1, 0.01)
self._sar_max_acceleration_factor = self.Param("SarMaxAccelerationFactor", 0.2) \
.SetRange(0.05, 0.5) \
.SetDisplay("SAR Max Acceleration Factor", "Maximum acceleration factor for Parabolic SAR", "SAR Settings") \
.SetCanOptimize(True) \
.SetOptimize(0.1, 0.3, 0.05)
self._hurst_period = self.Param("HurstPeriod", 100) \
.SetRange(20, 200) \
.SetDisplay("Hurst Period", "Period for Hurst exponent calculation", "Hurst Settings") \
.SetCanOptimize(True) \
.SetOptimize(50, 150, 25)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 4) \
.SetNotNegative() \
.SetDisplay("Signal Cooldown Bars", "Closed candles to wait before a new SAR crossover entry", "General")
self._parabolic_sar = None
self._hurst_indicator = None
self._prev_sar_value = 0.0
self._hurst_value = 0.5
self._prev_price_above_sar = None
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(parabolic_sar_hurst_strategy, self).OnReseted()
self._parabolic_sar = None
self._hurst_indicator = None
self._prev_sar_value = 0.0
self._hurst_value = 0.5
self._prev_price_above_sar = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(parabolic_sar_hurst_strategy, self).OnStarted2(time)
self._parabolic_sar = ParabolicSar()
self._parabolic_sar.Acceleration = Decimal(self._sar_acceleration_factor.Value)
self._parabolic_sar.AccelerationMax = Decimal(self._sar_max_acceleration_factor.Value)
self._hurst_indicator = HurstExponent()
self._hurst_indicator.Length = int(self._hurst_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.ProcessCandle).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent)
)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._parabolic_sar)
self.DrawIndicator(area, self._hurst_indicator)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
sar_val = self._parabolic_sar.Process(CandleIndicatorValue(self._parabolic_sar, candle))
hurst_val = self._hurst_indicator.Process(CandleIndicatorValue(self._hurst_indicator, candle))
if not self._parabolic_sar.IsFormed or not self._hurst_indicator.IsFormed or sar_val.IsEmpty or hurst_val.IsEmpty:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
sar_price = float(sar_val)
self._hurst_value = float(hurst_val)
current_sar_value = sar_price
price_above_sar = float(candle.ClosePrice) > sar_price
if self._prev_price_above_sar is None or self._prev_sar_value == 0.0:
self._prev_sar_value = current_sar_value
self._prev_price_above_sar = price_above_sar
return
cooldown_bars = int(self._signal_cooldown_bars.Value)
if self._hurst_value > 0.55:
bullish_cross = not self._prev_price_above_sar and price_above_sar
bearish_cross = self._prev_price_above_sar and not price_above_sar
if self._cooldown_remaining == 0 and bullish_cross and self.Position <= 0:
vol = self.Volume
if self.Position < 0:
vol = self.Volume + Math.Abs(self.Position)
self.BuyMarket(vol)
self._cooldown_remaining = cooldown_bars
elif self._cooldown_remaining == 0 and bearish_cross and self.Position >= 0:
vol = self.Volume
if self.Position > 0:
vol = self.Volume + Math.Abs(self.Position)
self.SellMarket(vol)
self._cooldown_remaining = cooldown_bars
else:
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self._prev_sar_value = current_sar_value
self._prev_price_above_sar = price_above_sar
def CreateClone(self):
return parabolic_sar_hurst_strategy()