This strategy is a StockSharp high-level port of the classic Moving Average expert advisor that ships with MetaTrader 4. The system observes completed candles and compares them with a shifted simple moving average (SMA) to detect direction changes. Orders are always executed at market, and the strategy stays in the market with at most one open position at any time.
Trading Logic
Subscribe to candles of the configurable timeframe (default: 5 minutes) and calculate an SMA with the requested period.
Shift the SMA by the specified number of completed candles to emulate the original iMA function behaviour.
Evaluate the previous finished candle:
Bullish cross (open below the shifted SMA and close above) triggers a long entry when no position is open.
Bearish cross (open above and close below the shifted SMA) triggers a short entry when no position is open.
Manage exits using the same cross rules:
A long position is closed when the last candle crosses below the shifted SMA.
A short position is closed when the last candle crosses above the shifted SMA.
Only one position can exist at any time, matching the behaviour of the original EA that alternated between buy and sell orders.
Parameters
Name
Description
Default
CandleType
Candle series used for calculations. Any time-frame DataType can be selected.
5-minute time frame
MovingPeriod
Number of candles for the SMA length.
12
MovingShift
Offset of the SMA value in completed candles. Emulates the shift argument of iMA.
6
BaseVolume
Default order volume for entries. The same volume is used for both long and short trades.
1
Indicator Handling
A SimpleMovingAverage indicator is created in OnStarted and bound to the candle subscription through the high-level Bind API.
The raw SMA output is buffered in a small FIFO queue to obtain the value from MovingShift candles ago. No manual indicator recalculation is performed.
The queue retains only MovingShift + 1 values, so memory usage remains constant even for large shifts.
Order and Risk Management
Orders are placed with BuyMarket/SellMarket and are sized by the BaseVolume parameter. When closing, the current absolute position size is used to ensure a full exit.
The original MetaTrader implementation dynamically adjusted lot size based on free margin and recent losses. The StockSharp port keeps the logic deterministic and delegates position sizing to the user through the BaseVolume parameter. This avoids relying on broker-specific account metrics while preserving the entry/exit rules.
Conversion Notes
Signals are evaluated on the previous candle, matching the Volume[0] == 1 check from MetaTrader that waited for a new bar before reacting.
Only completed candles (CandleStates.Finished) are processed to avoid premature trades.
The strategy uses the StockSharp chart helpers to plot candles, indicator values, and trade markers when a chart area is available.
Usage
Compile the strategy inside StockSharp Designer, Shell, or Runner.
Select the desired instrument and assign a portfolio.
Configure the parameters if different time frames, lengths, or volumes are required.
Start the strategy; it will subscribe to the chosen candle series, monitor SMA crosses, and trade accordingly.
Further Ideas
Add protective stops or take-profit levels using StartProtection if risk management beyond the basic reversal exit is required.
Replace the simple SMA with another indicator (EMA, LWMA, etc.) by modifying the indicator instance while keeping the existing subscription workflow.
Introduce position scaling rules by adjusting the GetEntryVolume method.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class MovingAverageShiftStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _cooldownCandles;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevClose;
private decimal _prevEma;
private bool _hasPrev;
private int _cooldownRemaining;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public MovingAverageShiftStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 50).SetDisplay("EMA Period", "EMA lookback", "Indicators");
_cooldownCandles = Param(nameof(CooldownCandles), 200).SetDisplay("Cooldown", "Candles between signals", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose = default;
_prevEma = default;
_hasPrev = default;
_cooldownRemaining = default;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevClose = 0;
_prevEma = 0;
_hasPrev = false;
_cooldownRemaining = 0;
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ema, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal ema)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
if (!_hasPrev) { _prevClose = close; _prevEma = ema; _hasPrev = true; return; }
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevClose = close;
_prevEma = ema;
return;
}
if (_prevClose <= _prevEma && close > ema && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
_cooldownRemaining = CooldownCandles;
}
else if (_prevClose >= _prevEma && close < ema && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
_cooldownRemaining = CooldownCandles;
}
_prevClose = close;
_prevEma = ema;
}
}
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 moving_average_shift_strategy(Strategy):
def __init__(self):
super(moving_average_shift_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 50) \
.SetDisplay("EMA Period", "EMA lookback", "Indicators")
self._cooldown_candles = self.Param("CooldownCandles", 200) \
.SetDisplay("Cooldown", "Candles between signals", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
self._cooldown_remaining = 0
@property
def ema_period(self):
return self._ema_period.Value
@property
def cooldown_candles(self):
return self._cooldown_candles.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(moving_average_shift_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(moving_average_shift_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
self._cooldown_remaining = 0
ema = ExponentialMovingAverage()
ema.Length = self.ema_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, self.process_candle).Start()
def process_candle(self, candle, ema):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
ema_val = float(ema)
if not self._has_prev:
self._prev_close = close
self._prev_ema = ema_val
self._has_prev = True
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_close = close
self._prev_ema = ema_val
return
if self._prev_close <= self._prev_ema and close > ema_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.cooldown_candles
elif self._prev_close >= self._prev_ema and close < ema_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.cooldown_candles
self._prev_close = close
self._prev_ema = ema_val
def CreateClone(self):
return moving_average_shift_strategy()