English Español Português
preview
Возможности SQLite в MQL5: Пример интерактивной панели с торговой статистикой в разрезе символов и магиков

Возможности SQLite в MQL5: Пример интерактивной панели с торговой статистикой в разрезе символов и магиков

MetaTrader 5Примеры | 31 октября 2024, 08:34
1 176 5
Artyom Trishkin
Artyom Trishkin

Содержание



Введение

MQL5.com — это ресурс, предлагающий пользователям широкий доступ к разнообразной справочной и учебной информации в сфере алготрейдинга. Несмотря на обширную базу данных и возможности, некоторые пользователи всё же испытывают затруднения с нахождением конкретных решений или адаптацией примеров под свои нужды. На такие случаи ресурс предоставляет возможность взаимодействия с сообществом через форум, где можно получить полезные советы или даже готовые решения.

Настоящая статья направлена на демонстрацию и решение одной из типичных задач — создания информационной панели для отображения торговой истории и статистики на аккаунте. Мы рассмотрим процесс разработки данной панели, опираясь исключительно на существующие материалы с mql5.com, что интересно не только практическим решением, но и как пример применения обучающих и справочных материалов в реальных условиях.


Ставим задачу

Требуется создать информационную панель, где по запросу можно будет отображать информацию о торговой истории на счёте и торговую статистику в разрезе символов и магиков (экспертов, торгующих на счёте). Также необходимо отображать и полную торговую статистику по счёту. Торговая история должна быть отсортирована по времени проведённых трейдов. Статистика торговли по символам и магикам должна быть отсортирована по чистой прибыли (Net Profit).

Тип программы сделаем — Индикатор. Никаких данных в буферы выводить не будем. То есть это будет безбуферный индикатор, основным рабочим пространством которого будет графическая панель. Панель будет визуально разделена на две области — слева будем выводить списки всех символов и магиков, на которых велась торговля на счёте, а справа будем выводить либо таблицы статистики по символам и магикам в виде списка, либо итоговую статистику по какому-либо одному выбранному символу или магику, либо полностью по торговле на счёте. Управлять выводом тех или иных списков будем при помощи кнопок на панели, либо по щелчку на строке со статистикой по символу/магику в таблице на правой стороне панели.

В статье "Делаем информационную панель для отображения данных в индикаторах и советниках" мы возьмем информационную панель.
Статья "SQLite: нативная работа с базами данных на SQL в MQL5" поможет нам получить статистику по счёту и в разрезе символов и магиков (торговых стратегий).



Информационная панель

С момента первой публикации статьи о создании информационной панели, её код претерпел некоторые изменения и доработки. Подробно расписывать все сделанные в коде изменения не входит в планы и задачи данной статьи. Поэтому просто сделаем краткий обзор изменений. А поглядеть, что и как именно было доработано, можно, скачав первую версию панели из указанной статьи и сравнив с кодом этой же панели, прилагаемым к статье.

Была проведена работа над ошибками, устранены ошибки позиционирования и отображения панели в некоторых ситуациях при переключении графиков. Теперь к панели можно прикрепить дочерние панели. В частности, из них можно делать кнопки, свернув дочернюю панель и оставив только её шапку, которая и будет служить кнопкой. Были сняты ограничения на позиционирование таблиц внутри панели. Изначально таблица не могла рисоваться за пределами панели — только в области видимости. В некоторых случаях таблицы, рисуемые на панели, должны позиционироваться за пределами видимой области панели. Это необходимо для прокрутки длинных или широких таблиц, списки которых по своим размерам больше размера панели. Таким образом, если разрешить позиционировать начальные координаты таблицы за пределами панели, мы сможем сделать прокрутку таблицы. Именно это мы и будем сегодня делать. Но изменять классы панели и её таблиц мы не будем (хотя так правильнее в смысле дальнейшего использования доработанного класса панели) для того, чтобы можно было наглядно показать, что, имея не совсем подходящие по функционалу примеры, при правильном подходе всегда можно довести функциональность до нужного результата.

Посмотрим вкратце, что было доработано в коде классов таблиц и панели.

В класс ячейки таблицы добавлена переменная для хранения текста ячейки и методы для работы с этой переменной:

//+------------------------------------------------------------------+
//| Класс ячейки таблицы                                             |
//+------------------------------------------------------------------+
class CTableCell : public CObject
  {
private:
   int               m_row;                     // Строка
   int               m_col;                     // Столбец
   int               m_x;                       // Координата X
   int               m_y;                       // Координата Y
   string            m_text;                    // Текст в ячейке
public:
//--- Методы установки значений
   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;     }
//--- Методы получения значений
   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 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);
                       }
//--- Конструктор/деструктор
                     CTableCell(const int row,const int column) : m_row(row),m_col(column){}
                    ~CTableCell(void){}
  };

В классе данных таблиц теперь идентификатор может иметь отрицательное значение, равно как и координаты таблицы могут быть отрицательными. Добавлен метод установки идентификатора таблицы:

//+------------------------------------------------------------------+
//| Класс данных таблиц                                              |
//+------------------------------------------------------------------+
class CTableData : public CObject
  {
private:
   CArrayObj         m_list_rows;               // Список строк
   int               m_id;                      // Идентификатор таблицы
   int               m_x1;                      // Координата X1
   int               m_y1;                      // Координата Y1
   int               m_x2;                      // Координата X2
   int               m_y2;                      // Координата Y2
   int               m_w;                       // Ширина
   int               m_h;                       // Высота
   string            m_name;                    // Наименование таблицы
public:
//--- Устанавливает (1) идентификатор, (2) наименование таблицы
   void              SetID(const int id)        { this.m_id=id;                  }
   void              SetName(const string name) { this.m_name=name;              }
//--- Возвращает (1) идентификатор, (2) наименование таблицы
   int               ID(void)             const { return this.m_id;              }
   string            Name(void)           const { return this.m_name;            }
   
//--- Устанавливает координату (1) X1, (2) X2
   void              SetX1(const int x1)        { this.m_x1=x1;                  }
   void              SetX2(const int x2)        { this.m_x2=x2;                  }
//--- Устанавливает координату (1) Y1, (2) Y2
   void              SetY1(const int y1)        { this.m_y1=y1;                  }
   void              SetY2(const int y2)        { this.m_y2=y2;                  }
//--- Устанавливает координаты таблицы
   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);
                       }

Добавлен конструктор по умолчанию:

//--- Конструктор/деструктор
                     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();                  }

Конструктор по умолчанию позволит объявить объект класса без создания посредством оператора new, а значения координат с типом int позволят устанавливать начальные координаты таблиц за пределами окна панели.

В классе объекта панели объявлены новые переменные:

//+------------------------------------------------------------------+
//| Класс Dashboard                                                  |
//+------------------------------------------------------------------+
class CDashboard : public CObject
  {
private:
   CTableData        m_table_tmp;               // Объект-таблица для поиска
   CCanvas           m_canvas;                  // Канвас
   CCanvas           m_workspace;               // Рабочая область
   CArrayObj         m_list_table;              // Список таблиц
   CArrayObj         m_list_obj;                // Список привязанных панелей
   ENUM_PROGRAM_TYPE m_program_type;            // Тип программы
   ENUM_MOUSE_STATE  m_mouse_state;             // Состояние кнопок мышки
   uint              m_id;                      // Идентификатор объекта
   long              m_chart_id;                // ChartID
   int               m_chart_w;                 // Ширина графика
   int               m_chart_h;                 // Высота графика
   int               m_x;                       // Координата X
   int               m_y;                       // Координата Y
   int               m_w;                       // Ширина
   int               m_h;                       // Высота
   int               m_x_dock;                  // Координата X закреплённой свёрнутой панели
   int               m_y_dock;                  // Координата Y закреплённой свёрнутой панели
   int               m_diff_x;                  // Смещение локальной координаты X относительно родителя
   int               m_diff_y;                  // Смещение локальной координаты Y относительно родителя
   
   bool              m_header;                  // Флаг наличия заголовка
   bool              m_butt_close;              // Флаг наличия кнопки закрытия
   bool              m_butt_minimize;           // Флаг наличия кнопки сворачивания/разворачивания
   bool              m_butt_pin;                // Флаг наличия кнопки закрепления
   bool              m_wider_wnd;               // Флаг превышения горизонтального размера панели ширины окна
   bool              m_higher_wnd;              // Флаг превышения вертикального размера панели высоты окна
   bool              m_movable;                 // Флаг перемещаемости панели
   int               m_header_h;                // Высота заголовка
   int               m_wnd;                     // Номер подокна графика
   int               m_title_x_shift;           // Смещение текста заголовка по горизонтали
   int               m_title_y_shift;           // Смещение текста заголовка по вертикали
   
   uchar             m_header_alpha;            // Прозрачность заголовка
   uchar             m_header_alpha_c;          // Текущая прозрачность заголовка
   color             m_header_back_color;       // Цвет фона заголовка
   color             m_header_back_color_c;     // Текущий цвет фона заголовка
   color             m_header_fore_color;       // Цвет текста заголовка
   color             m_header_fore_color_c;     // Текущий цвет текста заголовка
   color             m_header_border_color;     // Цвет рамки заголовка
   color             m_header_border_color_c;   // Текущий цвет рамки заголовка
   
   color             m_butt_close_back_color;   // Цвет фона кнопки закрытия
   color             m_butt_close_back_color_c; // Текущий цвет фона кнопки закрытия
   color             m_butt_close_fore_color;   // Цвет значка кнопки закрытия
   color             m_butt_close_fore_color_c; // Текущий цвет значка кнопки закрытия
   
   color             m_butt_min_back_color;     // Цвет фона кнопки сворачивания/разворачивания
   color             m_butt_min_back_color_c;   // Текущий цвет фона кнопки сворачивания/разворачивания
   color             m_butt_min_fore_color;     // Цвет значка кнопки сворачивания/разворачивания
   color             m_butt_min_fore_color_c;   // Текущий цвет значка кнопки сворачивания/разворачивания
   
   color             m_butt_pin_back_color;     // Цвет фона кнопки закрепления
   color             m_butt_pin_back_color_c;   // Текущий цвет фона кнопки закрепления
   color             m_butt_pin_fore_color;     // Цвет значка кнопки закрепления
   color             m_butt_pin_fore_color_c;   // Текущий цвет значка кнопки закрепления
   
   uchar             m_alpha;                   // Прозрачность панели
   uchar             m_alpha_c;                 // Текущая прозрачность панели
   uchar             m_fore_alpha;              // Прозрачность текста
   uchar             m_fore_alpha_c;            // Текущая прозрачность текста
   color             m_back_color;              // Цвет фона
   color             m_back_color_c;            // Текущий цвет фона
   color             m_fore_color;              // Цвет текста
   color             m_fore_color_c;            // Текущий цвет текста
   color             m_border_color;            // Цвет рамки
   color             m_border_color_c;          // Текущий цвет рамки
   
   string            m_title;                   // Текст заголовка
   string            m_title_font;              // Фонт заголовка
   int               m_title_font_size;         // Размер шрифта заголовка
   string            m_font;                    // Фонт
   int               m_font_size;               // Размер шрифта
   
   bool              m_minimized;               // Флаг свёрнутого окна панели
   string            m_program_name;            // Имя программы
   string            m_name_gv_x;               // Наименование глобальной переменной терминала, хранящей координату X
   string            m_name_gv_y;               // Наименование глобальной переменной терминала, хранящей координату Y
   string            m_name_gv_m;               // Наименование глобальной переменной терминала, хранящей флаг свёрнутости панели
   string            m_name_gv_u;               // Наименование глобальной переменной терминала, хранящей флаг закреплённой панели
   string            m_filename_bg;             // Наименование файла для сохранения пикселей фона
   string            m_filename_ws;             // Наименование файла для сохранения пикселей рабочей области
   
   uint              m_array_wpx[];             // Массив пикселей для сохранения/восстановления рабочей области
   uint              m_array_ppx[];             // Массив пикселей для сохранения/восстановления фона панели

   int               m_mouse_diff_x;            // Смещение курсора относительно угла привязки по X
   int               m_mouse_diff_y;            // Смещение курсора относительно угла привязки по Y
   bool              m_slave;                   // Признак привязанной (зависимой) панели
   string            m_name;                    // Наименование панели
   

Так как теперь к родительской панели можно привязывать дочерние, то некоторые методы работы с панелью перенесены из защищённой секции в публичную — к этим методам необходим доступ извне, и добавлены новые методы:

public:
//--- Возвращает (1) идентификатор графика, (2) номер подокна
   long              ChartID(void)        const { return this.m_chart_id;              }
   int               SubWindow(void)      const { return this.m_wnd;                   }
//--- (1) Сворачивает, (2) разворачивает панель
   void              Collapse(void);
   void              Expand(void);
//--- (1) Скрывает, (2) показывает, (3) переносит на передний план панель
   void              Hide(const bool redraw=false);
   void              Show(const bool redraw=false);
   void              BringToTop(void);
//--- Возвращает флаг скрытого объекта
   bool              IsHidden(void);
//--- Устанавливает новые цвета заголовка
   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;
                       }
//--- Устанавливает новые свойства заголовка
   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);
                       }
//--- Устанавливает (1) ширину, (2) высоту панели
   bool              SetWidth(const int width,const bool redraw=false);
   bool              SetHeight(const int height,const bool redraw=false);


//--- Отображает панель
   void              View(const string title)   { this.Draw(title);                    }
//--- Возвращает объект (1) CCanvas, (2) рабочую область, (3) идентификатор объекта
   CCanvas          *Canvas(void)               { return &this.m_canvas;               }
   CCanvas          *Workspace(void)            { return &this.m_workspace;            }
   uint              ID(void)             const { return this.m_id;                    }
   
//--- Возвращает координату (1) X, (2) Y панели
   int               CoordX(void)         const { return this.m_x;                     }
   int               CoordY(void)         const { return this.m_y;                     }
//--- Возвращает (1) ширину, (2) высоту панели
   int               Width(void)          const { return this.m_w;                     }
   int               Height(void)         const { return this.m_h;                     }

//--- Возвращает смещение локальной координаты (1) X, (2) Y панели
   int               CoordDiffX(void)     const { return this.m_diff_x;                }
   int               CoordDiffY(void)     const { return this.m_diff_y;                }
//--- Устанавливает смещение локальной координаты (1) X, (2) Y панели
   void              SetCoordDiffX(const int diff_x)  { this.m_diff_x=diff_x;          }
   void              SetCoordDiffY(const int diff_y)  { this.m_diff_y=diff_y;          }

//--- Устанавливает смещения текста заголовка по (1) горизонтали, (2) вертикали, (3) оба
   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;
                       }
                       
//--- Возвращает (1) ширину, (2) высоту, (3) размеры указанного текста
   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);                 }
   
//--- Устанавливает флаг (1) наличия, (2) отсутствия заголовка панели
   void              SetPanelHeaderOn(const bool redraw=false);
   void              SetPanelHeaderOff(const bool redraw=false);
