Открыть на GitHub

EES Hedger (расширенная версия)

Обзор

Стратегия воспроизводит логику классического советника MetaTrader «EES Hedger». Каждый раз, когда на том же счёте внешний советник, трейдер или ручная операция открывает позицию, стратегия немедленно создаёт противоположный хедж с настраиваемым объёмом. Далее хедж сопровождается стоп-лоссом, тейк-профитом, переносом в безубыток и трейлинг-стопом, что позволяет нейтрализовать риск и сохранить прибыль, полученную на встречной сделке.

Стратегия не генерирует собственных сигналов: она только наблюдает за сделками счёта, реагирует на подходящие операции и управляет хеджем до тех пор, пока позиция не будет закрыта защитными заявками либо вручную.

Логика работы

  1. Отслеживание внешних сделок. Поток сделок счёта поступает через коннектор. Если заполнен параметр OriginalOrderComment, учитываются только операции с подходящим комментарием; при пустом значении хеджируются все сделки по инструменту. Сделки самой стратегии исключаются по идентификаторам транзакций.
  2. Создание хеджа. После обнаружения подходящей сделки стратегия выставляет рыночный ордер противоположного направления и объёма HedgeVolume. Параметр HedgerOrderComment помогает отделить хеджевые заявки в отчётах.
  3. Управление рисками. После исполнения хеджа выставляются защитные стоп- и тейк-ордеры на расстояниях, заданных в пипсах. Когда достигается условие безубытка, стоп переносится к цене входа плюс один пип. При включённом трейлинге стоп двигается дальше по мере развития благоприятного движения.
  4. Сброс состояния. Когда позиция обнуляется (например, при ручном закрытии), все защитные ордера снимаются, а внутренние флаги очищаются — стратегия готова к следующему внешнему сигналу.

Параметры

Параметр Описание
HedgeVolume Объём встречной позиции.
StopLossPips Расстояние от цены входа до стоп-ордера.
TakeProfitPips Расстояние от цены входа до тейк-профита.
TrailingStopPips Дистанция трейлинг-стопа; ноль отключает функцию.
TrailingActivationPips Минимальная прибыль в пипсах, после которой трейлинг-стоп начинает двигаться.
BreakEvenPips Прибыль в пипсах, необходимая для переноса стопа в безубыток.
OriginalOrderComment Фильтр по комментарию исходных сделок; пустое значение — хеджировать все сделки.
HedgerOrderComment Комментарий, присваиваемый хеджевым и защитным ордерам стратегии.

Практические рекомендации

  • Запускайте стратегию на том же счёте, где работают исходные сделки. Только так она сможет увидеть внешние позиции и отреагировать на них.
  • При использовании мостов MetaTrader убедитесь, что комментарий исходной заявки передаётся в StockSharp, иначе фильтр по комментарию работать не будет.
  • Размер пипса автоматически вычисляется по шагу цены инструмента. Для пятизнаковых валютных котировок указанные значения переводятся в корректные ценовые приращения.
  • Безубыток и трейлинг никогда не ухудшают позицию: стоп только приближается к текущей цене и не возвращается в убыточную область.
  • Управление исходной позицией (закрытие, переворот и т. п.) остаётся за основным торговым решением.

Порядок работы

  1. Настройте параметры стратегии, особенно фильтры по комментариям и объём хеджа.
  2. Запустите стратегию и убедитесь, что соединение с брокером активно. До появления внешней сделки стратегия находится в режиме ожидания.
  3. Как только поступит подходящая сделка, проследите, как создаётся встречный ордер и выставляются защитные заявки в стакане.
  4. Контролируйте работу безубытка и трейлинга, чтобы выбранные расстояния соответствовали спецификациям брокера.
  5. По окончании работы остановите стратегию — все активные защитные ордера будут сняты автоматически.

Ограничения

  • Стратегия может хеджировать только те сделки, которые доступны в потоке коннектора. Невидимые операции остаются без реакции.
  • Требования к минимальному лоту у разных брокеров различаются. Убедитесь, что HedgeVolume соответствует шагу объёма инструмента.
  • Из-за мгновенной отправки рыночных ордеров в волатильный момент возможен проскальзывание. При необходимости увеличьте защитные расстояния, чтобы учесть этот риск.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Adapted from the MetaTrader "EES Hedger" expert advisor.
/// Uses EMA crossover signals with break-even and trailing stop risk management.
/// </summary>
public class EesHedgerAdvancedStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<DataType> _candleType;

	/// <summary>
	/// Fast EMA period.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance in price steps.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Candle type used by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes default parameters.
	/// </summary>
	public EesHedgerAdvancedStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");

		_stopLossPips = Param(nameof(StopLossPips), 500)
			.SetDisplay("Stop Loss", "Stop-loss distance", "Risk Management");

		_takeProfitPips = Param(nameof(TakeProfitPips), 500)
			.SetDisplay("Take Profit", "Take-profit distance", "Risk Management");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Candles used for calculations", "Market Data");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return new[] { (Security, CandleType) };
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		var fastEma = new EMA { Length = FastPeriod };
		var slowEma = new EMA { Length = SlowPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fastEma);
			DrawIndicator(area, slowEma);
			DrawOwnTrades(area);
		}

		// Use StartProtection for SL/TP
		var tp = TakeProfitPips > 0 ? new Unit(TakeProfitPips, UnitTypes.Absolute) : null;
		var sl = StopLossPips > 0 ? new Unit(StopLossPips, UnitTypes.Absolute) : null;
		StartProtection(tp, sl);

		base.OnStarted2(time);
	}

	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _hasPrev;

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = 0;
		_prevSlow = 0;
		_hasPrev = false;
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevFast = fastValue;
			_prevSlow = slowValue;
			_hasPrev = true;
			return;
		}

		if (!_hasPrev)
		{
			_prevFast = fastValue;
			_prevSlow = slowValue;
			_hasPrev = true;
			return;
		}

		// EMA crossover detection
		var crossedUp = _prevFast <= _prevSlow && fastValue > slowValue;
		var crossedDown = _prevFast >= _prevSlow && fastValue < slowValue;

		if (crossedUp)
		{
			// Close short if any, then go long
			if (Position < 0)
				BuyMarket(Math.Abs(Position));
			if (Position <= 0)
				BuyMarket(Volume);
		}
		else if (crossedDown)
		{
			// Close long if any, then go short
			if (Position > 0)
				SellMarket(Position);
			if (Position >= 0)
				SellMarket(Volume);
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}