Ver no GitHub

Estratégia de banda RM Stochastic

Visão geral

A RM Stochastic Band Strategy é uma versão StockSharp de alto nível do MetaTrader consultor especialista EA RM Stochastic Band de Ronny Maheza. A estratégia observa três osciladores estocásticos calculados em diferentes intervalos de tempo (base, médio e alto) e abre negociações somente quando todos os três confirmam condições de sobrevenda ou sobrecompra. Na entrada, os níveis de saída são derivados do Average True Range (ATR) medido no período de tempo mais alto, replicando os níveis de stop-loss e take-profit baseados em ATR no Expert Advisor original. Filtros de execução adicionais incluem um valor mínimo configurável do portfólio como proxy de margem e um controle de spread que adapta sua tolerância dependendo do spread observado.

Lógica principal

  1. Confirmação estocástica de vários períodos de tempo

    • O prazo de execução primário (padrão M1) gera o sinal de negociação.
    • Os prazos de confirmação (padrão M5 e M15) devem estar de acordo com a direção do sinal.
    • Uma negociação é aberta apenas se os valores %K estocásticos em todos os três períodos de tempo estiverem simultaneamente abaixo do nível de sobrevenda (configuração longa) ou acima do nível de sobrecompra (configuração curta).
  2. Saídas baseadas em volatilidade com ATR

    • ATR é calculado no período mais alto (padrão M15).
    • Stop loss = entry price ± ATR * StopLossMultiplier.
    • Lucro = entry price ± ATR * TakeProfitMultiplier.
    • Os preços são monitorados nas velas do período base; se uma vela tocar qualquer um dos níveis, a posição será fechada no mercado.
  3. Filtros de execução e segurança

    • Os pedidos são ignorados quando o spread observado (BestAsk - BestBid) excede o limite adaptativo. Se o spread for superior ao limite padrão, o limite mais flexível da conta em centavos será aplicado, refletindo a lógica EA de origem.
    • A negociação é bloqueada enquanto o valor da carteira estiver abaixo de MinMargin.
    • Apenas uma posição pode ser aberta por vez e nenhuma nova negociação será iniciada se existirem ordens ativas.

Indicadores e Assinaturas

Indicador Prazo Objetivo
Stochastic Oscilador Período base (padrão 1 minuto) Gera sinal primário (apenas %K é usado).
Stochastic Oscilador Período intermediário (padrão 5 minutos) Confirma a direção do sinal primário.
Stochastic Oscilador Prazo alto (padrão 15 minutos) Fornece confirmação de longo prazo.
Faixa Verdadeira Média Prazo alto (padrão 15 minutos) Define distâncias de stop-loss e take-profit ajustadas à volatilidade.

Os dados de nível 1 são assinados para capturar o melhor lance e solicitar avaliação de spread.

Regras de entrada

  • Configuração longa: todos os três valores estocásticos de %K estão abaixo de OversoldLevel. Quando acionada, a estratégia compra no volume de mercado OrderVolume e armazena níveis de saída baseados em ATR.
  • Configuração curta: todos os três valores estocásticos de %K estão acima de OverboughtLevel. Uma venda no mercado é executada com o mesmo manuseio de volume.

Regras de saída

  • Stop-loss: Para posições longas, saia quando a mínima da vela tocar entry - ATR * StopLossMultiplier. Para posições vendidas, saia quando a máxima da vela atingir entry + ATR * StopLossMultiplier.
  • Take-profit: Para posições longas, saia quando a máxima da vela tocar entry + ATR * TakeProfitMultiplier. Para posições curtas, saia quando a mínima da vela atingir entry - ATR * TakeProfitMultiplier.
  • Após uma saída, a parada interna e os espaços reservados de destino são apagados para que o próximo sinal possa recalcular novos níveis.

Parâmetros

