View on GitHub

Parabolic SAR Bug 2 Strategy

Overview

The Parabolic SAR Bug 2 Strategy is the StockSharp high-level conversion of the MetaTrader expert advisor pSAR_bug2 from the folder MQL/9503. The original EA reacts to the very first Parabolic SAR dot that appears on the opposite side of the price. When the dot flips below the close, the system closes any short trades and immediately opens a long position; when the dot jumps above the close, the logic mirrors the behaviour on the short side. Protective stop-loss and take-profit levels are calculated in raw price points, exactly like in MetaTrader where the values are multiplied by the instrument Point size.

The StockSharp port keeps the same intent while leveraging the framework's high-level API. It subscribes to finished candles, binds a Parabolic SAR indicator with configurable acceleration parameters, monitors dot reversals, and sends market orders sized to both flatten the previous exposure and establish the new trade.

Trading Logic

  1. Indicator preparation. The strategy subscribes to a user-defined candle type (15-minute time-frame by default) and binds a Parabolic SAR with acceleration step SarStep and maximum acceleration SarMaximum.
  2. State tracking. On the first completed candle the algorithm records whether the SAR value is above or below the close. Every new candle compares the fresh SAR position with the previously stored state.
  3. Entry rules.
    • Long entry: triggered when the SAR moves from above the close to below the close. The order volume is calculated as TradeVolume + |Position|, so an existing short position is closed and reversed in a single market order. After entry, stop-loss and take-profit levels are stored relative to the candle close.
    • Short entry: triggered when the SAR moves from below the close to above the close. Any existing long position is flattened and a new short trade is entered at market with the same combined size formula.
  4. Protective exits. On every completed candle the stored stop-loss and take-profit levels are compared with the high/low. If the price pierces a protective level, the strategy sends a market order to close the open position and resets the cached stop and take values.

Risk Management

  • Stop-loss and take-profit distances are calculated in raw price points by multiplying the configured StopLossPoints or TakeProfitPoints by the security price step. A conservative fallback of 0.0001 is used when the instrument does not publish a price step.
  • The strategy checks IsFormedAndOnlineAndAllowTrading() before submitting orders, ensuring that market data is online and trading is allowed.
  • Reversal entries always include the absolute current position size, guaranteeing that the new order flattens the previous exposure before establishing the opposite trade.

Parameters

Name Default Description
TradeVolume 0.1 Base order volume in lots. The same value is assigned to the internal Strategy.Volume property.
StopLossPoints 90 Stop-loss distance in price points. The distance is multiplied by the instrument price step to obtain the actual price offset.
TakeProfitPoints 20 Take-profit distance in price points converted through the instrument price step.
SarStep 0.001 Initial acceleration factor for the Parabolic SAR indicator.
SarMaximum 0.2 Maximum acceleration factor for the Parabolic SAR indicator.
CandleType 15m time-frame Candle type used for calculations and signal evaluation.

Notes on the Conversion

  • MetaTrader's broker-side stop-loss and take-profit orders are emulated by monitoring candle extremes and submitting market exits when the thresholds are breached.
  • The MetaTrader EA required manual management of OrdersTotal() and explicit OrderClose() calls. The StockSharp version achieves the same behaviour by sending a single market order sized as TradeVolume + |Position|, which simultaneously closes any opposite position and opens the new one.
  • No Python implementation is provided, matching the task request. The folder currently contains only the C# version of the strategy.
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Parabolic SAR Bug2: EMA crossover with RSI filter and ATR stops.
/// </summary>
public class ParabolicSarBug2Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;

	public ParabolicSarBug2Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");
		_fastEmaLength = Param(nameof(FastEmaLength), 10)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");
		_slowEmaLength = Param(nameof(SlowEmaLength), 30)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");
		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI period.", "Indicators");
		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int FastEmaLength { get => _fastEmaLength.Value; set => _fastEmaLength.Value = value; }
	public int SlowEmaLength { get => _slowEmaLength.Value; set => _slowEmaLength.Value = value; }
	public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
	public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0;

		var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fastEma, slowEma, rsi, atr, ProcessCandle).Start();

		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fastEma); DrawIndicator(area, slowEma); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal rsiVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished) return;
		if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0) { _prevFast = fastVal; _prevSlow = slowVal; return; }
		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if ((fastVal < slowVal && _prevFast >= _prevSlow) || close <= _entryPrice - atrVal * 2m) { SellMarket(); _entryPrice = 0; }
		}
		else if (Position < 0)
		{
			if ((fastVal > slowVal && _prevFast <= _prevSlow) || close >= _entryPrice + atrVal * 2m) { BuyMarket(); _entryPrice = 0; }
		}

		if (Position == 0)
		{
			if (fastVal > slowVal && _prevFast <= _prevSlow && rsiVal > 50) { _entryPrice = close; BuyMarket(); }
			else if (fastVal < slowVal && _prevFast >= _prevSlow && rsiVal < 50) { _entryPrice = close; SellMarket(); }
		}

		_prevFast = fastVal; _prevSlow = slowVal;
	}
}