GitHub で見る

eRP250ReversePoint戦略

この戦略は、MetaTrader 5エキスパートアドバイザーe_RP_250をStockSharpへ移植したものです。元のシステムはカスタムrPointインジケーターで検出されたリバーサルを取引します。そのインジケーターはStockSharpで利用できないため、移植版は移動する最高値・最安値トラッカーを使用して同じ動作を再現しています。新しいスイングハイまたはスイングローが現れると、戦略はポジションを反転させ、MQLバージョンと同じストップロス、テイクプロフィット、オプションのトレーリングロジックを適用します。

元のソースコードは検証済みのパフォーマンス結果を公開していないため、本番環境に戦略を展開する前に独自の評価を行う必要があります。

取引ロジック

  • CandleTypeパラメーターで定義されたローソク足(デフォルト:5分足)を購読します。
  • 最後のReversePointバー(デフォルト:250)にわたる最高高値と最低安値を追跡します。
  • 現在のローソク足が新しい最高高値を設定すると、ロングポジションを閉じてショートポジションを開きます。
  • 現在のローソク足が新しい最低安値を設定すると、ショートポジションを閉じてロングポジションを開きます。
  • 保護的なストップロスとテイクプロフィットのレベルは価格ポイントで表現され、StartProtectionを通じて再現されます。
  • オプションのトレーリングストップは、価格が設定されたポイント数動くと利益を確定します。

いつでも一つのポジションのみが有効です。戦略はまた、最後の実行時間を記憶することで同じローソク足での重複注文をブロックし、MQLスクリプトのTimeNセーフガードを複製します。

パラメーター

パラメーター 説明
TakeProfitPoints テイクプロフィット注文の価格ポイント単位の距離(デフォルト15)。自動利益確定を無効にするにはゼロに設定。
StopLossPoints ストップロス注文の価格ポイント単位の距離(デフォルト999)。固定ストップなしで取引するにはゼロに設定。
TrailingStopPoints 価格ポイント単位のオプションのトレーリングストップ距離(デフォルト0でトレーリングロジックを無効化)。
ReversePoint リバーサルポイントの検出に使用するローソク足の数。値が大きいほど反応が遅くなりますが、ノイズをフィルタリングします。
CandleType 分析するローソク足の集約。デフォルトは5分時間軸ですが、任意のDataTypeに切り替えることができます。

ポジション管理

  • StartProtectionはMT5エキスパートと同じストップロスとテイクプロフィットの距離を適用します。
  • トレーリングストップはエントリー後の最も有利な価格を追跡し、価格が設定された量だけ戻ったときに終了します。
  • 反対側からのリバーサルシグナルは、新しいポジションを開く前に即座に現在のポジションを閉じます。

使用上の注意

  • データソースが選択したローソク足タイプをサポートしていることを確認してください。そうでなければシグナルが生成されません。
  • 戦略は小数価格に依存しています。証券のPriceStepプロパティがポイント値を正しく反映していることを確認してください。
  • 取引するインストゥルメントのボラティリティに対してブレイクアウト感度を適応させるために、異なるReversePoint値をテストしてください。
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 e_RP_250 MQL script.
/// </summary>
public class ERp250Strategy : Strategy
{
	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 _latestHighSignal;
	private decimal _latestLowSignal;
	private decimal _lastExecutedHigh;
	private decimal _lastExecutedLow;
	private DateTimeOffset? _lastSignalTime;
	private decimal? _bestLongPrice;
	private decimal? _bestShortPrice;
	private decimal _trailingDistance;

	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	public decimal TrailingStopPoints
	{
		get => _trailingStopPoints.Value;
		set => _trailingStopPoints.Value = value;
	}

	public int ReversePoint
	{
		get => _reversePoint.Value;
		set => _reversePoint.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public ERp250Strategy()
	{
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 15m)
			.SetDisplay("Take Profit Points", "Take profit distance in price points", "Risk")
			;

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

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

		_reversePoint = Param(nameof(ReversePoint), 400)
			.SetDisplay("Reverse Point Length", "Candles used to confirm reversal points", "Signals")
			.SetGreaterThanZero()
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to analyse", "General");
	}

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

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

