Article-17457-MQL5-Optimiza.../FrameViewer.mqh
2026-03-24 15:23:43 +07:00

1901 行
187 KiB
MQL5

//+------------------------------------------------------------------+
//| 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 <Arrays\ArrayDouble.mqh> // Массив вещественных данных
#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; i++)
{
//--- Если цвет задан отсутствующим - используем цвета прибыльной и убыточной серий
if(clr==clrNONE)
{
clr=this.m_loss_color;
if(this.m_profitseria[i])
clr=this.m_profit_color;
}
//--- иначе - используем цвет, заданный для выбранной линии
else
clr=this.m_selected_color;
//--- Рисуем график результатов оптимизации
this.Draw(i, clr, line_width, false);
}
//--- Обновим оба канваса
this.m_background.Update(false);
this.m_foreground.Update(chart_redraw);
}
//+------------------------------------------------------------------+
//| Добавляет новую серию данных для отрисовки на графике |
//+------------------------------------------------------------------+
void CStatChart::AddSeria(const double &array[], bool profit)
{
//--- Добавляем массив в серию номер m_lastseria_index
this.m_seria[this.m_lastseria_index].Resize(0);
this.m_seria[this.m_lastseria_index].AddArray(array);
this.m_profitseria[this.m_lastseria_index]=profit;
//--- Отслеживаем индекс последней линии (не используется в данный момент)
this.m_lastseria_index++;
if(this.m_lastseria_index>=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; i<total; i++)
{
if(this.m_seria[seria_index].At(i)>res)
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<total; i++)
{
if(this.m_seria[seria_index].At(i)<res)
res=this.m_seria[seria_index].At(i);
}
//--- результат
return res;
}
//+------------------------------------------------------------------+
//| Перегрузка базовой функции рисования |
//+------------------------------------------------------------------+
void CStatChart::Line(int x1, int y1, int x2, int y2, uint col, int size)
{
//--- Если канвас не задан - уходим
if(this.m_foreground==NULL)
return;
//--- Так как ось Y перевернута, то нужно перевернуть y1 и y2
int y1_adj=this.m_bound_chart.Height()-CELL_H-y1;
int y2_adj=this.m_bound_chart.Height()-CELL_H-y2;
//--- Рисуем сглаженную линию
//--- Если толщина линии меньше 3, то рисуем линию с использованием алгоритма сглаживания Ву
//--- (при толщине 1 и 2 в методе LineThick() вызывается метод LineWu()),
//--- иначе - рисуем сглаженную линию заданной толщины при помощи LineThick
this.m_foreground.LineThick(x1, y1_adj, x2, y2_adj,::ColorToARGB(col), (size<1 ? 1 : size), STYLE_SOLID, LINE_END_ROUND);
}
//+------------------------------------------------------------------+
//| Отрисовка линии баланса на графике |
//+------------------------------------------------------------------+
void CStatChart::Draw(const int seria_index, color clr, const int line_width, const bool chart_redraw)
{
//--- Если канвас не задан - уходим
if(this.m_foreground==NULL)
return;
//--- Готовим коэффициенты для перевода значений в пиксели
double min=this.MaxValue(seria_index);
double max=this.MinValue(seria_index);
double size=this.m_seria[seria_index].Total();
//--- Отступы от края графика
double x_indent=this.m_bound_chart.Width()*0.05;
double y_indent=this.m_bound_chart.Height()*0.05;
//--- Вычислим коэффициенты
double k_y=(max-min)/(this.m_bound_chart.Height()-2*CELL_H-2*y_indent);
double k_x=(size)/(this.m_bound_chart.Width()-2*x_indent);
//--- Постоянные
double start_x=this.m_bound_chart.left+x_indent;
double start_y=this.m_bound_chart.bottom-2*CELL_H*2-y_indent;
//--- Теперь рисуем ломанную линию проходя по всем точкам серии
for(int i=1; i<size; i++)
{
//--- переводим значения в пиксели
int x1=(int)((i-0)/k_x+start_x); // номер значения откладываем на горизонтали
int y1=(int)(start_y-(m_seria[seria_index].At(i)-min)/k_y); // по вертикали
int x2=(int)((i-1-0)/k_x+start_x);// номер значения откладываем на горизонтали
int y2=(int)(start_y-(m_seria[seria_index].At(i-1)-min)/k_y); // по вертикали
//--- Выводим линию от предыдущей точки к текущей
this.Line(x1, y1, x2, y2, clr, line_width);
}
//--- Обновление канваса с перерисовкой графика (если флаг установлен)
this.m_foreground.Update(chart_redraw);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Перечисления |
//+------------------------------------------------------------------+
enum ENUM_FRAME_PROP // Свойства фрейма
{
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
};
//+------------------------------------------------------------------+
//| Класс данных фрейма |
//+------------------------------------------------------------------+
class CFrameData : public CObject
{
protected:
ulong m_pass; // Номер прохода
double m_sharpe_ratio; // Коэффициент Шарпа
double m_net_profit; // Общая прибыль
double m_profit_factor; // Доходность
double m_recovery_factor; // Фактор восстановления
public:
//--- Установка свойств фрейма (результатов прохода)
void SetPass(const ulong pass) { this.m_pass=pass; }
void SetSharpeRatio(const double value) { this.m_sharpe_ratio=value; }
void SetNetProfit(const double value) { this.m_net_profit=value; }
void SetProfitFactor(const double value) { this.m_profit_factor=value; }
void SetRecoveryFactor(const double value) { this.m_recovery_factor=value; }
//--- Возврат свойств фрейма (результатов прохода)
ulong Pass(void) const { return this.m_pass; }
double SharpeRatio(void) const { return this.m_sharpe_ratio; }
double NetProfit(void) const { return this.m_net_profit; }
double ProfitFactor(void) const { return this.m_profit_factor; }
double RecoveryFactor(void) const { return this.m_recovery_factor; }
//--- Описание свойств
string PassDescription(void) const { return ::StringFormat("Pass: %I64u", this.m_pass); }
string SharpeRatioDescription(void) const { return ::StringFormat("Sharpe Ratio: %.2f", this.m_sharpe_ratio); }
string NetProfitDescription(void) const { return ::StringFormat("Net Profit: %.2f", this.m_net_profit); }
string ProfitFactorDescription(void) const { return ::StringFormat("Profit Factor: %.2f", this.m_profit_factor); }
string RecoveryFactorDescription(void) const { return ::StringFormat("Recovery Factor: %.2f", this.m_recovery_factor); }
//--- Вывод в журнал свойств фрейма
void Print(void)
{
::PrintFormat("Frame %s:", this.PassDescription());
::PrintFormat(" - %s", this.SharpeRatioDescription());
::PrintFormat(" - %s", this.NetProfitDescription());
::PrintFormat(" - %s", this.ProfitFactorDescription());
::PrintFormat(" - %s", this.RecoveryFactorDescription());
}
//--- Метод сравнения двух объектов
virtual int Compare(const CObject *node,const int mode=0) const
{
//--- Вещественные значения сравниваем как двухзначные
const CFrameData *obj=node;
switch(mode)
{
case FRAME_PROP_SHARPE_RATIO : return(::NormalizeDouble(this.SharpeRatio(),2) > ::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()<obj.Pass() ? -1 : 0);
}
}
//--- Конструкторы/деструктор
CFrameData (const ulong pass, const double sharpe_ratio, const double net_profit, const double profit_factor, const double recovery_factor) :
m_pass(pass), m_sharpe_ratio(sharpe_ratio), m_net_profit(net_profit), m_profit_factor(profit_factor), m_recovery_factor(recovery_factor) {}
CFrameData (void) :
m_pass(0), m_sharpe_ratio(0), m_net_profit(0), m_profit_factor(0), m_recovery_factor(0) {}
~CFrameData (void) {}
};
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Класс просмотровщика фреймов |
//+------------------------------------------------------------------+
class CFrameViewer : public CObject
{
private:
int m_w; // Ширина графика
int m_h; // Высота графика
color m_selected_color; // Цвет выбранной серии из трёх лучших
uint m_line_width; // Толщина линии выбранной серии из трёх лучших
bool m_completed; // Флаг завершения оптимизации
CFrameData m_frame_tmp; // Объект фрейм для поиска по свойству
CArrayObj m_list_frames; // Список фреймов
CTabControl m_tab_control; // Элемент управления Tab Control
//--- Объявляем объекты вкладок на элементе управления Tab Control
//--- Вкладка 0 (Optimization) элемента управления Tab Control
CTableDataControl m_table_inp_0; // Таблица параметров оптимизации на вкладке 0
CTableDataControl m_table_stat_0; // Таблица результатов оптимизации на вкладке 0
CStatChart m_chart_stat_0; // График оптимизации на вкладке 0
CColorProgressBar*m_progress_bar; // Прогресс-бар на графике оптимизации на вкладке 0
//--- Вкладка 1 (Sharpe Ratio) элемента управления Tab Control
CTableDataControl m_table_inp_1; // Таблица параметров оптимизации на вкладке 1
CTableDataControl m_table_stat_1; // Таблица результатов оптимизации на вкладке 1
CStatChart m_chart_stat_1; // График результатов оптимизации на вкладке 1
//--- Вкладка 2 (Net Profit) элемента управления Tab Control
CTableDataControl m_table_inp_2; // Таблица параметров оптимизации на вкладке 2
CTableDataControl m_table_stat_2; // Таблица результатов оптимизации на вкладке 2
CStatChart m_chart_stat_2; // График результатов оптимизации на вкладке 2
//--- Вкладка 3 (Profit Factor) элемента управления Tab Control
CTableDataControl m_table_inp_3; // Таблица параметров оптимизации на вкладке 3
CTableDataControl m_table_stat_3; // Таблица результатов оптимизации на вкладке 3
CStatChart m_chart_stat_3; // График результатов оптимизации на вкладке 3
//--- Вкладка 4 (Recovery Factor) элемента управления Tab Control
CTableDataControl m_table_inp_4; // Таблица параметров оптимизации на вкладке 4
CTableDataControl m_table_stat_4; // Таблица результатов оптимизации на вкладке 4
CStatChart m_chart_stat_4; // График результатов оптимизации на вкладке 4
protected:
//--- Возвращает указатель на таблицу параметров оптимизации по индексу вкладки
CTableDataControl*GetTableInputs(const uint tab_id)
{
switch(tab_id)
{
case 0 : return this.m_table_inp_0.Get();
case 1 : return this.m_table_inp_1.Get();
case 2 : return this.m_table_inp_2.Get();
case 3 : return this.m_table_inp_3.Get();
case 4 : return this.m_table_inp_4.Get();
default: return NULL;
}
}
//--- Возвращает указатель на таблицу результатов оптимизации по индексу вкладки
CTableDataControl*GetTableStats(const uint tab_id)
{
switch(tab_id)
{
case 0 : return this.m_table_stat_0.Get();
case 1 : return this.m_table_stat_1.Get();
case 2 : return this.m_table_stat_2.Get();
case 3 : return this.m_table_stat_3.Get();
case 4 : return this.m_table_stat_4.Get();
default: return NULL;
}
}
//--- Возвращает указатель на график результатов оптимизации по индексу вкладки
CStatChart *GetChartStats(const uint tab_id)
{
switch(tab_id)
{
case 0 : return this.m_chart_stat_0.Get();
case 1 : return this.m_chart_stat_1.Get();
case 2 : return this.m_chart_stat_2.Get();
case 3 : return this.m_chart_stat_3.Get();
case 4 : return this.m_chart_stat_4.Get();
default: return NULL;
}
}
//--- Добавляет объект фрейм в список
bool AddFrame(CFrameData *frame)
{
if(frame==NULL)
{
::PrintFormat("%s: Error: Empty object passed",__FUNCTION__);
return false;
}
this.m_frame_tmp.SetPass(frame.Pass());
this.m_list_frames.Sort(FRAME_PROP_PASS_NUM);
int index=this.m_list_frames.Search(frame);
if(index>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 &params[], 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; i<total; i++)
{
CStatChart *chart=this.GetChartStats(i);
if(chart!=NULL)
chart.SetProfitColorLine(clr);
}
}
//--- Установка цвета убыточной серии
void SetLossColorLine(const color clr)
{
int total=this.m_tab_control.TabsTotal();
for(int i=1; i<total; i++)
{
CStatChart *chart=this.GetChartStats(i);
if(chart!=NULL)
chart.SetLossColorLine(clr);
}
}
//--- Установка цвета выбранной серии
void SetSelectedLineColor(const color clr)
{
int total=this.m_tab_control.TabsTotal();
for(int i=1; i<total; i++)
{
CStatChart *chart=this.GetChartStats(i);
if(chart!=NULL)
chart.SetSelectedLineColor(clr);
}
}
//--- Обработчики событий тестера стратегий
void OnTester(const double OnTesterValue);
int OnTesterInit(const int lines, const int selected_line_width, const color selected_line_color);
void OnTesterPass(void);
void OnTesterDeinit(void);
//--- Обработчики событий графика
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam,const int delay_ms);
protected:
//--- Обработчик (1) смены вкладки элемента Tab Control, (2) выбора кнопки переключателя Button Switch
void OnTabSwitchEvent(const int tab_id);
void OnButtonSwitchEvent(const int tab_id, const uint butt_id);
public:
//--- Конструктор/деструктор
CFrameViewer(void);
~CFrameViewer(void){ this.m_list_frames.Clear(); }
};
//+------------------------------------------------------------------+
//| Конструктор |
//+------------------------------------------------------------------+
CFrameViewer::CFrameViewer(void) : m_completed(false), m_progress_bar(NULL), m_selected_color(clrDodgerBlue), m_line_width(1)
{
//--- Размеры окна графика
this.m_w=(int)::ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
this.m_h=(int)::ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
//--- Получаем указатель на прогресс-бар из объекта чарта статистики
this.m_progress_bar=this.m_chart_stat_0.GetProgressBar();
this.m_list_frames.Clear();
}
//+------------------------------------------------------------------+
//| Должна вызываться в обработчике эксперта OnTesterInit() |
//+------------------------------------------------------------------+
int CFrameViewer::OnTesterInit(const int lines, const int selected_line_width, const color selected_line_color)
{
//--- Идентификатор графика с экспертом, работающем во Frame-режиме
long chart_id=::ChartID();
//--- Готовим плавающий график для рисования таблиц статистики и линий баланса
::ResetLastError();
if(!::ChartSetInteger(chart_id, CHART_SHOW, false))
{
::PrintFormat("%s: ChartSetInteger() failed. Error %d",__FUNCTION__, GetLastError());
return INIT_FAILED;
}
if(!::ChartSetInteger(chart_id, CHART_IS_DOCKED, false))
{
::PrintFormat("%s: ChartSetInteger() failed. Error %d",__FUNCTION__, GetLastError());
return INIT_FAILED;
}
//--- Очищаем график полностью от всех графических объектов
::ObjectsDeleteAll(chart_id);
//--- По размерам графика создаём элемент управления Tab Control с пятью вкладками
int w=(int)::ChartGetInteger(chart_id, CHART_WIDTH_IN_PIXELS);
int h=(int)::ChartGetInteger(chart_id, CHART_HEIGHT_IN_PIXELS);
if(this.m_tab_control.Create("TabControl", "", 0, 0, w, h))
{
//--- Если элемент управления создан успешно - добавляем к нему пять вкладок
bool res=true;
for(int i=0; i<5; i++)
{
string tab_text=(i==1 ? "Sharpe Ratio" : i==2 ? "Net Profit" : i==3 ? "Profit Factor" : i==4 ? "Recovery Factor" : "Optimization");
res &=this.m_tab_control.AddTab(i, tab_text);
}
if(!res)
{
::PrintFormat("%s: Errors occurred while adding tabs to the Tab Control",__FUNCTION__);
return INIT_FAILED;
}
}
else
{
Print("Tab Control creation failed");
return INIT_FAILED;
}
//--- Объекты CCanvas рабочей области вкладки 0 (Optimization) для рисования фоновых изображений и текста
CCanvas *tab0_background=this.m_tab_control.GetTabBackground(0);
CCanvas *tab0_foreground=this.m_tab_control.GetTabForeground(0);
//--- Объекты CCanvas рабочей области вкладки 1 (Sharpe Ratio) для рисования фоновых изображений и текста
CCanvas *tab1_background=this.m_tab_control.GetTabBackground(1);
CCanvas *tab1_foreground=this.m_tab_control.GetTabForeground(1);
//--- Объекты CCanvas рабочей области вкладки 2 (Net Profit) для рисования фоновых изображений и текста
CCanvas *tab2_background=this.m_tab_control.GetTabBackground(2);
CCanvas *tab2_foreground=this.m_tab_control.GetTabForeground(2);
//--- Объекты CCanvas рабочей области вкладки 3 (Profit Factor) для рисования фоновых изображений и текста
CCanvas *tab3_background=this.m_tab_control.GetTabBackground(3);
CCanvas *tab3_foreground=this.m_tab_control.GetTabForeground(3);
//--- Объекты CCanvas рабочей области вкладки 4 (Recovery Factor) для рисования фоновых изображений и текста
CCanvas *tab4_background=this.m_tab_control.GetTabBackground(4);
CCanvas *tab4_foreground=this.m_tab_control.GetTabForeground(4);
//--- Устанавливаем объектам графиков статистики оптимизации идентификаторы вкладок
this.m_chart_stat_0.SetTabID(0);
this.m_chart_stat_1.SetTabID(1);
this.m_chart_stat_2.SetTabID(2);
this.m_chart_stat_3.SetTabID(3);
this.m_chart_stat_4.SetTabID(4);
//--- Указываем для объектов чартов статистики, что рисуем на вкладке с соответствующим индексом
this.m_chart_stat_0.SetCanvas(tab0_background, tab0_foreground);
this.m_chart_stat_1.SetCanvas(tab1_background, tab1_foreground);
this.m_chart_stat_2.SetCanvas(tab2_background, tab2_foreground);
this.m_chart_stat_3.SetCanvas(tab3_background, tab3_foreground);
this.m_chart_stat_4.SetCanvas(tab4_background, tab4_foreground);
//--- Устанавливаем количество серий на графиках статистики оптимизации
this.m_chart_stat_0.SetLines(lines);
this.m_chart_stat_1.SetLines(lines);
this.m_chart_stat_2.SetLines(lines);
this.m_chart_stat_3.SetLines(lines);
this.m_chart_stat_4.SetLines(lines);
//--- Задаём цвета фона и переднего плана графиков статистики оптимизации
this.m_chart_stat_0.SetBackColor(clrIvory);
this.m_chart_stat_0.SetForeColor(C'200,200,200');
this.m_chart_stat_1.SetBackColor(clrIvory);
this.m_chart_stat_1.SetForeColor(C'200,200,200');
this.m_chart_stat_2.SetBackColor(clrIvory);
this.m_chart_stat_2.SetForeColor(C'200,200,200');
this.m_chart_stat_3.SetBackColor(clrIvory);
this.m_chart_stat_3.SetForeColor(C'200,200,200');
this.m_chart_stat_4.SetBackColor(clrIvory);
this.m_chart_stat_4.SetForeColor(C'200,200,200');
//--- Установим толщину и цвет выбранной линии лучшего прохода
this.SetSelectedLineWidth(selected_line_width);
this.SetSelectedLineColor(selected_line_color);
//--- Нарисуем на вкладке 0 (Optimization) две таблицы с результатами оптимизации и входными параметрами,
//--- и окно с полосой прогресса для вывода графиков и процесса оптимизации
this.TableStatDraw(0, 4, 4, CELL_W*2, CELL_H, false);
this.TableInpDraw(0, 4, this.m_table_stat_0.Y2()+4, CELL_W*2, CELL_H, 0, false);
this.ChartOptDraw(0, this.m_completed, true);
//--- Создадим на вкладке 0 кнопку воспроизведения оптимизации
if(!this.m_chart_stat_0.CreateButtonReplay())
{
Print("Button Replay creation failed");
return INIT_FAILED;
}
//--- Нарисуем на вкладке 1 (Sharpe Ratio) две таблицы с результатами оптимизации и входными параметрами,
//--- и окно для вывода графиков результатов оптимизации
this.TableStatDraw(1, 4, 4, CELL_W*2, CELL_H, false);
this.TableInpDraw(1, 4, this.m_table_stat_1.Y2()+4, CELL_W*2, CELL_H, 0, false);
this.ChartOptDraw(1, this.m_completed, true);
//--- Создадим на вкладке 1 кнопку выбора результата
if(!this.m_chart_stat_1.CreateButtonResults())
{
Print("Tab1: There were errors when creating the result buttons");
return INIT_FAILED;
}
//--- Нарисуем на вкладке 2 (Net Profit) две таблицы с результатами оптимизации и входными параметрами,
//--- и окно для вывода графиков результатов оптимизации
this.TableStatDraw(2, 4, 4, CELL_W*2, CELL_H, false);
this.TableInpDraw(2, 4, this.m_table_stat_1.Y2()+4, CELL_W*2, CELL_H, 0, false);
this.ChartOptDraw(2, this.m_completed, true);
//--- Создадим на вкладке 2 кнопку выбора результата
if(!this.m_chart_stat_2.CreateButtonResults())
{
Print("Tab2: There were errors when creating the result buttons");
return INIT_FAILED;
}
//--- Нарисуем на вкладке 3 (Profit Factor) две таблицы с результатами оптимизации и входными параметрами,
//--- и окно для вывода графиков результатов оптимизации
this.TableStatDraw(3, 4, 4, CELL_W*2, CELL_H, false);
this.TableInpDraw(3, 4, this.m_table_stat_1.Y2()+4, CELL_W*2, CELL_H, 0, false);
this.ChartOptDraw(3, this.m_completed, true);
//--- Создадим на вкладке 3 кнопку выбора результата
if(!this.m_chart_stat_3.CreateButtonResults())
{
Print("Tab3: There were errors when creating the result buttons");
return INIT_FAILED;
}
//--- Нарисуем на вкладке 4 (Recovery Factor) две таблицы с результатами оптимизации и входными параметрами,
//--- и окно для вывода графиков результатов оптимизации
this.TableStatDraw(4, 4, 4, CELL_W*2, CELL_H, false);
this.TableInpDraw(4, 4, this.m_table_stat_1.Y2()+4, CELL_W*2, CELL_H, 0, false);
this.ChartOptDraw(4, this.m_completed, true);
//--- Создадим на вкладке 4 кнопку выбора результата
if(!this.m_chart_stat_4.CreateButtonResults())
{
Print("Tab4: There were errors when creating the result buttons");
return INIT_FAILED;
}
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Должна вызываться в обработчике эксперта OnTesterDeinit() |
//+------------------------------------------------------------------+
void CFrameViewer::OnTesterDeinit(void)
{
//--- Получаем указатели на канвас для рисования фона и переднего плана
CCanvas *background=this.m_tab_control.GetTabBackground(0);
CCanvas *foreground=this.m_tab_control.GetTabForeground(0);
if(background==NULL || foreground==NULL)
return;
//--- Устанавливаем флаг завершения оптимизации
this.m_completed=true;
//--- Координаты заголовка графика
int x1=this.m_chart_stat_0.HeaderX1();
int y1=this.m_chart_stat_0.HeaderY1();
int x2=this.m_chart_stat_0.HeaderX2();
int y2=this.m_chart_stat_0.HeaderY2();
int x=(x1+x2)/2;
int y=(y1+y2)/2;
//--- Перекрасим фон и сотрём текст заголовка
background.FillRectangle(x1, y1, x2, y2, ::ColorToARGB(clrLightGreen));
foreground.FillRectangle(x1, y1, x2, y2, 0x00FFFFFF);
//--- Изменим текст и цвет шапки заголовка
string text="Optimization Complete: Click to Replay";
foreground.FontSet("Calibri", -100, FW_BLACK);
foreground.TextOut(x, y, text, ::ColorToARGB(clrMidnightBlue), TA_CENTER|TA_VCENTER);
background.Update(false);
foreground.Update(true);
//--- Получаем индекс активной вкладки и вызываем метод управления отображением управляющих объектов на графиках оптимизации
int tab_selected=this.m_tab_control.GetSelectedTabID();
this.ControlObjectsView(tab_selected);
//--- На каждой вкладке (1 - 4) нарисуем графики трёх лучших проходов оптимизации
this.DrawBestFrameDataAll();
::ChartRedraw();
}
//+------------------------------------------------------------------+
//| Готовит массив значений баланса и отправляет его во фрейме |
//| Должна вызываться в эксперте в обработчике OnTester() |
//+------------------------------------------------------------------+
void CFrameViewer::OnTester(const double OnTesterValue)
{
//--- Переменные для работы с результатами прохода
double balance[];
int data_count=0;
double balance_current=::TesterStatistics(STAT_INITIAL_DEPOSIT);
//--- Временные переменные для работы со сделками
ulong ticket=0;
double profit;
string symbol;
long entry;
//--- Запросим всю торговую историю
::ResetLastError();
if(!::HistorySelect(0, ::TimeCurrent()))
{
PrintFormat("%s: HistorySelect() failed. Error ",__FUNCTION__, ::GetLastError());
return;
}
//--- Собираем данные о сделках
uint deals_total=::HistoryDealsTotal();
for(uint i=0; i<deals_total; i++)
{
ticket=::HistoryDealGetTicket(i);
if(ticket==0)
continue;
symbol=::HistoryDealGetString(ticket, DEAL_SYMBOL);
entry =::HistoryDealGetInteger(ticket, DEAL_ENTRY);
profit=::HistoryDealGetDouble(ticket, DEAL_PROFIT);
if(entry!=DEAL_ENTRY_OUT && entry!=DEAL_ENTRY_INOUT)
continue;
balance_current+=profit;
data_count++;
::ArrayResize(balance, data_count);
balance[data_count-1]=balance_current;
}
//--- Массив data[] для отправки данных во фрейм
double data[];
::ArrayResize(data, ::ArraySize(balance)+DATA_COUNT);
::ArrayCopy(data, balance, DATA_COUNT, 0);
//--- Заполним первые DATA_COUNT значений массива результатами тестирования
data[0]=::TesterStatistics(STAT_SHARPE_RATIO); // коэффициент Шарпа
data[1]=::TesterStatistics(STAT_PROFIT); // чистая прибыль
data[2]=::TesterStatistics(STAT_PROFIT_FACTOR); // фактор прибыльности
data[3]=::TesterStatistics(STAT_RECOVERY_FACTOR); // фактор восстановления
data[4]=::TesterStatistics(STAT_TRADES); // количество трейдов
data[5]=::TesterStatistics(STAT_DEALS); // количество сделок
data[6]=::TesterStatistics(STAT_EQUITY_DDREL_PERCENT); // максимальная просадка средств в процентах
data[7]=OnTesterValue; // значение пользовательского критерия оптимизации
if(data[2]==DBL_MAX)
data[2]=0;
//--- Создадим фрейм с данными и отправим его в терминал
if(!::FrameAdd(::MQLInfoString(MQL_PROGRAM_NAME), FRAME_ID, deals_total, data))
::PrintFormat("%s: Frame add error: ",__FUNCTION__, ::GetLastError());
}
//+------------------------------------------------------------------+
//| Получает фрейм с данными при оптимизации и отображает график |
//| Должна вызываться в эксперте в обработчике OnTesterPass() |
//+------------------------------------------------------------------+
void CFrameViewer::OnTesterPass(void)
{
//--- Переменные для работы со фреймами
string name;
ulong pass;
long id;
double value, data[];
string params[];
uint par_count;
//--- Вспомогательные переменные
static datetime start=::TimeLocal();
static int frame_counter=0;
//--- При получении нового фрейма получаем из него данные
while(!::IsStopped() && ::FrameNext(pass, name, id, value, data))
{
frame_counter++;
string text=::StringFormat("Frames completed (tester passes): %d in %s", frame_counter,::TimeToString(::TimeLocal()-start, TIME_MINUTES|TIME_SECONDS));
//--- Получим входные параметры эксперта, для которых сформирован фрейм, и отправим их в таблицы и на график
//--- При успешном получении фрейма запишем его данные в объект фрейм и разместим его в списке
if(this.DrawFrameData(0, text, clrNONE, 0, pass, params, par_count, data))
{
//--- Результаты прохода тестера
double sharpe_ratio=data[0];
double net_profit=data[1];
double profit_factor=data[2];
double recovery_factor=data[3];
//--- Создаём новый объект фрейм и сохраняем его в списке
CFrameData *frame=new CFrameData(pass, sharpe_ratio, net_profit, profit_factor, recovery_factor);
if(frame!=NULL)
{
if(!this.AddFrame(frame))
delete frame;
}
::ChartRedraw();
}
}
}
//+------------------------------------------------------------------+
//| Обработка событий на графике |
//+------------------------------------------------------------------+
void CFrameViewer::OnChartEvent(const int id,const long &lparam,
const double &dparam,const string &sparam,
const int delay_ms)
{
//--- Вызываем обработчики событий объекта управления вкладками и графиков результатов оптимизации
this.m_tab_control.OnChartEvent(id, lparam, dparam, sparam);
this.m_chart_stat_0.OnChartEvent(id, lparam, dparam, sparam);
this.m_chart_stat_1.OnChartEvent(id, lparam, dparam, sparam);
this.m_chart_stat_2.OnChartEvent(id, lparam, dparam, sparam);
this.m_chart_stat_3.OnChartEvent(id, lparam, dparam, sparam);
this.m_chart_stat_4.OnChartEvent(id, lparam, dparam, sparam);
//--- Если пришло событие изменения графика
if(id==CHARTEVENT_CHART_CHANGE)
{
//--- получим размеры графика
int w=(int)::ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
int h=(int)::ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
if(w!=this.m_w || h!=this.m_h)
{
if(w==0 || h==0)
return;
//--- Изменим размер элемента управления Tab Control
this.m_tab_control.Resize(w, h);
//--- Получим идентификатор выбранной вкладки и нарисуем таблицы данных и график оптимизации на вкладке
int tab_selected=this.m_tab_control.GetSelectedTabID();
this.DrawDataChart(tab_selected);
//--- Получим указатель на кнопку-переключатель и выбранную кнопку показа результатов оптимизации
CButtonSwitch *button_switch=(tab_selected>0 ? 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; i<this.m_tab_control.TabsTotal(); i++)
this.DrawBestFrameData(i,-1);
}
//+------------------------------------------------------------------+
//| Получение данных текущего фрейма и вывод их на указанной вкладке |
//| в таблицу и на график результатов оптимизации |
//+------------------------------------------------------------------+
bool CFrameViewer::DrawFrameData(const uint tab_id, const string text, color clr, const uint line_width, ulong &pass, string &params[], uint &par_count, double &data[])
{
//--- Проверяем переданный идентификатор вкладки
if(tab_id>4)
{
::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; i<par_count; i++)
{
//--- Заполняем таблицу названиями и значениями входных параметров
string array[];
//--- Расщепим строку в params[i] на две подстроки и обновим ячейки в строке таблицы параметров тестирования
if(::StringSplit(params[i],'=',array)==2)
{
//--- Окрасим строки оптимизируемых параметров в бледно-желтый цвет,
//--- недоступные для оптимизации параметры - в бледно-розовый, остальные - в цвета по умолчанию
bool enable=false;
double value=0, start=0, step=0, stop=0;
color clr=clrMistyRose;
if(::ParameterGetRange(array[0], enable, value, start, step, stop))
clr=(enable ? clrLightYellow : clrNONE);
//--- Получим две ячейки таблицы по индексу параметра и выведем в них текст названия параметра и его значение
CTableCell *cell_0=table_inp.GetCell(i, 0);
CTableCell *cell_1=table_inp.GetCell(i, 1);
if(cell_0!=NULL && cell_1!=NULL)
{
//--- Обновим надписи в ячейках
cell_0.SetText(array[0]);
cell_1.SetText(array[1]);
cell_0.TextOut(foreground, 4, CELL_H/2, clr, 0, TA_VCENTER);
cell_1.TextOut(foreground, 4, CELL_H/2, clr, 0, TA_VCENTER);
}
}
}
//--- Обновим таблицу статистики оптимизации
//--- Строка заголовка таблицы
foreground.FillRectangle(table_stat.X1()+1, 4+1, table_stat.X1()+CELL_W*2-1, 4+CELL_H-1, 0x00FFFFFF);
foreground.FontSet("Calibri", -100, FW_BLACK);
foreground.TextOut(4+(CELL_W*2)/2, 4+CELL_H/2, ::StringFormat("Optimization results (pass %I64u)", pass), ::ColorToARGB(clrMidnightBlue), TA_CENTER|TA_VCENTER);
//--- В цикле по количеству строк таблицы
int total=table_stat.RowsTotal();
for(int i=0; i<total; i++)
{
//--- получим две ячейки текущей строки и
CTableCell *cell_0=table_stat.GetCell(i, 0);
CTableCell *cell_1=table_stat.GetCell(i, 1);
if(cell_0!=NULL && cell_1!=NULL)
{
//--- обновим значения результатов прохода во второй ячейке
string text="---";
switch(i)
{
case 0 : text=::StringFormat("%.2f", data[0]); break; // Sharpe Ratio
case 1 : text=::StringFormat("%.2f", data[1]); break; // Net Profit
case 2 : text=::StringFormat("%.2f", data[2]); break; // Profit Factor
case 3 : text=::StringFormat("%.2f", data[3]); break; // Recovery Factor
case 4 : text=::StringFormat("%.0f", data[4]); break; // Trades
case 5 : text=::StringFormat("%.0f", data[5]); break; // Deals
case 6 : text=::StringFormat("%.2f%%", data[6]);break; // Equity DD
case 7 : text=::StringFormat("%G", data[7]); break; // OnTester()
default: break;
}
//--- Подсветим цветом фон строки таблицы, соответствующей выбранной вкладке.
//--- Остальные строки будут иметь цвет по умолчанию
color clr=(tab_id>0 ? (i==tab_id-1 ? C'223,242,231' : clrNONE) : clrNONE);
//--- Обновим надписи в ячейках
cell_0.TextOut(foreground, 4, CELL_H/2, clr, 0, TA_VCENTER);
cell_1.SetText(text);
cell_1.TextOut(foreground, 4, CELL_H/2, clr, 0, TA_VCENTER);
}
}
//--- Массив для приема значений баланса текущего фрейма
double seria[];
::ArrayCopy(seria, data, 0, DATA_COUNT, ::ArraySize(data)-DATA_COUNT);
//--- Отправим массив для вывода на специальных график баланса
chart_stat.AddSeria(seria, data[1]>0);
//--- Обновим линии баланса на графике
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_id<FRAME_PROP_SHARPE_RATIO || tab_id>FRAME_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; row<total; row++)
{
//--- перебираем столбцы строк
for(int col=0; col<2; col++)
{
//--- Получаем ячейку таблицы в текущей строке и столбце
CTableCell *cell=table_stat.GetCell(row, col);
//--- Определяем текст в ячейке
//--- Для левой ячейки это будут заголовки результатов оптимизируемых параметров
if(col%2==0)
{
string text="OnTester()";
switch(row)
{
case 0 : text="Sharpe Ratio"; break;
case 1 : text="Net Profit"; break;
case 2 : text="Profit Factor"; break;
case 3 : text="Recovery Factor"; break;
case 4 : text="Trades"; break;
case 5 : text="Deals"; break;
case 6 : text="Equity DD"; break;
default: break;
}
cell.SetText(text);
}
//--- Для правой ячейки текст будет прочёркиванием для инициализируемой таблицы
else
cell.SetText(tab_id==0 ? " --- " : "");
//--- Выведем в ячейку соответствующий текст
cell.TextOut(foreground, 4, CELL_H/2, clrNONE, 0, TA_VCENTER);
}
}
//--- Обновим канвас фона и переднего плана
background.Update(false);
foreground.Update(chart_redraw);
}
//+------------------------------------------------------------------+
//|Рисует таблицу входных параметров оптимизации на указанной вкладке|
//+------------------------------------------------------------------+
void CFrameViewer::TableInpDraw(const uint tab_id, const int x, const int y, const int w, const int h, const uint rows, 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_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<w_min)
x2=x1+w_min;
if(y2-y1<180)
y2=y1+180;
//--- Установим размеры ограничивающего прямоугольника графика результатов оптимизации
chart_stat.SetChartBounds(x1, y1, x2, y2);
//--- Цвет и текст заголовка графика
color clr=clrLightGreen; // цвет заголовка при завершении оптимизации
string suff=
(
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" :
"Click to Replay"
);
string text="Optimization Completed: "+suff;
//--- Если оптимизация не завершена, укажем цвет и текст заголовка
if(!opt_completed)
{
clr=C'195,209,223';
text=::StringFormat("Optimization%sprogress%s", (tab_id==0 ? " " : " in "), (tab_id==0 ? "" : ": Waiting ... "));
}
//--- Рисуем заголовок и текст
background.FillRectangle(x1, 4, x2, y1, ::ColorToARGB(clr));
foreground.FillRectangle(x1, 4, x2, y2, 0x00FFFFFF);
foreground.FontSet("Calibri", -100, FW_BLACK);
foreground.TextOut((x1+x2)/2, 4+CELL_H/2, text, ::ColorToARGB(clrMidnightBlue), TA_CENTER|TA_VCENTER);
//--- Стираем полностью весь график результатов оптимизации
background.FillRectangle(x1, y1, x2, y2, 0x00FFFFFF);
foreground.FillRectangle(x1, y1, x2, y2, 0x00FFFFFF);
//--- Обновляем график оптимизации
chart_stat.Update(clrNONE, 0, chart_redraw);
}
//+-------------------------------------------------------------------+
//|Управляет отображением управляющих объектов на графиках оптимизации|
//+-------------------------------------------------------------------+
void CFrameViewer::ControlObjectsView(const uint tab_id)
{
//--- Получаем индекс активной вкладки
int tab_index=this.m_tab_control.GetSelectedTabID();
//--- Получаем указатель на активную вкладку и таблицу статистики оптимизации
CTab *tab=this.m_tab_control.GetTab(tab_index);
CTableDataControl *table_stat=this.GetTableStats(tab_index);
if(tab==NULL || table_stat==NULL)
return;
//--- Координаты левой и правой границ заголовка графика результатов оптимизации
int w=0, cpx=0, x=0, y=0;
int x1=table_stat.X2()+10;
int x2=tab.GetField().Right()-10;
//--- В зависимости от индекса выбранной вкладки
switch(tab_index)
{
//--- Optimization
case 0 :
//--- Смещаем кнопку Replay к центру заголовка
w=this.m_chart_stat_0.ButtonReplay().Width();
cpx=(x1+x2)/2;
x=cpx-w/2;
this.m_chart_stat_0.ButtonReplay().MoveX(x);
//--- Если оптимизация завершена - показываем кнопку на переднем плане
if(this.m_completed)
{
this.m_chart_stat_0.ButtonReplay().Show();
this.m_chart_stat_0.ButtonReplay().BringToTop();
}
//--- Скрываем кнопки всех остальных вкладок
this.m_chart_stat_1.ButtonsResultHide();
this.m_chart_stat_2.ButtonsResultHide();
this.m_chart_stat_3.ButtonsResultHide();
this.m_chart_stat_4.ButtonsResultHide();
break;
//--- Sharpe Ratio
case 1 :
//--- Скрываем кнопку Replay
this.m_chart_stat_0.ButtonReplay().Hide();
//--- Получаем координату Y и смещаем на неё кнопку-переключатель
y=this.m_chart_stat_1.ProgressBarY1()+CELL_H+2;
this.m_chart_stat_1.ButtonResult().MoveY(y);
//--- Кнопку-переключатель на вкладке 1 переносим на передний план,
//--- а все остальные кнопки на других вкладках скрываем
this.m_chart_stat_1.ButtonsResultBringToTop();
this.m_chart_stat_2.ButtonsResultHide();
this.m_chart_stat_3.ButtonsResultHide();
this.m_chart_stat_4.ButtonsResultHide();
break;
//--- Net Profit
case 2 :
this.m_chart_stat_0.ButtonReplay().Hide();
//--- Получаем координату Y и смещаем на неё кнопку-переключатель
y=this.m_chart_stat_2.ProgressBarY1()+CELL_H+2;
this.m_chart_stat_2.ButtonResult().MoveY(y);
//--- Кнопку-переключатель на вкладке 2 переносим на передний план,
//--- а все остальные кнопки на других вкладках скрываем
this.m_chart_stat_2.ButtonsResultBringToTop();
this.m_chart_stat_1.ButtonsResultHide();
this.m_chart_stat_3.ButtonsResultHide();
this.m_chart_stat_4.ButtonsResultHide();
break;
//--- Profit Factor
case 3 :
this.m_chart_stat_0.ButtonReplay().Hide();
//--- Получаем координату Y и смещаем на неё кнопку-переключатель
y=this.m_chart_stat_3.ProgressBarY1()+CELL_H+2;
this.m_chart_stat_3.ButtonResult().MoveY(y);
//--- Кнопку-переключатель на вкладке 3 переносим на передний план,
//--- а все остальные кнопки на других вкладках скрываем
this.m_chart_stat_3.ButtonsResultBringToTop();
this.m_chart_stat_1.ButtonsResultHide();
this.m_chart_stat_2.ButtonsResultHide();
this.m_chart_stat_4.ButtonsResultHide();
break;
//--- Recovery Factor
case 4 :
this.m_chart_stat_0.ButtonReplay().Hide();
//--- Получаем координату Y и смещаем на неё кнопку-переключатель
y=this.m_chart_stat_4.ProgressBarY1()+CELL_H+2;
this.m_chart_stat_4.ButtonResult().MoveY(y);
//--- Кнопку-переключатель на вкладке 4 переносим на передний план,
//--- а все остальные кнопки на других вкладках скрываем
this.m_chart_stat_4.ButtonsResultBringToTop();
this.m_chart_stat_1.ButtonsResultHide();
this.m_chart_stat_2.ButtonsResultHide();
this.m_chart_stat_3.ButtonsResultHide();
break;
default:
break;
}
//--- Перерисовываем график
::ChartRedraw();
}
//+------------------------------------------------------------------+