Открыть на GitHub

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

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

Vortex Oscillator System — это прямой порт советника MetaTrader 5, основанного на осцилляторе Vortex. Индикатор вычисляется как разница между положительной линией Vortex (VI+) и отрицательной линией Vortex (VI-) на выбранной серии свечей. Сильные отрицательные значения показывают доминирование VI-, а высокие положительные значения свидетельствуют о превосходстве VI+. Стратегия трактует эти экстремумы как потенциальные точки разворота и реагирует контртрендовыми входами, дополняя их выходами по уровням осциллятора.

Принцип работы стратегии

  1. Свечи строятся с использованием заданного таймфрейма и передаются во встроенный индикатор VortexIndicator.
  2. После формирования индикатора осциллятор на каждом закрытом баре вычисляется как VI+ - VI-.
  3. Осциллятор сравнивается с настраиваемыми порогами:
    • Если значение падает ниже порога покупок, формируется сигнал на вход в лонг.
    • Если значение поднимается выше порога продаж, формируется сигнал на вход в шорт.
  4. Дополнительные параметры могут ограничивать сигналы диапазоном между торговым порогом и уровнем стоп-лосса (для каждой стороны отдельно).
  5. При появлении нового сигнала стратегия закрывает противоположную позицию и открывает сделку в нужном направлении заданным объёмом.
  6. Открытые позиции постоянно контролируются: при достижении уровней стоп-лосса или тейк-профита по осциллятору позиция закрывается немедленно.

Такой алгоритм полностью повторяет логику MT5: расчёт выполняется только на закрытых барах, сигналы не конфликтуют между собой, а защитные правила привязаны к значениям осциллятора.

Правила входа

  • Покупка
    • Условие выполняется, когда осциллятор меньше либо равен порогу покупок.
    • Если включён стоп-лосс для лонгов, значение осциллятора также должно быть выше уровня стоп-лосса.
    • Перед открытием лонга существующая короткая позиция закрывается.
  • Продажа
    • Условие выполняется, когда осциллятор больше либо равен порогу продаж.
    • Если включён стоп-лосс для шортов, значение осциллятора также должно быть ниже уровня стоп-лосса.
    • Перед открытием шорта существующая длинная позиция закрывается.
  • Если значение осциллятора находится между порогами покупок и продаж, сигналы обнуляются и позиция не меняется.

Правила выхода

  • Длинные позиции
    • Закрываются, если осциллятор опускается до уровня стоп-лосса или ниже (при включённом параметре).
    • Закрываются, если осциллятор поднимается до уровня тейк-профита или выше (при включённом параметре).
  • Короткие позиции
    • Закрываются, если осциллятор поднимается до уровня стоп-лосса или выше (при включённом параметре).
    • Закрываются, если осциллятор опускается до уровня тейк-профита или ниже (при включённом параметре).

Проверка условий выхода выполняется после каждого закрытия свечи, что обеспечивает точное соответствие циклу мониторинга оригинального советника.

Параметры

  • Vortex Length — период расчёта индикатора Vortex (по умолчанию 14).
  • Candle Type — таймфрейм, используемый для построения свечей и расчёта индикатора.
  • Use Buy Stop Loss — включает фильтр и выход по стоп-лоссу для длинных позиций.
  • Use Buy Take Profit — включает выход по тейк-профиту для длинных позиций.
  • Use Sell Stop Loss — включает фильтр и выход по стоп-лоссу для коротких позиций.
  • Use Sell Take Profit — включает выход по тейк-профиту для коротких позиций.
  • Buy Threshold — значение осциллятора, при котором разрешён вход в лонг (по умолчанию -0.75).
  • Buy Stop Loss Level — значение осциллятора, закрывающее длинные позиции при активном стоп-лоссе (по умолчанию -1.00).
  • Buy Take Profit Level — значение осциллятора, закрывающее длинные позиции при активном тейк-профите (по умолчанию 0.00).
  • Sell Threshold — значение осциллятора, при котором разрешён вход в шорт (по умолчанию 0.75).
  • Sell Stop Loss Level — значение осциллятора, закрывающее короткие позиции при активном стоп-лоссе (по умолчанию 1.00).
  • Sell Take Profit Level — значение осциллятора, закрывающее короткие позиции при активном тейк-профите (по умолчанию 0.00).
  • Volume — размер позиции для новых сделок (по умолчанию 0.1, как в оригинальном советнике).

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

  • Обработка ведётся только по закрытым свечам, чтобы исключить повторные сигналы внутри бара.
  • Для основных порогов заданы диапазоны оптимизации, что позволяет исследовать параметры в тестере StockSharp.
  • При смене направления стратегия автоматически переворачивает позицию, отправляя заявку, достаточную для закрытия противоположного объёма и открытия новой сделки.
  • Настройки стоп-лосса и тейк-профита независимы: можно использовать любой набор защитных правил.
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>
/// Vortex oscillator trading system ported from the MetaTrader implementation.
/// Opens long positions when the oscillator drops below a configured level and
/// shorts when the oscillator rises above the upper threshold.
/// Optional stop-loss and take-profit rules monitor the oscillator value to exit positions.
/// </summary>
public class VortexOscillatorSystemStrategy : Strategy
{
	private readonly StrategyParam<int> _length;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<bool> _useBuyStopLoss;
	private readonly StrategyParam<bool> _useBuyTakeProfit;
	private readonly StrategyParam<bool> _useSellStopLoss;
	private readonly StrategyParam<bool> _useSellTakeProfit;
	private readonly StrategyParam<decimal> _buyThreshold;
	private readonly StrategyParam<decimal> _buyStopLossLevel;
	private readonly StrategyParam<decimal> _buyTakeProfitLevel;
	private readonly StrategyParam<decimal> _sellThreshold;
	private readonly StrategyParam<decimal> _sellStopLossLevel;
	private readonly StrategyParam<decimal> _sellTakeProfitLevel;

