Files
opencode-skill/skills/mql-developer/references/external-communication.md
Kunthawat Greethong b26c8199a5 Update skills: add website-creator, mql-developer, ecommerce-astro
Changes:
- Add FAL_KEY and GEMINI_API_KEY to .env.example
- Update picture-it to use ~/.config/opencode/.env (unified creds)
- Remove shodh-memory skill (no longer used)
- Remove alphaear-* skills (deprecated)
- Remove thai-frontend-dev skill (replaced by website-creator)
- Remove theme-factory skill
- Add mql-developer skill (MQL5 trading)
- Add ecommerce-astro skill (Astro e-commerce)
- Add website-creator skill (Next.js + Payload CMS)
- Update install script for new skills
2026-04-16 17:40:27 +07:00

32 KiB

External & Internal Communication

Reference for WebRequest, REST API integration, JSON handling, Node.js patterns, and inter-program communication in MQL4/MQL5.

Table of Contents


WebRequest - REST API Communication

Setup Requirements

  • Whitelist URLs: Tools > Options > Expert Advisors > "Allow WebRequest for listed URL"
  • Only available in EAs and Scripts (NOT indicators)
  • Not available during backtesting in Strategy Tester
  • Synchronous/blocking: freezes EA execution until response received or timeout expires
  • Each call blocks the entire EA thread; keep timeouts reasonable (5000-10000 ms)

MQL4 WebRequest Signatures

MQL4 provides two overloaded variants:

// Variant 1: cookie/referer (simpler, limited)
int WebRequest(
    const string method,           // "GET", "POST", "PUT", "DELETE"
    const string url,              // Full URL
    const string cookie,           // Cookie string (can be "")
    const string referer,          // Referer header (can be "")
    int timeout,                   // Timeout in milliseconds
    const char &data[],            // Request body (char array)
    int data_size,                 // Size of data array
    char &result[],                // Response body (output)
    string &result_headers         // Response headers (output)
);

// Variant 2: custom headers (preferred for REST APIs)
int WebRequest(
    const string method,           // "GET", "POST", "PUT", "DELETE"
    const string url,              // Full URL
    const string headers,          // Custom headers, each ending with \r\n
    int timeout,                   // Timeout in milliseconds
    const char &data[],            // Request body (char array)
    char &result[],                // Response body (output)
    string &result_headers         // Response headers (output)
);

MQL4 GET Request Example

string HttpGet(string url, int timeout = 5000)
{
    char   data[];
    char   result[];
    string resultHeaders;
    string headers = "Content-Type: application/json\r\n";

    ResetLastError();
    int statusCode = WebRequest("GET", url, headers, timeout, data, result, resultHeaders);

    if(statusCode == -1)
    {
        int error = GetLastError();
        if(error == 4014)
            Print("ERROR: URL not allowed. Add to Tools > Options > Expert Advisors: ", url);
        else if(error == 4060)
            Print("ERROR: WebRequest not allowed in this context (indicator or tester)");
        else
            Print("ERROR: WebRequest failed. Error code: ", error);
        return "";
    }

    if(statusCode != 200)
    {
        Print("HTTP Error: ", statusCode, " Response: ", CharArrayToString(result));
        return "";
    }

    return CharArrayToString(result);
}

MQL4 POST Request Example

string HttpPost(string url, string jsonBody, int timeout = 5000)
{
    char   data[];
    char   result[];
    string resultHeaders;
    string headers = "Content-Type: application/json\r\n";

    // CRITICAL: Use StringLen() to avoid including the null terminator
    StringToCharArray(jsonBody, data, 0, StringLen(jsonBody));

    ResetLastError();
    int statusCode = WebRequest("POST", url, headers, timeout, data, result, resultHeaders);

    if(statusCode == -1)
    {
        int error = GetLastError();
        Print("ERROR: WebRequest POST failed. Error: ", error);
        return "";
    }

    if(statusCode != 200 && statusCode != 201)
    {
        Print("HTTP Error: ", statusCode, " Response: ", CharArrayToString(result));
        return "";
    }

    return CharArrayToString(result);
}

