Стратегия повторяет советник "Adaptive Grid Mt4" на высокоуровневом API StockSharp. Она размещает симметричную сетку из
отложенных Buy Stop и Sell Stop вокруг цены закрытия текущей свечи. Расстояния между уровнями вычисляются через показатель
Average True Range (ATR), поэтому сетка автоматически подстраивается под волатильность рынка. Каждая заявка имеет ограниченный
срок жизни в свечах, что предотвращает захламление книги заявок во флэте.
При исполнении входящей заявки стратегия сразу выставляет соответствующие тейк-профит и стоп-лосс, используя значения ATR в
момент генерации сетки. Защитные приказы жёстко привязаны к исходной сделке и остаются активными до исполнения или ручной
отмены.
Параметры
Параметр
Описание
GridLevels
Количество уровней выше и ниже рынка (nGrid).
TimerBars
Максимальное число завершённых свечей до отмены отложенной заявки (nBars).
PriceOffsetMultiplier
Множитель ATR для стартового смещения от цены (Poffset).
GridStepMultiplier
Множитель ATR для шага между уровнями (Pstep).
StopLossMultiplier
Множитель ATR для вычисления стоп-лосса (StopLoss).
TakeProfitMultiplier
Множитель ATR для вычисления тейк-профита (TakeProfit).
AtrPeriod
Период усреднения ATR (аналог фиксированного значения 14 в оригинале).
OrderVolume
Объём каждой заявки (Lot).
CandleType
Таймфрейм свечей, по которым пересчитывается сетка (Wtf).
Логика торговли
Подписка на свечи выбранного CandleType и расчёт ATR(14).
На каждой завершённой свече:
Увеличить счётчик баров и отменить сеточные заявки, превысившие ограничение TimerBars.
Пропустить дальнейшую обработку, если ATR ещё не сформирован, есть активные заявки сетки или открыта позиция.
Рассчитать смещение, шаг сетки, стоп-лосс и тейк-профит как ATR * множитель.
Выставить по GridLevels пар Buy Stop и Sell Stop вокруг цены закрытия, нормализуя уровни через Security.ShrinkPrice.
При исполнении заявки удалить её из списка сетки и поставить соответствующие защитные приказы:
Для длинных позиций — SellStop и SellLimit.
Для коротких — BuyStop и BuyLimit.
Метод OnOrderChanged отслеживает завершённые защитные ордера и очищает списки.
Дополнительно
Новая сетка строится только после полного отсутствия позиций и активных отложенных заявок, что повторяет поведение функции
What() в MQL.
В качестве базовой цены используется закрытие свечи вместо последних Bid/Ask тиков, что позволяет оставаться в событийной
модели по свечам и всё равно получать симметричную сетку.
ATR фиксируется на момент генерации сетки и применятся к защитным приказам, повторяя прикреплённые стопы и тейки в MetaTrader.
Python-версия не создавалась по требованию.
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Adaptive grid strategy using ATR-based breakout levels.
/// Simplified from the "Adaptive Grid Mt4" expert advisor to use market orders.
/// </summary>
public class AdaptiveGridMt4Strategy : Strategy
{
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _breakoutMultiplier;
private readonly StrategyParam<DataType> _candleType;
private AverageTrueRange _atr;
private decimal? _prevClose;
private decimal? _prevAtr;
private decimal _stopPrice;
private decimal _takeProfitPrice;
/// <summary>
/// ATR averaging period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Breakout threshold in ATR multiples.
/// </summary>
public decimal BreakoutMultiplier
{
get => _breakoutMultiplier.Value;
set => _breakoutMultiplier.Value = value;
}
/// <summary>
/// Candle type used to drive calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public AdaptiveGridMt4Strategy()
{
_atrPeriod = Param(nameof(AtrPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Number of candles used for ATR smoothing", "Indicators");
_breakoutMultiplier = Param(nameof(BreakoutMultiplier), 2.5m)
.SetGreaterThanZero()
.SetDisplay("Breakout Multiplier", "ATR multiplier for breakout threshold", "Grid");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Candle type used to trigger grid recalculation", "General");
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevClose = null;
_prevAtr = null;
_stopPrice = 0;
_takeProfitPrice = 0;
_atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_atr.IsFormed || atrValue <= 0)
{
_prevClose = candle.ClosePrice;
_prevAtr = atrValue;
return;
}
// Check protective stops
if (Position > 0)
{
if (_stopPrice > 0 && candle.LowPrice <= _stopPrice)
{
SellMarket(Math.Abs(Position));
_stopPrice = 0;
_takeProfitPrice = 0;
}
else if (_takeProfitPrice > 0 && candle.HighPrice >= _takeProfitPrice)
{
SellMarket(Math.Abs(Position));
_stopPrice = 0;
_takeProfitPrice = 0;
}
}
else if (Position < 0)
{
if (_stopPrice > 0 && candle.HighPrice >= _stopPrice)
{
BuyMarket(Math.Abs(Position));
_stopPrice = 0;
_takeProfitPrice = 0;
}
else if (_takeProfitPrice > 0 && candle.LowPrice <= _takeProfitPrice)
{
BuyMarket(Math.Abs(Position));
_stopPrice = 0;
_takeProfitPrice = 0;
}
}
if (_prevClose is not decimal prevClose || _prevAtr is not decimal prevAtr || prevAtr <= 0)
{
_prevClose = candle.ClosePrice;
_prevAtr = atrValue;
return;
}
var threshold = prevAtr * BreakoutMultiplier;
var volume = Volume;
if (volume <= 0)
volume = 1;
// Breakout up
if (candle.ClosePrice > prevClose + threshold && Position <= 0)
{
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
_stopPrice = candle.ClosePrice - atrValue * 3;
_takeProfitPrice = candle.ClosePrice + atrValue * 4;
}
// Breakout down
else if (candle.ClosePrice < prevClose - threshold && Position >= 0)
{
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
_stopPrice = candle.ClosePrice + atrValue * 3;
_takeProfitPrice = candle.ClosePrice - atrValue * 4;
}
_prevClose = candle.ClosePrice;
_prevAtr = atrValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_atr = null;
_prevClose = null;
_prevAtr = null;
_stopPrice = 0;
_takeProfitPrice = 0;
base.OnReseted();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class adaptive_grid_mt4_strategy(Strategy):
def __init__(self):
super(adaptive_grid_mt4_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._atr_period = self.Param("AtrPeriod", 20)
self._breakout_multiplier = self.Param("BreakoutMultiplier", 2.5)
self._prev_close = 0.0
self._prev_atr = 0.0
self._has_prev = False
self._stop_price = 0.0
self._take_profit_price = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def AtrPeriod(self):
return self._atr_period.Value
@AtrPeriod.setter
def AtrPeriod(self, value):
self._atr_period.Value = value
@property
def BreakoutMultiplier(self):
return self._breakout_multiplier.Value
@BreakoutMultiplier.setter
def BreakoutMultiplier(self, value):
self._breakout_multiplier.Value = value
def OnReseted(self):
super(adaptive_grid_mt4_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_atr = 0.0
self._has_prev = False
self._stop_price = 0.0
self._take_profit_price = 0.0
self._entry_price = 0.0
def OnStarted2(self, time):
super(adaptive_grid_mt4_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._prev_atr = 0.0
self._has_prev = False
self._stop_price = 0.0
self._take_profit_price = 0.0
self._entry_price = 0.0
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(atr, self._process_candle).Start()
def _process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
atr_val = float(atr_value)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if atr_val <= 0:
self._prev_close = close
self._prev_atr = atr_val
self._has_prev = True
return
# Check protective stops
if self.Position > 0:
if self._stop_price > 0 and low <= self._stop_price:
self.SellMarket()
self._stop_price = 0.0
self._take_profit_price = 0.0
elif self._take_profit_price > 0 and high >= self._take_profit_price:
self.SellMarket()
self._stop_price = 0.0
self._take_profit_price = 0.0
elif self.Position < 0:
if self._stop_price > 0 and high >= self._stop_price:
self.BuyMarket()
self._stop_price = 0.0
self._take_profit_price = 0.0
elif self._take_profit_price > 0 and low <= self._take_profit_price:
self.BuyMarket()
self._stop_price = 0.0
self._take_profit_price = 0.0
if not self._has_prev or self._prev_atr <= 0:
self._prev_close = close
self._prev_atr = atr_val
self._has_prev = True
return
threshold = self._prev_atr * float(self.BreakoutMultiplier)
# Breakout up
if close > self._prev_close + threshold and self.Position <= 0:
self.BuyMarket()
self._stop_price = close - atr_val * 3.0
self._take_profit_price = close + atr_val * 4.0
self._entry_price = close
# Breakout down
elif close < self._prev_close - threshold and self.Position >= 0:
self.SellMarket()
self._stop_price = close + atr_val * 3.0
self._take_profit_price = close - atr_val * 4.0
self._entry_price = close
self._prev_close = close
self._prev_atr = atr_val
def CreateClone(self):
return adaptive_grid_mt4_strategy()