NRTR ATR Stop 策略
概述
NRTR ATR Stop 策略 是对 MetaTrader 专家顾问 Exp_NRTR_ATR_STOP_Tm 的完整移植。系统结合了非重绘趋势反转(NRTR)止损线和平均真实波幅(ATR)过滤器,用于识别主导趋势并动态移动保护线。所有信号都在所选时间框架的收盘价生成,并可通过可配置的已完成 K 线数量进行延迟,以复现原始 EA 的 SignalBar 设置。
该策略使用 StockSharp 的高级 API 实现,通过蜡烛订阅、指标绑定以及策略自带的下单辅助方法驱动,因而可以直接在 Designer、Shell、Runner 以及标准 API 环境中运行。
交易逻辑
- 指标计算
- 在所选时间框架上计算 ATR,周期由参数控制。
- 将 ATR 值与系数相乘,得到 NRTR 上下轨。
- 当前趋势在上一根蜡烛突破对侧 NRTR 水平时发生翻转,同时生成用于入场的箭头信号。
- 信号延迟
SignalBarDelay参数完全对应 MetaTrader 中的SignalBar输入,允许延迟若干根完整蜡烛再执行信号,从而获得与原始脚本一致的行为。
- 入场规则
- 当出现看涨 NRTR 反转并且允许做多时开多单。
- 当出现看跌 NRTR 反转并且允许做空时开空单。
- 离场规则
- 当出现相反方向信号时,若允许,对应方向的持仓立即平仓。
- 可选的时间过滤器会在交易窗口之外强制平仓并禁止开仓。
- 止损与止盈以价格步长为单位指定,同时 NRTR 水平会持续跟随趋势收紧保护价位,实现类似追踪止损的效果。
风险管理
- 下单量:使用
OrderVolume参数控制开仓量,与原 EA 一样可以参与优化。 - 止损 / 止盈:以价格步长(point)为单位设置,与 MetaTrader 版本保持一致。当同时存在手动止损与 NRTR 水平时,策略会选择距离市场更近的一侧,以避免扩大风险。
- 交易时间:启用
UseTradingWindow后,仅在[StartHour:StartMinute, EndHour:EndMinute]区间内允许开仓,并在时间窗外立即平仓。时间窗支持跨越午夜。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
OrderVolume |
1 | 每次下单的数量。 |
StopLossPoints |
1000 | 止损距离(价格步长)。设置为 0 表示关闭。 |
TakeProfitPoints |
2000 | 止盈距离(价格步长)。设置为 0 表示关闭。 |
BuyPosOpen / SellPosOpen |
true |
是否允许在 NRTR 反转时开多 / 开空。 |
BuyPosClose / SellPosClose |
true |
是否允许在相反信号出现时平多 / 平空。 |
UseTradingWindow |
true |
是否启用交易时段过滤。 |
StartHour / StartMinute |
0 / 0 | 允许交易的开始时间。 |
EndHour / EndMinute |
23 / 59 | 允许交易的结束时间,可设置跨日时段。 |
CandleType |
1 小时蜡烛 | 计算 ATR 与 NRTR 使用的蜡烛类型。 |
AtrPeriod |
20 | ATR 计算周期。 |
AtrMultiplier |
2 | ATR 与 NRTR 结合使用的系数。 |
SignalBarDelay |
1 | 执行信号前等待的完整蜡烛数量。 |
说明
- 策略仅在蜡烛收盘时做出决策,避免逐笔级别的差异,并与 StockSharp 的高级架构保持一致。
- 代码中的注释全部为英文,以满足项目要求。
- 根据需求未提供 Python 版本,仅包含 C# 实现。
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 ATR Stop strategy converted from the original MetaTrader expert.
/// The strategy relies on an ATR-based trailing reversal level to determine trend changes.
/// </summary>
public class NrtrAtrStopStrategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<bool> _buyPosOpen;
private readonly StrategyParam<bool> _sellPosOpen;
private readonly StrategyParam<bool> _buyPosClose;
private readonly StrategyParam<bool> _sellPosClose;
private readonly StrategyParam<bool> _useTradingWindow;
private readonly StrategyParam<int> _startHour;
private readonly StrategyParam<int> _startMinute;
private readonly StrategyParam<int> _endHour;
private readonly StrategyParam<int> _endMinute;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<int> _signalBarDelay;
private ATR _atrIndicator = null!;
private decimal? _previousUpLine;
private decimal? _previousDownLine;
private int _previousTrend;
private bool _hasPreviousCandle;
private decimal _prevCandleHigh;
private decimal _prevCandleLow;
private decimal? _longStop;
private decimal? _longTarget;
private decimal? _shortStop;
private decimal? _shortTarget;
private readonly List<NrtrSignal> _signalQueue = new();
private readonly struct NrtrSignal
{
public NrtrSignal(decimal? upLine, decimal? downLine, bool buySignal, bool sellSignal)
{
UpLine = upLine;
DownLine = downLine;
BuySignal = buySignal;
SellSignal = sellSignal;
}
public decimal? UpLine { get; }
public decimal? DownLine { get; }
public bool BuySignal { get; }
public bool SellSignal { get; }
}
/// <summary>
/// Volume used when opening new positions.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in price steps.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance expressed in price steps.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Allow opening long positions.
/// </summary>
public bool BuyPosOpen
{
get => _buyPosOpen.Value;
set => _buyPosOpen.Value = value;
}
/// <summary>
/// Allow opening short positions.
/// </summary>
public bool SellPosOpen
{
get => _sellPosOpen.Value;
set => _sellPosOpen.Value = value;
}
/// <summary>
/// Allow closing long positions on indicator signals.
/// </summary>
public bool BuyPosClose
{
get => _buyPosClose.Value;
set => _buyPosClose.Value = value;
}
/// <summary>
/// Allow closing short positions on indicator signals.
/// </summary>
public bool SellPosClose
{
get => _sellPosClose.Value;
set => _sellPosClose.Value = value;
}
/// <summary>
/// Enable the trading window restriction.
/// </summary>
public bool UseTradingWindow
{
get => _useTradingWindow.Value;
set => _useTradingWindow.Value = value;
}
/// <summary>
/// Start hour for the trading window.
/// </summary>
public int StartHour
{
get => _startHour.Value;
set => _startHour.Value = value;
}
/// <summary>
/// Start minute for the trading window.
/// </summary>
public int StartMinute
{
get => _startMinute.Value;
set => _startMinute.Value = value;
}
/// <summary>
/// End hour for the trading window.
/// </summary>
public int EndHour
{
get => _endHour.Value;
set => _endHour.Value = value;
}
/// <summary>
/// End minute for the trading window.
/// </summary>
public int EndMinute
{
get => _endMinute.Value;
set => _endMinute.Value = value;
}
/// <summary>
/// Candle type used for indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Period of the ATR indicator.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Multiplier applied to the ATR value when building the NRTR levels.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Number of fully closed bars to delay signal execution.
/// </summary>
public int SignalBarDelay
{
get => _signalBarDelay.Value;
set => _signalBarDelay.Value = value;
}
/// <summary>
/// Initializes strategy parameters with defaults converted from MQL inputs.
/// </summary>
public NrtrAtrStopStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Volume used when opening positions", "Trading")
.SetOptimize(1m, 5m, 1m);
_stopLossPoints = Param(nameof(StopLossPoints), 1000m)
.SetNotNegative()
.SetDisplay("Stop Loss (points)", "Stop-loss distance measured in price steps", "Risk Management");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000m)
.SetNotNegative()
.SetDisplay("Take Profit (points)", "Take-profit distance measured in price steps", "Risk Management");
_buyPosOpen = Param(nameof(BuyPosOpen), true)
.SetDisplay("Enable Long Entries", "Allow opening long positions", "Trading");
_sellPosOpen = Param(nameof(SellPosOpen), true)
.SetDisplay("Enable Short Entries", "Allow opening short positions", "Trading");
_buyPosClose = Param(nameof(BuyPosClose), true)
.SetDisplay("Close Long Positions", "Allow long exits generated by the indicator", "Trading");
_sellPosClose = Param(nameof(SellPosClose), true)
.SetDisplay("Close Short Positions", "Allow short exits generated by the indicator", "Trading");
_useTradingWindow = Param(nameof(UseTradingWindow), true)
.SetDisplay("Use Trading Window", "Restrict trading to a specific intraday window", "Session");
_startHour = Param(nameof(StartHour), 0)
.SetDisplay("Start Hour", "Hour when trading becomes available", "Session")
.SetOptimize(0, 23, 1);
_startMinute = Param(nameof(StartMinute), 0)
.SetDisplay("Start Minute", "Minute when trading becomes available", "Session")
.SetOptimize(0, 59, 1);
_endHour = Param(nameof(EndHour), 23)
.SetDisplay("End Hour", "Hour when trading stops", "Session")
.SetOptimize(0, 23, 1);
_endMinute = Param(nameof(EndMinute), 59)
.SetDisplay("End Minute", "Minute when trading stops", "Session")
.SetOptimize(0, 59, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe used by the NRTR ATR Stop indicator", "General");
_atrPeriod = Param(nameof(AtrPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Number of bars used to calculate ATR", "Indicator")
.SetOptimize(10, 40, 5);
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "Multiplier applied to the ATR value", "Indicator")
.SetOptimize(1m, 4m, 0.5m);
_signalBarDelay = Param(nameof(SignalBarDelay), 1)
.SetNotNegative()
.SetDisplay("Signal Bar", "Number of closed bars to wait before acting", "Indicator")
.SetOptimize(0, 3, 1);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousUpLine = null;
_previousDownLine = null;
_previousTrend = 0;
_hasPreviousCandle = false;
_prevCandleHigh = 0m;
_prevCandleLow = 0m;
_longStop = null;
_longTarget = null;
_shortStop = null;
_shortTarget = null;
_signalQueue.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = OrderVolume;
_atrIndicator = new ATR { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_atrIndicator, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
HandleRiskManagement(candle);
var inTradingWindow = !UseTradingWindow || IsWithinTradingWindow(candle.CloseTime);
if (UseTradingWindow && !inTradingWindow && Position != 0)
{
ForceFlat();
}
if (!_atrIndicator.IsFormed)
{
_hasPreviousCandle = true;
_prevCandleHigh = candle.HighPrice;
_prevCandleLow = candle.LowPrice;
return;
}
var nrtrSignal = CalculateNrtrSignal(candle, atrValue);
if (nrtrSignal is null)
return;
_signalQueue.Add(nrtrSignal.Value);
if (_signalQueue.Count <= SignalBarDelay)
return;
var signalToUse = _signalQueue[0];
try { _signalQueue.RemoveAt(0); } catch { }
if (Position > 0 && signalToUse.UpLine.HasValue)
{
var newStop = signalToUse.UpLine.Value;
_longStop = _longStop.HasValue ? Math.Max(_longStop.Value, newStop) : newStop;
}
else if (Position < 0 && signalToUse.DownLine.HasValue)
{
var newStop = signalToUse.DownLine.Value;
_shortStop = _shortStop.HasValue ? Math.Min(_shortStop.Value, newStop) : newStop;
}
if (!_atrIndicator.IsFormed)
return;
if (UseTradingWindow && !inTradingWindow)
return;
if (signalToUse.BuySignal)
{
if (SellPosClose)
CloseShort();
if (BuyPosOpen && Position <= 0)
OpenLong(candle.ClosePrice, signalToUse.UpLine);
}
else if (signalToUse.SellSignal)
{
if (BuyPosClose)
CloseLong();
if (SellPosOpen && Position >= 0)
OpenShort(candle.ClosePrice, signalToUse.DownLine);
}
}
private NrtrSignal? CalculateNrtrSignal(ICandleMessage candle, decimal atrValue)
{
if (!_hasPreviousCandle)
{
_hasPreviousCandle = true;
_prevCandleHigh = candle.HighPrice;
_prevCandleLow = candle.LowPrice;
return null;
}
if (atrValue <= 0)
{
_prevCandleHigh = candle.HighPrice;
_prevCandleLow = candle.LowPrice;
return null;
}
var prevLow = _prevCandleLow;
var prevHigh = _prevCandleHigh;
var rez = atrValue * AtrMultiplier;
var trend = _previousTrend;
var upPrev = NormalizeBuffer(_previousUpLine);
var downPrev = NormalizeBuffer(_previousDownLine);
if (trend <= 0)
{
if (downPrev is decimal downValue)
{
if (prevLow > downValue)
{
upPrev = prevLow - rez;
trend = 1;
}
}
else
{
upPrev = prevLow - rez;
trend = 1;
}
}
if (trend >= 0)
{
if (upPrev is decimal upValue)
{
if (prevHigh < upValue)
{
downPrev = prevHigh + rez;
trend = -1;
}
}
else
{
downPrev = prevHigh + rez;
trend = -1;
}
}
decimal? currentUp = null;
if (trend >= 0 && upPrev is decimal upLine)
{
currentUp = prevLow > upLine + rez
? prevLow - rez
: upLine;
}
decimal? currentDown = null;
if (trend <= 0 && downPrev is decimal downLine)
{
currentDown = prevHigh < downLine - rez
? prevHigh + rez
: downLine;
}
var buySignal = trend > 0 && _previousTrend <= 0 && currentUp.HasValue;
var sellSignal = trend < 0 && _previousTrend >= 0 && currentDown.HasValue;
_previousTrend = trend;
_previousUpLine = currentUp;
_previousDownLine = currentDown;
_prevCandleHigh = candle.HighPrice;
_prevCandleLow = candle.LowPrice;
return new NrtrSignal(currentUp, currentDown, buySignal, sellSignal);
}
private static decimal? NormalizeBuffer(decimal? value)
{
if (value is null)
return null;
return value <= 0m ? null : value;
}
private void HandleRiskManagement(ICandleMessage candle)
{
if (Position > 0)
{
if (_longStop.HasValue && candle.LowPrice <= _longStop.Value)
{
CloseLong();
}
else if (_longTarget.HasValue && candle.HighPrice >= _longTarget.Value)
{
CloseLong();
}
}
else if (Position < 0)
{
if (_shortStop.HasValue && candle.HighPrice >= _shortStop.Value)
{
CloseShort();
}
else if (_shortTarget.HasValue && candle.LowPrice <= _shortTarget.Value)
{
CloseShort();
}
}
}
private void OpenLong(decimal price, decimal? indicatorStop)
{
if (OrderVolume <= 0)
return;
BuyMarket();
var step = Security?.PriceStep ?? 0m;
decimal? manualStop = null;
if (step > 0m && StopLossPoints > 0m)
manualStop = price - StopLossPoints * step;
if (indicatorStop.HasValue && manualStop.HasValue)
_longStop = Math.Max(indicatorStop.Value, manualStop.Value);
else
_longStop = indicatorStop ?? manualStop;
if (step > 0m && TakeProfitPoints > 0m)
_longTarget = price + TakeProfitPoints * step;
else
_longTarget = null;
_shortStop = null;
_shortTarget = null;
}
private void OpenShort(decimal price, decimal? indicatorStop)
{
if (OrderVolume <= 0)
return;
SellMarket();
var step = Security?.PriceStep ?? 0m;
decimal? manualStop = null;
if (step > 0m && StopLossPoints > 0m)
manualStop = price + StopLossPoints * step;
if (indicatorStop.HasValue && manualStop.HasValue)
_shortStop = Math.Min(indicatorStop.Value, manualStop.Value);
else
_shortStop = indicatorStop ?? manualStop;
if (step > 0m && TakeProfitPoints > 0m)
_shortTarget = price - TakeProfitPoints * step;
else
_shortTarget = null;
_longStop = null;
_longTarget = null;
}
private void CloseLong()
{
if (Position <= 0)
return;
SellMarket();
_longStop = null;
_longTarget = null;
}
private void CloseShort()
{
if (Position >= 0)
return;
BuyMarket();
_shortStop = null;
_shortTarget = null;
}
private void ForceFlat()
{
if (Position > 0)
CloseLong();
else if (Position < 0)
CloseShort();
}
private bool IsWithinTradingWindow(DateTimeOffset time)
{
var current = time;
var hour = current.Hour;
var minute = current.Minute;
if (StartHour < EndHour)
{
if (hour == StartHour && minute >= StartMinute)
return true;
if (hour > StartHour && hour < EndHour)
return true;
if (hour > StartHour && hour == EndHour && minute < EndMinute)
return true;
}
else if (StartHour == EndHour)
{
if (hour == StartHour && minute >= StartMinute && minute < EndMinute)
return true;
}
else
{
if (hour > StartHour || (hour == StartHour && minute >= StartMinute))
return true;
if (hour < EndHour)
return true;
if (hour == EndHour && minute < EndMinute)
return true;
}
return false;
}
}
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 AverageTrueRange
class nrtr_atr_stop_strategy(Strategy):
"""NRTR ATR Stop: ATR-based trailing reversal levels determine trend changes."""
def __init__(self):
super(nrtr_atr_stop_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Order Volume", "Volume used when opening positions", "Trading")
self._stop_loss_points = self.Param("StopLossPoints", 1000.0) \
.SetDisplay("Stop Loss (points)", "Stop-loss distance in price steps", "Risk Management")
self._take_profit_points = self.Param("TakeProfitPoints", 2000.0) \
.SetDisplay("Take Profit (points)", "Take-profit distance in price steps", "Risk Management")
self._buy_pos_open = self.Param("BuyPosOpen", True) \
.SetDisplay("Enable Long Entries", "Allow opening long positions", "Trading")
self._sell_pos_open = self.Param("SellPosOpen", True) \
.SetDisplay("Enable Short Entries", "Allow opening short positions", "Trading")
self._buy_pos_close = self.Param("BuyPosClose", True) \
.SetDisplay("Close Long Positions", "Allow long exits by indicator", "Trading")
self._sell_pos_close = self.Param("SellPosClose", True) \
.SetDisplay("Close Short Positions", "Allow short exits by indicator", "Trading")
self._use_trading_window = self.Param("UseTradingWindow", True) \
.SetDisplay("Use Trading Window", "Restrict trading to intraday window", "Session")
self._start_hour = self.Param("StartHour", 0) \
.SetDisplay("Start Hour", "Hour when trading becomes available", "Session")
self._start_minute = self.Param("StartMinute", 0) \
.SetDisplay("Start Minute", "Minute when trading becomes available", "Session")
self._end_hour = self.Param("EndHour", 23) \
.SetDisplay("End Hour", "Hour when trading stops", "Session")
self._end_minute = self.Param("EndMinute", 59) \
.SetDisplay("End Minute", "Minute when trading stops", "Session")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Primary timeframe", "General")
self._atr_period = self.Param("AtrPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("ATR Period", "Number of bars used to calculate ATR", "Indicator")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("ATR Multiplier", "Multiplier applied to the ATR value", "Indicator")
self._signal_bar_delay = self.Param("SignalBarDelay", 1) \
.SetDisplay("Signal Bar", "Number of closed bars to wait before acting", "Indicator")
self._previous_up_line = None
self._previous_down_line = None
self._previous_trend = 0
self._has_previous_candle = False
self._prev_candle_high = 0.0
self._prev_candle_low = 0.0
self._long_stop = None
self._long_target = None
self._short_stop = None
self._short_target = None
self._signal_queue = []
@property
def OrderVolume(self):
return float(self._order_volume.Value)
@property
def StopLossPoints(self):
return float(self._stop_loss_points.Value)
@property
def TakeProfitPoints(self):
return float(self._take_profit_points.Value)
@property
def BuyPosOpen(self):
return self._buy_pos_open.Value
@property
def SellPosOpen(self):
return self._sell_pos_open.Value
@property
def BuyPosClose(self):
return self._buy_pos_close.Value
@property
def SellPosClose(self):
return self._sell_pos_close.Value
@property
def UseTradingWindow(self):
return self._use_trading_window.Value
@property
def StartHour(self):
return int(self._start_hour.Value)
@property
def StartMinute(self):
return int(self._start_minute.Value)
@property
def EndHour(self):
return int(self._end_hour.Value)
@property
def EndMinute(self):
return int(self._end_minute.Value)
@property
def CandleType(self):
return self._candle_type.Value
@property
def AtrPeriod(self):
return int(self._atr_period.Value)
@property
def AtrMultiplier(self):
return float(self._atr_multiplier.Value)
@property
def SignalBarDelay(self):
return int(self._signal_bar_delay.Value)
def OnStarted2(self, time):
super(nrtr_atr_stop_strategy, self).OnStarted2(time)
self._previous_up_line = None
self._previous_down_line = None
self._previous_trend = 0
self._has_previous_candle = False
self._prev_candle_high = 0.0
self._prev_candle_low = 0.0
self._long_stop = None
self._long_target = None
self._short_stop = None
self._short_target = None
self._signal_queue = []
self._atr = AverageTrueRange()
self._atr.Length = self.AtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._atr, self.process_candle).Start()
def process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
atr_val = float(atr_value)
self._handle_risk(candle)
in_window = not self.UseTradingWindow or self._is_within_window(candle.CloseTime)
if self.UseTradingWindow and not in_window and self.Position != 0:
self._force_flat()
if not self._atr.IsFormed:
self._has_previous_candle = True
self._prev_candle_high = float(candle.HighPrice)
self._prev_candle_low = float(candle.LowPrice)
return
nrtr = self._calc_nrtr(candle, atr_val)
if nrtr is None:
return
up_line, down_line, buy_signal, sell_signal = nrtr
self._signal_queue.append(nrtr)
if len(self._signal_queue) <= self.SignalBarDelay:
return
sig = self._signal_queue.pop(0)
s_up, s_down, s_buy, s_sell = sig
# Trail stops with NRTR levels
if self.Position > 0 and s_up is not None:
new_stop = s_up
self._long_stop = max(self._long_stop, new_stop) if self._long_stop is not None else new_stop
elif self.Position < 0 and s_down is not None:
new_stop = s_down
self._short_stop = min(self._short_stop, new_stop) if self._short_stop is not None else new_stop
if self.UseTradingWindow and not in_window:
return
if s_buy:
if self.SellPosClose:
self._close_short()
if self.BuyPosOpen and self.Position <= 0:
self._open_long(float(candle.ClosePrice), s_up)
elif s_sell:
if self.BuyPosClose:
self._close_long()
if self.SellPosOpen and self.Position >= 0:
self._open_short(float(candle.ClosePrice), s_down)
def _calc_nrtr(self, candle, atr_val):
if not self._has_previous_candle:
self._has_previous_candle = True
self._prev_candle_high = float(candle.HighPrice)
self._prev_candle_low = float(candle.LowPrice)
return None
if atr_val <= 0:
self._prev_candle_high = float(candle.HighPrice)
self._prev_candle_low = float(candle.LowPrice)
return None
prev_low = self._prev_candle_low
prev_high = self._prev_candle_high
rez = atr_val * self.AtrMultiplier
trend = self._previous_trend
up_prev = self._previous_up_line if self._previous_up_line is not None and self._previous_up_line > 0 else None
down_prev = self._previous_down_line if self._previous_down_line is not None and self._previous_down_line > 0 else None
if trend <= 0:
if down_prev is not None:
if prev_low > down_prev:
up_prev = prev_low - rez
trend = 1
else:
up_prev = prev_low - rez
trend = 1
if trend >= 0:
if up_prev is not None:
if prev_high < up_prev:
down_prev = prev_high + rez
trend = -1
else:
down_prev = prev_high + rez
trend = -1
current_up = None
if trend >= 0 and up_prev is not None:
current_up = prev_low - rez if prev_low > up_prev + rez else up_prev
current_down = None
if trend <= 0 and down_prev is not None:
current_down = prev_high + rez if prev_high < down_prev - rez else down_prev
buy_signal = trend > 0 and self._previous_trend <= 0 and current_up is not None
sell_signal = trend < 0 and self._previous_trend >= 0 and current_down is not None
self._previous_trend = trend
self._previous_up_line = current_up
self._previous_down_line = current_down
self._prev_candle_high = float(candle.HighPrice)
self._prev_candle_low = float(candle.LowPrice)
return (current_up, current_down, buy_signal, sell_signal)
def _handle_risk(self, candle):
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
if self.Position > 0:
if self._long_stop is not None and lo <= self._long_stop:
self._close_long()
elif self._long_target is not None and h >= self._long_target:
self._close_long()
elif self.Position < 0:
if self._short_stop is not None and h >= self._short_stop:
self._close_short()
elif self._short_target is not None and lo <= self._short_target:
self._close_short()
def _open_long(self, price, indicator_stop):
self.BuyMarket()
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 0.0
manual_stop = price - self.StopLossPoints * step if step > 0 and self.StopLossPoints > 0 else None
if indicator_stop is not None and manual_stop is not None:
self._long_stop = max(indicator_stop, manual_stop)
else:
self._long_stop = indicator_stop if indicator_stop is not None else manual_stop
self._long_target = price + self.TakeProfitPoints * step if step > 0 and self.TakeProfitPoints > 0 else None
self._short_stop = None
self._short_target = None
def _open_short(self, price, indicator_stop):
self.SellMarket()
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 0.0
manual_stop = price + self.StopLossPoints * step if step > 0 and self.StopLossPoints > 0 else None
if indicator_stop is not None and manual_stop is not None:
self._short_stop = min(indicator_stop, manual_stop)
else:
self._short_stop = indicator_stop if indicator_stop is not None else manual_stop
self._short_target = price - self.TakeProfitPoints * step if step > 0 and self.TakeProfitPoints > 0 else None
self._long_stop = None
self._long_target = None
def _close_long(self):
if self.Position <= 0:
return
self.SellMarket()
self._long_stop = None
self._long_target = None
def _close_short(self):
if self.Position >= 0:
return
self.BuyMarket()
self._short_stop = None
self._short_target = None
def _force_flat(self):
if self.Position > 0:
self._close_long()
elif self.Position < 0:
self._close_short()
def _is_within_window(self, time):
hour = time.Hour
minute = time.Minute
sh = self.StartHour
sm = self.StartMinute
eh = self.EndHour
em = self.EndMinute
if sh < eh:
if hour == sh and minute >= sm:
return True
if hour > sh and hour < eh:
return True
if hour > sh and hour == eh and minute < em:
return True
elif sh == eh:
if hour == sh and minute >= sm and minute < em:
return True
else:
if hour > sh or (hour == sh and minute >= sm):
return True
if hour < eh:
return True
if hour == eh and minute < em:
return True
return False
def OnReseted(self):
super(nrtr_atr_stop_strategy, self).OnReseted()
self._previous_up_line = None
self._previous_down_line = None
self._previous_trend = 0
self._has_previous_candle = False
self._prev_candle_high = 0.0
self._prev_candle_low = 0.0
self._long_stop = None
self._long_target = None
self._short_stop = None
self._short_target = None
self._signal_queue = []
def CreateClone(self):
return nrtr_atr_stop_strategy()