RRS カオス戦略
概要
オリジナルの RRS Chaotic EA は、ティックごとにサイコロを振り続け、成行注文を発行する前にランダムなシンボルとポジション サイズを選択します。 StockSharp ポートは、設定されたセキュリティ上のキャンドル ストリームからエントリを駆動することで、制御されたカオスの精神を維持します。閉じたローソク足はそれぞれ、エキスパートアドバイザーの資金管理ルールを反映しながら、方向と量の両方について新しいランダムな決定をトリガーします。
主な特長
- ランダムエントリー – 完了したローソク足ごとに 0 から 10 までのランダムな整数が生成されます。値
6または9はロングポジションをオープンし、3または8はショートポジションをオープンし、MT4 ロジックと一致します。 - 可変ボリューム – 取引ボリュームは、MinVolume パラメータと MaxVolume パラメータの間で均一にサンプリングされ、証券のボリュームステップに合わせて調整されます。
- スプレッド フィルター – 現在のスプレッド (ポイント単位) が MaxSpreadPoints を超えるたびに、新しいポジションはブロックされます。
- テイクプロフィットとストップロス – エキスパートの注文レベルの設定を再現するオプションのポイントベースのエグジット。
- ドローダウン ガード – 未実現損失は、固定の現金限度額またはポートフォリオ価値の割合と継続的に比較されます。制限に違反するとアクティブな注文がキャンセルされ、ポジションが平坦になります。
パラメーター
| 名前 | 説明 |
|---|---|
CandleType |
戦略をトリガーするために使用されるローソク足シリーズ (デフォルトの 1 分ローソク足)。 |
MinVolume / MaxVolume |
ランダムなロット生成の範囲。 |
TakeProfitPoints |
価格ポイントでの利食い距離。無効にするには、0 に設定します。 |
StopLossPoints |
価格ポイントでのストップロス距離。無効にするには、0 に設定します。 |
MaxOpenTrades |
同時に開いたままにすることができるボリュームステップで測定された最大正味ボリューム。 |
MaxSpreadPoints |
価格ポイントで表される最大許容スプレッド。 |
SlippagePoints |
情報的なスリッページ パラメータ (完全を期すために保持)。 |
RiskControlMode |
FixedMoney と BalancePercentage のリスク モデルを選択します。 |
RiskValue |
モードに応じて、リスクを負う金額または資本の割合のいずれかになります。 |
TradeComment |
監査を容易にするために、生成された注文にタグが追加されます。 |
戦略ロジック
- 設定されたキャンドル シリーズを購読し、完成したキャンドルを待ちます。
- ドローダウン制御を適用します。含み損がしきい値を超えた場合は、アクティブな注文をキャンセルし、現在のポジションを閉じます。
- MT4 の注文設定を反映するオプションのストップロスとテイクプロフィットのターゲットを維持します。
- 取引が許可され、スプレッドが許容できる場合は、乱数をロールして、ロングポジションをオープンするかショートポジションをオープンするかを決定します。
- 音量ステップ数を
MaxOpenTradesに制限することで、累積露出を制限します。
MQL4 バージョンとの違い
- 元の専門家は複数のランダムなシンボルを取引しました。 StockSharp 戦略は単一の証券で動作します。したがって、ランダム性は方向とサイズにのみ適用されます。
- プロテクティブストップは、ネイティブのストップロス/テイクプロフィット注文パラメーターではなく、ローソク足の終値で成行注文を通じて実行されます。
- スプレッドの評価では、MT4
MarketInfo関数の代わりに現在の最高の買値/売値が使用されます。 - 生成されたすべての注文には TradeComment テキストが含まれており、MT4 マジック ナンバーと同様のコンテキストを提供します。
使用上の注意
- 正確なポイントから価格への変換のために、接続されたセキュリティが有効な
PriceStep、MinStep、およびVolumeStepの値を公開していることを確認します。 - バックテスト パイプラインに負担をかけずにティック レベルのランダム性をエミュレートするために、デフォルトのローソク足の時間枠は 1 分です。取引頻度を減らすには、時間枠を増やします。
- リスク管理は、集約されたポジションから導き出される未実現損益に依存します。 MT4 バージョンに見られるようなロング/ショートの混合バスケットは、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>
/// Randomized breakout/mean-reversion hybrid that mimics the behaviour of the RRS Chaotic EA.
/// The strategy opens random buy or sell trades with variable volume while enforcing a risk budget.
/// </summary>
public class RrsChaoticStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _minVolume;
private readonly StrategyParam<decimal> _maxVolume;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _maxOpenTrades;
private readonly StrategyParam<int> _maxSpreadPoints;
private readonly StrategyParam<int> _slippagePoints;
private readonly StrategyParam<RiskModes> _riskMode;
private readonly StrategyParam<decimal> _riskValue;
private readonly StrategyParam<string> _tradeComment;
private int _tradeCounter;
private decimal? _longStopPrice;
private decimal? _shortStopPrice;
private decimal? _longTakePrice;
private decimal? _shortTakePrice;
private decimal _initialEquity;
private decimal _entryPrice;
/// <summary>
/// Trade direction for risk sizing.
/// </summary>
public enum RiskModes
{
/// <summary>
/// Risk a fixed cash amount.
/// </summary>
FixedMoney,
/// <summary>
/// Risk a percentage of the portfolio value.
/// </summary>
BalancePercentage
}
/// <summary>
/// Initializes a new instance of <see cref="RrsChaoticStrategy"/>.
/// </summary>
public RrsChaoticStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary candle series used to drive random entries.", "General");
_minVolume = Param(nameof(MinVolume), 0.01m)
.SetDisplay("Minimum Volume", "Lower bound for the randomly generated order volume.", "Trading");
_maxVolume = Param(nameof(MaxVolume), 0.5m)
.SetDisplay("Maximum Volume", "Upper bound for the randomly generated order volume.", "Trading");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 50000)
.SetDisplay("Take Profit", "Distance in points for the optional take-profit target.", "Risk");
_stopLossPoints = Param(nameof(StopLossPoints), 50000)
.SetDisplay("Stop Loss", "Distance in points for the protective stop-loss.", "Risk");
_maxOpenTrades = Param(nameof(MaxOpenTrades), 10)
.SetDisplay("Max Open Trades", "Maximum net volume measured in volume steps that may stay open.", "Trading");
_maxSpreadPoints = Param(nameof(MaxSpreadPoints), 100)
.SetDisplay("Max Spread", "Maximum allowed spread in points before new entries are blocked.", "Trading");
_slippagePoints = Param(nameof(SlippagePoints), 3)
.SetDisplay("Slippage", "Slippage tolerance in points (informational only).", "Trading")
;
_riskMode = Param(nameof(RiskControlMode), RiskModes.BalancePercentage)
.SetDisplay("Risk Mode", "Choose between fixed cash or balance percentage drawdown control.", "Risk");
_riskValue = Param(nameof(RiskValue), 5m)
.SetDisplay("Risk Value", "Either percentage of equity or fixed cash to risk before flattening.", "Risk");
_tradeComment = Param(nameof(TradeComment), "RRS")
.SetDisplay("Trade Comment", "Tag attached to generated orders for traceability.", "General")
;
}
/// <summary>
/// Candle type used to trigger the strategy logic.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Minimum random volume.
/// </summary>
public decimal MinVolume
{
get => _minVolume.Value;
set => _minVolume.Value = value;
}
/// <summary>
/// Maximum random volume.
/// </summary>
public decimal MaxVolume
{
get => _maxVolume.Value;
set => _maxVolume.Value = value;
}
/// <summary>
/// Take-profit distance in points.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Stop-loss distance in points.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Maximum simultaneously open trades expressed in volume steps.
/// </summary>
public int MaxOpenTrades
{
get => _maxOpenTrades.Value;
set => _maxOpenTrades.Value = value;
}
/// <summary>
/// Maximum allowed spread in points before entries are skipped.
/// </summary>
public int MaxSpreadPoints
{
get => _maxSpreadPoints.Value;
set => _maxSpreadPoints.Value = value;
}
/// <summary>
/// Slippage tolerance in points (informational parameter).
/// </summary>
public int SlippagePoints
{
get => _slippagePoints.Value;
set => _slippagePoints.Value = value;
}
/// <summary>
/// Selected risk control mode.
/// </summary>
public RiskModes RiskControlMode
{
get => _riskMode.Value;
set => _riskMode.Value = value;
}
/// <summary>
/// Risk magnitude expressed either as percentage or cash.
/// </summary>
public decimal RiskValue
{
get => _riskValue.Value;
set => _riskValue.Value = value;
}
/// <summary>
/// Comment appended to generated orders.
/// </summary>
public string TradeComment
{
get => _tradeComment.Value;
set => _tradeComment.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_tradeCounter = 0;
_longStopPrice = null;
_shortStopPrice = null;
_longTakePrice = null;
_shortTakePrice = null;
_initialEquity = 0m;
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_initialEquity = GetPortfolioValue();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
// Update protective levels based on the latest execution price.
var tradePrice = trade.Trade.Price;
var direction = trade.Order.Side;
if (Position != 0m && _entryPrice == 0m)
_entryPrice = tradePrice;
if (Position > 0m && direction == Sides.Buy)
{
_longStopPrice = CalculateStopPrice(true, tradePrice);
_longTakePrice = CalculateTakePrice(true, tradePrice);
_shortStopPrice = null;
_shortTakePrice = null;
}
else if (Position < 0m && direction == Sides.Sell)
{
_shortStopPrice = CalculateStopPrice(false, tradePrice);
_shortTakePrice = CalculateTakePrice(false, tradePrice);
_longStopPrice = null;
_longTakePrice = null;
}
if (Position == 0m)
{
_entryPrice = 0m;
_longStopPrice = null;
_longTakePrice = null;
_shortStopPrice = null;
_shortTakePrice = null;
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
HandleRiskControl(candle);
ApplyExitRules(candle);
if (Position != 0m)
return;
if (_tradeCounter % 2 == 0)
{
BuyMarket(Volume);
}
else
{
SellMarket(Volume);
}
_tradeCounter++;
}
private void ApplyExitRules(ICandleMessage candle)
{
if (Position > 0m)
{
if (_longTakePrice.HasValue && candle.HighPrice >= _longTakePrice.Value)
{
SellMarket(Math.Abs(Position));
return;
}
if (_longStopPrice.HasValue && candle.LowPrice <= _longStopPrice.Value)
{
SellMarket(Math.Abs(Position));
}
}
else if (Position < 0m)
{
if (_shortTakePrice.HasValue && candle.LowPrice <= _shortTakePrice.Value)
{
BuyMarket(Math.Abs(Position));
return;
}
if (_shortStopPrice.HasValue && candle.HighPrice >= _shortStopPrice.Value)
{
BuyMarket(Math.Abs(Position));
}
}
}
private void HandleRiskControl(ICandleMessage candle)
{
var threshold = CalculateRiskThreshold();
if (!threshold.HasValue)
return;
var unrealized = GetUnrealizedPnL(candle.ClosePrice);
if (unrealized <= threshold.Value)
{
ClosePosition();
}
}
private decimal? CalculateRiskThreshold()
{
return RiskControlMode switch
{
RiskModes.BalancePercentage => CalculatePercentageThreshold(),
RiskModes.FixedMoney => -Math.Abs(RiskValue),
_ => null
};
}
private decimal? CalculatePercentageThreshold()
{
var equity = GetPortfolioValue();
if (equity <= 0m)
return null;
return -Math.Abs(equity * RiskValue / 100m);
}
private decimal GetPriceStep()
{
var security = Security;
if (security == null)
return 0.0001m;
if ((security.PriceStep ?? 0m) > 0m)
return security.PriceStep.Value;
return 0.0001m;
}
private decimal? CalculateStopPrice(bool isLong, decimal entryPrice)
{
if (StopLossPoints <= 0)
return null;
var distance = StopLossPoints * GetPriceStep();
return isLong ? entryPrice - distance : entryPrice + distance;
}
private decimal? CalculateTakePrice(bool isLong, decimal entryPrice)
{
if (TakeProfitPoints <= 0)
return null;
var distance = TakeProfitPoints * GetPriceStep();
return isLong ? entryPrice + distance : entryPrice - distance;
}
private decimal GetUnrealizedPnL(decimal currentPrice)
{
if (Position == 0m)
return 0m;
var entry = _entryPrice;
if (entry == 0m)
return 0m;
var diff = currentPrice - entry;
return diff * Position;
}
private decimal GetPortfolioValue()
{
var portfolio = Portfolio;
if ((portfolio?.CurrentValue ?? 0m) > 0m)
return portfolio.CurrentValue.Value;
if ((portfolio?.BeginValue ?? 0m) > 0m)
return portfolio.BeginValue.Value;
return _initialEquity <= 0m ? 10000m : _initialEquity;
}
private void ClosePosition()
{
if (Position > 0m)
{
SellMarket(Math.Abs(Position));
}
else if (Position < 0m)
{
BuyMarket(Math.Abs(Position));
}
}
}
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_chaotic_strategy(Strategy):
def __init__(self):
super(rrs_chaotic_strategy, self).__init__()
self._tp_points = self.Param("TakeProfitPoints", 50000).SetDisplay("Take Profit", "TP distance in price steps", "Risk")
self._sl_points = self.Param("StopLossPoints", 50000).SetDisplay("Stop Loss", "SL distance in price steps", "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_chaotic_strategy, self).OnReseted()
self._trade_counter = 0
self._entry_price = 0
self._stop_price = None
self._take_price = None
def OnStarted2(self, time):
super(rrs_chaotic_strategy, self).OnStarted2(time)
self._trade_counter = 0
self._entry_price = 0
self._stop_price = None
self._take_price = None
self._step = 1.0
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 = candle.ClosePrice
# Check SL/TP
if self.Position > 0:
if self._take_price is not None and candle.HighPrice >= self._take_price:
self.SellMarket()
self._stop_price = None
self._take_price = None
return
if self._stop_price is not None and candle.LowPrice <= self._stop_price:
self.SellMarket()
self._stop_price = None
self._take_price = None
return
elif self.Position < 0:
if self._take_price is not None and candle.LowPrice <= self._take_price:
self.BuyMarket()
self._stop_price = None
self._take_price = None
return
if self._stop_price is not None and candle.HighPrice >= self._stop_price:
self.BuyMarket()
self._stop_price = None
self._take_price = None
return
if self.Position != 0:
return
sl_dist = self._sl_points.Value * self._step
tp_dist = self._tp_points.Value * self._step
if self._trade_counter % 2 == 0:
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._trade_counter += 1
def CreateClone(self):
return rrs_chaotic_strategy()