Стратегия Ilan 1.6 Dynamic
Стратегия Ilan 1.6 Dynamic представляет собой классический сеточный мартингейл. Первоначальная сделка открывается в выбранном направлении, а при движении цены против позиции через фиксированный шаг выставляются дополнительные ордера. Объём каждого нового ордера увеличивается геометрически на множитель LotExponent. Вся корзина закрывается, когда цена возвращается к средней цене входа плюс дистанция TakeProfit. При достаточной прибыли может быть задействован трейлинг-стоп для защиты результата.
Алгоритм полностью основан на движении цены и не использует индикаторы. Из-за увеличения объёма после каждого неблагоприятного движения система несёт высокий риск, но позволяет быстро отыгрывать откаты.
Детали
- Вход
- Первый ордер открывается в заданном направлении.
- Дополнительные ордера добавляются каждые
PipStep пунктов против позиции, до MaxTrades.
- Объём нового ордера =
InitialVolume * LotExponent^N.
- Выход
- Закрытие всей корзины при достижении
AveragePrice ± TakeProfit.
- Дополнительно может использоваться трейлинг-стоп после
TrailStart пунктов прибыли с расстоянием TrailStop.
- Управление позицией
- Одновременно ведётся только серия лонгов или шортов.
- После закрытия корзины стратегия запускается снова с исходного направления.
- Параметры
InitialVolume – объём первого ордера (по умолчанию 1).
LotExponent – множитель объёма последующих ордеров (1.6).
PipStep – расстояние между уровнями сетки в пунктах (30).
TakeProfit – цель прибыли от средней цены в пунктах (10).
MaxTrades – максимальное число активных ордеров (10).
StartLong – начинать с покупки, если true (true).
UseTrailingStop – включить трейлинг-стоп (false).
TrailStart – прибыль в пунктах для запуска трейлинга (10).
TrailStop – расстояние трейлинга в пунктах (10).
CandleType – таймфрейм свечей (1 минута).
- Фильтры
- Категория: Сетка
- Направление: Оба
- Индикаторы: Нет
- Стопы: Опционально
- Сложность: Средняя
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Высокий
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()