Auf GitHub ansehen

Horizontal Line Levels-Strategie

Die Horizontal Line Levels-Strategie emuliert den MetaTrader 5 Expert Advisor des gleichen Namens. Sie erstellt kontinuierlich zwei Preisniveaus rund um das aktuelle Kurs und benachrichtigt den Benutzer, sobald der Markt diese überschreitet. Die Implementierung stützt sich auf Level1 (Bid/Ask)-Marktdaten und ahmt den ursprünglichen OnTick/OnTimer-Workflow nach, ohne Orders zu senden.

Grundidee

  1. Level1-Daten abonnieren und die neuesten besten Bid- und Ask-Preise zwischenspeichern.
  2. Die MetaTrader-Punktdistanz in die StockSharp-Preisskala konvertieren.
  3. Das beste Ask nach oben und das beste Bid nach unten um die konfigurierte Distanz verschieben, wodurch zwei virtuelle horizontale Linien entstehen.
  4. Periodisch (über einen internen Timer) prüfen, ob Bid oder Ask diese Referenzniveaus überschreiten und Alerts im Strategie-Journal protokollieren.

Parameter

Name Standard Beschreibung
TimerPeriodMinutes 1 Minuten zwischen zwei aufeinanderfolgenden Timer-Prüfungen. Muss positiv bleiben.
OffsetPoints 50 Abstand in MetaTrader-Punkten, der über dem Ask und unter dem Bid beim Erstellen der Linien angewendet wird.

Verhaltensdetails

  • Datenabonnement: GetWorkingSecurities registriert einen Level1-Stream, damit die Strategie Bid/Ask-Updates auch ohne Kerzen empfängt.
  • Initialisierung: Wenn sowohl das beste Bid als auch das beste Ask zum ersten Mal verfügbar sind, speichert RecalculateLevels die aktuellen oberen und unteren horizontalen Niveaus.
  • Timer: Jeder Timer-Tick erstellt fehlende Niveaus neu (falls die Initialisierung vor der Kursverfügbarkeit erfolgte) und gibt Log-Meldungen aus, sobald der Markt eine der Grenzen verletzt.
  • MetaTrader-Punkt-Übersetzung: Der Helfer EnsurePointSize konvertiert MetaTrader-"Punkte" in absolute Preiserhöhungen mit Security.PriceStep. Dieselbe Technik wird in anderen konvertierten Strategien zur Aufrechterhaltung der numerischen Kompatibilität verwendet.
  • Kein Trading: Die Strategie sendet nie Orders; sie produziert nur Alerts über AddInfoLog. Dies entspricht dem ursprünglichen Expert, der Pop-up-Alerts anzeigte, wenn der Preis eine der Linien berührte.
  • Stopp/Reset: Das Stoppen der Strategie bricht den Timer ab und löscht alle zwischengespeicherten Werte, sodass der nächste Lauf von einem sauberen Zustand beginnt.

Typische Verwendung

  1. Die Strategie an das gewünschte Instrument anhängen und TimerPeriodMinutes und OffsetPoints in der Designer-UI einstellen.
  2. Die Strategie starten. Sobald ein vollständiger Kurs-Snapshot eintrifft, bestätigt ein Log-Eintrag wie Horizontal levels updated. Upper: 1.12345, Lower: 1.12245. die berechneten Schwellenwerte.
  3. Das Log-Fenster beobachten. Wenn das Ask über das obere Niveau steigt (oder das Bid unter das untere Niveau fällt), gibt die Strategie die entsprechende Alert-Meldung aus.
  4. Wenn der Offset geändert oder die Strategie neu gestartet wird, werden die Niveaus mit den neuen Parametern neu berechnet.

Klassifizierung

  • Kategorie: Dienstprogramme / Alerts
  • Richtung: Keine
  • Ausführungsstil: Ereignisgesteuertes Monitoring
  • Datenanforderungen: Level1 Bid/Ask
  • Komplexität: Grundlegend
  • Empfohlener Zeitrahmen: Beliebig (rein kursbasiert)
  • Risikomanagement: Nicht anwendbar (keine Positionen eröffnet)

Diese Konvertierung behält das Alert-zentrierte Verhalten des MetaTrader-Originals bei und nutzt dabei StockSharp-Abstraktionen wie Strategie-Timer und Level1-Abonnements.

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 HorizontalLineLevelsStrategy : 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 HorizontalLineLevelsStrategy()
	{
		_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;
	}
}