Trail SL Manager Strategy
Summary
Trail SL Manager is a utility strategy that reproduces the behaviour of the original MetaTrader trailSL expert.
It does not open trades on its own. Instead, it supervises existing positions and dynamically adjusts their protective stop levels.
The logic mirrors the source script: first the stop is pushed to break even once price advances by a configurable amount, then an incremental trailing algorithm keeps locking profits as the trend continues.
How it works
- Subscribes to the configured candle stream to monitor finished bars.
- Tracks the average entry price and direction of the current position.
- When the price moves in favour of the trade by
BreakEvenTriggerPoints, the stop is pushed to the entry price plus an optional offset. - After break-even activation, or immediately if allowed, the strategy increments the stop by
TrailOffsetPointseveryTrailStepPointsuntil price reverses and closes the position at market.
The trailing rules are calculated with the same point-based arithmetic as the MetaTrader version, so the behaviour stays familiar for traders migrating to StockSharp.
Parameters
| Name | Description | Default |
|---|---|---|
EnableBreakEven |
Enables moving the stop to break even once the trade becomes profitable. | true |
BreakEvenTriggerPoints |
Profit distance in points required to activate the break-even move. | 20 |
BreakEvenOffsetPoints |
Additional points added to the entry price when break even is executed. | 10 |
EnableTrailing |
Toggles the trailing stop logic. | true |
TrailAfterBreakEven |
If true, trailing starts only after the break-even adjustment. |
true |
TrailStartPoints |
Minimum profit in points before trailing is allowed. | 40 |
TrailStepPoints |
Profit step between trailing recalculations. | 10 |
TrailOffsetPoints |
Points added to the stop on each trailing step. | 10 |
InitialStopPoints |
Distance of the initial protective stop when a new position appears. | 200 |
CandleType |
Candle subscription used to monitor price changes. | 1 Minute |
Usage
- Attach the strategy to an environment where entries are generated by another strategy or manually.
- Configure the point-based thresholds to match the symbol volatility and broker requirements.
- Start the strategy so it can monitor finished candles and adjust stops automatically.
- Monitor the chart drawings to see how the stop levels evolve with each trailing step.
Note: The strategy closes positions with market orders when the simulated trailing stop is breached. Add exchange-specific protection (such as real stop orders) if required by your workflow.
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>
/// Utility strategy that mirrors the trailSL MetaTrader script.
/// It manages open positions by moving the protective stop to break even and trailing it as price advances.
/// The strategy does not generate its own entry signals.
/// </summary>
public class TrailSlManagerStrategy : Strategy
{
private readonly StrategyParam<bool> _enableBreakEven;
private readonly StrategyParam<int> _breakEvenTriggerPoints;
private readonly StrategyParam<int> _breakEvenOffsetPoints;
private readonly StrategyParam<bool> _enableTrailing;
private readonly StrategyParam<bool> _trailAfterBreakEven;
private readonly StrategyParam<int> _trailStartPoints;
private readonly StrategyParam<int> _trailStepPoints;
private readonly StrategyParam<int> _trailOffsetPoints;
private readonly StrategyParam<int> _initialStopPoints;
private readonly StrategyParam<DataType> _candleType;
private decimal _priceStep;
private decimal _longStop;
private decimal _shortStop;
private bool _longBreakEvenActive;
private bool _shortBreakEvenActive;
private decimal _lastEntryPrice;
private SimpleMovingAverage _smaFast;
private SimpleMovingAverage _smaSlow;
private int _cooldown;
private int _lastSignal;
/// <summary>
/// Enables automatic break-even adjustment.
/// </summary>
public bool EnableBreakEven
{
get => _enableBreakEven.Value;
set => _enableBreakEven.Value = value;
}
/// <summary>
/// Required profit in points before break-even is activated.
/// </summary>
public int BreakEvenTriggerPoints
{
get => _breakEvenTriggerPoints.Value;
set => _breakEvenTriggerPoints.Value = value;
}
/// <summary>
/// Additional points added to the break-even price once triggered.
/// </summary>
public int BreakEvenOffsetPoints
{
get => _breakEvenOffsetPoints.Value;
set => _breakEvenOffsetPoints.Value = value;
}
/// <summary>
/// Enables trailing stop management.
/// </summary>
public bool EnableTrailing
{
get => _enableTrailing.Value;
set => _enableTrailing.Value = value;
}
/// <summary>
/// Trailing starts only after a successful break-even move.
/// </summary>
public bool TrailAfterBreakEven
{
get => _trailAfterBreakEven.Value;
set => _trailAfterBreakEven.Value = value;
}
/// <summary>
/// Minimum profit in points before trailing begins.
/// </summary>
public int TrailStartPoints
{
get => _trailStartPoints.Value;
set => _trailStartPoints.Value = value;
}
/// <summary>
/// Distance in points between trailing recalculations.
/// </summary>
public int TrailStepPoints
{
get => _trailStepPoints.Value;
set => _trailStepPoints.Value = value;
}
/// <summary>
/// Stop loss increment applied on each trailing step (points).
/// </summary>
public int TrailOffsetPoints
{
get => _trailOffsetPoints.Value;
set => _trailOffsetPoints.Value = value;
}
/// <summary>
/// Initial protective stop distance measured in points.
/// </summary>
public int InitialStopPoints
{
get => _initialStopPoints.Value;
set => _initialStopPoints.Value = value;
}
/// <summary>
/// Candle type used for monitoring price progress.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public TrailSlManagerStrategy()
{
_enableBreakEven = Param(nameof(EnableBreakEven), true)
.SetDisplay("Break Even", "Enable break-even stop adjustment", "Risk")
;
_breakEvenTriggerPoints = Param(nameof(BreakEvenTriggerPoints), 20)
.SetDisplay("Break Even Trigger", "Points required before moving to break-even", "Risk")
;
_breakEvenOffsetPoints = Param(nameof(BreakEvenOffsetPoints), 10)
.SetDisplay("Break Even Offset", "Extra points locked when break-even triggers", "Risk")
;
_enableTrailing = Param(nameof(EnableTrailing), true)
.SetDisplay("Trailing", "Enable trailing stop management", "Risk")
;
_trailAfterBreakEven = Param(nameof(TrailAfterBreakEven), true)
.SetDisplay("Trail After Break Even", "Start trailing only after break-even", "Risk")
;
_trailStartPoints = Param(nameof(TrailStartPoints), 40)
.SetDisplay("Trail Start", "Points of profit before trailing is considered", "Risk")
;
_trailStepPoints = Param(nameof(TrailStepPoints), 10)
.SetDisplay("Trail Step", "Price step that triggers a new trailing recalculation", "Risk")
;
_trailOffsetPoints = Param(nameof(TrailOffsetPoints), 10)
.SetDisplay("Trail Offset", "Points added to the stop on every trailing step", "Risk")
;
_initialStopPoints = Param(nameof(InitialStopPoints), 200)
.SetDisplay("Initial Stop", "Initial stop distance used before trailing", "Risk")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle subscription for monitoring", "Data");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
_priceStep = 0;
_longStop = 0;
_shortStop = 0;
_longBreakEvenActive = false;
_shortBreakEvenActive = false;
_lastEntryPrice = 0;
_smaFast = default;
_smaSlow = default;
_cooldown = 0;
_lastSignal = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_priceStep = Security?.PriceStep ?? 1m;
if (_priceStep <= 0m)
_priceStep = 1m;
_smaFast = new SimpleMovingAverage { Length = 10 };
_smaSlow = new SimpleMovingAverage { Length = 30 };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_smaFast, _smaSlow, ProcessCandleWithIndicators).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (Position == 0)
{
ResetState();
return;
}
if (trade.Order.Side == Sides.Buy && Position > 0)
{
_longStop = InitialStopPoints > 0 ? _lastEntryPrice - InitialStopPoints * _priceStep : 0m;
_longBreakEvenActive = false;
}
else if (trade.Order.Side == Sides.Sell && Position < 0)
{
_shortStop = InitialStopPoints > 0 ? _lastEntryPrice + InitialStopPoints * _priceStep : 0m;
_shortBreakEvenActive = false;
}
}
private void ProcessCandleWithIndicators(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldown > 0)
{
_cooldown--;
}
else
{
var signal = fast > slow ? 1 : fast < slow ? -1 : 0;
if (signal == 1 && _lastSignal != 1 && Position == 0)
{
BuyMarket();
_lastEntryPrice = candle.ClosePrice;
_lastSignal = 1;
_cooldown = 20;
}
else if (signal == -1 && _lastSignal != -1 && Position == 0)
{
SellMarket();
_lastEntryPrice = candle.ClosePrice;
_lastSignal = -1;
_cooldown = 20;
}
}
ManageLongPosition(candle);
ManageShortPosition(candle);
}
private void ManageLongPosition(ICandleMessage candle)
{
if (Position <= 0)
{
_longStop = 0m;
_longBreakEvenActive = false;
return;
}
var entryPrice = _lastEntryPrice;
if (entryPrice <= 0m)
return;
var currentPrice = candle.ClosePrice;
var profitPoints = (currentPrice - entryPrice) / _priceStep;
if (EnableBreakEven && !_longBreakEvenActive && profitPoints >= BreakEvenTriggerPoints && BreakEvenTriggerPoints > 0)
{
var newStop = BreakEvenOffsetPoints > 0
? entryPrice + BreakEvenOffsetPoints * _priceStep
: entryPrice;
if (newStop < currentPrice)
{
_longStop = Math.Max(_longStop, newStop);
_longBreakEvenActive = true;
}
}
if (!EnableTrailing || TrailOffsetPoints <= 0 || TrailStepPoints <= 0)
return;
var requireBreakEven = TrailAfterBreakEven && EnableBreakEven;
if (requireBreakEven && !_longBreakEvenActive)
return;
var baseStop = requireBreakEven
? (_longStop > 0m ? _longStop : (BreakEvenOffsetPoints > 0 ? entryPrice + BreakEvenOffsetPoints * _priceStep : entryPrice))
: (InitialStopPoints > 0 ? entryPrice - InitialStopPoints * _priceStep : (_longStop > 0m ? _longStop : 0m));
if (baseStop <= 0m)
return;
if (!requireBreakEven && profitPoints < TrailStartPoints)
return;
if (requireBreakEven)
{
var baseDistance = (currentPrice - baseStop) / _priceStep;
if (baseDistance < TrailStartPoints)
return;
}
var startPrice = requireBreakEven
? baseStop + (TrailStartPoints - TrailStepPoints) * _priceStep
: entryPrice + (TrailStartPoints - TrailStepPoints) * _priceStep;
var stepDistance = TrailStepPoints * _priceStep;
if (stepDistance <= 0m)
return;
var openSteps = (currentPrice - startPrice) / stepDistance;
if (openSteps <= 0m)
return;
var stepOpenPrice = (int)Math.Floor(openSteps);
var currentStopSteps = _longStop > baseStop
? (int)Math.Floor((_longStop - baseStop) / (TrailOffsetPoints * _priceStep))
: 0;
if (stepOpenPrice <= currentStopSteps)
return;
var proposedStop = baseStop + stepOpenPrice * TrailOffsetPoints * _priceStep;
var maxStop = candle.LowPrice - _priceStep;
if (proposedStop >= maxStop)
proposedStop = maxStop;
if (proposedStop > _longStop && proposedStop < currentPrice)
_longStop = proposedStop;
if (_longStop > 0m && candle.LowPrice <= _longStop)
SellMarket(Position);
}
private void ManageShortPosition(ICandleMessage candle)
{
if (Position >= 0)
{
_shortStop = 0m;
_shortBreakEvenActive = false;
return;
}
var entryPrice = _lastEntryPrice;
if (entryPrice <= 0m)
return;
var currentPrice = candle.ClosePrice;
var profitPoints = (entryPrice - currentPrice) / _priceStep;
if (EnableBreakEven && !_shortBreakEvenActive && profitPoints >= BreakEvenTriggerPoints && BreakEvenTriggerPoints > 0)
{
var newStop = BreakEvenOffsetPoints > 0
? entryPrice - BreakEvenOffsetPoints * _priceStep
: entryPrice;
if (newStop > currentPrice)
{
_shortStop = _shortStop == 0m ? newStop : Math.Min(_shortStop, newStop);
_shortBreakEvenActive = true;
}
}
if (!EnableTrailing || TrailOffsetPoints <= 0 || TrailStepPoints <= 0)
return;
var requireBreakEven = TrailAfterBreakEven && EnableBreakEven;
if (requireBreakEven && !_shortBreakEvenActive)
return;
var baseStop = requireBreakEven
? (_shortStop > 0m ? _shortStop : (BreakEvenOffsetPoints > 0 ? entryPrice - BreakEvenOffsetPoints * _priceStep : entryPrice))
: (InitialStopPoints > 0 ? entryPrice + InitialStopPoints * _priceStep : (_shortStop > 0m ? _shortStop : 0m));
if (baseStop <= 0m)
return;
if (!requireBreakEven && profitPoints < TrailStartPoints)
return;
if (requireBreakEven)
{
var baseDistance = (baseStop - currentPrice) / _priceStep;
if (baseDistance < TrailStartPoints)
return;
}
var startPrice = requireBreakEven
? baseStop - (TrailStartPoints - TrailStepPoints) * _priceStep
: entryPrice - (TrailStartPoints - TrailStepPoints) * _priceStep;
var stepDistance = TrailStepPoints * _priceStep;
if (stepDistance <= 0m)
return;
var openSteps = (startPrice - currentPrice) / stepDistance;
if (openSteps <= 0m)
return;
var stepOpenPrice = (int)Math.Floor(openSteps);
var currentStopSteps = _shortStop > 0m
? (int)Math.Floor((baseStop - _shortStop) / (TrailOffsetPoints * _priceStep))
: 0;
if (stepOpenPrice <= currentStopSteps)
return;
var proposedStop = baseStop - stepOpenPrice * TrailOffsetPoints * _priceStep;
var minStop = candle.HighPrice + _priceStep;
if (proposedStop <= minStop)
proposedStop = minStop;
if ((_shortStop == 0m || proposedStop < _shortStop) && proposedStop > currentPrice)
_shortStop = proposedStop;
if (_shortStop > 0m && candle.HighPrice >= _shortStop)
BuyMarket(Math.Abs(Position));
}
private void ResetState()
{
_longStop = 0m;
_shortStop = 0m;
_longBreakEvenActive = false;
_shortBreakEvenActive = false;
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Sides
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class trail_sl_manager_strategy(Strategy):
"""SMA crossover (10/30) with break-even and trailing stop management."""
def __init__(self):
super(trail_sl_manager_strategy, self).__init__()
self._enable_be = self.Param("EnableBreakEven", True).SetDisplay("Break Even", "Enable break-even stop adjustment", "Risk")
self._be_trigger = self.Param("BreakEvenTriggerPoints", 20).SetDisplay("Break Even Trigger", "Points before break-even", "Risk")
self._be_offset = self.Param("BreakEvenOffsetPoints", 10).SetDisplay("Break Even Offset", "Extra points locked at break-even", "Risk")
self._enable_trailing = self.Param("EnableTrailing", True).SetDisplay("Trailing", "Enable trailing stop management", "Risk")
self._trail_after_be = self.Param("TrailAfterBreakEven", True).SetDisplay("Trail After Break Even", "Start trailing only after break-even", "Risk")
self._trail_start = self.Param("TrailStartPoints", 40).SetDisplay("Trail Start", "Points before trailing begins", "Risk")
self._trail_step = self.Param("TrailStepPoints", 10).SetDisplay("Trail Step", "Step for trailing recalculation", "Risk")
self._trail_offset = self.Param("TrailOffsetPoints", 10).SetDisplay("Trail Offset", "SL increment per trailing step", "Risk")
self._initial_stop = self.Param("InitialStopPoints", 200).SetDisplay("Initial Stop", "Initial stop distance", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candle subscription", "Data")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def _reset_state(self):
self._long_stop = 0.0
self._short_stop = 0.0
self._long_be_active = False
self._short_be_active = False
def OnReseted(self):
super(trail_sl_manager_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(trail_sl_manager_strategy, self).OnStarted2(time)
sec = self.Security
ps = 0.0
if sec is not None and sec.PriceStep is not None:
try:
ps = float(sec.PriceStep)
except:
ps = 0.0
self._price_step = ps if ps > 0 else 1.0
self._last_entry_price = 0.0
self._reset_state()
fast = SimpleMovingAverage()
fast.Length = 10
slow = SimpleMovingAverage()
slow.Length = 30
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(fast, slow, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnOwnTradeReceived(self, trade):
super(trail_sl_manager_strategy, self).OnOwnTradeReceived(trade)
if self.Position == 0:
self._reset_state()
return
if trade.Order.Side == Sides.Buy and self.Position > 0:
self._long_stop = self._last_entry_price - self._initial_stop.Value * self._price_step if self._initial_stop.Value > 0 else 0.0
self._long_be_active = False
elif trade.Order.Side == Sides.Sell and self.Position < 0:
self._short_stop = self._last_entry_price + self._initial_stop.Value * self._price_step if self._initial_stop.Value > 0 else 0.0
self._short_be_active = False
def OnProcess(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
fast = float(fast_val)
slow = float(slow_val)
if fast > slow and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._last_entry_price = float(candle.ClosePrice)
elif fast < slow and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._last_entry_price = float(candle.ClosePrice)
self._manage_long(candle)
self._manage_short(candle)
def _manage_long(self, candle):
if self.Position <= 0:
self._long_stop = 0.0
self._long_be_active = False
return
entry_price = self._last_entry_price
if entry_price <= 0:
return
ps = self._price_step
current_price = float(candle.ClosePrice)
profit_points = (current_price - entry_price) / ps
be_trigger = self._be_trigger.Value
be_offset = self._be_offset.Value
if self._enable_be.Value and not self._long_be_active and profit_points >= be_trigger and be_trigger > 0:
new_stop = entry_price + be_offset * ps if be_offset > 0 else entry_price
if new_stop < current_price:
self._long_stop = max(self._long_stop, new_stop)
self._long_be_active = True
trail_offset = self._trail_offset.Value
trail_step = self._trail_step.Value
if not self._enable_trailing.Value or trail_offset <= 0 or trail_step <= 0:
return
require_be = self._trail_after_be.Value and self._enable_be.Value
if require_be and not self._long_be_active:
return
if require_be:
base_stop = self._long_stop if self._long_stop > 0 else (entry_price + be_offset * ps if be_offset > 0 else entry_price)
else:
if self._initial_stop.Value > 0:
base_stop = entry_price - self._initial_stop.Value * ps
else:
base_stop = self._long_stop if self._long_stop > 0 else 0.0
if base_stop <= 0:
return
trail_start = self._trail_start.Value
if not require_be and profit_points < trail_start:
return
if require_be:
base_distance = (current_price - base_stop) / ps
if base_distance < trail_start:
return
if require_be:
start_price = base_stop + (trail_start - trail_step) * ps
else:
start_price = entry_price + (trail_start - trail_step) * ps
step_distance = trail_step * ps
if step_distance <= 0:
return
open_steps = (current_price - start_price) / step_distance
if open_steps <= 0:
return
step_open_price = int(math.floor(open_steps))
if self._long_stop > base_stop:
current_stop_steps = int(math.floor((self._long_stop - base_stop) / (trail_offset * ps)))
else:
current_stop_steps = 0
if step_open_price <= current_stop_steps:
return
proposed_stop = base_stop + step_open_price * trail_offset * ps
max_stop = float(candle.LowPrice) - ps
if proposed_stop >= max_stop:
proposed_stop = max_stop
if proposed_stop > self._long_stop and proposed_stop < current_price:
self._long_stop = proposed_stop
if self._long_stop > 0 and float(candle.LowPrice) <= self._long_stop:
self.SellMarket()
def _manage_short(self, candle):
if self.Position >= 0:
self._short_stop = 0.0
self._short_be_active = False
return
entry_price = self._last_entry_price
if entry_price <= 0:
return
ps = self._price_step
current_price = float(candle.ClosePrice)
profit_points = (entry_price - current_price) / ps
be_trigger = self._be_trigger.Value
be_offset = self._be_offset.Value
if self._enable_be.Value and not self._short_be_active and profit_points >= be_trigger and be_trigger > 0:
new_stop = entry_price - be_offset * ps if be_offset > 0 else entry_price
if new_stop > current_price:
self._short_stop = new_stop if self._short_stop == 0 else min(self._short_stop, new_stop)
self._short_be_active = True
trail_offset = self._trail_offset.Value
trail_step = self._trail_step.Value
if not self._enable_trailing.Value or trail_offset <= 0 or trail_step <= 0:
return
require_be = self._trail_after_be.Value and self._enable_be.Value
if require_be and not self._short_be_active:
return
if require_be:
base_stop = self._short_stop if self._short_stop > 0 else (entry_price - be_offset * ps if be_offset > 0 else entry_price)
else:
if self._initial_stop.Value > 0:
base_stop = entry_price + self._initial_stop.Value * ps
else:
base_stop = self._short_stop if self._short_stop > 0 else 0.0
if base_stop <= 0:
return
trail_start = self._trail_start.Value
if not require_be and profit_points < trail_start:
return
if require_be:
base_distance = (base_stop - current_price) / ps
if base_distance < trail_start:
return
if require_be:
start_price = base_stop - (trail_start - trail_step) * ps
else:
start_price = entry_price - (trail_start - trail_step) * ps
step_distance = trail_step * ps
if step_distance <= 0:
return
open_steps = (start_price - current_price) / step_distance
if open_steps <= 0:
return
step_open_price = int(math.floor(open_steps))
if self._short_stop > 0:
current_stop_steps = int(math.floor((base_stop - self._short_stop) / (trail_offset * ps)))
else:
current_stop_steps = 0
if step_open_price <= current_stop_steps:
return
proposed_stop = base_stop - step_open_price * trail_offset * ps
min_stop = float(candle.HighPrice) + ps
if proposed_stop <= min_stop:
proposed_stop = min_stop
if (self._short_stop == 0 or proposed_stop < self._short_stop) and proposed_stop > current_price:
self._short_stop = proposed_stop
if self._short_stop > 0 and float(candle.HighPrice) >= self._short_stop:
self.BuyMarket()
def CreateClone(self):
return trail_sl_manager_strategy()