MqlCIByLeo/Src/Steps/BackComparer.mqh

338 lines
12 KiB
MQL5

//+------------------------------------------------------------------+
//| 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 <TSN\\ExtraCodes\\RunnerProtDef.mqh>
//--- Funciones basicas
#include <TSN\\MQLArticles\\Utils\\Basic.mqh>
//--- Compilacion
#include <TSN\\ExtraCodes\\Func.mqh>
//+------------------------------------------------------------------+
//| 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