Открыть на GitHub

Стратегия Tunnel Gen4 Hedged Grid

Стратегия повторяет логику советника MetaTrader "Tunnel gen4" с использованием высокоуровневого API StockSharp. Она поддерживает нейтральный хедж, открывая стартовый набор из покупок и продаж, удваивает объём в направлении прорыва после прохождения ценой заданного числа пунктов и закрывает весь портфель, когда такая же дистанция пройдена за пределы второй опорной точки.

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

  • Начальный хедж: Когда активных позиций нет, стратегия одновременно отправляет рыночные заявки Buy и Sell объёмом StartVolume. Первая сделка определяет опорную цену, относительно которой измеряются все шаги.
  • Контроль шага: Параметр StepPips переводится в ценовое смещение с учётом минимального шага цены инструмента и поправки для трёх- и пятизнаковых валютных котировок. Обновления лучшей цены bid/ask из Level 1 сравниваются с этим смещением.
  • Усиление позиции: Если лучшая цена bid выросла минимум на один шаг от первой сделки, отправляется рыночная заявка Sell объёмом в два раза больше базового. Если лучшая цена ask упала на один шаг, то отправляется Buy такого же размера. Первая сделка по этой заявке фиксирует вторую опору.
  • Завершение цикла: После появления второй опоры любое дальнейшее смещение цены на величину шага в любую сторону запускает полное закрытие всех позиций. После закрытия обеих сторон состояние сбрасывается, и стратегия готова открыть новый хедж.
  • Проверка объёмов: При запуске стратегия удостоверяется, что стартовый и удвоенный объёмы соответствуют ограничениям инструмента по минимуму, максимуму и шагу, чтобы каждый ордер мог быть исполнен коннектором.

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

Усиление покупки

  • Существует хотя бы одна позиция из начального хеджа.
  • Вторая опора ещё не создана.
  • Текущая лучшая цена ask меньше либо равна первая_сделка - StepPips_в_цене.

Усиление продажи

  • Существует хотя бы одна позиция из начального хеджа.
  • Вторая опора ещё не создана.
  • Текущая лучшая цена bid больше либо равна первая_сделка + StepPips_в_цене.

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

  • Закрытие корзины: После появления второй опоры, если лучшая цена bid превышает вторая_опора + StepOffset или лучшая цена ask опускается ниже вторая_опора - StepOffset, стратегия отправляет рыночные заявки для закрытия суммарного длинного и короткого объёмов. Закрывающие заявки отслеживаются до полного исполнения, чтобы сброс состоялся только после подтверждённых сделок.
  • Сброс состояния: Когда обе стороны закрыты и активных заявок на выход нет, внутренние опоры очищаются и стратегия ожидает возможности открыть новый хедж.

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

  • Подписка Level 1 предоставляет лучшие цены bid и ask, по которым оцениваются шаги.
  • Дополнительные индикаторы не используются; логика полностью базируется на потоке котировок.
  • Конвертация шага цены повторяет поправку MetaTrader из point в pip, поэтому трёх- и пятизнаковые валютные пары ведут себя так же, как в исходном советнике.

Параметры

Параметр Описание
StartVolume Объём заявок Buy и Sell, формирующих первоначальный хедж.
StepPips Дистанция в пунктах, которая запускает усиление и последующее закрытие корзины.

Особенности реализации

  • StockSharp ведёт сводную позицию по инструменту. Стратегия хранит внутренние счётчики длинного и короткого объёмов, чтобы имитировать отдельные тики MetaTrader, и при закрытии корзины отправляет рыночные заявки на суммарные объёмы.
  • Для корректной работы необходимы актуальные спреды, поэтому и в тестах, и в реальной торговле нужно предоставлять поток Level 1. При отсутствии bid/ask торговый цикл приостанавливается.
  • Убедитесь, что торговый счёт поддерживает одновременные покупки и продажи одного инструмента, так как алгоритм предполагает сосуществование обеих сторон до наступления условия выхода.
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>
/// Tunnel strategy that uses Bollinger Bands to define a price channel.
/// Buys when price crosses above the lower band (reversal from oversold),
/// sells when price crosses below the upper band (reversal from overbought).
/// </summary>
public class TunnelGen4Strategy : Strategy
{
	private readonly StrategyParam<int> _bbLength;
	private readonly StrategyParam<decimal> _bbWidth;
	private readonly StrategyParam<decimal> _stepPips;

	private BollingerBands _bb;

	private decimal _prevClose;
	private decimal _prevUpper;
	private decimal _prevLower;
	private decimal _entryPrice;

	/// <summary>
	/// Bollinger Bands period length.
	/// </summary>
	public int BbLength
	{
		get => _bbLength.Value;
		set => _bbLength.Value = value;
	}

	/// <summary>
	/// Bollinger Bands width (standard deviations).
	/// </summary>
	public decimal BbWidth
	{
		get => _bbWidth.Value;
		set => _bbWidth.Value = value;
	}

	/// <summary>
	/// Step distance expressed in pips for profit target.
	/// </summary>
	public decimal StepPips
	{
		get => _stepPips.Value;
		set => _stepPips.Value = value;
	}

	/// <summary>
	/// Initialize strategy parameters.
	/// </summary>
	public TunnelGen4Strategy()
	{
		_bbLength = Param(nameof(BbLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("BB Length", "Bollinger Bands period", "Indicator");

		_bbWidth = Param(nameof(BbWidth), 2.0m)
			.SetGreaterThanZero()
			.SetDisplay("BB Width", "Bollinger Bands width", "Indicator");

		_stepPips = Param(nameof(StepPips), 50m)
			.SetGreaterThanZero()
			.SetDisplay("Step (pips)", "Distance between tunnel anchors", "Trading");
	}

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

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

		_bb = null;
		_prevClose = 0;
		_prevUpper = 0;
		_prevLower = 0;
		_entryPrice = 0;
	}

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

		_bb = new BollingerBands
		{
			Length = BbLength,
			Width = BbWidth
		};

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.BindEx(_bb, OnProcess);
		subscription.Start();
	}

	private void OnProcess(ICandleMessage candle, IIndicatorValue value)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var bb = (BollingerBandsValue)value;
		if (bb.UpBand is not decimal upper ||
			bb.LowBand is not decimal lower)
			return;

		if (!_bb.IsFormed)
		{
			_prevClose = candle.ClosePrice;
			_prevUpper = upper;
			_prevLower = lower;
			return;
		}

		var close = candle.ClosePrice;

		// Buy signal: price crosses above lower band from below
		if (_prevClose < _prevLower && close >= lower && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
		}
		// Sell signal: price crosses below upper band from above
		else if (_prevClose > _prevUpper && close <= upper && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
		}

		// Exit on profit target if in position
		if (Position > 0 && _entryPrice > 0)
		{
			var pipValue = Security?.PriceStep ?? 1m;
			var target = _entryPrice + StepPips * pipValue;
			if (close >= target)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			var pipValue = Security?.PriceStep ?? 1m;
			var target = _entryPrice - StepPips * pipValue;
			if (close <= target)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		_prevClose = close;
		_prevUpper = upper;
		_prevLower = lower;
	}
}