//--- Устанавливает флаг (1) наличия, (2) отсутствия кнопки закрытия
   void              SetButtonCloseOn(const bool redraw=false);
   void              SetButtonCloseOff(const bool redraw=false);
//--- Устанавливает флаг (1) наличия, (2) отсутствия кнопки сворачивания/разворачивания
   void              SetButtonMinimizeOn(const bool redraw=false);
   void              SetButtonMinimizeOff(const bool redraw=false);
//--- Устанавливает флаг (1) наличия, (2) отсутствия кнопки закрепления/открепления
   void              SetButtonPinOn(const bool redraw=false);
   void              SetButtonPinOff(const bool redraw=false);
   
//--- Устанавливает координаты панели
   bool              SetCoords(const int x,const int y);
//--- Устанавливает размеры панели
   bool              SetSizes(const int w,const int h,const bool update=false);
//--- Устанавливает координаты и размеры панели
   bool              SetParams(const int x,const int y,const int w,const int h,const bool update=false);

//--- Устанавливает прозрачность (1) заголовка, (2) рабочей области панели
   void              SetHeaderTransparency(const uchar value);
   void              SetTransparency(const uchar value);
//--- Устанавливает параметры шрифта (1) панели, (2) заголовка по умолчанию
   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);
//--- Возвращает установленные параметры шрифта (1) панели, (2) заголовка
   string            FontParams(int &size,uint &flags,uint &angle);
   string            FontHeaderParams(int &size,uint &flags,uint &angle);
//--- Возвращает установленные (1) шрифт, (2) размер, (3) флаги шрифта панели
   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(); }
//--- Возвращает установленные (1) шрифт, (2) размер, (3) флаги шрифта заголовка
   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) Устанавливает (2) возвращает цвет текста рабочей области панели
   void              SetForeColor(const color clr) { this.m_fore_color=clr;                  }
   color             ForeColor(void)         const { return this.m_fore_color;               }
//--- Выводит (2) текстовое сообщение, (2) закрашенный прямоугольник в указанные координаты
   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);
//--- Создаёт новую таблицу
   bool              CreateNewTable(const int id=WRONG_VALUE);
//--- Возвращает объект табличных данных по (1) идентификатору, (2) наименованию, (3) количество таблиц в списке
   CTableData       *GetTable(const uint id);
   CTableData       *GetTable(const string name);
   int               TableTotal(void)        const { return this.m_list_table.Total();       }
//--- Возвращает флаг наличия таблицы в списке по (1) идентификатору, (2) наименованию
   bool              TableIsExist(const uint id);
   bool              TableIsExist(const string name);
//--- Рисует (1) фоновую сетку, (2) с автоматическим размером ячеек
   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);
//--- Стирает всё нарисованное на панели и восстанавливает первоначальный вид
   void              Clear(void)
                       {
                        this.m_canvas.Erase(::ColorToARGB(this.m_back_color,this.m_alpha));
                        this.DrawFrame();
                        this.m_workspace.Erase(0x00FFFFFF);
                       }
//--- Распечатывает данные сетки (координаты пересечения линий)
   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);
                       }
//--- Записывает в переменные значения координат X и Y указанной ячейки таблицы
   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);
                       }
//--- Возвращает координату (1) X, (2) Y указанной ячейки таблицы
   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);
                       }
//--- Записывает в переменные значения координат X1 и Y1, X2 и Y2 указанной таблицы
   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();
                       }

//--- Возвращает координату (1) X1, (2) Y1, (3) X2, (4) Y2 указанной таблицы
   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);
                       }

//--- Сравнивает два объекта по идентификатору
   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);
                       }

//--- Создаёт и привязывает новую панель
   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;
                       }
//--- Возвращает указатель на панель по (1) идентификатору, (2) наименованию
   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) Устанавливает, (2) возвращает флаг ведомого объекта
   void              SetAsSlave(void)
                       {
                        this.m_slave=true;
                        this.m_movable=false;
                       }
   bool              IsSlave(void)     const { return this.m_slave;  }
//--- Возвращает флаг того, что объект с указанным именем принадлежит панели (например, создан объектом панели)
   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) Устанавливает, (2) возвращает наименование панели
   void              SetName(const string name) { this.m_name=name;  }
   string            Name(void)        const { return this.m_name;   }
   
//--- Возвращает текст заголовка панели
   string            HeaderTitle(void) const { return this.m_title;  }
   
//--- Обработчик событий

Все новые методы и произведённые доработки позволяют теперь прикреплять к родительской панели дочерние и использовать их как самостоятельные объекты, но зависимые от своего родителя. Теперь можно создавать прокручиваемые таблицы в случае, если их размер больше размера самой панели. Раньше таблицы могли быть только размером, меньшим, чем сама панель. Функционала прокрутки таблиц в классе панели нет — его мы организуем прямо из главной программы. Впоследствии, при необходимости, такой функционал будет добавлен к классу панели и её таблиц. Но на данный момент такой потребности нет.

Естественно, здесь мы рассмотрели только малую часть внесённых изменений в класс панели и её таблиц — только объявленные методы. Доработки вносились постепенно в достаточно обширную часть кода на протяжении времени, прошедшего с момента первой публикации. При желании всегда можно скачать первую версию панели из статьи её описания и сравнить с версией, представленной в этой статье. Файл информационной панели должен быть расположен в каталоге этого проекта: \MQL5\Indicators\StatisticsBy\Dashboard\Dashboard.mqh.



Функции для работы с базой данных

С базами данных я работал совсем давно и совсем немного, при этом это был совместный проект для игровой индустрии на C#, где БД занимался другой человек, а я лишь использовал предоставленный коннектор для подключения БД к проекту. Поэтому здесь пришлось вооружиться справочными материалами и статьями на ресурсе mql5.com. Начав изучать статью "SQLite: нативная работа с базами данных на SQL в MQL5", сразу увидел отсылки с документации, в первую же очередь к функции DatabasePrepare(). А там есть пример, в котором создаётся таблица сделок, а на её основе — таблица трейдов. Это же одно из того, что нам нужно! Вооружаемся терпением и изучаем пример и его функции.

Сначала видим две структуры для хранения данных сделок и трейдов:

//--- структура для хранения сделки
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
  };
//--- структура для хранения трейда - порядок членов соответствует позиции в терминале
struct Trade
  {
   datetime          time_in;          // время входа
   ulong             ticket;           // ID позиции
   char              type;             // покупка или продажа
   double            volume;           // объем
   string            symbol;           // символ
   double            price_in;         // цена входа
   datetime          time_out;         // время выхода
   double            price_out;        // цена выхода
   double            commission;       // комиссия за вход и выход
   double            swap;             // своп
   double            profit;           // прибыль или убыток
  };

Далее разбираем логику:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- создадим имя файла
   string filename=IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN))+"_trades.sqlite";
//--- открываем/создаем базу данных в общей папке терминалов
   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;
     }
//--- создадим таблицу DEALS
   if(!CreateTableDeals(db))
     {
      DatabaseClose(db);
      return;
     }
//--- запросим всю торговую историю
   datetime from_date=0;
   datetime to_date=TimeCurrent();
//--- запросим историю сделок в указанном интервале
   HistorySelect(from_date, to_date);
   int deals_total=HistoryDealsTotal();
   PrintFormat("Торговая история насчитывает сделок: %d ", deals_total);
//--- внесем в таблицу сделки
   if(!InsertDeals(db))
      return;
//--- покажем первые 10 сделок
   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;
     }
//--- выведем сделки на печать
   if(i>0)
     {
      ArrayResize(deals, i);
      PrintFormat("Первые %d сделок:", i);
      ArrayPrint(deals);
     }
//--- удалим запрос после использования
   DatabaseFinalize(request);
 
//--- проверим что на счете используется хеджинг для учета открытых позиций
   if((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
     {
      //--- не можем преобразовать сделки в трейды простым способом через транзакции, поэтому завершаем работу
      DatabaseClose(db);
      return;
     }
 
//--- теперь создадим таблицу TRADES на основе таблицы DEALS
   if(!CreateTableTrades(db))
     {
      DatabaseClose(db);
      return;
     }
//--- заполним через SQL-запрос таблицу TRADES на основе данных из DEALS
   ulong start=GetMicrosecondCount();
   if(DatabaseTableExists(db, "DEALS"))
      //--- заполним таблицу TRADES
      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;
   
//--- покажем первые 10 сделок
   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;
     }
//--- выведем трейды на печать
   if(i>0)
     {
      ArrayResize(trades, i);
      PrintFormat("\r\nПервые %d трейдов:", i);
      ArrayPrint(trades);
      PrintFormat("Заполнение таблицы TRADES заняло %.2f миллисекунд",double(transaction_time)/1000);
     }
//--- удалим запрос после использования
   DatabaseFinalize(request);
 
//--- закрываем базу данных
   DatabaseClose(db);
  }
  1. Создаётся база данных,
  2. создаётся в БД таблица сделок,
  3. запрашивается история сделок, и сделки вносятся в созданную таблицу,
  4. проверяется тип счёта. Должен быть хедж, так как для неттинга просто так, на основании одних сделок, торговую историю не создать,
  5. создаётся таблица трейдов на основе таблицы сделок, и в таблицу трейдов заносятся данные трейдов на основе данных таблицы сделок.

Представленный скрипт ещё выводит на печать первые десять сделок и первые десять трейдов из созданных таблиц. Нам это не нужно.

Исходя из логики, нам нужно создать несколько функций на основе тех, что представлены в примере, и из строк кода в теле скрипта примера:

//+------------------------------------------------------------------+
//| Создает таблицу DEALS                                            |
//+------------------------------------------------------------------+
bool CreateTableDeals(int database)
  {
//--- если таблица DEALS уже есть, удалим её
   if(!DeleteTable(database, "DEALS"))
     {
      return(false);
     }
//--- проверим наличие таблицы
   if(!DatabaseTableExists(database, "DEALS"))
      //--- создаем таблицу
      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);
        }
//--- таблица успешно создана
   return(true);
  }
//+------------------------------------------------------------------+
//| Удаляет из базы таблицу с указанным именем                       |
//+------------------------------------------------------------------+
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);
     }
//--- таблица успешно удалена
   return(true);
  }
//+------------------------------------------------------------------+
//| Вносит сделки в таблицу базы данных                              |
//+------------------------------------------------------------------+
bool InsertDeals(int database)
  {
//--- вспомогательные переменные
   ulong    deal_ticket;         // тикет сделки
   long     order_ticket;        // тикет ордера,по которому была совершена сделка
   long     position_ticket;     // ID позиции, к которой относится сделка
   datetime time;                // время совершения сделки
   long     type ;               // тип сделки
   long     entry ;              // направление сделки
   string   symbol;              // по какому символу была сделка
   double   volume;              // объем операции
   double   price;               // цена
   double   profit;              // финансовый результат
   double   swap;                // своп
   double   commission;          // комиссия
   long     magic;               // Magic number (ID советника)
   long     reason;              // причина или источник проведения сделки
//--- пройдем по всем сделкам и внесем их в базу данных
   bool failed=false;
   int deals=HistoryDealsTotal();
//--- заблокируем базу данных перед выполнением транзакций
   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);
      //--- внесем в таблицу каждую сделку через запрос
      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;
        }
     }
//--- проверим на наличие ошибок при выполнении транзакций
   if(failed)
     {
      //--- откатим все транзакции и разблокируем базу данных
      DatabaseTransactionRollback(database);
      PrintFormat("%s: DatabaseExecute() failed with code %d", __FUNCTION__, GetLastError());
      return(false);
     }
//--- все транзакции прошли успешно - зафиксируем изменения и разблокируем базу данных
   DatabaseTransactionCommit(database);
   return(true);
  }
//+------------------------------------------------------------------+
//| Создает таблицу TRADES                                           |
//+------------------------------------------------------------------+
bool CreateTableTrades(int database)
  {
//--- если таблица TRADES уже есть, удалим её
   if(!DeleteTable(database, "TRADES"))
      return(false);
//--- проверим наличие таблицы
   if(!DatabaseTableExists(database, "TRADES"))
      //--- создаем таблицу
      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);
        }
//--- таблица успешно создана
   return(true);
  }
//+------------------------------------------------------------------+

Кроме тех полей структур и таблиц, что представлены в примере, нам нужно ещё поле, в котором будет храниться номер счёта — это потребуется для создания таблицы статистики по торговле на счёте. Иными словами — полной статистики торговли.

А как же нам сделать таблицы статистики? Ладно, читаем далее статью и находим! Вот именно то, что нам и нужно:

Анализ портфеля в разрезе стратегий

В вышеприведенных результатах работы скрипта из DatabasePrepare видно, что торговля ведется на нескольких валютных парах. Но кроме того, там же мы видим в столбце [magic] значения от 100 до  600. Это значит, что на торговом счёте ведут торговлю несколько стратегий, каждая из которых имеет свой собственный Magic Number для идентификации своих сделок.

С помощью SQL-запроса мы можем сделать анализ торговли в разрезе значений magic:

//--- получим торговую статистику в разрезе советников по 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");

Результат:

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 из 6 стратегий показали прибыль. И для каждой из стратегий мы получили статистические показатели:

  • trades — количество трейдов по стратегии,
  • gross_profit — общая прибыль по стратегии (сумма всех положительных значений profit),
  • gross_loss — общий убыток по стратегии (сумма всех отрицательных значений profit),
  • total_commission — сумма всех комиссий по трейдам стратегии,
  • total_swap — сумма всех свопов по трейдам стратегии,
  • total_profit — сумма gross_profit  и gross_loss,
  • net_profit — сумма (gross_profit  + gross_loss + total_commission + total_swap),
  • win_trades — количество трейдов, где profit>0,
  • loss_trades— количество трейдов, где profit<0,
  • expected_payoff — матожидание трейда без учета свопов и комисссий = net_profit/trades,
  • win_percent — процент выигрышных трейдов,
  • loss_percent — процент проигрышных трейдов,
  • average_profit — средний выигрыш = gross_profit/win_trades,
  • average_loss — средний проигрыш = gross_loss /loss_trades,
  • profit_factor — профит фактор = gross_profit/gross_loss.

Данная статистика для вычисления прибыли и убытка не принимает во внимание свопы и комиссии, которые были начислены на позицию. Это позволяет увидеть данные издержки в чистом виде. Может оказаться, что сама стратегия дает небольшую прибыль, но за счет свопов и комиссий оказывается невыгодной.


Анализ сделок по символам

Мы можем провести анализ торговли в разрезе символов. Для этого сделаем такой запрос:

//--- получим торговую статистику в разрезе символов
   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");

Результат:

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

Статистика говорит нам, что только на 5 символах из 10 была получена чистая прибыль (net_profit>0), хотя на 6 из 10 профит фактор был положителен (profit_factor>1). Как раз тот случай, когда свопы и комиссии делают стратегию проигрышной на EURJPY.

