English Русский 日本語
preview
Formulierung eines dynamischen Multi-Paar-EA (Teil 4): Volatilität und Risikoanpassung

Formulierung eines dynamischen Multi-Paar-EA (Teil 4): Volatilität und Risikoanpassung

MetaTrader 5Beispiele |
36 3
Hlomohang John Borotho
Hlomohang John Borotho

Einführung

Eine der Herausforderungen beim Handel mit mehreren Paaren ist die uneinheitliche Performance, die durch die unterschiedliche Volatilität der verschiedenen Währungspaare verursacht wird. Wie im vorangegangenen Artikel beschrieben, kann eine Strategie, die beim EURUSD gut abschneidet, beim GBPJPY aufgrund der unterschiedlichen Volatilitätsprofile unterdurchschnittlich abschneiden oder zu riskant sein. Die Verwendung fester Losgrößen oder statischer Stop-Losses kann in volatilen Märkten zu übergroßen Positionen führen und in stabilen Märkten zu verpassten Chancen. Diese mangelnde Anpassungsfähigkeit führt häufig zu ungleichmäßiger Risikoexponierung, erhöhten Drawdowns und unvorhersehbaren Ergebnissen, insbesondere bei einschneidenden Nachrichtenereignissen oder plötzlichen Marktveränderungen.

Um dieses Problem zu lösen, führen wir eine Volatilitäts- und Risikoanpassung innerhalb des EA ein. Durch die Einbeziehung von Tools wie der Average True Range (ATR) und der dynamischen risikobasierten Größenbestimmung passt der EA die Handelsparameter automatisch an die aktuellen Marktbedingungen an. Dadurch wird sichergestellt, dass jede Position proportional zu ihrer Volatilität ausbalanciert ist, was zu einem konsistenten Risikomanagement führt und die Performance des EA über alle gehandelten Paare hinweg verbessert.



EA-Entwicklungsplan

Handelslogik:

1. Multi-Symbol-Handler:

  • Parser für Symbollisten
  • Vor-Symbol-Datenkontrolle
  • Verwaltung gleichzeitiger Positionen


2. Eintrittsbedingung:

3. Volatilitätsbasierte Risikoeinstufungen:

4. Größe der Position:

Risk Amount = Account Equity * Risk %
Position Size = Risk Amount / (SL Distance * Point Value)

5. V-Stopp Ziel-Identifizierung:

[  Current Price  ]
        |
        v
[ Resistance Area ]  <-- Previous V-Stop Upper (TP for sells)
        |
        v
[  Current Price  ]
        |
        v
[  Support Area   ]  <-- Previous V-Stop Lower (TP for buys)

6. Zeitplan für den Lebenszyklus des Handels:



Die ersten Schritte

//+------------------------------------------------------------------+
//|                               MultiSymbolVolatilityTraderEA.mq5  |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Trade/Trade.mqh>

Wie üblich beginnen wir mit dem Import der wesentlichen Handelsbibliothek, die unser Expert Advisor benötigt, um Aufträge auszuführen und Positionen zu verwalten.

//--- Input settings
input string   Symbols = "XAUUSD,GBPUSD,USDCAD,USDJPY";  // Symbols to trade
input int      RSI_Period = 14;                           // RSI Period
input double   RSI_Overbought = 70.0;                     // RSI Overbought Level
input double   RSI_Oversold = 30.0;                       // RSI Oversold Level
input uint     ATR_Period = 14;                           // ATR Period for Volatility
input double   ATR_Multiplier = 2.0;                      // ATR Multiplier for SL
input double   RiskPercent_High = 0.02;                   // Risk % High Volatility
input double   RiskPercent_Mod = 0.01;                    // Risk % Moderate Volatility
input double   RiskPercent_Low = 0.005;                   // Risk % Low Volatility
input int      Min_Bars = 50;                             // Minimum Bars Required
input double   In_Lot = 0.01;                             // Default lot size
input int      StopLoss = 100;                            // SL in points
input int      TakeProfit = 100;                          // TP in points

In diesem Block legen wir die Eingabeeinstellungen fest, die die Funktionsweise des Expert Advisors für mehrere Symbole konfigurieren. Die Eingabe von Symbolen ermöglicht es dem Händler, die zu handelnden Instrumente festzulegen, während RSI-bezogene Eingaben (RSI_Period, RSI_Overbought und RSI_Oversold) verwendet werden, um potenzielle Einstiegspunkte auf der Grundlage von überkauften oder überverkauften Bedingungen zu identifizieren. Die Volatilität wird durch die Average True Range (ATR) berücksichtigt, mit einstellbaren Parametern für die Periodenlänge und einem Multiplikator zur dynamischen Skalierung des Stop Loss.

