Entwicklung eines dynamischen Multi-Pair-EA (Teil 6): Adaptive Spread-Sensitivität für hochfrequente Symbolwechsel
- Einführung
- Systemübersicht und strategischer Ansatz
- Die ersten Schritte
- Backtest-Ergebnisse
- Schlussfolgerung
Einführung
In Teil 5 unserer Dynamic Multi-Pair EA-Serie haben wir uns mit der Herausforderung beschäftigt, den richtigen Handelsstil für unterschiedliche Marktbedingungen zu wählen, insbesondere mit der Entwicklung eines Systems, das zwischen Scalping- und Swing-Trading-Modus wechseln kann. Wir erläuterten, wie Scalping sich auf das Erfassen kleiner Kursbewegungen innerhalb enger Zeitrahmen konzentriert, während Swing-Trading auf größere Richtungsbewegungen über längere Zeiträume abzielt, und zeigten, wie ein EA seine Logik, Stopp-Levels und Zeithorizonte je nach Marktkontext dynamisch anpassen kann.
Im Gegensatz dazu verlagert Teil 6 den Schwerpunkt weg von spezifischen Ein- und Ausstiegslogiken im Handel und richtet den Fokus stattdessen auf die Ausführungsbedingungen selbst – insbesondere auf die über den Spread messbaren Handelskosten. In Teil 6 stellen wir nun ein Modul vor, das die Spread-Bedingungen für alle Symbole kontinuierlich in Echtzeit überwacht und anpasst, indem es dynamische Sensitivitätsschwellenwerte verwendet, um zu bestimmen, welche Symbole derzeit optimal zu handeln sind. Diese Hochfrequenz-Symbolumschaltung auf der Grundlage einer adaptiven Spread-Bewertung ergänzt die breitere Multi-Pair-Architektur, indem sie der Ausführungsqualität und Kosteneffizienz Vorrang vor reinen Strategiemechanismen einräumt.
Systemübersicht und strategischer Ansatz
Der Adaptive Spread-Sensitivity EA ist ein hoch entwickeltes Multi-Symbol-Handelssystem, das entwickelt wurde, um die Handelsausführung dynamisch über mehrere Finanzinstrumente hinweg zu optimieren. Das Kernstück des Systems ist die kontinuierliche Überwachung der Echtzeit-Spreads für alle konfigurierten Symbole, die anhand von Kosteneffizienz-Kennzahlen bewertet werden, um den Handel mit Instrumenten mit den günstigsten Ausführungsbedingungen zu priorisieren.

Im Gegensatz zu traditionellen Einzelsymbol-EAs implementiert dieses System eine intelligente Spread-Filterung, die Symbole mit ungewöhnlich hohen Spreads vorübergehend deaktiviert, um kostspielige Markteinstiege bei ungünstigen Liquiditätsbedingungen zu vermeiden, und sie automatisch wieder zu aktivieren, wenn sich die Spreads normalisieren. Die adaptive Architektur ermöglicht es dem EA, als „intelligenter Router“ zu fungieren, der dynamisch zwischen den verfügbaren Symbolen auf der Grundlage der sich verändernden Marktmikrostruktur umschaltet und so sicherstellt, dass die Trades immer auf dem wirtschaftlich effizientesten Instrument zu einem bestimmten Zeitpunkt ausgeführt werden.

Die Handelsstrategie basiert auf einem einfachen, aber effektiven Ansatz der technischen Analyse, der zwei gleitende Durchschnittsübergänge mit einer RSI-Momentum-Bestätigung kombiniert. Wenn die Spread-Bedingungen günstig sind, generiert das System Kaufsignale, wenn der schnelle EMA den langsamen EMA übersteigt, während der RSI überverkaufte Bedingungen anzeigt, und Verkaufssignale, wenn der schnelle EMA den langsamen EMA unterschreitet, während der RSI im überkauften Bereich liegt. Diese Kombination bietet ein ausgewogenes Einstiegs-Timing, indem gleitende Durchschnitte für die Trendrichtung und der RSI für die Einstiegspräzision verwendet werden.