Читаем статью далее и видим:

Полный исходный код с этими 3 видами запросов вы можете найти в примере к функции DatabaseExecute().

Чудесно! Идём по ссылке в справку и получаем полный код из примера к этой функции:

//--- статистика по символу
struct Symbol_Stats
  {
   string            name;             // имя символа
   int               trades;           // количество трейдов по символу
   double            gross_profit;     // общая прибыль по символу
   double            gross_loss;       // общий убыток по символу
   double            total_commission; // сумма комиссий по символу
   double            total_swap;       // сумма свопов по символу
   double            total_profit;     // общая прибыль без учета свопов и комиссий
   double            net_profit;       // чистая прибыль с учетом свопов и комиссий
   int               win_trades;       // количество прибыльных трейдов
   int               loss_trades;      // количество убыточных трейдов
   double            expected_payoff;  // матожидание трейда без учета свопов и комиссии
   double            win_percent;      // процент выигрышных трейдов
   double            loss_percent;     // процент проигрышных трейдов
   double            average_profit;   // средняя прибыль
   double            average_loss;     // средний убыток
   double            profit_factor;    // профит-фактор
  };
 
//--- статистика по Magic Number
struct Magic_Stats
  {
   long              magic;            // Magic Number советника
   int               trades;           // количество трейдов по символу
   double            gross_profit;     // общая прибыль по символу
   double            gross_loss;       // общий убыток по символу
   double            total_commission; // сумма комиссий по символу
   double            total_swap;       // сумма свопов по символу
   double            total_profit;     // общая прибыль без учета свопов и комиссий
   double            net_profit;       // чистая прибыль с учетом свопов и комиссий
   int               win_trades;       // количество прибыльных трейдов
   int               loss_trades;      // количество убыточных трейдов
   double            expected_payoff;  // матожидание трейда без учета свопов и комиссии
   double            win_percent;      // процент выигрышных трейдов
   double            loss_percent;     // процент проигрышных трейдов
   double            average_profit;   // средняя прибыль
   double            average_loss;     // средний убыток
   double            profit_factor;    // профит-фактор
  };
 
//--- статистика по часу входа
struct Hour_Stats
  {
   char              hour_in;          // час входа в рынок
   int               trades;           // количество трейдов в этот час входа
   double            volume;           // объем трейдов в этот час входа
   double            gross_profit;     // общая прибыль в этот час входа
   double            gross_loss;       // общий убыток в этот час входа
   double            net_profit;       // чистая прибыль с учетом свопов и комиссий
   int               win_trades;       // количество прибыльных трейдов
   int               loss_trades;      // количество убыточных трейдов
   double            expected_payoff;  // матожидание трейда без учета свопов и комиссии
   double            win_percent;      // процент выигрышных трейдов
   double            loss_percent;     // процент проигрышных трейдов
   double            average_profit;   // средняя прибыль
   double            average_loss;     // средний убыток
   double            profit_factor;    // профит-фактор
  };
 
int ExtDealsTotal=0;;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- создадим имя файла
   string filename=IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN))+"_stats.sqlite";
//--- открываем/создаем базу данных в общей папке терминалов
   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;
     }
//--- создадим таблицу DEALS
   if(!CreateTableDeals(db))
     {
      DatabaseClose(db);
      return;
     }
   PrintFormat("Торговая история насчитывает сделок: %d ", ExtDealsTotal);
 
//--- получим торговую статистику в разрезе символов
   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;
//--- получаем записи из результатов запроса
   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("");
//--- удалим запрос
   DatabaseFinalize(request);
 
 
//--- получим торговую статистику в разрезе советников по 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;
//--- выводим записи
   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("");
//--- удалим запрос
   DatabaseFinalize(request);
 
//--- проверим что на счете используется хеджинг для учета открытых позиций
   if((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
     {
      //--- не можем преобразовать сделки в трейды простым способом через транзакции, поэтому завершаем работу
      DatabaseClose(db);
      return;
     }
 
//--- теперь создадим таблицу TRADES на основе таблицы DEALS
   if(!CreateTableTrades(db))
     {
      DatabaseClose(db);
      return;
     }
//--- заполним через SQL-запрос таблицу TRADES на основе данных из DEALS
   if(DatabaseTableExists(db, "DEALS"))
      //--- заполним таблицу TRADES
      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;
        }
 
//--- получим торговую статистику в разрезе часа входа в рынок
   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;
//--- выводим записи
   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("");
//--- удалим запрос
   DatabaseFinalize(request);
 
//--- закроем базу данных
   DatabaseClose(db);
   return;
  }
/*
Торговая история насчитывает сделок: 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
*/  
//+------------------------------------------------------------------+
//| Создает таблицу DEALS                                            |
//+------------------------------------------------------------------+
bool CreateTableDeals(int database)
  {
//--- если таблица DEALS уже есть, удалим её
   if(!DeleteTable(database, "DEALS"))
     {
      return(false);
     }
//--- проверим наличие таблицы
   if(!DatabaseTableExists(database, "DEALS"))
      //--- создаем таблицу
      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);
        }
//---  запросим всю торговую историю
   datetime from_date=0;
   datetime to_date=TimeCurrent();
//--- запросим историю сделок в указанном интервале
   HistorySelect(from_date, to_date);
   ExtDealsTotal=HistoryDealsTotal();
//--- внесем в таблицу сделки
   if(!InsertDeals(database))
      return(false);
//--- таблица успешно создана
   return(true);
  }
//+------------------------------------------------------------------+
//| Удаляет из базы таблицу с указанным именем                       |
//+------------------------------------------------------------------+
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);
     }
//--- таблица успешно удалена
   return(true);
  }
//+------------------------------------------------------------------+
//| Вносит сделки в таблицу базы данных                              |
//+------------------------------------------------------------------+
bool InsertDeals(int database)
  {
//--- вспомогательные переменные
   ulong    deal_ticket;         // тикет сделки
   long     order_ticket;        // тикет ордера,по которому была совершена сделка
   long     position_ticket;     // ID позиции, к которой относится сделка
   datetime time;                // время совершения сделки
   long     type ;               // тип сделки
   long     entry ;              // направление сделки
   string   symbol;              // по какому символу была сделка
   double   volume;              // объем операции
   double   price;               // цена
   double   profit;              // финансовый результат
   double   swap;                // своп
   double   commission;          // комиссия
   long     magic;               // Magic number (ID советника)
   long     reason;              // причина или источник проведения сделки
   char     hour;                // час совершения сделки
   MqlDateTime time_strusture;
//--- пройдем по всем сделкам и внесем их в базу данных
   bool failed=false;
   int deals=HistoryDealsTotal();
//--- заблокируем базу данных перед выполнением транзакций
   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;
      //--- внесем в таблицу каждую сделку через запрос
      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;
        }
     }
//--- проверим на наличие ошибок при выполнении транзакций
   if(failed)
     {
      //--- откатим все транзакции и разблокируем базу данных
      DatabaseTransactionRollback(database);
      PrintFormat("%s: DatabaseExecute() failed with code ", __FUNCTION__, GetLastError());
      return(false);
     }
//--- все транзакции прошли успешно - зафиксируем изменения и разблокируем базу данных
   DatabaseTransactionCommit(database);
   return(true);
  }
//+------------------------------------------------------------------+
//| Создает таблицу TRADES                                           |
//+------------------------------------------------------------------+
bool CreateTableTrades(int database)
  {
//--- если таблица TRADES уже есть, удалим её
   if(!DeleteTable(database, "TRADES"))
      return(false);
//--- проверим наличие таблицы
   if(!DatabaseTableExists(database, "TRADES"))
      //--- создаем таблицу
      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);
        }
//--- таблица успешно создана
   return(true);
  }
//+------------------------------------------------------------------+

Его можно скопировать в редактор, скомпилировать и, запустив, поглядеть в журнале результат его работы.

Теперь у нас есть примеры того, как нужно работать с базой данных для получения требуемого нам результата. Потребуется лишь немного доработать представленные в справке коды. Например, нам нужно, чтобы данные были отсортированы по какому-либо полю, или получить только уникальные значения из таблиц. Для этого можно почитать справочную информацию по SQL. И на основании полученных знаний и примеров мы сможем сделать так, как необходимо нам для задуманного проекта.

Давайте создадим в каталоге терминала \MQL5\Indicators\ новую папку StaticticsBy\. В ней будут находиться все файлы данного проекта.
В созданной папке создадим новый файл SQLiteFunc.mqh и начнём его наполнять функциями для работы с базой данных.

В первую очередь напишем необходимые структуры:

//+------------------------------------------------------------------+
//|                                                   SQLiteFunc.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
//+------------------------------------------------------------------+
//| Структура для хранения сделки                                    |
//+------------------------------------------------------------------+
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
  };
//+------------------------------------------------------------------+
//| Структура для хранения трейда:                                   |
//| порядок членов соответствует позиции в терминале                 |
//+------------------------------------------------------------------+
struct STrade
  {
   long              account;          // номер счёта
   datetime          time_in;          // время входа
   ulong             ticket;           // ID позиции
   char              type;             // покупка или продажа
   double            volume;           // объем
   string            symbol;           // символ
   double            price_in;         // цена входа
   datetime          time_out;         // время выхода
   double            price_out;        // цена выхода
   double            commission;       // комиссия за вход и выход
   double            swap;             // своп
   double            profit;           // прибыль или убыток
  };
//+------------------------------------------------------------------+
//| Структура для хранения статистики по символу                     |
//+------------------------------------------------------------------+
struct SSymbolStats
  {
   string            name;             // имя символа
   int               trades;           // количество трейдов по символу
   double            gross_profit;     // общая прибыль по символу
   double            gross_loss;       // общий убыток по символу
   double            total_commission; // сумма комиссий по символу
   double            total_swap;       // сумма свопов по символу
   double            total_profit;     // общая прибыль без учета свопов и комиссий
   double            net_profit;       // чистая прибыль с учетом свопов и комиссий
   int               win_trades;       // количество прибыльных трейдов
   int               loss_trades;      // количество убыточных трейдов
   long              long_trades;      // покупки
   long              short_trades;     // продажи
   double            expected_payoff;  // матожидание трейда без учета свопов и комиссии
   double            win_percent;      // процент выигрышных трейдов
   double            loss_percent;     // процент проигрышных трейдов
   double            average_profit;   // средняя прибыль
   double            average_loss;     // средний убыток
   double            profit_factor;    // профит-фактор
  };
//+------------------------------------------------------------------+
//| Структура для хранения статистики по Magic Number                |
//+------------------------------------------------------------------+
struct SMagicStats
  {
   long              magic;            // Magic Number советника
   int               trades;           // количество трейдов по символу
   double            gross_profit;     // общая прибыль по символу
   double            gross_loss;       // общий убыток по символу
   double            total_commission; // сумма комиссий по символу
   double            total_swap;       // сумма свопов по символу
   double            total_profit;     // общая прибыль без учета свопов и комиссий
   double            net_profit;       // чистая прибыль с учетом свопов и комиссий
   int               win_trades;       // количество прибыльных трейдов
   int               loss_trades;      // количество убыточных трейдов
   long              long_trades;      // покупки
   long              short_trades;     // продажи
   double            expected_payoff;  // матожидание трейда без учета свопов и комиссии
   double            win_percent;      // процент выигрышных трейдов
   double            loss_percent;     // процент проигрышных трейдов
   double            average_profit;   // средняя прибыль
   double            average_loss;     // средний убыток
   double            profit_factor;    // профит-фактор
  };
//+------------------------------------------------------------------+
//| Структура для хранения статистики по счёту                       |
//+------------------------------------------------------------------+
struct SAccountStats
  {
   long              account;          // номер счёта
   int               trades;           // количество трейдов по символу
   double            gross_profit;     // общая прибыль по символу
   double            gross_loss;       // общий убыток по символу
   double            total_commission; // сумма комиссий по символу
   double            total_swap;       // сумма свопов по символу
   double            total_profit;     // общая прибыль без учета свопов и комиссий
   double            net_profit;       // чистая прибыль с учетом свопов и комиссий
   int               win_trades;       // количество прибыльных трейдов
   int               loss_trades;      // количество убыточных трейдов
   long              long_trades;      // покупки
   long              short_trades;     // продажи
   double            expected_payoff;  // матожидание трейда без учета свопов и комиссии
   double            win_percent;      // процент выигрышных трейдов
   double            loss_percent;     // процент проигрышных трейдов
   double            average_profit;   // средняя прибыль
   double            average_loss;     // средний убыток
   double            profit_factor;    // профит-фактор
  };

Структуры скопированы из показанных выше примеров в Документации, но им даны иные наименования и добавлены поля с номером счёта и с количеством длинных и коротких позиций, по которым можно будет делать выборку из БД.

История сделок нам нужна для создания таблицы сделок в БД. Поэтому и функцию для получения истории сделок напишем в этом же файле:

//+------------------------------------------------------------------+
//| Запрашивает историю сделок за указанный период                   |
//+------------------------------------------------------------------+
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;
  }

Сюда же впишем функцию для удаляения из базы данных таблицы с указанным именем:

//+------------------------------------------------------------------+
//| Удаляет из базы таблицу с указанным именем                       |
//+------------------------------------------------------------------+
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);
     }
//--- таблица успешно удалена
   return(true);
  }

Напишем функцию для создания таблицы сделок:

//+------------------------------------------------------------------+
//| Создает таблицу DEALS                                            |
//+------------------------------------------------------------------+
bool CreateTableDeals(int database)
  {
//--- если таблица DEALS уже есть, удалим её
   if(!DeleteTable(database, "DEALS"))
      return(false);

//--- проверим наличие таблицы
   ResetLastError();
   if(!DatabaseTableExists(database, "DEALS"))
      //--- создаем таблицу
      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);
        }
//--- таблица успешно создана
   return(true);
  }

Код функции скопирован из справки, но здесь добавлено ещё одно поле — номер счёта.

По аналогии напишем функцию для создания таблицы трейдов, где тоже добавлено поле с номером счёта:

//+------------------------------------------------------------------+
//| Создает таблицу TRADES                                           |
//+------------------------------------------------------------------+
bool CreateTableTrades(int database)
  {
//--- если таблица TRADES уже есть, удалим её
   if(!DeleteTable(database, "TRADES"))
      return(false);
//--- проверим наличие таблицы
   ResetLastError();
   if(!DatabaseTableExists(database, "TRADES"))
      //--- создаем таблицу
      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);
        }
//--- таблица успешно создана
   return(true);
  }

Напишем функцию для заполнения таблицы БД данными сделок:

