Открыть на GitHub

Стратегия Template M5 Envelopes

Конвертирована из эксперта MetaTrader 4 «Template_M5_Envelopes.mq4». Стратегия отслеживает канал на основе линейно-взвешенной скользящей средней (LWMA) на пятиминутных свечах и размещает стоп-заявки на пробой, когда цена уходит слишком далеко от границ канала. Висящие заявки автоматически перерегистрируются вслед за рынком, а открытые позиции защищаются стоп-лоссом, тейк-профитом и при необходимости трейлинг-стопом.

Логика торговли

  1. На основе средней цены свечи рассчитывается LWMA длиной EnvelopePeriod. Верхняя и нижняя границы канала получаются умножением базовой линии на коэффициент EnvelopeDeviation.
  2. После закрытия каждой 5-минутной свечи сохраняются значения канала, максимум и минимум. Сигнал формируется только тогда, когда доступны данные по «предыдущей» свече — так же, как и в оригинальном советнике, использовавшем iEnvelopes(..., shift = 1).
  3. Покупка активируется, если выполняются оба условия:
    • минимум прошлой свечи расположен не менее чем на DistancePoints ниже нижней границы канала;
    • текущая цена bid также остаётся минимум на DistancePoints ниже этой же границы.
  4. Продажа определяется зеркально — по максимуму прошлой свечи и верхней границе канала.
  5. Одновременно допускается только одна стоп-заявка или позиция (ограничение полностью повторяет исходный код). При появлении сигнала заявка выставляется на расстоянии EntryOffsetPoints от текущих bid/ask.
  6. Пока стоп-заявка активна, стратегия отслеживает рынок. Если разница между ценой заявки и актуальной bid/ask превышает EntryOffsetPoints + SlippagePoints, заявка отменяется и сразу перерегистрируется по новой цене, при этом стоп-лосс и тейк-профит пересчитываются относительно свежего значения.
  7. Если фактический спред больше 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;
	}
}