Auf GitHub ansehen

Parabolic SAR Alert Strategy

Overview

This strategy is the StockSharp port of the MetaTrader 4 expert advisor pSAR_alert.mq4. The original script only played an alert sound whenever the Parabolic SAR indicator flipped from one side of price to the other. The conversion keeps the same decision logic but turns the alerts into actual market orders, allowing the signal to be traded automatically inside StockSharp.

Trading Logic

  • The strategy subscribes to the configured candle type and runs a Parabolic SAR indicator with the classic acceleration factor (0.02) and maximum acceleration (0.2) by default.
  • For every finished candle the strategy compares the Parabolic SAR value with the candle close and also tracks the previous candle context.
  • When the previous candle closed below the SAR but the current close is above, the indicator has flipped downward and a long position is opened (or an existing short is reversed).
  • When the previous candle closed above the SAR but the current close is below, the indicator has flipped upward and a short position is opened (or an existing long is reversed).
  • Trade volume is calculated as the base strategy volume plus the absolute current position, ensuring reversals fully exit the prior trade before entering the new direction.
  • StartProtection() is executed on start so StockSharp automatically manages unexpected disconnections while positions are open.

Parameters

Parameter Default Description
AccelerationFactor 0.02 Initial acceleration step that controls how quickly the Parabolic SAR follows price movements.
MaxAccelerationFactor 0.2 Upper bound for the acceleration step, limiting how aggressively the SAR accelerates during strong trends.
CandleType 5 minute time frame Market data type used for indicator updates; change it to switch between time frames or other candle representations.

All parameters are exposed through StrategyParam<T> so they can be optimized directly in the StockSharp Designer.

Indicator Workflow

  1. Subscribe to the configured candle stream via SubscribeCandles.
  2. Bind the stream to a ParabolicSar indicator so StockSharp updates it automatically.
  3. Inside the binding callback compare the current SAR value with the close price and retain the previous SAR/close pair.
  4. Detect crossovers by evaluating whether the SAR moved from above to below the close (bullish flip) or from below to above (bearish flip).
  5. Execute BuyMarket or SellMarket accordingly and log descriptive messages for every trade.

Practical Notes

  • Because the strategy only reacts to confirmed candle closes it avoids premature signals that may disappear before the bar finishes.
  • The default parameters reproduce the behaviour of the MQL script, but you can adjust them to adapt the sensitivity of the Parabolic SAR.
  • Attach the strategy to instruments that trend cleanly; the SAR flip logic performs best when reversals are decisive rather than noisy.
  • Chart visualisation is enabled automatically when a chart area is available: candles, the Parabolic SAR indicator and own trades are drawn for quick inspection.

Files

  • CS/ParabolicSarCrossoverAlertStrategy.cs – C# implementation of the strategy.
  • README.md – This documentation in English.
  • README_zh.md – Chinese translation of the documentation.
  • README_ru.md – Russian translation of the documentation.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Parabolic SAR Crossover: EMA crossover with ATR stops.
/// </summary>
public class ParabolicSarCrossoverAlertStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _atrLength;

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

	public ParabolicSarCrossoverAlertStrategy()
	{
		_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");

		_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 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 atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, 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 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)
			{
				SellMarket();
				_entryPrice = 0;
			}
			else if (close <= _entryPrice - atrVal * 2m)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (fastVal > slowVal && _prevFast <= _prevSlow)
			{
				BuyMarket();
				_entryPrice = 0;
			}
			else if (close >= _entryPrice + atrVal * 2m)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

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

		_prevFast = fastVal;
		_prevSlow = slowVal;
	}
}