GitHub で見る

RPoint 250 リバーサル戦略

RPoint 250 リバーサル戦略は、MetaTrader 4 エキスパート アドバイザー e_RPoint_250 の StockSharp 移植です。オリジナルロボット は、最新のスイング高とスイング低を強調表示する RPoint と呼ばれるカスタム インジケーターに依存しています。なぜならその指標は StockSharp では利用できませんが、変換では組み込みの Highest および Lowest インジケーターを使用して同じ動作が再現されます。 新しい極値が以前に検出された極値に置き換わるたびに、戦略は即座に位置を反転し、同じ位置を復元します。 MQL バージョンで定義されたストップロス、テイクプロフィット、トレーリングロジック。

取引ワークフロー

  1. CandleType で指定されたローソク足シリーズを購読します (デフォルト: 5 分足ローソク足)。
  2. Track the rolling maximum and minimum over the last ReversePoint bars.これらの値は、エミュレートされた RPoint レベルを表します。
  3. 価格が新たな最高値を更新した場合は、ロングポジションをクローズし、ボリュームOrderVolumeのショートポジションをオープンします。
  4. 価格が新たな安値を記録した場合は、ショート ポジションをクローズし、ボリューム OrderVolume のロング ポジションをオープンします。
  5. StartProtection を使用して保護命令を適用します。ストップロスとテイクプロフィットの距離は、以下の価格ポイントで表されます。 パラメータ StopLossPointsTakeProfitPoints
  6. 必要に応じて、TrailingStopPoints まで利益を追跡します。 The trailing engine measures how far price has moved in favour of the ポジションを設定し、価格が設定されたポイント数だけリトレースしたときにクローズします。
  7. 同じバー内で複数の取引を開始することを避けるために、最後に成功したエントリーのローソク足の時間を覚えておいてください。 TimeN は、MQL スクリプトから保護します。

この戦略では常に最大 1 つのオープン ポジションが維持されます。 It closes existing trades before entering in the opposite direction and スケールインすることはありません。

パラメーター

パラメータ 種類 デフォルト 説明
OrderVolume decimal 0.1 各成行注文で送信されるボリューム。 Lots 入力を MetaTrader バージョンにミラーリングします。
TakeProfitPoints decimal 15 Distance to the take-profit order measured in price points.利益目標を無効にするには、0 に設定します。
StopLossPoints decimal 999 Distance to the protective stop expressed in price points.固定ストップなしで取引するには、0 に設定します。
TrailingStopPoints decimal 0 価格帯のオプションのトレーリング距離。ゼロの場合、後続ロジックは無効になります。
ReversePoint int 250 最新のスイングハイとスイングローを検索する際に考慮されるローソク足の数。値を大きくするとノイズが滑らかになります。
CandleType DataType TimeSpan.FromMinutes(5).TimeFrame() 戦略によって分析されたローソク足の集計。 Change it to match the chart timeframe used in MetaTrader.

実装メモ

  • HighestLowest は、高レベルの Bind API を介してキャンドル サブスクリプションにバインドされているため、手動インジケーター キューはありません。 必須です。
  • StartProtection reproduces the original stop-loss and take-profit distances in absolute price units. StockSharp が処理します 新しいポジションが現れたら注文を出します。
  • トレーリングストップは、完了した各ローソク足を監視することによって実装されます。 When price retreats by the configured number of points from エントリー後に最高価格が達成された場合、ポジションは成行注文でクローズされます。
  • このクラスは、重複を避けるために、最後に実行された反転レベル (_executedHighLevel および _executedLowLevel) を保存します。 エントリ。 This is equivalent to the Reverse_High / Reverse_Low variables in the MQL code.
  • The _lastSignalTime field mirrors the TimeN variable and blocks multiple orders inside the same candle, preventing accidental double submissions on illiquid markets.

使用ガイドライン

  1. Attach the strategy to a portfolio that supports the selected instrument and candle type.
  2. ブローカーの契約規模とリスク管理ルールに準拠するように OrderVolume を調整します。
  3. Tune ReversePoint to match the volatility of the traded asset. Higher values yield fewer but more meaningful reversals.
  4. StopLossPointsTakeProfitPoints、および TrailingStopPoints がセキュリティの PriceStep と互換性があることを確認します。
  5. Run a backtest in StockSharp Designer or Backtester to confirm the behaviour before trading live capital.
  6. Monitor the log output: informational messages will highlight position changes and can help validate the conversion.