MQL5 WebRequest

MQL5 uses the same WebRequest() function with identical signatures. The key difference is encoding support:

// MQL5 POST with explicit UTF-8 encoding
string HttpPostMQL5(string url, string jsonBody, int timeout = 5000)
{
    char   data[];
    char   result[];
    string resultHeaders;
    string headers = "Content-Type: application/json\r\n";

    // Use CP_UTF8 for proper Unicode handling
    // CRITICAL: Use StringLen() to avoid null terminator in body
    StringToCharArray(jsonBody, data, 0, StringLen(jsonBody), CP_UTF8);

    ResetLastError();
    int statusCode = WebRequest("POST", url, headers, timeout, data, result, resultHeaders);

    if(statusCode == -1)
    {
        int error = GetLastError();
        PrintFormat("WebRequest failed: error %d", error);
        return "";
    }

    // Decode response with UTF-8
    string response = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);
    return response;
}

CHttpClient Class (MQL5)

Complete reusable HTTP client class for REST API communication:

//+------------------------------------------------------------------+
//| CHttpClient - Reusable HTTP client for REST APIs                  |
//+------------------------------------------------------------------+
class CHttpClient
{
private:
    string m_baseUrl;
    int    m_timeout;
    string m_authHeader;    // Optional auth header

    string DoRequest(string method, string endpoint, string body = "")
    {
        char   data[];
        char   result[];
        string resultHeaders;

        string url = m_baseUrl + endpoint;
        string headers = "Content-Type: application/json\r\n";
        if(m_authHeader != "")
            headers += m_authHeader + "\r\n";

        if(body != "")
        {
            // CRITICAL: Use StringLen() to avoid null terminator byte in request body
            StringToCharArray(body, data, 0, StringLen(body), CP_UTF8);
        }

        ResetLastError();
        int statusCode = WebRequest(method, url, headers, m_timeout, data, result, resultHeaders);

        if(statusCode == -1)
        {
            int error = GetLastError();
            if(error == 4014)
                PrintFormat("ERROR: URL not whitelisted: %s", url);
            else if(error == 4060)
                PrintFormat("ERROR: WebRequest not allowed in this context");
            else
                PrintFormat("ERROR: WebRequest failed, error: %d", error);
            return "";
        }

        string response = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);

        if(statusCode < 200 || statusCode >= 300)
        {
            PrintFormat("HTTP %d: %s %s -> %s", statusCode, method, endpoint, response);
            return "";
        }

        return response;
    }

public:
    void Init(string baseUrl, int timeoutMs = 5000)
    {
        m_baseUrl = baseUrl;
        m_timeout = timeoutMs;
        m_authHeader = "";
    }

    void SetAuth(string token)
    {
        m_authHeader = "Authorization: Bearer " + token;
    }

    string Get(string endpoint)
    {
        return DoRequest("GET", endpoint);
    }

    string Post(string endpoint, string jsonBody)
    {
        return DoRequest("POST", endpoint, jsonBody);
    }

    string Put(string endpoint, string jsonBody)
    {
        return DoRequest("PUT", endpoint, jsonBody);
    }

    string Delete(string endpoint)
    {
        return DoRequest("DELETE", endpoint);
    }
};

Usage:

CHttpClient httpClient;

int OnInit()
{
    httpClient.Init("https://api.example.com", 5000);
    httpClient.SetAuth(InpApiToken);
    return INIT_SUCCEEDED;
}

void OnTick()
{
    string response = httpClient.Get("/api/signals?symbol=" + Symbol());
    if(response != "")
    {
        // Process response
    }
}

JSON Handling

MQL has no native JSON parser. For simple payloads, manual string building and parsing works well. For complex nested structures, consider the CJAVal library from MQL5 CodeBase.

Building JSON Manually