Der EA passt sein Risiko auf der Grundlage des Volatilitätsniveaus an, indem er unterschiedliche Risikoprozentsätze für hohe, moderate und niedrige Volatilitätsbedingungen verwendet. Zu den zusätzlichen Eingaben gehören Min_Bars zur Sicherstellung ausreichender historischer Daten, ein Standardwert für die Losgröße In_Lot und auf Festpunkten basierende Werte für StopLoss und TakeProfit, die als Fallback fungieren, wenn dynamische Levels nicht angewendet werden.

//--- Global variables
string symb_List[];
int    Num_symbs = 0;
int    ATR_Handles[];
int    RSI_Handles[];
double Prev_ATR[];
double Prev_RSI[];
datetime LastBarTime[];
CTrade trade;

In diesem Abschnitt werden die globalen Variablen deklariert, die im gesamten Expert Advisor verwendet werden. Symb_List[] ist ein Array, das die Liste der zu handelnden Symbole speichert, während Num_symbs die Gesamtzahl dieser Symbole enthält. Arrays wie ATR_Handles[] und RSI_Handles[] verwalten Indikator-Handles für ATR- und RSI-Berechnungen, sodass der EA mehrere Symbole gleichzeitig verarbeiten kann.

Prev_ATR[] und Prev_RSI[] speichern die jüngsten Werte dieser Indikatoren für jedes Symbol, die in der Entscheidungslogik verwendet werden. LastBarTime[] verfolgt den zuletzt verarbeiteten Balken für jedes Symbol, um doppelte Operationen für dieselbe Kerze zu vermeiden. Schließlich bietet das CTrade Zugang zu den in MQL5 integrierten Handelsfunktionen, die die Auftragsausführung und das Positionsmanagement ermöglichen.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    //--- Split symbol list
    ushort separator = StringGetCharacter(",", 0);
    StringSplit(Symbols, separator, symb_List);
    Num_symbs = ArraySize(symb_List);
    
    //--- Resize arrays
    ArrayResize(ATR_Handles, Num_symbs);
    ArrayResize(RSI_Handles, Num_symbs);
    ArrayResize(Prev_ATR, Num_symbs);
    ArrayResize(Prev_RSI, Num_symbs);
    ArrayResize(LastBarTime, Num_symbs);
    ArrayInitialize(Prev_ATR, 0.0);
    ArrayInitialize(Prev_RSI, 50.0);
    ArrayInitialize(LastBarTime, 0);
    
    //--- Create indicator handles
    for(int i = 0; i < Num_symbs; i++)
    {
        string symbol = symb_List[i];
        ATR_Handles[i] = iATR(symbol, PERIOD_CURRENT, ATR_Period);
        RSI_Handles[i] = iRSI(symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE);
        
        if(ATR_Handles[i] == INVALID_HANDLE || RSI_Handles[i] == INVALID_HANDLE)
        {
            Print("Error creating indicator handles for ", symbol, " - Error: ", GetLastError());
            return INIT_FAILED;
        }
    }
    
    return INIT_SUCCEEDED;
}

Die Funktion OnInit() ist für die Initialisierung des Expert Advisors verantwortlich, wenn er in den Chart geladen wird. Es beginnt mit der Aufteilung der kommagetrennten Eingabe Symbols in ein Array symb_List und berechnet die Gesamtzahl der zu verwaltenden Symbole. Anschließend wird die Größe mehrerer globaler Arrays, z. B. für ATR- und RSI-Handles, frühere Indikatorwerte und die Zeit des letzten verarbeiteten Balkens, angepasst und initialisiert, um sicherzustellen, dass jedes Symbol über einen eigenen Speicherplatz und ein eigenes Tracking verfügt. Die Anfangswerte werden festgelegt, um ein undefiniertes Verhalten während des ersten Ausführungszyklus des EA zu verhindern.

Als Nächstes durchläuft die Funktion jedes Symbol und erstellt Indikator-Handles für ATR und RSI unter Verwendung von iATR bzw. iRSI. Diese Handles sind für das Abrufen von Echtzeit-Indikatorwerten bei Handelsgeschäften unerlässlich. Wenn eine der Handle-Erstellungen fehlschlägt (d.h. INVALID_HANDLE zurückgibt), wird eine Fehlermeldung ausgegeben und die Initialisierung wird durch Rückgabe von INIT_FAILED abgebrochen. Wenn alle Indikatoren erfolgreich eingerichtet wurden, gibt die Funktion INIT_SUCCEEDED zurück und signalisiert, dass der EA bereit ist, mit der Ausführung zu beginnen.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    //--- Release indicator handles
    for(int i = 0; i < Num_symbs; i++)
    {
        if(ATR_Handles[i] != INVALID_HANDLE) 
            IndicatorRelease(ATR_Handles[i]);
        if(RSI_Handles[i] != INVALID_HANDLE) 
            IndicatorRelease(RSI_Handles[i]);
    }
}

