The NRTR Reversal strategy is a StockSharp port of the MetaTrader 4 expert "NRTR_Revers". The original system plots a Noise Reduction Trailing Range (NRTR) line derived from the Average True Range (ATR) and reverses positions whenever price convincingly breaks this adaptive barrier. The StockSharp version keeps the single-position behaviour of the expert advisor, mirrors the ATR-based offset calculation, and manages exits through the built-in protection module.
Trading logic
Subscribe to the main candle series configured by CandleType and process finished candles only, replicating the Bars counter check from MetaTrader.
Feed an AverageTrueRange indicator with period Period. The most recent ATR value is translated from price units into "points" (price steps) before being multiplied by AtrMultiplier / 10, just like the MQL expression MathRound(k * (iATR / Point) / 10).
Maintain a rolling cache of recent candles to rebuild the NRTR pivot. The lowest low (for an uptrend) or highest high (for a downtrend) over the last Period candles becomes the base pivot.
Shift the pivot by the ATR-based offset to form the trailing line:
Uptrend: line = lowestLow - offset.
Downtrend: line = highestHigh + offset.
Detect a reversal whenever either condition is met:
Close breakout: the latest candle close crosses the line by more than offset points.
Range expansion: the most recent Period / 2 candles extend beyond the line by at least ReverseDistancePoints points. This reproduces the secondary reversal test from the MQL code that looked further back in history.
When the direction flips, send a market order (BuyMarket or SellMarket) with volume TradeVolume + |Position|. This both closes the opposite exposure and opens the new position, matching the MetaTrader behaviour of closing and reversing immediately.
Exits are delegated to the risk manager started by StartProtection, which converts the configured stop-loss and take-profit distances from points into broker-specific price units.
Parameters
Name
Type
Default
Description
CandleType
DataType
15-minute timeframe
Candle series used for calculations.
TakeProfitPoints
decimal
4000
Take-profit distance expressed in instrument price steps. Set to zero to disable.
StopLossPoints
decimal
4000
Stop-loss distance in price steps. Set to zero to disable.
TrailingStopPoints
decimal
0
Reserved parameter for external trailing modules. Not used inside the strategy.
TradeVolume
decimal
0.1
Base volume (lots) mirrored from the MetaTrader setting.
Period
int
3
Number of candles used to compute the NRTR pivot.
ReverseDistancePoints
int
100
Additional breakout distance in points required for confirmation.
AtrMultiplier
decimal
3.0
Multiplier applied to ATR before building the offset.
Risk management
The strategy calls StartProtection with UnitTypes.Step, so the configured point distances are automatically converted into absolute price offsets based on Security.PriceStep.
If both stop-loss and take-profit are zero, StartProtection() is still called to enable StockSharp's position monitoring, replicating the safety checks used by the EA.
TrailingStopPoints is exposed for completeness but left for future extensions, because the original expert did not implement a trailing function despite declaring the parameter.
Implementation details
The strategy relies exclusively on the high-level API (SubscribeCandles().BindEx(...)) with indicator bindings; no manual indicator loops or prohibited GetValue calls are used.
A compact CandleSnapshot struct keeps only high/low/close values from recent candles, avoiding heavy ICandleMessage storage while still reproducing the NRTR lookback windows.
The ATR-to-points conversion honours the MetaTrader formula by dividing the ATR by the instrument step before applying the multiplier and rounding.
History trimming keeps the cache at Period * 3 candles to match the original lookback needs without uncontrolled growth.
Differences from the MetaTrader expert
Order closing is simplified: instead of iterating through every trade and calling OrderClose, the StockSharp port sends a single market order that both flatters the existing position and establishes the new direction.
Magic numbers, slippage and ticket-specific parameters are omitted because StockSharp manages orders differently.
Chart annotations are optional; when a chart area is available, the ATR series and own trades are plotted for debugging purposes.
Usage tips
Align TradeVolume with the exchange lot step (Security.VolumeStep) before enabling live trading.
Tune Period, AtrMultiplier, and ReverseDistancePoints together. Shorter periods require smaller reverse distances to avoid overtrading.
Set stop/target distances according to the instrument tick size. On instruments with large PriceStep, reduce the default 4000-point offsets to realistic levels.
Indicators
AverageTrueRange(Period) calculated on high/low/close prices.
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>
/// NRTR reversal strategy using ATR-based trailing stop.
/// Maintains a trailing line based on ATR distance from price extremes.
/// Reverses position when price crosses the trailing line.
/// </summary>
public class NrtrReversalStrategy : Strategy
{
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal _trailLine;
private decimal _extreme;
private int _trend; // 1 = up, -1 = down, 0 = init
private bool _isInitialized;
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public decimal AtrMultiplier { get => _atrMultiplier.Value; set => _atrMultiplier.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public NrtrReversalStrategy()
{
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetDisplay("ATR Period", "ATR period for trailing", "Indicators");
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetDisplay("ATR Multiplier", "ATR multiplier for trailing distance", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromDays(1).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_trailLine = 0m;
_extreme = 0m;
_trend = 0;
_isInitialized = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_isInitialized = false;
_trend = 0;
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
var offset = atrValue * AtrMultiplier;
if (!_isInitialized)
{
_extreme = close;
_trailLine = close - offset;
_trend = 1;
_isInitialized = true;
return;
}
if (_trend == 1)
{
if (close > _extreme)
_extreme = close;
_trailLine = Math.Max(_trailLine, _extreme - offset);
if (close < _trailLine)
{
// Switch to downtrend
_trend = -1;
_extreme = close;
_trailLine = close + offset;
if (Position > 0)
SellMarket();
SellMarket();
}
else if (Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
}
else
{
if (close < _extreme)
_extreme = close;
_trailLine = Math.Min(_trailLine, _extreme + offset);
if (close > _trailLine)
{
// Switch to uptrend
_trend = 1;
_extreme = close;
_trailLine = close - offset;
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
}
}
}
import clr
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
from StockSharp.Algo.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class nrtr_reversal_strategy(Strategy):
"""NRTR reversal strategy using ATR-based trailing stop.
Maintains a trailing line based on ATR distance from price extremes.
Reverses position when price crosses the trailing line."""
def __init__(self):
super(nrtr_reversal_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR period for trailing", "Indicators")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0) \
.SetDisplay("ATR Multiplier", "ATR multiplier for trailing distance", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromDays(1))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._trail_line = 0.0
self._extreme = 0.0
self._trend = 0
self._is_initialized = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def AtrPeriod(self):
return self._atr_period.Value
@property
def AtrMultiplier(self):
return self._atr_multiplier.Value
def OnReseted(self):
super(nrtr_reversal_strategy, self).OnReseted()
self._trail_line = 0.0
self._extreme = 0.0
self._trend = 0
self._is_initialized = False
def OnStarted2(self, time):
super(nrtr_reversal_strategy, self).OnStarted2(time)
self._is_initialized = False
self._trend = 0
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(atr, self._process_candle).Start()
def _process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
atr_val = float(atr_value)
offset = atr_val * float(self.AtrMultiplier)
if not self._is_initialized:
self._extreme = close
self._trail_line = close - offset
self._trend = 1
self._is_initialized = True
return
if self._trend == 1:
if close > self._extreme:
self._extreme = close
candidate = self._extreme - offset
if candidate > self._trail_line:
self._trail_line = candidate
if close < self._trail_line:
# Switch to downtrend
self._trend = -1
self._extreme = close
self._trail_line = close + offset
if self.Position > 0:
self.SellMarket()
self.SellMarket()
elif self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
else:
if close < self._extreme:
self._extreme = close
candidate = self._extreme + offset
if candidate < self._trail_line:
self._trail_line = candidate
if close > self._trail_line:
# Switch to uptrend
self._trend = 1
self._extreme = close
self._trail_line = close - offset
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return nrtr_reversal_strategy()