TrailingStopFrCnStrategy is a StockSharp port of the MetaTrader expert advisor TrailingStopFrCn.mq4. The original script manages stop-loss levels for existing positions using a mix of fixed trailing distances, Bill Williams fractals, or recent candle highs/lows. This port keeps the same flexibility while integrating with the high-level StockSharp API: the strategy subscribes to candles and Level 1 quotes, monitors the current net position, and automatically updates a protective stop order.
Unlike an entry strategy, TrailingStopFrCn focuses solely on risk management. It does not open new positions. Instead, it tracks the existing position of Strategy.Security, cancels obsolete stop orders when the position flips, and sends a single aggregated stop order that follows the logic of the MetaTrader advisor.
Trailing logic
Fixed trailing distance – when TrailingStopPips is greater than zero, the strategy behaves like the original MQL parameter TrailingStop. For long positions the stop is placed at bestBid - distance, for short positions at bestAsk + distance, with distance = TrailingStopPips × pip size.
Fractal trailing – when TrailingStopPips = 0 and TrailingMode = Fractals, the strategy detects five-bar Bill Williams fractals. Each finished candle is added to an internal buffer and, once enough history is available, the candle two bars back is evaluated as a potential fractal. The most recent fractal that is at least MinStopDistancePips away from the current price becomes the new stop candidate.
Candle trailing – when TrailingStopPips = 0 and TrailingMode = Candles, the strategy scans up to the last 99 closed candles and selects the first low (for longs) or high (for shorts) that is separated from the current price by at least MinStopDistancePips.
After computing the candidate level the strategy enforces the same protection rules as the MQL version:
OnlyProfit prevents moving the stop unless the new level would lock in profit (stop above entry for longs, stop below entry for shorts).
OnlyWithoutLoss stops further trailing once the active stop-loss already protects the position from losses (in the original script the trailing process stops after breakeven is reached).
The stop is only moved in the favourable direction: upwards for long positions and downwards for short positions.
Because StockSharp tracks a single net position per security, the stop order volume equals Math.Abs(Position) and all underlying fills are aggregated.
Parameters
Parameter
Description
OnlyProfit
Move the stop-loss only when the new level secures profit relative to the average entry price. Mirrors the OnlyProfit flag from MQL.
OnlyWithoutLoss
Stop trailing once the active stop-loss is at or beyond the entry price. This replicates OnlyWithoutLoss from the original advisor.
TrailingStopPips
Fixed trailing distance expressed in pips. Set to zero to activate fractal or candle trailing.
MinStopDistancePips
Minimal distance (in pips) between market price and stop-loss. Use it to emulate the broker MODE_STOPLEVEL restriction.
TrailingMode
Chooses the trailing source when TrailingStopPips = 0. Options: Fractals (Bill Williams five-bar fractals) or Candles (recent lows/highs).
CandleType
Candle data type used to build fractals or to search for swing points. The default is one-hour time frame.
Behavioural notes
The strategy subscribes to Level 1 data to access best bid/ask prices. Fixed-distance trailing reacts immediately to Level 1 updates, while fractal/candle trailing updates when new candles arrive.
When the position direction changes the current stop order is cancelled before the new order is submitted.
If no stop candidate is available (e.g., not enough candles), the strategy keeps the existing stop.
If the broker does not enforce a minimum stop distance you can leave MinStopDistancePips at zero.
Differences from the MetaTrader version
StockSharp maintains a net position, so individual MetaTrader “tickets” are not tracked. The stop order covers the entire aggregated position.
The Magic filter is not required: the strategy already operates on its own security context.
Trailing updates are driven by finished candles plus Level 1 data instead of a one-second polling loop.
Visual chart objects from the original EA are not recreated; instead, you can use StockSharp’s charting helpers when running the sample UI.
Usage tips
Run the strategy together with any entry logic that opens positions on the same Security. TrailingStopFrCn will automatically attach a stop order once the position appears.
Adjust CandleType to match the timeframe that should be analysed for fractals or swing points. Higher timeframes smooth trailing levels, while lower timeframes react faster.
Calibrate MinStopDistancePips according to your broker’s stop-level limitations. Setting it too low may lead to rejected orders.
When testing on historical data ensure that candle subscription and Level 1 messages are available in the data source so that the trailing logic can trigger correctly.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// TrailingStopFrCn: EMA crossover with ATR trailing stops.
/// </summary>
public class TrailingStopFrCnStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public TrailingStopFrCnStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 12)
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 26)
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastEmaLength { get => _fastEmaLength.Value; set => _fastEmaLength.Value = value; }
public int SlowEmaLength { get => _slowEmaLength.Value; set => _slowEmaLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fastEma, slowEma, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fastEma); DrawIndicator(area, slowEma); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0) { _prevFast = fastVal; _prevSlow = slowVal; return; }
var close = candle.ClosePrice;
if (Position > 0)
{
if ((fastVal < slowVal && _prevFast >= _prevSlow) || close <= _entryPrice - atrVal * 2m) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if ((fastVal > slowVal && _prevFast <= _prevSlow) || close >= _entryPrice + atrVal * 2m) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow) { _entryPrice = close; BuyMarket(); }
else if (fastVal < slowVal && _prevFast >= _prevSlow) { _entryPrice = close; SellMarket(); }
}
_prevFast = fastVal; _prevSlow = slowVal;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
class trailing_stop_fr_cn_strategy(Strategy):
def __init__(self):
super(trailing_stop_fr_cn_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_ema_length = self.Param("FastEmaLength", 12) \
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 26) \
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastEmaLength(self):
return self._fast_ema_length.Value
@property
def SlowEmaLength(self):
return self._slow_ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(trailing_stop_fr_cn_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastEmaLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowEmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if (fv < sv and self._prev_fast >= self._prev_slow) or close <= self._entry_price - av * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if (fv > sv and self._prev_fast <= self._prev_slow) or close >= self._entry_price + av * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if fv > sv and self._prev_fast <= self._prev_slow:
self._entry_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(trailing_stop_fr_cn_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def CreateClone(self):
return trailing_stop_fr_cn_strategy()