English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
preview
Mehrere Indikatoren in einem Chart (Teil 06): Umwandlung des MetaTrader 5 in ein RAD-System (II)

Mehrere Indikatoren in einem Chart (Teil 06): Umwandlung des MetaTrader 5 in ein RAD-System (II)

MetaTrader 5Handelssysteme | 18 Mai 2022, 07:47
374 0
Daniel Jose
Daniel Jose

Einführung

In meinem vorherigen Artikel habe ich Ihnen gezeigt, wie man einen Chart Trade mit Hilfe von MetaTrader 5 Objekten erstellt und somit die Plattform in ein RAD-System verwandelt. Das System funktioniert sehr gut, und sicher haben viele der Leser über die Erstellung einer Bibliothek nachgedacht, die es ermöglichen würde, die Funktionsweise des vorgeschlagenen Systems zu erweitern. Auf dieser Grundlage wäre es möglich, einen intuitiveren Expert Advisor mit einer schöneren und einfacher zu bedienenden Oberfläche zu entwickeln.

Die Idee ist so gut, dass sie mich dazu veranlasst hat, Ihnen Schritt für Schritt zu zeigen, wie Sie mit dem Hinzufügen von Funktionen beginnen können. Hier werde ich zwei neue und wichtige Funktionen implementieren (diese Informationen dienen als Grundlage für die Implementierung anderer Funktionen in der von uns benötigten und gewünschten Weise). Die einzige Einschränkung ist unsere Kreativität, da die Elemente selbst auf unterschiedlichste Weise verwendet werden können.


Planung

Die Änderungen an unserer IDE werden wie in den folgenden Bildern dargestellt sein:

         

Wie Sie sehen können, gibt es kleinere Änderungen am Design selbst. Es wurden zwei neue Bereiche hinzugefügt: der eine enthält den Namen des Assets, der andere den kumulierten Wert des Tages. Nun, das sind die Dinge, auf die wir verzichten können, und sie werden unsere Entscheidungen nicht beeinflussen. Aber sie können trotzdem interessant sein. Ich werde die einfachste und korrekte Art und Weise zeigen, wie wir unserer IDE Funktionalität hinzufügen können. Öffnen wir also die Liste der Objekte in der neuen Schnittstelle. Sie sieht wie folgt aus:


Den beiden eingekreisten Objekten sind keine Ereignisse zugeordnet, was bedeutet, dass sie in der IDE nicht funktionieren. Alle anderen Objekte sind bereits korrekt mit bestimmten Ereignissen verknüpft, und MetaTrader 5 kann die korrekte Ausführung dieser Ereignisse erzwingen, wenn sie im EA auftreten. Das heißt, wir können die IDE-Schnittstelle nach Belieben ändern, aber wenn die Funktionsweise noch nicht implementiert ist, wird MetaTrader 5 nichts weiter tun, als das Objekt im Chart anzuzeigen. Das Objekt EDIT 00 muss den Namen des zu handelnden Vermögenswerts erhalten, und dieser Name sollte in der Mitte des Objekts erscheinen. Das EDIT 01-Objekt wird den kumulierten Wert für einen bestimmten Zeitraum erhalten. Anhand des Tageszeitraums können wir feststellen, ob wir im Laufe des Tages Gewinne oder Verluste gemacht haben. Wenn der Wert negativ ist, wird er in einer Farbe angezeigt, wenn er positiv ist, wird eine andere Farbe verwendet.

Beide Werte können natürlich nicht vom Nutzer geändert werden, daher können Sie ihre Eigenschaften als schreibgeschützt belassen, wie in der Abbildung unten gezeigt.


Beachten Sie jedoch, dass es nicht möglich ist, festzulegen, wie die Informationen dargestellt werden, d. h. wir können den Text nicht so ausrichten, dass er in der Mitte des Objekts erscheint. Falls gewünscht, kann dies mit Hilfe von Code geschehen, da es eine Eigenschaft gibt, die den Text ausrichtet und ihn auf die Mitte setzt. Siehe "Object properties" (Objekteigenschaften) für weitere Details. Bitte beachten Sie ENUM_ALIGN_MODE in der Tabelle — es enthält die Objekte, bei denen Sie Blocksatz verwenden können.

