Ver no GitHub

Estratégia do Sistema Oscilador Vortex

Visão geral

O Sistema Oscilador Vortex é uma portagem direta do expert advisor de MetaTrader 5 que depende do Oscilador Vortex para capturar mudanças bruscas entre o movimento direcional positivo e negativo. O oscilador é construído como a diferença entre a linha positiva do Vortex (VI+) e a linha negativa do Vortex (VI-) calculada na série de velas selecionada. Leituras profundamente negativas indicam que VI- domina VI+, enquanto valores fortemente positivos mostram a liderança de VI+. A estratégia interpreta esses extremos como zonas de inflexão potencial e reage com entradas de estilo reversão à média apoiadas por saídas impulsionadas pelo oscilador.

Como a estratégia funciona

  1. As velas são construídas usando o período configurado e alimentadas ao VortexIndicator integrado.
  2. Uma vez que o indicador é formado, o valor do oscilador é derivado como VI+ - VI- em cada vela concluída.
  3. O oscilador é comparado com limiares definidos pelo usuário:
    • Quando cai abaixo do limiar de compra, uma configuração longa é detectada.
    • Quando sobe acima do limiar de venda, uma configuração curta é detectada.
  4. Filtros opcionais podem restringir sinais longos à zona entre o limiar de compra e um nível de stop-loss dedicado (e vice-versa para sinais curtos).
  5. Sempre que um novo setup aparece, a estratégia fecha qualquer posição oposta e abre uma operação na direção do sinal com o volume configurado.
  6. As posições abertas são monitoradas continuamente. Se o oscilador atingir os limites de stop-loss ou take-profit configurados, a posição é fechada imediatamente.

Esta sequência reproduz a lógica original do MetaTrader: as operações são avaliadas apenas em barras concluídas, ambas as direções são mutuamente exclusivas, e as regras protetoras baseadas no oscilador governam as saídas.

Critérios de entrada

  • Entrada comprada
    • Acionada quando o oscilador é menor ou igual ao limiar de compra.
    • Se a opção de stop-loss comprado estiver habilitada, o oscilador também deve permanecer acima do nível de stop-loss comprado.
    • Qualquer posição vendida ativa é fechada antes de abrir a operação comprada.
  • Entrada vendida
    • Acionada quando o oscilador é maior ou igual ao limiar de venda.
    • Se a opção de stop-loss vendido estiver habilitada, o oscilador também deve permanecer abaixo do nível de stop-loss vendido.
    • Qualquer posição comprada ativa é fechada antes de abrir a operação vendida.
  • Se o valor do oscilador estiver entre os limiares de compra e venda, todos os setups são cancelados e nenhuma alteração de posição ocorre.

Critérios de saída

  • Posições compradas
    • Fechar imediatamente quando o oscilador cruzar abaixo ou igualar o nível de stop-loss comprado (se habilitado).
    • Fechar imediatamente quando o oscilador subir até ou acima do nível de take-profit comprado (se habilitado).
  • Posições vendidas
    • Fechar imediatamente quando o oscilador cruzar acima ou igualar o nível de stop-loss vendido (se habilitado).
    • Fechar imediatamente quando o oscilador cair até ou abaixo do nível de take-profit vendido (se habilitado).

As verificações de saída são realizadas após cada fechamento de vela, garantindo uma recriação fiel do loop de monitoramento do MT5.

Parâmetros

  • Vortex Length – período de retrocesso para o indicador Vortex (padrão 14).
  • Candle Type – período usado para construir as velas fornecidas ao indicador.
  • Use Buy Stop Loss – habilita o filtro de stop-loss baseado no oscilador e saída para operações compradas.
  • Use Buy Take Profit – habilita a saída de take-profit baseada no oscilador para operações compradas.
  • Use Sell Stop Loss – habilita o filtro de stop-loss baseado no oscilador e saída para operações vendidas.
  • Use Sell Take Profit – habilita a saída de take-profit baseada no oscilador para operações vendidas.
  • Buy Threshold – valor do oscilador que qualifica uma entrada comprada (padrão -0.75).
  • Buy Stop Loss Level – valor do oscilador que fecha posições compradas quando a opção de stop-loss comprado está ativa (padrão -1.00).
  • Buy Take Profit Level – valor do oscilador que fecha posições compradas quando a opção de take-profit comprado está ativa (padrão 0.00).
  • Sell Threshold – valor do oscilador que qualifica uma entrada vendida (padrão 0.75).
  • Sell Stop Loss Level – valor do oscilador que fecha posições vendidas quando a opção de stop-loss vendido está ativa (padrão 1.00).
  • Sell Take Profit Level – valor do oscilador que fecha posições vendidas quando a opção de take-profit vendido está ativa (padrão 0.00).
  • Volume – tamanho da operação usado para novas posições (padrão 0.1, correspondendo ao expert advisor original).

