Русский
preview
Recursos do SQLite em MQL5: Exemplo de painel interativo com estatísticas de trading por símbolo e magic

Recursos do SQLite em MQL5: Exemplo de painel interativo com estatísticas de trading por símbolo e magic

MetaTrader 5Exemplos | 22 abril 2025, 08:57
20 0
Artyom Trishkin
Artyom Trishkin

Conteúdo



Introdução

A MQL5.com é uma plataforma que oferece aos usuários amplo acesso a diversas informações de referência e materiais educativos sobre algoritmos de negociação. Apesar de sua extensa base de dados e recursos, alguns usuários ainda enfrentam dificuldades para encontrar soluções específicas ou adaptar exemplos às suas necessidades. Para esses casos, a plataforma permite a interação com a comunidade por meio do fórum, onde é possível obter dicas úteis ou até mesmo soluções prontas.

Este artigo tem como objetivo demonstrar e resolver uma das tarefas mais comuns: a criação de um painel informativo para exibir o histórico e as estatísticas de trading de uma conta. Vamos analisar o processo de desenvolvimento deste painel usando exclusivamente os materiais existentes do mql5.com, o que é interessante não apenas pela solução prática, mas também como exemplo de aplicação de conteúdos didáticos e de referência em situações reais.


Definindo o objetivo

Precisamos criar um painel informativo no qual seja possível exibir, sob demanda, informações sobre o histórico de trading da conta e estatísticas de negociação por símbolo e magic (EAs que operam na conta). Também deve ser possível exibir as estatísticas completas de trading da conta. O histórico de operações deve ser ordenado pelo tempo de execução das operações. As estatísticas por símbolos e magics devem ser ordenadas pelo lucro líquido (Net Profit).

O tipo de programa será um indicador. Não exibiremos nenhum dado nos buffers. Ou seja, será um indicador sem buffers, cujo espaço de trabalho principal será um painel gráfico. O painel será visualmente dividido em duas áreas — à esquerda, serão exibidas listas de todos os símbolos e magics nos quais houve trading na conta, à direita, serão exibidas tabelas com estatísticas por símbolo e magic em formato de lista, estatísticas consolidadas de um único símbolo ou magic selecionado ou, ainda, de toda a conta. O controle do conteúdo exibido será feito por meio de botões no painel ou clicando sobre a linha da estatística do símbolo/magic na tabela à direita.

No artigo "Criamos um painel informativo para exibir dados em indicadores e EAs" utilizaremos o painel informativo.
O artigo "SQLite: uso nativo de bancos de dados com SQL em MQL5" nos ajudará a obter estatísticas da conta, bem como estatísticas por símbolo e magic (estratégias de negociação).



Painel informativo

Desde a primeira publicação do artigo sobre a criação do painel informativo, o código passou por algumas modificações e melhorias. Detalhar todas as alterações feitas no código não é o objetivo deste artigo. Por isso, faremos apenas uma breve visão geral dessas mudanças. Para ver exatamente o que foi ajustado, basta baixar a primeira versão do painel mencionada naquele artigo e compará-la com o código do painel incluído neste artigo.

Foram corrigidos erros de posicionamento e exibição do painel que ocorriam ao alternar gráficos em determinadas situações. Agora é possível anexar painéis filhos ao principal. Especificamente, esses painéis filhos podem ser transformados em botões ao serem minimizados, deixando visível apenas o cabeçalho, que funcionará como botão. As restrições de posicionamento das tabelas dentro do painel foram removidas. Originalmente, não era possível desenhar a tabela fora da área visível do painel. Em alguns casos, no entanto, as tabelas desenhadas no painel precisam ser posicionadas fora dessa área. Isso é necessário para permitir a rolagem de tabelas longas ou largas, cujas dimensões excedem o tamanho do painel. Assim, se permitirmos que as coordenadas iniciais da tabela fiquem fora do painel, será possível implementar a rolagem da tabela. É exatamente isso que faremos hoje. No entanto, não vamos alterar as classes do painel e de suas tabelas (embora isso fosse o mais correto do ponto de vista da reutilização do painel aprimorado), justamente para mostrar que, mesmo com exemplos cujo funcionamento não seja totalmente adequado, é possível atingir a funcionalidade desejada com a abordagem correta.

Vamos ver brevemente o que foi aprimorado no código das classes das tabelas e do painel.

