Auf GitHub ansehen

Gandalf PRO-Projektionsstrategie

Überblick

Die Gandalf PRO-Strategie ist eine StockSharp-Portierung des MetaTrader 4-Expertenberaters Gandalf_PRO. Der ursprüngliche Roboter baut einen adaptiver Glättungsfilter aus einem gewichteten gleitenden Durchschnitt und einer rekursiven Trendkomponente. Wenn sich der prognostizierte Preis bewegt Mindestens 15 Pips über dem aktuellen Marktpreis, steigt der EA mit einem entfernten Stop-Loss und einem Take-Profit in diese Richtung projiziertes Niveau. Die StockSharp-Konvertierung reproduziert denselben Filter und dieselbe Entscheidungslogik und stützt sich dabei auf die Kerze auf hoher Ebene API, sodass jede Berechnung an fertigen Stäben durchgeführt wird.

Handelslogik

  1. Abonnieren Sie den von CandleType ausgewählten Zeitrahmen (Standard: 1-Stunden-Kerzen) und verarbeiten Sie nur abgeschlossene Kerzen.
  2. Führen Sie eine fortlaufende Historie der Schlusskurse, die groß genug ist, um das Maximum von CountBuy und CountSell plus einen zusätzlichen Balken abzudecken.
  3. Erstellen Sie die Funktion MetaTrader Out() neu: Berechnen Sie linear gewichtete und einfache gleitende Durchschnitte (unter Verwendung einer Verschiebung um einen Balken) und leiten Sie die ab rekursive s- und t-Komponenten mit den konfigurierten Preis- und Trendfaktoren und erhalten Sie den prognostizierten Preis s[1] + t[1].
  4. Für lange Setups (EnableBuy):
    • Überprüfen Sie, ob der prognostizierte Preis mindestens 15 Pips über dem letzten Schlusskurs liegt (Bid + 15*x*Point in MT4).
    • Wenn keine Long-Position offen ist, kaufen Sie das konfigurierte Volumen (siehe BaseVolume und BuyRiskMultiplier).
    • Speichern Sie den prognostizierten Preis als Take-Profit und berechnen Sie den Stop-Loss, indem Sie BuyStopLossPips umgerechnet in Preisschritte subtrahieren.
  5. Für kurze Setups (EnableSell):
    • Der prognostizierte Preis muss mindestens 15 Pips unter dem letzten Schlusskurs liegen.
    • Wenn keine Short-Position offen ist, verkaufen Sie das konfigurierte Volumen (gegebenenfalls unter Umkehr einer bestehenden Long-Position).
    • Speichern Sie den prognostizierten Preis als Take-Profit und legen Sie den Stop-Loss SellStopLossPips Pips über dem Markt fest.
  6. Während eine Position existiert, überwachen Sie jede fertige Kerze:
    • Verlassen Sie Long-Positionen, wenn das Tief der Kerze den gespeicherten Stop kreuzt oder das Hoch den Take-Profit erreicht.
    • Verlassen Sie Short-Positionen, wenn das Hoch der Kerze den Stopp überschreitet oder das Tief das Ziel erreicht.
    • Exits verwenden ClosePosition(), wodurch die Nettopräsenz in StockSharp abgeflacht wird.

Parameter

Name Typ Standard Beschreibung
EnableBuy bool true Erlauben Sie der Strategie, Long-Positionen zu eröffnen.
CountBuy int 24 Länge des Glättungsfilters, der für lange Projektionen verwendet wird.
BuyPriceFactor decimal 0.18 Gewicht des aktuellen Abschlusses im langen rekursiven Filter.
BuyTrendFactor decimal 0.18 Gewichtung, die beim Erstellen der langen Projektion auf den Trendterm angewendet wird.
BuyStopLossPips int 62 Stop-Loss-Distanz für Long-Positionen, gemessen in Pips.
BuyRiskMultiplier decimal 0 Der Multiplikator wird auf BaseVolume angewendet, bevor eine Langbestellung gesendet wird (0 behält das Basisvolumen bei).
EnableSell bool true Erlauben Sie der Strategie, Short-Positionen zu eröffnen.
CountSell int 24 Länge des Glättungsfilters, der für kurze Projektionen verwendet wird.
SellPriceFactor decimal 0.18 Gewicht des aktuellen Abschlusses im kurzen rekursiven Filter.
SellTrendFactor decimal 0.18 Gewichtung, die beim Erstellen der Kurzprojektion auf den Trendterm angewendet wird.
SellStopLossPips int 62 Stop-Loss-Distanz für Short-Positionen, gemessen in Pips.
SellRiskMultiplier decimal 0 Der Multiplikator wird auf BaseVolume angewendet, bevor eine Kurzbestellung gesendet wird (0 behält das Basisvolumen bei).
BaseVolume decimal 1 Basisauftragsgröße, die verwendet wird, wenn beide Risikomultiplikatoren Null sind.
CandleType DataType 1-stündiger Zeitrahmen Von der Strategie verarbeitete Kerzenserie.

