Auf GitHub ansehen

Vortex-Oszillator-System-Strategie

Überblick

Das Vortex-Oszillator-System ist eine direkte Portierung des MetaTrader 5-Experten, der auf dem Vortex-Oszillator basiert, um starke Verschiebungen zwischen positiver und negativer Richtungsbewegung zu erfassen. Der Oszillator wird als Differenz zwischen der positiven Vortex-Linie (VI+) und der negativen Vortex-Linie (VI-) berechnet, die auf der ausgewählten Kerzenserie kalkuliert wird. Stark negative Werte zeigen an, dass VI- VI+ dominiert, während stark positive Werte die Führung von VI+ zeigen. Die Strategie interpretiert diese Extremwerte als potenzielle Wendepunkte und reagiert mit Mean-Reversion-artigen Einstiegen, unterstützt durch oszillatorgesteuerte Ausstiege.

Funktionsweise der Strategie

  1. Kerzen werden mit dem konfigurierten Zeitrahmen erstellt und dem integrierten VortexIndicator zugeführt.
  2. Sobald der Indikator geformt ist, wird der Oszillatorwert als VI+ - VI- für jede fertige Kerze abgeleitet.
  3. Der Oszillator wird mit benutzerdefinierten Schwellenwerten verglichen:
    • Wenn er unter den Kaufschwellenwert fällt, wird ein Long-Setup erkannt.
    • Wenn er über den Verkaufsschwellenwert steigt, wird ein Short-Setup erkannt.
  4. Optionale Filter können Long-Signale auf die Zone zwischen dem Kaufschwellenwert und einem dedizierten Stop-Loss-Level beschränken (und umgekehrt für Short-Signale).
  5. Wenn ein neues Setup erscheint, schließt die Strategie jede Gegenposition und eröffnet einen Trade in der Signalrichtung mit dem konfigurierten Volumen.
  6. Offene Positionen werden kontinuierlich überwacht. Wenn der Oszillator die konfigurierten Stop-Loss- oder Take-Profit-Grenzen erreicht, wird die Position sofort geschlossen.

Diese Sequenz reproduziert die ursprüngliche MetaTrader-Logik: Trades werden nur auf abgeschlossenen Bars bewertet, beide Richtungen schließen sich gegenseitig aus, und oszillatorbasierte Schutzregeln regeln die Ausstiege.

Einstiegskriterien

  • Long-Einstieg
    • Wird ausgelöst, wenn der Oszillator kleiner oder gleich dem Kaufschwellenwert ist.
    • Wenn die Long-Stop-Loss-Option aktiviert ist, muss der Oszillator auch über dem Long-Stop-Loss-Level bleiben.
    • Jede aktive Short-Position wird geschlossen, bevor der Long-Trade eröffnet wird.
  • Short-Einstieg
    • Wird ausgelöst, wenn der Oszillator größer oder gleich dem Verkaufsschwellenwert ist.
    • Wenn die Short-Stop-Loss-Option aktiviert ist, muss der Oszillator auch unter dem Short-Stop-Loss-Level bleiben.
    • Jede aktive Long-Position wird geschlossen, bevor der Short-Trade eröffnet wird.
  • Wenn der Oszillatorwert zwischen den Kauf- und Verkaufsschwellenwerten liegt, werden alle Setups storniert und keine Positionsänderung vorgenommen.

Ausstiegskriterien

  • Long-Positionen
    • Sofortiges Schließen, wenn der Oszillator unter das Long-Stop-Loss-Level kreuzt oder es erreicht (wenn aktiviert).
    • Sofortiges Schließen, wenn der Oszillator auf oder über das Long-Take-Profit-Level steigt (wenn aktiviert).
  • Short-Positionen
    • Sofortiges Schließen, wenn der Oszillator über das Short-Stop-Loss-Level kreuzt oder es erreicht (wenn aktiviert).
    • Sofortiges Schließen, wenn der Oszillator auf oder unter das Short-Take-Profit-Level fällt (wenn aktiviert).

Die Ausstiegsprüfungen werden nach jedem Kerzenschluss durchgeführt, um eine getreue Nachbildung der MT5-Überwachungsschleife zu gewährleisten.

Parameter

  • Vortex Length – Rückblickperiode für den Vortex-Indikator (Standard 14).
  • Candle Type – Zeitrahmen für die Erstellung der dem Indikator zugeführten Kerzen.
  • Use Buy Stop Loss – Aktiviert den oszillatorbasierten Stop-Loss-Filter und -Ausstieg für Long-Trades.
  • Use Buy Take Profit – Aktiviert den oszillatorbasierten Take-Profit-Ausstieg für Long-Trades.
  • Use Sell Stop Loss – Aktiviert den oszillatorbasierten Stop-Loss-Filter und -Ausstieg für Short-Trades.
  • Use Sell Take Profit – Aktiviert den oszillatorbasierten Take-Profit-Ausstieg für Short-Trades.
  • Buy Threshold – Oszillatorwert, der einen Long-Einstieg qualifiziert (Standard -0.75).
  • Buy Stop Loss Level – Oszillatorwert, der Long-Positionen schließt, wenn die Long-Stop-Loss-Option aktiv ist (Standard -1.00).
  • Buy Take Profit Level – Oszillatorwert, der Long-Positionen schließt, wenn die Long-Take-Profit-Option aktiv ist (Standard 0.00).
  • Sell Threshold – Oszillatorwert, der einen Short-Einstieg qualifiziert (Standard 0.75).
  • Sell Stop Loss Level – Oszillatorwert, der Short-Positionen schließt, wenn die Short-Stop-Loss-Option aktiv ist (Standard 1.00).
  • Sell Take Profit Level – Oszillatorwert, der Short-Positionen schließt, wenn die Short-Take-Profit-Option aktiv ist (Standard 0.00).
  • Volume – Handelsgröße für neue Positionen (Standard 0.1, entsprechend dem originalen Experten).

Implementierungshinweise

  • Die Verarbeitung erfolgt ausschließlich auf abgeschlossenen Kerzen, um Signalduplizierungen innerhalb desselben Bars zu vermeiden.
  • Oszillatorschwellenwerte können dank der in den Parameter-Metadaten bereitgestellten Bereiche optimiert werden.
  • Die Strategie dreht Positionen automatisch um, indem sie eine Marktorder sendet, die groß genug ist, um die Gegenseite zu schließen und die neue Exponierung zu etablieren.
  • Stop-Loss- und Take-Profit-Funktionen arbeiten unabhängig; die Aktivierung einer erfordert nicht die andere.
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();
			}
		}
	}
}