Fonte: Convertido do consultor especializado MetaTrader 5 "Vortex Indicator System" (MQL ID 19137).
Conceito: Usa o indicador Vortex para detectar cruzamentos de alta ou baixa e então arma gatilhos de rompimento no máximo/mínimo da vela do cruzamento.
Estilo de execução: Seguimento de rompimento; as operações são iniciadas somente após o preço confirmar o cruzamento ao superar o nível do gatilho.
Regime de mercado: Funciona em qualquer instrumento e período que suporte o indicador Vortex e dados de velas; nenhuma característica específica do corretor é necessária.
Tipos de ordens: Ordens a mercado via BuyMarket e SellMarket. A estratégia fecha automaticamente posições opostas antes de enfileirar um novo gatilho.
Lógica de trading
Inscrever-se no tipo de vela configurado e calcular o indicador Vortex com o comprimento especificado.
Detectar um cruzamento de alta quando VI+ se move acima de VI- depois de estar abaixo na vela anterior:
Fechar qualquer posição vendida existente usando ClosePosition().
Armazenar o máximo da vela do cruzamento como o preço de gatilho comprado.
Cancelar qualquer gatilho vendido pendente.
Detectar um cruzamento de baixa quando VI- se move acima de VI+ depois de estar abaixo na vela anterior:
Fechar qualquer posição comprada existente.
Armazenar o mínimo da vela do cruzamento como o preço de gatilho vendido.
Cancelar qualquer gatilho comprado pendente.
Enquanto um gatilho está ativo, monitorar as velas subsequentes:
Se o preço máximo romper o gatilho comprado armazenado e a posição atual for flat ou vendida, enviar uma compra a mercado dimensionada para reverter qualquer exposição vendida.
Se o preço mínimo romper o gatilho vendido armazenado e a posição atual for flat ou comprada, enviar uma venda a mercado dimensionada para reverter qualquer exposição comprada.
Cada operação executada limpa o gatilho correspondente. Gatilhos opostos são mutuamente exclusivos.
Parâmetros
Parâmetro
Padrão
Descrição
Length
14
Período do indicador Vortex. Corresponde à entrada MQL original VI_Length.
CandleType
Período de 60 minutos
Tipo de vela usado para o cálculo do indicador e a avaliação do gatilho. Pode ser ajustado para qualquer período suportado pela fonte de dados conectada.
Volume
Retirado da propriedade base Strategy
Volume de operação usado para ordens a mercado. Configure antes de iniciar a estratégia se for necessário um valor diferente de 1 contrato/lote.
Como os parâmetros afetam o comportamento
Aumentar Length suaviza as linhas Vortex, reduzindo o número de cruzamentos, mas melhorando sua confiabilidade.
Diminuir Length torna o sistema mais reativo, gerando mais gatilhos e operações potenciais.
O CandleType deve ser alinhado com a granularidade de dados na configuração MQL original (tipicamente o período do gráfico). Velas mais curtas fornecem sinais mais rápidos, enquanto velas mais longas se concentram em tendências mais amplas.
Notas de gestão de risco
O consultor especializado original não define níveis de stop-loss ou take-profit. Esta conversão mantém esse comportamento; a gestão de risco deve ser tratada externamente ou estendendo a estratégia.
A reversão de posição é imediata: quando ocorre um sinal oposto, a estratégia emite ClosePosition() e aguarda um rompimento além do gatilho antes de entrar na nova direção.
Apenas um gatilho (comprado ou vendido) pode estar ativo de cada vez. Os gatilhos são limpos se o preço os romper ou quando ocorre um cruzamento oposto.
Instruções de uso
Adicione a estratégia ao seu projeto StockSharp e certifique-se de que o pacote StockSharp.Algo.Indicators esteja disponível.
Configure o instrumento desejado e o conector na aplicação hospedeira.
Defina o parâmetro CandleType para o período que deseja operar. Deve corresponder a uma assinatura de velas disponível para o instrumento selecionado.
Opcionalmente ajuste Length e Volume antes de iniciar a estratégia ou através de otimização.
Inicie a estratégia. As ordens serão geradas assim que o indicador estiver formado e os dados em tempo real estiverem disponíveis.
Destaques de implementação
Usa a API de alto nível SubscribeCandles com vinculação de indicador (Bind) para processamento limpo baseado em eventos.
Armazena os valores anteriores do Vortex para detectar cruzamentos exatamente como a implementação MQL faz (comparações de VI+ e VI- entre duas velas consecutivas).
Os gatilhos de entrada são implementados como campos decimal anuláveis para imitar o mecanismo original de "armar e romper".
Comentários em inglês na linha do arquivo C# descrevem cada passo de decisão e ajudam a manter o código.
Possíveis extensões
Adicionar regras de stop-loss e take-profit (p. ex., saídas baseadas em ATR) se um controle de risco mais rígido for necessário.
Introduzir um período de resfriamento ou tempo máximo de manutenção para evitar períodos planos prolongados quando os gatilhos não executam.
Combinar com um filtro de volatilidade para operar apenas quando os intervalos de preço forem suficientemente amplos para justificar tentativas de rompimento.
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()