2025-09-19 13:56:45 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| OptimizerTask.mqh |
|
|
|
|
|
//| Copyright 2024, Yuriy Bykov |
|
|
|
|
|
//| https://www.mql5.com/ru/users/antekov |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
#property copyright "Copyright 2024, Yuriy Bykov"
|
|
|
|
|
#property link "https://www.mql5.com/ru/users/antekov"
|
|
|
|
|
#property version "1.06"
|
|
|
|
|
|
|
|
|
|
// Функция запуска исполняемого файла в операционной системе
|
|
|
|
|
#import "shell32.dll"
|
|
|
|
|
int ShellExecuteW(int hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd);
|
|
|
|
|
#import
|
|
|
|
|
|
|
|
|
|
#include "../Database/Database.mqh"
|
|
|
|
|
#include "../Utils/MTTester.mqh" // https://www.mql5.com/ru/code/26132
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Класс для задачи оптимизации |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
class COptimizerTask {
|
|
|
|
|
protected:
|
|
|
|
|
enum {
|
|
|
|
|
TASK_TYPE_UNKNOWN,
|
|
|
|
|
TASK_TYPE_EX5,
|
|
|
|
|
TASK_TYPE_PY
|
|
|
|
|
} m_type; // Тип задачи (MQL5 или Python)
|
|
|
|
|
ulong m_id; // Идентификатор задачи
|
|
|
|
|
string m_setting; // Строка инициализации параметров советника для текущей задачи
|
|
|
|
|
|
|
|
|
|
string m_fileName; // Имя файла базы данных
|
|
|
|
|
string m_pythonPath; // Полный путь к интерпретатору Python
|
|
|
|
|
|
|
|
|
|
// Получение полного или относительного пути к заданному файлу в текущей папке
|
|
|
|
|
string GetProgramPath(string name, bool rel = true);
|
|
|
|
|
|
|
|
|
|
// Получение строки инициализации из параметров задачи
|
|
|
|
|
void Parse();
|
|
|
|
|
|
|
|
|
|
// Получение типа задачи из параметров задачи
|
|
|
|
|
void ParseType();
|
|
|
|
|
|
2025-09-24 09:38:40 +03:00
|
|
|
static string s_criterionNames[];
|
|
|
|
|
|
2025-09-19 13:56:45 +03:00
|
|
|
public:
|
|
|
|
|
// Структура данных для чтения одной строки результата запроса
|
|
|
|
|
struct params {
|
|
|
|
|
string expert;
|
|
|
|
|
int optimization;
|
|
|
|
|
string from_date;
|
|
|
|
|
string to_date;
|
|
|
|
|
int forward_mode;
|
|
|
|
|
string forward_date;
|
|
|
|
|
double deposit;
|
|
|
|
|
string symbol;
|
|
|
|
|
string period;
|
|
|
|
|
string tester_inputs;
|
|
|
|
|
ulong id_task;
|
|
|
|
|
int optimization_criterion;
|
|
|
|
|
long max_duration;
|
|
|
|
|
} m_params;
|
|
|
|
|
|
|
|
|
|
// Конструктор
|
|
|
|
|
COptimizerTask(string p_fileName, string p_pythonPath = NULL) :
|
|
|
|
|
m_id(0), m_fileName(p_fileName), m_pythonPath(p_pythonPath) {}
|
|
|
|
|
|
|
|
|
|
// Идентификатор задачи
|
|
|
|
|
ulong Id() {
|
|
|
|
|
return m_id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Основной метод
|
|
|
|
|
void Process();
|
|
|
|
|
|
|
|
|
|
// Загрузка параметров задачи из базы данных
|
|
|
|
|
void Load(ulong p_id);
|
|
|
|
|
|
|
|
|
|
// Запуск задачи
|
|
|
|
|
void Start();
|
2025-09-24 09:38:40 +03:00
|
|
|
|
2025-09-19 13:56:45 +03:00
|
|
|
// Остановка задачи
|
|
|
|
|
void Stop();
|
|
|
|
|
|
|
|
|
|
// Завершение задачи
|
|
|
|
|
void Finish();
|
|
|
|
|
|
|
|
|
|
// Задача выполнена?
|
|
|
|
|
bool IsDone();
|
2025-09-24 09:38:40 +03:00
|
|
|
|
2025-09-25 11:28:48 +03:00
|
|
|
// Информация о текущей задаче
|
2025-09-19 13:56:45 +03:00
|
|
|
string Text();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2025-09-24 09:38:40 +03:00
|
|
|
string COptimizerTask::s_criterionNames[] = {
|
|
|
|
|
"Balance max",
|
|
|
|
|
"Profit Factor max",
|
|
|
|
|
"Expected Payoff max",
|
|
|
|
|
"Drawdown min",
|
|
|
|
|
"Recovery Factor max",
|
|
|
|
|
"Sharpe Ratio max",
|
|
|
|
|
"Custom max",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2025-09-19 13:56:45 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Получение строки инициализации из параметров задачи |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void COptimizerTask::Parse() {
|
|
|
|
|
// Получаем тип задачи из параметров задачи
|
|
|
|
|
ParseType();
|
|
|
|
|
|
|
|
|
|
// Если это задача на оптимизацию советника
|
|
|
|
|
if(m_type == TASK_TYPE_EX5) {
|
|
|
|
|
// Формируем строку параметров для тестера
|
|
|
|
|
m_setting = StringFormat(
|
|
|
|
|
"[Tester]\r\n"
|
|
|
|
|
"Expert=%s\r\n"
|
|
|
|
|
"Symbol=%s\r\n"
|
|
|
|
|
"Period=%s\r\n"
|
|
|
|
|
"Optimization=%d\r\n"
|
|
|
|
|
"Model=1\r\n"
|
|
|
|
|
"FromDate=%s\r\n"
|
|
|
|
|
"ToDate=%s\r\n"
|
|
|
|
|
"ForwardMode=%d\r\n"
|
|
|
|
|
"%s"
|
|
|
|
|
"Deposit=%.2f\r\n"
|
|
|
|
|
"Currency=USD\r\n"
|
|
|
|
|
"ProfitInPips=0\r\n"
|
|
|
|
|
"Leverage=200\r\n"
|
|
|
|
|
"ExecutionMode=0\r\n"
|
|
|
|
|
"OptimizationCriterion=%d\r\n"
|
|
|
|
|
"[TesterInputs]\r\n"
|
|
|
|
|
"idTask_=%d\r\n"
|
|
|
|
|
"fileName_=%s\r\n"
|
|
|
|
|
"%s\r\n",
|
|
|
|
|
GetProgramPath(m_params.expert),
|
|
|
|
|
m_params.symbol,
|
|
|
|
|
m_params.period,
|
|
|
|
|
m_params.optimization,
|
|
|
|
|
m_params.from_date,
|
|
|
|
|
m_params.to_date,
|
|
|
|
|
m_params.forward_mode,
|
|
|
|
|
(m_params.forward_mode == 4 ?
|
|
|
|
|
StringFormat("ForwardDate=%s\r\n", m_params.forward_date) : ""),
|
|
|
|
|
m_params.deposit,
|
|
|
|
|
m_params.optimization_criterion,
|
|
|
|
|
m_params.id_task,
|
|
|
|
|
DB::FileName(),
|
|
|
|
|
m_params.tester_inputs
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Если это задача на запуск программы на Python
|
|
|
|
|
} else if (m_type == TASK_TYPE_PY) {
|
|
|
|
|
// Формируем строку запуска программы на Python с параметрами
|
|
|
|
|
m_setting = StringFormat("\"%s\" \"%s\" %I64u %s",
|
|
|
|
|
GetProgramPath(m_params.expert, false), // Файл с программой на Python
|
|
|
|
|
DB::FileName(true), // Путь к файлу с базой данных
|
|
|
|
|
m_id, // Идентификатор задачи
|
|
|
|
|
m_params.tester_inputs // Парамтры запуска
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Получение типа задачи из параметров задачи |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void COptimizerTask::ParseType() {
|
|
|
|
|
string ext = StringSubstr(m_params.expert, StringLen(m_params.expert) - 3);
|
|
|
|
|
if(ext == ".py") {
|
|
|
|
|
m_type = TASK_TYPE_PY;
|
|
|
|
|
} else if (ext == "ex5") {
|
|
|
|
|
m_type = TASK_TYPE_EX5;
|
|
|
|
|
} else {
|
|
|
|
|
m_type = TASK_TYPE_UNKNOWN;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Получение полного или относительного пути к заданному файлу |
|
|
|
|
|
//| в текущей папке |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
string COptimizerTask::GetProgramPath(string name, bool rel = true) {
|
|
|
|
|
string path = MQLInfoString(MQL_PROGRAM_PATH);
|
|
|
|
|
string programName = MQLInfoString(MQL_PROGRAM_NAME) + ".ex5";
|
|
|
|
|
string terminalPath = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Experts\\";
|
|
|
|
|
if(rel) {
|
|
|
|
|
path = StringSubstr(path,
|
|
|
|
|
StringLen(terminalPath),
|
|
|
|
|
StringLen(path) - (StringLen(terminalPath) + StringLen(programName)));
|
|
|
|
|
} else {
|
|
|
|
|
path = StringSubstr(path, 0, StringLen(path) - (0 + StringLen(programName)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return path + name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Получение очередной задачи оптимизации из очереди |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void COptimizerTask::Load(ulong p_id) {
|
|
|
|
|
// Запоминаем идентификатор задачи
|
|
|
|
|
m_id = p_id;
|
|
|
|
|
|
|
|
|
|
// Запрос на получение задачи оптимизации из очереди по идентификатору
|
|
|
|
|
string query = StringFormat(
|
|
|
|
|
"SELECT s.expert,"
|
|
|
|
|
" s.optimization,"
|
|
|
|
|
" s.from_date,"
|
|
|
|
|
" s.to_date,"
|
|
|
|
|
" s.forward_mode,"
|
|
|
|
|
" s.forward_date,"
|
|
|
|
|
" s.deposit,"
|
|
|
|
|
" j.symbol,"
|
|
|
|
|
" j.period,"
|
|
|
|
|
" j.tester_inputs,"
|
|
|
|
|
" t.id_task,"
|
|
|
|
|
" t.optimization_criterion,"
|
|
|
|
|
" t.max_duration"
|
|
|
|
|
" FROM tasks t"
|
|
|
|
|
" JOIN"
|
|
|
|
|
" jobs j ON t.id_job = j.id_job"
|
|
|
|
|
" JOIN"
|
|
|
|
|
" stages s ON j.id_stage = s.id_stage"
|
|
|
|
|
" WHERE t.id_task=%I64u;", m_id);
|
|
|
|
|
|
|
|
|
|
// Открываем базу данных
|
|
|
|
|
if(DB::Connect(m_fileName)) {
|
|
|
|
|
// Выполняем запрос
|
|
|
|
|
int request = DatabasePrepare(DB::Id(), query);
|
|
|
|
|
|
|
|
|
|
// Если нет ошибки
|
|
|
|
|
if(request != INVALID_HANDLE) {
|
|
|
|
|
// Читаем данные из первой строки результата
|
|
|
|
|
if(DatabaseReadBind(request, m_params)) {
|
|
|
|
|
Parse();
|
|
|
|
|
} else {
|
|
|
|
|
// Сообщаем об ошибке при необходимости
|
|
|
|
|
PrintFormat(__FUNCTION__" | ERROR: Reading row for request \n%s\nfailed with code %d",
|
|
|
|
|
query, GetLastError());
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Сообщаем об ошибке при необходимости
|
|
|
|
|
PrintFormat(__FUNCTION__" | ERROR: request \n%s\nfailed with code %d", query, GetLastError());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Закрываем базу данных
|
|
|
|
|
DB::Close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Запуск задачи |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void COptimizerTask::Start() {
|
|
|
|
|
PrintFormat(__FUNCTION__" | Task ID = %d\n%s", m_id, m_setting);
|
|
|
|
|
|
|
|
|
|
// Если это задача на оптимизацию советника
|
|
|
|
|
if(m_type == TASK_TYPE_EX5) {
|
|
|
|
|
// Запускаем новую задачу оптимизации в тестере
|
|
|
|
|
MTTESTER::CloseNotChart();
|
|
|
|
|
MTTESTER::SetSettings2(m_setting);
|
|
|
|
|
MTTESTER::ClickStart();
|
|
|
|
|
|
|
|
|
|
// Обновляем статус задачи в базе данных
|
|
|
|
|
DB::Connect(m_fileName);
|
|
|
|
|
string query = StringFormat(
|
|
|
|
|
"UPDATE tasks SET "
|
|
|
|
|
" status='Process' "
|
|
|
|
|
" WHERE id_task=%d",
|
|
|
|
|
m_id);
|
|
|
|
|
DB::Execute(query);
|
|
|
|
|
DB::Close();
|
|
|
|
|
|
|
|
|
|
// Если это задача на запуск программы на Python
|
|
|
|
|
} else if (m_type == TASK_TYPE_PY) {
|
|
|
|
|
PrintFormat(__FUNCTION__" | SHELL EXEC: %s", m_pythonPath);
|
|
|
|
|
// Вызываем системную функцию запуска программы с параметрами
|
|
|
|
|
ShellExecuteW(NULL, NULL, m_pythonPath, m_setting, NULL, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
2025-09-24 09:38:40 +03:00
|
|
|
//| Остановка задачи |
|
2025-09-19 13:56:45 +03:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void COptimizerTask::Stop() {
|
|
|
|
|
PrintFormat(__FUNCTION__" | Task ID = %d", m_id);
|
|
|
|
|
|
|
|
|
|
// Если это задача на оптимизацию советника
|
|
|
|
|
if(m_type == TASK_TYPE_EX5) {
|
|
|
|
|
// Останавливаем оптимизацию в тестере
|
2025-09-24 09:38:40 +03:00
|
|
|
MTTESTER::ClickStart(false);
|
2025-09-25 11:28:48 +03:00
|
|
|
|
2025-09-19 13:56:45 +03:00
|
|
|
// Если это задача на запуск программы на Python
|
|
|
|
|
} else if (m_type == TASK_TYPE_PY) {
|
|
|
|
|
PrintFormat(__FUNCTION__" | STOP SHELL EXEC: %s", m_pythonPath);
|
|
|
|
|
// TODO: Добавить прерываение задачи на Python с таким запуском:
|
|
|
|
|
// ShellExecuteW(NULL, NULL, m_pythonPath, m_setting, NULL, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Завершение задачи |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void COptimizerTask::Finish() {
|
|
|
|
|
PrintFormat(__FUNCTION__" | Task ID = %d", m_id);
|
|
|
|
|
|
|
|
|
|
// Обновляем статус задачи в базе данных
|
|
|
|
|
DB::Connect(m_fileName);
|
|
|
|
|
string query = StringFormat(
|
|
|
|
|
"UPDATE tasks SET "
|
|
|
|
|
" status='Done' "
|
|
|
|
|
" WHERE id_task=%d",
|
|
|
|
|
m_id);
|
|
|
|
|
DB::Execute(query);
|
|
|
|
|
DB::Close();
|
2025-09-24 09:38:40 +03:00
|
|
|
|
|
|
|
|
// Сбрасываем идентификатор текущей задачи
|
|
|
|
|
m_id = 0;
|
2025-09-19 13:56:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Задача выполнена? |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool COptimizerTask::IsDone() {
|
|
|
|
|
// Если нет текущей задачи, то всё выполнено
|
|
|
|
|
if(m_id == 0) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Результат
|
|
|
|
|
bool res = false;
|
|
|
|
|
|
|
|
|
|
// Если это задача на оптимизацию советника
|
|
|
|
|
if(m_type == TASK_TYPE_EX5) {
|
|
|
|
|
// Проверяем, завершил ли работу тестер стратегий
|
|
|
|
|
res |= MTTESTER::IsReady();
|
|
|
|
|
|
2025-09-25 08:43:26 +03:00
|
|
|
// Если тестер работает и указана максимальная длительность, то
|
2025-09-19 13:56:45 +03:00
|
|
|
if(!res && m_params.max_duration > 0) {
|
2025-09-25 08:43:26 +03:00
|
|
|
// Запрос на получение прошедшего времени выполнения текущей задачи
|
2025-09-19 13:56:45 +03:00
|
|
|
string query = StringFormat("SELECT unixepoch(datetime()) - unixepoch(start_date) AS duration"
|
|
|
|
|
" FROM tasks"
|
|
|
|
|
" WHERE id_task=%I64u;", m_id);
|
2025-09-25 11:28:48 +03:00
|
|
|
|
2025-09-25 08:43:26 +03:00
|
|
|
// Получаем время выполнения в секундах
|
2025-09-19 13:56:45 +03:00
|
|
|
DB::Connect(m_fileName);
|
|
|
|
|
long duration = StringToInteger(DB::GetValue(query));
|
|
|
|
|
DB::Close();
|
|
|
|
|
|
2025-09-25 11:28:48 +03:00
|
|
|
// Если время выполнения больше максимально допустимого, то
|
2025-09-19 13:56:45 +03:00
|
|
|
if(duration > m_params.max_duration) {
|
2025-09-25 08:43:26 +03:00
|
|
|
// Останавливаем задачу
|
2025-09-19 13:56:45 +03:00
|
|
|
Stop();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Если это задача на запуск программы на Python, то
|
|
|
|
|
} else if(m_type == TASK_TYPE_PY) {
|
|
|
|
|
// Запрос на получение статуса текущей задачи
|
|
|
|
|
string query = StringFormat("SELECT status "
|
|
|
|
|
" FROM tasks"
|
|
|
|
|
" WHERE id_task=%I64u;", m_id);
|
|
|
|
|
// Открываем базу данных
|
|
|
|
|
if(DB::Connect(m_fileName)) {
|
|
|
|
|
// Выполняем запрос
|
|
|
|
|
int request = DatabasePrepare(DB::Id(), query);
|
|
|
|
|
|
|
|
|
|
// Если нет ошибки
|
|
|
|
|
if(request != INVALID_HANDLE) {
|
|
|
|
|
// Структура данных для чтения одной строки результата запроса
|
|
|
|
|
struct Row {
|
|
|
|
|
string status;
|
|
|
|
|
} row;
|
|
|
|
|
|
|
|
|
|
// Читаем данные из первой строки результата
|
|
|
|
|
if(DatabaseReadBind(request, row)) {
|
|
|
|
|
// Проверяем, равен ли статус Done
|
|
|
|
|
res = (row.status == "Done");
|
|
|
|
|
} else {
|
|
|
|
|
// Сообщаем об ошибке при необходимости
|
|
|
|
|
PrintFormat(__FUNCTION__" | ERROR: Reading row for request \n%s\nfailed with code %d",
|
|
|
|
|
query, GetLastError());
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Сообщаем об ошибке при необходимости
|
|
|
|
|
PrintFormat(__FUNCTION__" | ERROR: request \n%s\nfailed with code %d", query, GetLastError());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Закрываем базу данных
|
|
|
|
|
DB::Close();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
res = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-24 09:38:40 +03:00
|
|
|
//+------------------------------------------------------------------+
|
2025-09-25 11:28:48 +03:00
|
|
|
//| Информация о текущей задаче |
|
2025-09-24 09:38:40 +03:00
|
|
|
//+------------------------------------------------------------------+
|
2025-09-19 13:56:45 +03:00
|
|
|
string COptimizerTask::Text() {
|
|
|
|
|
string text = "";
|
2025-09-24 09:38:40 +03:00
|
|
|
|
2025-09-25 11:28:48 +03:00
|
|
|
// Если есть активная задача
|
2025-09-24 09:38:40 +03:00
|
|
|
if(m_params.id_task) {
|
|
|
|
|
DB::Connect(m_fileName);
|
|
|
|
|
|
2025-09-25 11:28:48 +03:00
|
|
|
// Добавляем информацию о проекте
|
2025-09-24 09:38:40 +03:00
|
|
|
text += StringFormat("═════════════════════════════════════════════════════════════════════════\n"
|
|
|
|
|
"PROJECT: %s v. %s\n%s\n\n",
|
|
|
|
|
DB::GetValue("SELECT name FROM projects WHERE status = 'Process' LIMIT 1"),
|
|
|
|
|
DB::GetValue("SELECT version FROM projects WHERE status = 'Process' LIMIT 1"),
|
|
|
|
|
DB::GetValue("SELECT description FROM projects WHERE status = 'Process' LIMIT 1")
|
|
|
|
|
);
|
|
|
|
|
|
2025-09-25 11:28:48 +03:00
|
|
|
// Запрос на получение всей информации о задаче
|
2025-09-24 09:38:40 +03:00
|
|
|
string query = "SELECT s.name, s.expert, s.from_date, s.to_date, "
|
|
|
|
|
" j.symbol, j.period, t.optimization_criterion, t.start_date, "
|
|
|
|
|
" time(max_duration, 'unixepoch') AS max_duration,"
|
|
|
|
|
" time(unixepoch('now') - unixepoch(t.start_date), 'unixepoch') AS elapsed_time,"
|
|
|
|
|
" time(MAX(0, max_duration - (unixepoch('now') - unixepoch(t.start_date))), 'unixepoch') AS remaining_time"
|
|
|
|
|
" FROM stages s"
|
|
|
|
|
" JOIN"
|
|
|
|
|
" projects p ON s.id_project = p.id_project AND"
|
|
|
|
|
" p.status = 'Process' AND"
|
|
|
|
|
" s.expert IS NOT NULL"
|
|
|
|
|
" JOIN jobs j ON j.id_stage = s.id_stage"
|
|
|
|
|
" JOIN tasks t ON t.id_job = j.id_job AND t.status = 'Process';";
|
|
|
|
|
|
|
|
|
|
// Выполняем запрос
|
|
|
|
|
int request = DatabasePrepare(DB::Id(), query);
|
|
|
|
|
|
|
|
|
|
struct Row {
|
|
|
|
|
string stage_name;
|
|
|
|
|
string expert_name;
|
|
|
|
|
string from_date;
|
|
|
|
|
string to_date;
|
|
|
|
|
string symbol;
|
|
|
|
|
string timeframe;
|
|
|
|
|
int optimization_criterion;
|
|
|
|
|
string start_date;
|
|
|
|
|
string max_duration;
|
|
|
|
|
string elapsed_time;
|
|
|
|
|
string remainig_time;
|
|
|
|
|
} row;
|
|
|
|
|
|
2025-09-25 11:28:48 +03:00
|
|
|
// Если нет ошибки
|
2025-09-24 09:38:40 +03:00
|
|
|
if(request != INVALID_HANDLE) {
|
|
|
|
|
|
2025-09-25 11:28:48 +03:00
|
|
|
// Читаем данные из первой строки результата и добавляем в текст
|
2025-09-24 09:38:40 +03:00
|
|
|
if(DatabaseReadBind(request, row)) {
|
|
|
|
|
text += StringFormat("TASK #%I64u:\n"
|
|
|
|
|
" %10.10s │ %14.14s │ %-23s │ %6.6s │ %-3.3s │ %15.15s │ %-10.10s │ %-10.10s\n"
|
|
|
|
|
"────────────┼────────────────┼─────────────────────────┼────────┼─────┼─────────────────┼────────────┼─────────────\n"
|
|
|
|
|
" %10.10s │ %14.14s │ %s - %s │ %6.6s │ %-3.3s │ %15.15s │ %-10.10s │ %-10.10s \n\n"
|
|
|
|
|
"═════════════════════════════════════════════════════════════════════════\n",
|
|
|
|
|
m_id,
|
|
|
|
|
"Stage",
|
|
|
|
|
"Expert",
|
|
|
|
|
"Testing period",
|
|
|
|
|
"Symbol",
|
|
|
|
|
"TF",
|
|
|
|
|
"Criterion",
|
|
|
|
|
"Max Durat.",
|
|
|
|
|
"Remaining",
|
|
|
|
|
row.stage_name,
|
|
|
|
|
row.expert_name,
|
|
|
|
|
row.from_date,
|
|
|
|
|
row.to_date,
|
|
|
|
|
row.symbol,
|
|
|
|
|
row.timeframe,
|
|
|
|
|
s_criterionNames[row.optimization_criterion],
|
|
|
|
|
m_params.max_duration ? row.max_duration : "Unlimited",
|
|
|
|
|
row.remainig_time
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
DatabaseFinalize(request);
|
|
|
|
|
|
|
|
|
|
DB::Close();
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-19 13:56:45 +03:00
|
|
|
return text;
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|