Открыть на GitHub

Стратегия Pending Limit Grid (конвертация MQL/8147)

Общее описание

Pending Limit Grid повторяет логику советника из каталога MQL/8147. Стратегия строит симметричную сетку лимитных заявок вокруг текущих котировок Bid/Ask и поддерживает её, пока плавающая прибыль находится между целевым значением и максимально допустимой просадкой. При достижении одного из порогов все заявки снимаются, позиции закрываются рыночными ордерами, после чего сетка создаётся заново на базе обновлённой стоимости портфеля.

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

  1. Подписка на поток Level1 для отслеживания лучших цен покупки и продажи.
  2. Фиксация текущей стоимости портфеля при получении первых данных и использование её как базового уровня для расчёта прибыли/убытка.
  3. Размещение LevelsPerSide заявок Sell Limit выше рынка и такого же числа заявок Buy Limit ниже рынка. Расстояние между уровнями определяется параметром GridStepPoints, который конвертируется в размер шага цены инструмента.
  4. Заявки не перевыставляются повторно после исполнения. Новая сетка строится только после полного сброса.
  5. Постоянный контроль плавающей прибыли:
    • При достижении ProfitTargetCurrency все позиции закрываются и сетка пересоздаётся.
    • При просадке ниже MaxDrawdownCurrency выполняется принудительное закрытие и последующий сброс.
  6. После сброса заново фиксируется базовая стоимость портфеля и сетка восстанавливается по актуальным котировкам Bid/Ask.

Параметры

Параметр Описание
ProfitTargetCurrency Прибыль в валюте счёта, при достижении которой выполняется полный сброс.
MaxDrawdownCurrency Допустимая плавающая просадка перед принудительным закрытием позиций.
GridStepPoints Расстояние между уровнями сетки в пунктах брокера.
LevelsPerSide Количество лимитных заявок, выставляемых выше и ниже рынка.
OrderVolume Объём каждой лимитной заявки.

Управление рисками

Отдельные заявки не снабжаются стопами или тейк-профитами. Вместо этого контролируется суммарный результат. Метод RequestFlatten снимает все ожидающие заявки и вызывает ClosePosition, чтобы закрыть остаточные позиции рыночными ордерами. После завершения закрытия состояние сетки и базовая стоимость портфеля сбрасываются, позволяя построить новую сетку.

Дополнительные замечания

  • Цены нормализуются с помощью Security.ShrinkPrice, чтобы учитывать шаг цены торгуемого инструмента.
  • Значение MetaTrader Point рассчитывается на основе PriceStep, что обеспечивает корректность для четырёх- и пятизначных котировок.
  • Стратегия не повторно выставляет уровни сетки в пределах одного цикла, полностью копируя поведение исходного советника на MQL.
using System;

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

namespace StockSharp.Samples.Strategies;

public class PendingLimitGridStrategy : Strategy
{
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cooldownBars;

	private decimal _prevClose;
	private decimal _prevMid;
	private bool _hasPrev;
	private int _barsSinceLastTrade;

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

	public PendingLimitGridStrategy()
	{
		_channelPeriod = Param(nameof(ChannelPeriod), 24).SetDisplay("Channel Period", "Grid channel lookback", "Indicators");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
		_cooldownBars = Param(nameof(CooldownBars), 200).SetDisplay("Cooldown Bars", "Minimum bars between trades", "Risk");
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_prevClose = 0;
		_prevMid = 0;
		_hasPrev = false;
		_barsSinceLastTrade = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_hasPrev = false;
		_barsSinceLastTrade = 0;
		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 highest, decimal lowest)
	{
		if (candle.State != CandleStates.Finished) return;

		var close = candle.ClosePrice;
		var mid = (highest + lowest) / 2;

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

		_barsSinceLastTrade++;

		if (_barsSinceLastTrade >= CooldownBars)
		{
			if (_prevClose <= _prevMid && close > mid && Position <= 0)
			{
				if (Position < 0) BuyMarket();
				BuyMarket();
				_barsSinceLastTrade = 0;
			}
			else if (_prevClose >= _prevMid && close < mid && Position >= 0)
			{
				if (Position > 0) SellMarket();
				SellMarket();
				_barsSinceLastTrade = 0;
			}
		}

		_prevClose = close;
		_prevMid = mid;
	}
}