View on GitHub

MARE5.1 Shifted Moving Average Strategy

Overview

The MARE5.1 Shifted Moving Average Strategy is a direct port of the original MetaTrader 5 expert advisor "MARE5.1" into the StockSharp high-level API. The system monitors one-minute candles (configurable) and compares two simple moving averages (SMA) that share a configurable forward shift. The logic looks for crossover patterns confirmed by historical SMA relationships and the direction of the latest completed candle.

Trading Logic

  • The strategy uses two SMAs: a fast SMA and a slow SMA. Both are shifted forward by the same number of bars, replicating the behaviour of the original expert advisor.
  • A short position is opened when all of the following are true:
    1. The slow SMA is at least one price step above the fast SMA on the current candle.
    2. Two candles ago the fast SMA was at least one price step above the slow SMA.
    3. Five candles ago the fast SMA was at least one price step above the slow SMA.
    4. The most recent closed candle (previous bar) is bearish.
  • A long position is opened when the opposite pattern occurs:
    1. The fast SMA is at least one price step above the slow SMA on the current candle.
    2. Two candles ago the slow SMA was at least one price step above the fast SMA.
    3. Five candles ago the slow SMA was at least one price step above the fast SMA.
    4. The most recent closed candle (previous bar) is bullish.
  • Only one position can be open at a time. The default order size comes from the TradeVolume parameter.
  • Trading is allowed only between the configured session hours (inclusive). This window replicates the hour-based filter of the original expert advisor.

Risk Management

The strategy mirrors the original fixed take-profit and stop-loss distances. They are defined in "pips" (points adjusted for three- and five-digit instruments) and converted into absolute price units when the strategy starts. Protective orders are managed through StartProtection with market-order exits.

Indicators and Data

  • Fast SMA – length defined by FastPeriod.
  • Slow SMA – length defined by SlowPeriod.
  • Data source – by default one-minute candles, but any candle type supported by StockSharp can be selected via the CandleType parameter.

Parameters

Name Default Description
TradeVolume 0.01 Order volume used for entries.
TakeProfitPips 35 Take-profit distance in adjusted pips. Set to zero to disable.
StopLossPips 55 Stop-loss distance in adjusted pips. Set to zero to disable.
FastPeriod 14 Period of the fast SMA.
SlowPeriod 79 Period of the slow SMA.
MovingAverageShift 4 Forward shift (in bars) applied to both SMAs.
SessionOpenHour 2 Start of the allowed trading window (0–23, inclusive).
SessionCloseHour 3 End of the allowed trading window (0–23, inclusive). Must be greater than SessionOpenHour.
CandleType 1-minute candles Candle data type used by the strategy.

Notes

  • Signals are evaluated on completed candles. Historical SMA values are internally buffered to replicate the index-based comparisons from the original MQL code.
  • The price-step value of the active security is used when comparing SMA differences to ensure the required distance equals at least one tick.
  • Stop-loss and take-profit levels rely on the security price step. For three- and five-decimal instruments the pip size is automatically expanded tenfold, matching the MetaTrader behaviour.
  • No automated position scaling is implemented. The strategy waits for all open positions to be closed before looking for the next entry signal.
  • This repository contains only the C# implementation; there is no Python port for this strategy.
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>
/// MARE5.1 strategy that trades shifted SMA crossovers with time filtering.
/// </summary>
public class Mare51Strategy : Strategy
{
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _movingAverageShift;
	private readonly StrategyParam<int> _sessionOpenHour;
	private readonly StrategyParam<int> _sessionCloseHour;
	private readonly StrategyParam<DataType> _candleType;

	private SMA _fastSma;
	private SMA _slowSma;
	private decimal?[] _fastBuffer;
	private decimal?[] _slowBuffer;
	private ICandleMessage _previousCandle;
	private decimal _pipSize;

	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public int MovingAverageShift
	{
		get => _movingAverageShift.Value;
		set => _movingAverageShift.Value = value;
	}

	public int SessionOpenHour
	{
		get => _sessionOpenHour.Value;
		set => _sessionOpenHour.Value = value;
	}

