在 GitHub 上查看

RSI MA on RSI Filling Step 策略

概述

RSI MA on RSI Filling Step 策略移植自 MetaTrader 专家顾问 RSI_MAonRSI_Filling Step EA.mq5。原版系统使用相对强弱指数 (RSI) 衡量动能,并对其进行移动平均平滑。当 RSI 与其移动均线发生交叉且两者都位于 50 中枢同侧时触发交易。该 StockSharp 版本保留了方向过滤、信号反转和日内交易时段等选项,并通过高层 API 完成指标绑定。

交易逻辑

  1. 订阅所选蜡烛类型,在每根已完成的蜡烛上计算两个指标:周期为 RsiPeriodRelativeStrengthIndex 与施加在 RSI 序列上的 MovingAverage(由 MaTypeMaPeriod 控制)。
  2. 仅在蜡烛收盘后评估信号,重现 EA 只在新柱上交易的约束,从而避免同一根 K 线多次进场。
  3. 若上一根 RSI 低于其均线且当前值向上穿越均线,同时 RSI 与均线均低于 MiddleLevel(默认 50),则生成多头信号。若 RSI 与均线同时高于中枢并发生向下交叉,则生成空头信号。
  4. ReverseSignals 选项会交换信号方向,使多头条件触发做空、空头条件触发做多。
  5. Mode 参数可限制只做多、只做空或双向交易。还可以选择在进场前平掉相反仓位,并通过 OnlyOnePosition 禁止在已有持仓时再次开仓。
  6. UseTimeWindow 搭配 SessionStartSessionEnd 可实现与 MQL 函数 TimeControlHourMinute 相同的日内时间过滤,包括跨午夜的交易区间。

参数说明

  • CandleType:策略处理的蜡烛类型,默认 1 小时。
  • RsiPeriod:RSI 平滑周期,默认 14。
  • MaPeriod:RSI 移动平均长度,默认 21。
  • MaType:应用于 RSI 的移动平均类型,默认 Simple
  • MiddleLevel:验证信号所用的中间水平线,默认 50。
  • ReverseSignals:是否反转交易方向,默认 false
  • Mode:交易方向限制(BuyOnlySellOnlyBoth)。
  • CloseOppositePositions:进场前是否平掉反向头寸,默认 false
  • OnlyOnePosition:已有持仓时禁止再开新仓,默认 false
  • UseTimeWindow:启用日内交易时段过滤,默认 false
  • SessionStart / SessionEnd:允许交易的开始与结束时间。

实现要点

  • 借助 Bind 方法直接获得指标结果,无需像 MQL 那样手动 CopyBuffer
  • 使用字段缓存上一根 RSI 及均线的取值,对应 EA 中的 RSI[m_bar_current+1] 访问方式,并通过 _lastSignalBarTime 确保每根柱子只触发一次决策。
  • 调用 BuyMarket()SellMarket() 模拟 EA 的即时市价下单。若启用了 CloseOppositePositions,则在下单前调用 ClosePosition() 平掉反向仓位。
  • 时间窗口函数完整复刻 TimeControlHourMinute,支持跨日交易时段。
  • 策略绘图包含价格区与单独的 RSI 面板,可在回测中直观观察交叉信号。

与原版 EA 的差异

  • 未移植资金管理模式 (ENUM_LOT_OR_RISK)、止损跟踪和 Freeze Level 检查,需要时可在 StockSharp 侧另行实现。
  • EA 中的魔术号验证、事务回调及手工订单队列在 StockSharp 中不再需要,策略直接依赖平台处理订单生命周期。
  • 默认不会自动挂载止损/止盈,如有需要请结合 StartProtection 或其它风险控制组件。

使用建议

  1. MiddleLevel 保持在 50 附近,可维持原策略以区间反转为主的风格。偏离过多会使策略偏向突破交易。
  2. 若希望每次仅持有一笔仓位,可启用 OnlyOnePosition;关闭后可配合自定义加仓逻辑。
  3. 在期货或股票品种上运行时,建议结合交易所交易时段设置 UseTimeWindow,避免在流动性不足的时段频繁触发信号。
  4. 调整到新标的时,请联合优化 RsiPeriodMaPeriodMiddleLevel

以上信息可帮助你在 StockSharp 平台上部署与扩展该策略。

namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

using Ecng.Common;

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

/// <summary>
/// RSI crossing its own moving average with trade signals.
/// Converted from the MetaTrader expert advisor "RSI_MAonRSI_Filling Step EA.mq5".
/// </summary>
public class RsiMaOnRsiFillingStepStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _maPeriod;

	private RelativeStrengthIndex _rsi;
	private readonly Queue<decimal> _rsiHistory = new();
	private decimal? _previousRsi;
	private decimal? _previousSignal;

	/// <summary>
	/// Initializes a new instance of <see cref="RsiMaOnRsiFillingStepStrategy"/>.
	/// </summary>
	public RsiMaOnRsiFillingStepStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe used for RSI calculations.", "General");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
		.SetGreaterThanZero()
		.SetDisplay("RSI Period", "Number of bars for the RSI smoothing window.", "Indicators");

		_maPeriod = Param(nameof(MaPeriod), 20)
		.SetGreaterThanZero()
		.SetDisplay("MA Period", "Length of the moving average applied to the RSI.", "Indicators");
	}

	/// <summary>
	/// Candle type used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// RSI averaging period.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// Moving average period applied to the RSI output.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_previousRsi = null;
		_previousSignal = null;
		_rsiHistory.Clear();
		_rsi = null!;
	}

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

		_previousRsi = null;
		_previousSignal = null;
		_rsiHistory.Clear();

		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };

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

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

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

		// Accumulate RSI values for moving average calculation
		_rsiHistory.Enqueue(rsiValue);
		while (_rsiHistory.Count > MaPeriod)
			_rsiHistory.Dequeue();

		if (!_rsi.IsFormed)
			return;

		// Need enough RSI values to compute the MA
		if (_rsiHistory.Count < MaPeriod)
		{
			_previousRsi = rsiValue;
			return;
		}

		// Calculate simple moving average of RSI
		var sum = 0m;
		var history = _rsiHistory.ToArray();
		foreach (var v in history)
			sum += v;
		var signalValue = sum / MaPeriod;

		if (_previousRsi is null || _previousSignal is null)
		{
			_previousRsi = rsiValue;
			_previousSignal = signalValue;
			return;
		}

		var crossUp = _previousRsi < _previousSignal && rsiValue > signalValue;
		var crossDown = _previousRsi > _previousSignal && rsiValue < signalValue;

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

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

		_previousRsi = rsiValue;
		_previousSignal = signalValue;
	}
}