Diese StockSharp-Strategie reproduziert den MetaTrader-Experten GANN_FAN unter Verwendung der High-Level-API. Sie kombiniert Trendfilter aus linear gewichteten gleitenden Durchschnitten mit Momentum-Bestätigung, einem MACD-Richtungstor und einer fraktalbasierten Rekonstruktion des Gann Fan, um bullische oder bärische Tendenzen zu bestimmen. Das Risikomanagement spiegelt den ursprünglichen Roboter mit gestapelten Martingale-Einstiegen, festen Stops, Trailing-Schutz und optionalen Break-even-Bewegungen wider.
Handelslogik
Trendfilter – Zwei linear gewichtete gleitende Durchschnitte (LWMA), die auf dem typischen Preis (H+L+C)/3 basieren, definieren den schnellen und langsamen Trend. Long-Trades erfordern, dass die schnelle LWMA über der langsamen LWMA bleibt; Short-Trades benötigen den umgekehrten Crossover.
Momentum-Bestätigung – Die Strategie berechnet den klassischen Momentum-Oszillator als 100 * Close / Close(n) und bewertet die Abweichung vom neutralen Niveau 100 über die letzten drei abgeschlossenen Kerzen. Mindestens eine Abweichung muss den konfigurierten Schwellenwert überschreiten, um die Stärke in Richtung des Trades zu bestätigen.
MACD-Richtung – Ein konfigurierbares MACD-Signal (schnelle, langsame und Signal-EMA-Perioden) muss mit dem Trend übereinstimmen. Long-Einstiege erfordern, dass die MACD-Linie größer als die Signallinie ist, während Shorts erfordern, dass die MACD-Linie unter der Signallinie bleibt.
Gann Fan-Ausrichtung – Bestätigte Bill-Williams-Fraktale rekonstruieren die bullischen und bärischen Fan-Strahlen. Die zwei jüngsten Abwärtsfraktale bilden den bullischen Strahl; seine Steigung muss positiv sein, um Longs zu erlauben. Die zwei neuesten Aufwärtsfraktale definieren den bärischen Strahl; seine Steigung muss negativ sein, um Leerverkäufe zu autorisieren.
Positions-Stapelung – Wenn ein neues Signal eintrifft, kann die Strategie einer bestehenden Position bis zum konfigurierten Maximum hinzufügen. Jede zusätzliche Order erhöht das Volumen durch Multiplikation des Basislots mit dem Lot-Exponenten, was die Martingale-Größenberechnung der MQL-Version nachahmt.
Risikomanagement
Fester Stop-Loss und Take-Profit – In Instrument-Preisschritten ausgedrückt, automatisch von der Strategie mit Security.PriceStep konvertiert.
Break-even-Kontrolle – Wenn aktiviert, wird der Stop nach Erreichen der Auslösedistanz auf Einstieg plus/minus dem konfigurierten Offset vorgeschoben.
Trailing Stop – Aktiviert sich nach Erreichen der Auslösedistanz. Der Stop kann dem Markt entweder durch einen festen Abstand zum Schluss folgen oder den niedrigsten (für Longs) / höchsten (für Shorts) Wert der jüngsten Kerzen plus einem Padding-Faktor einschließen.
Force-Exit-Schalter – Das Setzen von Force Exit auf true liquidiert sofort jede offene Exposition auf der nächsten abgeschlossenen Kerze.
Parameter
Parameter
Beschreibung
Volume
Basisauftragsgröße für den ersten Einstieg.
Fast LWMA / Slow LWMA
Perioden der linear gewichteten gleitenden Durchschnitte für den Trendfilter.
Momentum Period / Threshold
Rückblick der Momentum-Berechnung und minimale Abweichung von 100 zum Handeln.
MACD Fast / Slow / Signal
EMA-Perioden für den MACD-Bestätigungsfilter.
Fractal History
Maximale Anzahl bestätigter Fraktal-Punkte zum Aufbau der Gann Fan-Strahlen.
Max Trades
Maximale Anzahl gestapelter Einstiege in eine einzelne Richtung.
Lot Exponent
Multiplikator für das Basisvolumen bei jedem zusätzlichen Einstieg.
Stop Loss / Take Profit
Schutzabstände in Preisschritten.
Enable Trailing
Aktiviert das Trailing-Stop-Management.
Trail Trigger / Distance / Padding
Gewinnauslöser, Trailing-Distanz und zusätzliches Padding (in Preisschritten) beim Trailing über Kerzenextreme.
Use Candle Trail
Aktiviert kerzenbasiertes Trailing zusätzlich zum Festabstands-Trail.
Trailing Candles
Anzahl der letzten abgeschlossenen Kerzen für kerzenbasierte Trailing-Niveaus.
Enable Break-even
Schaltet die Break-even-Logik ein oder aus.
Break-even Trigger / Offset
Gewinnauslöser und Offset (in Preisschritten) zum Verschieben des Stops auf Break-even.
Use Gann Filter
Erzwingt die bullische/bärische Fan-Ausrichtung für Einstiege.
Force Exit
Zwingt die Strategie, alle Positionen auf der nächsten Kerze zu schließen.
Candle Type
Kerzenserie für Berechnungen und Ordergenerierung.
Hinweise
Alle Indikatorberechnungen arbeiten ausschließlich auf abgeschlossenen Kerzen von SubscribeCandles und Bind, gemäß den Best Practices der StockSharp High-Level-API.
Trailing- und Break-even-Abstände passen sich automatisch an die Tick-Größe des Instruments an. Wenn PriceStep nicht verfügbar ist, bleiben Schutzfunktionen inaktiv, bis der Connector sie bereitstellt.
Die Strategie hält separate Zustände für Long- und Short-Positionen, damit Trailing- und Break-even-Niveaus zurückgesetzt werden, wenn die Exposition die Richtung ändert.
Um den MetaTrader-Experten eng nachzuahmen, werden Alerts, Benachrichtigungen und explizite Diagrammobjekte durch StockSharp-native Fan-Rekonstruktion mit Fraktalen ersetzt.
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 GannFanStrategy : 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 GannFanStrategy()
{
_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;
}
}
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.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class gann_fan_strategy(Strategy):
def __init__(self):
super(gann_fan_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(gann_fan_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(gann_fan_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return gann_fan_strategy()