Открыть на GitHub

Стратегия Vortex Indicator System

Общее описание

  • Источник: Конвертация советника MetaTrader 5 "Vortex Indicator System" (MQL ID 19137).
  • Идея: Использует индикатор Vortex для поиска пересечения линий VI+ и VI-, после чего выставляет пробойные триггеры по максимуму или минимуму свечи пересечения.
  • Тип входа: Стратегия следует за пробоем — сделки открываются только при подтверждении пересечения ценой.
  • Рынки и таймфреймы: Работает на любых инструментах и таймфреймах, где доступен индикатор Vortex и свечные данные.
  • Типы заявок: Рыночные ордера BuyMarket и SellMarket. Перед активацией нового триггера стратегия закрывает противоположные позиции.

Логика торговли

  1. Подписка на выбранный тип свечей и расчёт индикатора Vortex с заданной длиной.
  2. Бычье пересечение: VI+ становится выше VI-, при этом на предыдущей свече VI+ был не выше VI-.
    • Закрыть текущую короткую позицию через ClosePosition().
    • Запомнить максимум свечи пересечения как цену активации лонга.
    • Сбросить возможный шорт-триггер.
  3. Медвежье пересечение: VI- становится выше VI+, при этом на предыдущей свече VI- был не выше VI+.
    • Закрыть текущую длинную позицию.
    • Запомнить минимум свечи пересечения как цену активации шорта.
    • Сбросить возможный лонг-триггер.
  4. Пока активен триггер, проверять закрывшиеся свечи:
    • Если максимум свечи превышает лонг-триггер и позиция плоская либо короткая, выставить BuyMarket на объём, достаточный для реверса.
    • Если минимум свечи пробивает шорт-триггер и позиция плоская либо длинная, выставить SellMarket на объём, достаточный для реверса.
  5. После исполнения ордера соответствующий триггер очищается. Одновременно может существовать только один тип триггера.

Параметры

Параметр Значение по умолчанию Описание
Length 14 Период индикатора Vortex. Полностью соответствует входному параметру VI_Length в оригинальном советнике.
CandleType Свечи 60 минут Тип свечей для расчёта индикатора и проверки триггеров. Можно менять на любой доступный таймфрейм.
Volume Значение из базового свойства Strategy.Volume Объём рыночных заявок. Настраивается пользователем до запуска стратегии.

Как параметры влияют на поведение

  • Увеличение Length сглаживает линии Vortex, снижает количество пересечений, но повышает надёжность сигналов.
  • Уменьшение Length делает систему более чувствительной и увеличивает число сделок.
  • CandleType желательно выбирать в соответствии с таймфреймом, на котором тестировался оригинальный советник.

Управление рисками

  • В советнике отсутствуют стоп-лосс и тейк-профит. Конверсия сохраняет это поведение, поэтому управление рисками должно выполняться внешними средствами или через расширение стратегии.
  • При появлении противоположного сигнала стратегия сперва закрывает текущую позицию (ClosePosition()), а затем ждёт подтверждающего пробоя.
  • Система всегда находится либо без позиции, либо в одной сделке; усреднение или наращивание не используется.

Инструкция по применению

  1. Добавьте стратегию в проект StockSharp и убедитесь, что доступна библиотека StockSharp.Algo.Indicators.
  2. Настройте подключение к брокеру/источнику данных и выберите торгуемый инструмент.
  3. Выставьте параметры CandleType, Length и Volume перед запуском (или используйте оптимизацию).
  4. Запустите стратегию. Сигналы появятся после формирования достаточного числа свечей для индикатора и получения онлайн-данных.

Особенности реализации

  • Используется высокоуровневый 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;
	}
}