English Русский 中文 Español Português
preview
Pair-Trading: Algorithmischer Handel mit automatischer Optimierung auf Basis von Z-Score-Differenzen

Pair-Trading: Algorithmischer Handel mit automatischer Optimierung auf Basis von Z-Score-Differenzen

MetaTrader 5Handelssysteme |
48 8
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Einführung

In der Welt des Handels, in der viele sich auf komplexe Indikatoren stützen und ständig auf der Suche nach dem „heiligen Gral“ sind, beruhen die effektivsten Strategien manchmal auf einfachen statistischen Prinzipien. Heute werden wir in die faszinierende Welt des Pair-Tradings eintauchen und untersuchen, wie moderne algorithmische Ansätze diese klassische Strategie auf ein neues Effizienzniveau bringen können.

Pair-Trading wurde ursprünglich in den 1980er Jahren von Quant-Tradern an der Wall Street entwickelt und war lange Zeit eine exklusive Domäne von großen Hedgefonds und institutionellen Anlegern. Mit dem Fortschritt der Technologie und der Verfügbarkeit von Handelsplattformen wie MetaTrader ist diese Strategie jedoch auch für Privatanleger zugänglich geworden.

Dieser Artikel basiert auf einem realen Handels-EA, der klassische Prinzipien des Pair-Tradings mit modernen Optimierungstechnologien und automatischer Anpassung an sich ändernde Marktbedingungen kombiniert. Unser Ansatz ermöglicht es, kurzfristige Anomalien im Preisverhältnis korrelierter Vermögenswerte zu erkennen und zugleich flexibel auf strukturelle Marktveränderungen zu reagieren.


Konzept des Pair-Tradings: Anwendung statistischer Methoden

Pair-Trading ist eine marktneutrale Strategie, die statistische Abweichungen im Verhalten korrelierter Vermögenswerte ausnutzt. Die Grundidee ist einfach und elegant: Wenn die Preise zweier korrelierter Instrumente stärker als üblich voneinander abweichen, besteht eine hohe Wahrscheinlichkeit, dass die Beziehung zu ihrem historischen Mittelwert zurückkehrt.

Mathematische Grundlage der Strategie

Die Strategie basiert auf zwei wichtigen statistischen Konzepten: Korrelation und Stationarität. Die Korrelation ist ein Maß für die statistische Beziehung zwischen zwei Variablen und gibt an, wie eng eine Veränderung der einen Variable mit einer Veränderung der anderen zusammenhängt. Im Kontext der Finanzmärkte kann die Korrelation zwischen zwei Vermögenswerten zwischen -1 (perfekte negative Korrelation) und +1 (perfekte positive Korrelation) liegen.

Stationarität ist eine Eigenschaft einer Zeitreihe, bei der ihre statistischen Merkmale, wie Mittelwert, Varianz und Autokorrelation, im Zeitverlauf konstant bleiben. Für das Pair-Trading ist es wichtig, dass das Preisverhältnis zwischen zwei Vermögenswerten stationär ist. Mit anderen Worten: Das Preisverhältnis tendiert dazu, zu seinem Mittelwert zurückzukehren.

Dies ist besonders für Devisenhändler von Bedeutung, da Währungspaare oft stark korrelieren. So bewegen sich beispielsweise EURUSD und GBPUSD in der Regel in dieselbe Richtung, jedoch mit unterschiedlichen Amplituden und periodischen Abweichungen. Es sind diese Abweichungen, die zur Quelle eines potenziellen Gewinns werden.

Praktisches Beispiel

Betrachten wir die Paare EURUSD und GBPUSD. In der Vergangenheit wiesen diese Währungspaare aufgrund der geografischen Nähe und der engen wirtschaftlichen Verflechtung zwischen der Eurozone und dem Vereinigten Königreich eine hohe positive Korrelation auf.

Wenn der EURUSD ab einem bestimmten Punkt stärker steigt als der GBPUSD, dann steigt auch das Verhältnis EURUSD/GBPUSD. Wenn dieses Verhältnis seinen historischen Mittelwert um einen statistisch signifikanten Betrag übersteigt, können wir davon ausgehen, dass eine Rückkehr zum Mittelwert wahrscheinlich ist. In diesem Fall empfiehlt die Strategie des Pair-Tradings den Verkauf von EURUSD (einem überbewerteten Vermögenswert) und den Kauf von GBPUSD (einem unterbewerteten Vermögenswert).

Vorteile einer marktneutralen Strategie

Der Hauptvorteil des Pair-Tradings ist seine relative Unabhängigkeit von der allgemeinen Marktrichtung. Da wir gleichzeitig Long- und Short-Positionen in korrelierten Vermögenswerten eröffnen, ist unsere Nettoposition im Vergleich zum Markt nahezu neutral. Das bedeutet, dass die Strategie sowohl in steigenden als auch in fallenden Märkten profitabel sein kann, was in Zeiten hoher Volatilität und Unsicherheit besonders wertvoll ist.

Z-Score als Schlüsselindikator

Unser algorithmischer Ansatz basiert auf der Verwendung des Z-Scores, einem statistischen Maß, das angibt, wie stark das aktuelle Kursverhältnis vom historischen Durchschnitt in Standardabweichungseinheiten abweicht:

Z-score = (Current Ratio - Average Ratio) / Standard deviation

