MA Parabolic SAR Strategy
The MA Parabolic SAR strategy attempts to capture sustained trends while using a trailing stop for risk control. A simple moving average determines the prevailing direction and the Parabolic SAR dots provide entry timing and stop placement. When both indicators align, the system assumes momentum is strong enough to follow.
Testing indicates an average annual return of about 76%. It performs best in the forex market.
A long position is opened when the closing price sits above the moving average and the Parabolic SAR dots flip below the market. A short position is taken when price is below the average and the SAR dots flip above price, signalling downward pressure. The strategy exits once price crosses the SAR in the opposite direction, locking in profits or limiting losses.
This approach is best suited for traders who prefer systematic trend following with clear, mechanical stops. The Parabolic SAR continuously adjusts as volatility changes, keeping exposure in line with market conditions while the moving average prevents trades against the broader trend.
Details
- Entry Criteria:
- Long: Price > MA && Price > Parabolic SAR
- Short: Price < MA && Price < Parabolic SAR
- Long/Short: Both sides.
- Exit Criteria:
- Long: Exit when price falls below Parabolic SAR
- Short: Exit when price rises above Parabolic SAR
- Stops: Yes, dynamic via Parabolic SAR and optional fixed stop.
- Default Values:
MaPeriod = 20
SarStep = 0.02m
SarMaxStep = 0.2m
CandleType = TimeSpan.FromMinutes(5)
TakeValue = new Unit(0, UnitTypes.Absolute)
StopValue = new Unit(2, UnitTypes.Percent)
- Filters:
- Category: Trend
- Direction: Both
- Indicators: MA, Parabolic SAR
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday
- 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 based on Moving Average and Parabolic SAR indicators.
/// Enters long when price is above MA and above SAR.
/// Enters short when price is below MA and below SAR.
/// Uses Parabolic SAR as dynamic stop-loss.
/// </summary>
public class MaParabolicSarStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<decimal> _sarStep;
private readonly StrategyParam<decimal> _sarMaxStep;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<Unit> _takeValue;
private readonly StrategyParam<Unit> _stopValue;
private SimpleMovingAverage _ma;
private ExponentialMovingAverage _sarProxy;
private decimal _lastSarValue;
private bool _hasPrevState;
private bool _prevAboveMa;
private bool _prevAboveSar;
private int _cooldown;
/// <summary>
/// Moving Average period.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Parabolic SAR acceleration factor.
/// </summary>
public decimal SarStep
{
get => _sarStep.Value;
set => _sarStep.Value = value;
}
/// <summary>
/// Parabolic SAR maximum acceleration factor.
/// </summary>
public decimal SarMaxStep
{
get => _sarMaxStep.Value;
set => _sarMaxStep.Value = value;
}
/// <summary>
/// Bars to wait between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type parameter.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Take profit value.
/// </summary>
public Unit TakeValue
{
get => _takeValue.Value;
set => _takeValue.Value = value;
}
/// <summary>
/// Stop loss value.
/// </summary>
public Unit StopValue
{
get => _stopValue.Value;
set => _stopValue.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MaParabolicSarStrategy()
{
_maPeriod = Param(nameof(MaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period for Moving Average calculation", "Indicators")
.SetOptimize(10, 50, 5);
_sarStep = Param(nameof(SarStep), 0.02m)
.SetGreaterThanZero()
.SetDisplay("SAR Step", "Acceleration factor for Parabolic SAR", "Indicators")
.SetOptimize(0.01m, 0.05m, 0.01m);
_sarMaxStep = Param(nameof(SarMaxStep), 0.2m)
.SetGreaterThanZero()
.SetDisplay("SAR Max Step", "Maximum acceleration factor for Parabolic SAR", "Indicators")
.SetOptimize(0.1m, 0.3m, 0.05m);
_cooldownBars = Param(nameof(CooldownBars), 20)
.SetRange(1, 200)
.SetDisplay("Cooldown Bars", "Bars between trades", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_takeValue = Param(nameof(TakeValue), new Unit(0, UnitTypes.Absolute))
.SetDisplay("Take Profit", "Take profit value", "Protection");
_stopValue = Param(nameof(StopValue), new Unit(2, UnitTypes.Percent))
.SetDisplay("Stop Loss", "Stop loss value", "Protection");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ma = null;
_sarProxy = null;
_lastSarValue = default;
_hasPrevState = false;
_prevAboveMa = false;
_prevAboveSar = false;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Initialize indicators
_ma = new() { Length = MaPeriod };
_sarProxy = new ExponentialMovingAverage
{
Length = Math.Max(2, MaPeriod / 2)
};
// Create candles subscription
var subscription = SubscribeCandles(CandleType);
// Bind indicators to subscription
subscription
.Bind(_ma, _sarProxy, ProcessCandle)
.Start();
// Setup chart if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ma);
DrawIndicator(area, _sarProxy);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal maValue, decimal sarValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Store current SAR value for stop-loss
_lastSarValue = sarValue;
// Trading logic
bool isPriceAboveMA = candle.ClosePrice > maValue;
bool isPriceAboveSAR = candle.ClosePrice > sarValue;
if (!_hasPrevState)
{
_hasPrevState = true;
_prevAboveMa = isPriceAboveMA;
_prevAboveSar = isPriceAboveSAR;
return;
}
var turnedBull = !_prevAboveSar && isPriceAboveSAR && isPriceAboveMA;
var turnedBear = _prevAboveSar && !isPriceAboveSAR && !isPriceAboveMA;
var sarFlipDown = _prevAboveSar && !isPriceAboveSAR;
var sarFlipUp = !_prevAboveSar && isPriceAboveSAR;
if (_cooldown > 0)
_cooldown--;
// Long signal: Price above MA and above SAR
if (_cooldown == 0 && turnedBull)
{
if (Position <= 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
// Short signal: Price below MA and below SAR
else if (_cooldown == 0 && turnedBear)
{
if (Position >= 0)
{
SellMarket();
_cooldown = CooldownBars;
}
}
// Exit long position: Price falls below SAR
else if (Position > 0 && sarFlipDown)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && sarFlipUp)
{
BuyMarket();
_cooldown = CooldownBars;
}
_prevAboveMa = isPriceAboveMA;
_prevAboveSar = 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
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_parabolic_sar_strategy(Strategy):
"""
MA + SAR proxy (EMA). Enters on combined MA/SAR flip signals.
"""
def __init__(self):
super(ma_parabolic_sar_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 20).SetDisplay("MA Period", "SMA period", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 20).SetDisplay("Cooldown", "Bars between trades", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
self._has_prev = False
self._prev_above_ma = False
self._prev_above_sar = False
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ma_parabolic_sar_strategy, self).OnReseted()
self._has_prev = False
self._prev_above_ma = False
self._prev_above_sar = False
self._cooldown = 0
def OnStarted2(self, time):
super(ma_parabolic_sar_strategy, self).OnStarted2(time)
ma = SimpleMovingAverage()
ma.Length = self._ma_period.Value
sar_proxy = ExponentialMovingAverage()
sar_proxy.Length = max(2, self._ma_period.Value // 2)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ma, sar_proxy, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma)
self.DrawIndicator(area, sar_proxy)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ma_val, sar_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
ma = float(ma_val)
sar = float(sar_val)
above_ma = close > ma
above_sar = close > sar
if not self._has_prev:
self._has_prev = True
self._prev_above_ma = above_ma
self._prev_above_sar = above_sar
return
turned_bull = not self._prev_above_sar and above_sar and above_ma
turned_bear = self._prev_above_sar and not above_sar and not above_ma
sar_flip_down = self._prev_above_sar and not above_sar
sar_flip_up = not self._prev_above_sar and above_sar
if self._cooldown > 0:
self._cooldown -= 1
if self._cooldown == 0 and turned_bull:
if self.Position <= 0:
self.BuyMarket()
self._cooldown = self._cooldown_bars.Value
elif self._cooldown == 0 and turned_bear:
if self.Position >= 0:
self.SellMarket()
self._cooldown = self._cooldown_bars.Value
elif self.Position > 0 and sar_flip_down:
self.SellMarket()
self._cooldown = self._cooldown_bars.Value
elif self.Position < 0 and sar_flip_up:
self.BuyMarket()
self._cooldown = self._cooldown_bars.Value
self._prev_above_ma = above_ma
self._prev_above_sar = above_sar
def CreateClone(self):
return ma_parabolic_sar_strategy()