Ver no GitHub

FiftyFiveMedianSlopeStrategy

Origin

  • Converted from the MetaTrader 4 expert advisor 55_MA_med_FIN.mq4.
  • Focuses on the slope of a 55-period moving average calculated on median candle prices.

Trading logic

  • Subscribes to the configured candle series (default: 1-hour time frame) and processes only completed candles.
  • Calculates a moving average on the median price (((High + Low) / 2)) using the selected method (SMA, EMA, SMMA or LWMA).
  • Stores the latest moving average values in a circular buffer to compare the value one bar ago with the value MaShift bars ago.
  • When the value from one bar ago is greater than the value from MaShift bars ago, the strategy:
    • Closes any short exposure first.
    • Opens a long position if the MaxOrders limit has not been reached.
  • When the value from one bar ago is less than the value from MaShift bars ago, it mirrors the behaviour for short positions.
  • Signals are alternated via internal flags so the strategy waits for an opposite crossover before re-entering in the same direction.
  • Trading is allowed only while the candle opening hour satisfies StartHour < hour < EndHour. The bounds are exclusive to match the original MQL implementation.

Position sizing and risk management

  • FixedVolume defines the lot size per market order. When set to zero, the strategy switches to risk-based sizing using RiskPercentage and the portfolio's current value.
  • MaxOrders limits how many times the base volume can be stacked in the same direction. A value of zero removes the cap.
  • Optional StopLossPoints and TakeProfitPoints recreate the MT4 stop-loss and take-profit distances via StartProtection using price steps.

Parameters

  • FixedVolume – primary lot size. Set to zero to enable percentage-based sizing.
  • RiskPercentage – fraction of the portfolio allocated when FixedVolume equals zero.
  • TakeProfitPoints / StopLossPoints – protective distances expressed in price steps.
  • MaPeriod – length of the median moving average (default 55).
  • MaShift – number of bars between the recent and historical moving average snapshots (default 13).
  • MaMethod – moving average calculation type (Simple, Exponential, Smoothed, LinearWeighted).
  • StartHour / EndHour – exclusive trading window in platform time (0–23 hours).
  • MaxOrders – maximum simultaneous entries per direction.
  • CandleType – time frame used for the signal candles.

Usage notes

  • Ensure the subscribed instrument provides a non-zero PriceStep and volume metadata so the volume alignment matches exchange requirements.
  • Risk-based sizing uses the portfolio current value and the latest close price. If either is unavailable the strategy falls back to zero volume (no trade).
  • The strategy cancels opposite exposure before opening a new position, emulating the original MT4 behaviour of closing opposing orders.
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Fifty Five Median Slope: EMA slope direction with ATR stops.
/// </summary>
public class FiftyFiveMedianSlopeStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<int> _slopeShift;

	private decimal _entryPrice;
	private decimal _prevEma;
	private int _barCount;
	private readonly decimal[] _emaHistory = new decimal[20];

	public FiftyFiveMedianSlopeStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_emaLength = Param(nameof(EmaLength), 55)
			.SetDisplay("EMA Length", "Moving average period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");

		_slopeShift = Param(nameof(SlopeShift), 13)
			.SetDisplay("Slope Shift", "Bars between slope comparison.", "Indicators");
	}

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

	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	public int SlopeShift
	{
		get => _slopeShift.Value;
		set => _slopeShift.Value = value;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_entryPrice = 0;
		_prevEma = 0;
		_barCount = 0;
		Array.Clear(_emaHistory, 0, _emaHistory.Length);
	}

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

		_entryPrice = 0;
		_prevEma = 0;
		_barCount = 0;

		var ema = new ExponentialMovingAverage { Length = EmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ema, atr, ProcessCandle)
			.Start();

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

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

		var len = Math.Min(SlopeShift + 1, _emaHistory.Length);
		var idx = _barCount % len;
		_emaHistory[idx] = emaVal;
		_barCount++;

		if (_barCount < len || atrVal <= 0)
			return;

		var shiftIdx = (_barCount - SlopeShift) % len;
		if (shiftIdx < 0) shiftIdx += len;
		var shiftedEma = _emaHistory[shiftIdx];

		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if (close >= _entryPrice + atrVal * 3m || close <= _entryPrice - atrVal * 1.5m || emaVal < shiftedEma)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close <= _entryPrice - atrVal * 3m || close >= _entryPrice + atrVal * 1.5m || emaVal > shiftedEma)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (emaVal > shiftedEma && _prevEma <= shiftedEma)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (emaVal < shiftedEma && _prevEma >= shiftedEma)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevEma = emaVal;
	}
}