View on GitHub

Separate Trade Strategy

Overview

The Separate Trade strategy is a conversion of the MetaTrader 5 expert advisor "Separate trade". It preserves the original multi-filter logic while adopting the StockSharp high-level API for robust order management and indicator handling. The strategy attempts to capture quiet market turns when volatility and dispersion are suppressed. Only one net position is maintained at a time, which mirrors the intent of the original code that limited the number of simultaneous positions.

Indicators and Data

  • Two moving averages with configurable method (SMA, EMA, SMMA or LWMA) and shared price source.
  • Average True Range (ATR) with separate periods and thresholds for long and short filters.
  • Standard deviation using the same applied price as the moving averages, again with direction-specific periods and ceilings.
  • Candles are supplied through a configurable DataType parameter so the strategy can be attached to any timeframe or custom candle builder.

Parameters

Parameter Description Default
TradeVolume Order size expressed in lots. 1
SlowMaPeriod Period of the slower moving average. 65
FastMaPeriod Period of the faster moving average. 14
MaMethod Smoothing method applied to both moving averages (Simple, Exponential, Smoothed, LinearWeighted). Exponential
PriceType Price input for the moving averages and standard deviation (Close, Open, High, Low, Median, Typical, Weighted). Close
StopLossBuyPips / StopLossSellPips Stop-loss distance for long and short trades in pips (0 disables the stop). 50
TakeProfitBuyPips / TakeProfitSellPips Take-profit distance for long and short trades in pips (0 disables the take-profit). 50
TrailingStopPips Trailing stop distance in pips. 5
TrailingStepPips Minimum profit advance in pips before the trailing stop is moved. Must be positive when trailing is enabled. 5
MaxPositions Maximum allowed simultaneous net positions. The StockSharp version operates with a single aggregated position even when the value is greater than one. 1
DeltaBuyPips / DeltaSellPips Maximum allowed distance between the fast and slow moving averages (per direction). A value of zero disables the distance filter. 2
AtrPeriodBuy / AtrPeriodSell ATR lookback period for the long and short filters. 26
AtrLevelBuy / AtrLevelSell Upper ATR threshold that must not be exceeded before entering a trade. 0.0016
StdDevPeriodBuy / StdDevPeriodSell Standard deviation lookback period for the long and short filters. 54
StdDevLevelBuy / StdDevLevelSell Standard deviation ceiling that must not be exceeded before entering a trade. 0.0051
CandleType Candle data type used by the subscription. TimeSpan.FromMinutes(15)

Trading Logic

  1. Bar synchronisation – the strategy acts only on finished candles received from the configured subscription. This replicates the OnTick new-bar guard from the MetaTrader script.
  2. Indicator filters – for long entries the slow MA must be below the fast MA, ATR must be below AtrLevelBuy, standard deviation must be below StdDevLevelBuy, and the MA distance must be smaller than DeltaBuyPips (if the delta is positive). Short entries invert the conditions and use their own ATR and deviation parameters.
  3. Position gating – trades are only taken when there is no open position and the latest entry time for the respective side is older than the current candle. This prevents re-entries within the same bar, matching the m_last_deal_IN_* check in the source EA.
  4. Order execution – market orders are placed with the configured volume. Reversal trades automatically flatten the current position before opening a new one thanks to the Volume + Math.Abs(Position) quantity that matches the MQL behaviour of closing opposite exposure.

Risk Management

  • Pip conversion – pip distances are converted using the security PriceStep. For instruments quoted with 3 or 5 decimals the pip size equals PriceStep * 10, mirroring the original digits_adjust logic.
  • Stop-loss / take-profit – the strategy tracks price levels internally and exits when the candle range touches the specified stop or target. Both can be disabled by setting the pip distance to zero.
  • Trailing stop – once price advances beyond TrailingStopPips + TrailingStepPips, the stop is moved to maintain the trailing distance. The trailing step requirement matches the MetaTrader implementation and avoids moving the stop by an insignificant amount.

Implementation Notes

  • The strategy uses a single aggregated position because StockSharp works with net positions by default. Although the MaxPositions parameter is retained for compatibility, exceeding one simply prevents new entries until the current position is closed.
  • Indicator values are calculated using the StockSharp indicator classes and the Bind infrastructure to avoid manual buffer access as required by the project guidelines.
  • The conversion keeps all comments in English and maps every original input to a dedicated StrategyParam so that optimisation and Designer integration remain available.
  • When TrailingStopPips is positive, TrailingStepPips must also be positive. The code stops the strategy early and writes an error message if this requirement is violated, reproducing the safety check from the MQL expert.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Separate trade strategy using dual EMA crossover with ATR volatility filter.
/// Trades when fast EMA crosses slow EMA and ATR confirms adequate volatility.
/// </summary>
public class SeparateTradeStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _atrPeriod;

	private decimal? _prevFast;
	private decimal? _prevSlow;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	public SeparateTradeStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_fastPeriod = Param(nameof(FastPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 65)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow EMA period", "Indicators");

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR period for volatility filter", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = null;
		_prevSlow = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevFast = null;
		_prevSlow = null;

		var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
		var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
		var atr = new AverageTrueRange { Length = AtrPeriod };

		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 fast, decimal slow, decimal atr)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		if (_prevFast == null || _prevSlow == null)
		{
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		var prevAbove = _prevFast.Value > _prevSlow.Value;
		var currAbove = fast > slow;

		_prevFast = fast;
		_prevSlow = slow;

		// Require minimum ATR relative to price
		if (atr < candle.ClosePrice * 0.0005m)
			return;

		// Golden cross
		if (!prevAbove && currAbove)
		{
			if (Position < 0)
				BuyMarket();
			if (Position <= 0)
				BuyMarket();
		}
		// Death cross
		else if (prevAbove && !currAbove)
		{
			if (Position > 0)
				SellMarket();
			if (Position >= 0)
				SellMarket();
		}
	}
}