Ver no GitHub

Estratégia RSI Trader

Visão geral

Esta estratégia replica o consultor especialista do MetaTrader "RSI trader v0.15" na API de alto nível do StockSharp. Ela alinha a direção da tendência entre a ação do preço e um Índice de Força Relativa (RSI) suavizado. O trading é realizado em um único instrumento usando velas de uma hora por padrão, mas o período é configurável através do parâmetro CandleType.

Lógica de trading

  1. Calcular um RSI padrão com um período configurável.
  2. Suavizar o RSI com duas médias móveis simples (SMA): uma média de sinal rápida e uma de confirmação mais lenta.
  3. Rastrear duas médias móveis do preço de fechamento: uma média móvel simples curta e uma média móvel ponderada longa para aproximar o par SMA/LWMA do MQL original.
  4. Gerar estados de tendência em cada vela terminada:
    • Alinhamento de alta: SMA de preço curta acima da longa e SMA RSI rápida acima da lenta.
    • Alinhamento de baixa: SMA de preço curta abaixo da longa e SMA RSI rápida abaixo da lenta.
    • Lateral / discordância: médias móveis apontando em direções opostas, sinalizando que não há tendência clara.
  5. Agir sobre o estado detectado:
    • Abrir uma posição comprada quando o alinhamento de alta aparecer e não houver posição atualmente aberta.
    • Abrir uma posição vendida quando o alinhamento de baixa aparecer e não houver posição atualmente aberta.
    • Fechar imediatamente qualquer posição aberta quando o estado lateral for detectado, espelhando a saída protetora da versão MQL.
  6. O modo de reversão opcional inverte todas as direções de entrada, permitindo ao usuário operar contra a tendência em relação aos sinais detectados.

A estratégia respeita o tratamento de proteção incorporado do StockSharp e requer velas concluídas antes de tomar qualquer ação.

Parâmetros

Parâmetro Descrição Padrão
RsiPeriod Período de retrospecto usado para o cálculo do RSI. 14
ShortRsiMaPeriod Comprimento da SMA rápida aplicada aos valores do RSI. 9
LongRsiMaPeriod Comprimento da SMA lenta aplicada aos valores do RSI. 45
ShortPriceMaPeriod Comprimento da SMA curta aplicada aos preços de fechamento. 9
LongPriceMaPeriod Comprimento da média móvel ponderada longa aplicada aos preços. 45
Reverse Quando true, as ordens de compra e venda são trocadas (espelha a entrada "Reverse" original). false
CandleType Tipo de dados para velas de preço. Padrão é período de uma hora. 1h

Todos os parâmetros inteiros expõem intervalos de otimização que refletem a flexibilidade das configurações de entrada do especialista do MetaTrader.

Gestão de risco

  • As posições são fechadas assim que as tendências de preço e RSI discordam (estado lateral), reproduzindo o comportamento de saída imediata do EA.
  • StartProtection() é habilitado no início para cooperar com a infraestrutura protetora do StockSharp.

Notas

  • A estratégia depende da propriedade base Volume de Strategy para definir o tamanho da operação.
  • Apenas velas concluídas são processadas; atualizações parciais são ignoradas para evitar sinais prematuros.
  • A média móvel ponderada é usada para corresponder à LWMA longa original aplicada aos fechamentos de preço.
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

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

