WorkflowsByLeo/Src/Final/Orquestador.mqh

750 lines
21 KiB
MQL5

//+------------------------------------------------------------------+
//| 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 <TSN\\MQLArticles\\Utils\\EnumReg.mqh>
// 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<ENUM_DATATYPE>(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<char>(node, "");
break;
case TYPE_UCHAR:
param.integer_value = TranslateNumber<uchar>(node, "");
break;
case TYPE_SHORT:
param.integer_value = TranslateNumber<short>(node, "");
break;
case TYPE_USHORT:
param.integer_value = TranslateNumber<ushort>(node, "");
break;
case TYPE_COLOR:
param.integer_value = long(color(TranslateStr(node, "")));
break;
case TYPE_INT:
param.integer_value = TranslateNumber<int>(node, "");
break;
case TYPE_UINT:
param.integer_value = TranslateNumber<uint>(node, "");
break;
case TYPE_DATETIME:
param.integer_value = long(datetime(TranslateStr(node, "")));
break;
case TYPE_LONG:
case TYPE_ULONG:
param.integer_value = TranslateNumber<long>(node, "");
break;
case TYPE_FLOAT:
param.double_value = TranslateNumber<float>(node, "");
break;
case TYPE_DOUBLE:
param.double_value = TranslateNumber<double>(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<char>(node, "");
break;
case TYPE_UCHAR:
param.integer_value = TranslateNumber<uchar>(node, "");
break;
case TYPE_SHORT:
param.integer_value = TranslateNumber<short>(node, "");
break;
case TYPE_USHORT:
param.integer_value = TranslateNumber<ushort>(node, "");
break;
case TYPE_COLOR:
param.integer_value = long(color(TranslateStr(node, "")));
break;
case TYPE_INT:
param.integer_value = TranslateNumber<int>(node, "");
break;
case TYPE_UINT:
param.integer_value = TranslateNumber<uint>(node, "");
break;
case TYPE_DATETIME:
param.integer_value = long(datetime(TranslateStr(node, "")));
break;
case TYPE_LONG:
case TYPE_ULONG:
param.integer_value = TranslateNumber<long>(node, "");
break;
case TYPE_FLOAT:
param.double_value = TranslateNumber<float>(node, "");
break;
case TYPE_DOUBLE:
param.double_value = TranslateNumber<double>(node, "");
break;
case TYPE_STRING:
param.string_value = TranslateStr(node, "");
break;
default:
param.string_value = TranslateStr(node, "");
break;
}
//---
return true;
}
//+------------------------------------------------------------------+
template <typename TEnum>
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<TEnum>(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 <typename TNumber>
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