Идея: Использует индикатор Vortex для поиска пересечения линий VI+ и VI-, после чего выставляет пробойные триггеры по максимуму или минимуму свечи пересечения.
Тип входа: Стратегия следует за пробоем — сделки открываются только при подтверждении пересечения ценой.
Рынки и таймфреймы: Работает на любых инструментах и таймфреймах, где доступен индикатор Vortex и свечные данные.
Типы заявок: Рыночные ордера BuyMarket и SellMarket. Перед активацией нового триггера стратегия закрывает противоположные позиции.
Логика торговли
Подписка на выбранный тип свечей и расчёт индикатора Vortex с заданной длиной.
Бычье пересечение: VI+ становится выше VI-, при этом на предыдущей свече VI+ был не выше VI-.
Закрыть текущую короткую позицию через ClosePosition().
Запомнить максимум свечи пересечения как цену активации лонга.
Сбросить возможный шорт-триггер.
Медвежье пересечение: VI- становится выше VI+, при этом на предыдущей свече VI- был не выше VI+.
Закрыть текущую длинную позицию.
Запомнить минимум свечи пересечения как цену активации шорта.
Сбросить возможный лонг-триггер.
Пока активен триггер, проверять закрывшиеся свечи:
Если максимум свечи превышает лонг-триггер и позиция плоская либо короткая, выставить BuyMarket на объём, достаточный для реверса.
Если минимум свечи пробивает шорт-триггер и позиция плоская либо длинная, выставить SellMarket на объём, достаточный для реверса.
После исполнения ордера соответствующий триггер очищается. Одновременно может существовать только один тип триггера.
Параметры
Параметр
Значение по умолчанию
Описание
Length
14
Период индикатора Vortex. Полностью соответствует входному параметру VI_Length в оригинальном советнике.
CandleType
Свечи 60 минут
Тип свечей для расчёта индикатора и проверки триггеров. Можно менять на любой доступный таймфрейм.
Volume
Значение из базового свойства Strategy.Volume
Объём рыночных заявок. Настраивается пользователем до запуска стратегии.
Как параметры влияют на поведение
Увеличение Length сглаживает линии Vortex, снижает количество пересечений, но повышает надёжность сигналов.
Уменьшение Length делает систему более чувствительной и увеличивает число сделок.
CandleType желательно выбирать в соответствии с таймфреймом, на котором тестировался оригинальный советник.
Управление рисками
В советнике отсутствуют стоп-лосс и тейк-профит. Конверсия сохраняет это поведение, поэтому управление рисками должно выполняться внешними средствами или через расширение стратегии.
При появлении противоположного сигнала стратегия сперва закрывает текущую позицию (ClosePosition()), а затем ждёт подтверждающего пробоя.
Система всегда находится либо без позиции, либо в одной сделке; усреднение или наращивание не используется.
Инструкция по применению
Добавьте стратегию в проект StockSharp и убедитесь, что доступна библиотека StockSharp.Algo.Indicators.
Настройте подключение к брокеру/источнику данных и выберите торгуемый инструмент.
Выставьте параметры CandleType, Length и Volume перед запуском (или используйте оптимизацию).
Запустите стратегию. Сигналы появятся после формирования достаточного числа свечей для индикатора и получения онлайн-данных.
Особенности реализации
Используется высокоуровневый API SubscribeCandles с привязкой индикатора через Bind, что упрощает обработку сигналов.
Переменные для предыдущих значений индикатора повторяют логику MQL-сценария и позволяют точно фиксировать моменты пересечения.
Триггеры на вход реализованы в виде nullable-полей, что делает код читаемым и исключает ложные повторные входы.
В коде добавлены поясняющие комментарии на английском языке, как требует инструкция.
Возможные доработки
Добавление стоп-лосса/тейк-профита (например, на основе ATR) для более строгого контроля риска.
Введение временного тайм-аута, по истечении которого неактивный триггер сбрасывается.
Фильтрация сигналов по волатильности или трендовым индикаторам, чтобы избегать флетовых периодов.
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()