Ver en GitHub

Moving Average Shift Strategy

Overview

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

  1. Subscribe to candles of the configurable timeframe (default: 5 minutes) and calculate an SMA with the requested period.
  2. Shift the SMA by the specified number of completed candles to emulate the original iMA function behaviour.
  3. 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.
  4. 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.
  5. 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

  1. Compile the strategy inside StockSharp Designer, Shell, or Runner.
  2. Select the desired instrument and assign a portfolio.
  3. Configure the parameters if different time frames, lengths, or volumes are required.
  4. 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;
	}
}