Foi adicionada à classe da célula da tabela uma variável para armazenar o texto da célula e métodos para trabalhar com essa variável:

//+------------------------------------------------------------------+
//| Класс ячейки таблицы                                             |
//+------------------------------------------------------------------+
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){}
  };

Na classe dos dados da tabela, agora o identificador pode ter um valor negativo, assim como as coordenadas da tabela também podem ser negativas. Foi adicionado um método para definir o identificador da tabela:

//+------------------------------------------------------------------+
//| Класс данных таблиц                                              |
//+------------------------------------------------------------------+
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);
                       }

Também foi incluído um construtor padrão:

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

O construtor padrão permitirá declarar um objeto da classe sem a necessidade de criá-lo usando o operador new, e os valores de coordenadas do tipo int possibilitarão definir as coordenadas iniciais das tabelas fora da janela do painel.

Na classe do objeto painel foram declaradas novas variáveis<1>:

//+------------------------------------------------------------------+
//| Класс 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;                    // Наименование панели
   

Como agora é possível vincular painéis filhos ao painel principal, alguns métodos de manipulação do painel foram movidos da seção protegida para a seção pública — é necessário acessá-los externamente — e também foram adicionados novos métodos<2>:

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;  }
   
//--- Обработчик событий

Todos os novos métodos e melhorias permitem agora anexar painéis filhos ao painel principal e usá-los como objetos independentes, porém dependentes do seu pai. Agora é possível criar tabelas com rolagem, caso o tamanho delas exceda o do próprio painel. Anteriormente, as tabelas só podiam ter tamanho menor do que o do próprio painel. O recurso de rolagem de tabelas não está presente na classe do painel; vamos implementá-lo diretamente no programa principal. Posteriormente, se necessário, esse recurso poderá ser incorporado à classe do painel e às suas tabelas. Porém, nesse momento, isso não é necessário.

Naturalmente, aqui apresentamos apenas uma pequena parte das alterações feitas na classe do painel e suas tabelas — apenas os métodos declarados. As melhorias foram realizadas gradualmente em uma parte bastante extensa do código ao longo do tempo, desde a primeira publicação. Quem quiser pode baixar a primeira versão do painel no artigo original e compará-la com a versão apresentada neste artigo. O arquivo do painel informativo deve estar localizado no diretório do projeto: \MQL5\Indicators\StatisticsBy\Dashboard\Dashboard.mqh.



Funções para trabalhar com banco de dados

Já faz muito tempo que trabalho com bancos de dados, mas apenas superficialmente, em um projeto conjunto para a indústria de jogos com C#, no qual o banco de dados era responsabilidade de outra pessoa, e eu apenas usava o conector fornecido para integrá-lo ao projeto. Por isso, foi necessário recorrer a materiais de referência e artigos no site mql5.com. Ao começar a ler o artigo "SQLite: uso nativo de bancos de dados com SQL em MQL5", logo notei referências à documentação, especialmente à função DatabasePrepare(). Lá há um exemplo no qual é criada uma tabela de operações, e com base nela, uma tabela de trades. É exatamente o que precisamos! Com paciência, vamos estudar o exemplo e suas funções.

Primeiro, vemos duas estruturas para armazenar dados de operações e de trades:

//--- структура для хранения сделки
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;           // прибыль или убыток
  };

Em seguida, analisamos a lógica:

//+------------------------------------------------------------------+
//| 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. É criado um banco de dados,
  2. é criada uma tabela de operações no banco de dados,
  3. o histórico de operações é requisitado, e as operações são inseridas na tabela criada,
  4. o tipo de conta é verificado. Deve ser do tipo hedge, pois no modo netting, com base apenas nas operações, não é possível construir o histórico de negociação,
  5. é criada uma tabela de trades com base na tabela de operações, e os dados de trades são inseridos nela com base nas informações da tabela de operações.

O script apresentado também imprime no log as dez primeiras operações e os dez primeiros trades das tabelas criadas. Isso não é necessário para nós.

Com base na lógica, precisamos criar algumas funções inspiradas naquelas apresentadas no exemplo, bem como aproveitar trechos de código presentes no corpo do script exemplo:

//+------------------------------------------------------------------+
//| Создает таблицу 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);
  }
//+------------------------------------------------------------------+