Die Funktion OnDeinit() wird ausgelöst, wenn der Expert Advisor aus dem Chart entfernt oder reinitialisiert wird. Sein Hauptzweck besteht darin, Ressourcen aufzuräumen, indem er die mit jedem Symbol verbundenen Indikator-Handles freigibt. Durch den Aufruf von IndicatorRelease() für ATR- und RSI-Handles nur dann, wenn sie gültig sind, wird sichergestellt, dass der Systemspeicher ordnungsgemäß freigegeben wird, wodurch Lecks oder unnötiger Ressourcenverbrauch vermieden werden. Dies trägt zur Stabilität der Plattform bei, insbesondere wenn mehrere EAs oder Indikatoren ausgeführt werden.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    for(int i = 0; i < Num_symbs; i++)
    {
        string symbol = symb_List[i];
        
        //--- Check for new bar
        datetime currentBarTime = iTime(symbol, PERIOD_CURRENT, 0);
        if(LastBarTime[i] == currentBarTime) continue;
        LastBarTime[i] = currentBarTime;
        
        //--- Get indicator values
        double atr[2] = {0.0, 0.0};
        double rsi[2] = {50.0, 50.0};
        
        if(CopyBuffer(ATR_Handles[i], 0, 1, 2, atr) < 2 || 
           CopyBuffer(RSI_Handles[i], 0, 1, 2, rsi) < 2)
            continue;
        
        Prev_ATR[i] = atr[0];  // Previous bar's ATR
        Prev_RSI[i] = rsi[0];  // Previous bar's RSI
        
        //--- Get current prices
        MqlTick lastTick;
        if(!SymbolInfoTick(symbol, lastTick)) continue;
        double ask = lastTick.ask;
        double bid = lastTick.bid;
        
        //--- Check existing positions
        bool hasLong = false, hasShort = false;
        CheckExistingPositions(symbol, hasLong, hasShort);
        
        //--- Calculate volatility-based risk
        double riskPercent = CalculateRiskLevel(symbol);
        
        //--- Get V-Stop levels
        double vStopUpper = CalculateVStop(symbol, 1, true);  // Previous bar's upper band
        double vStopLower = CalculateVStop(symbol, 1, false); // Previous bar's lower band
        
        //--- Trade Entry Logic
        if(!hasLong && !hasShort)
        {
            //--- Sell signal: RSI overbought + price below V-Stop upper band
            if(rsi[0] > RSI_Overbought && bid < vStopUpper)
            {
                double tp = GetProfitTarget(symbol, false);  // Previous V-Stop level
                double sl = ask + ATR_Multiplier * atr[0];
                double lots = CalculatePositionSize(symbol, riskPercent, sl, ask);
                
                if(lots > 0)
                    ExecuteTrade(ORDER_TYPE_SELL, symbol);
            }
            //--- Buy signal: RSI oversold + price above V-Stop lower band
            else if(rsi[0] < RSI_Oversold && ask > vStopLower)
            {
                double tp = GetProfitTarget(symbol, true);   // Previous V-Stop level
                double sl = bid - ATR_Multiplier * atr[0];
                double lots = CalculatePositionSize(symbol, riskPercent, sl, bid);
                
                if(lots > 0)
                    ExecuteTrade(ORDER_TYPE_BUY, symbol);
            }
        }
        
        //--- Trailing Stop Logic
        UpdateTrailingStops(symbol, atr[0]);
    }
}

Die Funktion OnTick() wird jedes Mal ausgeführt, wenn der Markt aktualisiert wird, und durchläuft jedes Symbol in der Handelsliste, um Echtzeitanalysen und Handelsmanagement durchzuführen. Er prüft zunächst, ob sich ein neuer Balken für das Symbol gebildet hat, indem er den Zeitstempel des aktuellen Balkens mit dem des letzten aufgezeichneten Balkens vergleicht. Wenn es sich um einen neuen Balken handelt, werden die letzten ATR- und RSI-Werte mit CopyBuffer() abgerufen und in den globalen Arrays für die Entscheidungsfindung gespeichert. Aktuelle Geld- und Briefkurse werden auch mit SymbolInfoTick() abgefragt, um genaue Ein- und Ausstiegslevel zu gewährleisten.