		_highest = null;
		_lowest = null;
		_latestHighSignal = 0m;
		_latestLowSignal = 0m;
		_lastExecutedHigh = 0m;
		_lastExecutedLow = 0m;
		_lastSignalTime = null;
		_bestLongPrice = null;
		_bestShortPrice = null;
		_trailingDistance = 0m;
	}

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

		_highest = new Highest { Length = ReversePoint };
		_lowest = new Lowest { Length = ReversePoint };

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

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

		// Enable protective orders that match the original stop and take-profit distances.
		StartProtection(
			takeDistance > 0m ? new Unit(takeDistance, UnitTypes.Absolute) : default,
			stopDistance > 0m ? new Unit(stopDistance, UnitTypes.Absolute) : default
		);

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

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

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

		var highValue = _highest.Process(new DecimalIndicatorValue(_highest, candle.HighPrice, candle.OpenTime) { IsFinal = true }).ToNullableDecimal();
		var lowValue = _lowest.Process(new DecimalIndicatorValue(_lowest, candle.LowPrice, candle.OpenTime) { IsFinal = true }).ToNullableDecimal();

		if (highValue is null || lowValue is null)
			return;

		// Update the latest reversal levels detected by the rolling highest/lowest indicators.
		if (highValue.Value == candle.HighPrice)
			_latestHighSignal = candle.HighPrice;

		if (lowValue.Value == candle.LowPrice)
			_latestLowSignal = candle.LowPrice;

		// Manage an existing long position by trailing profits and reacting to opposite signals.
		if (Position > 0)
		{
			_bestLongPrice = (_bestLongPrice is null || candle.HighPrice > _bestLongPrice) ? candle.HighPrice : _bestLongPrice;

			if (_trailingDistance > 0m && _bestLongPrice is decimal bestLong && bestLong - candle.ClosePrice >= _trailingDistance)
			{
				SellMarket();
				_bestLongPrice = null;
				return;
			}

			if (_latestHighSignal != 0m && _latestHighSignal != _lastExecutedHigh)
			{
				SellMarket();
				_bestLongPrice = null;
				return;
			}
		}
		else if (Position < 0)
		{
			_bestShortPrice = (_bestShortPrice is null || candle.LowPrice < _bestShortPrice) ? candle.LowPrice : _bestShortPrice;

			if (_trailingDistance > 0m && _bestShortPrice is decimal bestShort && candle.ClosePrice - bestShort >= _trailingDistance)
			{
				BuyMarket();
				_bestShortPrice = null;
				return;
			}

			if (_latestLowSignal != 0m && _latestLowSignal != _lastExecutedLow)
			{
				BuyMarket();
				_bestShortPrice = null;
				return;
			}
		}
		else
		{
			_bestLongPrice = null;
			_bestShortPrice = null;
		}

		if (Position != 0)
			return;

		// Avoid placing more than one order within the same candle.
		if (_lastSignalTime == candle.OpenTime)
			return;

		// Execute a new short position when a fresh reversal high is detected.
		if (_latestHighSignal != 0m && _latestHighSignal != _lastExecutedHigh)
		{
			SellMarket();
			_lastExecutedHigh = _latestHighSignal;
			_lastSignalTime = candle.OpenTime;
			_bestShortPrice = candle.ClosePrice;
			_bestLongPrice = null;
			return;
		}

		// Execute a new long position when a fresh reversal low is detected.
		if (_latestLowSignal != 0m && _latestLowSignal != _lastExecutedLow)
		{
			BuyMarket();
			_lastExecutedLow = _latestLowSignal;
			_lastSignalTime = candle.OpenTime;
			_bestLongPrice = candle.ClosePrice;
			_bestShortPrice = null;
		}
	}
}