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

1058 lines
32 KiB
Markdown

# 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](#webrequest---rest-api-communication)
- [JSON Handling](#json-handling)
- [Node.js Integration Patterns](#nodejs-integration-patterns)
- [Network Error Handling](#network-error-handling)
- [Internal Communication (Between MQL Programs)](#internal-communication-between-mql-programs)
- [MQL5 Sockets (Advanced)](#mql5-sockets-advanced)
- [Communication Method Comparison](#communication-method-comparison)
---
## 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:
```mql4
// 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
```mql4
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
```mql4
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
// 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:
```mql5
//+------------------------------------------------------------------+
//| 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:**
```mql5
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
```mql5
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()`):
```mql4
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:
```mql5
//+------------------------------------------------------------------+
//| 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:
```mql5
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:
```mql5
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:
```mql5
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:
```mql5
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
```mql5
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
```mql5
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).
```mql5
// 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:
```mql5
// --- 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`):
```mql5
// --- 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:
```mql5
// 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):
```mql5
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):
```mql4
#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
```mql5
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):
```mql5
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 |