Auf GitHub ansehen

EMA (barabashkakvn Edition) Strategy

Converted from the MetaTrader 5 expert advisor "EMA (barabashkakvn's edition)". The system trades the crossover of two exponential moving averages that are calculated on the median price and uses virtual take-profit/stop-loss levels expressed in pips. Positions are opened only after a confirmed crossover and a small retracement toward the previous candle extreme.

Core Idea

  1. Track 5- and 10-period EMAs (median price) on the selected timeframe.
  2. When the fast EMA crosses the slow EMA, arm a pending signal instead of trading immediately.
  3. Wait for price to retrace MoveBackPips from the previous candle extremum while the EMA spread exceeds 2 * pipSize.
  4. Enter in the direction of the crossover once the retracement occurs.
  5. Manage the open position with virtual targets and stops measured in pips from the entry price.

This behaviour mirrors the original MQL implementation: the expert waited for the crossover flag (check) and then required an EMA spread plus a price retracement relative to the previous candle to trigger the trade. The exit rules also follow the "virtual" approach by closing positions when the bid/ask would have touched the specified distances.

Indicators & Data

  • 5-period EMA on median price (high + low) / 2.
  • 10-period EMA on median price.
  • Previous finished candle high/low for retracement checks.
  • All processing uses finished candles from the configured CandleType subscription.

Parameters

Parameter Default Description
OrderVolume 0.1 Trading volume in lots/contracts for each entry.
VirtualProfitPips 5 Distance (in pips) between entry price and virtual take-profit.
MoveBackPips 3 Retracement required after the crossover, measured from the previous candle extremum.
StopLossPips 20 Distance (in pips) between entry price and virtual stop-loss.
PipSize 0.0001 Pip size expressed in price units. Override when trading symbols with a different pip definition.
FastLength 5 Length of the fast EMA.
SlowLength 10 Length of the slow EMA.
CandleType TimeFrame(1m) Candle source used for calculations.

All pip-based values are converted to price distances using pipValue = PipSize. If the parameter is left at zero or a negative number the strategy falls back to Security.PriceStep (when provided by the board).

Trading Logic

Entry Conditions

  • Signal arming: store a pending signal whenever a crossover occurs (FastEMA crosses above SlowEMA or vice versa). No trade is placed yet.
  • Short entry: requires
    • Pending signal present.
    • SlowEMA - FastEMA > 2 * pipSize.
    • Current candle high ≥ previous candle low + MoveBackPips * pipSize (price retraced upward from the prior low).
  • Long entry: requires
    • Pending signal present.
    • FastEMA - SlowEMA > 2 * pipSize.
    • Current candle low ≤ previous candle high - MoveBackPips * pipSize (price retraced downward from the prior high).

After opening a position the pending flag resets to avoid duplicate entries.

Exit Conditions

Virtual targets emulate the MQL behaviour by comparing the candle extremes with the preset distances:

  • Long position:
    • Close if candle high ≥ entry price + VirtualProfitPips * pipSize.
    • Close if candle low ≤ entry price - StopLossPips * pipSize.
  • Short position:
    • Close if candle low ≤ entry price - VirtualProfitPips * pipSize.
    • Close if candle high ≥ entry price + StopLossPips * pipSize.

After any exit the virtual levels reset and the strategy waits for the next crossover.

Implementation Notes

  • Uses the high-level candle subscription (SubscribeCandles) and draws EMAs plus trades on the optional chart area.
  • Median price is computed directly from the candle high/low to match PRICE_MEDIAN from MetaTrader.
  • The crossover flag (_hasCrossSignal) reproduces the original check variable, ensuring trades only occur after both crossover and retracement checks.
  • StartProtection() is called in OnStarted to enable built-in risk monitoring even though the strategy handles exits manually.
  • The code keeps all comments in English, as requested, and relies solely on finished candles without accessing indicator buffers directly.

