Trailing Stop Strategy
Overview
This strategy implements the trailing stop logic from the original MQL script TRAILING.mq4. It manages an existing open position and closes it when the market moves to a specified profit target or hits a stop loss. When the trailing parameter is enabled, the stop level follows the price to lock in profits.
Parameters
- TakeProfit – profit distance from the entry price in absolute price units.
- StopLoss – maximum adverse distance allowed from the entry price.
- Trailing – distance used for dynamic trailing of the stop level.
- CandleType – candle series used to obtain price updates.
How It Works
- The strategy subscribes to the chosen candle series.
- After each finished candle the current position is evaluated.
- For long positions the strategy closes the position when profit exceeds TakeProfit or loss exceeds StopLoss.
- If Trailing is greater than zero, the stop level moves up with the price. When price falls below the trailing stop the position is closed.
- Short positions follow the same logic but in the opposite direction.
- Entry price is recorded from the first executed trade and reset when the position is closed.
Notes
- The strategy uses the high‑level API with
Bindfor processing candles. - It does not open new positions by itself; it only manages an already opened position.
- Parameters are exposed via
StrategyParamand can be optimized.
using System;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that combines moving-average entries with a trailing-stop exit.
/// </summary>
public class TrailingStopStrategy : Strategy
{
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _trailing;
private readonly StrategyParam<int> _fastMaPeriod;
private readonly StrategyParam<int> _slowMaPeriod;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _fastMa;
private ExponentialMovingAverage _slowMa;
private decimal _prevFastMa;
private decimal _prevSlowMa;
private bool _isInitialized;
private int _barsSinceExit;
/// <summary>
/// Profit target distance from entry price.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Stop loss distance from entry price.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Trailing stop distance.
/// </summary>
public decimal Trailing
{
get => _trailing.Value;
set => _trailing.Value = value;
}
/// <summary>
/// Fast moving average period.
/// </summary>
public int FastMaPeriod
{
get => _fastMaPeriod.Value;
set => _fastMaPeriod.Value = value;
}
/// <summary>
/// Slow moving average period.
/// </summary>
public int SlowMaPeriod
{
get => _slowMaPeriod.Value;
set => _slowMaPeriod.Value = value;
}
/// <summary>
/// Bars to wait after a full exit before re-entering.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Type of candles to process.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="TrailingStopStrategy"/> class.
/// </summary>
public TrailingStopStrategy()
{
_takeProfit = Param(nameof(TakeProfit), 3500m)
.SetDisplay("Take Profit", "Profit distance in price units", "Risk");
_stopLoss = Param(nameof(StopLoss), 1200m)
.SetDisplay("Stop Loss", "Loss distance in price units", "Risk");
_trailing = Param(nameof(Trailing), 800m)
.SetDisplay("Trailing", "Trailing stop distance", "Risk");
_fastMaPeriod = Param(nameof(FastMaPeriod), 6)
.SetDisplay("Fast MA", "Fast moving average period", "Indicator");
_slowMaPeriod = Param(nameof(SlowMaPeriod), 18)
.SetDisplay("Slow MA", "Slow moving average period", "Indicator");
_cooldownBars = Param(nameof(CooldownBars), 1)
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for price updates", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastMa = null;
_slowMa = null;
_prevFastMa = 0m;
_prevSlowMa = 0m;
_isInitialized = false;
_barsSinceExit = CooldownBars;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastMa = new ExponentialMovingAverage { Length = FastMaPeriod };
_slowMa = new ExponentialMovingAverage { Length = SlowMaPeriod };
Indicators.Add(_fastMa);
Indicators.Add(_slowMa);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent));
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var price = candle.ClosePrice;
var fastValue = _fastMa.Process(new DecimalIndicatorValue(_fastMa, price, candle.OpenTime) { IsFinal = true }).ToDecimal();
var slowValue = _slowMa.Process(new DecimalIndicatorValue(_slowMa, price, candle.OpenTime) { IsFinal = true }).ToDecimal();
if (!_fastMa.IsFormed || !_slowMa.IsFormed)
return;
if (!_isInitialized)
{
_prevFastMa = fastValue;
_prevSlowMa = slowValue;
_isInitialized = true;
return;
}
if (Position != 0)
{
_prevFastMa = fastValue;
_prevSlowMa = slowValue;
return;
}
var crossUp = _prevFastMa <= _prevSlowMa && fastValue > slowValue;
var crossDown = _prevFastMa >= _prevSlowMa && fastValue < slowValue;
if (crossUp)
BuyMarket();
else if (crossDown)
SellMarket();
_prevFastMa = fastValue;
_prevSlowMa = slowValue;
}
}
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, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class trailing_stop_strategy(Strategy):
def __init__(self):
super(trailing_stop_strategy, self).__init__()
self._take_profit = self.Param("TakeProfit", 3500.0) \
.SetDisplay("Take Profit", "Profit distance in price units", "Risk")
self._stop_loss = self.Param("StopLoss", 1200.0) \
.SetDisplay("Stop Loss", "Loss distance in price units", "Risk")
self._trailing = self.Param("Trailing", 800.0) \
.SetDisplay("Trailing", "Trailing stop distance", "Risk")
self._fast_ma_period = self.Param("FastMaPeriod", 6) \
.SetDisplay("Fast MA", "Fast moving average period", "Indicator")
self._slow_ma_period = self.Param("SlowMaPeriod", 18) \
.SetDisplay("Slow MA", "Slow moving average period", "Indicator")
self._cooldown_bars = self.Param("CooldownBars", 1) \
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles for price updates", "General")
self._fast_ma = None
self._slow_ma = None
self._entry_price = 0.0
self._stop_price = 0.0
self._prev_fast_ma = 0.0
self._prev_slow_ma = 0.0
self._is_initialized = False
self._bars_since_exit = 0
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def Trailing(self):
return self._trailing.Value
@Trailing.setter
def Trailing(self, value):
self._trailing.Value = value
@property
def FastMaPeriod(self):
return self._fast_ma_period.Value
@FastMaPeriod.setter
def FastMaPeriod(self, value):
self._fast_ma_period.Value = value
@property
def SlowMaPeriod(self):
return self._slow_ma_period.Value
@SlowMaPeriod.setter
def SlowMaPeriod(self, value):
self._slow_ma_period.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(trailing_stop_strategy, self).OnStarted2(time)
self._fast_ma = ExponentialMovingAverage()
self._fast_ma.Length = self.FastMaPeriod
self._slow_ma = ExponentialMovingAverage()
self._slow_ma.Length = self.SlowMaPeriod
self.Indicators.Add(self._fast_ma)
self.Indicators.Add(self._slow_ma)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent))
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
price = candle.ClosePrice
fast_value = float(process_float(self._fast_ma, price, candle.OpenTime, True))
slow_value = float(process_float(self._slow_ma, price, candle.OpenTime, True))
if not self._fast_ma.IsFormed or not self._slow_ma.IsFormed:
return
if not self._is_initialized:
self._prev_fast_ma = fast_value
self._prev_slow_ma = slow_value
self._is_initialized = True
return
if self.Position != 0:
self._prev_fast_ma = fast_value
self._prev_slow_ma = slow_value
return
cross_up = self._prev_fast_ma <= self._prev_slow_ma and fast_value > slow_value
cross_down = self._prev_fast_ma >= self._prev_slow_ma and fast_value < slow_value
if cross_up:
self.BuyMarket()
elif cross_down:
self.SellMarket()
self._prev_fast_ma = fast_value
self._prev_slow_ma = slow_value
def OnReseted(self):
super(trailing_stop_strategy, self).OnReseted()
self._fast_ma = None
self._slow_ma = None
self._entry_price = 0.0
self._stop_price = 0.0
self._prev_fast_ma = 0.0
self._prev_slow_ma = 0.0
self._is_initialized = False
self._bars_since_exit = self.CooldownBars
def CreateClone(self):
return trailing_stop_strategy()