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
Subscribe to level one data to track the best bid and ask prices.
Capture the account equity the first time live data is received and store it as
the session baseline.
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.
Hold the pending orders without reissuing new ones when they are filled. The
grid is recreated only after a full reset.
Continuously monitor floating PnL:
If profit reaches ProfitTargetCurrency, close all exposure and reset.
If drawdown exceeds MaxDrawdownCurrency, flatten the book and reset.
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class pending_limit_grid_strategy(Strategy):
def __init__(self):
super(pending_limit_grid_strategy, self).__init__()
self._channel_period = self.Param("ChannelPeriod", 24).SetDisplay("Channel Period", "Grid channel lookback", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candle timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(pending_limit_grid_strategy, self).OnReseted()
self._has_prev = False
self._prev_close = 0
self._prev_mid = 0
def OnStarted2(self, time):
super(pending_limit_grid_strategy, self).OnStarted2(time)
self._has_prev = False
self._prev_close = 0
self._prev_mid = 0
highest = Highest()
highest.Length = self._channel_period.Value
lowest = Lowest()
lowest.Length = self._channel_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(highest, lowest, self.OnProcess).Start()
def OnProcess(self, candle, highest, lowest):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
mid = (highest + lowest) / 2.0
if not self._has_prev:
self._prev_close = close
self._prev_mid = mid
self._has_prev = True
return
if self._prev_close <= self._prev_mid and close > mid and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_close >= self._prev_mid and close < mid and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_close = close
self._prev_mid = mid
def CreateClone(self):
return pending_limit_grid_strategy()