Ver no GitHub

Estratégia de Trading com RSI Qualificado

Visão geral

Esta estratégia reproduz o consultor especialista do MetaTrader "Trade on qualified RSI" usando a API de alto nível do StockSharp. Ela se comporta como um sistema contrário: interpreta leituras prolongadas do Índice de Força Relativa (RSI) como exaustão e abre uma posição contra o movimento predominante após o momentum persistir por várias velas. Os stops trailing são gerenciados em passos de preço para que o stop siga a operação apenas quando o preço se move a favor da mesma.

Lógica de sinal

Indicador

  • Índice de Força Relativa com um período configurável (padrão: 28).
  • Calculado sobre a subscrição de velas selecionada (padrão: velas de 15 minutos).

Entrada curta

  1. A última vela fechada tem RSI maior ou igual ao limiar superior (padrão: 55).
  2. Cada uma das CountBars velas fechadas anteriores também teve RSI acima do mesmo limiar. Internamente a estratégia conta barras consecutivas; o sinal é disparado quando o contador atinge CountBars + 1.
  3. Não há posição ativa aberta. Quando ativada, a estratégia vende a mercado com o volume configurado e armazena o fechamento da vela como preço de entrada.

Entrada longa

  1. A última vela fechada tem RSI menor ou igual ao limiar inferior (padrão: 45).
  2. Cada uma das CountBars velas fechadas anteriores também teve RSI abaixo do mesmo limiar (são necessárias CountBars + 1 leituras consecutivas).
  3. Não existe posição aberta. Quando ativada, a estratégia compra a mercado com o volume configurado e registra o preço de entrada.

Gestão de posição

  • Stop inicial: logo após a entrada, o preço de stop é colocado a StopLossPoints passos de preço do fechamento de entrada (abaixo para comprados, acima para vendidos). Os passos de preço são obtidos de Security.PriceStep; se o ativo não o define, a estratégia recorre a 1.
  • Trailing: em cada vela terminada o stop é ajustado em direção ao fechamento atual. Para posições compradas o stop torna-se Fechamento - StopLossPoints * PriceStep quando esse valor está acima do stop anterior. Para posições vendidas o stop torna-se Fechamento + StopLossPoints * PriceStep quando esse valor está abaixo do stop anterior.
  • Saída: se a mínima da vela cruzar abaixo do stop quando comprado, ou a máxima da vela cruzar acima do stop quando vendido, a estratégia sai de toda a posição a mercado. Não há alvos de lucro adicionais nem sinais de reversão; novas entradas ocorrem apenas depois que a posição anterior for fechada.

Parâmetros

Nome Descrição Padrão
RsiPeriod Comprimento de retrospecto para o indicador RSI. 28
UpperThreshold Nível de RSI que qualifica uma configuração vendida. 55
LowerThreshold Nível de RSI que qualifica uma configuração comprada. 45
CountBars Quantas barras anteriores devem permanecer além do limiar (CountBars + 1 barras consecutivas no total). 5
StopLossPoints Distância do stop expressa em passos de preço. O deslocamento de preço real é igual a StopLossPoints * PriceStep. 21
TradeVolume Volume enviado com cada ordem de entrada. 1
CandleType Subscrição de velas usada para os cálculos do indicador. Velas de 15 minutos

Todos os parâmetros podem ser otimizados. Os limiares permitem valores decimais, portanto é possível um ajuste fino dos limites do RSI.

Notas de implementação

  • A estratégia usa SubscribeCandles(...).Bind(...) para alimentar o indicador RSI e reagir apenas quando a vela está completamente formada.
  • Os valores do RSI não são lidos de volta do indicador por índice; em vez disso, contadores rastreiam quantas velas terminadas consecutivas respeitam os limiares.
  • Os stops protetores são simulados dentro da estratégia. As ordens são fechadas a mercado quando o nível de stop é cruzado em vez de colocar ordens de stop separadas.
  • Mensagens de log são produzidas para entradas e saídas, espelhando a saída detalhada do consultor especialista original.

Uso

  1. Adicione a estratégia a uma aplicação StockSharp, atribua o ativo e portfólio desejados e configure a série de velas.
  2. Ajuste os limiares do RSI, o número de barras qualificadas e a distância do stop para corresponder à volatilidade do instrumento alvo.
  3. Inicie a estratégia. Monitore o log para ver quando os sinais ocorrem e como o stop trailing evolui.
  4. Considere executar o otimizador incorporado para buscar melhores combinações de limiares ou distâncias de stop para mercados específicos.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Contrarian RSI strategy converted from the "Trade on qualified RSI" expert advisor.
