Открыть на GitHub

Стратегия RSI RFTL

Стратегия переносит советник RSI RFTL EA из MetaTrader 5 на высокоуровневый API StockSharp. Логика торговли основана на построении трендовых линий по разворотам RSI с дополнительной фильтрацией через Recursive Filter Trend Line (RFTL). Реализация повторяет пошаговые решения оригинального советника, используя типичные для StockSharp сущности: StrategyParam, подписки на свечи и связывание индикаторов.

Как это работает

  1. Поиск разворотных точек RSI – последние 500 значений RSI просматриваются на локальные максимумы и минимумы. Пики должны быть выше уровней 40 и 60, впадины — ниже 60 и 40, как и в исходном MQL-коде.
  2. Проекция трендовых линий – когда найдены две валидные вершины или впадины, через них проводится линия, которая экстраполируется в текущий и предыдущий бар. Любые промежуточные экстремумы, пробивающие уровни 40/60, обнуляют линию.
  3. Фильтр RFTL – предыдущее значение Recursive Filter Trend Line (рассчитанное по исходной таблице коэффициентов) должно находиться выше предыдущего закрытия для шорта или ниже — для лонга. Это удерживает сделки в направлении фильтра.
  4. Фильтрация входов – RSI также должен находиться по «правильную» сторону от нейтральной зоны: для шортов требуется RSI выше 47/50, для лонгов — ниже 55/50.
  5. Риск-менеджмент – стоп-лосс, тейк-профит и трейлинг выражены в пунктах и обновляются на каждой завершённой свече, повторяя модификацию ордеров в советнике. Дополнительно позиции закрываются при выходе RSI за 70 (лонг) или падении ниже 30 (шорт).

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

  • Сигнал на продажу
    • Две впадины RSI ниже 60/40 формируют растущую линию тренда, которая пробивается вниз (RSI[1] < линия, RSI[2] > линия(предыдущая)).
    • Предыдущее значение RFTL выше предыдущего закрытия, что подтверждает давление вниз.
    • RSI остаётся в зоне быков (RSI[2] > 50, RSI[0] > 47), а найденные вершины расположены дальше по истории, чем впадины (pos₂ > pos₄).
  • Сигнал на покупку
    • Две вершины RSI выше 40/60 формируют нисходящую линию, которая пробивается вверх (RSI[1] > линия, RSI[2] < линия(предыдущая)).
    • Предыдущее значение RFTL ниже предыдущего закрытия.
    • RSI остаётся в «медвежьей» зоне (RSI[2] < 50, RSI[0] < 55), а впадины свежие относительно вершин (pos₄ > pos₂).

Сигналы рассчитываются только после того, как индикаторы полностью сформированы и набрана необходимая история, что исключает сделки по неполным данным.

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

  • Стоп-лосс / Тейк-профит – задаются в пунктах. Если текущая свеча пересекает соответствующий уровень, позиция закрывается, а состояние трейлинга сбрасывается.
  • Трейлинг-стоп – необязательный. После прохождения TrailingStopPips + TrailingStepPips в прибыльную сторону стоп подтягивается к закрытию, причём каждое следующее срабатывание требует дополнительного хода на TrailingStepPips.
  • Аварийный выход по RSI – лонг закрывается при RSI > 70, шорт – при RSI < 30, как и в оригинале.

Параметры

Параметр Значение по умолчанию Описание
CandleType 1 час Таймфрейм для расчёта RSI и RFTL.
TradeVolume 1 Объём заявки при открытии позиции.
RsiPeriod 30 Период расчёта RSI.
StopLossPips 50 Расстояние стоп-лосса в пунктах (0 отключает).
TakeProfitPips 50 Расстояние тейк-профита в пунктах (0 отключает).
TrailingStopPips 5 Отступ трейлинг-стопа в пунктах (0 отключает).
TrailingStepPips 5 Минимальный дополнительный ход перед подтяжкой трейлинга.

Все дистанции умножаются на PriceStep, что соответствует работе с пунктами в MQL.

Использование

  1. Подключите стратегию к инструменту и задайте CandleType, совпадающий с таймфреймом тестов в MetaTrader.
  2. Настройте параметры риска (стоп, тейк, трейлинг) в пунктах. Значение 0 отключает соответствующий механизм.
  3. Запустите стратегию – она подпишется на свечи, вычислит RSI и RFTL и начнёт искать сигналы после накопления истории.
  4. Следите за графиками: на ценовой панели отображаются свечи и линия RFTL, на второй панели – осциллятор RSI.

Особенности и отличия

  • Индикатор RFTL реализован напрямую на C# с исходной таблицей коэффициентов; дополнительные файлы не требуются.
  • Управление позицией одношаговое: стратегия последовательно переключается между лонгом, шортом и нейтральной позицией, как и советник с одним магическим номером.
  • Поскольку стопы и трейлинг исполняются внутри стратегии (StockSharp не исполняет MT5-ордеры автоматически), повторные входы пропускаются на том же баре, где сработала защита, что даёт консервативное, но безопасное приближение.
  • Буферы истории ограничены 600 значениями, что соответствует 500 элементам из исходного кода и не допускает роста памяти.
  • Все комментарии на английском, код соответствует стилистике высокоуровневого API 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-based trend strategy with a simple recursive filter (EMA) as trend confirmation.
/// Buys when RSI crosses above oversold level and EMA confirms uptrend.
/// Sells when RSI crosses below overbought level and EMA confirms downtrend.
/// </summary>
public class RsiRftlStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<decimal> _overbought;
	private readonly StrategyParam<decimal> _oversold;

	private RelativeStrengthIndex _rsi;
	private ExponentialMovingAverage _ema;

	private decimal _prevRsi;
	private decimal _entryPrice;
	private int _cooldown;

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

	/// <summary>
	/// EMA period for trend filter.
	/// </summary>
	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	/// <summary>
	/// Overbought RSI level.
	/// </summary>
	public decimal Overbought
	{
		get => _overbought.Value;
		set => _overbought.Value = value;
	}

	/// <summary>
	/// Oversold RSI level.
	/// </summary>
	public decimal Oversold
	{
		get => _oversold.Value;
		set => _oversold.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public RsiRftlStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "Length of the RSI oscillator", "Indicator");

		_emaPeriod = Param(nameof(EmaPeriod), 44)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "Length of the trend EMA filter", "Indicator");

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

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

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

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

		_rsi = null;
		_ema = null;
		_prevRsi = 0;
		_entryPrice = 0;
		_cooldown = 0;
	}

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

		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		_ema = new ExponentialMovingAverage { Length = EmaPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_rsi, _ema, OnProcess);
		subscription.Start();
	}

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

		if (!_rsi.IsFormed || !_ema.IsFormed)
		{
			_prevRsi = rsiValue;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevRsi = rsiValue;
			return;
		}

		var close = candle.ClosePrice;
		var trendUp = close > emaValue;
		var trendDown = close < emaValue;

		// Buy: RSI crosses above oversold + uptrend
		if (_prevRsi < Oversold && rsiValue >= Oversold && trendUp && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 10;
		}
		// Sell: RSI crosses below overbought + downtrend
		else if (_prevRsi > Overbought && rsiValue <= Overbought && trendDown && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

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

		// Exit long on overbought
		if (Position > 0 && rsiValue > 80m)
		{
			SellMarket();
			_entryPrice = 0;
			_cooldown = 10;
		}
		// Exit short on oversold
		else if (Position < 0 && rsiValue < 20m)
		{
			BuyMarket();
			_entryPrice = 0;
			_cooldown = 10;
		}

		_prevRsi = rsiValue;
	}
}