Além dos campos das estruturas e tabelas mostrados no exemplo, precisamos de um campo adicional que armazene o número da conta — isso será necessário para a criação da tabela de estatísticas de trading da conta. Em outras palavras, para a geração da estatística completa da conta.

Mas como vamos criar as tabelas de estatísticas? Tudo bem, continuamos lendo o artigo e encontramos! Aqui está exatamente o que precisamos:

Análise de portfólio por estratégia

Nos resultados apresentados pelo script de DatabasePrepare, vemos que a negociação é realizada em vários pares de moedas. Mas além disso, vemos também, na coluna [magic], valores que vão de 100 a 600. Isso indica que várias estratégias de negociação atuam na conta, cada uma com seu próprio Magic Number para identificar suas operações.

Com uma consulta SQL, podemos analisar a negociação com base nos valores de 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");

Resultado:

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

Vemos que 4 das 6 estratégias apresentaram lucro. E para cada estratégia obtivemos os seguintes indicadores estatísticos:

  • trades — quantidade de trades por estratégia,
  • gross_profit — lucro bruto por estratégia (soma de todos os valores positivos de profit),
  • gross_loss — prejuízo bruto por estratégia (soma de todos os valores negativos de profit),
  • total_commission — soma de todas as comissões dos trades da estratégia,
  • total_swap — soma de todos os swaps dos trades da estratégia,
  • total_profit — soma de gross_profit e gross_loss,
  • net_profit — soma (gross_profit + gross_loss + total_commission + total_swap),
  • win_trades — quantidade de trades com profit>0,
  • loss_trades — quantidade de trades com profit<0,
  • expected_payoff — expectativa matemática do trade, sem considerar swaps e comissões = net_profit/trades,
  • win_percent — percentual de trades vencedores,
  • loss_percent — percentual de trades perdedores,
  • average_profit — lucro médio = gross_profit/win_trades,
  • average_loss — prejuízo médio = gross_loss/loss_trades,
  • profit_factor — fator de lucro = gross_profit/gross_loss.

Essas estatísticas não consideram os swaps e as comissões no cálculo do lucro e prejuízo, permitindo visualizar esses custos de forma isolada. Pode acontecer de uma estratégia ser lucrativa por si só,


mas acabar sendo desvantajosa devido aos swaps e comissões.

Podemos realizar uma análise de trading por símbolo. Para isso, fazemos a seguinte consulta:

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

Resultado:

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

As estatísticas mostram que apenas em 5 dos 10 símbolos foi obtido lucro líquido (net_profit>0), embora em 6 dos 10 o fator de lucro tenha sido positivo (profit_factor>1). Esse é justamente o caso em que swaps e comissões tornam a estratégia perdedora no par EURJPY.

Continuando a leitura do artigo, encontramos:

O código-fonte completo com esses 3 tipos de consultas pode ser encontrado no exemplo da função DatabaseExecute().

Excelente! Acessamos o link na documentação e obtemos o código completo do exemplo relacionado a essa função:

//--- статистика по символу
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);
  }
//+------------------------------------------------------------------+

Ele pode ser copiado para o editor, compilado e, ao ser executado, permite visualizar o resultado no log.

Agora temos exemplos práticos de como trabalhar com o banco de dados para alcançar o resultado desejado. Será necessário apenas fazer alguns ajustes nos códigos fornecidos na documentação. Por exemplo, podemos querer que os dados sejam ordenados por algum campo específico ou obter apenas valores únicos das tabelas. Para isso, vale a pena consultar a documentação do SQL. Com o conhecimento adquirido e os exemplos em mãos, conseguiremos adaptar tudo conforme as necessidades do projeto.

Vamos criar no diretório do terminal \MQL5Indicators uma nova pasta StatisticsBy. Nela ficarão todos os arquivos deste projeto.
Na pasta criada, vamos criar um novo arquivo SQLiteFunc.mqh e começar a preenchê-lo com funções para trabalhar com o banco de dados.

Em primeiro lugar, vamos escrever as estruturas necessárias:

//+------------------------------------------------------------------+
//|                                                   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;    // профит-фактор
  };

As estruturas foram copiadas dos exemplos apresentados anteriormente na Documentação, mas receberam novos nomes e foram adicionados campos para armazenar o número da conta e a quantidade de posições compradas e vendidas, permitindo realizar filtros no banco de dados com base nesses dados.