Unabhängig davon, welche Änderungen wir vornehmen wollen, müssen wir als Erstes einen Plan erstellen: die neuen Funktionen, ihre Präsentationsform und die Art und Weise, wie der Nutzer mit ihnen interagieren wird, definieren. Dies wird es ermöglichen, das richtige Objekt auszuwählen und es so weit wie möglich über die MetaTrader 5 eigene Schnittstelle zu konfigurieren. Als Ergebnis haben wir eine fertige IDE, die wir nur noch durch den MQL5-Code anpassen müssen, sodass die IDE am Ende zu 100 % funktionsfähig ist. Lassen Sie uns also mit den Änderungen fortfahren.


Modifikationen

Um zu verhindern, dass der Code ein richtiger Frankenstein wird, müssen wir uns so gut wie möglich organisieren, um zu prüfen, welche Funktionen bereits vorhanden sind und welche tatsächlich implementiert werden müssen. In vielen Fällen kann es ausreichen, kleinere Änderungen am bestehenden Code vorzunehmen, einen neuen Code zu erstellen und diesen zu testen. Dieser neue Code wird in dem Code wiederverwendet, den wir gerade implementieren, und das Einzige, was wir noch testen müssen, sind die kleinen Kontrollfunktionen, die wir hinzufügen werden, um eine völlig neue Funktionsweise zu schaffen. Gute Programmierer machen das immer so: Sie versuchen, vorhandenen Code in irgendeiner Weise wiederzuverwenden, indem sie ihm Kontrollpunkte hinzufügen.


Modifikation 1. Hinzufügen des Asset-Namens

Um diesen Teil zu implementieren, benötigen wir keine wesentlichen Änderungen, aber diese Änderungen sollten an den richtigen Stellen implementiert werden. Zunächst fügen wir der Enumeration einen neuen Wert hinzu, der Quellcode ist unten dargestellt:

enum eObjectsIDE {eRESULT, eBTN_BUY, eBTN_SELL, eCHECK_DAYTRADE, eBTN_CANCEL, eEDIT_LEVERAGE, eEDIT_TAKE, eEDIT_STOP};



Nachfolgend finden Sie den neuen Code; der hervorgehobene Teil ist der hinzugefügte. Bitte beachten Sie, dass ich den neuen Wert weder an den Anfang noch an das Ende der Enumeration anfüge. Dies geschieht, um zu vermeiden, dass andere Codeteile, die bereits vorhanden sind und funktionieren, geändert werden müssen.

enum eObjectsIDE {eRESULT, eLABEL_SYMBOL, eBTN_BUY, eBTN_SELL, eCHECK_DAYTRADE, eBTN_CANCEL, eEDIT_LEVERAGE, eEDIT_TAKE, eEDIT_STOP};



Wenn Sie den neuen Wert entweder am Anfang oder am Ende der Enumeration einfügen, müssen Sie alle Stellen finden und ändern, an denen diese Grenzwerte verwendet wurden. In vielen Fällen können Auslassungen aufgrund von Vergesslichkeit gemacht werden, was zu Fehlern führt, die schwer zu finden sind. Man könnte meinen, dass die Fehler auf neue Hinzufügungen zurückzuführen sind, aber in Wirklichkeit sind sie auf Vergesslichkeit zurückzuführen. Deshalb fügen wir Änderungen irgendwo zwischen den Extremwerten hinzu.

Unmittelbar danach müssen wir dem System eine Meldung hinzufügen, da sonst ein Laufzeitfehler auftreten kann. Fügen wir also die folgende Zeile in unseren Quellcode hinzu.

static const string C_Chart_IDE::szMsgIDE[] = {
                                                "MSG_RESULT",
                                                "MSG_NAME_SYMBOL",
                                                "MSG_BUY_MARKET",
                                                "MSG_SELL_MARKET",
                                                "MSG_DAY_TRADE",
                                                "MSG_CLOSE_POSITION",
                                                "MSG_LEVERAGE_VALUE",
                                                "MSG_TAKE_VALUE",
                                                "MSG_STOP_VALUE"
                                              };



