The Envelope Limit Ladder Strategy is a C# port of the MetaTrader expert advisor E_2_12_5min.mq4 (ID 7671). It rebuilds the original ladder of limit orders around an EMA envelope on 5-minute candles while keeping the multi-target and trailing management model from the legacy robot.
Concept
Envelope filter – a moving average envelope (default EMA 144 with a 0.05% deviation) calculated on the configurable EnvelopeCandleType timeframe provides the midline and upper/lower bands.
Signal candle – trading signals are evaluated on the CandleType subscription (default 5 minutes). When the previous candle closes between the midline and the nearest band, the strategy arms limit orders at the midline.
Order ladder – up to three buy limits and three sell limits are placed simultaneously:
Entry price: aligned midline value.
Stop-loss: opposite envelope band.
Take-profit: band ± user defined offsets (8, 13 and 21 points by default).
Trading window – pending orders are created only when TradingStartHour < Hour < TradingEndHour. All remaining limits are cancelled once the opening hour reaches TradingEndHour.
Position management – each filled limit order immediately places its own stop and take-profit order. An optional trailing mode moves the stop to the moving average (or keeps it on the opposite band) when price breaks out beyond the envelope.
Parameters
Parameter
Default
Description
CandleType
5 minutes
Candle type for signal detection.
EnvelopeCandleType
5 minutes
Candle type used to compute the envelope. Use a higher timeframe to mimic the MT4 EnvTimeFrame input.
EnvelopePeriod
144
Moving average length of the envelope.
MaMethod
EMA
Moving average method (SMA, EMA, SMMA, LWMA).
EnvelopeDeviation
0.05
Envelope width in percent (0.05 = 0.05%).
TradingStartHour
0
First hour when pending orders may appear (exclusive check, matches MT4 behaviour).
TradingEndHour
17
Hour when all pending orders are removed (exclusive upper bound).
FirstTakeProfitPoints
8
Offset in points added beyond the envelope for the first ladder rung.
SecondTakeProfitPoints
13
Offset in points for the second rung.
ThirdTakeProfitPoints
21
Offset in points for the third rung.
UseOppositeEnvelopeTrailing
true
Keeps the stop on the opposite band (true) or trails it to the moving average (false). Mirrors the MT4 MaElineTSL flag.
OrderVolume
0.1
Volume per pending order (replaces the adaptive lot sizing from MT4).
Behaviour Notes
The strategy maintains a separate stop/take pair for every filled limit order. Exits do not interfere with the remaining rungs of the ladder.
Trailing only activates after a breakout beyond the envelope and only tightens the stop in the profitable direction.
When EnvelopeCandleType differs from CandleType, the most recent envelope values from the secondary subscription are reused for signal candles, closely matching the MT4 higher-timeframe envelope lookup.
The original MT4 money-management routine (LotsOptimized) is replaced by the explicit OrderVolume parameter to keep the port deterministic inside StockSharp.
Usage Tips
Match the envelope timeframe with the MT4 inputs to reproduce the original behaviour (e.g., keep EnvelopeCandleType at 5 minutes or switch to 1 hour/4 hour as needed).
Set UseOppositeEnvelopeTrailing to false if you want the trailing stop to jump to the moving average instead of the opposite band once price exits the envelope.
Optimise the take-profit offsets and envelope deviation together; the ladder distances rely on the volatility captured by the envelope.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Envelope Limit Ladder strategy - Bollinger band mean reversion.
/// Buys when price touches lower band, sells when it touches upper band.
/// Exits at the middle band.
/// </summary>
public class EnvelopeLimitLadderStrategy : Strategy
{
private readonly StrategyParam<int> _bbPeriod;
private readonly StrategyParam<decimal> _bbWidth;
private readonly StrategyParam<DataType> _candleType;
public int BbPeriod { get => _bbPeriod.Value; set => _bbPeriod.Value = value; }
public decimal BbWidth { get => _bbWidth.Value; set => _bbWidth.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public EnvelopeLimitLadderStrategy()
{
_bbPeriod = Param(nameof(BbPeriod), 20)
.SetDisplay("BB Period", "Bollinger bands period", "Indicators");
_bbWidth = Param(nameof(BbWidth), 1.5m)
.SetDisplay("BB Width", "Bollinger bands deviation", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
protected override void OnReseted() { base.OnReseted(); }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var bb = new BollingerBands { Length = BbPeriod, Width = BbWidth };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bb, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished)
return;
if (!value.IsFinal || value.IsEmpty)
return;
var bbVal = value as BollingerBandsValue;
if (bbVal == null)
return;
var upper = bbVal.UpBand;
var lower = bbVal.LowBand;
var middle = bbVal.MovingAverage;
if (upper == null || lower == null || middle == null)
return;
var close = candle.ClosePrice;
// Mean reversion: buy at lower band
if (close < lower.Value && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Mean reversion: sell at upper band
else if (close > upper.Value && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
// Exit long at middle
else if (Position > 0 && close > middle.Value)
{
SellMarket();
}
// Exit short at middle
else if (Position < 0 && close < middle.Value)
{
BuyMarket();
}
}
}
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.Indicators import BollingerBands
from StockSharp.Algo.Strategies import Strategy
class envelope_limit_ladder_strategy(Strategy):
def __init__(self):
super(envelope_limit_ladder_strategy, self).__init__()
self._bb_period = self.Param("BbPeriod", 20) \
.SetDisplay("BB Period", "Bollinger bands period", "Indicators")
self._bb_width = self.Param("BbWidth", 1.5) \
.SetDisplay("BB Width", "Bollinger bands deviation", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
@property
def bb_period(self):
return self._bb_period.Value
@property
def bb_width(self):
return self._bb_width.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(envelope_limit_ladder_strategy, self).OnReseted()
def OnStarted2(self, time):
super(envelope_limit_ladder_strategy, self).OnStarted2(time)
bb = BollingerBands()
bb.Length = self.bb_period
bb.Width = self.bb_width
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bb, self.process_candle).Start()
def process_candle(self, candle, bb_value):
if candle.State != CandleStates.Finished:
return
if not bb_value.IsFinal or bb_value.IsEmpty:
return
if bb_value.UpBand is None or bb_value.LowBand is None or bb_value.MovingAverage is None:
return
upper = float(bb_value.UpBand)
lower = float(bb_value.LowBand)
middle = float(bb_value.MovingAverage)
close = float(candle.ClosePrice)
if close < lower and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif close > upper and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
elif self.Position > 0 and close > middle:
self.SellMarket()
elif self.Position < 0 and close < middle:
self.BuyMarket()
def CreateClone(self):
return envelope_limit_ladder_strategy()