string BuildTradeJSON(ulong ticket, string symbol, string action,
                      double lots, double price, double sl, double tp)
{
    string json = "{";
    json += "\"ticket\":" + IntegerToString(ticket) + ",";
    json += "\"symbol\":\"" + symbol + "\",";
    json += "\"action\":\"" + action + "\",";
    json += "\"lots\":" + DoubleToString(lots, 2) + ",";
    json += "\"price\":" + DoubleToString(price, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)) + ",";
    json += "\"sl\":" + DoubleToString(sl, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)) + ",";
    json += "\"tp\":" + DoubleToString(tp, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)) + ",";
    json += "\"account\":" + IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN)) + ",";
    json += "\"broker\":\"" + AccountInfoString(ACCOUNT_COMPANY) + "\",";
    json += "\"balance\":" + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + ",";
    json += "\"equity\":" + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + ",";
    json += "\"timestamp\":\"" + TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS) + "\"";
    json += "}";
    return json;
}

MQL4 variant (uses AccountBalance(), AccountCompany(), etc. instead of AccountInfoXxx()):

string BuildTradeJSON_MQL4(int ticket, string symbol, string action,
                           double lots, double price, double sl, double tp)
{
    string json = "{";
    json += "\"ticket\":" + IntegerToString(ticket) + ",";
    json += "\"symbol\":\"" + symbol + "\",";
    json += "\"action\":\"" + action + "\",";
    json += "\"lots\":" + DoubleToString(lots, 2) + ",";
    json += "\"price\":" + DoubleToString(price, Digits) + ",";
    json += "\"sl\":" + DoubleToString(sl, Digits) + ",";
    json += "\"tp\":" + DoubleToString(tp, Digits) + ",";
    json += "\"account\":" + IntegerToString(AccountNumber()) + ",";
    json += "\"broker\":\"" + AccountCompany() + "\",";
    json += "\"balance\":" + DoubleToString(AccountBalance(), 2) + ",";
    json += "\"equity\":" + DoubleToString(AccountEquity(), 2) + ",";
    json += "\"timestamp\":\"" + TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS) + "\"";
    json += "}";
    return json;
}

Parsing JSON Manually

Helper functions for extracting values from flat JSON strings:

//+------------------------------------------------------------------+
//| Extract string value for a given key from JSON                    |
//+------------------------------------------------------------------+
string JsonGetString(const string json, const string key)
{
    string searchKey = "\"" + key + "\"";
    int keyPos = StringFind(json, searchKey);
    if(keyPos == -1) return "";

    // Find the colon after the key
    int colonPos = StringFind(json, ":", keyPos + StringLen(searchKey));
    if(colonPos == -1) return "";

    // Find opening quote of value
    int startQuote = StringFind(json, "\"", colonPos + 1);
    if(startQuote == -1) return "";

    // Find closing quote of value
    int endQuote = StringFind(json, "\"", startQuote + 1);
    if(endQuote == -1) return "";

    return StringSubstr(json, startQuote + 1, endQuote - startQuote - 1);
}

//+------------------------------------------------------------------+
//| Extract double value for a given key from JSON                    |
//+------------------------------------------------------------------+
double JsonGetDouble(const string json, const string key)
{
    string searchKey = "\"" + key + "\"";
    int keyPos = StringFind(json, searchKey);
    if(keyPos == -1) return 0.0;

    int colonPos = StringFind(json, ":", keyPos + StringLen(searchKey));
    if(colonPos == -1) return 0.0;

    // Skip whitespace after colon
    int valueStart = colonPos + 1;
    while(valueStart < StringLen(json) &&
          (StringGetCharacter(json, valueStart) == ' ' ||
           StringGetCharacter(json, valueStart) == '\t'))
        valueStart++;

    // Find end of number (comma, closing brace, closing bracket, or end of string)
    int valueEnd = valueStart;
    while(valueEnd < StringLen(json))
    {
        ushort ch = StringGetCharacter(json, valueEnd);
        if(ch == ',' || ch == '}' || ch == ']' || ch == ' ' || ch == '\n' || ch == '\r')
            break;
        valueEnd++;
    }

    string valueStr = StringSubstr(json, valueStart, valueEnd - valueStart);
    return StringToDouble(valueStr);
}