Wie Sie sehen, haben wir sie an der gleichen Stelle eingefügt, um die Reihenfolge beizubehalten. Aber im Moment kann die Konstante an jeder beliebigen Stelle eingefügt werden, es macht keinen Unterschied, da sie nur verwendet wird, um zu prüfen, welches Objekt die Nachricht erhalten wird. Für die strukturellen Zwecke haben wir sie als zweite Nachricht hinzugefügt.

Kehren wir nun zum MetaTrader 5 zurück und nehmen wir die Änderungen wie unten gezeigt vor:

         

Jetzt erkennt MetaTrader 5 das Objekt in unserer IDE bereits als ein Objekt, das eine Nachricht empfangen kann, und es muss nur noch eine Prozedur zum Senden der Nachricht erstellt werden. Der Text der Nachricht muss nur einmal hinzugefügt werden und kann gesendet werden, sobald MetaTrader 5 unsere IDE auf dem Chart platziert. Dies könnte durch einfaches Hinzufügen des erforderlichen Codes am Ende der Create-Funktion unserer Objektklasse geschehen. Aber damit der Code nicht zu einem Frankenstein voller Fehlerbehebungen wird, fügen wir den neuen Code innerhalb der Funktion DispatchMessage ein. Die ursprüngliche Funktion sieht wie folgt aus:

void DispatchMessage(int iMsg, string szArg, double dValue = 0.0)
{
        if (m_CountObject < eEDIT_STOP) return;
        switch (iMsg)
        {
                case CHARTEVENT_CHART_CHANGE:
                        if (szArg == szMsgIDE[eRESULT])
                        {
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_BGCOLOR, (dValue < 0 ? clrLightCoral : clrLightGreen));
                                ObjectSetString(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_TEXT, DoubleToString(dValue, 2));
                        }
                        break;
                case CHARTEVENT_OBJECT_CLICK:

// ... The rest of the code...

        }
}



Nachstehend der Code nach den entsprechenden Änderungen:

void DispatchMessage(int iMsg, string szArg, double dValue = 0.0)
{
        if (m_CountObject < eEDIT_STOP) return;
        switch (iMsg)
        {
                case CHARTEVENT_CHART_CHANGE:
                        if (szArg == szMsgIDE[eRESULT])
                        {
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_BGCOLOR, (dValue < 0 ? clrLightCoral : clrLightGreen));
                                ObjectSetString(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_TEXT, DoubleToString(dValue, 2));
                        }else if (szArg == szMsgIDE[eLABEL_SYMBOL])
                        {
                                ObjectSetString(Terminal.Get_ID(), m_ArrObject[eLABEL_SYMBOL].szName, OBJPROP_TEXT, Terminal.GetSymbol());
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eLABEL_SYMBOL].szName, OBJPROP_ALIGN, ALIGN_CENTER);
                        }
                        break;
                case CHARTEVENT_OBJECT_CLICK:

// ... The rest of the code

        }
}



Nachdem wir die Sendefunktion erstellt haben, können wir den Punkt wählen, an dem wir die Nachricht senden wollen. Die beste Stelle ist eigentlich am Ende der Funktion Create unserer Objektklasse, sodass der endgültige Code wie folgt aussehen wird:

bool Create(int nSub)
{
        m_CountObject = 0;
        if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) return false;
        FileReadInteger(m_fp, SHORT_VALUE);
                                
        for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = "";
        m_SubWindow = nSub;
        m_szLine = "";
        while (m_szLine != "</chart>")
        {
                if (!FileReadLine()) return false;
                if (m_szLine == "<object>")
                {
                        if (!FileReadLine()) return false;
                        if (m_szLine == "type")
                        {
                                if (m_szValue == "102") if (!LoopCreating(OBJ_LABEL)) return false;
                                if (m_szValue == "103") if (!LoopCreating(OBJ_BUTTON)) return false;
                                if (m_szValue == "106") if (!LoopCreating(OBJ_BITMAP_LABEL)) return false;
                                if (m_szValue == "107") if (!LoopCreating(OBJ_EDIT)) return false;
                                if (m_szValue == "110") if (!LoopCreating(OBJ_RECTANGLE_LABEL)) return false;
                        }
                }
        }
        FileClose(m_fp);
        DispatchMessage(CHARTEVENT_CHART_CHANGE, szMsgIDE[eLABEL_SYMBOL]);
        return true;
}