	public int SessionCloseHour
	{
		get => _sessionCloseHour.Value;
		set => _sessionCloseHour.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public Mare51Strategy()
	{
		_tradeVolume = Param(nameof(TradeVolume), 0.01m)
			.SetDisplay("Volume", "Default order volume", "Trading")
			.SetGreaterThanZero();

		_takeProfitPips = Param(nameof(TakeProfitPips), 35m)
			.SetDisplay("Take Profit (pips)", "Take profit distance in adjusted pips", "Risk")
			.SetNotNegative();

		_stopLossPips = Param(nameof(StopLossPips), 55m)
			.SetDisplay("Stop Loss (pips)", "Stop loss distance in adjusted pips", "Risk")
			.SetNotNegative();

		_fastPeriod = Param(nameof(FastPeriod), 14)
			.SetDisplay("Fast Period", "Fast SMA period", "Indicators")
			.SetGreaterThanZero();

		_slowPeriod = Param(nameof(SlowPeriod), 20)
			.SetDisplay("Slow Period", "Slow SMA period", "Indicators")
			.SetGreaterThanZero();

		_movingAverageShift = Param(nameof(MovingAverageShift), 1)
			.SetDisplay("MA Shift", "Forward shift applied to both SMAs", "Indicators")
			.SetNotNegative();

		_sessionOpenHour = Param(nameof(SessionOpenHour), 0)
			.SetDisplay("Session Open Hour", "Inclusive start hour for trading", "Session")
			.SetRange(0, 23);

		_sessionCloseHour = Param(nameof(SessionCloseHour), 23)
			.SetDisplay("Session Close Hour", "Inclusive end hour for trading", "Session")
			.SetRange(0, 23);

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Candle Type", "Candle data type", "Data");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();

		_fastSma = null;
		_slowSma = null;
		_fastBuffer = null;
		_slowBuffer = null;
		_previousCandle = null;
		_pipSize = 0m;
	}

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

		if (SessionOpenHour >= SessionCloseHour)
			throw new InvalidOperationException("SessionOpenHour must be less than SessionCloseHour.");

		Volume = TradeVolume;

		_fastSma = new SMA { Length = FastPeriod };
		_slowSma = new SMA { Length = SlowPeriod };

		_fastBuffer = new decimal?[MovingAverageShift + 6];
		_slowBuffer = new decimal?[MovingAverageShift + 6];

		_pipSize = CalculatePipSize();
		var takeProfitUnit = TakeProfitPips > 0m
			? new Unit(TakeProfitPips * _pipSize, UnitTypes.Absolute)
			: new Unit(0m);
		var stopLossUnit = StopLossPips > 0m
			? new Unit(StopLossPips * _pipSize, UnitTypes.Absolute)
			: new Unit(0m);

		StartProtection(stopLossUnit, takeProfitUnit);

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

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

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

		if (_fastBuffer == null || _slowBuffer == null)
			return;

		// Shift raw SMA values so we can later access shifted indexes.
		for (var i = _fastBuffer.Length - 1; i > 0; i--)
		{
			_fastBuffer[i] = _fastBuffer[i - 1];
			_slowBuffer[i] = _slowBuffer[i - 1];
		}

		_fastBuffer[0] = fastValue;
		_slowBuffer[0] = slowValue;

		var previousCandle = _previousCandle;
		_previousCandle = candle;

		//if (!IsFormedAndOnlineAndAllowTrading())
		//	return;

		if (previousCandle == null)
			return;

		if (_fastSma == null || _slowSma == null)
			return;

		if (!_fastSma.IsFormed || !_slowSma.IsFormed)
			return;

		var fast0 = GetShiftedValue(_fastBuffer, 0);
		var fast2 = GetShiftedValue(_fastBuffer, 2);
		var fast5 = GetShiftedValue(_fastBuffer, 5);
		var slow0 = GetShiftedValue(_slowBuffer, 0);
		var slow2 = GetShiftedValue(_slowBuffer, 2);
		var slow5 = GetShiftedValue(_slowBuffer, 5);

		if (fast0 is not decimal f0 || fast2 is not decimal f2 || fast5 is not decimal f5 ||
			slow0 is not decimal s0 || slow2 is not decimal s2 || slow5 is not decimal s5)
		{
			return;
		}

		if (!IsWithinSession(candle.OpenTime))
			return;

		var bearishPrevious = previousCandle.ClosePrice < previousCandle.OpenPrice;
		var bullishPrevious = previousCandle.ClosePrice > previousCandle.OpenPrice;

		var sellSignal = f5 >= s5 && f2 < s2 && f0 < s0 && bearishPrevious;
		var buySignal = f5 <= s5 && f2 > s2 && f0 > s0 && bullishPrevious;

		if (Position != 0)
			return;

		if (sellSignal)
		{
			// Enter short when slow SMA overtakes the fast SMA and previous bars confirm the reversal.
			SellMarket();
		}
		else if (buySignal)
		{
			// Enter long when fast SMA overtakes the slow SMA and previous bars confirm the reversal.
			BuyMarket();
		}
	}

	private decimal? GetShiftedValue(decimal?[] buffer, int index)
	{
		var targetIndex = index + MovingAverageShift;
		if (buffer == null)
			return null;
		if (targetIndex < 0 || targetIndex >= buffer.Length)
			return null;
		return buffer[targetIndex];
	}

	private bool IsWithinSession(DateTimeOffset time)
	{
		var hour = time.Hour;
		return hour >= SessionOpenHour && hour <= SessionCloseHour;
	}

	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
			return 1m;

		var scale = GetDecimalScale(step);
		return (scale == 3 || scale == 5) ? step * 10m : step;
	}

	private static int GetDecimalScale(decimal value)
	{
		var bits = decimal.GetBits(value);
		return (bits[3] >> 16) & 0xFF;
	}
}