NRTR Trailing Stop 策略
该策略使用 NRTR (Nick R's Trend Reverse) 指标跟踪市场趋势。算法根据最近蜡烛的平均波动范围计算跟踪止损水平,当价格突破该水平时,仓位会朝突破方向反转。策略可同时操作多头和空头,并支持可选的止损与止盈。
NRTR 的周期参数决定了跟踪止损的灵敏度:周期越短反应越快,但可能产生更多噪声;周期越长则能过滤波动。额外的位数偏移参数使指标适应不同价格精度的品种。策略订阅所选周期的蜡烛,在每根完成的蜡烛上计算 NRTR 值。
详情
- 入场逻辑:
- 做多:价格在下行趋势后上穿 NRTR 水平。
- 做空:价格在上行趋势后下穿 NRTR 水平。
- 出场逻辑:
- 当发生相反突破时反转持仓。
- 止损:通过
StartProtection设置可选止损和止盈。 - 默认参数:
Length= 10DigitsShift= 0TakeProfit= 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()