Pending Limit Grid повторяет логику советника из каталога MQL/8147.
Стратегия строит симметричную сетку лимитных заявок вокруг текущих котировок
Bid/Ask и поддерживает её, пока плавающая прибыль находится между целевым
значением и максимально допустимой просадкой. При достижении одного из
порогов все заявки снимаются, позиции закрываются рыночными ордерами, после
чего сетка создаётся заново на базе обновлённой стоимости портфеля.
Логика торговли
Подписка на поток Level1 для отслеживания лучших цен покупки и продажи.
Фиксация текущей стоимости портфеля при получении первых данных и
использование её как базового уровня для расчёта прибыли/убытка.
Размещение LevelsPerSide заявок Sell Limit выше рынка и такого же числа
заявок Buy Limit ниже рынка. Расстояние между уровнями определяется
параметром GridStepPoints, который конвертируется в размер шага цены
инструмента.
Заявки не перевыставляются повторно после исполнения. Новая сетка строится
только после полного сброса.
Постоянный контроль плавающей прибыли:
При достижении ProfitTargetCurrency все позиции закрываются и сетка
пересоздаётся.
При просадке ниже MaxDrawdownCurrency выполняется принудительное
закрытие и последующий сброс.
После сброса заново фиксируется базовая стоимость портфеля и сетка
восстанавливается по актуальным котировкам Bid/Ask.
Параметры
Параметр
Описание
ProfitTargetCurrency
Прибыль в валюте счёта, при достижении которой выполняется полный сброс.
MaxDrawdownCurrency
Допустимая плавающая просадка перед принудительным закрытием позиций.
GridStepPoints
Расстояние между уровнями сетки в пунктах брокера.
LevelsPerSide
Количество лимитных заявок, выставляемых выше и ниже рынка.
OrderVolume
Объём каждой лимитной заявки.
Управление рисками
Отдельные заявки не снабжаются стопами или тейк-профитами. Вместо этого
контролируется суммарный результат. Метод RequestFlatten снимает все
ожидающие заявки и вызывает ClosePosition, чтобы закрыть остаточные позиции
рыночными ордерами. После завершения закрытия состояние сетки и базовая
стоимость портфеля сбрасываются, позволяя построить новую сетку.
Дополнительные замечания
Цены нормализуются с помощью Security.ShrinkPrice, чтобы учитывать шаг цены
торгуемого инструмента.
Значение MetaTrader Point рассчитывается на основе PriceStep, что
обеспечивает корректность для четырёх- и пятизначных котировок.
Стратегия не повторно выставляет уровни сетки в пределах одного цикла,
полностью копируя поведение исходного советника на MQL.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class PendingLimitGridStrategy : Strategy
{
private readonly StrategyParam<int> _channelPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private decimal _prevClose;
private decimal _prevMid;
private bool _hasPrev;
private int _barsSinceLastTrade;
public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public PendingLimitGridStrategy()
{
_channelPeriod = Param(nameof(ChannelPeriod), 24).SetDisplay("Channel Period", "Grid channel lookback", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
_cooldownBars = Param(nameof(CooldownBars), 200).SetDisplay("Cooldown Bars", "Minimum bars between trades", "Risk");
}
protected override void OnReseted()
{
base.OnReseted();
_prevClose = 0;
_prevMid = 0;
_hasPrev = false;
_barsSinceLastTrade = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
_barsSinceLastTrade = 0;
var highest = new Highest { Length = ChannelPeriod };
var lowest = new Lowest { Length = ChannelPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(highest, lowest, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal highest, decimal lowest)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
var mid = (highest + lowest) / 2;
if (!_hasPrev) { _prevClose = close; _prevMid = mid; _hasPrev = true; return; }
_barsSinceLastTrade++;
if (_barsSinceLastTrade >= CooldownBars)
{
if (_prevClose <= _prevMid && close > mid && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
_barsSinceLastTrade = 0;
}
else if (_prevClose >= _prevMid && close < mid && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
_barsSinceLastTrade = 0;
}
}
_prevClose = close;
_prevMid = mid;
}
}
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 Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class pending_limit_grid_strategy(Strategy):
def __init__(self):
super(pending_limit_grid_strategy, self).__init__()
self._channel_period = self.Param("ChannelPeriod", 24).SetDisplay("Channel Period", "Grid channel lookback", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candle timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(pending_limit_grid_strategy, self).OnReseted()
self._has_prev = False
self._prev_close = 0
self._prev_mid = 0
def OnStarted2(self, time):
super(pending_limit_grid_strategy, self).OnStarted2(time)
self._has_prev = False
self._prev_close = 0
self._prev_mid = 0
highest = Highest()
highest.Length = self._channel_period.Value
lowest = Lowest()
lowest.Length = self._channel_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(highest, lowest, self.OnProcess).Start()
def OnProcess(self, candle, highest, lowest):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
mid = (highest + lowest) / 2.0
if not self._has_prev:
self._prev_close = close
self._prev_mid = mid
self._has_prev = True
return
if self._prev_close <= self._prev_mid and close > mid and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_close >= self._prev_mid and close < mid and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_close = close
self._prev_mid = mid
def CreateClone(self):
return pending_limit_grid_strategy()