Als Nächstes wird mit Hilfe der Funktion CheckExistingPositions() überprüft, ob für das aktuelle Symbol bereits Long- oder Short-Positionen offen sind. Anschließend wird mit CalculateRiskLevel() das angemessene Risikoniveau auf der Grundlage der Volatilität des Symbols berechnet und die letzten V-Stop-Niveaus bestimmt, um die Einstiegs- und Nachlauflogik zu steuern. Auf der Grundlage dieser Informationen wendet der EA seine Regeln für den Handelseinstieg an: Ein Verkauf wird ausgelöst, wenn der RSI überkaufte Bedingungen anzeigt und der Kurs unter den oberen V-Stopp fällt, während ein Kauf ausgelöst wird, wenn der RSI überverkaufte Bedingungen anzeigt und der Kurs über den unteren V-Stopp steigt. In beiden Fällen werden dynamische Stop-Loss- und Take-Profit-Levels unter Verwendung von ATR und V-Stop berechnet, und die Positionsgröße wird an das definierte Risikoniveau angepasst.

Schließlich ruft der EA unabhängig davon, ob ein neuer Handel eröffnet wird, UpdateTrailingStops() auf, um alle offenen Positionen zu verwalten. Diese Funktion passt die Stop-Loss-Werte an die neuesten Volatilitätsdaten an und hilft so, Gewinne zu sichern und Verluste zu begrenzen, wenn sich die Marktbedingungen ändern. Dieser dynamische Ansatz sorgt dafür, dass die Strategie über mehrere Symbole hinweg in Echtzeit reaktionsfähig und anpassungsfähig bleibt.

//+------------------------------------------------------------------+
//| Execute trade with risk parameters                               |
//+------------------------------------------------------------------+
void ExecuteTrade(ENUM_ORDER_TYPE tradeType, string symbol)
{
   double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
   double price = (tradeType == ORDER_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_ASK) :
                                                SymbolInfoDouble(symbol, SYMBOL_BID);

   // Convert StopLoss and TakeProfit from pips to actual price distances
   double sl_distance = StopLoss * point;
   double tp_distance = TakeProfit * point;
   
   double sl = (tradeType == ORDER_TYPE_BUY) ? price - sl_distance :
                                             price + sl_distance;
   
   double tp = (tradeType == ORDER_TYPE_BUY) ? price + tp_distance :
                                             price - tp_distance;

   trade.PositionOpen(symbol, tradeType, In_Lot, price, sl, tp, NULL);
}

Die Funktion ExecuteTrade() sorgt für die tatsächliche Ausführung eines Handels auf der Grundlage der angegebenen Auftragsart (Kauf oder Verkauf) und des Symbols. Zunächst wird der richtige Einstiegskurs ermittelt, wobei je nach Handelsrichtung entweder der aktuelle Geld- oder Briefkurs verwendet wird. Es berechnet dann die Stop-Loss- und Take-Profit-Levels, indem es die nutzerdefinierten Punktwerte in tatsächliche Preisabstände unter Verwendung der Punktgröße des Symbols umwandelt. Je nachdem, ob es sich um einen Kauf- oder Verkaufshandel handelt, werden SL und TP in die entsprechende Richtung gesetzt, und schließlich wird das CTrade-Objekt verwendet, um eine Position mit der festgelegten Losgröße und den Preisparametern zu eröffnen.

//+------------------------------------------------------------------+
//| Calculate V-Stop levels                                          |
//+------------------------------------------------------------------+
double CalculateVStop(string symbol, int shift, bool isUpper)
{
    double atr[1];
    double high[1], low[1], close[1];
    
    if(CopyBuffer(ATR_Handles[GetSymbolIndex(symbol)], 0, shift, 1, atr) < 1 ||
       CopyHigh(symbol, PERIOD_CURRENT, shift, 1, high) < 1 ||
       CopyLow(symbol, PERIOD_CURRENT, shift, 1, low) < 1 ||
       CopyClose(symbol, PERIOD_CURRENT, shift, 1, close) < 1)
        return 0.0;
    
    double price = (high[0] + low[0] + close[0]) / 3.0;  // Typical price
    return isUpper ? price + ATR_Multiplier * atr[0] : price - ATR_Multiplier * atr[0];
}

Die Funktion CalculateVStop() berechnet den dynamischen V-Stop-Level für ein bestimmtes Symbol und eine Taktverschiebung und passt ihn auf der Grundlage der Volatilität an. Sie ruft die ATR-, Hoch-, Tief- und Schlusskurse für den angegebenen Balken mithilfe der CopyBuffer()- und Preisreihenfunktionen ab. Anschließend wird der typische Preis als Durchschnitt aus Höchst-, Tiefst- und Schlusskurs berechnet. Je nachdem, ob der obere oder untere V-Stop benötigt wird (isUpper-Flag), wird ein Vielfaches der ATR zu diesem typischen Preis addiert oder subtrahiert, um ein volatilitätsangepasstes Unterstützungs- oder Widerstandsniveau zu erzeugen, das für die Handelsfilterung und die Trailing-Logik verwendet wird.

