LLmRegiteryByLeo/Src/Main.mqh
Nique_372 ead91b6b69
2026-07-21 07:47:28 -05:00

480 lines
16 KiB
MQL5

//+------------------------------------------------------------------+
//| 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<uint>(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<uint>(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