Die hinzugefügte Nachricht ist grün hervorgehoben. Beachten Sie, dass wir fast ohne Änderungen bereits einen zu 100 % implementierten Nachrichtenfluss haben, und wir können zur nächsten zu implementierenden Nachricht übergehen.


Modifikation 2. Hinzufügen des kumulierten Wertes für den Tag (Deckungspunkt)

Auch hier folgen wir der gleichen Logik wie beim Hinzufügen des Asset-Namens, sodass der neue Code wie folgt aussehen wird:

enum eObjectsIDE {eRESULT, eLABEL_SYMBOL, eROOF_DIARY, eBTN_BUY, eBTN_SELL, eCHECK_DAYTRADE, eBTN_CANCEL, eEDIT_LEVERAGE, eEDIT_TAKE, eEDIT_STOP};

// ... Rest of the code

static const string C_Chart_IDE::szMsgIDE[] = {
                                                "MSG_RESULT",
                                                "MSG_NAME_SYMBOL",
                                                "MSG_ROOF_DIARY",
                                                "MSG_BUY_MARKET",
                                                "MSG_SELL_MARKET",
                                                "MSG_DAY_TRADE",
                                                "MSG_CLOSE_POSITION",
                                                "MSG_LEVERAGE_VALUE",
                                                "MSG_TAKE_VALUE",
                                                "MSG_STOP_VALUE"
                                              };



Danach ändern wir die IDE mit einer neuen Nachricht:

         

Unsere neue IDE ist fertig. Nun werden wir den Code implementieren, der eine Nachricht mit dem akkumulierten Wert für den Tag erstellt. Wir sollten zuerst entscheiden, in welcher Klasse diese Funktion implementiert werden soll. Viele Leute würden diese Funktion wahrscheinlich hier, in der Klasse C_Chart_IDE, erstellen, aber aus organisatorischen Gründen wäre es besser, sie zusammen mit den Funktionen, die mit Aufträgen arbeiten, zu implementieren. Daher wird der Code in der Klasse C_OrderView implementiert. Der Code ist unten dargestellt:

double UpdateRoof(void)
{
        ulong   ticket;
        int     max;
        string  szSymbol = Terminal.GetSymbol();
        double  Accumulated = 0;
                                
        HistorySelect(macroGetDate(TimeLocal()), TimeLocal());
        max = HistoryDealsTotal();
        for (int c0 = 0; c0 < max; c0++) if ((ticket = HistoryDealGetTicket(c0)) > 0)
                if (HistoryDealGetString(ticket, DEAL_SYMBOL) == szSymbol)
                        Accumulated += HistoryDealGetDouble(ticket, DEAL_PROFIT);
                                                
        return Accumulated;
}



Nun, da der Code implementiert ist, müssen wir die Meldung in das System einfügen. Um dem Anwender das Leben zu erleichtern, habe ich bereits den Code für die Meldung bereits abgeschlossener Ergebnisse hinzugefügt. Das ist deren Code:

void DispatchMessage(int iMsg, string szArg, double dValue = 0.0)
{
        static double AccumulatedRoof = 0.0;
        bool    b0;
        double  d0;

        if (m_CountObject < eEDIT_STOP) return;
        switch (iMsg)
        {
                case CHARTEVENT_CHART_CHANGE:
                        if ((b0 = (szArg == szMsgIDE[eRESULT])) || (szArg == szMsgIDE[eROOF_DIARY]))
                        {
                                if (b0)
                                {
                                        ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_BGCOLOR, (dValue < 0 ? clrLightCoral : clrLightGreen));
                                        ObjectSetString(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_TEXT, DoubleToString(dValue, 2));
                                }else
                                {
                                        AccumulatedRoof = dValue;
                                        dValue = 0;
                                }
                                d0 = AccumulatedRoof + dValue;
                                ObjectSetString(Terminal.Get_ID(), m_ArrObject[eROOF_DIARY].szName, OBJPROP_TEXT, DoubleToString(MathAbs(d0), 2));
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eROOF_DIARY].szName, OBJPROP_BGCOLOR, (d0 >= 0 ? clrForestGreen : clrFireBrick));
                        }else   if (szArg == szMsgIDE[eLABEL_SYMBOL])
                        {
                                ObjectSetString(Terminal.Get_ID(), m_ArrObject[eLABEL_SYMBOL].szName, OBJPROP_TEXT, Terminal.GetSymbol());
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eLABEL_SYMBOL].szName, OBJPROP_ALIGN, ALIGN_CENTER);
                        }
                        break;
                case CHARTEVENT_OBJECT_CLICK:

// .... The rest of the code....

        }
}



Die hervorgehobenen Teile unterstützen das System wie oben beschrieben. Wäre die Implementierung nicht auf diese Weise erfolgt, müssten wir zwei Nachrichten an das System senden, um die Informationen korrekt zu aktualisieren. Mit der Art und Weise, wie der Code implementiert wurde, können wir jedoch sowohl das Ergebnis der offenen Position als auch das Ergebnis des Tages mit einer einzigen Nachricht verfolgen.

Eine weitere Änderung im EA betrifft die Funktion OnTrade. Sie sieht wie folgt aus:

void OnTrade()
{
        SubWin.DispatchMessage(CHARTEVENT_CHART_CHANGE, C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY], NanoEA.UpdateRoof());
        NanoEA.UpdatePosition();
}



Obwohl dieses System funktioniert, müssen Sie mit der Ausführungszeit der Funktion OnTrade vorsichtig sein, die zusammen mit OnTick die Leistung des EA beeinträchtigen kann. Im Falle des in OnTick enthaltenen Codes ist es nicht sehr gut, und die Optimierung ist entscheidend. Bei OnTrade ist es dagegen einfacher, da die Funktion tatsächlich aufgerufen wird, wenn sich die Position ändert. Da wir dies wissen, haben wir zwei Alternativen. Die erste besteht darin, die Position UpdateRoof zu ändern, um ihre Ausführungszeit zu begrenzen. Eine andere Alternative ist, die Funktion OnTrade selbst zu ändern. Aus praktischen Gründen werden wir die Funktion UpdateRoof ändern und so die Ausführungszeit bei einer offenen Position zumindest leicht verbessern. Die neue Funktion lautet wie folgt:

double UpdateRoof(void)
{
        ulong           ticket;
        string  szSymbol = Terminal.GetSymbol();
        int             max;
        static int      memMax = 0;
        static double   Accumulated = 0;
                
        HistorySelect(macroGetDate(TimeLocal()), TimeLocal());
        max = HistoryDealsTotal();
        if (memMax == max) return Accumulated; else memMax = max;
        for (int c0 = 0; c0 < max; c0++) if ((ticket = HistoryDealGetTicket(c0)) > 0)
                if (HistoryDealGetString(ticket, DEAL_SYMBOL) == szSymbol)
                        Accumulated += HistoryDealGetDouble(ticket, DEAL_PROFIT);
                                                
        return Accumulated;
}


Die hervorgehobenen Zeilen zeigen den Code, der der ursprünglichen Funktion hinzugefügt wurde. Auch wenn es den Anschein hat, dass sie keinen großen Unterschied machen, so machen sie doch einen großen Unterschied. Schauen wir, warum. Wenn der Code zum ersten Mal aufgerufen wird, werden sowohl die statische Variable memMax als auch Accumulated auf Null gesetzt, wenn in der Auftragshistorie für den angegebenen Zeitraum keine Werte vorhanden waren. Der Test spiegelt dies wider und die Routine kehrt zurück, aber wenn Daten vorhanden sind, werden sie getestet und sowohl memMax als auch Accumulated spiegeln den neuen Zustand wider. Die Tatsache, dass diese Variablen statisch sind, bedeutet, dass ihre Werte zwischen den Aufrufen beibehalten werden. Wenn sich also der Wert einer Position aufgrund der natürlichen Bewegung eines Vermögenswertes ändert, erzeugt MetaTrader 5 ein Ereignis, das die Funktion OnTrade aufruft. An diesem Punkt erfolgt ein neuer Aufruf der Funktion UpdateRoof, und wenn die Position nicht geschlossen wurde, kehrt die Funktion zum Kontrollpunkt zurück, wodurch der Rückgabeprozess beschleunigt wird.


