Das kennt wohl jeder der schon einmal programmiert hat. Nach der Erstellung tun Programme am Anfang nicht, was man von ihnen erwartet. Es haben sich irgendwo Fehler eingeschlichen. Es bleibt also nichts weiteres übrig, als mühsam diese Fehler mit Hilfe eines sogenannten Debuggers zu suchen und zu beheben.
In diesem Beitrag geht es nicht um die Fehler, die market maker schon automatisch in der Legende des Charts anzeigt, so wie z. B. „# Formel ungültig (Formeltext erwartet) #“. Solche kryptischen Meldungen gehörten in der Dokumentation etwas ausführlicher erklärt und möglich Ursachen dafür gelistet. Bei solchen Fehlern ist es oft noch möglich, über Fehler anzeigen (Kontextmenü) zurück zu verfolgen, bei welcher Funktion es zu einem Fehler kam. Es geht in diesem Beitrag darum, darzustellen, welche Inhalte/Werte welches Ergebnisobjekt hat, was ein Debugger auch leistet.
Doch was tut man wenn wie in MM-Talk kein Debugger vorhanden ist? Vor genau diesem Problem stand ich auch, als ich anfing eigene Indikatoren und Auswertungen mit MM-Talk zu realisieren. Es gibt ein paar Hilfskonstruktionen um mit MM-Talk Zwischenergebnisse/Werte von Objekten sichtbar zu machen.
Da sich die meisten meiner Makros auf Darstellungen im Chart beziehen, arbeite ich für die Fehlersuche auch mit Charts.
In Charts können in market maker nur Zeitreihen dargestellt werden. Dargestellt wird ein Ergebnisobjekt das von keinem Semikolon mehr abgeschlossen ist, und das als letztes im Makro steht. So weit kennt es wohl jeder, der die ersten Schritte mit MM-Talk gemacht hat.
Zahlen, boolsche Werte und Texte als einfache Objekte sind nicht direkt im Chart darstellbar. Das Gleiche gilt erst recht für komplexere Objekt wie Listen, Tupel (Listen von Listen) und Kollektionen, aber diese setzen sich wieder aus den einfacheren Objekten zusammen.
Doch nun zum Lösungsweg:Erst einmal die Vorbereitungen. Im Chart sollten die Legenden eingeblendet sein. Dann erscheinen dort auch (meist links oben im Chart Fenster) die Namen der Ergebnisobjekte, die Ihr im Chart anzeigen lasst. Wählt die Zeitreihe aus die Ihr testen wollt. Dann mit der rechten Maustaste im Kontextmenü „Zeitreihe“ anwählen. In der Registerkarte „Darstellung“ die Liniendarstellung auswählen und in der Registerkarte „Definition“ einen Haken in das Feld „Kurs in Legende anzeigen“ machen. Dieser Haken hat die Auswirkung, dass der letzte/aktuellste Wert der dargestellten Zeitreihe in der Legende mit angezeigt wird. Des weiteren sollten mit „Strg+A“ die veränderbaren Parameter mit dem Chart eingeblendet werden.
Die im Folgenden verwendeten Programm Beispiele haben nur den Zweck zu verdeutlichen, wie man einen bestimmten Objekttyp in MM-Talk erzeugt und wie man den Wert dieses Typs in einem Chart darstellen kann.
Zahlen: Sie lassen sich durch die Fuktion „makez[1]“ in eine gerade Linie (Zeitreihe) umwandeln und sind somit im Chart darstellbar.
Beispiel:
Code:{Über den Parameter $Eingabe ein Objekt erzeugen; Typ:Zahl }
$Erg:= $Eingabe; {Zahlenwert (z.B. 15) dem Ergebnisobjekt (Typ:Zahl) zuweisen}
$Erg.makez[1] {Ausgabe (z.B. 15) als Zeitreihe im Chart }
Im Chart erscheint eine gerade Linie. In der Legende wird der letzte Wert der Linie angezeigt (z.B. 15). Geht man mit dem Cursor auf die Linie, dann wird auch der Wert der Linie unter dem Cursor angezeigt.
Alles was eine Zahl ist lässt sich so als Wert im Chart darstellen! Wir werden also versuchen möglichst vieles auf eine Zahl zurückzuführen und den Wert dann so darstellen lassen.
Boolsche Werte: Um zu testen welchen Zweig ein Programm bei einer If-Abfrage genommen hat, setzt man im Then und Else Zweig zwei unterschiedliche Zahlen ein. Damit ist dann das Ergebnisobjekt der If-Abfrage eine Zahl, die sich dann wie oben darstellen lässt.
Texte: Um Texte sichtbar zu machen, benutze ich die Funktion Error. Sie gibt den Text den man der Funktion übergibt in der Legende des Charts aus.
Beispiel:
Code:$Erg:= „Dies ist ein Test“; {Text dem Ergebnisobjekt (Typ:String) zuweisen}
$Erg.error {Ausgabe des Strings in der Legende }
Listen: Eine Liste ist eine Aufzählung von N Objekten gleichen Typs. Die einzelnen Elemente werden durch ihre Stelle in der Liste beschrieben. Die erste Stelle ist 0 die letzte N-1.
D.h. eine Liste kann immer nur aus Zahlen, oder aus Boolschen Werten, oder aus Zeitreihen, oder aus Listen, usw. bestehen. Ein bestimmtes Element aus der Liste spricht man mit der Funktion $ListenName.nth[$Stelle] an. Um jetzt die Inhalte einer Liste im Chart darstellen zu können, muss man wissen, welchen Objekttyp die Elemente haben, die die Liste enthält, um zu entscheiden, ob man wie bei Zahlen oder wie bei Texten den Inhalt des jeweiligen Elementes anzeigen kann.
Beispiel für eine Liste mit Zahlen:
Code:{Wir definieren uns eine Liste mit 4 Zahlen, damit wir wissen welche Ergebnisse wir erwarten.}
$Liste:=15.List[27; -12; 0];
{Wir wollen uns jetzt die einzelnen Inhalte der Liste ansehen. Um jedes Element für die Anzeige ansprechen zu können, verwenden wir einen Parameter $Stelle vom Typ:Zahl.
Die einzelnen Elemente sind vom Typ Zahl, also verwenden wir die Methode Zahlen.}
$Liste.nth[$Stelle] {N-tes Element in der Liste}
.makez[1] {umwandeln in eine Zeitreihe}
Je nach dem welchen Wert der Parameter $Stelle annimmt (0, 1, 2, 3) werden die entsprechenden Werte der Liste angezeigt. Nimmt $Stelle einen Wert außerhalb dieses Bereichs an, dann erscheint in der Legende der Text „# Element existiert nicht #“.
Tupel: Wie weiter oben schon gesagt, sind Tupel nichts anderes als Listen von Listen. D.h. Die Elemente einer Liste sind keine einfachen Objekttypen (Zahlen, Strings) sondern Ihrerseits wieder eine Liste. Im obigen Beispiel der Liste setzen wir für jedes Element statt einer Zahl wieder eine Liste ein.
Code:{ Ein anspruchsvolleres Beispiel:
Wir definieren uns eine Liste mit 4 Listen}
$Liste:=(13.List[15; 4]) {a) Liste mit 3 Zahlen}
.List[26.List[14; 6; -5; -1]; {b) Liste mit 4 Zahlen}
"Dies".List["ist"; "ein"; "Test"]; {c) Liste mit 4 Buchstaben}
0.tolist]; {d) Liste mit nur einer Zahl}
{Da hier unterschiedliche Objekttypen (Zahl / String) ausgewählt werden können,
weisen wir die Auswahl einem Ergebnisobjekt zu.}
$Erg:= $Liste.nth[$Stelle_x] {Stellenzeiger für die äußere Liste, Typ:Zahl}
.nth[$Stelle_Y]; {Stellenzeiger für die innere Liste; Typ:Zahl}
{Bei der Anzeige der Elemente aus den äußeren Listen (a, b, d) sind die Elemente
Zahlen, bei der Liste c sind die Elemente Strings. Deshalb müssen wir für die
beiden unterschiedlichen Typen auch unterschiedliche Ausgaben verwenden.}
If($Stelle_x=2;
$Erg.error;
$Erg.makez[1])
Wir können uns also damit auch wieder die Werte aller Elemente ansehen.
Einige werden sich jetzt sicherlich wundern, dass die inneren Listen verschiedene Objekttypen enthalten. Aber man muss sich klarmachen, dass eine Liste mit Zahlen und eine Liste mit Strings beide dem Objekttyp Liste entsprechen, und somit können sie beide als Elemente in einer Liste verwendet werden.
Ihr werdet Euch nach dem vorherigen Beispiel vielleicht sagen, Tupels braucht man in market maker nicht oder kommen nicht vor. Aber lasst uns einmal ein Gedankenspiel anstellen.
Ihr führt in market maker mehrere Inhaber und wollt für diese den Depotbestand einlesen, um darauf dann irgendwelche Auswertungen zu machen.
Mehrere Inhaber: -> Liste [Inhaber_1; Inhaber_2; … Inhaber_N]
Inhaber sind keine einfachen Objekte sondern bestehen mindestens aus: Namen, Adresse, Bank, also ist jeder Inhaber wieder eine Liste.
Namen und Adresse sind einfache Objekte jeweils vom Typ String. Bank lässt sich durch die Bankleitzahl beschreiben. Aber halt, ein Inhaber kann ja bei mehreren Banken Depots haben. Also ist Bank wieder eine Liste.
Auf jeder Bank kann der Inhaber mehrere Depots führen (also Depot wieder eine Liste) und in einem Depot können wieder mehrere Wertpapiere Liegen (also Wertpapiere wieder eine Liste).
Man sieht, das obige Beispiel war doch nicht so anspruchsvoll, wie sich die Realität gestalten kann, wenn man deren alltäglichen Aufgaben möglichst allgemeingültig mit Programmen abbilden will. Und genau diese Komplexität lässt sich in MM-Talk durch die Funktionen, die unter dem Oberbegriff Listen zusammengefasst sind komfortabel abbilden und bearbeiten.
Aber man muss nicht bis in diese Tiefen der Programmierung hinabsteigen, um sich in MM-Talk sinnvolle Zusatzindikatoren, Formelfilter, Handelssysteme, Signalsysteme, Tabellen, usw. zu erstellen.
So jetzt zum TestablaufIn der Realität läuft das Ganze dann etwa folgendermaßen ab. Man hat ein Makro, das von einem anderen Makro aufgerufen wird und das man testen möchte. Das aufgerufene Makro sieht etwa folgendermaßen aus:
Code:$Eing:= Object; {Eingangsobject retten; Typ:Liste(Zahlen)}
{Länge der Liste ermitteln}
$Wert_1:=$Eing.Length; { Wert_1; Typ:Zahl}
... {Weitere Funktionen}
...
{Mit jedem Element aus dem übergebenen Objekt $Eing eine Berechnung durchführen.
Das Ergebnis ($Erg) der Berechnung, für jedes einzelne Element aus der
Liste $Eing, soll vom Typ Zahl sein. Deshalb ist $List_a wieder Typ:Liste(Zahlen)}
$List_a:= #[]($L_W1:=Objekt; {von Map übergebener Wert}
$L_W2:= ...; {Berechnungsfunktion für L_W2; Typ:String}
... {Weitere Funktionen}
$Erg:= ...; {Berechnungsfunktion für Erg; Typ:Zahl}
$Erg)
.map[$Eing]
$List_a {Rückgabe des Ergebnisses aus dem Makro}
Am Ende des aufgerufenen Makros steht der Rückgabewert $List_a, der an das aufrufende Makro zurückgegeben wird.
Jetzt möchte ich mir die Ergebnisse von verschiedenen Variablen ansehen. Zuerst wird das Rückgabeobjekt (Letzte Zeile) auskommentiert. An dieser Stelle wird die Variable eingesetzt die ich mir ansehen möchte, und mit den oben beschriebenen Methoden versehen, um sie im Chart anzeigen zu können.
1. Welche Werte wurden dem Makro übergeben?Übergeben werden sollte eine Liste mit Zahlen. Um diese sich anzusehen, wird dann als letztes im Makro folgender Ausdruck eingesetzt.
$Eing.nth[$Stelle].makez[1]
2. Wie lang war die übergebene Liste?Dies wird in dem Ausdruck $Wert_1:=$Eing.Length; ermittelt. Die Länge kann nur eine Zahl sein. Um diese sich anzusehen, wird dann als letztes im Makro folgender Ausdruck eingesetzt.
$Wert_1.makez[1]
3. Was macht eigentlich Map? Wie wird der Parameter $Eing an die vor Map stehende Funktion übergeben? Um sich dies anzusehen, muss man erst den Rückgabewert der Funktion vor Map ($Erg) auskommentieren, und an dessen Stelle $L_W1 einsetzen. Die Werte der Funktion werden an die Variable $List_a übergeben. $List_a ist als Ergebnisobjekt der Funktion Map eine Liste. Deshalb wird dann als letztes im Makro folgender Ausdruck eingesetzt.
$List_a.nth[$Stelle].makez[1]
Und so weiter, bis klar ist, dass das Programm im aufgerufenen Makro das geforderte Ergebnis liefert. Und ganz am Ende nicht vergessen die zuvor auskommentierten Stellen wieder zurückzusetzen.
Ich hoffe diese Darstellung bringt Euch bei der Fehlersuche weiter. Ich benutze Methode um logische Fehler zu finden und um zu überprüfen, dass mit den richtigen Werten gerechnet wird und somit das erwartete Ergebnis mit der Ausgabe auch übereinstimmt. Die Methode ist zwar kein Ersatz für einen wirklichen Debugger, aber zumindest eine Möglichkeit nachzuprüfen, welche Werte in der Abarbeitung des Programms verwendet werden.
Mit freundlichen Güßen