Конвертирована из эксперта MetaTrader 4 «Template_M5_Envelopes.mq4». Стратегия отслеживает канал на основе линейно-взвешенной скользящей средней (LWMA) на пятиминутных свечах и размещает стоп-заявки на пробой, когда цена уходит слишком далеко от границ канала. Висящие заявки автоматически перерегистрируются вслед за рынком, а открытые позиции защищаются стоп-лоссом, тейк-профитом и при необходимости трейлинг-стопом.
Логика торговли
На основе средней цены свечи рассчитывается LWMA длиной EnvelopePeriod. Верхняя и нижняя границы канала получаются умножением базовой линии на коэффициент EnvelopeDeviation.
После закрытия каждой 5-минутной свечи сохраняются значения канала, максимум и минимум. Сигнал формируется только тогда, когда доступны данные по «предыдущей» свече — так же, как и в оригинальном советнике, использовавшем iEnvelopes(..., shift = 1).
Покупка активируется, если выполняются оба условия:
минимум прошлой свечи расположен не менее чем на DistancePoints ниже нижней границы канала;
текущая цена bid также остаётся минимум на DistancePoints ниже этой же границы.
Продажа определяется зеркально — по максимуму прошлой свечи и верхней границе канала.
Одновременно допускается только одна стоп-заявка или позиция (ограничение полностью повторяет исходный код). При появлении сигнала заявка выставляется на расстоянии EntryOffsetPoints от текущих bid/ask.
Пока стоп-заявка активна, стратегия отслеживает рынок. Если разница между ценой заявки и актуальной bid/ask превышает EntryOffsetPoints + SlippagePoints, заявка отменяется и сразу перерегистрируется по новой цене, при этом стоп-лосс и тейк-профит пересчитываются относительно свежего значения.
Если фактический спред больше MaxSpreadPoints, все ожидающие заявки удаляются, чтобы не торговать при плохой ликвидности.
Управление ордерами
После исполнения стоп-заявки фиксируется цена входа и выставляются защитные заявки: стоп-лосс на расстоянии StopLossPoints, тейк-профит на TakeProfitPoints. При нулевом значении соответствующая защита не создаётся.
При включённом UseTrailingStop трейлинг-стоп отслеживает лучшие bid/ask. Как только цена движется в прибыльную сторону больше чем на TrailingStopPoints, стоп-заявка подтягивается ближе к рынку с помощью ReRegisterOrder. Для длинных позиций стоп перемещается только вверх, для коротких — только вниз.
После полного закрытия позиции все защитные заявки отменяются, а внутренняя память очищается. Новые входы рассматриваются только при нулевом нетто-позиционировании.
Параметры
Параметр
Описание
MaxSpreadPoints
Максимально допустимый спред; при превышении стоп-заявки снимаются.
TakeProfitPoints
Расстояние до тейк-профита после входа.
StopLossPoints
Расстояние до стоп-лосса для заявок и открытых позиций.
EntryOffsetPoints
Отступ от bid/ask, на котором размещаются стоп-заявки.
UseTrailingStop
Включает трейлинг-стоп.
TrailingStopPoints
Дистанция трейлинг-стопа от текущей цены.
FixedVolume
Постоянный объём сделки.
EnvelopePeriod
Период LWMA, формирующей канал.
EnvelopeDeviation
Ширина канала в процентах.
DistancePoints
Минимальный разрыв между ценой и каналом для появления сигнала.
SlippagePoints
Дополнительный зазор (в пунктах), при котором заявка переоценивается.
CandleType
Таймфрейм свечей для расчёта канала (по умолчанию M5).
Примечания
Стратегия подписывается как на свечи, так и на поток Level1. Без данных по bid/ask проверка спреда и трейлинг-стопа невозможна, поэтому новые заявки не формируются.
При каждом обновлении трейлинг-стопа защитные заявки пересоздаются с учётом текущего объёма позиции.
Все комментарии в коде написаны на английском языке, а для отступов используются табуляции в соответствии с требованиями репозитория.
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()