Usage Tips

  • Adjust PipSize when running on instruments with non-standard pip definitions (e.g., JPY pairs, indices, crypto quotes).
  • Because exits rely on candle extremes, using shorter timeframes (1–5 minutes) keeps behaviour closer to the original tick-based expert.
  • Optimization can explore EMA lengths, pip distances, and retracement values using the provided parameter metadata.
  • The strategy trades one position at a time; any external positions on the same security can interfere with the virtual exit tracking.

Risks

  • Candle-based simulation may miss intrabar touches of the virtual levels; consider higher-resolution data if precision is critical.
  • Virtual exits do not place real protective orders, so disconnections or slippage can lead to larger losses than expected in live trading.
  • As with any crossover system, performance degrades in ranging markets; combine with filters if necessary.
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>
/// EMA crossover strategy with virtual take profit and stop loss distances.
/// Converted from the MQL5 expert "EMA (barabashkakvn's edition)".
/// </summary>
public class EmaBarabashkakvnEditionStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<int> _virtualProfitPips;
	private readonly StrategyParam<int> _moveBackPips;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<decimal> _pipSize;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _fastEma;
	private ExponentialMovingAverage _slowEma;
	private bool _hasCrossSignal;
	private decimal? _prevFast;
	private decimal? _prevSlow;
	private decimal? _prevHigh;
	private decimal? _prevLow;
	private decimal? _entryPrice;
	private decimal? _virtualTarget;
	private decimal? _virtualStop;

	/// <summary>
	/// Order volume in lots.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Virtual take profit distance in pips.
	/// </summary>
	public int VirtualProfitPips
	{
		get => _virtualProfitPips.Value;
		set => _virtualProfitPips.Value = value;
	}

	/// <summary>
	/// Retracement distance after a crossover in pips.
	/// </summary>
	public int MoveBackPips
	{
		get => _moveBackPips.Value;
		set => _moveBackPips.Value = value;
	}

	/// <summary>
	/// Virtual stop loss distance in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Pip size in price units.
	/// </summary>
	public decimal PipSize
	{
		get => _pipSize.Value;
		set => _pipSize.Value = value;
	}

	/// <summary>
	/// Fast EMA length applied to median price.
	/// </summary>
	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	/// <summary>
	/// Slow EMA length applied to median price.
	/// </summary>
	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

	/// <summary>
	/// Candle type used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="EmaBarabashkakvnEditionStrategy"/>.
	/// </summary>
	public EmaBarabashkakvnEditionStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Volume", "Order volume in lots", "Trading")
			
			.SetOptimize(0.05m, 1m, 0.05m);

		_virtualProfitPips = Param(nameof(VirtualProfitPips), 5)
			.SetGreaterThanZero()
			.SetDisplay("Virtual Profit", "Take profit distance in pips", "Risk")
			
			.SetOptimize(2, 20, 1);

		_moveBackPips = Param(nameof(MoveBackPips), 3)
			.SetGreaterThanZero()
			.SetDisplay("Move Back", "Retracement after crossover in pips", "Entries")
			
			.SetOptimize(1, 10, 1);

		_stopLossPips = Param(nameof(StopLossPips), 20)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss", "Virtual stop loss distance in pips", "Risk")
			
			.SetOptimize(10, 60, 2);

		_pipSize = Param(nameof(PipSize), 0.0001m)
			.SetGreaterThanZero()
			.SetDisplay("Pip Size", "Instrument pip size in price units", "General");

		_fastLength = Param(nameof(FastLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA length on median price", "Indicators")
			
			.SetOptimize(3, 15, 1);

		_slowLength = Param(nameof(SlowLength), 10)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA length on median price", "Indicators")
			
			.SetOptimize(8, 40, 1);

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Source candles", "General");
	}

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

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

		_fastEma = default;
		_slowEma = default;
		_hasCrossSignal = false;
		_prevFast = default;
		_prevSlow = default;
		_prevHigh = default;
		_prevLow = default;
		_entryPrice = default;
		_virtualTarget = default;
		_virtualStop = default;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_fastEma = new EMA { Length = FastLength };
		_slowEma = new EMA { Length = SlowLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

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

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		// Calculate median price as in the original expert (PRICE_MEDIAN).
		var medianPrice = (candle.HighPrice + candle.LowPrice) / 2m;

		// Update EMA values using the median price.
		var fastValue = _fastEma.Process(new DecimalIndicatorValue(_fastEma, medianPrice, candle.OpenTime) { IsFinal = true });
		var slowValue = _slowEma.Process(new DecimalIndicatorValue(_slowEma, medianPrice, candle.OpenTime) { IsFinal = true });

		if (!_fastEma.IsFormed || !_slowEma.IsFormed)
		{
			_prevHigh = candle.HighPrice;
			_prevLow = candle.LowPrice;
			_prevFast = fastValue.ToDecimal();
			_prevSlow = slowValue.ToDecimal();
			return;
		}

		var fast = fastValue.ToDecimal();
		var slow = slowValue.ToDecimal();

		if (_prevFast is decimal prevFast && _prevSlow is decimal prevSlow)
		{
			var bullishCross = prevFast <= prevSlow && fast > slow;
			var bearishCross = prevFast >= prevSlow && fast < slow;

			if (bullishCross || bearishCross)
				_hasCrossSignal = true;
		}

		_prevFast = fast;
		_prevSlow = slow;

		var pipValue = PipSize;
		if (pipValue <= 0m)
			pipValue = Security?.PriceStep ?? 0.0001m;

		var moveBackPrice = MoveBackPips * pipValue;
		var profitDistance = VirtualProfitPips * pipValue;
		var stopDistance = StopLossPips * pipValue;

		if (Position == 0 && _hasCrossSignal && _prevHigh is decimal prevHigh && _prevLow is decimal prevLow)
		{
			var bearishSpread = slow - fast;
			var bullishSpread = fast - slow;

			var bearishReady = bearishSpread > 2m * pipValue && candle.HighPrice >= prevLow + moveBackPrice;
			var bullishReady = bullishSpread > 2m * pipValue && candle.LowPrice <= prevHigh - moveBackPrice;

			if (bearishReady)
			{
				// Enter short after bearish cross and retracement above the previous low.
				_entryPrice = candle.ClosePrice;
				_virtualTarget = _entryPrice - profitDistance;
				_virtualStop = _entryPrice + stopDistance;
				SellMarket();
				_hasCrossSignal = false;
			}
			else if (bullishReady)
			{
				// Enter long after bullish cross and retracement below the previous high.
				_entryPrice = candle.ClosePrice;
				_virtualTarget = _entryPrice + profitDistance;
				_virtualStop = _entryPrice - stopDistance;
				BuyMarket();
				_hasCrossSignal = false;
			}
		}
		else if (Position != 0 && _entryPrice is decimal && _virtualTarget is decimal target && _virtualStop is decimal stop)
		{
			if (Position > 0)
			{
				// Long position: use high for profit target and low for stop.
				var hitTarget = candle.HighPrice >= target;
				var hitStop = candle.LowPrice <= stop;

				if (hitTarget || hitStop)
				{
					SellMarket();
					_hasCrossSignal = false;
					_entryPrice = null;
					_virtualTarget = null;
					_virtualStop = null;
				}
			}
			else if (Position < 0)
			{
				// Short position: use low for profit target and high for stop.
				var hitTarget = candle.LowPrice <= target;
				var hitStop = candle.HighPrice >= stop;

				if (hitTarget || hitStop)
				{
					BuyMarket();
					_hasCrossSignal = false;
					_entryPrice = null;
					_virtualTarget = null;
					_virtualStop = null;
				}
			}
		}

		if (Position == 0)
		{
			_entryPrice = null;
			_virtualTarget = null;
			_virtualStop = null;
		}

		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
	}
}