Source: Converted from the MetaTrader 5 expert advisor "Vortex Indicator System" (MQL ID 19137).
Concept: Uses the Vortex indicator to detect bullish or bearish crossovers and then arms breakout triggers on the high/low of the crossover candle.
Execution Style: Breakout following; trades are initiated only after price confirms the crossover by exceeding the trigger level.
Market Regime: Works on any instrument and timeframe that supports the Vortex indicator and candle data; no broker-specific features are required.
Order Types: Market orders via BuyMarket and SellMarket. The strategy automatically closes opposite positions before queuing a new trigger.
Trading Logic
Subscribe to the configured candle type and calculate the Vortex indicator with the specified length.
Detect a bullish crossover when VI+ moves above VI- after being below it on the previous candle:
Close any existing short position using ClosePosition().
Store the high of the crossover candle as the long trigger price.
Cancel any pending short trigger.
Detect a bearish crossover when VI- moves above VI+ after being below it on the previous candle:
Close any existing long position.
Store the low of the crossover candle as the short trigger price.
Cancel any pending long trigger.
While a trigger is active, monitor subsequent candles:
If the high price breaks the stored long trigger and the current position is flat or short, send a market buy sized to reverse any short exposure.
If the low price breaks the stored short trigger and the current position is flat or long, send a market sell sized to reverse any long exposure.
Each executed trade clears its corresponding trigger. Opposite triggers are mutually exclusive.
Parameters
Parameter
Default
Description
Length
14
Period of the Vortex indicator. Matches the original MQL input VI_Length.
CandleType
60-minute timeframe
Candle type used for indicator calculation and trigger evaluation. Can be adjusted to any timeframe supported by the connected data source.
Volume
Taken from the base Strategy property
Trade volume used for market orders. Configure it before starting the strategy if a value different from 1 contract/lot is required.
How parameters affect behaviour
Increasing Length smooths the Vortex lines, reducing the number of crossovers but improving their reliability.
Decreasing Length makes the system more reactive, generating more triggers and potential trades.
The CandleType should be aligned with the data granularity in the original MQL setup (typically the chart timeframe). Shorter candles provide faster signals, whereas longer candles focus on broader trends.
Risk Management Notes
The original expert advisor does not define stop loss or take profit levels. This conversion keeps that behaviour; risk management must be handled externally or by extending the strategy.
Position reversal is immediate: when an opposite signal occurs, the strategy issues ClosePosition() and waits for a breakout beyond the trigger before entering in the new direction.
Only one trigger (long or short) can be active at a time. Triggers are cleared if price breaks them or when an opposite crossover occurs.
Usage Instructions
Add the strategy to your StockSharp project and ensure the StockSharp.Algo.Indicators package is available.
Configure the desired security and connector in the host application.
Set the CandleType parameter to the timeframe you want to trade. It should correspond to an available candle subscription for the selected instrument.
Optionally adjust Length and Volume before starting the strategy or through optimization.
Start the strategy. Orders will be generated once the indicator is formed and real-time data is available.
Implementation Highlights
Uses the high-level SubscribeCandles API with indicator binding (Bind) for clean event-driven processing.
Stores the previous Vortex values to detect crossovers exactly as the MQL implementation does (VI+ and VI- comparisons across two consecutive candles).
Entry triggers are implemented as nullable decimal fields to mimic the original "arm and break" mechanism.
English inline comments in the C# file describe each decision step and help maintain the code.
Possible Extensions
Add stop loss and take profit rules (e.g., ATR-based exits) if tighter risk control is required.
Introduce a cooldown period or maximum holding time to avoid prolonged flat periods when triggers do not execute.
Combine with a volatility filter to trade only when price ranges are wide enough to justify breakout attempts.
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()