//+------------------------------------------------------------------+
//| Extract boolean value for a given key from JSON                   |
//+------------------------------------------------------------------+
bool JsonGetBool(const string json, const string key)
{
    string searchKey = "\"" + key + "\"";
    int keyPos = StringFind(json, searchKey);
    if(keyPos == -1) return false;

    int colonPos = StringFind(json, ":", keyPos + StringLen(searchKey));
    if(colonPos == -1) return false;

    // Check if "true" appears after colon (before next comma/brace)
    int truePos = StringFind(json, "true", colonPos);
    int commaPos = StringFind(json, ",", colonPos);
    int bracePos = StringFind(json, "}", colonPos);

    if(truePos == -1) return false;
    if(commaPos != -1 && truePos > commaPos) return false;
    if(bracePos != -1 && truePos > bracePos) return false;

    return true;
}

//+------------------------------------------------------------------+
//| Extract integer value for a given key from JSON                   |
//+------------------------------------------------------------------+
int JsonGetInt(const string json, const string key)
{
    return (int)JsonGetDouble(json, key);
}

//+------------------------------------------------------------------+
//| Extract long value for a given key from JSON                      |
//+------------------------------------------------------------------+
long JsonGetLong(const string json, const string key)
{
    string searchKey = "\"" + key + "\"";
    int keyPos = StringFind(json, searchKey);
    if(keyPos == -1) return 0;

    int colonPos = StringFind(json, ":", keyPos + StringLen(searchKey));
    if(colonPos == -1) return 0;

    int valueStart = colonPos + 1;
    while(valueStart < StringLen(json) &&
          (StringGetCharacter(json, valueStart) == ' ' ||
           StringGetCharacter(json, valueStart) == '\t'))
        valueStart++;

    int valueEnd = valueStart;
    while(valueEnd < StringLen(json))
    {
        ushort ch = StringGetCharacter(json, valueEnd);
        if(ch == ',' || ch == '}' || ch == ']' || ch == ' ')
            break;
        valueEnd++;
    }

    string valueStr = StringSubstr(json, valueStart, valueEnd - valueStart);
    return StringToInteger(valueStr);
}

Note: These helpers work for flat (non-nested) JSON. For production use with nested objects or arrays, consider the CJAVal library from MQL5 CodeBase, which provides proper recursive JSON parsing with object/array traversal.


Node.js Integration Patterns

EA -> Node.js: Send Trade Data

Report executed trades to a Node.js backend:

CHttpClient httpClient;

void NotifyServer(string action, string symbol, double lots, double price, ulong ticket)
{
    string json = BuildTradeJSON(ticket, symbol, action, lots, price, 0, 0);
    string response = httpClient.Post("/api/trades", json);
    if(response == "")
        PrintFormat("WARNING: Failed to notify server about %s %s", action, symbol);
}

// Call after trade execution:
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
{
    if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
    {
        // New deal executed
        NotifyServer("BUY", trans.symbol, trans.volume, trans.price, trans.deal);
    }
}

Node.js -> EA: Receive Commands (Polling)

Timer-based polling pattern to receive trading signals from a Node.js server:

CHttpClient httpClient;

int OnInit()
{
    httpClient.Init("https://api.example.com", 5000);
    httpClient.SetAuth(InpApiToken);
    EventSetTimer(5);  // Poll every 5 seconds
    return INIT_SUCCEEDED;
}

void OnTimer()
{
    string endpoint = "/api/signals?account=" +
                      IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN)) +
                      "&symbol=" + Symbol();
    string response = httpClient.Get(endpoint);
    if(response == "") return;

    string action = JsonGetString(response, "action");
    if(action == "") return;

    double lots   = JsonGetDouble(response, "lots");
    double sl     = JsonGetDouble(response, "sl");
    double tp     = JsonGetDouble(response, "tp");
    string symbol = JsonGetString(response, "symbol");
    if(symbol == "") symbol = Symbol();

    if(action == "BUY")
        ExecuteBuy(symbol, lots, sl, tp);
    else if(action == "SELL")
        ExecuteSell(symbol, lots, sl, tp);
    else if(action == "CLOSE")
        ClosePosition(symbol);
}

void OnDeinit(const int reason)
{
    EventKillTimer();
}

