Открыть на GitHub

Стратегия Envelope MA Short

Общее описание

Envelope MA Short — порт экспертного советника MetaTrader EnvelopeMA.mq4 (ID 9533), переписанный на C# для платформы StockSharp. Стратегия работает на свечах 15 минут и открывает только короткие позиции. Она использует экспоненциальную скользящую среднюю по максимумам для построения ценового конверта, две EMA по минимумам и три варианта индикатора Parabolic SAR. Когда цена и скользящие опускаются в нижнюю половину конверта, стратегия размещает отложенный ордер Sell Stop на уровне нижней границы. После активации ордера позиция сопровождается фиксированными уровнями стоп-лосса/тейк-профита и дополнительными условиями выхода по индикаторам.

Индикаторы и условия

  • Базовый конверт: EMA от максимумов свечей с периодом EnvelopePeriod (по умолчанию 280). Ширина конверта задаётся параметром EnvelopeDeviation (0,08%). Нижняя граница используется как уровень размещения sell stop.
  • Быстрая EMA: EMA от минимумов (FastMaPeriod, по умолчанию 6) — подтверждает импульс перед установкой отложенного ордера.
  • Медленная EMA со смещением: EMA от минимумов (SlowMaPeriod, по умолчанию 18) берётся со значением предыдущей свечи, что соответствует параметру shift в MetaTrader.
  • Три Parabolic SAR: набор SAR с ускорениями 0.03/0.5, 0.015/0.6 и 0.02/0.2. Они должны находиться выше текущей цены, чтобы разрешить выход по индикатору.

Стратегия анализирует только завершённые свечи. Если быстрая и смещённая медленная EMA, а также цена закрытия расположены между верхней и нижней границами конверта, размещается отложенный ордер Sell Stop на нижней границе. Отложенный ордер действует около пяти свечей, после чего отменяется.

Управление позицией

  • Защитные уровни: После входа вычисляются внутренние уровни стоп-лосса и тейк-профита на основе параметров StopLossPips и TakeProfitPips. Для имитации внутридневных срабатываний используется максимум/минимум завершённых свечей.
  • Выход по индикаторам: Короткая позиция закрывается преждевременно, если обе EMA и цена закрытия находятся ниже цены входа, все три SAR располагаются выше цены, а быстрая EMA пересекает медленную снизу вверх.
  • Перестановка стопа: Спустя как минимум четыре свечи, если максимумы с момента входа опустились минимум на три шага цены ниже входа и закрытие находится ниже нижней границы конверта, стоп-лосс подтягивается к текущему значению конверта.

Управление риском

  • Порог ликвидности: Параметр LiquidityThreshold (по умолчанию 0.58) контролирует отношение текущей стоимости портфеля к начальному балансу. При падении ниже порога стратегия закрывает короткие позиции и отменяет ожидающие ордера.
  • Срок действия ордеров: Для Sell Stop рассчитывается время истечения (пять интервалов текущего таймфрейма). Если до указанного момента ордер не исполнен, он отменяется.

Параметры

Параметр Описание Значение по умолчанию
CandleType Таймфрейм/тип свечи, по которому ведётся расчёт. Свеча 15 минут
EnvelopePeriod Период EMA для базового конверта. 280
EnvelopeDeviation Ширина конверта в процентах. 0.08
FastMaPeriod Период быстрой EMA по минимумам. 6
SlowMaPeriod Период медленной EMA (используется значение предыдущей свечи). 18
StopLossPips Расстояние стоп-лосса от цены входа (в пунктах). 25
TakeProfitPips Расстояние тейк-профита от цены входа (в пунктах). 25
TradeVolume Объём сделок и отложенных ордеров. 1
LiquidityThreshold Минимальное отношение equity/баланс; при нарушении закрываются шорты. 0.58

Особенности конверсии

  • В оригинале размер лота зависел от свободной маржи и «counter-pips». В версии для StockSharp используется явный параметр TradeVolume, соответствующий модели работы Strategy.
  • MetaTrader поддерживает встроенное поле истечения ордеров. В StockSharp логика истечения реализована внутри стратегии с использованием времени закрытия свечей.
  • Стоп-лосс и тейк-профит отслеживаются по максимумам/минимумам завершённых свечей, что воспроизводит подход исходного советника, который также принимал решения только после закрытия бара.
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Envelope MA Short: EMA band reversion with ATR stops.
/// </summary>
public class EnvelopeMaShortStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<decimal> _bandPercent;

	private decimal _entryPrice;

	public EnvelopeMaShortStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");
		_emaLength = Param(nameof(EmaLength), 20)
			.SetDisplay("EMA Length", "EMA period.", "Indicators");
		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
		_bandPercent = Param(nameof(BandPercent), 1.0m)
			.SetDisplay("Band %", "Band width percent.", "Indicators");
	}

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
	public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
	public decimal BandPercent { get => _bandPercent.Value; set => _bandPercent.Value = value; }

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_entryPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_entryPrice = 0;
		var ema = new ExponentialMovingAverage { Length = EmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, atr, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, ema); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal emaVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished) return;
		if (atrVal <= 0 || emaVal <= 0) return;
		var close = candle.ClosePrice;
		var factor = BandPercent / 100m;
		var upper = emaVal * (1m + factor);
		var lower = emaVal * (1m - factor);

		if (Position > 0)
		{
			if (close >= emaVal || close <= _entryPrice - atrVal * 1.5m) { SellMarket(); _entryPrice = 0; }
		}
		else if (Position < 0)
		{
			if (close <= emaVal || close >= _entryPrice + atrVal * 1.5m) { BuyMarket(); _entryPrice = 0; }
		}

		if (Position == 0)
		{
			if (close < lower) { _entryPrice = close; BuyMarket(); }
			else if (close > upper) { _entryPrice = close; SellMarket(); }
		}
	}
}