//+------------------------------------------------------------------+
//| Вносит сделки в таблицу базы данных                              |
//+------------------------------------------------------------------+
bool InsertDeals(int database)
  {
//--- вспомогательные переменные
   long     account_login=AccountInfoInteger(ACCOUNT_LOGIN);   // номер счёта
   ulong    deal_ticket;         // тикет сделки
   long     order_ticket;        // тикет ордера,по которому была совершена сделка
   long     position_ticket;     // ID позиции, к которой относится сделка
   datetime time;                // время совершения сделки
   long     type ;               // тип сделки
   long     entry ;              // направление сделки
   string   symbol;              // по какому символу была сделка
   double   volume;              // объем операции
   double   price;               // цена
   double   profit;              // финансовый результат
   double   swap;                // своп
   double   commission;          // комиссия
   long     magic;               // Magic number (ID советника)
   long     reason;              // причина или источник проведения сделки
//--- пройдем по всем сделкам и внесем их в базу данных
   bool failed=false;
   int deals=HistoryDealsTotal();
//--- заблокируем базу данных перед выполнением транзакций
   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);
      //--- внесем в таблицу каждую сделку через запрос
      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;
        }
     }
//--- проверим на наличие ошибок при выполнении транзакций
   if(failed)
     {
      //--- откатим все транзакции и разблокируем базу данных
      DatabaseTransactionRollback(database);
      PrintFormat("%s: DatabaseExecute() failed with code %d", __FUNCTION__, GetLastError());
      return(false);
     }
//--- все транзакции прошли успешно - зафиксируем изменения и разблокируем базу данных
   DatabaseTransactionCommit(database);
   return(true);
  }

Код функции взят из примера в справке по функции DatabaseExecute(). Здесь дополнительно создана переменная для хранения номера счёта, и исправлен текст запроса, так как в справке скорее всего есть ошибка: при составлении текста строки запроса указаны типы данных как int для данных, имеющих тип long:

//--- внесем в таблицу каждую сделку через запрос
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);

Здесь мы это исправили, добавив номер счёта и удалив час входа в сделку, так как часы входа в сделку нам здесь не нужны.

Напишем функцию, для заполнения таблицы трейдов на основе таблицы сделок:

//+------------------------------------------------------------------+
//| Заполняет таблицу TRADES на основе таблицы DEALS                 |
//+------------------------------------------------------------------+
bool FillTRADEStableBasedOnDEALStable(int database)
  {
   if(!DatabaseTableExists(database, "DEALS"))
     {
      PrintFormat("%s: Error. DEALS table is missing in the database", __FUNCTION__);
      return false;
     }
//--- заполним таблицу TRADES
   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;
  }

Точно так же, как и в функциях выше, здесь добавлен номер счёта.

Напишем функцию, заполняющую из базы данных список всех трейдов:

//+------------------------------------------------------------------+
//| Заполняет из БД список всех трейдов                              |
//+------------------------------------------------------------------+
bool FillsListTradesFromDB(int database, string db_name, STrade &array[])
  {
   STrade trade;
   ResetLastError();
   //--- Запросим из БД список трейдов, отсортированный по убыванию времени входа в рынок
   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;
     }
   //--- Прочитаем в массив структур данные созданной таблицы трейдов
   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;
     }
   //--- удалим запрос после использования
   DatabaseFinalize(request);
   return true;
  }

Здесь мы добавили сортировку списка трейдов по убыванию времени входа в рынок. Если этого не сделать, то в списке последний трейд будет в конце, соответственно и в отображаемой на панели таблице он будет в самом низу. Это неудобно. Сортировка по убыванию переместит последний трейд в самое начало таблицы — вверх на панели, и последние трейды будут сразу видимы без длительной прокрутки списка трейдов до них.

Напишем функцию, заполняющую из базы данных список всех символов, на которых велась торговля:

//+------------------------------------------------------------------+
//| Заполняет из БД список всех символов                             |
//+------------------------------------------------------------------+
bool FillsListSymbolsFromDB(int database, string db_name, string &array[])
  {
//--- Проверим наличие созданной таблицы трейдов в базе данных
   ResetLastError();
   if(!DatabaseTableExists(database, "TRADES"))
     {
      //--- Если таблица ещё не создана - сообщим о том, как её создать
      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;
     }
     
//--- запросим из БД список всех символов, на которых осуществлялась торговля. Список отсортирован по алфавиту
   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;
     }
   
//--- Прочитаем в массив данные созданной таблицы символов
   for(int i=0; DatabaseRead(request); i++)
     {
      ArrayResize(array, i+1);
      DatabaseColumnText(request, 0, array[i]);
     }
   //--- удалим запрос после использования
   DatabaseFinalize(request);
   return true;
  }

Для того, чтобы получить список, в котором будут лежать только уникальные неповторяющиеся наименования символов, нужно использовать ключевое слово "DISTINCT", ну и получать список нам необходимо в алфавитном порядке.

По аналогии напишем функцию, заполняющую из базы данных список всех магиков в порядке возрастания:

//+------------------------------------------------------------------+
//| Заполняет из БД список всех магиков                              |
//+------------------------------------------------------------------+
bool FillsListMagicsFromDB(int database, string db_name, long &array[])
  {
//--- Проверим наличие созданной таблицы трейдов в базе данных
   ResetLastError();
   if(!DatabaseTableExists(database, "DEALS"))
     {
      //--- Если таблица ещё не создана - сообщим о том, как её создать
      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;
     }
     
//--- запросим из БД список всех магиков, на которых осуществлялась торговля. Список отсортирован по возрастанию
   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;
     }
   
//--- Прочитаем в массив данные созданной таблицы магиков
   for(int i=0; DatabaseRead(request); i++)
     {
      ArrayResize(array, i+1);
      DatabaseColumnLong(request, 0, array[i]);
     }
   //--- удалим запрос после использования
   DatabaseFinalize(request);
   return true;
  }

Создадим функцию, которая получает из базы данных и сохраняет в массив статистику торговли по символам:

//+-------------------------------------------------------------------+
//|Получает из БД и сохраняет в массив статистику торговли по символам|
//+-------------------------------------------------------------------+
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;
     }
     
//--- получаем записи из результатов запроса
   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;
     }
//--- удалим запрос после использования
   DatabaseFinalize(request);
   return true;
  }

Здесь добавлены строки для учёта длинных и коротких позиций, и получаем список, отсортированный по убыванию чистой прибыли — чтобы символы, по которым была получена большая прибыль, находились в начале таблицы.

Аналогично напишем функцию для получения из базы данных и сохранения в массив статистики торговли по магикам:

//+------------------------------------------------------------------+
//|Получает из БД и сохраняет в массив статистику торговли по магикам|
//+------------------------------------------------------------------+
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;
     }
     
//--- получаем записи из результатов запроса
   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;
     }

//--- удалим запрос после использования
   DatabaseFinalize(request);
   return true; 
  }

Наконец, напишем аналогичную функцию, получающую из базы данных и сохраняющую в массив статистику торговли по счёту:

//+------------------------------------------------------------------+
//| Получает из БД и сохраняет в массив статистику торговли по счёту |
//+------------------------------------------------------------------+
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;
     }
     
//--- получаем записи из результатов запроса
   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;
     }
//--- удалим запрос после использования
   DatabaseFinalize(request);
   return true;
  }

Всё, мы подготовили базовые вещи для создания проекта — выбрали панель и создали функции для работы с базой данных по информации из документации. Нам осталось сделать только логику взаимодействия панели и её таблиц с базой данных посредством написанных функций. Вполне вероятно, что списки, размещаемые в таблицах на панели, будут достаточно обширными, и размеры таблиц будут выходить за размеры панели. В этом случае нам нужно будет прокручивать таблицы по вертикали и горизонтали. Такой функционал сделаем прямо в создаваемом индикаторе — таблицы будут прокручиваться по вертикали вращением колёсика мышки, а по горизонтали — колёсиком мышки с зажатой клавишей Shift.

Статистика по выбранному символу или магику будет выводиться при щелчке по строке статистики нужного символа или магика. Для этого сделаем отслеживание расположения курсора над строками таблицы и щелчка по строке. Правильно делать такой функционал в классе панели — чтобы он мог использоваться в иных проектах. Но здесь покажем, как можно сделать всё то же самое без доработки классов панели.



Собираем проект — информационную панель

В ранее созданной папке \MQL5\Indicators\StatisticsBy\ создадим файл нового индикатора с именем StatisticsBy.mq5.

Подключим файлы классов таблиц и панели, и файл функций для работы с БД, и укажем, что у индикатора нет рисуемых буферов:

//+------------------------------------------------------------------+
//|                                                 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"

Далее впишем макроподстановки, массив расположения столбцов таблиц статистики, входные параметры и глобальные переменные:

#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))   // Имя программы
#define  DB_NAME           (PROGRAM_NAME+"_DB.sqlite")         // Имя базы данных
#define  DATE_FROM         0                                   // Дата начала истории сделок
#define  DATE_TO           (TimeCurrent())                     // Дата окончания истории сделок

//--- Ширина ячеек таблиц
#define  CELL_W_TRADES     94                                  // Ширина ячеек таблицы истории торговли
#define  CELL_W_SYMBOLS    62                                  // Ширина ячеек таблицы символов, используемых в торговле
#define  CELL_W_MAGICS     62                                  // Ширина ячеек таблицы магиков, используемых в торговле
#define  CELL_H            16                                  // Высота ячейки таблицы
//--- Размеры итоговой таблицы статистики
#define  TABLE_STAT_ROWS   9                                   // Количество строк итоговой таблицы статистики
#define  TABLE_STAT_COLS   4                                   // Количество столбцов итоговой таблицы статистики
//--- Таблицы
#define  TABLE_TRADES      1                                   // Идентификатор таблицы истории торговли
#define  TABLE_SYMBOLS     2                                   // Идентификатор таблицы символов, используемых в торговле
#define  TABLE_MAGICS      3                                   // Идентификатор таблицы магиков, используемых в торговле
#define  TABLE_ACCOUNT     4                                   // Идентификатор таблицы статистики счёта
#define  TABLE_STATS       5                                   // Идентификатор итоговой таблицы статистики выбранного символа или магика

//--- Заголовки таблиц (полный/сокращённый)
#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"

//--- Массив расположения столбцов таблиц статистики слева направо
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 */ // Координата X панели
input int                  InpPanelY   =  20;                  /* Dashboard Y */ // Координата Y панели
input int                  InpUniqID   =  0;                   /* Unique ID   */ // Уникальный идентификатор для объекта-панели

//--- global variables
int                        DBHandle;                           // Хэндл базы данных
int                        LPanelTable;                        // Активная панель в левом поле
int                        RPanelTable;                        // Активная панель в правом поле
long                       ArrayMagics[];                      // Массив магиков
string                     ArraySymbols[];                     // Массив символов
STrade                     ArrayTrades[];                      // Массив трейдов
SSymbolStats               ArraySymbolStats[];                 // Массив статистики по символам
SMagicStats                ArrayMagicStats[];                  // Массив статистики по магикам
SAccountStats              ArrayAccountStats[];                // Массив статистики по счёту
CDashboard                *dashboard=NULL;                     // Указатель на экземпляр панели

Для указания количества столбцов таблиц статистики и указания количества и расположения данных в таблице, удобно использовать массив, в котором расположены константы наименований заголовков таблицы и, соответственно, данных под этими заголовками. При необходимости изменения порядка следования различных данных в таблице, достаточно поменять порядок их объявления в этом массиве и перекомпилировать индикатор. Так же можно убрать ненужные данные, закомментировав их в этом массиве, либо добавить новые. Но при добавлении новых данных, нужно будет добавлять их в функции работы с базой данных и в иных функциях, где рассчитываются и отображаются данные таблиц.

Рассмотрим обработчик OnInit() индикатора, где создаётся база данных и панель с её графическим наполнением:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Проверим, что на счете используется хеджинг для учета открытых позиций
   if((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
     {
      //--- Если счёт неттинговый, то мы не можем преобразовать сделки в трейды простым способом через транзакции, поэтому завершаем работу
      Print("For a Netting account, there is no way to convert deals into trades in a simple way.");
      return INIT_FAILED;
     }         
  
//--- Указываем путь расположения и создаём БД (\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);
   
//--- Создаём панель-окно программы
   dashboard = new CDashboard(InpUniqID, InpPanelX, InpPanelY, 601, 300);
   if(dashboard==NULL)
     {
      Print("Error. Failed to create dashboard object");
      return INIT_FAILED;
     }
     
//--- Отображаем панель с текстом названия программы в заголовке окна
   dashboard.SetFontParams("Calibri",8);
   dashboard.SetName("Main");
   dashboard.View(PROGRAM_NAME);
   
//--- Рисуем рабочее пространство
//--- Кнопка для выбора символов
   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);
     }
     
//--- Кнопка для выбора магиков
   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);
     }
     
//--- Кнопка для создания списка трейдов
   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);
     }
     
//--- Панель слева для вывода таблицы символов/магиков
   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);
     }
     
//--- Панель справа для вывода заголовков статистики по списку трейдов и выбранному символу/магику
   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);
     }
     
//--- Панель справа для вывода статистики по списку трейдов и выбранному символу/магику
   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);
     }
     
//--- Все таблицы на левой и правой панелях изначально не активны
   LPanelTable=WRONG_VALUE;
   RPanelTable=WRONG_VALUE;

//--- Всё успешно
   return(INIT_SUCCEEDED);
  }

По сути, здесь сначала проверяется тип учёта позиций, и если это неттинг, то индикатор завершает работу, так как для такого учёта позиций невозможно простым способом, только по сделкам входа-выхода, создать таблицу трейдов.

Далее в каталоге данных терминала (TERMINAL_DATA_PATH + \MQL5\Files\) создаётся база данных в папке с наименованием программы в подкаталоге Database (\StatisticsBy\Database\). После успешного создания базы данных, создаётся панель и наполняется содержимым — кнопками управления и панелями для вывода таблиц:

Что интересно, здесь мы вместо кнопок используем прикреплённые к основному окну дочерние панели в свёрнутом виде — от них виден только заголовок панели. У него есть свои обработчики для взаимодействия с курсором мышки, и таким образом, мы из обычных панелей создали кнопки, которые интерактивно взаимодействуют с пользователем и отправляют в основную программу события взаимодействия с мышью.

В обработчике OnDeinit() закрываем базу данных и панель:

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Закрываем базу данных
   DatabaseClose(DBHandle);
   if(GetLastError()==ERR_DATABASE_INVALID_HANDLE)
      Print("Error. An invalid database handle was passed to the DatabaseClose() function");

//--- Если объект панели существует - удаляем
   if(dashboard!=NULL)
     {
      delete dashboard;
      ChartRedraw();
     }
  }

Обработчик OnCalculate() индикатора оставим пустым (индикатор ничего не рассчитывает):

//+------------------------------------------------------------------+
//| 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 next call
   return(rates_total);
  }

Вся работа по интерактивному взаимодействию панели с пользователем производится в обработчике событий индикатора.

Рассмотрим обработчик событий OnChartEvent() целиком. Его код подробно прокомментирован. При внимательном изучении комментариев обработчика событий, вся логика взаимодействия панели с пользователем будет понятна:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Идентификатор активной панели
   int table_id=WRONG_VALUE;
   