Notas de implementação

  • O processamento ocorre estritamente em velas concluídas para evitar duplicação de sinais dentro da mesma barra.
  • Os limiares do oscilador podem ser otimizados graças aos intervalos fornecidos nos metadados de parâmetros.
  • A estratégia inverte automaticamente as posições enviando uma ordem a mercado grande o suficiente para fechar o lado oposto e estabelecer a nova exposição.
  • As funcionalidades de stop-loss e take-profit funcionam de forma independente; habilitar uma não requer a outra.
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>
/// Vortex oscillator trading system ported from the MetaTrader implementation.
/// Opens long positions when the oscillator drops below a configured level and
/// shorts when the oscillator rises above the upper threshold.
/// Optional stop-loss and take-profit rules monitor the oscillator value to exit positions.
/// </summary>
public class VortexOscillatorSystemStrategy : Strategy
{
	private readonly StrategyParam<int> _length;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<bool> _useBuyStopLoss;
	private readonly StrategyParam<bool> _useBuyTakeProfit;
	private readonly StrategyParam<bool> _useSellStopLoss;
	private readonly StrategyParam<bool> _useSellTakeProfit;
	private readonly StrategyParam<decimal> _buyThreshold;
	private readonly StrategyParam<decimal> _buyStopLossLevel;
	private readonly StrategyParam<decimal> _buyTakeProfitLevel;
	private readonly StrategyParam<decimal> _sellThreshold;
	private readonly StrategyParam<decimal> _sellStopLossLevel;
	private readonly StrategyParam<decimal> _sellTakeProfitLevel;

	private VortexIndicator _vortexIndicator = null!;

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public VortexOscillatorSystemStrategy()
	{
		_length = Param(nameof(Length), 14)
			.SetGreaterThanZero()
			.SetDisplay("Vortex Length", "Period used for the Vortex indicator.", "General")
			
			.SetOptimize(7, 28, 7);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used to build candles for calculations.", "General");

		_useBuyStopLoss = Param(nameof(UseBuyStopLoss), false)
			.SetDisplay("Use Buy Stop Loss", "Enable oscillator-based stop loss for long positions.", "Risk Management");

		_useBuyTakeProfit = Param(nameof(UseBuyTakeProfit), false)
			.SetDisplay("Use Buy Take Profit", "Enable oscillator-based take profit for long positions.", "Risk Management");

		_useSellStopLoss = Param(nameof(UseSellStopLoss), false)
			.SetDisplay("Use Sell Stop Loss", "Enable oscillator-based stop loss for short positions.", "Risk Management");

		_useSellTakeProfit = Param(nameof(UseSellTakeProfit), false)
			.SetDisplay("Use Sell Take Profit", "Enable oscillator-based take profit for short positions.", "Risk Management");

		_buyThreshold = Param(nameof(BuyThreshold), -0.1m)
			.SetDisplay("Buy Threshold", "Oscillator value that triggers a long setup.", "Signals")
			
			.SetOptimize(-1.5m, -0.25m, 0.25m);

		_buyStopLossLevel = Param(nameof(BuyStopLossLevel), -1m)
			.SetDisplay("Buy Stop Loss Level", "Oscillator value that closes long trades when stop loss is enabled.", "Signals");

		_buyTakeProfitLevel = Param(nameof(BuyTakeProfitLevel), 0m)
			.SetDisplay("Buy Take Profit Level", "Oscillator value that closes long trades when take profit is enabled.", "Signals");

		_sellThreshold = Param(nameof(SellThreshold), 0.1m)
			.SetDisplay("Sell Threshold", "Oscillator value that triggers a short setup.", "Signals")
			
			.SetOptimize(0.25m, 1.5m, 0.25m);

		_sellStopLossLevel = Param(nameof(SellStopLossLevel), 1m)
			.SetDisplay("Sell Stop Loss Level", "Oscillator value that closes short trades when stop loss is enabled.", "Signals");

		_sellTakeProfitLevel = Param(nameof(SellTakeProfitLevel), 0m)
			.SetDisplay("Sell Take Profit Level", "Oscillator value that closes short trades when take profit is enabled.", "Signals");

		Volume = 0.1m;
	}

	/// <summary>
	/// Vortex indicator lookback length.
	/// </summary>
	public int Length
	{
		get => _length.Value;
		set => _length.Value = value;
	}

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

	/// <summary>
	/// Enable oscillator-based stop loss for long positions.
	/// </summary>
	public bool UseBuyStopLoss
	{
		get => _useBuyStopLoss.Value;
		set => _useBuyStopLoss.Value = value;
	}

	/// <summary>
	/// Enable oscillator-based take profit for long positions.
	/// </summary>
	public bool UseBuyTakeProfit
	{
		get => _useBuyTakeProfit.Value;
		set => _useBuyTakeProfit.Value = value;
	}

