Ilan 1.6 Dynamic Grid Strategy
The Ilan 1.6 Dynamic strategy is a classic grid and martingale expert advisor. It opens an initial trade in a selected direction and places additional orders every time price moves against the position by a fixed step. Volume of new orders grows geometrically by a lot exponent. All positions in the basket are closed when price returns to the average entry price plus a take-profit distance. A trailing stop can optionally protect profits if price moves far enough in the favorable direction.
The algorithm relies only on price movement and does not use indicators. Because position size increases after each adverse move, the system carries high risk but can capture quick reversals.
Details
- Entry
- First order is opened in the configured direction.
- Additional orders are added every
PipStep points against the current position, up to MaxTrades.
- Each new order volume =
InitialVolume * LotExponent^N.
- Exit
- Close all when price touches
AveragePrice ± TakeProfit.
- Optional trailing stop starts after
TrailStart points of profit and follows price at TrailStop distance.
- Position management
- Only long or only short series at a time.
- After closing the basket the strategy restarts from the initial direction.
- Parameters
InitialVolume – volume of the first order (default 1).
LotExponent – multiplier for subsequent order size (default 1.6).
PipStep – distance in points between grid levels (default 30).
TakeProfit – profit target from average price in points (default 10).
MaxTrades – maximum number of active orders (default 10).
StartLong – open first trade as long if true (default true).
UseTrailingStop – enable trailing stop (default false).
TrailStart – profit in points to start trailing (default 10).
TrailStop – trailing distance in points (default 10).
CandleType – timeframe of candles (default 1 minute).
- Filters
- Category: Grid
- Direction: Both
- Indicators: None
- Stops: Optional
- Complexity: Medium
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: High
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 averaging strategy based on the Ilan 1.6 Dynamic expert advisor.
/// Adds positions when price moves against the current one and closes the
/// whole basket on a take profit.
/// Each grid level trades 1 unit; closing flattens via multiple market orders.
/// </summary>
public class Ilan16DynamicStrategy : Strategy
{
private readonly StrategyParam<decimal> _pipStep;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<int> _maxTrades;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<bool> _startLong;
private int _tradeCount;
private decimal _lastEntryPrice;
private decimal _avgPrice;
private bool _isLong;
/// <summary>
/// Distance in price steps between grid levels.
/// </summary>
public decimal PipStep { get => _pipStep.Value; set => _pipStep.Value = value; }
/// <summary>
/// Profit target from average price in price steps.
/// </summary>
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
/// <summary>
/// Maximum number of averaging entries.
/// </summary>
public int MaxTrades { get => _maxTrades.Value; set => _maxTrades.Value = value; }
/// <summary>
/// Type of candles to process.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Open first trade as long if true.
/// </summary>
public bool StartLong { get => _startLong.Value; set => _startLong.Value = value; }
/// <summary>
/// Constructor.
/// </summary>
public Ilan16DynamicStrategy()
{
_pipStep = Param(nameof(PipStep), 50000m)
.SetGreaterThanZero()
.SetDisplay("Pip Step", "Distance in price steps between grid levels", "Trading")
.SetOptimize(10000m, 100000m, 10000m);
_takeProfit = Param(nameof(TakeProfit), 30000m)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Profit target from average price in price steps", "Trading")
.SetOptimize(10000m, 100000m, 10000m);
_maxTrades = Param(nameof(MaxTrades), 3)
.SetGreaterThanZero()
.SetDisplay("Max Trades", "Maximum number of averaging entries", "Trading")
.SetOptimize(2, 10, 1);
_startLong = Param(nameof(StartLong), true)
.SetDisplay("Start Long", "Open first trade as long", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_isLong = StartLong;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var step = Security.PriceStep ?? 1m;
var price = candle.ClosePrice;
// No position - open initial entry
if (Position == 0)
{
if (_isLong)
BuyMarket();
else
SellMarket();
_tradeCount = 1;
_lastEntryPrice = price;
_avgPrice = price;
return;
}
// Check take profit: close entire basket
if (_isLong && price >= _avgPrice + TakeProfit * step)
{
CloseAll();
return;
}
else if (!_isLong && price <= _avgPrice - TakeProfit * step)
{
CloseAll();
return;
}
// Check for grid averaging entry (price moved against us)
if (_isLong && _tradeCount < MaxTrades && _lastEntryPrice - price >= PipStep * step)
{
BuyMarket();
_tradeCount++;
_avgPrice = (_avgPrice * (_tradeCount - 1) + price) / _tradeCount;
_lastEntryPrice = price;
}
else if (!_isLong && _tradeCount < MaxTrades && price - _lastEntryPrice >= PipStep * step)
{
SellMarket();
_tradeCount++;
_avgPrice = (_avgPrice * (_tradeCount - 1) + price) / _tradeCount;
_lastEntryPrice = price;
}
}
private void CloseAll()
{
var pos = Position;
if (pos > 0)
{
// Close long: sell abs(pos) times
for (var i = 0; i < (int)Math.Abs(pos); i++)
SellMarket();
}
else if (pos < 0)
{
// Close short: buy abs(pos) times
for (var i = 0; i < (int)Math.Abs(pos); i++)
BuyMarket();
}
ResetState();
}
private void ResetState()
{
_tradeCount = 0;
_lastEntryPrice = 0m;
_avgPrice = 0m;
_isLong = StartLong;
}
}
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 ilan_16_dynamic_strategy(Strategy):
def __init__(self):
super(ilan_16_dynamic_strategy, self).__init__()
self._pip_step = self.Param("PipStep", 50000.0) \
.SetGreaterThanZero() \
.SetDisplay("Pip Step", "Distance in price steps between grid levels", "Trading") \
.SetOptimize(10000.0, 100000.0, 10000.0)
self._take_profit = self.Param("TakeProfit", 30000.0) \
.SetGreaterThanZero() \
.SetDisplay("Take Profit", "Profit target from average price in price steps", "Trading") \
.SetOptimize(10000.0, 100000.0, 10000.0)
self._max_trades = self.Param("MaxTrades", 3) \
.SetGreaterThanZero() \
.SetDisplay("Max Trades", "Maximum number of averaging entries", "Trading") \
.SetOptimize(2, 10, 1)
self._start_long = self.Param("StartLong", True) \
.SetDisplay("Start Long", "Open first trade as long", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._trade_count = 0
self._last_entry_price = 0.0
self._avg_price = 0.0
self._is_long = True
@property
def pip_step(self):
return self._pip_step.Value
@property
def take_profit(self):
return self._take_profit.Value
@property
def max_trades(self):
return self._max_trades.Value
@property
def start_long(self):
return self._start_long.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ilan_16_dynamic_strategy, self).OnReseted()
self._reset_state()
def _reset_state(self):
self._trade_count = 0
self._last_entry_price = 0.0
self._avg_price = 0.0
self._is_long = self.start_long
def OnStarted2(self, time):
super(ilan_16_dynamic_strategy, self).OnStarted2(time)
self._is_long = self.start_long
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
step = self.Security.PriceStep if self.Security.PriceStep is not None else 1.0
step = float(step)
price = float(candle.ClosePrice)
# No position - open initial entry
if self.Position == 0:
if self._is_long:
self.BuyMarket()
else:
self.SellMarket()
self._trade_count = 1
self._last_entry_price = price
self._avg_price = price
return
# Check take profit: close entire basket
if self._is_long and price >= self._avg_price + float(self.take_profit) * step:
self._close_all()
return
elif not self._is_long and price <= self._avg_price - float(self.take_profit) * step:
self._close_all()
return
# Check for grid averaging entry (price moved against us)
if self._is_long and self._trade_count < self.max_trades and self._last_entry_price - price >= float(self.pip_step) * step:
self.BuyMarket()
self._trade_count += 1
self._avg_price = (self._avg_price * (self._trade_count - 1) + price) / self._trade_count
self._last_entry_price = price
elif not self._is_long and self._trade_count < self.max_trades and price - self._last_entry_price >= float(self.pip_step) * step:
self.SellMarket()
self._trade_count += 1
self._avg_price = (self._avg_price * (self._trade_count - 1) + price) / self._trade_count
self._last_entry_price = price
def _close_all(self):
pos = self.Position
if pos > 0:
for i in range(int(abs(pos))):
self.SellMarket()
elif pos < 0:
for i in range(int(abs(pos))):
self.BuyMarket()
self._reset_state()
def CreateClone(self):
return ilan_16_dynamic_strategy()