Send Account Status Updates

Periodically report account state to the server:

void SendAccountStatus()
{
    string json = "{";
    json += "\"account\":" + IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN)) + ",";
    json += "\"balance\":" + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + ",";
    json += "\"equity\":" + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + ",";
    json += "\"margin\":" + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN), 2) + ",";
    json += "\"freeMargin\":" + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_FREE), 2) + ",";
    json += "\"openPositions\":" + IntegerToString(PositionsTotal()) + ",";
    json += "\"server\":\"" + AccountInfoString(ACCOUNT_SERVER) + "\",";
    json += "\"timestamp\":\"" + TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS) + "\"";
    json += "}";

    httpClient.Post("/api/status", json);
}

Common API Endpoints Pattern

Method Endpoint Purpose
POST /api/trades Report executed trades
GET /api/signals Poll for trading signals
POST /api/status Send account status (balance, equity, positions)
GET /api/config Fetch EA configuration parameters
POST /api/errors Report errors and alerts
POST /api/logs Send EA log entries

Authentication

Include an authentication token in every request via headers:

input string InpApiToken = "";  // API Bearer Token

// In CHttpClient or raw WebRequest:
string headers = "Content-Type: application/json\r\n"
               + "Authorization: Bearer " + InpApiToken + "\r\n";

For the CHttpClient class, use httpClient.SetAuth(InpApiToken) after Init().


Network Error Handling

Retry Pattern with Progressive Backoff

string RequestWithRetry(string method, string url, string headers,
                        string body, int maxRetries = 3)
{
    char   data[];
    char   result[];
    string resultHeaders;

    if(body != "")
        StringToCharArray(body, data, 0, StringLen(body), CP_UTF8);

    for(int attempt = 0; attempt < maxRetries; attempt++)
    {
        if(attempt > 0)
        {
            int delayMs = 1000 * (attempt + 1);  // 2s, 3s progressive backoff
            PrintFormat("Retry %d/%d after %d ms delay...", attempt + 1, maxRetries, delayMs);
            Sleep(delayMs);
        }

        ResetLastError();
        int statusCode = WebRequest(method, url, headers, 5000, data, result, resultHeaders);

        if(statusCode == -1)
        {
            int error = GetLastError();
            PrintFormat("WebRequest attempt %d failed: error %d", attempt + 1, error);
            // Don't retry non-recoverable errors
            if(error == 4014 || error == 4060) return "";
            continue;
        }

        if(statusCode >= 500)
        {
            PrintFormat("Server error %d, attempt %d", statusCode, attempt + 1);
            continue;  // Retry on server errors
        }

        // Return response for any non-server-error status
        return CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);
    }

    PrintFormat("All %d attempts failed for %s %s", maxRetries, method, url);
    return "";
}

Common Error Codes

Status Code GetLastError() Meaning
-1 4014 URL not in allowed list (whitelist in terminal settings)
-1 4060 Function not allowed (called from indicator or Strategy Tester)
-1 5203 No connection to server / network error
-1 5200-5299 Various network/internet errors
HTTP 0 - Timeout / no response from server
HTTP 400 - Bad request (check JSON format)
HTTP 401 - Authentication failed (check token)
HTTP 403 - Forbidden (check permissions)
HTTP 404 - Endpoint not found (check URL)
HTTP 429 - Rate limited (add delays between requests)
HTTP 500 - Internal server error (server-side issue)
HTTP 502/503 - Server unavailable (retry later)

Error Reporting to Server

void ReportErrorToServer(string source, int errorCode, string details)
{
    string json = "{";
    json += "\"source\":\"" + source + "\",";
    json += "\"errorCode\":" + IntegerToString(errorCode) + ",";
    json += "\"details\":\"" + details + "\",";
    json += "\"account\":" + IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN)) + ",";
    json += "\"timestamp\":\"" + TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS) + "\"";
    json += "}";

    // Don't retry error reporting itself to avoid infinite loops
    httpClient.Post("/api/errors", json);
}

Internal Communication (Between MQL Programs)

Global Variables of the Terminal

