Ver no GitHub

Estratégia Improve MA & RSI com Cobertura

Esta estratégia porta o expert original do MetaTrader "Improve" para o StockSharp usando a API de alto nível. Opera simultaneamente dois instrumentos: o símbolo principal selecionado para a estratégia e um símbolo de cobertura. A direção da operação é definida pela relação entre duas médias móveis suavizadas no instrumento principal e o índice de força relativa (RSI). A perna de cobertura espelha a direção da perna principal, criando uma exposição pareada que busca lucrar com movimentos de momentum sincronizados enquanto limita o risco de um único instrumento.

Lógica da estratégia

  • Calcular duas Médias Móveis Suavizadas (SMMA) no símbolo primário com períodos rápido e lento configuráveis.
  • Calcular RSI nas mesmas velas e monitorar os limiares de sobrevenda/sobrecompra.
  • Entrar comprado em ambos os instrumentos quando a SMMA lenta está acima da SMMA rápida e o RSI está em ou abaixo do limiar de sobrevenda.
  • Entrar vendido em ambos os instrumentos quando a SMMA lenta está abaixo da SMMA rápida e o RSI está em ou acima do limiar de sobrecompra.
  • As posições permanecem abertas até que o lucro aberto combinado de ambas as pernas exceda a meta monetária configurada, momento em que a estratégia liquida ambos os lados.

O algoritmo acompanha os preços de fechamento mais recentes de cada instrumento. O lucro combinado é estimado a partir da diferença entre o fechamento atual e o preço de entrada armazenado de cada perna. Como nenhum stop-loss é aplicado, as posições podem permanecer abertas por períodos prolongados quando o preço não atinge a meta de lucro.

Parâmetros

Parâmetro Descrição
Volume Quantidade de ordem para ambos os instrumentos, o primário e o de cobertura.
Profit Target Meta monetária compartilhada por ambas as pernas; quando atingida, a estratégia fecha cada posição aberta.
Hedge Security Instrumento secundário que é negociado junto com o instrumento principal.
Fast MA Período da Média Móvel Suavizada rápida (padrão 8).
Slow MA Período da Média Móvel Suavizada lenta (padrão 21). Deve ser maior que o período da MA rápida.
RSI Period Comprimento usado para calcular o RSI (padrão 21).
Oversold Nível RSI que aciona entradas compradas junto com a condição de MA (padrão 30).
Overbought Nível RSI que aciona entradas vendidas junto com a condição de MA (padrão 70).
Candle Type Período para cálculos; padrão velas de 1 hora, mas pode ser ajustado.

Indicadores

  • Média Móvel Suavizada (SMMA) – usada duas vezes para definir os componentes de tendência rápida e lenta.
  • Índice de Força Relativa (RSI) – determina condições de sobrevenda/sobrecompra para confirmação.

Regras de entrada e saída

  1. Entrada comprada
    • SMMA lenta > SMMA rápida no símbolo primário.
    • RSI ≤ Sobrevenda.
    • Ambas as pernas são abertas com ordens a mercado na mesma direção (compra/compra).
  2. Entrada vendida
    • SMMA lenta < SMMA rápida no símbolo primário.
    • RSI ≥ Sobrecompra.
    • Ambas as pernas são abertas com ordens a mercado na mesma direção (venda/venda).
  3. Saída
    • Quando (lucro primário + lucro de cobertura) ≥ Profit Target, a estratégia fecha ambas as posições usando ordens a mercado.
    • Nenhuma lógica adicional de stop-loss ou trailing é aplicada; o gerenciamento de risco deve ser adicionado externamente se necessário.

