Ver no GitHub

Altarius RSI Stochastic Estratégia Dupla

Visão geral

Altarius RSI Stochastic Dual Strategy é uma conversão do MetaTrader consultor especialista AltariusRSIxampnSTOH. A lógica combina dois osciladores estocásticos com um filtro RSI de curto período. O estocástico lento identifica a direção da tendência e as zonas de sobrecompra/sobrevenda, enquanto o estocástico rápido mede a força do impulso. As saídas dependem de RSI e da linha de sinal estocástica lenta para rastrear negociações vencedoras e reduzir perdas. Recursos adicionais de gerenciamento de dinheiro refletem a lógica original MQL, reduzindo o tamanho da posição após perdas e aplicando um limite de levantamento de capital.

Lógica de negociação

  1. Fonte de dados – A estratégia funciona em velas configuráveis (barras padrão de 15 minutos). Todos os cálculos usam dados de fechamento de velas.
  2. Condições de entrada
    • Configuração longa: A linha principal do estocástico lento (15,8,8) está acima de sua linha de sinal, mas ainda abaixo de BuyStochasticLimit (50 por padrão). O estocástico rápido (10,3,3) mostra o impulso com uma diferença absoluta entre as linhas principal e de sinal acima de StochasticDifferenceThreshold (5 por padrão).
    • Configuração curta: A linha principal do Slow Stochastic está abaixo de sua linha de sinal, mas permanece acima de SellStochasticLimit (55 por padrão). O estocástico rápido deve novamente mostrar uma diferença maior que o limite do momento.
  3. Exit Conditions
    • Saída longa: acionada quando o RSI (período 4) excede ExitRsiHigh (60) e a linha de sinal estocástico lento cai abaixo de seu valor anterior enquanto permanece acima de ExitStochasticHigh (70).
    • Saída curta: Acionada quando RSI cai abaixo de ExitRsiLow (40) e a linha de sinal estocástica lenta sobe acima de seu valor anterior enquanto permanece abaixo de ExitStochasticLow (30).
    • Saída de risco: Se o PnL flutuante cair abaixo do rebaixamento de patrimônio permitido (MaximumRiskPercent), todas as posições serão achatadas imediatamente.
  4. Dimensionamento de posição – Começa com BaseVolume e reduz o tamanho efetivo após perdas consecutivas em negociações via DecreaseFactor. As restrições de volume do corretor são respeitadas usando a etapa e os limites do volume de segurança.

Parâmetros

Parâmetro Descrição
BaseVolume Tamanho base do pedido antes dos ajustes de gerenciamento de risco.
MaximumRiskPercent Porcentagem do patrimônio da conta que pode ser perdida antes que a estratégia feche posições à força.
DecreaseFactor Divisor que controla a rapidez com que o tamanho da posição se contrai após perdas consecutivas.
RsiPeriod Comprimento RSI usado para decisões de saída.
SlowStochasticPeriod, SlowStochasticK, SlowStochasticD Configuração do oscilador estocástico lento que orienta a direção da tendência.
FastStochasticPeriod, FastStochasticK, FastStochasticD Configuração do oscilador estocástico rápido que mede o momento.
StochasticDifferenceThreshold Distância mínima entre as linhas principal estocástica rápida e de sinal para confirmar o impulso.
BuyStochasticLimit, SellStochasticLimit Níveis estocásticos lentos que definem a zona de negociação aceitável para novas posições.
ExitRsiHigh, ExitRsiLow RSI níveis que preparam saídas longas ou curtas.
ExitStochasticHigh, ExitStochasticLow Níveis de sinal estocásticos lentos que finalizam as saídas.
CandleType Fonte de dados Candle para cálculos de indicadores.

Notas

  • A estratégia negocia uma única posição de cada vez, espelhando o comportamento original do consultor especialista.
  • Os ajustes de volume e a proteção contra rebaixamento são calculados usando as informações atuais do portfólio disponíveis em StockSharp.
  • A visualização do gráfico desenha velas, osciladores estocásticos e marcadores comerciais quando uma área do gráfico está disponível.
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>
/// Strategy converted from AltariusRSIxampnSTOH MQL4 expert advisor.
/// Combines dual stochastic filters with RSI based exits and dynamic position sizing.
/// </summary>
public class AltariusRsiStochasticDualStrategy : Strategy
{
	private readonly StrategyParam<decimal> _baseVolume;
	private readonly StrategyParam<decimal> _maximumRiskPercent;
	private readonly StrategyParam<decimal> _decreaseFactor;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _slowStochPeriod;
	private readonly StrategyParam<int> _slowStochK;
	private readonly StrategyParam<int> _slowStochD;
	private readonly StrategyParam<int> _fastStochPeriod;
	private readonly StrategyParam<int> _fastStochK;
	private readonly StrategyParam<int> _fastStochD;
	private readonly StrategyParam<decimal> _differenceThreshold;
	private readonly StrategyParam<decimal> _buyLimit;
	private readonly StrategyParam<decimal> _sellLimit;
	private readonly StrategyParam<decimal> _exitRsiHigh;
	private readonly StrategyParam<decimal> _exitRsiLow;
	private readonly StrategyParam<decimal> _exitStochHigh;
	private readonly StrategyParam<decimal> _exitStochLow;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _previousSlowSignal;
	private bool _hasPreviousSlowSignal;
	private decimal _lastRealizedPnL;
	private int _consecutiveLosses;
	/// <summary>
	/// Base order volume before risk and loss adjustments.
	/// </summary>
	public decimal BaseVolume
	{
		get => _baseVolume.Value;
		set => _baseVolume.Value = value;
	}

