Открыть на GitHub

Стратегия Hedge Any Positions

Обзор

Hedge Any Positions Strategy — это портированная на StockSharp версия советника Hedge any positions (barabashkakvn's edition) для MQL5. Логика осталась прежней: каждая открытая позицийная «нога», созданная стратегией, контролируется, и при неблагоприятном движении цены на заданное число пунктов автоматически открывается противоположная сделка увеличенного объёма. Реализация целиком опирается на высокоуровневый API StockSharp, поэтому для хеджирования используются рыночные ордера, а состояние позиций хранится во внутренних структурах без дополнительного инфраструктурного кода.

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

Алгоритм хеджирования

  1. Ленточные данные — стратегию ведёт выбранный тип свечей CandleType. Обрабатываются только полностью сформированные свечи.
  2. Расчёт убытка — на закрытии каждой свечи проверяется, превысило ли снижение (или рост для короткой позиции) цену входа хотя бы на LosingPips, умноженные на рассчитанный размер пункта.
  3. Открытие хеджа — если обнаружена убыточная нога, отправляется рыночный ордер в противоположном направлении. Объём ордера равен объёму исходной ноги, умноженному на LotCoefficient, округляется по шагу объёма инструмента и ограничивается допустимыми минимумом и максимумом.
  4. Обновление состояния — после отправки противоположного ордера исходная нога помечается как захеджированная, а новая сделка добавляется в список и может быть застрахована позднее, если рынок вновь развернётся.

Параметры

Параметр Описание Значение по умолчанию
CandleType Таймфрейм, по которому оценивается движение цены и срабатывает хеджирование. Свечи 1 минуты
LosingPips Количество пунктов против позиции, после которого открывается хедж. 5
LotCoefficient Множитель объёма исходной ноги при отправке хеджирующего ордера. 2.0
AutoPlaceInitialTrade При включении автоматически создаёт первую сделку при старте стратегии. Выключено
InitialVolume Объём первоначального ордера при автоматическом старте (округляется по шагу объёма). 0.10
InitialDirection Направление (покупка или продажа) для стартовой сделки. Покупка

Важно: установите свойство Strategy.Volume в значение базового объёма. Остальные параметры управляют только логикой хеджирования.

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

  1. Перед запуском назначьте стратегии Security, Portfolio и нужный базовый Volume.
  2. Подберите LosingPips и LotCoefficient под волатильность выбранного инструмента и ваш риск-профиль.
  3. Включите AutoPlaceInitialTrade, если нужно, чтобы первая позиция открывалась автоматически; иначе откройте её вручную или другой стратегией.
  4. Поскольку высокоуровневый API StockSharp работает с нетто-позициями, внутренняя структура стратегии эмулирует отдельные «ноги». При торговле на неттинговых счетах контролируйте совокупное плечо и маржу.
  5. Отслеживайте отчёты об исполнении — каждое хеджирование выполняется рыночным ордером (BuyMarket или SellMarket).

Отличия от оригинального советника

  • Проверки маржи, контроль проскальзывания и подробный вывод результатов убраны: StockSharp сам сообщает об ошибках через события стратегии.
  • Вместо тиковых данных используется логика на закрытии свечей. При необходимости быстрого реагирования выберите меньший таймфрейм.
  • Округление объёма основано на Security.VolumeStep, Security.MinVolume и Security.MaxVolume, что обеспечивает соответствие биржевым требованиям.
  • Уведомления и случайный стартовый трейд, применявшиеся в тестере MQL5, исключены. Им на смену пришёл настраиваемый параметр автоматического входа.

Возможные доработки

  • Скомбинировать модуль хеджирования с отдельной стратегией, отвечающей за начальное открытие позиции.
  • Добавить ограничения по капиталу или максимальной глубине цепочки, чтобы избежать бесконтрольного накопления сделок.
  • Подключить мониторинг портфеля для контроля маржинальных требований при торговле сразу несколькими инструментами.
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;

/// <summary>
/// Hedge Any Positions strategy using ATR-based mean reversion.
/// Enters long when price drops below EMA by ATR threshold, enters short on rally above.
/// Uses stop-loss and take-profit for risk management.
/// </summary>
public class HedgeAnyPositionsStrategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _atrMultiplier;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _ema;
	private AverageTrueRange _atr;

	private decimal _entryPrice;
	private int _cooldown;

	/// <summary>
	/// EMA period.
	/// </summary>
	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	/// <summary>
	/// ATR period.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// Multiplier applied to ATR for entry threshold.
	/// </summary>
	public decimal AtrMultiplier
	{
		get => _atrMultiplier.Value;
		set => _atrMultiplier.Value = value;
	}

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

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

	/// <summary>
	/// Initializes a new instance of the <see cref="HedgeAnyPositionsStrategy"/> class.
	/// </summary>
	public HedgeAnyPositionsStrategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA period for mean price", "Indicator");

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR calculation period", "Indicator");

		_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
			.SetGreaterThanZero()
			.SetDisplay("ATR Multiplier", "Multiplier for entry distance", "Indicator");

		_stopLossPoints = Param(nameof(StopLossPoints), 200)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 300)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk");
	}

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

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

		_ema = null;
		_atr = null;
		_entryPrice = 0;
		_cooldown = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_ema = new ExponentialMovingAverage { Length = EmaPeriod };
		_atr = new AverageTrueRange { Length = AtrPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_ema, _atr, ProcessCandle);
		subscription.Start();
	}

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

		if (!_ema.IsFormed || !_atr.IsFormed)
			return;

		if (_cooldown > 0)
		{
			_cooldown--;
			return;
		}

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;
		var threshold = atrValue * AtrMultiplier;

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				return;
			}

			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				return;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				return;
			}

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				return;
			}
		}

		// Mean reversion: buy when price drops below EMA by threshold
		if (close < emaValue - threshold && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 80;
		}
		// Mean reversion: sell when price rallies above EMA by threshold
		else if (close > emaValue + threshold && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
			_cooldown = 80;
		}
	}
}