//+------------------------------------------------------------------+
//| Get profit target from V-Stop history                            |
//+------------------------------------------------------------------+
double GetProfitTarget(string symbol, bool forLong)
{
    int bars = 50;  // Look back 50 bars
    double target = 0.0;
    
    for(int i = 1; i <= bars; i++)
    {
        double vStop = forLong ? 
            CalculateVStop(symbol, i, false) :  // For longs, find support levels
            CalculateVStop(symbol, i, true);     // For shorts, find resistance levels
        
        if(vStop != 0.0)
        {
            target = vStop;
            break;
        }
    }
    
    // Fallback: Use fixed multiplier if no V-Stop found
    MqlTick lastTick;
    SymbolInfoTick(symbol, lastTick);
    return (target == 0.0) ? 
        (forLong ? lastTick.ask + 5*Prev_ATR[GetSymbolIndex(symbol)] : 
                   lastTick.bid - 5*Prev_ATR[GetSymbolIndex(symbol)]) : 
        target;
}

Die Funktion GetProfitTarget() bestimmt ein dynamisches Take-Profit-Level auf der Grundlage historischer V-Stop-Werte. Er blickt bis zu 50 Balken zurück und sucht nach dem nächstgelegenen gültigen V-Stop-Niveau - entweder ein unteres Band für Käufe (das als Unterstützung dient) oder ein oberes Band für Verkäufe (das als Widerstand dient). Wenn ein gültiges Niveau gefunden wird, wird es als Gewinnziel verwendet. Wenn innerhalb des Lookback-Fensters keine Daten verfügbar sind, greift die Funktion auf ein Standardziel zurück, das als 5× ATR-Abstand vom aktuellen Geld- oder Briefkurs berechnet wird, um sicherzustellen, dass der EA auch bei fehlenden historischen V-Stop-Daten ein logisches Take-Profit-Level setzt.

//+------------------------------------------------------------------+
//| Calculate risk level based on volatility                         |
//+------------------------------------------------------------------+
double CalculateRiskLevel(string symbol)
{
    double atrValues[20];
    if(CopyBuffer(ATR_Handles[GetSymbolIndex(symbol)], 0, 1, 20, atrValues) < 20)
        return RiskPercent_Mod;
    
    double avgATR = 0.0;
    for(int i = 0; i < 20; i++) avgATR += atrValues[i];
    avgATR /= 20.0;
    
    double currentATR = atrValues[0];  // Most recent ATR
    
    if(currentATR > avgATR * 1.5)
        return RiskPercent_High;
    else if(currentATR < avgATR * 0.5)
        return RiskPercent_Low;
    
    return RiskPercent_Mod;
}

Die Funktion CalculateRiskLevel() passt das Risiko des EAs dynamisch an die aktuelle Marktvolatilität an. Er ruft die letzten 20 ATR-Werte für das gegebene Symbol ab und berechnet ihren Durchschnitt, um eine Basislinie zu erstellen. Der jüngste ATR-Wert wird dann mit diesem Durchschnittswert verglichen: Ist er deutlich höher (über dem 1,5-fachen), gilt der Markt als sehr volatil und es wird der höhere Risikoprozentsatz verwendet; ist er viel niedriger (unter dem 0,5-fachem), wird der niedrigere Risikoprozentsatz verwendet. Andernfalls wird ein moderates Risikoniveau gewählt. Dadurch wird sichergestellt, dass die Handelsgröße adaptiv ist und sich an die Marktbedingungen in Echtzeit anpasst.

//+------------------------------------------------------------------+
//| Calculate position size based on risk                            |
//+------------------------------------------------------------------+
double CalculatePositionSize(string symbol, double riskPercent, double sl, double entryPrice)
{
    double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
    double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
    double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
    
    if(tickSize == 0 || point == 0) return 0.0;
    
    double riskAmount = AccountInfoDouble(ACCOUNT_EQUITY) * riskPercent;
    double slDistance = MathAbs(entryPrice - sl) / point;
    double moneyRisk = slDistance * tickValue / (tickSize / point);
    
    if(moneyRisk <= 0) return 0.0;
    double lots = riskAmount / moneyRisk;
    
    // Normalize and validate lot size
    double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
    double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
    double step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
    
    lots = MathMax(minLot, MathMin(maxLot, lots));
    lots = MathRound(lots / step) * step;
    
    return lots;
}

Die Funktion CalculatePositionSize() bestimmt die geeignete Losgröße für einen Handel auf der Grundlage des angegebenen Risikoprozentsatzes und des Stop-Loss-Abstands. Zunächst werden die wichtigsten Handelsparameter wie Tickgröße, Tickwert und Punktgröße für das Symbol erfasst. Anhand des Kapitals des Kontos und des gewählten Risikoprozentsatzes wird der Gesamtbetrag berechnet, den der Händler zu riskieren bereit ist.