//--- Вызываем обработчик событий панели, которая в свою очередь отправляет сюда свои события
   dashboard.OnChartEvent(id,lparam,dparam,sparam);

//--- Если получили пользовательское событие от панели-окна программы
   if(id>CHARTEVENT_CUSTOM)
     {
      //--- Нажата кнопка закрытия на панели
      if(id==1001)
        {
         //--- Здесь может быть обработка щелчка по кнопке закрытия
        }
      //--- Нажатия кнопок работы с БД - идентификатор всегда 1002, а уточнение - по значению в lparam
      if(id==1002)
        {
         //--- получение истории сделок с сервера и трейдов из БД (нажата кнопка Get trade history)
         if(lparam==3)
           {
            //--- Если история сделок не получена - уходим
            if(!GetHistoryDeals(DATE_FROM, DATE_TO))
               return;
            //--- Если в истории нет сделок - сообщаем об этом и уходим
            int deals_total=HistoryDealsTotal();
            if(deals_total==0)
              {
               Print("No deals in history");
               return;
              }
            //--- создадим таблицу сделок (DEALS) в базе данных
            if(!CreateTableDeals(DBHandle))
               return;
            //--- внесем в созданную таблицу сделки
            if(!InsertDeals(DBHandle))
               return;
            //--- Создадим таблицу трейдов (TRADES) на основе таблицы DEALS
            if(!CreateTableTrades(DBHandle))
               return;
            //--- Заполним через SQL-запрос таблицу TRADES на основе данных из таблицы DEALS
            if(!FillTRADEStableBasedOnDEALStable(DBHandle))
               return;
            //--- Запросим из БД список всех трейдов
            if(!FillsListTradesFromDB(DBHandle, DB_NAME, ArrayTrades))
               return;
            //--- Выведем на панель количество сделок и количество трейдов в истории
            dashboard.DrawText(" ",2,2,clrNONE,0,0);  // стираем ранее написанное
            dashboard.DrawText("Total deals in history: "+(string)deals_total+", trades: "+(string)ArrayTrades.Size(),216,3);


            //--- Получаем указатель на панель заголовков
            CDashboard *panel_h=dashboard.GetPanel("FieldH");
            if(panel_h==NULL)
               return;
            //--- Проверяем наличие и получаем или создаём объект-таблицу для вывода заголовка таблицы трейдов
            CTableData *table_h=NULL;
            if(!panel_h.TableIsExist(TABLE_TRADES) && !panel_h.CreateNewTable(TABLE_TRADES))
               return;
            //--- Получаем указатель на объект-таблицу заголовков трейдов
            table_h=panel_h.GetTable(TABLE_TRADES);
            if(table_h==NULL)
               return;
            //--- Очищаем панель заголовка таблицы и выводим на неё таблицу заголовков
            panel_h.Clear();
            panel_h.DrawGrid(TABLE_TRADES,2,2,1,11,CELL_H,CELL_W_TRADES,C'200,200,200',false);
            //--- Заполняем таблицу заголовков трейдов
            FillsHeaderTradeTable(panel_h,table_h);

            //--- Получаем указатель на правую панель
            CDashboard *panel_r=dashboard.GetPanel("FieldR");
            if(panel_r==NULL)
               return;
            //--- Проверяем наличие и получаем или создаём объект-таблицу для вывода трейдов
            if(!panel_r.TableIsExist(TABLE_TRADES) && !panel_r.CreateNewTable(TABLE_TRADES))
               return;
            //--- Получаем указатель на объект-таблицу трейдов
            CTableData *table_r=panel_r.GetTable(TABLE_TRADES);
            if(table_r==NULL)
               return;
            //--- Очищаем панель и выводим на неё таблицу трейдов
            panel_r.Clear();
            panel_r.DrawGrid(TABLE_TRADES,2,2,ArrayTrades.Size(),11,CELL_H,CELL_W_TRADES,C'220,220,220');
            //--- Заполняем таблицу данными трейдов и указываем, что справа активна таблица TABLE_TRADES
            FillsTradeTable(panel_r,table_r);
            RPanelTable=TABLE_TRADES;
           }
         
         //--- Если нажата кнопка отображения символов
         if(lparam==1)
           {
            //--- запросим из БД список всех символов, на которых осуществлялась торговля, и заполним массив символов
            if(!FillsListSymbolsFromDB(DBHandle, DB_NAME, ArraySymbols))
               return;
            //--- Увеличим массив символов на 1 для записи в него пункта "Все символы" (ALL)
            int size=(int)ArraySymbols.Size();
            if(ArrayResize(ArraySymbols, size+1)==size+1)
               ArraySymbols[size]="ALL";
            //--- Получаем указатель на левую панель
            CDashboard *panel=dashboard.GetPanel("FieldL");
            if(panel==NULL)
               return;
            //--- Проверяем наличие и получаем или создаём объект-таблицу для вывода списка символов
            CTableData *table=NULL;
            if(!panel.TableIsExist(TABLE_SYMBOLS) && !panel.CreateNewTable(TABLE_SYMBOLS))
               return;
            //--- Получаем указатель на объект-таблицу
            table=panel.GetTable(TABLE_SYMBOLS);
            if(table==NULL)
               return;
            //--- Очищаем панель и рисуем на ней таблицу символов
            panel.Clear();
            panel.DrawGrid(TABLE_SYMBOLS,2,2,ArraySymbols.Size(),1,CELL_H,panel.Width()-5,C'220,220,220');
            //--- Заполняем таблицу наименованиями символов и указываем, что на левой панели активна таблица TABLE_SYMBOLS
            FillsSymbolTable(panel,table);
            LPanelTable=TABLE_SYMBOLS;
            
            //--- получим торговую статистику в разрезе символов
            if(!GetTradingStatsBySymbols(DBHandle, DB_NAME, ArraySymbolStats))
               return;

            //--- Выведем на панель количество символов, использованных в торговле
            dashboard.DrawText(" ",2,2,clrNONE,0,0);  // Стираем всё содержимое панели
            dashboard.DrawText("Total number of symbols used in trade: "+(string)ArraySymbols.Size(),216,3);
            
            //--- Получаем указатель на панель заголовков
            CDashboard *panel_h=dashboard.GetPanel("FieldH");
            if(panel_h==NULL)
               return;
            //--- Проверяем наличие и получаем или создаём объект-таблицу для вывода заголовка таблицы статистики символов
            CTableData *table_h=NULL;
            if(!panel_h.TableIsExist(TABLE_SYMBOLS) && !panel_h.CreateNewTable(TABLE_SYMBOLS))
               return;
            //--- Получаем указатель на объект-таблицу заголовков статистики символов
            table_h=panel_h.GetTable(TABLE_SYMBOLS);
            if(table_h==NULL)
               return;
            //--- Очищаем панель заголовка таблицы и выводим на неё таблицу
            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);
            //--- Заполняем таблицу заголовка статистики символов
            FillsHeaderTradingStatsTable(panel_h,table_h);

            //--- Получаем указатель на правую панель
            CDashboard *panel_r=dashboard.GetPanel("FieldR");
            if(panel_r==NULL)
               return;
            //--- Проверяем наличие и получаем или создаём объект-таблицу для вывода статистики символов
            if(!panel_r.TableIsExist(TABLE_SYMBOLS) && !panel_r.CreateNewTable(TABLE_SYMBOLS))
               return;
            //--- Получаем указатель на объект-таблицу статистики символов
            CTableData *table_r=panel_r.GetTable(TABLE_SYMBOLS);
            if(table_r==NULL)
               return;
            //--- Очищаем панель и выводим на неё таблицу статистики символов
            panel_r.Clear();
            panel_r.DrawGrid(TABLE_SYMBOLS,2,2,ArraySymbolStats.Size(),ArrayDataName.Size(),CELL_H,CELL_W_SYMBOLS,C'220,220,220');
            //--- Заполняем таблицу данными статистики символов и указываем, что справа активна таблица TABLE_SYMBOLS
            FillsTradingStatsBySymbolsTable(panel_r,table_r);
            RPanelTable=TABLE_SYMBOLS;
           }

         //--- Если нажата кнопка отображения магиков
         if(lparam==2)
           {
            //--- Запросим из БД список всех магиков, на которых осуществлялась торговля, и заполним массив магиков
            if(!FillsListMagicsFromDB(DBHandle, DB_NAME, ArrayMagics))
               return;
            //--- Увеличим массив магиков на 1 для записи в него пункта "Все магики" (значение LONG_MAX указывает на это)
            int size=(int)ArrayMagics.Size();
            if(ArrayResize(ArrayMagics, size+1)==size+1)
               ArrayMagics[size]=LONG_MAX;
            //--- Получаем указатель на левую панель
            CDashboard *panel=dashboard.GetPanel("FieldL");
            if(panel==NULL)
               return;
            //--- Проверяем наличие и получаем или создаём объект-таблицу для вывода магиков
            CTableData *table=NULL;
            if(!panel.TableIsExist(TABLE_MAGICS) && !panel.CreateNewTable(TABLE_MAGICS))
               return;
            //--- Получаем указатель на объект-таблицу
            table=panel.GetTable(TABLE_MAGICS);
            if(table==NULL)
               return;
            //--- Очищаем панель и рисуем на ней таблицу магиков
            panel.Clear();
            panel.DrawGrid(TABLE_MAGICS,2,2,ArrayMagics.Size(),1,CELL_H,panel.Width()-5,C'220,220,220');
            //--- Заполняем таблицу значениями магиков и указываем, что на левой панели активна таблица TABLE_MAGICS
            FillsMagicTable(panel,table);
            LPanelTable=TABLE_MAGICS;
            
            //--- Получаем торговую статистику в разрезе магиков
            if(!GetTradingStatsByMagics(DBHandle, DB_NAME, ArrayMagicStats))
               return;

            //--- Выводим на панель количество магиков, использованных в торговле
            dashboard.DrawText(" ",2,2,clrNONE,0,0);
            dashboard.DrawText("Total number of magics used in trade: "+(string)ArrayMagics.Size(),216,3);            
            
            //--- Получаем указатель на панель заголовков
            CDashboard *panel_h=dashboard.GetPanel("FieldH");
            if(panel_h==NULL)
               return;
            //--- Проверяем наличие и получаем или создаём объект-таблицу для вывода заголовка таблицы статистики магиков
            CTableData *table_h=NULL;
            if(!panel_h.TableIsExist(TABLE_MAGICS) && !panel_h.CreateNewTable(TABLE_MAGICS))
               return;
            //--- Получаем указатель на объект-таблицу заголовков статистики магиков
            table_h=panel_h.GetTable(TABLE_MAGICS);
            if(table_h==NULL)
               return;
            //--- Очищаем панель заголовка таблицы и выводим на неё таблицу
            panel_h.Clear();
            panel_h.DrawGrid(TABLE_MAGICS,2,2,1,ArrayDataName.Size(),CELL_H,CELL_W_MAGICS,C'200,200,200',false);
            //--- Заполняем таблицу заголовка статистики символов
            FillsHeaderTradingStatsTable(panel_h,table_h);

            //--- Получаем указатель на правую панель
            CDashboard *panel_r=dashboard.GetPanel("FieldR");
            if(panel_r==NULL)
               return;
            //--- Проверяем наличие и получаем или создаём объект-таблицу для вывода статистики магиков
            if(!panel_r.TableIsExist(TABLE_MAGICS) && !panel_r.CreateNewTable(TABLE_MAGICS))
               return;
            //--- Получаем указатель на объект-таблицу статистики магиков
            CTableData *table_r=panel_r.GetTable(TABLE_MAGICS);
            if(table_r==NULL)
               return;
            //--- Очищаем панель и выводим на неё таблицу статистики магиков
            panel_r.Clear();
            panel_r.DrawGrid(TABLE_MAGICS,2,2,ArrayMagicStats.Size(),ArrayDataName.Size(),CELL_H,CELL_W_MAGICS,C'220,220,220');
            //--- Заполняем таблицу данными статистики магиков и указываем, что активна таблица TABLE_MAGICS
            FillsTradingStatsByMagicsTable(panel_r,table_r);
            RPanelTable=TABLE_MAGICS;
           }
        }
     }
     
//--- Если получили событие прокрутки колёсика мышки
   if(id==CHARTEVENT_MOUSE_WHEEL)
     {
      static int index_l_p=WRONG_VALUE;            // Прошлый индекс строки под курсором в таблице на левой панели
      static int index_r_p=WRONG_VALUE;            // Прошлый индекс строки под курсором в таблице на правой панели
      
       //--- разберем состояние кнопок и колесика мышки для этого события  
      int flg_keys = (int)(lparam>>32);          // флаг состояний клавиш Ctrl, Shift и кнопок мышки 
      int x_cursor = (int)(short)lparam;         // X-координата, в которой произошло событие колесика мышки 
      int y_cursor = (int)(short)(lparam>>16);   // Y-координата, в которой произошло событие колесика мышки 
      int delta    = (int)dparam;                // суммарное значение прокрутки колесика, срабатывает при достижении +120 или -120 
      
      //--- Получаем указатель на левую панель и вызываем для неё обработчик прокрутки колёсика мышки
      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);
      
      //--- Получаем указатель на правую панель и вызываем для неё обработчик прокрутки колёсика мышки
      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);
      
      //--- При необходимости можно обработать строку таблицы, над которой находится курсор.
      //--- Номер строки записан в index_l для левой панели и в index_r - для правой
      
      //--- Обновляем график после всех изменений, произошедших в обработчиках прокрутки колёсика мышки
      if(index_l_p!=index_l)
        {
         index_l_p=index_l;
         ChartRedraw();
        }
      if(index_r_p!=index_r)
        {
         index_r_p=index_r;
         ChartRedraw();
        }
     }
     
//--- Если получили событие перемещения мышки
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      static int index_l_p=WRONG_VALUE;            // Прошлый индекс строки под курсором в таблице на левой панели
      static int index_r_p=WRONG_VALUE;            // Прошлый индекс строки под курсором в таблице на правой панели
      
      int x_cursor = (int)lparam;                  // X-координата курсора мышки 
      int y_cursor = (int)dparam;                  // Y-координата курсора мышки 
      
      //--- Получаем указатель на левую панель и вызываем для неё обработчик перемещения мышки
      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);
      
      //--- Получаем указатель на правую панель и вызываем для неё обработчик перемещения мышки
      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);
      
      //--- При необходимости можно обработать строку таблицы, над которой находится курсор.
      //--- Номер строки записан в index_l для левой панели и в index_r - для правой
      
      //--- Обновляем график после всех изменений, произошедших в обработчиках перемещения мышки
      if(index_l_p!=index_l)
        {
         index_l_p=index_l;
         ChartRedraw();
        }
      if(index_r_p!=index_r)
        {
         index_r_p=index_r;
         ChartRedraw();
        }
     }
     
