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
Abonnieren Sie den von CandleType ausgewählten Zeitrahmen (Standard: 1-Stunden-Kerzen) und verarbeiten Sie nur abgeschlossene Kerzen.
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.
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].
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.
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.
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];
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import AverageTrueRange
class gandalf_pro_projection_strategy(Strategy):
def __init__(self):
super(gandalf_pro_projection_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._filter_length = self.Param("FilterLength", 24) \
.SetDisplay("Filter Length", "Smoothing filter length", "Filter")
self._price_factor = self.Param("PriceFactor", 0.18) \
.SetDisplay("Price Factor", "Close price weight in filter", "Filter")
self._trend_factor = self.Param("TrendFactor", 0.18) \
.SetDisplay("Trend Factor", "Trend term weight in filter", "Filter")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period for entry buffer", "Indicators")
self._close_buffer = []
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FilterLength(self):
return self._filter_length.Value
@property
def PriceFactor(self):
return self._price_factor.Value
@property
def TrendFactor(self):
return self._trend_factor.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(gandalf_pro_projection_strategy, self).OnStarted2(time)
self._close_buffer = []
self._entry_price = 0.0
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, atr_val):
if candle.State != CandleStates.Finished:
return
av = float(atr_val)
close = float(candle.ClosePrice)
self._close_buffer.append(close)
max_depth = self.FilterLength + 2
while len(self._close_buffer) > max_depth:
self._close_buffer.pop(0)
fl = self.FilterLength
if len(self._close_buffer) <= fl or av <= 0:
return
target = self._calculate_target()
if target is None:
return
buffer_dist = av * 0.3
# Manage position
if self.Position > 0:
if target < close - buffer_dist:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if target > close + buffer_dist:
self.BuyMarket()
self._entry_price = 0.0
# Entry
if self.Position == 0:
if target > close + buffer_dist:
self._entry_price = close
self.BuyMarket()
elif target < close - buffer_dist:
self._entry_price = close
self.SellMarket()
def _calculate_target(self):
n = self.FilterLength
if n < 2 or len(self._close_buffer) < n + 1:
return None
total = 0.0
for i in range(1, n + 1):
total += self._get_close(i)
sm = total / n
weighted_sum = 0.0
for i in range(n):
price = self._get_close(i + 1)
weight = n - i
weighted_sum += price * weight
denominator = n * (n + 1) / 2.0
if denominator <= 0:
return None
lm = weighted_sum / denominator
divisor = n - 1
if divisor <= 0:
return None
pf = float(self.PriceFactor)
tf = float(self.TrendFactor)
s = [0.0] * (n + 2)
t = [0.0] * (n + 2)
tn = (6.0 * lm - 6.0 * sm) / divisor
sn = 4.0 * sm - 3.0 * lm - tn
s[n] = sn
t[n] = tn
for k in range(n - 1, 0, -1):
c = self._get_close(k)
s[k] = pf * c + (1.0 - pf) * (s[k + 1] + t[k + 1])
t[k] = tf * (s[k] - s[k + 1]) + (1.0 - tf) * t[k + 1]
return s[1] + t[1]
def _get_close(self, index):
idx = len(self._close_buffer) - 1 - index
if idx < 0:
idx = 0
if idx >= len(self._close_buffer):
idx = len(self._close_buffer) - 1
return self._close_buffer[idx]
def OnReseted(self):
super(gandalf_pro_projection_strategy, self).OnReseted()
self._close_buffer = []
self._entry_price = 0.0
def CreateClone(self):
return gandalf_pro_projection_strategy()