NRTR Reversal — это порт стратегии MetaTrader 4 «NRTR_Revers» на платформу StockSharp. Исходный советник строит линию Noise Reduction Trailing Range (NRTR) на основе индикатора ATR и переворачивает позицию, когда цена уверенно пробивает эту адаптивную границу. Реализация для StockSharp сохраняет модель «одна позиция одновременно», повторяет расчёт ATR-смещения и передаёт выходы встроенному модулю защиты.
Торговая логика
Подписка на свечи, указанные параметром CandleType, и обработка только завершённых баров — аналог проверки счётчика Bars в MetaTrader.
Индикатор AverageTrueRange с периодом Period получает каждую свечу. Его последнее значение переводится из ценовых единиц в «пункты» (шаги цены) и умножается на AtrMultiplier / 10, что соответствует формуле MathRound(k * (iATR / Point) / 10) в MQL.
Поддерживается скользящее окно свечей для расчёта опорной точки NRTR. Минимум за Period свечей (для ап-тренда) или максимум (для даун-тренда) выступает базовой точкой.
Опорная точка смещается на величину ATR:
Ап-тренд: line = lowestLow - offset.
Даун-тренд: line = highestHigh + offset.
Переворот тренда происходит, когда выполняется одно из условий:
Пробой по закрытию: цена закрытия последней свечи ушла за линию больше чем на offset пунктов.
Расширение диапазона: последние Period / 2 свечей заходят за линию как минимум на ReverseDistancePoints пунктов. Это повторяет дополнительную проверку из MQL, которая анализировала более ранние бары.
При смене направления отправляется рыночная заявка (BuyMarket или SellMarket) объёмом TradeVolume + |Position|, что одновременно закрывает противоположную позицию и открывает новую — так же, как в MetaTrader.
Выход из позиции передан в StartProtection, который автоматически превращает стоп и тейк из пунктов в реальные ценовые величины брокера.
Параметры
Название
Тип
Значение по умолчанию
Описание
CandleType
DataType
Таймфрейм 15 минут
Свечи, используемые в расчётах.
TakeProfitPoints
decimal
4000
Дистанция тейк-профита в шагах цены. Ноль отключает тейк.
StopLossPoints
decimal
4000
Дистанция стоп-лосса в шагах цены. Ноль отключает стоп.
TrailingStopPoints
decimal
0
Резервный параметр для внешнего трейлинга. В стратегии не используется.
TradeVolume
decimal
0.1
Базовый объём (лоты), перенесённый из MetaTrader.
Period
int
3
Количество свечей для расчёта опорной точки NRTR.
ReverseDistancePoints
int
100
Дополнительная дистанция подтверждения пробоя в пунктах.
AtrMultiplier
decimal
3.0
Множитель ATR перед построением смещения.
Управление рисками
Стратегия вызывает StartProtection с параметрами в UnitTypes.Step, поэтому указанные в пунктах дистанции автоматически переводятся в абсолютные цены на основе Security.PriceStep.
Даже если стоп и тейк выключены (равны нулю), StartProtection() запускается для контроля позиций StockSharp — как и в оригинальном советнике.
Параметр TrailingStopPoints оставлен для совместимости: в MQL он объявлен, но логика трейлинга отсутствовала.
Особенности реализации
Используется только высокоуровневый API (SubscribeCandles().BindEx(...)); вручную индикаторы не пересчитываются, запрещённых GetValue нет.
Компактная структура CandleSnapshot хранит только High/Low/Close последних свечей, что повторяет нужные окна NRTR без тяжёлых объектов ICandleMessage.
Конвертация ATR в пункты выполняется по оригинальной формуле: значение делится на шаг цены, затем умножается на коэффициент и округляется.
История ограничена Period * 3 свечами, чтобы предотвратить рост памяти при длительной работе.
Отличия от версии MetaTrader
Закрытие сделок упрощено: вместо перебора ордеров и OrderClose отправляется одна рыночная заявка, которая и закрывает старую позицию, и открывает новую.
Magic number, проскальзывание и номера тикетов опущены — в StockSharp эти параметры обрабатываются иначе.
Графические элементы необязательны: при наличии области графика отображаются ATR и сделки для удобства анализа.
Практические советы
Перед реальной торговлей согласуйте TradeVolume с шагом лота инструмента (Security.VolumeStep).
Настраивайте Period, AtrMultiplier и ReverseDistancePoints совместно: чем короче период, тем меньше дистанция разворота во избежание гиперторговли.
Подберите величины стопов и тейков под размер тика. Для инструментов с крупным PriceStep уменьшите стандартные 4000 пунктов до реалистичных значений.
Индикаторы
AverageTrueRange(Period) на данных High/Low/Close.
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()