//--- Если получили событие щелчка мышки
   if(id==CHARTEVENT_CLICK)
     {
      int x_cursor = (int)lparam;                  // X-координата курсора мышки 
      int y_cursor = (int)dparam;                  // Y-координата курсора мышки 
      
      //--- Получаем указатель на левую панель и вызываем обработчик щелчка мышки
      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);
      
      //--- Получаем указатель на правую панель и вызываем обработчик щелчка мышки
      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);

      
      //--- Обработаем строку таблицы, по которой был щелчок
      //--- Если в левой панели был щелчок по символу из списка
      if(LPanelTable==TABLE_SYMBOLS && index_l>WRONG_VALUE)
        {
         //--- Получим таблицу символов с левой панели
         CTableData *table=panel_l.GetTable(TABLE_SYMBOLS);
         if(table==NULL)
            return;
         //--- Получим единственную ячейку таблицы с номером строки index_l
         CTableCell *cell=table.GetCell(index_l,0);
         if(cell==NULL)
            return;
         //--- Если щелчок по последнему пункту (ALL)
         if(index_l==ArraySymbols.Size()-1)
           {
            //--- получим и отобразим торговую статистику аккаунта, и укажем, что справа активна панель TABLE_STATS
            if(!GetTradingStatsByAccount(DBHandle, DB_NAME, ArrayAccountStats))
               return;
            if(ViewStatistic(TABLE_ACCOUNT,(string)AccountInfoInteger(ACCOUNT_LOGIN)))
               RPanelTable=TABLE_STATS;
           }
         //--- Щелчок по наименованию символа - отобразим статистику по символу и укажем, что справа активна панель TABLE_STATS
         else
           {
            if(ViewStatistic(TABLE_SYMBOLS,cell.Text()))
               RPanelTable=TABLE_STATS;
           }
        }
      
      //--- Если в левой панели был щелчок по магику из списка
      if(LPanelTable==TABLE_MAGICS && index_l>WRONG_VALUE)
        {
         //--- Получим таблицу магиков с левой панели
         CTableData *table=panel_l.GetTable(TABLE_MAGICS);
         if(table==NULL)
            return;
         //--- Получим единственную ячейку таблицы с номером строки index_l
         CTableCell *cell=table.GetCell(index_l,0);
         if(cell==NULL)
            return;
         //--- Если щелчок по последнему пункту (ALL)
         if(index_l==ArrayMagics.Size()-1)
           {
            //--- получим и отобразим торговую статистику аккаунта, и укажем, что справа активна панель TABLE_STATS
            if(!GetTradingStatsByAccount(DBHandle, DB_NAME, ArrayAccountStats))
               return;
            if(ViewStatistic(TABLE_ACCOUNT,(string)AccountInfoInteger(ACCOUNT_LOGIN)))
               RPanelTable=TABLE_STATS;
           }
         //--- Щелчок по значению магика - отобразим статистику по магику и укажем, что справа активна панель TABLE_STATS
         else
           {
            if(ViewStatistic(TABLE_MAGICS,cell.Text()))
               RPanelTable=TABLE_STATS;
           }
        }

      //--- Если в правой панели был щелчок по символу из списка
      if(RPanelTable==TABLE_SYMBOLS && index_r>WRONG_VALUE)
        {
         //--- Получим таблицу статистики символов с правой панели
         CTableData *table=panel_r.GetTable(TABLE_SYMBOLS);
         if(table==NULL)
            return;
         //--- Получим нулевую ячейку таблицы с номером строки index_r
         CTableCell *cell=table.GetCell(index_r,0);
         if(cell==NULL)
            return;
         //--- Отобразим итоговую статистику по символу и укажем, что справа активна панель TABLE_STATS
         if(ViewStatistic(TABLE_SYMBOLS,cell.Text()))
            RPanelTable=TABLE_STATS;
        }
      
      //--- Если в правой панели был щелчок по магику из списка
      if(RPanelTable==TABLE_MAGICS && index_r>WRONG_VALUE)
        {
         //--- Получим таблицу статистики магиков с правой панели
         CTableData *table=panel_r.GetTable(TABLE_MAGICS);
         if(table==NULL)
            return;
         //--- Получим нулевую ячейку таблицы с номером строки index_r
         CTableCell *cell=table.GetCell(index_r,0);
         if(cell==NULL)
            return;
         //--- Отобразим итоговую статистику по магику и укажем, что справа активна панель TABLE_STATS
         if(ViewStatistic(TABLE_MAGICS,cell.Text()))
            RPanelTable=TABLE_STATS;
        }
     }
  }

Теперь рассмотрим остальные функции, которые вызываются из обработчика событий. Код каждой функции подробно прокомментирован, и не должен вызывать непонимания.

Функция, возвращающая индекс строки таблицы по координатам курсора:

//+------------------------------------------------------------------+
//| Возвращает индекс строки таблицы по координатам курсора          |
//+------------------------------------------------------------------+
int TableSelectRowByMouse(const int x_cursor, const int y_cursor, const int cell_h, CDashboard *panel, CTableData *table)
  {
//--- Проверяем указатели на панель и таблицу
   if(panel==NULL || table==NULL)
      return WRONG_VALUE;
      
   int index=WRONG_VALUE;  // Индекс строки таблицы, расположенной под курсором
   
//--- В цикле по строкам таблицы
   int total=table.RowsTotal();
   for(int i=0;i<total;i++)
     {
      //--- получаем очередную нулевую ячейку таблицы в строке с индексом цикла
      CTableCell *cell=table.GetCell(i,0);
      if(cell==NULL)
         continue;
      
      //--- Рассчитываем верхнюю и нижнюю координаты расположения строки таблицы по координате Y ячейки
      int y1=panel.CoordY()+cell.Y()+1;
      int y2=y1+cell_h;
      
      //--- Если курсор по вертикали находится внутри рассчитанных координат строки таблицы
      if(y_cursor>y1 && y_cursor<y2)
        {
         //--- Записываем индекс строки, рисуем прямоугольную область на всю ширину таблицы (выделение строки под курсором) и возвращаем индекс строки
         index=cell.Row();
         panel.DrawRectangleFill(2,cell.Y()+1,panel.Width()-4,y2-y1-1,C'220,220,220',240);
         return index;
        }
     }
//--- Ничего не нашли
   return WRONG_VALUE;
  }

Функция выполняет сразу две задачи: (1) возвращает номер строки таблицы, над которой находится курсор мышки, и (2) подсвечивает эту строку фоновым цветом.

Функция-обработчик прокрутки колёсика мышки внутри таблицы левой панели:

//+------------------------------------------------------------------+
//| Обработчик прокрутки колёсика мышки внутри таблицы левой панели  |
//+------------------------------------------------------------------+
int TableMouseWhellHandlerL(const int x_cursor,const int y_cursor,const bool shift_flag,const int delta,CDashboard *panel,const int table_id)
  {
//--- Проверяем указатель на левую панель
   if(panel==NULL)
      return WRONG_VALUE;
   
//--- Проверяем расположение курсора внутри панели
   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;
   
//--- Проверяем наличие таблицы на панели
   if(!panel.TableIsExist(table_id))
      return WRONG_VALUE;
   
//--- Получаем указатель на активную таблицу на панели
   CTableData *table=panel.GetTable(table_id);
   if(table==NULL)
      return WRONG_VALUE;
   
//--- Рассчитываем смещение таблицы на половину высоты строки таблицы
   int shift=CELL_H/2*(delta<0 ? -1 : 1);
   
//--- Рассчитываем координаты, в пределах которых смещается таблица
   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;
   
//--- Очищаем панель и выводим на неё активную таблицу
   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');
   
//--- Заполняем таблицу значениями
   if(table_id==TABLE_SYMBOLS)
      FillsSymbolTable(panel,table);
   else
      FillsMagicTable(panel,table);
   
//--- Получаем номер строки таблицы, над которой расположен курсор
   int index=TableSelectRowByMouse(x_cursor,y_cursor,CELL_H,panel,table);
   return index;
  }

При прокрутке колёсика мышки, если курсор находится над таблицей на панели, таблицу необходимо тоже прокручивать, в случае, если её размер превышает размер панели. Это и делает этот обработчик — сдвигает таблицу по указанным начальным координатам. Кроме того, строка, находящаяся под курсором, подсвечивается при помощи функции TableSelectRowByMouse(), и возвращается индекс строки под курсором, возвращаемый этой функцией. В левую панель выводятся небольшие по размерам списки символов и магиков, поэтому здесь выполнена упрощенная прокрутка — сразу смещаем таблицу на рассчитанные координаты. Для правой панели будет немного сложнее.

Функция-обработчик смещения курсора мышки внутри таблицы левой панели:

//+------------------------------------------------------------------+
//| Обработчик смещения курсора мышки внутри таблицы левой панели    |
//+------------------------------------------------------------------+
int TableMouseMoveHandlerL(const int x_cursor,const int y_cursor,CDashboard *panel,const int table_id)
  {
//--- Проверяем указатель на левую панель
   if(panel==NULL)
      return WRONG_VALUE;
   
//--- Проверяем наличие таблицы на панели
   if(!panel.TableIsExist(table_id))
      return WRONG_VALUE;
   
//--- Получаем указатель на активную таблицу на панели
   CTableData *table=panel.GetTable(table_id);
   if(table==NULL)
      return WRONG_VALUE;
      
//--- Проверяем расположение курсора внутри панели
//--- Если курсор за пределами панели - рисуем активную таблицу и возвращаем -1 (чтобы убрать выделение строки, над которой был курсор)
   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;
     }
      
//--- Очищаем панель и выводим на неё активную таблицу
   panel.Clear();
   panel.DrawGrid(table_id,2,table.Y1(),total,1,CELL_H,panel.Width()-5,C'220,220,220');
   
//--- Заполняем таблицу значениями
   if(table_id==TABLE_SYMBOLS)
      FillsSymbolTable(panel,table);
   else
      FillsMagicTable(panel,table);
   
//--- Получаем и возвращаем номер строки таблицы, над которой расположен курсор
   int index=TableSelectRowByMouse(x_cursor,y_cursor,CELL_H,panel,table);
   return index;
  }

Здесь также, как и в предыдущей функции, ищется и выделяется строка под курсором. Но таблица, естественно, не прокручивается.

Функция-обработчик прокрутки колёсика мышки внутри таблицы правой панели:

//+------------------------------------------------------------------+
//| Обработчик прокрутки колёсика мышки внутри таблицы правой панели |
//+------------------------------------------------------------------+
int TableMouseWhellHandlerR(const int x_cursor,const int y_cursor,const bool shift_flag,const int delta,CDashboard *panel,const int table_id)
  {
//--- Проверяем указатель на правую панель
   if(panel==NULL)
      return WRONG_VALUE;
   
//--- Проверяем расположение курсора внутри панели
   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;
   
//--- Проверяем наличие таблицы на панели
   if(!panel.TableIsExist(table_id))
      return WRONG_VALUE;
   
//--- Получаем указатель на активную таблицу на панели
   CTableData *table=panel.GetTable(table_id);
   if(table==NULL)
      return WRONG_VALUE;
//--- Рассчитываем вертикальное смещение таблицы на половину высоты строки таблицы
   int shift_y=CELL_H/2*(delta<0 ? -1 : 1);
   
//--- Рассчитываем горизонтальное смещение таблицы на размер высоты строки таблицы
   int shift_x=(shift_flag ? CELL_H*(delta<0 ? -1 : 1) : 0);
   
//--- Рассчитываем координаты, в пределах которых смещается таблица по 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();
   
//--- Рассчитываем координаты, в пределах которых смещается таблица по 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();
     }
   
//--- Если вся таблица умещается в размеры панели - ничего прокручивать не нужно, возвращаем -1
   if(table.Height()<panel.Height() && table.Width()<panel.Width())
      return WRONG_VALUE;
      
//---  Определяем размеры таблицы
   int total=0;         // количество строк
   int columns=0;       // количество столбцов
   int cell_w=0;        // ширина ячейки таблицы (столбца)
   int cell_h=CELL_H;   // высота ячейки таблицы (строки)
   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;
     }
//--- Очищаем панель и выводим на неё активную таблицу
   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));
   
//--- Заполняем таблицу значениями
   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;
     }
   
//--- Получаем указатель на панель заголовка
   CDashboard *panel_h=dashboard.GetPanel("FieldH");
   if(panel_h==NULL)
      return WRONG_VALUE;
//--- Получаем указатель на таблицу заголовка
   CTableData *table_h=panel_h.GetTable(table_id);
   if(table_h==NULL)
      return WRONG_VALUE;   
   //--- Очищаем панель заголовка таблицы и выводим на неё таблицу
   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);
   //--- Заполняем таблицу заголовков
   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;
     }
   
//--- Для таблицы итоговой статистики номер строки под курсором искать не нужно
   if(table.ID()==TABLE_STATS)
      return WRONG_VALUE;
      
//--- Получаем номер строки таблицы, над которой расположен курсор
   int index=TableSelectRowByMouse(x_cursor,y_cursor,cell_h,panel,table);
   return index;
  }

Здесь в одной функции обрабатываются сразу три таблицы и заголовки этих же таблиц. Всё зависит от типа таблицы, переданного в функцию. При прокрутке больших таблиц, их необходимо прокручивать не только по высоте, но и по ширине. За прокрутку по ширине отвечает флаг shift_flag — это признак удерживаемой клавиши Shift при вращении колёсика мышки. При прокрутке самой таблицы, одновременно с ней прокручивается и заголовок, расположенный на другой панели.

Функция-обработчик смещения курсора мышки внутри таблицы правой панели:

//+------------------------------------------------------------------+
//| Обработчик смещения курсора мышки внутри таблицы правой панели   |
//+------------------------------------------------------------------+
int TableMouseMoveHandlerR(const int x_cursor,const int y_cursor,CDashboard *panel,const int table_id)
  {
//--- Проверяем указатель на левую панель
   if(panel==NULL)
      return WRONG_VALUE;
   
//--- Проверяем наличие таблицы на панели
   if(!panel.TableIsExist(table_id))
      return WRONG_VALUE;
   
//--- Получаем указатель на активную таблицу на панели
   CTableData *table=panel.GetTable(table_id);
   if(table==NULL)
      return WRONG_VALUE;
   
//---  Определяем размеры таблицы
   int total=0;         // количество строк
   int columns=0;       // количество столбцов
   int cell_w=0;        // ширина ячейки таблицы (столбца)
   int cell_h=CELL_H;   // высота ячейки таблицы (строки)
   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;
     }
   
//--- Проверяем расположение курсора внутри панели
//--- Если курсор за пределами панели - рисуем активную таблицу и возвращаем -1 (чтобы убрать выделение строки, над которой был курсор)
   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;
     }
      
//--- Очищаем панель и выводим на неё активную таблицу
   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));
   
//--- Заполняем таблицу значениями
   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;
     }

//--- Для таблицы итоговой статистики номер строки под курсором искать не нужно
   if(table.ID()==TABLE_STATS)
      return WRONG_VALUE;
      