Unterschiede zum Original MetaTrader EA

  • MetaTrader kann gleichzeitig unabhängige Kauf- und Verkaufstickets halten. StockSharp verwendet Nettopositionen, sodass der Port geschlossen wird oder kehrt eine bestehende Position um, bevor die gegenüberliegende Seite geöffnet wird.
  • Die MT4-Lotfunktion nutzte die kontofreie Marge. Die Konvertierung legt BaseVolume und zwei Risikomultiplikatoren offen; wenn sie Null sind Das Basisvolumen wird unverändert verwendet, andernfalls wird das Volumen einfach skaliert (BaseVolume * RiskMultiplier).
  • Stop-Loss- und Take-Profit-Level werden durch die Überwachung abgeschlossener Kerzen erreicht. Intrabar-Füllungen können daher von MetaTrader abweichen. bei denen Schutzanordnungen vom Makler verwaltet werden.
  • Die fünfstellige Digits/Point-Anpassung wird durch Überprüfung von Security.Decimals und Security.PriceStep emuliert, um Pip umzurechnen Distanzen in absolute Preise umwandeln.
  • Alle Indikatorberechnungen werden in verwaltetem Code durchgeführt, ohne dass iMA aufgerufen wird. Der rekursive Filter wird in neu erstellt CalculateTarget unter Verwendung derselben Koeffizienten wie die Funktion MQL.

Nutzungshinweise

  • Weisen Sie Strategy.Security das gewünschte Instrument zu, bevor Sie beginnen. Die Strategie löst eine Ausnahme aus, wenn keine Sicherheit angehängt ist.
  • Konfigurieren Sie BaseVolume so, dass es der von Ihrem Veranstaltungsort erwarteten Vertragsgröße entspricht. Passen Sie die Risikomultiplikatoren nur an, wenn Sie skalieren möchten die Belichtung relativ zum Basisvolumen.
  • Der Kerzenverlauf muss mindestens max(CountBuy, CountSell) + 1 Balken enthalten, bevor ein Handel generiert werden kann. Ausreichend bereitstellen Aufwärmdaten oder starten Sie die Strategie mit geladenen historischen Kerzen.
  • Der 15-Pip-Eingabepuffer ist fest (genau wie im EA). Erhöhen Sie CountBuy/CountSell, um die Projektion zu glätten oder zu optimieren Preis-/Trendfaktoren, die dem in MetaTrader beobachteten Verhalten entsprechen.
  • Da Exits von den Extremwerten der Kerze abhängen, aktivieren Sie einen Zeitrahmen, der zu Ihrer Ausführungslatenz passt. Niedrigere Zeitrahmen werden früher reagieren erfordern jedoch mehr historische Daten und können möglicherweise mehr Signale erzeugen.

Details zur Implementierung

  • Verwendet SubscribeCandles() mit Bind(ProcessCandle), sodass jede Entscheidung auf endgültigen Kerzen basiert.
  • Behält eine kompakte Liste der letzten Abschlüsse und erstellt den rekursiven s/t-Filter bei Bedarf neu, wodurch die Out()-Routine nachgeahmt wird.
  • Konvertiert Pip-basierte Offsets über die Tick-Größe des Instruments und die Dezimalgenauigkeit, um die Skalierung MetaTrader x * Point zu reproduzieren.
  • ClosePosition() wird aufgerufen, wenn Schutzniveaus durchbrochen werden, um sicherzustellen, dass die Nettoposition abgeflacht wird, bevor es zu einem weiteren Eintrag kommt berücksichtigt.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Gandalf PRO trend-following strategy using adaptive smoothing filter.
