Стратегия NRTR Trailing Stop
Эта стратегия следует за рыночным трендом с помощью индикатора NRTR (Nick R's Trend Reverse). Алгоритм вычисляет уровень трейлинг-стопа, основанный на среднем диапазоне последних свечей. При пробое этого уровня позиция разворачивается в сторону пробоя. Система работает как в длинную, так и в короткую сторону и включает необязательные стоп-лосс и тейк-профит.
Параметр длины NRTR определяет чувствительность трейлинг-стопа: меньший период реагирует быстрее, но может давать лишние сигналы; больший период фильтрует шум. Дополнительный сдвиг по разрядам адаптирует индикатор к инструментам с разными масштабами цен. Стратегия подписывается на свечи выбранного таймфрейма и рассчитывает значения NRTR на каждой завершённой свече.
Детали
- Логика входа:
- Покупка: цена пересекает уровень NRTR вверх после нисходящего тренда.
- Продажа: цена пересекает уровень NRTR вниз после восходящего тренда.
- Логика выхода:
- Позиция переворачивается при противоположном пробое.
- Стопы: опциональные стоп-лосс и тейк-профит через
StartProtection.
- Значения по умолчанию:
Length = 10
DigitsShift = 0
TakeProfit = 2000 пунктов
StopLoss = 1000 пунктов
CandleType = свечи 1 часа
- Фильтры:
- Категория: Следование тренду
- Направление: Оба
- Индикаторы: NRTR, ATR
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Гибкий
- Сезонность: Нет
- Нейросети: Нет
- Дивергенции: Нет
- Уровень риска: Средний
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// NRTR trailing stop strategy based on the NRTR indicator.
/// Opens long when trend turns up and short when trend turns down.
/// Includes optional stop-loss and take-profit.
/// </summary>
public class NrtrTrailingStopStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<int> _digitsShift;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<DataType> _candleType;
private decimal _price;
private decimal _value;
private int _trend;
private bool _isInitialized;
/// <summary>
/// Number of bars for average range calculation.
/// </summary>
public int Length
{
get => _length.Value;
set => _length.Value = value;
}
/// <summary>
/// Digits adjustment for indicator sensitivity.
/// </summary>
public int DigitsShift
{
get => _digitsShift.Value;
set => _digitsShift.Value = value;
}
/// <summary>
/// Take-profit in price points.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Stop-loss in price points.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Type of candles used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="NrtrTrailingStopStrategy"/>.
/// </summary>
public NrtrTrailingStopStrategy()
{
_length = Param(nameof(Length), 20)
.SetGreaterThanZero()
.SetDisplay("NRTR Length", "Number of bars for average range", "Indicator")
.SetOptimize(5, 20, 5);
_digitsShift = Param(nameof(DigitsShift), 0)
.SetDisplay("Digits Shift", "Adjustment for price digits", "Indicator");
_takeProfit = Param(nameof(TakeProfit), 2000m)
.SetNotNegative()
.SetDisplay("Take Profit (pts)", "Take profit level in points", "Risk")
.SetOptimize(500m, 3000m, 500m);
_stopLoss = Param(nameof(StopLoss), 1000m)
.SetNotNegative()
.SetDisplay("Stop Loss (pts)", "Stop loss level in points", "Risk")
.SetOptimize(500m, 3000m, 500m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for processing", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_price = 0m;
_value = 0m;
_trend = 0;
_isInitialized = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var atr = new AverageTrueRange { Length = Length };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!atrValue.IsFormed)
return;
var atr = atrValue.GetValue<decimal>();
var dK = atr / Length * (decimal)Math.Pow(10, -DigitsShift);
if (!_isInitialized)
{
_price = candle.ClosePrice;
_value = _price;
_trend = 0;
_isInitialized = true;
return;
}
var isOnline = IsFormedAndOnlineAndAllowTrading();
if (_trend >= 0)
{
_price = Math.Max(_price, candle.ClosePrice);
_value = Math.Max(_value, _price * (1m - dK));
if (candle.ClosePrice < _value)
{
_price = candle.ClosePrice;
_value = _price * (1m + dK);
_trend = -1;
if (isOnline && Position >= 0)
SellMarket();
}
}
else
{
_price = Math.Min(_price, candle.ClosePrice);
_value = Math.Min(_value, _price * (1m + dK));
if (candle.ClosePrice > _value)
{
_price = candle.ClosePrice;
_value = _price * (1m - dK);
_trend = 1;
if (isOnline && Position <= 0)
BuyMarket();
}
}
}
}
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_trailing_stop_strategy(Strategy):
def __init__(self):
super(nrtr_trailing_stop_strategy, self).__init__()
self._length = self.Param("Length", 20) \
.SetDisplay("NRTR Length", "Number of bars for average range", "Indicator")
self._digits_shift = self.Param("DigitsShift", 0) \
.SetDisplay("Digits Shift", "Adjustment for price digits", "Indicator")
self._take_profit = self.Param("TakeProfit", 2000.0) \
.SetDisplay("Take Profit (pts)", "Take profit level in points", "Risk")
self._stop_loss = self.Param("StopLoss", 1000.0) \
.SetDisplay("Stop Loss (pts)", "Stop loss level in points", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles for processing", "General")
self._price = 0.0
self._value = 0.0
self._trend = 0
self._is_initialized = False
@property
def length(self):
return self._length.Value
@property
def digits_shift(self):
return self._digits_shift.Value
@property
def take_profit(self):
return self._take_profit.Value
@property
def stop_loss(self):
return self._stop_loss.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(nrtr_trailing_stop_strategy, self).OnReseted()
self._price = 0.0
self._value = 0.0
self._trend = 0
self._is_initialized = False
def OnStarted2(self, time):
super(nrtr_trailing_stop_strategy, self).OnStarted2(time)
atr = AverageTrueRange()
atr.Length = self.length
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(atr, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
if not atr_value.IsFormed:
return
atr = float(atr_value)
dk = atr / self.length * (10.0 ** (-self.digits_shift))
close = float(candle.ClosePrice)
if not self._is_initialized:
self._price = close
self._value = close
self._trend = 0
self._is_initialized = True
return
if self._trend >= 0:
self._price = max(self._price, close)
self._value = max(self._value, self._price * (1.0 - dk))
if close < self._value:
self._price = close
self._value = self._price * (1.0 + dk)
self._trend = -1
if self.Position >= 0:
self.SellMarket()
else:
self._price = min(self._price, close)
self._value = min(self._value, self._price * (1.0 + dk))
if close > self._value:
self._price = close
self._value = self._price * (1.0 - dk)
self._trend = 1
if self.Position <= 0:
self.BuyMarket()
def CreateClone(self):
return nrtr_trailing_stop_strategy()