
Fortgeschrittene Algorithmen für die Auftragsausführung in MQL5: TWAP, VWAP und Eisberg-Aufträge
- Einführung
- Verstehen von Ausführungsalgorithmen
- Die Implementation in MQL5
- Implementierung des Performance Analyzers
- Leistungsvergleich der Algorithmen
- Integration von Ausführungsalgorithmen in Handelsstrategien
- Beispiele für die Integration
- Integrierte Strategie
- Backtest-Ergebnisse
- Schlussfolgerung
Einführung
Stellen Sie sich vor, Sie stehen am Rande des Handelsparketts und sehen mit klopfendem Herzen die Kurse in Echtzeit vorbeiziehen. Eine falsche Bewegung, ein überdimensionierter Auftrag, und Ihr Vorsprung ist im Nu dahin. Willkommen in der Welt, in der die Qualität der Ausführung nicht nur ein Nice-to-have ist, sondern die Geheimwaffe, die die Gewinner von den anderen unterscheidet.
Jahrzehntelang haben institutionelle Schwergewichte in aller Stille ausgeklügelte Algorithmen eingesetzt, um ihre Aufträge zu zerschneiden, zu würfeln und heimlich zu verteilen, um Ausrutscher zu vermeiden und die Auswirkungen auf den Markt einzudämmen. Dank der Flexibilität von MQL5 kann jeder ehrgeizige Einzelhändler jetzt auf das gleiche leistungsstarke Spielbuch zugreifen.
Was ist daran so schlimm?
Stellen Sie sich Folgendes vor: Sie entdecken eine einmalige Gelegenheit und beschließen, groß einzusteigen. Sie erteilen einen Marktauftrag in voller Höhe und müssen dann mit ansehen, wie der Kurs unter dem Gewicht Ihres eigenen Handels nachgibt. In Sekundenschnelle wird aus dem idealen Einstieg ein wackeliger Kompromiss. Das ist der berüchtigte Nachteil der Marktauswirkungen, der selbst an den liquidesten Handelsplätzen zu spüren ist.
Ausführungsalgorithmen sind Ihr Gegenmittel. Indem sie einen großen Auftrag in eine Reihe kleinerer, strategisch zeitlich abgestimmter Abschnitte aufteilen, glätten sie Ihren Fußabdruck im Auftragsbuch. Das Ergebnis? Weniger Schlupf, engere Füllungen und eine allgemeine Verbesserung des durchschnittlichen Ausführungspreises.
Von den Elfenbeintürmen auf Ihren Desktop
„Sicher“, könnten Sie mit den Schultern zucken, „aber ich werde keine institutionellen Summen verschieben“. Der Clou: Das müssen Sie nicht. Ganz gleich, ob Sie ein halbes Los oder eine Handvoll Minilose einsetzen, die Volatilität kann Ihre Ausführung immer noch beeinträchtigen. Diese Werkzeuge helfen Ihnen:
- Eingrenzen des Slippage: Selbst bescheidene Aufträge können sich in unruhigen Märkten verschieben.
- Schärfen Sie Ihre Kanten: Bei gestaffelten Ausführungen erzielen Sie oft einen günstigeren Durchschnittspreis als bei einem einmaligen Glücksspiel.
- Ruhig bleiben: Automatisierte Arbeitsabläufe nehmen der Versuchung, in Panik zu kaufen oder zu verkaufen, den Schrecken.
- Geschmeidig skalieren: Auch wenn Ihr Konto wächst, bleibt die Ausführung knackig - egal wie umfangreich Ihre Aufträge werden.
-
Fliegen Sie unter dem Radar: Vor allem Eisberg-Aufträge verschleiern Ihre wahre Auftragsgröße und lassen neugierige Algos im Dunkeln.
Die heutige demokratisierte Landschaft bedeutet, dass die gleiche Ausführungstechnologie, die früher Millionenbudgets erforderte, jetzt auf Ihrer persönlichen Handelsstation laufen kann. Indem Sie den ausgefeilten MQL5-Code für TWAP-, VWAP- und Iceberg-Strategien in Ihre Plattform einfügen, rüsten Sie sich mit institutioneller Feuerkraft aus - ohne jemals den Einzelhandelsbereich zu verlassen.
Machen Sie sich bereit, das Drehbuch für Ihren Ausführungsprozess umzudrehen. Das Spiel ändert sich, und mit diesen Algorithmen in Ihrem Werkzeugkasten spielen Sie, um zu gewinnen.
Verstehen von Ausführungsalgorithmen
Bevor Sie sich mit den Implementierungsdetails befassen, sollten Sie die Theorie hinter den einzelnen Ausführungsalgorithmen verstehen und wissen, warum sie in verschiedenen Marktszenarien effektiv sind.
- Zeitlich gewichteter Durchschnittspreis (TWAP): TWAP ist ein einfacher Ausführungsalgorithmus, der einen großen Auftrag in gleiche Teile aufteilt und diese in festen Zeitabständen über einen bestimmten Zeitraum versendet. Sein Ziel ist es, den Durchschnittspreis des Instruments während dieses Zeitraums zu erreichen.
-
So funktioniert es:
- Sende Aufträge in regelmäßigen Zeitabständen zwischen Start und Ende.
- Normalerweise werden gleich große Aufträge verwendet (Sie können die Größen aber auch zufällig festlegen).
- Folge einem vorbestimmten Zeitplan, unabhängig von den Kursbewegungen.
-
Verteile die Marktauswirkungen gleichmäßig über die Zeit, um die Abweichung gering zu halten.
-
Wann ist das zu verwenden:
- Sie benötigen einen durchschnittlichen Ausführungskurs über einen bestimmten Zeitraum.
- Die Liquidität ist während des gesamten Handelszeitraums konstant.
- Sie haben ein festes Zeitfenster, um Ihre Aufträge abzuschließen.
-
Sie bevorzugen einen einfachen, vorhersehbaren Ansatz.
-
- Volumengewichteter Durchschnittspreis (VWAP): Der VWAP verbessert den TWAP, indem er die Auftragsgrößen nach dem erwarteten Volumen gewichtet. Anstelle von gleich großen Stücken werden größere Handelsgeschäfte gesendet, wenn das Volumen tendenziell höher ist.
-
So funktioniert es:
- Verteile die Auftragsgröße im Verhältnis zu historischen Volumenmustern.
- Analysiere vergangene Handelsvolumina, um die zukünftige Volumenverteilung vorherzusagen.
- Es kann sich bei einigen Implementierungen an Volumenänderungen in Echtzeit anpassen.
-
Führt in Zeiten hohen Aufkommens mehr aus, um die Auswirkungen zu verringern.
-
Wann ist das zu verwenden:
- Ihre Leistung wird am VWAP gemessen.
- Volume follows a predictable daily pattern.
- Sie handeln auf einem Markt, auf dem die Liquidität während der Sitzung schwankt.
-
Sie wollen sich dem natürlichen Fluss des Marktes anpassen.
-
- Eisberg-Aufträge: Eisberg-Aufträge zielen darauf ab, den wahren Umfang eines großen Auftrags zu verbergen. Es ist immer nur eine kleine „Spitze“ sichtbar; sobald sie sich füllt, erscheint der nächste Teil.
-
So funktioniert es:
- Zeigt nur einen Teil des gesamten Auftrags an.
- Gibt nach der Ausführung jedes Tipps neue sichtbare Teile frei.
- Sie können die sichtbare Größe festlegen oder randomisieren, um die Erkennung zu reduzieren.
-
Sie werden häufig als Limitaufträge zur besseren Preiskontrolle erteilt.
-
Wann ist das zu verwenden:
- Sie müssen den vollen Umfang Ihrer Auftrags verbergen.
- Der Markt ist nicht sehr liquide, und große Transaktionen können die Preise beeinflussen.
- Sie möchten die Ausführung auf einem bestimmten Preisniveau beibehalten.
-
Sie sind besorgt darüber, dass andere Händler Ihren Auftrag entdecken und ihm zuvorkommen könnten.
-
Die Implementation in MQL5
Nachdem wir nun die Theorie hinter diesen Ausführungsalgorithmen verstanden haben, wollen wir sie in MQL5 implementieren. Wir werden einen modularen, objektorientierten Rahmen schaffen, der es ermöglicht, diese Algorithmen einzeln oder kombiniert in einem einheitlichen Ausführungssystem zu verwenden.
Basisklasse: CExecutionAlgorithm
Wir beginnen mit der Definition einer Basisklasse, die gemeinsame Funktionen für alle Ausführungsalgorithmen bietet:
//+------------------------------------------------------------------+ //| Base class for all execution algorithms | //+------------------------------------------------------------------+ class CExecutionAlgorithm { protected: string m_symbol; // Symbol to trade double m_totalVolume; // Total volume to execute double m_executedVolume; // Volume already executed double m_remainingVolume; // Volume remaining to execute datetime m_startTime; // Start time for execution datetime m_endTime; // End time for execution bool m_isActive; // Flag indicating if the algorithm is active int m_totalOrders; // Total number of orders placed int m_filledOrders; // Number of filled orders double m_avgExecutionPrice; // Average execution price double m_executionValue; // Total value of executed orders int m_slippage; // Allowed slippage in points public: // Constructor CExecutionAlgorithm(string symbol, double volume, datetime startTime, datetime endTime, int slippage = 3); // Destructor virtual ~CExecutionAlgorithm(); // Common methods virtual bool Initialize(); virtual bool Execute() = 0; virtual bool Update() = 0; virtual bool Terminate(); // Utility methods bool PlaceOrder(ENUM_ORDER_TYPE orderType, double volume, double price); bool CancelOrder(ulong ticket); void UpdateAverageExecutionPrice(double price, double volume); // Getters string GetSymbol() const { return m_symbol; } double GetTotalVolume() const { return m_totalVolume; } double GetExecutedVolume() const { return m_executedVolume; } double GetRemainingVolume() const { return m_remainingVolume; } datetime GetStartTime() const { return m_startTime; } datetime GetEndTime() const { return m_endTime; } bool IsActive() const { return m_isActive; } int GetTotalOrders() const { return m_totalOrders; } int GetFilledOrders() const { return m_filledOrders; } double GetAverageExecutionPrice() const { return m_avgExecutionPrice; } };
Die Methode PlaceOrder ist besonders wichtig, da sie die eigentliche Auftragsausführung übernimmt und die Volumenverfolgung aktualisiert:
bool CExecutionAlgorithm::PlaceOrder(ENUM_ORDER_TYPE orderType, double volume, double price) { // Prepare the trade request MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_DEAL; request.symbol = m_symbol; request.volume = volume; request.type = orderType; request.price = price; request.deviation = m_slippage; request.magic = 123456; // Magic number for identification // Send the order bool success = OrderSend(request, result); if(!success) { Print("OrderSend error: ", GetLastError()); return false; } // Check the result if(result.retcode != TRADE_RETCODE_DONE) { Print("OrderSend failed with code: ", result.retcode); return false; } // Update statistics m_totalOrders++; m_filledOrders++; m_executedVolume += volume; m_remainingVolume -= volume; UpdateAverageExecutionPrice(price, volume); // Store the order ticket for future reference ulong ticket = result.order; return true; }
Diese Funktion erstellt und sendet eine Marktorder, indem sie einen MqlTradeRequest und ein MqlTradeResult nullinitialisiert, Symbol, Volumen, Ordertyp, Preis, Slippage und eine magische Zahl eingibt und dann OrderSend aufruft. Wenn das Senden fehlschlägt oder der Rückgabecode des Brokers nicht TRADE_RETCODE_DONE ist, wird der Fehler protokolliert und false zurückgegeben. Bei Erfolg werden die internen Zähler (Gesamtzahl/Anzahl der Füllungen, ausgeführtes und verbleibendes Volumen) aktualisiert, der Durchschnittspreis neu berechnet, die Ticket-ID erfasst und „true“ zurückgegeben.
Umsetzung des TWAP
Der TWAP-Algorithmus unterteilt den Ausführungszeitraum in gleiche Zeitintervalle und platziert in jedem Intervall Aufträge von gleicher (oder zufälliger) Größe:
//+------------------------------------------------------------------+ //| Time-Weighted Average Price (TWAP) Algorithm | //+------------------------------------------------------------------+ class CTWAP : public CExecutionAlgorithm { private: int m_intervals; // Number of time intervals int m_currentInterval; // Current interval datetime m_nextExecutionTime; // Next execution time double m_intervalVolume; // Volume per interval bool m_useRandomization; // Whether to randomize order sizes double m_randomizationFactor; // Factor for randomization (0-1) ENUM_ORDER_TYPE m_orderType; // Order type (buy or sell) bool m_firstOrderPlaced; // Flag to track if first order has been placed int m_initialDelay; // Initial delay in seconds before first execution datetime m_lastCheckTime; // Last time order status was checked int m_checkInterval; // How often to check order status (seconds) public: // Constructor CTWAP(string symbol, double volume, datetime startTime, datetime endTime, int intervals, ENUM_ORDER_TYPE orderType, bool useRandomization = false, double randomizationFactor = 0.2, int slippage = 3, int initialDelay = 10); // Implementation of virtual methods virtual bool Initialize() override; virtual bool Execute() override; virtual bool Update() override; virtual bool Terminate() override; // TWAP specific methods void CalculateIntervalVolume(); datetime CalculateNextExecutionTime(); double GetRandomizedVolume(double baseVolume); bool IsTimeToExecute(); };
Schlüssel Methode: CalculateNextExecutionTime
Mit dieser Methode wird sichergestellt, dass die Aufträge zeitlich richtig verteilt werden, wobei der erste Auftrag mit einer gewissen Verzögerung erfolgt:datetime CTWAP::CalculateNextExecutionTime() { // Calculate the duration of each interval int totalSeconds = (int)(m_endTime - m_startTime); int intervalSeconds = totalSeconds / m_intervals; // Calculate the next execution time datetime nextTime; if(m_currentInterval == 0) { // First interval - start at the defined start time plus initial delay nextTime = m_startTime + m_initialDelay; Print("TWAP: First execution time calculated with ", m_initialDelay, " seconds delay: ", TimeToString(nextTime)); } else { // For subsequent intervals, ensure proper spacing from current time datetime currentTime = TimeCurrent(); nextTime = currentTime + intervalSeconds; // Make sure we don't exceed the end time if(nextTime > m_endTime) nextTime = m_endTime; Print("TWAP: Next execution time calculated: ", TimeToString(nextTime), " (interval: ", intervalSeconds, " seconds)"); } return nextTime; }
Diese Methode teilt das Fenster von m_startTime bis m_endTime in m_intervals gleiche Segmente auf und gibt zurück, wann das nächste Handelsgeschäft ausgelöst werden soll: beim allerersten Aufruf ist es einfach m_startTime + m_initialDelay, und bei jedem weiteren Aufruf ist es TimeCurrent() + ein Intervall von Sekunden (aber nie über m_endTime hinaus).
Execute-Methode:Die Execute-Methode prüft, ob es an der Zeit ist, einen Auftrag zu erteilen, und führt die eigentliche Auftragserteilung durch:
bool CTWAP::Execute() { if(!m_isActive) return false; // Check if it's time to execute the next order if(!IsTimeToExecute()) return true; // Not time yet // Calculate the volume for this execution double volumeToExecute = m_useRandomization ? GetRandomizedVolume(m_intervalVolume) : m_intervalVolume; // Ensure we don't exceed the remaining volume if(volumeToExecute > m_remainingVolume) volumeToExecute = m_remainingVolume; // Get current market price double price = 0.0; if(m_orderType == ORDER_TYPE_BUY) price = SymbolInfoDouble(m_symbol, SYMBOL_ASK); else price = SymbolInfoDouble(m_symbol, SYMBOL_BID); Print("TWAP: Placing order for interval ", m_currentInterval, ", Volume: ", DoubleToString(volumeToExecute, 2), ", Price: ", DoubleToString(price, _Digits)); // Place the order using OrderSend directly for more control MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_DEAL; request.symbol = m_symbol; request.volume = volumeToExecute; request.type = m_orderType; request.price = price; request.deviation = m_slippage; request.magic = 123456; // Magic number for identification // Send the order bool success = OrderSend(request, result); if(!success) { Print("TWAP: OrderSend error: ", GetLastError()); return false; } // Check the result if(result.retcode != TRADE_RETCODE_DONE) { Print("TWAP: OrderSend failed with code: ", result.retcode); return false; } // Update statistics m_totalOrders++; m_filledOrders++; m_executedVolume += volumeToExecute; m_remainingVolume -= volumeToExecute; // Update interval counter m_currentInterval++; m_firstOrderPlaced = true; // Calculate the time for the next execution if(m_currentInterval < m_intervals && m_remainingVolume > 0) m_nextExecutionTime = CalculateNextExecutionTime(); else m_isActive = false; // All intervals completed or no volume left Print("TWAP: Executed ", DoubleToString(volumeToExecute, 2), " at price ", DoubleToString(price, _Digits), ". Remaining: ", DoubleToString(m_remainingVolume, 2), ", Next execution: ", TimeToString(m_nextExecutionTime)); return true; }
Diese Execute-Methode verwaltet einen Teil Ihres TWAP-Laufs. Zunächst bricht er ab, wenn die Strategie nicht aktiv ist oder wenn es noch nicht an der Zeit ist, zu handeln. Wenn dies der Fall ist, wird entweder ein fester oder ein zufällig gewählter Teil des verbleibenden Volumens ausgewählt (der nie über das verbleibende Volumen hinausgeht) und dann das aktuelle Angebot (für Käufe) oder die aktuelle Nachfrage (für Verkäufe) abgefragt. Er protokolliert das Intervall, das Volumen und den Preis, erstellt einen MqlTradeRequest mit Ihrem Symbol, dem Volumen, dem Typ, dem Preis, der Slippage und der Magic Number und ruft OrderSend auf. Wenn das Senden fehlschlägt oder der Broker etwas anderes als TRADE_RETCODE_DONE zurückgibt, wird ein Fehler gedruckt und false zurückgegeben.
Bei Erfolg werden die Auftragszähler erhöht, die ausgeführten und verbleibenden Volumina angepasst, die Intervallanzahl erhöht, markiert, dass der erste Auftrag ausgeführt wurde, und dann entweder die nächste Ausführungszeit geplant oder die Strategie deaktiviert, wenn keine Intervalle oder kein Volumen mehr vorhanden sind. Schließlich wird protokolliert, was passiert ist, und true zurückgegeben.
Einführung von VWAP
Der VWAP-Algorithmus ähnelt dem TWAP-Algorithmus, verteilt aber die Auftragsgrößen auf der Grundlage historischer Volumenmuster://+------------------------------------------------------------------+ //| Volume-Weighted Average Price (VWAP) Algorithm | //+------------------------------------------------------------------+ class CVWAP : public CExecutionAlgorithm { private: int m_intervals; // Number of time intervals int m_currentInterval; // Current interval datetime m_nextExecutionTime; // Next execution time double m_volumeProfile[]; // Historical volume profile double m_intervalVolumes[]; // Volume per interval based on profile bool m_adaptiveMode; // Whether to adapt to real-time volume ENUM_ORDER_TYPE m_orderType; // Order type (buy or sell) int m_historyDays; // Number of days to analyze for volume profile bool m_profileLoaded; // Flag indicating if profile was loaded bool m_firstOrderPlaced; // Flag to track if first order has been placed int m_initialDelay; // Initial delay in seconds before first execution datetime m_lastCheckTime; // Last time order status was checked int m_checkInterval; // How often to check order status (seconds) public: // Constructor CVWAP(string symbol, double volume, datetime startTime, datetime endTime, int intervals, ENUM_ORDER_TYPE orderType, int historyDays = 5, bool adaptiveMode = true, int slippage = 3, int initialDelay = 10); // Implementation of virtual methods virtual bool Initialize() override; virtual bool Execute() override; virtual bool Update() override; virtual bool Terminate() override; // VWAP specific methods bool LoadVolumeProfile(); void CalculateIntervalVolumes(); void AdjustToRealTimeVolume(); datetime CalculateNextExecutionTime(); double GetCurrentVWAP(); bool IsTimeToExecute(); };Wie TWAP implementiert auch VWAP die CalculateNextExecutionTime-Methode, um den richtigen Abstand zwischen den Aufträgen zu gewährleisten:
datetime CVWAP::CalculateNextExecutionTime() { // Calculate the duration of each interval int totalSeconds = (int)(m_endTime - m_startTime); int intervalSeconds = totalSeconds / m_intervals; // Calculate the next execution time datetime nextTime; if(m_currentInterval == 0) { // First interval - start at the defined start time plus initial delay nextTime = m_startTime + m_initialDelay; Print("VWAP: First execution time calculated with ", m_initialDelay, " seconds delay: ", TimeToString(nextTime)); } else { // For subsequent intervals, ensure proper spacing from current time datetime currentTime = TimeCurrent(); nextTime = currentTime + intervalSeconds; // Make sure we don't exceed the end time if(nextTime > m_endTime) nextTime = m_endTime; Print("VWAP: Next execution time calculated: ", TimeToString(nextTime), " (interval: ", intervalSeconds, " seconds)"); } return nextTime; }
Umsetzung der Eisberg-Aufträge
Eisberg-Aufträge verbergen den wahren Umfang eines Auftrags, indem sie nur einen kleinen Teil zu einem bestimmten Zeitpunkt dem Markt zugänglich machen://+------------------------------------------------------------------+ //| Iceberg Order Implementation | //+------------------------------------------------------------------+ class CIcebergOrder : public CExecutionAlgorithm { private: double m_visibleVolume; // Visible portion of the order double m_minVisibleVolume; // Minimum visible volume double m_maxVisibleVolume; // Maximum visible volume bool m_useRandomVisibleVolume; // Whether to randomize visible volume int m_orderPlacementDelay; // Delay between order placements (ms) bool m_avoidRoundNumbers; // Whether to avoid round numbers in price double m_limitPrice; // Limit price for the orders ulong m_currentOrderTicket; // Current active order ticket ENUM_ORDER_TYPE m_orderType; // Order type (buy or sell) bool m_orderActive; // Flag indicating if an order is currently active int m_priceDeviation; // Price deviation to avoid round numbers (in points) datetime m_lastCheckTime; // Last time order status was checked int m_checkInterval; // How often to check order status (seconds) int m_maxOrderLifetime; // Maximum lifetime for an order in seconds datetime m_orderPlacementTime; // When the current order was placed public: // Constructor CIcebergOrder(string symbol, double volume, double limitPrice, ENUM_ORDER_TYPE orderType, double visibleVolume, double minVisibleVolume = 0.0, double maxVisibleVolume = 0.0, bool useRandomVisibleVolume = true, int orderPlacementDelay = 1000, bool avoidRoundNumbers = true, int priceDeviation = 2, int slippage = 3); // Implementation of virtual methods virtual bool Initialize() override; virtual bool Execute() override; virtual bool Update() override; virtual bool Terminate() override; // Iceberg specific methods double GetRandomVisibleVolume(); double AdjustPriceToAvoidRoundNumbers(double price); bool CheckAndReplaceOrder(); bool IsOrderFilled(ulong ticket); bool IsOrderPartiallyFilled(ulong ticket, double &filledVolume); bool IsOrderCancelled(ulong ticket); bool IsOrderExpired(ulong ticket); bool IsOrderTimeout(); ulong GetCurrentOrderTicket() { return m_currentOrderTicket; } bool IsOrderActive() { return m_orderActive; } };Die Execute-Methode platziert einen neuen sichtbaren Teil des Auftrags:
bool CIcebergOrder::Execute() { if(!m_isActive) { Print("Iceberg: Execute called but algorithm is not active"); return false; } // If an order is already active, check its status if(m_orderActive) { Print("Iceberg: Execute called with active order ", m_currentOrderTicket); return CheckAndReplaceOrder(); } // Calculate the volume for this execution double volumeToExecute = m_useRandomVisibleVolume ? GetRandomVisibleVolume() : m_visibleVolume; // Ensure we don't exceed the remaining volume if(volumeToExecute > m_remainingVolume) volumeToExecute = m_remainingVolume; Print("Iceberg: Placing order for ", DoubleToString(volumeToExecute, 2), " at price ", DoubleToString(m_limitPrice, _Digits)); // Place the order using OrderSend directly for more control MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_PENDING; request.symbol = m_symbol; request.volume = volumeToExecute; request.type = m_orderType; request.price = m_limitPrice; request.deviation = m_slippage; request.magic = 123456; // Magic number for identification // Send the order bool success = OrderSend(request, result); if(!success) { Print("Iceberg: OrderSend error: ", GetLastError()); return false; } // Check the result if(result.retcode != TRADE_RETCODE_DONE) { Print("Iceberg: OrderSend failed with code: ", result.retcode); return false; } // Store the order ticket m_currentOrderTicket = result.order; m_orderActive = true; m_orderPlacementTime = TimeCurrent(); Print("Iceberg: Order placed successfully. Ticket: ", m_currentOrderTicket, ", Volume: ", DoubleToString(volumeToExecute, 2), ", Remaining: ", DoubleToString(m_remainingVolume, 2)); return true; }
Wenn Execute ausgeführt wird, prüft es zunächst, ob der Algorithmus aktiv ist. Wenn es bereits einen aktiven Eisberg-Auftrag gibt, wird CheckAndReplaceOrder() aufgerufen, um festzustellen, ob diese storniert oder aufgefüllt werden muss. Andernfalls wird eine sichtbare Scheibe (entweder fest oder zufällig) ausgewählt, um die verbleibende Gesamtsumme gekappt und die Größe und der Preis protokolliert.
Anschließend wird die Anfrage eines schwebenden Auftrags (TRADE_ACTION_PENDING) mit Symbol, Volumen, Limitpreis, Slippage und Magic Number erstellt und OrderSend aufgerufen. Bei Fehlern oder nicht erledigten Rückgabewerten wird ein Protokoll erstellt und ein falscher Wert zurückgegeben; bei Erfolg wird das neue Ticket gespeichert, der Auftrag als aktiv markiert, der Zeitpunkt der Platzierung aufgezeichnet, die Details protokolliert und der richtige Wert zurückgegeben.
Die Aktualisierungsmethode umfasst eine Timeout-Erkennung, um sicherzustellen, dass die Aufträge nicht unbegrenzt aktiv bleiben:
bool CIcebergOrder::Update() { if(!m_isActive) { Print("Iceberg: Update called but algorithm is not active"); return false; } // Check if all volume has been executed if(m_remainingVolume <= 0) { Print("Iceberg: All volume executed. Terminating algorithm."); return Terminate(); } // Check if it's time to check order status datetime currentTime = TimeCurrent(); if(currentTime >= m_lastCheckTime + m_checkInterval) { m_lastCheckTime = currentTime; // Log current market conditions double currentBid = SymbolInfoDouble(m_symbol, SYMBOL_BID); double currentAsk = SymbolInfoDouble(m_symbol, SYMBOL_ASK); Print("Iceberg: Market update - Bid: ", DoubleToString(currentBid, _Digits), ", Ask: ", DoubleToString(currentAsk, _Digits), ", Limit Price: ", DoubleToString(m_limitPrice, _Digits)); // If an order is active, check its status if(m_orderActive) { // Check if the order has been active too long if(IsOrderTimeout()) { Print("Iceberg: Order ", m_currentOrderTicket, " has timed out. Replacing it."); // Cancel the current order if(!CancelOrder(m_currentOrderTicket)) { Print("Iceberg: Failed to cancel timed out order ", m_currentOrderTicket); } // Reset order tracking m_orderActive = false; m_currentOrderTicket = 0; // Place a new order after a delay Sleep(m_orderPlacementDelay); return Execute(); } return CheckAndReplaceOrder(); } else { // If no order is active, execute a new one Print("Iceberg: No active order, executing new order"); return Execute(); } } return true; }
Update fragt periodisch den Markt- und Auftragsstatus in den durch m_checkInterval definierten Intervallen ab. Wenn das gesamte Volumen aufgebraucht ist, wird das Programm beendet. Andernfalls wird bei Eintreffen der Prüfzeit der aktuelle Geld-/Briefkurs und der Limitkurs aufgezeichnet. Wenn eine Auftrag aktiv ist, wird geprüft, ob eine Zeitüberschreitung vorliegt: Wenn diese abgelaufen ist, wird der Auftrag abgebrochen, der Status zurückgesetzt, eine Pause für m_orderPlacementDelay eingelegt und der Auftrag erneut ausgeführt, um ein neues Slice zu platzieren; wenn keine Zeitüberschreitung vorliegt, wird CheckAndReplaceOrder() vorgezogen. Liegt kein aktiver Auftrag vor, wird einfach Execute aufgerufen, um den nächsten sichtbaren Teil zu senden.
Implementierung des Performance Analyzers
Unsere Klasse des Performance Analyzer verfolgt diese Metriken und bietet Methoden zur Analyse und zum Vergleich der Algorithmenleistung://+------------------------------------------------------------------+ //| Performance Analyzer for Execution Algorithms | //+------------------------------------------------------------------+ class CPerformanceAnalyzer { private: string m_symbol; // Symbol being analyzed datetime m_startTime; // Analysis start time datetime m_endTime; // Analysis end time double m_decisionPrice; // Price at decision time double m_avgExecutionPrice; // Average execution price double m_totalVolume; // Total volume executed double m_implementationShortfall; // Implementation shortfall double m_marketImpact; // Estimated market impact double m_slippage; // Average slippage int m_executionTime; // Total execution time in seconds double m_priceImprovement; // Total price improvement public: // Constructor CPerformanceAnalyzer(string symbol, double decisionPrice); // Analysis methods void RecordExecution(datetime time, double price, double volume); void CalculateMetrics(); void CompareAlgorithms(CPerformanceAnalyzer &other); // Reporting methods void PrintReport(); void SaveReportToFile(string filename); // Getters double GetImplementationShortfall() const { return m_implementationShortfall; } double GetMarketImpact() const { return m_marketImpact; } double GetSlippage() const { return m_slippage; } int GetExecutionTime() const { return m_executionTime; } double GetPriceImprovement() const { return m_priceImprovement; } };
Der CPerformanceAnalyzer kapselt alle Ihre Post-Trade-Metriken an einem Ort. Bei der Konstruktion geben Sie ihm ein Symbol und den Benchmark-Preis zum Entscheidungszeitpunkt; er stempelt den aktuellen Zeitpunkt als Start. Wenn jeder untergeordnete Auftrag ausgeführt wird, rufen Sie RecordExecution(time, price, volume) auf, wodurch die laufenden Summen aktualisiert werden - kumulatives Volumen, gewichteter durchschnittlicher Ausführungspreis und Zeitstempel. Sobald die Strategie beendet ist (oder in regelmäßigen Abständen), rufen Sie CalculateMetrics() auf, das Berechnungen durchführt:
- Umsetzungsdefizit (die GuV-Differenz zwischen Ihrem Entscheidungspreis und den tatsächlichen Ausführungszahlen),
- Durchschnittlicher Slippage im Vergleich zu den notierten Preisen,
- Geschätzte Marktauswirkungen durch Ihren Fußabdruck,
- Gesamtausführungszeit (Ende minus Anfang),
- Etwaige Preisverbesserungen gegenüber den Benchmarks.
Sie können sogar zwei Durchläufe über CompareAlgorithms(otherAnalyzer) vergleichen, um zu sehen, welche Strategie besser abschneidet. Schließlich gibt PrintReport() die wichtigsten Statistiken zur schnellen Überprüfung in das Protokoll aus, und mit SaveReportToFile(Dateiname) können Sie einen vollständigen Bericht extern speichern. Leichtgewichtige Getter machen jede Metrik für nutzerdefinierte Dashboards oder weitere Analysen zugänglich.
Leistungsvergleich der Algorithmen
Unterschiedliche Marktbedingungen begünstigen unterschiedliche Ausführungsalgorithmen. Hier ist ein allgemeiner Vergleich bzw. eine Faustregel:- TWAP:
- Geeignet für: Stabile Märkte mit gleichbleibender Liquidität
- Vorteile: Einfaches, vorhersehbares Ausführungsmuster
- Nachteile: Passt sich nicht an veränderte Marktbedingungen an
- VWAP:
- Geeignet für: Märkte mit vorhersehbaren Volumenmustern
- Vorteile: Passt sich dem natürlichen Marktrhythmus an, erzielt oft bessere Preise
- Nachteile: Erfordert historische Volumendaten, komplexere Implementierung
- Eisberg-Aufträge:
- Geeignet für: Weniger liquide Märkte oder wenn die Preisempfindlichkeit hoch ist
- Vorteile: Minimiert die Auswirkungen auf den Markt, unterstützt eine Preiskontrolle
- Nachteile: Die Ausführungszeit kann unvorhersehbar sein, es besteht das Risiko einer teilweisen Ausführung.
Integration von Ausführungsalgorithmen in Handelsstrategien
Die wahre Stärke dieser Ausführungsalgorithmen zeigt sich, wenn sie in Handelsstrategien integriert werden. In diesem Abschnitt wird gezeigt, wie unsere Ausführungsalgorithmen in vollständige Handelssysteme integriert werden können.
Ausführungsmanager
Um die Integration zu vereinfachen, erstellen wir eine Klasse für den Execution Manager, die als Fassade für alle unsere Ausführungsalgorithmen dient:
//+------------------------------------------------------------------+ //| Execution Manager - Facade for all execution algorithms | //+------------------------------------------------------------------+ class CExecutionManager { private: CExecutionAlgorithm* m_algorithm; // Current execution algorithm CPerformanceAnalyzer* m_analyzer; // Performance analyzer public: // Constructor CExecutionManager(); // Destructor ~CExecutionManager(); // Algorithm creation methods bool CreateTWAP(string symbol, double volume, datetime startTime, datetime endTime, int intervals, ENUM_ORDER_TYPE orderType, bool useRandomization = false, double randomizationFactor = 0.2, int slippage = 3); bool CreateVWAP(string symbol, double volume, datetime startTime, datetime endTime, int intervals, ENUM_ORDER_TYPE orderType, int historyDays = 5, bool adaptiveMode = true, int slippage = 3); bool CreateIcebergOrder(string symbol, double volume, double limitPrice, ENUM_ORDER_TYPE orderType, double visibleVolume, double minVisibleVolume = 0.0, double maxVisibleVolume = 0.0, bool useRandomVisibleVolume = true, int orderPlacementDelay = 1000, bool avoidRoundNumbers = true, int priceDeviation = 2, int slippage = 3); // Execution methods bool Initialize(); bool Execute(); bool Update(); bool Terminate(); // Performance analysis void EnablePerformanceAnalysis(double decisionPrice); void PrintPerformanceReport(); // Getters CExecutionAlgorithm* GetAlgorithm() { return m_algorithm; } };
Der CExecutionManager fungiert als einfache Fassade für alle Ihre Ausführungsalgorithmen und bindet sie in einen einheitlichen Handelsablauf ein. Intern enthält es einen Zeiger auf den aktuell ausgewählten CExecutionAlgorithm (TWAP, VWAP oder Iceberg) sowie einen CPerformanceAnalyzer, um zu verfolgen, wie gut Ihre Aufträge abschneiden.
Sie wählen Ihre Strategie aus, indem Sie eine der Methoden Create... aufrufen. Dabei geben Sie das Symbol, das Gesamtvolumen, die Start-/Endzeit (für TWAP/VWAP), die Intervallanzahl oder die Slice-Größe, den Auftragstyp und alle algorithmusspezifischen Regler (Randomisierung, Historienfenster, Grenzpreis usw.) an. Nach der Erstellung durchlaufen Sie den üblichen Lebenszyklus:
- Initialize() richtet alle benötigten Zustände oder Daten ein.
- Execute() löst den nächsten Slice- oder Child-Auftrag aus.
- Update() fragt Füllungen, Marktdaten oder Zeitüberschreitungen ab.
-
Terminate() räumt auf, wenn Sie alles ausgefüllt haben oder aufhören wollen.
Wenn Sie die Performance-Analyse mit EnablePerformanceAnalysis() aktivieren, zeichnet der Manager die Ausführungspreise im Vergleich zu einer Entscheidungs-Benchmark auf, und Sie können einen übersichtlichen P/L- und Slippage-Bericht über PrintPerformanceReport() ausgeben. Sie können jederzeit das rohe Algorithmusobjekt mit GetAlgorithm() für nutzerdefinierte Tests oder Metriken abrufen.
Beispiele für die Integration
Hier finden Sie Beispiele dafür, wie unsere Ausführungsalgorithmen in verschiedene Arten von Handelsstrategien integriert werden können:
-
Trendfolgestrategie mit TWAP-Ausführung.
//+------------------------------------------------------------------+ //| Trend-Following Strategy with TWAP Execution | //+------------------------------------------------------------------+ void OnTick() { // Strategy parameters int maPeriodFast = 20; int maPeriodSlow = 50; double volume = 1.0; int executionIntervals = 5; // Calculate indicators double maFast = iMA(Symbol(), PERIOD_CURRENT, maPeriodFast, 0, MODE_SMA, PRICE_CLOSE, 0); double maSlow = iMA(Symbol(), PERIOD_CURRENT, maPeriodSlow, 0, MODE_SMA, PRICE_CLOSE, 0); // Check for entry conditions static bool inPosition = false; static CExecutionManager executionManager; if(!inPosition) { // Buy signal: Fast MA crosses above Slow MA if(maFast > maSlow) { // Create TWAP execution algorithm datetime startTime = TimeCurrent(); datetime endTime = startTime + 3600; // 1 hour execution window if(executionManager.CreateTWAP(Symbol(), volume, startTime, endTime, executionIntervals, ORDER_TYPE_BUY)) { executionManager.Initialize(); inPosition = true; Print("Buy signal detected. Starting TWAP execution."); } } } else { // Update the execution algorithm if(executionManager.Update()) { // Check if execution is complete if(!executionManager.GetAlgorithm().IsActive()) { inPosition = false; Print("TWAP execution completed."); } } } }
-
Mean-Reversion-Strategie mit VWAP-Ausführung.
//+------------------------------------------------------------------+ //| Mean-Reversion Strategy with VWAP Execution | //+------------------------------------------------------------------+ void OnTick() { // Strategy parameters int rsiPeriod = 14; int rsiOversold = 30; int rsiOverbought = 70; double volume = 1.0; int executionIntervals = 5; // Calculate indicators double rsi = iRSI(Symbol(), PERIOD_CURRENT, rsiPeriod, PRICE_CLOSE, 0); // Check for entry conditions static bool inPosition = false; static bool isLong = false; static CExecutionManager executionManager; if(!inPosition) { // Buy signal: RSI oversold if(rsi < rsiOversold) { // Create VWAP execution algorithm datetime startTime = TimeCurrent(); datetime endTime = startTime + 3600; // 1 hour execution window if(executionManager.CreateVWAP(Symbol(), volume, startTime, endTime, executionIntervals, ORDER_TYPE_BUY)) { executionManager.Initialize(); inPosition = true; isLong = true; Print("Buy signal detected. Starting VWAP execution."); } } // Sell signal: RSI overbought else if(rsi > rsiOverbought) { // Create VWAP execution algorithm datetime startTime = TimeCurrent(); datetime endTime = startTime + 3600; // 1 hour execution window if(executionManager.CreateVWAP(Symbol(), volume, startTime, endTime, executionIntervals, ORDER_TYPE_SELL)) { executionManager.Initialize(); inPosition = true; isLong = false; Print("Sell signal detected. Starting VWAP execution."); } } } else { // Update the execution algorithm if(executionManager.Update()) { // Check if execution is complete if(!executionManager.GetAlgorithm().IsActive()) { inPosition = false; Print("VWAP execution completed."); } } // Check for exit conditions if(isLong && rsi > rsiOverbought) { executionManager.Terminate(); inPosition = false; Print("Exit signal detected. Terminating VWAP execution."); } else if(!isLong && rsi < rsiOversold) { executionManager.Terminate(); inPosition = false; Print("Exit signal detected. Terminating VWAP execution."); } } }
- Breakout-Strategie mit Eisberg-Aufträgen.
//+------------------------------------------------------------------+ //| Breakout Strategy with Iceberg Orders | //+------------------------------------------------------------------+ void OnTick() { // Strategy parameters int channelPeriod = 20; double volume = 1.0; double visibleVolume = 0.1; // Calculate indicators double upperChannel = iHigh(Symbol(), PERIOD_CURRENT, iHighest(Symbol(), PERIOD_CURRENT, MODE_HIGH, channelPeriod, 1)); double lowerChannel = iLow(Symbol(), PERIOD_CURRENT, iLowest(Symbol(), PERIOD_CURRENT, MODE_LOW, channelPeriod, 1)); double currentPrice = SymbolInfoDouble(Symbol(), SYMBOL_BID); // Check for entry conditions static bool inPosition = false; static CExecutionManager executionManager; if(!inPosition) { // Buy signal: Price breaks above upper channel if(currentPrice > upperChannel) { // Create Iceberg Order double limitPrice = upperChannel; // Place limit order at breakout level if(executionManager.CreateIcebergOrder(Symbol(), volume, limitPrice, ORDER_TYPE_BUY, visibleVolume, visibleVolume * 0.8, visibleVolume * 1.2, true, 1000, true, 2, 3)) { executionManager.Initialize(); inPosition = true; Print("Buy breakout detected. Starting Iceberg Order execution."); } } // Sell signal: Price breaks below lower channel else if(currentPrice < lowerChannel) { // Create Iceberg Order double limitPrice = lowerChannel; // Place limit order at breakout level if(executionManager.CreateIcebergOrder(Symbol(), volume, limitPrice, ORDER_TYPE_SELL, visibleVolume, visibleVolume * 0.8, visibleVolume * 1.2, true, 1000, true, 2, 3)) { executionManager.Initialize(); inPosition = true; Print("Sell breakout detected. Starting Iceberg Order execution."); } } } else { // Update the execution algorithm if(executionManager.Update()) { // Check if execution is complete if(!executionManager.GetAlgorithm().IsActive()) { inPosition = false; Print("Iceberg Order execution completed."); } } } }
In allen drei Beispielen folgt die Integration dem gleichen Muster:
-
Zustand beibehalten
- Der boolesche Wert inPosition zeigt an, ob Sie gerade einen Auftrag ausführen.
-
Ein statischer CExecutionManager executionManager lebt über Ticks hinweg, um den Lebenszyklus des gewählten Algorithmus zu verwalten.
-
Eingabe-Logik
- Rufen Sie bei Ihrem Einstiegssignal (MA-Crossover, RSI-Schwelle, Channel-Break) die entsprechende Erstellungsmethode im executionManager (TWAP, VWAP oder Iceberg) auf und übergeben Sie dabei Symbol, Gesamtvolumen, Zeitfenster oder Limitpreis, Slice-Parameter und Ordertyp.
-
Wenn die Erstellung erfolgreich war, rufen Sie sofort executionManager.Initialize() auf, setzen inPosition=true und protokollieren Ihren Start.
-
Laufende Ausführung
- Solange inPosition wahr ist, ruft OnTick() jedes Mal executionManager.Update() auf.
-
Innerhalb von Update() ruft der Manager intern Execute() nach Bedarf auf, fragt Füllungen ab, behandelt Timeouts oder Marktaktualisierungen und plant den nächsten Slice (oder storniert/ersetzt Child-Orders des Eisbergs).
-
Abschluss & Ausstieg
- Nach jeder Aktualisierung überprüfen Sie executionManager.GetAlgorithm()->IsActive(). Sobald der Wert false zurückgegeben wird (alle Intervalle sind abgeschlossen oder das Volumen ist erschöpft), setzen Sie inPosition=false und protokollieren, dass die Ausführung abgeschlossen ist.
-
Im Beispiel der VWAP-Mittelwertumkehr gibt es eine zusätzliche Ausstiegsprüfung: Wenn der Kurs während der Ausführung den RSI-Schwellenwert überschreitet, rufen Sie executionManager.Terminate() auf, um vorzeitig zu beenden.
- Trend-Following + TWAP
Eröffnung: Das Überschreiten des schnellen MA über den langsamen MA löst sie aus.
Ausführung: Teilt Ihren 1-Lot-Kauf in 5 gleiche Abschnitte über die nächste Stunde auf - Mittelwertumkehr + VWAP
Eröffnung: RSI < 30 für Käufe, > 70 für Verkäufe
Ausführung: Verteilt 1 Lot gegen historisches Volumen über 5 Teile in einer Stunde
Frühzeitiges Ausscheiden: Wenn das Signal umschlägt (z. B. RSI > 70 während eines Kaufs), bricht executionManager.Terminate() die restlichen Teile ab. - Breakout + Eisberg
Eröffnung: Preis bricht Kanal nach oben (kaufen) oder nach unten (verkaufen)
Ausführung: Platziert ein schwebenden Limit-Auftrag zum Ausbruchspreis, wobei jeweils nur ~0,1 Lots aufgedeckt und aufgefüllt werden, bis das volle 1 Lot erreicht ist
Durch den Austausch von CreateTWAP, CreateVWAP oder CreateIcebergOrder können Sie jeden beliebigen Ausführungsalgorithmus in Ihre Signallogik einbinden, ohne dass Sie die Auftragsverwaltungsformulare duplizieren müssen.
Integrierte Strategie
Vollständiger Code:
//+------------------------------------------------------------------+ //| IntegratedStrategy.mq5 | //| Copyright 2025, MetaQuotes Software Corp. | //| https://www.metaquotes.net | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Software Corp." #property link "https://www.metaquotes.net" #property version "1.00" #include "ExecutionAlgorithm.mqh" #include "TWAP.mqh" #include "VWAP.mqh" #include "IcebergOrder.mqh" #include "PerformanceAnalyzer.mqh" #include "ExecutionManager.mqh" // Input parameters input int FastMA = 20; // Fast moving average period input int SlowMA = 50; // Slow moving average period input double TradingVolume = 0.1; // Trading volume input bool UseAdaptiveExecution = true; // Use adaptive execution based on market conditions // Global variables CExecutionManager *g_executionManager = NULL; int g_maHandle1 = INVALID_HANDLE; int g_maHandle2 = INVALID_HANDLE; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Initialize execution manager g_executionManager = new CExecutionManager(Symbol(), 3, UseAdaptiveExecution); // Initialize indicators g_maHandle1 = iMA(Symbol(), Period(), FastMA, 0, MODE_SMA, PRICE_CLOSE); g_maHandle2 = iMA(Symbol(), Period(), SlowMA, 0, MODE_SMA, PRICE_CLOSE); if(g_maHandle1 == INVALID_HANDLE || g_maHandle2 == INVALID_HANDLE) { Print("Failed to create indicator handles"); return INIT_FAILED; } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Clean up if(g_executionManager != NULL) { delete g_executionManager; g_executionManager = NULL; } // Release indicator handles IndicatorRelease(g_maHandle1); IndicatorRelease(g_maHandle2); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Update execution algorithms if(g_executionManager != NULL) g_executionManager.UpdateAlgorithms(); // Only process at bar open if(iVolume(_Symbol, PERIOD_CURRENT, 0) > 1) return; // Get indicator values double fastMA[2], slowMA[2]; if(CopyBuffer(g_maHandle1, 0, 0, 2, fastMA) <= 0 || CopyBuffer(g_maHandle2, 0, 0, 2, slowMA) <= 0) { Print("Failed to copy indicator buffers"); return; } // Check for trend signals bool buySignal = (fastMA[0] > slowMA[0]) && (fastMA[1] <= slowMA[1]); bool sellSignal = (fastMA[0] < slowMA[0]) && (fastMA[1] >= slowMA[1]); // Execute signals using the execution manager if(buySignal) { Print("Buy signal detected"); if(UseAdaptiveExecution) { // Let the execution manager select the best algorithm g_executionManager.ExecuteSignal(SIGNAL_TYPE_BUY, TradingVolume); } else { // Manually create a TWAP algorithm datetime currentTime = TimeCurrent(); CTWAP *twap = g_executionManager.CreateTWAP(TradingVolume, currentTime, currentTime + 3600, 6, ORDER_TYPE_BUY, true); } } else if(sellSignal) { Print("Sell signal detected"); if(UseAdaptiveExecution) { // Let the execution manager select the best algorithm g_executionManager.ExecuteSignal(SIGNAL_TYPE_SELL, TradingVolume); } else { // Manually create a TWAP algorithm datetime currentTime = TimeCurrent(); CTWAP *twap = g_executionManager.CreateTWAP(TradingVolume, currentTime, currentTime + 3600, 6, ORDER_TYPE_SELL, true); } } } //+------------------------------------------------------------------+
Der Expertenberater IntegratedStrategy.mq5 beginnt mit der Deklaration seiner Metadaten (Copyright, Link, Version) und enthält die Header für alle unsere Ausführungsalgorithmusklassen und den Performance-Analyzer. Anschließend werden vier vom Nutzer einstellbare Eingaben definiert: die schnellen und langsamen SMA-Perioden, das Gesamthandelsvolumen pro Signal und ein boolesches Flag zum Umschalten der „adaptiven“ Ausführung (bei der der Manager entscheidet, ob TWAP, VWAP oder Iceberg unter der Haube verwendet wird). Ein globaler Zeiger auf CExecutionManager und zwei Indikator-Handles werden ebenfalls deklariert, damit sie zwischen Ticks bestehen bleiben.
In OnInit() instanziieren wir den Ausführungsmanager - und übergeben ihm das aktuelle Symbol, maximal drei gleichzeitige Algorithmen und unser adaptives Flag - und erstellen dann die beiden SMA-Indikator-Handles. Wenn eines der beiden Handles fehlschlägt, wird die Initialisierung abgebrochen. OnDeinit() räumt einfach auf, indem es den Manager löscht und die Indikator-Handles freigibt, um sicherzustellen, dass kein Speicher oder Handle-Lecks entstehen, wenn der EA entfernt oder die Plattform heruntergefahren wird.
Die Hauptlogik befindet sich in OnTick(). Zunächst rufen wir UpdateAlgorithms() auf dem Ausführungsmanager auf, sodass alle bestehenden untergeordneten Aufträge (TWAP-Slices, VWAP-Eimer oder Iceberg-Legs) je nach Bedarf bearbeitet, storniert oder aufgefüllt werden. Dann warten wir auf einen neuen Balken (indem wir ihn überspringen, wenn sich das Tick-Volumen noch aufbaut). Wenn der Balken eröffnet, rufen wir die letzten beiden SMA-Werte für die schnelle und die langsame Periode ab. Ein Kreuzen von unten nach oben löst ein Kaufsignal aus, der umgekehrte Fall löst ein Verkaufssignal aus.
Wenn die adaptive Ausführung aktiviert ist, übergeben wir das Signal und die Volumen an g_executionManager.ExecuteSignal() und überlassen der Methode die Auswahl des geeigneten Algorithmus. Andernfalls starten wir manuell eine TWAP-Instanz für ein einstündiges Fenster und sechs Teile. Dieses Muster trennt Ihre Einstiegslogik (Trenderkennung) sauber von Ihrer Auftragsverwaltungslogik, sodass dieselbe Fassade mehrere Ausführungsstile steuern kann, ohne dass die Vorlage dupliziert wird.
Nach der Aufnahme von Take Profit ändert sich der Code in:
//+------------------------------------------------------------------+ //| IntegratedStrategy.mq5 | //| Copyright 2025, MetaQuotes Software Corp. | //| https://www.metaquotes.net | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Software Corp." #property link "https://www.metaquotes.net" #property version "1.00" #include "ExecutionAlgorithm.mqh" #include "TWAP.mqh" #include "VWAP.mqh" #include "IcebergOrder.mqh" #include "PerformanceAnalyzer.mqh" #include "ExecutionManager.mqh" #include <Trade\Trade.mqh> // Input parameters input int FastMA = 20; // Fast moving average period input int SlowMA = 50; // Slow moving average period input double TradingVolume = 0.1; // Trading volume input bool UseAdaptiveExecution = true; // Use adaptive execution based on market conditions input double EquityTPPercent = 10.0; // Equity Take Profit in percent input double EquitySLPercent = 5.0; // Equity Stop Loss in percent // Global variables CExecutionManager *g_executionManager = NULL; int g_maHandle1 = INVALID_HANDLE; int g_maHandle2 = INVALID_HANDLE; double g_initialEquity = 0.0; CTrade trade; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Record initial equity g_initialEquity = AccountInfoDouble(ACCOUNT_EQUITY); // Initialize execution manager g_executionManager = new CExecutionManager(Symbol(), 3, UseAdaptiveExecution); // Initialize indicators g_maHandle1 = iMA(Symbol(), Period(), FastMA, 0, MODE_SMA, PRICE_CLOSE); g_maHandle2 = iMA(Symbol(), Period(), SlowMA, 0, MODE_SMA, PRICE_CLOSE); if(g_maHandle1 == INVALID_HANDLE || g_maHandle2 == INVALID_HANDLE) { Print("Failed to create indicator handles"); return INIT_FAILED; } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Clean up if(g_executionManager != NULL) { delete g_executionManager; g_executionManager = NULL; } // Release indicator handles IndicatorRelease(g_maHandle1); IndicatorRelease(g_maHandle2); } //+------------------------------------------------------------------+ //| Check equity-based TP and SL, then reset baseline | //+------------------------------------------------------------------+ void CheckEquityTPandSL() { double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY); double tpEquity = g_initialEquity * (1.0 + EquityTPPercent / 100.0); double slEquity = g_initialEquity * (1.0 - EquitySLPercent / 100.0); if(currentEquity >= tpEquity) { Print("Equity Take Profit reached: ", currentEquity); CloseAllPositions(); g_initialEquity = currentEquity; Print("Equity baseline reset to: ", g_initialEquity); } else if(currentEquity <= slEquity) { Print("Equity Stop Loss reached: ", currentEquity); CloseAllPositions(); g_initialEquity = currentEquity; Print("Equity baseline reset to: ", g_initialEquity); } } //+------------------------------------------------------------------+ //| Close all open positions | //+------------------------------------------------------------------+ void CloseAllPositions() { CPositionInfo m_position; CTrade m_trade; for(int i = PositionsTotal() - 1; i >= 0; i--) // loop all Open Positions if(m_position.SelectByIndex(i)) { // select a position m_trade.PositionClose(m_position.Ticket()); // then delete it --period } } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Check and reset equity thresholds CheckEquityTPandSL(); // Update execution algorithms if(g_executionManager != NULL) g_executionManager.UpdateAlgorithms(); // Only process at bar open if(iVolume(_Symbol, PERIOD_CURRENT, 0) > 1) return; // Get indicator values double fastMA[2], slowMA[2]; if(CopyBuffer(g_maHandle1, 0, 0, 2, fastMA) <= 0 || CopyBuffer(g_maHandle2, 0, 0, 2, slowMA) <= 0) { Print("Failed to copy indicator buffers"); return; } // Check for trend signals bool buySignal = (fastMA[0] > slowMA[0]) && (fastMA[1] <= slowMA[1]); bool sellSignal = (fastMA[0] < slowMA[0]) && (fastMA[1] >= slowMA[1]); // Execute signals using the execution manager if(buySignal) { Print("Buy signal detected"); if(UseAdaptiveExecution) { // Let the execution manager select the best algorithm g_executionManager.ExecuteSignal(SIGNAL_TYPE_BUY, TradingVolume); } else { datetime currentTime = TimeCurrent(); CTWAP *twap = g_executionManager.CreateTWAP(TradingVolume, currentTime, currentTime + 3600, 6, ORDER_TYPE_BUY, true); } } else if(sellSignal) { Print("Sell signal detected"); if(UseAdaptiveExecution) { // Let the execution manager select the best algorithm g_executionManager.ExecuteSignal(SIGNAL_TYPE_SELL, TradingVolume); } else { datetime currentTime = TimeCurrent(); CTWAP *twap = g_executionManager.CreateTWAP(TradingVolume, currentTime, currentTime + 3600, 6, ORDER_TYPE_SELL, true); } } } //+------------------------------------------------------------------+
Backtest-Ergebnisse
1. Kapital- & Saldenkurven
Die grünen Treppenstufen des Saldos zeigen das buchhalterisch Kapital Ihres Kontos an, wenn der EA eine Position schließt; die blaue Kapitalkurve glättet nicht realisierte P&L zwischen den Handelsgeschäften. Von Januar bis Anfang März ist ein klarer Aufwärtstrend zu erkennen, mit einigen Rückschlägen, die jeweils einen Höchststand von 10-16 % erreichen, bevor die nächste Gewinnserie den Gewinn wiederherstellt. Dieses Muster deutet darauf hin, dass das System in einem Trend gedeiht, aber immer noch erträgliche Aktieneinbrüche erleidet.
2. Volumen & Risikoauslastung
Unten schrumpfen die Dreiecke der „Einzahlungslast“ im Laufe der Zeit allmählich - dies ist Ihre Positionsgröße in Prozent des Eigenkapitals. Es beginnt bei 10 % Ihres Guthabens und verringert sich mit steigendem Eigenkapital (bei fester Volumengröße), d. h. unser Risiko pro Handel nimmt mit steigendem Konto ab. Aus diesem Grund bleiben die Drawdowns proportional ähnlich, auch wenn Ihr Kapital in Dollar steigt.
3. Wichtige Rentabilitätskennzahlen
-
Ersteinzahlung: $1.000
-
Nettogewinn: + 703 $ (eine Rendite von 70% über ~2 Monate)
-
Profit-Faktor: 2,34 (Sie verdienen 2,34 $ für jeden verlorenen $1)
-
Erwartete Auszahlung: durchschnittlich 2,34 $ pro Handel
-
Sharpe Ratio: 5,47 (sehr hohe risikoadjustierte Rendite)
Diese Zahlen zeigen uns, dass die Strategie nicht nur rentabel ist, sondern auch einen gesunden Puffer über die eigene Volatilität hinaus erwirtschaftet.
4. Drawdown und Recovery-Faktor
-
Maximaler Drawdown des Kapitals: 156 Punkte oder 9,99 %.
-
Maximaler Kapital-Drawdown: 228 Punkte oder 15,89 %.
-
Recovery-Faktor: 3,08 (Nettogewinn ÷ maximale Inanspruchnahme)
Ein Erholungsfaktor von mehr als 2 gilt im Allgemeinen als gut, d. h. mit 3,08 erwirtschaften Sie mehr als das Dreifache Ihres schlimmsten Verlusts an Gewinn.
5. Handelsverteilung
-
Gesamtpositionen: 300 (600 Handelsgeschäfte, d. h. jedes Eröffnen + Schließen zählt als zwei)
-
Gewinnrate: 76% (228 Gewinner gegenüber 72 Verlierer)
-
Durchschnittlicher Gewinn: $5,39
-
Durchschnittlicher Verlust: - $7,31
Obwohl unsere Gewinnquote und unser Gewinnfaktor sehr hoch sind, müssen wir feststellen, dass unsere Verlierer im Durchschnitt größer sind als die Gewinner - etwas, das wir im Auge behalten sollten, wenn sich die Marktbedingungen ändern.
6. Streifen und Beständigkeit
-
Maximale Anzahl an aufeinanderfolgenden Siegen: 87 Handelsgeschäfte, + $302
-
Max. aufeinanderfolgende Verluste: 23 Handelsgeschäfte, - $156
-
Durchschnittliche Siegesserie: 57 Handelsgeschäfte
-
Durchschnittliche Pechsträhne: 18 Handelsgeschäfte
Lange Gewinnserien treiben den Aufwärtstrend voran, während die längste Verlustserie immer noch nur etwa 15 % des Kapitals kostet.
Schlussfolgerung
Stellen Sie sich vor, Sie sind ein Einzelhändler in einer überfüllten Marktarena - jeder Tick zählt, jeder Füllpreis verrät Gewinn oder Verlust. Indem Sie TWAP, VWAP und Eisberg-Aufträge in Ihr Toolkit einbinden, reagieren Sie nicht mehr nur auf Preisschwankungen, sondern steuern sie. Diese einst hochkarätigen, institutionellen Algorithmen stehen Ihnen jetzt zur Verfügung, um die Liquidität wie ein Laser zu durchschneiden und chaotische Orderbücher in Chancen zu verwandeln.
TWAP wird zu Ihrem stetigen Metronom, das Ihre Größe gleichmäßig über ein festgelegtes Intervall verteilt - perfekt, wenn die Gezeiten ruhig sind und Sie einfach nur eine ruhige Fahrt wünschen. VWAP verwandelt Sie in einen versierten Volumen-Tracker, der die stärksten Handelszeiten des Tages angreift und den Puls des Marktes selbst bestimmt. Und wenn Sie Ihre Absichten verschleiern müssen, lassen die Eisberg-Aufträge Ihre wahre Größe unter der Oberfläche verschwinden und geben gerade genug preis, um gefüllt zu werden, ohne die großen Spieler zu verschrecken.
Aber das sind nicht nur einzelne Tricks. Mit unserem modularen MQL5-Framework können Sie sie in jede beliebige Strategie einbinden - Trendfolger, Mean-Reverser, Ausbruchsjäger - und zwar so einfach wie das Aufsetzen eines neuen Objektivs. Mit einer einzigen ExecutionManager-Fassade können Sie Algorithmen während des Handels austauschen, kombinieren oder sogar überlagern, während der PerformanceAnalyzer wie ein Falke den Überblick behält und Slippage, Shortfall und Marktauswirkungen bis auf den letzten Pip misst.
Was kommt als Nächstes? Betrachten Sie die Ausführung als ein Lebewesen, das sich anpasst. Lassen Sie Ihren TWAP aus Volatilitätsspitzen lernen. Leiten Sie Ihre VWAP-Teile an die tiefsten Pools weiter. Bringen Sie Ihrem Eisberg bei, zu spüren, wo Raubtiere lauern und sich tiefer zu verstecken. Und warum dort aufhören? Nutzen Sie das maschinelle Lernen, um die perfekte Mikrosekunde für den Abschuss vorherzusagen, oder mischen Sie Auftragstypen zu maßgeschneiderten Hybriden, die Ihrem einzigartigen Vorteil entsprechen.
Die Welt des Handels steht niemals still - und Ihre Auftragsausführung sollte es auch nicht. Tauchen Sie ein, experimentieren Sie kühn und verwandeln Sie jedes Stückchen, jede Füllung in einen kalkulierten Vorteil. Ihr Vorteil wartet im Code.
Zu Ihrer Erleichterung finden Sie hier eine Zusammenfassung der in diesem Artikel enthaltenen Dateien:
Dateiname | Beschreibung |
---|---|
ExecutionAlgorithm.mqh | Basisklasse für alle Ausführungsalgorithmen |
TWAP.mqh | Umsetzung des zeitgewichteten Durchschnittspreises |
VWAP.mqh | Umsetzung des volumengewichteten Durchschnittspreises |
IcebergOrder.mqh | Umsetzung der Eisberg-Aufträge |
PerformanceAnalyzer.mqh | Werkzeuge zur Analyse der Ausführungsleistung |
ExecutionManager.mqh | Fassade für die einfache Integration in Handelsstrategien |
IntegratedStrategy.mq5 | Beispiel EA mit Integration in eine Handelsstrategie |
IntegratedStrategy - Take Profit.mq5 | Beispiel EA mit Integration einer Handelsstrategie mit Take Profit und Stop Loss in Prozent des Kontostandes |
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/17934
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.





- 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.
Testing your algo.
IN file, ExecutionAlgorithm.mqh, added this line request.type_filling = ORDER_FILLING_IOC; in placing order to fix order placing issue.
Zurück getestet es auf M5, es öffnete nur 1 Handel für 2 Monate Zeitraum, keine partielle Bestellung geöffnet.
Getestet es auf H1, es nie angewendet, die SL oder TP und alle Trades geschlossen in Verlust.
auch während der Kompilierung erzeugt es Warnungen
schlagen Sie vor, wie der Algo getestet werden soll,
Zeitrahmen. und andere Empfehlungen.
auch beim Kompilieren werden Warnungen erzeugt
Ich habe die Codezeile
m_volumeProfile[intervalIndex] += rates[i].tick_volu
in
geändert.
Es behebt die Warnungen
Jetzt brauchen Sie guidence in Bezug auf meine anderen Fragen, wie
Zeitrahmen
und auch
warum alle Trades während Backtest Ergebnis in Verlust
wie man diese große Arbeit von Ihnen zu testen.
schlagen Sie vor, wie der Algo getestet werden soll,
Zeitrahmen. und andere Empfehlungen.
Die Warnungen sind nicht das Problem, könnten aber schnell behoben werden. Aber ja, es wäre toll, wenn der Autor Schritt für Schritt zeigen könnte, welche Einstellungen und Eingaben er für den Backtest verwendet hat.
Ich stimme Dominic zu, da die Warnungen nur Warnungen sind. Die Ergebnisse von I_Virgo sind wahrscheinlich darauf zurückzuführen, dass er den falschen Zeitrahmen und das falsche Währungspaar verwendet hat. Aus dem Backtest-Bericht mit fast 2000 Bars geht hervor, dass es entweder M1 oder M5 als Zeitrahmen mit einem unbekannten Paar war.
Es wäre schön, wenn MQ den Zeitrahmen und das Währungspaar oder die Währungspaare hinzufügen und auch die Paar-Ergebnisse in mehr Details zum Back-Test-Bericht trennen würde, so dass wir die Back-Test-Ergebnisse des Autors genauer replizieren und seine Anwendbarkeit auf die Forex-Paare bestimmen könnten. Außerdem wäre es äußerst hilfreich, wenn der EA Text auf das Diagramm schreiben könnte, während er läuft.
Ich denke auch, dass es ein großartiger Artikel ist und plane, ihn gründlich zu studieren, um seine Techniken auf andere EAs zu übertragen.
CapeCoddah