Стратегия повторяет логику советника MetaTrader Dual StopLoss.mq4. Она выступает как слой управления рисками: отслеживает защитные стоп-заявки у уже открытых позиций и принудительно закрывает их за несколько пунктов до срабатывания стопа. Такой ранний выход помогает уменьшить проскальзывание на резких движениях, но при этом сохраняет исходную логику постановки стоп-лосса.
Как работает
Подписывается на данные Level1, чтобы получать лучшие цены Bid/Ask и дистанцию StopLevel (или аналогичное поле) от брокера.
При каждом обновлении цены, изменении заявок или собственных сделок ищет ближайшую активную стоп-заявку по управляемому инструменту.
Сравнивает расстояние от текущей цены до защитного стопа с порогом:
pointValue соответствует значению MetaTrader Point (0.0001 для большинства валютных пар) и вычисляется автоматически из настроек инструмента.
stopLevelDistance берётся из Level1 полей (StopLevel, MinStopPrice, StopPrice, StopDistance), если они доступны. В противном случае принимается равным нулю.
Когда расстояние становится меньше или равно порогу, стратегия закрывает позицию рыночной заявкой до того, как брокер исполнит стоп.
Логика покрывает как длинные, так и короткие позиции. Для лонга сравнивается Bid с ценой sell stop, для шорта — Ask с ценой buy stop. Учитываются только активные стоп- и стоп-лимит заявки.
Параметры
Параметр
Описание
WhenToClosePoints
Дистанция (в пунктах MetaTrader) от уровня стоп-лосса, при которой нужно закрыть позицию заранее. Значение по умолчанию: 10. При нуле учитывается только минимальная дистанция брокера.
Примечания и ограничения
Стратегия не открывает позиции самостоятельно — она управляет только теми позициями, у которых уже стоят защитные стопы.
Для учёта минимальной дистанции брокера необходимо, чтобы коннектор передавал поле StopLevel (или эквивалент) через Level1. Если данных нет, алгоритм использует только заданное значение WhenToClosePoints.
Вызов StartProtection() активирует встроенную защиту StockSharp, чтобы экстренные выходы оставались доступными после запуска.
Поиск стопов ведётся по коллекции Orders стратегии, поэтому защитные стопы должны регистрироваться в рамках той же стратегии.
Если установлено несколько стопов в одном направлении, используется ближайший к рынку.
Рекомендации по применению
Подключите стратегию к портфелю и инструменту, где позиции открываются вручную или другими алгоритмами, но защитные стопы регистрируются через эту же стратегию.
Настройте WhenToClosePoints в соответствии с тем, сколько «подушки» нужно перед стопом. Параметр интерпретируется так же, как в MetaTrader (в пунктах, а не в денежных единицах).
Запустите стратегию и следите за логом. При приближении цены к стопу будет отправлена рыночная заявка на закрытие позиции.
Комбинируйте модуль с другими стратегиями входа или управления объёмом, чтобы построить полный торговый процесс.
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;
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class dual_stoploss_strategy(Strategy):
"""
Dual Stoploss strategy: triple SMA crossover with confirmation.
Buys when fast SMA crosses above mid SMA and mid is above slow.
Sells on opposite crossover.
"""
def __init__(self):
super(dual_stoploss_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._fast_period = self.Param("FastPeriod", 3) \
.SetDisplay("Fast SMA", "Fast SMA period", "Indicators")
self._mid_period = self.Param("MidPeriod", 10) \
.SetDisplay("Mid SMA", "Mid SMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 30) \
.SetDisplay("Slow SMA", "Slow SMA period", "Indicators")
self._prev_fast = 0.0
self._prev_mid = 0.0
self._has_prev = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(dual_stoploss_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_mid = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(dual_stoploss_strategy, self).OnStarted2(time)
self._has_prev = False
fast = SimpleMovingAverage()
fast.Length = self._fast_period.Value
mid = SimpleMovingAverage()
mid.Length = self._mid_period.Value
slow = SimpleMovingAverage()
slow.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, mid, slow, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, mid)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val, mid_val, slow_val):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_val)
mid_val = float(mid_val)
slow_val = float(slow_val)
if self._has_prev:
if self._prev_fast <= self._prev_mid and fast_val > mid_val and mid_val > slow_val and self.Position <= 0:
self.BuyMarket()
elif self._prev_fast >= self._prev_mid and fast_val < mid_val and mid_val < slow_val and self.Position >= 0:
self.SellMarket()
else:
if fast_val > mid_val and mid_val > slow_val and self.Position <= 0:
self.BuyMarket()
elif fast_val < mid_val and mid_val < slow_val and self.Position >= 0:
self.SellMarket()
self._prev_fast = fast_val
self._prev_mid = mid_val
self._has_prev = True
def CreateClone(self):
return dual_stoploss_strategy()