Wenn ein Z-Score einen bestimmten Schwellenwert (z. B. +2,0) überschreitet, signalisiert er einen signifikanten Ausreißer, der nach der statistischen Theorie eine hohe Wahrscheinlichkeit hat, zum Mittelwert zurückzukehren.

Der Z-Score misst im Wesentlichen, wie „abnormal“ das aktuelle Kursverhältnis im historischen Kontext ist. Ein Wert von +2,0 bedeutet, dass das aktuelle Verhältnis zwei Standardabweichungen vom Mittelwert abweicht, was bei einer Normalverteilung nur in ~2,3 % der Fälle vorkommt.

Interpretation der Z-Score-Werte

Ein Z-Score > 0 bedeutet, dass das erste Währungspaar (Symbol1) gegenüber dem zweiten (Symbol2) überbewertet ist. Z-Score < 0 bedeutet, dass das erste Währungspaar (Symbol1) gegenüber dem zweiten (Symbol2) unterbewertet ist. Wenn |Z-Score| < 1 ist, liegt das Preisverhältnis im normalen Bereich, und wenn 1 < |Z-Score| < 2 ist, liegt eine moderate Abweichung von der Norm vor. Der Wert 2 < |Z-Score| < 3 weist auf einen signifikanten Ausreißer hin (potenzieller Einstiegspunkt), während |Z-Score| > 3 einen extremen Ausreißer anzeigt (hohe Wahrscheinlichkeit einer Rückkehr zum Mittelwert).

Auswahl des optimalen Zeitraums für die Berechnung

Die Wahl des Zeitraums für die Berechnung des Z-Scores ist ein entscheidender Parameter, der die Effizienz der Strategie beeinflusst. Ein zu kurzer Zeitraum kann zu häufigen Fehlsignalen führen, während ein zu langer Zeitraum dazu führen kann, dass kurzfristige Handelsmöglichkeiten verpasst werden.

Unser EA löst dieses Problem durch die automatische Optimierung des Z-Score-Berechnungszeitraums auf der Grundlage historischer Daten und passt sich den aktuellen Marktbedingungen an.


Architektur des Algorithmus

Der EA basiert auf drei Grundprinzipien: statistische Analyse zur Ermittlung anormaler Abweichungen, dynamische Optimierung der Parameter in Echtzeit und adaptives Risikomanagement unter Berücksichtigung von Korrelationsänderungen.

Allgemeine EA-Struktur

Unser Algorithmus besteht aus miteinander verbundenen Modulen: Datenerfassung und -vorverarbeitung, statistische Analyse, Entscheidungsfindung im Handel, automatische Optimierung und Risikomanagement. Das Datenerfassungsmodul empfängt historische und aktuelle Preise von Währungspaaren und filtert und normalisiert sie. Die statistische Analyse berechnet Kursverhältnisse, Z-Scores und Korrelationen zwischen Währungspaaren. Das Entscheidungsmodul bestimmt Einstiegspunkte und verwaltet offene Positionen. Die Optimierung testet regelmäßig verschiedene Kombinationen von Parametern und wählt die besten aus. Das Risikomanagementmodul berechnet die optimale Positionsgröße und überwacht Verlustserien.

Berechnung des Verhältnisses und des Z-Scores

Die Schlüsselkomponente unseres Algorithmus ist die Funktion, die das Preisverhältnis berechnet und in einen Z-Score umwandelt:

void CalculateRatioAndZScore()
{
    // Price ratio calculation
    for(int i = 0; i < CurrentZScorePeriod; i++)
    {
        if(prices2[i] == 0) continue;
        ratio[i] = prices1[i] / prices2[i];
    }
    
    // Calculate the average
    double mean = 0;
    for(int i = 0; i < CurrentZScorePeriod; i++)
    {
        mean += ratio[i];
    }
    mean /= CurrentZScorePeriod;
    
    // Calculation of standard deviation
    double stdDev = 0;
    for(int i = 0; i < CurrentZScorePeriod; i++)
    {
        stdDev += MathPow(ratio[i] - mean, 2);
    }
    stdDev = MathSqrt(stdDev / CurrentZScorePeriod);
    
    // Calculate Z-score
    for(int i = 0; i < CurrentZScorePeriod; i++)
    {
        if(stdDev == 0)
            zscore[i] = 0;
        else
            zscore[i] = (ratio[i] - mean) / stdDev;
    }
}

Dieser Ausschnitt zeigt, wie wir das Preisverhältnis berechnen und in einen Z-Score umwandeln. Man beachte die Behandlung von Sonderfällen, bei denen die Standardabweichung nahe bei Null liegt, was die Stabilität des Algorithmus gewährleistet.

Korrelationen überwachen und Einstiegspunkte identifizieren

Eine der wichtigsten Neuerungen unseres Ansatzes ist die Nutzung der dynamischen Korrelationsverfolgung zur Bestimmung optimaler Einstiegspunkte:

