Waddah Attar Win Grid Strategy повторяет эксперта MetaTrader 4 из файла MQL/8210. Стратегия постоянно поддерживает симметричную сетку из отложенных заявок buy limit и sell limit вокруг текущих цен bid/ask. Когда цена подходит к последнему уровню сетки, автоматически добавляется новая заявка на расстоянии одного шага, при желании с увеличением объёма. Плавающая прибыль контролируется при каждом обновлении стакана, и как только достигается заданный прирост капитала, все позиции и активные заявки закрываются одновременно.
Как работает алгоритм
Инициализация
Подписка на обновления стакана, чтобы мгновенно реагировать на изменения bid/ask.
Запоминание текущей стоимости портфеля как базового уровня капитала.
Активация встроенной подсистемы защиты StockSharp.
Управление базовым балансом
Когда нет активных заявок и позиция равна нулю, последняя стоимость портфеля становится новой опорной величиной. Это повторяет подход оригинального советника, который на каждом тике сохранял баланс счёта.
Первое построение сетки
При разрешённой торговле и отсутствии активных заявок выставляются две отложенные заявки:
Buy limit на расстоянии Step Points ниже текущей цены ask.
Sell limit на расстоянии Step Points выше текущей цены bid.
Обе заявки используют объём First Volume.
Наращивание сетки
Когда цена ask подходит к последнему buy limit ближе, чем на пять минимальных шагов цены, ставится новая buy limit ещё на один шаг ниже предыдущей.
Когда цена bid подходит к последнему sell limit ближе, чем на пять минимальных шагов цены, ставится новая sell limit ещё на один шаг выше предыдущей.
Каждый новый уровень увеличивает объём на величину Increment Volume, что позволяет построить пирамиду ордеров.
Фиксация прибыли
Плавающая прибыль вычисляется как разница между текущей стоимостью портфеля и сохранённым базовым балансом.
После превышения 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;
}
}
}
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()