Fuente: Convertido del asesor experto de MetaTrader 5 "Vortex Indicator System" (MQL ID 19137).
Concepto: Usa el indicador Vortex para detectar cruces alcistas o bajistas y luego arma disparadores de ruptura en el máximo/mínimo de la vela del cruce.
Estilo de ejecución: Seguimiento de ruptura; las operaciones se inician sólo después de que el precio confirma el cruce superando el nivel del disparador.
Régimen de mercado: Funciona en cualquier instrumento y marco temporal que admita el indicador Vortex y datos de velas; no se requieren características específicas del bróker.
Tipos de órdenes: Órdenes de mercado mediante BuyMarket y SellMarket. La estrategia cierra automáticamente posiciones opuestas antes de poner en cola un nuevo disparador.
Lógica de trading
Suscribirse al tipo de vela configurado y calcular el indicador Vortex con la longitud especificada.
Detectar un cruce alcista cuando VI+ se mueve por encima de VI- después de estar por debajo en la vela anterior:
Cerrar cualquier posición corta existente usando ClosePosition().
Almacenar el máximo de la vela del cruce como el precio de disparador largo.
Cancelar cualquier disparador corto pendiente.
Detectar un cruce bajista cuando VI- se mueve por encima de VI+ después de estar por debajo en la vela anterior:
Cerrar cualquier posición larga existente.
Almacenar el mínimo de la vela del cruce como el precio de disparador corto.
Cancelar cualquier disparador largo pendiente.
Mientras un disparador está activo, monitorear las velas posteriores:
Si el precio máximo rompe el disparador largo almacenado y la posición actual es plana o corta, enviar una compra de mercado dimensionada para revertir cualquier exposición corta.
Si el precio mínimo rompe el disparador corto almacenado y la posición actual es plana o larga, enviar una venta de mercado dimensionada para revertir cualquier exposición larga.
Cada operación ejecutada borra su disparador correspondiente. Los disparadores opuestos son mutuamente excluyentes.
Parámetros
Parámetro
Predeterminado
Descripción
Length
14
Período del indicador Vortex. Corresponde a la entrada MQL original VI_Length.
CandleType
Marco temporal de 60 minutos
Tipo de vela usado para el cálculo del indicador y la evaluación del disparador. Puede ajustarse a cualquier marco temporal admitido por la fuente de datos conectada.
Volume
Tomado de la propiedad base Strategy
Volumen de operación usado para las órdenes de mercado. Configúralo antes de iniciar la estrategia si se requiere un valor diferente a 1 contrato/lote.
Cómo los parámetros afectan el comportamiento
Aumentar Length suaviza las líneas Vortex, reduciendo el número de cruces pero mejorando su confiabilidad.
Disminuir Length hace el sistema más reactivo, generando más disparadores y operaciones potenciales.
El CandleType debe alinearse con la granularidad de datos en la configuración MQL original (típicamente el marco temporal del gráfico). Las velas más cortas proporcionan señales más rápidas, mientras que las velas más largas se centran en tendencias más amplias.
Notas de gestión de riesgo
El asesor experto original no define niveles de stop-loss o take-profit. Esta conversión mantiene ese comportamiento; la gestión de riesgo debe manejarse externamente o extendiendo la estrategia.
La reversión de posición es inmediata: cuando ocurre una señal opuesta, la estrategia emite ClosePosition() y espera un rompimiento más allá del disparador antes de entrar en la nueva dirección.
Solo puede estar activo un disparador (largo o corto) a la vez. Los disparadores se borran si el precio los rompe o cuando ocurre un cruce opuesto.
Instrucciones de uso
Agrega la estrategia a tu proyecto StockSharp y asegúrate de que el paquete StockSharp.Algo.Indicators esté disponible.
Configura el instrumento deseado y el conector en la aplicación anfitriona.
Establece el parámetro CandleType al marco temporal que deseas operar. Debe corresponder a una suscripción de velas disponible para el instrumento seleccionado.
Opcionalmente ajusta Length y Volume antes de iniciar la estrategia o a través de optimización.
Inicia la estrategia. Se generarán órdenes una vez que el indicador esté formado y los datos en tiempo real estén disponibles.
Aspectos destacados de implementación
Usa la API de alto nivel SubscribeCandles con vinculación de indicador (Bind) para un procesamiento limpio basado en eventos.
Almacena los valores anteriores del Vortex para detectar cruces exactamente como lo hace la implementación MQL (comparaciones VI+ y VI- entre dos velas consecutivas).
Los disparadores de entrada se implementan como campos decimal anulables para imitar el mecanismo original de "armar y romper".
Los comentarios en línea en inglés en el archivo C# describen cada paso de decisión y ayudan a mantener el código.
Posibles extensiones
Agregar reglas de stop-loss y take-profit (p. ej., salidas basadas en ATR) si se requiere un control de riesgo más estricto.
Introducir un período de enfriamiento o tiempo máximo de mantenimiento para evitar períodos planos prolongados cuando los disparadores no se ejecutan.
Combinar con un filtro de volatilidad para operar sólo cuando los rangos de precio sean suficientemente amplios para justificar intentos de ruptura.
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>
/// Breakout strategy based on the Vortex indicator crossover system.
/// Replicates the logic of the original MQL expert by arming entry triggers
/// on the candle where VI+ and VI- lines cross and executing when price breaks the trigger.
/// </summary>
public class VortexIndicatorSystemStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<DataType> _candleType;
private VortexIndicator _vortex = null!;
private decimal _previousPlus;
private decimal _previousMinus;
private bool _hasPrevious;
private decimal? _pendingBuyTrigger;
private decimal? _pendingSellTrigger;
/// <summary>
/// Length of the Vortex indicator.
/// </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>
/// Initializes parameters for the strategy.
/// </summary>
public VortexIndicatorSystemStrategy()
{
_length = Param(nameof(Length), 14)
.SetDisplay("Vortex Length", "Period for the Vortex indicator", "General")
.SetGreaterThanZero()
.SetOptimize(7, 28, 7);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for analysis", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousPlus = 0m;
_previousMinus = 0m;
_hasPrevious = false;
_pendingBuyTrigger = null;
_pendingSellTrigger = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_vortex = new VortexIndicator
{
Length = Length
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_vortex, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue vortexValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_vortex.IsFormed)
return;
if (vortexValue is not VortexIndicatorValue typed)
return;
var viPlusN = typed.PlusVi;
var viMinusN = typed.MinusVi;
if (viPlusN is not decimal viPlus || viMinusN is not decimal viMinus)
return;
if (_pendingBuyTrigger is decimal buyTrigger && candle.HighPrice > buyTrigger)
{
if (Position <= 0)
{
// Reverse existing short if present and open a new long position when price breaks the trigger.
BuyMarket();
}
_pendingBuyTrigger = null;
}
else if (_pendingSellTrigger is decimal sellTrigger && candle.LowPrice < sellTrigger)
{
if (Position >= 0)
{
// Reverse existing long if present and open a new short position when price breaks the trigger.
SellMarket();
}
_pendingSellTrigger = null;
}
if (!_hasPrevious)
{
_previousPlus = viPlus;
_previousMinus = viMinus;
_hasPrevious = true;
return;
}
var crossedUp = _previousPlus <= _previousMinus && viPlus > viMinus;
var crossedDown = _previousPlus >= _previousMinus && viPlus < viMinus;
if (crossedUp)
{
if (Position < 0)
{
// Flatten existing short positions when a bullish crossover appears.
BuyMarket();
}
_pendingBuyTrigger = candle.HighPrice;
_pendingSellTrigger = null;
}
else if (crossedDown)
{
if (Position > 0)
{
// Flatten existing long positions when a bearish crossover appears.
SellMarket();
}
_pendingSellTrigger = candle.LowPrice;
_pendingBuyTrigger = null;
}
_previousPlus = viPlus;
_previousMinus = viMinus;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import VortexIndicator
class vortex_indicator_system_strategy(Strategy):
"""Vortex indicator crossover breakout: arms triggers on VI+/VI- cross, executes on price breakout."""
def __init__(self):
super(vortex_indicator_system_strategy, self).__init__()
self._length = self.Param("Length", 14) \
.SetGreaterThanZero() \
.SetDisplay("Vortex Length", "Period for the Vortex indicator", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe used for analysis", "General")
self._previous_plus = 0.0
self._previous_minus = 0.0
self._has_previous = False
self._pending_buy_trigger = None
self._pending_sell_trigger = None
@property
def Length(self):
return int(self._length.Value)
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(vortex_indicator_system_strategy, self).OnStarted2(time)
self._previous_plus = 0.0
self._previous_minus = 0.0
self._has_previous = False
self._pending_buy_trigger = None
self._pending_sell_trigger = None
self._vortex = VortexIndicator()
self._vortex.Length = self.Length
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(self._vortex, self.process_candle).Start()
def process_candle(self, candle, vortex_value):
if candle.State != CandleStates.Finished:
return
if not self._vortex.IsFormed:
return
vi_plus_n = vortex_value.PlusVi
vi_minus_n = vortex_value.MinusVi
if vi_plus_n is None or vi_minus_n is None:
return
vi_plus = float(vi_plus_n)
vi_minus = float(vi_minus_n)
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
# Check pending triggers
if self._pending_buy_trigger is not None and h > self._pending_buy_trigger:
if self.Position <= 0:
self.BuyMarket()
self._pending_buy_trigger = None
elif self._pending_sell_trigger is not None and lo < self._pending_sell_trigger:
if self.Position >= 0:
self.SellMarket()
self._pending_sell_trigger = None
if not self._has_previous:
self._previous_plus = vi_plus
self._previous_minus = vi_minus
self._has_previous = True
return
crossed_up = self._previous_plus <= self._previous_minus and vi_plus > vi_minus
crossed_down = self._previous_plus >= self._previous_minus and vi_plus < vi_minus
if crossed_up:
if self.Position < 0:
self.BuyMarket()
self._pending_buy_trigger = h
self._pending_sell_trigger = None
elif crossed_down:
if self.Position > 0:
self.SellMarket()
self._pending_sell_trigger = lo
self._pending_buy_trigger = None
self._previous_plus = vi_plus
self._previous_minus = vi_minus
def OnReseted(self):
super(vortex_indicator_system_strategy, self).OnReseted()
self._previous_plus = 0.0
self._previous_minus = 0.0
self._has_previous = False
self._pending_buy_trigger = None
self._pending_sell_trigger = None
def CreateClone(self):
return vortex_indicator_system_strategy()