GitHub で見る

EMA Pullback Strategy

Overview

The EMA Pullback strategy is a high-level port of the MetaTrader "Ema" expert advisor. It observes a pair of exponential moving averages (EMA) with periods 5 and 10 calculated on median candle prices. When a bullish or bearish crossover appears, the strategy waits for price to retrace towards the previous candle's extreme before entering in the direction of the crossover. Fixed take-profit and stop-loss levels measured in price points manage risk once the position is open.

Trading Logic

  1. Subscribe to the configured candle series (default: 5-minute time frame) and calculate two EMAs on the median price (high + low) / 2.
  2. Detect a bullish crossover when the fast EMA crosses above the slow EMA, or a bearish crossover when the fast EMA crosses below the slow EMA.
  3. Arm a pullback entry after the crossover occurs:
    • For a long setup, wait until the close price retreats to the previous candle high minus the MoveBackPoints offset while the fast EMA remains above the slow EMA by at least two price points.
    • For a short setup, wait until the close price returns to the previous candle low plus the MoveBackPoints offset while the slow EMA stays above the fast EMA by at least two price points.
  4. When the pullback condition is satisfied, send a market order with the configured trade volume.
  5. Upon entry, compute static take-profit and stop-loss levels using the TakeProfitPoints and StopLossPoints settings, converted into absolute price offsets from the entry price.
  6. Monitor every finished candle and close the position once either the take-profit or stop-loss level is touched by the candle's high/low.

Parameters

Name Default Description
TradeVolume 0.1 Volume used for each market order.
FastLength 5 Period of the fast EMA applied to median prices.
SlowLength 10 Period of the slow EMA applied to median prices.
MoveBackPoints 3 Pullback distance, in price points, measured from the previous candle's extreme.
TakeProfitPoints 5 Take-profit distance, in price points.
StopLossPoints 20 Stop-loss distance, in price points.
CandleType 5m Time frame used for candle subscription and indicator calculations.

Notes

  • Only fully formed candles are processed to avoid premature signals.
  • The strategy automatically aligns the Strategy.Volume property with the TradeVolume parameter on start.
  • All calculations rely on the instrument PriceStep to convert point-based distances into absolute prices.
  • The strategy opens at most one position at a time and requires a new EMA crossover before preparing another trade.
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>
/// EMA Pullback strategy - fast/slow EMA crossover with pullback entry.
/// After a bullish crossover, waits for a pullback to fast EMA to enter long.
/// After a bearish crossover, waits for a pullback to fast EMA to enter short.
/// </summary>
public class EmaPullbackStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _prevClose;
	private bool _hasPrev;
	private bool _bullishCross;
	private bool _bearishCross;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public EmaPullbackStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 8)
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

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

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
	protected override void OnReseted() { base.OnReseted(); _prevFast = 0m; _prevSlow = 0m; _prevClose = 0m; _hasPrev = false; _bullishCross = false; _bearishCross = false; }

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

		_hasPrev = false;
		_bullishCross = false;
		_bearishCross = false;

		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fast, slow, ProcessCandle)
			.Start();
	}

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

		var close = candle.ClosePrice;

		if (!_hasPrev)
		{
			_prevFast = fast;
			_prevSlow = slow;
			_prevClose = close;
			_hasPrev = true;
			return;
		}

		// Detect crossovers
		if (_prevFast <= _prevSlow && fast > slow)
		{
			_bullishCross = true;
			_bearishCross = false;
		}
		else if (_prevFast >= _prevSlow && fast < slow)
		{
			_bearishCross = true;
			_bullishCross = false;
		}

		// Pullback entry: after bullish cross, wait for close to touch fast EMA
		if (_bullishCross && fast > slow && _prevClose > _prevFast && close <= fast && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
			_bullishCross = false;
		}
		// Pullback entry: after bearish cross, wait for close to touch fast EMA
		else if (_bearishCross && fast < slow && _prevClose < _prevFast && close >= fast && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
			_bearishCross = false;
		}
		// Exit on opposite crossover
		else if (Position > 0 && fast < slow)
		{
			SellMarket();
		}
		else if (Position < 0 && fast > slow)
		{
			BuyMarket();
		}

		_prevFast = fast;
		_prevSlow = slow;
		_prevClose = close;
	}
}