//+------------------------------------------------------------------+ //| Orquestador.mqh | //| Copyright 2025, Niquel Mendoza. | //| https://www.mql5.com/es/users/nique_372 | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Niquel Mendoza." #property link "https://www.mql5.com/es/users/nique_372" #property strict #ifndef WORKFLOWSBYLEO_FINAL_ORQUESTADOR_MQH #define WORKFLOWSBYLEO_FINAL_ORQUESTADOR_MQH //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #include "..\\Core\\Def.mqh" #include // Para saber cuando termino un wf crear un callback on end SIEMPRE //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CWorkflow::CWorkflow(void) { m_call_on_step_size = ArrayResize(m_call_on_step, 0); m_call_on_start_size = ArrayResize(m_call_on_start, 0); m_call_on_end_size = ArrayResize(m_call_on_end, 0); m_steps_size = ArrayResize(m_steps, 0); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CWorkflow::~CWorkflow() { //--- Callbacks for(int i = 0; i < m_call_on_step_size; i++) { if(CheckPointer(m_call_on_step[i]) == POINTER_DYNAMIC) delete m_call_on_step[i]; } for(int i = 0; i < m_call_on_start_size; i++) { if(CheckPointer(m_call_on_start[i]) == POINTER_DYNAMIC) delete m_call_on_start[i]; } for(int i = 0; i < m_call_on_end_size; i++) { if(CheckPointer(m_call_on_end[i]) == POINTER_DYNAMIC) delete m_call_on_end[i]; } //--- Pasos for(int i = 0; i < m_steps_size; i++) { if(CheckPointer(m_steps[i]) == POINTER_DYNAMIC) delete m_steps[i]; } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ /* 2026.04.13 12:36:25.128 tas (EURUSD,H1) TYPE_BOOL = 1 2026.04.13 12:36:25.128 tas (EURUSD,H1) TYPE_CHAR = 2 2026.04.13 12:36:25.128 tas (EURUSD,H1) TYPE_UCHAR = 3 2026.04.13 12:36:25.128 tas (EURUSD,H1) TYPE_SHORT = 4 2026.04.13 12:36:25.128 tas (EURUSD,H1) TYPE_USHORT = 5 2026.04.13 12:36:25.128 tas (EURUSD,H1) TYPE_COLOR = 6 2026.04.13 12:36:25.128 tas (EURUSD,H1) TYPE_INT = 7 2026.04.13 12:36:25.128 tas (EURUSD,H1) TYPE_UINT = 8 2026.04.13 12:36:25.128 tas (EURUSD,H1) TYPE_DATETIME = 9 2026.04.13 12:36:25.128 tas (EURUSD,H1) TYPE_LONG = 10 2026.04.13 12:36:25.128 tas (EURUSD,H1) TYPE_ULONG = 11 2026.04.13 12:36:25.128 tas (EURUSD,H1) TYPE_FLOAT = 12 2026.04.13 12:36:25.128 tas (EURUSD,H1) TYPE_DOUBLE = 13 2026.04.13 12:36:25.128 tas (EURUSD,H1) TYPE_STRING = 14 */ //+------------------------------------------------------------------+ //| Variables del YML | //+------------------------------------------------------------------+ bool CWorkflow::AssingMqlParam(MqlParam& param, CYmlNode& node, const string stype) { //--- if(!node.IsValidDataType()) // Si no es un tipo de data valido salimos return false; //--- const ENUM_DATATYPE type = CEnumReg::GetValue(stype, WRONG_VALUE); if(type == WRONG_VALUE) return false; // Tipo solicitado invalido //--- param.type = type; //--- switch(type) { case TYPE_BOOL: param.integer_value = TranslateBool(node, ""); break; case TYPE_CHAR: param.integer_value = TranslateNumber(node, ""); break; case TYPE_UCHAR: param.integer_value = TranslateNumber(node, ""); break; case TYPE_SHORT: param.integer_value = TranslateNumber(node, ""); break; case TYPE_USHORT: param.integer_value = TranslateNumber(node, ""); break; case TYPE_COLOR: param.integer_value = long(color(TranslateStr(node, ""))); break; case TYPE_INT: param.integer_value = TranslateNumber(node, ""); break; case TYPE_UINT: param.integer_value = TranslateNumber(node, ""); break; case TYPE_DATETIME: param.integer_value = long(datetime(TranslateStr(node, ""))); break; case TYPE_LONG: case TYPE_ULONG: param.integer_value = TranslateNumber(node, ""); break; case TYPE_FLOAT: param.double_value = TranslateNumber(node, ""); break; case TYPE_DOUBLE: param.double_value = TranslateNumber(node, ""); break; case TYPE_STRING: param.string_value = TranslateStr(node, ""); break; default: param.string_value = TranslateStr(node, ""); break; } //--- return true; } //+------------------------------------------------------------------+ bool CWorkflow::AssingMqlParam(MqlParam& param, CYmlNode& node, ENUM_DATATYPE type) { //--- if(!node.IsValidDataType()) // Si no es un tipo de data valido salimos return false; //--- param.type = type; //--- switch(type) { case TYPE_BOOL: param.integer_value = TranslateBool(node, ""); break; case TYPE_CHAR: param.integer_value = TranslateNumber(node, ""); break; case TYPE_UCHAR: param.integer_value = TranslateNumber(node, ""); break; case TYPE_SHORT: param.integer_value = TranslateNumber(node, ""); break; case TYPE_USHORT: param.integer_value = TranslateNumber(node, ""); break; case TYPE_COLOR: param.integer_value = long(color(TranslateStr(node, ""))); break; case TYPE_INT: param.integer_value = TranslateNumber(node, ""); break; case TYPE_UINT: param.integer_value = TranslateNumber(node, ""); break; case TYPE_DATETIME: param.integer_value = long(datetime(TranslateStr(node, ""))); break; case TYPE_LONG: case TYPE_ULONG: param.integer_value = TranslateNumber(node, ""); break; case TYPE_FLOAT: param.double_value = TranslateNumber(node, ""); break; case TYPE_DOUBLE: param.double_value = TranslateNumber(node, ""); break; case TYPE_STRING: param.string_value = TranslateStr(node, ""); break; default: param.string_value = TranslateStr(node, ""); break; } //--- return true; } //+------------------------------------------------------------------+ template TEnum CWorkflow::TranslateEnum(const CYmlNode &val, const string &def) { const string res = val.ToString(def); const int t = StringLen(res); int k = 0; string final_res = ""; while(k < t) { if(res[k] < 33) { // Si son espacios ignoramos //--- const int len = StringLen(final_res); StringSetLength(final_res, len + 1); final_res.SetChar(len, res[k++]); continue; } //--- if(k + 2 < t && res[k] == '$' && res[k + 1] == '{' && res[k + 2] == '{') { k += 3; const int start = k; //--- Buscamos el final while(k + 1 < t) { if(res[k] == '}' && res[k + 1] == '}') { break; } k++; } //--- // ${{aidata.hola}}|${{aidata.no}} string temp = StringSubstrRange(res, start, k - 1); if(!m_hash_str_val_to_str.TryGetStr(temp, temp)) { final_res += "${{" + temp + "}}"; } else { final_res += temp; } k += 2; // Saltamos los }} } //--- const int len = StringLen(final_res); StringSetLength(final_res, len + 1); final_res.SetChar(len, res[k++]); } return CEnumReg::GetValue(final_res, WRONG_VALUE); // Tal cual // NOTA: dado que es WRONG_VALUE chekear eso o poner un valorp def en el str como "_Period" por ejemplo } //+------------------------------------------------------------------+ string CWorkflow::TranslateStr(const CYmlNode& val, const string& def) { const string res = val.ToString(def); const int t = StringLen(res); int k = 0; string final_res = ""; while(k < t) { if(res[k] < 33) { // Si son espacios ignoramos //--- const int len = StringLen(final_res); StringSetLength(final_res, len + 1); final_res.SetChar(len, res[k++]); continue; } //--- if(k + 2 < t && res[k] == '$' && res[k + 1] == '{' && res[k + 2] == '{') { k += 3; const int start = k; //--- Buscamos el final while(k + 1 < t) { if(res[k] == '}' && res[k + 1] == '}') { break; } k++; } //--- // ${{aidata.hola}}|${{aidata.no}} string temp = StringSubstrRange(res, start, k - 1); if(!m_hash_str_val_to_str.TryGetStr(temp, temp)) { final_res += "${{" + temp + "}}"; } else { final_res += temp; } k += 2; // Saltamos los }} } //--- const int len = StringLen(final_res); StringSetLength(final_res, len + 1); final_res.SetChar(len, res[k++]); } return final_res; // Tal cual } //+------------------------------------------------------------------+ // Integer \ Dbl template TNumber CWorkflow::TranslateNumber(const CYmlNode& val, const string& def) { const string res = val.ToString(def); const int t = StringLen(res); int k = 0; string final_res = ""; while(k < t) { if(res[k] < 33) { // Si son espacios ignoramos //--- const int len = StringLen(final_res); StringSetLength(final_res, len + 1); final_res.SetChar(len, res[k++]); continue; } //--- if(k + 2 < t && res[k] == '$' && res[k + 1] == '{' && res[k + 2] == '{') { k += 3; const int start = k; //--- Buscamos el final while(k + 1 < t) { if(res[k] == '}' && res[k + 1] == '}') { break; } k++; } //--- // ${{aidata.hola}}|${{aidata.no}} string temp = StringSubstrRange(res, start, k - 1); if(!m_hash_str_val_to_str.TryGetStr(temp, temp)) { final_res += "${{" + temp + "}}"; } else { final_res += temp; } k += 2; // Saltamos los }} } //--- const int len = StringLen(final_res); StringSetLength(final_res, len + 1); final_res.SetChar(len, res[k++]); } return TNumber(final_res); // Tal cual } //+------------------------------------------------------------------+ bool CWorkflow::TranslateBool(const CYmlNode& val, const string& def) { const string res = val.ToString(def); const int t = StringLen(res); int k = 0; string final_res = ""; while(k < t) { if(res[k] < 33) { // Si son espacios ignoramos //--- const int len = StringLen(final_res); StringSetLength(final_res, len + 1); final_res.SetChar(len, res[k++]); continue; } //--- if(k + 2 < t && res[k] == '$' && res[k + 1] == '{' && res[k + 2] == '{') { k += 3; const int start = k; //--- Buscamos el final while(k + 1 < t) { if(res[k] == '}' && res[k + 1] == '}') { break; } k++; } //--- // ${{aidata.hola}}|${{aidata.no}} string temp = StringSubstrRange(res, start, k - 1); if(!m_hash_str_val_to_str.TryGetStr(temp, temp)) { final_res += "${{" + temp + "}}"; } else { final_res += temp; } k += 2; // Saltamos los }} } //--- const int len = StringLen(final_res); StringSetLength(final_res, len + 1); final_res.SetChar(len, res[k++]); } return (final_res == "true" || int(final_res) != 0); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CWorkflow::Init(const string &file_name, bool comon_flag) { //--- m_initialized = false; if(!m_yml.ParseFile(file_name, comon_flag)) { int errcol, errline; m_yml.GetLastErrorLocation(errline, errcol); LogError(StringFormat("Failed parser yml, last err = %s | col = %d, line = %d", EnumToString(m_yml.LastErr()), errcol, errline), FUNCION_ACTUAL); return false; } //Print(ShortArrayToString(m_yml.m_yml)); //--- return Procesar(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CWorkflow::Init(const string &yml) { //--- m_initialized = false; if(!m_yml.Parse(yml)) { LogError(StringFormat("Failed parser yml, last err = %s", EnumToString(m_yml.LastErr())), FUNCION_ACTUAL); return false; } //--- return Procesar(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CWorkflow::Procesar(void) { //--- Cleaning (limpiamos variables) for(int i = 0; i < m_steps_size; i++) { if(CheckPointer(m_steps[i]) == POINTER_DYNAMIC) delete m_steps[i]; } m_steps_size = ArrayResize(m_steps, 0); m_step_curr_pos = 0; m_is_running = false; //--- Get Root CYmlNode root = m_yml.GetRoot(); //--- Env CYmlNode env = root["env"]; if(!env.IsObject()) { LogWarning("No hay variables de entorno que revisar", FUNCION_ACTUAL); } else { //--- LogCaution("Hay variables de entorno que se van a procesar", FUNCION_ACTUAL); //--- CYmlIteratorObj it = env.BeginObj(); while(it.IsValid()) { const string key = "env." + it.Key(); const string val = it.Val().ToString(); //PrintFormat("%s = %s", key, val); Variables().SetStr(key, val); it.Next(); } } //--- Metadata // Name m_name = root["name"].ToString("Mi flujo de trabajo"); //--- Pasos // Steps CYmlNode steps = root["steps"]; if(!steps.IsArray()) { LogError("El campo steps debe de ser del tipo array", FUNCION_ACTUAL); return false; } // Check size m_steps_size = ArrayResize(m_steps, steps.Size()); int k = 0; if(m_steps_size < 1) { LogError("No hay ninguno paso", FUNCION_ACTUAL); return false; } CYmlIteratorArray it = steps.BeginArr(); //--- Iteration while(it.IsValid()) { //--- CYmlNode step = it.Val(); CYmlNode params = step["with"]; // [module] [name] const string name = step["name"].ToString(""); const string module = step["module"].ToString(""); //--- CWorkflowStep* s = CWorflowsFactory::GetWorkflowStep(module, name); if(!CheckPointer(s)) { LogError(StringFormat("Par (%s\\%s) invalido, el puntero obtenido es invalid pointer", module, name), FUNCION_ACTUAL); return false; } //--- s.MainPointer(&this); s.ParseIndexes(step["valid_results"]); // Parseamos (en caso no exista por defecto soiemrpe se habilita el 0) //--- if(!s.Init(params)) { delete s; LogError(StringFormat("Fallo al iniciar el work = (%s\\%s)", module, name), FUNCION_ACTUAL); return false; } //--- m_steps[k++] = s; //--- it.Next(); } //--- Succes m_initialized = true; //--- return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CWorkflow::AddCallBack(IWorkflowCallback *callback) { //--- if(!CheckPointer(callback)) { LogError("No se agrego callback dado que es invalido", FUNCION_ACTUAL); return; } //--- const uint8_t flags = callback.FlagsCall(); if((flags & WORKFLOWBYLEO_CALLBACK_ON_START) != 0) { m_call_on_start[ArrayResize(m_call_on_start, (++m_call_on_start_size)) - 1] = callback; } if((flags & WORKFLOWBYLEO_CALLBACK_ON_STEP) != 0) { m_call_on_step[ArrayResize(m_call_on_step, (++m_call_on_step_size)) - 1] = callback; } if((flags & WORKFLOWBYLEO_CALLBACK_ON_END) != 0) { m_call_on_end[ArrayResize(m_call_on_end, (++m_call_on_end_size)) - 1] = callback; } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ // Funcion que se llama luego de terminar cada training // La idea es reinicar valores como posiciones actual o is running void CWorkflow::OnEnd(const int p, const int res) { //--- Siempre primero fasle para evitar ambiguedad m_is_running = false; if(res != 0) LogError(StringFormat("Fallo al ejeuctar el step %d", p), FUNCION_ACTUAL); //--- m_step_curr_pos = p; // En caso de se consulten proiedas como GetStep.. enviatmos el fuera de rango (p esta normalziadno bien) for(int k = 0; k < m_call_on_step_size; k++) m_call_on_step[k].OnWorkflowEnd(res, m_step_curr_pos); //--- Reinicamos m_step_curr_pos = 0; } //+------------------------------------------------------------------+ //| Ejecucion de tareas tanto asyncronas como sincronas | //+------------------------------------------------------------------+ // Algirtmo compatible con steps aynsc, y sincropnicos #define WORKFLOWBYLEO_SUCCES (0) #define WORKFLOWBYLEO_ERR_FIRST_NO_INIT (1) #define WORKFLOWBYLEO_ERR_FIRST_CALLBACK_ONSTART (2) //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CWorkflow::First(void) { //--- // Nota: para recibir los eventos de finalziacion hace falta registrarse // Esta funcion no retura ture cuando se completa el flujo retora true si el primer paso tuvo exito //--- if(!m_initialized) return WORKFLOWBYLEO_ERR_FIRST_NO_INIT; //--- Hook start for(int i = 0; i < m_call_on_start_size; i++) { if(!m_call_on_start[i].OnWorkflowStart()) { LogError(StringFormat("No se pudo ejeuctar la tarea, fallo al llamar al callback = %d", i), FUNCION_ACTUAL); return WORKFLOWBYLEO_ERR_FIRST_CALLBACK_ONSTART; } } //--- Ejecucion m_is_running = true; const int res = m_steps[m_step_curr_pos].Run(); _RecibedEvent(res, true); //--- return WORKFLOWBYLEO_SUCCES; } //+------------------------------------------------------------------+ // Notas: // flag: siempre debe de ser false si se llama de afuera siempre... (la clase si lo varia) // otro: ovbiamnte esto no se peude llamar de afuera daod que podria dar array out range etc.. asi que cuidado con eso no deberia de llamarse void CWorkflow::_RecibedEvent(const int res, bool flag = false) { //--- if(!m_steps[m_step_curr_pos].IsPassed(res)) { OnEnd(m_step_curr_pos, res); return; // Termino ya no hace falta ir abajo } //--- Solo revisamos el siguintye si es sincrenoico if(!m_steps[m_step_curr_pos].m_async || !flag) { m_step_curr_pos++; // Aumentamos dado que el paso anterior no era sincrono //--- if(m_step_curr_pos >= m_steps_size) { // Se supero todo finalizamos OnEnd(m_step_curr_pos - 1, res); } else { // Hay mas tareas // Primero notificamremos de la tarea anteiorr for(int k = 0; k < m_call_on_step_size; k++) m_call_on_step[k].OnWorkflowStep(res, m_step_curr_pos - 1); // Luego continuamos con la siguiente const int res = m_steps[m_step_curr_pos].Run(); _RecibedEvent(res, true); } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ __forceinline void CWorkflow::ChartEvent(const int32_t id, const long& lparam, const double& dparam, const string& sparam) { if(m_is_running && (m_steps[m_step_curr_pos].m_async_flags & WORKFLOWBYLEO_STEP_FLAG_ON_CHART_EVENT) != 0) m_steps[m_step_curr_pos].ChartEvent(id, lparam, dparam, sparam); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ __forceinline void CWorkflow::TimerEvent(void) { if(m_is_running && (m_steps[m_step_curr_pos].m_async_flags & WORKFLOWBYLEO_STEP_FLAG_ON_TIMER_EVENT) != 0) m_steps[m_step_curr_pos].OnTimerEvent(); } //--- #endif // WORKFLOWSBYLEO_FINAL_ORQUESTADOR_MQH