
SQLite-Fähigkeiten in MQL5: Beispiel für ein Dashboard mit Handelsstatistiken nach Symbolen und magischen Zahlen
Inhalt
- Einführung
- Formulierung der Aufgabe
- Informations-Panel
- Funktionen für die Arbeit mit der Datenbank
- Zusammenbau des Dashboards
- Schlussfolgerung
Einführung
MQL5.com ist eine Ressource, die den Nutzern einen breiten Zugang zu einer Vielzahl von Referenz- und Bildungsinformationen im Bereich des algorithmischen Handels bietet. Trotz der umfangreichen Datenbank und der vielen Möglichkeiten haben einige Nutzer immer noch Schwierigkeiten, spezifische Lösungen zu finden oder die Beispiele an ihre Bedürfnisse anzupassen. In solchen Fällen bietet die Ressource die Möglichkeit, über das Forum mit der Gemeinschaft zu interagieren, wo Sie nützliche Ratschläge oder sogar fertige Lösungen erhalten können.
Der Artikel zielt darauf ab, eine der typischen Aufgaben zu demonstrieren und zu lösen - die Erstellung eines Info-Panels, um den Handelsverlauf und die Statistiken des Kontos anzuzeigen. Wir werden uns den Entwicklungsprozess dieses Panels ansehen, wobei wir uns ausschließlich auf die vorhandenen Materialien von mql5.com stützen, was nicht nur als praktische Lösung interessant ist, sondern auch als Beispiel für die Anwendung von Schulungs- und Referenzmaterialien unter realen Bedingungen.
Formulierung der Aufgabe
Es ist notwendig, ein Info-Panel einzurichten, in dem auf Wunsch Informationen über die Handelsgeschichte des Kontos und Handelsstatistiken, aufgeschlüsselt nach Symbolen und magischen Zahlen (Experten, die auf dem Konto handeln), angezeigt werden können. Es ist auch notwendig, vollständige Handelsstatistiken für das Konto anzuzeigen. Der Handelsverlauf sollte nach dem Zeitpunkt der getätigten Geschäfte sortiert werden. Die Handelsstatistiken für Symbole und magische Zahlen sollten nach Nettogewinn sortiert werden.
Der angewandte Programmtyp ist Indikator. Es werden keine Daten in den Puffern ausgegeben. Mit anderen Worten, es handelt sich um einen pufferlosen Indikator, dessen Hauptarbeitsbereich ein Grafikpanel ist. Das Panel wird visuell in zwei Bereiche unterteilt - auf der linken Seite werden Listen aller Symbole und magischen Zahlen angezeigt, mit denen auf dem Konto gehandelt wurde, und auf der rechten Seite werden entweder Tabellen mit Statistiken über Symbole und magische Zahlen in Form einer Liste oder endgültige Statistiken über ein ausgewähltes Symbol oder eine magische Zahl oder den gesamten Handel auf dem Konto angezeigt. Wir werden die Ausgabe bestimmter Listen mit Hilfe der Schaltflächen auf dem Bedienfeld verwalten, oder indem wir auf die Zeile mit den Statistiken für ein Symbol/eine Magie in der Tabelle auf der rechten Seite des Bedienfelds klicken.
In dem Artikel „Erstellung eines Dashboards zur Anzeige von Daten in Indikatoren und EAs“ werden wir ein Infopanel verwenden.
Der Artikel „SQLite: Natives Arbeiten mit SQL-Datenbanken in MQL5“ wird uns helfen, Statistiken über das Konto und in Bezug auf die Symbole und magische Zahlen (Handelsstrategien).
Informations-Panel
Seit dem ersten Artikel über die Erstellung eines Dashboards hat der Code einige Änderungen und Verbesserungen erfahren. Es ist nicht der Plan oder das Ziel dieses Artikels, alle am Code vorgenommenen Änderungen im Detail zu beschreiben. Verschaffen wir uns also einen kurzen Überblick über die Änderungen. Wir können sehen, was und wie genau geändert wurde, indem wir die erste Version des Panels aus dem angegebenen Artikel herunterladen und sie mit dem Code desselben Panels vergleichen, der dem Artikel beigefügt ist.
Ich habe Fehler bei der Positionierung und Anzeige des Panels in einigen Situationen beim Umschalten von Charts behoben. Jetzt können wir untergeordnete Dashboards an das Haupt-Dashboard anhängen. Insbesondere können wir aus ihnen Schaltflächen machen, indem wir das untergeordnete Dashboard zusammenklappen und nur seine Kopfzeile übrig lassen, die als Schaltfläche dient. Die Einschränkungen für die Positionierung von Tabellen innerhalb des Panels wurden aufgehoben. Ursprünglich konnte die Tabelle nicht außerhalb des Panels gezeichnet werden - nur im Sichtbarkeitsbereich. In manchen Fällen sollten Tabellen, die auf einem Panel gezeichnet werden, außerhalb des sichtbaren Bereichs des Panels positioniert werden. Dies ist notwendig, um lange oder breite Tabellen zu scrollen, deren Listen größer sind als das Dashboard. Wenn wir also zulassen, dass die Anfangskoordinaten der Tabelle außerhalb des Dashboards positioniert werden, können wir die Tabelle rollen lassen. Genau das werden wir heute tun. Wir werden jedoch die Klassen des Dashboards und seiner Tabellen nicht ändern (obwohl dies im Hinblick auf die weitere Verwendung der geänderten Panel-Klasse korrekter ist), damit wir deutlich zeigen können, dass es mit dem richtigen Ansatz immer möglich ist, die Funktionalität zum gewünschten Ergebnis zu bringen, auch wenn es Beispiele gibt, die in Bezug auf die Funktionalität nicht ganz geeignet sind.
Schauen wir uns kurz an, was im Code der Tabellen- und Dashboardklassen geändert wurde.
Die Klasse der Tabellenzelle verfügt nun über die Variable zur Speicherung des Zelltextes und die Methoden zur Handhabung der Variable:
//+------------------------------------------------------------------+ //| Table cell class | //+------------------------------------------------------------------+ class CTableCell : public CObject { private: int m_row; // Row int m_col; // Column int m_x; // X coordinate int m_y; // Y coordinate string m_text; // Text in the cell public: //--- Methods of setting values void SetRow(const uint row) { this.m_row=(int)row; } void SetColumn(const uint col) { this.m_col=(int)col; } void SetX(const uint x) { this.m_x=(int)x; } void SetY(const uint y) { this.m_y=(int)y; } void SetXY(const uint x,const uint y) { this.m_x=(int)x; this.m_y=(int)y; } void SetText(const string text) { this.m_text=text; } //--- Methods of obtaining values int Row(void) const { return this.m_row; } int Column(void) const { return this.m_col; } int X(void) const { return this.m_x; } int Y(void) const { return this.m_y; } string Text(void) const { return this.m_text; } //--- Virtual method for comparing two objects virtual int Compare(const CObject *node,const int mode=0) const { const CTableCell *compared=node; return(this.Column()>compared.Column() ? 1 : this.Column()<compared.Column() ? -1 : 0); } //--- Constructor/destructor CTableCell(const int row,const int column) : m_row(row),m_col(column){} ~CTableCell(void){} };
In der Klasse der Tabellendaten kann die ID nun einen negativen Wert haben, so wie auch die Tabellenkoordinaten negativ sein können. Die Methode zum Setzen der Tabellen-ID wurde hinzugefügt:
//+------------------------------------------------------------------+ //| Table data class | //+------------------------------------------------------------------+ class CTableData : public CObject { private: CArrayObj m_list_rows; // List of rows int m_id; // Table ID int m_x1; // X1 coordinate int m_y1; // Y1 coordinate int m_x2; // X2 coordinate int m_y2; // Y2 coordinate int m_w; // Width int m_h; // Height string m_name; // Table name public: //--- Set table (1) ID and (2) name void SetID(const int id) { this.m_id=id; } void SetName(const string name) { this.m_name=name; } //--- Return table (1) ID and (2) name int ID(void) const { return this.m_id; } string Name(void) const { return this.m_name; } //--- Set coordinate (1) X1, (2) X2 void SetX1(const int x1) { this.m_x1=x1; } void SetX2(const int x2) { this.m_x2=x2; } //--- Set coordinate (1) Y1, (2) Y2 void SetY1(const int y1) { this.m_y1=y1; } void SetY2(const int y2) { this.m_y2=y2; } //--- Set table coordinates void SetCoords(const int x1,const int y1,const int x2,const int y2) { this.SetX1(x1); this.SetY1(y1); this.SetX2(x2); this.SetY2(y2); }
Der Standardkonstruktor wurde hinzugefügt:
//--- Constructor/destructor CTableData(void) : m_id(-1) { this.m_list_rows.Clear(); this.m_name=""; } CTableData(const uint id) : m_id((int)id) { this.m_list_rows.Clear(); this.m_name=""; } ~CTableData(void) { this.m_list_rows.Clear(); }
Der Standardkonstruktor ermöglicht es uns, ein Objekt einer Klasse zu deklarieren, ohne es mit dem new-Operator zu erstellen, während int-Koordinaten es uns ermöglichen, die Anfangskoordinaten von Tabellen außerhalb des Dashboard-Fensters festzulegen.
In der Panel-Objektklasse sind neue Variablen deklariert worden:
//+------------------------------------------------------------------+ //| Dashboard class | //+------------------------------------------------------------------+ class CDashboard : public CObject { private: CTableData m_table_tmp; // Table object for search CCanvas m_canvas; // Canvas CCanvas m_workspace; // Work space CArrayObj m_list_table; // List of tables CArrayObj m_list_obj; // List of linked panels ENUM_PROGRAM_TYPE m_program_type; // Program type ENUM_MOUSE_STATE m_mouse_state; // Mouse button status uint m_id; // Object ID long m_chart_id; // ChartID int m_chart_w; // Chart width int m_chart_h; // Chart height int m_x; // X coordinate int m_y; // Y coordinate int m_w; // Width int m_h; // Height int m_x_dock; // X coordinate of the pinned collapsed panel int m_y_dock; // Y coordinate of the pinned collapsed panel int m_diff_x; // Offset of local X coordinate relative to parent int m_diff_y; // Offset of local Y coordinate relative to parent bool m_header; // Header presence flag bool m_butt_close; // Close button presence flag bool m_butt_minimize; // Collapse/expand button presence flag bool m_butt_pin; // Pin button presence flag bool m_wider_wnd; // Flag for exceeding the horizontal size of the window width panel bool m_higher_wnd; // Flag for exceeding the vertical size of the window height panel bool m_movable; // Panel movability flag int m_header_h; // Header height int m_wnd; // Chart subwindow index int m_title_x_shift; // Horizontal offset of the header text int m_title_y_shift; // Vertical offset of the header text uchar m_header_alpha; // Header transparency uchar m_header_alpha_c; // Current header transparency color m_header_back_color; // Header background color color m_header_back_color_c; // Current header background color color m_header_fore_color; // Header text color color m_header_fore_color_c; // Current header text color color m_header_border_color; // Header border color color m_header_border_color_c; // Current header border color color m_butt_close_back_color; // Close button background color color m_butt_close_back_color_c; // Current close button background color color m_butt_close_fore_color; // Close button icon color color m_butt_close_fore_color_c; // Current close button color color m_butt_min_back_color; // Expand/collapse button background color color m_butt_min_back_color_c; // Current expand/collapse button background color color m_butt_min_fore_color; // Expand/collapse button icon color color m_butt_min_fore_color_c; // Current expand/collapse button icon color color m_butt_pin_back_color; // Pin button background color color m_butt_pin_back_color_c; // Current pin button background color color m_butt_pin_fore_color; // Pin button icon color color m_butt_pin_fore_color_c; // Current pin button icon color uchar m_alpha; // Panel transparency uchar m_alpha_c; // Current panel transparency uchar m_fore_alpha; // Text transparency uchar m_fore_alpha_c; // Current text transparency color m_back_color; // Background color color m_back_color_c; // Current background color color m_fore_color; // Text color color m_fore_color_c; // Current text color color m_border_color; // Border color color m_border_color_c; // Current border color string m_title; // Title text string m_title_font; // Title font int m_title_font_size; // Title font size string m_font; // Font int m_font_size; // Font size bool m_minimized; // Collapsed panel window flag string m_program_name; // Program name string m_name_gv_x; // Name of the global terminal variable storing the X coordinate string m_name_gv_y; // Name of the global terminal variable storing the Y coordinate string m_name_gv_m; // Name of the global terminal variable storing the collapsed panel flag string m_name_gv_u; // Name of the global terminal variable storing the flag of the pinned panel string m_filename_bg; // File name to save background pixels string m_filename_ws; // File name for saving work space pixels uint m_array_wpx[]; // Array of pixels to save/restore the workspace uint m_array_ppx[]; // Array of pixels to save/restore the panel background int m_mouse_diff_x; // Offset the cursor relative to the X anchor angle int m_mouse_diff_y; // Offset the cursor relative to the Y anchor angle bool m_slave; // Flag of a linked (dependent) dashboard string m_name; // Dashboard name
Da wir nun untergeordnete Dashboards an das übergeordnete Dashboard binden können, wurden einige Methoden zur Handhabung des Panels aus dem geschützten Bereich in den öffentlichen Bereich verschoben. Diese Methoden erfordern einen externen Zugang. Außerdem wurden neue Methoden hinzugefügt:
public: //--- Return (1) chart ID and (2) subwindow index long ChartID(void) const { return this.m_chart_id; } int SubWindow(void) const { return this.m_wnd; } //--- (1) Collapse and (2) expand the panel void Collapse(void); void Expand(void); //--- (1) Hide, (2) show and (3) bring the panel to the foreground void Hide(const bool redraw=false); void Show(const bool redraw=false); void BringToTop(void); //--- Return the hidden object flag bool IsHidden(void); //--- Set new header colors void SetHeaderNewColors(const color new_bg_color=clrNONE, const color title_new_color=clrNONE, const ushort new_alpha=USHORT_MAX) { this.m_header_back_color=(new_bg_color==clrNONE ? this.m_header_back_color : new_bg_color); this.m_header_back_color_c=this.m_header_back_color; this.m_header_fore_color=(title_new_color==clrNONE ? this.m_header_fore_color : title_new_color); this.m_header_fore_color_c=this.m_header_fore_color; this.m_header_alpha=uchar(new_alpha==USHORT_MAX ? this.m_header_alpha : (new_alpha>255 ? 255 : new_alpha)); this.m_header_alpha_c=this.m_header_alpha; } //--- Set new header properties void SetHeaderNewParams(const string title,const color new_bg_color, const color title_new_color, const ushort new_alpha=USHORT_MAX, const int title_x_shift=0,const int title_y_shift=0, const string font_name="Calibri",const int font_size=8,const uint font_flags=0) { this.SetHeaderFontParams(font_name, font_size, font_flags); this.SetTitleShift(title_x_shift,title_y_shift); this.SetHeaderNewColors(new_bg_color,title_new_color,new_alpha); this.RedrawHeaderArea(new_bg_color, title, title_new_color, new_alpha); } //--- Set the panel (1) width and (2) height bool SetWidth(const int width,const bool redraw=false); bool SetHeight(const int height,const bool redraw=false); //--- Display the panel void View(const string title) { this.Draw(title); } //--- Return the (1) CCanvas object, (2) working space, (3) object ID CCanvas *Canvas(void) { return &this.m_canvas; } CCanvas *Workspace(void) { return &this.m_workspace; } uint ID(void) const { return this.m_id; } //--- Return the panel (1) X and (2) Y coordinates int CoordX(void) const { return this.m_x; } int CoordY(void) const { return this.m_y; } //--- Return the panel (1) width and (2) height int Width(void) const { return this.m_w; } int Height(void) const { return this.m_h; } //--- Returns the offset of the dashboard (1) X and (2) Y local coordinate int CoordDiffX(void) const { return this.m_diff_x; } int CoordDiffY(void) const { return this.m_diff_y; } //--- Set the offset of the dashboard (1) X and (2) Y local coordinate void SetCoordDiffX(const int diff_x) { this.m_diff_x=diff_x; } void SetCoordDiffY(const int diff_y) { this.m_diff_y=diff_y; } //--- Set the offsets of the header text (1) horizontally, (2) vertically, (3) both void SetTitleXShift(const int shift) { this.m_title_x_shift=shift; } void SetTitleYShift(const int shift) { this.m_title_y_shift=shift; } void SetTitleShift(const int x_shift, const int y_shift) { if(this.m_title_x_shift!=x_shift) this.m_title_x_shift=x_shift; if(this.m_title_y_shift!=y_shift) this.m_title_y_shift=y_shift; } //--- Return the (1) width, (2) height and (3) size of the specified text int TextWidth(const string text) { return this.m_workspace.TextWidth(text); } int TextHeight(const string text) { return this.m_workspace.TextHeight(text); } void TextSize(const string text,int &width,int &height) { this.m_workspace.TextSize(text,width,height); } //--- Set the panel header (1) presence or (2) absence flag void SetPanelHeaderOn(const bool redraw=false); void SetPanelHeaderOff(const bool redraw=false); //--- Set the close button (1) presence, (2) absence flag void SetButtonCloseOn(const bool redraw=false); void SetButtonCloseOff(const bool redraw=false); //--- Set the collapse/expand button (1) presence, (2) absence flag void SetButtonMinimizeOn(const bool redraw=false); void SetButtonMinimizeOff(const bool redraw=false); //--- Sets the flag (1) of presence, (2) absence of the pin/unpin button void SetButtonPinOn(const bool redraw=false); void SetButtonPinOff(const bool redraw=false); //--- Set the panel coordinates bool SetCoords(const int x,const int y); //--- Set the panel size bool SetSizes(const int w,const int h,const bool update=false); //--- Set panel coordinates and size bool SetParams(const int x,const int y,const int w,const int h,const bool update=false); //--- Set the transparency of the panel (1) header and (2) working space void SetHeaderTransparency(const uchar value); void SetTransparency(const uchar value); //--- Sets the default font parameters (1) of the dashboard, (2) of the header void SetFontParams(const string name,const int size,const uint flags=0,const uint angle=0); void SetHeaderFontParams(const string name,const int size,const uint flags=0,const uint angle=0); //--- Return the set font parameters (1) of the dashboard, (2) of the header string FontParams(int &size,uint &flags,uint &angle); string FontHeaderParams(int &size,uint &flags,uint &angle); //--- Return the specified panel (1) font, (2) size and font flags string FontName(void) const { return this.m_workspace.FontNameGet(); } int FontSize(void) const { return this.m_workspace.FontSizeGet(); } uint FontFlags(void) const { return this.m_workspace.FontFlagsGet(); } //--- Return the set (1) font, (2) size, (3) flags of the header font string FontHeaderName(void) const { return this.m_canvas.FontNameGet(); } int FontHeaderSize(void) const { return this.m_canvas.FontSizeGet(); } uint FontHeaderFlags(void) const { return this.m_canvas.FontFlagsGet(); } //--- (1) Set and (2) return the color of the text of the dashboard work area void SetForeColor(const color clr) { this.m_fore_color=clr; } color ForeColor(void) const { return this.m_fore_color; } //--- Display (2) a text message, (2) a filled rectangle at the specified coordinates void DrawText(const string text,const int x,const int y,const color clr=clrNONE,const int width=WRONG_VALUE,const int height=WRONG_VALUE); void DrawRectangleFill(const int x,const int y,const int width,const int height,const color clr,const uchar alpha); //--- Create a new table bool CreateNewTable(const int id=WRONG_VALUE); //--- Return the tabular data object by (1) ID, (2) name and the (3) number of tables in the list CTableData *GetTable(const uint id); CTableData *GetTable(const string name); int TableTotal(void) const { return this.m_list_table.Total(); } //--- Return the flag of the presence of a table in the list by (1) ID and (2) name bool TableIsExist(const uint id); bool TableIsExist(const string name); //--- Draw a (1) background grid (2) with automatic cell size void DrawGrid(const uint table_id,const int x,const int y,const uint rows,const uint columns,const uint row_size,const uint col_size,const color line_color=clrNONE,bool alternating_color=true); void DrawGridAutoFill(const uint table_id,const uint border,const uint rows,const uint columns,const color line_color=clrNONE,bool alternating_color=true); //--- Erases everything drawn on the dashboard and restores the original appearance void Clear(void) { this.m_canvas.Erase(::ColorToARGB(this.m_back_color,this.m_alpha)); this.DrawFrame(); this.m_workspace.Erase(0x00FFFFFF); } //--- Print grid data (line intersection coordinates) void GridPrint(const uint table_id,const uint tabulation=0) { CTableData *table=this.GetTable(table_id); if(table==NULL) { ::PrintFormat("%s: Error. Failed to get table object with id %lu",__FUNCTION__,table_id); return; } table.Print(tabulation); } //--- Write the X and Y coordinate values of the specified table cell to variables void CellXY(const uint table_id,const uint row,const uint column, int &x, int &y) { CTableData *table=this.GetTable(table_id); if(table==NULL) { ::PrintFormat("%s: Error. Failed to get table object with id %lu",__FUNCTION__,table_id); return; } table.CellXY(row,column,x,y); } //--- Return the (1) X and (2) Y coordinate of the specified table cell int CellX(const uint table_id,const uint row,const uint column) { CTableData *table=this.GetTable(table_id); if(table==NULL) { ::PrintFormat("%s: Error. Failed to get table object with id %lu",__FUNCTION__,table_id); return WRONG_VALUE; } return table.CellX(row,column); } int CellY(const uint table_id,const uint row,const uint column) { CTableData *table=this.GetTable(table_id); if(table==NULL) { ::PrintFormat("%s: Error. Failed to get table object with id %lu",__FUNCTION__,table_id); return WRONG_VALUE; } return table.CellY(row,column); } //--- Write X1 and Y1, X2 and Y2 coordinate values of the specified table to the variables void TableCoords(const uint table_id,int &x1,int &y1,int &x2,int &y2) { x1=y1=x2=y2=WRONG_VALUE; CTableData *table=this.GetTable(table_id); if(table==NULL) return; x1=table.X1(); y1=table.Y1(); x2=table.X2(); y2=table.Y2(); } //--- Return the (1) X1, (2) Y1, (3) X2 and (4) Y2 coordinate of the specified table int TableX1(const uint table_id) { CTableData *table=this.GetTable(table_id); return(table!=NULL ? table.X1() : WRONG_VALUE); } int TableY1(const uint table_id) { CTableData *table=this.GetTable(table_id); return(table!=NULL ? table.Y1() : WRONG_VALUE); } int TableX2(const uint table_id) { CTableData *table=this.GetTable(table_id); return(table!=NULL ? table.X2() : WRONG_VALUE); } int TableY2(const uint table_id) { CTableData *table=this.GetTable(table_id); return(table!=NULL ? table.Y2() : WRONG_VALUE); } //--- Compare two objects by ID virtual int Compare(const CObject *node,const int mode=0) const { const CDashboard *obj=node; return(this.ID()>obj.ID() ? 1 : this.ID()<obj.ID() ? -1 : 0); } //--- Create and bind a new dashboard CDashboard *InsertNewPanel(const uint id, const int x, const int y, const int w, const int h) { CDashboard *obj=new CDashboard(id, this.CoordX()+x, this.CoordY()+y, w, (h>20 ? h : 21)); if(obj==NULL) return NULL; int diff_x=obj.CoordX()-this.CoordX(); int diff_y=obj.CoordY()-this.CoordY(); this.m_list_obj.Sort(); if(this.m_list_obj.Search(obj)==0 || !this.m_list_obj.Add(obj)) { delete obj; return NULL; } obj.SetCoordDiffX(diff_x); obj.SetCoordDiffY(diff_y); obj.SetAsSlave(); return obj; } //--- Return a pointer to a panel by (1) ID and (2) name CDashboard *GetPanel(const uint id) { for(int i=0;i<this.m_list_obj.Total();i++) { CDashboard *obj=this.m_list_obj.At(i); if(obj!=NULL && obj.ID()==id) return obj; } return NULL; } CDashboard *GetPanel(const string name) { for(int i=0;i<this.m_list_obj.Total();i++) { CDashboard *obj=this.m_list_obj.At(i); if(obj!=NULL && obj.Name()==name) return obj; } return NULL; } //--- (1) Set and (2) return the dependent object flag void SetAsSlave(void) { this.m_slave=true; this.m_movable=false; } bool IsSlave(void) const { return this.m_slave; } //--- Return a flag that the object with the specified name belongs to the dashboard (e.g. created by the dashboard object) bool IsOwnObject(const string object_name) const { string bmp=::ObjectGetString(this.m_chart_id,object_name,OBJPROP_BMPFILE); return(::StringFind(bmp,this.m_program_name+".ex5::")>WRONG_VALUE); } //--- (1) Set and (2) return the panel name void SetName(const string name) { this.m_name=name; } string Name(void) const { return this.m_name; } //--- Return the panel header text string HeaderTitle(void) const { return this.m_title; } //--- Event handler
Alle neuen Methoden und Verbesserungen ermöglichen es uns nun, untergeordnete Panels an das übergeordnete Dashboard anzuhängen und sie als unabhängige Objekte zu verwenden, die jedoch von ihrem übergeordneten Objekt abhängig sind. Es ist jetzt möglich, scrollbare Tabellen zu erstellen, wenn ihre Größe größer ist als die Größe des Dashboards selbst. Bisher konnten Tabellen nur so groß sein wie das Dashboard selbst. In der Dashboard-Klasse gibt es keine Funktion zum Blättern in der Tabelle - wir werden sie direkt im Hauptprogramm einrichten. In der Folge wird diese Funktionalität, falls erforderlich, der Panel-Klasse und ihren Tabellen hinzugefügt. Im Moment besteht jedoch keine solche Notwendigkeit.
Natürlich haben wir hier nur einen kleinen Teil der an der Dashboard-Klasse und ihren Tabellen vorgenommenen Änderungen berücksichtigt - nur die deklarierten Methoden. Im Laufe der Zeit seit der ersten Veröffentlichung wurden schrittweise Verbesserungen an einem großen Teil des Codes vorgenommen. Sie können jederzeit die erste Version des Dashboards aus dem Artikel herunterladen und sie mit der im Artikel vorgestellten Version vergleichen. Die Dashboard-Datei sollte sich im Projektverzeichnis befinden: \MQL5\Indicators\StatisticsBy\Dashboard\Dashboard.mqh.
Funktionen für die Arbeit mit der Datenbank
Meine Erfahrung im Umgang mit Datenbanken ist nicht allzu umfangreich. Es handelte sich um ein gemeinsames Projekt für die Spieleindustrie in C#, bei dem eine andere Person an der DB arbeitete und ich nur den mitgelieferten Connector verwendete, um die DB mit dem Projekt zu verbinden. Daher musste ich mich mit Referenzmaterialien und Artikeln auf mql5.com bewaffnen. Beim Studium des Artikels „SQLite: Natives Arbeiten mit SQL-Datenbanken in MQL5“, sah ich sofort Verweise auf die Dokumentation, nämlich die Funktion DatabasePrepare(). Die Funktion enthält ein Beispiel für die Erstellung einer Tabelle der Deals, die wiederum zur Erstellung einer Tabelle von Handelsgeschäften verwendet wird. Das ist eines der Dinge, die wir brauchen! Wappnen wir uns mit Geduld und studieren wir das Beispiel und seine Funktionen.
Zunächst sehen wir zwei Strukturen für die Speicherung von Geschäfts- und Handelsdaten:
//--- structure to store the deal struct Deal { ulong ticket; // DEAL_TICKET long order_ticket; // DEAL_ORDER long position_ticket; // DEAL_POSITION_ID datetime time; // DEAL_TIME char type; // DEAL_TYPE char entry; // DEAL_ENTRY string symbol; // DEAL_SYMBOL double volume; // DEAL_VOLUME double price; // DEAL_PRICE double profit; // DEAL_PROFIT double swap; // DEAL_SWAP double commission; // DEAL_COMMISSION long magic; // DEAL_MAGIC char reason; // DEAL_REASON }; //--- structure to store the trade: the order of members corresponds to the position in the terminal struct Trade { datetime time_in; // login time ulong ticket; // position ID char type; // buy or sell double volume; // volume pair symbol; // symbol double price_in; // entry price datetime time_out; // exit time double price_out; // exit price double commission; // entry and exit fees double swap; // swap double profit; // profit or loss };
Als Nächstes analysieren wir die Logik:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- create the file name string filename=IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN))+"_trades.sqlite"; //--- open/create the database in the common terminal folder int db=DatabaseOpen(filename, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE | DATABASE_OPEN_COMMON); if(db==INVALID_HANDLE) { Print("DB: ", filename, " open failed with code ", GetLastError()); return; } //--- create the DEALS table if(!CreateTableDeals(db)) { DatabaseClose(db); return; } //--- request the entire trading history datetime from_date=0; datetime to_date=TimeCurrent(); //--- request the history of deals in the specified interval HistorySelect(from_date, to_date); int deals_total=HistoryDealsTotal(); PrintFormat("Deals in the trading history: %d ", deals_total); //--- add deals to the table if(!InsertDeals(db)) return; //--- show the first 10 deals Deal deals[], deal; ArrayResize(deals, 10); int request=DatabasePrepare(db, "SELECT * FROM DEALS"); if(request==INVALID_HANDLE) { Print("DB: ", filename, " request failed with code ", GetLastError()); DatabaseClose(db); return; } int i; for(i=0; DatabaseReadBind(request, deal); i++) { if(i>=10) break; deals[i].ticket=deal.ticket; deals[i].order_ticket=deal.order_ticket; deals[i].position_ticket=deal.position_ticket; deals[i].time=deal.time; deals[i].type=deal.type; deals[i].entry=deal.entry; deals[i].symbol=deal.symbol; deals[i].volume=deal.volume; deals[i].price=deal.price; deals[i].profit=deal.profit; deals[i].swap=deal.swap; deals[i].commission=deal.commission; deals[i].magic=deal.magic; deals[i].reason=deal.reason; } //--- print the deals if(i>0) { ArrayResize(deals, i); PrintFormat("The first %d deals:", i); ArrayPrint(deals); } //--- remove the query after use DatabaseFinalize(request); //--- make sure that hedging system for open position management is used on the account if((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) { //--- deals cannot be transformed to trades using a simple method through transactions, therefore complete operation DatabaseClose(db); return; } //--- now create the TRADES table based on the DEALS table if(!CreateTableTrades(db)) { DatabaseClose(db); return; } //--- fill in the TRADES table using an SQL query based on DEALS table data ulong start=GetMicrosecondCount(); if(DatabaseTableExists(db, "DEALS")) //--- fill in the TRADES table if(!DatabaseExecute(db, "INSERT INTO TRADES(TIME_IN,TICKET,TYPE,VOLUME,SYMBOL,PRICE_IN,TIME_OUT,PRICE_OUT,COMMISSION,SWAP,PROFIT) " "SELECT " " d1.time as time_in," " d1.position_id as ticket," " d1.type as type," " d1.volume as volume," " d1.symbol as symbol," " d1.price as price_in," " d2.time as time_out," " d2.price as price_out," " d1.commission+d2.commission as commission," " d2.swap as swap," " d2.profit as profit " "FROM DEALS d1 " "INNER JOIN DEALS d2 ON d1.position_id=d2.position_id " "WHERE d1.entry=0 AND d2.entry=1")) { Print("DB: fillng the TRADES table failed with code ", GetLastError()); return; } ulong transaction_time=GetMicrosecondCount()-start; //--- show the first 10 deals Trade trades[], trade; ArrayResize(trades, 10); request=DatabasePrepare(db, "SELECT * FROM TRADES"); if(request==INVALID_HANDLE) { Print("DB: ", filename, " request failed with code ", GetLastError()); DatabaseClose(db); return; } for(i=0; DatabaseReadBind(request, trade); i++) { if(i>=10) break; trades[i].time_in=trade.time_in; trades[i].ticket=trade.ticket; trades[i].type=trade.type; trades[i].volume=trade.volume; trades[i].symbol=trade.symbol; trades[i].price_in=trade.price_in; trades[i].time_out=trade.time_out; trades[i].price_out=trade.price_out; trades[i].commission=trade.commission; trades[i].swap=trade.swap; trades[i].profit=trade.profit; } //--- print trades if(i>0) { ArrayResize(trades, i); PrintFormat("\r\nThe first %d trades:", i); ArrayPrint(trades); PrintFormat("Filling the TRADES table took %.2f milliseconds",double(transaction_time)/1000); } //--- remove the query after use DatabaseFinalize(request); //--- close the database DatabaseClose(db); }
- Wir erstellen eine Datenbank und
- eine Tabelle der Deals in der Datenbank,
- wir fragen die Handelshistorie ab und tragen die Deals in die erstellte Tabelle ein,
- prüfen den Kontotyp, es muss ja eine Absicherung geben, da es für ein Netting-Konto einfach unmöglich ist, eine Handelsgeschichte allein auf der Grundlage von Deals zu erstellen, und
- wir erstellen eine Handelstabelle auf der Grundlage der Tabelle der Deals erstellt, und die Handelsdaten werden auf der Grundlage der Daten der Handelstabelle in die Handelstabelle eingegeben.
Das vorgestellte Skript druckt auch die ersten zehn Deals und die ersten zehn Deals aus den erstellten Tabellen aus. Das brauchen wir nicht.
Auf der Grundlage der Logik müssen wir mehrere Funktionen erstellen, die auf den im Beispiel vorgestellten Funktionen und den Codezeilen im Hauptteil des Beispielskripts basieren:
//+------------------------------------------------------------------+ //| Create DEALS table | //+------------------------------------------------------------------+ bool CreateTableDeals(int database) { //--- if the DEALS table already exists, delete it if(!DeleteTable(database, "DEALS")) { return(false); } //--- check if the table exists if(!DatabaseTableExists(database, "DEALS")) //--- create a table if(!DatabaseExecute(database, "CREATE TABLE DEALS(" "ID INT KEY NOT NULL," "ORDER_ID INT NOT NULL," "POSITION_ID INT NOT NULL," "TIME INT NOT NULL," "TYPE INT NOT NULL," "ENTRY INT NOT NULL," "SYMBOL CHAR(10)," "VOLUME REAL," "PRICE REAL," "PROFIT REAL," "SWAP REAL," "COMMISSION REAL," "MAGIC INT," "REASON INT );")) { Print("DB: create the DEALS table failed with code ", GetLastError()); return(false); } //--- the table has been successfully created return(true); } //+------------------------------------------------------------------+ //| Delete a table with the specified name from the database | //+------------------------------------------------------------------+ bool DeleteTable(int database, string table_name) { if(!DatabaseExecute(database, "DROP TABLE IF EXISTS "+table_name)) { Print("Failed to drop the DEALS table with code ", GetLastError()); return(false); } //--- the table has been successfully deleted return(true); } //+------------------------------------------------------------------+ //| Add deals to the database table | //+------------------------------------------------------------------+ bool InsertDeals(int database) { //--- auxiliary variables ulong deal_ticket; // deal ticket long order_ticket; // a ticket of an order a deal was executed by long position_ticket; // ID of a position a deal belongs to datetime time; // deal execution time long type ; // deal type long entry ; // deal direction string symbol; // a symbol a deal was executed for double volume; // operation volume double price; // price double profit; // financial result double swap; // swap double commission; // commission long magic; // Magic number (Expert Advisor ID) long reason; // deal execution reason or source //--- go through all deals and add them to the database bool failed=false; int deals=HistoryDealsTotal(); // --- lock the database before executing transactions DatabaseTransactionBegin(database); for(int i=0; i<deals; i++) { deal_ticket= HistoryDealGetTicket(i); order_ticket= HistoryDealGetInteger(deal_ticket, DEAL_ORDER); position_ticket=HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID); time= (datetime)HistoryDealGetInteger(deal_ticket, DEAL_TIME); type= HistoryDealGetInteger(deal_ticket, DEAL_TYPE); entry= HistoryDealGetInteger(deal_ticket, DEAL_ENTRY); symbol= HistoryDealGetString(deal_ticket, DEAL_SYMBOL); volume= HistoryDealGetDouble(deal_ticket, DEAL_VOLUME); price= HistoryDealGetDouble(deal_ticket, DEAL_PRICE); profit= HistoryDealGetDouble(deal_ticket, DEAL_PROFIT); swap= HistoryDealGetDouble(deal_ticket, DEAL_SWAP); commission= HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION); magic= HistoryDealGetInteger(deal_ticket, DEAL_MAGIC); reason= HistoryDealGetInteger(deal_ticket, DEAL_REASON); //--- add each deal to the table using the following query string request_text=StringFormat("INSERT INTO DEALS (ID,ORDER_ID,POSITION_ID,TIME,TYPE,ENTRY,SYMBOL,VOLUME,PRICE,PROFIT,SWAP,COMMISSION,MAGIC,REASON)" "VALUES (%d, %d, %d, %d, %d, %d, '%s', %G, %G, %G, %G, %G, %d, %d)", deal_ticket, order_ticket, position_ticket, time, type, entry, symbol, volume, price, profit, swap, commission, magic, reason); if(!DatabaseExecute(database, request_text)) { PrintFormat("%s: failed to insert deal #%d with code %d", __FUNCTION__, deal_ticket, GetLastError()); PrintFormat("i=%d: deal #%d %s", i, deal_ticket, symbol); failed=true; break; } } //--- check for transaction execution errors if(failed) { //--- roll back all transactions and unlock the database DatabaseTransactionRollback(database); PrintFormat("%s: DatabaseExecute() failed with code %d", __FUNCTION__, GetLastError()); return(false); } //--- all transactions have been performed successfully - record changes and unlock the database DatabaseTransactionCommit(database); return(true); } //+------------------------------------------------------------------+ //| Create TRADES table | //+------------------------------------------------------------------+ bool CreateTableTrades(int database) { //--- if the TRADES table already exists, delete it if(!DeleteTable(database, "TRADES")) return(false); //--- check if the table exists if(!DatabaseTableExists(database, "TRADES")) //--- create a table if(!DatabaseExecute(database, "CREATE TABLE TRADES(" "TIME_IN INT NOT NULL," "TICKET INT NOT NULL," "TYPE INT NOT NULL," "VOLUME REAL," "SYMBOL CHAR(10)," "PRICE_IN REAL," "TIME_OUT INT NOT NULL," "PRICE_OUT REAL," "COMMISSION REAL," "SWAP REAL," "PROFIT REAL);")) { Print("DB: create the TRADES table failed with code ", GetLastError()); return(false); } //--- the table has been successfully created return(true); } //+------------------------------------------------------------------+
Zusätzlich zu den Feldern der im Beispiel vorgestellten Strukturen und Tabellen benötigen wir auch ein Feld, in dem der Kontoindex gespeichert wird - dieser wird benötigt, um eine Statistiktabelle für den Handel auf dem Konto zu erstellen. Mit anderen Worten: Wir brauchen eine vollständige Handelsstatistik.
Wie können wir statistische Tabellen erstellen? Lesen Sie diesen Artikel! Das ist genau das, was wir brauchen:
Portfolioanalyse nach Strategien
Die Ergebnisse der oben gezeigten Skriptoperation DatabasePrepare machen deutlich, dass der Handel mit mehreren Währungspaaren durchgeführt wird. Außerdem werden in der Spalte [magic] die Werte von 100 bis 600 angezeigt. Das bedeutet, dass das Handelskonto von mehreren Strategien verwaltet wird, von denen jede ihre eigene Magic-Number hat, um ihre Deals zu identifizieren.
Eine SQL-Abfrage ermöglicht es uns, den Handel im Zusammenhang mit magischen Werten zu analysieren:
//--- get trading statistics for Expert Advisors by Magic Number request=DatabasePrepare(db, "SELECT r.*," " (case when r.trades != 0 then (r.gross_profit+r.gross_loss)/r.trades else 0 end) as expected_payoff," " (case when r.trades != 0 then r.win_trades*100.0/r.trades else 0 end) as win_percent," " (case when r.trades != 0 then r.loss_trades*100.0/r.trades else 0 end) as loss_percent," " r.gross_profit/r.win_trades as average_profit," " r.gross_loss/r.loss_trades as average_loss," " (case when r.gross_loss!=0.0 then r.gross_profit/(-r.gross_loss) else 0 end) as profit_factor " "FROM " " (" " SELECT MAGIC," " sum(case when entry =1 then 1 else 0 end) as trades," " sum(case when profit > 0 then profit else 0 end) as gross_profit," " sum(case when profit < 0 then profit else 0 end) as gross_loss," " sum(swap) as total_swap," " sum(commission) as total_commission," " sum(profit) as total_profit," " sum(profit+swap+commission) as net_profit," " sum(case when profit > 0 then 1 else 0 end) as win_trades," " sum(case when profit < 0 then 1 else 0 end) as loss_trades " " FROM DEALS " " WHERE SYMBOL <> '' and SYMBOL is not NULL " " GROUP BY MAGIC" " ) as r");
Ergebnis:
Trade statistics by Magic Number [magic] [trades] [gross_profit] [gross_loss] [total_commission] [total_swap] [total_profit] [net_profit] [win_trades] [loss_trades] [expected_payoff] [win_percent] [loss_percent] [average_profit] [average_loss] [profit_factor] [0] 100 242 2584.80000 -2110.00000 -33.36000 -93.53000 474.80000 347.91000 143 99 1.96198 59.09091 40.90909 18.07552 -21.31313 1.22502 [1] 200 254 3021.92000 -2834.50000 -29.45000 -98.22000 187.42000 59.75000 140 114 0.73787 55.11811 44.88189 21.58514 -24.86404 1.06612 [2] 300 250 2489.08000 -2381.57000 -34.37000 -96.58000 107.51000 -23.44000 134 116 0.43004 53.60000 46.40000 18.57522 -20.53078 1.04514 [3] 400 224 1272.50000 -1283.00000 -24.43000 -64.80000 -10.50000 -99.73000 131 93 -0.04687 58.48214 41.51786 9.71374 -13.79570 0.99182 [4] 500 198 1141.23000 -1051.91000 -27.66000 -63.36000 89.32000 -1.70000 116 82 0.45111 58.58586 41.41414 9.83819 -12.82817 1.08491 [5] 600 214 1317.10000 -1396.03000 -34.12000 -68.48000 -78.93000 -181.53000 116 98 -0.36883 54.20561 45.79439 11.35431 -14.24520 0.94346
4 von 6 Strategien haben sich als rentabel erwiesen. Wir haben für jede Strategie statistische Werte erhalten:
- trades - Anzahl der Handelsgeschäfte pro Strategie,
- gross_profit - Gesamtgewinn nach Strategie (die Summe aller Gewinne),
- gross_loss - Gesamtverlust nach Strategie (Summe aller Verluste),
- total_commission - Summe aller Provisionen für Strategiegeschäfte,
- total_swap - Summe aller Swaps von Strategiegeschäften,
- total_profit - Summe aus gross_profit und gross_loss,
- net_profit - Summe von (gross_profit + gross_loss + total_commission + total_swap),
- win_trades - Anzahl der Handelsgeschäfte, bei denen das Ergebnis > 0 war,
- loss_trades - Anzahl der Geschäfte, bei denen das Ergebnis < 0 ist,
- expected_payoff - erwarteter Auszahlungsbetrag für den Handel ohne Swaps und Provisionen = net_profit/Handelsgeschäfte,
- win_percent - Prozentsatz der Handelsgeschäfte mit Gewinn,
- loss_percent - Prozentsatz der Handelsgeschäfte mit Verlust,
- average_profit - durchschnittlicher Gewinn = gross_profit/win_trades,
- average_loss - durchschnittlicher Verlust = gross_loss /loss_trades,
- profit_factor - Gewinnfaktor = gross_profit/gross_loss.
In den Statistiken zur Berechnung von Gewinn und Verlust werden Swaps und Provisionen, die auf die Position anfallen, nicht berücksichtigt. So können Sie die Nettokosten sehen. Es kann sich herausstellen, dass eine Strategie zwar einen kleinen Gewinn abwirft, aber aufgrund von Swaps und Provisionen generell unrentabel ist.
Analyse der Deals nach Symbolen
Wir sind in der Lage, den Handel nach Symbolen zu analysieren. Machen Sie dazu die folgende Abfrage:
//--- get trading statistics per symbols int request=DatabasePrepare(db, "SELECT r.*," " (case when r.trades != 0 then (r.gross_profit+r.gross_loss)/r.trades else 0 end) as expected_payoff," " (case when r.trades != 0 then r.win_trades*100.0/r.trades else 0 end) as win_percent," " (case when r.trades != 0 then r.loss_trades*100.0/r.trades else 0 end) as loss_percent," " r.gross_profit/r.win_trades as average_profit," " r.gross_loss/r.loss_trades as average_loss," " (case when r.gross_loss!=0.0 then r.gross_profit/(-r.gross_loss) else 0 end) as profit_factor " "FROM " " (" " SELECT SYMBOL," " sum(case when entry =1 then 1 else 0 end) as trades," " sum(case when profit > 0 then profit else 0 end) as gross_profit," " sum(case when profit < 0 then profit else 0 end) as gross_loss," " sum(swap) as total_swap," " sum(commission) as total_commission," " sum(profit) as total_profit," " sum(profit+swap+commission) as net_profit," " sum(case when profit > 0 then 1 else 0 end) as win_trades," " sum(case when profit < 0 then 1 else 0 end) as loss_trades " " FROM DEALS " " WHERE SYMBOL <> '' and SYMBOL is not NULL " " GROUP BY SYMBOL" " ) as r");
Ergebnis:
Trade statistics by Symbol [name] [trades] [gross_profit] [gross_loss] [total_commission] [total_swap] [total_profit] [net_profit] [win_trades] [loss_trades] [expected_payoff] [win_percent] [loss_percent] [average_profit] [average_loss] [profit_factor] [0] "AUDUSD" 112 503.20000 -568.00000 -8.83000 -24.64000 -64.80000 -98.27000 70 42 -0.57857 62.50000 37.50000 7.18857 -13.52381 0.88592 [1] "EURCHF" 125 607.71000 -956.85000 -11.77000 -45.02000 -349.14000 -405.93000 54 71 -2.79312 43.20000 56.80000 11.25389 -13.47676 0.63512 [2] "EURJPY" 127 1078.49000 -1057.83000 -10.61000 -45.76000 20.66000 -35.71000 64 63 0.16268 50.39370 49.60630 16.85141 -16.79095 1.01953 [3] "EURUSD" 233 1685.60000 -1386.80000 -41.00000 -83.76000 298.80000 174.04000 127 106 1.28240 54.50644 45.49356 13.27244 -13.08302 1.21546 [4] "GBPCHF" 125 1881.37000 -1424.72000 -22.60000 -51.56000 456.65000 382.49000 80 45 3.65320 64.00000 36.00000 23.51712 -31.66044 1.32052 [5] "GBPJPY" 127 1943.43000 -1776.67000 -18.84000 -52.46000 166.76000 95.46000 76 51 1.31307 59.84252 40.15748 25.57145 -34.83667 1.09386 [6] "GBPUSD" 121 1668.50000 -1438.20000 -7.96000 -49.93000 230.30000 172.41000 77 44 1.90331 63.63636 36.36364 21.66883 -32.68636 1.16013 [7] "USDCAD" 99 405.28000 -475.47000 -8.68000 -31.68000 -70.19000 -110.55000 51 48 -0.70899 51.51515 48.48485 7.94667 -9.90563 0.85238 [8] "USDCHF" 206 1588.32000 -1241.83000 -17.98000 -65.92000 346.49000 262.59000 131 75 1.68199 63.59223 36.40777 12.12458 -16.55773 1.27902 [9] "USDJPY" 107 464.73000 -730.64000 -35.12000 -34.24000 -265.91000 -335.27000 50 57 -2.48514 46.72897 53.27103 9.29460 -12.81825 0.63606
Die Statistik zeigt, dass der Nettogewinn bei 5 von 10 Symbolen erhalten wurde (net_profit>0), während der Gewinnfaktor bei 6 von 10 Symbolen positiv war (profit_factor>1). Dies ist genau dann der Fall, wenn Swaps und Kommissionen die Strategie bei EURJPY unrentabel machen.
Lesen wir den Artikel weiter:
Großartig! Folgen Sie dem Link zur Hilfe, um den vollständigen Code des Beispiels für diese Funktion zu erhalten:
//--- symbol statistics struct Symbol_Stats { string name; // symbol name int trades; // number of trades for the symbol double gross_profit; // total profit for the symbol double gross_loss; // total loss for the symbol double total_commission; // total commission for the symbol double total_swap; // total swaps for the symbol double total_profit; // total profit excluding swaps and commissions double net_profit; // net profit taking into account swaps and commissions int win_trades; // number of profitable trades int loss_trades; // number of losing trades double expected_payoff; // expected payoff for the trade excluding swaps and commissions double win_percent; // percentage of winning trades double loss_percent; // percentage of losing trades double average_profit; // average profit double average_loss; // average loss double profit_factor; // profit factor }; //--- Magic Number statistics struct Magic_Stats { long magic; // EA's Magic Number int trades; // number of trades for the symbol double gross_profit; // total profit for the symbol double gross_loss; // total loss for the symbol double total_commission; // total commission for the symbol double total_swap; // total swaps for the symbol double total_profit; // total profit excluding swaps and commissions double net_profit; // net profit taking into account swaps and commissions int win_trades; // number of profitable trades int loss_trades; // number of losing trades double expected_payoff; // expected payoff for the trade excluding swaps and commissions double win_percent; // percentage of winning trades double loss_percent; // percentage of losing trades double average_profit; // average profit double average_loss; // average loss double profit_factor; // profit factor }; //--- statistics by entry hour struct Hour_Stats { char hour_in; // market entry hour int trades; // number of trades in this entry hour double volume; // volume of trades in this entry hour double gross_profit; // total profit in this entry hour double gross_loss; // total loss in this entry hour double net_profit; // net profit taking into account swaps and commissions int win_trades; // number of profitable trades int loss_trades; // number of losing trades double expected_payoff; // expected payoff for the trade excluding swaps and commissions double win_percent; // percentage of winning trades double loss_percent; // percentage of losing trades double average_profit; // average profit double average_loss; // average loss double profit_factor; // profit factor }; int ExtDealsTotal=0;; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- create the file name string filename=IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN))+"_stats.sqlite"; //--- open/create the database in the common terminal folder int db=DatabaseOpen(filename, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE | DATABASE_OPEN_COMMON); if(db==INVALID_HANDLE) { Print("DB: ", filename, " open failed with code ", GetLastError()); return; } //--- create the DEALS table if(!CreateTableDeals(db)) { DatabaseClose(db); return; } PrintFormat("Deals in the trading history: %d ", ExtDealsTotal); //--- get trading statistics per symbols int request=DatabasePrepare(db, "SELECT r.*," " (case when r.trades != 0 then (r.gross_profit+r.gross_loss)/r.trades else 0 end) as expected_payoff," " (case when r.trades != 0 then r.win_trades*100.0/r.trades else 0 end) as win_percent," " (case when r.trades != 0 then r.loss_trades*100.0/r.trades else 0 end) as loss_percent," " r.gross_profit/r.win_trades as average_profit," " r.gross_loss/r.loss_trades as average_loss," " (case when r.gross_loss!=0.0 then r.gross_profit/(-r.gross_loss) else 0 end) as profit_factor " "FROM " " (" " SELECT SYMBOL," " sum(case when entry =1 then 1 else 0 end) as trades," " sum(case when profit > 0 then profit else 0 end) as gross_profit," " sum(case when profit < 0 then profit else 0 end) as gross_loss," " sum(swap) as total_swap," " sum(commission) as total_commission," " sum(profit) as total_profit," " sum(profit+swap+commission) as net_profit," " sum(case when profit > 0 then 1 else 0 end) as win_trades," " sum(case when profit < 0 then 1 else 0 end) as loss_trades " " FROM DEALS " " WHERE SYMBOL <> '' and SYMBOL is not NULL " " GROUP BY SYMBOL" " ) as r"); if(request==INVALID_HANDLE) { Print("DB: ", filename, " request failed with code ", GetLastError()); DatabaseClose(db); return; } Symbol_Stats stats[], symbol_stats; ArrayResize(stats, ExtDealsTotal); int i=0; //--- get entries from request results for(; DatabaseReadBind(request, symbol_stats) ; i++) { stats[i].name=symbol_stats.name; stats[i].trades=symbol_stats.trades; stats[i].gross_profit=symbol_stats.gross_profit; stats[i].gross_loss=symbol_stats.gross_loss; stats[i].total_commission=symbol_stats.total_commission; stats[i].total_swap=symbol_stats.total_swap; stats[i].total_profit=symbol_stats.total_profit; stats[i].net_profit=symbol_stats.net_profit; stats[i].win_trades=symbol_stats.win_trades; stats[i].loss_trades=symbol_stats.loss_trades; stats[i].expected_payoff=symbol_stats.expected_payoff; stats[i].win_percent=symbol_stats.win_percent; stats[i].loss_percent=symbol_stats.loss_percent; stats[i].average_profit=symbol_stats.average_profit; stats[i].average_loss=symbol_stats.average_loss; stats[i].profit_factor=symbol_stats.profit_factor; } ArrayResize(stats, i); Print("Trade statistics by Symbol"); ArrayPrint(stats); Print(""); //--- delete the query DatabaseFinalize(request); //--- get trading statistics for Expert Advisors by Magic Number request=DatabasePrepare(db, "SELECT r.*," " (case when r.trades != 0 then (r.gross_profit+r.gross_loss)/r.trades else 0 end) as expected_payoff," " (case when r.trades != 0 then r.win_trades*100.0/r.trades else 0 end) as win_percent," " (case when r.trades != 0 then r.loss_trades*100.0/r.trades else 0 end) as loss_percent," " r.gross_profit/r.win_trades as average_profit," " r.gross_loss/r.loss_trades as average_loss," " (case when r.gross_loss!=0.0 then r.gross_profit/(-r.gross_loss) else 0 end) as profit_factor " "FROM " " (" " SELECT MAGIC," " sum(case when entry =1 then 1 else 0 end) as trades," " sum(case when profit > 0 then profit else 0 end) as gross_profit," " sum(case when profit < 0 then profit else 0 end) as gross_loss," " sum(swap) as total_swap," " sum(commission) as total_commission," " sum(profit) as total_profit," " sum(profit+swap+commission) as net_profit," " sum(case when profit > 0 then 1 else 0 end) as win_trades," " sum(case when profit < 0 then 1 else 0 end) as loss_trades " " FROM DEALS " " WHERE SYMBOL <> '' and SYMBOL is not NULL " " GROUP BY MAGIC" " ) as r"); if(request==INVALID_HANDLE) { Print("DB: ", filename, " request failed with code ", GetLastError()); DatabaseClose(db); return; } Magic_Stats EA_stats[], magic_stats; ArrayResize(EA_stats, ExtDealsTotal); i=0; //--- display entries for(; DatabaseReadBind(request, magic_stats) ; i++) { EA_stats[i].magic=magic_stats.magic; EA_stats[i].trades=magic_stats.trades; EA_stats[i].gross_profit=magic_stats.gross_profit; EA_stats[i].gross_loss=magic_stats.gross_loss; EA_stats[i].total_commission=magic_stats.total_commission; EA_stats[i].total_swap=magic_stats.total_swap; EA_stats[i].total_profit=magic_stats.total_profit; EA_stats[i].net_profit=magic_stats.net_profit; EA_stats[i].win_trades=magic_stats.win_trades; EA_stats[i].loss_trades=magic_stats.loss_trades; EA_stats[i].expected_payoff=magic_stats.expected_payoff; EA_stats[i].win_percent=magic_stats.win_percent; EA_stats[i].loss_percent=magic_stats.loss_percent; EA_stats[i].average_profit=magic_stats.average_profit; EA_stats[i].average_loss=magic_stats.average_loss; EA_stats[i].profit_factor=magic_stats.profit_factor; } ArrayResize(EA_stats, i); Print("Trade statistics by Magic Number"); ArrayPrint(EA_stats); Print(""); //--- delete the query DatabaseFinalize(request); //--- make sure that hedging system for open position management is used on the account if((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) { //--- deals cannot be transformed to trades using a simple method through transactions, therefore complete operation DatabaseClose(db); return; } //--- now create the TRADES table based on the DEALS table if(!CreateTableTrades(db)) { DatabaseClose(db); return; } //--- fill in the TRADES table using an SQL query based on DEALS table data if(DatabaseTableExists(db, "DEALS")) //--- fill in the TRADES table if(!DatabaseExecute(db, "INSERT INTO TRADES(TIME_IN,HOUR_IN,TICKET,TYPE,VOLUME,SYMBOL,PRICE_IN,TIME_OUT,PRICE_OUT,COMMISSION,SWAP,PROFIT) " "SELECT " " d1.time as time_in," " d1.hour as hour_in," " d1.position_id as ticket," " d1.type as type," " d1.volume as volume," " d1.symbol as symbol," " d1.price as price_in," " d2.time as time_out," " d2.price as price_out," " d1.commission+d2.commission as commission," " d2.swap as swap," " d2.profit as profit " "FROM DEALS d1 " "INNER JOIN DEALS d2 ON d1.position_id=d2.position_id " "WHERE d1.entry=0 AND d2.entry=1 ")) { Print("DB: fillng the table TRADES failed with code ", GetLastError()); return; } //--- get trading statistics by market entry hours request=DatabasePrepare(db, "SELECT r.*," " (case when r.trades != 0 then (r.gross_profit+r.gross_loss)/r.trades else 0 end) as expected_payoff," " (case when r.trades != 0 then r.win_trades*100.0/r.trades else 0 end) as win_percent," " (case when r.trades != 0 then r.loss_trades*100.0/r.trades else 0 end) as loss_percent," " r.gross_profit/r.win_trades as average_profit," " r.gross_loss/r.loss_trades as average_loss," " (case when r.gross_loss!=0.0 then r.gross_profit/(-r.gross_loss) else 0 end) as profit_factor " "FROM " " (" " SELECT HOUR_IN," " count() as trades," " sum(volume) as volume," " sum(case when profit > 0 then profit else 0 end) as gross_profit," " sum(case when profit < 0 then profit else 0 end) as gross_loss," " sum(profit) as net_profit," " sum(case when profit > 0 then 1 else 0 end) as win_trades," " sum(case when profit < 0 then 1 else 0 end) as loss_trades " " FROM TRADES " " WHERE SYMBOL <> '' and SYMBOL is not NULL " " GROUP BY HOUR_IN" " ) as r"); if(request==INVALID_HANDLE) { Print("DB: ", filename, " request failed with code ", GetLastError()); DatabaseClose(db); return; } Hour_Stats hours_stats[], h_stats; ArrayResize(hours_stats, ExtDealsTotal); i=0; //--- display entries for(; DatabaseReadBind(request, h_stats) ; i++) { hours_stats[i].hour_in=h_stats.hour_in; hours_stats[i].trades=h_stats.trades; hours_stats[i].volume=h_stats.volume; hours_stats[i].gross_profit=h_stats.gross_profit; hours_stats[i].gross_loss=h_stats.gross_loss; hours_stats[i].net_profit=h_stats.net_profit; hours_stats[i].win_trades=h_stats.win_trades; hours_stats[i].loss_trades=h_stats.loss_trades; hours_stats[i].expected_payoff=h_stats.expected_payoff; hours_stats[i].win_percent=h_stats.win_percent; hours_stats[i].loss_percent=h_stats.loss_percent; hours_stats[i].average_profit=h_stats.average_profit; hours_stats[i].average_loss=h_stats.average_loss; hours_stats[i].profit_factor=h_stats.profit_factor; } ArrayResize(hours_stats, i); Print("Trade statistics by entry hour"); ArrayPrint(hours_stats); Print(""); //--- delete the query DatabaseFinalize(request); //--- close the database DatabaseClose(db); return; } /* Deals in the trading history: 2771 Trade statistics by Symbol [name] [trades] [gross_profit] [gross_loss] [total_commission] [total_swap] [total_profit] [net_profit] [win_trades] [loss_trades] [expected_payoff] [win_percent] [loss_percent] [average_profit] [average_loss] [profit_factor] [0] "AUDUSD" 112 503.20000 -568.00000 -8.83000 -24.64000 -64.80000 -98.27000 70 42 -0.57857 62.50000 37.50000 7.18857 -13.52381 0.88592 [1] "EURCHF" 125 607.71000 -956.85000 -11.77000 -45.02000 -349.14000 -405.93000 54 71 -2.79312 43.20000 56.80000 11.25389 -13.47676 0.63512 [2] "EURJPY" 127 1078.49000 -1057.83000 -10.61000 -45.76000 20.66000 -35.71000 64 63 0.16268 50.39370 49.60630 16.85141 -16.79095 1.01953 [3] "EURUSD" 233 1685.60000 -1386.80000 -41.00000 -83.76000 298.80000 174.04000 127 106 1.28240 54.50644 45.49356 13.27244 -13.08302 1.21546 [4] "GBPCHF" 125 1881.37000 -1424.72000 -22.60000 -51.56000 456.65000 382.49000 80 45 3.65320 64.00000 36.00000 23.51712 -31.66044 1.32052 [5] "GBPJPY" 127 1943.43000 -1776.67000 -18.84000 -52.46000 166.76000 95.46000 76 51 1.31307 59.84252 40.15748 25.57145 -34.83667 1.09386 [6] "GBPUSD" 121 1668.50000 -1438.20000 -7.96000 -49.93000 230.30000 172.41000 77 44 1.90331 63.63636 36.36364 21.66883 -32.68636 1.16013 [7] "USDCAD" 99 405.28000 -475.47000 -8.68000 -31.68000 -70.19000 -110.55000 51 48 -0.70899 51.51515 48.48485 7.94667 -9.90563 0.85238 [8] "USDCHF" 206 1588.32000 -1241.83000 -17.98000 -65.92000 346.49000 262.59000 131 75 1.68199 63.59223 36.40777 12.12458 -16.55773 1.27902 [9] "USDJPY" 107 464.73000 -730.64000 -35.12000 -34.24000 -265.91000 -335.27000 50 57 -2.48514 46.72897 53.27103 9.29460 -12.81825 0.63606 Trade statistics by Magic Number [magic] [trades] [gross_profit] [gross_loss] [total_commission] [total_swap] [total_profit] [net_profit] [win_trades] [loss_trades] [expected_payoff] [win_percent] [loss_percent] [average_profit] [average_loss] [profit_factor] [0] 100 242 2584.80000 -2110.00000 -33.36000 -93.53000 474.80000 347.91000 143 99 1.96198 59.09091 40.90909 18.07552 -21.31313 1.22502 [1] 200 254 3021.92000 -2834.50000 -29.45000 -98.22000 187.42000 59.75000 140 114 0.73787 55.11811 44.88189 21.58514 -24.86404 1.06612 [2] 300 250 2489.08000 -2381.57000 -34.37000 -96.58000 107.51000 -23.44000 134 116 0.43004 53.60000 46.40000 18.57522 -20.53078 1.04514 [3] 400 224 1272.50000 -1283.00000 -24.43000 -64.80000 -10.50000 -99.73000 131 93 -0.04687 58.48214 41.51786 9.71374 -13.79570 0.99182 [4] 500 198 1141.23000 -1051.91000 -27.66000 -63.36000 89.32000 -1.70000 116 82 0.45111 58.58586 41.41414 9.83819 -12.82817 1.08491 [5] 600 214 1317.10000 -1396.03000 -34.12000 -68.48000 -78.93000 -181.53000 116 98 -0.36883 54.20561 45.79439 11.35431 -14.24520 0.94346 Trade statistics by entry hour [hour_in] [trades] [volume] [gross_profit] [gross_loss] [net_profit] [win_trades] [loss_trades] [expected_payoff] [win_percent] [loss_percent] [average_profit] [average_loss] [profit_factor] [ 0] 0 50 5.00000 336.51000 -747.47000 -410.96000 21 29 -8.21920 42.00000 58.00000 16.02429 -25.77483 0.45020 [ 1] 1 20 2.00000 102.56000 -57.20000 45.36000 12 8 2.26800 60.00000 40.00000 8.54667 -7.15000 1.79301 [ 2] 2 6 0.60000 38.55000 -14.60000 23.95000 5 1 3.99167 83.33333 16.66667 7.71000 -14.60000 2.64041 [ 3] 3 38 3.80000 173.84000 -200.15000 -26.31000 22 16 -0.69237 57.89474 42.10526 7.90182 -12.50938 0.86855 [ 4] 4 60 6.00000 361.44000 -389.40000 -27.96000 27 33 -0.46600 45.00000 55.00000 13.38667 -11.80000 0.92820 [ 5] 5 32 3.20000 157.43000 -179.89000 -22.46000 20 12 -0.70187 62.50000 37.50000 7.87150 -14.99083 0.87515 [ 6] 6 18 1.80000 95.59000 -162.33000 -66.74000 11 7 -3.70778 61.11111 38.88889 8.69000 -23.19000 0.58886 [ 7] 7 14 1.40000 38.48000 -134.30000 -95.82000 9 5 -6.84429 64.28571 35.71429 4.27556 -26.86000 0.28652 [ 8] 8 42 4.20000 368.48000 -322.30000 46.18000 24 18 1.09952 57.14286 42.85714 15.35333 -17.90556 1.14328 [ 9] 9 118 11.80000 1121.62000 -875.21000 246.41000 72 46 2.08822 61.01695 38.98305 15.57806 -19.02630 1.28154 [10] 10 206 20.60000 2280.59000 -2021.80000 258.79000 115 91 1.25626 55.82524 44.17476 19.83122 -22.21758 1.12800 [11] 11 138 13.80000 1377.02000 -994.18000 382.84000 84 54 2.77420 60.86957 39.13043 16.39310 -18.41074 1.38508 [12] 12 152 15.20000 1247.56000 -1463.80000 -216.24000 84 68 -1.42263 55.26316 44.73684 14.85190 -21.52647 0.85227 [13] 13 64 6.40000 778.27000 -516.22000 262.05000 36 28 4.09453 56.25000 43.75000 21.61861 -18.43643 1.50763 [14] 14 62 6.20000 536.93000 -427.47000 109.46000 38 24 1.76548 61.29032 38.70968 14.12974 -17.81125 1.25606 [15] 15 50 5.00000 699.92000 -413.00000 286.92000 28 22 5.73840 56.00000 44.00000 24.99714 -18.77273 1.69472 [16] 16 88 8.80000 778.55000 -514.00000 264.55000 51 37 3.00625 57.95455 42.04545 15.26569 -13.89189 1.51469 [17] 17 76 7.60000 533.92000 -1019.46000 -485.54000 44 32 -6.38868 57.89474 42.10526 12.13455 -31.85813 0.52373 [18] 18 52 5.20000 237.17000 -246.78000 -9.61000 24 28 -0.18481 46.15385 53.84615 9.88208 -8.81357 0.96106 [19] 19 52 5.20000 407.67000 -150.36000 257.31000 30 22 4.94827 57.69231 42.30769 13.58900 -6.83455 2.71129 [20] 20 18 1.80000 65.92000 -89.09000 -23.17000 9 9 -1.28722 50.00000 50.00000 7.32444 -9.89889 0.73993 [21] 21 10 1.00000 41.86000 -32.38000 9.48000 7 3 0.94800 70.00000 30.00000 5.98000 -10.79333 1.29277 [22] 22 14 1.40000 45.55000 -83.72000 -38.17000 6 8 -2.72643 42.85714 57.14286 7.59167 -10.46500 0.54408 [23] 23 2 0.20000 1.20000 -1.90000 -0.70000 1 1 -0.35000 50.00000 50.00000 1.20000 -1.90000 0.63158 */ //+------------------------------------------------------------------+ //| Create DEALS table | //+------------------------------------------------------------------+ bool CreateTableDeals(int database) { //--- if the DEALS table already exists, delete it if(!DeleteTable(database, "DEALS")) { return(false); } //--- check if the table exists if(!DatabaseTableExists(database, "DEALS")) //--- create a table if(!DatabaseExecute(database, "CREATE TABLE DEALS(" "ID INT KEY NOT NULL," "ORDER_ID INT NOT NULL," "POSITION_ID INT NOT NULL," "TIME INT NOT NULL," "TYPE INT NOT NULL," "ENTRY INT NOT NULL," "SYMBOL CHAR(10)," "VOLUME REAL," "PRICE REAL," "PROFIT REAL," "SWAP REAL," "COMMISSION REAL," "MAGIC INT," "HOUR INT," "REASON INT);")) { Print("DB: create the DEALS table failed with code ", GetLastError()); return(false); } //--- request the entire trading history datetime from_date=0; datetime to_date=TimeCurrent(); //--- request the history of deals in the specified interval HistorySelect(from_date, to_date); ExtDealsTotal=HistoryDealsTotal(); //--- add deals to the table if(!InsertDeals(database)) return(false); //--- the table has been successfully created return(true); } //+------------------------------------------------------------------+ //| Delete a table with the specified name from the database | //+------------------------------------------------------------------+ bool DeleteTable(int database, string table_name) { if(!DatabaseExecute(database, "DROP TABLE IF EXISTS "+table_name)) { Print("Failed to drop the DEALS table with code ", GetLastError()); return(false); } //--- the table has been successfully deleted return(true); } //+------------------------------------------------------------------+ //| Add deals to the database table | //+------------------------------------------------------------------+ bool InsertDeals(int database) { //--- auxiliary variables ulong deal_ticket; // deal ticket long order_ticket; // a ticket of an order a deal was executed by long position_ticket; // ID of a position a deal belongs to datetime time; // deal execution time long type ; // deal type long entry ; // deal direction string symbol; // a symbol a deal was executed for double volume; // operation volume double price; // price double profit; // financial result double swap; // swap double commission; // commission long magic; // Magic number (Expert Advisor ID) long reason; // deal execution reason or source char hour; // deal execution hour MqlDateTime time_strusture; //--- go through all deals and add them to the database bool failed=false; int deals=HistoryDealsTotal(); // --- lock the database before executing transactions DatabaseTransactionBegin(database); for(int i=0; i<deals; i++) { deal_ticket= HistoryDealGetTicket(i); order_ticket= HistoryDealGetInteger(deal_ticket, DEAL_ORDER); position_ticket=HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID); time= (datetime)HistoryDealGetInteger(deal_ticket, DEAL_TIME); type= HistoryDealGetInteger(deal_ticket, DEAL_TYPE); entry= HistoryDealGetInteger(deal_ticket, DEAL_ENTRY); symbol= HistoryDealGetString(deal_ticket, DEAL_SYMBOL); volume= HistoryDealGetDouble(deal_ticket, DEAL_VOLUME); price= HistoryDealGetDouble(deal_ticket, DEAL_PRICE); profit= HistoryDealGetDouble(deal_ticket, DEAL_PROFIT); swap= HistoryDealGetDouble(deal_ticket, DEAL_SWAP); commission= HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION); magic= HistoryDealGetInteger(deal_ticket, DEAL_MAGIC); reason= HistoryDealGetInteger(deal_ticket, DEAL_REASON); TimeToStruct(time, time_strusture); hour= (char)time_strusture.hour; //--- add each deal to the table using the following query string request_text=StringFormat("INSERT INTO DEALS (ID,ORDER_ID,POSITION_ID,TIME,TYPE,ENTRY,SYMBOL,VOLUME,PRICE,PROFIT,SWAP,COMMISSION,MAGIC,REASON,HOUR)" "VALUES (%d, %d, %d, %d, %d, %d, '%s', %G, %G, %G, %G, %G, %d, %d,%d)", deal_ticket, order_ticket, position_ticket, time, type, entry, symbol, volume, price, profit, swap, commission, magic, reason, hour); if(!DatabaseExecute(database, request_text)) { PrintFormat("%s: failed to insert deal #%d with code %d", __FUNCTION__, deal_ticket, GetLastError()); PrintFormat("i=%d: deal #%d %s", i, deal_ticket, symbol); failed=true; break; } } //--- check for transaction execution errors if(failed) { //--- roll back all transactions and unlock the database DatabaseTransactionRollback(database); PrintFormat("%s: DatabaseExecute() failed with code ", __FUNCTION__, GetLastError()); return(false); } //--- all transactions have been performed successfully - record changes and unlock the database DatabaseTransactionCommit(database); return(true); } //+------------------------------------------------------------------+ //| Create TRADES table | //+------------------------------------------------------------------+ bool CreateTableTrades(int database) { //--- if the TRADES table already exists, delete it if(!DeleteTable(database, "TRADES")) return(false); //--- check if the table exists if(!DatabaseTableExists(database, "TRADES")) //--- create a table if(!DatabaseExecute(database, "CREATE TABLE TRADES(" "TIME_IN INT NOT NULL," "HOUR_IN INT NOT NULL," "TICKET INT NOT NULL," "TYPE INT NOT NULL," "VOLUME REAL," "SYMBOL CHAR(10)," "PRICE_IN REAL," "TIME_OUT INT NOT NULL," "PRICE_OUT REAL," "COMMISSION REAL," "SWAP REAL," "PROFIT REAL);")) { Print("DB: create the TRADES table failed with code ", GetLastError()); return(false); } //--- the table has been successfully created return(true); } //+------------------------------------------------------------------+
Sie können sie in den Editor kopieren, kompilieren, ausführen und die Ergebnisse ihrer Arbeit in der Zeitschrift sehen.
Jetzt haben wir Beispiele dafür, wie wir mit einer Datenbank arbeiten können, um das gewünschte Ergebnis zu erhalten. Es ist nur eine kleine Änderung der in der Hilfe vorgestellten Codes erforderlich. Beispielsweise müssen die Daten nach einem bestimmten Feld sortiert werden, oder es werden nur eindeutige Werte aus Tabellen abgerufen. Zu diesem Zweck können wir die Referenzinformationen auf SQL lesen. Mit dem Wissen und den Beispielen, die wir gewonnen haben, werden wir in der Lage sein, das zu tun, was wir für das geplante Projekt brauchen.
Im Terminalordner \MQL5\Indicators\ erstellen wir den Ordner StaticticsBy\. Er enthält alle Dateien des Projekts.
In dem erstellten Ordner erstellen wir die neue Datei SQLiteFunc.mqh und beginnen, sie mit Funktionen zur Handhabung der Datenbank zu füllen.
Zunächst müssen wir die notwendigen Strukturen schaffen:
//+------------------------------------------------------------------+ //| SQLiteFunc.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| Structure for storing the deal | //+------------------------------------------------------------------+ struct SDeal { long account; // ACCOUNT ulong ticket; // DEAL_TICKET long order_ticket; // DEAL_ORDER long position_ticket; // DEAL_POSITION_ID datetime time; // DEAL_TIME char type; // DEAL_TYPE char entry; // DEAL_ENTRY string symbol; // DEAL_SYMBOL double volume; // DEAL_VOLUME double price; // DEAL_PRICE double profit; // DEAL_PROFIT double swap; // DEAL_SWAP double commission; // DEAL_COMMISSION long magic; // DEAL_MAGIC char reason; // DEAL_REASON }; //+------------------------------------------------------------------+ //| Structure to store the date: | //| the order of members corresponds to the position in the terminal | //+------------------------------------------------------------------+ struct STrade { long account; // account index datetime time_in; // login time ulong ticket; // position ID char type; // buy or sell double volume; // volume pair symbol; // symbol double price_in; // entry price datetime time_out; // exit time double price_out; // exit price double commission; // entry and exit fees double swap; // swap double profit; // profit or loss }; //+------------------------------------------------------------------+ //| Structure for storing statistics on a symbol | //+------------------------------------------------------------------+ struct SSymbolStats { string name; // symbol name int trades; // number of trades for the symbol double gross_profit; // total profit for the symbol double gross_loss; // total loss for the symbol double total_commission; // total commission for the symbol double total_swap; // total swaps for the symbol double total_profit; // total profit excluding swaps and commissions double net_profit; // net profit taking into account swaps and commissions int win_trades; // number of profitable trades int loss_trades; // number of losing trades long long_trades; // long trades long short_trades; // short trades double expected_payoff; // expected payoff for the trade excluding swaps and commissions double win_percent; // percentage of winning trades double loss_percent; // percentage of losing trades double average_profit; // average profit double average_loss; // average loss double profit_factor; // profit factor }; //+------------------------------------------------------------------+ //| Structure for storing statistics on Magic Number | //+------------------------------------------------------------------+ struct SMagicStats { long magic; // EA's Magic Number int trades; // number of trades for the symbol double gross_profit; // total profit for the symbol double gross_loss; // total loss for the symbol double total_commission; // total commission for the symbol double total_swap; // total swaps for the symbol double total_profit; // total profit excluding swaps and commissions double net_profit; // net profit taking into account swaps and commissions int win_trades; // number of profitable trades int loss_trades; // number of losing trades long long_trades; // long trades long short_trades; // short trades double expected_payoff; // expected payoff for the trade excluding swaps and commissions double win_percent; // percentage of winning trades double loss_percent; // percentage of losing trades double average_profit; // average profit double average_loss; // average loss double profit_factor; // profit factor }; //+------------------------------------------------------------------+ //| Structure for storing statistics on an account | //+------------------------------------------------------------------+ struct SAccountStats { long account; // account index int trades; // number of trades for the symbol double gross_profit; // total profit for the symbol double gross_loss; // total loss for the symbol double total_commission; // total commission for the symbol double total_swap; // total swaps for the symbol double total_profit; // total profit excluding swaps and commissions double net_profit; // net profit taking into account swaps and commissions int win_trades; // number of profitable trades int loss_trades; // number of losing trades long long_trades; // long trades long short_trades; // short trades double expected_payoff; // expected payoff for the trade excluding swaps and commissions double win_percent; // percentage of winning trades double loss_percent; // percentage of losing trades double average_profit; // average profit double average_loss; // average loss double profit_factor; // profit factor };
Die Strukturen sind von den oben in der Dokumentation gezeigten Beispielen kopiert, aber sie haben andere Namen. Außerdem fügen wir Felder hinzu mit dem Kontoindex mit der Anzahl der Kauf- und Verkaufspositionen, mit denen eine Auswahl aus der Datenbank getroffen werden kann.
Wir benötigen die Historie der Deals, um die Tabelle der Deals in der Datenbank zu erstellen. Daher werden wir die Funktion zum Abrufen des Geschäftsverlaufs in dieselbe Datei schreiben:
//+------------------------------------------------------------------+ //| Request the deal history for the specified period | //+------------------------------------------------------------------+ bool GetHistoryDeals(const datetime from_date, const datetime to_date) { ResetLastError(); if(HistorySelect(from_date, to_date)) return true; Print("HistorySelect() failed. Error ", GetLastError()); return false; }
Hier werden wir auch eine Funktion zum Löschen einer Tabelle mit dem angegebenen Namen aus der Datenbank eingeben:
//+------------------------------------------------------------------+ //| Delete a table with the specified name from the database | //+------------------------------------------------------------------+ bool DeleteTable(int database, string table_name) { ResetLastError(); if(!DatabaseExecute(database, "DROP TABLE IF EXISTS "+table_name)) { Print("Failed to drop the DEALS table with code ", GetLastError()); return(false); } //--- the table has been successfully deleted return(true); }
Schreiben wir die Funktion für die Tabelle der Deals:
//+------------------------------------------------------------------+ //| Create DEALS table | //+------------------------------------------------------------------+ bool CreateTableDeals(int database) { //--- if the DEALS table already exists, delete it if(!DeleteTable(database, "DEALS")) return(false); //--- check if the table exists ResetLastError(); if(!DatabaseTableExists(database, "DEALS")) //--- create a table if(!DatabaseExecute(database, "CREATE TABLE DEALS(" "ID INT KEY NOT NULL," "ACCOUNT INT NOT NULL," "ORDER_ID INT NOT NULL," "POSITION_ID INT NOT NULL," "TIME INT NOT NULL," "TYPE INT NOT NULL," "ENTRY INT NOT NULL," "SYMBOL CHAR(10)," "VOLUME REAL," "PRICE REAL," "PROFIT REAL," "SWAP REAL," "COMMISSION REAL," "MAGIC INT," "REASON INT );")) { Print("DB: create the DEALS table failed with code ", GetLastError()); return(false); } //--- the table has been successfully created return(true); }
Der Funktionscode ist aus der Hilfe kopiert. Ich habe jedoch ein weiteres Feld hinzugefügt - den Kontenindex.
In ähnlicher Weise implementieren wir die Funktion der Handelstabelle, die ebenfalls das Feld mit dem Kontoindex enthält:
//+------------------------------------------------------------------+ //| Create TRADES table | //+------------------------------------------------------------------+ bool CreateTableTrades(int database) { //--- if the TRADES table already exists, delete it if(!DeleteTable(database, "TRADES")) return(false); //--- check if the table exists ResetLastError(); if(!DatabaseTableExists(database, "TRADES")) //--- create a table if(!DatabaseExecute(database, "CREATE TABLE TRADES(" "ACCOUNT INT NOT NULL," "TIME_IN INT NOT NULL," "TICKET INT NOT NULL," "TYPE INT NOT NULL," "VOLUME REAL," "SYMBOL CHAR(10)," "PRICE_IN REAL," "TIME_OUT INT NOT NULL," "PRICE_OUT REAL," "COMMISSION REAL," "SWAP REAL," "PROFIT REAL);")) { Print("DB: create the TRADES table failed with code ", GetLastError()); return(false); } //--- the table has been successfully created return(true); }
Schreiben wir die Funktion, um die Datenbanktabelle mit Geschäftsdaten zu füllen:
//+------------------------------------------------------------------+ //| Add deals to the database table | //+------------------------------------------------------------------+ bool InsertDeals(int database) { //--- auxiliary variables long account_login=AccountInfoInteger(ACCOUNT_LOGIN); // account index ulong deal_ticket; // deal ticket long order_ticket; // a ticket of an order a deal was executed by long position_ticket; // ID of a position a deal belongs to datetime time; // deal execution time long type ; // deal type long entry ; // deal direction string symbol; // a symbol a deal was executed for double volume; // operation volume double price; // price double profit; // financial result double swap; // swap double commission; // commission long magic; // Magic number (Expert Advisor ID) long reason; // deal execution reason or source //--- go through all deals and add them to the database bool failed=false; int deals=HistoryDealsTotal(); // --- lock the database before executing transactions DatabaseTransactionBegin(database); ResetLastError(); for(int i=0; i<deals; i++) { deal_ticket= HistoryDealGetTicket(i); order_ticket= HistoryDealGetInteger(deal_ticket, DEAL_ORDER); position_ticket=HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID); time= (datetime)HistoryDealGetInteger(deal_ticket, DEAL_TIME); type= HistoryDealGetInteger(deal_ticket, DEAL_TYPE); entry= HistoryDealGetInteger(deal_ticket, DEAL_ENTRY); symbol= HistoryDealGetString(deal_ticket, DEAL_SYMBOL); volume= HistoryDealGetDouble(deal_ticket, DEAL_VOLUME); price= HistoryDealGetDouble(deal_ticket, DEAL_PRICE); profit= HistoryDealGetDouble(deal_ticket, DEAL_PROFIT); swap= HistoryDealGetDouble(deal_ticket, DEAL_SWAP); commission= HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION); magic= HistoryDealGetInteger(deal_ticket, DEAL_MAGIC); reason= HistoryDealGetInteger(deal_ticket, DEAL_REASON); //--- add each deal to the table using the following query string request_text=StringFormat("INSERT INTO DEALS (ID,ACCOUNT,ORDER_ID,POSITION_ID,TIME,TYPE,ENTRY,SYMBOL,VOLUME,PRICE,PROFIT,SWAP,COMMISSION,MAGIC,REASON)" "VALUES (%I64d, %I64d, %I64d, %I64d, %d, %d, %d, '%s', %G, %G, %G, %G, %G, %I64d, %d)", deal_ticket, account_login, order_ticket, position_ticket, time, type, entry, symbol, volume, price, profit, swap, commission, magic, reason); if(!DatabaseExecute(database, request_text)) { PrintFormat("%s: failed to insert deal #%d with code %d", __FUNCTION__, deal_ticket, GetLastError()); PrintFormat("i=%d: deal #%d %s", i, deal_ticket, symbol); failed=true; break; } } //--- check for transaction execution errors if(failed) { //--- roll back all transactions and unlock the database DatabaseTransactionRollback(database); PrintFormat("%s: DatabaseExecute() failed with code %d", __FUNCTION__, GetLastError()); return(false); } //--- all transactions have been performed successfully - record changes and unlock the database DatabaseTransactionCommit(database); return(true); }
Der Funktionscode ist dem Beispiel der Funktion DatabaseExecute() in der Hilfe entnommen. Ich habe eine zusätzliche Variable für die Speicherung des Kontoindexes implementiert und den Abfragetext korrigiert, da es wahrscheinlich einen Fehler in der Hilfe gibt: Der Datentyp int wird für Daten des Typs long beim Zusammenstellen des Abfragetextes angegeben:
//--- add each deal to the table using the following query string request_text=StringFormat("INSERT INTO DEALS (ID,ORDER_ID,POSITION_ID,TIME,TYPE,ENTRY,SYMBOL,VOLUME,PRICE,PROFIT,SWAP,COMMISSION,MAGIC,REASON,HOUR)" "VALUES (%d, %d, %d, %d, %d, %d, '%s', %G, %G, %G, %G, %G, %d, %d,%d)", deal_ticket, order_ticket, position_ticket, time, type, entry, symbol, volume, price, profit, swap, commission, magic, reason, hour);
Wir haben dieses Problem behoben, indem wir den Kontoindex hinzugefügt und die Stunde der Eröffnung entfernt haben, da wir sie hier nicht benötigen.
Schreiben wir die Funktion zum Füllen der Tabelle der Handelsgeschäfte auf der Grundlage der Tabelle der Deals:
//+------------------------------------------------------------------+ //| Fill the TRADES table based on DEALS table | //+------------------------------------------------------------------+ bool FillTRADEStableBasedOnDEALStable(int database) { if(!DatabaseTableExists(database, "DEALS")) { PrintFormat("%s: Error. DEALS table is missing in the database", __FUNCTION__); return false; } //--- fill in the TRADES table if(!DatabaseExecute(database, "INSERT INTO TRADES(TIME_IN,ACCOUNT,TICKET,TYPE,VOLUME,SYMBOL,PRICE_IN,TIME_OUT,PRICE_OUT,COMMISSION,SWAP,PROFIT) " "SELECT " " d1.time as time_in," " d1.account as account," " d1.position_id as ticket," " d1.type as type," " d1.volume as volume," " d1.symbol as symbol," " d1.price as price_in," " d2.time as time_out," " d2.price as price_out," " d1.commission+d2.commission as commission," " d2.swap as swap," " d2.profit as profit " "FROM DEALS d1 " "INNER JOIN DEALS d2 ON d1.position_id=d2.position_id " "WHERE d1.entry=0 AND d2.entry=1")) { Print("DB: fillng the TRADES table failed with code ", GetLastError()); return false; } return true; }
Wie bei den obigen Funktionen wurde auch hier der Kontenindex hinzugefügt.
Schreiben wir die Funktion, die eine Liste aller Gewerke aus der Datenbank füllt:
//+------------------------------------------------------------------+ //| Fill the list of all trades from the database | //+------------------------------------------------------------------+ bool FillsListTradesFromDB(int database, string db_name, STrade &array[]) { STrade trade; ResetLastError(); //--- Request a list of trades from the DB sorted by descending market entry time int request=DatabasePrepare(database, "SELECT * FROM TRADES ORDER BY time_in DESC"); if(request==INVALID_HANDLE) { Print("DB: ", db_name, " request failed with code ", GetLastError()); DatabaseClose(database); return false; } //--- Read the data of the created trade table into the array of structures for(int i=0; DatabaseReadBind(request, trade); i++) { ArrayResize(array, i+1); array[i].account=trade.account; array[i].time_in=trade.time_in; array[i].ticket=trade.ticket; array[i].type=trade.type; array[i].volume=trade.volume; array[i].symbol=trade.symbol; array[i].price_in=trade.price_in; array[i].time_out=trade.time_out; array[i].price_out=trade.price_out; array[i].commission=trade.commission; array[i].swap=trade.swap; array[i].profit=trade.profit; } //--- remove the query after use DatabaseFinalize(request); return true; }
Hier haben wir die Liste der Handelsgeschäfte in absteigender Reihenfolge der Markteintrittszeit sortiert. Wenn wir dies nicht tun, befindet sich der letzte Abschluss am Ende der Liste und dementsprechend auch in der Tabelle auf dem Bedienfeld ganz am Ende. Das ist ungünstig. Wenn wir in absteigender Reihenfolge sortieren, wird der neueste Handel ganz an den Anfang der Tabelle gesetzt - oben auf dem Dashboard - und die neuesten Handelsgeschäfte sind sofort sichtbar, ohne dass man lange durch die Liste der Handelsgeschäfte blättern müssen, um sie zu erreichen.
Schreiben wir nun die Funktion, die die Liste aller gehandelten Symbole aus der Datenbank ausfüllt:
//+------------------------------------------------------------------+ //| Fill the list of all symbols from the database | //+------------------------------------------------------------------+ bool FillsListSymbolsFromDB(int database, string db_name, string &array[]) { //--- Check the presence of the created trade table in the database ResetLastError(); if(!DatabaseTableExists(database, "TRADES")) { //--- If the table has not been created yet, inform on how to create it if(GetLastError()==5126) Alert("First you need to get the trade history.\nClick the \"Get trade history\" button."); else Print("DatabaseTableExists() failed. Error ",GetLastError()); return false; } //--- request the list of all symbols trading was carried out on from the database. The list is sorted alphabetically int request=DatabasePrepare(database, "SELECT DISTINCT symbol FROM TRADES ORDER BY symbol ASC"); if(request==INVALID_HANDLE) { Print("DB: ", db_name, " request failed with code ", GetLastError()); DatabaseClose(database); return false; } //--- Read the data of the created symbol table into the array for(int i=0; DatabaseRead(request); i++) { ArrayResize(array, i+1); DatabaseColumnText(request, 0, array[i]); } //--- remove the query after use DatabaseFinalize(request); return true; }
Wir verwenden das Schlüsselwort „DISTINCT“, um eine Liste zu erhalten, die nur eindeutige, sich nicht wiederholende Symbolnamen enthält. Die Liste wird in alphabetischer Reihenfolge erstellt.
Auf ähnliche Weise implementieren wir die Funktion, die die Liste aller magischen Zahlen in aufsteigender Reihenfolge aus der Datenbank füllt:
//+------------------------------------------------------------------+ //| Fill the list of all magic numbers from the database | //+------------------------------------------------------------------+ bool FillsListMagicsFromDB(int database, string db_name, long &array[]) { //--- Check the presence of the created trade table in the database ResetLastError(); if(!DatabaseTableExists(database, "DEALS")) { //--- If the table has not been created yet, inform on how to create it if(GetLastError()==5126) Alert("First you need to get the trade history.\nClick the \"Get trade history\" button."); else Print("DatabaseTableExists() failed. Error ",GetLastError()); return false; } //--- request the list of all magic numbers trading was carried out on from the database. The list is sorted in ascending order. int request=DatabasePrepare(database, "SELECT DISTINCT magic FROM DEALS ORDER BY magic ASC"); if(request==INVALID_HANDLE) { Print("DB: ", db_name, " request failed with code ", GetLastError()); DatabaseClose(database); return false; } //--- Read the data of the created table of magic numbers into the array for(int i=0; DatabaseRead(request); i++) { ArrayResize(array, i+1); DatabaseColumnLong(request, 0, array[i]); } //--- remove the query after use DatabaseFinalize(request); return true; }
Erstellen wir die Funktion, die symbolbasierte Handelsstatistiken aus der Datenbank abruft und in einem Array speichert:
//+------------------------------------------------------------------------------+ //|Get symbol-based trading statistics from the database and save it to the array| //+------------------------------------------------------------------------------+ bool GetTradingStatsBySymbols(int database, string db_name, SSymbolStats &array[]) { int request=DatabasePrepare(database, "SELECT r.*," " (case when r.trades != 0 then (r.gross_profit+r.gross_loss)/r.trades else 0 end) as expected_payoff," " (case when r.trades != 0 then r.win_trades*100.0/r.trades else 0 end) as win_percent," " (case when r.trades != 0 then r.loss_trades*100.0/r.trades else 0 end) as loss_percent," " r.gross_profit/r.win_trades as average_profit," " r.gross_loss/r.loss_trades as average_loss," " (case when r.gross_loss!=0.0 then r.gross_profit/(-r.gross_loss) else 0 end) as profit_factor, " " r.long_trades as long_trades," " r.short_trades as short_trades " "FROM " " (" " SELECT SYMBOL," " sum(case when entry =1 then 1 else 0 end) as trades," " sum(case when profit > 0 then profit else 0 end) as gross_profit," " sum(case when profit < 0 then profit else 0 end) as gross_loss," " sum(swap) as total_swap," " sum(commission) as total_commission," " sum(profit) as total_profit," " sum(profit+swap+commission) as net_profit," " sum(case when profit > 0 then 1 else 0 end) as win_trades," " sum(case when profit < 0 then 1 else 0 end) as loss_trades, " " sum(case when type = 0 AND entry = 0 then 1 else 0 end) as long_trades, " " sum(case when type = 1 AND entry = 0 then 1 else 0 end) as short_trades " " FROM DEALS " " WHERE SYMBOL <> '' and SYMBOL is not NULL " " GROUP BY SYMBOL ORDER BY net_profit DESC" " ) as r"); if(request==INVALID_HANDLE) { Print("DB: ", db_name, " request failed with code ", GetLastError()); DatabaseClose(database); return false; } //--- get entries from request results SSymbolStats symbol_stats; for(int i=0; DatabaseReadBind(request, symbol_stats) ; i++) { ArrayResize(array, i+1); array[i].name=symbol_stats.name; array[i].trades=symbol_stats.trades; array[i].long_trades=symbol_stats.long_trades; array[i].short_trades=symbol_stats.short_trades; array[i].gross_profit=symbol_stats.gross_profit; array[i].gross_loss=symbol_stats.gross_loss; array[i].total_commission=symbol_stats.total_commission; array[i].total_swap=symbol_stats.total_swap; array[i].total_profit=symbol_stats.total_profit; array[i].net_profit=symbol_stats.net_profit; array[i].win_trades=symbol_stats.win_trades; array[i].loss_trades=symbol_stats.loss_trades; array[i].expected_payoff=symbol_stats.expected_payoff; array[i].win_percent=symbol_stats.win_percent; array[i].loss_percent=symbol_stats.loss_percent; array[i].average_profit=symbol_stats.average_profit; array[i].average_loss=symbol_stats.average_loss; array[i].profit_factor=symbol_stats.profit_factor; } //--- remove the query after use DatabaseFinalize(request); return true; }
Hier habe ich Zeichenketten hinzugefügt, um Kauf- und Verkaufs-Positionen zu berücksichtigen und die Liste in absteigender Reihenfolge des Nettogewinns zu sortieren, sodass die Symbole, die die größten Gewinne erzielt haben, am Anfang der Tabelle stehen.
In ähnlicher Weise werden wir die Funktion zum Abrufen der auf der magischen Zahl basierenden Handelsstatistiken aus der Datenbank schreiben und sie im Array speichern:
//+------------------------------------------------------------------------------------+ //|Get magic number-based trading statistics from the database and save it to the array| //+------------------------------------------------------------------------------------+ bool GetTradingStatsByMagics(int database, string db_name, SMagicStats &array[]) { int request=DatabasePrepare(database, "SELECT r.*," " (case when r.trades != 0 then (r.gross_profit+r.gross_loss)/r.trades else 0 end) as expected_payoff," " (case when r.trades != 0 then r.win_trades*100.0/r.trades else 0 end) as win_percent," " (case when r.trades != 0 then r.loss_trades*100.0/r.trades else 0 end) as loss_percent," " r.gross_profit/r.win_trades as average_profit," " r.gross_loss/r.loss_trades as average_loss," " (case when r.gross_loss!=0.0 then r.gross_profit/(-r.gross_loss) else 0 end) as profit_factor, " " r.long_trades as long_trades," " r.short_trades as short_trades " "FROM " " (" " SELECT MAGIC," " sum(case when entry =1 then 1 else 0 end) as trades," " sum(case when profit > 0 then profit else 0 end) as gross_profit," " sum(case when profit < 0 then profit else 0 end) as gross_loss," " sum(swap) as total_swap," " sum(commission) as total_commission," " sum(profit) as total_profit," " sum(profit+swap+commission) as net_profit," " sum(case when profit > 0 then 1 else 0 end) as win_trades," " sum(case when profit < 0 then 1 else 0 end) as loss_trades, " " sum(case when type = 0 AND entry = 0 then 1 else 0 end) as long_trades, " " sum(case when type = 1 AND entry = 0 then 1 else 0 end) as short_trades " " FROM DEALS " " WHERE SYMBOL <> '' and SYMBOL is not NULL " " GROUP BY MAGIC ORDER BY net_profit DESC" " ) as r"); if(request==INVALID_HANDLE) { Print("DB: ", db_name, " request failed with code ", GetLastError()); DatabaseClose(database); return false; } //--- get entries from request results SMagicStats magic_stats; for(int i=0; DatabaseReadBind(request, magic_stats) ; i++) { ArrayResize(array, i+1); array[i].magic=magic_stats.magic; array[i].trades=magic_stats.trades; array[i].long_trades=magic_stats.long_trades; array[i].short_trades=magic_stats.short_trades; array[i].gross_profit=magic_stats.gross_profit; array[i].gross_loss=magic_stats.gross_loss; array[i].total_commission=magic_stats.total_commission; array[i].total_swap=magic_stats.total_swap; array[i].total_profit=magic_stats.total_profit; array[i].net_profit=magic_stats.net_profit; array[i].win_trades=magic_stats.win_trades; array[i].loss_trades=magic_stats.loss_trades; array[i].expected_payoff=magic_stats.expected_payoff; array[i].win_percent=magic_stats.win_percent; array[i].loss_percent=magic_stats.loss_percent; array[i].average_profit=magic_stats.average_profit; array[i].average_loss=magic_stats.average_loss; array[i].profit_factor=magic_stats.profit_factor; } //--- remove the query after use DatabaseFinalize(request); return true; }
Abschließend wollen wir eine ähnliche Funktion für kontobasierte Handelsstatistiken schreiben:
//+---------------------------------------------------------------------------------+ //| Get account-based trading statistics from the database and save it to the array | //+---------------------------------------------------------------------------------+ bool GetTradingStatsByAccount(int database, string db_name, SAccountStats &array[]) { int request=DatabasePrepare(database, "SELECT r.*," " (case when r.trades != 0 then (r.gross_profit+r.gross_loss)/r.trades else 0 end) as expected_payoff," " (case when r.trades != 0 then r.win_trades*100.0/r.trades else 0 end) as win_percent," " (case when r.trades != 0 then r.loss_trades*100.0/r.trades else 0 end) as loss_percent," " r.gross_profit/r.win_trades as average_profit," " r.gross_loss/r.loss_trades as average_loss," " (case when r.gross_loss!=0.0 then r.gross_profit/(-r.gross_loss) else 0 end) as profit_factor, " " r.long_trades as long_trades," " r.short_trades as short_trades " "FROM " " (" " SELECT ACCOUNT," " sum(case when entry =1 then 1 else 0 end) as trades," " sum(case when profit > 0 then profit else 0 end) as gross_profit," " sum(case when profit < 0 then profit else 0 end) as gross_loss," " sum(swap) as total_swap," " sum(commission) as total_commission," " sum(profit) as total_profit," " sum(profit+swap+commission) as net_profit," " sum(case when profit > 0 then 1 else 0 end) as win_trades," " sum(case when profit < 0 then 1 else 0 end) as loss_trades, " " sum(case when type = 0 AND entry = 0 then 1 else 0 end) as long_trades, " " sum(case when type = 1 AND entry = 0 then 1 else 0 end) as short_trades " " FROM DEALS " " WHERE SYMBOL <> '' and SYMBOL is not NULL " " GROUP BY ACCOUNT ORDER BY net_profit DESC" " ) as r"); if(request==INVALID_HANDLE) { Print("DB: ", db_name, " request failed with code ", GetLastError()); DatabaseClose(database); return false; } //--- get entries from request results SAccountStats account_stats; for(int i=0; DatabaseReadBind(request, account_stats) ; i++) { ArrayResize(array, i+1); array[i].account=account_stats.account; array[i].trades=account_stats.trades; array[i].long_trades=account_stats.long_trades; array[i].short_trades=account_stats.short_trades; array[i].gross_profit=account_stats.gross_profit; array[i].gross_loss=account_stats.gross_loss; array[i].total_commission=account_stats.total_commission; array[i].total_swap=account_stats.total_swap; array[i].total_profit=account_stats.total_profit; array[i].net_profit=account_stats.net_profit; array[i].win_trades=account_stats.win_trades; array[i].loss_trades=account_stats.loss_trades; array[i].expected_payoff=account_stats.expected_payoff; array[i].win_percent=account_stats.win_percent; array[i].loss_percent=account_stats.loss_percent; array[i].average_profit=account_stats.average_profit; array[i].average_loss=account_stats.average_loss; array[i].profit_factor=account_stats.profit_factor; } //--- remove the query after use DatabaseFinalize(request); return true; }
Wir haben die grundlegenden Dinge für die Erstellung eines Projekts vorbereitet. Wir haben nämlich das Dashboard ausgewählt und auf der Grundlage von Informationen aus der Dokumentation Funktionen zur Handhabung der Datenbank erstellt. Jetzt müssen wir nur noch die Logik für die Interaktion des Dashboards und seiner Tabellen mit der Datenbank mithilfe der implementierten Funktionen erstellen. Es ist wahrscheinlich, dass die Listen in den Tabellen auf dem Dashboard recht umfangreich sind und die Größe der Tabellen die Abmessungen des Dashboards übersteigt. In diesem Fall müssen wir die Tabellen vertikal und horizontal verschieben. Wir werden diese Funktionalität direkt in den zu erstellenden Indikator implementieren - die Tabellen werden vertikal durch Drehen des Mausrads und horizontal durch Verwendung des Mausrads bei gedrückter Umschalttaste gescrollt.
Die Statistik für das ausgewählte Symbol oder die magische Zahl wird angezeigt, wenn Sie auf die Statistikzeile des gewünschten Symbols oder der magischen Zahl klicken. Dazu verfolgen wir die Position des Cursors über den Tabellenzeilen und klicken auf die Zeile. Es wäre sinnvoll, eine solche Funktionsweise in die Dashboard-Klasse aufzunehmen, damit sie in anderen Projekten verwendet werden kann. Aber hier werden wir zeigen, wie wir das Gleiche tun können, ohne die Dashboard-Klassen zu ändern.
Konstruktion des Dashboards
Im zuvor erstellten Ordner \MQL5\Indicators\StatisticsBy\ erstellen wir die neue Indikatordatei namens StatisticsBy.mq5.
Die Dateien für die Tabellen- und Dashboard-Klassen sowie die Funktionsdatei für die Handhabung der DB binden wir ein und legen fest, dass der Indikator keine gerenderten Puffer hat:
//+------------------------------------------------------------------+ //| StatisticsBy.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_chart_window #property indicator_buffers 0 #property indicator_plots 0 #include "Dashboard\Dashboard.mqh" #include "SQLiteFunc.mqh"
Als Nächstes fügen wir Makrosubstitutionen, eine Reihe von Spalten der Statistiktabelle, Eingaben und globale Variablen hinzu:
#property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_chart_window #property indicator_buffers 0 #property indicator_plots 0 #include "Dashboard\Dashboard.mqh" #include "SQLiteFunc.mqh" #define PROGRAM_NAME (MQLInfoString(MQL_PROGRAM_NAME)) // Program name #define DB_NAME (PROGRAM_NAME+"_DB.sqlite") // Database name #define DATE_FROM 0 // Start date of deal history #define DATE_TO (TimeCurrent()) // End date of deal history //--- Table cell width #define CELL_W_TRADES 94 // Width of trading history table cells #define CELL_W_SYMBOLS 62 // Width of used symbol table cells #define CELL_W_MAGICS 62 // Width of used magic number table cells #define CELL_H 16 // Table cell height //--- Dimensions of the final statistics table #define TABLE_STAT_ROWS 9 // Number of rows in the final statistics table #define TABLE_STAT_COLS 4 // Number of columns in the final statistics table //--- Tables #define TABLE_TRADES 1 // Trade history table ID #define TABLE_SYMBOLS 2 // ID of the table of symbols used in trading #define TABLE_MAGICS 3 // ID of the table of magic numbers used in trading #define TABLE_ACCOUNT 4 // Account statistics table ID #define TABLE_STATS 5 // ID of the final statistics table of the selected symbol or magic number //--- Table headers (full/short) #define H_TRADES "Trades" #define H_TRADES_S "Trades" #define H_LONG "Long" #define H_LONG_S "Long" #define H_SHORT "Short" #define H_SHORT_S "Short" #define H_GROSS_PROFIT "Gross Profit" #define H_GROSS_PROFIT_S "Gross Profit" #define H_GROSS_LOSS "Gross Loss" #define H_GROSS_LOSS_S "Gross Loss" #define H_COMMISSIONS "Commission total" #define H_COMMISSIONS_S "Fees" #define H_SWAPS "Swap total" #define H_SWAPS_S "Swaps" #define H_PROFITS "Profit Loss" #define H_PROFITS_S "P/L" #define H_NET_PROFIT "Net Profit" #define H_NET_PROFIT_S "Net Profit" #define H_WINS "Win trades" #define H_WINS_S "Win" #define H_LOST "Loss trades" #define H_LOST_S "Lost" #define H_EXP_PAYOFF "Expected Payoff" #define H_EXP_PAYOFF_S "Avg $" #define H_WIN_PRC "Win percent" #define H_WIN_PRC_S "Win %" #define H_LOSS_PRC "Loss percent" #define H_LOSS_PRC_S "Loss %" #define H_AVG_PROFIT "Average Profit" #define H_AVG_PROFIT_S "Avg Profit" #define H_AVG_LOSS "Average Loss" #define H_AVG_LOSS_S "Avg Loss" #define H_PRF_FACTOR "Profit factor" #define H_PRF_FACTOR_S "PF" //--- Array of the location of the statistics table columns from left to right string ArrayDataName[18]= { "HEADER", H_NET_PROFIT_S, H_TRADES_S, H_GROSS_PROFIT_S, H_GROSS_LOSS_S, H_COMMISSIONS_S, H_SWAPS_S, H_PROFITS_S, H_LONG_S, H_SHORT_S, H_WINS_S, H_LOST_S, H_EXP_PAYOFF_S, H_WIN_PRC_S, H_LOSS_PRC_S, H_AVG_PROFIT_S, H_AVG_LOSS_S, H_PRF_FACTOR_S, }; //--- input parameters input int InpPanelX = 20; /* Dashboard X */ // Panel X coordinate input int InpPanelY = 20; /* Dashboard Y */ // Panel Y coordinate input int InpUniqID = 0; /* Unique ID */ // Unique ID for the panel object //--- global variables int DBHandle; // Database handle int LPanelTable; // Active panel in the left field int RPanelTable; // Active panel in the right field long ArrayMagics[]; // Array of magic numbers string ArraySymbols[]; // Array of symbols STrade ArrayTrades[]; // Array of trades SSymbolStats ArraySymbolStats[]; // Array of statistics by symbols SMagicStats ArrayMagicStats[]; // Array of statistics by magic numbers SAccountStats ArrayAccountStats[]; // Array of statistics by account CDashboard *dashboard=NULL; // Pointer to the dashboard instance
Um die Anzahl der Spalten in den Statistiktabellen und die Menge und Position der Daten in der Tabelle festzulegen, ist es zweckmäßig, ein Array zu verwenden, das Konstanten für die Namen der Tabellenüberschriften und dementsprechend für die Daten unter diesen Überschriften enthält. Wenn es notwendig ist, die Reihenfolge der verschiedenen Daten in der Tabelle zu ändern, genügt es, die Reihenfolge ihrer Deklaration in diesem Array zu ändern und den Indikator neu zu kompilieren. Wir können auch unnötige Daten entfernen, indem wir sie in diesem Array auskommentieren, oder neue Daten hinzufügen. Wenn wir jedoch neue Daten hinzufügen, müssen wir sie zu den Datenbankfunktionen und anderen Funktionen hinzufügen, mit denen Tabellendaten berechnet und angezeigt werden.
Betrachten wir OnInit(), mit dem die Datenbank und das Dashboard mit seinem grafischen Inhalt erstellt werden:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Make sure that hedging system for open position management is used on the account if((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) { //--- In case of a netting account, deals cannot be transformed to trades using a simple method through transactions, therefore we complete the operation Print("For a Netting account, there is no way to convert deals into trades in a simple way."); return INIT_FAILED; } //--- Specify the path and create the database (\MQL5\Files\StatisticsBy\Database\) string path=PROGRAM_NAME+"\\Database\\"; DBHandle=DatabaseOpen(path+DB_NAME,DATABASE_OPEN_CREATE); if(DBHandle==INVALID_HANDLE) { Print("DatabaseOpen() failed. Error ", GetLastError()); return(INIT_FAILED); } PrintFormat("Database \"%s\" successfully created at MQL5\\Files\\%s", DB_NAME, path); //--- Create the program window panel dashboard = new CDashboard(InpUniqID, InpPanelX, InpPanelY, 601, 300); if(dashboard==NULL) { Print("Error. Failed to create dashboard object"); return INIT_FAILED; } //--- Display the dashboard with the program name text in the window header dashboard.SetFontParams("Calibri",8); dashboard.SetName("Main"); dashboard.View(PROGRAM_NAME); //--- Draw the workspace //--- Button for selecting symbols CDashboard *panel1=dashboard.InsertNewPanel(dashboard.ID()+1, 3, 20, 49, 21); if(panel1!=NULL) { panel1.SetName("SymbolButton"); panel1.SetButtonCloseOff(); panel1.SetButtonMinimizeOff(); panel1.SetButtonPinOff(); panel1.View(""); panel1.Collapse(); panel1.SetHeaderNewParams("Symbol",clrLightGray,clrBlack,USHORT_MAX,5,-1); } //--- Button for selecting magic numbers CDashboard *panel2=dashboard.InsertNewPanel(dashboard.ID()+2, 54, 20, 48, 21); if(panel2!=NULL) { panel2.SetName("MagicButton"); panel2.SetButtonCloseOff(); panel2.SetButtonMinimizeOff(); panel2.SetButtonPinOff(); panel2.View(""); panel2.Collapse(); panel2.SetHeaderNewParams("Magic",clrLightGray,clrBlack,USHORT_MAX,8,-1); } //--- Button for creating a list of trades CDashboard *panel3=dashboard.InsertNewPanel(dashboard.ID()+3, 105, 20, 106, 21); if(panel3!=NULL) { panel3.SetName("TradesButton"); panel3.SetButtonCloseOff(); panel3.SetButtonMinimizeOff(); panel3.SetButtonPinOff(); panel3.View(""); panel3.Collapse(); panel3.SetHeaderNewParams("Get trade history",clrLightGray,clrBlack,USHORT_MAX,10,-1); } //--- Left panel for displaying the table of symbols/magic numbers CDashboard *panel4=dashboard.InsertNewPanel(dashboard.ID()+4, 2, 38, 101, dashboard.Height()-38-2); if(panel4!=NULL) { panel4.SetName("FieldL"); panel4.SetButtonCloseOff(); panel4.SetButtonMinimizeOff(); panel4.SetButtonPinOff(); panel4.View(""); panel4.SetPanelHeaderOff(true); panel4.SetFontParams("Calibri",8); } //--- Panel on the right for displaying statistics headers for the list of trades and the selected symbol/magic number CDashboard *panel5=dashboard.InsertNewPanel(dashboard.ID()+5, 104, 38, dashboard.Width()-104-2, 20); if(panel5!=NULL) { panel5.SetName("FieldH"); panel5.SetButtonCloseOff(); panel5.SetButtonMinimizeOff(); panel5.SetButtonPinOff(); panel5.View(""); panel5.SetPanelHeaderOff(true); panel5.SetFontParams("Calibri",8,FW_EXTRABOLD); } //--- Panel on the right for displaying statistics for the list of trades and the selected symbol/magic number CDashboard *panel6=dashboard.InsertNewPanel(dashboard.ID()+6, 104, 38+20, dashboard.Width()-104-2, dashboard.Height()-38-20-2); if(panel5!=NULL) { panel6.SetName("FieldR"); panel6.SetButtonCloseOff(); panel6.SetButtonMinimizeOff(); panel6.SetButtonPinOff(); panel6.View(""); panel6.SetPanelHeaderOff(true); panel6.SetFontParams("Calibri",8); } //--- All tables on the left and right panels are initially inactive LPanelTable=WRONG_VALUE; RPanelTable=WRONG_VALUE; //--- All is successful return(INIT_SUCCEEDED); }
Im Wesentlichen wird hier zunächst die Art der Bestandsbuchhaltung geprüft, und wenn es sich um Netting handelt, dann funktioniert das Kennzeichen nicht mehr, da es unmöglich ist, auf einfache Weise eine Handelstabelle (nur für Entry-Exit-Geschäfte) für eine solche Bestandsbuchhaltung zu erstellen.
Dann erstellen wir im Terminaldatenordner (TERMINAL_DATA_PATH + \MQL5\Files\) eine Datenbank im Ordner mit dem Programmnamen im Unterverzeichnis Database (\StatisticsBy\Database\). Nach erfolgreicher Erstellung der Datenbank wird das Dashboard erstellt und mit Inhalt gefüllt - Schaltflächen und Felder für die Anzeige von Tabellen:
Interessanterweise verwenden wir anstelle von Schaltflächen untergeordnete Dashboards, die in zusammengeklappter Form an das Hauptfenster angehängt sind - nur der Kopf des Dashboards ist sichtbar. Es hat seine eigenen Handler für die Interaktion mit dem Mauszeiger, und so haben wir Schaltflächen aus normalen Dashboards erstellt, die mit dem Nutzer interagieren und Mausinteraktionsereignisse an das Hauptprogramm senden.
Wir schließen die Datenbank und das Dashboard mit dem OnDeinit():
//+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Close the database DatabaseClose(DBHandle); if(GetLastError()==ERR_DATABASE_INVALID_HANDLE) Print("Error. An invalid database handle was passed to the DatabaseClose() function"); //--- If the panel object exists, delete it if(dashboard!=NULL) { delete dashboard; ChartRedraw(); } }
Wir lassen OnCalculate() leer (der Indikator berechnet nichts):
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- //--- return value of prev_calculated for the next call return(rates_total); }
Die gesamte Arbeit an der Dashboard-Interaktion mit dem Nutzer wird in der Ereignisbehandlung des Indikators ausgeführt.
Schauen wir uns die Ereignishandlung OnChartEvent() in seiner Gesamtheit an. Sein Code wurde gründlich kommentiert. Wenn wir die Kommentare der Ereignisbehandlungsroutinen genau studieren, wird die gesamte Logik der Interaktion des Dashboards mit dem Nutzer deutlich:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Active dashboard ID int table_id=WRONG_VALUE; //--- Call the dashboard event handler, which in turn sends its events here dashboard.OnChartEvent(id,lparam,dparam,sparam); //--- If we received a user event from the program dashboard window if(id>CHARTEVENT_CUSTOM) { //--- Dashboard close button clicked if(id==1001) { //--- Here we can implement handling a click on the close button } //--- Clicking buttons for working with DB - the ID is always 1002, while clarification is done by the lparam value if(id==1002) { //--- get deal history from the server and trade history from the DB ("Get trade history" is clicked) if(lparam==3) { //--- If the deal history is not received, leave if(!GetHistoryDeals(DATE_FROM, DATE_TO)) return; //--- If there are no deals in history, inform of that and leave int deals_total=HistoryDealsTotal(); if(deals_total==0) { Print("No deals in history"); return; } //--- create DEALS table in the database if(!CreateTableDeals(DBHandle)) return; //--- enter deals to the created table if(!InsertDeals(DBHandle)) return; //--- Create TRADES table based on DEALS table if(!CreateTableTrades(DBHandle)) return; //--- Fill in the TRADES table using an SQL query based on DEALS table data if(!FillTRADEStableBasedOnDEALStable(DBHandle)) return; //--- Request a list of all trades from the DB if(!FillsListTradesFromDB(DBHandle, DB_NAME, ArrayTrades)) return; //--- Display the number of deals and trades in history on the dashboard dashboard.DrawText(" ",2,2,clrNONE,0,0); // erase previous displayed data dashboard.DrawText("Total deals in history: "+(string)deals_total+", trades: "+(string)ArrayTrades.Size(),216,3); //--- Get the pointer to the header panel CDashboard *panel_h=dashboard.GetPanel("FieldH"); if(panel_h==NULL) return; //--- Check the presence and get or create the table object for displaying the trade table header CTableData *table_h=NULL; if(!panel_h.TableIsExist(TABLE_TRADES) && !panel_h.CreateNewTable(TABLE_TRADES)) return; //--- Get the pointer to the trade header table object table_h=panel_h.GetTable(TABLE_TRADES); if(table_h==NULL) return; //--- Clear the table header panel and display the header table on it panel_h.Clear(); panel_h.DrawGrid(TABLE_TRADES,2,2,1,11,CELL_H,CELL_W_TRADES,C'200,200,200',false); //--- Fill in the trade header table FillsHeaderTradeTable(panel_h,table_h); //--- Get the pointer to the right panel CDashboard *panel_r=dashboard.GetPanel("FieldR"); if(panel_r==NULL) return; //--- Check for availability and get or create a table object for displaying trades if(!panel_r.TableIsExist(TABLE_TRADES) && !panel_r.CreateNewTable(TABLE_TRADES)) return; //--- Get the pointer to the trade table object CTableData *table_r=panel_r.GetTable(TABLE_TRADES); if(table_r==NULL) return; //--- Clear the panel and display the trade table on it panel_r.Clear(); panel_r.DrawGrid(TABLE_TRADES,2,2,ArrayTrades.Size(),11,CELL_H,CELL_W_TRADES,C'220,220,220'); //--- Fill the table with trade data and specify TABLE_TRADES table is active to the right FillsTradeTable(panel_r,table_r); RPanelTable=TABLE_TRADES; } //--- If the display symbol button is pressed if(lparam==1) { //--- request the list of all symbols trading was carried out on from the database and fill in the symbol array if(!FillsListSymbolsFromDB(DBHandle, DB_NAME, ArraySymbols)) return; //--- Increase the symbol array by 1 to set "All symbols" (ALL) to it int size=(int)ArraySymbols.Size(); if(ArrayResize(ArraySymbols, size+1)==size+1) ArraySymbols[size]="ALL"; //--- Get the pointer to the left panel CDashboard *panel=dashboard.GetPanel("FieldL"); if(panel==NULL) return; //--- Check for availability and get or create a table object for displaying the list of symbols CTableData *table=NULL; if(!panel.TableIsExist(TABLE_SYMBOLS) && !panel.CreateNewTable(TABLE_SYMBOLS)) return; //--- Get the pointer to the table object table=panel.GetTable(TABLE_SYMBOLS); if(table==NULL) return; //--- Clear the panel and draw a symbol table on it panel.Clear(); panel.DrawGrid(TABLE_SYMBOLS,2,2,ArraySymbols.Size(),1,CELL_H,panel.Width()-5,C'220,220,220'); //--- Fill the table with symbol names and indicate that the TABLE_SYMBOLS table is active on the left panel FillsSymbolTable(panel,table); LPanelTable=TABLE_SYMBOLS; //--- get trading statistics by symbols if(!GetTradingStatsBySymbols(DBHandle, DB_NAME, ArraySymbolStats)) return; //--- Display the number of symbols used in trading dashboard.DrawText(" ",2,2,clrNONE,0,0); // Erase all dashboard contents dashboard.DrawText("Total number of symbols used in trade: "+(string)ArraySymbols.Size(),216,3); //--- Get the pointer to the header panel CDashboard *panel_h=dashboard.GetPanel("FieldH"); if(panel_h==NULL) return; //--- Check for presence and get or create a table object to display the symbol statistics table header CTableData *table_h=NULL; if(!panel_h.TableIsExist(TABLE_SYMBOLS) && !panel_h.CreateNewTable(TABLE_SYMBOLS)) return; //--- Get the pointer to the symbol statistics header table object table_h=panel_h.GetTable(TABLE_SYMBOLS); if(table_h==NULL) return; //--- Clear the table header panel and display the table on it RPanelTable=TABLE_SYMBOLS; panel_h.Clear(); panel_h.DrawGrid(TABLE_SYMBOLS,2,2,1,ArrayDataName.Size(),CELL_H,CELL_W_SYMBOLS,C'200,200,200',false); //--- Fill the symbol statistics header table FillsHeaderTradingStatsTable(panel_h,table_h); //--- Get the pointer to the right panel CDashboard *panel_r=dashboard.GetPanel("FieldR"); if(panel_r==NULL) return; //--- Check for availability and get or create a table object for displaying the symbol statistics if(!panel_r.TableIsExist(TABLE_SYMBOLS) && !panel_r.CreateNewTable(TABLE_SYMBOLS)) return; //--- Get the pointer to the symbol statistics table object CTableData *table_r=panel_r.GetTable(TABLE_SYMBOLS); if(table_r==NULL) return; //--- Clear the panel and display the symbol statistics table on it panel_r.Clear(); panel_r.DrawGrid(TABLE_SYMBOLS,2,2,ArraySymbolStats.Size(),ArrayDataName.Size(),CELL_H,CELL_W_SYMBOLS,C'220,220,220'); //--- Fill the table with symbol statistics data and indicate that the TABLE_SYMBOLS table is active on the right FillsTradingStatsBySymbolsTable(panel_r,table_r); RPanelTable=TABLE_SYMBOLS; } //--- If the button to display magic numbers is clicked if(lparam==2) { //--- Request the list of all magic numbers trading was performed on from the DB and fill in the array of magic numbers if(!FillsListMagicsFromDB(DBHandle, DB_NAME, ArrayMagics)) return; //--- Increase the array of magic numbers by 1 to set "All magic numbers" to it (LONG_MAX value notifies of that) int size=(int)ArrayMagics.Size(); if(ArrayResize(ArrayMagics, size+1)==size+1) ArrayMagics[size]=LONG_MAX; //--- Get the pointer to the left panel CDashboard *panel=dashboard.GetPanel("FieldL"); if(panel==NULL) return; //--- Check for availability and get or create a table object for displaying magic numbers CTableData *table=NULL; if(!panel.TableIsExist(TABLE_MAGICS) && !panel.CreateNewTable(TABLE_MAGICS)) return; //--- Get the pointer to the table object table=panel.GetTable(TABLE_MAGICS); if(table==NULL) return; //--- Clear the panel and draw a table of magic numbers on it panel.Clear(); panel.DrawGrid(TABLE_MAGICS,2,2,ArrayMagics.Size(),1,CELL_H,panel.Width()-5,C'220,220,220'); //--- Fill the table with magic number values and indicate that TABLE_MAGICS table is active on the left panel FillsMagicTable(panel,table); LPanelTable=TABLE_MAGICS; //--- Get trading statistics in the context of magic numbers if(!GetTradingStatsByMagics(DBHandle, DB_NAME, ArrayMagicStats)) return; //--- Display the number of magic numbers used in trading dashboard.DrawText(" ",2,2,clrNONE,0,0); dashboard.DrawText("Total number of magics used in trade: "+(string)ArrayMagics.Size(),216,3); //--- Get the pointer to the header panel CDashboard *panel_h=dashboard.GetPanel("FieldH"); if(panel_h==NULL) return; //--- Check for presence and get or create a table object to display the magic number statistics table header CTableData *table_h=NULL; if(!panel_h.TableIsExist(TABLE_MAGICS) && !panel_h.CreateNewTable(TABLE_MAGICS)) return; //--- Get the pointer to the magic number statistics header table object table_h=panel_h.GetTable(TABLE_MAGICS); if(table_h==NULL) return; //--- Clear the table header panel and display the table on it panel_h.Clear(); panel_h.DrawGrid(TABLE_MAGICS,2,2,1,ArrayDataName.Size(),CELL_H,CELL_W_MAGICS,C'200,200,200',false); //--- Fill the symbol statistics header table FillsHeaderTradingStatsTable(panel_h,table_h); //--- Get the pointer to the right panel CDashboard *panel_r=dashboard.GetPanel("FieldR"); if(panel_r==NULL) return; //--- Check for availability and get or create a table object for displaying the magic number statistics if(!panel_r.TableIsExist(TABLE_MAGICS) && !panel_r.CreateNewTable(TABLE_MAGICS)) return; //--- Get the pointer to the magic number statistics table object CTableData *table_r=panel_r.GetTable(TABLE_MAGICS); if(table_r==NULL) return; //--- Clear the panel and display the magic number statistics table on it panel_r.Clear(); panel_r.DrawGrid(TABLE_MAGICS,2,2,ArrayMagicStats.Size(),ArrayDataName.Size(),CELL_H,CELL_W_MAGICS,C'220,220,220'); //--- Fill the table with magic number statistics and indicate that TABLE_MAGICS table is active FillsTradingStatsByMagicsTable(panel_r,table_r); RPanelTable=TABLE_MAGICS; } } } //--- If we received a mouse wheel scroll event if(id==CHARTEVENT_MOUSE_WHEEL) { static int index_l_p=WRONG_VALUE; // Previous index of the row under the cursor in the table on the left panel static int index_r_p=WRONG_VALUE; // Previous index of the row under the cursor in the table on the right panel //--- consider the state of mouse buttons and wheel for this event int flg_keys = (int)(lparam>>32); // the flag of states of the Ctrl and Shift keys, and mouse buttons int x_cursor = (int)(short)lparam; // X coordinate where the mouse wheel event occurred int y_cursor = (int)(short)(lparam>>16); // Y coordinate where the mouse wheel event occurred int delta = (int)dparam; // total value of mouse scroll, triggers when +120 or -120 is reached //--- Get the pointer to the left panel and call the mouse wheel scroll handler for it int index_l=WRONG_VALUE; CDashboard *panel_l=dashboard.GetPanel("FieldL"); if(panel_l!=NULL) index_l=TableMouseWhellHandlerL(x_cursor,y_cursor,((flg_keys&0x0004)!=0),delta,panel_l,LPanelTable); //--- Get the pointer to the right panel and call the mouse wheel scroll handler for it int index_r=WRONG_VALUE; CDashboard *panel_r=dashboard.GetPanel("FieldR"); if(panel_r!=NULL) index_r=TableMouseWhellHandlerR(x_cursor,y_cursor,((flg_keys&0x0004)!=0),delta,panel_r,RPanelTable); //--- If necessary, we can handle the table row, over which the cursor is located. //--- The line number is set in index_l for the left panel and in index_r for the right one //--- Update the chart after all changes that occurred in the mouse wheel scroll handlers if(index_l_p!=index_l) { index_l_p=index_l; ChartRedraw(); } if(index_r_p!=index_r) { index_r_p=index_r; ChartRedraw(); } } //--- In case the mouse movement event received if(id==CHARTEVENT_MOUSE_MOVE) { static int index_l_p=WRONG_VALUE; // Previous index of the row under the cursor in the table on the left panel static int index_r_p=WRONG_VALUE; // Previous index of the row under the cursor in the table on the right panel int x_cursor = (int)lparam; // Mouse cursor X coordinate int y_cursor = (int)dparam; // Mouse cursor Y coordinate //--- Get the pointer to the left panel and call the mouse movement handler for it int index_l=WRONG_VALUE; CDashboard *panel_l=dashboard.GetPanel("FieldL"); if(panel_l!=NULL) index_l=TableMouseMoveHandlerL(x_cursor,y_cursor,panel_l,LPanelTable); //--- Get the pointer to the right panel and call the mouse movement handler for it int index_r=WRONG_VALUE; CDashboard *panel_r=dashboard.GetPanel("FieldR"); if(panel_r!=NULL) index_r=TableMouseMoveHandlerR(x_cursor,y_cursor,panel_r,RPanelTable); //--- If necessary, we can handle the table row, over which the cursor is located. //--- The line number is set in index_l for the left panel and in index_r for the right one //--- Update the chart after all changes that occurred in the mouse movement handlers if(index_l_p!=index_l) { index_l_p=index_l; ChartRedraw(); } if(index_r_p!=index_r) { index_r_p=index_r; ChartRedraw(); } } //--- In case the mouse click event received if(id==CHARTEVENT_CLICK) { int x_cursor = (int)lparam; // Mouse cursor X coordinate int y_cursor = (int)dparam; // Mouse cursor Y coordinate //--- Get the pointer to the left panel and call the mouse click handler int index_l=WRONG_VALUE; CDashboard *panel_l=dashboard.GetPanel("FieldL"); if(panel_l!=NULL) index_l=TableMouseClickHandler(x_cursor,y_cursor,panel_l,LPanelTable); //--- Get the pointer to the right panel and call the mouse click handler int index_r=WRONG_VALUE; CDashboard *panel_r=dashboard.GetPanel("FieldR"); if(panel_r!=NULL) index_r=TableMouseClickHandler(x_cursor,y_cursor,panel_r,RPanelTable); //--- Handle the clicked table row //--- If there was a click on a symbol from the list in the left panel if(LPanelTable==TABLE_SYMBOLS && index_l>WRONG_VALUE) { //--- Get the symbol table from the left panel CTableData *table=panel_l.GetTable(TABLE_SYMBOLS); if(table==NULL) return; //--- Get the only table cell with the index_l row CTableCell *cell=table.GetCell(index_l,0); if(cell==NULL) return; //--- If the last item (ALL) is clicked if(index_l==ArraySymbols.Size()-1) { //--- get and display account trading statistics and specify that TABLE_STATS panel is active on the right if(!GetTradingStatsByAccount(DBHandle, DB_NAME, ArrayAccountStats)) return; if(ViewStatistic(TABLE_ACCOUNT,(string)AccountInfoInteger(ACCOUNT_LOGIN))) RPanelTable=TABLE_STATS; } //--- Click on the symbol name - display statistics for the symbol and indicate that TABLE_STATS panel is active on the right else { if(ViewStatistic(TABLE_SYMBOLS,cell.Text())) RPanelTable=TABLE_STATS; } } //--- If there was a click on a magic number from the list in the left panel if(LPanelTable==TABLE_MAGICS && index_l>WRONG_VALUE) { //--- Get the magic number table from the left panel CTableData *table=panel_l.GetTable(TABLE_MAGICS); if(table==NULL) return; //--- Get the only table cell with the index_l row CTableCell *cell=table.GetCell(index_l,0); if(cell==NULL) return; //--- If the last item (ALL) is clicked if(index_l==ArrayMagics.Size()-1) { //--- get and display account trading statistics and specify that TABLE_STATS panel is active on the right if(!GetTradingStatsByAccount(DBHandle, DB_NAME, ArrayAccountStats)) return; if(ViewStatistic(TABLE_ACCOUNT,(string)AccountInfoInteger(ACCOUNT_LOGIN))) RPanelTable=TABLE_STATS; } //--- Click on the magic number value - display the magic number statistics and indicate that TABLE_STATS panel is active on the right else { if(ViewStatistic(TABLE_MAGICS,cell.Text())) RPanelTable=TABLE_STATS; } } //--- If there was a click on a symbol from the list in the right panel if(RPanelTable==TABLE_SYMBOLS && index_r>WRONG_VALUE) { //--- Get the table of symbol statistics from the right panel CTableData *table=panel_r.GetTable(TABLE_SYMBOLS); if(table==NULL) return; //--- Get the zero cell of the table with the index_r row index CTableCell *cell=table.GetCell(index_r,0); if(cell==NULL) return; //--- Display the summary statistics for the symbol and indicate that TABLE_STATS panel on the right is active if(ViewStatistic(TABLE_SYMBOLS,cell.Text())) RPanelTable=TABLE_STATS; } //--- If there was a click on a magic number from the list in the right panel if(RPanelTable==TABLE_MAGICS && index_r>WRONG_VALUE) { //--- Get the table of magic number statistics from the right panel CTableData *table=panel_r.GetTable(TABLE_MAGICS); if(table==NULL) return; //--- Get the zero cell of the table with the index_r row index CTableCell *cell=table.GetCell(index_r,0); if(cell==NULL) return; //--- Display the summary statistics for the magic number and indicate that TABLE_STATS panel on the right is active if(ViewStatistic(TABLE_MAGICS,cell.Text())) RPanelTable=TABLE_STATS; } } }
Betrachten wir nun den Rest der Funktionen, die von der Ereignisbehandlung aufgerufen werden. Der Code für jede Funktion ist ausführlich kommentiert und sollte keine Missverständnisse hervorrufen.
Die Funktion, die den Index einer Tabellenzeile auf der Grundlage der Cursor-Koordinaten zurückgibt:
//+------------------------------------------------------------------+ //| Return the index of the table row by the cursor coordinates | //+------------------------------------------------------------------+ int TableSelectRowByMouse(const int x_cursor, const int y_cursor, const int cell_h, CDashboard *panel, CTableData *table) { //--- Check the pointers to the panel and table if(panel==NULL || table==NULL) return WRONG_VALUE; int index=WRONG_VALUE; // Index of the table row located under the cursor //--- In the loop by table rows int total=table.RowsTotal(); for(int i=0;i<total;i++) { //--- get the next zero cell of the table in the row with the loop index CTableCell *cell=table.GetCell(i,0); if(cell==NULL) continue; //--- Calculate the upper and lower coordinates of the table row location based on the cell Y coordinate int y1=panel.CoordY()+cell.Y()+1; int y2=y1+cell_h; //--- If the cursor is vertically inside the calculated coordinates of the table row if(y_cursor>y1 && y_cursor<y2) { //--- Write the row index, draw a rectangular area across the entire width of the table (selecting the row under the cursor) and return the row index index=cell.Row(); panel.DrawRectangleFill(2,cell.Y()+1,panel.Width()-4,y2-y1-1,C'220,220,220',240); return index; } } //--- Nothing found return WRONG_VALUE; }
Die Funktion erfüllt zwei Aufgaben auf einmal: (1) Sie gibt die Nummer der Tabellenzeile zurück, über der sich der Mauszeiger befindet, und (2) sie hebt diese Zeile mit einer Hintergrundfarbe hervor.
Die Funktion, die das Scrollen des Mausrads innerhalb der linken Panel-Tabelle abfängt:
//+------------------------------------------------------------------+ //| Mouse wheel scroll handler inside the left panel table | //+------------------------------------------------------------------+ int TableMouseWhellHandlerL(const int x_cursor,const int y_cursor,const bool shift_flag,const int delta,CDashboard *panel,const int table_id) { //--- Check the pointer to the left panel if(panel==NULL) return WRONG_VALUE; //--- Check the cursor location inside the panel if(x_cursor<panel.CoordX()+2 || x_cursor>panel.CoordX()+panel.Width() || y_cursor<panel.CoordY()+4 || y_cursor>panel.CoordY()+panel.Height()) return WRONG_VALUE; //--- Check if the table is present on the panel if(!panel.TableIsExist(table_id)) return WRONG_VALUE; //--- Get the pointer to the active table on the panel CTableData *table=panel.GetTable(table_id); if(table==NULL) return WRONG_VALUE; //--- Calculate the table offset by half the height of the table row int shift=CELL_H/2*(delta<0 ? -1 : 1); //--- Calculate the coordinates within which the table is shifted int y=table.Y1()+shift; if(y>2) y=2; if(y+table.Height()<panel.Height()-2) y=panel.Height()-2-table.Height(); if(table.Height()<panel.Height()) return WRONG_VALUE; //--- Clear the panel and display the active table on it int total=int(table_id==TABLE_SYMBOLS ? ArraySymbols.Size() : ArrayMagics.Size()); panel.Clear(); panel.DrawGrid(table_id,2,y,total,1,CELL_H,panel.Width()-5,C'220,220,220'); //--- Fill the table with values if(table_id==TABLE_SYMBOLS) FillsSymbolTable(panel,table); else FillsMagicTable(panel,table); //--- Get the row index of the table, over which the cursor is located int index=TableSelectRowByMouse(x_cursor,y_cursor,CELL_H,panel,table); return index; }
Befindet sich der Cursor beim Scrollen mit dem Mausrad über einer Tabelle im Panel, sollte auch die Tabelle gescrollt werden, wenn ihre Größe die Größe des Panels übersteigt. Genau das tut diese Ereignisbehandlung - sie verschiebt die Tabelle entlang der angegebenen Anfangskoordinaten. Außerdem wird die Zeile unter dem Cursor mit der Funktion TableSelectRowByMouse() hervorgehoben, und der Index der Zeile unter dem Cursor, der von dieser Funktion gesendet wird, wird zurückgegeben. Die linke Tafel zeigt kleine Listen mit Symbolen und magischen Zahlen, sodass hier ein vereinfachtes Blättern möglich ist - wir verschieben die Tabelle sofort zu den berechneten Koordinaten. Was die rechte Tafel betrifft, so ist der Fall etwas komplizierter.
Die Funktion für den Mauszeiger-Offset innerhalb der linken Panel-Tabelle:
//+------------------------------------------------------------------+ //| Handler for the mouse cursor offset inside the left panel table | //+------------------------------------------------------------------+ int TableMouseMoveHandlerL(const int x_cursor,const int y_cursor,CDashboard *panel,const int table_id) { //--- Check the pointer to the left panel if(panel==NULL) return WRONG_VALUE; //--- Check if the table is present on the panel if(!panel.TableIsExist(table_id)) return WRONG_VALUE; //--- Get the pointer to the active table on the panel CTableData *table=panel.GetTable(table_id); if(table==NULL) return WRONG_VALUE; //--- Check the cursor location inside the panel //--- If the cursor is outside the panel, draw the active table and return -1 (to remove the selection of the row, over which the cursor was located) int total=int(table_id==TABLE_SYMBOLS ? ArraySymbols.Size() : ArrayMagics.Size()); if(x_cursor<panel.CoordX()+2 || x_cursor>panel.CoordX()+panel.Width() || y_cursor<panel.CoordY()+4 || y_cursor>panel.CoordY()+panel.Height()) { panel.Clear(); panel.DrawGrid(table_id,2,table.Y1(),total,1,CELL_H,panel.Width()-5,C'220,220,220'); return WRONG_VALUE; } //--- Clear the panel and display the active table on it panel.Clear(); panel.DrawGrid(table_id,2,table.Y1(),total,1,CELL_H,panel.Width()-5,C'220,220,220'); //--- Fill the table with values if(table_id==TABLE_SYMBOLS) FillsSymbolTable(panel,table); else FillsMagicTable(panel,table); //--- Get and return the row index of the table, over which the cursor is located int index=TableSelectRowByMouse(x_cursor,y_cursor,CELL_H,panel,table); return index; }
Wie bei der vorherigen Funktion wird auch hier die Zeile unter dem Cursor gesucht und ausgewählt. Die Tabelle lässt sich jedoch nicht verschieben.
Die Funktion, wenn das Mausrad innerhalb der rechten Panel-Tabelle gedreht wird:
//+------------------------------------------------------------------+ //| Mouse wheel scroll handler inside the right panel table | //+------------------------------------------------------------------+ int TableMouseWhellHandlerR(const int x_cursor,const int y_cursor,const bool shift_flag,const int delta,CDashboard *panel,const int table_id) { //--- Check the pointer to the right panel if(panel==NULL) return WRONG_VALUE; //--- Check the cursor location inside the panel if(x_cursor<panel.CoordX()+2 || x_cursor>panel.CoordX()+panel.Width() || y_cursor<panel.CoordY()+4 || y_cursor>panel.CoordY()+panel.Height()) return WRONG_VALUE; //--- Check if the table is present on the panel if(!panel.TableIsExist(table_id)) return WRONG_VALUE; //--- Get the pointer to the active table on the panel CTableData *table=panel.GetTable(table_id); if(table==NULL) return WRONG_VALUE; //--- Calculate the table vertical offset by half the height of the table row int shift_y=CELL_H/2*(delta<0 ? -1 : 1); //--- Calculate the table horizontal offset by the height of the table row int shift_x=(shift_flag ? CELL_H*(delta<0 ? -1 : 1) : 0); //--- Calculate the coordinates within which the table is shifted by Y int y=table.Y1()+shift_y; if(y>2) y=2; if(y+table.Height()<panel.Height()-2) y=panel.Height()-2-table.Height(); //--- Calculate the coordinates within which the table is shifted by X int x=0; if(shift_flag) { x=table.X1()+shift_x; if(x>2) x=2; if(x+table.Width()<panel.Width()-2) x=panel.Width()-2-table.Width(); } //--- If the entire table fits into the panel dimensions, there is no need to scroll anything, return -1 if(table.Height()<panel.Height() && table.Width()<panel.Width()) return WRONG_VALUE; //--- Define the table size int total=0; // number of rows int columns=0; // number of columns int cell_w=0; // table cell (column) width int cell_h=CELL_H; // table cell (row) height switch(table_id) { case TABLE_TRADES : total=(int)ArrayTrades.Size(); columns=11; cell_w=CELL_W_TRADES; break; case TABLE_SYMBOLS: total=(int)ArraySymbolStats.Size(); columns=(int)ArrayDataName.Size(); cell_w=CELL_W_SYMBOLS; break; case TABLE_MAGICS : total=(int)ArrayMagicStats.Size(); columns=(int)ArrayDataName.Size(); cell_w=CELL_W_MAGICS; break; case TABLE_STATS : total=TABLE_STAT_ROWS; columns=TABLE_STAT_COLS; cell_w=(panel.Width()-4)/TABLE_STAT_COLS; cell_h=(panel.Height()-4)/total; break; default : break; } //--- Clear the panel and display the active table on it panel.Clear(); panel.DrawGrid(table_id, (shift_flag ? x : table.X1()), (!shift_flag && table.Height()>panel.Height() ? y : table.Y1()), total,columns,cell_h,cell_w, (table_id!=TABLE_STATS ? C'220,220,220' : C'230,230,230'), (table_id!=TABLE_STATS)); //--- Fill the table with values switch(table_id) { case TABLE_TRADES : FillsTradeTable(panel,table); break; case TABLE_SYMBOLS: FillsTradingStatsBySymbolsTable(panel,table); break; case TABLE_MAGICS : FillsTradingStatsByMagicsTable(panel,table); break; default : break; } //--- Get the pointer to the header panel CDashboard *panel_h=dashboard.GetPanel("FieldH"); if(panel_h==NULL) return WRONG_VALUE; //--- Get the pointer to the header table CTableData *table_h=panel_h.GetTable(table_id); if(table_h==NULL) return WRONG_VALUE; //--- Clear the table header panel and display the table on it panel_h.Clear(); panel_h.DrawGrid(table_id,(shift_flag ? x : table_h.X1()),2,1,columns,cell_h,cell_w,C'200,200,200',false); //--- Fill the header table switch(table_id) { case TABLE_TRADES : FillsHeaderTradeTable(panel_h,table_h); break; case TABLE_SYMBOLS: case TABLE_MAGICS : FillsHeaderTradingStatsTable(panel_h,table_h); break; default : break; } //--- For the summary statistics table, there is no need to search for the row number under the cursor if(table.ID()==TABLE_STATS) return WRONG_VALUE; //--- Get the row index of the table, over which the cursor is located int index=TableSelectRowByMouse(x_cursor,y_cursor,cell_h,panel,table); return index; }
Hier werden drei Tabellen und die Kopfzeilen dieser Tabellen in einer Funktion behandelt. Es hängt alles vom Tabellentyp ab, der der Funktion übergeben wird. Beim Scrollen großer Tabellen müssen diese nicht nur in der Höhe, sondern auch in der Breite gescrollt werden. Das Flag, das für den Bildlauf in der Breite verantwortlich ist, ist shift_flag - das ist ein Flag für das Gedrückthalten der Umschalttaste beim Drehen des Mausrads. Wenn Sie die Tabelle selbst verschieben, wird auch die Kopfzeile in einem anderen Feld mitgeschoben.
Die Funktion für den Mauszeiger-Offset innerhalb der rechten Panel-Tabelle:
//+------------------------------------------------------------------+ //| Handler for mouse cursor offset inside the right panel table | //+------------------------------------------------------------------+ int TableMouseMoveHandlerR(const int x_cursor,const int y_cursor,CDashboard *panel,const int table_id) { //--- Check the pointer to the left panel if(panel==NULL) return WRONG_VALUE; //--- Check if the table is present on the panel if(!panel.TableIsExist(table_id)) return WRONG_VALUE; //--- Get the pointer to the active table on the panel CTableData *table=panel.GetTable(table_id); if(table==NULL) return WRONG_VALUE; //--- Define the table size int total=0; // number of rows int columns=0; // number of columns int cell_w=0; // table cell (column) width int cell_h=CELL_H; // table cell (row) height switch(table_id) { case TABLE_TRADES : total=(int)ArrayTrades.Size(); columns=11; cell_w=CELL_W_TRADES; break; case TABLE_SYMBOLS: total=(int)ArraySymbolStats.Size(); columns=(int)ArrayDataName.Size(); cell_w=CELL_W_SYMBOLS; break; case TABLE_MAGICS : total=(int)ArrayMagicStats.Size(); columns=(int)ArrayDataName.Size(); cell_w=CELL_W_MAGICS; break; case TABLE_STATS : total=TABLE_STAT_ROWS; columns=TABLE_STAT_COLS; cell_w=(panel.Width()-4)/TABLE_STAT_COLS; cell_h=(panel.Height()-4)/total; break; default : break; } //--- Check the cursor location inside the panel //--- If the cursor is outside the panel, draw the active table and return -1 (to remove the selection of the row, over which the cursor was located) if(x_cursor<panel.CoordX()+2 || x_cursor>panel.CoordX()+panel.Width() || y_cursor<panel.CoordY()+4 || y_cursor>panel.CoordY()+panel.Height()) { panel.Clear(); panel.DrawGrid(table_id,table.X1(),table.Y1(),total,columns,cell_h,cell_w,(table_id!=TABLE_STATS ? C'220,220,220' : C'230,230,230'),(table_id!=TABLE_STATS)); return WRONG_VALUE; } //--- Clear the panel and display the active table on it panel.Clear(); panel.DrawGrid(table_id,table.X1(),table.Y1(),total,columns,cell_h,cell_w,(table_id!=TABLE_STATS ? C'220,220,220' : C'230,230,230'),(table_id!=TABLE_STATS)); //--- Fill the table with values switch(table_id) { case TABLE_TRADES : FillsTradeTable(panel,table); break; case TABLE_SYMBOLS: FillsTradingStatsBySymbolsTable(panel,table); break; case TABLE_MAGICS : FillsTradingStatsByMagicsTable(panel,table); break; default : break; } //--- For the summary statistics table, there is no need to search for the row number under the cursor if(table.ID()==TABLE_STATS) return WRONG_VALUE; //--- Get the row index of the table, over which the cursor is located int index=TableSelectRowByMouse(x_cursor,y_cursor,cell_h,panel,table); return index; }
Im Allgemeinen müssen wir hier (und in den oben besprochenen Funktionen) nur die Zeilennummer der Tabelle finden, über der sich der Cursor befindet. Alles andere ist nur die Bedienung der „visuellen Dinge“, um die Zeile unter dem Cursor hervorzuheben, was letztlich zu einem erhöhten Verbrauch von CPU-Ressourcen führt. Schließlich müssen wir ständig den gesamten sichtbaren Teil der Tabelle neu zeichnen, und je größer das Armaturenbrett und die darauf befindliche Tabelle sind, desto mehr Platz müssen wir einzeichnen. Natürlich ist der physisch gezeichnete Raum durch die Abmessungen des Armaturenbretts begrenzt, aber er ist dennoch nicht optimal. Wenn wir solche Zeilenauswahlen in der Dashboardel-Klasse vornehmen würden, wäre alles anders - wir würden nur die an den Cursor angrenzenden Tabellenzeilen neu zeichnen, wobei wir uns die Hintergrundfarbe vor und nach dem Hervorheben merken und sie dann wiederherstellen würden. Aber hier machen wir der Einfachheit halber alles direkt in den Programmfunktionen.
Die Funktion für einen Mausklick innerhalb der Dashboard-Tabelle:
//+------------------------------------------------------------------+ //| Handler for mouse click inside the dashboard table | //+------------------------------------------------------------------+ int TableMouseClickHandler(const int x_cursor,const int y_cursor,CDashboard *panel,const int table_id) { //--- Check the pointer to the left panel if(panel==NULL) return WRONG_VALUE; //--- Check the cursor location inside the panel if(x_cursor<panel.CoordX()+2 || x_cursor>panel.CoordX()+panel.Width() || y_cursor<panel.CoordY()+4 || y_cursor>panel.CoordY()+panel.Height()) return WRONG_VALUE; //--- Check if the table is present on the panel if(!panel.TableIsExist(table_id)) return WRONG_VALUE; //--- Get the pointer to the active table on the panel CTableData *table=panel.GetTable(table_id); if(table==NULL) return WRONG_VALUE; //--- For the summary statistics table, there is no need to search for the row number under the cursor if(table.ID()==TABLE_STATS) return WRONG_VALUE; //--- Get the index of the clicked table row int index=TableSelectRowByMouse(x_cursor,y_cursor,CELL_H,panel,table); return index; }
Wenn wir auf eine Tabellenzeile klicken, müssen wir den Zeilenindex finden und zurückgeben, in dem der Klick stattgefunden hat, um ihn weiter zu verarbeiten. Dies wurde bereits in der Ereignisbehandlung der Nutzer OnChartEvent() gezeigt.
Die Funktion, die die Tabelle mit Symbolnamen füllt:
//+------------------------------------------------------------------+ //| Fill the table with symbol names | //+------------------------------------------------------------------+ void FillsSymbolTable(CDashboard *panel,CTableData *table) { //--- Check the pointers to the panel and table if(panel==NULL || table==NULL) return; //--- Calculate the index of the row, from which we need to start filling the table CTableCell *cell=table.GetCell(0,0); if(cell==NULL) return; int y=panel.CoordY()+cell.Y()-2; int diff=panel.CoordY()-y; int index=diff/CELL_H; //--- Fill the table with values from the array starting with the 'index' row for(int i=index;i<(int)ArraySymbols.Size();i++) { CTableCell *cell=table.GetCell(i,0); if(cell==NULL) continue; //--- do not draw invisible areas of the table if(cell.X()>panel.CoordX()+panel.Width()) continue; if(cell.Y()>panel.CoordY()+panel.Height()) break; //--- display data from the array to the table cells cell.SetText(ArraySymbols[i]); panel.DrawText(cell.Text(),cell.X()+2,cell.Y()+1); } }
Hier werden nicht nur keine Bereiche der Tabelle gezeichnet, die über das Panel hinausgehen, sondern auch der Beginn der Schleife wird auf die erste Tabellenzeile begrenzt, die oben sichtbar ist. Wenn Sie in der Tabelle nach oben blättern, kann die erste Zeile weit über das Feld am oberen Rand hinausgehen. Um die Schleife nicht noch einmal zu drehen und nicht zu versuchen, die Tabellenzeilen außerhalb des Panels zu zeichnen, die ohnehin nicht gezeichnet werden, müssen wir den Index der ersten von oben sichtbaren Tabellenzeile berechnen und die Schleife von dort aus starten. Bei relativ großen Tabellen führt dieser Ansatz zu bemerkenswerten Ergebnissen, da das starke Verlangsamen beim Blättern durch eine Tabelle mit Hunderten von Zeilen beseitigt werden.
Die Funktion füllt der Tabelle mit magischen Zahlen:
//+------------------------------------------------------------------+ //| Fill the table with magic number values | //+------------------------------------------------------------------+ void FillsMagicTable(CDashboard *panel,CTableData *table) { //--- Check the pointers to the panel and table if(panel==NULL || table==NULL) return; //--- Calculate the index of the row, from which we need to start filling the table CTableCell *cell=table.GetCell(0,0); if(cell==NULL) return; int y=panel.CoordY()+cell.Y()-2; int diff=panel.CoordY()-y; int index=diff/CELL_H; //--- Fill the table with values from the array starting with the 'index' row for(int i=index;i<(int)ArrayMagics.Size();i++) { CTableCell *cell=table.GetCell(i,0); if(cell==NULL) continue; //--- do not draw invisible areas of the table if(cell.X()>panel.CoordX()+panel.Width()) continue; if(cell.Y()>panel.CoordY()+panel.Height()) break; //--- display data from the array to the table cells string text=(i<(int)ArrayMagics.Size()-1 ? (string)ArrayMagics[i] : "ALL"); cell.SetText(text); panel.DrawText(cell.Text(),cell.X()+2,cell.Y()+1); } }
Die Funktion zum Füllen des Kopfes der Handelstabelle:
//+------------------------------------------------------------------+ //| Fill the trade header table | //+------------------------------------------------------------------+ void FillsHeaderTradeTable(CDashboard *panel,CTableData *table) { //--- Check the pointers to the panel and table if(panel==NULL || table==NULL) return; //--- Fill the table with values int total=11; // 11 table columns CTableCell *cell=NULL; for(int i=0;i<total;i++) { //--- Get the i-th table cell from the zeroth (and only) table row cell=table.GetCell(0,i); if(cell==NULL) continue; //--- do not draw invisible areas of the table if(cell.X()>panel.CoordX()+panel.Width()) continue; if(cell.Y()>panel.CoordY()+panel.Height()) break; //--- Write the names of the headers depending on the loop index string cell_text=""; switch(i) { case 0 : cell_text="Time Entry In"; break; // login time case 1 : cell_text="Position ID"; break; // position ID case 2 : cell_text="Position Type"; break; // buy or sell case 3 : cell_text="Volume"; break; // volume case 4 : cell_text="Symbol"; break; // symbol case 5 : cell_text="Price Entry In"; break; // entry price case 6 : cell_text="Time Entry Out"; break; // exit time case 7 : cell_text="Price Entry Out"; break; // exit price case 8 : cell_text="Commission"; break; // entry and exit fees case 9 : cell_text="Swap"; break; // swap case 10 : cell_text="Profit"; break; // profit or loss default : break; } //--- display entries to cell tables cell.SetText(cell_text); panel.DrawText(cell.Text(),cell.X()+6,cell.Y()+2); } }
Die Funktion zum Füllen der Handelstabelle:
//+------------------------------------------------------------------+ //| Fill in the trade table | //+------------------------------------------------------------------+ void FillsTradeTable(CDashboard *panel,CTableData *table) { //--- Check the pointers to the panel and table if(panel==NULL || table==NULL) return; //--- Fill in the table with values from the array CTableCell *cell=NULL; int total=(int)ArrayTrades.Size(); if(total==0) { PrintFormat("%s: Error: Trades array is empty",__FUNCTION__); return; } //--- Calculate the index of the row, from which we need to start filling the table cell=table.GetCell(0,0); if(cell==NULL) return; int y=panel.CoordY()+cell.Y()-2; int diff=panel.CoordY()-y; int index=diff/CELL_H; //--- In a loop by the number of rows (size of the trades array), starting from the 'index' row for(int i=index;i<total;i++) { //--- in a loop by the number of columns (11 for this table) for(int j=0;j<11;j++) { //--- get the next table cell cell=table.GetCell(i,j); if(cell==NULL) continue; //--- do not draw invisible areas of the table if(cell.X()>panel.CoordX()+panel.Width()) continue; if(cell.Y()>panel.CoordY()+panel.Height()) break; //--- Get table data from the trades array string cell_text=""; int digits=(int)SymbolInfoInteger(ArrayTrades[i].symbol,SYMBOL_DIGITS); switch(j) { case 0 : cell_text=TimeToString(ArrayTrades[i].time_in); break; // login time case 1 : cell_text=IntegerToString(ArrayTrades[i].ticket); break; // position ID case 2 : cell_text=(ArrayTrades[i].type==0 ? "Buy" : "Sell"); break; // buy or sell case 3 : cell_text=DoubleToString(ArrayTrades[i].volume,2); break; // volume case 4 : cell_text=ArrayTrades[i].symbol; break; // symbol case 5 : cell_text=DoubleToString(ArrayTrades[i].price_in,digits); break; // entry price case 6 : cell_text=TimeToString(ArrayTrades[i].time_out); break; // exit time case 7 : cell_text=DoubleToString(ArrayTrades[i].price_out,digits); break; // exit price case 8 : cell_text=DoubleToString(ArrayTrades[i].commission,2); break; // entry and exit fees case 9 : cell_text=DoubleToString(ArrayTrades[i].swap,2); break; // swap case 10 : cell_text=DoubleToString(ArrayTrades[i].profit,2); break; // profit or loss default : break; } //--- display entries to cell tables cell.SetText(cell_text); panel.DrawText(cell.Text(),cell.X()+6,cell.Y()+1); } } }
Die beiden Funktionen zeichnen im Wesentlichen eine Tabelle. Eine Funktion zeichnet eine Tabellenüberschrift mit Spalten, die anhand des Spaltenindexes benannt sind, und die zweite Funktion zeichnet eine Tabelle unterhalb der Überschrift mit Werten, die den Spaltenüberschriften entsprechen. Im Allgemeinen arbeiten die Funktionen paarweise.
Die Funktion zum Ausfüllen der Handelsstatistik-Kopftabelle:
//+------------------------------------------------------------------+ //| Fill the trading statistics header table | //+------------------------------------------------------------------+ void FillsHeaderTradingStatsTable(CDashboard *panel,CTableData *table) { //--- Check the pointers to the panel and table if(panel==NULL || table==NULL) return; //--- Fill the table with values in a loop by the number of columns (by the size of the data in ArrayDataName) int total=(int)ArrayDataName.Size(); CTableCell *cell=NULL; for(int i=0;i<total;i++) { //--- get the next table cell cell=table.GetCell(0,i); if(cell==NULL) continue; //--- do not draw invisible areas of the table if(cell.X()>panel.CoordX()+panel.Width()) continue; if(cell.Y()>panel.CoordY()+panel.Height()) break; //--- Write the names of the headers depending on the loop index string cell_text=(i>0 ? ArrayDataName[i] : table.ID()==TABLE_SYMBOLS ? "Symbol" : "Magic"); //--- display entries to cell tables cell.SetText(cell_text); panel.DrawText(cell.Text(),cell.X()+6,cell.Y()+2); } }
Wenn eine statistische Tabelle durch Symbole oder magische Zahlen dargestellt wird, ist es notwendig, eine Kopfzeile für die statistische Tabelle zu zeichnen. Aber wir haben zwei Tabellen mit statistischen Daten: eine für Symbole, die zweite für magische Zahlen. Die Überschriften dieser Tabellen unterscheiden sich nur in der ersten Spalte, alle anderen sind identisch. Hier wird die Tabellen-ID geprüft, und je nach ihr wird die Überschrift der ersten Spalte entweder mit einem Symbol oder einer magischen Zahl signiert.
Die Funktion zum Ausfüllen der Tabelle der Symbolhandelsstatistiken:
//+------------------------------------------------------------------+ //| Fill in the symbol trading statistics table | //+------------------------------------------------------------------+ void FillsTradingStatsBySymbolsTable(CDashboard *panel,CTableData *table) { //--- Check the pointers to the panel and table if(panel==NULL || table==NULL) return; //--- Fill in the table with values from the array CTableCell *cell=NULL; int total=(int)ArraySymbolStats.Size(); if(total==0) { PrintFormat("%s: Error: The array of trading statistics by symbols is empty",__FUNCTION__); return; } //--- Calculate the index of the row, from which we need to start filling the table cell=table.GetCell(0,0); if(cell==NULL) return; int y=panel.CoordY()+cell.Y()-2; int diff=panel.CoordY()-y; int index=diff/CELL_H; //--- In a loop by the number of rows (size of the symbol statistics array), starting from the 'index' row for(int i=index;i<total;i++) { //--- in a loop by the number of statistics columns (array of the location of the statistics table columns from left to right) for(int j=0;j<(int)ArrayDataName.Size();j++) { //--- get the next table cell cell=table.GetCell(i,j); if(cell==NULL) continue; //--- do not draw invisible areas of the table if(cell.X()>panel.CoordX()+panel.Width()) continue; if(cell.Y()>panel.CoordY()+panel.Height()) break; //--- Get table data from the array of structures string cell_text=""; cell_text=GetDataStatsStr(TABLE_SYMBOLS, ArrayDataName[j],i); //--- display entries to cell tables cell.SetText(cell_text); panel.DrawText(cell.Text(),cell.X()+6,cell.Y()+1); } } }
Die Funktion zum Ausfüllen der magische Zahl Handelsstatistik-Tabelle:
//+------------------------------------------------------------------+ //| Fill in the magic number trading statistics table | //+------------------------------------------------------------------+ void FillsTradingStatsByMagicsTable(CDashboard *panel,CTableData *table) { //--- Check the pointers to the panel and table if(panel==NULL || table==NULL) return; //--- Fill in the table with values from the array CTableCell *cell=NULL; int total=(int)ArrayMagicStats.Size(); if(total==0) { PrintFormat("%s: Error: The array of trading statistics by magics is empty",__FUNCTION__); return; } //--- Calculate the index of the row, from which we need to start filling the table cell=table.GetCell(0,0); if(cell==NULL) return; int y=panel.CoordY()+cell.Y()-2; int diff=panel.CoordY()-y; int index=diff/CELL_H; //--- In the loop by the number of rows (size of the magic number statistics array), starting from the 'index' row for(int i=index;i<total;i++) { //--- in a loop by the number of statistics columns (array of the location of the statistics table columns from left to right) for(int j=0;j<(int)ArrayDataName.Size();j++) { //--- get the next table cell cell=table.GetCell(i,j); if(cell==NULL) continue; //--- do not draw invisible areas of the table if(cell.X()>panel.CoordX()+panel.Width()) continue; if(cell.Y()>panel.CoordY()+panel.Height()) break; //--- Get table data from the array of structures string cell_text=GetDataStatsStr(TABLE_MAGICS, ArrayDataName[j],i); //--- display entries to cell tables cell.SetText(cell_text); panel.DrawText(cell.Text(),cell.X()+6,cell.Y()+1); } } }
Zwei ähnliche Funktionen, die die Tabellen der Statistiken für Symbole und magische Zahlen ausfüllen.
Die Funktion, die Daten aus der Struktur nach Kopftyp zurückgibt:
//+------------------------------------------------------------------+ //| Return data from the structure by header type | //+------------------------------------------------------------------+ double GetDataStats(const int table_type, const string data_type, const int index) { //--- Depending on the data type in the table, return data from the fields of the data_type structure by 'index' array index switch(table_type) { case TABLE_SYMBOLS : return ( data_type==H_TRADES_S ? ArraySymbolStats[index].trades : data_type==H_GROSS_PROFIT_S ? ArraySymbolStats[index].gross_profit : data_type==H_GROSS_LOSS_S ? ArraySymbolStats[index].gross_loss : data_type==H_COMMISSIONS_S ? ArraySymbolStats[index].total_commission : data_type==H_SWAPS_S ? ArraySymbolStats[index].total_swap : data_type==H_PROFITS_S ? ArraySymbolStats[index].total_profit : data_type==H_NET_PROFIT_S ? ArraySymbolStats[index].net_profit : data_type==H_WINS_S ? ArraySymbolStats[index].win_trades : data_type==H_LOST_S ? ArraySymbolStats[index].loss_trades : data_type==H_LONG_S ? ArraySymbolStats[index].long_trades : data_type==H_SHORT_S ? ArraySymbolStats[index].short_trades : data_type==H_EXP_PAYOFF_S ? ArraySymbolStats[index].expected_payoff : data_type==H_WIN_PRC_S ? ArraySymbolStats[index].win_percent : data_type==H_LOSS_PRC_S ? ArraySymbolStats[index].loss_percent : data_type==H_AVG_PROFIT_S ? ArraySymbolStats[index].average_profit : data_type==H_AVG_LOSS_S ? ArraySymbolStats[index].average_loss : data_type==H_PRF_FACTOR_S ? ArraySymbolStats[index].profit_factor : 0 ); case TABLE_MAGICS : return ( data_type==H_TRADES_S ? ArrayMagicStats[index].trades : data_type==H_GROSS_PROFIT_S ? ArrayMagicStats[index].gross_profit : data_type==H_GROSS_LOSS_S ? ArrayMagicStats[index].gross_loss : data_type==H_COMMISSIONS_S ? ArrayMagicStats[index].total_commission : data_type==H_SWAPS_S ? ArrayMagicStats[index].total_swap : data_type==H_PROFITS_S ? ArrayMagicStats[index].total_profit : data_type==H_NET_PROFIT_S ? ArrayMagicStats[index].net_profit : data_type==H_WINS_S ? ArrayMagicStats[index].win_trades : data_type==H_LOST_S ? ArrayMagicStats[index].loss_trades : data_type==H_LONG_S ? ArrayMagicStats[index].long_trades : data_type==H_SHORT_S ? ArrayMagicStats[index].short_trades : data_type==H_EXP_PAYOFF_S ? ArrayMagicStats[index].expected_payoff : data_type==H_WIN_PRC_S ? ArrayMagicStats[index].win_percent : data_type==H_LOSS_PRC_S ? ArrayMagicStats[index].loss_percent : data_type==H_AVG_PROFIT_S ? ArrayMagicStats[index].average_profit : data_type==H_AVG_LOSS_S ? ArrayMagicStats[index].average_loss : data_type==H_PRF_FACTOR_S ? ArrayMagicStats[index].profit_factor : 0 ); case TABLE_ACCOUNT : return ( data_type==H_TRADES_S ? ArrayAccountStats[index].trades : data_type==H_GROSS_PROFIT_S ? ArrayAccountStats[index].gross_profit : data_type==H_GROSS_LOSS_S ? ArrayAccountStats[index].gross_loss : data_type==H_COMMISSIONS_S ? ArrayAccountStats[index].total_commission : data_type==H_SWAPS_S ? ArrayAccountStats[index].total_swap : data_type==H_PROFITS_S ? ArrayAccountStats[index].total_profit : data_type==H_NET_PROFIT_S ? ArrayAccountStats[index].net_profit : data_type==H_WINS_S ? ArrayAccountStats[index].win_trades : data_type==H_LOST_S ? ArrayAccountStats[index].loss_trades : data_type==H_LONG_S ? ArrayAccountStats[index].long_trades : data_type==H_SHORT_S ? ArrayAccountStats[index].short_trades : data_type==H_EXP_PAYOFF_S ? ArrayAccountStats[index].expected_payoff : data_type==H_WIN_PRC_S ? ArrayAccountStats[index].win_percent : data_type==H_LOSS_PRC_S ? ArrayAccountStats[index].loss_percent : data_type==H_AVG_PROFIT_S ? ArrayAccountStats[index].average_profit : data_type==H_AVG_LOSS_S ? ArrayAccountStats[index].average_loss : data_type==H_PRF_FACTOR_S ? ArrayAccountStats[index].profit_factor : 0 ); default : return 0; } }
Die Funktion empfängt den Typ der Statistiktabelle (Symbol/magische Zahl/Konto), den Datentyp (Spaltenkopfwert) und den Datenindex im Strukturarray. Je nach den an die Funktion übergebenen Daten wird ein Wert aus dem entsprechenden Array von Strukturen zurückgegeben. Der Einfachheit halber wird der Wert immer mit dem Typ double zurückgegeben, auch wenn er in der Struktur eine ganze Zahl ist. In der folgenden Funktion wird dieser Wert als String mit der gewünschten Anzahl von Dezimalstellen zurückgegeben.
Die Funktion gibt Daten aus der Struktur nach Kopftyp als String zurück:
//+------------------------------------------------------------------+ //| Return data from the structure by header type as a string | //+------------------------------------------------------------------+ string GetDataStatsStr(const int table_type, const string data_type, const int index) { //--- Depending on the data type, we determine the number of decimal places //--- (2 - for a real property and 0 - for an integer) int digits=(data_type==H_TRADES_S || data_type==H_WINS_S || data_type==H_LOST_S || data_type==H_LONG_S || data_type==H_SHORT_S ? 0 : 2); //--- If data type is "Header" if(data_type=="HEADER") { //--- return the name depending on the table type (symbol, magic number, account) switch(table_type) { case TABLE_SYMBOLS : return ArraySymbolStats[index].name; case TABLE_MAGICS : return (string)ArrayMagicStats[index].magic; case TABLE_ACCOUNT : return (string)ArrayAccountStats[index].account; default : return "Unknown:"+(string)table_type; } } //--- For all other data types, return their string value with the previously defined number of decimal places return(DoubleToString(GetDataStats(table_type, data_type, index),digits)); }
Funktionen werden verwendet, um die Werte der Zellen der Statistiktabellen an die Dashboard-Tabellen zu senden.
Die Funktion liefert den Symbolindex im Array der Symbolstatistik:
//+------------------------------------------------------------------+ //| Return the index of the symbol in the symbol statistics array | //+------------------------------------------------------------------+ int GetIndexSymbol(const string symbol) { int total=(int)ArraySymbolStats.Size(); for(int i=0;i<total;i++) { if(ArraySymbolStats[i].name==symbol) return i; } return WRONG_VALUE; }
Die Funktion liefert den Symbolindex im Statistik-Array durch magische Zahlen:
//+------------------------------------------------------------------------+ //| Return the magic number index in the statistics array by magic numbers | //+------------------------------------------------------------------------+ int GetIndexMagic(const long magic) { int total=(int)ArrayMagicStats.Size(); for(int i=0;i<total;i++) { if(ArrayMagicStats[i].magic==magic) return i; } return WRONG_VALUE; }
Beide Funktionen geben den Index des gesuchten Symbols bzw. der gesuchten magischen Zahl im entsprechenden Array der Symbole bzw. der magischen Zahlenstatistik zurück.
Die Funktion, die die endgültige Statistik für das ausgewählte Symbol, die magische Zahl oder das Konto anzeigt:
//+--------------------------------------------------------------------+ //| Display statistics for the selected symbol, magic number or account| //+--------------------------------------------------------------------+ bool ViewStatistic(const int table_type,const string cell_text) { //--- Get the pointers to the header panel and the right panel for displaying statistics CDashboard *panel_h=dashboard.GetPanel("FieldH"); CDashboard *panel_r=dashboard.GetPanel("FieldR"); if(panel_h==NULL || panel_r==NULL) return false; //--- Determine the source of statistical data (symbol/magic number/account) string source=(table_type==TABLE_SYMBOLS ? "symbol" : table_type==TABLE_MAGICS ? "magic" : "account"); int index=WRONG_VALUE; //--- Depending on the text in the selected table cell (cell_text) passed to the function, //--- get the index that contains the data in the corresponding statistics array switch(table_type) { case TABLE_SYMBOLS: index=GetIndexSymbol(cell_text); break; case TABLE_MAGICS : index=GetIndexMagic(StringToInteger(cell_text)); break; case TABLE_ACCOUNT: index=(ArrayAccountStats.Size()==1 ? 0 : -1); break; default : break; } //--- If the index could not be obtained, we assume that the corresponding statistics array is empty if(index==WRONG_VALUE) { PrintFormat("%s: Error. Empty array of %s statistics",__FUNCTION__,source); return false; } //--- Get and save the font properties set for the header bar int f_size,f_flags,f_angle; string f_name=panel_h.FontParams(f_size,f_flags,f_angle); //--- Clear the header bar and display the description of the selected data in Tahoma font size 8 panel_h.Clear(); panel_h.SetFontParams("Tahoma",8,f_flags,f_angle); panel_h.DrawText(StringFormat("Trade statistics by %s %s",source,cell_text),8,3,C'150,150,150'); //--- Return the header bar font to its previous saved properties panel_h.SetFontParams(f_name,f_size,f_flags,f_angle); //--- Check for availability and get or create a table object for displaying statistics on the right panel if(!panel_r.TableIsExist(TABLE_STATS) && !panel_r.CreateNewTable(TABLE_STATS)) return false; //--- Get the pointer to the created table CTableData *table_r=panel_r.GetTable(TABLE_STATS); if(table_r==NULL) return false; //--- Clear the right panel and draw a table on it panel_r.Clear(); panel_r.DrawGrid(TABLE_STATS,2,2,TABLE_STAT_ROWS,TABLE_STAT_COLS,(panel_r.Height()-4)/TABLE_STAT_ROWS,(panel_r.Width()-4)/TABLE_STAT_COLS,C'230,230,230',false); //--- Declare a structure for storing statistics data //--- (symbol/magic number/account) by previously obtained data index. //--- All fields of the SSymbolStats, SMagicStats and SAccountStats structures are the same, //--- except for the first field with the symbol name, magic value or account number. //--- Since the first field is not needed here, any of the three structure types is sufficient. //--- Fill the declared structure with data depending on the selected source SSymbolStats stats={}; switch(table_type) { case TABLE_SYMBOLS: stats.trades = ArraySymbolStats[index].trades; stats.gross_profit = ArraySymbolStats[index].gross_profit; stats.gross_loss = ArraySymbolStats[index].gross_loss; stats.total_commission= ArraySymbolStats[index].total_commission; stats.total_swap = ArraySymbolStats[index].total_swap; stats.total_profit = ArraySymbolStats[index].total_profit; stats.net_profit = ArraySymbolStats[index].net_profit; stats.win_trades = ArraySymbolStats[index].win_trades; stats.loss_trades = ArraySymbolStats[index].loss_trades; stats.long_trades = ArraySymbolStats[index].long_trades; stats.short_trades = ArraySymbolStats[index].short_trades; stats.expected_payoff = ArraySymbolStats[index].expected_payoff; stats.win_percent = ArraySymbolStats[index].win_percent; stats.loss_percent = ArraySymbolStats[index].loss_percent; stats.average_profit = ArraySymbolStats[index].average_profit; stats.average_loss = ArraySymbolStats[index].average_loss; stats.profit_factor = ArraySymbolStats[index].profit_factor; break; case TABLE_MAGICS : stats.trades = ArrayMagicStats[index].trades; stats.gross_profit = ArrayMagicStats[index].gross_profit; stats.gross_loss = ArrayMagicStats[index].gross_loss; stats.total_commission= ArrayMagicStats[index].total_commission; stats.total_swap = ArrayMagicStats[index].total_swap; stats.total_profit = ArrayMagicStats[index].total_profit; stats.net_profit = ArrayMagicStats[index].net_profit; stats.win_trades = ArrayMagicStats[index].win_trades; stats.loss_trades = ArrayMagicStats[index].loss_trades; stats.long_trades = ArrayMagicStats[index].long_trades; stats.short_trades = ArrayMagicStats[index].short_trades; stats.expected_payoff = ArrayMagicStats[index].expected_payoff; stats.win_percent = ArrayMagicStats[index].win_percent; stats.loss_percent = ArrayMagicStats[index].loss_percent; stats.average_profit = ArrayMagicStats[index].average_profit; stats.average_loss = ArrayMagicStats[index].average_loss; stats.profit_factor = ArrayMagicStats[index].profit_factor; break; case TABLE_ACCOUNT: stats.trades = ArrayAccountStats[index].trades; stats.gross_profit = ArrayAccountStats[index].gross_profit; stats.gross_loss = ArrayAccountStats[index].gross_loss; stats.total_commission= ArrayAccountStats[index].total_commission; stats.total_swap = ArrayAccountStats[index].total_swap; stats.total_profit = ArrayAccountStats[index].total_profit; stats.net_profit = ArrayAccountStats[index].net_profit; stats.win_trades = ArrayAccountStats[index].win_trades; stats.loss_trades = ArrayAccountStats[index].loss_trades; stats.long_trades = ArrayAccountStats[index].long_trades; stats.short_trades = ArrayAccountStats[index].short_trades; stats.expected_payoff = ArrayAccountStats[index].expected_payoff; stats.win_percent = ArrayAccountStats[index].win_percent; stats.loss_percent = ArrayAccountStats[index].loss_percent; stats.average_profit = ArrayAccountStats[index].average_profit; stats.average_loss = ArrayAccountStats[index].average_loss; stats.profit_factor = ArrayAccountStats[index].profit_factor; break; default: break; } //--- Get and save the font properties set for the right panel f_name=panel_r.FontParams(f_size,f_flags,f_angle); //--- Set a new font Tahoma size 8 for the right panel panel_r.SetFontParams("Tahoma",8,FW_BLACK,f_angle); //--- Variables for calculating the location of text in a table cell CTableCell *cellH=NULL, *cellV=NULL; int cols=table_r.ColumnsInRow(0); // number of columns in the statistics table int cw=table_r.Width()/cols; // width of one table column int y_shift=6; // shift text height int x_shift=21; // shift text width int tw=0; // text width string text=""; double value=0; //--- Left column (data -- value) //--- Get cells 0.0 and 0.1 of the table and send Trades and its value to their coordinates cellH=table_r.GetCell(0,0); cellV=table_r.GetCell(0,1); if(cellH==NULL || cellV==NULL) return false; text=(string)stats.trades; tw=panel_r.TextWidth(text); panel_r.DrawText(H_TRADES+":",cellH.X()+x_shift,cellH.Y()+y_shift,C'150,150,150'); panel_r.DrawText(text,cellV.X()+cw-tw-x_shift,cellV.Y()+y_shift,C'150,150,150'); //--- Get cells 1.0 and 1.1 of the table and send Long and its value to their coordinates cellH=table_r.GetCell(1,0); cellV=table_r.GetCell(1,1); if(cellH==NULL || cellV==NULL) return false; text=(string)stats.long_trades; tw=panel_r.TextWidth(text); panel_r.DrawText(H_LONG+":",cellH.X()+x_shift,cellH.Y()+y_shift,C'150,150,150'); panel_r.DrawText(text,cellV.X()+cw-tw-x_shift,cellV.Y()+y_shift,C'150,150,150'); //--- Get cells 2.0 and 2.1 of the table and send Short and its value to their coordinates cellH=table_r.GetCell(2,0); cellV=table_r.GetCell(2,1); if(cellH==NULL || cellV==NULL) return false; text=(string)stats.short_trades; tw=panel_r.TextWidth(text); panel_r.DrawText(H_SHORT+":",cellH.X()+x_shift,cellH.Y()+y_shift,C'150,150,150'); panel_r.DrawText(text,cellV.X()+cw-tw-x_shift,cellV.Y()+y_shift,C'150,150,150'); //--- Get cells 3.0 and 3.1 of the table and send Net Profit and its value to their coordinates cellH=table_r.GetCell(3,0); cellV=table_r.GetCell(3,1); if(cellH==NULL || cellV==NULL) return false; value=stats.net_profit; text=DoubleToString(value,2); tw=panel_r.TextWidth(text); panel_r.DrawText(H_NET_PROFIT+":",cellH.X()+x_shift,cellH.Y()+y_shift,C'150,150,150'); panel_r.DrawText(text,cellV.X()+cw-tw-x_shift,cellV.Y()+y_shift,(value>0 ? C'86,119,204' : value<0 ? C'234,50,50' : C'150,150,150')); //--- Get cells 4.0 and 4.1 of the table and send Profit Loss and its value to their coordinates cellH=table_r.GetCell(4,0); cellV=table_r.GetCell(4,1); if(cellH==NULL || cellV==NULL) return false; value=stats.total_profit; text=DoubleToString(value,2); tw=panel_r.TextWidth(text); panel_r.DrawText(H_PROFITS+":",cellH.X()+x_shift,cellH.Y()+y_shift,C'150,150,150'); panel_r.DrawText(text,cellV.X()+cw-tw-x_shift,cellV.Y()+y_shift,(value>0 ? C'86,119,204' : value<0 ? C'234,50,50' : C'150,150,150')); //--- Get cells 5.0 and 5.1 of the table and send Gross Profit and its value to their coordinates cellH=table_r.GetCell(5,0); cellV=table_r.GetCell(5,1); if(cellH==NULL || cellV==NULL) return false; value=stats.gross_profit; text=DoubleToString(value,2); tw=panel_r.TextWidth(text); panel_r.DrawText(H_GROSS_PROFIT+":",cellH.X()+x_shift,cellH.Y()+y_shift,C'150,150,150'); panel_r.DrawText(text,cellV.X()+cw-tw-x_shift,cellV.Y()+y_shift,(value>0 ? C'86,119,204' : C'150,150,150')); //--- Get cells 6.0 and 6.1 of the table and send Gross Loss and its value to their coordinates cellH=table_r.GetCell(6,0); cellV=table_r.GetCell(6,1); if(cellH==NULL || cellV==NULL) return false; value=stats.gross_loss; text=DoubleToString(value,2); tw=panel_r.TextWidth(text); panel_r.DrawText(H_GROSS_LOSS+":",cellH.X()+x_shift,cellH.Y()+y_shift,C'150,150,150'); panel_r.DrawText(text,cellV.X()+cw-tw-x_shift,cellV.Y()+y_shift,(value<0 ? C'234,50,50' : C'150,150,150')); //--- Get cells 7.0 and 7.1 of the table and send Commission and its value to their coordinates cellH=table_r.GetCell(7,0); cellV=table_r.GetCell(7,1); if(cellH==NULL || cellV==NULL) return false; value=stats.total_commission; text=DoubleToString(value,2); tw=panel_r.TextWidth(text); panel_r.DrawText(H_COMMISSIONS+":",cellH.X()+x_shift,cellH.Y()+y_shift,C'150,150,150'); panel_r.DrawText(text,cellV.X()+cw-tw-x_shift,cellV.Y()+y_shift,(value<0 ? C'234,50,50' : C'150,150,150')); //--- Get cells 8.0 and 8.1 of the table and send Swap and its value to their coordinates cellH=table_r.GetCell(8,0); cellV=table_r.GetCell(8,1); if(cellH==NULL || cellV==NULL) return false; value=stats.total_swap; text=DoubleToString(value,2); tw=panel_r.TextWidth(text); panel_r.DrawText(H_SWAPS+":",cellH.X()+x_shift,cellH.Y()+y_shift,C'150,150,150'); panel_r.DrawText(text,cellV.X()+cw-tw-x_shift,cellV.Y()+y_shift,(value<0 ? C'234,50,50' : C'150,150,150')); //--- Right column (data -- value) //--- Get cells 0.2 and 0.3 of the table and send Win trades and its value to their coordinates cellH=table_r.GetCell(0,2); cellV=table_r.GetCell(0,3); if(cellH==NULL || cellV==NULL) return false; text=(string)stats.win_trades; tw=panel_r.TextWidth(text); panel_r.DrawText(H_WINS+":",cellH.X()+x_shift,cellH.Y()+y_shift,C'150,150,150'); panel_r.DrawText(text,cellV.X()+cw-tw-x_shift,cellV.Y()+y_shift,C'150,150,150'); //--- Get cells 1.2 and 1.3 of the table and send Loss trades and its value to their coordinates cellH=table_r.GetCell(1,2); cellV=table_r.GetCell(1,3); if(cellH==NULL || cellV==NULL) return false; text=(string)stats.loss_trades; tw=panel_r.TextWidth(text); panel_r.DrawText(H_LOST+":",cellH.X()+x_shift,cellH.Y()+y_shift,C'150,150,150'); panel_r.DrawText(text,cellV.X()+cw-tw-x_shift,cellV.Y()+y_shift,C'150,150,150'); //--- Get cells 2.2 and 2.3 of the table and send Expected Payoff and its value to their coordinates cellH=table_r.GetCell(2,2); cellV=table_r.GetCell(2,3); if(cellH==NULL || cellV==NULL) return false; value=stats.expected_payoff; text=DoubleToString(value,2); tw=panel_r.TextWidth(text); panel_r.DrawText(H_EXP_PAYOFF+":",cellH.X()+x_shift,cellH.Y()+y_shift,C'150,150,150'); panel_r.DrawText(text,cellV.X()+cw-tw-x_shift,cellV.Y()+y_shift,C'150,150,150'); //--- Get cells 3.2 and 3.3 of the table and send Win percent and its value to their coordinates cellH=table_r.GetCell(3,2); cellV=table_r.GetCell(3,3); if(cellH==NULL || cellV==NULL) return false; value=stats.win_percent; text=DoubleToString(value,2); tw=panel_r.TextWidth(text); panel_r.DrawText(H_WIN_PRC+":",cellH.X()+x_shift,cellH.Y()+y_shift,C'150,150,150'); panel_r.DrawText(text,cellV.X()+cw-tw-x_shift,cellV.Y()+y_shift,C'86,119,204'); //--- Get cells 4.2 and 4.3 of the table and send Loss percent and its value to their coordinates cellH=table_r.GetCell(4,2); cellV=table_r.GetCell(4,3); if(cellH==NULL || cellV==NULL) return false; value=stats.loss_percent; text=DoubleToString(value,2); tw=panel_r.TextWidth(text); panel_r.DrawText(H_LOSS_PRC+":",cellH.X()+x_shift,cellH.Y()+y_shift,C'150,150,150'); panel_r.DrawText(text,cellV.X()+cw-tw-x_shift,cellV.Y()+y_shift,C'234,50,50'); //--- Get cells 5.2 and 5.3 of the table and send Average Profit and its value to their coordinates cellH=table_r.GetCell(5,2); cellV=table_r.GetCell(5,3); if(cellH==NULL || cellV==NULL) return false; value=stats.average_profit; text=DoubleToString(value,2); tw=panel_r.TextWidth(text); panel_r.DrawText(H_AVG_PROFIT+":",cellH.X()+x_shift,cellH.Y()+y_shift,C'150,150,150'); panel_r.DrawText(text,cellV.X()+cw-tw-x_shift,cellV.Y()+y_shift,(value>0 ? C'86,119,204' : C'150,150,150')); //--- Get cells 6.2 and 6.3 of the table and send Average Loss and its value to their coordinates cellH=table_r.GetCell(6,2); cellV=table_r.GetCell(6,3); if(cellH==NULL || cellV==NULL) return false; value=stats.average_loss; text=DoubleToString(value,2); tw=panel_r.TextWidth(text); panel_r.DrawText(H_AVG_LOSS+":",cellH.X()+x_shift,cellH.Y()+y_shift,C'150,150,150'); panel_r.DrawText(text,cellV.X()+cw-tw-x_shift,cellV.Y()+y_shift,(value<0 ? C'234,50,50' : C'150,150,150')); //--- Get cells 7.2 and 7.3 of the table and send Profit factor and its value to their coordinates cellH=table_r.GetCell(7,2); cellV=table_r.GetCell(7,3); if(cellH==NULL || cellV==NULL) return false; value=stats.profit_factor; text=DoubleToString(value,2); tw=panel_r.TextWidth(text); panel_r.DrawText(H_PRF_FACTOR+":",cellH.X()+x_shift,cellH.Y()+y_shift,C'150,150,150'); panel_r.DrawText(text,cellV.X()+cw-tw-x_shift,cellV.Y()+y_shift,C'150,150,150'); //--- Return the right panel font to its previous saved properties panel_r.SetFontParams(f_name,f_size,f_flags,f_angle); return true; }
Die Funktion dient dazu, die endgültige statistische Tabelle für das ausgewählte Symbol, die magische Zahl oder das gesamte Konto zu erstellen. Die Funktion erhält den Typ der Statistiktabelle und den Namen des Symbols oder den Stringwert der magischen Zahl oder die Kontonummer. Der Text bestimmt den Index des Symbols, der magischen Zahl oder des Kontos in dem entsprechenden Array der statistischen Datenstrukturen. Ausgehend von der gewünschten Struktur verwenden wir den erhaltenen Index, um alle statistischen Daten in die Struktur zu bringen, und ordnen sie dann in der gerenderten Tabelle entsprechend den Koordinaten ihrer Zellen an. In diesem Fall werden die horizontalen Versätze des angezeigten Textes so berechnet, dass die Datenüberschrift an den linken Rand der Tabellenzelle und der Text des Datenwertes an den rechten Rand seiner Tabellenzelle gebunden ist. Alle Daten werden in vier Spalten angezeigt, sodass sie auf dem Panel visuell in zwei Spalten in der Form „Titel - Wert“ gruppiert sind.
Kompilieren wir den Indikator und sehen wir, was wir haben:
Wir können sehen, dass alle angegebenen Funktionen wie erwartet funktionieren. Beim Bewegen des Cursors und beim Scrollen von Tabellen ist ein leichtes „Blinken“ des Textes in den Tabellen zu sehen. Dies ist jedoch das Ergebnis eines suboptimalen Schemas des Neuzeichnens - der gesamte sichtbare Teil der Tabelle wird ständig neu dargestellt. Dies kann durch eine komplexere Logik für die Behandlung von Tabellenzeilen unter dem Cursor vermieden werden, aber das ist hier nicht unser Ziel.
Tabellen können vertikal durch Drehen des Mausrades und horizontal durch Drehen des Rades bei gedrückter Umschalttaste gescrollt werden. Wenn wir uns das Video genau ansehen, werden wir feststellen, dass bei der Anzeige der Statistiken für eine magische Zahl mit dem Wert 0 die Anzahl der Kauf- und Verkaufs-Positionen mit Null angezeigt wird. Dies wird durch einen Fehler bei der Definition eines Handels in einer Abfrage der DB verursacht. Die Tabelle der Handelsgeschäfte wird aus der Tabelle der Deals erstellt. Wenn eine Position von einem EA eröffnet (in diesem Fall mit der magischen Zahl 600) und manuell geschlossen wurde, dann wird für die Eröffnung des Deals eine magische Zahl angegeben, während die für ihr Schließen eine magische Zahl von Null angegeben wird. Dies ist aus dem Deal-Historie ersichtlich:
In diesem Fall verwenden wir schließende Deals, um das Handelsgeschäft zu bestimmen, und wenn dessen magische Zahl Null ist. Es ist unmöglich, ein Eröffnungs-Deal mit der magischen Zahl Null zu finden. Dementsprechend werden weder Kauf- noch Verkaufsposition mit der magische Zahl Null gefunden. Bei der Erstellung einer Handelstabelle sollte daher die Möglichkeit in Betracht gezogen werden, dass eine Position von einem EA eröffnet und manuell geschlossen werden kann und umgekehrt. Wenn wir das berücksichtigen, dann sollten solche Fehler nicht mehr auftreten.
Schlussfolgerung
In diesem Artikel haben wir uns die Aufgabe gestellt, ein Dashboard zu erstellen, das Handelsstatistiken anzeigt, und wir mussten sie anhand von Beispielen aus Artikeln und Dokumentationen auf dieser Ressource lösen. Wie wir sehen, ist es immer einfach, Antworten auf Ihre Fragen zu finden, auch ohne etwas zu wissen, indem Sie die riesige Wissensbasis nutzen, die diese Ressource bietet, und ein voll funktionsfähiges Produkt erstellen.
Studieren Sie die auf der Website angebotenen Informationen, lesen Sie Artikel und Dokumentationen, tauschen Sie sich mit erfahreneren Kollegen aus, verfeinern Sie die gefundenen Beispiele, damit sie zu Ihren Aufgaben passen, und alles wird klappen!
Alle Dateien der betrachteten Klassen, Funktionen und Indikatoren sind dem Artikel beigefügt. Ebenfalls beigefügt ist ein Archiv, das in das Terminal-Datenverzeichnis entpackt werden kann. Alle erforderlichen Dateien werden im Ordner \MQL5\Indicators\StatisticsBy abgelegt, sodass sie sofort kompiliert werden können und die Indikator-Datei gestartet werden kann.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/16233
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.
Es fehlt an Werkzeugen, die es Ihnen ermöglichen, mit einer umfangreichen Handelshistorie zu arbeiten.
Leider hängt sich dieses Toolkit bei der Abfrage der Historie einfach auf, wie viele andere auch.
Es dauert fünf Minuten, um die Historie abzurufen. Dann ist es unmöglich, irgendetwas mit dem Fenster zu tun - volle CPU-Last.
Es mangelt an Instrumenten für den Umgang mit einer umfangreichen Handelsgeschichte.
Leider hängt sich dieses Toolkit bei der Abfrage der Historie auf, wie viele andere auch.
Fünf Minuten, um den Verlauf zu erhalten. Dann ist es unmöglich, etwas mit dem Fenster zu tun - volle CPU-Last.
Kann ich als Anleger Zugriff auf das Konto haben?
Leider gibt es keine solche Möglichkeit. Aber Sie können so etwas selbst erstellen: Verwenden Sie auf einem Demokonto ein Skript, um die erforderliche Anzahl von Positionen für verschiedene Symbole/Magier in einer Stunde mit asynchronem OrderSend zu öffnen/zu schließen.
Möchte nicht an der Moskauer Börse arbeiten
Möchte nicht an der Moskauer Börse arbeiten
Natürlich. Alles, was sich auf die Position bezieht, mit Ausnahme der Gesamtposition, ist beim Netting nutzlos, wenn mehr als ein Roboter an einem Symbol arbeitet (oder Roboter plus manueller Handel).