Открыть на GitHub

Стратегия Pending Stop Grid

Обзор

Pending Stop Grid Strategy — это прямой перенос эксперта MetaTrader 4 new.mq4. Стратегия одновременно поддерживает две симметричные лестницы отложенных заявок:

  • Лесенку buy stop выше текущей цены ask;
  • Лесенку sell stop ниже текущей цены bid.

На каждом уровне расстояние до рынка и торговый объём увеличиваются пропорционально номеру ступени. Для каждой заявки отдельно задаются уровни стоп-лосса и тейк-профита.

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

  1. Стратегия подписывается на поток Level 1 и непрерывно отслеживает последние значения лучшего bid и ask.
  2. После получения котировок и разрешения на торговлю вычисляется размер пункта на основе минимального шага цены (для инструментов с тремя или пятью знаками выполняется автоматическая нормализация).
  3. Перед постановкой заявок проверяется, что базовый объём соответствует ограничениям по минимальному и максимальному объёму инструмента.
  4. Для каждого индекса i от 1 до NumberOfTrades выполняется:
    • Расчёт объёма BaseVolume * i с округлением к ближайшему допустимому шагу объёма;
    • Постановка buy stop по цене Ask + DistancePips * i * pipSize с заданными стопом и профитом;
    • Постановка sell stop по цене Bid - DistancePips * i * pipSize с зеркальными параметрами стопа и профита.
  5. При исполнении, отмене или отказе любой заявки соответствующее место в лестнице освобождается и оперативно заполняется новой заявкой, когда это позволяет рынок.
  6. В методе OnStarted вызывается StartProtection() для активации встроенной защиты платформы.

Параметры

Название Описание Значение по умолчанию
BaseVolume Объём первой заявки. Каждый следующий уровень умножает базовый объём на номер ступени. 0.1
NumberOfTrades Количество одновременно поддерживаемых buy stop и sell stop заявок. 10
DistancePips Расстояние в пунктах от текущей цены до каждой заявки. 10
StopLossPips Расстояние стоп-лосса для каждой заявки. Ноль отключает постановку стопа. 10
TakeProfitPips Расстояние тейк-профита для каждой заявки. Ноль отключает тейк-профит. 10

Все параметры оформлены как оптимизируемые параметры стратегии и защищены проверками от недопустимых значений.

Дополнительные замечания

  • Объёмы округляются к ближайшему разрешённому шагу и ограничиваются биржевыми минимальными и максимальными значениями.
  • Цены нормализуются через Security.ShrinkPrice, чтобы соблюдать шаг цены инструмента.
  • Стратегия не хранит устойчивое состояние: при сбросе или изменении прав весь набор заявок формируется заново.
  • Логика целиком построена на высокоуровневом API StockSharp, что соответствует требованиям проекта по конвертации.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

public class PendingStopGridStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public PendingStopGridStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}