	/// <summary>
	/// Maximum share of account equity allowed to be lost before forcing an exit.
	/// </summary>
	public decimal MaximumRiskPercent
	{
		get => _maximumRiskPercent.Value;
		set => _maximumRiskPercent.Value = value;
	}

	/// <summary>
	/// Factor controlling how quickly the volume shrinks after consecutive losses.
	/// </summary>
	public decimal DecreaseFactor
	{
		get => _decreaseFactor.Value;
		set => _decreaseFactor.Value = value;
	}

	/// <summary>
	/// Period for the RSI exit filter.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// Period for the slow stochastic oscillator used to open trades.
	/// </summary>
	public int SlowStochasticPeriod
	{
		get => _slowStochPeriod.Value;
		set => _slowStochPeriod.Value = value;
	}

	/// <summary>
	/// %K smoothing length for the slow stochastic.
	/// </summary>
	public int SlowStochasticK
	{
		get => _slowStochK.Value;
		set => _slowStochK.Value = value;
	}

	/// <summary>
	/// %D smoothing length for the slow stochastic.
	/// </summary>
	public int SlowStochasticD
	{
		get => _slowStochD.Value;
		set => _slowStochD.Value = value;
	}

	/// <summary>
	/// Period for the fast stochastic oscillator used as momentum filter.
	/// </summary>
	public int FastStochasticPeriod
	{
		get => _fastStochPeriod.Value;
		set => _fastStochPeriod.Value = value;
	}

	/// <summary>
	/// %K smoothing length for the fast stochastic.
	/// </summary>
	public int FastStochasticK
	{
		get => _fastStochK.Value;
		set => _fastStochK.Value = value;
	}

	/// <summary>
	/// %D smoothing length for the fast stochastic.
	/// </summary>
	public int FastStochasticD
	{
		get => _fastStochD.Value;
		set => _fastStochD.Value = value;
	}

	/// <summary>
	/// Minimum distance between fast stochastic main and signal lines to allow entries.
	/// </summary>
	public decimal StochasticDifferenceThreshold
	{
		get => _differenceThreshold.Value;
		set => _differenceThreshold.Value = value;
	}

	/// <summary>
	/// Upper bound on the slow stochastic main line when opening long trades.
	/// </summary>
	public decimal BuyStochasticLimit
	{
		get => _buyLimit.Value;
		set => _buyLimit.Value = value;
	}

	/// <summary>
	/// Lower bound on the slow stochastic main line when opening short trades.
	/// </summary>
	public decimal SellStochasticLimit
	{
		get => _sellLimit.Value;
		set => _sellLimit.Value = value;
	}

	/// <summary>
	/// RSI threshold that triggers exit for long positions.
	/// </summary>
	public decimal ExitRsiHigh
	{
		get => _exitRsiHigh.Value;
		set => _exitRsiHigh.Value = value;
	}

	/// <summary>
	/// RSI threshold that triggers exit for short positions.
	/// </summary>
	public decimal ExitRsiLow
	{
		get => _exitRsiLow.Value;
		set => _exitRsiLow.Value = value;
	}

	/// <summary>
	/// Stochastic level that confirms the exit of long positions.
	/// </summary>
	public decimal ExitStochasticHigh
	{
		get => _exitStochHigh.Value;
		set => _exitStochHigh.Value = value;
	}

	/// <summary>
	/// Stochastic level that confirms the exit of short positions.
	/// </summary>
	public decimal ExitStochasticLow
	{
		get => _exitStochLow.Value;
		set => _exitStochLow.Value = value;
	}

