//+------------------------------------------------------------------+ //| BackComparer.mqh | //| Copyright 2026,Niquel Mendoza. | //| https://www.mql5.com/en/users/nique_372 | //+------------------------------------------------------------------+ #property copyright "Copyright 2026,Niquel Mendoza." #property link "https://www.mql5.com/en/users/nique_372" #property strict #ifndef MQLCYBYLEO_SRC_STEPS_BACKCOMPARER_MQH #define MQLCYBYLEO_SRC_STEPS_BACKCOMPARER_MQH //+------------------------------------------------------------------+ //| Include | //+------------------------------------------------------------------+ //--- Resultados #include "..\\BacktestResults\\Base.mqh" //--- Procolo de comunicaion con el orquestaodr del CI #include "..\\Core\\Def.mqh" //--- Procolo de comuniciopn con el runner del tester #include //--- Funciones basicas #include //--- Compilacion #include //+------------------------------------------------------------------+ //| Enums \ Structs \ Intefaces | //+------------------------------------------------------------------+ enum ENUM_BACKCOMPARARER_STATE { BACKCOMPARER_STATE_FIRST_EVALUATE = 0, BACKCOMPARER_STATE_LAST_EVALUATE }; //--- interface IBackResComparer { // true si hay diferencia false si no lo haya (todo ok) bool CompareBack(const BackRes& back_prev, const BackRes& back_curr, string& msg); }; //--- struct BackComparerConfig { MTTesterTask backtests[]; // configs de cada backtest string expert_path_exe; // path del ea a ejecutar string expert_path_compile; // compilar string file_ea_comunication; // donde el EA escribe su BackRes string folder_path_comunication_tester; string test_name; long chart_id_orquestador; IBackResComparer* comparador; // quien decide si paso o no uint timeout_ms_compile; bool common_folder; }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CBackEaComparer : public CLoggerBase { private: //--- ENUM_BACKCOMPARARER_STATE m_state; IBackResComparer* m_comparador; //--- MTTesterTask m_backtests_config[]; int m_backtests_config_size; //--- BackRes m_back_res_prev[]; BackRes m_back_res_current[]; int m_pos_back_res; //--- string m_expert_path_exe; string m_expert_path_compile; string m_file_ea_comunication; bool m_common_folder; string m_folder_path_comunication_tester; const long m_current_chart_id; long m_chart_id_orquestador; string m_test_name; uint m_max_timeout_ms_compile; long m_chart_id_runner; //--- Funciones // Procesar evento de mttester void RecibirEventoFinishBack(const bool exito, const string& sparam_log); // Mandar eventos de ejeuccion de backtet void SendBacktestCommand(); // Compilar bool CompileEa(); // Comparar void CompararResultados(); public: CBackEaComparer(void) : m_current_chart_id(ChartID()) {} ~CBackEaComparer(void); //--- void Init(const BackComparerConfig& init); //--- void ChartEvent(const int32_t id, const long& lparam, const double& dparam, const string& sparam); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CBackEaComparer::~CBackEaComparer(void) { if(CheckPointer(m_comparador) == POINTER_DYNAMIC) delete m_comparador; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CBackEaComparer::Init(const BackComparerConfig& init) { //--- m_expert_path_exe = init.expert_path_exe; m_expert_path_compile = init.expert_path_compile; m_file_ea_comunication = init.file_ea_comunication; m_common_folder = init.common_folder; m_folder_path_comunication_tester = init.folder_path_comunication_tester; m_chart_id_orquestador = init.chart_id_orquestador; m_test_name = init.test_name; m_comparador = init.comparador; m_max_timeout_ms_compile = init.timeout_ms_compile; m_chart_id_runner = DEFMTTesterGetChartId(); //--- m_backtests_config_size = ArrayCopyAll(m_backtests_config, init.backtests); ArrayResize(m_back_res_prev, m_backtests_config_size); ArrayResize(m_back_res_current, m_backtests_config_size); //--- m_state = BACKCOMPARER_STATE_FIRST_EVALUATE; m_pos_back_res = 0; //--- SendBacktestCommand(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CBackEaComparer::CompararResultados(void) { //--- if(!::CheckPointer(m_comparador)) { LogError("No hay comparador asignado", FUNCION_ACTUAL); //--- ::EventChartCustom(m_chart_id_orquestador, CIBYLEO_ONEVENT_RES, CYBYLEO_CODE_ERROR, 0.00, m_test_name); Remover(); return; } //--- for(int i = 0; i < m_backtests_config_size; i++) { static string msg; if(m_comparador.CompareBack(m_back_res_prev[i], m_back_res_current[i], msg)) { LogError(StringFormat("Resultados difieren en config[%d], mes:\n%s", i, msg), FUNCION_ACTUAL); //--- ::EventChartCustom(m_chart_id_orquestador, CIBYLEO_ONEVENT_RES, CYBYLEO_CODE_ERROR, 0.00, m_test_name); Remover(); return; } } //--- ::EventChartCustom(m_chart_id_orquestador, CIBYLEO_ONEVENT_RES, CYBYLEO_CODE_SUCCES, 0.00, m_test_name); Remover(); // Sale } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CBackEaComparer::CompileEa(void) { // PATh libre tal cual string log_path; if(!CompileFileWithLogFile(TERMINAL_MT5_ROOT + m_expert_path_compile, log_path, m_max_timeout_ms_compile)) { LogError(StringFormat("Fallo al compilar: %s", m_expert_path_compile), FUNCION_ACTUAL); //--- ::EventChartCustom(m_chart_id_orquestador, CIBYLEO_ONEVENT_RES, CYBYLEO_CODE_ERROR, 0.00, m_test_name); Remover(); return false; } return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CBackEaComparer::SendBacktestCommand(void) { //--- // Aqui nos encargaremos de ejecutar los backtest inciales //--- Corremos asi tal cual for(int i = 0; i < m_backtests_config_size; i++) { const string full_path = m_folder_path_comunication_tester + StringFormat("data_back_%d.csv", i); // Asignamos los campso m_backtests_config[i].expert_path = m_expert_path_exe; // le tenemos que aumentar el .ex5 m_backtests_config[i].chart_id_from = m_current_chart_id; //--- Creamos el archivo if(!DEFMMTesterCreateFileWriteData(full_path, m_common_folder, m_backtests_config[i].ToString())) { //--- Imprimimos error LogError(StringFormat("Fallo al guardar config para: %s", full_path), FUNCION_ACTUAL); // Salimos ::EventChartCustom(m_chart_id_orquestador, CIBYLEO_ONEVENT_RES, CYBYLEO_CODE_ERROR, 0.00, m_test_name); Remover(); return; } //--- Enviamos evento ::ResetLastError(); if(!::EventChartCustom(m_chart_id_runner, DEFMTTESTER_E_ON_TASK, m_current_chart_id, DEFMTTESTER_TO_DBL_ON_TASK(m_common_folder), full_path)) { //--- Imprimimos el err LogError(StringFormat("Fallo al enviar evento de run back para %s\nUltimo error = %d", full_path, ::GetLastError()), FUNCION_ACTUAL); // Salimos ::EventChartCustom(m_chart_id_orquestador, CIBYLEO_ONEVENT_RES, CYBYLEO_CODE_ERROR, 0.00, m_test_name); Remover(); return; } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CBackEaComparer::RecibirEventoFinishBack(const bool exito, const string &sparam_log) { //--- Check inicial if(!exito) { // En caso al ejeuctar el backtest hayamos recibido un error entonces daremos por temrinado el test LogError("Fallo al ejeuctar EA", FUNCION_ACTUAL); // Salimos ::EventChartCustom(m_chart_id_orquestador, CIBYLEO_ONEVENT_RES, CYBYLEO_CODE_ERROR, 0.00, m_test_name); Remover(); return; } //--- Cargamos resultados if(m_state == BACKCOMPARER_STATE_FIRST_EVALUATE) { if(!m_back_res_prev[m_pos_back_res++].Load(m_file_ea_comunication, m_common_folder)) { LogError("Fallo al cargar resultados de backtest de EA", FUNCION_ACTUAL); // Salimos ::EventChartCustom(m_chart_id_orquestador, CIBYLEO_ONEVENT_RES, CYBYLEO_CODE_ERROR, 0.00, m_test_name); Remover(); return; } // Ya lo tenemos cargado if(m_pos_back_res >= m_backtests_config_size) // Superamos siguiente paso { m_state = BACKCOMPARER_STATE_LAST_EVALUATE; // Siguiente m_pos_back_res = 0; if(!CompileEa()) return; SendBacktestCommand(); } } else { if(!m_back_res_current[m_pos_back_res++].Load(m_file_ea_comunication, m_common_folder)) { LogError("Fallo al cargar resultados de backtest de EA", FUNCION_ACTUAL); // Salimos ::EventChartCustom(m_chart_id_orquestador, CIBYLEO_ONEVENT_RES, CYBYLEO_CODE_ERROR, 0.00, m_test_name); Remover(); return; } // Ya lo tenemos cargado if(m_pos_back_res >= m_backtests_config_size) // Superamos siguiente paso { m_pos_back_res = 0; CompararResultados(); } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CBackEaComparer::ChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- de vuelta //lparam = combinacion de banderas en caso de error 0 exito total //sparam = se empaqueta como (chartid|log) //dparam = nada //--- // Si se finaliza if(id == CHARTEVENT_CUSTOM + DEFMTTESTER_E_FINISH_TASK) { //--- static string arr[2]; if(StringSplit(sparam, DEFMTTESTER_OUT_SPARAM_SEP_U, arr) != DEFMTTESTER_OUT_SPARAM_TOTAL) // formato invalido { return; } //--- const long chart_id = long(arr[DEFMTTESTER_OUT_SPARAM_IDX_CHARTID]); Print("Nuevo evento chart_id =", chart_id); if(chart_id != m_current_chart_id) return; //--- LogInfo(StringFormat("Evento recibido desde = %I64d | Resultado = %I64d", chart_id, lparam), FUNCION_ACTUAL); //--- RecibirEventoFinishBack((lparam == DEFMTTESTER_TESTER_R_EXITO), arr[DEFMTTESTER_OUT_SPARAM_IDX_LOG]); } } //+------------------------------------------------------------------+ #endif // MQLCYBYLEO_SRC_STEPS_BACKCOMPARER_MQH