Parabolic SAR Sentiment Divergence
The Parabolic SAR Sentiment Divergence strategy is built around Parabolic SAR Sentiment Divergence.
Testing indicates an average annual return of about 127%. It performs best in the stocks market.
Signals trigger when Parabolic confirms divergence setups on intraday (5m) data. This makes the method suitable for active traders.
Stops rely on ATR multiples and factors like StartAf, MaxAf. 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:
StartAf = 0.02mMaxAf = 0.2mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: Parabolic, Divergence
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday (5m)
- Seasonality: No
- Neural Networks: No
- Divergence: Yes
- 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 strategy with sentiment divergence.
/// </summary>
public class ParabolicSarSentimentDivergenceStrategy : Strategy
{
private readonly StrategyParam<decimal> _startAf;
private readonly StrategyParam<decimal> _maxAf;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private ParabolicSar _parabolicSar;
private decimal _prevPrice;
private bool _prevAboveSar;
private bool _isFirstCandle = true;
private int _cooldownRemaining;
/// <summary>
/// SAR Starting acceleration factor.
/// </summary>
public decimal StartAf
{
get => _startAf.Value;
set => _startAf.Value = value;
}
/// <summary>
/// SAR Maximum acceleration factor.
/// </summary>
public decimal MaxAf
{
get => _maxAf.Value;
set => _maxAf.Value = value;
}
/// <summary>
/// Closed candles to wait before another position change.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="ParabolicSarSentimentDivergenceStrategy"/>.
/// </summary>
public ParabolicSarSentimentDivergenceStrategy()
{
_startAf = Param(nameof(StartAf), 0.02m)
.SetRange(0.01m, 0.1m)
.SetDisplay("Starting AF", "Starting acceleration factor for Parabolic SAR", "SAR Parameters");
_maxAf = Param(nameof(MaxAf), 0.2m)
.SetRange(0.1m, 0.5m)
.SetDisplay("Maximum AF", "Maximum acceleration factor for Parabolic SAR", "SAR Parameters");
_cooldownBars = Param(nameof(CooldownBars), 24)
.SetNotNegative()
.SetDisplay("Cooldown Bars", "Closed candles to wait before another position change", "General");
_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();
_parabolicSar = null;
_prevPrice = default;
_prevAboveSar = default;
_isFirstCandle = true;
_cooldownRemaining = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicator
_parabolicSar = new ParabolicSar
{
Acceleration = StartAf,
AccelerationMax = MaxAf,
};
// Create subscription
var subscription = SubscribeCandles(CandleType);
// Bind indicator and processor
subscription
.Bind(_parabolicSar, ProcessCandle)
.Start();
// Setup visualization
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _parabolicSar);
DrawOwnTrades(area);
}
// Start position protection
StartProtection(
new Unit(2, UnitTypes.Percent), // Take profit 2%
new Unit(2, UnitTypes.Percent), // Stop loss 2%
true // Use trailing stop
);
}
private void ProcessCandle(ICandleMessage candle, decimal sarPrice)
{
if (candle.State != CandleStates.Finished)
return;
var price = candle.ClosePrice;
var priceAboveSar = price > sarPrice;
if (_isFirstCandle)
{
_prevPrice = price;
_prevAboveSar = priceAboveSar;
_isFirstCandle = false;
return;
}
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var bullishFlip = !_prevAboveSar && priceAboveSar;
var bearishFlip = _prevAboveSar && !priceAboveSar;
if (_cooldownRemaining == 0 && Position == 0)
{
if (bullishFlip)
{
BuyMarket();
_cooldownRemaining = CooldownBars;
}
else if (bearishFlip)
{
SellMarket();
_cooldownRemaining = CooldownBars;
}
}
_prevPrice = price;
_prevAboveSar = priceAboveSar;
}
private decimal GetSentiment(ICandleMessage candle)
{
var totalRange = candle.HighPrice - candle.LowPrice;
if (totalRange <= 0)
return 0m;
var body = candle.ClosePrice - candle.OpenPrice;
var bodyRatio = body / totalRange;
var rangeRatio = totalRange / Math.Max(candle.OpenPrice, 1m);
var sentiment = (bodyRatio * 0.7m) + (Math.Sign(body) * Math.Min(0.3m, rangeRatio * 10m));
return Math.Max(-1m, Math.Min(1m, sentiment));
}
}
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
from StockSharp.Algo.Strategies import Strategy
class parabolic_sar_sentiment_divergence_strategy(Strategy):
"""Parabolic SAR strategy with sentiment divergence."""
def __init__(self):
super(parabolic_sar_sentiment_divergence_strategy, self).__init__()
self._start_af = self.Param("StartAf", 0.02) \
.SetRange(0.01, 0.1) \
.SetDisplay("Starting AF", "Starting acceleration factor for Parabolic SAR", "SAR Parameters")
self._max_af = self.Param("MaxAf", 0.2) \
.SetRange(0.1, 0.5) \
.SetDisplay("Maximum AF", "Maximum acceleration factor for Parabolic SAR", "SAR Parameters")
self._cooldown_bars = self.Param("CooldownBars", 24) \
.SetNotNegative() \
.SetDisplay("Cooldown Bars", "Closed candles to wait before another position change", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_sentiment = 0.0
self._prev_price = 0.0
self._prev_above_sar = False
self._is_first_candle = True
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_sentiment_divergence_strategy, self).OnReseted()
self._prev_sentiment = 0.0
self._prev_price = 0.0
self._prev_above_sar = False
self._is_first_candle = True
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(parabolic_sar_sentiment_divergence_strategy, self).OnStarted2(time)
sar = ParabolicSar()
sar.Acceleration = Decimal(float(self._start_af.Value))
sar.AccelerationMax = Decimal(float(self._max_af.Value))
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sar, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sar)
self.DrawOwnTrades(area)
self.StartProtection(
Unit(2, UnitTypes.Percent),
Unit(2, UnitTypes.Percent),
True
)
def ProcessCandle(self, candle, sar_price):
if candle.State != CandleStates.Finished:
return
price = float(candle.ClosePrice)
sar = float(sar_price)
price_above_sar = price > sar
if self._is_first_candle:
self._prev_price = price
self._prev_above_sar = price_above_sar
self._is_first_candle = False
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
cooldown = int(self._cooldown_bars.Value)
bullish_flip = (not self._prev_above_sar) and price_above_sar
bearish_flip = self._prev_above_sar and (not price_above_sar)
if self._cooldown_remaining == 0 and self.Position == 0:
if bullish_flip:
self.BuyMarket()
self._cooldown_remaining = cooldown
elif bearish_flip:
self.SellMarket()
self._cooldown_remaining = cooldown
self._prev_price = price
self._prev_above_sar = price_above_sar
def CreateClone(self):
return parabolic_sar_sentiment_divergence_strategy()