Стратегия переносит советник MetaTrader Vortex Indicator System.mq4 на высокоуровневый API StockSharp. Исходная идея была опу
бликована в журнале Technical Analysis of Stocks & Commodities (январь 2010 года) и строится на пересечениях индикатора Vortex.
После пересечения экстремум бара используется как точка пробоя: при достижении этого уровня следующая свеча запускает рыночную
сделку. Перенос сохраняет тот же алгоритм – противоположная позиция закрывается при сигнале, уровень пробоя записывается, а след
ующая свеча, пробивающая его, активирует заявку.
Как работает стратегия
Открывается подписка на свечи типа CandleType. Поток данных привязывается к одному экземпляру VortexIndicator через метод
Bind, поэтому стратегия синхронно получает значения VI+ и VI- для завершённых свечей.
После формирования индикатора стратегия хранит значения VI предыдущей свечи, чтобы обнаруживать те же условия пересечения, чт
о и в MQL-версии: VI+ пересекает VI- сверху или снизу между двумя последними закрытыми барами.
Этап подготовки – при бычьем пересечении любая открытая короткая позиция закрывается, а максимум бара пересечения становит
ся триггером для лонга. Медвежье пересечение закрывает лонг и сохраняет минимум бара как триггер для шорта.
Этап активации – каждая следующая завершённая свеча проверяет, был ли достигнут уровень триггера (HighPrice ≥ уровень дл
я лонга или LowPrice ≤ уровень для шорта). При выполнении условия отправляется рыночная заявка объёмом TradeVolume, увеличенн
ым на остаточный противоположный объём (если предыдущее закрытие ещё не выполнено).
После исполнения заявки соответствующий триггер очищается. Если пробой не происходит, подготовка остаётся активной до следующ
его пересечения, которое перезапишет уровень.
Выход из позиции осуществляется только по сигналу противоположного пересечения: позиция закрывается немедленно, записывается н
овый уровень пробоя, полностью повторяя логику MetaTrader.
Сигналы
Бычья подготовка – когда на предыдущей свече VI+ ≤ VI-, а на текущей закрытой свече VI+ > VI-. Триггером становится
максимум этой свечи.
Бычья активация – первая свеча, чей максимум достигает уровня, отправляет рыночную покупку объёмом TradeVolume (плюс объё
м, необходимый для закрытия оставшегося шорта).
Медвежья подготовка – когда ранее VI- ≤ VI+, а на текущей закрытой свече VI- > VI+. Триггером становится минимум эт
ой свечи.
Медвежья активация – первая свеча, чей минимум касается уровня, отправляет рыночную продажу объёмом TradeVolume (плюс объё
м, необходимый для закрытия лонга).
Параметры
Параметр
Значение по умолчанию
Описание
VortexLength
14
Период индикатора Vortex.
CandleType
1 час
Таймфрейм свечей и обновления индикатора.
TradeVolume
1
Объём рыночной заявки при открытии позиции.
Особенности реализации
Стратегия реагирует только на закрытые свечи согласно требованиям конвертации. Внутридневные пробои фиксируются, когда све
ча закрывается с максимумом/минимумом за пределами уровня.
В методе OnStopped очищаются все ожидающие триггеры, чтобы при повторном запуске не оставалось старого состояния.
При исполнении пробойной сделки объём увеличивается на величину текущей противоположной позиции, что соответствует поведению M
etaTrader, где активная позиция закрывалась перед открытием новой.
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()