//+------------------------------------------------------------------+ //| MadTurtle_API.mqh - HTTP REST bridge to Python inference server | //+------------------------------------------------------------------+ #ifndef MADTURTLE_API #define MADTURTLE_API #import "wininet.dll" int InternetOpenW(string, int, string, string, int); int InternetOpenUrlW(int, string, string, int, int, int); int InternetReadFile(int, uchar &[], int, int &); int InternetCloseHandle(int); #import // Timeout and buffer constants #define MT5_API_TIMEOUT_MS 8000 #define MT5_API_BUFFER_SIZE 8192 // Server connection config struct MadTurtleServerCfg { string host; int port; string api_token; int timeout_ms; int retry_count; bool use_ssl; }; // API response container struct MadTurtleAPIResponse { int http_code; string body; bool success; string error_msg; }; //+------------------------------------------------------------------+ //| Initialize default server config | //+------------------------------------------------------------------+ MadTurtleServerCfg MadTurtle_InitDefaultServer() { MadTurtleServerCfg cfg; cfg.host = "127.0.0.1"; cfg.port = 8000; cfg.api_token = ""; cfg.timeout_ms = MT5_API_TIMEOUT_MS; cfg.retry_count = 2; cfg.use_ssl = false; return cfg; } //+------------------------------------------------------------------+ //| Build full URL from path | //+------------------------------------------------------------------+ string MadTurtle_BuildURL(const MadTurtleServerCfg &cfg, string path) { string proto = cfg.use_ssl ? "https" : "http"; return StringFormat("%s://%s:%d%s", proto, cfg.host, cfg.port, path); } //+------------------------------------------------------------------+ //| HTTP GET request | //+------------------------------------------------------------------+ MadTurtleAPIResponse MadTurtle_HTTPGet(const MadTurtleServerCfg &cfg, string path) { MadTurtleAPIResponse resp; resp.http_code = 0; resp.success = false; resp.body = ""; resp.error_msg = ""; int hInternet = InternetOpenW("MadTurtleEA/2.0", 1, NULL, NULL, 0); if(hInternet == 0) { resp.error_msg = "InternetOpenW failed"; return resp; } string url = MadTurtle_BuildURL(cfg, path); int hUrl = InternetOpenUrlW(hInternet, url, NULL, 0, 0x04000000, 0); if(hUrl == 0) { InternetCloseHandle(hInternet); resp.error_msg = "InternetOpenUrlW failed: " + IntegerToString(GetLastError()); return resp; } uchar buf[]; ArrayResize(buf, MT5_API_BUFFER_SIZE); int bytesRead = 0; string body = ""; while(true) { int res = InternetReadFile(hUrl, buf, MT5_API_BUFFER_SIZE, bytesRead); if(res == 0 || bytesRead == 0) break; body += CharArrayToString(buf, 0, bytesRead, CP_UTF8); } resp.body = body; resp.http_code = 200; resp.success = (StringLen(body) > 0); InternetCloseHandle(hUrl); InternetCloseHandle(hInternet); return resp; } //+------------------------------------------------------------------+ //| Call inference server with feature vector (GET) | //+------------------------------------------------------------------+ MadTurtleAPIResponse MadTurtle_GetSignal(const MadTurtleServerCfg &cfg, double &features[]) { MadTurtleAPIResponse resp; resp.http_code = 0; resp.success = false; resp.body = ""; resp.error_msg = ""; int n = ArraySize(features); if(n == 0) { resp.error_msg = "Empty feature vector"; return resp; } string qs = "?"; for(int i = 0; i < n; i++) { if(i > 0) qs += "&"; qs += StringFormat("f%d=%.8f", i, features[i]); } resp = MadTurtle_HTTPGet(cfg, "/v1/signal" + qs); for(int attempt = 1; attempt < cfg.retry_count && !resp.success; attempt++) { Sleep(500); resp = MadTurtle_HTTPGet(cfg, "/v1/signal" + qs); } return resp; } #endif // MADTURTLE_API