![Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 18): Neues Auftragssystems (I)](https://c.mql5.com/2/49/Developing_a_trading_Expert_Advisor_from_scratch_009_600x314.jpg)
Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 18): Neues Auftragssystems (I)
Einführung
Seit wir mit der Dokumentation dieses EAs in dieser Serie im ersten Artikel Einen handelnden Expert Advisor von Grund auf neu entwickeln begonnen haben, hat er verschiedene Änderungen und Verbesserungen erfahren, wobei das gleiche System der Aufträge auf dem Chart beibehalten wurde. Er ist sehr einfach und funktionell. In vielen Situationen ist er jedoch nicht für den realen Handel geeignet.
Natürlich könnten wir das ursprüngliche System um einige Dinge erweitern, sodass wir Informationen über offene und schwebende Aufträge erhalten. Aber das würde unseren Code in einen Frankenstein verwandeln, was den Verbesserungsprozess letztlich zu einem echten Alptraum machen würde. Selbst wenn Sie Ihre eigene Methodik haben, wird diese mit der Zeit verloren gehen, da der Code zu groß und zu kompliziert wird.
Wir müssen also ein völlig anderes System in Bezug auf das von uns verwendete Auftragsmodell schaffen. Gleichzeitig sollte sie für den Händler leicht verständlich sein und alle Informationen enthalten, die wir für ein sicheres Arbeiten benötigen.
Wichtiger Hinweis
Das System, das ich in diesem Artikel beschreibe, ist für Netting-Konten ACCOUNT_MARGIN_MODE_RETAIL_NETTING konzipiert, die nur eine offene Position pro Symbol erlauben. Wenn Sie ein Hedging-Konto ACCOUNT_MARGIN_MODE_RETAIL_HEDGING verwenden, wird der Artikel Ihrem EA nichts nützen, da Sie in diesem Fall so viele Positionen haben können, wie Sie benötigen, und diese sich nicht gegenseitig beeinträchtigen. Sie können also einfach alle Änderungen aus dem endgültigen Code zurücksetzen und löschen.
Der Modus ACCOUNT_MARGIN_MODE_RETAIL_HEDGING wird am häufigsten verwendet, wenn Sie möchten, dass ein EA automatisch läuft und Positionen ohne Ihr Zutun öffnet und schließt, während Sie gleichzeitig manuell weiter handeln. Außerdem handeln Sie denselben Vermögenswert, den auch Ihr EA handelt. Beim Hedging haben die Aktivitäten Ihres EA keinen Einfluss auf Ihre Positionen, sodass Sie unabhängige Positionen haben.
Aus diesem Grund werde ich alle hinzugefügten oder geänderten Codeteile hervorheben. Wir werden alle Änderungen und Ergänzungen langsam und schrittweise einführen, sodass Sie, wenn Sie etwas aus Ihrem Code entfernen müssen, die entsprechenden Codeteile leicht finden können.
Obwohl die Änderungen entfernt werden können, gibt es einen Teil, in dem ich das System überprüfe. Auch wenn Sie die Änderungen, die wir hier betrachten, speichern, können Sie diesen EA auf jeder Art von Handelskonto, NETTING und HEDGING, verwenden, da der Expert Advisor für das jeweilige Modell prüft und entsprechend anpasst.
1.0. Planung
Als Erstes muss man verstehen, was mit den Aufträgen geschieht, die dem System hinzugefügt und ausgeführt werden, sobald sie die entsprechenden Preise erreichen. Viele Händler wissen das vielleicht nicht, oder besser gesagt, sie haben nie darüber nachgedacht und daher keine Tests durchgeführt, um diese Idee zu verstehen und dann ein angemessenes und zuverlässiges System zu implementieren.
Um zu verstehen, was tatsächlich passiert, lassen Sie uns ein einfaches Beispiel analysieren: Sie haben eine offene Position, sagen wir Kaufen, für einen bestimmten Vermögenswert mit dem Anfangsvolumen von 1 Lot. Dann erteilen Sie einen neuen Kaufauftrag über 2 Lots zu einem etwas höheren Preis. So weit so gut, nichts Besonderes. Aber sobald diese 2 Lots gekauft sind, wird etwas passieren, und genau da liegt das Problem.
Sobald diese beiden Lots gekauft sind, haben Sie 3 Lots, aber das System aktualisiert Ihren Anfangspreis und setzt ihn auf den Durchschnitt. Das scheint klar zu sein, und jeder versteht das. Aber was passiert mit den Stop-Loss und Take-Profit der neuen Position?
Viele Händler kennen die Antwort nicht, aber sie sollten darüber nachdenken. Wenn Sie bei allen Geschäften ein OCO-Auftragssystem (One Cancel The Other) verwenden, können Sie jedes Mal etwas Interessantes feststellen, wenn Sie eine Position mit einem Teil des gesamten gehandelten Volumens eröffnen oder schließen.
Im Artikel über Cross-Orders habe ich eine Möglichkeit vorgestellt, Take-Profit und Stop-Loss direkt auf dem Chart zu platzieren, ohne das Standardsystem von MetaTrader 5 zu verwenden. In der Tat ist diese Methode fast identisch mit dem MetaTrader 5-System, da seine Funktionalität sehr nahe an der der Plattform ist, aber mit den richtigen Proportionen. Nach einigen Tests habe ich jedoch festgestellt, dass bei einem offenen OCO-Auftrag und einem schwebenden Auftrag (pending order) auch der OCO-Auftrag vom Handelssystem erfasst wird, da der Preis den im Auftrag angegebenen Wert erreicht hat. Neben dem neuen Durchschnittspreis haben sich auch die Take-Profit- und Stop-Loss-Werte geändert - sie werden auf die Werte geändert, die im letzten erfassten OCO-Auftrag angegeben wurden. Je nachdem, wie er konfiguriert ist, wird der EA ihn also sofort schließen, nachdem das Handelssystem neue Take-Profit- und Stop-Loss-Werte gemeldet hat.
Dies geschieht aufgrund der folgenden, im EA implementierten Prüfung:
inline double CheckPosition(void) { double Res = 0, last, sl; ulong ticket; last = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_LAST); for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol()) { ticket = PositionGetInteger(POSITION_TICKET); Res += PositionGetDouble(POSITION_PROFIT); sl = PositionGetDouble(POSITION_SL); if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { if (last < sl) ClosePosition(ticket); }else { if ((last > sl) && (sl > 0)) ClosePosition(ticket); } } return Res; };
Die hervorgehobenen Linien führen diese Prüfung durch und schließen einen Auftrag, wenn der Preis den Kanal verlässt, der durch die Take-Profit- und Stop-Loss-Werte gebildet wird.
Es ist wichtig, dies zu wissen, denn wir sprechen hier nicht von einem EA-Fehler, einem Problem oder Defekt des Handelssystems. Das eigentliche Problem besteht darin, dass wir dem Geschehen meist nicht die gebührende Aufmerksamkeit schenken und es ignorieren, bis es nicht mehr möglich ist, die Augen vor dieser Tatsache zu verschließen.
Wenn Sie auf eine Art und Weise handeln, die keine Durchschnittspreise vorsieht, werden Sie diese Art von Ereignissen nicht erleben. Es gibt jedoch ein sehr breites Spektrum von Operationen, bei denen ein Durchschnittspreis angemessen und sogar notwendig ist. Wenn Sie also in diesen Fällen das System ohne Kenntnis der oben genannten Details ausführen, können Sie die Position früher schließen, als Sie möchten, selbst wenn Sie zuvor den OCO-Auftrag der offenen Position entsprechend geändert haben. Sobald ein schwebender OCO-Auftrag erfasst wird, werden die Schwellenwerte ( jedes Mal, wenn ich von Schwellenwerten spreche, beziehe ich mich auf die vorherigen Take-Profit und Stop-Loss.) durch die Werte ersetzt, die in dem kürzlich erfassten schwebenden OCO-Auftrag angegeben sind.
Es gibt eine Möglichkeit, dies zu beheben oder zu vermeiden: Verwenden Sie keine OCO-Aufträge, zumindest wenn Sie bereits eine offene Position haben. Alle anderen Aufträge, die in das System eingegeben werden, müssen vom einfachen Typ sein, ohne dass Take-Profit und Stop-Loss festgelegt werden.
Im Grunde ist das alles. Aber wenn ein EA auf dem Chart ist, ist er da, um uns zu helfen und uns das Leben zu erleichtern. Es macht also keinen Sinn, einen Expert Advisor zu programmieren, wenn man ihn später nicht nutzen kann.
2.0. Einführung eines neuen Systems
Wir müssen kleine Änderungen am Code vornehmen, um das System zu implementieren und sicherzustellen, dass es so funktioniert, wie wir es erwarten — der EA sollte uns dabei helfen und Fehler vermeiden helfen.
Das ist nicht sehr schwierig, aber diese Änderungen garantieren, dass wir nicht Gefahr laufen, dass der OCO-Befehl zu einem unerwünschten Zeitpunkt eintrifft und unsere Abläufe durcheinander bringt.
Beginnen wir mit den folgenden Änderungen:
2.0.1. Modifikation der Klasse C_Router
Die Klasse C_Router ist für das Parsen und Senden von Aufträgen an uns zuständig. Fügen wir ihm eine private Variable hinzu. Wenn eine offene Position für den Vermögenswert gefunden wird, der vom EA gehandelt wird, speichert diese Variable die entsprechenden Informationen. Jedes Mal, wenn der EA wissen will, ob es eine offene Position gibt, wird er uns dies mitteilen.
Diese Implementierung ist im folgenden Code dargestellt. Dieser Code wird jedoch in dem Artikel weiter geändert werden. Ich möchte nur alle Änderungen Schritt für Schritt zeigen, damit Sie verstehen, wie der Änderungsprozess tatsächlich aussah.
//+------------------------------------------------------------------+ inline bool ExistPosition(void) const { return m_bContainsPosition; } //+------------------------------------------------------------------+ void UpdatePosition(void) { static int memPositions = 0, memOrder = 0; ulong ul; int p, o; p = PositionsTotal(); o = OrdersTotal(); if ((memPositions != p) || (memOrder != o)) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); RemoveAllsLines(); ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); memOrder = o; memPositions = p; m_bContainsPosition = false; }; for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false); SetLineOrder(ul, PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, PositionGetDouble(POSITION_SL), HL_STOP, true); } for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol()) { SetLineOrder(ul, OrderGetDouble(ORDER_PRICE_OPEN), HL_PRICE, true); SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true); SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true); } }; //+------------------------------------------------------------------+
Die hervorgehobenen Zeilen zeigen die erforderlichen Ergänzungen zur Durchführung aller Prüfungen, die später an bestimmten Stellen des EA-Codes hinzugefügt werden.
In gewisser Weise könnten wir alle Checks und Anpassungen nur in der Klasse C_Router implementieren, aber das wird nicht ausreichen. Ich werde dies später erklären. Lassen Sie uns nun mit den Änderungen fortfahren. Nachdem wir die obige Prüfung erstellt haben, fügen wir einen Konstruktor hinzu, um die neu hinzugefügte Variable zu initialisieren.
C_Router() : m_bContainsPosition(false) {}
Nun bearbeiten wir die Funktion, die schwebende Aufträge platziert, wie folgt:
ulong CreateOrderPendent(const bool IsBuy, const double Volume, const double Price, const double Take, const double Stop, const bool DayTrade = true) { double last = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_LAST); ZeroMemory(TradeRequest); ZeroMemory(TradeResult); TradeRequest.action = TRADE_ACTION_PENDING; TradeRequest.symbol = Terminal.GetSymbol(); TradeRequest.volume = Volume; TradeRequest.type = (IsBuy ? (last >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : (last < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP)); TradeRequest.price = NormalizeDouble(Price, Terminal.GetDigits()); TradeRequest.sl = NormalizeDouble((m_bContainsPosition ? 0 : Stop), Terminal.GetDigits()); TradeRequest.tp = NormalizeDouble((m_bContainsPosition ? 0 : Take), Terminal.GetDigits()); TradeRequest.type_time = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC); TradeRequest.stoplimit = 0; TradeRequest.expiration = 0; TradeRequest.type_filling = ORDER_FILLING_RETURN; TradeRequest.deviation = 1000; TradeRequest.comment = "Order Generated by Experts Advisor."; if (!Send()) return 0; return TradeResult.order; };
Die hervorgehobenen Teile sind die Änderungen, die umgesetzt werden sollen.
Kehren wir nun zu dem aktualisierten Code zurück, um eine neue Änderung vorzunehmen. Denken Sie daran, dass sie von der Funktion OnTrade aufgerufen wird und jedes Mal, wenn die Aufträge geändert werden, aufgerufen wird. Dies ist im folgenden Code zu sehen:
void UpdatePosition(void) { static int memPositions = 0, memOrder = 0; ulong ul; int p, o; p = PositionsTotal(); o = OrdersTotal(); if ((memPositions != p) || (memOrder != o)) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); RemoveAllsLines(); ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); memOrder = o; memPositions = p; m_bContainsPosition = false; }; for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false); SetLineOrder(ul, PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, PositionGetDouble(POSITION_SL), HL_STOP, true); } for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol()) { if (m_bContainsPosition) { ModifyOrderPendent(ul, OrderGetDouble(ORDER_PRICE_OPEN), 0, 0); (OrderSelect(ul) ? 0 : 0); } SetLineOrder(ul, OrderGetDouble(ORDER_PRICE_OPEN), HL_PRICE, true); SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true); SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true); } };
Nun müssen wir sicherstellen, dass der Nutzer eine einfache Pending Order nicht in eine Pending OCO Order umwandelt, wenn bereits eine offene Position besteht, d.h. wenn der Nutzer die Toolbox öffnet und versucht, den Take-Profit oder Stop-Loss zu bearbeiten. Wenn der Nutzer dies versucht, informiert uns der Handelsserver über die OnTrade-Funktion, sodass der EA sofort von der Änderung erfährt und die vom Nutzer vorgenommene Änderung rückgängig macht, wodurch die Zuverlässigkeit des Systems gewährleistet wird.
Aber es gibt noch einen weiteren Punkt, der ebenfalls geändert werden muss, nämlich die Marktaufträge. Dies ist eine sehr einfache Änderung, da sie keine Änderungen im Zusammenhang mit Kontrollen erfordert. Der neue Funktionscode ist unten abgebildet:
ulong ExecuteOrderInMarket(const bool IsBuy, const double Volume, const double Price, const double Take, const double Stop, const bool DayTrade = true) { ZeroMemory(TradeRequest); ZeroMemory(TradeResult); TradeRequest.action = TRADE_ACTION_DEAL; TradeRequest.symbol = Terminal.GetSymbol(); TradeRequest.volume = Volume; TradeRequest.type = (IsBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL); TradeRequest.price = NormalizeDouble(Price, Terminal.GetDigits()); TradeRequest.sl = NormalizeDouble((m_bContainsPosition ? 0 : Stop), Terminal.GetDigits()); TradeRequest.tp = NormalizeDouble((m_bContainsPosition ? 0 : Take), Terminal.GetDigits()); TradeRequest.type_time = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC); TradeRequest.stoplimit = 0; TradeRequest.expiration = 0; TradeRequest.type_filling = ORDER_FILLING_RETURN; TradeRequest.deviation = 1000; TradeRequest.comment = "[ Order Market ] Generated by Experts Advisor."; if (!Send()) return 0; return TradeResult.order; };
Auch wenn es seltsam erscheinen mag, bieten diese Änderungen bereits ein ausreichendes Maß an Sicherheit (zumindest ein akzeptables Maß), das es ermöglicht, einen OCO, einen schwebenden oder Marktauftrag nicht zu verpassen, wenn es bereits eine offene Position für denselben Vermögenswert gibt, den der EA handelt. Der EA kümmert sich also um das System der Auftragsübermittlung.
Das ist doch alles zu schön und zu wunderbar, um wahr zu sein, nicht wahr? Man könnte meinen, dass dies bereits eine gute Sicherheitsmarge garantiert, aber das ist nicht ganz so. Diese Änderungen gewährleisten, dass ein OCO-Auftrag nicht als Pending Order auf den Markt kommt und dort bleibt, wenn es eine offene Position gibt. Aber diese Modifikationen haben einen fatalen Fehler, der, wenn er nicht richtig korrigiert wird, Ihnen große Kopfschmerzen und einen enormen Verlust bescheren kann, der Ihr Konto zum Einsturz bringen kann oder die Position, die der Broker mangels Marge geschlossen hat.
Beachten Sie, dass nicht geprüft wird, ob ein schwebender Auftrag innerhalb eines Limits für offene Positionen liegt oder nicht. Dies ist sehr gefährlich, denn wenn Sie beim derzeitigen Stand des Systems einen schwebenden OCO-Auftrag außerhalb des Limits für offene Positionen hinzufügen, lässt der EA nicht zu, dass dieser Auftrag vom Typ OCO ist. Mit anderen Worten: Der Auftrag hat keine Limits, es handelt sich um einen Auftrag ohne Take-Profit oder Stop-Loss. Wenn Sie also eine Position schließen und diesen schwebenden Auftrag eingeben, müssen Sie diese Werte so schnell wie möglich anpassen. Wenn Sie dies vergessen, besteht die Gefahr, dass Sie eine offene Position ohne Limits haben.
Um die Level einzustellen, müssten Sie das Nachrichtenfenster öffnen, den Auftrag öffnen und die Werte der Preislevel bearbeiten. Dieses Problem wird bald behoben sein. Lassen Sie uns nun das aktuelle Problem beheben.
Daher müssen wir die Art und Weise ändern, wie der EA mit schwebenden Aufträgen arbeitet, denn wenn der Nutzer einen Auftrag ohne die Levels erstellen möchte, wird der EA ihn als etwas Normales behandeln, aber wenn der Nutzer am Ende einen Limit-Auftrag erstellt, muss der EA den Auftrag entsprechend anpassen: Limits setzen, wenn der Auftrag außerhalb der offenen Position platziert wird, oder die Limits eines schwebenden Auftrags entfernen, wenn der Auftrag innerhalb der offenen Position platziert wird.
2.0.2. Arbeit innerhalb der Grenzen
Als Erstes werden wir Überprüfungsgrenzen festlegen. Es ist ganz einfach. Es erfordert jedoch viel Liebe zum Detail, da es zwei mögliche Fälle gibt, die auf weitere Fälle ausgedehnt werden können. Um zu verstehen, was zu tun ist, reichen diese beiden Fälle aus.
Der erste Fall ist oben dargestellt. Es wird einen schwebenden Auftrag außerhalb der Limits haben (Limit ist in diesem Fall ein Bereich im Gradienten, d.h. wir haben eine Position, entweder kaufen oder verkaufen, und wir haben ein oberes Limit. Erreicht oder überschreitet der Kurs diese Grenze, wird die Position geschlossen. In diesem Fall kann der schwebende Auftrag vom Nutzer als OCO-Order konfiguriert werden, und der EA muss die Art und Weise akzeptieren, in der die Order vom Nutzer konfiguriert wurde, sei es eine einfache Order oder eine OCO-Order — der EA sollte in diesem Fall nicht eingreifen.
Der zweite Fall ist unten dargestellt. Hier befindet sich der schwebende Auftrag innerhalb des Bereichs, der durch die offene Position begrenzt wird. In diesem Fall muss der EA die vom Nutzer konfigurierbaren Grenzen entfernen.
Achten Sie darauf, dass unabhängig davon, wie weit das Limit geht, ob wir kaufen oder verkaufen, wenn die Order in diesen Bereich eintritt, muss der EA die Limitwerte aus der schwebenden Order entfernen. Verlässt er jedoch diesen Bereich, muss der EA den Auftrag so belassen, wie er vom Nutzer konfiguriert wurde.
Nach dieser Definition müssen wir einige Variablen erstellen (siehe unten):
class C_Router : public C_HLineTrade { protected: MqlTradeRequest TradeRequest; MqlTradeResult TradeResult; private : bool m_bContainsPosition; struct st00 { double TakeProfit, StopLoss; bool IsBuy; }m_Limits; // ... Rest of the code
Jetzt haben wir eine Möglichkeit, die Limits in der Phase zu überprüfen, in der in OnTrade das Ereignis der Auslösung der Order eintritt. Also ändern wir wieder einmal die Aktualisierungsfunktion der Klasse C_Router.
// Rest of the code.... //+------------------------------------------------------------------+ #define macro_MAX(A, B) (A > B ? A : B) #define macro_MIN(A, B) (A < B ? A : B) void UpdatePosition(void) { static int memPositions = 0, memOrder = 0; ulong ul; int p, o; double price; bool bTest; p = PositionsTotal(); o = OrdersTotal(); if ((memPositions != p) || (memOrder != o)) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); RemoveAllsLines(); ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); memOrder = o; memPositions = p; m_bContainsPosition = false; m_Limits.StopLoss = -1; m_Limits.TakeProfit = -1; m_Limits.IsBuy = false; }; for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false); SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true); m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY; } for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol()) { price = OrderGetDouble(ORDER_PRICE_OPEN); if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else { bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price)); bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price)); bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price))); } if ((m_bContainsPosition) && (bTest)) { ModifyOrderPendent(ul, price, 0, 0); (OrderSelect(ul) ? 0 : 0); } SetLineOrder(ul, price, HL_PRICE, true); SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true); SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true); } }; #undef macro_MAX #undef macro_MIN //+------------------------------------------------------------------+ // ... The rest of the code...
Die Klasse behandelt nun schwebende Aufträge, um zu unterscheiden, ob sie sich in einem Bereich befinden, in dem es keine schwebenden OCO-Aufträge geben kann, oder ob sie außerhalb dieses Bereichs liegen. Beachten Sie, dass diese Funktion bei jeder Statusänderung im Auftragssystem aufgerufen wird. Als Erstes werden die Variablen richtig initialisiert.
m_Limits.StopLoss = -1; m_Limits.TakeProfit = -1; m_Limits.IsBuy = false;
Sobald dies geschehen ist, prüfen wir, ob es eine offene Position gibt oder nicht. Dies kann jederzeit geschehen. Sobald wir eine offene Position haben, wird sie den Bereich abgrenzen, in dem es nicht möglich ist, OCO-Aufträge zu haben - dies ist an diesem Punkt erreicht.
SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true); m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
Jetzt können wir jeden der ausstehenden Aufträge daraufhin überprüfen, ob er sich innerhalb des Grenzbereichs befindet oder nicht. Ein wichtiger Hinweis: Hier müssen wir wissen, ob es sich um einen Kauf- oder Verkaufsauftrag handelt, da wir möglicherweise keinen Take-Profit, sondern einen Stop-Loss haben. In diesem Fall ist es also notwendig, die Art der Stelle zu kennen. Um dies zu verstehen, sehen Sie sich die folgenden Abbildungen an:
Wenn es sich um eine Verkaufsposition handelt, markiert der Stop-Loss die obere Grenze...
Bei Kaufpositionen ist der Stop-Loss eine Untergrenze.
Mit anderen Worten, in einigen Fällen können schwebende Aufträge vom Typ OCO sein, wenn sie maximal erteilt werden. In anderen Fällen sollten sie unter dem Mindestwert liegen. Es kann aber auch ein anderer Fall eintreten, in dem die ausstehenden Aufträge auch vom Typ OCO sein können, wie unten dargestellt:
Schwebende Aufträge außerhalb der Grenzen, was ein typischer Fall ist.
Um dies zu überprüfen, verwenden wir das folgende Fragment
price = OrderGetDouble(ORDER_PRICE_OPEN); if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else { bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price)); bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price)); bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price))); }
Es wird geprüft, ob offene Stellen vorhanden sind. Ist dies nicht der Fall, muss der EA die vom Nutzer festgelegten Auftragseinstellungen berücksichtigen. Wenn eine Position gefunden wird, werden die folgenden Prüfungen in der folgenden Reihenfolge durchgeführt:
- Wenn wir „short“ sind, muss der Preis, zu dem eine Pending Order platziert wird, größer sein als der Stop-Loss-Wert der offenen Position;
- Bei einer Kaufposition muss der Preis, zu dem eine Pending Order platziert wird, unter dem Stop-Loss-Wert der offenen Position liegen;
- Wenn das System den Auftrag immer noch als OCO-Typ akzeptiert, führen wir einen letzten Test durch, um festzustellen, ob der Auftrag nicht mehr in Position ist.
Sobald dies geschehen ist, können wir sicher sein, dass der schwebende Auftrag so belassen werden kann, wie der Nutzer ihn konfiguriert hat, und das Leben geht weiter... Aber es gibt noch einen letzten Zusatz, bevor wir zum nächsten Schritt übergehen. Eigentlich ist es die letzte Prüfung, die ich zu Beginn des Artikels erwähnt habe, und diese ist im folgenden Fragment zu finden:
void UpdatePosition(void) { // ... Internal code... for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false); SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true); m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY; } if (AccountInfoInteger(ACCOUNT_TRADE_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) m_bContainsPosition = false; for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol()) { price = OrderGetDouble(ORDER_PRICE_OPEN); if (m_bContainsPosition) { if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else { bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price)); bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price)); bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price))); } if (bTest) { ModifyOrderPendent(ul, price, 0, 0); (OrderSelect(ul) ? 0 : 0); } } SetLineOrder(ul, price, HL_PRICE, true); SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true); SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true); } };
In dem hervorgehobenen Teil wird geprüft, ob die Kontoart HEDGING ist. Selbst wenn die Variable anzeigte, dass wir mit Grenzwerten arbeiten müssten, würde sie zu diesem Zeitpunkt anzeigen, dass wir nicht mit ihnen arbeiten müssen. Daher ignoriert der EA alle Einschränkungen, die sich ergeben können, und behandelt die Aufträge so, wie sie vom Nutzer konfiguriert wurden. Dies ist eine sehr einfache Prüfung, die jedoch in diesem Stadium durchgeführt werden muss, um sicherzustellen, dass unser gesamtes System ordnungsgemäß funktioniert.
2.1.0. Anpassen der Positionierung
Obwohl die meisten Probleme durch die Anpassung der Objektklasse C_Router gelöst wurden, haben wir immer noch kein angemessenes System. Es gibt noch ein weiteres Problem, das gelöst werden muss: die Korrektur des Systems der Positionierung mit der Maus, was ein weiterer, ebenso wichtiger Schritt ist. Dies hat mehrere Auswirkungen, da wir definieren müssen, wie das in der Klasse C_OrderView vorhandene System funktionieren soll.
Die große Frage, die davon abhängt, wie Sie tatsächlich vorgehen wollen, ist, ob die Klasse C_OrderView Limits für schwebende Aufträge erstellt, wenn diese die Limits einer offenen Position verlassen oder nicht.
Obwohl es verlockend ist, dies jedes Mal zu tun, gibt es einige Dinge, die bei dieser Entscheidung berücksichtigt werden müssen. Aber machen wir es Stück für Stück, wie Jack the Ripper sagen würde. Die einzige wirkliche Änderung, die wir in der Klasse C_OrderView vornehmen müssen, ist im folgenden Code dargestellt:
inline void MoveTo(uint Key) { static double local = 0; datetime dt; bool bEClick, bKeyBuy, bKeySell, bCheck; double take = 0, stop = 0, price; bEClick = (Key & 0x01) == 0x01; //Left click bKeyBuy = (Key & 0x04) == 0x04; //SHIFT pressed bKeySell = (Key & 0x08) == 0x08; //CTRL pressed Mouse.GetPositionDP(dt, price); if (bKeyBuy != bKeySell) { Mouse.Hide(); bCheck = CheckLimits(price); } else Mouse.Show(); ObjectMove(Terminal.Get_ID(), m_Infos.szHLinePrice, 0, 0, price = (bKeyBuy != bKeySell ? price : 0)); ObjectMove(Terminal.Get_ID(), m_Infos.szHLineTake, 0, 0, take = (bCheck ? 0 : price + (m_Infos.TakeProfit * (bKeyBuy ? 1 : -1)))); ObjectMove(Terminal.Get_ID(), m_Infos.szHLineStop, 0, 0, stop = (bCheck ? 0 : price + (m_Infos.StopLoss * (bKeyBuy ? -1 : 1)))); if((bEClick) && (bKeyBuy != bKeySell) && (local == 0)) CreateOrderPendent(bKeyBuy, m_Infos.Volume, local = price, take, stop, m_Infos.IsDayTrade); local = (local != price ? 0 : local); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLinePrice, OBJPROP_COLOR, (bKeyBuy != bKeySell ? m_Infos.cPrice : clrNONE)); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineTake, OBJPROP_COLOR, (take > 0 ? m_Infos.cTake : clrNONE)); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineStop, OBJPROP_COLOR, (stop > 0 ? m_Infos.cStop : clrNONE)); };
Ist das alles? Ja, das ist alles, was wir tun müssen. Der Rest der Logik befindet sich in der Klasse C_Router. Was nicht geändert wurde, wird vom MetaTrader 5-eigenen Nachrichtensystem ausgeführt, denn bei einer Änderung in der Liste der Aufträge (Pending oder Positionen) wird die OnTrade-Routine aufgerufen. Wenn dies geschieht, wird die Aktualisierungsroutine in der Klasse C_Router ausgelöst, die die notwendigen Anpassungen vornimmt. Aber es gibt einen Code, der in dieser Routine auftaucht und der Sie verrückt machen kann, wenn Sie ihn suchen. Tatsächlich befindet sie sich in der Klasse C_Router, wie unten zu sehen ist:
#define macro_MAX(A, B) (A > B ? A : B) #define macro_MIN(A, B) (A < B ? A : B) inline bool CheckLimits(const double price) { bool bTest = false; if ((!m_bContainsPosition) || ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1))) return bTest; bTest = ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price)); if (m_Limits.TakeProfit == 0) { bTest = (bTest ? bTest : (!m_Limits.IsBuy) && (m_Limits.StopLoss > price)); bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price)); } return bTest; }; #undef macro_MAX #undef macro_MIN
Dieser Code befand sich innerhalb der Aktualisierungsfunktion der Klasse C_Router. Es wurde von dort entfernt und durch einen Anruf ersetzt...
2.2.0. Begrenzen oder nicht begrenzen, das ist hier die Frage
Unsere Arbeit ist fast abgeschlossen, aber es bleibt noch eine letzte Frage zu klären, die im Moment vielleicht die schwierigste ist. Wenn Sie den Inhalt bis zu diesem Punkt verfolgt und verstanden haben, ist Ihnen vielleicht aufgefallen, dass das System sehr gut funktioniert, aber immer wenn ein als OCO konfigurierter schwebender Auftrag in die Limits einer offenen Position eintritt, verliert der Auftrag die für ihn konfigurierten Limits. Das wird immer passieren.
Wenn der Händler jedoch zufällig die Limits für offene Positionen ändert oder wenn ein Auftrag, der ein OCO war und nun ein einfacher Auftrag ist, diese Limits überschreitet, bleibt er ein einfacher Auftrag. Wir haben also ein potenzielles Problem.
Ein weiteres wichtiges Detail: Wie soll der EA vorgehen? Soll der Händler darüber informiert werden, dass gerade ein einfacher Auftrag für den Vermögenswert eingegangen ist? Oder sollte sie einfach Grenzen für den Auftrag festlegen und ihn in OCO umwandeln?
Diese Frage ist äußerst wichtig, wenn Sie wirklich wollen, dass der EA Ihnen beim Handel hilft. Es wäre gut, wenn die EA eine Warnung herausgeben würde, die uns darüber informiert, was passiert ist. Aber wenn Sie in einem Vermögenswert, in einer Zeit der hohen Volatilität ist es auch gut, dass die EA automatisch einige Grenze für die Reihenfolge, sodass es nicht bleiben dort lose, die großen Schaden verursachen können, noch bevor wir erkennen, was passiert ist.
Um dieses Problem zu lösen, wurde das System ein letztes Mal geändert, aber wie ich oben erklärt habe, sollten Sie ernsthaft darüber nachdenken, wie Sie mit diesem Problem umgehen. Im Folgenden wird beschrieben, wie ich eine mögliche Lösung umgesetzt habe.
Zunächst habe ich eine neue Variable hinzugefügt, über die der Händler dem EA mitteilen kann, welche Prozedur durchgeführt werden soll. Sie ist unten dargestellt:
// ... Code ... input group "Chart Trader" input int user20 = 1; //Leverage factor input int user21 = 100; //Take Profit (financial) input int user22 = 75; //Stop Loss (financial) input color user23 = clrBlue; //Price line color input color user24 = clrForestGreen; //Take Profit line color input color user25 = clrFireBrick; //Stop Loss line color input bool user26 = true; //Day Trade? input bool user27 = true; //Always set loose order limits // ... Rest of the code... void OnTrade() { Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, OrderView.UpdateRoof(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY]); OrderView.UpdatePosition(user27); } // ... Rest of the code...
Nun müssen wir zur Klasse C_Router zurückkehren und ihr 3 neue Funktionen hinzufügen. Er ist unten zu sehen.
//+------------------------------------------------------------------+ void SetFinance(const int Contracts, const int Take, const int Stop) { m_Limits.Contract = Contracts; m_Limits.FinanceTake = Take; m_Limits.FinanceStop = Stop; } //+------------------------------------------------------------------+ inline double GetDisplacementTake(const bool IsBuy, const double Vol) const { return (Terminal.AdjustPrice(m_Limits.FinanceTake * (Vol / m_Limits.Contract) * Terminal.GetAdjustToTrade() / Vol) * (IsBuy ? 1 : -1)); } //+------------------------------------------------------------------+ inline double GetDisplacementStop(const bool IsBuy, const double Vol) const { return (Terminal.AdjustPrice(m_Limits.FinanceStop * (Vol / m_Limits.Contract) * Terminal.GetAdjustToTrade() / Vol) * (IsBuy ? -1 : 1)); } //+------------------------------------------------------------------+
Der Code behält die Werte bei, die im Chart Trader mitgeteilt werden, wie im nächsten Bild zu sehen ist, korrigiert aber auch proportional den Wert, den wir als Limits in OCO Pending Orders verwenden sollten.
Das heißt, wir wissen bereits, woher wir die Werte bekommen, die wir verwenden werden, damit der EA einen OCO-Auftrag minimal konfigurieren kann, wenn ein schwebender Auftrag ausgelöst wird. Wie Sie vielleicht vermuten, müssen wir jedoch eine neue Änderung am Aktualisierungscode der Klasse C_Router vornehmen. Die Änderung ist unten dargestellt:
void UpdatePosition(bool bAdjust) { static int memPositions = 0, memOrder = 0; ulong ul; int p, o; long info; double price, stop, take, vol; bool bIsBuy, bTest; p = PositionsTotal(); o = OrdersTotal(); if ((memPositions != p) || (memOrder != o)) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); RemoveAllsLines(); ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); memOrder = o; memPositions = p; m_bContainsPosition = false; m_Limits.StopLoss = -1; m_Limits.TakeProfit = -1; m_Limits.IsBuy = false; }; for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false); SetLineOrder(ul, take = PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, stop = PositionGetDouble(POSITION_SL), HL_STOP, true); m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY; m_Limits.TakeProfit = (m_Limits.TakeProfit < 0 ? take : (m_Limits.IsBuy ? (m_Limits.TakeProfit > take ? m_Limits.TakeProfit : take) : (take > m_Limits.TakeProfit ? m_Limits.TakeProfit : take))); m_Limits.StopLoss = (m_Limits.StopLoss < 0 ? stop : (m_Limits.IsBuy ? (m_Limits.StopLoss < stop ? m_Limits.StopLoss : stop) : (stop < m_Limits.StopLoss ? m_Limits.StopLoss : stop))); } if ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) m_bContainsPosition = false; for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol()) { price = OrderGetDouble(ORDER_PRICE_OPEN); take = OrderGetDouble(ORDER_TP); stop = OrderGetDouble(ORDER_SL); bTest = CheckLimits(price); if ((take == 0) && (stop == 0) && (bAdjust) && (!bTest)) { info = OrderGetInteger(ORDER_TYPE); vol = OrderGetDouble(ORDER_VOLUME_CURRENT); bIsBuy = ((info == ORDER_TYPE_BUY_LIMIT) || (info == ORDER_TYPE_BUY_STOP) || (info == ORDER_TYPE_BUY_STOP_LIMIT) || (info == ORDER_TYPE_BUY)); take = price + GetDisplacementTake(bIsBuy, vol); stop = price + GetDisplacementStop(bIsBuy, vol); ModifyOrderPendent(ul, price, take, stop); } if ((take != 0) && (stop != 0) && (bTest)) ModifyOrderPendent(ul, price, take = 0, stop = 0); SetLineOrder(ul, price, HL_PRICE, true); SetLineOrder(ul, take, HL_TAKE, true); SetLineOrder(ul, stop, HL_STOP, true); } };
Die hervorgehobenen Zeilen prüfen, ob der Auftrag frei ist und ob der EA in den Auftrag eingreifen soll. Wenn der EA eingreift, wird die Berechnung auf der Grundlage des im Chart Trader dargestellten Finanzwertes und des ausstehenden Auftragsvolumens vorgenommen. Der einfache schwebende Auftrag erhält dann Limits, die auf der Grundlage der gesammelten Informationen berechnet werden. Der EA wird Zeilen erstellen, um darüber zu informieren, dass die Limits erstellt werden, wodurch der einfache schwebende Auftrag in einen schwebenden OCO-Auftrag umgewandelt wird.
Schlussfolgerung
Trotz aller Versuche, das System zu testen und zu sehen, ob es das Konto als Hedging-Konto erkennt, war ich in diesem Stadium nicht erfolgreich. Der EA meldete immer, dass sich das Konto im Netting-Modus befand, auch wenn die MetaTrader 5-Plattform meldete, dass es ein Hedging-Konto war. Deshalb sollten Sie vorsichtig sein. Obwohl es so funktioniert, wie wir es wollten, werden schwebende Aufträge sogar auf einem Hedging-Konto angepasst, als wäre es ein Netting-Konto...
Das Video zeigt deutlich, was oben beschrieben wurde. Wie Sie sehen, ist das System sehr interessant in der Anwendung.
Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/10462
![Lernen Sie, wie man ein Handelssystem mit dem Chaikin Oscillator entwickelt](https://c.mql5.com/2/48/why-and-how__1.png)
![Datenwissenschaft und maschinelles Lernen (Teil 06): Gradientenverfahren](https://c.mql5.com/2/47/data_science_articles_series__1.png)
![Neuronale Netze leicht gemacht (Teil 17): Reduzierung der Dimensionalität](https://c.mql5.com/2/48/Neural_networks_made_easy_017.png)
![Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 17): Zugang zu Daten im Internet (III)](https://c.mql5.com/2/47/development.png)
![MQL5 - Sprache von Handelsstrategien, eingebaut ins Kundenterminal MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- 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.