Ver en GitHub

Estrategia del Sistema Oscilador Vortex

Descripción general

El Sistema Oscilador Vortex es un puerto directo del asesor experto de MetaTrader 5 que se basa en el Oscilador Vortex para capturar cambios bruscos entre el movimiento direccional positivo y negativo. El oscilador se construye como la diferencia entre la línea positiva del Vortex (VI+) y la línea negativa del Vortex (VI-) calculada en la serie de velas seleccionada. Las lecturas profundamente negativas indican que VI- domina a VI+, mientras que los valores fuertemente positivos muestran el liderazgo de VI+. La estrategia interpreta esos extremos como zonas de inflexión potencial y reacciona con entradas de estilo reversión a la media respaldadas por salidas impulsadas por el oscilador.

Cómo funciona la estrategia

  1. Las velas se construyen usando el marco temporal configurado y se alimentan al VortexIndicator integrado.
  2. Una vez que el indicador se forma, el valor del oscilador se deriva como VI+ - VI- en cada vela terminada.
  3. El oscilador se compara con umbrales definidos por el usuario:
    • Cuando cae por debajo del umbral de compra, se detecta una configuración larga.
    • Cuando sube por encima del umbral de venta, se detecta una configuración corta.
  4. Los filtros opcionales pueden restringir las señales largas a la zona entre el umbral de compra y un nivel de stop-loss dedicado (y viceversa para las señales cortas).
  5. Cuando aparece una nueva configuración, la estrategia cierra cualquier posición opuesta y abre una operación en la dirección de la señal con el volumen configurado.
  6. Las posiciones abiertas se monitorean continuamente. Si el oscilador alcanza los límites de stop-loss o take-profit configurados, la posición se cierra inmediatamente.

Esta secuencia reproduce la lógica original de MetaTrader: las operaciones se evalúan solo en barras completadas, ambas direcciones son mutuamente excluyentes, y las reglas protectoras basadas en el oscilador gobiernan las salidas.

Criterios de entrada

  • Entrada larga
    • Se activa cuando el oscilador es menor o igual al umbral de compra.
    • Si la opción de stop-loss largo está habilitada, el oscilador también debe permanecer por encima del nivel de stop-loss largo.
    • Cualquier posición corta activa se cierra antes de abrir la operación larga.
  • Entrada corta
    • Se activa cuando el oscilador es mayor o igual al umbral de venta.
    • Si la opción de stop-loss corto está habilitada, el oscilador también debe permanecer por debajo del nivel de stop-loss corto.
    • Cualquier posición larga activa se cierra antes de abrir la operación corta.
  • Si el valor del oscilador está entre los umbrales de compra y venta, todas las configuraciones se cancelan y no se produce ningún cambio de posición.

Criterios de salida

  • Posiciones largas
    • Cierre inmediato cuando el oscilador cruce por debajo o iguale el nivel de stop-loss largo (si está habilitado).
    • Cierre inmediato cuando el oscilador suba hasta o por encima del nivel de take-profit largo (si está habilitado).
  • Posiciones cortas
    • Cierre inmediato cuando el oscilador cruce por encima o iguale el nivel de stop-loss corto (si está habilitado).
    • Cierre inmediato cuando el oscilador caiga hasta o por debajo del nivel de take-profit corto (si está habilitado).

Las verificaciones de salida se realizan después de cada cierre de vela, garantizando una recreación fiel del bucle de monitoreo de MT5.

Parámetros

  • Vortex Length – período de retroceso para el indicador Vortex (predeterminado 14).
  • Candle Type – marco temporal usado para construir las velas suministradas al indicador.
  • Use Buy Stop Loss – habilita el filtro de stop-loss basado en el oscilador y la salida para operaciones largas.
  • Use Buy Take Profit – habilita la salida de take-profit basada en el oscilador para operaciones largas.
  • Use Sell Stop Loss – habilita el filtro de stop-loss basado en el oscilador y la salida para operaciones cortas.
  • Use Sell Take Profit – habilita la salida de take-profit basada en el oscilador para operaciones cortas.
  • Buy Threshold – valor del oscilador que califica una entrada larga (predeterminado -0.75).
  • Buy Stop Loss Level – valor del oscilador que cierra posiciones largas cuando la opción de stop-loss largo está activa (predeterminado -1.00).
  • Buy Take Profit Level – valor del oscilador que cierra posiciones largas cuando la opción de take-profit largo está activa (predeterminado 0.00).
  • Sell Threshold – valor del oscilador que califica una entrada corta (predeterminado 0.75).
  • Sell Stop Loss Level – valor del oscilador que cierra posiciones cortas cuando la opción de stop-loss corto está activa (predeterminado 1.00).
  • Sell Take Profit Level – valor del oscilador que cierra posiciones cortas cuando la opción de take-profit corto está activa (predeterminado 0.00).
  • Volume – tamaño de la operación usado para nuevas posiciones (predeterminado 0.1, coincidiendo con el asesor experto original).

Notas de implementación

  • El procesamiento ocurre estrictamente en velas completadas para evitar duplicar señales dentro de la misma barra.
  • Los umbrales del oscilador pueden optimizarse gracias a los rangos proporcionados en los metadatos de parámetros.
  • La estrategia voltea automáticamente las posiciones enviando una orden de mercado lo suficientemente grande para cerrar el lado contrario y establecer la nueva exposición.
  • Las características de stop-loss y take-profit funcionan de forma independiente; habilitar una no requiere la otra.
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();
			}
		}
	}
}