	/// <summary>
	/// Enable oscillator-based stop loss for short positions.
	/// </summary>
	public bool UseSellStopLoss
	{
		get => _useSellStopLoss.Value;
		set => _useSellStopLoss.Value = value;
	}

	/// <summary>
	/// Enable oscillator-based take profit for short positions.
	/// </summary>
	public bool UseSellTakeProfit
	{
		get => _useSellTakeProfit.Value;
		set => _useSellTakeProfit.Value = value;
	}

	/// <summary>
	/// Oscillator level used to open long trades.
	/// </summary>
	public decimal BuyThreshold
	{
		get => _buyThreshold.Value;
		set => _buyThreshold.Value = value;
	}

	/// <summary>
	/// Oscillator level that closes long trades when stop loss is enabled.
	/// </summary>
	public decimal BuyStopLossLevel
	{
		get => _buyStopLossLevel.Value;
		set => _buyStopLossLevel.Value = value;
	}

	/// <summary>
	/// Oscillator level that closes long trades when take profit is enabled.
	/// </summary>
	public decimal BuyTakeProfitLevel
	{
		get => _buyTakeProfitLevel.Value;
		set => _buyTakeProfitLevel.Value = value;
	}

	/// <summary>
	/// Oscillator level used to open short trades.
	/// </summary>
	public decimal SellThreshold
	{
		get => _sellThreshold.Value;
		set => _sellThreshold.Value = value;
	}

	/// <summary>
	/// Oscillator level that closes short trades when stop loss is enabled.
	/// </summary>
	public decimal SellStopLossLevel
	{
		get => _sellStopLossLevel.Value;
		set => _sellStopLossLevel.Value = value;
	}

	/// <summary>
	/// Oscillator level that closes short trades when take profit is enabled.
	/// </summary>
	public decimal SellTakeProfitLevel
	{
		get => _sellTakeProfitLevel.Value;
		set => _sellTakeProfitLevel.Value = value;
	}

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

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

		_vortexIndicator = new VortexIndicator
		{
			Length = Length,
		};

		var subscription = SubscribeCandles(CandleType);

		subscription
			.BindEx(_vortexIndicator, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue vortexValue)
	{
		// Process only finished candles to mirror bar-close logic from the original script.
		if (candle.State != CandleStates.Finished)
			return;

		if (!_vortexIndicator.IsFormed)
			return;

		var vortexTyped = (VortexIndicatorValue)vortexValue;
		var viPlus = vortexTyped.PlusVi ?? 0m;
		var viMinus = vortexTyped.MinusVi ?? 0m;

		// Vortex oscillator equals the difference between VI+ and VI- lines.
		var oscillator = viPlus - viMinus;

		var longSetupExists = false;
		var shortSetupExists = false;

		// Long setups are considered when the oscillator falls below the buy threshold.
		if (UseBuyStopLoss)
		{
			if (oscillator <= BuyThreshold && oscillator > BuyStopLossLevel)
			{
				longSetupExists = true;
				shortSetupExists = false;
			}
		}
		else if (oscillator <= BuyThreshold)
		{
			longSetupExists = true;
			shortSetupExists = false;
		}

		// Short setups require the oscillator to rise above the sell threshold.
		if (UseSellStopLoss)
		{
			if (oscillator >= SellThreshold && oscillator < SellStopLossLevel)
			{
				shortSetupExists = true;
				longSetupExists = false;
			}
		}
		else if (oscillator >= SellThreshold)
		{
			shortSetupExists = true;
			longSetupExists = false;
		}

		// Neutral zone cancels both long and short intentions.
		if (oscillator >= BuyThreshold && oscillator <= SellThreshold)
		{
			longSetupExists = false;
			shortSetupExists = false;
		}

		var currentPosition = Position;

		if (longSetupExists && currentPosition <= 0)
		{
			// Close existing shorts and open a long position when a valid long setup appears.
			BuyMarket();
		}
		else if (shortSetupExists && currentPosition >= 0)
		{
			// Close existing longs and open a short position when a valid short setup appears.
			SellMarket();
		}

		currentPosition = Position;

		if (currentPosition > 0)
		{
			// Manage long positions with oscillator-based stops and targets.
			if (UseBuyStopLoss && oscillator <= BuyStopLossLevel)
			{
				SellMarket();
				return;
			}

			if (UseBuyTakeProfit && oscillator >= BuyTakeProfitLevel)
			{
				SellMarket();
				return;
			}
		}
		else if (currentPosition < 0)
		{
			// Manage short positions with oscillator-based stops and targets.
			if (UseSellStopLoss && oscillator >= SellStopLossLevel)
			{
				BuyMarket();
				return;
			}

			if (UseSellTakeProfit && oscillator <= SellTakeProfitLevel)
			{
				BuyMarket();
			}
		}
	}
}