This strategy replicates the behaviour of the MetaTrader expert Dual StopLoss.mq4. It acts as a risk-management layer: it monitors the protective stop-loss orders attached to open positions and closes those positions a few points before the stop would trigger. The early exit is designed to avoid negative slippage on highly volatile moves while still respecting the trader's initial stop placement.
How it works
The strategy subscribes to Level1 data to track the current best bid/ask and the StopLevel (or equivalent) distance published by the broker.
Every time new prices arrive or orders/trades change, it searches for the nearest active stop order that belongs to the managed security.
The distance between the market price and that protective stop is compared with a configurable threshold:
pointValue matches MetaTrader's Point (0.0001 for most FX pairs, auto-detected from the security settings).
stopLevelDistance comes from Level1 fields (StopLevel, MinStopPrice, StopPrice, or StopDistance) when available, otherwise zero.
When the remaining distance is smaller than or equal to the threshold, the position is closed immediately using a market order.
The logic covers both long and short positions. For long positions the best bid is compared with the sell stop price; for short positions the best ask is compared with the buy stop price. Only stop and stop-limit orders in the active state are considered.
Parameters
Parameter
Description
WhenToClosePoints
Distance (in MetaTrader points) from the stop level that should trigger the early exit. Default: 10. Set to zero to only rely on the broker's minimal stop level distance.
Notes and limitations
The strategy does not open positions on its own; it only manages positions that already exist and have protective stop orders.
Ensure that the underlying connector/broker supplies stop level values through Level1 data if you want to account for broker-imposed minimal distances. If that information is missing, the strategy still works using only the configured point distance.
The StartProtection() call enables StockSharp's built-in safety guards so that emergency exits remain active once the strategy has started.
Stops are detected from the strategy's Orders collection. Make sure that protective stops are registered through the same strategy instance so that they appear in this list.
When multiple stop orders exist for the same direction, the one closest to the market is used.
Usage tips
Attach the strategy to a portfolio/security where positions are opened manually or by another system, but protective stops are placed under the same strategy context.
Configure WhenToClosePoints to match how much cushion you need before the stop. This value is interpreted exactly like in MetaTrader (points, not price units).
Start the strategy and monitor the log. When the market price approaches the stop, the strategy will issue a market order to close the position proactively.
Combine this module with other entry or position-sizing strategies to create a complete trading workflow.
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()