Открыть на GitHub

Стратегия JMaster RSX

Обзор

JMaster RSX — это прямая конвертация советника MetaTrader 4 jMasterRSXv1. Стратегия сравнивает значения осциллятора Jurik RSX на быстром (M5) и медленном (M30) таймфреймах и открывает позицию, когда направление старшего периода подтверждает перекупленность или перепроданность младшего. Все вычисления выполняются на открытии новой свечи, используя полностью завершённые бары, как и в оригинальном коде с обращением к значениям shift = 1.

Индикаторы и данные

  • Jurik RSX на быстром таймфрейме (длина = RsxLength) — рассчитывается по свечам FastCandleType (по умолчанию 5 минут). Встроенный индикатор полностью повторяет рекурсивный фильтр из файла rsx.mq4.
  • Jurik RSX на медленном таймфрейме — вычисляется на серии SlowCandleType (по умолчанию 30 минут) с тем же периодом. Полученное значение откладывается на один бар перед использованием, чтобы воспроизвести поведение shift в MT4.

Логика входа

  1. Дождаться закрытия очередной быстрой свечи.
  2. Взять предыдущее значение быстрого RSX и предыдущее (на один бар старое) значение медленного RSX.
  3. Условия для покупки: медленный RSX выше MidlineLevel (по умолчанию 50), а быстрый RSX ниже OversoldLevel (по умолчанию 25).
  4. Условия для продажи: медленный RSX ниже MidlineLevel, а быстрый RSX выше OverboughtLevel (по умолчанию 75).
  5. Если позиция отсутствует, открыть рыночный ордер объёмом Volume.

Логика выхода

  • Закрыть длинную позицию, как только выполняются условия для короткой сделки (медленный RSX ниже середины, быстрый RSX выше уровня перекупленности).
  • Закрыть короткую позицию, как только выполняются условия для длинной сделки.
  • Стратегия не наращивает позиции: перед новым входом позиция всегда закрывается.

Управление объёмом

  • Ордеры отправляются с фиксированным объёмом Volume (по умолчанию 0.1).
  • Дополнительные алгоритмы управления капиталом не реализованы, что соответствует поведению исходного советника при DecreaseFactor = 0.

Параметры

Имя Описание Значение по умолчанию
FastCandleType Тип свечей для быстрого RSX M5
SlowCandleType Тип свечей для медленного RSX M30
RsxLength Общий период обоих RSX 14
OverboughtLevel Порог перекупленности для входа в шорт 75
OversoldLevel Порог перепроданности для входа в лонг 25
MidlineLevel Средняя линия медленного RSX 50
Volume Объём рыночных заявок 0.1

Рекомендации по использованию

  • Убедитесь, что поток данных содержит завершённые свечи для обоих таймфреймов, иначе сигналы не появятся.
  • Задержка на один бар по медленному RSX приводит к тому, что развороты старшего периода отражаются с задержкой на одну быструю свечу — это повторяет оригинальное поведение и исключает подглядывание в будущее.
  • Встроенный 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;
	}
}