Schlussfolgerung

In diesem Artikel haben wir gesehen, wie man dem RAD-System neue Funktionen hinzufügt und so die Erstellung einer Bibliothek ermöglicht, die das System ideal für die Erstellung einer IDE-Schnittstelle mit viel mehr Einfachheit und weniger Fehlern beim Aufbau einer Interaktions- und Steuerungsschnittstelle macht. Von nun an wird die einzige reale Einschränkung Ihre Kreativität sein, da wir hier nur die Arbeit mit MQL5 betrachtet haben, aber Sie können die gleiche Idee in externe Bibliotheken integrieren und damit die Möglichkeiten zur Erstellung einer IDE erheblich erweitern.


Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/10301

Beigefügte Dateien |
EA_1.05.zip (3274.9 KB)
Grafiken in der Bibliothek DoEasy (Teil 100): Verbesserungen im Umgang mit erweiterten grafischen Standardobjekten Grafiken in der Bibliothek DoEasy (Teil 100): Verbesserungen im Umgang mit erweiterten grafischen Standardobjekten
Im aktuellen Artikel werde ich offensichtliche Fehler bei der gleichzeitigen Behandlung von erweiterten (und Standard-) Grafikobjekten und Formularobjekten auf der Leinwand beseitigen sowie Fehler beheben, die bei dem im vorherigen Artikel durchgeführten Test entdeckt wurden. Der Artikel schließt diesen Teil der Bibliotheksbeschreibung ab.
Mehrere Indikatoren in einem Chart (Teil 05): Umwandlung des MetaTrader 5 in ein RAD-System (I) Mehrere Indikatoren in einem Chart (Teil 05): Umwandlung des MetaTrader 5 in ein RAD-System (I)
Es gibt viele Menschen, die keine Ahnung vom Programmieren haben, aber sehr kreativ sind und tolle Ideen haben. Der Mangel an Programmierkenntnissen hindert sie jedoch daran, diese Ideen umzusetzen. Schauen wir uns gemeinsam an, wie man einen Chart Trade mit der MetaTrader 5 Plattform selbst erstellt, als wäre es eine IDE (integrierte Entwicklungsumgebung).
Lernen Sie, wie man ein Handelssystem mit Hilfe von Parabolic SAR entwickelt Lernen Sie, wie man ein Handelssystem mit Hilfe von Parabolic SAR entwickelt
In diesem Artikel setzen wir unsere Serie darüber fort, wie man ein Handelssystem mit den beliebtesten Indikatoren entwickelt. In diesem Artikel lernen wir den Parabolic SAR Indikator im Detail kennen und erfahren, wie wir ein Handelssystem für den MetaTrader 5 mit Hilfe einiger einfacher Strategien entwickeln können.
Grafiken in der DoEasy-Bibliothek (Teil 99): Verschieben eines erweiterten grafischen Objekts mit einem einzigen Steuerpunkt Grafiken in der DoEasy-Bibliothek (Teil 99): Verschieben eines erweiterten grafischen Objekts mit einem einzigen Steuerpunkt
Im vorigen Artikel habe ich die Möglichkeit implementiert, Angelpunkte eines erweiterten grafischen Objekts mithilfe von Steuerformularen zu verschieben. Jetzt werde ich die Möglichkeit implementieren, ein zusammengesetztes grafisches Objekt mithilfe eines einzelnen grafischen Objektsteuerungspunkts (Formulars) zu verschieben.