RRS Non-Directional 策略
概述
本策略将 MetaTrader 4 的 “RRS Non-Directional” 智能交易系统迁移到 StockSharp 框架。原始版本根据所选模式同时建立买入和卖出网格,并通过虚拟的止损、止盈与跟踪止损来管理仓位。StockSharp 版本保留了全部可配置模式、资金风险控制以及虚拟保护机制,同时适配 StockSharp 的净额结算组合结构。因此,在所谓的“对冲”模式下,仓位会在多空之间交替,而不是像 MT4 那样同时持有两个方向。
交易逻辑
- 订阅 Level-1 行情以获得最新买价和卖价,并在每次准备入场前对比
MaxSpreadPoints限制。 - 入场逻辑由
TradingMode控制:HedgeStyle与AutoSwap通过在多空之间轮换来模拟双向网格(StockSharp 无法同时保留独立的买单与卖单)。BuySellRandom每次机会都会随机选择方向。BuySell始终开出与最近一次平仓相反的方向。BuyOrder与SellOrder将交易限制在单一方向。
AllowNewTrades对应 MT4 的New_Trade变量,可随时阻止新的市场单。- 所有订单都使用
TradeVolume指定的基础数量,并在订单中写入TradeComment以便外部平台识别。
风险控制与离场
- 止损与止盈距离以 MetaTrader 点数表示,并通过
PriceStep转换成实际价格差,因而无需针对不同品种手动调整。 StopMode、TakeMode与TrailingMode在关闭、虚拟和“经典”之间切换。在 StockSharp 实现中,只要不是关闭,保护逻辑都会以虚拟方式执行:一旦触发条件,就通过市价单平仓,从而保证跨平台表现一致。- 当价格向盈利方向运行超过
TrailingStartPoints时,策略会启动跟踪止损,并保持距离为TrailingGapPoints。 - 每次 Level-1 更新都会重新计算未实现盈亏。如果亏损超过
RiskMode与MoneyInRisk定义的阈值,仓位会立刻被强制平掉。
参数说明
| 参数 | 说明 |
|---|---|
TradingMode |
入口模式,源自原始 EA。由于净额结算,所谓的对冲模式会在多空之间交替。 |
AllowNewTrades |
是否允许开立新的市场单。 |
TradeVolume |
每次下单的基础数量。 |
StopMode |
止损管理方式(Disabled、Virtual、Classic)。 |
StopLossPoints |
以 MetaTrader 点数表示的止损距离。 |
TakeMode |
止盈管理方式(Disabled、Virtual、Classic)。 |
TakeProfitPoints |
以 MetaTrader 点数表示的止盈距离。 |
TrailingMode |
跟踪止损管理方式(Disabled、Virtual、Classic)。 |
TrailingStartPoints |
激活跟踪止损所需的盈利点数。 |
TrailingGapPoints |
跟踪止损启动后保持的点数间隔。 |
RiskMode |
解释 MoneyInRisk 时所采用的模式:余额百分比或绝对金额。 |
MoneyInRisk |
风险阈值,未实现盈亏低于该值时立即清仓。 |
MaxSpreadPoints |
允许开仓时的最大点差。 |
SlippagePoints |
为保持与原始输入一致而保留的滑点信息参数。 |
TradeComment |
写入到订单的备注。 |
注意事项
- MT4 中的 AutoSwap 依赖于经纪商提供的隔夜利息数据。大部分 StockSharp 连接器不会在 Level-1 中给出这些值,因此该模式会自动降级为
HedgeStyle并记录提示。 - “经典”止损、止盈与跟踪止损在此版本中同样通过虚拟方式完成。如果需要真正的挂单保护,应在更底层自行实现。
- 由于 StockSharp 对每个工具只维护一个净仓位,策略在对冲模式下无法同时持有双向仓,实际表现为多空交替,这一点需要在回测与实盘中特别注意。
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>
/// Translation of the MetaTrader "RRS Non-Directional" expert advisor.
/// The strategy recreates the randomised entry modes, virtual stop/target logic and trailing management using StockSharp's netting model.
/// </summary>
public class RrsNonDirectionalStrategy : Strategy
{
private readonly StrategyParam<RrsTradingModes> _tradingMode;
private readonly StrategyParam<bool> _allowNewTrades;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<RrsStopModes> _stopMode;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<RrsTakeModes> _takeMode;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<RrsTrailingModes> _trailingMode;
private readonly StrategyParam<int> _trailingStartPoints;
private readonly StrategyParam<int> _trailingGapPoints;
private readonly StrategyParam<RrsRiskModes> _riskMode;
private readonly StrategyParam<decimal> _moneyInRisk;
private readonly StrategyParam<int> _maxSpreadPoints;
private readonly StrategyParam<int> _slippagePoints;
private readonly StrategyParam<string> _tradeComment;
private readonly StrategyParam<DataType> _candleType;
private int _tradeCounter;
private decimal? _lastBid;
private decimal? _lastAsk;
private decimal _pointSize;
private decimal _tickValue;
private decimal _entryPrice;
private decimal? _longStopPrice;
private decimal? _shortStopPrice;
private decimal? _longTakePrice;
private decimal? _shortTakePrice;
private decimal? _longTrailingStop;
private decimal? _shortTrailingStop;
private string _statusMessage = "Initializing";
private Sides? _lastClosedSide;
private decimal _previousPosition;
/// <summary>
/// Initializes a new instance of the <see cref="RrsNonDirectionalStrategy"/> class.
/// </summary>
public RrsNonDirectionalStrategy()
{
_tradingMode = Param(nameof(RrsTradingMode), RrsTradingModes.HedgeStyle)
.SetDisplay("Trading Strategy", "Entry style reproduced from the MT4 extern Trading_Strategy", "General")
;
_allowNewTrades = Param(nameof(AllowNewTrades), true)
.SetDisplay("Enable Trading", "Master switch that mirrors the New_Trade extern", "General")
;
_tradeVolume = Param(nameof(TradeVolume), 1m)
.SetDisplay("Trade Volume", "Base volume used for market orders", "General")
;
_stopMode = Param(nameof(StopMode), RrsStopModes.Virtual)
.SetDisplay("Stop-Loss Type", "Chooses between virtual or classic stop-loss handling", "Risk")
;
_stopLossPoints = Param(nameof(StopLossPoints), 200)
.SetDisplay("Stop-Loss (points)", "MetaTrader points converted with the instrument price step", "Risk")
;
_takeMode = Param(nameof(TakeMode), RrsTakeModes.Virtual)
.SetDisplay("Take-Profit Type", "Chooses between virtual or classic take-profit handling", "Risk")
;
_takeProfitPoints = Param(nameof(TakeProfitPoints), 100)
.SetDisplay("Take-Profit (points)", "MetaTrader points converted with the instrument price step", "Risk")
;
_trailingMode = Param(nameof(TrailingMode), RrsTrailingModes.Virtual)
.SetDisplay("Trailing Type", "Switch between virtual and classic trailing implementation", "Risk")
;
_trailingStartPoints = Param(nameof(TrailingStartPoints), 30)
.SetDisplay("Trailing Start (points)", "Distance in points before the trailing stop activates", "Risk")
;
_trailingGapPoints = Param(nameof(TrailingGapPoints), 30)
.SetDisplay("Trailing Gap (points)", "Distance maintained behind the best price once trailing is active", "Risk")
;
_riskMode = Param(nameof(RiskMode), RrsRiskModes.BalancePercentage)
.SetDisplay("Risk Mode", "Determines how MoneyInRisk is interpreted", "Risk")
;
_moneyInRisk = Param(nameof(MoneyInRisk), 5m)
.SetDisplay("Money In Risk", "Either a percent of balance or an absolute currency amount", "Risk")
;
_maxSpreadPoints = Param(nameof(MaxSpreadPoints), 50)
.SetDisplay("Max Spread (points)", "Maximum allowed spread expressed in MetaTrader points", "Filters")
;
_slippagePoints = Param(nameof(SlippagePoints), 3)
.SetDisplay("Slippage (points)", "Displayed for completeness, market exits ignore this value", "Filters")
;
_tradeComment = Param(nameof(TradeComment), "RRS")
.SetDisplay("Trade Comment", "Tag attached to every market order", "General")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle type for price updates", "General")
;
}
/// <summary>
/// Selected entry mode from the original EA.
/// </summary>
public RrsTradingModes RrsTradingMode
{
get => _tradingMode.Value;
set => _tradingMode.Value = value;
}
/// <summary>
/// Enables or disables new market entries.
/// </summary>
public bool AllowNewTrades
{
get => _allowNewTrades.Value;
set => _allowNewTrades.Value = value;
}
/// <summary>
/// Base volume for market orders.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Stop-loss handling mode.
/// </summary>
public RrsStopModes StopMode
{
get => _stopMode.Value;
set => _stopMode.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in MetaTrader points.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit handling mode.
/// </summary>
public RrsTakeModes TakeMode
{
get => _takeMode.Value;
set => _takeMode.Value = value;
}
/// <summary>
/// Take-profit distance expressed in MetaTrader points.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Trailing management mode.
/// </summary>
public RrsTrailingModes TrailingMode
{
get => _trailingMode.Value;
set => _trailingMode.Value = value;
}
/// <summary>
/// Trailing activation distance in points.
/// </summary>
public int TrailingStartPoints
{
get => _trailingStartPoints.Value;
set => _trailingStartPoints.Value = value;
}
/// <summary>
/// Trailing gap maintained once armed.
/// </summary>
public int TrailingGapPoints
{
get => _trailingGapPoints.Value;
set => _trailingGapPoints.Value = value;
}
/// <summary>
/// Risk management interpretation for <see cref="MoneyInRisk"/>.
/// </summary>
public RrsRiskModes RiskMode
{
get => _riskMode.Value;
set => _riskMode.Value = value;
}
/// <summary>
/// Risk threshold expressed either as percent of balance or absolute currency amount.
/// </summary>
public decimal MoneyInRisk
{
get => _moneyInRisk.Value;
set => _moneyInRisk.Value = value;
}
/// <summary>
/// Maximum accepted spread before suppressing new trades.
/// </summary>
public int MaxSpreadPoints
{
get => _maxSpreadPoints.Value;
set => _maxSpreadPoints.Value = value;
}
/// <summary>
/// Informational slippage setting shown on the UI.
/// </summary>
public int SlippagePoints
{
get => _slippagePoints.Value;
set => _slippagePoints.Value = value;
}
/// <summary>
/// Comment passed to every generated order.
/// </summary>
public string TradeComment
{
get => _tradeComment.Value;
set => _tradeComment.Value = value;
}
/// <summary>
/// Candle type for price updates.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Human readable status updated during processing.
/// </summary>
public string StatusMessage => _statusMessage;
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_tradeCounter = 0;
_lastBid = null;
_lastAsk = null;
_pointSize = 0m;
_tickValue = 0m;
_entryPrice = 0m;
_longStopPrice = null;
_shortStopPrice = null;
_longTakePrice = null;
_shortTakePrice = null;
_longTrailingStop = null;
_shortTrailingStop = null;
_statusMessage = "Reset";
_lastClosedSide = null;
_previousPosition = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = TradeVolume;
_pointSize = Security?.PriceStep ?? 0m;
if (_pointSize <= 0m)
_pointSize = 0.0001m;
_tickValue = GetSecurityValue<decimal?>(Level1Fields.StepPrice) ?? 0m;
if (_tickValue <= 0m)
_tickValue = 1m;
if (RrsTradingMode == RrsTradingModes.AutoSwap)
InitializeAutoSwap();
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
}
private void InitializeAutoSwap()
{
RrsTradingMode = RrsTradingModes.HedgeStyle;
LogInfo("AutoSwap mode falls back to HedgeStyle because swap rates are not available through Level1 data.");
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_lastBid = candle.ClosePrice;
_lastAsk = candle.ClosePrice;
ManageOpenPosition();
ApplyRiskCut();
TryOpenTrade();
}
private void ManageOpenPosition()
{
var bid = _lastBid;
var ask = _lastAsk;
if (bid is null || ask is null)
return;
if (Position > 0m)
{
if (_longStopPrice is not null && bid <= _longStopPrice)
ClosePositionManual("Stop-loss hit");
else if (_longTakePrice is not null && bid >= _longTakePrice)
ClosePositionManual("Take-profit hit");
else
UpdateTrailingForLong(bid.Value);
}
else if (Position < 0m)
{
if (_shortStopPrice is not null && ask >= _shortStopPrice)
ClosePositionManual("Stop-loss hit");
else if (_shortTakePrice is not null && ask <= _shortTakePrice)
ClosePositionManual("Take-profit hit");
else
UpdateTrailingForShort(ask.Value);
}
}
private void UpdateTrailingForLong(decimal bid)
{
if (TrailingMode == RrsTrailingModes.Disabled)
return;
var activation = GetPriceDistance(TrailingStartPoints);
var gap = GetPriceDistance(TrailingGapPoints);
if (activation <= 0m || gap <= 0m)
return;
var entryPrice = _entryPrice;
if (entryPrice <= 0m)
return;
if (bid >= entryPrice + activation)
{
var candidate = bid - gap;
if (_longTrailingStop is null || candidate > _longTrailingStop)
{
_longTrailingStop = candidate;
_longStopPrice = candidate;
}
}
if (_longTrailingStop is not null && bid <= _longTrailingStop)
ClosePositionManual("Trailing stop");
}
private void UpdateTrailingForShort(decimal ask)
{
if (TrailingMode == RrsTrailingModes.Disabled)
return;
var activation = GetPriceDistance(TrailingStartPoints);
var gap = GetPriceDistance(TrailingGapPoints);
if (activation <= 0m || gap <= 0m)
return;
var entryPrice = _entryPrice;
if (entryPrice <= 0m)
return;
if (ask <= entryPrice - activation)
{
var candidate = ask + gap;
if (_shortTrailingStop is null || candidate < _shortTrailingStop)
{
_shortTrailingStop = candidate;
_shortStopPrice = candidate;
}
}
if (_shortTrailingStop is not null && ask >= _shortTrailingStop)
ClosePositionManual("Trailing stop");
}
private void ApplyRiskCut()
{
if (Position == 0m)
return;
var bid = _lastBid;
var ask = _lastAsk;
if (bid is null || ask is null)
return;
var unrealized = CalculateUnrealizedPnL(bid.Value, ask.Value);
var threshold = GetRiskThreshold();
if (unrealized <= threshold)
{
ClosePositionManual("Risk control");
_statusMessage = "Risk stop activated";
}
}
private decimal CalculateUnrealizedPnL(decimal bid, decimal ask)
{
if (Position == 0m)
return 0m;
var entryPrice = _entryPrice;
if (entryPrice <= 0m)
return 0m;
var currentPrice = Position > 0m ? bid : ask;
var priceDiff = currentPrice - entryPrice;
var direction = Position > 0m ? 1m : -1m;
if (_pointSize <= 0m || _tickValue <= 0m)
return priceDiff * direction * Position;
var steps = priceDiff / _pointSize;
return steps * _tickValue * Position * direction;
}
private decimal GetRiskThreshold()
{
var balance = GetPortfolioValue();
if (RiskMode == RrsRiskModes.BalancePercentage)
return -Math.Abs(balance * (MoneyInRisk / 100m));
return -Math.Abs(MoneyInRisk);
}
private void TryOpenTrade()
{
if (!AllowNewTrades)
{
_statusMessage = "Trading disabled";
return;
}
if (_lastBid is null || _lastAsk is null)
return;
var spread = _lastAsk.Value - _lastBid.Value;
var spreadPoints = _pointSize > 0m ? spread / _pointSize : spread;
if (spreadPoints > MaxSpreadPoints)
{
_statusMessage = "Spread filter";
return;
}
if (Position > 0m)
{
_statusMessage = "Long position active";
return;
}
if (Position < 0m)
{
_statusMessage = "Short position active";
return;
}
switch (RrsTradingMode)
{
case RrsTradingModes.HedgeStyle:
case RrsTradingModes.AutoSwap:
OpenHedgeReplacement();
break;
case RrsTradingModes.BuyOrder:
OpenLong();
break;
case RrsTradingModes.SellOrder:
OpenShort();
break;
case RrsTradingModes.BuySellRandom:
if (_tradeCounter++ % 2 == 0)
OpenLong();
else
OpenShort();
break;
case RrsTradingModes.BuySell:
if (_lastClosedSide == Sides.Sell || _lastClosedSide is null)
OpenLong();
else
OpenShort();
break;
}
}
private void OpenHedgeReplacement()
{
if (_lastClosedSide == Sides.Buy)
{
OpenShort();
return;
}
OpenLong();
}
private void OpenLong()
{
var order = BuyMarket(TradeVolume);
if (order != null)
{
_statusMessage = "Opened long";
PrepareProtectionForLong();
}
}
private void OpenShort()
{
var order = SellMarket(TradeVolume);
if (order != null)
{
_statusMessage = "Opened short";
PrepareProtectionForShort();
}
}
private void PrepareProtectionForLong()
{
var entryPrice = _entryPrice;
if (entryPrice <= 0m)
return;
var stopDistance = GetPriceDistance(StopLossPoints);
var takeDistance = GetPriceDistance(TakeProfitPoints);
_longStopPrice = StopMode == RrsStopModes.Disabled || stopDistance <= 0m ? null : entryPrice - stopDistance;
_longTakePrice = TakeMode == RrsTakeModes.Disabled || takeDistance <= 0m ? null : entryPrice + takeDistance;
_longTrailingStop = null;
}
private void PrepareProtectionForShort()
{
var entryPrice = _entryPrice;
if (entryPrice <= 0m)
return;
var stopDistance = GetPriceDistance(StopLossPoints);
var takeDistance = GetPriceDistance(TakeProfitPoints);
_shortStopPrice = StopMode == RrsStopModes.Disabled || stopDistance <= 0m ? null : entryPrice + stopDistance;
_shortTakePrice = TakeMode == RrsTakeModes.Disabled || takeDistance <= 0m ? null : entryPrice - takeDistance;
_shortTrailingStop = null;
}
private decimal GetPriceDistance(int points)
{
if (points <= 0)
return 0m;
var point = _pointSize > 0m ? _pointSize : 0.0001m;
return points * point;
}
/// <inheritdoc />
protected override void OnPositionReceived(Position position)
{
base.OnPositionReceived(position);
if (_previousPosition > 0m && Position == 0m)
_lastClosedSide = Sides.Buy;
else if (_previousPosition < 0m && Position == 0m)
_lastClosedSide = Sides.Sell;
_previousPosition = Position;
if (Position == 0m)
{
_longStopPrice = null;
_shortStopPrice = null;
_longTakePrice = null;
_shortTakePrice = null;
_longTrailingStop = null;
_shortTrailingStop = null;
}
}
private void ClosePositionManual(string reason)
{
if (Position > 0m)
SellMarket(Math.Abs(Position));
else if (Position < 0m)
BuyMarket(Math.Abs(Position));
_statusMessage = reason;
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (Position != 0m && _entryPrice == 0m)
_entryPrice = trade.Trade.Price;
if (Position == 0m)
_entryPrice = 0m;
}
private decimal GetPortfolioValue()
{
var current = Portfolio?.CurrentValue ?? 0m;
if (current > 0m)
return current;
var begin = Portfolio?.BeginValue ?? 0m;
return begin > 0m ? begin : current;
}
public enum RrsTradingModes
{
HedgeStyle,
BuySellRandom,
BuySell,
AutoSwap,
BuyOrder,
SellOrder,
}
/// <summary>
/// Stop-loss handling options.
/// </summary>
public enum RrsStopModes
{
Disabled,
Virtual,
Classic,
}
/// <summary>
/// Take-profit handling options.
/// </summary>
public enum RrsTakeModes
{
Disabled,
Virtual,
Classic,
}
/// <summary>
/// Trailing management options.
/// </summary>
public enum RrsTrailingModes
{
Disabled,
Virtual,
Classic,
}
/// <summary>
/// Interpretation modes for the risk threshold.
/// </summary>
public enum RrsRiskModes
{
BalancePercentage,
FixedMoney,
}
}
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.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class rrs_non_directional_strategy(Strategy):
def __init__(self):
super(rrs_non_directional_strategy, self).__init__()
self._sl_points = self.Param("StopLossPoints", 200).SetDisplay("Stop Loss (pts)", "SL distance", "Risk")
self._tp_points = self.Param("TakeProfitPoints", 100).SetDisplay("Take Profit (pts)", "TP distance", "Risk")
self._trailing_start = self.Param("TrailingStartPoints", 30).SetDisplay("Trailing Start", "Trailing activation", "Risk")
self._trailing_gap = self.Param("TrailingGapPoints", 30).SetDisplay("Trailing Gap", "Trailing gap", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Candle timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(rrs_non_directional_strategy, self).OnReseted()
self._entry_price = 0
self._stop_price = None
self._take_price = None
self._trailing_stop = None
self._open_long_next = True
def OnStarted2(self, time):
super(rrs_non_directional_strategy, self).OnStarted2(time)
self._entry_price = 0
self._stop_price = None
self._take_price = None
self._trailing_stop = None
self._open_long_next = True
self._step = 0.0001
if self.Security is not None and self.Security.PriceStep is not None and self.Security.PriceStep > 0:
self._step = float(self.Security.PriceStep)
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self.OnProcess).Start()
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
# Manage position
if self.Position > 0:
if self._stop_price is not None and candle.LowPrice <= self._stop_price:
self.SellMarket()
self._reset()
return
if self._take_price is not None and candle.HighPrice >= self._take_price:
self.SellMarket()
self._reset()
return
# Trailing
if self._trailing_start.Value > 0 and self._trailing_gap.Value > 0 and self._entry_price > 0:
act = self._trailing_start.Value * self._step
gap = self._trailing_gap.Value * self._step
if close >= self._entry_price + act:
candidate = close - gap
if self._trailing_stop is None or candidate > self._trailing_stop:
self._trailing_stop = candidate
self._stop_price = candidate
if self._trailing_stop is not None and close <= self._trailing_stop:
self.SellMarket()
self._reset()
return
elif self.Position < 0:
if self._stop_price is not None and candle.HighPrice >= self._stop_price:
self.BuyMarket()
self._reset()
return
if self._take_price is not None and candle.LowPrice <= self._take_price:
self.BuyMarket()
self._reset()
return
if self._trailing_start.Value > 0 and self._trailing_gap.Value > 0 and self._entry_price > 0:
act = self._trailing_start.Value * self._step
gap = self._trailing_gap.Value * self._step
if close <= self._entry_price - act:
candidate = close + gap
if self._trailing_stop is None or candidate < self._trailing_stop:
self._trailing_stop = candidate
self._stop_price = candidate
if self._trailing_stop is not None and close >= self._trailing_stop:
self.BuyMarket()
self._reset()
return
if self.Position != 0:
return
sl_dist = self._sl_points.Value * self._step
tp_dist = self._tp_points.Value * self._step
if self._open_long_next:
self.BuyMarket()
self._entry_price = close
self._stop_price = close - sl_dist if self._sl_points.Value > 0 else None
self._take_price = close + tp_dist if self._tp_points.Value > 0 else None
else:
self.SellMarket()
self._entry_price = close
self._stop_price = close + sl_dist if self._sl_points.Value > 0 else None
self._take_price = close - tp_dist if self._tp_points.Value > 0 else None
self._open_long_next = not self._open_long_next
def _reset(self):
self._entry_price = 0
self._stop_price = None
self._take_price = None
self._trailing_stop = None
def CreateClone(self):
return rrs_non_directional_strategy()