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
Subscribe to the configured candle series (default: 5-minute time frame) and calculate two EMAs on the median price (high + low) / 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.
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.
When the pullback condition is satisfied, send a market order with the configured trade volume.
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.
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ema_pullback_strategy(Strategy):
def __init__(self):
super(ema_pullback_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 8) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 21) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._prev_close = 0.0
self._has_prev = False
self._bullish_cross = False
self._bearish_cross = False
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ema_pullback_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._prev_close = 0.0
self._has_prev = False
self._bullish_cross = False
self._bearish_cross = False
def OnStarted2(self, time):
super(ema_pullback_strategy, self).OnStarted2(time)
self._has_prev = False
self._bullish_cross = False
self._bearish_cross = False
fast = ExponentialMovingAverage()
fast.Length = self.fast_period
slow = ExponentialMovingAverage()
slow.Length = self.slow_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self.process_candle).Start()
def process_candle(self, candle, fast, slow):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast)
slow_val = float(slow)
close = float(candle.ClosePrice)
if not self._has_prev:
self._prev_fast = fast_val
self._prev_slow = slow_val
self._prev_close = close
self._has_prev = True
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val:
self._bullish_cross = True
self._bearish_cross = False
elif self._prev_fast >= self._prev_slow and fast_val < slow_val:
self._bearish_cross = True
self._bullish_cross = False
if self._bullish_cross and fast_val > slow_val and self._prev_close > self._prev_fast and close <= fast_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._bullish_cross = False
elif self._bearish_cross and fast_val < slow_val and self._prev_close < self._prev_fast and close >= fast_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._bearish_cross = False
elif self.Position > 0 and fast_val < slow_val:
self.SellMarket()
elif self.Position < 0 and fast_val > slow_val:
self.BuyMarket()
self._prev_fast = fast_val
self._prev_slow = slow_val
self._prev_close = close
def CreateClone(self):
return ema_pullback_strategy()