	/// <summary>
	/// Candle type used for indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public AltariusRsiStochasticDualStrategy()
	{
		_baseVolume = Param(nameof(BaseVolume), 1m)
		.SetNotNegative()
		.SetDisplay("Base Volume", "Initial volume before money management rules", "Trading")
		
		.SetOptimize(0.1m, 2m, 0.1m);

		_maximumRiskPercent = Param(nameof(MaximumRiskPercent), 0.1m)
		.SetNotNegative()
		.SetDisplay("Max Risk %", "Equity drawdown percentage that forces position closure", "Risk Management")
		
		.SetOptimize(0.05m, 0.3m, 0.05m);

		_decreaseFactor = Param(nameof(DecreaseFactor), 3m)
		.SetNotNegative()
		.SetDisplay("Decrease Factor", "Loss streak divider applied to volume", "Risk Management")
		
		.SetOptimize(1m, 5m, 1m);

		_rsiPeriod = Param(nameof(RsiPeriod), 4)
		.SetGreaterThanZero()
		.SetDisplay("RSI Period", "Length of RSI used for exits", "Indicators")
		
		.SetOptimize(2, 8, 1);

		_slowStochPeriod = Param(nameof(SlowStochasticPeriod), 15)
		.SetGreaterThanZero()
		.SetDisplay("Slow Stochastic Period", "Main period of slow stochastic", "Indicators")
		
		.SetOptimize(10, 25, 1);

		_slowStochK = Param(nameof(SlowStochasticK), 8)
		.SetGreaterThanZero()
		.SetDisplay("Slow Stochastic %K", "Smoothing of %K for slow stochastic", "Indicators");

		_slowStochD = Param(nameof(SlowStochasticD), 8)
		.SetGreaterThanZero()
		.SetDisplay("Slow Stochastic %D", "Smoothing of %D for slow stochastic", "Indicators");

		_fastStochPeriod = Param(nameof(FastStochasticPeriod), 10)
		.SetGreaterThanZero()
		.SetDisplay("Fast Stochastic Period", "Main period of fast stochastic", "Indicators")
		
		.SetOptimize(5, 15, 1);

		_fastStochK = Param(nameof(FastStochasticK), 3)
		.SetGreaterThanZero()
		.SetDisplay("Fast Stochastic %K", "Smoothing of %K for fast stochastic", "Indicators");

		_fastStochD = Param(nameof(FastStochasticD), 3)
		.SetGreaterThanZero()
		.SetDisplay("Fast Stochastic %D", "Smoothing of %D for fast stochastic", "Indicators");

		_differenceThreshold = Param(nameof(StochasticDifferenceThreshold), 5m)
		.SetNotNegative()
		.SetDisplay("Momentum Threshold", "Minimum difference between fast stochastic lines", "Trading")
		
		.SetOptimize(2m, 10m, 1m);

		_buyLimit = Param(nameof(BuyStochasticLimit), 50m)
		.SetDisplay("Buy Stochastic Limit", "Upper bound of slow stochastic for longs", "Trading");

		_sellLimit = Param(nameof(SellStochasticLimit), 55m)
		.SetDisplay("Sell Stochastic Limit", "Lower bound of slow stochastic for shorts", "Trading");

		_exitRsiHigh = Param(nameof(ExitRsiHigh), 60m)
		.SetDisplay("Exit RSI High", "RSI threshold to exit longs", "Exits");

		_exitRsiLow = Param(nameof(ExitRsiLow), 40m)
		.SetDisplay("Exit RSI Low", "RSI threshold to exit shorts", "Exits");

		_exitStochHigh = Param(nameof(ExitStochasticHigh), 70m)
		.SetDisplay("Exit Stochastic High", "Slow stochastic signal level confirming long exit", "Exits");

		_exitStochLow = Param(nameof(ExitStochasticLow), 30m)
		.SetDisplay("Exit Stochastic Low", "Slow stochastic signal level confirming short exit", "Exits");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
		.SetDisplay("Candle Type", "Candles used for calculations", "Market Data");
	}

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

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