// The logic for opening new positions is to enter when the correlation is at a minimum
if(!isPositionOpen)
{
    double currentCorrelation = correlationHistory[0];
    double minCorrelation = GetMinimumCorrelation();
    
    // If the current correlation is close to the minimum and the Z-score exceeds the threshold
    if(MathAbs(currentCorrelation - minCorrelation) < 0.01 && 
       MathAbs(currentZScore) >= CurrentEntryThreshold)
    {
        // Calculate lot size based on risk
        double riskLot = CalculatePositionSize();
        
        if(currentZScore > 0) // Symbol1 is overvalued, Symbol2 is undervalued
        {
            // Sell Symbol1 and buy Symbol2
            if(OpenPairPosition(POSITION_TYPE_SELL, Symbol1, 
                               POSITION_TYPE_BUY, Symbol2, riskLot))
            {
                Log("Paired position opened: SELL " + Symbol1 + ", BUY " + Symbol2);
                isPositionOpen = true;
            }
        }
        else // Symbol1 is undervalued, Symbol2 is overvalued
        {
            // Buy Symbol1 and sell Symbol2
            if(OpenPairPosition(POSITION_TYPE_BUY, Symbol1, 
                               POSITION_TYPE_SELL, Symbol2, riskLot))
            {
                Log("Paired position opened: BUY " + Symbol1 + ", SELL " + Symbol2);
                isPositionOpen = true;
            }
        }
    }
}

Diese Funktion nimmt zwei Arrays mit Werten (die Schlusskurse der einzelnen Instrumente) und gibt einen Korrelationskoeffizienten zurück, der zwischen -1 und 1 liegt.

Aktualisierung der Korrelationshistorie

Um die Korrelationsdynamik effektiv zu verfolgen, speichern wir eine Historie der Werte:

void UpdateCorrelationHistory()
{
    // Shift the correlation history
    for(int i = CorrelationPeriod-1; i > 0; i--)
    {
        correlationHistory[i] = correlationHistory[i-1];
    }
    
    // Add a new correlation
    correlationHistory[0] = CalculateCurrentCorrelation();
}

double GetMinimumCorrelation()
{
    double minCorr = 1.0;
    for(int i = 0; i < CorrelationPeriod; i++)
    {
        if(correlationHistory[i] < minCorr)
            minCorr = correlationHistory[i];
    }
    return minCorr;
}

Mit diesem Modul können wir die Momente verfolgen, in denen die Korrelation zwischen den Instrumenten ein lokales Minimum erreicht, was ein zusätzliches Signal für den Einstieg in den Markt darstellt.

Unser Trumpf: die automatisierte Parameteroptimierung

Traditionelle Pair-Tradingsstrategien arbeiten oft mit festen Parametern, die im Laufe der Zeit veralten. Unser Algorithmus löst dieses Problem durch regelmäßige automatische Optimierung der Schlüsselparameter:

void Optimize()
{
    Print("Starting optimization...");
    
    optimizationResults.Clear();
    
    // Optimization ranges
    int zScorePeriodMin = 50, zScorePeriodMax = 200, zScorePeriodStep = 25;
    double entryThresholdMin = 1.5, entryThresholdMax = 3.0, entryThresholdStep = 0.25;
    double exitThresholdMin = 0.0, exitThresholdMax = 1.0, exitThresholdStep = 0.25;
    
    // Iterate over all combinations of parameters
    for(int period = zScorePeriodMin; period <= zScorePeriodMax; period += zScorePeriodStep)
    {
        for(double entry = entryThresholdMin; entry <= entryThresholdMax; entry += entryThresholdStep)
        {
            for(double exit = exitThresholdMin; exit <= exitThresholdMax; exit += exitThresholdStep)
            {
                // Test parameters
                double profit = TestParameters(period, entry, exit);
                
                OptimizationResult* result = new OptimizationResult();
                result.zScorePeriod = period;
                result.entryThreshold = entry;
                result.exitThreshold = exit;
                result.profit = profit;
                
                optimizationResults.Add(result);
            }
        }
    }
    
    // Find the best result and apply new parameters
    OptimizationResult* bestResult = NULL;
    for(int i = 0; i < optimizationResults.Total(); i++)
    {
        OptimizationResult* currentResult = optimizationResults.At(i);
        if(bestResult == NULL || currentResult.profit > bestResult.profit)
        {
            bestResult = currentResult;
        }
    }
    
    if(bestResult != NULL)
    {
        // Update the EA internal parameters
        CurrentZScorePeriod = bestResult.zScorePeriod;
        CurrentEntryThreshold = bestResult.entryThreshold;
        CurrentExitThreshold = bestResult.exitThreshold;
        
        // Update arrays
        ArrayResize(prices1, CurrentZScorePeriod);
        ArrayResize(prices2, CurrentZScorePeriod);
        ArrayResize(ratio, CurrentZScorePeriod);
        ArrayResize(zscore, CurrentZScorePeriod);
        
        Print("Optimization complete. New parameters: ZScorePeriod = ", CurrentZScorePeriod, 
              ", EntryThreshold = ", CurrentEntryThreshold, ", ExitThreshold = ", CurrentExitThreshold);
    }
}

Diese Funktion iteriert durch verschiedene Kombinationen von Z-Score-Berechnungszeitraum, Einstiegs- und Ausstiegsschwellen und wählt die optimale Kombination auf der Grundlage historischer Daten. Im Gegensatz zur klassischen manuellen Optimierung, die einmalig durchgeführt wird, passt sich unser Algorithmus kontinuierlich an die sich ändernden Marktbedingungen an, was die langfristige Stabilität der Ergebnisse erhöht.