Anschließend wird das monetäre Risiko pro Lot unter Berücksichtigung der Stop-Loss-Distanz in Punkten berechnet und in einen Wert auf Basis der Tick-Parameter umgerechnet. Die Funktion teilt den Gesamtrisikobetrag durch dieses Pro-Lot-Risiko, um die optimale Losgröße zu erhalten. Schließlich passt es die Losgröße an die Mindest-, Höchst- und Schrittgrößenanforderungen des Brokers an und stellt sicher, dass die Positionsgröße gültig ist und für die Ausführung richtig gerundet wird.

//+------------------------------------------------------------------+
//| Update trailing stops                                            |
//+------------------------------------------------------------------+
void UpdateTrailingStops(string symbol, double currentATR)
{
    double newSL = 0.0;
    for(int pos = PositionsTotal()-1; pos >= 0; pos--)
    {
        if(PositionGetSymbol(pos) != symbol) continue;
        
        ulong ticket = PositionGetInteger(POSITION_TICKET);
        double currentSL = PositionGetDouble(POSITION_SL);
        double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
        double currentProfit = PositionGetDouble(POSITION_PROFIT);
        double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
        
        if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
        {
            newSL = currentPrice - ATR_Multiplier * currentATR;
            if(newSL > currentSL && newSL > openPrice && currentProfit > 0)
                ModifySL(ticket, newSL);
        }
        else
        {
            newSL = currentPrice + ATR_Multiplier * currentATR;
            if((newSL < currentSL || currentSL == 0) && newSL < openPrice && currentProfit > 0)
                ModifySL(ticket, newSL);
        }
    }
}

Die Funktion UpdateTrailingStops() verwaltet aktiv offene Positionen, indem sie deren Stop-Loss auf der Grundlage der aktuellen Marktvolatilität anpasst. Für jede Position, die dem angegebenen Symbol entspricht, wird ein neues Trailing-Stop-Level berechnet, indem die letzte ATR mit einem vordefinierten Faktor multipliziert wird. Bei Kaufpositionen wird der Stop-Loss nach oben verschoben, wenn das neue Niveau über dem aktuellen Stop-Loss und über dem Eröffnungskurs liegt, um Gewinne zu sichern, sobald sich der Handel im positiven Bereich befindet. Umgekehrt wird bei Verkaufsposition der Stop-Loss unter ähnlichen Bedingungen nach unten angepasst. Dieser dynamische Trailing-Stop-Ansatz schützt die Gewinne und lässt dem Handel in volatilen Märkten Raum zum Atmen.

//+------------------------------------------------------------------+
//| Modify stop loss                                                 |
//+------------------------------------------------------------------+
bool ModifySL(ulong ticket, double newSL)
{
    MqlTradeRequest request = {};
    MqlTradeResult result = {};
    
    request.action = TRADE_ACTION_SLTP;
    request.position = ticket;
    request.sl = newSL;
    request.deviation = 5;
    
    if(!OrderSend(request, result))
    {
        Print("Modify SL error: ", GetLastError());
        return false;
    }
    return true;
}

Die Funktion ModifySL() aktualisiert das Stop-Loss-Niveau einer bestehenden Position, die durch ihre Ticketnummer identifiziert wird. Er erstellt eine Handelsanfrage, die die Aktion zur Änderung des Stop-Loss (TRADE_ACTION_SLTP) angibt, weist den neuen Stop-Loss-Kurs zu und legt eine maximal zulässige Kursabweichung fest. Die Anfrage wird dann über OrderSend() gesendet, und wenn die Änderung fehlschlägt, wird eine Fehlermeldung mit dem entsprechenden Fehlercode ausgegeben. Die Funktion gibt bei Erfolg true zurück und false, wenn die Stop-Loss-Aktualisierung nicht erfolgreich war, wodurch der EA in der Lage ist, Fehler elegant zu behandeln.

//+------------------------------------------------------------------+
//| Check existing positions                                         |
//+------------------------------------------------------------------+
void CheckExistingPositions(string symbol, bool &hasLong, bool &hasShort)
{
    hasLong = false;
    hasShort = false;
    
    for(int pos = PositionsTotal()-1; pos >= 0; pos--)
    {
        if(PositionGetSymbol(pos) == symbol)
        {
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                hasLong = true;
            else
                hasShort = true;
        }
    }
}

In diesem Codeblock durchsucht die Funktion CheckExistingPositions() alle offenen Positionen, um festzustellen, ob es aktive Kauf- oder Verkaufspositionen für das angegebene Symbol gibt. Es initialisiert die Ausgabeflags hasLong und hasShort mit false und iteriert dann durch alle Positionen. Für jede Position, die dem Symbol entspricht, wird hasLong auf true gesetzt, wenn es sich um eine Kaufposition handelt, oder hasShort auf true, wenn es sich um eine Verkaufsposition handelt.