Die ersten Schritte
//+------------------------------------------------------------------+ //| Spread Sensitivity.mq5 | //| GIT under Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com/en/users/johnhlomohang/ | //+------------------------------------------------------------------+ #property copyright "GIT under Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/johnhlomohang/" #property version "1.00" #property description "Multi-Symbol EA with Adaptive Spread Sensitivity" #property description "Dynamically switches between symbols based on spread efficiency" #include <Trade/Trade.mqh> #include <Trade/PositionInfo.mqh> //+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ input string TradePairs = "EURUSD,GBPUSD,XAUUSD,US100,BTCUSD"; // Trading Pairs (comma separated) input double RiskPerTrade = 0.01; // Risk % per trade input int MagicNumber = 98765; // Magic Number // Spread Sensitivity Settings input group "=== Spread Filter Settings ===" input double MaxAbsoluteSpread = 10.0; // Max absolute spread (pips) input double MaxSpreadATRRatio = 0.25; // Max spread/ATR ratio input bool UseAdaptiveFilter = true; // Enable adaptive filtering input int DisableTimeoutSec = 60; // Disable symbol timeout (seconds) input bool EnableSpreadRanking = true; // Enable symbol ranking by spread input int MaxActiveSymbols = 3; // Maximum active symbols at once // Trading Strategy Settings input group "=== Trading Strategy Settings ===" input ENUM_TIMEFRAMES TradingTimeframe = PERIOD_M5; // Trading timeframe input int EMA_Fast_Period = 9; // Fast EMA period input int EMA_Slow_Period = 21; // Slow EMA period input int RSI_Period = 14; // RSI period input double RSI_Overbought = 70; // RSI overbought level input double RSI_Oversold = 30; // RSI oversold level input int StopLoss_Pips = 30; // Stop Loss in pips input int TakeProfit_Pips = 60; // Take Profit in pips input int MaxOpenPositions = 1; // Max positions per symbol input int TradeCooldownSeconds = 300; // Cooldown between trades (seconds) // ATR Settings for adaptive SL/TP input group "=== ATR Settings (Optional) ===" input bool UseATR_SL_TP = false; // Use ATR for dynamic SL/TP input double ATR_SL_Multiplier = 1.5; // ATR multiplier for SL input double ATR_TP_Multiplier = 2.0; // ATR multiplier for TP // Dashboard Settings input group "=== Dashboard Settings ===" input bool ShowDashboard = true; // Show dashboard on chart input color DashboardBGColor = clrBlack; // Dashboard background color input color DashboardTextColor = clrWhite;// Dashboard text color input int DashboardX = 20; // Dashboard X position input int DashboardY = 20; // Dashboard Y position input int FontSize = 8; // Dashboard font size //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ string SymbolList[]; int TotalPairs; CTrade Trade; CPositionInfo PositionInfo; datetime LastDashboardUpdate = 0; // Spread monitoring structure struct SpreadData { string symbol; double spreadInPips; double atrValue; double spreadATRRatio; double spreadScore; datetime disabledUntil; bool isTradeable; bool isActive; datetime lastTradeTime; int tradeAttempts; int successfulTrades; color statusColor; }; SpreadData spreadData[]; // Dashboard messages string DashboardMessages[10];
Zu Beginn bauen wir eine flexible Konfigurationsebene für unseren dynamischen Multi-Pair Expert Advisor auf, indem wir alle benutzerdefinierten Eingaben in einer sauberen und modularen Weise gruppieren. Wir beginnen mit der allgemeinen Handelskontrolle, wie der Liste der zu überwachenden Symbole, dem Risiko pro Handel und einer „Magic Number“ für die Positionsverfolgung. Von dort aus führen wir einen speziellen Spread-Sensitivity-Bereich ein, der den Kern der Ausführungslogik des EA bildet. Diese Eingaben definieren absolute und adaptive Spread-Limits, ATR-normierte Spread-Schwellenwerte, temporäre Symbol-Deaktivierung und Symbol-Ranking-Beschränkungen, sodass der EA intelligent entscheiden kann, welche Märkte zu einem bestimmten Zeitpunkt kosteneffizient genug für den Handel sind. Wichtig ist, dass diese Ebene nicht vorschreibt, wie Trades eingegeben werden, sondern ob ein Symbol zur Teilnahme berechtigt ist, um die Ausführungsqualität für mehrere Instrumente zu gewährleisten.
Darüber hinaus definiert der Code Eingaben auf Strategieebene und unterstützende Infrastrukturen, die nur dann funktionieren, wenn ein Symbol den Spread-Filter passiert hat. Die Handelseinstellungen konfigurieren Indikatorparameter (EMA, RSI), Risikogrenzen (SL/TP, Cooldowns, Maximalpositionen) und optionale ATR-basierte dynamische Ausstiege, die eine kontrollierte und konsistente Handelsausführung ermöglichen. Unterhalb der Eingaben bilden globale Variablen und die SpreadData-Struktur die interne Zustandsmaschine des EA, die Echtzeit-Spread-Metriken, Symbolstatus, Aktivitätsflags, Handelsstatistiken und Dashboard-Visualisierungen verfolgt. Diese Struktur ermöglicht es dem EA, Symbole zu priorisieren, sie dynamisch zu deaktivieren und wieder zu aktivieren sowie das Systemverhalten in einem Live-Dashboard sichtbar zu machen, wodurch der EA sowohl in seiner Logik adaptiv als auch im Betrieb transparent ist.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Split trading pairs SplitString(TradePairs, ",", SymbolList); TotalPairs = ArraySize(SymbolList); if(TotalPairs == 0) { Print("Error: No symbols specified"); return INIT_FAILED; } // Initialize spread data array ArrayResize(spreadData, TotalPairs); // Initialize dashboard messages for(int i = 0; i < 10; i++) DashboardMessages[i] = ""; // Initialize each symbol for(int i = 0; i < TotalPairs; i++) { string symbol = SymbolList[i]; // Validate symbol if(!SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE)) { Print("Warning: Symbol ", symbol, " is not available for trading"); continue; } // Initialize spread data spreadData[i].symbol = symbol; spreadData[i].spreadInPips = 0; spreadData[i].atrValue = 0; spreadData[i].spreadATRRatio = 0; spreadData[i].spreadScore = 0; spreadData[i].disabledUntil = 0; spreadData[i].isTradeable = true; spreadData[i].isActive = true; spreadData[i].lastTradeTime = 0; spreadData[i].tradeAttempts = 0; spreadData[i].successfulTrades = 0; spreadData[i].statusColor = clrGreen; // Subscribe to symbol SymbolSelect(symbol, true); } // Set trade parameters Trade.SetExpertMagicNumber(MagicNumber); Trade.SetDeviationInPoints(10); // Initialize dashboard if(ShowDashboard) CreateDashboard(); AddDashboardMessage("EA Initialized with " + IntegerToString(TotalPairs) + " symbols"); Print("EA Initialized. Total pairs: ", TotalPairs); return INIT_SUCCEEDED; }
Die Funktion OnInit() übernimmt die komplette Startvorbereitung des Expert Advisors, indem sie Symbole, interne Datenstrukturen und Ausführungseinstellungen konfiguriert, bevor der Handel beginnt. Sie beginnt mit der Analyse der benutzerdefinierten Symbolliste und überprüft, ob mindestens ein handelbares Symbol vorhanden ist, und bricht die Initialisierung sicher ab, wenn keines gefunden wird. Die Funktion weist dann die spreadData-Struktur für jedes Symbol zu und initialisiert sie. Dabei werden Standardwerte für Spread-Metriken, Handelsstatus, Statistiken und visuelle Statusindikatoren festgelegt, während gleichzeitig jedes Symbol für Echtzeit-Datenaktualisierungen abonniert wird. Schließlich konfiguriert er die Parameter für die Handelsausführung wie die Magic Number und die Slippage-Toleranz, initialisiert das On-Chart-Dashboard, falls es aktiviert ist, protokolliert eine Startnachricht und bestätigt die erfolgreiche Initialisierung, um sicherzustellen, dass der EA vollständig synchronisiert, überwacht und für den adaptiven Multi-Symbol-Betrieb bereit ist.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Remove dashboard objects if(ShowDashboard) RemoveDashboard(); // Print statistics Print("=== Trading Statistics ==="); for(int i = 0; i < TotalPairs; i++) { Print(spreadData[i].symbol, ": ", spreadData[i].tradeAttempts, " attempts, ", spreadData[i].successfulTrades, " successful trades"); } Print("EA Deinitialized"); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { static int tickCounter = 0; tickCounter++; // Process one symbol per tick (prevents overloading) int symbolIndex = tickCounter % TotalPairs; string symbol = spreadData[symbolIndex].symbol; // Update spread data UpdateSpreadData(symbolIndex); // Check if symbol is tradeable if(!spreadData[symbolIndex].isTradeable || !spreadData[symbolIndex].isActive) return; // Check cooldown period if(TimeCurrent() - spreadData[symbolIndex].lastTradeTime < TradeCooldownSeconds) return; // Execute trading logic ExecuteTradingLogic(symbolIndex); // Update dashboard every 10 ticks if(ShowDashboard && (tickCounter % 10 == 0) && (TimeCurrent() - LastDashboardUpdate >= 1)) { UpdateDashboard(); LastDashboardUpdate = TimeCurrent(); } }
Die Funktion OnDeinit() sorgt für ein sauberes und informatives Herunterfahren des Expert Advisors. Wenn der EA entfernt oder das Terminal geschlossen wird, werden zunächst alle Dashboard-Objekte aus dem Chart gelöscht, um visuelle Überbleibsel zu vermeiden. Anschließend wird eine kurze Zusammenfassung der Handelsleistung für jedes Symbol ausgedruckt, in der die Anzahl der während der Laufzeit aufgezeichneten Handelsversuche und erfolgreichen Ausführungen angegeben ist. Dieser abschließende Protokollierungsschritt sorgt für Transparenz und Post-Run-Diagnose und erleichtert die Bewertung des Verhaltens auf Symbolebene und der Systemeffizienz, bevor der EA vollständig deinitialisiert wird.
Die Funktion OnTick() hingegen definiert den Echtzeit-Ablauf des EA und ist auf Effizienz in einer Multi-Symbol-Umgebung ausgelegt. Anstatt alle Symbole bei jedem Tick zu verarbeiten, wird mithilfe eines Modulo-Zählers ein Symbol pro Tick durchlaufen, wodurch die CPU-Belastung verringert und Ausführungsengpässe vermieden werden. Für jedes ausgewählte Symbol aktualisiert der EA die Spread-Daten, prüft, ob das Symbol aktuell handelbar ist, erzwingt Sperrfristen und führt dann die Handelslogik aus, wenn alle Bedingungen erfüllt sind. Die Aktualisierung des Dashboards erfolgt nicht bei jedem Tick, sondern in regelmäßigen Abständen, sodass die Schnittstelle reaktionsschnell bleibt und die Leistungsstabilität in Szenarien mit hoher Symbolüberwachungsfrequenz erhalten bleibt.
//+------------------------------------------------------------------+ //| Timer function for spread updates | //+------------------------------------------------------------------+ void OnTimer() { // Update all spread data and rank symbols UpdateAllSpreadData(); if(EnableSpreadRanking) RankSymbolsBySpread(); } //+------------------------------------------------------------------+ //| Update spread data for all symbols | //+------------------------------------------------------------------+ void UpdateAllSpreadData() { for(int i = 0; i < TotalPairs; i++) { UpdateSpreadData(i); } } //+------------------------------------------------------------------+ //| Update spread data for specific symbol | //+------------------------------------------------------------------+ void UpdateSpreadData(int index) { string symbol = spreadData[index].symbol; // Get current bid and ask double bid = SymbolInfoDouble(symbol, SYMBOL_BID); double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); if(bid == 0 || ask == 0) return; // Calculate spread in pips double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double spreadPoints = (ask - bid) / point; spreadData[index].spreadInPips = NormalizeDouble(spreadPoints / 10, 1); // Calculate ATR for spread ratio spreadData[index].atrValue = CalculateATR(symbol, PERIOD_H1, 14); // Calculate spread/ATR ratio if(spreadData[index].atrValue > 0) { spreadData[index].spreadATRRatio = NormalizeDouble(spreadData[index].spreadInPips / spreadData[index].atrValue, 3); } // Evaluate tradeability EvaluateTradeability(index); } //+------------------------------------------------------------------+ //| Evaluate if symbol is tradeable based on spread | //+------------------------------------------------------------------+ void EvaluateTradeability(int index) { // Check if symbol is in timeout if(TimeCurrent() < spreadData[index].disabledUntil) { spreadData[index].isTradeable = false; spreadData[index].statusColor = clrOrange; return; } // Check absolute spread limit if(spreadData[index].spreadInPips > MaxAbsoluteSpread) { spreadData[index].isTradeable = false; spreadData[index].disabledUntil = TimeCurrent() + DisableTimeoutSec; AddDashboardMessage(spreadData[index].symbol + " disabled: High spread " + DoubleToString(spreadData[index].spreadInPips, 1)); spreadData[index].statusColor = clrRed; return; } // Check ATR ratio if adaptive filtering is enabled if(UseAdaptiveFilter && spreadData[index].spreadATRRatio > MaxSpreadATRRatio) { spreadData[index].isTradeable = false; spreadData[index].statusColor = clrRed; return; } // All checks passed spreadData[index].isTradeable = true; spreadData[index].statusColor = clrGreen; }
Dieser Block führt ein timergesteuertes Spread-Monitoring-System ein, das unabhängig von der Tickfrequenz arbeitet und eine konsistente und rechtzeitige Auswertung aller Symbole gewährleistet. Die Funktion OnTimer() fungiert als Scheduler, der periodisch die Spread-Daten für jedes konfigurierte Symbol aktualisiert und sie optional nach Spread-Effizienz einstuft, wenn dies aktiviert ist. Dieses Design entkoppelt die Spread-Analyse von den Preis-Ticks, sodass der EA auch in Zeiten geringer Liquidität reaktionsfähig bleibt. Der Aktualisierungsfluss läuft kaskadenförmig über UpdateAllSpreadData() und UpdateSpreadData(), wo Geld-/Briefkurse in Echtzeit gesammelt werden, Spreads in Pips berechnet werden und Volatilitätskontext durch die Berechnung von ATR-Werten hinzugefügt wird, was die Grundlage für die adaptive Spread-Bewertung bildet.
Die Funktion EvaluateTradeability() wendet dann einen mehrstufigen Entscheidungsprozess an, um festzustellen, ob jedes Symbol für den Handel geeignet ist. Es erzwingt zunächst Sperrfristen für zuvor deaktivierte Symbole, um ein schnelles Wiedereintreten unter instabilen Bedingungen zu verhindern. Als Nächstes werden absolute Spread-Limits und adaptive ATR-normierte Schwellenwerte überprüft, wobei Symbole, deren Handel zu kostspielig wird, automatisch deaktiviert und diese Ereignisse zur Transparenz auf dem Dashboard aufgezeichnet werden. Wenn alle Spread-Bedingungen erfüllt sind, wird das Symbol als handelbar markiert und optisch als gesund gekennzeichnet. Damit wird ein robuster Schutzmechanismus vervollständigt, der Symbole dynamisch auf der Grundlage der Ausführungsqualität in Echtzeit und nicht nach statischen Regeln filtert.
//+------------------------------------------------------------------+ //| Rank symbols by spread efficiency | //+------------------------------------------------------------------+ void RankSymbolsBySpread() { // Calculate spread score for each symbol for(int i = 0; i < TotalPairs; i++) { // Lower spread = better score double spreadComponent = 1.0 / (1.0 + spreadData[i].spreadInPips); // Lower ATR ratio = better score double atrComponent = 1.0 / (1.0 + spreadData[i].spreadATRRatio); // Combine components with weights spreadData[i].spreadScore = (0.6 * spreadComponent) + (0.4 * atrComponent); } // Simple bubble sort by score for(int i = 0; i < TotalPairs - 1; i++) { for(int j = i + 1; j < TotalPairs; j++) { if(spreadData[j].spreadScore > spreadData[i].spreadScore) { SpreadData temp = spreadData[i]; spreadData[i] = spreadData[j]; spreadData[j] = temp; } } } // Activate top N symbols for(int i = 0; i < TotalPairs; i++) { spreadData[i].isActive = (i < MaxActiveSymbols); } } //+------------------------------------------------------------------+ //| Execute trading logic for symbol | //+------------------------------------------------------------------+ void ExecuteTradingLogic(int index) { string symbol = spreadData[index].symbol; // Check if symbol already has max positions if(CountOpenPositions(symbol) >= MaxOpenPositions) return; // Get indicator handles int handleEmaFast = iMA(symbol, TradingTimeframe, EMA_Fast_Period, 0, MODE_EMA, PRICE_CLOSE); int handleEmaSlow = iMA(symbol, TradingTimeframe, EMA_Slow_Period, 0, MODE_EMA, PRICE_CLOSE); int handleRSI = iRSI(symbol, TradingTimeframe, RSI_Period, PRICE_CLOSE); if(handleEmaFast == INVALID_HANDLE || handleEmaSlow == INVALID_HANDLE || handleRSI == INVALID_HANDLE) { Print("Error: Failed to create indicator handles for ", symbol); return; } // Get indicator values double emaFast[1], emaSlow[1], rsi[1]; if(CopyBuffer(handleEmaFast, 0, 0, 1, emaFast) < 1) { IndicatorRelease(handleEmaFast); IndicatorRelease(handleEmaSlow); IndicatorRelease(handleRSI); return; } if(CopyBuffer(handleEmaSlow, 0, 0, 1, emaSlow) < 1) { IndicatorRelease(handleEmaFast); IndicatorRelease(handleEmaSlow); IndicatorRelease(handleRSI); return; } if(CopyBuffer(handleRSI, 0, 0, 1, rsi) < 1) { IndicatorRelease(handleEmaFast); IndicatorRelease(handleEmaSlow); IndicatorRelease(handleRSI); return; } // Release indicator handles IndicatorRelease(handleEmaFast); IndicatorRelease(handleEmaSlow); IndicatorRelease(handleRSI); double emaF = emaFast[0]; double emaS = emaSlow[0]; double rsiV = rsi[0]; // Generate trading signals if(emaF > emaS && rsiV < RSI_Oversold) // Buy signal: Fast EMA above Slow EMA and RSI oversold { spreadData[index].tradeAttempts++; double lotSize = CalculateLotSize(symbol); if(lotSize > 0) { if(ExecuteTrade(ORDER_TYPE_BUY, symbol, lotSize, StopLoss_Pips, TakeProfit_Pips)) { spreadData[index].lastTradeTime = TimeCurrent(); spreadData[index].successfulTrades++; AddDashboardMessage("BUY " + symbol + " | Spread: " + DoubleToString(spreadData[index].spreadInPips, 1)); } } } else if(emaF < emaS && rsiV > RSI_Overbought) // Sell signal: Fast EMA below Slow EMA and RSI overbought { spreadData[index].tradeAttempts++; double lotSize = CalculateLotSize(symbol); if(lotSize > 0) { if(ExecuteTrade(ORDER_TYPE_SELL, symbol, lotSize, StopLoss_Pips, TakeProfit_Pips)) { spreadData[index].lastTradeTime = TimeCurrent(); spreadData[index].successfulTrades++; AddDashboardMessage("SELL " + symbol + " | Spread: " + DoubleToString(spreadData[index].spreadInPips, 1)); } } } }
Hier stellen wir eine Spread-Effizienz-Ranking-Maschine vor, die bestimmt, welche Symbole zu einem bestimmten Zeitpunkt am Handel teilnehmen dürfen. In RankSymbolsBySpread() wird jedem Symbol ein zusammengesetzter Spread-Score zugewiesen, der auf zwei Faktoren der Ausführungsqualität basiert: dem absoluten Spread und dem Spread-ATR-Verhältnis. Beide Komponenten werden invertiert, sodass niedrigere Kosten zu höheren Werten führen, und dann unter Verwendung der gewichteten Bedeutung kombiniert, um die rohe Spanne hervorzuheben und gleichzeitig den Volatilitätskontext zu berücksichtigen. Nach der Berechnung der Punkte werden die Symbole in absteigender Reihenfolge sortiert, sodass die kosteneffizientesten Instrumente ganz oben auf der Prioritätenliste stehen.
Nach dem Ranking wendet der EA einen dynamischen Aktivierungsfilter an, indem er nur die obersten MaxActiveSymbols aktiviert und die übrigen als inaktiv markiert. Dieser Mechanismus stellt sicher, dass der EA keine Ressourcen oder Kapital für Symbole mit minderwertigen Ausführungsbedingungen verschwendet, selbst wenn diese ansonsten gültig sind. Anstatt Symbole dauerhaft auszuschließen, bewertet dieses System sie kontinuierlich neu und mischt sie um, wenn sich die Spreads entwickeln, sodass zuvor inaktive Symbole wieder in die Rotation aufgenommen werden können, wenn sich ihre Ausführungsqualität verbessert. So entsteht ein sich selbst ausgleichendes, anpassungsfähiges Universum handelbarer Symbole, das ausschließlich von der Kosteneffizienz in Echtzeit bestimmt wird.
Der zweite Teil, ExecuteTradingLogic(), wird nur für Symbole ausgeführt, die alle Spread- und Aktivierungsfilter bestanden haben, wodurch die Ausführungsqualität sauber von der Strategielogik getrennt wird. Es ruft die erforderlichen EMA- und RSI-Indikatoren ab, validiert die Datenverfügbarkeit und generiert dann Handelssignale auf der Grundlage von Trendausrichtung und Momentum-Erschöpfung. Wenn ein Signal bestätigt wird, zeichnet der EA Handelsversuche auf, berechnet die Positionsgröße, führt den Handel aus und aktualisiert die Performance-Metriken auf Symbolebene sowie die Dashboard-Meldungen. Diese Struktur stellt sicher, dass Handelsentscheidungen nur auf die bestplatzierten Symbole angewendet werden, was die Kernphilosophie des adaptiven, ausführungsorientierten Multi-Pair-Handels unterstreicht.
//+------------------------------------------------------------------+ //| Execute trade with CTrade | //+------------------------------------------------------------------+ bool ExecuteTrade(ENUM_ORDER_TYPE tradeType, string symbol, double lotSize, int stopLossPips, int takeProfitPips) { // Get symbol info double point = SymbolInfoDouble(symbol, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); // Get current price double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(symbol, SYMBOL_BID); double price = (tradeType == ORDER_TYPE_BUY) ? ask : bid; // Calculate pip size for different instruments double pipSize = CalculatePipSize(symbol, digits, point); // Use ATR for dynamic SL/TP if enabled double slDistance = 0, tpDistance = 0; if(UseATR_SL_TP) { double atr = iATR(symbol, TradingTimeframe, 14); if(atr > 0) { slDistance = atr * ATR_SL_Multiplier; tpDistance = atr * ATR_TP_Multiplier; } } // Fallback to fixed pips if ATR not used or failed if(slDistance == 0) slDistance = stopLossPips * pipSize; if(tpDistance == 0) tpDistance = takeProfitPips * pipSize; // Calculate SL and TP prices double sl = 0, tp = 0; if(slDistance > 0) { sl = (tradeType == ORDER_TYPE_BUY) ? price - slDistance : price + slDistance; sl = NormalizeDouble(sl, digits); } if(tpDistance > 0) { tp = (tradeType == ORDER_TYPE_BUY) ? price + tpDistance : price - tpDistance; tp = NormalizeDouble(tp, digits); } // Execute trade with CTrade bool success = false; if(tradeType == ORDER_TYPE_BUY) { success = Trade.Buy(lotSize, symbol, price, sl, tp, "Adaptive Spread EA"); } else if(tradeType == ORDER_TYPE_SELL) { success = Trade.Sell(lotSize, symbol, price, sl, tp, "Adaptive Spread EA"); } if(success) { PrintFormat("%s %s | Lot: %.2f | Price: %.5f | SL: %.5f | TP: %.5f | Spread: %.1f", EnumToString(tradeType), symbol, lotSize, price, sl, tp, SymbolInfoDouble(symbol, SYMBOL_ASK) - SymbolInfoDouble(symbol, SYMBOL_BID)); return true; } else { PrintFormat("Failed to open %s on %s | Error: %d", EnumToString(tradeType), symbol, GetLastError()); return false; } } //+------------------------------------------------------------------+ //| Calculate pip size for different instruments | //+------------------------------------------------------------------+ double CalculatePipSize(string symbol, int digits, double point) { // Detect pip size automatically if(StringFind(symbol, "JPY") != -1) // JPY pairs return (digits == 3) ? point * 10 : point; else if(StringFind(symbol, "XAU") != -1 || StringFind(symbol, "GOLD") != -1) // Metals return 0.10; else if(StringFind(symbol, "BTC") != -1 || StringFind(symbol, "ETH") != -1) // Cryptos return point * 100.0; else if(StringFind(symbol, "US") != -1 && digits <= 2) // Indices return point; else return (digits == 3 || digits == 5) ? point * 10 : point; // Default Forex } //+------------------------------------------------------------------+ //| Calculate position size based on risk | //+------------------------------------------------------------------+ double CalculateLotSize(string symbol) { double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); if(accountBalance <= 0) return minLot; // Simple lot calculation based on risk percentage double riskAmount = accountBalance * (RiskPerTrade / 100.0); double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); if(tickValue <= 0) { // Fallback calculation double contractSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_CONTRACT_SIZE); tickValue = (SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE) * contractSize) / SymbolInfoDouble(symbol, SYMBOL_POINT); } double pipSize = CalculatePipSize(symbol, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS), SymbolInfoDouble(symbol, SYMBOL_POINT)); double stopLossPoints = StopLoss_Pips * 10; // Convert pips to points if(stopLossPoints > 0 && tickValue > 0) { double lotSize = riskAmount / (stopLossPoints * tickValue); lotSize = NormalizeDouble(lotSize, 2); // Apply lot size limits lotSize = MathMax(lotSize, minLot); lotSize = MathMin(lotSize, maxLot); lotSize = MathRound(lotSize / lotStep) * lotStep; return lotSize; } return minLot; }
In diesem Abschnitt definieren wir die Handelsausführungs- und Risikokontrollschicht des EA, beginnend mit ExecuteTrade(), die standardisiert, wie Aufträge unabhängig vom Symboltyp eröffnet werden. Die Funktion ruft zunächst symbolspezifische Preisangaben ab und ermittelt den korrekten Ausführungskurs auf der Grundlage der Handelsrichtung. Die Pip-Größe wird dann dynamisch berechnet, um Forex-Paare, Metalle, Indizes und Kryptowährungen ohne hart kodierte Annahmen zu unterstützen. Stop-Loss- und Take-Profit-Abstände werden entweder von ATR-basierten Volatilitätsmessungen oder von festen Pip-Werten abgeleitet, um die Robustheit bei unterschiedlichen Marktbedingungen zu gewährleisten. Sobald die Preisniveaus auf Symbolgenauigkeit normalisiert sind, werden die Trades mit der CTrade-Klasse ausgeführt, wobei sowohl erfolgreiche als auch fehlgeschlagene Ausführungen detailliert protokolliert werden, um Transparenz und Fehlersuche zu gewährleisten.
Die unterstützenden Funktionen CalculatePipSize() und CalculateLotSize() sorgen für eine konsistente Positionsgrößenbestimmung und ein einheitliches Risikomanagement bei heterogenen Instrumenten. CalculatePipSize() passt Pip-Definitionen automatisch auf der Grundlage von Symbolcharakteristika an, sodass der EA gemischte Anlageklassen präzise handeln kann. CalculateLotSize() berechnet dann die Positionsgröße anhand des Kontostands und des Risikoprozentsatzes, wobei das monetäre Risiko in ein Volumen umgerechnet wird und gleichzeitig die Beschränkungen des Brokers wie Mindest-, Höchst- und Schrittgrößen beachtet werden. Zusammen gewährleisten diese Funktionen, dass jeder Handel mit kontrolliertem Risiko, instrumentenbezogener Präzision und konsistentem Verhalten ausgeführt wird – und verstärken so das adaptive Multi-Symbol-Ausführungssystem des EA.
void UpdateDashboard() { if(!ShowDashboard) return; // Update ranking for(int i = 0; i < MathMin(MaxActiveSymbols + 2, TotalPairs); i++) { string objName = "Dashboard_Rank_" + IntegerToString(i); string status = spreadData[i].isActive ? "Yes" : "No"; string text = IntegerToString(i+1) + ". " + spreadData[i].symbol + " | Spread: " + DoubleToString(spreadData[i].spreadInPips, 1) + " | Score: " + DoubleToString(spreadData[i].spreadScore, 3) + " | Active: " + status; ObjectSetString(0, objName, OBJPROP_TEXT, text); ObjectSetInteger(0, objName, OBJPROP_COLOR, spreadData[i].statusColor); } // Update messages for(int i = 0; i < 10; i++) { string objName = "Dashboard_Msg_" + IntegerToString(i); ObjectSetString(0, objName, OBJPROP_TEXT, DashboardMessages[i]); } } void AddDashboardMessage(string message) { // Shift messages up for(int i = 9; i > 0; i--) { DashboardMessages[i] = DashboardMessages[i-1]; } // Add new message at the beginning DashboardMessages[0] = TimeToString(TimeCurrent(), TIME_SECONDS) + ": " + message; } //+------------------------------------------------------------------+ //| Chart Event Handler | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) { if(ShowDashboard && (id == CHARTEVENT_CHART_CHANGE || id == CHARTEVENT_CLICK)) { UpdateDashboard(); } } //+------------------------------------------------------------------+ //| Dashboard Functions | //+------------------------------------------------------------------+ void CreateDashboard() { // Create main dashboard background ObjectCreate(0, "Dashboard_BG", OBJ_RECTANGLE_LABEL, 0, 0, 0); ObjectSetInteger(0, "Dashboard_BG", OBJPROP_XDISTANCE, DashboardX); ObjectSetInteger(0, "Dashboard_BG", OBJPROP_YDISTANCE, DashboardY); ObjectSetInteger(0, "Dashboard_BG", OBJPROP_XSIZE, 400); ObjectSetInteger(0, "Dashboard_BG", OBJPROP_YSIZE, 350); ObjectSetInteger(0, "Dashboard_BG", OBJPROP_BGCOLOR, DashboardBGColor); ObjectSetInteger(0, "Dashboard_BG", OBJPROP_BORDER_TYPE, BORDER_FLAT); ObjectSetInteger(0, "Dashboard_BG", OBJPROP_BORDER_COLOR, clrGray); ObjectSetInteger(0, "Dashboard_BG", OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, "Dashboard_BG", OBJPROP_HIDDEN, true); // Create title ObjectCreate(0, "Dashboard_Title", OBJ_LABEL, 0, 0, 0); ObjectSetString(0, "Dashboard_Title", OBJPROP_TEXT, "=== Adaptive Spread EA ==="); ObjectSetInteger(0, "Dashboard_Title", OBJPROP_XDISTANCE, DashboardX + 10); ObjectSetInteger(0, "Dashboard_Title", OBJPROP_YDISTANCE, DashboardY + 10); ObjectSetInteger(0, "Dashboard_Title", OBJPROP_COLOR, clrYellow); ObjectSetInteger(0, "Dashboard_Title", OBJPROP_FONTSIZE, FontSize + 2); ObjectSetString(0, "Dashboard_Title", OBJPROP_FONT, "Consolas"); ObjectSetInteger(0, "Dashboard_Title", OBJPROP_CORNER, CORNER_LEFT_UPPER); // Create ranking header ObjectCreate(0, "Dashboard_RankHeader", OBJ_LABEL, 0, 0, 0); ObjectSetString(0, "Dashboard_RankHeader", OBJPROP_TEXT, "=== Symbol Ranking ==="); ObjectSetInteger(0, "Dashboard_RankHeader", OBJPROP_XDISTANCE, DashboardX + 10); ObjectSetInteger(0, "Dashboard_RankHeader", OBJPROP_YDISTANCE, DashboardY + 35); ObjectSetInteger(0, "Dashboard_RankHeader", OBJPROP_COLOR, clrYellow); ObjectSetInteger(0, "Dashboard_RankHeader", OBJPROP_FONTSIZE, FontSize); ObjectSetString(0, "Dashboard_RankHeader", OBJPROP_FONT, "Consolas"); ObjectSetInteger(0, "Dashboard_RankHeader", OBJPROP_CORNER, CORNER_LEFT_UPPER); // Create symbol ranking labels for(int i = 0; i < MaxActiveSymbols + 2; i++) { string objName = "Dashboard_Rank_" + IntegerToString(i); ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, DashboardX + 10); ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, DashboardY + 55 + (i * 20)); ObjectSetInteger(0, objName, OBJPROP_COLOR, DashboardTextColor); ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, FontSize); ObjectSetString(0, objName, OBJPROP_FONT, "Consolas"); ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); } // Create messages header ObjectCreate(0, "Dashboard_MsgHeader", OBJ_LABEL, 0, 0, 0); ObjectSetString(0, "Dashboard_MsgHeader", OBJPROP_TEXT, "=== Messages ==="); ObjectSetInteger(0, "Dashboard_MsgHeader", OBJPROP_XDISTANCE, DashboardX + 10); ObjectSetInteger(0, "Dashboard_MsgHeader", OBJPROP_YDISTANCE, DashboardY + 180); ObjectSetInteger(0, "Dashboard_MsgHeader", OBJPROP_COLOR, clrYellow); ObjectSetInteger(0, "Dashboard_MsgHeader", OBJPROP_FONTSIZE, FontSize); ObjectSetString(0, "Dashboard_MsgHeader", OBJPROP_FONT, "Consolas"); ObjectSetInteger(0, "Dashboard_MsgHeader", OBJPROP_CORNER, CORNER_LEFT_UPPER); // Create message labels for(int i = 0; i < 10; i++) { string objName = "Dashboard_Msg_" + IntegerToString(i); ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, DashboardX + 10); ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, DashboardY + 200 + (i * 15)); ObjectSetInteger(0, objName, OBJPROP_COLOR, DashboardTextColor); ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, FontSize); ObjectSetString(0, objName, OBJPROP_FONT, "Consolas"); ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); } } void RemoveDashboard() { ObjectsDeleteAll(0, "Dashboard_"); }
Dieser Code implementiert eine visuelle Echtzeit-Dashboard-Ebene, die die interne Entscheidungsfindung des EA direkt im Chart sichtbar macht. Die Funktion UpdateDashboard() aktualisiert die Rangliste der Symbole, indem sie die Spread-Werte, die Spread-Effizienz-Scores und den aktiven Status für die am besten platzierten Symbole anzeigt und dabei farbliche Hinweise verwendet, die den aktuellen Status der Handelbarkeit jedes Symbols widerspiegeln. Neben den Ranglistendaten zeigt das Dashboard auch ein fortlaufendes Meldungsprotokoll an, in dem wichtige Systemereignisse wie die Deaktivierung von Symbolen oder die Ausführung von Trades erfasst werden. Die Hilfsfunktion AddDashboardMessage() pflegt dieses Protokoll, indem sie ältere Nachrichten nach unten verschiebt und neue Einträge mit einem Zeitstempel versieht, sodass die aktuellsten und wichtigsten Informationen immer auf einen Blick sichtbar sind.
Die übrigen Funktionen behandeln den Lebenszyklus und die Interaktivität des Dashboards. OnChartEvent() stellt sicher, dass das Dashboard mit Chartaktualisierungen und Benutzerinteraktionen synchronisiert bleibt, indem es eine Aktualisierung erzwingt, wenn sich das Chart ändert oder angeklickt wird. CreateDashboard() erstellt die gesamte Oberfläche von Grund auf neu, einschließlich des Hintergrundpanels, der Abschnittsüberschriften, der Beschriftungen für die Rangfolge der Symbole und der Nachrichtenzeilen, die alle so positioniert und gestaltet sind, dass sie übersichtlich sind und das Chart möglichst wenig stören. Schließlich bietet RemoveDashboard() einen sauberen Abrissmechanismus, indem es alle dashboardbezogenen Objekte löscht, wenn der EA entfernt wird. So wird sichergestellt, dass keine visuellen Artefakte zurückbleiben und die Chartumgebung aufgeräumt und professionell bleibt.
Backtest-Ergebnisse
Die Tests wurden über ein etwa zweimonatiges Testfenster vom 19. November 2025 bis zum 17. Januar 2026 mit den folgenden Einstellungen durchgeführt:

Nun die Equity-Kurve und die Backtest-Ergebnisse:


Schlussfolgerung
Zusammenfassend lässt sich sagen, dass wir ein Adaptive Spread-Sensitivity Framework entwickelt und implementiert haben, das als intelligente Ausführungsschicht innerhalb eines dynamischen Multi-Pair-EA arbeitet. Das System überwacht kontinuierlich die Spreads in Echtzeit, normalisiert sie anhand des Volatilitätskontexts, ordnet die Symbole nach Kosteneffizienz und aktiviert oder deaktiviert die Instrumente dynamisch auf der Grundlage der aktuellen Ausführungsqualität. Durch die Trennung der Spread-Bewertung von der Handelslogik haben wir sichergestellt, dass die Symbolauswahl, die Priorisierung und die Schutzmechanismen anpassungsfähig, leichtgewichtig und skalierbar bleiben, sodass der EA den Fokus in hoher Frequenz zwischen den Symbolen wechseln kann, ohne die zugrunde liegenden Strategieregeln zu ändern.
Zusammenfassend lässt sich sagen, dass dieser Ansatz den Händlern ein leistungsfähiges Instrument an die Hand gibt, um die Handelskosten und das Ausführungsrisiko in schnelllebigen Multi-Symbol-Umgebungen zu kontrollieren. Anstatt alle Paare blind zu handeln, konzentriert der EA seine Aktivitäten auf intelligente Weise auf die effizientesten Märkte zu einem bestimmten Zeitpunkt, reduziert Slippage, vermeidet ungünstige Spread-Bedingungen und verbessert die allgemeine Handelsqualität. Für die Händler bedeutet dies eine sauberere Ausführung, eine bessere Kapitaleffizienz und ein widerstandsfähigeres automatisiertes System, das sich an die sich verändernde Marktmikrostruktur anpasst, anstatt durch statische Annahmen eingeschränkt zu sein.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/20371
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
Vom Einsteiger zum Experten: Statistische Validierung von Angebots- und Nachfragezonen
Eine alternative Log-datei mit der Verwendung der HTML und CSS
Erstellen von nutzerdefinierten Indikatoren in MQL5 (Teil 6): Weiterentwicklung der RSI-Berechnungen mit Glättung, Farbwechsel und Multi-Timeframe-Unterstützung
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.