Estratégia de Aleatoriedade RRS
Visão geral
A Estratégia de Aleatoriedade RRS é uma versão StockSharp da “RRS Aleatoriedade na Natureza EA” para MetaTrader 4. Ela emula o Expert Advisor original com entradas de compra ou venda pseudoaleatórias, verificações de stop-loss e take-profit em candles concluídos, trailing opcional e liquidação quando a perda flutuante atinge o limite configurado.
Como StockSharp usa posições líquidas por título, a exposição simultânea comprada e vendida não é suportada. Assim, DoubleSide começa comprado e alterna a direção após cada entrada, em vez de manter duas negociações protegidas como no MetaTrader.
Lógica de negociação
- Em cada candle concluído, a estratégia usa o fechamento para as proteções e, quando disponíveis, bid/ask de Nível 1 para o spread e o preço de liquidação.
- Com posição aberta, verifica stop loss, take profit, trailing stop e o limite de perda flutuante; envia no máximo uma ordem de fechamento por candle.
- Quando estável, valida as restrições de spread e volume antes de abrir uma nova negociação:
- DoubleSide alterna entre compra e venda, começando por compra.
- OneSide usa um inteiro pseudoaleatório repetível em
[0,5]:1ou4compra,0ou3vende e2ou5ignora o candle. A sequência reinicia ao iniciar ou resetar a estratégia.
- Os volumes de negociação são desenhados uniformemente entre o mínimo e o máximo configurados e alinhados ao passo de volume do instrumento.
Parâmetros
| Grupo | Nome | Descrição |
|---|---|---|
| Geral | Mode |
Entradas alternadas (DoubleSide, 0) ou filtradas aleatoriamente (OneSide, 1). |
| Configurações de lote | MinVolume / MaxVolume |
Faixa de volume para negociações geradas aleatoriamente. |
| Proteção | TakeProfitPoints |
Distância de lucro em etapas de preço. |
| Proteção | StopLossPoints |
Distância de stop-loss em etapas de preço. |
| Proteção | TrailingStartPoints |
Distância de lucro que permite o gerenciamento de trailing stop. |
| Proteção | TrailingGapPoints |
Compensação entre o preço de mercado e o trailing stop. |
| Filtros | MaxSpreadPoints |
Spread máximo de Nível 1 em passos de preço. Zero bloqueia novas entradas; valor positivo permite fallback por candles sem bid/ask. |
| Filtros | SlippagePoints |
Configuração de deslizamento informativo (não aplicada automaticamente). |
| Gestão de Risco | MoneyRiskMode |
Perda fixa (FixedMoney, 0) ou percentual do portfólio (BalancePercentage, 1). |
| Gestão de Risco | RiskValue |
Quantidade de risco (moeda ou porcentagem dependendo da modalidade). |
| Geral | TradeComment |
Comentário das entradas; ordens de fechamento acrescentam o motivo do acionamento. |
| Geral | CandleType |
Série de velas conduzindo o ciclo de decisão. |
Notas
- Cotações de Nível 1 melhoram o cálculo do spread e do preço de liquidação. Sem os dois lados, limite positivo permite fallback por candles;
MaxSpreadPoints = 0sempre bloqueia entradas. - As proteções são avaliadas em candles concluídos. O trailing ativa após
TrailingStartPoints + TrailingGapPointspassos de lucro e acompanha o preço aTrailingGapPoints. FixedMoneyinterpretaRiskValuena moeda da conta;BalancePercentageusa esse percentual do valor atual do portfólio.
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()