//+------------------------------------------------------------------+
//| Get symbol index                                                 |
//+------------------------------------------------------------------+
int GetSymbolIndex(string symbol)
{
    for(int i = 0; i < Num_symbs; i++)
        if(symb_List[i] == symbol)
            return i;
    return -1;
}

Schließlich bietet die Funktion GetSymbolIndex() ein einfaches Hilfsmittel, um den Index eines bestimmten Symbols aus dem symb_List-Array abzurufen. Sie durchläuft alle gespeicherten Symbole in einer Schleife und gibt den passenden Index zurück, wenn das Symbol gefunden wird. Wenn das Symbol nicht in der Liste vorhanden ist, wird -1 zurückgegeben. Diese Funktion ist wichtig für die Synchronisierung von symbol-spezifischen Daten wie Indikator-Handles oder gespeicherte Werte in verschiedenen Arrays, die im EA verwendet werden.

Backtest-Ergebnisse

Die Backtests wurde für den 1H-Zeitrahmen über ein etwa zweimonatiges Testfenster (11. Juni 2025 bis 01. August 2025) mit den folgenden Eingabeeinstellungen durchgeführt:

  • RSI Zeitraum = 60
  • RSI überkauftes Niveau = 70
  • RSI überverkauftes Niveau = 45
  • ATR-Zeitraum für Volatilität = 62
  • ATR-Multiplikator für SL = 3,2
  • Risiko % Hohe Volatilität = 0,19
  • Risiko % Moderate Volatilität = 0,064
  • Risiko % Geringe Volatilität = 0,0335
  • Erforderliche Mindestanzahl von Balken = 50
  • Standard-Losgröße = 0,01
  • SL in Punkten = 610
  • TP in Punkten = 980



Schlussfolgerung

Zusammenfassend lässt sich sagen, dass wir einen dynamischen Multi-Pair Expert Advisor entwickelt haben, der sich an veränderte Marktbedingungen anpasst, indem er ein volatilitätsbasiertes Risikomanagement integriert. Das System verarbeitet mehrere Symbole gleichzeitig und verwendet technische Indikatoren wie ATR und RSI, um Handelsmöglichkeiten zu identifizieren. Es berechnet V-Stop-Levels, um Einstiege zu filtern und Ausstiege zu verwalten, während es gleichzeitig die Losgrößen und Stop-Losses auf der Grundlage der Echtzeit-Volatilität dynamisch anpasst. Durch modulare Funktionen verwaltet der EA die Handelsausführung, Trailing Stops und die Risikokategorisierung (hoch, moderat, niedrig), um sicherzustellen, dass jeder Handel mit dem aktuellen Verhalten des Symbols übereinstimmt.

Zusammenfassend lässt sich sagen, dass dieser Ansatz den Händlern ein robustes und anpassungsfähiges Instrument an die Hand gibt, das eine konsistente Performance über verschiedene Währungspaare hinweg gewährleistet. Durch die Anpassung des Risikoengagements und der Handelsparameter auf Basis der Volatilität reduziert der EA das Überengagement in volatilen Märkten und vermeidet eine Underperformance in ruhigen Märkten.

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/18165

Beigefügte Dateien |
Letzte Kommentare | Zur Diskussion im Händlerforum (3)
CapeCoddah
CapeCoddah | 11 Aug. 2025 in 11:29

Hallo,


Ein sehr interessanter Artikel. Ich habe ihn nur überflogen und beschlossen, ihn ausführlich zu bewerten, da er einem EA ähnelt, den ich gerade entwickle.

Meine erste Frage ist, gibt es irgendeinen Grund, dass alle Trades Käufe und keine Verkäufe waren? Planen Sie außerdem weitere Artikel zu diesem Thema?

Nach meiner kurzen Durchsicht Ihres Codes möchte ich vorschlagen, dass GetSymbolIndex und andere Variablen wie point an den Anfang der Symbolschleife verschoben und Variablen zugewiesen werden sollten, um die Effizienz zu verbessern, indem Redundanzen reduziert werden. Je mehr Symbole der Paarliste hinzugefügt werden, desto mehr Zeit wird für die Ausführung von redundantem Code aufgewendet. Sie könnten auch in Betracht ziehen, einen PairCode-Index zu Ihren Arrays hinzuzufügen, damit direkt auf sie zugegriffen werden kann.


CapeCoddah

