Открыть на GitHub

Стратегия Dual Stoploss

Стратегия повторяет логику советника MetaTrader Dual StopLoss.mq4. Она выступает как слой управления рисками: отслеживает защитные стоп-заявки у уже открытых позиций и принудительно закрывает их за несколько пунктов до срабатывания стопа. Такой ранний выход помогает уменьшить проскальзывание на резких движениях, но при этом сохраняет исходную логику постановки стоп-лосса.

Как работает

  1. Подписывается на данные Level1, чтобы получать лучшие цены Bid/Ask и дистанцию StopLevel (или аналогичное поле) от брокера.
  2. При каждом обновлении цены, изменении заявок или собственных сделок ищет ближайшую активную стоп-заявку по управляемому инструменту.
  3. Сравнивает расстояние от текущей цены до защитного стопа с порогом:
    • Порог = WhenToClosePoints × pointValue + stopLevelDistance.
    • pointValue соответствует значению MetaTrader Point (0.0001 для большинства валютных пар) и вычисляется автоматически из настроек инструмента.
    • stopLevelDistance берётся из Level1 полей (StopLevel, MinStopPrice, StopPrice, StopDistance), если они доступны. В противном случае принимается равным нулю.
  4. Когда расстояние становится меньше или равно порогу, стратегия закрывает позицию рыночной заявкой до того, как брокер исполнит стоп.

Логика покрывает как длинные, так и короткие позиции. Для лонга сравнивается Bid с ценой sell stop, для шорта — Ask с ценой buy stop. Учитываются только активные стоп- и стоп-лимит заявки.

Параметры

Параметр Описание
WhenToClosePoints Дистанция (в пунктах MetaTrader) от уровня стоп-лосса, при которой нужно закрыть позицию заранее. Значение по умолчанию: 10. При нуле учитывается только минимальная дистанция брокера.

Примечания и ограничения

  • Стратегия не открывает позиции самостоятельно — она управляет только теми позициями, у которых уже стоят защитные стопы.
  • Для учёта минимальной дистанции брокера необходимо, чтобы коннектор передавал поле StopLevel (или эквивалент) через Level1. Если данных нет, алгоритм использует только заданное значение WhenToClosePoints.
  • Вызов StartProtection() активирует встроенную защиту StockSharp, чтобы экстренные выходы оставались доступными после запуска.
  • Поиск стопов ведётся по коллекции Orders стратегии, поэтому защитные стопы должны регистрироваться в рамках той же стратегии.
  • Если установлено несколько стопов в одном направлении, используется ближайший к рынку.

Рекомендации по применению

  1. Подключите стратегию к портфелю и инструменту, где позиции открываются вручную или другими алгоритмами, но защитные стопы регистрируются через эту же стратегию.
  2. Настройте WhenToClosePoints в соответствии с тем, сколько «подушки» нужно перед стопом. Параметр интерпретируется так же, как в MetaTrader (в пунктах, а не в денежных единицах).
  3. Запустите стратегию и следите за логом. При приближении цены к стопу будет отправлена рыночная заявка на закрытие позиции.
  4. Комбинируйте модуль с другими стратегиями входа или управления объёмом, чтобы построить полный торговый процесс.
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Dual Stoploss strategy: dual SMA crossover with confirmation.
/// Buys when fast SMA crosses above mid SMA and mid is above slow, sells on opposite.
/// </summary>
public class DualStoplossStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _midPeriod;
	private readonly StrategyParam<int> _slowPeriod;

	private decimal _prevFast;
	private decimal _prevMid;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int MidPeriod { get => _midPeriod.Value; set => _midPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }

	public DualStoplossStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_fastPeriod = Param(nameof(FastPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("Fast SMA", "Fast SMA period", "Indicators");
		_midPeriod = Param(nameof(MidPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Mid SMA", "Mid SMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow SMA", "Slow SMA period", "Indicators");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = 0;
		_prevMid = 0;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_hasPrev = false;
		var fast = new SimpleMovingAverage { Length = FastPeriod };
		var mid = new SimpleMovingAverage { Length = MidPeriod };
		var slow = new SimpleMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fast, mid, slow, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal midValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;

		if (_hasPrev)
		{
			if (_prevFast <= _prevMid && fastValue > midValue && midValue > slowValue && Position <= 0)
				BuyMarket();
			else if (_prevFast >= _prevMid && fastValue < midValue && midValue < slowValue && Position >= 0)
				SellMarket();
		}
		else
		{
			if (fastValue > midValue && midValue > slowValue && Position <= 0)
				BuyMarket();
			else if (fastValue < midValue && midValue < slowValue && Position >= 0)
				SellMarket();
		}

		_prevFast = fastValue;
		_prevMid = midValue;
		_hasPrev = true;
	}
}