View on GitHub

Pending Limit Grid Strategy (MQL/8147 Conversion)

Overview

The Pending Limit Grid Strategy reproduces the behaviour of the MetaTrader expert stored in MQL/8147. The strategy builds a symmetric grid of pending limit orders around the current bid/ask prices. It keeps the grid active while floating profit remains within a configured profit target and drawdown threshold. When one of the thresholds is breached, all orders are cancelled, open positions are flattened, and the grid is rebuilt using the new account equity as baseline.

Trading Logic

  1. Subscribe to level one data to track the best bid and ask prices.
  2. Capture the account equity the first time live data is received and store it as the session baseline.
  3. Place LevelsPerSide sell limits above the market and the same number of buy limits below the market. The distance between grid levels is controlled by GridStepPoints converted to the instrument price step.
  4. Hold the pending orders without reissuing new ones when they are filled. The grid is recreated only after a full reset.
  5. Continuously monitor floating PnL:
    • If profit reaches ProfitTargetCurrency, close all exposure and reset.
    • If drawdown exceeds MaxDrawdownCurrency, flatten the book and reset.
  6. After every reset the baseline equity is captured again and the grid is rebuilt using the most recent bid/ask snapshot.

Parameters

Parameter Description
ProfitTargetCurrency Net profit (in account currency) that triggers a full reset of the grid.
MaxDrawdownCurrency Maximum tolerated floating loss before all exposure is closed.
GridStepPoints Distance between consecutive grid levels expressed in broker points.
LevelsPerSide Number of pending orders created above and below the market.
OrderVolume Volume assigned to each pending limit order.

Risk Management

The strategy does not attach per-order stops or targets. Instead it supervises the aggregated profit and loss. The RequestFlatten helper cancels pending orders and uses market orders (via ClosePosition) to remove any open exposure. After the flattening completes, the grid state and baseline equity are reset before placing new orders.

Notes

  • Prices are normalised through Security.ShrinkPrice to respect the exchange price step.
  • The MetaTrader "Point" value is emulated by analysing the instrument PriceStep to match four- and five-digit quotes.
  • The strategy avoids re-sending grid orders once they are placed, mimicking the original expert that relied on flag variables to keep every level unique until a manual or automatic reset occurs.
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;
	}
}