Notas de uso

  • Garantir que tanto o instrumento principal quanto o de cobertura estejam atribuídos antes de iniciar a estratégia; caso contrário, lançará uma exceção.
  • A estimativa de lucro combinado depende dos preços de fechamento de velas. Derrapagem e diferenças de execução entre as duas pernas podem afetar o lucro real realizado.
  • Como a estratégia abre ambas as pernas simultaneamente, é adequada para instrumentos correlacionados (por exemplo, pares de moedas ou futuros relacionados) onde se espera que se movam em conjunto.
  • Considerar adicionar controles de risco em nível de portfólio ao negociar ao vivo, pois o algoritmo original usa apenas a meta de lucro virtual para saídas.
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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Dual smoothed moving average and RSI hedge strategy converted from Improve.mq5.
/// </summary>
public class ImproveMaRsiHedgeStrategy : Strategy
{
	private readonly StrategyParam<decimal> _profitTarget;
	private readonly StrategyParam<Security> _hedgeSecurity;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _oversoldLevel;
	private readonly StrategyParam<decimal> _overboughtLevel;
	private readonly StrategyParam<DataType> _candleType;

	private SmoothedMovingAverage _fastMa = null!;
	private SmoothedMovingAverage _slowMa = null!;
	private RelativeStrengthIndex _rsi = null!;

	private decimal _baseLastClose;
	private decimal _hedgeLastClose;
	private decimal _baseEntryPrice;
	private decimal _hedgeEntryPrice;
	private bool _hasBaseClose;
	private bool _hasHedgeClose;
	private int _pairDirection;


	/// <summary>
	/// Profit target across both legs expressed in money.
	/// </summary>
	public decimal ProfitTarget
	{
		get => _profitTarget.Value;
		set => _profitTarget.Value = value;
	}

	/// <summary>
	/// Second instrument traded alongside the primary security.
	/// </summary>
	public Security HedgeSecurity
	{
		get => _hedgeSecurity.Value;
		set => _hedgeSecurity.Value = value;
	}

	/// <summary>
	/// Smoothed moving average period for the fast line.
	/// </summary>
	public int FastMaPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Smoothed moving average period for the slow line.
	/// </summary>
	public int SlowMaPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

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

	/// <summary>
	/// RSI oversold threshold.
	/// </summary>
	public decimal OversoldLevel
	{
		get => _oversoldLevel.Value;
		set => _oversoldLevel.Value = value;
	}

	/// <summary>
	/// RSI overbought threshold.
	/// </summary>
	public decimal OverboughtLevel
	{
		get => _overboughtLevel.Value;
		set => _overboughtLevel.Value = value;
	}

	/// <summary>
	/// Type of candles used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="ImproveMaRsiHedgeStrategy"/> class.
	/// </summary>
	public ImproveMaRsiHedgeStrategy()
	{

		_profitTarget = Param(nameof(ProfitTarget), 50m)
			.SetGreaterThanZero()
			.SetDisplay("Profit Target", "Combined profit target across both legs", "Risk")
			;

		_hedgeSecurity = Param<Security>(nameof(HedgeSecurity))
			.SetDisplay("Hedge Security", "Secondary instrument to trade", "General");

		_fastPeriod = Param(nameof(FastMaPeriod), 8)
			.SetGreaterThanZero()
			.SetDisplay("Fast MA", "Fast smoothed MA period", "Indicators")
			;

		_slowPeriod = Param(nameof(SlowMaPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA", "Slow smoothed MA period", "Indicators")
			;

		_rsiPeriod = Param(nameof(RsiPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "Length of the RSI", "Indicators")
			;

		_oversoldLevel = Param(nameof(OversoldLevel), 30m)
			.SetDisplay("Oversold", "RSI oversold threshold", "Indicators")
			;

		_overboughtLevel = Param(nameof(OverboughtLevel), 70m)
			.SetDisplay("Overbought", "RSI overbought threshold", "Indicators")
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Time frame for calculations", "Data");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		if (Security != null)
			yield return (Security, CandleType);

		if (HedgeSecurity != null)
			yield return (HedgeSecurity, CandleType);
	}

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

		_fastMa = null!;
		_slowMa = null!;
		_rsi = null!;
		_baseLastClose = 0m;
		_hedgeLastClose = 0m;
		_baseEntryPrice = 0m;
		_hedgeEntryPrice = 0m;
		_hasBaseClose = false;
		_hasHedgeClose = false;
		_pairDirection = 0;
	}

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

		if (Security == null)
			throw new InvalidOperationException("Primary security must be specified.");

		if (HedgeSecurity == null)
			throw new InvalidOperationException("Hedge security must be specified.");

		if (FastMaPeriod >= SlowMaPeriod)
			throw new InvalidOperationException("Fast MA period must be less than slow MA period.");

		_fastMa = new SmoothedMovingAverage { Length = FastMaPeriod };
		_slowMa = new SmoothedMovingAverage { Length = SlowMaPeriod };
		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };

		var baseSubscription = SubscribeCandles(CandleType);
		baseSubscription
			.Bind(_fastMa, _slowMa, _rsi, ProcessBaseCandle)
			.Start();

		var hedgeSubscription = SubscribeCandles(CandleType, false, HedgeSecurity);
		hedgeSubscription
			.Bind(ProcessHedgeCandle)
			.Start();
	}

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