/// <summary>
/// RSI trader strategy aligning price and RSI moving-average trends.
/// </summary>
public class RsiTraderStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _shortRsiMaPeriod;
	private readonly StrategyParam<int> _longRsiMaPeriod;
	private readonly StrategyParam<int> _shortPriceMaPeriod;
	private readonly StrategyParam<int> _longPriceMaPeriod;
	private readonly StrategyParam<bool> _reverse;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _closes = new();
	private readonly List<decimal> _rsiValues = new();

	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
	public int ShortRsiMaPeriod { get => _shortRsiMaPeriod.Value; set => _shortRsiMaPeriod.Value = value; }
	public int LongRsiMaPeriod { get => _longRsiMaPeriod.Value; set => _longRsiMaPeriod.Value = value; }
	public int ShortPriceMaPeriod { get => _shortPriceMaPeriod.Value; set => _shortPriceMaPeriod.Value = value; }
	public int LongPriceMaPeriod { get => _longPriceMaPeriod.Value; set => _longPriceMaPeriod.Value = value; }
	public bool Reverse { get => _reverse.Value; set => _reverse.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public RsiTraderStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 14).SetDisplay("RSI Period", "RSI calculation length", "RSI").SetGreaterThanZero();
		_shortRsiMaPeriod = Param(nameof(ShortRsiMaPeriod), 12).SetDisplay("Short RSI MA", "Short moving average on RSI", "RSI").SetGreaterThanZero();
		_longRsiMaPeriod = Param(nameof(LongRsiMaPeriod), 60).SetDisplay("Long RSI MA", "Long moving average on RSI", "RSI").SetGreaterThanZero();
		_shortPriceMaPeriod = Param(nameof(ShortPriceMaPeriod), 12).SetDisplay("Short Price MA", "Short simple moving average", "Price").SetGreaterThanZero();
		_longPriceMaPeriod = Param(nameof(LongPriceMaPeriod), 60).SetDisplay("Long Price MA", "Long weighted moving average", "Price").SetGreaterThanZero();
		_reverse = Param(nameof(Reverse), false).SetDisplay("Reverse", "Flip buy/sell signals", "Trading");
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame()).SetDisplay("Candle Type", "Primary candle type", "Data");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, CandleType);
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_closes.Clear();
		_rsiValues.Clear();
	}

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

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

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

		_closes.Add(candle.ClosePrice);

		var maxCache = Math.Max(LongPriceMaPeriod, Math.Max(LongRsiMaPeriod + RsiPeriod, 300));
		if (_closes.Count > maxCache)
			_closes.RemoveAt(0);

		var rsi = CalculateRsi();
		if (rsi is null)
			return;

		_rsiValues.Add(rsi.Value);
		if (_rsiValues.Count > maxCache)
			_rsiValues.RemoveAt(0);

		if (_rsiValues.Count < LongRsiMaPeriod || _closes.Count < LongPriceMaPeriod)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var shortRsi = AverageLast(_rsiValues, ShortRsiMaPeriod);
		var longRsi = AverageLast(_rsiValues, LongRsiMaPeriod);
		var shortPrice = AverageLast(_closes, ShortPriceMaPeriod);
		var longPrice = WeightedAverageLast(_closes, LongPriceMaPeriod);

		var goLong = shortPrice > longPrice && shortRsi > longRsi;
		var goShort = shortPrice < longPrice && shortRsi < longRsi;
		var sideways = !goLong && !goShort;

		if (sideways && Position != 0)
		{
			if (Position > 0)
				SellMarket(Position);
			else
				BuyMarket(Math.Abs(Position));

			return;
		}

		if (Position != 0)
			return;

		if (goLong)
		{
			if (Reverse)
				SellMarket();
			else
				BuyMarket();
		}
		else if (goShort)
		{
			if (Reverse)
				BuyMarket();
			else
				SellMarket();
		}
	}

	private decimal? CalculateRsi()
	{
		if (_closes.Count <= RsiPeriod)
			return null;

		decimal gainSum = 0m;
		decimal lossSum = 0m;
		var start = _closes.Count - RsiPeriod;

		for (var i = start; i < _closes.Count; i++)
		{
			var change = _closes[i] - _closes[i - 1];
			if (change > 0m)
				gainSum += change;
			else
				lossSum -= change;
		}

		var averageGain = gainSum / RsiPeriod;
		var averageLoss = lossSum / RsiPeriod;

		if (averageLoss == 0m)
			return 100m;

		var rs = averageGain / averageLoss;
		return 100m - 100m / (1m + rs);
	}

	private static decimal AverageLast(IReadOnlyList<decimal> values, int length)
	{
		decimal sum = 0m;
		var start = values.Count - length;

		for (var i = start; i < values.Count; i++)
			sum += values[i];

		return sum / length;
	}

	private static decimal WeightedAverageLast(IReadOnlyList<decimal> values, int length)
	{
		decimal weightedSum = 0m;
		decimal weightSum = 0m;
		var start = values.Count - length;
		var weight = 1m;

		for (var i = start; i < values.Count; i++)
		{
			weightedSum += values[i] * weight;
			weightSum += weight;
			weight += 1m;
		}

		return weightSum > 0m ? weightedSum / weightSum : values[^1];
	}
}