//--- Получаем номер строки таблицы, над которой расположен курсор
   int index=TableSelectRowByMouse(x_cursor,y_cursor,cell_h,panel,table);
   return index;
  }

Вообще, здесь (да и в функциях, рассмотренных выше) нам нужно только найти номер строки таблицы, над которой находится курсор. Всё остальное — это обслуживание "красивостей" для выделения строки под курсором, что в итоге приводит к повышенному потреблению ресурсов процессора. Ведь нам приходится постоянно перерисовывать всю видимую часть таблицы и, чем больше по размерам панель и таблица на ней, тем большее пространство приходится обрисовывать. Конечно же, здесь (и далее) ограничено физически рисуемое пространство размерами панели, но всё равно это не оптимально. Если бы мы делали такие выделения строк в классе панели, то было бы всё иначе — мы бы перерисовывали только прилегающие к курсору строки таблицы, запоминая цвет фона до- и после подсветки, потом его восстанавливая. Но здесь, так как только в качестве примера, мы делаем всё прямо в функциях программы, и не будем сильно всё усложнять.

Функция-обработчик щелчка мышки внутри таблицы на панели:

//+------------------------------------------------------------------+
//| Обработчик щелчка мышки внутри таблицы на панели                 |
//+------------------------------------------------------------------+
int TableMouseClickHandler(const int x_cursor,const int y_cursor,CDashboard *panel,const int table_id)
  {
//--- Проверяем указатель на левую панель
   if(panel==NULL)
      return WRONG_VALUE;
      
//--- Проверяем расположение курсора внутри панели
   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;
      
//--- Проверяем наличие таблицы на панели
   if(!panel.TableIsExist(table_id))
      return WRONG_VALUE;
   
//--- Получаем указатель на активную таблицу на панели
   CTableData *table=panel.GetTable(table_id);
   if(table==NULL)
      return WRONG_VALUE;
   
//--- Для таблицы итоговой статистики номер строки под курсором искать не нужно
   if(table.ID()==TABLE_STATS)
      return WRONG_VALUE;
      
//--- Получаем номер строки таблицы, на которой был щелчок
   int index=TableSelectRowByMouse(x_cursor,y_cursor,CELL_H,panel,table);
   return index;
  }

При щелчке мышкой по строке таблицы, нам нужно найти и вернуть номер строки, внутри которой был щелчок, чтобы далее его обработать. Что, впрочем, уже и было показано в обработчике пользовательских событий в OnChartEvent().

Функция, заполняющая таблицу наименованиями символов:

//+------------------------------------------------------------------+
//| Заполняет таблицу наименованиями символов                        |
//+------------------------------------------------------------------+
void FillsSymbolTable(CDashboard *panel,CTableData *table)
  {
//--- Проверяем указатели на панель и таблицу
   if(panel==NULL || table==NULL)
      return;
      
//--- Рассчитаем индекс строки, с которой необходимо начать заполнение таблицы
   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;
      
//--- Заполняем таблицу значениями из массива, начиная со строки index
   for(int i=index;i<(int)ArraySymbols.Size();i++)
     {
      CTableCell *cell=table.GetCell(i,0);
      if(cell==NULL)
         continue;
      
      //--- невидимые области таблицы не рисуем
      if(cell.X()>panel.CoordX()+panel.Width())
         continue;
      if(cell.Y()>panel.CoordY()+panel.Height())
         break;

      //--- выводим данные из массива в ячейки таблицы
      cell.SetText(ArraySymbols[i]);
      panel.DrawText(cell.Text(),cell.X()+2,cell.Y()+1);
     }
  }

Здесь, в дополнение к тому, что области таблицы, выходящие за пределы панели, не рисуются, мы ещё ограничиваем начало цикла той строкой таблицы, которая видима сверху самой первой. Поясню: при прокрутке таблицы вверх, самая первая строка может уйти далеко за пределы панели сверху. И чтобы лишний раз не "крутить цикл" и не пытаться рисовать за пределами панели те строки таблицы, которые всё равно не будут рисоваться, нам нужно рассчитать индекс первой видимой сверху строки таблицы, и с него начать цикл. При достаточно больших таблицах такой подход даёт ощутимый результат, убирая сильные замедления при прокрутке таблицы с сотнями строк.

Функция, заполняющая таблицу значениями магиков:

//+------------------------------------------------------------------+
//| Заполняет таблицу значениями магиков                             |
//+------------------------------------------------------------------+
void FillsMagicTable(CDashboard *panel,CTableData *table)
  {
//--- Проверяем указатели на панель и таблицу
   if(panel==NULL || table==NULL)
      return;
      
//--- Рассчитаем индекс строки, с которой необходимо начать заполнение таблицы
   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;
      
//--- Заполняем таблицу значениями из массива, начиная со строки index
   for(int i=index;i<(int)ArrayMagics.Size();i++)
     {
      CTableCell *cell=table.GetCell(i,0);
      if(cell==NULL)
         continue;
      
      //--- невидимые области таблицы не рисуем
      if(cell.X()>panel.CoordX()+panel.Width())
         continue;
      if(cell.Y()>panel.CoordY()+panel.Height())
         break;

      //--- выводим данные из массива в ячейки таблицы
      string text=(i<(int)ArrayMagics.Size()-1 ? (string)ArrayMagics[i] : "ALL");
      cell.SetText(text);
      panel.DrawText(cell.Text(),cell.X()+2,cell.Y()+1);
     }
  }

Функция, заполняющая таблицу заголовков трейдов:

//+------------------------------------------------------------------+
//| Заполняет таблицу заголовков трейдов                             |
//+------------------------------------------------------------------+
void FillsHeaderTradeTable(CDashboard *panel,CTableData *table)
  {
//--- Проверяем указатели на панель и таблицу
   if(panel==NULL || table==NULL)
      return;
//--- Заполняем таблицу значениями
   int total=11;                    // 11 столбцов таблицы
   CTableCell *cell=NULL;
   for(int i=0;i<total;i++)
     {
      //--- Получаем ячейку i таблицы из нулевой (и единственной) строки таблицы
      cell=table.GetCell(0,i);
      if(cell==NULL)
         continue;
      
      //--- невидимые области таблицы не рисуем
      if(cell.X()>panel.CoordX()+panel.Width())
         continue;
      if(cell.Y()>panel.CoordY()+panel.Height())
         break;
         
      //--- Записываем наименования заголовков в зависимости от индекса цикла
      string cell_text="";
      switch(i)
        {
         case 0   :  cell_text="Time Entry In";    break;   // время входа
         case 1   :  cell_text="Position ID";      break;   // ID позиции
         case 2   :  cell_text="Position Type";    break;   // покупка или продажа
         case 3   :  cell_text="Volume";           break;   // объем
         case 4   :  cell_text="Symbol";           break;   // символ
         case 5   :  cell_text="Price Entry In";   break;   // цена входа
         case 6   :  cell_text="Time Entry Out";   break;   // время выхода
         case 7   :  cell_text="Price Entry Out";  break;   // цена выхода
         case 8   :  cell_text="Commission";       break;   // комиссия за вход и выход
         case 9   :  cell_text="Swap";             break;   // своп
         case 10  :  cell_text="Profit";           break;   // прибыль или убыток
         default  :  break;
        }
      //--- выводим записи в ячейки таблицы
      cell.SetText(cell_text);
      panel.DrawText(cell.Text(),cell.X()+6,cell.Y()+2);
     }
  }

Функция, заполняющая таблицу трейдов:

//+------------------------------------------------------------------+
//| Заполняет таблицу трейдов                                        |
//+------------------------------------------------------------------+
void FillsTradeTable(CDashboard *panel,CTableData *table)
  {
//--- Проверяем указатели на панель и таблицу
   if(panel==NULL || table==NULL)
      return;
      
//--- Заполняем таблицу значениями из массива
   CTableCell *cell=NULL;
   int total=(int)ArrayTrades.Size();
   if(total==0)
     {
      PrintFormat("%s: Error: Trades array is empty",__FUNCTION__);
      return;
     }
     
//--- Рассчитаем индекс строки, с которой необходимо начать заполнение таблицы
   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;
   
//--- В цикле по количеству строк (размер массива трейдов), начиная со строки index
   for(int i=index;i<total;i++)
     {
      //--- в цикле по количеству столбцов (11 для данной таблицы)
      for(int j=0;j<11;j++)
        {
         //--- получаем очередную ячейку таблицы
         cell=table.GetCell(i,j);
         if(cell==NULL)
            continue;
         
         //--- невидимые области таблицы не рисуем
         if(cell.X()>panel.CoordX()+panel.Width())
            continue;
         if(cell.Y()>panel.CoordY()+panel.Height())
            break;
         
         //--- Получаем данные таблицы из массива трейдов
         string cell_text="";
         int digits=(int)SymbolInfoInteger(ArrayTrades[i].symbol,SYMBOL_DIGITS);
         switch(j)
           {
            case 0   :  cell_text=TimeToString(ArrayTrades[i].time_in);             break;   // время входа
            case 1   :  cell_text=IntegerToString(ArrayTrades[i].ticket);           break;   // ID позиции
            case 2   :  cell_text=(ArrayTrades[i].type==0 ? "Buy" : "Sell");        break;   // покупка или продажа
            case 3   :  cell_text=DoubleToString(ArrayTrades[i].volume,2);          break;   // объем
            case 4   :  cell_text=ArrayTrades[i].symbol;                            break;   // символ
            case 5   :  cell_text=DoubleToString(ArrayTrades[i].price_in,digits);   break;   // цена входа
            case 6   :  cell_text=TimeToString(ArrayTrades[i].time_out);            break;   // время выхода
            case 7   :  cell_text=DoubleToString(ArrayTrades[i].price_out,digits);  break;   // цена выхода
            case 8   :  cell_text=DoubleToString(ArrayTrades[i].commission,2);      break;   // комиссия за вход и выход
            case 9   :  cell_text=DoubleToString(ArrayTrades[i].swap,2);            break;   // своп
            case 10  :  cell_text=DoubleToString(ArrayTrades[i].profit,2);          break;   // прибыль или убыток
            default  :  break;
           }
         //--- выводим записи в ячейки таблицы
         cell.SetText(cell_text);
         panel.DrawText(cell.Text(),cell.X()+6,cell.Y()+1);
        }
     }
  }

Две функции, по сути, рисуют одну таблицу. Одна функция рисует заголовок таблицы со столбцами, озаглавленными в зависимости от индекса столбца, а вторая функция рисует под заголовком таблицу со значениями, соответствующими заголовкам столбцов. В общем — функции работают в паре.

Функция, заполняющая таблицу заголовков статистики торговли:

//+------------------------------------------------------------------+
//| Заполняет таблицу заголовков статистики торговли                 |
//+------------------------------------------------------------------+
void FillsHeaderTradingStatsTable(CDashboard *panel,CTableData *table)
  {
//--- Проверяем указатели на панель и таблицу
   if(panel==NULL || table==NULL)
      return;
      
//--- Заполняем таблицу значениями в цикле по количеству столбцов (по размеру данных в ArrayDataName)
   int total=(int)ArrayDataName.Size();
   CTableCell *cell=NULL;
   for(int i=0;i<total;i++)
     {
      //--- получаем очередную ячейку таблицы
      cell=table.GetCell(0,i);
      if(cell==NULL)
         continue;
      
      //--- невидимые области таблицы не рисуем
      if(cell.X()>panel.CoordX()+panel.Width())
         continue;
      if(cell.Y()>panel.CoordY()+panel.Height())
         break;
         
      //--- Записываем наименования заголовков в зависимости от индекса цикла
      string cell_text=(i>0 ? ArrayDataName[i] : table.ID()==TABLE_SYMBOLS ? "Symbol" : "Magic");
      //--- выводим записи в ячейки таблицы
      cell.SetText(cell_text);
      panel.DrawText(cell.Text(),cell.X()+6,cell.Y()+2);
     }
  }

При выводе статистической таблицы по символам или магикам, требуется нарисовать заголовок таблицы статистики. Но у нас есть две таблицы со статистическими данными: одна по символам, вторая — по магикам. Заголовки этих таблиц отличаются лишь первым столбцом, а все остальные одинаковые. Здесь проверяется идентификатор таблицы и, в зависимости от него, заголовок первого столбца подписывается либо символом, либо магиком.

Функция, заполняющая таблицу статистики торговли по символам:

//+------------------------------------------------------------------+
//| Заполняет таблицу статистики торговли по символам                |
//+------------------------------------------------------------------+
void FillsTradingStatsBySymbolsTable(CDashboard *panel,CTableData *table)
  {
//--- Проверяем указатели на панель и таблицу
   if(panel==NULL || table==NULL)
      return;
      
//--- Заполняем таблицу значениями из массива
   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;
     }
     
//--- Рассчитаем индекс строки, с которой необходимо начать заполнение таблицы
   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;
   
//--- В цикле по количеству строк (размер массива статистики символов), начиная со строки index
   for(int i=index;i<total;i++)
     {
      //--- в цикле по количеству столбцов статистики (массив расположения столбцов таблиц статистики слева-направо)
      for(int j=0;j<(int)ArrayDataName.Size();j++)
        {
         //--- получаем очередную ячейку таблицы
         cell=table.GetCell(i,j);
         if(cell==NULL)
            continue;
         
         //--- невидимые области таблицы не рисуем
         if(cell.X()>panel.CoordX()+panel.Width())
            continue;
         if(cell.Y()>panel.CoordY()+panel.Height())
            break;
            
         //--- Получаем данные таблицы из массива структур
         string cell_text="";
         
         cell_text=GetDataStatsStr(TABLE_SYMBOLS, ArrayDataName[j],i);
         //--- выводим записи в ячейки таблицы
         cell.SetText(cell_text);
         panel.DrawText(cell.Text(),cell.X()+6,cell.Y()+1);
        }
     }
  }

Функция, заполняющая таблицу статистики торговли по магикам:

//+------------------------------------------------------------------+
//| Заполняет таблицу статистики торговли по магикам                 |
//+------------------------------------------------------------------+
void FillsTradingStatsByMagicsTable(CDashboard *panel,CTableData *table)
  {
//--- Проверяем указатели на панель и таблицу
   if(panel==NULL || table==NULL)
      return;
      
//--- Заполняем таблицу значениями из массива
   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;
     }
     
//--- Рассчитаем индекс строки, с которой необходимо начать заполнение таблицы
   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;
   
//--- В цикле по количеству строк (размер массива статистики магиков), начиная со строки index
   for(int i=index;i<total;i++)
     {
      //--- в цикле по количеству столбцов статистики (массив расположения столбцов таблиц статистики слева-направо)
      for(int j=0;j<(int)ArrayDataName.Size();j++)
        {
         //--- получаем очередную ячейку таблицы
         cell=table.GetCell(i,j);
         if(cell==NULL)
            continue;
         
         //--- невидимые области таблицы не рисуем
         if(cell.X()>panel.CoordX()+panel.Width())
            continue;
         if(cell.Y()>panel.CoordY()+panel.Height())
            break;
            
         //--- Получаем данные таблицы из массива структур
         string cell_text=GetDataStatsStr(TABLE_MAGICS, ArrayDataName[j],i);
         //--- выводим записи в ячейки таблицы
         cell.SetText(cell_text);
         panel.DrawText(cell.Text(),cell.X()+6,cell.Y()+1);
        }
     }
  }