Global variables are shared across all MQL programs running in a single terminal instance. They store double values and persist across program restarts (saved to disk by the terminal).

// Write a value
GlobalVariableSet("EA_Signal_EURUSD", 1.0);

// Read a value
double signal = GlobalVariableGet("EA_Signal_EURUSD");

// Check existence
bool exists = GlobalVariableCheck("EA_Signal_EURUSD");

// Delete
GlobalVariableDel("EA_Signal_EURUSD");

// Create temporary (auto-deleted when terminal closes, not saved to disk)
GlobalVariableTemp("EA_Temp_Signal");

// Set only if doesn't exist (atomic check-and-set, useful for locking)
bool wasCreated = GlobalVariableSetOnCondition("EA_Lock", 1.0, 0.0);

// Get value and set time simultaneously
datetime lastAccess;
double value = GlobalVariableGet("EA_Signal", lastAccess);

// List and iterate all global variables
int total = GlobalVariablesTotal();
for(int i = 0; i < total; i++)
{
    string name = GlobalVariableName(i);
    double val = GlobalVariableGet(name);
    PrintFormat("GVar: %s = %f", name, val);
}

Constraints:

  • Name maximum: 63 characters
  • Value type: always double (encode other types as double or use naming conventions)
  • Shared across all programs in the same terminal
  • Persisted to disk on terminal shutdown (except GlobalVariableTemp)

Use cases:

  • Signal passing between EAs and indicators on different charts
  • Simple locking mechanism with GlobalVariableSetOnCondition()
  • State persistence across EA restarts
  • Coordination between multiple EAs (e.g., portfolio-level risk)

Custom Events (MQL5 Only)

Custom events allow one MQL5 program to send events to a chart's OnChartEvent() handler:

// --- Define custom event IDs ---
#define EVENT_SIGNAL_BUY    (CHARTEVENT_CUSTOM + 1)
#define EVENT_SIGNAL_SELL   (CHARTEVENT_CUSTOM + 2)
#define EVENT_UPDATE_PANEL  (CHARTEVENT_CUSTOM + 3)
#define EVENT_CLOSE_ALL     (CHARTEVENT_CUSTOM + 4)

// --- Sender (indicator, script, or another EA on the same chart): ---
// Send to current chart
EventChartCustom(ChartID(), EVENT_SIGNAL_BUY, 0, 1.23456, "EURUSD");

// Send to a specific chart by chart ID
long targetChart = ChartFirst();
EventChartCustom(targetChart, EVENT_SIGNAL_BUY, 12345, 1.5, "EURUSD");

// --- Receiver (EA with OnChartEvent handler): ---
void OnChartEvent(const int id, const long &lparam,
                  const double &dparam, const string &sparam)
{
    if(id == EVENT_SIGNAL_BUY)
    {
        long   ticket = lparam;    // Custom long parameter
        double price  = dparam;    // Custom double parameter
        string symbol = sparam;    // Custom string parameter
        PrintFormat("BUY signal received: %s at %f", symbol, price);
    }
    else if(id == EVENT_SIGNAL_SELL)
    {
        PrintFormat("SELL signal received: %s at %f", sparam, dparam);
    }
    else if(id == EVENT_CLOSE_ALL)
    {
        // Close all positions
    }
}

Parameters per event:

  • lparam (long): one integer/long value
  • dparam (double): one double value
  • sparam (string): one string value (can encode JSON for more data)

Constraints:

  • Can only send to charts in the same terminal
  • The receiving chart must have an EA or indicator with OnChartEvent()
  • Custom event IDs range: CHARTEVENT_CUSTOM to CHARTEVENT_CUSTOM + 65535

File-Based Communication

Programs can communicate by reading and writing files in the terminal data folder (MQL4/Files or MQL5/Files):

// --- Writer (EA or Script) ---
void WriteSignalFile(string signal, string symbol, double price)
{
    string filename = "signals_" + symbol + ".csv";
    int handle = FileOpen(filename, FILE_WRITE | FILE_CSV | FILE_ANSI, ',');
    if(handle == INVALID_HANDLE)
    {
        PrintFormat("Failed to open file: %s, error: %d", filename, GetLastError());
        return;
    }
    FileWrite(handle, signal, symbol, DoubleToString(price, 5),
              TimeToString(TimeCurrent()));
    FileClose(handle);
}

