Pair-Trading: Algorithmischer Handel mit automatischer Optimierung auf Basis von Z-Score-Differenzen
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 RechtfertigungTraditionell 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
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.
Battle Royale Optimizer (BRO)
Neuronale Netze im Trading: Duales Clustering multivariater Zeitreihen (Abschlussteil)
Von der Grundstufe bis zur Mittelstufe: Struktur (III)
Marktsimulation (Teil 17): Sockets (XI)
- 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.
[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.
[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.
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.