RRS Randomness Strategy
Overview
The RRS Randomness Strategy is a StockSharp port of the “RRS Randomness in Nature EA” for MetaTrader 4.
It emulates the original expert advisor by generating random long or short market entries, applies stop-loss and take-profit levels, optionally trails profitable trades, and performs risk-based liquidation when floating losses exceed the configured threshold.
Because StockSharp uses net positions per security, simultaneous long and short exposure is not supported. The "DoubleSide" mode therefore alternates the entry direction on each opportunity instead of maintaining two hedged trades as in MetaTrader.
Trading Logic
- On every finished candle the strategy evaluates the latest market price obtained from trades or Level1 quotes.
- If there is an open position it enforces stop-loss, take-profit and trailing-stop rules and performs a portfolio risk check.
- When flat, it validates spread and volume constraints before opening a new trade:
- DoubleSide mode alternates between long and short entries.
- OneSide mode follows the original EA rule: a random integer in
[0,5] opens longs for values 1 or 4 and shorts for 0 or 3.
- Trade volumes are drawn uniformly between the configured minimum and maximum and are aligned to the instrument volume step.
Parameters
| Group |
Name |
Description |
| General |
Mode |
Trading mode: alternate entries (DoubleSide) or random gated entries (OneSide). |
| Lot Settings |
MinVolume / MaxVolume |
Volume range for randomly generated trades. |
| Protection |
TakeProfitPoints |
Take-profit distance in price steps. |
| Protection |
StopLossPoints |
Stop-loss distance in price steps. |
| Protection |
TrailingStartPoints |
Profit distance that enables trailing stop management. |
| Protection |
TrailingGapPoints |
Offset between market price and trailing stop. |
| Filters |
MaxSpreadPoints |
Maximum allowed spread (in price steps) for opening new positions. |
| Filters |
SlippagePoints |
Informational slippage setting (not enforced automatically). |
| Risk Management |
MoneyRiskMode |
Choose between fixed currency loss or percent of portfolio value. |
| Risk Management |
RiskValue |
Amount of risk (currency or percent depending on the mode). |
| General |
TradeComment |
Informational comment attached to generated orders. |
| General |
CandleType |
Candle series driving the decision loop. |
Notes
- The strategy relies on market data subscriptions for candles, Level1 quotes and trades. Ensure the selected data type is available for the chosen security.
- Trailing stop logic mirrors the MQL implementation: it activates after the price gains
TrailingStartPoints + TrailingGapPoints steps and then follows price at a distance of TrailingGapPoints.
- Risk management compares floating PnL with the configured loss threshold and liquidates the position when the threshold is breached.
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>
/// 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 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 int _tradeCounter;
private decimal? _trailingStopPrice;
private bool _openLongNext;
private decimal _entryPrice;
/// <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)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_tradeCounter = 0;
_trailingStopPrice = null;
_openLongNext = true;
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var price = candle.ClosePrice;
ApplyProtection(price);
ApplyTrailing(price);
TryOpenTrade();
}
private void ApplyProtection(decimal marketPrice)
{
if (Position == 0)
return;
var priceStep = Security.PriceStep ?? 0.0001m;
if (priceStep <= 0m)
priceStep = 0.0001m;
var entryPrice = _entryPrice;
if (entryPrice <= 0m)
return;
if (Position > 0)
{
if (StopLossPoints > 0m)
{
var stopPrice = entryPrice - StopLossPoints * priceStep;
if (marketPrice <= stopPrice)
{
SellMarket(Math.Abs(Position));
_trailingStopPrice = null;
return;
}
}
if (TakeProfitPoints > 0m)
{
var takePrice = entryPrice + TakeProfitPoints * priceStep;
if (marketPrice >= takePrice)
{
SellMarket(Math.Abs(Position));
_trailingStopPrice = null;
}
}
}
else if (Position < 0)
{
if (StopLossPoints > 0m)
{
var stopPrice = entryPrice + StopLossPoints * priceStep;
if (marketPrice >= stopPrice)
{
BuyMarket(Math.Abs(Position));
_trailingStopPrice = null;
return;
}
}
if (TakeProfitPoints > 0m)
{
var takePrice = entryPrice - TakeProfitPoints * priceStep;
if (marketPrice <= takePrice)
{
BuyMarket(Math.Abs(Position));
_trailingStopPrice = null;
}
}
}
}
private void ApplyTrailing(decimal marketPrice)
{
if (Position == 0 || TrailingGapPoints <= 0m || TrailingStartPoints <= 0m)
return;
var priceStep = Security.PriceStep ?? 0m;
if (priceStep <= 0m)
return;
var entryPrice = _entryPrice;
if (entryPrice <= 0m)
return;
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)
{
SellMarket(Math.Abs(Position));
_trailingStopPrice = null;
}
}
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)
{
BuyMarket(Math.Abs(Position));
_trailingStopPrice = null;
}
}
}
/// <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 void ClosePosition()
{
var volume = Math.Abs(Position);
if (volume <= 0m)
return;
if (Position > 0)
SellMarket(volume);
else if (Position < 0)
BuyMarket(volume);
}
private void TryOpenTrade()
{
if (Position != 0)
return;
if (_openLongNext)
{
BuyMarket(Volume);
}
else
{
SellMarket(Volume);
}
_openLongNext = !_openLongNext;
_tradeCounter++;
}
/// <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.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_randomness_strategy(Strategy):
def __init__(self):
super(rrs_randomness_strategy, self).__init__()
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._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_randomness_strategy, self).OnReseted()
self._trailing_stop = None
self._open_long_next = True
self._entry_price = 0
def OnStarted2(self, time):
super(rrs_randomness_strategy, self).OnStarted2(time)
self._trailing_stop = None
self._open_long_next = True
self._entry_price = 0
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)
self._apply_protection(close)
self._apply_trailing(close)
if self.Position != 0:
return
if self._open_long_next:
self.BuyMarket()
self._entry_price = close
else:
self.SellMarket()
self._entry_price = close
self._open_long_next = not self._open_long_next
def _apply_protection(self, price):
if self.Position == 0 or self._entry_price <= 0:
return
step = self._step
if self.Position > 0:
if self._sl_points.Value > 0:
sl = self._entry_price - self._sl_points.Value * step
if price <= sl:
self.SellMarket()
self._trailing_stop = None
return
if self._tp_points.Value > 0:
tp = self._entry_price + self._tp_points.Value * step
if price >= tp:
self.SellMarket()
self._trailing_stop = None
elif self.Position < 0:
if self._sl_points.Value > 0:
sl = self._entry_price + self._sl_points.Value * step
if price >= sl:
self.BuyMarket()
self._trailing_stop = None
return
if self._tp_points.Value > 0:
tp = self._entry_price - self._tp_points.Value * step
if price <= tp:
self.BuyMarket()
self._trailing_stop = None
def _apply_trailing(self, price):
if self.Position == 0 or self._trailing_gap.Value <= 0 or self._trailing_start.Value <= 0:
return
step = self._step
gap = self._trailing_gap.Value * step
trigger = (self._trailing_start.Value + self._trailing_gap.Value) * step
if self.Position > 0:
profit = price - self._entry_price
if profit > 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:
self.SellMarket()
self._trailing_stop = None
elif self.Position < 0:
profit = self._entry_price - price
if profit > 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:
self.BuyMarket()
self._trailing_stop = None
def CreateClone(self):
return rrs_randomness_strategy()