	private VortexIndicator _vortexIndicator = null!;

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public VortexOscillatorSystemStrategy()
	{
		_length = Param(nameof(Length), 14)
			.SetGreaterThanZero()
			.SetDisplay("Vortex Length", "Period used for the Vortex indicator.", "General")
			
			.SetOptimize(7, 28, 7);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used to build candles for calculations.", "General");

		_useBuyStopLoss = Param(nameof(UseBuyStopLoss), false)
			.SetDisplay("Use Buy Stop Loss", "Enable oscillator-based stop loss for long positions.", "Risk Management");

		_useBuyTakeProfit = Param(nameof(UseBuyTakeProfit), false)
			.SetDisplay("Use Buy Take Profit", "Enable oscillator-based take profit for long positions.", "Risk Management");

		_useSellStopLoss = Param(nameof(UseSellStopLoss), false)
			.SetDisplay("Use Sell Stop Loss", "Enable oscillator-based stop loss for short positions.", "Risk Management");

		_useSellTakeProfit = Param(nameof(UseSellTakeProfit), false)
			.SetDisplay("Use Sell Take Profit", "Enable oscillator-based take profit for short positions.", "Risk Management");

		_buyThreshold = Param(nameof(BuyThreshold), -0.1m)
			.SetDisplay("Buy Threshold", "Oscillator value that triggers a long setup.", "Signals")
			
			.SetOptimize(-1.5m, -0.25m, 0.25m);

		_buyStopLossLevel = Param(nameof(BuyStopLossLevel), -1m)
			.SetDisplay("Buy Stop Loss Level", "Oscillator value that closes long trades when stop loss is enabled.", "Signals");

		_buyTakeProfitLevel = Param(nameof(BuyTakeProfitLevel), 0m)
			.SetDisplay("Buy Take Profit Level", "Oscillator value that closes long trades when take profit is enabled.", "Signals");

		_sellThreshold = Param(nameof(SellThreshold), 0.1m)
			.SetDisplay("Sell Threshold", "Oscillator value that triggers a short setup.", "Signals")
			
			.SetOptimize(0.25m, 1.5m, 0.25m);

		_sellStopLossLevel = Param(nameof(SellStopLossLevel), 1m)
			.SetDisplay("Sell Stop Loss Level", "Oscillator value that closes short trades when stop loss is enabled.", "Signals");

		_sellTakeProfitLevel = Param(nameof(SellTakeProfitLevel), 0m)
			.SetDisplay("Sell Take Profit Level", "Oscillator value that closes short trades when take profit is enabled.", "Signals");

		Volume = 0.1m;
	}

	/// <summary>
	/// Vortex indicator lookback length.
	/// </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>
	/// Enable oscillator-based stop loss for long positions.
	/// </summary>
	public bool UseBuyStopLoss
	{
		get => _useBuyStopLoss.Value;
		set => _useBuyStopLoss.Value = value;
	}

	/// <summary>
	/// Enable oscillator-based take profit for long positions.
	/// </summary>
	public bool UseBuyTakeProfit
	{
		get => _useBuyTakeProfit.Value;
		set => _useBuyTakeProfit.Value = value;
	}

	/// <summary>
	/// Enable oscillator-based stop loss for short positions.
	/// </summary>
	public bool UseSellStopLoss
	{
		get => _useSellStopLoss.Value;
		set => _useSellStopLoss.Value = value;
	}