Parâmetro Descrição Padrão
OrderVolume Volume de cada ordem de mercado. 0,1
StochasticLength %K período de retrospectiva. 5
StochasticSmoothing Suavização aplicada a %K. 3
StochasticSignalLength %D comprimento. 3
AtrPeriod Período ATR no período máximo. 14
StopLossMultiplier Multiplicador ATR para o stop loss. 1,5
TakeProfitMultiplier Multiplicador de ATR para o lucro. 3,0
MinMargin Valor mínimo do portfólio exigido para negociação. 100
MaxSpreadStandard Limite de spread para contas padrão. 3
MaxSpreadCent Limite de spread usado quando o spread atual já excede o limite padrão. 10
OversoldLevel Limite de sobrevenda para %K estocástico. 20
OverboughtLevel Limite de sobrecompra para %K estocástico. 80
BaseCandleType Período primário (velas padrão de 1 minuto). 1 minuto
MidCandleType Prazo de confirmação (velas padrão de 5 minutos). 5 minutos
HighCandleType Confirmação + período de ATR (velas padrão de 15 minutos). 15 minutos

Todos os parâmetros suportam intervalos de otimização idênticos às entradas MetaTrader quando apropriado.

Notas de implementação

  • A estratégia usa SubscribeCandles(...).BindEx(...) para obter valores de indicadores estritamente por meio do API de alto nível, conforme exigido pelas diretrizes do projeto.
  • O spread é calculado a partir de atualizações ao vivo de nível 1; sem dados de compra/venda, a negociação permanece desativada, garantindo uma operação segura em feeds de dados que não fornecem cotações.
  • As posições são gerenciadas exclusivamente por meio de ordens de mercado, refletindo o EA original que dependia de entradas de mercado com níveis de stop-loss e take-profit pré-calculados.
  • Não há ponto de equilíbrio ou lógica final porque a fonte MQL não implementou esses recursos, apesar de ter parâmetros de entrada relacionados.

