Открыть на GitHub

Стратегия Sprut Pending Order Grid

Общее описание

Sprut Pending Order Grid Strategy переносит советник MetaTrader 5 Sprut (barabashkakvn's edition) в экосистему StockSharp. Стратегия строит настраиваемую сетку отложенных заявок вокруг текущей цены, управляет жизненным циклом каждой заявки, масштабирует объёмы и после исполнения выставляет защитные приказы с помощью высокоуровневых методов StockSharp (BuyStop, SellStop, BuyLimit, SellLimit).

Главные особенности, унаследованные от оригинала:

  • первая заявка каждого включённого направления может иметь фиксированную цену или автоматически рассчитываться как смещение в пунктах от лучшей котировки;
  • сетка расширяется пошагово с раздельными шагами для стоп и лимитных заявок;
  • объёмы увеличиваются согласно коэффициенту, полностью повторяя формулу из MT5;
  • после исполнения заявки сразу выставляются собственные стоп-лосс и тейк-профит на заданное число пунктов от входа;
  • глобальные пороги прибыли и убытка принудительно закрывают позицию и снимают все оставшиеся заявки;
  • по желанию каждую заявку можно автоматически снимать по истечении заданного времени.

Логика работы

  1. Данные рынка. Стратегия подписывается на стакан котировок, чтобы отслеживать лучшие bid/ask, и на свечи (по умолчанию 1 минута), чтобы регулярно запускать обслуживание. Индикаторы не используются.
  2. Инициализация сетки. При отсутствии позиции и активных заявок вычисляются стартовые цены:
    • Buy Stop: лучшая ask + DeltaFirstBuyStop (если FirstBuyStop = 0).
    • Buy Limit: лучшая bid − DeltaFirstBuyLimit (если FirstBuyLimit = 0).
    • Sell Stop: лучшая bid − DeltaFirstSellStop (если FirstSellStop = 0).
    • Sell Limit: лучшая ask + DeltaFirstSellLimit (если FirstSellLimit = 0). Смещения переводятся из пунктов через Security.PriceStep (резервное значение — 0.0001).
  3. Наращивание сетки. Для каждого активного направления создаётся CountOrders заявок с шагом StepStop или StepLimit (в пунктах). Объём первой заявки равен базовому значению; каждая следующая получает объём baseVolume * N * coefficient, когда коэффициент больше 1. Объёмы нормализуются под VolumeStep, MinVolume и MaxVolume инструмента.
  4. Истечение срока. Если ExpirationMinutes > 0, каждая отложенная заявка получает временную метку и отменяется после истечения срока.
  5. Защита после исполнения. Когда StockSharp сообщает о выполнении входной заявки, стратегия регистрирует соответствующие защитные приказы (стоп/тейк) на расстояниях StopLoss и TakeProfit в пунктах. Нулевое значение отключает нужную защиту.
  6. Контроль PnL. Совокупный результат (реализованный + нереализованный) пересчитывается при каждом обновлении. Если ProfitClose достигнут или LossClose (обычно отрицательный) пробит, инициируется полная ликвидация: позиция закрывается по рынку, сетка и защиты отменяются. После возврата к нулевой позиции стратегия автоматически строит сетку заново.
  7. Постоянное обслуживание. На каждом шаге выполняется очистка завершённых заявок, снятие просроченных, повторная постановка сетки при возможности и блокировка новых заявок во время ликвидации.

Параметры

Параметр Описание Значение по умолчанию
CountOrders Количество заявок на каждое активное направление. 5
FirstBuyStop, FirstBuyLimit, FirstSellStop, FirstSellLimit Фиксированные цены для первой заявки (0 = использовать смещение). 0
DeltaFirstBuyStop, DeltaFirstBuyLimit, DeltaFirstSellStop, DeltaFirstSellLimit Смещения в пунктах относительно лучшей цены. 15
UseBuyStop, UseBuyLimit, UseSellStop, UseSellLimit Флаги включения каждого направления. false
StepStop, StepLimit Расстояние между соседними стоп/лимит заявками (в пунктах). 50
VolumeStop, VolumeLimit Базовый объём первой стоп/лимит заявки. 0.01
CoefficientStop, CoefficientLimit Множитель объёма для следующих заявок (>1 повторяет MT5). 1.6
ProfitClose Порог общей прибыли, вызывающий полную ликвидацию (в валюте счёта). 10
LossClose Порог убытка, вызывающий полную ликвидацию (в валюте счёта, обычно отрицательный). -100
ExpirationMinutes Срок жизни отложенных заявок (минуты, 0 = без ограничения). 60
StopLoss, TakeProfit Расстояния для защитных приказов (в пунктах, 0 = отключить). 50 / 0
CandleType Тип свечей для обслуживания стратегии. 1 минута

Практические замечания

  • Включите хотя бы один из четырёх флагов (UseBuyStop, UseBuyLimit, UseSellStop, UseSellLimit), иначе сетка не будет построена.
  • Пересчёт пунктов выполняется через PriceStep; инструменты с необычным шагом цены могут требовать подстройки смещений.
  • Пороговые ProfitClose/LossClose сравнивают сумму реализованного PnL (Strategy.PnL) и текущего плавающего PnL, вычисленного по лучшим котировкам. Убедитесь, что для инструмента настроены PriceStep и StepPrice.
  • Защитные приказы — отдельные заявки StockSharp. Если позиция закрыта вручную, остаточные защиты будут отменены, как только позиция обнулится.
  • CandleType отвечает лишь за периодичность обслуживания; сами заявки выставляются при обновлении стакана.

Отличия от MT5-версии

  • Учёт позиций ведётся в неттинговом режиме, аналогичном MT5.
  • Защитные приказы создаются отдельными заявками после исполнения, а не через поля SL/TP внутри отложенного ордера.
  • Нормализация объёмов использует биржевые атрибуты VolumeStep, MinVolume, MaxVolume — проверьте их для CFD и криптовалют.
  • Ручной «кнопки закрытия» нет — ликвидация выполняется автоматически при достижении порогов прибыли/убытка.

Порядок запуска

  1. Подключите стратегию к коннектору, который поставляет стакан и свечи выбранного таймфрейма.
  2. Настройте направления торговли и базовые объёмы согласно риск-профилю.
  3. Задайте StopLoss/TakeProfit, если требуются защитные приказы (0 отключает соответствующий приказ).
  4. Отрегулируйте ProfitClose и LossClose под размер депозита.
  5. Запустите стратегию — сетка будет построена после получения первой котировки стакана.

Python-версия отсутствует. По запросу реализована только C#-версия.

using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Sprut Pending Order Grid strategy. Uses Highest/Lowest midline crossover (period 14).
/// </summary>
public class SprutPendingOrderGridStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _period;
	private decimal? _prevMid;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int Period { get => _period.Value; set => _period.Value = value; }

	public SprutPendingOrderGridStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_period = Param(nameof(Period), 14).SetGreaterThanZero().SetDisplay("Channel Period", "Highest/Lowest lookback", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevMid = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevMid = null;
		var highest = new Highest { Length = Period };
		var lowest = new Lowest { Length = Period };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(highest, lowest, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal high, decimal low)
	{
		if (candle.State != CandleStates.Finished) return;
		var mid = (high + low) / 2m;
		var close = candle.ClosePrice;
		if (!IsFormedAndOnlineAndAllowTrading()) { _prevMid = mid; return; }
		if (_prevMid == null) { _prevMid = mid; return; }
		if (close > mid && close > _prevMid.Value && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
		else if (close < mid && close < _prevMid.Value && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
		_prevMid = mid;
	}
}