	/// <summary>
	/// Enable oscillator-based take profit for short positions.
	/// </summary>
	public bool UseSellTakeProfit
	{
		get => _useSellTakeProfit.Value;
		set => _useSellTakeProfit.Value = value;
	}

	/// <summary>
	/// Oscillator level used to open long trades.
	/// </summary>
	public decimal BuyThreshold
	{
		get => _buyThreshold.Value;
		set => _buyThreshold.Value = value;
	}

	/// <summary>
	/// Oscillator level that closes long trades when stop loss is enabled.
	/// </summary>
	public decimal BuyStopLossLevel
	{
		get => _buyStopLossLevel.Value;
		set => _buyStopLossLevel.Value = value;
	}

	/// <summary>
	/// Oscillator level that closes long trades when take profit is enabled.
	/// </summary>
	public decimal BuyTakeProfitLevel
	{
		get => _buyTakeProfitLevel.Value;
		set => _buyTakeProfitLevel.Value = value;
	}

	/// <summary>
	/// Oscillator level used to open short trades.
	/// </summary>
	public decimal SellThreshold
	{
		get => _sellThreshold.Value;
		set => _sellThreshold.Value = value;
	}

	/// <summary>
	/// Oscillator level that closes short trades when stop loss is enabled.
	/// </summary>
	public decimal SellStopLossLevel
	{
		get => _sellStopLossLevel.Value;
		set => _sellStopLossLevel.Value = value;
	}

	/// <summary>
	/// Oscillator level that closes short trades when take profit is enabled.
	/// </summary>
	public decimal SellTakeProfitLevel
	{
		get => _sellTakeProfitLevel.Value;
		set => _sellTakeProfitLevel.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_vortexIndicator = new VortexIndicator
		{
			Length = Length,
		};

		var subscription = SubscribeCandles(CandleType);

		subscription
			.BindEx(_vortexIndicator, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue vortexValue)
	{
		// Process only finished candles to mirror bar-close logic from the original script.
		if (candle.State != CandleStates.Finished)
			return;

		if (!_vortexIndicator.IsFormed)
			return;

		var vortexTyped = (VortexIndicatorValue)vortexValue;
		var viPlus = vortexTyped.PlusVi ?? 0m;
		var viMinus = vortexTyped.MinusVi ?? 0m;

		// Vortex oscillator equals the difference between VI+ and VI- lines.
		var oscillator = viPlus - viMinus;

		var longSetupExists = false;
		var shortSetupExists = false;

		// Long setups are considered when the oscillator falls below the buy threshold.
		if (UseBuyStopLoss)
		{
			if (oscillator <= BuyThreshold && oscillator > BuyStopLossLevel)
			{
				longSetupExists = true;
				shortSetupExists = false;
			}
		}
		else if (oscillator <= BuyThreshold)
		{
			longSetupExists = true;
			shortSetupExists = false;
		}

		// Short setups require the oscillator to rise above the sell threshold.
		if (UseSellStopLoss)
		{
			if (oscillator >= SellThreshold && oscillator < SellStopLossLevel)
			{
				shortSetupExists = true;
				longSetupExists = false;
			}
		}
		else if (oscillator >= SellThreshold)
		{
			shortSetupExists = true;
			longSetupExists = false;
		}

		// Neutral zone cancels both long and short intentions.
		if (oscillator >= BuyThreshold && oscillator <= SellThreshold)
		{
			longSetupExists = false;
			shortSetupExists = false;
		}

		var currentPosition = Position;

		if (longSetupExists && currentPosition <= 0)
		{
			// Close existing shorts and open a long position when a valid long setup appears.
			BuyMarket();
		}
		else if (shortSetupExists && currentPosition >= 0)
		{
			// Close existing longs and open a short position when a valid short setup appears.
			SellMarket();
		}

		currentPosition = Position;

		if (currentPosition > 0)
		{
			// Manage long positions with oscillator-based stops and targets.
			if (UseBuyStopLoss && oscillator <= BuyStopLossLevel)
			{
				SellMarket();
				return;
			}

			if (UseBuyTakeProfit && oscillator >= BuyTakeProfitLevel)
			{
				SellMarket();
				return;
			}
		}
		else if (currentPosition < 0)
		{
			// Manage short positions with oscillator-based stops and targets.
			if (UseSellStopLoss && oscillator >= SellStopLossLevel)
			{
				BuyMarket();
				return;
			}

			if (UseSellTakeProfit && oscillator <= SellTakeProfitLevel)
			{
				BuyMarket();
			}
		}
	}
}