Auswahl der optimalen Optimierungsfrequenz

Die Optimierungsfrequenz ist ein wichtiger Parameter, der die Systemleistung beeinflusst. Eine zu häufige Optimierung kann zu einer „Überanpassung“ des Algorithmus und einem unnötigen Verbrauch von Rechenressourcen führen, während eine zu seltene Optimierung möglicherweise nicht mit den sich ändernden Marktbedingungen Schritt hält.

In unserem Algorithmus wird die Optimierung nach einer bestimmten Anzahl von Ticks ausgelöst, was ein Gleichgewicht zwischen Anpassungsfähigkeit und Stabilität gewährleistet:

// Auto optimization
if(AutoOptimize && ++tickCount >= OptimizationPeriod)
{
    Optimize();
    tickCount = 0;
}

Der Standardwert des Parameters OptimizationPeriod ist auf 5000 Ticks festgelegt, kann aber je nach den Besonderheiten der gehandelten Instrumente und der Zeitskala der Strategie geändert werden.

Intelligente Parameterprüfung

Für jeden Satz von Parametern testen wir sie anhand historischer Daten:

double TestParameters(int period, double entry, double exit)
{
    // Initialize test arrays
    double test_prices1[], test_prices2[], test_ratio[], test_zscore[];
    ArrayResize(test_prices1, period);
    ArrayResize(test_prices2, period);
    ArrayResize(test_ratio, period);
    ArrayResize(test_zscore, period);
    
    double close1[], close2[];
    ArraySetAsSeries(close1, true);
    ArraySetAsSeries(close2, true);
    
    int copied1 = CopyClose(Symbol1, PERIOD_CURRENT, 0, MinDataPoints, close1);
    int copied2 = CopyClose(Symbol2, PERIOD_CURRENT, 0, MinDataPoints, close2);
    
    if(copied1 < MinDataPoints || copied2 < MinDataPoints)
    {
        Print("Not enough data for testing");
        return -DBL_MAX;
    }
    
    double profit = 0;
    bool inPosition = false;
    double entryPrice1 = 0, entryPrice2 = 0;
    ENUM_POSITION_TYPE posType1 = POSITION_TYPE_BUY, posType2 = POSITION_TYPE_BUY;
    
    // Create a correlation history for testing
    double testCorrelations[];
    ArrayResize(testCorrelations, CorrelationPeriod);
    
    // Fill in the initial data
    for(int i = 0; i < period; i++)
    {
        test_prices1[i] = close1[MinDataPoints - 1 - i];
        test_prices2[i] = close2[MinDataPoints - 1 - i];
    }
    
    // Inverse simulation on historical data
    for(int i = period; i < MinDataPoints; i++)
    {
        // Update data, calculate Z-score and simulate trading decisions 
        // ...
        
        double currentZScore = test_zscore[0];
        
        // Simulate closing positions
        if(inPosition)
        {
            double currentProfit = 0;
            
            if(posType1 == POSITION_TYPE_BUY)
                currentProfit += (close1[MinDataPoints - 1 - i] - entryPrice1) * 10000;
            else
                currentProfit += (entryPrice1 - close1[MinDataPoints - 1 - i]) * 10000;
                
            if(posType2 == POSITION_TYPE_BUY)
                currentProfit += (close2[MinDataPoints - 1 - i] - entryPrice2) * 10000;
            else
                currentProfit += (entryPrice2 - close2[MinDataPoints - 1 - i]) * 10000;
                
            if(currentProfit >= ProfitTarget)
            {
                profit += currentProfit;
                inPosition = false;
            }
        }
        
        // Simulate opening new positions
        if(!inPosition && i > CorrelationPeriod)
        {
            // Check entry conditions based on Z-score and correlation
            // ...
        }
    }
    
    return profit; // Return the resulting profit for the given set of parameters
}

Umgang mit Überanpassung

Eine der größten Herausforderungen bei der Optimierung von Handelssystemen ist das Risiko der Überanpassung, das entsteht, wenn ein System so genau auf historische Daten abgestimmt ist, dass es seine Fähigkeit zur Verallgemeinerung und zur Verarbeitung neuer Daten verliert.

Unser Algorithmus verwendet mehrere Ansätze, um diesem Problem zu begegnen. Erstens verwenden wir nur einen Teil der historischen Daten für die Optimierung und lassen den anderen Teil für die Überprüfung (Out-of-Sample-Tests) übrig. Zweitens konzentrieren wir uns nur auf die wichtigsten Parameter, um die Dimensionalität des Suchraums zu reduzieren. Drittens: Anstatt alle möglichen Werte auszuprobieren, verwenden wir vernünftige Schritte (z. B. zScorePeriodStep = 25), was die Wahrscheinlichkeit einer Überanpassung an bestimmte Merkmale historischer Daten verringert. Und schließlich sorgen wir durch die regelmäßige Aktualisierung der Parameter dafür, dass sich das System an die veränderten Marktbedingungen anpasst.

Verbessertes Averaging von Positionen

Eine der wichtigsten Innovationen unseres Algorithmus ist das adaptive Positionsmanagement, wenn die Korrelation zwischen gehandelten Instrumenten sinkt:

