GitHub で見る

Waddah Attar Win Grid Strategy

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

  1. 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.
    • Starts StockSharp's built-in risk protection subsystem.
  2. Baseline management
    • 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.
  3. 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.
  4. 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.
  5. 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;
		}
	}
}