Converted from the MetaTrader 4 expert advisor "Template_M5_Envelopes.mq4". The strategy tracks a linear weighted moving average (LWMA) envelope on five-minute candles and arms breakout stop orders whenever price stretches far enough away from the channel. Pending orders are dynamically repriced to follow the market, and filled positions are protected by configurable stop-loss, take-profit and trailing-stop logic.
Trading logic
A LWMA based on the median candle price is calculated with the configured EnvelopePeriod. Upper and lower envelope bands are derived by applying the EnvelopeDeviation percentage.
Every finished five-minute candle stores its envelope values alongside the high and low. Signals are only evaluated once a full set of "previous" values is available, matching the MetaTrader implementation that referenced iEnvelopes(..., shift = 1) and the prior bar.
A buy setup appears when:
The previous candle low sits at least DistancePoints below the previous lower envelope, and
The current bid price remains at least DistancePoints below the same envelope value.
A sell setup mirrors the logic with the previous high and the upper envelope.
When a setup is active, only one stop order is allowed (the original EA also restricted itself to a single market or pending order). The order is placed at the current ask/bid plus the EntryOffsetPoints distance.
While the pending order remains active, the strategy monitors the market. If the difference between the order price and the current bid/ask exceeds EntryOffsetPoints + SlippagePoints, the order is cancelled and immediately re-registered at the new reference price, keeping the attached stop-loss and take-profit aligned with the desired offsets.
If the current spread exceeds MaxSpreadPoints, all pending entries are cancelled to avoid trading during unfavourable liquidity conditions.
Order management
Upon entry order activation, the strategy records the execution price and registers protective stop and take-profit orders at StopLossPoints and TakeProfitPoints offsets respectively. If either value is zero, the corresponding protection is skipped.
The trailing stop module (enabled with UseTrailingStop) tracks the best bid/ask. Whenever price moves in favour of the open position by more than TrailingStopPoints, the stop order is repriced closer to the market using ReRegisterOrder. Long stops only trail upwards, while short stops only trail downwards.
When the position is fully closed, all protective orders are cancelled and internal state is reset. No new entry orders are considered until the position returns to flat.
Parameters
Parameter
Description
MaxSpreadPoints
Maximum allowed spread before pending orders are cancelled.
TakeProfitPoints
Take-profit distance applied to filled positions.
StopLossPoints
Stop-loss distance applied to pending and filled positions.
EntryOffsetPoints
Offset (in points) from bid/ask where stop entries are placed.
UseTrailingStop
Enables trailing stop management for open positions.
TrailingStopPoints
Distance (in points) maintained by the trailing stop.
FixedVolume
Trading volume submitted with each entry order.
EnvelopePeriod
Length of the LWMA used as the envelope basis.
EnvelopeDeviation
Width of the envelope in percent.
DistancePoints
Minimum gap between price and envelope required for a signal.
SlippagePoints
Extra tolerance (in points) added to the repricing threshold.
CandleType
Timeframe used to calculate the LWMA envelope (default M5).
Notes
The strategy subscribes to both candles and level-1 quotes. If bid/ask data is unavailable, entry conditions will not trigger because spread and trailing-stop calculations depend on it.
Protective stop and take-profit orders are recreated with the latest volume whenever the trailing logic adjusts the stop-loss price.
All comments inside the code are written in English, and tabs are used for indentation to match the project conventions.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Template M5 Envelopes strategy: WMA envelope breakout.
/// Buys above upper envelope, sells below lower envelope.
/// </summary>
public class TemplateM5EnvelopesStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _wmaPeriod;
private readonly StrategyParam<decimal> _deviation;
private bool _wasAboveUpper;
private bool _wasBelowLower;
private bool _hasPrevSignal;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int WmaPeriod { get => _wmaPeriod.Value; set => _wmaPeriod.Value = value; }
public decimal Deviation { get => _deviation.Value; set => _deviation.Value = value; }
public TemplateM5EnvelopesStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_wmaPeriod = Param(nameof(WmaPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("WMA Period", "Weighted MA period", "Indicators");
_deviation = Param(nameof(Deviation), 0.3m)
.SetDisplay("Deviation %", "Envelope deviation percent", "Indicators");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_wasAboveUpper = false;
_wasBelowLower = false;
_hasPrevSignal = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrevSignal = false;
var wma = new WeightedMovingAverage { Length = WmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(wma, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal wmaValue)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
var upper = wmaValue * (1 + Deviation / 100m);
var lower = wmaValue * (1 - Deviation / 100m);
var aboveUpper = close > upper;
var belowLower = close < lower;
if (_hasPrevSignal)
{
if (aboveUpper && !_wasAboveUpper && Position <= 0)
BuyMarket();
else if (belowLower && !_wasBelowLower && Position >= 0)
SellMarket();
}
_wasAboveUpper = aboveUpper;
_wasBelowLower = belowLower;
_hasPrevSignal = true;
}
}
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 WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class template_m5_envelopes_strategy(Strategy):
def __init__(self):
super(template_m5_envelopes_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._wma_period = self.Param("WmaPeriod", 50)
self._deviation = self.Param("Deviation", 0.3)
self._was_above_upper = False
self._was_below_lower = False
self._has_prev_signal = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def WmaPeriod(self):
return self._wma_period.Value
@WmaPeriod.setter
def WmaPeriod(self, value):
self._wma_period.Value = value
@property
def Deviation(self):
return self._deviation.Value
@Deviation.setter
def Deviation(self, value):
self._deviation.Value = value
def OnReseted(self):
super(template_m5_envelopes_strategy, self).OnReseted()
self._was_above_upper = False
self._was_below_lower = False
self._has_prev_signal = False
def OnStarted2(self, time):
super(template_m5_envelopes_strategy, self).OnStarted2(time)
self._has_prev_signal = False
wma = WeightedMovingAverage()
wma.Length = self.WmaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(wma, self._process_candle).Start()
def _process_candle(self, candle, wma_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
wma_val = float(wma_value)
upper = wma_val * (1 + float(self.Deviation) / 100.0)
lower = wma_val * (1 - float(self.Deviation) / 100.0)
above_upper = close > upper
below_lower = close < lower
if self._has_prev_signal:
if above_upper and not self._was_above_upper and self.Position <= 0:
self.BuyMarket()
elif below_lower and not self._was_below_lower and self.Position >= 0:
self.SellMarket()
self._was_above_upper = above_upper
self._was_below_lower = below_lower
self._has_prev_signal = True
def CreateClone(self):
return template_m5_envelopes_strategy()