// Logic for averaging positions when correlation drops
if(isPositionOpen && EnableAveraging)
{
    double currentCorrelation = correlationHistory[0];
    
    // Check for a drop in correlation from the moment a position is opened
    if(averagingCount == 0)
    {
        // If this is the first check after opening a position
        initialCorrelation = currentCorrelation;
        averagingCount++;
    }
    else if(initialCorrelation - currentCorrelation > CorrelationDropThreshold)
    {
        // If the correlation has fallen below the threshold, add an averaging counter trade 
        double averagingLot = lastLotSize * AveragingLotMultiplier;
        
        // Check the type of our current positions
        ENUM_POSITION_TYPE posType1 = POSITION_TYPE_BUY;
        string posSymbol = "";
        bool foundPos1 = false, foundPos2 = false;
        ENUM_POSITION_TYPE posType2 = POSITION_TYPE_BUY;
        
        // Check our open positions
        for(int i = 0; i < ArraySize(posTickets); i++)
        {
            if(PositionSelectByTicket(posTickets[i]))
            {
                string symbol = PositionGetString(POSITION_SYMBOL);
                ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
                
                if(symbol == Symbol1)
                {
                    posType1 = type;
                    foundPos1 = true;
                }
                else if(symbol == Symbol2)
                {
                    posType2 = type;
                    foundPos2 = true;
                }
                
                if(foundPos1 && foundPos2) break;
            }
        }
        
        if(foundPos1 && foundPos2)
        {
            // Open counter trades with an increased lot
            ENUM_POSITION_TYPE reverseType1 = (posType1 == POSITION_TYPE_BUY) ? POSITION_TYPE_SELL : POSITION_TYPE_BUY;
            ENUM_POSITION_TYPE reverseType2 = (posType2 == POSITION_TYPE_BUY) ? POSITION_TYPE_SELL : POSITION_TYPE_BUY;
            
            if(OpenPairPosition(reverseType1, Symbol1, reverseType2, Symbol2, averagingLot))
            {
                Log("Averaging counter position opened: " + 
                    (reverseType1 == POSITION_TYPE_BUY ? "BUY " : "SELL ") + Symbol1 + ", " +
                    (reverseType2 == POSITION_TYPE_BUY ? "BUY " : "SELL ") + Symbol2);
                
                // Update the initial correlation for the next check
                initialCorrelation = currentCorrelation;
                averagingCount++;
            }
        }
    }
}

Dieser Ansatz ermöglicht es uns nicht nur, uns vor Verlusten zu schützen, wenn die Korrelation abnimmt, sondern auch, die Gewinne potenziell zu erhöhen, wenn sich die Marktsituation in eine für uns günstige Richtung entwickelt.

Mechanismus des Averaging und seine Rechtfertigung

Traditionell gilt das Averaging von Positionen als eine sehr gefährliche und riskante Praxis, da es die Positionsgröße erhöht, wenn sich der Preis gegen uns bewegt. Im Zusammenhang mit dem Pair-Trading ist die Situation jedoch anders: Wir stocken Positionen nicht bei ungünstigen Kursbewegungen auf, sondern bei Veränderungen der Korrelation.

Wenn die Korrelation zwischen einem Paar von Instrumenten sinkt, kann dies zweierlei bedeuten: entweder eine vorübergehende Abweichung, die sich bald wieder korrigiert, oder eine strukturelle Veränderung der Marktbeziehungen. Im ersten Fall kann die Eröffnung zusätzlicher Positionen in der entgegengesetzten Richtung (mit einer erhöhten Losgröße) die Gewinne erheblich steigern, wenn die Korrelation zu normalen Werten zurückkehrt. Im zweiten Fall schützt sie uns vor weiteren Verlusten, da wir im Wesentlichen eine Position eröffnen, die unser ursprüngliches Risiko absichert.

Es ist wichtig zu beachten, dass unser Algorithmus Schutzmechanismen enthält, die den Umfang des Averaging begrenzen. Der Parameter AveragingLotMultiplier bestimmt, wie oft die Losgröße bei jedem Averaging erhöht wird, und die Anzahl solcher Nachkäufe/Nachpositionierungen wird über die Korrelationshistorie begrenzt.

// If the correlation has fallen below the threshold, we add an averaging counter trade
double averagingLot = lastLotSize * AveragingLotMultiplier;

// Open counter trades with an increased lot
ENUM_POSITION_TYPE reverseType1 = (posType1 == POSITION_TYPE_BUY) ? POSITION_TYPE_SELL : POSITION_TYPE_BUY;
ENUM_POSITION_TYPE reverseType2 = (posType2 == POSITION_TYPE_BUY) ? POSITION_TYPE_SELL : POSITION_TYPE_BUY;

Verwendung von schützenden Stop-Orders und Take-Profits

Zusätzlich zur zentralen Handelslogik ermöglicht unser Algorithmus das Setzen von Stop-Loss- und Take-Profit-Orders, um das Risiko auf der Ebene der einzelnen Positionen zu steuern:

// Calculate protective stop losses and take profits, if enabled
double sl1 = 0, sl2 = 0, tp1 = 0, tp2 = 0;
double point1 = SymbolInfoDouble(symbol1, SYMBOL_POINT);
double point2 = SymbolInfoDouble(symbol2, SYMBOL_POINT);

