Estrategia de aleatoriedad RRS
Descripción general
La Estrategia de aleatoriedad RRS es una adaptación StockSharp de "RRS Aleatoriedad en la naturaleza EA" para MetaTrader 4. Emula al asesor experto original con entradas de mercado largas o cortas seudoaleatorias, comprobaciones de stop-loss y take-profit en velas finalizadas, trailing opcional y liquidación cuando la pérdida flotante alcanza el umbral configurado.
Debido a que StockSharp utiliza posiciones netas por valor, no se admiten exposiciones largas y cortas simultáneas. Por lo tanto, DoubleSide empieza con una compra y alterna la dirección tras cada entrada, en vez de mantener dos operaciones cubiertas como en MetaTrader.
Lógica de trading
- En cada vela finalizada usa el cierre para las protecciones y, si están disponibles, el bid/ask de Nivel 1 para el spread y el precio de liquidación.
- Con una posición abierta comprueba stop loss, take profit, trailing stop y el límite de pérdida flotante; envía como máximo una orden de cierre por vela.
- Cuando está plano, valida las restricciones de diferencial y volumen antes de abrir una nueva operación:
- DoubleSide alterna entre entradas largas y cortas, empezando por una larga.
- OneSide usa un entero seudoaleatorio repetible en
[0,5]:1o4abre largo,0o3abre corto y2o5omite la vela. La secuencia se reinicia al iniciar o restablecer la estrategia.
- Los volúmenes comerciales se dibujan uniformemente entre el mínimo y el máximo configurados y están alineados con el paso de volumen del instrumento.
Parámetros
| grupo | Nombre | Descripción |
|---|---|---|
| generales | Mode |
Entradas alternas (DoubleSide, 0) o filtradas al azar (OneSide, 1). |
| Configuración de lote | MinVolume / MaxVolume |
Rango de volumen para operaciones generadas aleatoriamente. |
| Protección | TakeProfitPoints |
Distancia de obtención de beneficios en pasos de precio. |
| Protección | StopLossPoints |
Distancia de stop-loss en pasos de precio. |
| Protección | TrailingStartPoints |
Distancia de beneficio que permite la gestión del trailing stop. |
| Protección | TrailingGapPoints |
Compensación entre el precio de mercado y el trailing stop. |
| Filtros | MaxSpreadPoints |
Spread máximo de Nivel 1 en pasos de precio. Cero bloquea nuevas entradas; un valor positivo permite el fallback de velas sin bid/ask. |
| Filtros | SlippagePoints |
Configuración de deslizamiento informativo (no se aplica automáticamente). |
| Gestión de riesgos | MoneyRiskMode |
Pérdida fija (FixedMoney, 0) o porcentaje de la cartera (BalancePercentage, 1). |
| Gestión de riesgos | RiskValue |
Cantidad de riesgo (moneda o porcentaje según la modalidad). |
| generales | TradeComment |
Comentario de las entradas; las salidas añaden el motivo de activación. |
| generales | CandleType |
Serie de velas que impulsa el ciclo de decisión. |
Notas
- Las cotizaciones de Nivel 1 mejoran el cálculo del spread y del precio de liquidación. Sin ambas puntas, un límite positivo permite el fallback de velas;
MaxSpreadPoints = 0siempre bloquea entradas. - Las protecciones se evalúan en velas finalizadas. El trailing se activa tras ganar
TrailingStartPoints + TrailingGapPointspasos y sigue el precio aTrailingGapPoints. FixedMoneyinterpretaRiskValueen moneda de la cuenta;BalancePercentageusa ese porcentaje del valor actual de la cartera.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Randomized trading strategy converted from the "RRS Randomness in Nature" MQL expert advisor.
/// The strategy opens random market orders with optional trailing, stop-loss, take-profit and risk protection.
/// </summary>
public class RrsRandomnessStrategy : Strategy
{
private const uint _initialRandomState = 3710u;
private readonly StrategyParam<TradingModes> _tradingMode;
private readonly StrategyParam<decimal> _minVolume;
private readonly StrategyParam<decimal> _maxVolume;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _trailingStartPoints;
private readonly StrategyParam<decimal> _trailingGapPoints;
private readonly StrategyParam<decimal> _maxSpreadPoints;
private readonly StrategyParam<decimal> _slippagePoints;
private readonly StrategyParam<RiskModes> _riskMode;
private readonly StrategyParam<decimal> _riskValue;
private readonly StrategyParam<string> _tradeComment;
private readonly StrategyParam<DataType> _candleType;
private uint _randomState;
private decimal? _trailingStopPrice;
private bool _openLongNext;
private decimal _entryPrice;
private decimal? _bestBid;
private decimal? _bestAsk;
private decimal _priceStep;
private decimal _stepPrice;
/// <summary>
/// Trading direction selection logic.
/// </summary>
public TradingModes Mode
{
get => _tradingMode.Value;
set => _tradingMode.Value = value;
}
/// <summary>
/// Minimal order volume.
/// </summary>
public decimal MinVolume
{
get => _minVolume.Value;
set => _minVolume.Value = value;
}
/// <summary>
/// Maximal order volume.
/// </summary>
public decimal MaxVolume
{
get => _maxVolume.Value;
set => _maxVolume.Value = value;
}
/// <summary>
/// Take-profit distance expressed in price steps.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in price steps.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Profit distance that enables the trailing stop.
/// </summary>
public decimal TrailingStartPoints
{
get => _trailingStartPoints.Value;
set => _trailingStartPoints.Value = value;
}
/// <summary>
/// Trailing stop offset from current price measured in price steps.
/// </summary>
public decimal TrailingGapPoints
{
get => _trailingGapPoints.Value;
set => _trailingGapPoints.Value = value;
}
/// <summary>
/// Maximal spread allowed for opening trades (price steps).
/// </summary>
public decimal MaxSpreadPoints
{
get => _maxSpreadPoints.Value;
set => _maxSpreadPoints.Value = value;
}
/// <summary>
/// Slippage tolerance in price steps (informational parameter).
/// </summary>
public decimal SlippagePoints
{
get => _slippagePoints.Value;
set => _slippagePoints.Value = value;
}
/// <summary>
/// Risk management mode.
/// </summary>
public RiskModes MoneyRiskMode
{
get => _riskMode.Value;
set => _riskMode.Value = value;
}
/// <summary>
/// Risk value in account currency or percent depending on the mode.
/// </summary>
public decimal RiskValue
{
get => _riskValue.Value;
set => _riskValue.Value = value;
}
/// <summary>
/// Trade comment stored for informational purposes.
/// </summary>
public string TradeComment
{
get => _tradeComment.Value;
set => _tradeComment.Value = value;
}
/// <summary>
/// Candle type used to schedule strategy checks.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="RrsRandomnessStrategy"/> class.
/// </summary>
public RrsRandomnessStrategy()
{
_tradingMode = Param(nameof(Mode), TradingModes.DoubleSide)
.SetDisplay("Trading Mode", "Select whether a trade is chosen every cycle or only on random matches.", "General");
_minVolume = Param(nameof(MinVolume), 0.01m)
.SetGreaterThanZero()
.SetDisplay("Min Volume", "Minimal volume for a market order.", "Lot Settings");
_maxVolume = Param(nameof(MaxVolume), 0.5m)
.SetGreaterThanZero()
.SetDisplay("Max Volume", "Maximum volume for a market order.", "Lot Settings");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000m)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit distance in price steps.", "Protection");
_stopLossPoints = Param(nameof(StopLossPoints), 3000m)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss distance in price steps.", "Protection");
_trailingStartPoints = Param(nameof(TrailingStartPoints), 1500m)
.SetNotNegative()
.SetDisplay("Trailing Start", "Profit distance that enables the trailing stop.", "Protection");
_trailingGapPoints = Param(nameof(TrailingGapPoints), 1000m)
.SetNotNegative()
.SetDisplay("Trailing Gap", "Offset between current price and trailing stop.", "Protection");
_maxSpreadPoints = Param(nameof(MaxSpreadPoints), 100m)
.SetNotNegative()
.SetDisplay("Max Spread", "Maximum spread allowed for new trades (price steps).", "Filters");
_slippagePoints = Param(nameof(SlippagePoints), 3m)
.SetNotNegative()
.SetDisplay("Slippage", "Expected slippage in price steps. Used for reference only.", "Filters");
_riskMode = Param(nameof(MoneyRiskMode), RiskModes.BalancePercentage)
.SetDisplay("Risk Mode", "Choose whether risk is fixed or percentage based.", "Risk Management");
_riskValue = Param(nameof(RiskValue), 5m)
.SetNotNegative()
.SetDisplay("Risk Value", "Risk amount in currency or percent.", "Risk Management");
_tradeComment = Param(nameof(TradeComment), "RRS")
.SetDisplay("Trade Comment", "Informational comment attached to generated orders.", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle type used to trigger the strategy logic.", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType), (Security, DataType.Level1)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetRuntimeState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (MaxVolume < MinVolume)
throw new InvalidOperationException($"{nameof(MaxVolume)} cannot be less than {nameof(MinVolume)}.");
ResetRuntimeState();
UpdateInstrumentValues();
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
SubscribeLevel1()
.Bind(ProcessLevel1)
.Start();
}
private void ResetRuntimeState()
{
_randomState = _initialRandomState;
_trailingStopPrice = null;
_openLongNext = true;
_entryPrice = 0m;
_bestBid = null;
_bestAsk = null;
_priceStep = 0m;
_stepPrice = 0m;
}
private void UpdateInstrumentValues()
{
_priceStep = Security?.PriceStep ?? 0m;
_stepPrice = 0m;
}
private void ProcessLevel1(Level1ChangeMessage message)
{
if (message.TryGetDecimal(Level1Fields.BestBidPrice) is decimal bid && bid > 0m)
_bestBid = bid;
if (message.TryGetDecimal(Level1Fields.BestAskPrice) is decimal ask && ask > 0m)
_bestAsk = ask;
if (message.TryGetDecimal(Level1Fields.PriceStep) is decimal priceStep && priceStep > 0m)
_priceStep = priceStep;
if (message.TryGetDecimal(Level1Fields.StepPrice) is decimal stepPrice && stepPrice > 0m)
_stepPrice = stepPrice;
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var price = candle.ClosePrice;
if (ApplyProtection(price) || ApplyTrailing(price) || ApplyRiskControl(price))
return;
TryOpenTrade();
}
private bool ApplyProtection(decimal marketPrice)
{
if (Position == 0)
return false;
var priceStep = GetPriceStep();
var entryPrice = _entryPrice;
if (entryPrice <= 0m)
return false;
if (Position > 0)
{
if (StopLossPoints > 0m)
{
var stopPrice = entryPrice - StopLossPoints * priceStep;
if (marketPrice <= stopPrice)
return ClosePosition("Stop loss");
}
if (TakeProfitPoints > 0m)
{
var takePrice = entryPrice + TakeProfitPoints * priceStep;
if (marketPrice >= takePrice)
return ClosePosition("Take profit");
}
}
else if (Position < 0)
{
if (StopLossPoints > 0m)
{
var stopPrice = entryPrice + StopLossPoints * priceStep;
if (marketPrice >= stopPrice)
return ClosePosition("Stop loss");
}
if (TakeProfitPoints > 0m)
{
var takePrice = entryPrice - TakeProfitPoints * priceStep;
if (marketPrice <= takePrice)
return ClosePosition("Take profit");
}
}
return false;
}
private bool ApplyTrailing(decimal marketPrice)
{
if (Position == 0 || TrailingGapPoints <= 0m || TrailingStartPoints <= 0m)
{
_trailingStopPrice = null;
return false;
}
var priceStep = GetPriceStep();
var entryPrice = _entryPrice;
if (entryPrice <= 0m)
return false;
var gap = TrailingGapPoints * priceStep;
var triggerDistance = (TrailingStartPoints + TrailingGapPoints) * priceStep;
if (Position > 0)
{
var profit = marketPrice - entryPrice;
if (profit >= triggerDistance)
{
var candidate = marketPrice - gap;
if (_trailingStopPrice == null || candidate > _trailingStopPrice)
_trailingStopPrice = candidate;
}
if (_trailingStopPrice != null && marketPrice <= _trailingStopPrice)
return ClosePosition("Trailing stop");
}
else if (Position < 0)
{
var profit = entryPrice - marketPrice;
if (profit >= triggerDistance)
{
var candidate = marketPrice + gap;
if (_trailingStopPrice == null || candidate < _trailingStopPrice)
_trailingStopPrice = candidate;
}
if (_trailingStopPrice != null && marketPrice >= _trailingStopPrice)
return ClosePosition("Trailing stop");
}
return false;
}
private bool ApplyRiskControl(decimal marketPrice)
{
if (Position == 0m || _entryPrice <= 0m)
return false;
var riskLimit = GetRiskLimit();
if (riskLimit is null)
return false;
var liquidationPrice = Position > 0m ? _bestBid ?? marketPrice : _bestAsk ?? marketPrice;
var floatingPnL = CalculateFloatingPnL(liquidationPrice);
return floatingPnL <= -riskLimit.Value && ClosePosition("Risk control");
}
private decimal? GetRiskLimit()
{
var risk = Math.Abs(RiskValue);
if (MoneyRiskMode == RiskModes.FixedMoney)
return risk;
var portfolioValue = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
return portfolioValue > 0m ? portfolioValue * risk / 100m : null;
}
private decimal CalculateFloatingPnL(decimal marketPrice)
{
var direction = Position > 0m ? 1m : -1m;
var difference = (marketPrice - _entryPrice) * direction;
var volume = Math.Abs(Position);
if (_stepPrice > 0m)
return difference / GetPriceStep() * _stepPrice * volume;
return difference * (Security?.Multiplier ?? 1m) * volume;
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (Position != 0m && _entryPrice == 0m)
_entryPrice = trade.Trade.Price;
if (Position == 0m)
{
_entryPrice = 0m;
_trailingStopPrice = null;
}
}
private bool ClosePosition(string reason)
{
var volume = Math.Abs(Position);
if (volume <= 0m)
return false;
SubmitMarket(Position > 0m ? Sides.Sell : Sides.Buy, volume, reason);
_trailingStopPrice = null;
return true;
}
private void TryOpenTrade()
{
if (Position != 0m || !IsSpreadAllowed())
return;
Sides? side = null;
if (Mode == TradingModes.DoubleSide)
{
side = _openLongNext ? Sides.Buy : Sides.Sell;
_openLongNext = !_openLongNext;
}
else if (Mode == TradingModes.OneSide)
{
var randomValue = NextRandomInt(6);
side = randomValue switch
{
1 or 4 => Sides.Buy,
0 or 3 => Sides.Sell,
_ => null,
};
}
if (side is null)
return;
var volume = GenerateVolume();
if (volume <= 0m)
return;
SubmitMarket(side.Value, volume, null);
}
private bool IsSpreadAllowed()
{
if (MaxSpreadPoints <= 0m)
return false;
if (_bestBid is not decimal bid || _bestAsk is not decimal ask)
return true;
if (ask < bid)
return false;
return (ask - bid) / GetPriceStep() <= MaxSpreadPoints;
}
private decimal GenerateVolume()
{
var min = Math.Max(MinVolume, Security?.MinVolume ?? 0m);
var max = Math.Min(MaxVolume, Security?.MaxVolume ?? decimal.MaxValue);
if (max < min)
return 0m;
var raw = min == max ? min : min + (max - min) * (decimal)NextRandomUnit();
var step = Security?.VolumeStep ?? 0m;
if (step <= 0m)
return raw;
var first = Math.Ceiling(min / step) * step;
var last = Math.Floor(max / step) * step;
if (first > last)
return 0m;
var aligned = Math.Floor(raw / step) * step;
return Math.Clamp(aligned, first, last);
}
private decimal GetPriceStep()
=> _priceStep > 0m ? _priceStep : 0.0001m;
private double NextRandomUnit()
{
_randomState = unchecked(_randomState * 1664525u + 1013904223u);
return _randomState / 4294967296d;
}
private int NextRandomInt(int maxExclusive)
=> (int)(NextRandomUnit() * maxExclusive);
private void SubmitMarket(Sides side, decimal volume, string reason)
{
var order = CreateOrder(side, 0m, volume);
order.Comment = string.IsNullOrWhiteSpace(reason)
? TradeComment
: string.IsNullOrWhiteSpace(TradeComment) ? reason : $"{TradeComment}: {reason}";
RegisterOrder(order);
}
/// <summary>
/// Trading mode options.
/// </summary>
public enum TradingModes
{
/// <summary>
/// Alternate between long and short entries every cycle.
/// </summary>
DoubleSide,
/// <summary>
/// Enter only when the random generator matches specific values.
/// </summary>
OneSide,
}
/// <summary>
/// Risk management configuration.
/// </summary>
public enum RiskModes
{
/// <summary>
/// Risk is defined as a fixed currency value.
/// </summary>
FixedMoney,
/// <summary>
/// Risk is calculated as a percentage of the portfolio value.
/// </summary>
BalancePercentage,
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import CandleStates, DataType, Level1Fields, Sides
from StockSharp.Algo.Strategies import Strategy
class rrs_randomness_strategy(Strategy):
MODE_DOUBLE_SIDE = 0
MODE_ONE_SIDE = 1
RISK_FIXED_MONEY = 0
RISK_BALANCE_PERCENTAGE = 1
INITIAL_RANDOM_STATE = 3710
def __init__(self):
super(rrs_randomness_strategy, self).__init__()
self._mode = self.Param("Mode", self.MODE_DOUBLE_SIDE) \
.SetDisplay("Trading Mode", "Alternate every cycle or use random gated entries", "General")
self._min_volume = self.Param("MinVolume", 0.01) \
.SetGreaterThanZero() \
.SetDisplay("Min Volume", "Minimum volume for a market order", "Lot Settings")
self._max_volume = self.Param("MaxVolume", 0.5) \
.SetGreaterThanZero() \
.SetDisplay("Max Volume", "Maximum volume for a market order", "Lot Settings")
self._tp_points = self.Param("TakeProfitPoints", 2000.0) \
.SetNotNegative() \
.SetDisplay("Take Profit", "TP in price steps", "Protection")
self._sl_points = self.Param("StopLossPoints", 3000.0) \
.SetNotNegative() \
.SetDisplay("Stop Loss", "SL in price steps", "Protection")
self._trailing_start = self.Param("TrailingStartPoints", 1500.0) \
.SetNotNegative() \
.SetDisplay("Trailing Start", "Profit to enable trailing", "Protection")
self._trailing_gap = self.Param("TrailingGapPoints", 1000.0) \
.SetNotNegative() \
.SetDisplay("Trailing Gap", "Trailing offset", "Protection")
self._max_spread = self.Param("MaxSpreadPoints", 100.0) \
.SetNotNegative() \
.SetDisplay("Max Spread", "Maximum spread for new entries in price steps", "Filters")
self._slippage = self.Param("SlippagePoints", 3.0) \
.SetNotNegative() \
.SetDisplay("Slippage", "Informational slippage in price steps", "Filters")
self._risk_mode = self.Param("MoneyRiskMode", self.RISK_BALANCE_PERCENTAGE) \
.SetDisplay("Risk Mode", "Fixed money or portfolio percentage", "Risk Management")
self._risk_value = self.Param("RiskValue", 5.0) \
.SetNotNegative() \
.SetDisplay("Risk Value", "Risk amount in money or percent", "Risk Management")
self._trade_comment = self.Param("TradeComment", "RRS") \
.SetDisplay("Trade Comment", "Comment attached to generated orders", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._reset_runtime()
@property
def Mode(self):
return self._mode.Value
@Mode.setter
def Mode(self, value):
self._mode.Value = value
@property
def MinVolume(self):
return self._min_volume.Value
@MinVolume.setter
def MinVolume(self, value):
self._min_volume.Value = value
@property
def MaxVolume(self):
return self._max_volume.Value
@MaxVolume.setter
def MaxVolume(self, value):
self._max_volume.Value = value
@property
def MaxSpreadPoints(self):
return self._max_spread.Value
@MaxSpreadPoints.setter
def MaxSpreadPoints(self, value):
self._max_spread.Value = value
@property
def MoneyRiskMode(self):
return self._risk_mode.Value
@MoneyRiskMode.setter
def MoneyRiskMode(self, value):
self._risk_mode.Value = value
@property
def RiskValue(self):
return self._risk_value.Value
@RiskValue.setter
def RiskValue(self, value):
self._risk_value.Value = value
@property
def TradeComment(self):
return self._trade_comment.Value
@TradeComment.setter
def TradeComment(self, value):
self._trade_comment.Value = value
@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_randomness_strategy, self).OnReseted()
self._reset_runtime()
def OnStarted2(self, time):
super(rrs_randomness_strategy, self).OnStarted2(time)
if float(self._max_volume.Value) < float(self._min_volume.Value):
raise ValueError("MaxVolume cannot be less than MinVolume.")
self._reset_runtime()
self._update_instrument_values()
self.SubscribeCandles(self.CandleType) \
.Bind(self.OnProcess) \
.Start()
self.SubscribeLevel1() \
.Bind(self._process_level1) \
.Start()
def _reset_runtime(self):
self._random_state = self.INITIAL_RANDOM_STATE
self._trailing_stop = None
self._open_long_next = True
self._entry_price = 0.0
self._best_bid = None
self._best_ask = None
self._price_step = 0.0
self._step_price = 0.0
def _update_instrument_values(self):
if self.Security is None:
return
price_step = self.Security.PriceStep
if price_step is not None and price_step > 0:
self._price_step = float(price_step)
def _process_level1(self, message):
bid = message.TryGetDecimal(Level1Fields.BestBidPrice)
ask = message.TryGetDecimal(Level1Fields.BestAskPrice)
price_step = message.TryGetDecimal(Level1Fields.PriceStep)
step_price = message.TryGetDecimal(Level1Fields.StepPrice)
if bid is not None and bid > 0:
self._best_bid = float(bid)
if ask is not None and ask > 0:
self._best_ask = float(ask)
if price_step is not None and price_step > 0:
self._price_step = float(price_step)
if step_price is not None and step_price > 0:
self._step_price = float(step_price)
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
if self._apply_protection(close):
return
if self._apply_trailing(close):
return
if self._apply_risk_control(close):
return
self._try_open_trade()
def _apply_protection(self, price):
if self.Position == 0 or self._entry_price <= 0:
return False
step = self._get_price_step()
stop_points = float(self._sl_points.Value)
take_points = float(self._tp_points.Value)
if self.Position > 0:
if stop_points > 0 and price <= self._entry_price - stop_points * step:
return self._close_position("Stop loss")
if take_points > 0 and price >= self._entry_price + take_points * step:
return self._close_position("Take profit")
else:
if stop_points > 0 and price >= self._entry_price + stop_points * step:
return self._close_position("Stop loss")
if take_points > 0 and price <= self._entry_price - take_points * step:
return self._close_position("Take profit")
return False
def _apply_trailing(self, price):
start_points = float(self._trailing_start.Value)
gap_points = float(self._trailing_gap.Value)
if self.Position == 0 or start_points <= 0 or gap_points <= 0:
self._trailing_stop = None
return False
if self._entry_price <= 0:
return False
step = self._get_price_step()
gap = gap_points * step
trigger = (start_points + gap_points) * step
if self.Position > 0:
if price - self._entry_price >= trigger:
candidate = price - gap
if self._trailing_stop is None or candidate > self._trailing_stop:
self._trailing_stop = candidate
if self._trailing_stop is not None and price <= self._trailing_stop:
return self._close_position("Trailing stop")
else:
if self._entry_price - price >= trigger:
candidate = price + gap
if self._trailing_stop is None or candidate < self._trailing_stop:
self._trailing_stop = candidate
if self._trailing_stop is not None and price >= self._trailing_stop:
return self._close_position("Trailing stop")
return False
def _apply_risk_control(self, price):
if self.Position == 0 or self._entry_price <= 0:
return False
risk_limit = self._get_risk_limit()
if risk_limit is None:
return False
liquidation_price = price
if self.Position > 0 and self._best_bid is not None:
liquidation_price = self._best_bid
elif self.Position < 0 and self._best_ask is not None:
liquidation_price = self._best_ask
if self._calculate_floating_pnl(liquidation_price) <= -risk_limit:
return self._close_position("Risk control")
return False
def _get_risk_limit(self):
risk = abs(float(self._risk_value.Value))
if int(self._risk_mode.Value) == self.RISK_FIXED_MONEY:
return risk
portfolio_value = 0.0
if self.Portfolio is not None:
value = self.Portfolio.CurrentValue
if value is None or value <= 0:
value = self.Portfolio.BeginValue
if value is not None:
portfolio_value = float(value)
return portfolio_value * risk / 100.0 if portfolio_value > 0 else None
def _calculate_floating_pnl(self, market_price):
direction = 1.0 if self.Position > 0 else -1.0
difference = (market_price - self._entry_price) * direction
volume = float(abs(self.Position))
if self._step_price > 0:
return difference / self._get_price_step() * self._step_price * volume
multiplier = 1.0
if self.Security is not None and self.Security.Multiplier is not None:
multiplier = float(self.Security.Multiplier)
return difference * multiplier * volume
def OnOwnTradeReceived(self, trade):
super(rrs_randomness_strategy, self).OnOwnTradeReceived(trade)
if self.Position != 0 and self._entry_price <= 0:
self._entry_price = float(trade.Trade.TradePrice)
if self.Position == 0:
self._entry_price = 0.0
self._trailing_stop = None
def _close_position(self, reason):
volume = abs(self.Position)
if volume <= 0:
return False
side = Sides.Sell if self.Position > 0 else Sides.Buy
self._submit_market(side, volume, reason)
self._trailing_stop = None
return True
def _try_open_trade(self):
if self.Position != 0 or not self._is_spread_allowed():
return
side = None
mode = int(self._mode.Value)
if mode == self.MODE_DOUBLE_SIDE:
side = Sides.Buy if self._open_long_next else Sides.Sell
self._open_long_next = not self._open_long_next
elif mode == self.MODE_ONE_SIDE:
random_value = self._next_random_int(6)
if random_value == 1 or random_value == 4:
side = Sides.Buy
elif random_value == 0 or random_value == 3:
side = Sides.Sell
if side is None:
return
volume = self._generate_volume()
if volume <= 0:
return
self._submit_market(side, volume, None)
def _is_spread_allowed(self):
max_spread = float(self._max_spread.Value)
if max_spread <= 0:
return False
if self._best_bid is None or self._best_ask is None:
return True
if self._best_ask < self._best_bid:
return False
spread_points = (self._best_ask - self._best_bid) / self._get_price_step()
return spread_points <= max_spread
def _generate_volume(self):
security_min = 0.0
security_max = float("inf")
step = 0.0
if self.Security is not None:
if self.Security.MinVolume is not None:
security_min = float(self.Security.MinVolume)
if self.Security.MaxVolume is not None:
security_max = float(self.Security.MaxVolume)
if self.Security.VolumeStep is not None:
step = float(self.Security.VolumeStep)
minimum = max(float(self._min_volume.Value), security_min)
maximum = min(float(self._max_volume.Value), security_max)
if maximum < minimum:
return 0.0
raw = minimum if minimum == maximum else minimum + (maximum - minimum) * self._next_random_unit()
if step <= 0:
return raw
first = int(-(-minimum // step)) * step
last = int(maximum // step) * step
if first > last:
return 0.0
aligned = int(raw // step) * step
return round(max(first, min(aligned, last)), 12)
def _get_price_step(self):
return self._price_step if self._price_step > 0 else 0.0001
def _next_random_unit(self):
self._random_state = (self._random_state * 1664525 + 1013904223) & 0xFFFFFFFF
return float(self._random_state) / 4294967296.0
def _next_random_int(self, maximum):
return int(self._next_random_unit() * maximum)
def _submit_market(self, side, volume, reason):
order = self.CreateOrder(side, 0, volume)
base_comment = self._trade_comment.Value
if reason is None or reason == "":
order.Comment = base_comment
elif base_comment is None or base_comment == "":
order.Comment = reason
else:
order.Comment = "{0}: {1}".format(base_comment, reason)
self.RegisterOrder(order)
def CreateClone(self):
return rrs_randomness_strategy()