This strategy is a direct conversion of the MetaTrader 5 expert advisor "TP SL Trailing". The strategy does not generate entries by itself. Instead, it manages existing positions by installing a protective stop-loss and take-profit and by trailing the stop once the trade becomes profitable. The pip-based configuration matches the parameters of the original script and allows the logic to be attached to any symbol supported by StockSharp.
Trading Logic
When a new position appears, the strategy can optionally set an initial stop-loss and take-profit using the configured pip distances. This behavior is controlled by the Only Zero Values flag, just as in the original expert advisor.
For long positions, the strategy moves the stop-loss upward once the unrealized profit exceeds the sum of the trailing stop and the trailing step. The stop is moved to current price - trailing stop, guaranteeing that a minimum portion of the profit is locked in.
For short positions, the strategy mirrors the same idea and moves the stop downward once the profit exceeds the trailing thresholds.
If both the trailing stop and the trailing step are zero, the strategy leaves the stop-loss untouched.
The take-profit level is never trailed. It is only set during the initial placement phase when Only Zero Values is enabled, fully replicating the MQL behavior.
Parameters
Name
Description
CandleType
Timeframe of the candles used to track price movements. A faster timeframe improves trailing accuracy.
StopLossPips
Distance in pips between the entry price and the initial stop-loss. Applied only when Only Zero Values is enabled.
TakeProfitPips
Distance in pips between the entry price and the initial take-profit. Applied only when Only Zero Values is enabled.
TrailingStopPips
Core trailing distance in pips. Defines how far behind the current price the stop should remain after activation.
TrailingStepPips
Additional pip buffer that must be exceeded before the stop moves again. Prevents over-frequent stop updates.
OnlyZeroValues
Matches the original EA flag. When enabled, initial protective orders are created only for positions that currently have no stop-loss or take-profit assigned.
Conversion Notes
Pip distances are converted to price units using the security's PriceStep. This keeps the logic instrument-agnostic and mirrors the 3/5-digit adjustment in the MQL version.
Protective orders are re-registered whenever the trailing logic moves the stop-loss. Active orders from a previous position are cancelled automatically when the position size returns to zero.
All code comments are written in English, while this documentation is intentionally detailed to help reproduce every decision made during the porting process.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// TP SL Trailing strategy. Uses EMA with price crossover for entries.
/// </summary>
public class TpSlTrailingStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaPeriod;
private decimal? _prevClose;
private decimal? _prevEma;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public TpSlTrailingStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
_emaPeriod = Param(nameof(EmaPeriod), 20).SetGreaterThanZero().SetDisplay("EMA Period", "EMA lookback", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose = null;
_prevEma = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevClose = null; _prevEma = null;
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ema, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, ema); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal emaVal)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
if (!IsFormedAndOnlineAndAllowTrading()) { _prevClose = close; _prevEma = emaVal; return; }
if (_prevClose == null || _prevEma == null) { _prevClose = close; _prevEma = emaVal; return; }
if (_prevClose.Value < _prevEma.Value && close >= emaVal && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
else if (_prevClose.Value > _prevEma.Value && close <= emaVal && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
_prevClose = close; _prevEma = emaVal;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class tp_sl_trailing_strategy(Strategy):
"""EMA price crossover for entries."""
def __init__(self):
super(tp_sl_trailing_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20).SetGreaterThanZero().SetDisplay("EMA Period", "EMA lookback", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(tp_sl_trailing_strategy, self).OnReseted()
self._prev_close = 0
self._prev_ema = 0
def OnStarted2(self, time):
super(tp_sl_trailing_strategy, self).OnStarted2(time)
self._prev_close = 0
self._prev_ema = 0
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(ema, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def OnProcess(self, candle, ema_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
if self._prev_close == 0 or self._prev_ema == 0:
self._prev_close = close
self._prev_ema = float(ema_val)
return
ema_f = float(ema_val)
if self._prev_close < self._prev_ema and close >= ema_f and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_close > self._prev_ema and close <= ema_f and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_close = close
self._prev_ema = ema_f
def CreateClone(self):
return tp_sl_trailing_strategy()