if(EnableProtectiveStops)
{
    if(type1 == POSITION_TYPE_BUY)
    {
        double ask1 = SymbolInfoDouble(symbol1, SYMBOL_ASK);
        sl1 = ask1 - ProtectiveStopPips * point1;
    }
    else
    {
        double bid1 = SymbolInfoDouble(symbol1, SYMBOL_BID);
        sl1 = bid1 + ProtectiveStopPips * point1;
    }
    
    // Similar calculations for the second instrument
    // ...
}

// Take profit calculation
if(EnableTakeProfit)
{
    // Calculations for take profits
    // ...
}

Diese Schutzmechanismen sind besonders wichtig beim Handel auf sehr volatilen Märkten oder in Zeiten wirtschaftlicher Instabilität, wenn selbst stark korrelierte Instrumente erhebliche Verhaltensabweichungen aufweisen können.

Dynamisches Risikomanagement

Ein entscheidendes Element für den langfristigen Erfolg eines jeden Handelssystems ist ein angemessenes Risikomanagement. Unser Algorithmus berechnet automatisch die Positionsgröße auf der Grundlage eines bestimmten Risikoprozentsatzes des Kontosaldos:

double CalculatePositionSize()
{
    double balance = AccountInfoDouble(ACCOUNT_BALANCE);
    double riskAmount = balance * RiskPercent / 100.0;
    
    double tickValue1 = SymbolInfoDouble(Symbol1, SYMBOL_TRADE_TICK_VALUE);
    double tickSize1 = SymbolInfoDouble(Symbol1, SYMBOL_TRADE_TICK_SIZE);
    double point1 = SymbolInfoDouble(Symbol1, SYMBOL_POINT);
    
    double lotStep = SymbolInfoDouble(Symbol1, SYMBOL_VOLUME_STEP);
    double minLot = SymbolInfoDouble(Symbol1, SYMBOL_VOLUME_MIN);
    double maxLot = SymbolInfoDouble(Symbol1, SYMBOL_VOLUME_MAX);
    
    // Risk-based lot calculation (using an approximate stop loss of 100 pips for calculation)
    double virtualStopLoss = 100;
    double riskPerPoint = tickValue1 * (point1 / tickSize1);
    double lotSizeByRisk = riskAmount / (virtualStopLoss * riskPerPoint);
    
    // Round to the nearest lot step
    lotSizeByRisk = MathFloor(lotSizeByRisk / lotStep) * lotStep;
    
    // Check for minimum and maximum lot size
    lotSizeByRisk = MathMax(minLot, MathMin(maxLot, lotSizeByRisk));
    
    return lotSizeByRisk;
}

Dieser Ansatz gewährleistet eine proportionale Erhöhung der Positionsgröße bei wachsendem Kapital und eine Verringerung in Drawdown-Phasen, was mit den Grundsätzen eines soliden Kapitalmanagements vereinbar ist.

Mechanismus zur Begrenzung aufeinanderfolgender Verluste

Darüber hinaus enthält unser Algorithmus einen Schutzmechanismus, der die maximale Anzahl der aufeinander folgenden Trades mit Verlust begrenzt:

if(totalProfit < 0)
    consecutiveLosses++;
else
    consecutiveLosses = 0;

if(consecutiveLosses >= MaxConsecutiveLosses)
{
    Log("Reached maximum number of consecutive losses. Trading suspended.");
    // Implementation of logic for temporary suspension of trading
}

Dieser Mechanismus schützt vor fortgesetztem Handel in Zeiten, in denen die Marktbedingungen nicht mit den Annahmen unserer Strategie übereinstimmen, z. B. bei strukturellen Veränderungen der Korrelationsbeziehungen zwischen Währungspaaren.

Adaptives Risikomanagement

Ein wichtiges Merkmal unseres Ansatzes ist die Anpassung des Risikoniveaus in Abhängigkeit von der historischen Volatilität und den Ergebnissen früherer Trades. Wenn die letzten Trades gewinnbringend waren, kann der Algorithmus die Positionsgröße leicht erhöhen, und umgekehrt, nach einer Reihe von Trades mit Verlust, wird die Positionsgröße verringert.

Dies wird durch eine dynamische Anpassung des Parameters RiskPercent erreicht:

// Example of implementing adaptive risk management
double GetAdaptiveRiskPercent()
{
    double baseRisk = RiskPercent;
    
    // Reduce risk in case of consecutive losses
    if(consecutiveLosses > 0)
        baseRisk = baseRisk * (1.0 - 0.1 * consecutiveLosses);
    
    // Limit the minimum and maximum risk
    return MathMax(0.2, MathMin(baseRisk, 2.0));
}


Bewertung der Effizienz des Pair-Tradings anhand realer Daten

Um die Effizienz des vorgeschlagenen Algorithmus objektiv zu bewerten, wurde eine Reihe von Tests mit historischen Daten verschiedener Währungspaare durchgeführt. Sehen wir uns die Ergebnisse für drei besonders repräsentative Paare an.

Testergebnisse für das Paar EURUSD/AUDUSD

Dieses klassische Paar von Instrumenten weist eine hohe historische Korrelation auf. Die Tests der letzten 5 Jahre haben folgende Ergebnisse gezeigt:

  • Gesamtgewinn: +14% auf das Anfangskapital
  • Maximaler Drawdown 0,33 %
  • Prozentsatz der profitablen Trades: 59 %
  • Die durchschnittliche Handelsdauer beträgt 3 Stunden und 42 Minuten.
  • Sharpe Ratio: 5,3