Precisamos do histórico de operações para criar a tabela de operações no banco de dados. Por isso, a função que obtém esse histórico também será escrita nesse mesmo arquivo:

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

Neste arquivo também será incluída uma função para excluir do banco de dados uma tabela com o nome especificado:

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

Vamos escrever uma função para criar a tabela de operações:

//+------------------------------------------------------------------+
//| Создает таблицу 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);
  }

O código da função foi copiado da documentação, mas aqui foi adicionado um campo extra — o número da conta.

De forma análoga, escrevemos a função para criação da tabela de trades, também com o campo do número da conta incluído:

//+------------------------------------------------------------------+
//| Создает таблицу 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);
  }

Agora, vamos escrever a função para preencher a tabela de operações no banco de dados com os dados coletados:

//+------------------------------------------------------------------+
//| Вносит сделки в таблицу базы данных                              |
//+------------------------------------------------------------------+
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);
  }

O código da função foi retirado do exemplo na documentação da função DatabaseExecute(). Aqui foi criada uma variável adicional para armazenar o número da conta, e corrigido o texto da consulta, pois a documentação provavelmente contém um erro: ao compor a string da consulta, os tipos de dados foram declarados como int para campos que, na verdade, têm tipo 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);

Corrigimos isso aqui, adicionamos o campo do número da conta e removemos a hora de entrada da operação, pois neste caso a hora de entrada não é necessária.

Vamos escrever a função para preencher a tabela de trades com base na tabela de operações:

//+------------------------------------------------------------------+
//| Заполняет таблицу 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;
  }

Assim como nas funções anteriores, aqui também foi adicionado o campo do número da conta.

Agora, escrevemos a função que preenche a lista de todos os trades a partir do banco de dados:

//+------------------------------------------------------------------+
//| Заполняет из БД список всех трейдов                              |
//+------------------------------------------------------------------+
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;
  }

Aqui adicionamos a ordenação da lista de trades por ordem decrescente do horário de entrada no mercado. Se não fizermos isso, o último trade da lista aparecerá no final, o que significa que ele será exibido na parte inferior da tabela do painel. Isso é inconveniente. Ao ordenar de forma decrescente, o último trade será colocado no topo da tabela — na parte superior do painel — e os trades mais recentes estarão visíveis de imediato, sem a necessidade de rolar toda a lista.

Vamos escrever a função que preenche, a partir do banco de dados, a lista de todos os símbolos nos quais houve trading:

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

Para obter uma lista contendo apenas os nomes de símbolos únicos e não repetidos, é necessário usar a palavra-chave "DISTINCT", e o ideal é que essa lista seja retornada em ordem alfabética.

De forma semelhante, escrevemos a função que preenche a partir do banco de dados a lista de todos os magics em ordem crescente:

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

Criamos agora uma função que obtém, do banco de dados, e armazena em um array as estatísticas de trading por símbolo:

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

Aqui foram adicionadas linhas para contabilizar posições compradas e vendidas, e obtemos uma lista ordenada por lucro líquido em ordem decrescente — ou seja, os símbolos mais lucrativos aparecem no início da tabela.

De forma análoga, escrevemos uma função para obter do banco de dados e armazenar em um array as estatísticas de trading por magic:

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

Por fim, escrevemos uma função semelhante, que obtém do banco de dados e armazena em um array as estatísticas de trading da conta:

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

Agora, temos todos os elementos básicos do projeto: selecionamos o painel e criamos funções para manipulação do banco de dados com base nas informações da documentação. Agora falta apenas implementar a lógica de interação entre o painel, suas tabelas e o banco de dados, usando as funções que escrevemos. É bem possível que as listas exibidas nas tabelas do painel sejam bastante longas, o que fará com que o tamanho delas ultrapasse os limites do painel. Nesses casos, será necessário implementar rolagem vertical e horizontal. Esse recurso será implementado diretamente no indicador que estamos criando: as tabelas serão roladas verticalmente com a rotação do scroll do mouse, e horizontalmente com o scroll do mouse enquanto a tecla Shift estiver pressionada.

A estatística de um símbolo ou magic selecionado será exibida ao clicar sobre a linha correspondente na tabela de estatísticas. Para isso, faremos o rastreamento da posição do cursor sobre as linhas da tabela e detectaremos os cliques nessas linhas. O ideal seria que esse recurso estivesse na própria classe do painel, permitindo sua reutilização em outros projetos. Mas aqui vamos mostrar como fazer tudo isso sem precisar modificar as classes do painel.



