Открыть на GitHub

Exp RJTX Matches Smoothed Duplex

Обзор

Стратегия повторяет логику советника MetaTrader 5 Exp_RJTX_Matches_Smoothed_Duplex.mq5. Два независимых блока RJTX анализируют сглаженные ряды цен открытия и закрытия на выбранных таймфреймах. Каждый законченный бар классифицируется как бычий или медвежий в зависимости от того, превысило ли сглаженное закрытие сглаженное открытие Period баров назад. Бычьи «спички» активируют длинный модуль, а медвежьи управляют коротким модулем.

Формирование сигналов

  1. Сглаживание – оба блока подают цены открытия и закрытия в выбранный метод сглаживания. Для открытия и закрытия создаются отдельные экземпляры индикаторов, чтобы избежать пересечения буферов.
  2. Сравнение – после накопления достаточной истории текущее сглаженное закрытие сравнивается со сглаженным открытием, зафиксированным Period баров назад.
  3. Определение спички – если закрытие выше, бар считается бычьим, иначе – медвежьим. Сигналы оцениваются после сдвига на SignalBar закрытых свечей, как и при чтении буферов в MT5.

Управление позицией

  • Длинный блок открывает лонг (при необходимости закрывая шорт) при появлении бычьей спички в окне оценки. Медвежья спичка закрывает длинную позицию, если включено разрешение на выход.
  • Короткий блок действует зеркально: медвежья спичка открывает шорт (с закрытием лонга при разрешении), а бычья закрывает его.
  • В StockSharp стратегия работает в неттинговом режиме. Поэтому при появлении противоположного сигнала текущая позиция закрывается перед открытием новой, вместо одновременного ведения двух хеджированных позиций, как в MT5. Чтобы запретить автоматическое закрытие противоположной позиции, отключите соответствующий параметр Allow ... Close.

Управление рисками

Стоп-лосс и тейк-профит задаются в шагах цены (PriceStep × points). Для каждого закрытого бара стратегия проверяет, достиг ли диапазон свечи уровней стопа или профита, и сразу закрывает позицию при срабатывании. Это имитирует защитные ордера MT5 без необходимости регистрировать их на стороне брокера.

Параметры

Раздел Параметр Значение по умолчанию Описание
Long LongCandleType H4 Таймфрейм, используемый длинным блоком RJTX.
Long LongVolume 0.1 Объём открытия длинной позиции.
Long LongAllowOpen true Разрешить открытие лонгов.
Long LongAllowClose true Разрешить закрытие лонгов по медвежьим спичкам.
Long LongStopLossPoints 1000 Расстояние стоп-лосса в шагах цены (0 отключает проверку).
Long LongTakeProfitPoints 2000 Расстояние тейк-профита в шагах цены (0 отключает проверку).
Long LongSignalBar 1 Сдвиг при чтении буферов RJTX (0 = текущая закрытая свеча).
Long LongPeriod 10 Количество баров между текущим сглаженным закрытием и историческим сглаженным открытием.
Long LongMethod Sma Метод сглаживания (Sma, Ema, Smma, Lwma, Jjma, Jurx, Parma, T3, Vidya, Ama).
Long LongLength 12 Длина фильтра сглаживания для рядов открытия/закрытия.
Long LongPhase 15 Параметр фазы для фильтров Jurik (оставлен для совместимости).
Short ShortCandleType H4 Таймфрейм, используемый коротким блоком RJTX.
Short ShortVolume 0.1 Объём открытия короткой позиции.
Short ShortAllowOpen true Разрешить открытие шортов.
Short ShortAllowClose true Разрешить закрытие шортов по бычьим спичкам.
Short ShortStopLossPoints 1000 Расстояние стоп-лосса для шортов в шагах цены (0 отключает проверку).
Short ShortTakeProfitPoints 2000 Расстояние тейк-профита для шортов в шагах цены (0 отключает проверку).
Short ShortSignalBar 1 Сдвиг при чтении буферов RJTX в коротком блоке.
Short ShortPeriod 10 Количество баров между текущим сглаженным закрытием и историческим сглаженным открытием.
Short ShortMethod Sma Метод сглаживания в коротком блоке.
Short ShortLength 12 Длина фильтра сглаживания для коротких сигналов.
Short ShortPhase 15 Параметр фазы для фильтров Jurik в коротком блоке.

Примечания

  • Jjma соответствует Jurik Moving Average. Методы Jurx, Parma и Vidya аппроксимируются Zero-Lag EMA, Arnaud Legoux MA и EMA соответственно, поскольку в StockSharp нет точных аналогов из библиотеки SmoothAlgorithms.
  • Проверка стоп-лосса и тейк-профита выполняется по экстремумам свечи. Внутрибарачные движения, не попавшие в диапазон high/low, не приведут к срабатыванию.
  • Обработка сигналов происходит только на завершённых свечах, что соответствует проверке IsNewBar в оригинальном советнике.
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>
/// Exp RJTX Matches Smoothed Duplex strategy using SmoothedMA crossover.
/// Buys when fast SmoothedMA crosses above slow SmoothedMA.
/// Sells on reverse crossover.
/// </summary>
public class ExpRjtxMatchesSmoothedDuplexStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private SmoothedMovingAverage _fast;
	private SmoothedMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	/// <summary>
	/// Fast smoothed MA period.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow smoothed MA period.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="ExpRjtxMatchesSmoothedDuplexStrategy"/> class.
	/// </summary>
	public ExpRjtxMatchesSmoothedDuplexStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast smoothed MA period", "Indicator");

		_slowPeriod = Param(nameof(SlowPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow smoothed MA period", "Indicator");

		_stopLossPoints = Param(nameof(StopLossPoints), 200)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

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

		_fast = null;
		_slow = null;
		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
		_cooldown = 0;
	}

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

		_fast = new SmoothedMovingAverage { Length = FastPeriod };
		_slow = new SmoothedMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

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

		if (!_fast.IsFormed || !_slow.IsFormed)
		{
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}

		// SmoothedMA crossover
		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 80;
		}
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
			_cooldown = 80;
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}