//+------------------------------------------------------------------+ //| Main.mqh | //| Copyright 2026, Niquel Mendoza. | //| https://www.mql5.com/ | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Niquel Mendoza." #property link "https://www.mql5.com/" #property strict #ifndef LLMREGISTERYBYLEO_SRC_MAIN_MQH #define LLMREGISTERYBYLEO_SRC_MAIN_MQH //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #include "Def.mqh" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #resource "Init.sql" as const string g_sql_init //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ namespace TSN { //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CLlmRegistery : public CLoggerBase { private: //--- int m_sql_handle; CJsonParser m_json_parser; CJsonBuilder m_json_builder; string m_file_name_json; bool m_common; bool m_check_revised; // ahora int m_segundos_timer; // fijo siempre.. int m_tiemout_ms_req; // int m_intentos; int m_max_intentos; int m_segundos_to_retry; //--- void CheckInitialNextTime(); bool TraerEventos(); bool WriteAllEvents(); void SaveExito(); void Try(); public: CLlmRegistery(void); ~CLlmRegistery(void); //--- bool Init(const string &folder_name, bool in_comon, int horas_update, int max_intenos, int segundos_try, int timeout_ms); //--- void OnTimerEvent(); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CLlmRegistery::CLlmRegistery() : m_sql_handle(INVALID_HANDLE), m_file_name_json(NULL), m_common(true), m_check_revised(false), m_segundos_timer(0), m_intentos(0), m_max_intentos(5), m_segundos_to_retry(0) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CLlmRegistery::~CLlmRegistery(void) { if(m_sql_handle != INVALID_HANDLE) { DatabaseClose(m_sql_handle); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CLlmRegistery::Init(const string &folder_name, bool in_comon, int horas_update, int max_intenos, int segundos_try, int timeout_ms) { //--- m_common = in_comon; m_max_intentos = max_intenos; m_segundos_to_retry = segundos_try; m_segundos_timer = horas_update * 3600; m_tiemout_ms_req = timeout_ms; //--- m_file_name_json = folder_name + LLMREG_FILENAME_CONFIRM_JSON; //--- const string sqlname = folder_name + LLMREG_FILENAME_SQL; ::ResetLastError(); m_sql_handle = DatabaseOpen(sqlname, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE | (in_comon ? DATABASE_OPEN_COMMON : 0)); if(m_sql_handle == INVALID_HANDLE) { LogCriticalError(StringFormat("Fallo al abrir\\crear la base de dato = %s, ultimo err = %d", sqlname, ::GetLastError()), FUNCION_ACTUAL); return false; } //--- Aqui creamos las tablas en caso no existan.... if(!DatabaseExecute(m_sql_handle, g_sql_init)) { LogCriticalError(StringFormat("Fallo al ejeuctar el comando incial sql, ultimo err = %d", sqlname, ::GetLastError()), FUNCION_ACTUAL); return false; } //--- CheckInitialNextTime(); return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CLlmRegistery::CheckInitialNextTime(void) { //--- Vericamos si no existe // En ese caso creamos todo de 0 // En caso exista solo configuramos timer para ello.. //--- Existe el archivo ::ResetLastError(); const int ext = m_json_parser.AssingFile(m_file_name_json, m_common); if(m_json_parser.AutoParseByFileExt(ext)) { const datetime t = StringToTime(m_json_parser.GetRoot()["last_date_revised"].ToString("0")); const int diff = int(TimeLocal() - t); if(t > 0 && diff > 0) // solo si el valor es valido y hay difiencia positiva (hay tiempo aun) { LogInfo("Existe arhicov base.. oncigunado siguiente acutlziacoin", FUNCION_ACTUAL); EventSetTimer(diff); // Esos segundos.. return; // Salimos.. } // Es invalido digmoas menor a 0.... asi qeu acutlizamos ahora mismo.. } else { // Fallo al abrir o fallo al parsear... LogError(StringFormat("No se pudo leer json en el archivo = %s, json info: ", m_file_name_json), FUNCION_ACTUAL); m_json_parser.ErrorInfo(); } //--- Actulizacion ahora mismo... LogWarning("Configurando timer ahora mismo, no existe el archivo base o no se pudo abrir..", FUNCION_ACTUAL); m_check_revised = true; // Seteamos.. OnTimerEvent(); // Primer calling... } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CLlmRegistery::Try() { //--- if(m_intentos == 0) { // En caos esta en true digmoas uqe vino con desfase pero falla // Entonces quitamos la revision daod qeu aqui ya la haremos.. m_check_revised = false; EventKillTimer(); LogWarning(StringFormat("Fallo al traer eventos o escribir valores en db.. Reintnado entro de %d segundo", m_segundos_to_retry), FUNCION_ACTUAL); } //--- m_intentos++; if(m_intentos > m_max_intentos) { EventKillTimer(); m_intentos = 0; // Reinicamos... LogError("No se pudo concretar la acutlizacion luego de varios intentos, esperando al sigueinte turno", FUNCION_ACTUAL); EventSetTimer(m_segundos_timer); } else { // Esperamos los segundos de try EventSetTimer(m_segundos_to_retry); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CLlmRegistery::OnTimerEvent(void) { // Solo se llama cuando hayqeu volver a chekear if(!TraerEventos()) { Try(); // vemos.. return; } if(!WriteAllEvents()) // Acutlizamos la base de datos { Try(); // vemos.. return; } if(m_check_revised) // Por revisar.. ahora mismo usando timpeo custom { EventKillTimer(); LogInfo("Timer configurado denuevo con seg base, dadao que m_check_revised es true", FUNCION_ACTUAL); EventSetTimer(m_segundos_timer); //--- reinicio previo... m_check_revised = false; m_intentos = 0; } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CLlmRegistery::TraerEventos(void) { //--- uchar body[]; ::ResetLastError(); static string res_headers = ""; const int status = WebRequest( "GET", // metodo "https://models.dev/api.json", // endpoint "", // headers custom m_tiemout_ms_req, // timeout ms body, // body m_json_parser.m_raw, // respuesta res_headers // headers de respuesta ); //--- if(status == -1) { LogError("Posible WebRequest no habilitado, habilitado en config, para la url:\n'https://models.dev/api.json'", FUNCION_ACTUAL); return false; } //--- if(status != 200) { LogError(StringFormat("Status no es igual a 200 [es = %d], primeros 20 bytes del json: ", status), FUNCION_ACTUAL); Print(CharArrayToString(m_json_parser.m_raw, 0, 20, CP_UTF8)); return false; } return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CLlmRegistery::SaveExito() { //--- m_json_builder.Clear(); m_json_builder.Obj(); m_json_builder.KeyWV("last_date_revised").ValSWV(TimeToString(TimeLocal(), TIME_DATE | TIME_MINUTES | TIME_SECONDS)); m_json_builder.KeyWV("chart_id").Val(ChartID()); m_json_builder.EndObj(); //--- m_json_builder.Truncate(); //--- ::ResetLastError(); if(!FileSave(m_file_name_json, m_json_builder.m_buf, (m_common ? FILE_COMMON : 0))) { LogError(StringFormat("No se pudo gaurdar json en el archivo = %s, ultimo err = %d", m_file_name_json, ::GetLastError()), FUNCION_ACTUAL); } //--- Preparmeos req ::ResetLastError(); const int req = DatabasePrepare(m_sql_handle, "SELECT * FROM charts_id"); if(req == INVALID_HANDLE) { LogError(StringFormat("Fallo al prearar req: %d", ::GetLastError()), FUNCION_ACTUAL); return; } //--- Iteramos por toda la base y mandamos while(DatabaseRead(req)) { long chart_id; DatabaseColumnLong(req, 0, chart_id); ::ResetLastError(); if(!::EventChartCustom(chart_id, LLMREG_ON_UPDATE, 0, 0.0, "")) { LogError(StringFormat("Fallo al enviar evento a chart id = %I64d, last err = %d", chart_id, ::GetLastError()) , FUNCION_ACTUAL); } } // Finalizamos DatabaseFinalize(req); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CLlmRegistery::WriteAllEvents(void) { //--- m_json_parser.CalcLen(); m_json_parser.CorrectPadding(); const ulong a = GetMicrosecondCount(); if(!m_json_parser.Parse()) { LogError("Fallo al parsear json: ", FUNCION_ACTUAL); m_json_parser.ErrorInfo(); return false; } const ulong b = GetMicrosecondCount(); LogInfo(StringFormat("Json api.json parseado en = %I64u microsegundos", (b - a)), FUNCION_ACTUAL); //--- CJsonNode root = m_json_parser.GetRoot(); if(root.Size() < 1) { LogError("No hay modelos en el json", FUNCION_ACTUAL); return false; } //--- Iniciamos ::ResetLastError(); if(!DatabaseTransactionBegin(m_sql_handle)) { LogError(StringFormat("Fallo al inicar transacion datos al m_sql_handle: ", ::GetLastError()), FUNCION_ACTUAL); return false; } //--- ::ResetLastError(); const int stmt_org = DatabasePrepare(m_sql_handle, "INSERT OR REPLACE INTO llm_orgs (id, name, doc) VALUES(?,?,?)"); if(stmt_org == INVALID_HANDLE) { LogError(StringFormat("Fallo al ejeuctar un preprare de la base de datos, ultimo err = %d", ::GetLastError()), FUNCION_ACTUAL); return false; } const int stmt_models = DatabasePrepare(m_sql_handle, "INSERT OR REPLACE INTO llm_models " "(id, org_id, name, description, cost_in, cost_out, release_date, last_update_date, " "flags, in_modals, out_modals, context_window, context_output, context_input) " "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); if(stmt_models == INVALID_HANDLE) { LogError(StringFormat("Fallo al ejeuctar un preprare de la base de datos, ultimo err = %d", ::GetLastError()), FUNCION_ACTUAL); return false; } //--- CJsonIteratorObj it = root.BeginObj(); //--- Es valido while(it.IsValid()) { //--- CJsonNode val = it.Val(); const string org_id = val["id"].ToString(); DatabaseReset(stmt_org); DatabaseBind(stmt_org, 0, org_id); DatabaseBind(stmt_org, 1, val["name"].ToString()); DatabaseBind(stmt_org, 2, val["doc"].ToString()); DatabaseRead(stmt_org); //--- CJsonNode models = val["models"]; const int size = models.Size(); if(size > 0) { CJsonIteratorObj it_models = models.BeginObj(); while(it_models.IsValid()) { CJsonNode model = it_models.Val(); //--- DatabaseReset(stmt_models); //--- base DatabaseBind(stmt_models, 0, model["id"].ToString()); DatabaseBind(stmt_models, 1, org_id); DatabaseBind(stmt_models, 2, model["name"].ToString()); DatabaseBind(stmt_models, 3, model["description"].ToString()); DatabaseBind(stmt_models, 4, model["cost"]["input"].ToDouble(0.00)); DatabaseBind(stmt_models, 5, model["cost"]["output"].ToDouble(0.00)); DatabaseBind(stmt_models, 6, StringToTime(model["release_date"].ToString())); DatabaseBind(stmt_models, 7, StringToTime(model["last_update_date"].ToString())); //-- flags uint f = 0; f |= model["tool_call"].ToBool(false) ? LLMDEF_FLAG_TOOLS : 0; f |= model["reasoning"].ToBool(false) ? LLMDEF_FLAG_REASONING : 0; f |= model["temperature"].ToBool(false) ? LLMDEF_FLAG_TEMPERATURE : 0; f |= model["open_weights"].ToBool(false) ? LLMDEF_FLAG_OPEN_WEIGHTS : 0; f |= model["structured_output"].ToBool(false) ? LLMDEF_FLAG_STRUCTURED_OUTPUT : 0; DatabaseBind(stmt_models, 8, f); //--- complementiosn CJsonNode modalities = model["modalities"]; f = 0; if(modalities["input"].IsArray()) { CJsonIteratorArray it_in = modalities["input"].BeginArr(); while(it_in.IsValid()) { f |= CEnumRegLLmReg::GetValNoRef(it_in.Val().ToString(""), 0); it_in.Next(); } } DatabaseBind(stmt_models, 9, f); // in modlas //--- f = 0; if(modalities["output"].IsArray()) { CJsonIteratorArray it_out = modalities["output"].BeginArr(); while(it_out.IsValid()) { f |= CEnumRegLLmReg::GetValNoRef(it_out.Val().ToString(""), 0); it_out.Next(); } } DatabaseBind(stmt_models, 10, f); // out modlas //--- contexts CJsonNode limit = model["limit"]; DatabaseBind(stmt_models, 11, (int)limit["context"].ToInt(0)); DatabaseBind(stmt_models, 12, (int)limit["output"].ToInt(0)); DatabaseBind(stmt_models, 13, (int)limit["input"].ToInt(0)); DatabaseRead(stmt_models); //--- it_models.Next(); } } it.Next(); } //--- ::ResetLastError(); if(!DatabaseTransactionCommit(m_sql_handle)) { LogError(StringFormat("Fallo al enviar datos al m_sql_handle: ", ::GetLastError()), FUNCION_ACTUAL); return false; } DatabaseFinalize(stmt_models); DatabaseFinalize(stmt_org); //--- SaveExito(); return true; } //+------------------------------------------------------------------+ } //+------------------------------------------------------------------+ #endif // LLMREGISTERYBYLEO_SRC_MAIN_MQH