		_previousSlowSignal = 0m;
		_hasPreviousSlowSignal = false;
		_lastRealizedPnL = PnLManager?.RealizedPnL ?? 0m;
		_consecutiveLosses = 0;
	}

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

		_lastRealizedPnL = PnLManager?.RealizedPnL ?? 0m;

		var rsi = new RelativeStrengthIndex
		{
			Length = RsiPeriod,
		};

		var slowStochastic = new StochasticOscillator();
		slowStochastic.K.Length = SlowStochasticK;
		slowStochastic.D.Length = SlowStochasticD;

		var fastStochastic = new StochasticOscillator();
		fastStochastic.K.Length = FastStochasticK;
		fastStochastic.D.Length = FastStochasticD;

		var subscription = SubscribeCandles(CandleType);

		subscription
		.BindEx(rsi, slowStochastic, fastStochastic, ProcessIndicators)
		.Start();

		var chartArea = CreateChartArea();
		if (chartArea != null)
		{
			DrawCandles(chartArea, subscription);
			DrawIndicator(chartArea, rsi);
			DrawIndicator(chartArea, slowStochastic);
			DrawIndicator(chartArea, fastStochastic);
			DrawOwnTrades(chartArea);
		}
	}

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

		if (!IsFormedAndOnlineAndAllowTrading())
		return;

		if (MaximumRiskPercent > 0m)
		{
			var unrealizedPnL = PnLManager?.UnrealizedPnL ?? 0m;
			if (unrealizedPnL < 0m)
			{
				var portfolio = Portfolio;
				var equity = portfolio?.CurrentValue ?? portfolio?.BeginValue ?? 0m;
				if (equity > 0m)
				{
					var allowedLoss = equity * MaximumRiskPercent;
					if (Math.Abs(unrealizedPnL) >= allowedLoss)
					{
						CloseCurrentPosition();
						return;
					}
				}
			}
		}

		if (rsiValue.IsEmpty || slowValue.IsEmpty || fastValue.IsEmpty)
			return;

		var rsi = rsiValue.ToDecimal();
		var slow = slowValue as StochasticOscillatorValue;
		var fast = fastValue as StochasticOscillatorValue;

		if (slow == null || fast == null)
			return;

		if (slow.K is not decimal slowMainValue ||
		slow.D is not decimal slowSignalValue ||
		fast.K is not decimal fastMainValue ||
		fast.D is not decimal fastSignalValue)
		{
			return;
		}

		if (!_hasPreviousSlowSignal)
		{
			_previousSlowSignal = slowSignalValue;
			_hasPreviousSlowSignal = true;
			return;
		}

		if (Position == 0m)
		{
			var momentum = Math.Abs(fastMainValue - fastSignalValue);
			if (slowMainValue > slowSignalValue && slowMainValue < BuyStochasticLimit && momentum > StochasticDifferenceThreshold)
			{
				EnterPosition(Sides.Buy);
			}
			else if (slowMainValue < slowSignalValue && slowMainValue > SellStochasticLimit && momentum > StochasticDifferenceThreshold)
			{
				EnterPosition(Sides.Sell);
			}
		}
		else if (Position > 0m)
		{
			if (rsi > ExitRsiHigh && slowSignalValue < _previousSlowSignal && slowSignalValue > ExitStochasticHigh)
			{
				ExitPosition(Sides.Buy);
			}
		}
		else if (Position < 0m)
		{
			if (rsi < ExitRsiLow && slowSignalValue > _previousSlowSignal && slowSignalValue < ExitStochasticLow)
			{
				ExitPosition(Sides.Sell);
			}
		}

		_previousSlowSignal = slowSignalValue;
	}

	private void EnterPosition(Sides side)
	{
		var volume = CalculateOrderVolume();
		if (volume <= 0m)
		return;

		if (side == Sides.Buy)
		{
			BuyMarket(volume);
		}
		else
		{
			SellMarket(volume);
		}

	}

	private void ExitPosition(Sides side)
	{
		var position = Position;
		if (position == 0m)
		return;

		if (side == Sides.Buy && position > 0m)
		{
			SellMarket(position);
		}
		else if (side == Sides.Sell && position < 0m)
		{
			BuyMarket(Math.Abs(position));
		}
	}

	private void CloseCurrentPosition()
	{
		var position = Position;
		if (position > 0m)
		{
			SellMarket(position);
		}
		else if (position < 0m)
		{
			BuyMarket(Math.Abs(position));
		}
	}

	private decimal CalculateOrderVolume()
	{
		var volume = BaseVolume;

		if (DecreaseFactor > 0m && _consecutiveLosses > 1)
		{
			var reduction = volume * _consecutiveLosses / DecreaseFactor;
			volume -= reduction;
		}

		if (volume <= 0m)
		volume = BaseVolume;

		var security = Security;
		if (security != null)
		{
			var step = security.VolumeStep ?? 0m;
			if (step <= 0m)
			step = 0.1m;

			var minVolume = security.MinVolume ?? step;
			var maxVolume = security.MaxVolume;

			var steps = decimal.Floor(volume / step);
			if (steps < 1m)
			steps = 1m;

			volume = steps * step;

			if (volume < minVolume)
			volume = minVolume;

			if (maxVolume is decimal max && max > 0m && volume > max)
			volume = max;
		}

		return volume;
	}

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);

		if (Position == 0m)
		{
			var realizedPnL = PnLManager?.RealizedPnL ?? 0m;
			var gain = realizedPnL - _lastRealizedPnL;
			_lastRealizedPnL = realizedPnL;

			if (gain > 0m)
			{
				_consecutiveLosses = 0;
			}
			else if (gain < 0m)
			{
				_consecutiveLosses++;
			}
		}
	}
}