View on GitHub

MSL EA Strategy

Overview

MSL EA is a breakout strategy that builds dynamic support and resistance lines from recent local extremes. The strategy detects short-term fractal highs and lows, adjusts them by a specified distance in ticks, and opens positions when price closes beyond these levels. It was converted from the original MQL4 implementation.

How It Works

  1. The algorithm tracks candle highs and lows to determine local extremes.
  2. The highest high and lowest low among the last Level detected extremes are stored as resistance and support lines.
  3. Each line is shifted by Distance ticks to account for market noise.
  4. When the close price breaks above the upper line, a long position is opened; when it breaks below the lower line, a short position is opened.
  5. The number of simultaneous trades is limited by Max Trades.

Parameters

  • Max Trades – maximum allowed open positions.
  • Level – number of local extremes used to build levels.
  • Distance – offset from extreme in ticks when placing lines.
  • Candle Type – timeframe of candles processed by the strategy.

Notes

This C# version uses the high-level StockSharp API and includes English comments. Risk management functions from the original MQL4 helper library are simplified to basic position checks.

using System;
using System.Linq;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// MSL EA strategy.
/// Builds support and resistance lines from local extremes and trades breakouts.
/// </summary>
public class MsleaStrategy : Strategy
{
	private readonly StrategyParam<int> _maxTrades;
	private readonly StrategyParam<int> _level;
	private readonly StrategyParam<int> _distance;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _highLevels = new();
	private readonly List<decimal> _lowLevels = new();

	private decimal? _prevHigh1;
	private decimal? _prevHigh2;
	private decimal? _prevLow1;
	private decimal? _prevLow2;

	private decimal? _msh;
	private decimal? _msl;

	/// <summary>
	/// Maximum allowed open trades.
	/// </summary>
	public int MaxTrades
	{
		get => _maxTrades.Value;
		set => _maxTrades.Value = value;
	}

	/// <summary>
	/// Number of local extremes used to build levels.
	/// </summary>
	public int Level
	{
		get => _level.Value;
		set => _level.Value = value;
	}

	/// <summary>
	/// Distance from extremes in ticks.
	/// </summary>
	public int Distance
	{
		get => _distance.Value;
		set => _distance.Value = value;
	}

	/// <summary>
	/// Candle type.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initialize Mslea strategy.
	/// </summary>
	public MsleaStrategy()
	{
		_maxTrades = Param(nameof(MaxTrades), 2)
			.SetGreaterThanZero()
			.SetDisplay("Max Trades", "Maximum simultaneous trades", "General");

		_level = Param(nameof(Level), 1)
			.SetGreaterThanZero()
			.SetDisplay("Level", "Number of extremes to look back", "General");

		_distance = Param(nameof(Distance), 4)
			.SetGreaterThanZero()
			.SetDisplay("Distance", "Offset from extreme in ticks", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_highLevels.Clear();
		_lowLevels.Clear();
		_prevHigh1 = _prevHigh2 = _prevLow1 = _prevLow2 = null;
		_msh = _msl = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

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

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

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

		if (_prevHigh2 is decimal h2 && _prevHigh1 is decimal h1 && h2 < h1 && h1 > candle.HighPrice)
			AddHigh(h1);

		if (_prevLow2 is decimal l2 && _prevLow1 is decimal l1 && l2 > l1 && l1 < candle.LowPrice)
			AddLow(l1);

		_prevHigh2 = _prevHigh1;
		_prevHigh1 = candle.HighPrice;
		_prevLow2 = _prevLow1;
		_prevLow1 = candle.LowPrice;

		if (_msh is decimal top && _msl is decimal bottom)
		{
			var step = Security.PriceStep ?? 0.01m;
			var offset = step * Distance;
			var upper = top + offset;
			var lower = bottom - offset;

			if (candle.ClosePrice > upper && Position <= 0)
				BuyMarket();
			else if (candle.ClosePrice < lower && Position >= 0)
				SellMarket();
		}
	}

	private void AddHigh(decimal high)
	{
		_highLevels.Insert(0, high);
		TrimList(_highLevels);
		_msh = GetMax(_highLevels);
	}

	private void AddLow(decimal low)
	{
		_lowLevels.Insert(0, low);
		TrimList(_lowLevels);
		_msl = GetMin(_lowLevels);
	}

	private void TrimList(List<decimal> list)
	{
		while (list.Count > Level)
			list.RemoveAt(list.Count - 1);
	}

	private static decimal GetMax(List<decimal> list)
	{
		var max = list[0];
		for (var i = 1; i < list.Count; i++)
		{
			var v = list[i];
			if (v > max)
				max = v;
		}
		return max;
	}

	private static decimal GetMin(List<decimal> list)
	{
		var min = list[0];
		for (var i = 1; i < list.Count; i++)
		{
			var v = list[i];
			if (v < min)
				min = v;
		}
		return min;
	}
}