在 GitHub 上查看

5min RSI Qualified 策略

概述

5min RSI Qualified 策略源自 MetaTrader 智能交易系统 “5min_rsi_qual_01a”。原始 EA 在 5 分钟周期上使用 28 周期 RSI 指标,当指标在极值区域连续保持若干根 K 线后,立即反向建仓,并将止损挂在上一根 K 线收盘价附近。StockSharp 版本完整保留了这一确认逻辑、点差距离以及“同一时刻仅持有一笔交易”的约束,并通过高层级的蜡烛图订阅 API 实现。

默认情况下策略订阅 5 分钟蜡烛,但 CandleType 参数可以替换为任何受支持的时间框架。所有阈值和止损距离依旧以 MetaTrader 的“point”为单位,这样可以直接复用在 MT4 中调好的参数。

交易逻辑

  1. RSI 计算 —— 在每根收盘的蜡烛上更新 28 周期 RSI。只处理已完成的蜡烛,以对应 MQL4 中对 Close[1] 的引用。
  2. 资格计数器 —— 两个计数器分别记录 RSI 连续高于超买阈值 (UpperThreshold) 和连续低于超卖阈值 (LowerThreshold) 的蜡烛数量,对应 MQL4 代码中循环检查最近 12 根 K 线的逻辑。
  3. 入场条件 —— 当没有持仓且超买计数器达到 QualificationLength 时,策略按市价做空;当超卖计数器达到要求时,按市价做多。这样可以确保任何时刻只有一笔仓位。
  4. 跟踪止损 —— 持仓期间,每根收盘蜡烛都会根据上一根收盘价减/加 StopLossPoints(转换为绝对价格)来更新止损。止损只会朝盈利方向移动,与原版中 OrderModify 的行为一致。
  5. 初始止损 —— 每次成交后按照 InitialStopPoints 设置初始保护止损。如果该值比跟踪距离更小,跟踪逻辑不会将其放宽,完全复刻 MT4 的处理方式。

风险控制

  • 止损距离以 MetaTrader point 表示,并根据标的的 PriceStep(或备用的 MinStep)转换为实际价格偏移。
  • 策略不会加仓或网格,必须等到当前持仓完全平仓后才会寻找下一次机会。
  • 在启动时调用 StartProtection(),以便 StockSharp 的保护机制与手动管理的止损保持同步。

参数

参数 说明 默认值
RsiPeriod RSI 指标的周期。 28
QualificationLength RSI 必须连续位于极值区域的蜡烛数量。 12
UpperThreshold 触发做空信号的 RSI 阈值。 55
LowerThreshold 触发做多信号的 RSI 阈值。 45
StopLossPoints 跟踪止损距离(point)。设为 0 可关闭跟踪。 21
InitialStopPoints 初始止损距离(point),在成交后立即生效。设为 0 表示不使用初始止损。 11
CandleType 用于计算信号的蜡烛类型(默认 5 分钟)。 5 分钟时间框架

使用建议

  • 请确认标的的最小价位跳动与 MT4 优化时的 point 定义一致。对于五位小数的外汇品种,一个 point 等于 0.00010(一个点),因此默认的 11/21 设置可以还原原 EA 的参数。
  • 该策略属于逆势策略,更适用于震荡行情。如果用于趋势品种,可适当提高 QualificationLength 或扩大 RSI 阈值。
  • 下单手数来自基类的 Volume 属性,请在启动策略前配置。
  • 所有主要参数均启用了 SetCanOptimize(),可在 StockSharp 优化器中批量测试。

转换说明

  • 蜡烛处理流程、RSI 计算方式和“同一时间只持有一单”的限制完全忠实于原 MQL4 代码。
  • 跟踪止损根据上一根收盘价更新,与原版 Close[1] 逻辑保持一致,确保行情反转时的离场价位相同。
  • 原脚本中的柱数、可用保证金检查在 StockSharp 中由平台负责,因此不再单独实现。
using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// 5-minute RSI qualified strategy.
/// Counts consecutive candles in RSI extreme zones.
/// Buys after sustained oversold, sells after sustained overbought.
/// </summary>
public class FiveMinRsiQualifiedStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _qualificationLength;
	private readonly StrategyParam<decimal> _upperThreshold;
	private readonly StrategyParam<decimal> _lowerThreshold;
	private readonly StrategyParam<DataType> _candleType;

	private int _overboughtCount;
	private int _oversoldCount;

	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
	public int QualificationLength { get => _qualificationLength.Value; set => _qualificationLength.Value = value; }
	public decimal UpperThreshold { get => _upperThreshold.Value; set => _upperThreshold.Value = value; }
	public decimal LowerThreshold { get => _lowerThreshold.Value; set => _lowerThreshold.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public FiveMinRsiQualifiedStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetDisplay("RSI Period", "RSI lookback", "Indicators");

		_qualificationLength = Param(nameof(QualificationLength), 3)
			.SetDisplay("Qual Length", "Consecutive candles in extreme zone", "Indicators");

		_upperThreshold = Param(nameof(UpperThreshold), 65m)
			.SetDisplay("Upper", "RSI overbought threshold", "Indicators");

		_lowerThreshold = Param(nameof(LowerThreshold), 35m)
			.SetDisplay("Lower", "RSI oversold threshold", "Indicators");

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_overboughtCount = 0;
		_oversoldCount = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_overboughtCount = 0;
		_oversoldCount = 0;

		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };

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

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

		// Track consecutive overbought candles
		if (rsiValue >= UpperThreshold)
			_overboughtCount++;
		else
			_overboughtCount = 0;

		// Track consecutive oversold candles
		if (rsiValue <= LowerThreshold)
			_oversoldCount++;
		else
			_oversoldCount = 0;

		// After qualified oversold period, buy (contrarian)
		if (_oversoldCount >= QualificationLength && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
			_oversoldCount = 0;
		}
		// After qualified overbought period, sell (contrarian)
		else if (_overboughtCount >= QualificationLength && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
			_overboughtCount = 0;
		}
	}
}