Открыть на GitHub

Стратегия Hedger Drawdown

Портирование на StockSharp советника MetaTrader 5 hedger.mq5 (MQL №23511). Исходная система открывает защитный хедж в противоположную сторону, когда текущая позиция уходит в просадку на заданное количество пунктов. После частичного отката цена уменьшает убыток хеджа, и при достижении меньшего порога позиция закрывается даже с убытком, чтобы дать основному ордеру восстановиться. Конвертация воспроизводит этот цикл с использованием высокоуровневого API StockSharp и адаптирует механику к неттинговой модели платформы.

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

  1. Стратегия отслеживает закрытие каждой свечи выбранного таймфрейма.
  2. Для каждой не-хеджевой длинной позиции вычисляется расстояние между ценой входа и текущим закрытием. Если оно больше или равно DrawdownOpenPips и активного короткого хеджа нет, открывается короткий ордер тем же объёмом.
  3. Для коротких позиций выполняется зеркальное правило — длинный хедж появляется, когда убыток достигает порога открытия.
  4. Активные хеджи закрываются, когда их плавающий убыток достигает значения DrawdownClosePips, что повторяет логику MetaTrader по снятию защиты после частичного восстановления.
  5. Если счёт в нуле и включён параметр StartWithLong, стратегия открывает стартовую длинную позицию, чтобы запустить цикл.

Поскольку StockSharp ведёт неттинговые позиции, стратегия хранит внутренние журналы длинных и коротких ордеров (с признаком хеджа). Каждый рыночный ордер обновляет эти журналы, позволяя управлять хеджами независимо от того, что брокер агрегирует позиции.

Параметры

Параметр Описание
DrawdownOpenPips Просадка в пунктах, при которой открывается противоположный хедж.
DrawdownClosePips Просадка в пунктах, при которой хедж принудительно закрывается.
InitialVolume Объём стартовой сделки при запуске цикла.
StartWithLong При включении автоматически открывает первую длинную позицию, когда нет позиций.
EnableVerboseLogging Записывает действия по управлению хеджами в журнал стратегии.
CandleType Ряд свечей, по которому измеряется просадка.

Отличия от версии MetaTrader

  • В оригинале признак хеджа задавался комментариями тикетов (hedge_buy / hedge_sell). В портированной версии состояние хранится в памяти из-за неттинговой модели StockSharp.
  • Проверки маржи и настройка проскальзывания опущены: размещение заявок выполняется через BuyMarket / SellMarket.
  • Пороговые значения и объём имеют заданные диапазоны оптимизации для использования стандартных оптимизаторов StockSharp.

Рекомендации по использованию

  1. Подключите стратегию к нужному инструменту и портфелю.
  2. Подберите пороги в пунктах с учётом волатильности инструмента.
  3. Включайте подробный лог на этапе тестирования — он фиксирует каждое открытие и закрытие хеджа с указанием количества пунктов.
  4. Запускайте на таймфреймах с информативными закрытиями свечей (например, M15–H1), чтобы избежать избыточной торговли.
using System;
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;

public class HedgerDrawdownStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public HedgerDrawdownStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}