Auf GitHub ansehen

Vhf Sliding Windows-Strategie

Überblick

  • Konvertiert vom MetaTrader-5-Expert-Advisor „VHF EA" von Vladimir Karputov.
  • Verwendet den Vertical Horizontal Filter (VHF)-Indikator, um das Marktregime als Trending oder Ranging einzustufen.
  • Funktioniert auf jedem von StockSharp unterstützten Instrument und Zeitrahmen; einfach den Kerzentyp-Parameter an das gewünschte Chart anpassen.

Handelslogik

  1. Die ausgewählte Kerzenserie abonnieren und den VHF-Indikator mit Periode VhfPeriod bei jeder abgeschlossenen Kerze berechnen.
  2. Zwei gleitende Fenster aktueller VHF-Werte führen:
    • Hauptfenster (MainWindowSize) – etabliert den gesamten VHF-Bereich und Mittelpunkt.
    • Arbeitsfenster (WorkingWindowSize) – erkennt kurzfristige Durchbrüche über oder unter der lokalen VHF-Median.
  3. Ein bullisches oder bärisches Trend-Regime wird nur bestätigt, wenn der aktuelle VHF-Wert größer als der Mittelpunkt beider Fenster ist.
  4. Während im Trend-Regime, den letzten Schlusskurs mit dem Schluss vor MainWindowSize Bars vergleichen:
    • Schluss höher als Referenz → Standardverhalten ist Long-Position öffnen/beibehalten.
    • Schluss niedriger als Referenz → Standardverhalten ist Short-Position öffnen/beibehalten.
    • ReverseSignals aktivieren, um diese Richtungen umzukehren.
  5. Die Strategie schließt alle offenen Positionen, sobald der VHF-Wert wieder in die Ranging-Zone fällt (aktueller VHF liegt nicht über beiden Mittelpunkten).
  6. Positionswechsel werden durch Kaufen/Verkaufen von ausreichend Volumen gehandhabt, um sowohl die Gegenseite zu schließen als auch die neue Position in einer einzelnen Marktorder zu eröffnen.

Parameter

Parameter Beschreibung Standard Hinweise
MainWindowSize Anzahl der VHF-Werte im primären gleitenden Fenster. 11 Muss größer als WorkingWindowSize sein.
WorkingWindowSize Anzahl der VHF-Werte im sekundären Fenster. 7 Bietet schnellere Ausbruchsbestätigung.
VhfPeriod Rückblickperiode des Vertical Horizontal Filters. 9 Bestimmt die Sensitivität des Indikators.
Volume Order-Volumen (Lots) für neue Einstiege. 1 Wird zum absoluten aktuellen Positionswert addiert beim Richtungswechsel.
ReverseSignals Long/Short-Logik aus Preisrichtung invertieren. true Entspricht dem Standardverhalten des ursprünglichen EA.
CandleType Zeitrahmen und Kerzentyp für Datenabonnement. 15-Minuten-Zeitrahmen Ändern, um die Strategie an andere Charts anzupassen.

Geldmanagement und Ausstiege

  • Die Strategie handelt immer mit einem festen Volumen definiert durch Volume.
  • Schützende Stop-Verwaltung wird an den eingebauten StockSharp-Helfer StartProtection() delegiert, der unerwartete Restpositionen sicher schließt.
  • Keine Stop-Loss- oder Take-Profit-Ziele sind kodiert; Ausstiege beruhen auf dem von VHF erkannten Regimewechsel.

Implementierungshinweise

  • Verwendet die hochrangige Kerzen-Abonnement-API mit Indikator-Bindung, gemäß den Projektrichtlinien.
  • Ein benutzerdefinierter Vertical Horizontal Filter-Indikator identisch mit der MQL-Version ist in die Strategie eingebettet.
  • Log-Anweisungen beschreiben jeden Positionswechsel und Regimeübergang zur einfacheren Fehlersuche.
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class VhfSlidingWindowsStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public VhfSlidingWindowsStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}