The Trailing Stop Trigger Manager Strategy is a StockSharp port of the MetaTrader expert advisor Trailing Sl.mq5. The original EA
did not open trades on its own. Instead, it monitored already open positions with a matching magic number and tightened their
stop-loss levels when the market moved in the desired direction. This C# implementation reproduces that behaviour using
StockSharp's high-level strategy API, delivering transparent trailing-stop management that works with any instrument supported by
StockSharp.
Trailing logic
Subscribes to the order book in order to read the latest best bid and best ask quotes.
Detects whether the strategy currently holds a long or short net position.
Calculates the floating profit using the appropriate side of the market (best bid for longs, best ask for shorts).
Activates the trailing mode once the profit exceeds TriggerPoints (converted to price units through PriceStep).
Sets the trailing stop at the configured distance TrailingPoints away from the current market quote.
Moves the trailing stop only towards the market to keep locking in additional profit.
Sends a market order to flatten the position as soon as the best quote touches the calculated trailing stop level.
Order and risk management
The strategy does not submit initial entry orders. It only manages an existing position that may have been opened manually
or by another strategy.
Market exits are placed with BuyMarket/SellMarket, mirroring the PositionModify calls from the original MetaTrader code.
The stop distance automatically scales with the instrument's PriceStep, which preserves the point-based configuration from
the EA.
Once the position is closed, the trailing state resets so that new positions start from a clean slate.
Parameters
Name
Type
Default
Description
TrailingPoints
int
1000
Distance between the current price and the trailing stop, measured in price steps.
TriggerPoints
int
1500
Minimum profit in price steps required to start trailing the position.
Usage notes
Attach the strategy to the security whose position you want to supervise. It will immediately start tracking the existing
exposure.
Configure the initial Volume of the strategy to match the size of your open position. StockSharp uses net positions, so the
strategy will exit the entire lot when the trailing stop is triggered.
If the broker delivers coarse price steps, adjust TrailingPoints and TriggerPoints accordingly to avoid premature exits.
The strategy keeps its state entirely inside StockSharp, so it can be combined with any discretionary or automated system that
leaves the actual order execution to StockSharp.
Differences from the original MetaTrader expert
MetaTrader managed separate positions per ticket and filtered them by magic number. StockSharp works with a net position per
security, removing the need for ticket filtering.
The Setloss, TakeProfit, and Lots inputs were unused in the original EA. They are therefore omitted in the StockSharp
version to keep the configuration focused on trailing behaviour.
Order modifications are replaced by direct market exits, which is the idiomatic approach for netting accounts in StockSharp.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that mirrors the MetaTrader "Trailing Sl" expert by managing trailing stops for existing positions.
/// Adds SMA crossover entries for backtesting.
/// </summary>
public class TrailingStopTriggerManagerStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _trailingPoints;
private readonly StrategyParam<int> _triggerPoints;
private decimal _lastEntryPrice;
private decimal? _activeStopPrice;
private bool _trailingEnabled;
private decimal _trailingDistance;
private decimal _triggerDistance;
private decimal _prevFast;
private decimal _prevSlow;
private bool _hasPrev;
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Trailing stop distance in price steps.
/// </summary>
public int TrailingPoints
{
get => _trailingPoints.Value;
set => _trailingPoints.Value = value;
}
/// <summary>
/// Profit distance that activates trailing stop.
/// </summary>
public int TriggerPoints
{
get => _triggerPoints.Value;
set => _triggerPoints.Value = value;
}
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public TrailingStopTriggerManagerStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_trailingPoints = Param(nameof(TrailingPoints), 1000)
.SetGreaterThanZero()
.SetDisplay("Trailing Points", "Trailing stop distance", "Trailing Management");
_triggerPoints = Param(nameof(TriggerPoints), 1500)
.SetGreaterThanZero()
.SetDisplay("Trigger Points", "Profit to activate trailing", "Trailing Management");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_lastEntryPrice = 0m;
_activeStopPrice = null;
_trailingEnabled = false;
_trailingDistance = 0m;
_triggerDistance = 0m;
_prevFast = 0m;
_prevSlow = 0m;
_hasPrev = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var step = Security?.PriceStep ?? 1m;
if (step <= 0m) step = 1m;
_trailingDistance = step * TrailingPoints;
_triggerDistance = step * TriggerPoints;
var smaFast = new SimpleMovingAverage { Length = 10 };
var smaSlow = new SimpleMovingAverage { Length = 30 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(smaFast, smaSlow, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
_lastEntryPrice = trade.Trade.Price;
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
var price = candle.ClosePrice;
// Trailing stop management
if (Position > 0 && _lastEntryPrice > 0)
{
var profit = price - _lastEntryPrice;
if (!_trailingEnabled && profit >= _triggerDistance)
{
_trailingEnabled = true;
_activeStopPrice = price - _trailingDistance;
}
else if (_trailingEnabled)
{
var desiredStop = price - _trailingDistance;
if (!_activeStopPrice.HasValue || desiredStop > _activeStopPrice.Value)
_activeStopPrice = desiredStop;
}
if (_trailingEnabled && _activeStopPrice.HasValue && price <= _activeStopPrice.Value)
{
SellMarket();
ResetTrailingState();
return;
}
}
else if (Position < 0 && _lastEntryPrice > 0)
{
var profit = _lastEntryPrice - price;
if (!_trailingEnabled && profit >= _triggerDistance)
{
_trailingEnabled = true;
_activeStopPrice = price + _trailingDistance;
}
else if (_trailingEnabled)
{
var desiredStop = price + _trailingDistance;
if (!_activeStopPrice.HasValue || desiredStop < _activeStopPrice.Value)
_activeStopPrice = desiredStop;
}
if (_trailingEnabled && _activeStopPrice.HasValue && price >= _activeStopPrice.Value)
{
BuyMarket();
ResetTrailingState();
return;
}
}
// SMA crossover entries
if (_hasPrev)
{
var crossUp = _prevFast <= _prevSlow && fast > slow;
var crossDown = _prevFast >= _prevSlow && fast < slow;
if (crossUp && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
ResetTrailingState();
}
else if (crossDown && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
ResetTrailingState();
}
}
_prevFast = fast;
_prevSlow = slow;
_hasPrev = true;
}
private void ResetTrailingState()
{
_trailingEnabled = false;
_activeStopPrice = null;
}
}
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 trailing_stop_trigger_manager_strategy(Strategy):
def __init__(self):
super(trailing_stop_trigger_manager_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._trailing_points = self.Param("TrailingPoints", 1000) \
.SetGreaterThanZero() \
.SetDisplay("Trailing Points", "Trailing stop distance", "Trailing Management")
self._trigger_points = self.Param("TriggerPoints", 1500) \
.SetGreaterThanZero() \
.SetDisplay("Trigger Points", "Profit to activate trailing", "Trailing Management")
self._last_entry_price = 0.0
self._active_stop_price = None
self._trailing_enabled = False
self._trailing_distance = 0.0
self._trigger_distance = 0.0
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def TrailingPoints(self):
return self._trailing_points.Value
@TrailingPoints.setter
def TrailingPoints(self, value):
self._trailing_points.Value = value
@property
def TriggerPoints(self):
return self._trigger_points.Value
@TriggerPoints.setter
def TriggerPoints(self, value):
self._trigger_points.Value = value
def OnReseted(self):
super(trailing_stop_trigger_manager_strategy, self).OnReseted()
self._last_entry_price = 0.0
self._active_stop_price = None
self._trailing_enabled = False
self._trailing_distance = 0.0
self._trigger_distance = 0.0
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(trailing_stop_trigger_manager_strategy, self).OnStarted2(time)
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
s = float(self.Security.PriceStep)
if s > 0:
step = s
self._trailing_distance = step * self.TrailingPoints
self._trigger_distance = step * self.TriggerPoints
sma_fast = SimpleMovingAverage()
sma_fast.Length = 10
sma_slow = SimpleMovingAverage()
sma_slow.Length = 30
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(sma_fast, sma_slow, self._process_candle).Start()
def OnOwnTradeReceived(self, trade):
super(trailing_stop_trigger_manager_strategy, self).OnOwnTradeReceived(trade)
self._last_entry_price = float(trade.Trade.Price)
def _process_candle(self, candle, fast, slow):
if candle.State != CandleStates.Finished:
return
price = float(candle.ClosePrice)
fv = float(fast)
sv = float(slow)
# Trailing stop management for long
if self.Position > 0 and self._last_entry_price > 0:
profit = price - self._last_entry_price
if not self._trailing_enabled and profit >= self._trigger_distance:
self._trailing_enabled = True
self._active_stop_price = price - self._trailing_distance
elif self._trailing_enabled:
desired_stop = price - self._trailing_distance
if self._active_stop_price is None or desired_stop > self._active_stop_price:
self._active_stop_price = desired_stop
if self._trailing_enabled and self._active_stop_price is not None and price <= self._active_stop_price:
self.SellMarket()
self._reset_trailing_state()
return
# Trailing stop management for short
elif self.Position < 0 and self._last_entry_price > 0:
profit = self._last_entry_price - price
if not self._trailing_enabled and profit >= self._trigger_distance:
self._trailing_enabled = True
self._active_stop_price = price + self._trailing_distance
elif self._trailing_enabled:
desired_stop = price + self._trailing_distance
if self._active_stop_price is None or desired_stop < self._active_stop_price:
self._active_stop_price = desired_stop
if self._trailing_enabled and self._active_stop_price is not None and price >= self._active_stop_price:
self.BuyMarket()
self._reset_trailing_state()
return
# SMA crossover entries
if self._has_prev:
cross_up = self._prev_fast <= self._prev_slow and fv > sv
cross_down = self._prev_fast >= self._prev_slow and fv < sv
if cross_up and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._reset_trailing_state()
elif cross_down and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._reset_trailing_state()
self._prev_fast = fv
self._prev_slow = sv
self._has_prev = True
def _reset_trailing_state(self):
self._trailing_enabled = False
self._active_stop_price = None
def CreateClone(self):
return trailing_stop_trigger_manager_strategy()