Montando o projeto — painel informativo

Na pasta anteriormente criada \MQL5\IndicatorsStatisticsBy, vamos criar o arquivo de um novo indicador com o nome StatisticsBy.mq5.

Incluímos os arquivos das classes de tabelas e do painel, além do arquivo de funções para manipulação do banco de dados, e especificamos que o indicador não possui buffers desenháveis:

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

Em seguida, inserimos as macros, o array com a disposição das colunas das tabelas de estatísticas, os parâmetros de entrada e as variáveis globais:

#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;                     // Указатель на экземпляр панели

Para indicar a quantidade de colunas das tabelas de estatísticas e definir a disposição e quantidade de dados nas tabelas, é conveniente usar um array contendo constantes com os nomes dos cabeçalhos das tabelas e, por consequência, os dados sob esses cabeçalhos. Se for necessário alterar a ordem de exibição dos dados, basta reorganizar sua declaração nesse array e recompilar o indicador. Também é possível remover dados desnecessários comentando-os no array, ou adicionar novos dados. No entanto, ao adicionar novos dados, será necessário incluí-los nas funções de manipulação do banco de dados e em outras funções responsáveis por calcular e exibir os dados nas tabelas.

Vamos analisar o manipulador OnInit() do indicador, onde o banco de dados é criado e o painel com seu conteúdo gráfico é montado:

//+------------------------------------------------------------------+
//| 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);
  }

Basicamente, aqui primeiro é verificado o tipo de contabilização de posições e, se for netting, o indicador é encerrado, pois nesse tipo de conta não é possível criar uma tabela de trades de forma simples, utilizando apenas as operações de entrada e saída.

Depois, no diretório de dados do terminal (TERMINAL_DATA_PATH + \MQL5\Files), é criado o banco de dados dentro de uma subpasta com o nome do programa, no subdiretório Database (\StatisticsBy\Database). Após a criação bem-sucedida do banco de dados, o painel é criado e preenchido com conteúdo, como botões de controle e painéis para exibição das tabelas:

O interessante aqui é que, em vez de botões convencionais, usamos painéis filhos anexados à janela principal em estado minimizado — apenas o cabeçalho do painel fica visível. Esse cabeçalho tem seus próprios manipuladores para interagir com o cursor do mouse e, dessa forma, transformamos painéis comuns em botões que interagem de maneira interativa com o usuário e enviam eventos de interação com o mouse ao programa principal.

No manipulador OnDeinit(), fechamos o banco de dados e o painel:

//+------------------------------------------------------------------+
//| 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();
     }
  }

O manipulador OnCalculate() do indicador permanecerá vazio (o indicador não realiza nenhum cálculo):

//+------------------------------------------------------------------+
//| 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);
  }

Toda a lógica de interação interativa entre o painel e o usuário será tratada no manipulador de eventos do indicador.

Vamos analisar o manipulador de eventos OnChartEvent() por completo. Seu código está comentado em detalhes. Com uma leitura atenta dos comentários dentro do manipulador, toda a lógica de interação entre o painel e o usuário se torna clara:

//+------------------------------------------------------------------+
//| 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;
        }
     }
  }

Agora vamos examinar as demais funções chamadas a partir do manipulador de eventos. O código de cada função está comentado de forma detalhada, de modo que não deve gerar dúvidas.

Função que retorna o índice da linha da tabela com base nas coordenadas do cursor:

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

Essa função realiza duas tarefas ao mesmo tempo: (1) retorna o número da linha da tabela sobre a qual o cursor do mouse está posicionado e (2) destaca essa linha com uma cor de fundo.

Função manipuladora da rolagem do scroll do mouse dentro da tabela no painel da esquerda:

//+------------------------------------------------------------------+
//| Обработчик прокрутки колёсика мышки внутри таблицы левой панели  |
//+------------------------------------------------------------------+
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;
  }