Hlomohang John Borotho
Hlomohang John Borotho | 12 Aug. 2025 in 15:52
CapeCoddah desto mehr Zeit wird für die Ausführung von redundantem Code aufgewendet. Sie könnten auch in Erwägung ziehen, einen PairCode-Index zu Ihren Arrays hinzuzufügen, damit direkt auf sie zugegriffen werden kann.


CapeCoddah

Hey,

Der Grund dafür, dass alle Trades Käufe sind, hängt von den Eingabeeinstellungen ab, die Sie eingegeben haben, und wenn Sie die gleichen Einstellungen wie ich beim Backtest verwendet haben, liegt das daran, dass ich den EA optimiert habe. Weitere Artikel zu diesem Thema werden zu gegebener Zeit erscheinen.

Danke für Ihre Anregung, ich werde das im Hinterkopf behalten.

JohnHlomohang
Ademir Temoteo De Vasco
Ademir Temoteo De Vasco | 6 Okt. 2025 in 01:28

Hallo... guten Abend!!!

Zunächst einmal möchte ich Ihnen zu Ihrer hervorragenden Arbeit gratulieren.

Ich führe Tests durch und bin mit den Ergebnissen sehr zufrieden, aber ich muss gestehen, dass ich nur eine Währung für den Test verwende, da die Einstellungen für jeden Vermögenswert anders sind.

Ich verstehe nicht, wie diese Logik der gleichzeitigen Platzierung mehrerer Währungen funktioniert.

Wenn Sie mir das erklären könnten, wäre ich Ihnen sehr dankbar.

Automatisierte Übersetzung durch den Moderator. Im englischen Forum schreiben Sie bitte auf Englisch.

Vom Neuling zum Experten: Animierte Nachrichtenüberschrift mit MQL5 (IX) – Verwaltung mehrerer Symbole in einem einzigen Chart für den Nachrichtenhandel Vom Neuling zum Experten: Animierte Nachrichtenüberschrift mit MQL5 (IX) – Verwaltung mehrerer Symbole in einem einzigen Chart für den Nachrichtenhandel
Der Handel mit Nachrichten erfordert aufgrund der erhöhten Volatilität häufig die Verwaltung mehrerer Positionen und Symbole in sehr kurzer Zeit. In der heutigen Diskussion gehen wir auf die Herausforderungen des Multi-Symbol-Handels ein, indem wir diese Funktion in unseren News Headline EA integrieren. Seien Sie dabei, wenn wir untersuchen, wie der algorithmische Handel mit MQL5 den Multi-Symbol-Handel effizienter und leistungsfähiger macht.
Selbstoptimierende Expert Advisors in MQL5 (Teil 12): Aufbau von linearen Klassifikatoren durch Matrixfaktorisierung Selbstoptimierende Expert Advisors in MQL5 (Teil 12): Aufbau von linearen Klassifikatoren durch Matrixfaktorisierung
Dieser Artikel befasst sich mit der leistungsfähigen Rolle der Matrixfaktorisierung im algorithmischen Handel, insbesondere in MQL5-Anwendungen. Von Regressionsmodellen bis hin zu Multi-Target-Klassifikatoren gehen wir durch praktische Beispiele, die zeigen, wie einfach diese Techniken mit Hilfe von integrierten MQL5-Funktionen integriert werden können. Ganz gleich, ob Sie die Kursrichtung vorhersagen oder das Verhalten von Indikatoren modellieren wollen, dieser Leitfaden schafft eine solide Grundlage für den Aufbau intelligenter Handelssysteme mit Hilfe von Matrixmethoden.
Statistische Arbitrage durch kointegrierte Aktien (Teil 2): Expert Advisor, Backtests und Optimierung Statistische Arbitrage durch kointegrierte Aktien (Teil 2): Expert Advisor, Backtests und Optimierung
In diesem Artikel wird eine Beispielimplementierung eines Expert Advisors für den Handel mit einem Korb von vier Nasdaq-Aktien vorgestellt. Die Aktien wurden zunächst anhand von Pearson-Korrelationstests gefiltert. Die gefilterte Gruppe wurde dann mit Johansen-Tests auf Kointegration geprüft. Schließlich wurde der kointegrierte Spread mit dem ADF- und dem KPSS-Test auf Stationarität geprüft. Hier sehen wir einige Anmerkungen zu diesem Prozess und die Ergebnisse der Backtests nach einer kleinen Optimierung.
Parafrac-Oszillator: Kombination von Parabel- und Fraktalindikator Parafrac-Oszillator: Kombination von Parabel- und Fraktalindikator
Wir werden untersuchen, wie der Parabolic SAR und der Fractal-Indikator kombiniert werden können, um einen neuen oszillatorbasierten Indikator zu schaffen. Durch die Integration der einzigartigen Stärken beider Instrumente können Händler eine raffiniertere und effektivere Handelsstrategie entwickeln.