在 GitHub 上查看

JMaster RSX 策略

概述

JMaster RSX 策略是 MetaTrader 4 智能交易系统 jMasterRSXv1 的直接移植版本。系统在快速(M5)与慢速(M30)两个时间框架上计算 Jurik RSX 振荡指标,并在高周期方向与低周期超买/超卖条件一致时开仓。所有判断都在新 K 线开盘时使用上一根已完成的蜡烛数据,完全复制原始 EA 使用 shift = 1 的做法。

指标与数据

  • 快速周期 Jurik RSX(长度 = RsxLength——在 FastCandleType 指定的蜡烛序列(默认 5 分钟)上运行,自带的 RSX 指标严格按照原始 rsx.mq4 递归滤波结构实现。
  • 慢速周期 Jurik RSX——在 SlowCandleType 指定的蜡烛序列(默认 30 分钟)上计算。最新完成的慢速数值会延迟一根柱子后再用于判定,以模拟 MT4 中的 shift 行为。

入场逻辑

  1. 等待新的快速周期蜡烛开盘(即 StockSharp 中一根蜡烛完成)。
  2. 读取上一根快速 RSX 数值以及上一根慢速 RSX 数值(慢速序列再滞后一根)。
  3. 做多条件: 慢速 RSX 高于 MidlineLevel(默认 50),且快速 RSX 低于 OversoldLevel(默认 25)。
  4. 做空条件: 慢速 RSX 低于 MidlineLevel,且快速 RSX 高于 OverboughtLevel(默认 75)。
  5. 当当前无持仓时,以 Volume 指定的手数市价开仓。

出场逻辑

  • 当做空条件成立时,立即平掉已有的多单。
  • 当做多条件成立时,立即平掉已有的空单。
  • 策略不会叠加仓位;每次都会先平仓再等待下一次信号。

仓位管理

  • 下单手数由 Volume 参数控制(默认 0.1)。
  • 没有实现递进加仓或资金管理逻辑,与原始 EA 在 DecreaseFactor = 0 时的行为一致。

参数

名称 说明 默认值
FastCandleType 快速 RSX 所使用的蜡烛类型 M5
SlowCandleType 慢速 RSX 所使用的蜡烛类型 M30
RsxLength 两个 RSX 指标共同的长度 14
OverboughtLevel 做空所需的快速 RSX 阈值 75
OversoldLevel 做多所需的快速 RSX 阈值 25
MidlineLevel 慢速 RSX 牛熊分界线 50
Volume 市价单下单手数 0.1

使用提示

  • 请确保历史数据同时提供两个时间框架的完整蜡烛,策略只会在蜡烛收盘后做出反应。
  • 慢速 RSX 数值会有一根柱子的延迟,因此高周期的反转会在下一根快速 K 线上才反映出来,此设计与原版 EA 完全一致,可避免偷看未来。
  • 内置 RSX 指标输出 0–100 范围数值,方便与其他振荡指标进行组合或对比。
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>
/// JMaster RSX: RSI-based momentum with EMA trend filter.
/// Buys when RSI exits oversold and price above EMA.
/// Sells when RSI exits overbought and price below EMA.
/// </summary>
public class JmasterRsxStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<decimal> _overbought;
	private readonly StrategyParam<decimal> _oversold;

	private decimal _prevRsi;
	private decimal _entryPrice;

	public JmasterRsxStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI period.", "Indicators");

		_emaLength = Param(nameof(EmaLength), 30)
			.SetDisplay("EMA Length", "EMA trend filter.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period for stops.", "Indicators");

		_overbought = Param(nameof(Overbought), 75m)
			.SetDisplay("Overbought", "RSI overbought level.", "Signals");

		_oversold = Param(nameof(Oversold), 25m)
			.SetDisplay("Oversold", "RSI oversold level.", "Signals");
	}

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

	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	public decimal Overbought
	{
		get => _overbought.Value;
		set => _overbought.Value = value;
	}

	public decimal Oversold
	{
		get => _oversold.Value;
		set => _oversold.Value = value;
	}

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

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

		_prevRsi = 0;
		_entryPrice = 0;
	}

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

		_prevRsi = 0;
		_entryPrice = 0;

		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var ema = new ExponentialMovingAverage { Length = EmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

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

		StartProtection(
			takeProfit: new Unit(2, UnitTypes.Percent),
			stopLoss: new Unit(1, UnitTypes.Percent)
		);

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

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

		if (_prevRsi == 0 || atrVal <= 0)
		{
			_prevRsi = rsiVal;
			return;
		}

		var close = candle.ClosePrice;

		// Entry
		if (Position == 0)
		{
			if (_prevRsi < Oversold && rsiVal >= Oversold && close > emaVal)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (_prevRsi > Overbought && rsiVal <= Overbought && close < emaVal)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevRsi = rsiVal;
	}
}