Dicas de uso

  1. Anexe a estratégia à segurança desejada e garanta que os dados de Nível 1 (oferta/oferta) estejam disponíveis para filtragem de spread adequada.
  2. Ajuste os limites estocásticos e os multiplicadores ATR para corresponder ao perfil de volatilidade do instrumento alvo.
  3. Ao otimizar, considere testar diferentes combinações de prazos se o mercado que você negocia tiver ciclos dominantes diferentes da estrutura original M1/M5/M15.
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>
/// Multi-timeframe stochastic oscillator strategy with ATR-based stop-loss and take-profit management.
/// Emulates the logic of the "EA RM Stochastic Band" MetaTrader expert advisor.
/// </summary>
public class RmStochasticBandStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<int> _stochasticLength;
	private readonly StrategyParam<int> _stochasticSmoothing;
	private readonly StrategyParam<int> _stochasticSignalLength;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _stopLossMultiplier;
	private readonly StrategyParam<decimal> _takeProfitMultiplier;
	private readonly StrategyParam<decimal> _minMargin;
	private readonly StrategyParam<decimal> _maxSpreadStandard;
	private readonly StrategyParam<decimal> _maxSpreadCent;
	private readonly StrategyParam<decimal> _oversoldLevel;
	private readonly StrategyParam<decimal> _overboughtLevel;
	private readonly StrategyParam<DataType> _baseCandleType;
	private readonly StrategyParam<DataType> _midCandleType;
	private readonly StrategyParam<DataType> _highCandleType;

	private decimal? _stochM1;
	private decimal? _stochM5;
	private decimal? _stochM15;
	private decimal? _atrValue;
	private decimal? _longStopPrice;
	private decimal? _longTakeProfit;
	private decimal? _shortStopPrice;
	private decimal? _shortTakeProfit;
	private decimal? _bestBid;
	private decimal? _bestAsk;

	/// <summary>
	/// Trade volume used for market orders.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// %K lookback for the stochastic oscillator.
	/// </summary>
	public int StochasticLength
	{
		get => _stochasticLength.Value;
		set => _stochasticLength.Value = value;
	}

	/// <summary>
	/// Smoothing period applied to %K.
	/// </summary>
	public int StochasticSmoothing
	{
		get => _stochasticSmoothing.Value;
		set => _stochasticSmoothing.Value = value;
	}

	/// <summary>
	/// %D moving average length.
	/// </summary>
	public int StochasticSignalLength
	{
		get => _stochasticSignalLength.Value;
		set => _stochasticSignalLength.Value = value;
	}

	/// <summary>
	/// ATR lookback used for volatility-based exits.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// Multiplier applied to ATR for stop-loss calculation.
	/// </summary>
	public decimal StopLossMultiplier
	{
		get => _stopLossMultiplier.Value;
		set => _stopLossMultiplier.Value = value;
	}

	/// <summary>
	/// Multiplier applied to ATR for take-profit calculation.
	/// </summary>
	public decimal TakeProfitMultiplier
	{
		get => _takeProfitMultiplier.Value;
		set => _takeProfitMultiplier.Value = value;
	}

	/// <summary>
	/// Minimum portfolio value required before placing trades.
	/// </summary>
	public decimal MinMargin
	{
		get => _minMargin.Value;
		set => _minMargin.Value = value;
	}

	/// <summary>
	/// Maximum spread (in price units) tolerated on standard accounts.
	/// </summary>
	public decimal MaxSpreadStandard
	{
		get => _maxSpreadStandard.Value;
		set => _maxSpreadStandard.Value = value;
	}

	/// <summary>
	/// Maximum spread (in price units) tolerated on cent accounts.
	/// </summary>
	public decimal MaxSpreadCent
	{
		get => _maxSpreadCent.Value;
		set => _maxSpreadCent.Value = value;
	}

	/// <summary>
	/// Threshold for oversold conditions.
	/// </summary>
	public decimal OversoldLevel
	{
		get => _oversoldLevel.Value;
		set => _oversoldLevel.Value = value;
	}

	/// <summary>
	/// Threshold for overbought conditions.
	/// </summary>
	public decimal OverboughtLevel
	{
		get => _overboughtLevel.Value;
		set => _overboughtLevel.Value = value;
	}

	/// <summary>
	/// Primary timeframe used for signal execution.
	/// </summary>
	public DataType BaseCandleType
	{
		get => _baseCandleType.Value;
		set => _baseCandleType.Value = value;
	}

	/// <summary>
	/// Intermediate timeframe used for stochastic confirmation.
	/// </summary>
	public DataType MidCandleType
	{
		get => _midCandleType.Value;
		set => _midCandleType.Value = value;
	}

	/// <summary>
	/// Higher timeframe used for stochastic confirmation and ATR calculation.
	/// </summary>
	public DataType HighCandleType
	{
		get => _highCandleType.Value;
		set => _highCandleType.Value = value;
	}

	public RmStochasticBandStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Order Volume", "Volume of each market order", "Trading");

		_stochasticLength = Param(nameof(StochasticLength), 5)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic Length", "%K lookback period", "Indicators")
			
			.SetOptimize(3, 15, 1);

		_stochasticSmoothing = Param(nameof(StochasticSmoothing), 3)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic Smoothing", "Smoothing period applied to %K", "Indicators")
			
			.SetOptimize(1, 7, 1);

		_stochasticSignalLength = Param(nameof(StochasticSignalLength), 3)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic Signal", "%D moving average length", "Indicators")
			
			.SetOptimize(1, 10, 1);

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "Lookback for ATR volatility filter", "Indicators")
			
			.SetOptimize(7, 30, 1);

		_stopLossMultiplier = Param(nameof(StopLossMultiplier), 1.5m)
			.SetGreaterThanZero()
			.SetDisplay("SL Multiplier", "ATR multiplier for stop-loss", "Risk")
			
			.SetOptimize(0.5m, 3m, 0.25m);

		_takeProfitMultiplier = Param(nameof(TakeProfitMultiplier), 3m)
			.SetGreaterThanZero()
			.SetDisplay("TP Multiplier", "ATR multiplier for take-profit", "Risk")
			
			.SetOptimize(1m, 6m, 0.5m);

		_minMargin = Param(nameof(MinMargin), 100m)
			.SetGreaterThanZero()
			.SetDisplay("Minimum Margin", "Required portfolio value before trading", "Risk");

		_maxSpreadStandard = Param(nameof(MaxSpreadStandard), 3m)
			.SetGreaterThanZero()
			.SetDisplay("Max Spread Standard", "Maximum spread allowed for standard accounts", "Filters");

		_maxSpreadCent = Param(nameof(MaxSpreadCent), 10m)
			.SetGreaterThanZero()
			.SetDisplay("Max Spread Cent", "Maximum spread allowed for cent accounts", "Filters");

		_oversoldLevel = Param(nameof(OversoldLevel), 20m)
			.SetDisplay("Oversold Level", "Threshold that defines oversold conditions", "Signals")
			
			.SetOptimize(5m, 40m, 5m);

		_overboughtLevel = Param(nameof(OverboughtLevel), 80m)
			.SetDisplay("Overbought Level", "Threshold that defines overbought conditions", "Signals")
			
			.SetOptimize(60m, 95m, 5m);

		_baseCandleType = Param(nameof(BaseCandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Base Timeframe", "Primary execution timeframe", "General");

		_midCandleType = Param(nameof(MidCandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Mid Timeframe", "Secondary confirmation timeframe", "General");

		_highCandleType = Param(nameof(HighCandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("High Timeframe", "Higher confirmation timeframe", "General");
	}

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

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

		_stochM1 = null;
		_stochM5 = null;
		_stochM15 = null;
		_atrValue = null;
		_longStopPrice = null;
		_longTakeProfit = null;
		_shortStopPrice = null;
		_shortTakeProfit = null;
		_bestBid = null;
		_bestAsk = null;
	}

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

		var baseStochastic = CreateStochastic();
		var midStochastic = CreateStochastic();
		var highStochastic = CreateStochastic();
		var atr = new AverageTrueRange { Length = AtrPeriod };

		var baseSubscription = SubscribeCandles(BaseCandleType);
		baseSubscription.BindEx(baseStochastic, ProcessBaseCandle).Start();

		SubscribeCandles(MidCandleType)
			.BindEx(midStochastic, ProcessMidCandle)
			.Start();

		SubscribeCandles(HighCandleType)
			.BindEx(highStochastic, atr, ProcessHighCandle)
			.Start();

		// Level1 removed for backtest compatibility
	}

	private StochasticOscillator CreateStochastic()
	{
		return new StochasticOscillator
		{
			K = { Length = StochasticLength },
			D = { Length = StochasticSignalLength }
		};
	}

	private void ProcessLevel1(Level1ChangeMessage message)
	{
		if (message.Changes.TryGetValue(Level1Fields.BestBidPrice, out var bidValue))
			_bestBid = (decimal)bidValue;

		if (message.Changes.TryGetValue(Level1Fields.BestAskPrice, out var askValue))
			_bestAsk = (decimal)askValue;
	}

	private void ProcessMidCandle(ICandleMessage candle, IIndicatorValue stochValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!stochValue.IsFinal)
			return;

		var stochastic = (StochasticOscillatorValue)stochValue;

		if (stochastic.K is decimal kValue)
			_stochM5 = kValue;
	}

	private void ProcessHighCandle(ICandleMessage candle, IIndicatorValue stochValue, IIndicatorValue atrValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!stochValue.IsFinal || !atrValue.IsFinal)
			return;

		var stochastic = (StochasticOscillatorValue)stochValue;

		if (stochastic.K is decimal kValue)
			_stochM15 = kValue;

		_atrValue = atrValue.ToDecimal();
	}

	private void ProcessBaseCandle(ICandleMessage candle, IIndicatorValue stochValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!stochValue.IsFinal)
			return;

		var stochastic = (StochasticOscillatorValue)stochValue;
		if (stochastic.K is not decimal kValue)
			return;

		_stochM1 = kValue;

		ManageOpenPosition(candle);
		TryEnterPosition(candle);
	}

	private void ManageOpenPosition(ICandleMessage candle)
	{
		if (Position == 0)
		{
			_longStopPrice = null;
			_longTakeProfit = null;
			_shortStopPrice = null;
			_shortTakeProfit = null;
			return;
		}

		if (Position > 0)
		{
			if (_longStopPrice is decimal stop && candle.LowPrice <= stop)
			{
				SellMarket(Position);
				_longStopPrice = null;
				_longTakeProfit = null;
				return;
			}

			if (_longTakeProfit is decimal target && candle.HighPrice >= target)
			{
				SellMarket(Position);
				_longStopPrice = null;
				_longTakeProfit = null;
			}
		}
		else if (Position < 0)
		{
			var shortVolume = Math.Abs(Position);
			if (_shortStopPrice is decimal stop && candle.HighPrice >= stop)
			{
				BuyMarket(shortVolume);
				_shortStopPrice = null;
				_shortTakeProfit = null;
				return;
			}

			if (_shortTakeProfit is decimal target && candle.LowPrice <= target)
			{
				BuyMarket(shortVolume);
				_shortStopPrice = null;
				_shortTakeProfit = null;
			}
		}
	}

	private void TryEnterPosition(ICandleMessage candle)
	{
		if (!HasSufficientMargin())
			return;

		if (Position != 0)
			return;

		if (HasActiveOrders())
			return;

		if (_stochM1 is not decimal stochFast ||
			_stochM5 is not decimal stochMid ||
			_stochM15 is not decimal stochSlow ||
			_atrValue is not decimal atr)
		{
			return;
		}

		var oversold = OversoldLevel;
		var overbought = OverboughtLevel;

		if (stochFast < oversold && stochMid < oversold && stochSlow < oversold)
		{
			EnterLong(candle.ClosePrice, atr);
		}
		else if (stochFast > overbought && stochMid > overbought && stochSlow > overbought)
		{
			EnterShort(candle.ClosePrice, atr);
		}
	}

	private bool HasSufficientMargin()
	{
		var currentValue = Portfolio?.CurrentValue ?? 0m;
		return currentValue >= MinMargin;
	}

	private bool IsSpreadAcceptable()
	{
		if (_bestBid is not decimal bid || _bestAsk is not decimal ask)
			return false;

		var spread = ask - bid;
		if (spread <= 0m)
			return true;

		var limit = spread > MaxSpreadStandard ? MaxSpreadCent : MaxSpreadStandard;
		return spread <= limit;
	}

	private bool HasActiveOrders()
	{
		foreach (var order in Orders)
		{
			if (!order.State.IsFinal())
				return true;
		}

		return false;
	}

	private void EnterLong(decimal price, decimal atr)
	{
		var volume = OrderVolume;
		if (volume <= 0m)
			return;

		BuyMarket(volume);

		_longStopPrice = price - atr * StopLossMultiplier;
		_longTakeProfit = price + atr * TakeProfitMultiplier;
		_shortStopPrice = null;
		_shortTakeProfit = null;
	}

	private void EnterShort(decimal price, decimal atr)
	{
		var volume = OrderVolume;
		if (volume <= 0m)
			return;

		SellMarket(volume);

		_shortStopPrice = price + atr * StopLossMultiplier;
		_shortTakeProfit = price - atr * TakeProfitMultiplier;
		_longStopPrice = null;
		_longTakeProfit = null;
	}
}