		_baseLastClose = candle.ClosePrice;
		_hasBaseClose = true;

		CheckProfitTarget();

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (!_fastMa.IsFormed || !_slowMa.IsFormed || !_rsi.IsFormed)
			return;

		if (_pairDirection != 0)
			return;

		if (!_hasHedgeClose)
			return;

		if (slowValue > fastValue && rsiValue <= OversoldLevel)
		{
			OpenPair(1);
		}
		else if (slowValue < fastValue && rsiValue >= OverboughtLevel)
		{
			OpenPair(-1);
		}
	}

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

		_hedgeLastClose = candle.ClosePrice;
		_hasHedgeClose = true;

		CheckProfitTarget();
	}

	private void OpenPair(int direction)
	{
		if (direction == 0)
			return;

		var basePos = GetPositionValue(Security, Portfolio) ?? 0m;
		var hedgePos = GetPositionValue(HedgeSecurity, Portfolio) ?? 0m;

		if (basePos != 0m || hedgePos != 0m)
			return;

		var volume = Volume;

		if (direction > 0)
		{
			BuyMarket(volume, Security);
			BuyMarket(volume, HedgeSecurity);
		}
		else
		{
			SellMarket(volume, Security);
			SellMarket(volume, HedgeSecurity);
		}

		_pairDirection = direction;
		_baseEntryPrice = _baseLastClose;
		_hedgeEntryPrice = _hedgeLastClose;
	}

	private void CheckProfitTarget()
	{
		if (_pairDirection == 0 || !_hasBaseClose || !_hasHedgeClose)
			return;

		var baseProfit = _pairDirection > 0
			? (_baseLastClose - _baseEntryPrice) * Volume
			: (_baseEntryPrice - _baseLastClose) * Volume;

		var hedgeProfit = _pairDirection > 0
			? (_hedgeLastClose - _hedgeEntryPrice) * Volume
			: (_hedgeEntryPrice - _hedgeLastClose) * Volume;

		var totalProfit = baseProfit + hedgeProfit;

		if (totalProfit >= ProfitTarget)
		{
			ClosePair();
		}
	}

	private void ClosePair()
	{
		var basePos = GetPositionValue(Security, Portfolio) ?? 0m;
		if (basePos > 0)
		{
			SellMarket(basePos, Security);
		}
		else if (basePos < 0)
		{
			BuyMarket(-basePos, Security);
		}

		var hedgePos = GetPositionValue(HedgeSecurity, Portfolio) ?? 0m;
		if (hedgePos > 0)
		{
			SellMarket(hedgePos, HedgeSecurity);
		}
		else if (hedgePos < 0)
		{
			BuyMarket(-hedgePos, HedgeSecurity);
		}

		_pairDirection = 0;
		_baseEntryPrice = 0m;
		_hedgeEntryPrice = 0m;
	}
}