Открыть на GitHub

Сетка OverHedge V2

Стратегия переносит эксперт OverHedge V2 из MetaTrader на высокоуровневый API StockSharp. Она строит хеджирующую сетку: определяет направление по быстрой и медленной EMA, затем чередует покупки и продажи внутри динамического туннеля. Объём каждой новой сделки увеличивается геометрически, а весь пул позиций закрывается после достижения заданной совокупной прибыли.

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

  • Фильтр тренда. EMA(8) должна отстоять от EMA(21) не менее чем на MinDistancePips. Это определяет направление первой сделки в каждом цикле.
  • Туннель. Его ширина равна удвоенному текущему спреду плюс TunnelWidthPips, пересчитанный в цену. Границы туннеля служат триггером для сделки в противоположную сторону.
  • Чередование. Первые три позиции открываются по тренду, затем стратегия чередует сторону сделок, чтобы захеджировать позицию, сохраняя привязку к тем же уровням.
  • Рост объёма. Каждая последующая сделка умножает объём на BaseMultiplier, начиная с StartVolume. Значение приводится к допустимому шагу лота инструмента.
  • Выход из цикла. Когда совокупная нереализованная прибыль в пересчёте на лот превышает MinProfitTargetPips, а общий результат превысил ProfitTargetPips, все позиции закрываются и цикл начинается заново.
  • Принудительное завершение. Параметр ShutdownGrid = true мгновенно закрывает позиции и блокирует открытие новых, пока флаг не будет сброшен.

Условия входа

Покупка

  • Фильтр тренда указывает на рост (EMA_short - EMA_long > MinDistancePips).
  • Текущая цена Ask не ниже уровня покупки.
  • Стратегия не находится в режиме остановки и целевая прибыль ещё не достигнута.

Продажа

  • Фильтр тренда указывает на снижение (EMA_long - EMA_short > MinDistancePips).
  • Цена Ask не выше текущей точки продажи.
  • Флаг остановки снят, целевая прибыль ещё не получена.

Управление выходом

  • Фиксация прибыли. Все позиции закрываются по рынку, если совокупный профит корзины превышает ProfitTargetPips, а каждая сторона заработала не менее MinProfitTargetPips на лот.
  • Аварийный выход. Установка ShutdownGrid = true немедленно ликвидирует позицию.

Индикаторы и данные

  • EMA(8) и EMA(21), рассчитываемые по выбранному типу свечей.
  • Подписка Level 1 необходима для отслеживания лучшего Bid/Ask при расчёте туннеля и проверке условий входа.

Параметры

Параметр Описание
StartVolume Начальный объём первой сделки в цикле.
BaseMultiplier Множитель, увеличивающий объём каждой следующей заявки.
TunnelWidthPips Дополнительная ширина туннеля в пунктах поверх двойного спреда.
ProfitTargetPips Целевая прибыль корзины в пунктах.
MinProfitTargetPips Минимальное движение в прибыль по каждой стороне перед закрытием цикла.
ShortEmaPeriod Период быстрой EMA для определения тренда.
LongEmaPeriod Период медленной EMA для подтверждения тренда.
MinDistancePips Минимальное расхождение EMA, необходимое для входа.
CandleType Таймфрейм свечей, на которых работают индикаторы и торговый цикл.
ShutdownGrid Флаг остановки: закрывает позиции и запрещает новые сделки.

Практические замечания

  • По умолчанию используется часовой таймфрейм; при необходимости установите тот же период, что и в оригинальном советнике.
  • Для корректной работы требуется поток котировок Level 1, обеспечивающий актуальные Bid/Ask.
  • StockSharp ведёт нетто-позицию по инструменту, поэтому встречные сделки уменьшают или разворачивают общий объём, а не создают отдельные хеджевые ордера, хотя логика закрытия корзины остаётся прежней.
  • Перед запуском убедитесь, что шаг цены и объёмов инструмента соответствуют заданным параметрам туннеля и шкалы лотов.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Grid hedging strategy based on EMA crossover direction.
/// Opens positions on EMA trend, reverses on direction change.
/// </summary>
public class OverHedgeV2Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _shortEmaPeriod;
	private readonly StrategyParam<int> _longEmaPeriod;

	private int _prevSignal;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int ShortEmaPeriod
	{
		get => _shortEmaPeriod.Value;
		set => _shortEmaPeriod.Value = value;
	}

	public int LongEmaPeriod
	{
		get => _longEmaPeriod.Value;
		set => _longEmaPeriod.Value = value;
	}

	public OverHedgeV2Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");

		_shortEmaPeriod = Param(nameof(ShortEmaPeriod), 8)
			.SetGreaterThanZero()
			.SetDisplay("Short EMA", "Fast EMA length", "Indicators");

		_longEmaPeriod = Param(nameof(LongEmaPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("Long EMA", "Slow EMA length", "Indicators");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevSignal = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevSignal = 0;

		var shortEma = new ExponentialMovingAverage { Length = ShortEmaPeriod };
		var longEma = new ExponentialMovingAverage { Length = LongEmaPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(shortEma, longEma, ProcessCandle)
			.Start();

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var signal = shortEma > longEma ? 1 : shortEma < longEma ? -1 : _prevSignal;

		if (signal == _prevSignal)
			return;

		var oldSignal = _prevSignal;
		_prevSignal = signal;

		if (signal == 1 && oldSignal <= 0)
		{
			if (Position < 0)
				BuyMarket();
			if (Position <= 0)
				BuyMarket();
		}
		else if (signal == -1 && oldSignal >= 0)
		{
			if (Position > 0)
				SellMarket();
			if (Position >= 0)
				SellMarket();
		}
	}
}