在 GitHub 上查看

e_RP_250 反转点策略

本策略是 MetaTrader 5 专家顾问 e_RP_250 在 StockSharp 平台上的移植版本。原始脚本依赖自定义 rPoint 指标来识别 关键反转点。由于该指标在 StockSharp 中不可用,本移植使用 HighestLowest 指标组合来重现相同的效果。一旦 出现新的摆动高点或低点,策略就会翻转持仓,并应用与 MQL 版本相同的止损、止盈以及可选的跟踪止损规则。

原始源码未提供经过验证的历史测试结果,因此在实盘运行之前请务必自行评估策略表现。

交易逻辑

  • 订阅由 CandleType 参数指定的 K 线数据(默认使用 5 分钟 K 线)。
  • 计算最近 ReversePoint 根 K 线内的最高价和最低价(默认窗口为 250 根)。
  • 当当前 K 线刷新最高价时,平掉多头仓位并开立新的空头仓位。
  • 当当前 K 线刷新最低价时,平掉空头仓位并开立新的多头仓位。
  • 通过 StartProtection 以价格点为单位复现原策略的止损和止盈距离。
  • 可选的跟踪止损在价格朝有利方向运行指定点数后锁定利润。

任意时刻仅允许存在一笔仓位。策略还会记录上一次下单的 K 线时间,从而避免在同一根 K 线上重复触发信号, 这一点与原脚本中的 TimeN 机制保持一致。

参数说明

参数 说明
TakeProfitPoints 止盈距离(单位:价格点),默认 15。设为 0 可关闭自动止盈。
StopLossPoints 止损距离(单位:价格点),默认 999。设为 0 表示不设置固定止损。
TrailingStopPoints 跟踪止损距离(单位:价格点),默认 0 表示禁用。
ReversePoint 用于识别反转点的窗口长度。值越大,信号越平滑、触发越晚。
CandleType 使用的 K 线类型,默认是 5 分钟,可根据需要调整为任何 DataType

仓位管理

  • StartProtection 用来应用与 MT5 专家相同的止损与止盈设置。
  • 跟踪止损会跟踪入场后的最有利价格,并在价格回撤指定点数时退出。
  • 相反方向的信号会立即平掉当前仓位,然后再开立新的仓位。

使用建议

  • 确认数据源支持所选的 K 线周期,否则策略不会产生信号。
  • 请检查交易品种的 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;
		}
	}
}