The Waddah Attar Win Grid Strategy replicates the MetaTrader 4 expert advisor from the MQL/8210 script. It continuously maintains a symmetric ladder of buy- and sell-limit orders around the current bid/ask. When price drifts toward the most recent grid level the strategy automatically stacks a new pending order one step farther away, optionally increasing the volume of each additional order. Floating profit is monitored on every order book update and, once the configured equity gain is reached, all positions and working orders are closed simultaneously.
How it works
Initialization
Subscribes to order book updates in order to react instantly to bid/ask changes.
Records the current portfolio value to use as the baseline equity reference.
Whenever there are no active orders and the net position is flat, the latest portfolio value becomes the new reference balance. This mirrors the original expert advisor, which stored the current account balance on each tick.
Initial grid placement
As soon as trading is allowed and no orders are active, the strategy places two pending orders:
A buy-limit Step Points below the current ask price.
A sell-limit Step Points above the current bid price.
Both orders use the First Volume value.
Stacking new orders
When the ask price moves within five price steps of the latest buy-limit, the strategy places a new buy-limit one full step below the previous level.
When the bid price moves within five price steps of the latest sell-limit, the strategy places a new sell-limit one full step above the previous level.
Each new pending order increases the volume by Increment Volume, enabling martingale-style pyramiding if desired.
Profit capture
The floating profit is calculated as the difference between the current portfolio equity and the stored reference balance.
Once this profit exceeds Min Profit, every active order is cancelled and all open positions are flattened with a single CloseAll call.
The baseline equity is refreshed, allowing the grid to restart with a clean slate.
Strategy characteristics
Market data: operates purely on level-1 order book snapshots (best bid/ask).
Order types: uses only limit orders; no stops or market entries are generated automatically.
Exposure: can hold simultaneous long and short positions in hedging-enabled portfolios.
Risk control: lacks hard stop-losses; relies on the floating profit target and external risk rules.
Re-entry: after flattening or manual cancellation of orders, the initial grid is recreated automatically the next time the market data loop runs.
Parameters
Parameter
Default
Description
Step Points
120
Distance between consecutive grid levels, expressed in price points (price step multiples).
First Volume
0.1
Volume used for the very first pair of pending orders.
Increment Volume
0.0
Additional volume added to each newly stacked order; set to zero to keep all orders equal sized.
Min Profit
450
Floating profit (in account currency) required to close all open positions and pending orders.
Notes and limitations
Make sure the instrument's PriceStep is set correctly; the strategy multiplies Step Points by PriceStep to derive actual prices.
Because the algorithm cancels and replaces orders frequently, broker or exchange limits on pending order counts should be considered.
There is no built-in drawdown protection—consider combining the strategy with external risk management or portfolio-level stops.
The grid can expand indefinitely if price trends sharply without hitting the profit target; choose Increment Volume carefully to control margin usage.
Files
CS/WaddahAttarWinGridStrategy.cs — C# implementation of the trading logic.
README.md — this documentation (English).
README_ru.md — Russian translation with identical content.
README_zh.md — Chinese translation with identical content.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Grid strategy converted from the "Waddah Attar Win" MetaTrader 4 expert advisor.
/// Places paired orders around the market, pyramids positions with an optional volume increment,
/// and closes the entire exposure once the floating profit target is achieved.
/// </summary>
public class WaddahAttarWinGridStrategy : Strategy
{
private readonly StrategyParam<int> _stepPoints;
private readonly StrategyParam<decimal> _firstVolume;
private readonly StrategyParam<decimal> _incrementVolume;
private readonly StrategyParam<decimal> _minProfit;
private readonly StrategyParam<DataType> _candleType;
private decimal _lastBuyGridPrice;
private decimal _lastSellGridPrice;
private decimal _currentBuyVolume;
private decimal _currentSellVolume;
private decimal _referenceBalance;
private bool _gridActive;
/// <summary>
/// Distance in price points between consecutive grid levels.
/// </summary>
public int StepPoints
{
get => _stepPoints.Value;
set => _stepPoints.Value = value;
}
/// <summary>
/// Volume for the very first pair of orders.
/// </summary>
public decimal FirstVolume
{
get => _firstVolume.Value;
set => _firstVolume.Value = value;
}
/// <summary>
/// Volume increment applied to each newly stacked order.
/// </summary>
public decimal IncrementVolume
{
get => _incrementVolume.Value;
set => _incrementVolume.Value = value;
}
/// <summary>
/// Floating profit target in account currency that closes all positions.
/// </summary>
public decimal MinProfit
{
get => _minProfit.Value;
set => _minProfit.Value = value;
}
/// <summary>
/// Candle type for price data.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public WaddahAttarWinGridStrategy()
{
_stepPoints = Param(nameof(StepPoints), 1500)
.SetGreaterThanZero()
.SetDisplay("Step (Points)", "Distance between grid levels in points", "Grid")
.SetOptimize(20, 400, 10);
_firstVolume = Param(nameof(FirstVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("First Volume", "Volume for the initial orders", "Trading");
_incrementVolume = Param(nameof(IncrementVolume), 0m)
.SetDisplay("Increment Volume", "Additional volume added when stacking new orders", "Trading");
_minProfit = Param(nameof(MinProfit), 450m)
.SetNotNegative()
.SetDisplay("Min Profit", "Floating profit target in account currency", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle type for price data", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_lastBuyGridPrice = 0m;
_lastSellGridPrice = 0m;
_currentBuyVolume = 0m;
_currentSellVolume = 0m;
_referenceBalance = 0m;
_gridActive = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_referenceBalance = Portfolio?.CurrentValue ?? 0m;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var priceStep = Security?.PriceStep ?? 0.01m;
if (priceStep <= 0m)
priceStep = 0.01m;
var stepOffset = StepPoints * priceStep;
if (stepOffset <= 0m)
return;
var price = candle.ClosePrice;
// Check profit target
var floatingProfit = (Portfolio?.CurrentValue ?? 0m) - _referenceBalance;
if (MinProfit > 0m && floatingProfit >= MinProfit && _gridActive)
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(Math.Abs(Position));
_referenceBalance = Portfolio?.CurrentValue ?? _referenceBalance;
_gridActive = false;
_lastBuyGridPrice = 0m;
_lastSellGridPrice = 0m;
_currentBuyVolume = 0m;
_currentSellVolume = 0m;
return;
}
// Initialize grid on first candle
if (!_gridActive)
{
_lastBuyGridPrice = price;
_lastSellGridPrice = price;
_currentBuyVolume = FirstVolume;
_currentSellVolume = FirstVolume;
_gridActive = true;
_referenceBalance = Portfolio?.CurrentValue ?? _referenceBalance;
return;
}
// Check if price dropped enough to trigger a buy grid level
if (price <= _lastBuyGridPrice - stepOffset)
{
BuyMarket(_currentBuyVolume);
_lastBuyGridPrice = price;
_currentBuyVolume += IncrementVolume;
}
// Check if price rose enough to trigger a sell grid level
if (price >= _lastSellGridPrice + stepOffset)
{
SellMarket(_currentSellVolume);
_lastSellGridPrice = price;
_currentSellVolume += IncrementVolume;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class waddah_attar_win_grid_strategy(Strategy):
def __init__(self):
super(waddah_attar_win_grid_strategy, self).__init__()
self._step_points = self.Param("StepPoints", 1500).SetDisplay("Step (Points)", "Distance between grid levels in points", "Grid")
self._first_volume = self.Param("FirstVolume", 0.1).SetDisplay("First Volume", "Volume for the initial orders", "Trading")
self._increment_volume = self.Param("IncrementVolume", 0.0).SetDisplay("Increment Volume", "Additional volume added when stacking new orders", "Trading")
self._min_profit = self.Param("MinProfit", 450.0).SetDisplay("Min Profit", "Floating profit target in account currency", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candle type for price data", "General")
self._last_buy_grid_price = 0.0
self._last_sell_grid_price = 0.0
self._current_buy_volume = 0.0
self._current_sell_volume = 0.0
self._reference_balance = 0.0
self._grid_active = False
@property
def StepPoints(self): return self._step_points.Value
@property
def FirstVolume(self): return self._first_volume.Value
@property
def IncrementVolume(self): return self._increment_volume.Value
@property
def MinProfit(self): return self._min_profit.Value
@property
def CandleType(self): return self._candle_type.Value
def OnStarted2(self, time):
super(waddah_attar_win_grid_strategy, self).OnStarted2(time)
self._reference_balance = float(self.Portfolio.CurrentValue) if self.Portfolio is not None else 0.0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
price_step = 0.01
if self.Security is not None and self.Security.PriceStep is not None and float(self.Security.PriceStep) > 0:
price_step = float(self.Security.PriceStep)
step_offset = int(self.StepPoints) * price_step
if step_offset <= 0:
return
price = float(candle.ClosePrice)
current_value = float(self.Portfolio.CurrentValue) if self.Portfolio is not None else 0.0
floating_profit = current_value - self._reference_balance
if float(self.MinProfit) > 0 and floating_profit >= float(self.MinProfit) and self._grid_active:
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(abs(self.Position))
self._reference_balance = float(self.Portfolio.CurrentValue) if self.Portfolio is not None else self._reference_balance
self._grid_active = False
self._last_buy_grid_price = 0.0
self._last_sell_grid_price = 0.0
self._current_buy_volume = 0.0
self._current_sell_volume = 0.0
return
if not self._grid_active:
self._last_buy_grid_price = price
self._last_sell_grid_price = price
self._current_buy_volume = float(self.FirstVolume)
self._current_sell_volume = float(self.FirstVolume)
self._grid_active = True
self._reference_balance = float(self.Portfolio.CurrentValue) if self.Portfolio is not None else self._reference_balance
return
if price <= self._last_buy_grid_price - step_offset:
self.BuyMarket(self._current_buy_volume)
self._last_buy_grid_price = price
self._current_buy_volume += float(self.IncrementVolume)
if price >= self._last_sell_grid_price + step_offset:
self.SellMarket(self._current_sell_volume)
self._last_sell_grid_price = price
self._current_sell_volume += float(self.IncrementVolume)
def OnReseted(self):
super(waddah_attar_win_grid_strategy, self).OnReseted()
self._last_buy_grid_price = 0.0
self._last_sell_grid_price = 0.0
self._current_buy_volume = 0.0
self._current_sell_volume = 0.0
self._reference_balance = 0.0
self._grid_active = False
def CreateClone(self):
return waddah_attar_win_grid_strategy()