Открыть на GitHub

Waddah Attar Win Grid Strategy

Waddah Attar Win Grid Strategy повторяет эксперта MetaTrader 4 из файла MQL/8210. Стратегия постоянно поддерживает симметричную сетку из отложенных заявок buy limit и sell limit вокруг текущих цен bid/ask. Когда цена подходит к последнему уровню сетки, автоматически добавляется новая заявка на расстоянии одного шага, при желании с увеличением объёма. Плавающая прибыль контролируется при каждом обновлении стакана, и как только достигается заданный прирост капитала, все позиции и активные заявки закрываются одновременно.

Как работает алгоритм

  1. Инициализация
    • Подписка на обновления стакана, чтобы мгновенно реагировать на изменения bid/ask.
    • Запоминание текущей стоимости портфеля как базового уровня капитала.
    • Активация встроенной подсистемы защиты StockSharp.
  2. Управление базовым балансом
    • Когда нет активных заявок и позиция равна нулю, последняя стоимость портфеля становится новой опорной величиной. Это повторяет подход оригинального советника, который на каждом тике сохранял баланс счёта.
  3. Первое построение сетки
    • При разрешённой торговле и отсутствии активных заявок выставляются две отложенные заявки:
      • Buy limit на расстоянии Step Points ниже текущей цены ask.
      • Sell limit на расстоянии Step Points выше текущей цены bid.
    • Обе заявки используют объём First Volume.
  4. Наращивание сетки
    • Когда цена ask подходит к последнему buy limit ближе, чем на пять минимальных шагов цены, ставится новая buy limit ещё на один шаг ниже предыдущей.
    • Когда цена bid подходит к последнему sell limit ближе, чем на пять минимальных шагов цены, ставится новая sell limit ещё на один шаг выше предыдущей.
    • Каждый новый уровень увеличивает объём на величину Increment Volume, что позволяет построить пирамиду ордеров.
  5. Фиксация прибыли
    • Плавающая прибыль вычисляется как разница между текущей стоимостью портфеля и сохранённым базовым балансом.
    • После превышения Min Profit отменяются все активные заявки и выполняется принудительное закрытие позиций через CloseAll.
    • Опорный баланс обновляется, позволяя стратегии начать цикл заново.

Особенности

  • Рыночные данные: используется только лучший bid/ask из стакана (уровень 1).
  • Типы заявок: формируются только лимитные ордера; рыночные входы не генерируются автоматически.
  • Позиционирование: допускается одновременное наличие длинных и коротких позиций в хеджинговых портфелях.
  • Управление риском: отсутствуют жёсткие стоп-лоссы; контроль ведётся через целевую плавающую прибыль и внешние ограничения.
  • Повторный вход: после закрытия позиций или ручной отмены заявок сетка автоматически строится заново при следующем проходе цикла.

Параметры

Параметр Значение по умолчанию Описание
Step Points 120 Расстояние между соседними уровнями сетки в пунктах (в шагах цены инструмента).
First Volume 0.1 Объём первой пары отложенных заявок.
Increment Volume 0.0 Увеличение объёма для каждого нового уровня; оставьте 0, чтобы объёмы были одинаковыми.
Min Profit 450 Плавающая прибыль (в валюте счёта), при достижении которой стратегия закрывает все позиции и заявки.

Замечания и ограничения

  • Убедитесь, что PriceStep инструмента настроен корректно: фактические цены вычисляются как произведение Step Points на шаг цены.
  • Частые отмены и перерегистрации заявок могут столкнуться с ограничениями брокера или биржи по количеству отложенных ордеров.
  • Алгоритм не защищён от глубокой просадки — рекомендуется подключать внешнее управление рисками.
  • При сильном тренде сетка может расширяться без остановки; выбирайте Increment Volume с учётом доступной маржи.

Файлы

  • CS/WaddahAttarWinGridStrategy.cs — реализация стратегии на C#.
  • README.md — документация на английском языке.
  • README_ru.md — данный файл с описанием на русском языке.
  • README_zh.md — перевод описания на китайский язык.
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;
		}
	}
}