Files

139 lines
4.0 KiB
Plaintext

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