//+------------------------------------------------------------------+ //| FrameViewer.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #include "Controls.mqh" // Классы контроллов #include "Table.mqh" // Класс таблиц #include // Массив вещественных данных #define CELL_W 128 // Ширина ячеек таблицы #define CELL_H 19 // Высота ячейки таблицы #define BUTT_RES_W CELL_W+30 // Ширина кнопки выбора результата оптимизации #define DATA_COUNT 8 // Количество данных #define FRAME_ID 1 // Идентификатор фреймов #define TABLE_OPT_STAT_ID 1 // Идентификатор таблицы статистики на вкладке оптимизации #define TABLE_OPT_INP_ID 2 // Идентификатор таблицы входных параметров на вкладке оптимизации //+------------------------------------------------------------------+ //| Класс прогресс-бара, рисующий двумя цветами | //+------------------------------------------------------------------+ class CColorProgressBar :public CObject { private: CCanvas *m_background; // Указатель на объект класса CCanvas для рисования на фоне CCanvas *m_foreground; // Указатель на объект класса CCanvas для рисования на переднем плане CRect m_bound; // Координаты и размеры рабочей области color m_good_color, m_bad_color; // Цвета прибыльной и убыточной серий color m_back_color, m_fore_color; // Цвета фона и рамки bool m_passes[]; // Количество обработанных проходов int m_last_index; // Номер последнего прохода public: //--- Конструктор/деструктор CColorProgressBar(void); ~CColorProgressBar(void){}; //--- Устанавливает указатель на канвас void SetCanvas(CCanvas *background, CCanvas *foreground) { if(background==NULL) { ::Print(__FUNCTION__, ": Error. Background is NULL"); return; } if(foreground==NULL) { ::Print(__FUNCTION__, ": Error. Foreground is NULL"); return; } this.m_background=background; this.m_foreground=foreground; } //--- Устанавливает координаты и размеры рабочей области на канвасе void SetBound(const int x1, const int y1, const int x2, const int y2) { this.m_bound.SetBound(x1, y1, x2, y2); } //--- Возврат координат границ прямоугольной области int X1(void) const { return this.m_bound.left; } int Y1(void) const { return this.m_bound.top; } int X2(void) const { return this.m_bound.right; } int Y2(void) const { return this.m_bound.bottom; } //--- Установка цвета фона и рамки void SetBackColor(const color clr) { this.m_back_color=clr; } void SetForeColor(const color clr) { this.m_fore_color=clr; } //--- Возврат цвета фона и рамки color BackColor(void) const { return this.m_back_color; } color ForeColor(void) const { return this.m_fore_color; } //--- Сбрасывает счетчик в ноль void Reset(void) { this.m_last_index=0; } //--- Добавляет результат для отрисовки полоски в прогресс-баре void AddResult(bool good, const bool chart_redraw); //--- Обновляет прогресс-бар на графике void Update(const bool chart_redraw); }; //+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CColorProgressBar::CColorProgressBar() : m_last_index(0), m_good_color(clrSeaGreen), m_bad_color(clrLightPink) { //--- Зададим размер массива проходов с запасом ::ArrayResize(this.m_passes, 5000, 1000); ::ArrayInitialize(this.m_passes, 0); } //+------------------------------------------------------------------+ //| Добавление результата | //+------------------------------------------------------------------+ void CColorProgressBar::AddResult(bool good, const bool chart_redraw) { this.m_passes[this.m_last_index]=good; //--- Добавим еще одну вертикальную черту нужного цвета в прогресс-бар this.m_foreground.LineVertical(this.X1()+1+this.m_last_index, this.Y1()+1, this.Y2()-1, ::ColorToARGB(good ? this.m_good_color : this.m_bad_color)); //--- Обновление на графике this.m_foreground.Update(chart_redraw); //--- Обновление индекса this.m_last_index++; if(this.m_last_index>=this.m_bound.Width()-1) this.m_last_index=0; } //+------------------------------------------------------------------+ //| Обновление прогресс-бара на графике | //+------------------------------------------------------------------+ void CColorProgressBar::Update(const bool chart_redraw) { //--- Зальем фон цветом фона this.m_background.FillRectangle(this.X1(), this.Y1(), this.X2(), this.Y2(), ::ColorToARGB(this.m_back_color)); //--- Нарисуем рамку this.m_background.Rectangle(this.X1(), this.Y1(), this.X2(), this.Y2(), ::ColorToARGB(this.m_fore_color)); //--- Обновим чарт this.m_background.Update(chart_redraw); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Класс для отрисовки графиков статистики и таблиц | //| результатов оптимизации и параметров настройки советника | //+------------------------------------------------------------------+ class CStatChart: public CObject { private: color m_back_color; // Цвет фона color m_fore_color; // Цвет рамки int m_line_width; // Толщина линии в пискелях int m_lines; // Количество линий на графике CArrayDouble m_seria[]; // Массивы для хранения значений графика bool m_profitseria[]; // Прибыльная серия или нет int m_lastseria_index; // Индекс свежей линии на графике color m_profit_color; // Цвет прибыльной серии color m_loss_color; // Цвет убыточной серии color m_selected_color; // Цвет выбранной лучшей серии protected: CCanvas *m_background; // Указатель на объект класса CCanvas для рисования на фоне CCanvas *m_foreground; // Указатель на объект класса CCanvas для рисования на переднем плане CRect m_bound_chart; // Рабочая область графика CRect m_bound_head; // Рабочая область заголовка чарта CColorProgressBar m_progress_bar; // Прогресс-бар CButton m_button_replay; // Кнопка воспроизведения CButtonSwitch m_button_res; // Кнопка выбора одного из трёх лучших результатов int m_tab_id; // Идентификатор вкладки public: //--- Конструктор/деструктор CStatChart() : m_lastseria_index(0), m_profit_color(clrForestGreen), m_loss_color(clrOrangeRed), m_selected_color(clrDodgerBlue), m_tab_id(0) {}; ~CStatChart() { this.m_background=NULL; this.m_foreground=NULL; } //--- Устанавливает указатель на канвас void SetCanvas(CCanvas *background, CCanvas *foreground) { if(background==NULL) { ::Print(__FUNCTION__, ": Error. Background is NULL"); return; } if(foreground==NULL) { ::Print(__FUNCTION__, ": Error. Foreground is NULL"); return; } this.m_background=background; this.m_foreground=foreground; this.m_progress_bar.SetCanvas(background, foreground); } //--- Устанавливает координаты и размеры рабочей области чарта и прогресс-бара на канвасе void SetChartBounds(const int x1, const int y1, const int x2, const int y2) { this.m_bound_chart.SetBound(x1, y1, x2, y2); this.SetBoundHeader(x1, y1-CELL_H, x2, y1); this.m_progress_bar.SetBound(x1, y2-CELL_H, x2, y2); } //--- Устанавливает координаты и размеры заголовка чарта на канвасе void SetBoundHeader(const int x1, const int y1, const int x2, const int y2) { this.m_bound_head.SetBound(x1, y1, x2, y2); } //--- Возвращает указатель на (1) себя, (2) прогресс-бар CStatChart *Get(void) { return &this; } CColorProgressBar*GetProgressBar(void) { return(&this.m_progress_bar); } //--- Установка/возврат идентификатора вкладки void SetTabID(const int id) { this.m_tab_id=id; } int TabID(void) const { return this.m_tab_id; } //--- Возврат координат границ прямоугольной области чарта int X1(void) const { return this.m_bound_chart.left; } int Y1(void) const { return this.m_bound_chart.top; } int X2(void) const { return this.m_bound_chart.right; } int Y2(void) const { return this.m_bound_chart.bottom; } //--- Возврат координат границ прямоугольной области заголовка int HeaderX1(void) const { return this.m_bound_head.left; } int HeaderY1(void) const { return this.m_bound_head.top; } int HeaderX2(void) const { return this.m_bound_head.right; } int HeaderY2(void) const { return this.m_bound_head.bottom; } //--- Возврат координат границ прямоугольной области прогресс-бара int ProgressBarX1(void) const { return this.m_progress_bar.X1(); } int ProgressBarY1(void) const { return this.m_progress_bar.Y1(); } int ProgressBarX2(void) const { return this.m_progress_bar.X2(); } int ProgressBarY2(void) const { return this.m_progress_bar.Y2(); } //--- Возвращает указатель на кнопку (1) воспроизведения, (2) выбора результата (3) худшего, (4) среднего, (5) лучшего результата CButton *ButtonReplay(void) { return(&this.m_button_replay); } CButtonSwitch *ButtonResult(void) { return(&this.m_button_res); } CButtonTriggered *ButtonResultMin(void) { return(this.m_button_res.GetButton(0)); } CButtonTriggered *ButtonResultMid(void) { return(this.m_button_res.GetButton(1)); } CButtonTriggered *ButtonResultMax(void) { return(this.m_button_res.GetButton(2)); } //--- (1) Скрывает, (2) показывает, (3) переносит на передний план кнопку вывбора результатов bool ButtonsResultHide(void) { return(this.m_button_res.Hide()); } bool ButtonsResultShow(void) { return(this.m_button_res.Show()); } bool ButtonsResultBringToTop(void) { return(this.m_button_res.BringToTop()); } //--- Создаёт кнопку воспроизведения bool CreateButtonReplay(void) { if(this.m_background==NULL) { ::PrintFormat("%s: Background is not assigned (use SetCanvas() function first)"); return false; } string text="Optimization Completed: Click to Replay"; int w=this.m_background.TextWidth(text); //--- Левая верхняя координата кнопки CPoint cp=this.m_bound_head.CenterPoint(); int x=cp.x-w/2; int y=this.Y1()+this.m_bound_head.top-2; //--- Создаём кнопу и устанавливаем для неё новые цвета, скрываем созданную кнопку if(!this.m_button_replay.Create(::StringFormat("Tab%d_ButtonReplay", this.m_tab_id), text, x, y, w, CELL_H-1)) return false; this.m_button_replay.SetDefaultColors(COLOR_BACKGROUND, STATE_OFF, C'144,238,144', C'144,228,144', C'144,218,144', clrSilver); this.m_button_replay.SetDefaultColors(COLOR_BORDER, STATE_OFF, C'144,238,144', C'144,228,144', C'144,218,144', clrSilver); this.m_button_replay.SetDefaultColors(COLOR_FOREGROUND, STATE_OFF, clrBlack, clrBlack, clrBlack, clrGray); this.m_button_replay.ResetUsedColors(STATE_OFF); this.m_button_replay.Draw(false); this.m_button_replay.Hide(); return true; } //--- Создаёт кнопку выбора результатов bool CreateButtonResults(void) { if(this.m_background==NULL) { ::PrintFormat("%s: Background is not assigned (use SetCanvas() function first)"); return false; } //--- Левая верхняя координата кнопки int x=this.m_bound_head.left+1; int y=this.m_progress_bar.Y1()+CELL_H+2; int w=BUTT_RES_W; //--- Создаём кнопу и устанавливаем для неё новые цвета, скрываем созданную кнопку if(!this.m_button_res.Create(::StringFormat("Tab%u_ButtonRes",this.m_tab_id), "", x, y, w, CELL_H-1)) return false; string text[3]={"Worst result of the top 3", "Average result of the top 3", "Best result of the top 3"}; if(!this.m_button_res.AddNewButton(text, w)) return false; this.m_button_res.GetButton(0).SetDefaultColors(COLOR_BORDER, STATE_OFF, C'228,228,228', C'228,228,228', C'228,228,228', clrSilver); this.m_button_res.GetButton(0).ResetUsedColors(STATE_OFF); this.m_button_res.GetButton(1).SetDefaultColors(COLOR_BORDER, STATE_OFF, C'228,228,228', C'228,228,228', C'228,228,228', clrSilver); this.m_button_res.GetButton(1).ResetUsedColors(STATE_OFF); this.m_button_res.GetButton(2).SetDefaultColors(COLOR_BORDER, STATE_OFF, C'228,228,228', C'228,228,228', C'228,228,228', clrSilver); this.m_button_res.GetButton(2).ResetUsedColors(STATE_OFF); this.m_button_res.Draw(false); this.m_button_res.Hide(); return true; } //--- Устанавливает цвет фона void SetBackColor(const color clr) { this.m_back_color=clr; this.m_progress_bar.SetBackColor(clr); } //--- Устанавливает цвет рамки void SetForeColor(const color clr) { this.m_fore_color=clr; this.m_progress_bar.SetForeColor(clr); } //--- Задаёт количество линий на графике void SetLines(const int num) { this.m_lines=num; ::ArrayResize(this.m_seria, num); ::ArrayResize(this.m_profitseria, num); } //--- Установка цвета (1) прибыльной, (2) убыточной, (3) выбранной серии void SetProfitColorLine(const color clr) { this.m_profit_color=clr; } void SetLossColorLine(const color clr) { this.m_loss_color=clr; } void SetSelectedLineColor(const color clr) { this.m_selected_color=clr; } //--- Обновление объекта на экране void Update(color clr, const int line_width, const bool chart_redraw); //--- Добавление данных из массива void AddSeria(const double &array[], bool profit); //--- Рисует график void Draw(const int seria_index, color clr, const int line_width, const bool chart_redraw); //--- Рисует линию в привычных координатах (слева-направо, снизу-вверх) void Line(int x1, int y1, int x2, int y2, uint col, int size); //--- Получение макс. и мин. значения в серии double MaxValue(const int seria_index); double MinValue(const int seria_index); //--- Обработчик событий void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- Если кнопка воспроизведения не скрыта - вызываем её обработчик событий if(!this.m_button_replay.IsHidden()) this.m_button_replay.OnChartEvent(id, lparam, dparam, sparam); //--- Если кнопка выбора результата не скрыта - вызываем её обработчик событий if(!this.m_button_res.IsHidden()) this.m_button_res.OnChartEvent(id, lparam, dparam, sparam); } }; //+------------------------------------------------------------------+ //| Обновление чарта | //+------------------------------------------------------------------+ void CStatChart::Update(color clr, const int line_width, const bool chart_redraw) { //--- Если канвас для фона или переднего плана не установлен - уходим if(this.m_background==NULL || this.m_foreground==NULL) return; //--- StatChart зальем фон this.m_background.FillRectangle(this.X1(), this.Y1(), this.X2(), this.Y2(), ::ColorToARGB(this.m_back_color)); //--- StatChart нарисуем рамку this.m_background.Rectangle(this.X1(), this.Y1(), this.X2(), this.Y2(), ::ColorToARGB(this.m_fore_color)); //--- ProgressBar зальем фон и нарисуем рамку this.m_progress_bar.Update(false); //--- Отрисуем каждую серию на 80% доступной площади чарта по вертикали и горизонтали for(int i=0; i=this.m_lines) this.m_lastseria_index=0; } //+------------------------------------------------------------------+ //| Получение максимального значения указанной серии | //+------------------------------------------------------------------+ double CStatChart::MaxValue(const int seria_index) { double res=this.m_seria[seria_index].At(0); int total=this.m_seria[seria_index].Total(); //--- Переберем массив и сравним каждые две соседние серии for(int i=1; ires) res=this.m_seria[seria_index].At(i); } //--- результат return res; } //+------------------------------------------------------------------+ //| Получение минимального значения указанной серии | //+------------------------------------------------------------------+ double CStatChart::MinValue(const int seria_index) { double res=this.m_seria[seria_index].At(0);; int total=this.m_seria[seria_index].Total(); //--- Переберем массив и сравним каждые две соседние серии for(int i=1; i ::NormalizeDouble(obj.SharpeRatio(),2) ? 1 : ::NormalizeDouble(this.SharpeRatio(),2) < ::NormalizeDouble(obj.SharpeRatio(),2) ? -1 : 0); case FRAME_PROP_NET_PROFIT : return(::NormalizeDouble(this.NetProfit(),2) > ::NormalizeDouble(obj.NetProfit(),2) ? 1 : ::NormalizeDouble(this.NetProfit(),2) < ::NormalizeDouble(obj.NetProfit(),2) ? -1 : 0); case FRAME_PROP_PROFIT_FACTOR : return(::NormalizeDouble(this.ProfitFactor(),2) > ::NormalizeDouble(obj.ProfitFactor(),2) ? 1 : ::NormalizeDouble(this.ProfitFactor(),2) < ::NormalizeDouble(obj.ProfitFactor(),2) ? -1 : 0); case FRAME_PROP_RECOVERY_FACTOR : return(::NormalizeDouble(this.RecoveryFactor(),2)> ::NormalizeDouble(obj.RecoveryFactor(),2) ? 1 : ::NormalizeDouble(this.RecoveryFactor(),2)< ::NormalizeDouble(obj.RecoveryFactor(),2) ? -1 : 0); //---FRAME_PROP_PASS_NUM default : return(this.Pass()>obj.Pass() ? 1 : this.Pass()WRONG_VALUE) return false; return this.m_list_frames.Add(frame); } //--- Рисует таблицу статистики оптимизации на указанной вкладке void TableStatDraw(const uint tab_id, const int x, const int y, const int w, const int h, const bool chart_redraw); //--- Рисует таблицу входных параметров оптимизации на указанной вкладке void TableInpDraw(const uint tab_id, const int x, const int y, const int w, const int h, const uint rows, const bool chart_redraw); //--- Рисует график оптимизации на указанной вкладке void ChartOptDraw(const uint tab_id, const bool opt_completed, const bool chart_redraw); //--- Рисует таблицы данных и график оптимизации void DrawDataChart(const uint tab_id); //--- Рисует графики трёх лучших проходов по критерию оптимизации void DrawBestFrameData(const uint tab_id, const int res_index); //--- Управляет отображением управляющих объектов на графиках оптимизации void ControlObjectsView(const uint tab_id); //--- Повторное проигрывание фреймов после окончания оптимизации void ReplayFrames(const int delay_ms); //--- Получение данных текущего фрейма и вывод их на указанной вкладке в таблицу и на график результатов оптимизации bool DrawFrameData(const uint tab_id, const string text, color clr, const uint line_width, ulong &pass, string ¶ms[], uint &par_count, double &data[]); //--- Выводит данные указанного фрейма на график оптимизации bool DrawFrameDataByPass(const uint tab_id, const ulong pass_num, const string text, color clr, const uint line_width, double &data[]); //--- Заполняет массив индексами фреймов трёх лучших проходов для указанного критерия оптимизации (по индексу вкладки) bool FillArrayBestFrames(const uint tab_id, ulong &array_passes[]); //--- Выводит на графики результатов оптимизации на каждой вкладке по три лучших прохода void DrawBestFrameDataAll(void); //--- Ищет и возвращает указатель на объект фрейма, со значением свойства меньше образца CFrameData *FrameSearchLess(CFrameData *frame, const int mode); public: //--- Установка толщины выбранной линии void SetSelectedLineWidth(const uint width) { this.m_line_width=width; } //--- Установка цвета прибыльной серии void SetProfitColorLine(const color clr) { int total=this.m_tab_control.TabsTotal(); for(int i=1; i0 ? this.GetChartStats(tab_selected).ButtonResult() : NULL); uint res_index=(button_switch!=NULL ? button_switch.SelectedButton() : -1); //--- В зависимости от выбранной вкладки switch(tab_selected) { //--- вкладка 0 (Optimization) case 0 : //--- Рисуем график с линией последнего прохода и две пустые таблицы this.DrawDataChart(0); //--- Запускает воспроизведение проведённой оптимизации, //--- что останавливает работу с остальным пока длится воспроизведение //if(this.m_completed) // this.ReplayFrames(1); break; //--- вкладки 1 - 4 default: //--- Получаем индекс выбранной кнопки прохода оптимизации res_index=button_switch.SelectedButton(); //--- Рисуем график с результатами трёх лучших проходов выбранной вкладки this.DrawDataChart(tab_selected); this.DrawBestFrameData(tab_selected, -1); this.DrawBestFrameData(tab_selected, res_index); //--- На вкладке 0 рисуем график с линией последнего прохода и две пустые таблицы this.DrawDataChart(0); //--- Запускает воспроизведение проведённой оптимизации, //--- что останавливает работу с остальным пока длится воспроизведение //--- Чтобы заново нарисовать графики всех проходов, можно нажать кнопку воспроизведения //if(this.m_completed) // this.ReplayFrames(1); break; } //--- Запомним новые размеры для последующей проверки this.m_w=w; this.m_h=h; } } //--- Если процесс оптимизации не завершён - уходим if(!this.m_completed) return; //--- Если пришло пользовательское событие if(id>CHARTEVENT_CUSTOM) { //--- Если пришло событие кнопки Replay и оптимизация завершена if(sparam==this.m_chart_stat_0.ButtonReplay().Name() && this.m_completed) { //--- скроем кнопку Replay, this.m_chart_stat_0.ButtonReplay().Hide(); //--- Инициализируем график результатов оптимизации, this.ChartOptDraw(0, this.m_completed, true); //--- запустим воспроизведение, this.m_completed=false; // заблокируем, чтобы не запустить несколько раз подряд this.ReplayFrames(delay_ms); // процедура воспроизведения this.m_completed=true; // снимаем блокировку //--- После завершения воспроизведения покажем кнопку Replay и перерисуем график this.m_chart_stat_0.ButtonReplay().Show(); ::ChartRedraw(); } //--- Получаем указатели на кнопки вкладок CTabButton *tab_btn0=this.m_tab_control.GetTabButton(0); CTabButton *tab_btn1=this.m_tab_control.GetTabButton(1); CTabButton *tab_btn2=this.m_tab_control.GetTabButton(2); CTabButton *tab_btn3=this.m_tab_control.GetTabButton(3); CTabButton *tab_btn4=this.m_tab_control.GetTabButton(4); if(tab_btn0==NULL || tab_btn1==NULL || tab_btn2==NULL || tab_btn3==NULL || tab_btn4==NULL) return; //--- Получаем идентификатор выбранной вкладки int tab_selected=this.m_tab_control.GetSelectedTabID(); //--- Если пришло событие переключения на вкладку 0 if(sparam==tab_btn0.Name()) { //--- На вкладке 0 рисуем график с линией последнего прохода и две таблицы с пустыми результатами this.DrawDataChart(0); //--- Запускает воспроизведение проведённой оптимизации //--- (может долго длиться - при желании, чтобы отобразить графики, можно нажать кнопку Replay) //if(this.m_completed) // this.ReplayFrames(1); ::ChartRedraw(); return; } //--- Получаем указатель на чарт выбранной вкладки CStatChart *chart_stat=this.GetChartStats(tab_selected); if(tab_selected==0 || chart_stat==NULL) return; //--- Получаем указатели на кнопки чарта выбранной вкладки (индекс вкладки 1 - 4) CButtonTriggered *button_min=chart_stat.ButtonResultMin(); CButtonTriggered *button_mid=chart_stat.ButtonResultMid(); CButtonTriggered *button_max=chart_stat.ButtonResultMax(); if(button_min==NULL || button_mid==NULL || button_max==NULL) return; //--- Если пришло событие переключения на вкладку 1 if(sparam==tab_btn1.Name()) { //--- вызываем обработчик переключения на вкладку this.OnTabSwitchEvent(1); } //--- Если пришло событие переключения на вкладку 2 if(sparam==tab_btn2.Name()) { //--- вызываем обработчик переключения на вкладку this.OnTabSwitchEvent(2); } //--- Если пришло событие переключения на вкладку 3 if(sparam==tab_btn3.Name()) { //--- вызываем обработчик переключения на вкладку this.OnTabSwitchEvent(3); } //--- Если пришло событие переключения на вкладку 4 if(sparam==tab_btn4.Name()) { //--- вызываем обработчик переключения на вкладку this.OnTabSwitchEvent(4); } //--- Если пришло событие нажатие на кнопку минимального результата выбранной вкладки if(sparam==button_min.Name()) { //--- вызываем обработчик переключения кнопки-переключателя this.OnButtonSwitchEvent(tab_selected, 0); } //--- Если пришло событие нажатие на кнопку среднего результата выбранной вкладки if(sparam==button_mid.Name()) { //--- вызываем обработчик переключения кнопки-переключателя this.OnButtonSwitchEvent(tab_selected, 1); } //--- Если пришло событие нажатие на кнопку лучшего результата выбранной вкладки if(sparam==button_max.Name()) { //--- вызываем обработчик переключения кнопки-переключателя this.OnButtonSwitchEvent(tab_selected, 2); } } } //+------------------------------------------------------------------+ //| Обработчик переключения вкладки | //+------------------------------------------------------------------+ void CFrameViewer::OnTabSwitchEvent(const int tab_id) { //--- Получаем указатель на чарт выбранной вкладки CStatChart *chart_stat=this.GetChartStats(tab_id); if(chart_stat==NULL) return; //--- Получаем указатель на кнопку-переключатель чарта выбранной вкладки CButtonSwitch *button_switch=chart_stat.ButtonResult(); if(button_switch==NULL) return; //--- Индекс нажатой кнопки uint butt_index=button_switch.SelectedButton(); //--- Инициализируем график результатов на вкладке tab_id и this.DrawDataChart(tab_id); //--- вызываем метод, контролирующий отображение управляющих элементов на всех вкладках this.ControlObjectsView(tab_id); //--- Рисуем все три лучших прохода this.DrawBestFrameData(tab_id, -1); //--- Выделяем проход, выбранный кнопкой this.DrawBestFrameData(tab_id, butt_index); } //+------------------------------------------------------------------+ //| Обработчик переключения кнопки-переключателя | //+------------------------------------------------------------------+ void CFrameViewer::OnButtonSwitchEvent(const int tab_id, const uint butt_id) { //--- Инициализируем график результатов на вкладке tab_id this.DrawDataChart(tab_id); //--- Рисуем все три лучших прохода this.DrawBestFrameData(tab_id, -1); //--- Выделяем проход, выбранный кнопкой butt_id this.DrawBestFrameData(tab_id, butt_id); } //+------------------------------------------------------------------+ //| Рисует таблицы данных и график оптимизации | //+------------------------------------------------------------------+ void CFrameViewer::DrawDataChart(const uint tab_id) { //--- Рисуем таблицу статистики, таблицу входных параметров и график оптимизации this.TableStatDraw(tab_id, 4, 4, CELL_W*2, CELL_H, false); this.TableInpDraw(tab_id, 4, this.GetTableStats(tab_id).Y2()+4, CELL_W*2, CELL_H, this.GetTableInputs(tab_id).RowsTotal(), false); this.ChartOptDraw(tab_id, this.m_completed, true); //--- вызываем метод, контролирующий отображение управляющих элементов на всех вкладках this.ControlObjectsView(tab_id); } //+------------------------------------------------------------------+ //| Повторное проигрывание фреймов после окончания оптимизации | //+------------------------------------------------------------------+ void CFrameViewer::ReplayFrames(const int delay_ms) { //--- Переменные для работы со фреймами string name; ulong pass; long id; double value, data[]; string params[]; uint par_count; //--- Счетчик фреймов int frame_counter=0; //--- Очистим счетчики прогресс-бара this.m_progress_bar.Reset(); this.m_progress_bar.Update(false); //--- Переводим указатель фреймов в начало и запускаем перебор фреймов ::FrameFirst(); while(!::IsStopped() && ::FrameNext(pass, name, id, value, data)) { //--- Увеличиваем счётчик фреймов и подготавливаем текст заголовка графика оптимизации frame_counter++; string text=::StringFormat("Playing with pause %d ms: frame %d", delay_ms, frame_counter); //--- Получаем входные параметры эксперта, для которых сформирован фрейм, данные фрейма и выводим их на график if(this.DrawFrameData(0, text, clrNONE, 0, pass, params, par_count, data)) ::ChartRedraw(); //--- Подождём delay_ms миллисекунд ::Sleep(delay_ms); } } //+------------------------------------------------------------------+ //| Выводит данные указанного фрейма на график оптимизации | //+------------------------------------------------------------------+ bool CFrameViewer::DrawFrameDataByPass(const uint tab_id, const ulong pass_num, const string text, color clr, const uint line_width, double &data[]) { //--- Переменные для работы со фреймами string name; ulong pass; long id; uint par_count; double value; string params[]; //--- Переводим указатель фреймов в начало и запускаем поиск фрейма pass_num ::FrameFirst(); while(::FrameNext(pass, name, id, value, data)) { //--- Если номер прохода соответствует искомому - //--- получаем данные фрейма и выводим их в таблицу //--- и на график на вкладке tab_id if(pass==pass_num) { if(DrawFrameData(tab_id, text, clr, line_width, pass, params, par_count, data)) return true; } } //--- Проход не найден return false; } //+------------------------------------------------------------------+ //| Рисует графики трёх лучших проходов по критерию оптимизации | //+------------------------------------------------------------------+ void CFrameViewer::DrawBestFrameData(const uint tab_id, const int res_index) { //--- Если переданы некорректные идентификаторы таблицы и нажатой кнопки - уходим if(tab_id<1 || tab_id>4 || res_index>2) { ::PrintFormat("%s: Error. Incorrect table (%u) or selected button (%d) identifiers passed",__FUNCTION__, tab_id, res_index); return; } //--- Массивы для получения результатов проходов ulong array_passes[3]; double data[]; //--- Создаём текст заголовка графика проходов string res= ( tab_id==1 ? "Results by Sharpe Ratio" : tab_id==2 ? "Results by Net Profit" : tab_id==3 ? "Results by Profit Factor" : tab_id==4 ? "Results by Recovery Factor" : "" ); string text="Optimization Completed: "+res; //--- Заполняем массив array_passes индексами трёх лучших проходов this.FillArrayBestFrames(tab_id, array_passes); //--- Если индекс кнопки прохода задан отрицательным числом - if(res_index<0) { //--- выводим на график все три прохода //--- (цвет линий указывается как clrNONE для автоматического выбора цвета линий прибыльной или убыточной серий) for(int i=0; i<(int)array_passes.Size(); i++) this.DrawFrameDataByPass(tab_id, array_passes[i], text, clrNONE, 0, data); } //--- Иначе - выводим на график серию, указанную индексом нажатой кнопки (res_index), //--- цветом, заданным в m_selected_color, и толщиной, указанной в m_line_width else this.DrawFrameDataByPass(tab_id, array_passes[res_index], text, this.m_selected_color, this.m_line_width, data); } //+------------------------------------------------------------------+ //| Выводит на графики результатов оптимизации | //| на каждой вкладке по три лучших прохода | //+------------------------------------------------------------------+ void CFrameViewer::DrawBestFrameDataAll(void) { //--- В цикле по всем вкладкам от вкладки 1, рисуем графики трёх лучших проходов для каждой вкладки for(int i=1; i4) { ::PrintFormat("%s: Error: Invalid tab ID passed (%u)",__FUNCTION__, tab_id); return false; } //--- Получаем указатели на используемые объекты на указанной вкладке CCanvas *foreground=this.m_tab_control.GetTabForeground(tab_id); CTableDataControl *table_stat=this.GetTableStats(tab_id); CTableDataControl *table_inp=this.GetTableInputs(tab_id); CStatChart *chart_stat=this.GetChartStats(tab_id); if(foreground==NULL || table_stat==NULL || table_inp==NULL || chart_stat==NULL) return false; //--- Получим входные параметры эксперта, для которых сформирован фрейм, данные фрейма и выведем их на график ::ResetLastError(); if(::FrameInputs(pass, params, par_count)) { //--- Нарисуем таблицу входных параметров на графике this.TableInpDraw(tab_id, 4, table_stat.Y2()+4, CELL_W*2, CELL_H, par_count, false); //--- Перебираем параметры, params[i], строка выглядит как "parameter=value" for(uint i=0; i0); //--- Обновим линии баланса на графике chart_stat.Update(clr, line_width, false); //--- Обновим прогресс бар (только для вкладки с идентификатором 0) if(tab_id==0) this.m_progress_bar.AddResult(data[1]>0, false); //--- Обновим надпись на шапке графика int x1=chart_stat.HeaderX1(); int y1=chart_stat.HeaderY1(); int x2=chart_stat.HeaderX2(); int y2=chart_stat.HeaderY2(); int x=(x1+x2)/2; int y=(y1+y2)/2; foreground.FillRectangle(x1, y1, x2, y2, 0x00FFFFFF); foreground.FontSet("Calibri", -100, FW_BLACK); foreground.TextOut(x, y, text, ::ColorToARGB(clrMidnightBlue), TA_CENTER|TA_VCENTER); foreground.Update(false); //--- Всё успешно return true; } //--- Что-то пошло не так... else PrintFormat("%s: FrameInputs() failed. Error %d",__FUNCTION__, ::GetLastError()); return false; } //+------------------------------------------------------------------+ //| Заполняет массив индексами фреймов трёх лучших проходов | //| для указанного критерия оптимизации (по индексу вкладки) | //+------------------------------------------------------------------+ bool CFrameViewer::FillArrayBestFrames(const uint tab_id, ulong &array_passes[]) { //--- Очищаем переданный в метод массив индексов проходов оптимизации ::ZeroMemory(array_passes); //FRAME_PROP_PASS_NUM, // Номер прохода //FRAME_PROP_SHARPE_RATIO, // Результат Sharpe Ratio //FRAME_PROP_NET_PROFIT, // Результат Net Profit //FRAME_PROP_PROFIT_FACTOR, // Результат Profit Factor //FRAME_PROP_RECOVERY_FACTOR, // Результат Recovery Factor //--- По идентификатору вкладки будем определять свойство, по которому искать лучшие проходы оптимизации //--- Проверяем идентификатор вкладки, чтобы был в пределах от 1 до 4 if(tab_idFRAME_PROP_RECOVERY_FACTOR) { ::PrintFormat("%s: Error: Invalid tab ID passed (%u)",__FUNCTION__, tab_id); return false; } //--- Преобразуем идентификатор таблицы в свойство фрейма ENUM_FRAME_PROP prop=(ENUM_FRAME_PROP)tab_id; //--- Сортируем список фреймов в порядке возрастания по свойству, //--- соответствующему значению tab_id в виде перечисления ENUM_FRAME_PROP this.m_list_frames.Sort(prop); //--- После сортировки, фрейм с лучшим результатом будет находиться в конце списка //--- Получаем по индексу фрейм из списка с максимальным значением результата и int index=this.m_list_frames.Total()-1; CFrameData *frame_next=this.m_list_frames.At(index); if(frame_next==NULL) return false; //--- записываем номер прохода в последнюю ячейку массива array_passes array_passes[2]=frame_next.Pass(); //--- Теперь найдём объекты, у которых результат оптимизации по убыванию меньше найденного максимального //--- В цикле от 1 до 0 (оставшиеся ячейки массива array_passes) for(int i=1; i>=0; i--) { //--- ищем предыдущий объект со значением свойства меньше, чем у объекта frame_next frame_next=this.FrameSearchLess(frame_next, prop); //--- В очередную ячейку массива array_passes вписываем номер прохода найденного объекта //--- Если объект не найден - значит, нет объектов со значением, меньше, чем у объекта frame_next, //--- и в очередную ячейку массива array_passes в этом случае записываем его предыдущее значение array_passes[i]=(frame_next!=NULL ? frame_next.Pass() : array_passes[i+1]); } //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| Ищет и возвращает указатель на объект фрейма, | //| со значением свойства меньше образца | //+------------------------------------------------------------------+ CFrameData *CFrameViewer::FrameSearchLess(CFrameData *frame, const int mode) { //--- В зависимости от типа свойства фрейма switch(mode) { //--- во временный объект записываем соответствующее свойство переданного в метод объекта case FRAME_PROP_SHARPE_RATIO : this.m_frame_tmp.SetSharpeRatio(frame.SharpeRatio()); break; case FRAME_PROP_NET_PROFIT : this.m_frame_tmp.SetNetProfit(frame.NetProfit()); break; case FRAME_PROP_PROFIT_FACTOR : this.m_frame_tmp.SetProfitFactor(frame.ProfitFactor()); break; case FRAME_PROP_RECOVERY_FACTOR : this.m_frame_tmp.SetRecoveryFactor(frame.RecoveryFactor()); break; default : this.m_frame_tmp.SetPass(frame.Pass()); break; } //--- Сортируем массив фреймов по указанному свойству и this.m_list_frames.Sort(mode); //--- получаем индекс ближайшего объекта с меньшим значением свойства, либо -1 int index=this.m_list_frames.SearchLess(&this.m_frame_tmp); //--- Получаем из списка объект по индексу и возвращаем указатель на него, либо NULL CFrameData *obj=this.m_list_frames.At(index); return obj; } //+------------------------------------------------------------------+ //| Рисует таблицу статистики оптимизации на указанной вкладке | //+------------------------------------------------------------------+ void CFrameViewer::TableStatDraw(const uint tab_id, const int x, const int y, const int w, const int h, const bool chart_redraw) { //--- Проверяем переданный идентификатор вкладки if(tab_id>4) { ::PrintFormat("%s: Error: Invalid tab ID passed (%u)",__FUNCTION__, tab_id); return; } //--- Получаем указатели на используемые объекты на указанной вкладке CCanvas *background=this.m_tab_control.GetTabBackground(tab_id); CCanvas *foreground=this.m_tab_control.GetTabForeground(tab_id); CTableDataControl *table_stat=this.GetTableStats(tab_id); if(background==NULL || foreground==NULL || table_stat==NULL) return; //--- Рисуем заголовок таблицы результатов оптимизации background.FillRectangle(x, y, x+CELL_W*2, y+CELL_H, ::ColorToARGB(C'195,209,223')); // C'180,190,230' foreground.FillRectangle(x+1, y+1, x+CELL_W*2-1, y+CELL_H-1, 0x00FFFFFF); foreground.FontSet("Calibri", -100, FW_BLACK); foreground.TextOut(x+(CELL_W*2)/2, y+CELL_H/2, "Optimization results", ::ColorToARGB(clrMidnightBlue), TA_CENTER|TA_VCENTER); //--- Задаём таблице её идентификатор и рисуем сетку таблицы table_stat.SetID(TABLE_OPT_STAT_ID+10*tab_id); table_stat.DrawGrid(background, x, y+CELL_H, 0, DATA_COUNT, 2, CELL_H, CELL_W, C'200,200,200', false); //--- Нарисуем пустую таблицу результатов оптимизации - только заголовки, без значений //--- В цикле по строкам таблицы int total=table_stat.RowsTotal(); for(int row=0; row4) { ::PrintFormat("%s: Error: Invalid tab ID passed (%u)",__FUNCTION__, tab_id); return; } //--- Получаем указатели на используемые объекты на указанной вкладке CCanvas *background=this.m_tab_control.GetTabBackground(tab_id); CCanvas *foreground=this.m_tab_control.GetTabForeground(tab_id); CTableDataControl *table_inp=this.GetTableInputs(tab_id); if(background==NULL || foreground==NULL || table_inp==NULL) return; //--- Рисуем заголовок таблицы параметров оптимизации background.FillRectangle(x, y, x+CELL_W*2, y+CELL_H, ::ColorToARGB(C'195,209,223')); foreground.FillRectangle(x+1, y+1, x+CELL_W*2-1, y+CELL_H-1, 0x00FFFFFF); foreground.FontSet("Calibri", -100, FW_BLACK); foreground.TextOut(x+(CELL_W*2)/2, y+CELL_H/2, "Input parameters", ::ColorToARGB(clrMidnightBlue), TA_CENTER|TA_VCENTER); //--- Задаём таблице её идентификатор и рисуем сетку таблицы table_inp.SetID(TABLE_OPT_INP_ID+10*tab_id); table_inp.DrawGrid(background, x, y+CELL_H, 0, rows, 2, CELL_H, CELL_W, C'200,200,200', false); //--- Обновим канвас фона и переднего плана background.Update(false); foreground.Update(chart_redraw); } //+------------------------------------------------------------------+ //| Рисует график оптимизации на указанной вкладке | //+------------------------------------------------------------------+ void CFrameViewer::ChartOptDraw(const uint tab_id, const bool opt_completed, const bool chart_redraw) { //--- Проверяем переданный идентификатор вкладки if(tab_id>4) { ::PrintFormat("%s: Error: Invalid tab ID passed (%u)",__FUNCTION__, tab_id); return; } //--- Получаем указатели на используемые объекты на указанной вкладке CCanvas *background=this.m_tab_control.GetTabBackground(tab_id); CCanvas *foreground=this.m_tab_control.GetTabForeground(tab_id); CTab *tab=this.m_tab_control.GetTab(tab_id); CTableDataControl *table_stat=this.GetTableStats(tab_id); CStatChart *chart_stat=this.GetChartStats(tab_id); if(background==NULL || foreground==NULL || tab==NULL || table_stat==NULL || chart_stat==NULL) return; //--- Рассчитаем координаты четырёх углов графика результатов оптимизации int x1=table_stat.X2()+10; int y1=table_stat.Y1(); int x2=tab.GetField().Right()-10; int y2=tab.GetField().Bottom()-tab.GetButton().Height()-12; //--- Проверим ограничения размеров по минимальным ширине и высоте (480 x 180) int w_min=480; if(x2-x1