在 GitHub 上查看

RSI MA on RSI Dual 策略

概述

RSI MA on RSI Dual 策略在 StockSharp 中重现了 MetaTrader 的 "RSI_MAonRSI_Dual" 专家顾问。策略同时计算快慢两组相对强弱指数 (RSI),并对每组 RSI 结果使用相同周期的简单移动平均进行平滑。当两条平滑 RSI 线在同一侧穿越中性水平线时触发交易信号。

本实现保留了原始 EA 的核心逻辑,同时提供时间过滤、方向限制以及反转信号等选项。

指标

  • 快 RSI:可配置周期的相对强弱指数。
  • 慢 RSI:使用独立周期的相对强弱指数。
  • RSI 上的移动平均:对快慢两组 RSI 结果应用同一长度的简单移动平均。

所有指标共用同一个价格类型(默认收盘价),平滑后的两条 RSI 曲线会绘制在单独的图表面板上。

入场规则

  1. 等待当前完成 K 线上的两条平滑 RSI 都形成。
  2. 做多条件
    • 快线在当前 K 线上穿慢线(当前值大于慢线,上一根 K 线小于慢线)。
    • 两条平滑 RSI 均低于中性水平(默认 50)。
  3. 做空条件
    • 快线在当前 K 线下穿慢线(当前值小于慢线,上一根 K 线上于慢线)。
    • 两条平滑 RSI 均高于中性水平。
  4. 可通过 ReverseSignals 参数反转买卖方向。
  5. 每根 K 线最多触发一次信号,避免重复下单。

仓位管理

  • AllowLong / AllowShort 控制是否允许开多或开空。
  • CloseOpposite 会在反向开仓前平掉现有头寸。
  • OnlyOnePosition 限制同一时间最多持有一个仓位。
  • 策略使用 Volume 指定的固定数量发送市价单。

时间过滤

通过 UseTimeFilter 控制是否启用交易时段过滤。当启用时,仅在 SessionStartSessionEnd 之间允许交易,支持跨越午夜的时段。时间依据收到的 K 线所提供的交易所时区进行判断。

参数

参数 说明
CandleType 策略分析的 K 线类型。
FastRsiPeriod 快 RSI 的周期。
SlowRsiPeriod 慢 RSI 的周期。
MaPeriod 平滑两条 RSI 的移动平均长度。
AppliedPrice 参与 RSI 计算的价格类型。
NeutralLevel 划分多空区域的 RSI 中性水平。
AllowLong / AllowShort 控制多空方向是否允许交易。
ReverseSignals 反转买卖信号方向。
CloseOpposite 开新仓前是否平掉相反仓位。
OnlyOnePosition 是否限制为单一持仓。
UseTimeFilter 是否启用交易时段过滤。
SessionStart / SessionEnd 交易窗口的开始与结束时间。

与原始 EA 的差异

  • 未复刻原始 MQL5 代码中的资金管理、止损或移动止损模块。StockSharp 版本仅使用固定手数市价单。
  • 移除了所有平台专用的日志与提示,若需要可使用 StockSharp 的日志系统。
  • 交易状态跟踪改由 StockSharp 的订单事件处理。

尽管如此,核心的信号生成与方向过滤逻辑与原始专家顾问保持一致。

using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Dual moving averages calculated on top of RSI values.
/// Fast RSI MA crossing slow RSI MA generates entry signals.
/// </summary>
public class RsiMaOnRsiDualStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastRsiPeriod;
	private readonly StrategyParam<int> _slowRsiPeriod;
	private readonly StrategyParam<int> _maPeriod;

	private RelativeStrengthIndex _fastRsi;
	private RelativeStrengthIndex _slowRsi;
	private readonly Queue<decimal> _fastRsiHistory = new();
	private readonly Queue<decimal> _slowRsiHistory = new();

	private decimal? _previousFastMa;
	private decimal? _previousSlowMa;

	public RsiMaOnRsiDualStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle type", "Candles processed by the strategy.", "General");

		_fastRsiPeriod = Param(nameof(FastRsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Fast RSI period", "Length of the fast RSI smoothing window.", "Indicators");

		_slowRsiPeriod = Param(nameof(SlowRsiPeriod), 28)
			.SetGreaterThanZero()
			.SetDisplay("Slow RSI period", "Length of the slow RSI smoothing window.", "Indicators");

		_maPeriod = Param(nameof(MaPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("MA period", "Number of RSI values averaged by the smoothing moving average.", "Indicators");
	}

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

	public int FastRsiPeriod
	{
		get => _fastRsiPeriod.Value;
		set => _fastRsiPeriod.Value = value;
	}

	public int SlowRsiPeriod
	{
		get => _slowRsiPeriod.Value;
		set => _slowRsiPeriod.Value = value;
	}

	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_previousFastMa = null;
		_previousSlowMa = null;
		_fastRsiHistory.Clear();
		_slowRsiHistory.Clear();
		_fastRsi = null!;
		_slowRsi = null!;
	}

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

		_previousFastMa = null;
		_previousSlowMa = null;
		_fastRsiHistory.Clear();
		_slowRsiHistory.Clear();

		_fastRsi = new RelativeStrengthIndex { Length = FastRsiPeriod };
		_slowRsi = new RelativeStrengthIndex { Length = SlowRsiPeriod };

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

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

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

		_fastRsiHistory.Enqueue(fastRsiValue);
		_slowRsiHistory.Enqueue(slowRsiValue);
		while (_fastRsiHistory.Count > MaPeriod)
			_fastRsiHistory.Dequeue();
		while (_slowRsiHistory.Count > MaPeriod)
			_slowRsiHistory.Dequeue();

		if (!_fastRsi.IsFormed || !_slowRsi.IsFormed)
			return;

		if (_fastRsiHistory.Count < MaPeriod || _slowRsiHistory.Count < MaPeriod)
			return;

		// Calculate SMA of each RSI
		var fastSum = 0m;
		var fastHistory = _fastRsiHistory.ToArray();
		foreach (var v in fastHistory)
			fastSum += v;
		var fastMa = fastSum / MaPeriod;

		var slowSum = 0m;
		var slowHistory = _slowRsiHistory.ToArray();
		foreach (var v in slowHistory)
			slowSum += v;
		var slowMa = slowSum / MaPeriod;

		if (_previousFastMa is null || _previousSlowMa is null)
		{
			_previousFastMa = fastMa;
			_previousSlowMa = slowMa;
			return;
		}

		var crossUp = _previousFastMa < _previousSlowMa && fastMa > slowMa;
		var crossDown = _previousFastMa > _previousSlowMa && fastMa < slowMa;

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		if (crossUp)
		{
			if (Position <= 0)
				BuyMarket(volume);
		}
		else if (crossDown)
		{
			if (Position >= 0)
				SellMarket(volume);
		}

		_previousFastMa = fastMa;
		_previousSlowMa = slowMa;
	}
}