Ao girar o scroll do mouse, se o cursor estiver sobre uma tabela no painel, a tabela também deve ser rolada, caso seu tamanho exceda o tamanho do painel. Essa função faz exatamente isso: desloca a tabela com base nas coordenadas iniciais indicadas. Além disso, a linha sob o cursor é destacada com a função TableSelectRowByMouse(), que também retorna o índice da linha posicionada sob o cursor. Como no painel da esquerda são exibidas listas pequenas de símbolos e magics, a rolagem aqui é simplificada: a tabela é deslocada diretamente para as coordenadas calculadas. Já no painel da direita, a lógica será um pouco mais complexa.

Função manipuladora do movimento do cursor do mouse dentro da tabela do painel da esquerda:

//+------------------------------------------------------------------+
//| Обработчик смещения курсора мышки внутри таблицы левой панели    |
//+------------------------------------------------------------------+
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;
  }

Assim como na função anterior, aqui também é localizada e destacada a linha que está sob o cursor. A tabela, naturalmente, não é rolada.

Função manipuladora da rolagem do scroll do mouse dentro da tabela no painel da direita:

//+------------------------------------------------------------------+
//| Обработчик прокрутки колёсика мышки внутри таблицы правой панели |
//+------------------------------------------------------------------+
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;
  }

Aqui, uma única função trata simultaneamente três tabelas diferentes, além dos respectivos cabeçalhos. Tudo depende do tipo de tabela passado como argumento. Ao rolar tabelas grandes, é necessário permitir a rolagem tanto na vertical quanto na horizontal. A rolagem horizontal é controlada por um sinalizador chamado shift_flag , que indica se a tecla Shift está pressionada durante a rolagem do scroll do mouse. Quando a tabela é rolada, o cabeçalho correspondente, que está em outro painel, também é rolado simultaneamente.

Função manipuladora do movimento do cursor do mouse dentro da tabela no painel da direita:

//+------------------------------------------------------------------+
//| Обработчик смещения курсора мышки внутри таблицы правой панели   |
//+------------------------------------------------------------------+
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;
  }

Na verdade, aqui (e também nas funções analisadas anteriormente), tudo o que precisamos é identificar a linha da tabela sobre a qual o cursor está posicionado. Todo o restante está relacionado à parte visual do destaque da linha sob o cursor, o que aumenta o consumo de recursos do processador. Pois precisamos redesenhar constantemente toda a parte visível da tabela, e quanto maiores forem o painel e a tabela, maior será a área a ser redesenhada. Embora aqui (e mais adiante) o espaço desenhável esteja fisicamente limitado pelo tamanho do painel, esse processo não é ideal. Se estivéssemos implementando esse destaque de linha diretamente na classe do painel, seria diferente: apenas as linhas próximas ao cursor seriam redesenhadas, e a cor de fundo seria memorizada antes e depois do destaque, sendo restaurada em seguida. No entanto, como estamos apenas apresentando um exemplo, vamos implementar tudo diretamente nas funções do programa, sem complicar demais.

Função manipuladora do clique do mouse dentro da tabela no painel:

//+------------------------------------------------------------------+
//| Обработчик щелчка мышки внутри таблицы на панели                 |
//+------------------------------------------------------------------+
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;
  }

Ao clicar com o mouse sobre uma linha da tabela, é necessário localizar e retornar o número da linha onde ocorreu o clique, para que possamos tratá-lo posteriormente. Isso, aliás, já foi mostrado no manipulador de eventos do usuário dentro de OnChartEvent().

Função que preenche a tabela com os nomes dos símbolos:

//+------------------------------------------------------------------+
//| Заполняет таблицу наименованиями символов                        |
//+------------------------------------------------------------------+
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);
     }
  }

Aqui, além de garantir que as áreas da tabela que extrapolam os limites do painel não sejam desenhadas, limitamos também o início do laço à linha da tabela que está visível no topo. Explicando melhor: ao rolar a tabela para cima, a primeira linha pode acabar saindo completamente da área visível do painel. E, para evitar processar linhas que não serão exibidas de qualquer forma, calculamos o índice da primeira linha visível no topo da tabela e iniciamos o laço a partir daí. Com tabelas suficientemente grandes, esse método traz um ganho perceptível de desempenho, eliminando travamentos durante a rolagem de tabelas com centenas de linhas.

Função que preenche a tabela com os valores dos magics:

//+------------------------------------------------------------------+
//| Заполняет таблицу значениями магиков                             |
//+------------------------------------------------------------------+
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);
     }
  }

Função que preenche a tabela com os cabeçalhos dos trades:

//+------------------------------------------------------------------+
//| Заполняет таблицу заголовков трейдов                             |
//+------------------------------------------------------------------+
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);
     }
  }

Função que preenche a tabela com os dados dos trades:

//+------------------------------------------------------------------+
//| Заполняет таблицу трейдов                                        |
//+------------------------------------------------------------------+
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);
        }
     }
  }

Na verdade, essas duas funções desenham uma única tabela. Uma delas desenha o cabeçalho da tabela com os nomes das colunas, dependendo do índice da coluna, enquanto a outra desenha, logo abaixo, a tabela com os valores correspondentes a esses cabeçalhos. Em resumo, são funções que atuam em conjunto.

Função que preenche a tabela com os cabeçalhos da estatística de trading:

//+------------------------------------------------------------------+
//| Заполняет таблицу заголовков статистики торговли                 |
//+------------------------------------------------------------------+
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);
     }
  }

Ao exibir a tabela estatística por símbolos ou por magics, é necessário desenhar o cabeçalho correspondente. Como temos duas tabelas de estatísticas — uma por símbolos, outra por magics — os cabeçalhos dessas tabelas diferem apenas na primeira coluna; todas as demais colunas são idênticas. Aqui é feita uma verificação do identificador da tabela e, com base nele, o cabeçalho da primeira coluna é preenchido com o nome do símbolo ou do magic, conforme apropriado.

Função que preenche a tabela com a estatística de trading por símbolos:

//+------------------------------------------------------------------+
//| Заполняет таблицу статистики торговли по символам                |
//+------------------------------------------------------------------+
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);
        }
     }
  }

Função que preenche a tabela com a estatística de trading por magics:

//+------------------------------------------------------------------+
//| Заполняет таблицу статистики торговли по магикам                 |
//+------------------------------------------------------------------+
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);
        }
     }
  }

Duas funções semelhantes são responsáveis por preencher as tabelas de estatísticas por símbolos e por magics.

Função que retorna dados da estrutura com base no tipo de cabeçalho:

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

A função recebe como parâmetros o tipo da tabela de estatísticas (símbolo, magic ou conta), o tipo de dado (correspondente ao cabeçalho da coluna) e o índice dos dados no array de estruturas. Com base nessas informações, é retornado o valor correspondente do array de estruturas. Para simplificar, o valor é sempre retornado como tipo double, mesmo que, na estrutura original, o tipo seja inteiro. Na função seguinte, esse valor será convertido em uma string com a quantidade necessária de casas decimais.

Função que retorna dados da estrutura com base no tipo de cabeçalho no formato string:

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

Essas funções são utilizadas para exibir os valores das células das tabelas de estatísticas nas tabelas visíveis no painel.

Função que retorna o índice de um símbolo no array de estatísticas por símbolos:

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

Função que retorna o índice de um símbolo no array de estatísticas por magics:

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

Ambas as funções retornam o índice do símbolo ou do magic buscado dentro do respectivo array de estatísticas.

Função que exibe no painel a estatística consolidada para o símbolo, magic ou conta selecionado:

//+------------------------------------------------------------------+
//| Отображает статистику по выбранному символу, магику или счёту    |
//+------------------------------------------------------------------+
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;
  }

Essa função é responsável por desenhar a tabela de estatísticas consolidadas para o símbolo, magic ou conta selecionado. Ela recebe como parâmetros o tipo de tabela de estatísticas e o nome do símbolo, o valor em string do magic ou o número da conta. A partir do texto fornecido, determina-se o índice correspondente no array de estruturas de dados estatísticos. Em seguida, todos os dados da estrutura referente a esse índice são obtidos e posicionados na tabela desenhada, de acordo com as coordenadas das células. Os deslocamentos horizontais dos textos exibidos são calculados de forma que o título do dado fique alinhado à esquerda da célula e o valor correspondente fique alinhado à direita da sua célula. Todos os dados são exibidos em quatro colunas, organizados visualmente no painel como dois pares “cabeçalho: valor”.

Vamos compilar o indicador e observar o que obtivemos:

E então, vemos que toda a funcionalidade prevista está operando conforme esperado. Sim, há pequenos “piscares” de texto nas tabelas ao mover o cursor e rolar a tela. No entanto, isso é resultado de um esquema de redesenho não otimizado: toda a parte visível da tabela é redesenhada continuamente. Esse efeito poderia ser evitado com uma lógica mais sofisticada para tratar apenas as linhas sob o cursor, o que, no entanto, foge ao escopo deste artigo.

