Открыть на GitHub

Стратегия L3H3 Pivot

Обзор

L3H3 Pivot Strategy — это перенос советника MetaTrader "L3_H3_Expert" на платформу StockSharp. Изначальный код рассчитывает дневные уровни пивота и размещает две отложенные заявки возле границ диапазона предыдущей сессии, чтобы отработать пробой или откат. Версия на StockSharp сохраняет ту же концепцию: после закрытия каждой свечи повышенного таймфрейма (по умолчанию — дневной) заново вычисляются уровни пивота, а затем стратегия решает, ставить ли стоп или лимит заявку в зависимости от текущего положения цены относительно вчерашнего диапазона.

Торговая логика

  1. Статистика сессии

    • После закрытия свечи PivotCandleType фиксируются цены открытия, максимума, минимума и закрытия предыдущего периода.
    • Пивот рассчитывается по формуле (High + Low + Close) / 3 и действует в течение следующей торговой сессии.
  2. Подготовка входа

    • Цена покупки задаётся чуть выше вчерашнего минимума. Сдвиг равен параметру EntryOffsetPips, выраженному в размерах тика.
    • Цена продажи совпадает со вчерашним максимумом — как и в оригинальном советнике, дополнительный буфер не используется.
    • При наступлении нового торгового дня (согласно основной серии свечей EntryCandleType) выставляются свежие отложенные заявки:
      • Если текущая цена ниже вчерашнего минимума, ставится buy-stop для торговли пробоя вверх.
      • Если текущая цена выше вчерашнего максимума, ставится sell-stop для игры на разворот вниз.
      • В остальных случаях выбираются лимитные заявки на тех же уровнях, чтобы купить на откате или продать на росте.
    • Стоп-лосс отстоит от базового минимума/максимума на StopLossPips, повторяя фиксированное смещение 16 пунктов из MT4.
    • Тейк-профит обеих заявок выставляется на уровне пивота.
  3. Управление заявками

    • При каждом пересчёте пивота существующие отложенные заявки отменяются и пересоздаются с новыми параметрами.
    • При переходе к новой сессии также выполняется отмена устаревших заявок, чтобы не накапливать лишние ордера.
    • После исполнения заявки соответствующая ссылка очищается, что предотвращает повторные попытки отмены.

Параметры

Имя Описание Значение по умолчанию
EntryCandleType Серия свечей, по которой отслеживается текущая сессия и запускается размещение заявок. 5-минутные свечи
PivotCandleType Свечи повышенного таймфрейма для расчёта статистики предыдущей сессии. Дневные свечи
EntryOffsetPips Смещение точки входа в покупку относительно вчерашнего минимума (в пунктах). 2
StopLossPips Расстояние стоп-лосса от контрольного минимума/максимума (в пунктах). 16

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

  • Советник MT4 переключался между азиатской, лондонской и нью-йоркской сессиями через magic number и набор временных окон. StockSharp-реализация упрощает схему, используя настраиваемый таймфрейм для расчёта пивота (по умолчанию — дневной), что облегчает адаптацию под разных брокеров.
  • В MetaTrader решение о типе отложенной заявки принималось по текущим котировкам Bid/Ask. В портированной версии используется последняя завершённая свеча серии EntryCandleType, что делает логику событийно-ориентированной и повторяемой.
  • Комментарии к ордерам и magic number были специфичны для MT4, поэтому в StockSharp они не применяются: стратегия хранит прямые ссылки на свои ордера и управляет ими программно.

Рекомендации по применению

  • Проверьте, что у инструмента задан корректный PriceStep. При его отсутствии стратегия завершит запуск с исключением.
  • Для точного воспроизведения оригинальных торговых окон можно настроить PivotCandleType на часовые свечи и агрегировать их в нужные временные диапазоны, а также скорректировать параметры смещения и стоп-лосса.
  • При работе на реальном счёте учитывайте требования брокера к минимальному расстоянию и сроку действия отложенных заявок.
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>
/// L3/H3 Pivot strategy - trades around Highest/Lowest channel midpoint.
/// Buys when close crosses above the midpoint, sells when below.
/// Uses a longer lookback to simulate daily pivot levels.
/// </summary>
public class L3H3PivotStrategy : Strategy
{
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevClose;
	private decimal _prevMid;
	private bool _hasPrev;

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

	public L3H3PivotStrategy()
	{
		_channelPeriod = Param(nameof(ChannelPeriod), 48)
			.SetDisplay("Channel Period", "Lookback for pivot channel", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevClose = 0m;
		_prevMid = 0m;
		_hasPrev = false;
	}

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

		_hasPrev = false;

		var highest = new Highest { Length = ChannelPeriod };
		var lowest = new Lowest { Length = ChannelPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(highest, lowest, ProcessCandle)
			.Start();
	}

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

		var close = candle.ClosePrice;
		var mid = (high + low) / 2m;

		if (!_hasPrev)
		{
			_prevClose = close;
			_prevMid = mid;
			_hasPrev = true;
			return;
		}

		// Cross above midpoint
		if (_prevClose <= _prevMid && close > mid && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Cross below midpoint
		else if (_prevClose >= _prevMid && close < mid && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevClose = close;
		_prevMid = mid;
	}
}