// --- Reader (another EA or Indicator) ---
string ReadSignalFile(string symbol)
{
    string filename = "signals_" + symbol + ".csv";
    if(!FileIsExist(filename)) return "";

    int handle = FileOpen(filename, FILE_READ | FILE_CSV | FILE_ANSI, ',');
    if(handle == INVALID_HANDLE) return "";

    string signal = FileReadString(handle);
    FileClose(handle);

    // Delete after reading (one-time signal)
    FileDelete(filename);
    return signal;
}

Cross-terminal file sharing using FILE_COMMON flag:

// Write to common folder (shared across all terminal instances)
int handle = FileOpen("shared_signal.txt", FILE_WRITE | FILE_TXT | FILE_COMMON);
FileWriteString(handle, "BUY EURUSD 1.12345");
FileClose(handle);

// Read from common folder in another terminal
int handle2 = FileOpen("shared_signal.txt", FILE_READ | FILE_TXT | FILE_COMMON);
string data = FileReadString(handle2);
FileClose(handle2);

File locking pattern (prevent concurrent read/write corruption):

bool AcquireFileLock(string lockName, int timeoutMs = 5000)
{
    string lockFile = lockName + ".lock";
    datetime start = TimeLocal();
    while(FileIsExist(lockFile))
    {
        if((TimeLocal() - start) * 1000 > timeoutMs)
            return false;  // Timeout
        Sleep(50);
    }
    // Create lock file
    int h = FileOpen(lockFile, FILE_WRITE | FILE_TXT);
    if(h == INVALID_HANDLE) return false;
    FileClose(h);
    return true;
}

void ReleaseFileLock(string lockName)
{
    FileDelete(lockName + ".lock");
}

Named Pipes (MQL4, Windows Only)

