This strategy ports the MetaTrader expert Vortex Indicator System.mq4 into the StockSharp high level API. The original idea wa
s published in Technical Analysis of Stocks & Commodities (January 2010) and relies on the Vortex indicator crossover to arm bre
akout orders at the high/low of the crossover candle. The StockSharp version keeps the same decision flow: a crossover closes the
opposite position, arms a breakout trigger at the crossover bar extreme, and the next candle that breaks that level executes the
market order.
How it works
A single candle subscription is opened according to CandleType. The resulting stream is bound to one VortexIndicator insta
nce using Bind, so the strategy always receives synchronized VI+ and VI- values for the finished candles.
When the indicator finishes warming up, the algorithm tracks the previous VI values to detect the same crossover conditions u
sed in the MQL expert: VI+ crossing above VI- or vice versa between the last two closed candles.
Setup phase – as soon as a bullish crossover is detected, any open short position is closed immediately and the high of th
e crossover candle becomes the pending long trigger. The opposite crossover closes an existing long position and stores the low
of that bar as the short trigger.
Trigger phase – on each subsequent finished candle the strategy checks whether the recorded trigger price was touched (Hi ghPrice ≥ long trigger or LowPrice ≤ short trigger). If so, it submits a market order sized to both flatten the remaining oppo
site exposure (if the previous order was not completed yet) and open a new position with TradeVolume.
Once an order fires, the corresponding trigger is cleared. If no breakout happens the setup stays active until a new crossove
r overrides it.
Exits rely exclusively on the crossover logic: the opposite signal immediately flattens the current position and arms a new b
reakout trigger, mirroring the MetaTrader implementation.
Signals
Bullish setup – occurs when VI+ was below or equal to VI- on the previous closed candle and rises above it on the most r
ecent one. The long trigger is set to that candle’s high.
Bullish execution – the next candle whose high reaches the trigger sends a market buy order using TradeVolume (plus any vo
lume required to close an outstanding short position).
Bearish setup – occurs when VI- was below or equal to VI+ on the previous closed candle and rises above it on the most r
ecent one. The short trigger is set to that candle’s low.
Bearish execution – the next candle whose low touches the trigger sends a market sell order using TradeVolume (plus the vo
lume necessary to flatten an open long position).
Parameters
Parameter
Default
Description
VortexLength
14
Period applied to the Vortex indicator.
CandleType
1 hour
Timeframe used for candles and indicator updates.
TradeVolume
1
Market order size used for new entries.
Implementation notes
The strategy only reacts to finished candles to comply with the conversion guidelines. Intrabar breakouts are recognised a
s soon as a candle closes with a high/low beyond the stored trigger.
Pending triggers are cleared on OnStopped so the instance can be restarted cleanly without leftover state.
When executing a breakout order the algorithm increases the volume if it still holds an opposite position, achieving the same e
ffect as the MetaTrader expert, which closed the active order before opening the new one.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Vortex Indicator Breakout: Dual EMA crossover breakout with ATR stops.
/// </summary>
public class VortexIndicatorBreakoutStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public VortexIndicatorBreakoutStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 14)
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 28)
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastEmaLength
{
get => _fastEmaLength.Value;
set => _fastEmaLength.Value = value;
}
public int SlowEmaLength
{
get => _slowEmaLength.Value;
set => _slowEmaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
{
_prevFast = fastVal;
_prevSlow = slowVal;
return;
}
var close = candle.ClosePrice;
if (Position > 0)
{
if (fastVal < slowVal && _prevFast >= _prevSlow)
{
SellMarket();
_entryPrice = 0;
}
else if (close <= _entryPrice - atrVal * 2m)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow)
{
BuyMarket();
_entryPrice = 0;
}
else if (close >= _entryPrice + atrVal * 2m)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow)
{
_entryPrice = close;
BuyMarket();
}
else if (fastVal < slowVal && _prevFast >= _prevSlow)
{
_entryPrice = close;
SellMarket();
}
}
_prevFast = fastVal;
_prevSlow = slowVal;
}
}
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 ExponentialMovingAverage, AverageTrueRange
class vortex_indicator_breakout_strategy(Strategy):
def __init__(self):
super(vortex_indicator_breakout_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_ema_length = self.Param("FastEmaLength", 14) \
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 28) \
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastEmaLength(self):
return self._fast_ema_length.Value
@property
def SlowEmaLength(self):
return self._slow_ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(vortex_indicator_breakout_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastEmaLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowEmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if fv < sv and self._prev_fast >= self._prev_slow:
self.SellMarket()
self._entry_price = 0.0
elif close <= self._entry_price - av * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if fv > sv and self._prev_fast <= self._prev_slow:
self.BuyMarket()
self._entry_price = 0.0
elif close >= self._entry_price + av * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if fv > sv and self._prev_fast <= self._prev_slow:
self._entry_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(vortex_indicator_breakout_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def CreateClone(self):
return vortex_indicator_breakout_strategy()