/// </summary>
public class TradeOnQualifiedRSIStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _upperThreshold;
	private readonly StrategyParam<decimal> _lowerThreshold;
	private readonly StrategyParam<int> _countBars;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<DataType> _candleType;

	private RelativeStrengthIndex _rsi;
	private decimal? _stopPrice;
	private decimal _entryPrice;
	private int _aboveCounter;
	private int _belowCounter;

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

	/// <summary>
	/// Upper RSI threshold used to qualify short entries.
	/// </summary>
	public decimal UpperThreshold
	{
		get => _upperThreshold.Value;
		set => _upperThreshold.Value = value;
	}

	/// <summary>
	/// Lower RSI threshold used to qualify long entries.
	/// </summary>
	public decimal LowerThreshold
	{
		get => _lowerThreshold.Value;
		set => _lowerThreshold.Value = value;
	}

	/// <summary>
	/// Number of previous RSI bars that must stay beyond the threshold.
	/// </summary>
	public int CountBars
	{
		get => _countBars.Value;
		set => _countBars.Value = value;
	}

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

	/// <summary>
	/// Order volume used for entries.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Candle type used as the RSI data source.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initialize <see cref="TradeOnQualifiedRSIStrategy"/>.
	/// </summary>
	public TradeOnQualifiedRSIStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 28)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "Lookback period for RSI calculation.", "RSI")
			
			.SetOptimize(10, 50, 2);

		_upperThreshold = Param(nameof(UpperThreshold), 65m)
			.SetDisplay("Upper Threshold", "RSI level used to qualify short signals.", "RSI")

			.SetOptimize(50m, 70m, 1m);

		_lowerThreshold = Param(nameof(LowerThreshold), 35m)
			.SetDisplay("Lower Threshold", "RSI level used to qualify long signals.", "RSI")

			.SetOptimize(30m, 50m, 1m);

		_countBars = Param(nameof(CountBars), 8)
			.SetGreaterThanZero()
			.SetDisplay("Qualification Bars", "How many previous RSI bars must stay beyond the threshold.", "Signals")

			.SetOptimize(1, 10, 1);

		_stopLossPoints = Param(nameof(StopLossPoints), 1000)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss Points", "Stop loss distance expressed in price steps.", "Risk")

			.SetOptimize(5, 50, 5);

		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Order volume used for entries.", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Source timeframe for RSI calculation.", "General");
	}

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

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

		Volume = TradeVolume;
		_stopPrice = null;
		_entryPrice = 0m;
		_aboveCounter = 0;
		_belowCounter = 0;
	}

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

		Volume = TradeVolume;

		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };

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

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

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

		if (_rsi == null || !_rsi.IsFormed)
		{
			_aboveCounter = 0;
			_belowCounter = 0;
			return;
		}

		if (Volume <= 0)
			return;

		var distance = CalculateStopDistance();
		if (distance <= 0)
			return;

		UpdateCounters(rsiValue);

		var requiredBars = CountBars + 1;

		if (Position == 0)
		{
			_stopPrice = null;
			_entryPrice = 0m;

			var shortSignal = rsiValue >= UpperThreshold && _aboveCounter >= requiredBars;
			var longSignal = rsiValue <= LowerThreshold && _belowCounter >= requiredBars;

			if (shortSignal)
			{
				this.LogInfo($"Open short: RSI={rsiValue:F2}, counter={_aboveCounter}");
				SellMarket();
				_entryPrice = candle.ClosePrice;
				_stopPrice = candle.ClosePrice + distance;
				return;
			}

			if (longSignal)
			{
				this.LogInfo($"Open long: RSI={rsiValue:F2}, counter={_belowCounter}");
				BuyMarket();
				_entryPrice = candle.ClosePrice;
				_stopPrice = candle.ClosePrice - distance;
			}

			return;
		}

		if (Position > 0)
		{
			if (_stopPrice == null)
				_stopPrice = _entryPrice - distance;

			var newStop = candle.ClosePrice - distance;
			if (_stopPrice == null || newStop > _stopPrice)
				_stopPrice = newStop;

			if (_stopPrice != null && candle.LowPrice <= _stopPrice)
			{
				this.LogInfo($"Exit long via stop at {_stopPrice:F5}");
				SellMarket();
				_stopPrice = null;
				_entryPrice = 0m;
			}

			return;
		}

		if (Position < 0)
		{
			if (_stopPrice == null)
				_stopPrice = _entryPrice + distance;

			var newStop = candle.ClosePrice + distance;
			if (_stopPrice == null || newStop < _stopPrice)
				_stopPrice = newStop;

			if (_stopPrice != null && candle.HighPrice >= _stopPrice)
			{
				this.LogInfo($"Exit short via stop at {_stopPrice:F5}");
				BuyMarket();
				_stopPrice = null;
				_entryPrice = 0m;
			}
		}
	}

	private decimal CalculateStopDistance()
	{
		var step = Security?.PriceStep ?? 1m;
		if (step <= 0)
			step = 1m;

		return StopLossPoints * step;
	}

	private void UpdateCounters(decimal rsiValue)
	{
		// Track consecutive closes above and below the thresholds.
		if (rsiValue >= UpperThreshold)
		{
			_aboveCounter++;
		}
		else
		{
			_aboveCounter = 0;
		}

		if (rsiValue <= LowerThreshold)
		{
			_belowCounter++;
		}
		else
		{
			_belowCounter = 0;
		}
	}
}