Две однотипные функции, выполняющие заполнение таблиц статистики по символам и по магикам.

Функция, возвращающая данные из структуры по типу заголовка:

//+------------------------------------------------------------------+
//| Возвращает данные из структуры по типу заголовка                 |
//+------------------------------------------------------------------+
double GetDataStats(const int table_type, const string data_type, const int index)
  {
//--- В зависимости от типа данных в таблице возвращаем данные из полей структуры data_type, по индексу массива 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;
     }
  }

В функцию передаётся тип таблицы статистики (символ/магик/аккаунт), тип данных (значение заголовка столбца) и индекс данных в массиве структур. В зависимости от переданных в функцию данных, возвращается значение из соответствующего массива структур. Для упрощения, значение всегда возвращается с типом double, даже если в структуре оно целочисленное. В следующей функции это значение возвращается в виде строки с нужным количеством десятичных знаков.

Функция, возвращающая данные из структуры по типу заголовка в виде строки:

//+------------------------------------------------------------------+
//| Возвращает данные из структуры по типу заголовка в виде строки   |
//+------------------------------------------------------------------+
string GetDataStatsStr(const int table_type, const string data_type, const int index)
  {
//--- В зависимости от типа данных, определяем количество знаков после запятой
//--- (2 - для вещественного свойства и 0 - для целочисленного)
   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=="HEADER")
     {
      //--- возвращаем наименование в зависимости от типа таблицы (символ, магик, аккаунт)
      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;
        }
     }
//--- Для всех остальных типов данных возвращаем их строковое значение с ранее определённым количеством десятичных знаков
   return(DoubleToString(GetDataStats(table_type, data_type, index),digits));
  }

Функции используются для вывода значений ячеек таблиц статистики в таблицы на панели.

Функция, возвращающая индекс символа в массиве статистики по символам:

//+------------------------------------------------------------------+
//| Возвращает индекс символа в массиве статистики по символам       |
//+------------------------------------------------------------------+
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;
  }

Функция, возвращающая индекс символа в массиве статистики по магикам:

//+------------------------------------------------------------------+
//| Возвращает индекс магика в массиве статистики по магикам         |
//+------------------------------------------------------------------+
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;
  }

Обе функции возвращают индекс искомого символа или магика в соответствующем массиве статистики символов или магиков.

Функция, отображающая на панели итоговую статистику по выбранному символу, магику или аккаунту:

//+------------------------------------------------------------------+
//| Отображает статистику по выбранному символу, магику или счёту    |
//+------------------------------------------------------------------+
bool ViewStatistic(const int table_type,const string cell_text)
  {
//--- Получаем указатели на панель заголовков и правую панель для вывода статистики
   CDashboard *panel_h=dashboard.GetPanel("FieldH");
   CDashboard *panel_r=dashboard.GetPanel("FieldR");
   if(panel_h==NULL || panel_r==NULL)
      return false;
      
//--- Определяем источник статистических данных (символ/магик/аккаунт)
   string source=(table_type==TABLE_SYMBOLS ? "symbol" : table_type==TABLE_MAGICS ? "magic" : "account");
   int index=WRONG_VALUE;
   
//--- В зависимости от текста в выбранной ячейке таблицы (cell_text), переданному в функцию,
//--- получаем индекс, по которому содержатся данные в соответствующем массиве статистики
   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(index==WRONG_VALUE)
     {
      PrintFormat("%s: Error. Empty array of %s statistics",__FUNCTION__,source);
      return false;
     }
   
//--- Получим и сохраним установленные для панели заголовков свойства шрифта
   int f_size,f_flags,f_angle;
   string f_name=panel_h.FontParams(f_size,f_flags,f_angle);
   
//--- Очищаем панель заголовков и выводим на неё описание выбранных данных шрифтом Tahoma размером 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');
//--- Возвращаем шрифту панели заголовков прежние сохранённые свойства
   panel_h.SetFontParams(f_name,f_size,f_flags,f_angle);
   
//--- Проверяем наличие и получаем или создаём объект-таблицу для вывода статистики на правой панели
   if(!panel_r.TableIsExist(TABLE_STATS) && !panel_r.CreateNewTable(TABLE_STATS))
      return false;
//--- Получаем указатель на созданную таблицу
   CTableData *table_r=panel_r.GetTable(TABLE_STATS);
   if(table_r==NULL)
      return false;

//--- Очищаем правую панель и рисуем на ней таблицу
   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);

//--- Объявляем структуру для хранения данных статистики
//--- (символ/магик/аккаунт) по ранее полученному индексу данных.
//--- Все поля структур SSymbolStats, SMagicStats и SAccountStats одинаковы,
//--- кроме первого поля с наименованием символа, значением магика или номером счёта.
//--- Т.к. первое поле здесь не нужно, поэтому здесь достаточно любого типа структуры из трёх.
//--- Заполняем объявленную структуру данными в зависимости от выбранного источника
   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;
     }

//--- Получим и сохраним установленные для правой панели свойства шрифта
   f_name=panel_r.FontParams(f_size,f_flags,f_angle);
//--- Устанавливаем для правой панели новый шрифт Tahoma размером 8
   panel_r.SetFontParams("Tahoma",8,FW_BLACK,f_angle);
   
//--- Переменные для расчёта места расположения текста в ячейке таблицы
   CTableCell *cellH=NULL, *cellV=NULL;
   int    cols=table_r.ColumnsInRow(0);   // количество столбцов таблицы статистики
   int    cw=table_r.Width()/cols;        // ширина одного столбца таблицы
   int    y_shift=6;                      // смещение текста по высоте
   int    x_shift=21;                     // смещение текста по ширине
   int    tw=0;                           // ширина текста
   string text="";
   double value=0;
   
//--- Левый столбец (данные -- значение)
//--- Получаем ячейки 0,0 и 0,1 таблицы и выводим в их координаты Trades и его значение
   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');
   
//--- Получаем ячейки 1,0 и 1,1 таблицы и выводим в их координаты Long и его значение
   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');
   
//--- Получаем ячейки 2,0 и 2,1 таблицы и выводим в их координаты Short и его значение
   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');
   
//--- Получаем ячейки 3,0 и 3,1 таблицы и выводим в их координаты Net Profit и его значение
   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'));
   
//--- Получаем ячейки 4,0 и 4,1 таблицы и выводим в их координаты Profit Loss и его значение
   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'));
   
//--- Получаем ячейки 5,0 и 5,1 таблицы и выводим в их координаты Gross Profit и его значение
   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'));
   
//--- Получаем ячейки 6,0 и 6,1 таблицы и выводим в их координаты Gross Loss и его значение
   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'));
   
//--- Получаем ячейки 7,0 и 7,1 таблицы и выводим в их координаты Commission total и его значение
   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'));
   
//--- Получаем ячейки 8,0 и 8,1 таблицы и выводим в их координаты Swap total и его значение
   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'));
   
   
//--- Правый столбец (данные -- значение)
//--- Получаем ячейки 0,2 и 0,3 таблицы и выводим в их координаты Win trades и его значение
   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');
   
//--- Получаем ячейки 1,2 и 1,3 таблицы и выводим в их координаты Loss trades и его значение
   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');
   
//--- Получаем ячейки 2,2 и 2,3 таблицы и выводим в их координаты Expected Payoff и его значение
   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');
   
//--- Получаем ячейки 3,2 и 3,3 таблицы и выводим в их координаты Win percent и его значение
   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');
   
//--- Получаем ячейки 4,2 и 4,3 таблицы и выводим в их координаты Loss percent и его значение
   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');
   
//--- Получаем ячейки 5,2 и 5,3 таблицы и выводим в их координаты Average Profit и его значение
   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'));
   
//--- Получаем ячейки 6,2 и 6,3 таблицы и выводим в их координаты Average Loss и его значение
   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'));
   
//--- Получаем ячейки 7,2 и 7,3 таблицы и выводим в их координаты Profit factor и его значение
   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');
   
//--- Возвращаем шрифту правой панели прежние сохранённые свойства
   panel_r.SetFontParams(f_name,f_size,f_flags,f_angle);
   return true;
  }

Функция предназначена для рисования итоговой таблицы статистики по выбранному символу, магику, или полностью по счёту. В функцию передаётся тип таблицы статистики и наименования символа, или строковое значение магика, или номера счёта. По тексту определяется индекс символа, магика или аккаунта в соответствующем массиве структур статистических данных. Из требуемой структуры по полученному индексу получаем в структуру все статистические данные, а далее располагаем их в нарисованной таблице по координатам расположения её ячеек. При этом смещения выводимого текста по горизонтали рассчитываются таким образом, чтобы заголовок данных был привязан к левому краю ячейки таблицы, а текст значения этих данных был привязан к правому краю своей ячейки таблицы. Все данные выводятся в четыре столбца таким образом, чтобы визуально они были сгруппированы на панели в два столбца в виде "заголовок -- значение".

Давайте скомпилируем индикатор и посмотрим воочию что мы получили:

Ну что ж, видим, что весь заявленный функционал работает как предполагалось. Да, заметны небольшие "моргания" текста в таблицах при перемещении курсора и прокрутке таблиц. Но это результат неоптимальной схемы перерисовки — вся видимая часть таблицы постоянно перерисовывается. Этого можно избежать более сложной логикой обработки строк таблиц под курсором, что не входит в планы этой статьи.

Таблицы можно прокручивать вертикально вращением колёсика мышки, а горизонтально — вращением колёсика с зажатой клавишей Shift. При внимательном рассмотрении видео можно заметить, что при отображении статистики по магику со значением 0, количество длинных и коротких позиций указано нулевым. Это результат ошибки при определении трейда в запросе из БД. Таблица трейдов создаётся из таблицы сделок. Если позиция была открыта советником (в данном случае с магиком 600), а закрыта вручную, то у сделки открытия будет прописан магик, а у сделки закрытия он будет нулевым. Это видно при просмотре истории сделок:

Здесь для определения трейда берутся сделки закрытия, а у неё магик равен нулю. А по нулевому магику невозможно найти сделку открытия — её нет. Соответственно, для нулевого магика не находятся ни длинные, ни короткие позиции. Значит, при создании таблицы трейдов стоит учитывать такую вероятность, что позиция может быть открыта советником, а закрыта вручную и наоборот. Если это учесть, то таких ошибок уже быть не должно.



Заключение

Сегодня мы поставили себе задачу создать информационную панель, отображающую статистику торговли, а решать мы её должны были на примерах из статей и документации на данном ресурсе. И, как видим, что даже чего-то не зная, всегда несложно найти ответы на свои вопросы, воспользовавшись огромной базой знаний, предлагаемой ресурсом, и сделать вполне работоспособный продукт.

Изучайте предлагаемую сайтом информацию, читайте статьи и документацию, общайтесь с более опытными коллегами, дорабатывайте найденные примеры под свои задачи, и всё обязательно получится!

Все файлы рассмотренных классов, функций и индикатора приложены к статье. Также приложен архив, который можно распаковать в каталог данных терминала, и все нужные файлы будут размещены в папке\MQL5\Indicators\StatisticsBy, их можно сразу скомпилировать и запустить файл индикатора.

Прикрепленные файлы |
Dashboard.mqh (258.28 KB)
SQLiteFunc.mqh (63.95 KB)
StatisticsBy.mq5 (164.27 KB)
MQL5.zip (44.64 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (5)
fxsaber
fxsaber | 31 окт. 2024 в 09:26

Не хватает инструментария, который позволял бы работать с большой историей торговли.

К сожалению, данный инструментарий просто зависает при запросе истории, как и многие другие.



Пять минут на получение истории. Далее что-либо делать с окном невозможно - полная нагрузка CPU.

Artyom Trishkin
Artyom Trishkin | 31 окт. 2024 в 10:08
fxsaber #:

Не хватает инструментария, который позволял бы работать с большой историей торговли.

К сожалению, данный инструментарий просто зависает при запросе истории, как и многие другие.



Пять минут на получение истории. Далее что-либо делать с окном невозможно - полная нагрузка CPU.

Можно в личку инвесторский доступ к счëту? 
fxsaber
fxsaber | 31 окт. 2024 в 12:01
Artyom Trishkin #:
Можно в личку инвесторский доступ к счëту? 

К сожалению, нет такой возможности. Но Вы можете сами создать нечто подобное: на демо-счете скриптом за час асинхронным OrderSend открыть/закрыть нужное количество позиций по разным символам/мэджикам.

Konstantin Seredkin
Konstantin Seredkin | 7 дек. 2024 в 09:36

На московской бирже работать не хочет



JRandomTrader
JRandomTrader | 7 дек. 2024 в 10:29
Konstantin Seredkin #:

На московской бирже работать не хочет

Естественно. Всё, что связано с позицией, кроме суммарной, при неттинге бесполезно, если работает больше одного робота на одном символе (или робот плюс ручная торговля).

Нейросети в трейдинге: Модели направленной диффузии (DDM) Нейросети в трейдинге: Модели направленной диффузии (DDM)
Предлагаем познакомиться с моделями направленной диффузии, которые используют анизотропные и направленные шумы, зависящие от данных, в процессе прямой диффузии для захвата значимых графовых представлений.
Методы оптимизации библиотеки Alglib (Часть II) Методы оптимизации библиотеки Alglib (Часть II)
В статье продолжим изучение оставшихся методов оптимизации из библиотеки ALGLIB, уделяя особое внимание их тестированию на сложных многомерных функциях. Это позволит нам не только оценить эффективность каждого из алгоритмов, но и выявить их сильные и слабые стороны в различных условиях.
Разработка системы репликации (Часть 55): Модуль управления Разработка системы репликации (Часть 55): Модуль управления
В этой статье мы реализуем индикатор управления, чтобы его можно было интегрировать в разрабатываемую систему обмена сообщениями. Несмотря на то, что это не очень сложно, необходимо понять некоторые детали инициализации этого модуля. Представленный здесь материал предназначен исключительно для учебных целей. Ни в коем случае он не должен рассматриваться как приложение, целью которого не является изучение и освоение показанных концепций.
Возможности Мастера MQL5, которые вам нужно знать (Часть 22): Условные генеративно-состязательные сети (cGAN) Возможности Мастера MQL5, которые вам нужно знать (Часть 22): Условные генеративно-состязательные сети (cGAN)
Генеративно-состязательные сети — это пара нейронных сетей, которые обучаются друг на друге для получения более точных результатов. Мы рассмотрим условный тип этих сетей в контексте их возможного применения в прогнозировании финансовых временных рядов в рамках класса сигналов советника.