Waddah Attar Win Strategy
This strategy mirrors the original Waddah Attar Win expert advisor. It continuously maintains a symmetric grid of buy- and sell-limit orders spaced at a fixed number of points from the current bid/ask. Whenever the market price approaches the last submitted order, the strategy stacks a new limit at the same distance with an optional volume increment. Floating profit is monitored on every order book update and all positions together with pending orders are closed once the configured profit target in account currency is reached.
Details
- Entry Criteria:
- Initial buy-limit placed
Step Pointsbelow the bid and sell-limit placed the same distance above the ask. - Additional pending orders are added when price comes within five price steps of the latest order on each side.
- Initial buy-limit placed
- Long/Short: Both, hedged grid.
- Exit Criteria:
- Close all positions and cancel orders once equity exceeds the stored balance by
Min Profit.
- Close all positions and cancel orders once equity exceeds the stored balance by
- Stops: None.
- Default Values:
Step Points= 20First Volume= 0.1Increment Volume= 0.0Min Profit= 910
- Notes:
- Works with hedging portfolios because long and short positions can coexist.
- Uses order book data to react immediately to bid/ask changes.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Grid strategy that places symmetric market orders around grid levels and takes profit when equity target is reached.
/// Based on Waddah Attar grid concept: define a grid step, enter in the direction of price movement
/// when price crosses a grid level, and close all when profit target is met.
/// </summary>
public class WaddahAttarWinStrategy : Strategy
{
private readonly StrategyParam<int> _stepPoints;
private readonly StrategyParam<decimal> _firstVolume;
private readonly StrategyParam<decimal> _incrementVolume;
private readonly StrategyParam<decimal> _minProfit;
private decimal _gridOrigin;
private int _lastGridIndex;
private decimal _currentVolume;
private decimal _entryPrice;
private bool _initialized;
private int _totalOrders;
/// <summary>
/// Distance in points between grid levels.
/// </summary>
public int StepPoints
{
get => _stepPoints.Value;
set => _stepPoints.Value = value;
}
/// <summary>
/// Initial volume for orders.
/// </summary>
public decimal FirstVolume
{
get => _firstVolume.Value;
set => _firstVolume.Value = value;
}
/// <summary>
/// Volume increment added on each new grid level entry.
/// </summary>
public decimal IncrementVolume
{
get => _incrementVolume.Value;
set => _incrementVolume.Value = value;
}
/// <summary>
/// Profit target in price points to close the position.
/// </summary>
public decimal MinProfit
{
get => _minProfit.Value;
set => _minProfit.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public WaddahAttarWinStrategy()
{
_stepPoints = Param(nameof(StepPoints), 500)
.SetGreaterThanZero()
.SetDisplay("Step (Points)", "Distance between grid levels in price steps", "General")
.SetOptimize(100, 2000, 100);
_firstVolume = Param(nameof(FirstVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("First Volume", "Volume for grid orders", "General");
_incrementVolume = Param(nameof(IncrementVolume), 0m)
.SetDisplay("Increment Volume", "Additional volume on subsequent grid entries", "General");
_minProfit = Param(nameof(MinProfit), 200m)
.SetNotNegative()
.SetDisplay("Min Profit", "Price movement profit target to close position", "Risk");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_gridOrigin = 0m;
_lastGridIndex = 0;
_currentVolume = 0m;
_entryPrice = 0m;
_initialized = false;
_totalOrders = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var tf = TimeSpan.FromMinutes(5).TimeFrame();
SubscribeCandles(tf)
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormed)
return;
// cap total orders to avoid exceeding limits
if (_totalOrders >= 80)
return;
var close = candle.ClosePrice;
var priceStep = Security?.PriceStep ?? 0.01m;
if (priceStep <= 0m)
priceStep = 0.01m;
var stepOffset = StepPoints * priceStep;
if (stepOffset <= 0m)
return;
// initialize grid origin on first candle
if (!_initialized)
{
_gridOrigin = close;
_lastGridIndex = 0;
_currentVolume = FirstVolume;
_initialized = true;
return;
}
// calculate which grid index the price is at
var gridIndex = (int)Math.Floor((close - _gridOrigin) / stepOffset);
// check profit target: close position if in profit
if (Position != 0 && _entryPrice > 0m)
{
var pnl = Position > 0
? close - _entryPrice
: _entryPrice - close;
if (pnl >= MinProfit * priceStep)
{
if (Position > 0)
{
SellMarket();
_totalOrders++;
}
else
{
BuyMarket();
_totalOrders++;
}
// reset grid around current price
_gridOrigin = close;
_lastGridIndex = 0;
_currentVolume = FirstVolume;
_entryPrice = 0m;
return;
}
}
// price crossed to a new grid level
if (gridIndex != _lastGridIndex)
{
if (gridIndex > _lastGridIndex)
{
// price moved up - buy (or add to long / close short)
if (Position < 0)
{
// close short first
BuyMarket();
_totalOrders++;
_entryPrice = close;
_gridOrigin = close;
_lastGridIndex = 0;
_currentVolume = FirstVolume;
}
else
{
BuyMarket();
_totalOrders++;
if (Position <= 0)
_entryPrice = close;
_currentVolume += IncrementVolume;
}
}
else
{
// price moved down - sell (or add to short / close long)
if (Position > 0)
{
// close long first
SellMarket();
_totalOrders++;
_entryPrice = close;
_gridOrigin = close;
_lastGridIndex = 0;
_currentVolume = FirstVolume;
}
else
{
SellMarket();
_totalOrders++;
if (Position >= 0)
_entryPrice = close;
_currentVolume += IncrementVolume;
}
}
_lastGridIndex = gridIndex;
}
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class waddah_attar_win_strategy(Strategy):
"""Grid strategy that places symmetric market orders around grid levels and takes profit when equity target is reached."""
def __init__(self):
super(waddah_attar_win_strategy, self).__init__()
self._step_points = self.Param("StepPoints", 500) \
.SetGreaterThanZero() \
.SetDisplay("Step (Points)", "Distance between grid levels in price steps", "General")
self._first_volume = self.Param("FirstVolume", 1) \
.SetGreaterThanZero() \
.SetDisplay("First Volume", "Volume for grid orders", "General")
self._increment_volume = self.Param("IncrementVolume", 0) \
.SetDisplay("Increment Volume", "Additional volume on subsequent grid entries", "General")
self._min_profit = self.Param("MinProfit", 200) \
.SetNotNegative() \
.SetDisplay("Min Profit", "Price movement profit target to close position", "Risk")
self._grid_origin = 0.0
self._last_grid_index = 0
self._current_volume = 0.0
self._entry_price = 0.0
self._initialized = False
self._total_orders = 0
@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
def OnStarted2(self, time):
super(waddah_attar_win_strategy, self).OnStarted2(time)
tf = DataType.TimeFrame(TimeSpan.FromMinutes(5))
self.SubscribeCandles(tf) \
.Bind(self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
if not self.IsFormed:
return
# cap total orders to avoid exceeding limits
if self._total_orders >= 80:
return
close = float(candle.ClosePrice)
sec = self.Security
price_step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 0.01
if price_step <= 0:
price_step = 0.01
step_offset = float(self.StepPoints) * price_step
if step_offset <= 0:
return
# initialize grid origin on first candle
if not self._initialized:
self._grid_origin = close
self._last_grid_index = 0
self._current_volume = float(self.FirstVolume)
self._initialized = True
return
# calculate which grid index the price is at
grid_index = int(math.floor((close - self._grid_origin) / step_offset))
# check profit target: close position if in profit
if self.Position != 0 and self._entry_price > 0:
if self.Position > 0:
pnl = close - self._entry_price
else:
pnl = self._entry_price - close
if pnl >= float(self.MinProfit) * price_step:
if self.Position > 0:
self.SellMarket()
self._total_orders += 1
else:
self.BuyMarket()
self._total_orders += 1
# reset grid around current price
self._grid_origin = close
self._last_grid_index = 0
self._current_volume = float(self.FirstVolume)
self._entry_price = 0.0
return
# price crossed to a new grid level
if grid_index != self._last_grid_index:
if grid_index > self._last_grid_index:
# price moved up - buy
if self.Position < 0:
# close short first
self.BuyMarket()
self._total_orders += 1
self._entry_price = close
self._grid_origin = close
self._last_grid_index = 0
self._current_volume = float(self.FirstVolume)
else:
self.BuyMarket()
self._total_orders += 1
if self.Position <= 0:
self._entry_price = close
self._current_volume = self._current_volume + float(self.IncrementVolume)
else:
# price moved down - sell
if self.Position > 0:
# close long first
self.SellMarket()
self._total_orders += 1
self._entry_price = close
self._grid_origin = close
self._last_grid_index = 0
self._current_volume = float(self.FirstVolume)
else:
self.SellMarket()
self._total_orders += 1
if self.Position >= 0:
self._entry_price = close
self._current_volume = self._current_volume + float(self.IncrementVolume)
self._last_grid_index = grid_index
def OnReseted(self):
super(waddah_attar_win_strategy, self).OnReseted()
self._grid_origin = 0.0
self._last_grid_index = 0
self._current_volume = 0.0
self._entry_price = 0.0
self._initialized = False
self._total_orders = 0
def CreateClone(self):
return waddah_attar_win_strategy()