Die Strategie hat sich in Zeiten erhöhter Volatilität, wie z. B. dem Brexit und der COVID-19-Pandemie, als besonders effizient erwiesen, da vorübergehende Korrelationsabweichungen zahlreiche Handelsmöglichkeiten geschaffen haben.

Ergebnisse zu AUDUSD/NZDUSD

Die Währungspaare des australischen und des neuseeländischen Dollars weisen aufgrund der ähnlichen Struktur ihrer rohstofforientierten Volkswirtschaften ebenfalls eine hohe Korrelation auf. Testergebnisse:

  • Gesamtgewinn: +17 % auf das Anfangskapital
  • Maximaler Drawdown 0,21 %
  • Prozentsatz der profitablen Trades: 56 %
  • Die durchschnittliche Handelsdauer beträgt 3 Stunden und 26 Minuten.
  • Sharpe Ratio: 7,82

Ein interessantes Merkmal dieses Paares ist die kürzere durchschnittliche Handelsdauer, die sich durch die schnellere Rückkehr zum Mittelwert der Korrelation erklären lässt.

Einstellung von Parametern in Abhängigkeit vom Zeitrahmen

Die Wahl des optimalen Zeitrahmens hängt vom Zeithorizont des Handels ab. Für den mittelfristigen Handel (Positionen, die mehrere Tage bis Wochen gehalten werden) empfiehlt sich der Tages- oder der 4-Stunden-Zeitrahmen. Für den kurzfristigen Handel (Positionen, die mehrere Stunden bis mehrere Tage gehalten werden) ist ein stündlicher oder 15-minütiger Zeitrahmen geeignet.

Wenn wir den Zeitrahmen ändern, müssen wir die Parameter entsprechend anpassen:

  • Bei höheren Zeitrahmen (D1, H4) sollte der Zeitraum für die Berechnung des Z-Scores verlängert und die Schwellenwerte für den Ein- und Ausstieg reduziert werden.
  • Bei niedrigeren Zeitrahmen (H1, M15) sollten Sie den Z-Score-Berechnungszeitraum verkürzen und die Einstiegs-/Ausstiegsschwellen erhöhen, um Marktrauschen herauszufiltern.


Entwicklungsperspektiven und weitere Verbesserungen

Obwohl die aktuelle Version des Algorithmus bereits gute Ergebnisse zeigt, gibt es mehrere Bereiche, in denen weitere Verbesserungen möglich sind.

Anwendung von Methoden des maschinellen Lernens

Ein vielversprechender Bereich ist die Integration von Methoden des maschinellen Lernens zur Vorhersage der Dynamik der Korrelation zwischen Instrumenten. Algorithmen wie neuronale Netze mit langem Kurzzeitgedächtnis (LSTM) können komplexe Muster in Korrelationsbeziehungen effektiv erfassen und deren Veränderungen mit hoher Genauigkeit vorhersagen.

Erweiterung auf Portfolios mit mehreren Währungen

Die aktuelle Version des Algorithmus arbeitet mit einem Währungspaar, aber das Konzept kann auf ein Portfolio mit mehreren Währungen ausgeweitet werden, bei dem die Korrelationen zwischen mehreren Instrumenten gleichzeitig verfolgt werden. Dieser Ansatz wird es uns ermöglichen, die Diversifizierung effektiver zu nutzen und Handelsmöglichkeiten in einem breiteren Spektrum von Marktbedingungen zu finden.

Integration mit der Fundamentalanalyse

Eine weitere vielversprechende Richtung ist die Integration grundlegender wirtschaftlicher Kennziffern in den Entscheidungsalgorithmus. Die Berücksichtigung von Unterschieden bei Zinssätzen, Inflation und anderen makroökonomischen Indikatoren kann beispielsweise dazu beitragen, langfristige Veränderungen in den Korrelationsbeziehungen zwischen Währungspaaren besser vorherzusagen.


Schlussfolgerung

Der vorgestellte Algorithmus mit automatisierter Parameteroptimierung für das Pair-Trading zeigt, wie relativ einfache statistische Prinzipien mit modernen algorithmischen Handelsmethoden in ein effizientes Handelssystem umgewandelt werden können.

Der Hauptvorteil unseres Ansatzes ist seine Anpassungsfähigkeit: Im Gegensatz zu Systemen mit festen Parametern passt sich unser Algorithmus kontinuierlich an die sich ändernden Marktbedingungen an und gewährleistet so seine langfristige Lebensfähigkeit.

Es ist wichtig, darauf hinzuweisen, dass trotz der Automatisierung des Handelsprozesses ein tiefes Verständnis der Funktionsprinzipien des Algorithmus und eine regelmäßige Überwachung seiner Funktionsweise weiterhin notwendige Voraussetzungen für die erfolgreiche Anwendung dieser Strategie sind.

Im nächsten Artikel werden wir uns mit weiteren Verbesserungen unseres Algorithmus befassen, u. a. mit dem Einsatz von Methoden des maschinellen Lernens zur Vorhersage der Korrelationsdynamik und der Parameteroptimierung durch genetische Algorithmen.

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/17800