RPoint インジケーターは組み込みコンポーネントで近似されるため、MetaTrader の実行との小さな違いは次のとおりです。 ギャップや異なる丸めルールのある履歴データでも可能です。独自の市場データ フィードを使用して結果を常に検証します。 本番環境で戦略に頼る前に。

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>
/// Reverse-point breakout strategy converted from the MetaTrader 4 expert e_RPoint_250.
/// </summary>
public class RPoint250Strategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<int> _reversePoint;
	private readonly StrategyParam<DataType> _candleType;

	private Highest _highest;
	private Lowest _lowest;

	private decimal _lastHighLevel;
	private decimal _lastLowLevel;
	private decimal _executedHighLevel;
	private decimal _executedLowLevel;
	private DateTimeOffset? _lastSignalTime;
	private decimal _priceStep;
	private decimal _trailingDistance;
	private decimal? _bestLongPrice;
	private decimal? _bestShortPrice;

	public RPoint250Strategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 1m)
			.SetDisplay("Order Volume", "Base volume for market entries.", "Trading")
			;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
			.SetDisplay("Take Profit Points", "Take profit distance expressed in price points.", "Risk")
			;

		_stopLossPoints = Param(nameof(StopLossPoints), 999m)
			.SetDisplay("Stop Loss Points", "Stop loss distance expressed in price points.", "Risk")
			;

		_trailingStopPoints = Param(nameof(TrailingStopPoints), 0m)
			.SetDisplay("Trailing Stop Points", "Optional trailing distance in price points.", "Risk")
			;

		_reversePoint = Param(nameof(ReversePoint), 250)
			.SetDisplay("Reverse Point Length", "Number of candles scanned for the latest reversal levels.", "Signals")
			.SetGreaterThanZero()
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Candle aggregation used for calculations.", "General");
	}

	/// <summary>
	/// Market order volume used for both entries and reversals.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Take-profit distance in price points.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price points.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Trailing-stop distance in price points.
	/// </summary>
	public decimal TrailingStopPoints
	{
		get => _trailingStopPoints.Value;
		set => _trailingStopPoints.Value = value;
	}

	/// <summary>
	/// Number of candles used to approximate the rPoint indicator.
	/// </summary>
	public int ReversePoint
	{
		get => _reversePoint.Value;
		set => _reversePoint.Value = value;
	}

	/// <summary>
	/// Candle type processed by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, CandleType);
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_highest = null;
		_lowest = null;
		_lastHighLevel = 0m;
		_lastLowLevel = 0m;
		_executedHighLevel = 0m;
		_executedLowLevel = 0m;
		_lastSignalTime = null;
		_priceStep = 0m;
		_trailingDistance = 0m;
		_bestLongPrice = null;
		_bestShortPrice = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_highest = new Highest { Length = Math.Max(1, ReversePoint) };
		_lowest = new Lowest { Length = Math.Max(1, ReversePoint) };

		_priceStep = Security?.PriceStep ?? 0m;
		if (_priceStep <= 0m)
			_priceStep = 1m;

		var takeDistance = TakeProfitPoints > 0m ? _priceStep * TakeProfitPoints : 0m;
		var stopDistance = StopLossPoints > 0m ? _priceStep * StopLossPoints : 0m;
		_trailingDistance = TrailingStopPoints > 0m ? _priceStep * TrailingStopPoints : 0m;

		// Apply the same static protection as in the original MQL script.
		var tp = takeDistance > 0m ? new Unit(takeDistance, UnitTypes.Absolute) : (Unit)null;
		var sl = stopDistance > 0m ? new Unit(stopDistance, UnitTypes.Absolute) : (Unit)null;
		if (tp != null || sl != null)
			StartProtection(tp, sl);

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_highest, _lowest, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _highest);
			DrawIndicator(area, _lowest);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal highestValue, decimal lowestValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_highest.IsFormed || !_lowest.IsFormed)
			return;

		// Capture the latest swing levels as soon as they appear.
		if (highestValue == candle.HighPrice && highestValue != _lastHighLevel)
			_lastHighLevel = highestValue;

		if (lowestValue == candle.LowPrice && lowestValue != _lastLowLevel)
			_lastLowLevel = lowestValue;

		if (Position > 0)
		{
			_bestLongPrice = _bestLongPrice is null || candle.HighPrice > _bestLongPrice
				? candle.HighPrice
				: _bestLongPrice;

			if (!IsFormedAndOnlineAndAllowTrading())
				return;

			// Close the long position when price retraces by the trailing distance.
			if (_trailingDistance > 0m && _bestLongPrice is decimal bestLong && bestLong - candle.LowPrice >= _trailingDistance)
			{
				SellMarket(Position);
				_bestLongPrice = null;
				return;
			}

			// Reverse the position when a new high reversal point appears.
			if (_lastHighLevel != 0m && _lastHighLevel != _executedHighLevel)
			{
				SellMarket(Position);
				_bestLongPrice = null;
				return;
			}
		}
		else if (Position < 0)
		{
			_bestShortPrice = _bestShortPrice is null || candle.LowPrice < _bestShortPrice
				? candle.LowPrice
				: _bestShortPrice;

			if (!IsFormedAndOnlineAndAllowTrading())
				return;

			// Close the short position when price rallies by the trailing distance.
			if (_trailingDistance > 0m && _bestShortPrice is decimal bestShort && candle.HighPrice - bestShort >= _trailingDistance)
			{
				BuyMarket(-Position);
				_bestShortPrice = null;
				return;
			}

			// Reverse the position when a new low reversal point appears.
			if (_lastLowLevel != 0m && _lastLowLevel != _executedLowLevel)
			{
				BuyMarket(-Position);
				_bestShortPrice = null;
				return;
			}
		}
		else
		{
			_bestLongPrice = null;
			_bestShortPrice = null;

			if (!IsFormedAndOnlineAndAllowTrading())
				return;

			if (OrderVolume <= 0m)
				return;

			if (_lastSignalTime == candle.OpenTime)
				return;

			// Enter short when the reversal high changes.
			if (_lastHighLevel != 0m && _lastHighLevel != _executedHighLevel)
			{
				SellMarket(OrderVolume);
				_executedHighLevel = _lastHighLevel;
				_lastSignalTime = candle.OpenTime;
				_bestShortPrice = candle.ClosePrice;
				return;
			}

			// Enter long when the reversal low changes.
			if (_lastLowLevel != 0m && _lastLowLevel != _executedLowLevel)
			{
				BuyMarket(OrderVolume);
				_executedLowLevel = _lastLowLevel;
				_lastSignalTime = candle.OpenTime;
				_bestLongPrice = candle.ClosePrice;
			}
		}
	}
}