Envelope Limit Ladder Strategy — порт эксперта MetaTrader E_2_12_5min.mq4 (ID 7671) на платформу StockSharp. Стратегия воссоздаёт лестницу отложенных лимитных ордеров вокруг конверта EMA на пятиминутных свечах и сохраняет оригинальную схему из трёх тейк-профитов и трейлинг-стопа.
Идея
Фильтр-конверт. Рассчитывается конверт скользящей средней (по умолчанию EMA 144 с отклонением 0,05%) на таймфрейме EnvelopeCandleType. Он задаёт среднюю линию и границы.
Сигнальная свеча. Анализ ведётся по подписке CandleType (по умолчанию 5 минут). Когда предыдущая свеча закрывается между средней линией и ближайшей границей, стратегия выставляет лимитные заявки по средней.
Лестница ордеров. Одновременно размещаются до трёх заявок Buy Limit и трёх Sell Limit:
Цена входа — выровненное значение средней линии.
Стоп-лосс — противоположная граница конверта.
Тейк-профит — граница ± пользовательские отступы (8, 13 и 21 пункт по умолчанию).
Торговое окно. Заявки появляются только при выполнении условия TradingStartHour < Hour < TradingEndHour. Когда час открытия достигает TradingEndHour, все невыполненные лимиты удаляются.
Сопровождение позиций. Каждая исполненная заявка получает собственные ордера стоп-лосс/тейк-профит. Дополнительно можно включить перенос стопа на скользящую среднюю (или оставить на противоположной границе) после прорыва за конверт.
Параметры
Параметр
Значение по умолчанию
Описание
CandleType
5 минут
Таймфрейм для поиска сигналов.
EnvelopeCandleType
5 минут
Таймфрейм для расчёта конверта (аналог MT4 EnvTimeFrame).
EnvelopePeriod
144
Период скользящей средней.
MaMethod
EMA
Метод скользящей (SMA, EMA, SMMA, LWMA).
EnvelopeDeviation
0.05
Ширина конверта в процентах (0.05 = 0.05%).
TradingStartHour
0
Первый час, когда допускается постановка ордеров (жёсткое неравенство, как в MT4).
TradingEndHour
17
Час, после которого лимиты удаляются.
FirstTakeProfitPoints
8
Отступ для первого тейк-профита в пунктах.
SecondTakeProfitPoints
13
Отступ для второго тейк-профита.
ThirdTakeProfitPoints
21
Отступ для третьего тейк-профита.
UseOppositeEnvelopeTrailing
true
Использовать противоположную границу для трейлинга (true) или переносить стоп на среднюю (false). Соответствует флагу MT4 MaElineTSL.
OrderVolume
0.1
Объём каждой отложенной заявки (вместо функции LotsOptimized).
Особенности поведения
Для каждой заполненной заявки создаётся отдельная пара стоп/тейк — закрытие одной ступени не влияет на остальные.
Трейлинг включается только после выхода цены за границу конверта и срабатывает строго в сторону прибыли.
При различии EnvelopeCandleType и CandleType стратегия использует последнюю рассчитанную величину конверта с дополнительной подписки, что повторяет обращение к старшему таймфрейму в MT4.
Адаптивное управление объёмом из исходного советника заменено явным параметром OrderVolume, что делает поведение стратегии детерминированным.
Рекомендации
Подбирайте таймфрейм конверта в соответствии с настройками оригинального эксперта (5 минут, 1 час и т. д.).
Установите UseOppositeEnvelopeTrailing = false, если хотите переносить стоп на среднюю линию после прорыва.
Оптимизируйте ширину конверта и расстояния тейк-профитов совместно — ступени лестницы зависят от текущей волатильности инструмента.
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()