Beigefügte Dateien |
PairsTradingOpt.mq5 (71.85 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (8)
Alamgir Malek
Alamgir Malek | 1 Apr. 2026 in 10:05
Funktioniert es bei jedem Diagramm, bei jedem Paar?
Solomon Anietie Sunday
Solomon Anietie Sunday | 1 Apr. 2026 in 17:03
Lieber Autor, ich weiß nicht viel über die Mean-Reversion-Strategie. Kann diese Strategie auf jedem Zeitrahmen verwendet werden? Wenn ja, wie wirkt es sich auf Eingabeparameter und Konfiguration?
anandpandya
anandpandya | 5 Apr. 2026 in 09:00
Die Kurve ist angepasst. Ihre Konzepte sind interessant...
Ryan L Johnson
Ryan L Johnson | 7 Apr. 2026 in 19:17
Solomon Anietie Sunday #:
[Die Strategie in diesem Artikel funktioniert am besten mit einem Renko-Diagramm.

Renko-Charts sind zeitlos, wenn Sie so wollen. Obwohl jeder Renko-Baustein immer noch einen Zeitstempel hat, wird die Zeitskala eines benutzerdefinierten Renko-Charts nicht gleichmäßig inkrementiert. Daher kann die Verwendung von Renko das Rätselraten um die manuelle Auswahl des besten Zeitrahmens aus dem Handelsprozess nehmen. Stattdessen müssen Sie nur noch die beste Ziegelsteingröße auswählen. Außerdem glättet Renko von Natur aus das Chartrauschen, da jeder Baustein die gleiche Größe hat.

Solomon Anietie Sunday #:
[W]as bedeuten die obigen Bilder? Was wollen Sie uns damit sagen?

Obwohl ich kein Chinesisch lesen kann, kann ich ein Piktogramm lesen. Es scheint, dass die Aktivierung von "Gewinn in Pips für schnellere Berechnung" für diesen Code im Tester profitabel ist, während die Deaktivierung im Tester unprofitabel ist.

QuantGlobal
QuantGlobal | 20 Apr. 2026 in 06:07

Hallo, Es ist eine interessante Strategie, ich habe sie auf Indizes ausprobiert.

Wie kann man die Anzahl der Trades reduzieren? Seine wobei zu viele, bis die Z-Score ist unter oder über 2.

Battle Royale Optimizer (BRO) Battle Royale Optimizer (BRO)
Der Artikel untersucht den Algorithmus Battle Royale Optimizer – eine Metaheuristik, bei der Lösungen mit ihren nächsten Nachbarn konkurrieren, „Schaden“ anhäufen, ersetzt werden, wenn ein Schwellenwert überschritten wird, und den Suchraum um die aktuell beste Lösung herum regelmäßig verkleinern. Es werden sowohl Pseudocode als auch eine MQL5-Implementierung der Klasse C_AO_BRO vorgestellt, einschließlich Nachbarschaftssuche, Bewegung in Richtung der besten Lösung und eines adaptiven Delta-Intervalls. Die Testergebnisse für die Funktionen „Hilly“, „Forest“ und „Megacity“ zeigen die Stärken und Grenzen des Ansatzes auf. Der Leser erhält eine gebrauchsfertige Grundlage für Experimente und die Einstellung wichtiger Parameter wie popSize und maxDamage.
Neuronale Netze im Trading: Duales Clustering multivariater Zeitreihen (Abschlussteil) Neuronale Netze im Trading: Duales Clustering multivariater Zeitreihen (Abschlussteil)
Wir implementieren weiterhin die von den Autoren des DUET-Frameworks vorgeschlagenen Ansätze, die einen innovativen Ansatz zur Analyse von Zeitreihen bieten, indem sie zeitliches und kanalbasiertes Clustering kombinieren, um versteckte Muster in den analysierten Daten aufzudecken.
Von der Grundstufe bis zur Mittelstufe: Struktur (III) Von der Grundstufe bis zur Mittelstufe: Struktur (III)
In diesem Artikel werden wir untersuchen, was strukturierter Code ist. Viele Leute verwechseln strukturierten Code mit organisiertem Code, aber es gibt einen Unterschied zwischen diesen beiden Konzepten. Genau darum geht es in diesem Artikel. Trotz der offensichtlichen Komplexität, die Sie vielleicht empfinden, wenn Sie dieser Art des Codierens zum ersten Mal begegnen, habe ich versucht, das Thema so einfach wie möglich anzugehen. Dieser Artikel ist jedoch nur der erste Schritt zu etwas Größerem.
Marktsimulation (Teil 17): Sockets (XI) Marktsimulation (Teil 17): Sockets (XI)
Die Implementierung des Teils des Codes, der in MetaTrader 5 ausgeführt werden soll, ist unproblematisch. Es gibt jedoch einige Punkte, die berücksichtigt werden müssen. Das ist notwendig, damit das System korrekt funktioniert. Denken Sie an einen wichtigen Punkt: Es läuft nicht nur ein einziges Programm. Tatsächlich müssen drei Programme gleichzeitig ausgeführt werden. In Wirklichkeit müssen drei Programme gleichzeitig laufen. Es ist wichtig, sie so zu implementieren und zu strukturieren, dass sie miteinander interagieren und kommunizieren können und dass jedes von ihnen versteht, was die anderen tun oder beabsichtigen.