As tabelas podem ser roladas verticalmente girando o scroll do mouse, e horizontalmente girando o scroll com a tecla Shift pressionada. Ao observar atentamente o vídeo, é possível notar que, ao exibir a estatística para o magic com valor 0, a quantidade de posições compradas e vendidas aparece como zero. Isso é resultado de um erro na definição do trade na consulta ao banco de dados. A tabela de trades é criada com base na tabela de operações. Se uma posição foi aberta por um EA (neste caso, com magic 600) e fechada manualmente, a operação de abertura terá o magic definido, mas a operação de fechamento aparecerá com magic igual a zero. Isso pode ser visto ao analisar o histórico de operações:

Nesse caso, o trade é determinado com base na operação de fechamento, e como ela tem o magic igual a zero, não é possível localizar a operação de abertura correspondente — ela simplesmente não será encontrada. Consequentemente, para o magic zero, não serão registradas posições compradas nem vendidas. Isso significa que, ao criar a tabela de trades, deve-se considerar a possibilidade de que uma posição tenha sido aberta por um EA e fechada manualmente, ou o contrário. Se essa situação for levada em conta, tais erros deixarão de ocorrer. Considerações finais



Considerações finais

E, como foi possível ver, mesmo sem saber tudo de antemão, é sempre fácil encontrar as respostas necessárias consultando a vasta base de conhecimento oferecida pela plataforma, e assim criar um produto totalmente funcional. Aprofunde-se no conteúdo oferecido pelo site, leia os artigos e a documentação, interaja com colegas mais experientes e adapte os exemplos encontrados às suas necessidades.

Com certeza você terá sucesso!

Todos os arquivos dos classes, funções e do indicador abordados estão anexados ao artigo. Também está incluído um arquivo compactado que pode ser extraído diretamente na pasta de dados do terminal, e todos os arquivos necessários serão colocados automaticamente na pasta \MQL5\Indicators\StatisticsBy, podendo ser compilados e executados de imediato.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/16233

Arquivos anexados |
Dashboard.mqh (258.28 KB)
SQLiteFunc.mqh (63.95 KB)
StatisticsBy.mq5 (164.27 KB)
MQL5.zip (44.64 KB)
Métodos de otimização da biblioteca Alglib (Parte II) Métodos de otimização da biblioteca Alglib (Parte II)
Neste artigo, continuaremos a análise dos métodos de otimização restantes da biblioteca ALGLIB, com foco especial em seus testes em funções complexas e multidimensionais. Isso nos permitirá não apenas avaliar a eficiência de cada algoritmo, mas também identificar seus pontos fortes e fracos em diferentes condições.
Treinamento de perceptron multicamadas com o algoritmo de Levenberg-Marquardt Treinamento de perceptron multicamadas com o algoritmo de Levenberg-Marquardt
Este artigo apresenta a implementação do algoritmo de Levenberg-Marquardt para o treinamento de redes neurais com propagação para frente. Foi feita uma análise comparativa de desempenho com os algoritmos da biblioteca scikit-learn do Python. Primeiramente, são discutidos métodos de treinamento mais simples, como a descida do gradiente, a descida do gradiente com momentum e a descida do gradiente estocástica.
Técnicas do MQL5 Wizard que você deve conhecer (Parte 35): Regressão por Vetores de Suporte Técnicas do MQL5 Wizard que você deve conhecer (Parte 35): Regressão por Vetores de Suporte
A Regressão por Vetores de Suporte é uma maneira idealista de encontrar uma função ou 'hiperplano' que melhor descreva a relação entre dois conjuntos de dados. Tentamos explorar isso na previsão de séries temporais dentro das classes personalizadas do MQL5 wizard.
Redes neurais em trading: Modelo hiperbólico de difusão latente (HypDiff) Redes neurais em trading: Modelo hiperbólico de difusão latente (HypDiff)
Esse artigo analisa formas de codificar dados brutos no espaço latente hiperbólico por meio de processos de difusão anisotrópicos. Isso ajuda a preservar com mais precisão as características topológicas da situação atual do mercado e melhora a qualidade de sua análise.