Named pipes provide fast IPC for Windows-based communication (e.g., between MetaTrader and a C#/Python application):

#import "kernel32.dll"
int CreateFileW(string name, uint access, uint share, int security,
                uint creation, uint flags, int template);
int WriteFile(int handle, const uchar &buffer[], int bytes,
              int &written[], int overlapped);
int ReadFile(int handle, uchar &buffer[], int bytes,
             int &read[], int overlapped);
int CloseHandle(int handle);
int FlushFileBuffers(int handle);
#import

#define GENERIC_READ       0x80000000
#define GENERIC_WRITE      0x40000000
#define OPEN_EXISTING      3
#define INVALID_HANDLE_VALUE -1

// Connect to a named pipe server
int ConnectToPipe(string pipeName)
{
    string fullName = "\\\\.\\pipe\\" + pipeName;
    int pipe = CreateFileW(fullName,
                           GENERIC_READ | GENERIC_WRITE,
                           0, 0, OPEN_EXISTING, 0, 0);
    if(pipe == INVALID_HANDLE_VALUE)
    {
        Print("Failed to connect to pipe: ", pipeName);
        return INVALID_HANDLE_VALUE;
    }
    return pipe;
}

// Send message through pipe
bool SendPipeMessage(int pipe, string message)
{
    uchar data[];
    StringToCharArray(message, data);
    int written[];
    ArrayResize(written, 1);
    return WriteFile(pipe, data, ArraySize(data), written, 0) != 0;
}

// Read message from pipe
string ReadPipeMessage(int pipe, int bufferSize = 4096)
{
    uchar buffer[];
    ArrayResize(buffer, bufferSize);
    int bytesRead[];
    ArrayResize(bytesRead, 1);
    if(ReadFile(pipe, buffer, bufferSize, bytesRead, 0))
        return CharArrayToString(buffer, 0, bytesRead[0]);
    return "";
}

// Clean up
void ClosePipe(int pipe)
{
    FlushFileBuffers(pipe);
    CloseHandle(pipe);
}

MQL5 Sockets (Advanced)

MQL5 provides built-in TCP socket support for real-time bidirectional communication. This is more efficient than WebRequest polling for scenarios requiring low-latency or persistent connections.

Plain TCP Socket

int g_socket = INVALID_HANDLE;

bool SocketConnectToServer(string host, int port, int timeoutMs = 5000)
{
    g_socket = SocketCreate();
    if(g_socket == INVALID_HANDLE)
    {
        PrintFormat("SocketCreate failed: %d", GetLastError());
        return false;
    }

    if(!SocketConnect(g_socket, host, port, timeoutMs))
    {
        PrintFormat("SocketConnect failed: %d", GetLastError());
        SocketClose(g_socket);
        g_socket = INVALID_HANDLE;
        return false;
    }

    PrintFormat("Connected to %s:%d", host, port);
    return true;
}

bool SocketSendMessage(string message)
{
    if(g_socket == INVALID_HANDLE) return false;

    uchar data[];
    int len = StringToCharArray(message, data, 0, StringLen(message), CP_UTF8);

    int sent = SocketSend(g_socket, data, len);
    if(sent == -1)
    {
        PrintFormat("SocketSend failed: %d", GetLastError());
        return false;
    }
    return true;
}

string SocketReceiveMessage(int timeoutMs = 1000)
{
    if(g_socket == INVALID_HANDLE) return "";

    uchar response[];
    int received = SocketRead(g_socket, response, 4096, timeoutMs);
    if(received <= 0) return "";

    return CharArrayToString(response, 0, received, CP_UTF8);
}

void SocketDisconnect()
{
    if(g_socket != INVALID_HANDLE)
    {
        SocketClose(g_socket);
        g_socket = INVALID_HANDLE;
    }
}

TLS/SSL Encrypted Socket

For secure communication (HTTPS servers, encrypted APIs):

bool SocketConnectTLS(string host, int port, int timeoutMs = 5000)
{
    g_socket = SocketCreate();
    if(g_socket == INVALID_HANDLE) return false;

    if(!SocketConnect(g_socket, host, port, timeoutMs))
    {
        SocketClose(g_socket);
        g_socket = INVALID_HANDLE;
        return false;
    }

    // Perform TLS handshake
    if(!SocketTlsHandshake(g_socket, host))
    {
        PrintFormat("TLS handshake failed: %d", GetLastError());
        SocketClose(g_socket);
        g_socket = INVALID_HANDLE;
        return false;
    }

    return true;
}

// For TLS, use SocketTlsSend / SocketTlsRead instead:
bool SocketSendTLS(string message)
{
    if(g_socket == INVALID_HANDLE) return false;

    uchar data[];
    int len = StringToCharArray(message, data, 0, StringLen(message), CP_UTF8);
    int sent = SocketTlsSend(g_socket, data, len);
    return (sent > 0);
}

string SocketReceiveTLS(int timeoutMs = 1000)
{
    if(g_socket == INVALID_HANDLE) return "";

    uchar response[];
    int received = SocketTlsRead(g_socket, response, 4096, timeoutMs);
    if(received <= 0) return "";

    return CharArrayToString(response, 0, received, CP_UTF8);
}

Socket Constraints

  • Only available in EAs and Scripts (NOT indicators)
  • Maximum 128 sockets per program
  • Server URLs/IPs must be whitelisted in terminal settings (same as WebRequest)
  • Non-blocking reads with timeout; blocking sends
  • MQL5 does not support acting as a socket server (client only)
  • For WebSocket protocol, you must implement the handshake and framing manually or use a library

Communication Method Comparison

Method Direction Latency Complexity MQL4 MQL5 Notes
WebRequest EA -> Server High (blocking) Low Yes Yes Simple REST, synchronous
Sockets Bidirectional Low Medium No Yes Persistent connection
Global Variables Internal Very low Very low Yes Yes Double values only
Custom Events Internal Very low Low No Yes Same terminal only
Files Both Medium Low Yes Yes FILE_COMMON for cross-terminal
Named Pipes Bidirectional Low High Yes (DLL) Yes (DLL) Windows only, requires DLL import