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
- The algorithm tracks candle highs and lows to determine local extremes.
- The highest high and lowest low among the last Level detected extremes are stored as resistance and support lines.
- Each line is shifted by Distance ticks to account for market noise.
- 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.
- 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;
}
}
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.Strategies import Strategy
class mslea_strategy(Strategy):
def __init__(self):
super(mslea_strategy, self).__init__()
self._max_trades = self.Param("MaxTrades", 2) \
.SetDisplay("Max Trades", "Maximum simultaneous trades", "General")
self._level = self.Param("Level", 1) \
.SetDisplay("Level", "Number of extremes to look back", "General")
self._distance = self.Param("Distance", 4) \
.SetDisplay("Distance", "Offset from extreme in ticks", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._high_levels = []
self._low_levels = []
self._prev_high1 = None
self._prev_high2 = None
self._prev_low1 = None
self._prev_low2 = None
self._msh = None
self._msl = None
@property
def max_trades(self):
return self._max_trades.Value
@property
def level(self):
return self._level.Value
@property
def distance(self):
return self._distance.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(mslea_strategy, self).OnReseted()
self._high_levels = []
self._low_levels = []
self._prev_high1 = None
self._prev_high2 = None
self._prev_low1 = None
self._prev_low2 = None
self._msh = None
self._msl = None
def OnStarted2(self, time):
super(mslea_strategy, self).OnStarted2(time)
self._high_levels = []
self._low_levels = []
self._prev_high1 = None
self._prev_high2 = None
self._prev_low1 = None
self._prev_low2 = None
self._msh = None
self._msl = None
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def on_process(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self._prev_high2 is not None and self._prev_high1 is not None:
if self._prev_high2 < self._prev_high1 and self._prev_high1 > high:
self._add_high(self._prev_high1)
if self._prev_low2 is not None and self._prev_low1 is not None:
if self._prev_low2 > self._prev_low1 and self._prev_low1 < low:
self._add_low(self._prev_low1)
self._prev_high2 = self._prev_high1
self._prev_high1 = high
self._prev_low2 = self._prev_low1
self._prev_low1 = low
if self._msh is not None and self._msl is not None:
step = 0.01
offset = step * self.distance
upper = self._msh + offset
lower = self._msl - offset
close = float(candle.ClosePrice)
if close > upper and self.Position <= 0:
self.BuyMarket()
elif close < lower and self.Position >= 0:
self.SellMarket()
def _add_high(self, high):
self._high_levels.insert(0, high)
while len(self._high_levels) > self.level:
self._high_levels.pop()
self._msh = max(self._high_levels)
def _add_low(self, low):
self._low_levels.insert(0, low)
while len(self._low_levels) > self.level:
self._low_levels.pop()
self._msl = min(self._low_levels)
def CreateClone(self):
return mslea_strategy()