Ver en GitHub

Parabolic SAR EA Strategy

Overview

The Parabolic SAR EA Strategy is the StockSharp high-level conversion of the MetaTrader expert advisor Parabolic SAR EA.mq5 located in MQL/23039. The original MQL script reacts to Parabolic SAR reversals on a configurable timeframe, opening market positions with fixed stop-loss and take-profit distances expressed in MetaTrader "pips" (fractional pip support included). The C# port subscribes to candles, binds the built-in ParabolicSar indicator, and reproduces the same bar-by-bar decision process while respecting StockSharp best practices.

Trading Logic

  1. Data preparation
    • The strategy subscribes to the user-selected candle type (30-minute candles by default) and binds a Parabolic SAR indicator configured with adjustable acceleration step and maximum values.
    • The SAR value is calculated for every candle and delivered to the strategy through the high-level Bind callback.
  2. Signal generation
    • Buy signal: when the Parabolic SAR value of the finished candle is strictly below the candle low.
    • Sell signal: when the Parabolic SAR value of the finished candle is strictly above the candle high.
    • Signals are evaluated only on completed candles (CandleStates.Finished) to match the MQL new-bar processing.
  3. Position management
    • Opposite exposure is flattened before a new entry by increasing the requested market order size with the absolute current position value, replicating the MetaTrader ClosePosition plus OpenPosition sequence.
    • Every entry recalculates protective stop-loss and take-profit levels using the same pip-to-price conversion rules as MetaTrader (digits 3/5 instruments receive a ×10 multiplier for the PriceStep).
  4. Protective exits
    • On every finished candle the strategy checks whether the high/low breaches the stored stop-loss or take-profit level. If triggered, the position is closed with a market order and the corresponding targets are cleared.
    • Protective logic fires before new signals on the same bar, mirroring the original Expert Advisor behaviour where stop orders are broker-side.

Indicator and Data Notes

  • Uses the built-in ParabolicSar indicator from StockSharp with parameters SarStep and SarMaximum.
  • Candle subscription is handled through SubscribeCandles without adding the indicator to Strategy.Indicators, as required by the project guidelines.
  • Trading is allowed only when IsFormedAndOnlineAndAllowTrading() reports true, ensuring that live data is present and the connector permits order submission.

Parameters

Name Default Description
TradeVolume 1 Market order size in lots. Updating the value also refreshes Strategy.Volume.
StopLossPips 50 Stop-loss distance in MetaTrader pips. A pip equals PriceStep × 10 for instruments with 3 or 5 decimals, otherwise just PriceStep. Set to 0 to disable.
TakeProfitPips 50 Take-profit distance in MetaTrader pips using the same conversion rules as the stop-loss. Set to 0 to disable.
SarStep 0.02 Acceleration step used by the Parabolic SAR indicator.
SarMaximum 0.2 Maximum acceleration factor for Parabolic SAR.
CandleType 30m timeframe Candle type used for calculations. Supports any DataType derived from TimeFrame.

Risk Management and Behaviour

  • Stop-loss and take-profit are recalculated after each fill and stored internally; no pending orders are registered with the exchange.
  • If both protective levels are hit inside a single candle, the stop-loss check fires first, replicating the conservative handling of the source MQL logic.
  • When the connector does not report a valid PriceStep, the conversion falls back to 0.0001 to avoid zero-distance protective levels.
  • No averaging or pyramiding is performed; the strategy operates with a single net position, flipping direction when the Parabolic SAR crosses price.

Conversion Notes

  • MetaTrader InpBarCurrent equals 1, meaning the EA evaluates the previous finished candle. The StockSharp port achieves the same outcome by processing only Finished candles in the Bind callback.
  • The original expert used CheckVolumeValue to validate lots and broker constraints. StockSharp delegates these checks to the connector, while the TradeVolume parameter still enforces a positive volume requirement.
  • Python implementation is intentionally omitted, matching the task requirements.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Parabolic SAR EA strategy using EMA crossover as proxy.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class ParabolicSarEaStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public ParabolicSarEaStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}