/// Opens trades when projected price exceeds a buffer threshold.
/// </summary>
public class GandalfProProjectionStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _filterLength;
	private readonly StrategyParam<decimal> _priceFactor;
	private readonly StrategyParam<decimal> _trendFactor;
	private readonly StrategyParam<int> _atrLength;

	private readonly List<decimal> _closeBuffer = new();
	private decimal _entryPrice;

	public GandalfProProjectionStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_filterLength = Param(nameof(FilterLength), 24)
			.SetDisplay("Filter Length", "Smoothing filter length.", "Filter");

		_priceFactor = Param(nameof(PriceFactor), 0.18m)
			.SetDisplay("Price Factor", "Close price weight in filter.", "Filter");

		_trendFactor = Param(nameof(TrendFactor), 0.18m)
			.SetDisplay("Trend Factor", "Trend term weight in filter.", "Filter");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period for entry buffer.", "Indicators");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int FilterLength
	{
		get => _filterLength.Value;
		set => _filterLength.Value = value;
	}

	public decimal PriceFactor
	{
		get => _priceFactor.Value;
		set => _priceFactor.Value = value;
	}

	public decimal TrendFactor
	{
		get => _trendFactor.Value;
		set => _trendFactor.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_closeBuffer.Clear();
		_entryPrice = 0;
	}

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

		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(atr, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_closeBuffer.Add(candle.ClosePrice);
		var maxDepth = FilterLength + 2;
		while (_closeBuffer.Count > maxDepth)
			_closeBuffer.RemoveAt(0);

		if (_closeBuffer.Count <= FilterLength || atrVal <= 0)
			return;

		var close = candle.ClosePrice;
		var target = CalculateTarget();
		if (target == null)
			return;

		var targetPrice = target.Value;
		var buffer = atrVal * 0.3m;

		// Manage position
		if (Position > 0)
		{
			// Exit if projection flips below close or on stop
			if (targetPrice < close - buffer)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (targetPrice > close + buffer)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		// Entry
		if (Position == 0)
		{
			if (targetPrice > close + buffer)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (targetPrice < close - buffer)
			{
				_entryPrice = close;
				SellMarket();
			}
		}
	}

	private decimal? CalculateTarget()
	{
		var n = FilterLength;
		if (n < 2 || _closeBuffer.Count < n + 1)
			return null;

		var sum = 0m;
		for (var i = 1; i <= n; i++)
			sum += GetClose(i);

		var sm = sum / n;

		var weightedSum = 0m;
		for (var i = 0; i < n; i++)
		{
			var price = GetClose(i + 1);
			var weight = n - i;
			weightedSum += price * weight;
		}

		var denominator = (decimal)n * (n + 1) / 2m;
		if (denominator <= 0m)
			return null;

		var lm = weightedSum / denominator;
		var divisor = n - 1;
		if (divisor <= 0)
			return null;

		var s = new decimal[n + 2];
		var t = new decimal[n + 2];

		var tn = (6m * lm - 6m * sm) / divisor;
		var sn = 4m * sm - 3m * lm - tn;
		s[n] = sn;
		t[n] = tn;

		for (var k = n - 1; k > 0; k--)
		{
			var close = GetClose(k);
			s[k] = PriceFactor * close + (1m - PriceFactor) * (s[k + 1] + t[k + 1]);
			t[k] = TrendFactor * (s[k] - s[k + 1]) + (1m - TrendFactor) * t[k + 1];
		}

		return s[1] + t[1];
	}

	private decimal GetClose(int index)
	{
		var idx = _closeBuffer.Count - 1 - index;
		if (idx < 0) idx = 0;
		if (idx >= _closeBuffer.Count) idx = _closeBuffer.Count - 1;
		return _closeBuffer[idx];
	}
}