RRS 随机策略
概述
RRS Randomness Strategy 是 MetaTrader 4 上 “RRS Randomness in Nature EA” 的 StockSharp 移植版。
策略会随机打开多头或空头市价单,设置止损与止盈,可选地启动跟踪止损,并在浮动亏损超过设定阈值时触发风险控制强制平仓。
由于 StockSharp 对同一品种采用净持仓模式,不能同时持有多头和空头仓位。因此 “DoubleSide” 模式会交替开仓,而不是像 MetaTrader 那样同时维持两笔对冲交易。
交易逻辑
- 每根 K 线收盘后,策略会根据最新成交价或 Level1 报价计算当前市场价格。
- 如果存在持仓,则依次检查止损、止盈与跟踪止损条件,并根据浮动盈亏评估风险。
- 在空仓状态下,先检查点差与可用成交量再决定是否进场:
- DoubleSide 模式按顺序交替开多或开空。
- OneSide 模式沿用原版规则:从
[0,5]之间生成的随机整数,当结果为1或4时开多,为0或3时开空。
- 下单手数在最小与最大值之间均匀随机,并会按照交易品种的最小变动手数进行对齐。
参数
| 组别 | 名称 | 说明 |
|---|---|---|
| General | Mode |
选择方向逻辑:交替进场 (DoubleSide) 或随机过滤进场 (OneSide)。 |
| Lot Settings | MinVolume / MaxVolume |
随机生成手数的范围。 |
| Protection | TakeProfitPoints |
止盈距离(价格步长)。 |
| Protection | StopLossPoints |
止损距离(价格步长)。 |
| Protection | TrailingStartPoints |
激活跟踪止损所需的盈利距离。 |
| Protection | TrailingGapPoints |
跟踪止损相对当前价格的距离。 |
| Filters | MaxSpreadPoints |
允许进场的最大点差(以价格步长计)。 |
| Filters | SlippagePoints |
预期滑点,仅作参考。 |
| Risk Management | MoneyRiskMode |
风险模式:固定金额或按账户价值百分比。 |
| Risk Management | RiskValue |
风险阈值(货币金额或百分比,取决于模式)。 |
| General | TradeComment |
附加在订单上的说明文字。 |
| General | CandleType |
用于驱动策略的 K 线类型。 |
备注
- 策略需要订阅 K 线、Level1 报价以及成交数据,请确保所选品种提供这些数据。
- 跟踪止损逻辑与 MQL 版本一致:当盈利超过
TrailingStartPoints + TrailingGapPoints个价格步长时启动,并始终保持TrailingGapPoints的距离。 - 风险控制会比较浮动盈亏与设定阈值,一旦亏损超限立即平仓。
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()