
Mql Developer
- 304 installs
- 35 repo stars
- Updated February 8, 2026
- thomaspraun/mql-developer
Helps with ai & agent building tasks.
About
mql-developer is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- mql-developer
- AI & Agent Building
- AI-coding skill
Mql Developer by the numbers
- 304 all-time installs (skills.sh)
- +17 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #2,279 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thomaspraun/mql-developer --skill mql-developerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 304 |
|---|---|
| repo stars | ★ 35 |
| Last updated | February 8, 2026 |
| Repository | thomaspraun/mql-developer ↗ |
What it does
Helps with ai & agent building tasks.
Files
MQL Developer
Guide for professional MQL4/MQL5 development on MetaTrader platforms.
Quick Reference Navigation
Load the appropriate reference file based on the task:
| Task | Reference File |
|---|---|
| MQL4 syntax, types, functions, predefined vars | references/mql4-reference.md |
| MQL5 syntax, OOP, CTrade, Standard Library | references/mql5-reference.md |
| Project structure, EA architecture, design patterns | references/architecture-patterns.md |
| Orders, positions, risk management, trailing stops | references/trading-operations.md |
| Custom indicators, UI panels, scripts, chart objects | references/indicators-and-ui.md |
| WebRequest, JSON, REST API, Node.js integration | references/external-communication.md |
| Strategy Tester, optimization, walk-forward, Monte Carlo | references/backtesting.md |
| Code protection, licensing, anti-decompilation | references/security-licensing.md |
Search Patterns for Large References
For targeted lookup in large files, grep for these section headers:
mql4-reference.md: Data Types, Variables, Operators, Arrays, Strings, Program Types, Predefined Variables, Technical Indicator Functions, Order Management, Market Information, Account Functions, Preprocessor, Error Handling, Common Gotchas, File Operations, WebRequest, Utility Functions, Global Terminal Variables
architecture-patterns.md: Project Structure, Simple Single-File, Modular EA, State Machine, Multi-Timeframe, Multi-Symbol, Singleton, Strategy Pattern, Observer, Include File Design, Complete Templates
mql5-reference.md: OOP Features, Trade Functions, CTrade, Native Trade, Event Handlers, Standard Library, Key Enumerations, SQLite, Sockets, Resources, OpenCL
MQL4 vs MQL5 Key Differences
| Aspect | MQL4 | MQL5 |
|---|---|---|
| Paradigm | Procedural (C-like) | Full OOP (C++-like) |
| Trade model | Orders only (OrderSend) | Orders + Deals + Positions (CTrade) |
| Account model | Hedging only | Netting + Hedging |
| Indicator buffers | Max 8 | Max 512 |
| Draw styles | 6 basic | 18 (basic + color) |
| Standard Library | Minimal | Comprehensive |
| Database | None | SQLite built-in |
| Sockets | None | TCP + TLS |
| OpenCL | No | Yes |
Core Workflow
Creating an Expert Advisor
1. Define strategy signal logic (entry/exit conditions) 2. Choose architecture: simple (single-file) or modular (Signal + Trade + Risk + Filter) 3. Implement order/position management with proper error handling and retries 4. Add risk management (position sizing, drawdown control) 5. Add filters (time, spread, volatility) 6. Backtest with Strategy Tester (Open Prices first, then Every Tick) 7. Walk-forward validate and Monte Carlo test
Creating a Custom Indicator
1. Choose window: indicator_chart_window or indicator_separate_window 2. Define buffers and plots (indicator_buffers, indicator_plots in MQL5) 3. Implement OnCalculate() with efficient recalculation using prev_calculated 4. Set draw styles, colors, labels 5. Handle multi-timeframe data if needed
Communicating with External APIs
1. Whitelist URL in Tools > Options > Expert Advisors 2. Use WebRequest() for REST calls (POST/GET) 3. Build JSON manually (MQL has no native JSON) 4. Parse response with string functions 5. Use EventSetTimer() for polling patterns 6. Handle network errors with retries
Critical Gotchas
- Double comparison: Never use
==with doubles. UseNormalizeDouble()or tolerance - 4-digit vs 5-digit brokers: 1 pip = 1 point (4-digit) or 10 points (5-digit). Always detect
- Reverse loop for closing: Iterate
OrdersTotal()-1down to0when closing orders (MQL4) - ECN brokers: Some require two-step:
OrderSend()without SL/TP, thenOrderModify() - Filling policy (MQL5): Always detect via
SYMBOL_FILLING_MODE, never hardcode FOK - WebRequest limitations: Synchronous/blocking, not available in indicators or Strategy Tester
- Trade context busy (MQL4): Only one EA can trade at a time per terminal
- Array indexing: Series arrays index 0 = newest bar. Use
ArraySetAsSeries()to control
Project Structure (Recommended)
MQL5/ (or MQL4/)
├── Experts/
│ └── MyEA/
│ └── MyEA.mq5 // EA entry point
├── Indicators/
│ └── MyIndicator.mq5
├── Scripts/
│ └── MyScript.mq5
├── Include/
│ ├── Core/
│ │ ├── CTradeManager.mqh // Order execution + retries
│ │ ├── CRiskManager.mqh // Position sizing + drawdown
│ │ └── CSignalBase.mqh // Signal interface
│ ├── Communication/
│ │ ├── CHttpClient.mqh // WebRequest wrapper
│ │ └── CJsonHelper.mqh // JSON build/parse
│ ├── UI/
│ │ └── CPanel.mqh // Trading panel
│ └── Utils/
│ ├── CTimeFilter.mqh // Session/time filters
│ └── CSymbolHelper.mqh // Multi-market helpers
└── Libraries/For simpler projects, a single-file EA with inline functions is acceptable.
Code Style Conventions
- Prefix member variables with
m_(e.g.,m_magicNumber) - Prefix global variables with
g_(e.g.,g_isInitialized) - Use
inputfor user parameters, notextern - Always use
#property strictin MQL4 - Normalize all prices before sending to server:
NormalizeDouble(price, Digits) - Always check return values of
OrderSelect(),OrderSend(), trade operations - Comment magic numbers and explain non-obvious trading logic
Official Documentation
- MQL4: https://docs.mql4.com/
- MQL5: https://www.mql5.com/en/docs
- MQL5 Articles: https://www.mql5.com/en/articles
- MQL5 Code Base: https://www.mql5.com/en/code
MIT License
Copyright (c) 2026 Thomas Praun
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
mql-developer
Claude Code skill for professional MQL4/MQL5 development on MetaTrader 4 and MetaTrader 5 platforms.
Covers the full ecosystem: Expert Advisors, custom indicators, scripts, libraries, UI panels, trading operations, external API communication, backtesting, and code protection.
What It Provides
- MQL4 & MQL5 language reference — data types, functions, predefined variables, preprocessor, error handling
- OOP & Standard Library (MQL5) — classes, interfaces, templates, CTrade, CPositionInfo, CCanvas
- EA architecture patterns — single-file, modular (Signal + Trade + Risk + Filter), state machine, multi-timeframe, multi-symbol
- Trading operations — order/position management with retry logic, risk-based position sizing, drawdown control, trailing stops
- Indicators & UI — custom indicators (buffers, draw styles, OnCalculate), graphical objects, CAppDialog panels, scripts
- External communication — WebRequest REST API, JSON handling, Node.js server integration, sockets, inter-program communication
- Backtesting — Strategy Tester modes, walk-forward analysis, Monte Carlo simulation, optimization
- Security & licensing — account-based licensing, server-side validation, anti-decompilation, MQL5 Cloud Protector
Skill Structure
mql-developer/
├── SKILL.md # Entry point with navigation table
└── references/
├── mql4-reference.md # MQL4 language reference
├── mql5-reference.md # MQL5 OOP, CTrade, Standard Library
├── architecture-patterns.md # EA architectures and design patterns
├── trading-operations.md # Orders, risk management, trailing stops
├── indicators-and-ui.md # Indicators, panels, scripts, chart objects
├── external-communication.md # WebRequest, JSON, Node.js, sockets
├── backtesting.md # Strategy Tester, optimization, Monte Carlo
└── security-licensing.md # Code protection and licensingUses progressive disclosure: SKILL.md loads first (~140 lines), then only the relevant reference file is loaded based on the task.
Installation
Copy the skill to your Claude Code skills directory:
# Option 1: Clone and copy
git clone https://github.com/YOUR_USERNAME/mql-developer.git
cp -r mql-developer ~/.claude/skills/
# Option 2: Direct copy (if already cloned)
cp -r mql-developer ~/.claude/skills/The skill activates automatically when Claude detects MQL-related tasks.
Usage Examples
You: "Create an EA with MA crossover strategy for EURUSD"
You: "Add risk management with 2% per trade and max 10% daily drawdown"
You: "Build a custom RSI indicator with color zones"
You: "Set up WebRequest to send trade notifications to my Node.js server"
You: "Add account-based licensing to my EA"Design Principles
Built following the skill-creator best practices:
- SKILL.md under 500 lines — concise entry point with navigation
- Progressive disclosure — reference files loaded only when needed
- No duplication — language references contain API signatures, specialized files contain production patterns
- Table of contents — all reference files include TOC for quick navigation
- Grep patterns — SKILL.md includes section headers for targeted search in large files
License
MIT
Backtesting & Optimization
Table of Contents
- Strategy Tester Modes
- Minimum Requirements for Valid Backtest
- OnTester() - Custom Optimization Criterion
- TesterStatistics() Constants
- Key Performance Metrics
- Walk-Forward Analysis
- Avoiding Overfitting
- Monte Carlo Simulation
- Multi-Currency Testing (MQL5)
- Optimization Modes (MT5)
- Frame Functions (Inter-Pass Communication)
- Practical Backtesting Workflow
---
Strategy Tester Modes
| Mode | Speed | Accuracy | When to Use |
|---|---|---|---|
| Every tick based on real ticks | Slowest | Most realistic | Final validation with broker-specific ticks |
| Every tick | Slow | High | Final validation, scalpers, intra-bar logic |
| 1-minute OHLC | Medium | Good | Most strategies (4 ticks per M1 bar) |
| Open prices only | Fast | Low | Bar-open strategies, rapid optimization |
| Math calculations | Instant | N/A | Pure computation without ticks |
Best practice: Optimize with Open Prices first, validate winners with Every Tick.
Minimum Requirements for Valid Backtest
- 1,000+ trades minimum (ideally 2,000+)
- 2+ full market cycles (bull + bear + range)
- Multiple years of data
- Consistent spread settings (or variable spread)
OnTester() - Custom Optimization Criterion (MQL5)
double OnTester() {
double profit = TesterStatistics(STAT_PROFIT);
double profitFactor = TesterStatistics(STAT_PROFIT_FACTOR);
double sharpeRatio = TesterStatistics(STAT_SHARPE_RATIO);
double recoveryFactor = TesterStatistics(STAT_RECOVERY_FACTOR);
double maxDrawdown = TesterStatistics(STAT_EQUITY_DD_RELATIVE);
double totalTrades = TesterStatistics(STAT_TRADES);
if(totalTrades < 100) return 0; // Reject small samples
if(maxDrawdown > 25) return 0; // Reject excessive DD
if(profitFactor < 1.3) return 0; // Reject low PF
return sharpeRatio * recoveryFactor * MathSqrt(totalTrades);
}TesterStatistics() Constants
Complete table:
- STAT_PROFIT - Net profit
- STAT_GROSS_PROFIT - Gross profit
- STAT_GROSS_LOSS - Gross loss
- STAT_TRADES - Total trades
- STAT_PROFIT_TRADES - Winning trades
- STAT_LOSS_TRADES - Losing trades
- STAT_PROFIT_FACTOR - Gross profit / gross loss
- STAT_EXPECTED_PAYOFF - Expected payoff per trade
- STAT_SHARPE_RATIO - Sharpe ratio
- STAT_RECOVERY_FACTOR - Net profit / max drawdown
- STAT_EQUITY_DD - Max equity drawdown in money
- STAT_EQUITY_DD_PERCENT - Max equity drawdown %
- STAT_EQUITY_DD_RELATIVE - Relative equity drawdown %
- STAT_BALANCE_DD - Max balance drawdown
- STAT_BALANCE_DD_PERCENT - Max balance drawdown %
- STAT_MAX_PROFITTRADE - Largest profitable trade
- STAT_MAX_LOSSTRADE - Largest losing trade
- STAT_CONPROFITMAX - Max consecutive profit
- STAT_CONLOSSMAX - Max consecutive loss
- STAT_SHORT_TRADES - Short trades
- STAT_LONG_TRADES - Long trades
- STAT_WIN_SHORT_TRADES - Winning short trades
- STAT_WIN_LONG_TRADES - Winning long trades
Key Performance Metrics
| Metric | Formula | Good Value | Excellent |
|---|---|---|---|
| Sharpe Ratio | (Mean Return - Rf) / StdDev | > 1.0 | > 2.0 |
| Profit Factor | Gross Profit / Gross Loss | > 1.5 | > 2.0 |
| Recovery Factor | Net Profit / Max DD | > 3.0 | > 5.0 |
| Max Drawdown | Peak-to-trough % | < 20% | < 10% |
| Expected Payoff | Net Profit / Total Trades | > 0 | Context |
| Win Rate | Winning / Total | Context | Context |
Walk-Forward Analysis
Methodology
Window 1: [===== IN-SAMPLE =====][= OOS =]
Window 2: [===== IN-SAMPLE =====][= OOS =]
Window 3: [===== IN-SAMPLE =====][= OOS =]
Window 4: [===== IN-SAMPLE =====][= OOS =]Steps
1. Split data into overlapping windows (e.g., 12-month IS, 3-month OOS) 2. Optimize on in-sample period 3. Test optimized params on out-of-sample period 4. Slide window forward, repeat 5. Accept if OOS performance >= 50-80% of IS 6. MT5 supports natively: Strategy Tester > Settings > Forward period
Walk-Forward Efficiency
WFE = (OOS annualized return) / (IS annualized return)
- WFE > 0.5 = acceptable
- WFE > 0.7 = good
- WFE < 0.3 = likely overfitted
Avoiding Overfitting
Principles
1. Parameter stability: Good params have good neighbors. If X=14 works but X=13 and X=15 don't, it's curve-fitted 2. Fewer parameters: Each extra parameter increases overfitting risk exponentially 3. Out-of-sample testing: Reserve 25-30% of data untouched 4. Cross-market validation: Test EURUSD strategy on GBPUSD 5. Regime awareness: Test across trending AND ranging markets
Signs of Overfitting
- Sharp performance drop in forward test
- Many parameters (>5 optimized)
- Strategy only works on specific date range
- Unrealistically high backtest metrics
- Parameter sensitivity (small changes cause large performance swings)
Monte Carlo Simulation
Purpose
Test if results are due to skill or luck by randomizing trade sequence.
Implementation
double OnTester() {
// Collect all trade P&Ls
double trades[];
// ... populate from history ...
int simulations = 1000;
double worstDD95;
for(int sim = 0; sim < simulations; sim++) {
// Fisher-Yates shuffle
// Calculate max drawdown for shuffled sequence
}
// Sort DDs, find 95th percentile
// Reject if 95th percentile DD > 30%
return profit / (1 + worstDD95);
}What Monte Carlo Tells You
- Expected range of drawdowns (not just the one historical path)
- Probability of ruin at different risk levels
- Confidence interval for returns
- If strategy is fragile (high variance across simulations)
Multi-Currency Testing (MQL5)
MT5 Strategy Tester supports multi-symbol natively:
int OnInit() {
// Reference other symbols to include them in test
int handle_eur = iMA("EURUSD", PERIOD_H1, 14, 0, MODE_SMA, PRICE_CLOSE);
int handle_gbp = iMA("GBPUSD", PERIOD_H1, 14, 0, MODE_SMA, PRICE_CLOSE);
// Tester auto-synchronizes all referenced symbols
return INIT_SUCCEEDED;
}Optimization Modes (MT5)
| Mode | Description |
|---|---|
| Slow (Complete) | Tests every combination (exhaustive) |
| Fast (Genetic) | Genetic algorithm, finds near-optimal efficiently |
| Custom max/min | Optimizes by OnTester() return value |
Cloud Computing
MQL5 Cloud Network distributes optimization across thousands of agents worldwide.
Frame Functions (Inter-Pass Communication)
// In EA (agent): send data at end of each pass
double OnTester() {
uchar data[];
// serialize results into data
FrameAdd("Results", 1, profit, data);
return profit;
}
// In terminal: receive during optimization
void OnTesterPass() {
ulong pass; string name; long id; double value; uchar data[];
while(FrameNext(pass, name, id, value, data)) {
PrintFormat("Pass #%d: profit=%.2f", pass, value);
}
}
// Control functions
void OnTesterInit() { /* before optimization starts */ }
void OnTesterDeinit() { /* after optimization finishes, aggregate results */ }Practical Backtesting Workflow
1. Quick scan: Open Prices Only, wide parameter ranges, genetic optimization 2. Narrow down: Reduce ranges around promising areas, complete optimization 3. Validate: Every Tick mode with best parameters 4. Walk-forward: Set forward period, verify OOS performance 5. Monte Carlo: Randomize trade sequence, check robustness 6. Multi-market: Test on correlated instruments 7. Demo forward test: Run on demo account for 1-3 months minimum
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
- JSON Handling
- Node.js Integration Patterns
- Network Error Handling
- Internal Communication (Between MQL Programs)
- MQL5 Sockets (Advanced)
- 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:
// 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 valuedparam(double): one double valuesparam(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_CUSTOMtoCHARTEVENT_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 |
Indicators, UI Panels & Scripts
Table of Contents
- Custom Indicators - MQL4
- Custom Indicators - MQL5
- Indicator Handle System (MQL5)
- Graphical Objects
- UI Panels
- Scripts
- Chart Operations
---
Custom Indicators - MQL4
Properties
#property strict
#property indicator_chart_window // or indicator_separate_window
#property indicator_buffers 2 // visual buffers (max 8)
#property indicator_color1 clrRed
#property indicator_color2 clrBlue
#property indicator_width1 2
#property indicator_style1 STYLE_SOLID
#property indicator_minimum 0 // for separate window
#property indicator_maximum 100
#property indicator_level1 30 // horizontal levels
#property indicator_level2 70Buffer Setup
IndicatorBuffers(n)sets total buffer count (visual + calculation)#property indicator_bufferssets visual buffer count- If you need extra calculation buffers:
IndicatorBuffers(visual + extra) - Max 8 buffers in MQL4
double Buffer1[], Buffer2[], CalcBuffer[];
int OnInit()
{
IndicatorBuffers(3); // 2 visual + 1 calculation
SetIndexBuffer(0, Buffer1);
SetIndexBuffer(1, Buffer2);
SetIndexBuffer(2, CalcBuffer);
SetIndexStyle(0, DRAW_LINE, STYLE_SOLID, 2, clrRed);
SetIndexStyle(1, DRAW_HISTOGRAM, STYLE_SOLID, 1, clrBlue);
SetIndexLabel(0, "Main Line");
SetIndexLabel(1, "Histogram");
SetIndexDrawBegin(0, 14); // skip first 14 bars
SetIndexEmptyValue(0, EMPTY_VALUE);
IndicatorShortName("My Indicator");
return(INIT_SUCCEEDED);
}Draw types (MQL4): DRAW_LINE, DRAW_HISTOGRAM, DRAW_ARROW, DRAW_NONE, DRAW_SECTION, DRAW_ZIGZAG
OnCalculate Template (MQL4)
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
// Efficient recalculation: only process new bars
int limit = rates_total - prev_calculated;
if(prev_calculated > 0) limit++; // recheck last bar
// MQL4 default: index 0 = oldest bar (non-series)
// If you want series order (0 = newest):
// ArraySetAsSeries(close, true);
// ArraySetAsSeries(Buffer1, true);
for(int i = limit - 1; i >= 0; i--)
{
// Calculate from oldest to newest
int pos = rates_total - 1 - i; // convert to non-series index
Buffer1[pos] = close[pos]; // your calculation here
}
return(rates_total);
}Multi-Timeframe Indicator (MQL4)
// Read indicator values from other timeframes
double htfMA = iMA(Symbol(), PERIOD_H4, 20, 0, MODE_SMA, PRICE_CLOSE, 0);
// Read custom indicator from other timeframe
double val = iCustom(Symbol(), PERIOD_H1, "MyIndicator", param1, param2, bufferIndex, shift);
// Shift bar alignment: find H4 bar that corresponds to current M15 bar
int htfShift = iBarShift(Symbol(), PERIOD_H4, Time[i]);Gotcha: MTF indicators may repaint if higher timeframe bar is still forming.
---
Custom Indicators - MQL5
Properties and Buffers
#property indicator_chart_window
#property indicator_buffers 3 // total buffers (up to 512)
#property indicator_plots 2 // visual plots
// Plot 1 configuration
#property indicator_label1 "Main"
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrBlue
#property indicator_width1 2
#property indicator_style1 STYLE_SOLID
// Plot 2 configuration
#property indicator_label2 "Signal"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrRed
#property indicator_width2 1Buffers vs Plots
| Concept | What it is | Count rule |
|---|---|---|
indicator_buffers | Total arrays (data + color + calc) | Must be >= plots |
indicator_plots | Number of visual drawings | What user sees |
| Buffer types | INDICATOR_DATA, INDICATOR_COLOR_INDEX, INDICATOR_CALCULATIONS | Set in OnInit |
double MainBuffer[], SignalBuffer[], CalcBuffer[];
int OnInit()
{
// Map buffers to indices
SetIndexBuffer(0, MainBuffer, INDICATOR_DATA);
SetIndexBuffer(1, SignalBuffer, INDICATOR_DATA);
SetIndexBuffer(2, CalcBuffer, INDICATOR_CALCULATIONS);
// Configure plots programmatically (alternative to #property)
PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_LINE);
PlotIndexSetInteger(0, PLOT_LINE_COLOR, clrBlue);
PlotIndexSetInteger(0, PLOT_LINE_WIDTH, 2);
PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, 14);
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
PlotIndexSetString(0, PLOT_LABEL, "Main Line");
// Arrow indicator
// PlotIndexSetInteger(0, PLOT_ARROW, 233); // wingdings code
IndicatorSetString(INDICATOR_SHORTNAME, "My Indicator");
IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
return(INIT_SUCCEEDED);
}OnCalculate - Two Variants
Full form (receives all OHLCV data):
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
// Default: arrays are NOT series (index 0 = oldest)
// To use series order:
// ArraySetAsSeries(close, true);
// ArraySetAsSeries(MainBuffer, true);
int limit = (prev_calculated == 0) ? 0 : prev_calculated - 1;
for(int i = limit; i < rates_total; i++)
{
MainBuffer[i] = close[i]; // your calculation
}
return(rates_total);
}Short form (single data source - "Apply to" dropdown):
int OnCalculate(const int rates_total,
const int prev_calculated,
const int begin,
const double &price[])
{
int limit = (prev_calculated == 0) ? begin : prev_calculated - 1;
for(int i = limit; i < rates_total; i++)
{
MainBuffer[i] = price[i];
}
return(rates_total);
}Important: You can only have ONE variant in your indicator. The short form is used when the indicator needs to be applied to different data sources (close, open, another indicator's output, etc.).
Draw Styles Reference
| Style | Data Buffers | Color Buffer | Total | Description |
|---|---|---|---|---|
DRAW_NONE | 1 | - | 1 | Hidden buffer |
DRAW_LINE | 1 | - | 1 | Simple line |
DRAW_SECTION | 1 | - | 1 | Line segments |
DRAW_HISTOGRAM | 1 | - | 1 | Histogram from zero |
DRAW_HISTOGRAM2 | 2 | - | 2 | Histogram between two values |
DRAW_ARROW | 1 | - | 1 | Arrow symbols |
DRAW_ZIGZAG | 2 | - | 2 | Zigzag line |
DRAW_FILLING | 2 | - | 2 | Filled area between lines |
DRAW_BARS | 4 | - | 4 | OHLC bars |
DRAW_CANDLES | 4 | - | 4 | Candlesticks |
DRAW_COLOR_LINE | 1 | 1 | 2 | Multi-color line |
DRAW_COLOR_SECTION | 1 | 1 | 2 | Multi-color sections |
DRAW_COLOR_HISTOGRAM | 1 | 1 | 2 | Multi-color histogram |
DRAW_COLOR_HISTOGRAM2 | 2 | 1 | 3 | Multi-color histogram2 |
DRAW_COLOR_ARROW | 1 | 1 | 2 | Multi-color arrows |
DRAW_COLOR_ZIGZAG | 2 | 1 | 3 | Multi-color zigzag |
DRAW_COLOR_BARS | 4 | 1 | 5 | Multi-color bars |
DRAW_COLOR_CANDLES | 4 | 1 | 5 | Multi-color candles |
Color Line Example (MQL5)
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots 1
#property indicator_type1 DRAW_COLOR_LINE
#property indicator_color1 clrGreen,clrYellow,clrRed // color palette
#property indicator_width1 2
double ValueBuffer[];
double ColorBuffer[];
int OnInit()
{
SetIndexBuffer(0, ValueBuffer, INDICATOR_DATA);
SetIndexBuffer(1, ColorBuffer, INDICATOR_COLOR_INDEX);
PlotIndexSetInteger(0, PLOT_COLOR_INDEXES, 3); // 3 colors
return(INIT_SUCCEEDED);
}
int OnCalculate(const int rates_total, const int prev_calculated,
const datetime &time[], const double &open[],
const double &high[], const double &low[],
const double &close[], const long &tick_volume[],
const long &volume[], const int &spread[])
{
int limit = (prev_calculated == 0) ? 14 : prev_calculated - 1;
for(int i = limit; i < rates_total; i++)
{
// Example: RSI-like value
ValueBuffer[i] = /* your calculation */;
// Set color index: 0=Green, 1=Yellow, 2=Red
if(ValueBuffer[i] > 70) ColorBuffer[i] = 2; // Red (overbought)
else if(ValueBuffer[i] < 30) ColorBuffer[i] = 0; // Green (oversold)
else ColorBuffer[i] = 1; // Yellow (neutral)
}
return(rates_total);
}PlotIndex Functions Reference
// Integer properties
PlotIndexSetInteger(plotIndex, PLOT_DRAW_TYPE, DRAW_LINE);
PlotIndexSetInteger(plotIndex, PLOT_LINE_STYLE, STYLE_SOLID);
PlotIndexSetInteger(plotIndex, PLOT_LINE_WIDTH, 2);
PlotIndexSetInteger(plotIndex, PLOT_LINE_COLOR, clrBlue);
PlotIndexSetInteger(plotIndex, PLOT_COLOR_INDEXES, 3);
PlotIndexSetInteger(plotIndex, PLOT_ARROW, 233); // wingdings code
PlotIndexSetInteger(plotIndex, PLOT_SHIFT, 0);
PlotIndexSetInteger(plotIndex, PLOT_DRAW_BEGIN, 14);
PlotIndexSetInteger(plotIndex, PLOT_SHOW_DATA, true);
// Double properties
PlotIndexSetDouble(plotIndex, PLOT_EMPTY_VALUE, EMPTY_VALUE);
// String properties
PlotIndexSetString(plotIndex, PLOT_LABEL, "My Line");
// Indicator-level settings
IndicatorSetString(INDICATOR_SHORTNAME, "Name (params)");
IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
IndicatorSetInteger(INDICATOR_LEVELS, 2);
IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, 30.0);
IndicatorSetDouble(INDICATOR_LEVELVALUE, 1, 70.0);
IndicatorSetInteger(INDICATOR_LEVELCOLOR, 0, clrSilver);
IndicatorSetInteger(INDICATOR_LEVELSTYLE, 0, STYLE_DOT);
IndicatorSetDouble(INDICATOR_MINIMUM, 0);
IndicatorSetDouble(INDICATOR_MAXIMUM, 100);---
Indicator Handle System (MQL5)
In MQL5, built-in indicators return handles (int). Use CopyBuffer() to retrieve data.
Creating Handles
// Create in OnInit - only once
int g_maHandle, g_rsiHandle, g_bbHandle;
int OnInit()
{
g_maHandle = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE);
g_rsiHandle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE);
g_bbHandle = iBands(_Symbol, PERIOD_CURRENT, 20, 0, 2.0, PRICE_CLOSE);
if(g_maHandle == INVALID_HANDLE || g_rsiHandle == INVALID_HANDLE)
{
Print("Failed to create indicator handles");
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
IndicatorRelease(g_maHandle);
IndicatorRelease(g_rsiHandle);
IndicatorRelease(g_bbHandle);
}Reading Data from Handles
double maBuffer[], rsiBuffer[];
double bbUpper[], bbMiddle[], bbLower[];
void OnTick()
{
ArraySetAsSeries(maBuffer, true);
ArraySetAsSeries(rsiBuffer, true);
ArraySetAsSeries(bbUpper, true);
ArraySetAsSeries(bbMiddle, true);
ArraySetAsSeries(bbLower, true);
// CopyBuffer(handle, bufferIndex, startPos, count, array)
if(CopyBuffer(g_maHandle, 0, 0, 3, maBuffer) < 3) return;
if(CopyBuffer(g_rsiHandle, 0, 0, 3, rsiBuffer) < 3) return;
// Bollinger has 3 buffers: 0=middle, 1=upper, 2=lower
if(CopyBuffer(g_bbHandle, 0, 0, 3, bbMiddle) < 3) return;
if(CopyBuffer(g_bbHandle, 1, 0, 3, bbUpper) < 3) return;
if(CopyBuffer(g_bbHandle, 2, 0, 3, bbLower) < 3) return;
// maBuffer[0] = current bar, maBuffer[1] = previous bar
double currentMA = maBuffer[0];
double currentRSI = rsiBuffer[0];
}Custom Indicator Handle
// iCustom(symbol, timeframe, "IndicatorName", param1, param2, ...)
int customHandle = iCustom(_Symbol, PERIOD_H1, "MyIndicator", 14, 2.0);
double buf[];
ArraySetAsSeries(buf, true);
CopyBuffer(customHandle, 0, 0, 10, buf); // buffer 0, last 10 valuesCommon Built-in Indicator Handles
| Function | Buffers | Notes |
|---|---|---|
iMA() | 0: MA value | MODE_SMA, MODE_EMA, MODE_SMMA, MODE_LWMA |
iRSI() | 0: RSI value | |
iMACD() | 0: main, 1: signal | |
iBands() | 0: middle, 1: upper, 2: lower | |
iATR() | 0: ATR value | |
iStochastic() | 0: main, 1: signal | |
iADX() | 0: ADX, 1: +DI, 2: -DI | |
iCCI() | 0: CCI value | |
iSAR() | 0: SAR value | |
iIchimoku() | 0: tenkan, 1: kijun, 2: senkouA, 3: senkouB, 4: chikou |
---
Graphical Objects
Common Object Types
| Type | Description | Anchor Points |
|---|---|---|
OBJ_HLINE | Horizontal line | 1 (price only) |
OBJ_VLINE | Vertical line | 1 (time only) |
OBJ_TREND | Trend line | 2 |
OBJ_RECTANGLE | Rectangle | 2 (corners) |
OBJ_TRIANGLE | Triangle | 3 |
OBJ_ELLIPSE | Ellipse | 2 |
OBJ_LABEL | Text label (pixel coords) | 0 |
OBJ_TEXT | Text on chart (price/time) | 1 |
OBJ_EDIT | Editable text field | 0 |
OBJ_BUTTON | Clickable button | 0 |
OBJ_BITMAP_LABEL | Image (pixel coords) | 0 |
OBJ_RECTANGLE_LABEL | Rectangle (pixel coords) | 0 |
OBJ_ARROW | Arrow symbol | 1 |
Creating Objects
// Price-anchored objects (use time/price coordinates)
ObjectCreate(0, "myTrend", OBJ_TREND, 0, time1, price1, time2, price2);
ObjectCreate(0, "myHLine", OBJ_HLINE, 0, 0, priceLevel);
// Pixel-anchored objects (use XDISTANCE/YDISTANCE)
ObjectCreate(0, "myLabel", OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, "myLabel", OBJPROP_XDISTANCE, 50);
ObjectSetInteger(0, "myLabel", OBJPROP_YDISTANCE, 50);
ObjectSetInteger(0, "myLabel", OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetString(0, "myLabel", OBJPROP_TEXT, "Hello World");
ObjectSetString(0, "myLabel", OBJPROP_FONT, "Arial");
ObjectSetInteger(0, "myLabel", OBJPROP_FONTSIZE, 12);
ObjectSetInteger(0, "myLabel", OBJPROP_COLOR, clrWhite);Common Object Properties
// Set properties
ObjectSetInteger(chartId, name, OBJPROP_COLOR, clrRed);
ObjectSetInteger(chartId, name, OBJPROP_WIDTH, 2);
ObjectSetInteger(chartId, name, OBJPROP_STYLE, STYLE_DASH);
ObjectSetInteger(chartId, name, OBJPROP_BACK, true); // draw behind candles
ObjectSetInteger(chartId, name, OBJPROP_SELECTABLE, false); // prevent user selection
ObjectSetInteger(chartId, name, OBJPROP_HIDDEN, true); // hide from object list
ObjectSetInteger(chartId, name, OBJPROP_ZORDER, 10); // z-order for click priority
// Get properties
long colorVal = ObjectGetInteger(0, name, OBJPROP_COLOR);
double priceVal = ObjectGetDouble(0, name, OBJPROP_PRICE);
string textVal = ObjectGetString(0, name, OBJPROP_TEXT);
// Delete objects
ObjectDelete(0, "myLabel");
ObjectsDeleteAll(0, "prefix_"); // delete by name prefix
ObjectsDeleteAll(0, 0, OBJ_LABEL); // delete by type in subwindow 0Button Example
void CreateButton(string name, int x, int y, int width, int height, string text)
{
ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetInteger(0, name, OBJPROP_XSIZE, width);
ObjectSetInteger(0, name, OBJPROP_YSIZE, height);
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetInteger(0, name, OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clrDodgerBlue);
ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, clrNONE);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 10);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
}
// In OnInit:
CreateButton("btnBuy", 10, 30, 100, 30, "BUY");
CreateButton("btnSell", 120, 30, 100, 30, "SELL");OnChartEvent Handler
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
// Button click
if(id == CHARTEVENT_OBJECT_CLICK)
{
if(sparam == "btnBuy")
{
// Execute buy logic
ObjectSetInteger(0, "btnBuy", OBJPROP_STATE, false); // reset button
}
else if(sparam == "btnSell")
{
// Execute sell logic
ObjectSetInteger(0, "btnSell", OBJPROP_STATE, false);
}
}
// Mouse click on chart (not on object)
if(id == CHARTEVENT_CLICK)
{
int x = (int)lparam; // pixel X
int y = (int)dparam; // pixel Y
// Convert to price/time:
datetime time;
double price;
int subwindow;
ChartXYToTimePrice(0, x, y, subwindow, time, price);
}
// Keyboard
if(id == CHARTEVENT_KEYDOWN)
{
long keyCode = lparam; // virtual key code
}
// Mouse move (requires ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true))
if(id == CHARTEVENT_MOUSE_MOVE)
{
int mouseX = (int)lparam;
int mouseY = (int)dparam;
// sparam contains mouse button flags
}
// Edit field changed
if(id == CHARTEVENT_OBJECT_ENDEDIT)
{
string editValue = ObjectGetString(0, sparam, OBJPROP_TEXT);
}
// Custom events (from EventChartCustom)
if(id >= CHARTEVENT_CUSTOM)
{
int customId = id - CHARTEVENT_CUSTOM;
}
}Event Types:
| Event | id | lparam | dparam | sparam |
|---|---|---|---|---|
CHARTEVENT_KEYDOWN | 0 | key code | repeat count | key flags |
CHARTEVENT_MOUSE_MOVE | 1 | x | y | flags |
CHARTEVENT_OBJECT_CREATE | 2 | - | - | object name |
CHARTEVENT_OBJECT_CHANGE | 3 | - | - | object name |
CHARTEVENT_OBJECT_DELETE | 4 | - | - | object name |
CHARTEVENT_CLICK | 5 | x | y | - |
CHARTEVENT_OBJECT_CLICK | 6 | x | y | object name |
CHARTEVENT_OBJECT_DRAG | 7 | - | - | object name |
CHARTEVENT_OBJECT_ENDEDIT | 8 | - | - | object name |
CHARTEVENT_CHART_CHANGE | 9 | - | - | - |
CHARTEVENT_CUSTOM+n | 1000+n | lparam | dparam | sparam |
Sending Custom Events
// Send to own chart
EventChartCustom(0, 0, longParam, doubleParam, "stringParam");
// id in OnChartEvent will be CHARTEVENT_CUSTOM + 0 = 1000
// Send to another chart
long otherChartId = ChartFirst();
EventChartCustom(otherChartId, 1, 0, 0.0, "message");---
UI Panels
Simple Panel with Objects (Lightweight)
string g_prefix = "panel_";
void CreatePanel()
{
int panelX = 10, panelY = 30;
int panelW = 220, panelH = 180;
// Background
ObjectCreate(0, g_prefix + "bg", OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, g_prefix + "bg", OBJPROP_XDISTANCE, panelX);
ObjectSetInteger(0, g_prefix + "bg", OBJPROP_YDISTANCE, panelY);
ObjectSetInteger(0, g_prefix + "bg", OBJPROP_XSIZE, panelW);
ObjectSetInteger(0, g_prefix + "bg", OBJPROP_YSIZE, panelH);
ObjectSetInteger(0, g_prefix + "bg", OBJPROP_BGCOLOR, C'32,32,32');
ObjectSetInteger(0, g_prefix + "bg", OBJPROP_BORDER_TYPE, BORDER_FLAT);
ObjectSetInteger(0, g_prefix + "bg", OBJPROP_BORDER_COLOR, clrDimGray);
ObjectSetInteger(0, g_prefix + "bg", OBJPROP_CORNER, CORNER_LEFT_UPPER);
// Title label
CreateLabel(g_prefix + "title", panelX + 10, panelY + 5, "Trade Panel", 11, clrWhite);
// Data labels
CreateLabel(g_prefix + "spread", panelX + 10, panelY + 30, "Spread: --", 9, clrSilver);
CreateLabel(g_prefix + "profit", panelX + 10, panelY + 50, "P/L: --", 9, clrSilver);
// Buttons
CreateButton(g_prefix + "buy", panelX + 10, panelY + 80, 95, 30, "BUY");
CreateButton(g_prefix + "sell", panelX + 115, panelY + 80, 95, 30, "SELL");
ChartRedraw();
}
void CreateLabel(string name, int x, int y, string text, int fontSize, color clr)
{
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetString(0, name, OBJPROP_FONT, "Consolas");
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
}
void UpdatePanel()
{
double spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * _Point;
ObjectSetString(0, g_prefix + "spread", OBJPROP_TEXT,
StringFormat("Spread: %.1f pips", spread / _Point / 10));
double profit = AccountInfoDouble(ACCOUNT_PROFIT);
color profitClr = profit >= 0 ? clrLime : clrRed;
ObjectSetString(0, g_prefix + "profit", OBJPROP_TEXT,
StringFormat("P/L: %.2f", profit));
ObjectSetInteger(0, g_prefix + "profit", OBJPROP_COLOR, profitClr);
ChartRedraw();
}
void DestroyPanel()
{
ObjectsDeleteAll(0, g_prefix);
}CAppDialog Panel (MQL5 Standard Library)
#include <Controls/Dialog.mqh>
#include <Controls/Button.mqh>
#include <Controls/Label.mqh>
#include <Controls/Edit.mqh>
#include <Controls/ComboBox.mqh>
#include <Controls/SpinEdit.mqh>
#include <Controls/CheckBox.mqh>
class CTradePanel : public CAppDialog
{
private:
CButton m_btnBuy;
CButton m_btnSell;
CButton m_btnClose;
CLabel m_lblSpread;
CEdit m_editLots;
CSpinEdit m_spinSL;
CComboBox m_cmbSymbol;
public:
bool Create(const long chart, const string name, const int subwin,
const int x1, const int y1, const int x2, const int y2);
void UpdateInfo();
// Event handlers
void OnClickBuy();
void OnClickSell();
void OnClickClose();
// Event map
virtual bool OnEvent(const int id, const long &lparam,
const double &dparam, const string &sparam);
};
// Event map macro
EVENT_MAP_BEGIN(CTradePanel)
ON_EVENT(ON_CLICK, m_btnBuy, OnClickBuy)
ON_EVENT(ON_CLICK, m_btnSell, OnClickSell)
ON_EVENT(ON_CLICK, m_btnClose, OnClickClose)
EVENT_MAP_END(CAppDialog)
bool CTradePanel::Create(const long chart, const string name, const int subwin,
const int x1, const int y1, const int x2, const int y2)
{
if(!CAppDialog::Create(chart, name, subwin, x1, y1, x2, y2))
return false;
// Create controls (coordinates relative to dialog client area)
if(!m_lblSpread.Create(m_chart_id, m_name + "Spread", m_subwin, 10, 10, 200, 25))
return false;
m_lblSpread.Text("Spread: --");
if(!Add(m_lblSpread)) return false;
if(!m_editLots.Create(m_chart_id, m_name + "Lots", m_subwin, 10, 35, 100, 55))
return false;
m_editLots.Text("0.1");
if(!Add(m_editLots)) return false;
if(!m_btnBuy.Create(m_chart_id, m_name + "Buy", m_subwin, 10, 65, 95, 95))
return false;
m_btnBuy.Text("BUY");
m_btnBuy.ColorBackground(clrForestGreen);
if(!Add(m_btnBuy)) return false;
if(!m_btnSell.Create(m_chart_id, m_name + "Sell", m_subwin, 105, 65, 190, 95))
return false;
m_btnSell.Text("SELL");
m_btnSell.ColorBackground(clrCrimson);
if(!Add(m_btnSell)) return false;
if(!m_btnClose.Create(m_chart_id, m_name + "Close", m_subwin, 10, 105, 190, 130))
return false;
m_btnClose.Text("CLOSE ALL");
if(!Add(m_btnClose)) return false;
return true;
}
void CTradePanel::OnClickBuy()
{
double lots = StringToDouble(m_editLots.Text());
// Execute buy...
}
void CTradePanel::OnClickSell()
{
double lots = StringToDouble(m_editLots.Text());
// Execute sell...
}
void CTradePanel::OnClickClose()
{
// Close all positions...
}
// In EA:
CTradePanel g_panel;
int OnInit()
{
if(!g_panel.Create(0, "TradePanel", 0, 20, 20, 240, 190))
return INIT_FAILED;
g_panel.Run();
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
g_panel.Destroy(reason);
}
void OnChartEvent(const int id, const long &lparam,
const double &dparam, const string &sparam)
{
g_panel.ChartEvent(id, lparam, dparam, sparam);
}
void OnTick()
{
g_panel.UpdateInfo();
}CCanvas (MQL5 Pixel Drawing)
#include <Canvas/Canvas.mqh>
CCanvas g_canvas;
void CreateCanvas()
{
g_canvas.CreateBitmapLabel("myPanel", 10, 10, 400, 300,
COLOR_FORMAT_ARGB_NORMALIZE);
// Clear background
g_canvas.Erase(ColorToARGB(C'30,30,30'));
// Draw shapes
g_canvas.FillRectangle(0, 0, 400, 30, ColorToARGB(clrDarkBlue));
g_canvas.Rectangle(0, 0, 399, 299, ColorToARGB(clrGray));
g_canvas.Line(0, 30, 400, 30, ColorToARGB(clrGray));
g_canvas.FillCircle(200, 150, 50, ColorToARGB(clrDodgerBlue, 128));
// Draw text
g_canvas.FontSet("Arial", 14, FW_BOLD);
g_canvas.TextOut(10, 5, "Dashboard", ColorToARGB(clrWhite));
g_canvas.FontSet("Consolas", 11);
g_canvas.TextOut(10, 40, "Balance: $10,000", ColorToARGB(clrLime));
// Apply changes
g_canvas.Update();
}
void DestroyCanvas()
{
g_canvas.Destroy();
}CCanvas key methods:
Erase(argb)- Clear entire canvasPixel(x, y, argb)- Single pixelLine(x1, y1, x2, y2, argb)- LineRectangle(x1, y1, x2, y2, argb)- Rectangle outlineFillRectangle(x1, y1, x2, y2, argb)- Filled rectangleCircle(x, y, r, argb)/FillCircle()- CircleTriangle(x1,y1, x2,y2, x3,y3, argb)/FillTriangle()FontSet(name, size, flags)- Set fontTextOut(x, y, text, argb)- Draw textUpdate()- Apply all changes to screen
---
Scripts
MQL4 Script Template
#property strict
#property show_inputs // show input dialog before execution
input double Lots = 0.1;
input int MagicNum = 12345;
void OnStart()
{
// One-time execution
Print("Script started");
// Scripts can trade, modify objects, read files, etc.
// Scripts run once and terminate
}MQL5 Script Template
#property script_show_inputs
input double InpLots = 0.1;
void OnStart()
{
// One-time execution
Print("Script started");
}Close All Positions Script (MQL5)
#property script_show_inputs
#include <Trade/Trade.mqh>
input string InpSymbol = ""; // Symbol filter (empty = all)
input long InpMagic = 0; // Magic filter (0 = all)
input bool InpConfirm = true; // Ask confirmation
void OnStart()
{
int total = PositionsTotal();
if(total == 0)
{
Print("No open positions");
return;
}
if(InpConfirm)
{
int answer = MessageBox(
StringFormat("Close %d position(s)?", total),
"Confirm", MB_YESNO | MB_ICONQUESTION);
if(answer != IDYES) return;
}
CTrade trade;
int closed = 0, errors = 0;
// Reverse loop: closing changes indices
for(int i = total - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
// Apply filters
if(InpSymbol != "" && PositionGetString(POSITION_SYMBOL) != InpSymbol)
continue;
if(InpMagic != 0 && PositionGetInteger(POSITION_MAGIC) != InpMagic)
continue;
if(trade.PositionClose(ticket))
closed++;
else
{
errors++;
Print("Failed to close #", ticket, ": ", trade.ResultRetcodeDescription());
}
}
Print(StringFormat("Closed: %d, Errors: %d", closed, errors));
}Close All Orders Script (MQL4)
#property strict
#property show_inputs
input int MagicFilter = 0; // 0 = all
void OnStart()
{
int closed = 0;
// Close market orders (reverse loop!)
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if(MagicFilter != 0 && OrderMagicNumber() != MagicFilter) continue;
bool result = false;
if(OrderType() == OP_BUY)
result = OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_BID), 3);
else if(OrderType() == OP_SELL)
result = OrderClose(OrderTicket(), OrderLots(), MarketInfo(OrderSymbol(), MODE_ASK), 3);
else // pending order
result = OrderDelete(OrderTicket());
if(result) closed++;
else Print("Failed: ", GetLastError());
}
Print("Closed: ", closed);
}Common Script Use Cases
- Close all positions by symbol/magic/direction
- Export trade history to CSV file
- Place grid orders (multiple pending orders at intervals)
- Batch modify SL/TP on all open positions
- Account statistics calculator and display
- Delete all objects by type or prefix
- Symbol scanner (check conditions across multiple symbols)
- Lot calculator (calculate position size from risk parameters)
---
Chart Operations
Chart Properties
// Set properties
ChartSetInteger(0, CHART_MODE, CHART_CANDLES); // CHART_BARS, CHART_LINE
ChartSetInteger(0, CHART_AUTOSCROLL, true);
ChartSetInteger(0, CHART_SHIFT, true); // right margin
ChartSetInteger(0, CHART_SHOW_GRID, false);
ChartSetInteger(0, CHART_SHOW_VOLUMES, CHART_VOLUME_HIDE);
ChartSetInteger(0, CHART_SHOW_TRADE_LEVELS, true);
ChartSetInteger(0, CHART_SHOW_DATE_SCALE, true);
ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, true);
ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); // enable mouse events
ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true);
// Colors
ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrBlack);
ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrWhite);
ChartSetInteger(0, CHART_COLOR_GRID, clrDimGray);
ChartSetInteger(0, CHART_COLOR_CANDLE_BULL, clrLime);
ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, clrRed);
ChartSetInteger(0, CHART_COLOR_CHART_UP, clrLime);
ChartSetInteger(0, CHART_COLOR_CHART_DOWN, clrRed);
ChartSetInteger(0, CHART_COLOR_CHART_LINE, clrWhite);
// Get properties
long chartMode = ChartGetInteger(0, CHART_MODE);
int chartW = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
int chartH = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
int firstBar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR);
int visBars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);
// Force redraw
ChartRedraw(0);Chart Navigation
// Change symbol/timeframe
ChartSetSymbolPeriod(0, "EURUSD", PERIOD_H1);
// Navigate to specific position
ChartNavigate(0, CHART_END, 0); // go to end
ChartNavigate(0, CHART_BEGIN, 0); // go to beginning
ChartNavigate(0, CHART_CURRENT_POS, -50); // scroll left 50 bars
// Iterate all open charts
long chartId = ChartFirst();
while(chartId >= 0)
{
string sym = ChartSymbol(chartId);
ENUM_TIMEFRAMES tf = ChartPeriod(chartId);
Print(sym, " ", EnumToString(tf));
chartId = ChartNext(chartId);
}
// Open new chart
long newChart = ChartOpen("GBPUSD", PERIOD_M15);
// Coordinate conversion
datetime time;
double price;
int subwindow;
ChartXYToTimePrice(0, pixelX, pixelY, subwindow, time, price);
int x, y;
ChartTimePriceToXY(0, 0, time, price, x, y);Chart Templates
// Apply template
ChartApplyTemplate(0, "MyTemplate.tpl");
// Save template
ChartSaveTemplate(0, "MyTemplate.tpl");
// Screenshot
ChartScreenShot(0, "screenshot.png", 1920, 1080, ALIGN_RIGHT);MQL5 Language Reference
Comprehensive reference for MQL5 development on MetaTrader 5.
Table of Contents
- Key Differences from MQL4
- OOP Features
- Trade Functions
- Event Handlers
- Standard Library
- Key Enumerations
- MQL5-Specific Features
- MQL5 vs MQL4 Summary Table
---
Key Differences from MQL4
Architectural Paradigm
MQL5 is a full object-oriented language (C++-like) compared to MQL4's procedural (C-like) approach. The most fundamental difference is the trade model:
| Concept | Description |
|---|---|
| Order | A trade request sent to the server. Can be market (executed immediately) or pending (waits for price). |
| Deal | An executed trade operation resulting from a filled order. Recorded in history. |
| Position | The net result of one or more deals on a symbol. This is the current open exposure. |
Account Models
| Model | Behavior | Notes |
|---|---|---|
| Netting | One position per symbol. New deals increase/decrease/reverse the single position. | Default for most brokers. ACCOUNT_MARGIN_MODE_RETAIL_NETTING. |
| Hedging | Multiple independent positions per symbol. Each has its own ticket. | Closer to MQL4 behavior. ACCOUNT_MARGIN_MODE_RETAIL_HEDGING. |
Detect at runtime:
ENUM_ACCOUNT_MARGIN_MODE marginMode = (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
if(marginMode == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
Print("Hedging account");---
OOP Features
Classes
class CMyClass
{
private:
int m_id;
double m_value;
string m_name;
protected:
double CalculateInternal() const { return m_value * 2.0; }
public:
// Default constructor
CMyClass() : m_id(0), m_value(0.0), m_name("") {}
// Parametric constructor with initialization list
CMyClass(int id, double value, string name)
: m_id(id), m_value(value), m_name(name) {}
// Copy constructor
CMyClass(const CMyClass &other)
: m_id(other.m_id), m_value(other.m_value), m_name(other.m_name) {}
// Destructor
~CMyClass() { Print("Destroyed: ", m_name); }
// Const method (does not modify object state)
int GetId() const { return m_id; }
double GetValue() const { return m_value; }
string GetName() const { return m_name; }
// Mutator methods
void SetValue(double value) { m_value = value; }
// Regular method
void PrintInfo()
{
PrintFormat("ID=%d, Value=%.2f, Name=%s", m_id, m_value, m_name);
}
};Inheritance
class CBase
{
public:
virtual void Process() { Print("CBase::Process"); }
virtual string GetType() const { return "Base"; }
};
class CDerived : public CBase // Single inheritance only
{
public:
void Process() override { Print("CDerived::Process"); } // override keyword
string GetType() const override final { return "Derived"; } // final = no further override
};
class CFinalClass final : public CDerived // final class = cannot be inherited
{
public:
void Process() override { Print("CFinalClass::Process"); }
// Cannot override GetType() because it's marked final in CDerived
};Virtual Functions and Polymorphism
// Abstract class with pure virtual function
class CSignal
{
public:
virtual int GetSignal() = 0; // Pure virtual (= 0), makes class abstract
virtual void Release() { delete &this; }
virtual ~CSignal() {}
};
class CMASignal : public CSignal
{
public:
int GetSignal() override
{
// MA crossover logic
return 1; // Buy
}
};
class CRSISignal : public CSignal
{
public:
int GetSignal() override
{
// RSI overbought/oversold logic
return -1; // Sell
}
};
// Dynamic dispatch with pointer arrays
CSignal *signals[];
void OnInit()
{
ArrayResize(signals, 2);
signals[0] = new CMASignal(); // new allocates on heap
signals[1] = new CRSISignal();
}
void OnDeinit(const int reason)
{
for(int i = 0; i < ArraySize(signals); i++)
delete signals[i]; // delete frees heap memory
}
void OnTick()
{
for(int i = 0; i < ArraySize(signals); i++)
Print("Signal[", i, "]: ", signals[i].GetSignal()); // Polymorphic call
}Interfaces
// interface keyword: no data members, all methods implicitly pure virtual
interface ITradeExecutor
{
bool Execute(string symbol, double volume, int direction);
void Cancel();
};
interface ILogger
{
void Log(string message);
void LogError(string message);
};
// Multiple interface inheritance is allowed
class CSmartTrader : public ITradeExecutor, public ILogger
{
private:
string m_logPrefix;
public:
CSmartTrader(string prefix) : m_logPrefix(prefix) {}
// Must implement ALL interface methods
bool Execute(string symbol, double volume, int direction) override
{
Log(StringFormat("Executing %s %.2f lots dir=%d", symbol, volume, direction));
return true;
}
void Cancel() override { Log("Trade cancelled"); }
void Log(string message) override
{
Print(m_logPrefix, ": ", message);
}
void LogError(string message) override
{
Print(m_logPrefix, " ERROR: ", message);
}
};Operator Overloading
class CPrice
{
public:
double value;
CPrice() : value(0.0) {}
CPrice(double v) : value(v) {}
// Binary operators
CPrice operator+(const CPrice &rhs) const { return CPrice(value + rhs.value); }
CPrice operator-(const CPrice &rhs) const { return CPrice(value - rhs.value); }
CPrice operator*(double factor) const { return CPrice(value * factor); }
CPrice operator/(double divisor) const { return CPrice(value / divisor); }
// Comparison operators
bool operator==(const CPrice &rhs) const { return MathAbs(value - rhs.value) < 0.000001; }
bool operator!=(const CPrice &rhs) const { return !(this == rhs); }
bool operator<(const CPrice &rhs) const { return value < rhs.value; }
bool operator>(const CPrice &rhs) const { return value > rhs.value; }
// Assignment operators
CPrice *operator=(const CPrice &rhs) { value = rhs.value; return &this; }
CPrice *operator+=(const CPrice &rhs) { value += rhs.value; return &this; }
// Unary operators
CPrice operator-() const { return CPrice(-value); }
CPrice operator++() { value += _Point; return this; } // prefix
CPrice operator++(int) { CPrice tmp = this; value += _Point; return tmp; } // postfix
// Indexing operator (for array-like objects)
// double operator[](int index) const { ... }
};Templates
// Function template
template<typename T>
T Max(T a, T b)
{
return (a > b) ? a : b;
}
// Class template
template<typename T>
class CStack
{
private:
T m_data[];
int m_top;
public:
CStack() : m_top(-1) { ArrayResize(m_data, 0); }
void Push(T item)
{
m_top++;
ArrayResize(m_data, m_top + 1);
m_data[m_top] = item;
}
T Pop()
{
if(m_top < 0) return (T)NULL;
T item = m_data[m_top];
m_top--;
ArrayResize(m_data, m_top + 1);
return item;
}
int Size() const { return m_top + 1; }
bool IsEmpty() const { return m_top < 0; }
};
// Usage
CStack<double> priceStack;
priceStack.Push(1.2345);
double val = priceStack.Pop();
int maxVal = Max<int>(10, 20); // explicit type
double maxD = Max(1.5, 2.5); // type deduction---
Trade Functions
CTrade Class
Include: #include <Trade/Trade.mqh>
Configuration
#include <Trade/Trade.mqh>
CTrade trade;
void OnInit()
{
trade.SetExpertMagicNumber(12345);
trade.SetDeviationInPoints(10);
trade.SetTypeFillingBySymbol(_Symbol); // Auto-detect filling (PREFERRED)
trade.SetAsyncMode(false); // true = non-blocking OrderSendAsync
}CTrade Methods Summary
| Method | Description |
|---|---|
Buy(vol, sym, price, sl, tp, comment) | Market buy (price=0 for current Ask) |
Sell(vol, sym, price, sl, tp, comment) | Market sell (price=0 for current Bid) |
BuyLimit(vol, price, sym, sl, tp, type_time, exp, comment) | Pending buy below market |
SellLimit(vol, price, sym, sl, tp, type_time, exp, comment) | Pending sell above market |
BuyStop(vol, price, sym, sl, tp, type_time, exp, comment) | Pending buy above market |
SellStop(vol, price, sym, sl, tp, type_time, exp, comment) | Pending sell below market |
PositionOpen(sym, type, vol, price, sl, tp, comment) | Open position (alternative) |
PositionModify(sym_or_ticket, sl, tp) | Modify SL/TP |
PositionClose(sym_or_ticket, deviation) | Close entire position |
PositionClosePartial(sym_or_ticket, vol, deviation) | Partial close |
PositionCloseBy(ticket, ticketOpposite) | Close by opposite (hedging) |
OrderOpen(sym, type, vol, limitPrice, price, sl, tp, type_time, exp, comment) | Generic pending |
OrderModify(ticket, price, sl, tp, type_time, exp) | Modify pending |
OrderDelete(ticket) | Delete pending |
Result Access
if(trade.Buy(0.1, _Symbol))
{
uint retcode = trade.ResultRetcode();
string desc = trade.ResultRetcodeDescription();
ulong deal = trade.ResultDeal();
ulong order = trade.ResultOrder();
double volume = trade.ResultVolume();
double price = trade.ResultPrice();
}Native Trade Functions
Position / Order / History Access
// Positions (open trades)
int total = PositionsTotal();
for(int i = 0; i < total; i++) {
ulong ticket = PositionGetTicket(i);
// PositionGetString(POSITION_SYMBOL), PositionGetInteger(POSITION_TYPE)
// PositionGetDouble(POSITION_VOLUME/POSITION_PRICE_OPEN/POSITION_SL/POSITION_TP/POSITION_PROFIT)
// PositionGetInteger(POSITION_MAGIC/POSITION_IDENTIFIER)
}
PositionSelect(_Symbol); // Select by symbol (netting)
PositionSelectByTicket(ticket); // Select by ticket (hedging)
// Pending orders
int totalOrd = OrdersTotal();
for(int i = 0; i < totalOrd; i++) {
ulong ticket = OrderGetTicket(i);
// OrderGetString(ORDER_SYMBOL), OrderGetInteger(ORDER_TYPE)
// OrderGetDouble(ORDER_VOLUME_CURRENT/ORDER_PRICE_OPEN/ORDER_SL/ORDER_TP)
}
// History (deals + orders)
HistorySelect(from, to); // Select time range
HistorySelectByPosition(positionId); // Select by position ID
int deals = HistoryDealsTotal(); // HistoryDealGetTicket(i)
int orders = HistoryOrdersTotal(); // HistoryOrderGetTicket(i)
// HistoryDealGetDouble(ticket, DEAL_PROFIT/DEAL_COMMISSION/DEAL_SWAP)
// HistoryDealGetInteger(ticket, DEAL_TYPE/DEAL_ENTRY)OrderSend / OrderSendAsync
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = 0.1;
request.type = ORDER_TYPE_BUY;
request.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
request.sl = request.price - 500 * _Point;
request.tp = request.price + 1000 * _Point;
request.deviation = 10;
request.magic = 12345;
request.type_filling = ORDER_FILLING_FOK;
if(!OrderSend(request, result))
PrintFormat("Error %d: retcode=%u", GetLastError(), result.retcode);
// Async: OrderSendAsync(request, result) - handle in OnTradeTransaction()Production patterns (retry logic, filling policy detection, error handling, trailing stops, risk management): See trading-operations.md
---
Event Handlers
Complete Event Handler Table
| Handler | Context | Parameters | Description |
|---|---|---|---|
OnInit() | EA, Indicator, Script | None. Returns int (INIT_SUCCEEDED, INIT_FAILED, etc.) | Called once on load/recompile. Initialize resources. |
OnDeinit(const int reason) | EA, Indicator, Script | reason: REASON_PROGRAM, REASON_REMOVE, REASON_RECOMPILE, REASON_CHARTCHANGE, REASON_CHARTCLOSE, REASON_PARAMETERS, REASON_ACCOUNT, REASON_TEMPLATE, REASON_INITFAILED, REASON_CLOSE | Called on unload. Cleanup resources, delete objects. |
OnTick() | EA | None. Returns void | Called on every new tick. Main EA logic. |
OnTimer() | EA, Indicator | None. Returns void | Called on timer event. Set with EventSetTimer(seconds) or EventSetMillisecondTimer(ms). Kill with EventKillTimer(). |
OnTrade() | EA | None. Returns void | Called when a trade event occurs (order placed, modified, deleted, deal executed, position changed). No parameters about what changed. |
OnTradeTransaction() | EA | const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result | Called with details about each trade transaction. More granular than OnTrade(). |
OnBookEvent(const string &symbol) | EA | symbol: Symbol name | Called when Depth of Market changes. Must call MarketBookAdd(symbol) first. Call MarketBookRelease(symbol) in OnDeinit. |
OnChartEvent() | EA, Indicator | const int id, const long &lparam, const double &dparam, const string &sparam | Chart events: mouse clicks, key presses, object events, custom events. |
OnCalculate() | Indicator | Two forms (see below) | Called on every new tick. Main indicator calculation logic. |
OnTester() | EA | None. Returns double | Called after backtesting. Return custom optimization criterion. |
OnTesterInit() | EA | None. Returns void | Called before optimization starts (in the optimization agent). |
OnTesterPass() | EA | None. Returns void | Called when an optimization pass result is received. Use FrameFirst()/FrameNext(). |
OnTesterDeinit() | EA | None. Returns void | Called after optimization ends. Cleanup. |
OnTradeTransaction Example
void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request,
const MqlTradeResult &result)
{
// trans.type is ENUM_TRADE_TRANSACTION_TYPE:
// TRADE_TRANSACTION_ORDER_ADD - new order added
// TRADE_TRANSACTION_ORDER_UPDATE - order updated
// TRADE_TRANSACTION_ORDER_DELETE - order removed from active
// TRADE_TRANSACTION_DEAL_ADD - deal executed
// TRADE_TRANSACTION_DEAL_UPDATE - deal updated
// TRADE_TRANSACTION_DEAL_DELETE - deal deleted
// TRADE_TRANSACTION_HISTORY_ADD - order moved to history
// TRADE_TRANSACTION_HISTORY_UPDATE - history order updated
// TRADE_TRANSACTION_HISTORY_DELETE - history order deleted
// TRADE_TRANSACTION_POSITION - position changed (not related to deal)
// TRADE_TRANSACTION_REQUEST - trade request processed
switch(trans.type)
{
case TRADE_TRANSACTION_DEAL_ADD:
PrintFormat("Deal added: ticket=%I64u, symbol=%s, type=%d, volume=%.2f, price=%.5f",
trans.deal, trans.symbol, trans.deal_type, trans.volume, trans.price);
// For async trading: match via trans.order to your sent order
if(HistoryDealSelect(trans.deal))
{
double profit = HistoryDealGetDouble(trans.deal, DEAL_PROFIT);
long entry = HistoryDealGetInteger(trans.deal, DEAL_ENTRY);
if(entry == DEAL_ENTRY_IN)
Print("Position opened");
else if(entry == DEAL_ENTRY_OUT)
PrintFormat("Position closed, profit=%.2f", profit);
}
break;
case TRADE_TRANSACTION_REQUEST:
PrintFormat("Request processed: retcode=%u, order=%I64u",
result.retcode, result.order);
break;
}
}OnTester Example
double OnTester()
{
// Custom optimization criterion
// Return value is used as the optimization target when "Custom max" is selected
double profit = TesterStatistics(STAT_PROFIT);
double dd = TesterStatistics(STAT_EQUITY_DD_RELATIVE); // % max drawdown
int trades = (int)TesterStatistics(STAT_TRADES);
double pf = TesterStatistics(STAT_PROFIT_FACTOR);
// Example: profit factor weighted by number of trades, penalize low trade count
if(trades < 30 || dd > 30.0)
return 0.0;
// Calmar-like ratio: profit / drawdown
double criterion = (dd > 0.0) ? profit / dd : 0.0;
return criterion;
}OnCalculate Forms
// Form 1: Short form (for indicators that use price data directly)
int OnCalculate(const int rates_total,
const int prev_calculated,
const int begin,
const double &price[])
{
for(int i = (prev_calculated > 0 ? prev_calculated - 1 : 0); i < rates_total; i++)
{
// price[] corresponds to the "Apply to" setting (Close, Open, High, etc.)
Buffer[i] = price[i];
}
return rates_total;
}
// Form 2: Full form (access to OHLCV and time arrays)
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
for(int i = (prev_calculated > 0 ? prev_calculated - 1 : 0); i < rates_total; i++)
{
Buffer[i] = (high[i] + low[i] + close[i]) / 3.0; // Typical price
}
return rates_total;
}OnBookEvent Example
int OnInit()
{
MarketBookAdd(_Symbol); // Subscribe to DOM updates
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
MarketBookRelease(_Symbol); // Unsubscribe from DOM
}
void OnBookEvent(const string &symbol)
{
if(symbol != _Symbol) return;
MqlBookInfo book[];
if(MarketBookGet(symbol, book))
{
for(int i = 0; i < ArraySize(book); i++)
{
PrintFormat("Type=%s, Price=%.5f, Volume=%.0f",
(book[i].type == BOOK_TYPE_SELL) ? "ASK" : "BID",
book[i].price, book[i].volume);
}
}
}---
Standard Library
Trade Classes (#include <Trade/...>)
| Class | Include | Purpose |
|---|---|---|
CTrade | <Trade/Trade.mqh> | Trade execution: market orders, pending orders, position/order management. |
CPositionInfo | <Trade/PositionInfo.mqh> | Query open position properties (symbol, type, volume, SL, TP, profit, etc.). |
COrderInfo | <Trade/OrderInfo.mqh> | Query pending order properties. |
CDealInfo | <Trade/DealInfo.mqh> | Query historical deal properties. |
CSymbolInfo | <Trade/SymbolInfo.mqh> | Symbol properties: bid, ask, point, digits, spread, volume limits, session times, etc. |
CAccountInfo | <Trade/AccountInfo.mqh> | Account properties: balance, equity, margin, free margin, leverage, etc. |
CTerminalInfo | <Trade/TerminalInfo.mqh> | Terminal info: connected, trade allowed, community account, path, etc. |
#include <Trade/Trade.mqh>
#include <Trade/PositionInfo.mqh>
#include <Trade/SymbolInfo.mqh>
#include <Trade/AccountInfo.mqh>
CTrade trade;
CPositionInfo posInfo;
CSymbolInfo symInfo;
CAccountInfo accInfo;
void OnInit()
{
symInfo.Name(_Symbol);
trade.SetExpertMagicNumber(12345);
trade.SetTypeFillingBySymbol(_Symbol);
PrintFormat("Balance=%.2f, Leverage=%d", accInfo.Balance(), accInfo.Leverage());
PrintFormat("Point=%.5f, Digits=%d, Spread=%d",
symInfo.Point(), symInfo.Digits(), symInfo.Spread());
}
void OnTick()
{
symInfo.RefreshRates(); // Must refresh before accessing prices
double ask = symInfo.Ask();
double bid = symInfo.Bid();
// Iterate open positions
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(posInfo.SelectByIndex(i))
{
if(posInfo.Symbol() == _Symbol && posInfo.Magic() == 12345)
{
PrintFormat("Pos: type=%s, vol=%.2f, profit=%.2f",
posInfo.TypeDescription(), posInfo.Volume(), posInfo.Profit());
}
}
}
}Indicator Classes (#include <Indicators/...>)
Trend Indicators
| Class | Indicator |
|---|---|
CiMA | Moving Average |
CiADX | Average Directional Index |
CiBands | Bollinger Bands |
CiIchimoku | Ichimoku Kinko Hyo |
CiParabolicSAR | Parabolic SAR |
CiStdDev | Standard Deviation |
CiDEMA | Double Exponential MA |
CiTEMA | Triple Exponential MA |
CiFrAMA | Fractal Adaptive MA |
CiAMA | Adaptive MA |
CiVIDyA | Variable Index Dynamic Average |
CiEnvelopes | Envelopes |
Oscillators
| Class | Indicator |
|---|---|
CiMACD | MACD |
CiRSI | Relative Strength Index |
CiStochastic | Stochastic Oscillator |
CiCCI | Commodity Channel Index |
CiATR | Average True Range |
CiMomentum | Momentum |
CiOsMA | Moving Average of Oscillator |
CiWPR | Williams' Percent Range |
CiRVI | Relative Vigor Index |
CiForce | Force Index |
CiDeMarker | DeMarker |
CiBearsPower | Bears Power |
CiBullsPower | Bulls Power |
CiTriX | Triple Exponential MA Oscillator |
CiAO | Awesome Oscillator |
CiAC | Accelerator Oscillator |
Volume Indicators
| Class | Indicator |
|---|---|
CiVolumes | Volumes |
CiOBV | On Balance Volume |
CiMFI | Money Flow Index |
CiAD | Accumulation/Distribution |
Usage Pattern
#include <Indicators/Trend.mqh>
#include <Indicators/Oscilators.mqh>
CiMA ma;
CiRSI rsi;
CiMACD macd;
int OnInit()
{
// Create(symbol, timeframe, period, shift, method, applied_price)
if(!ma.Create(_Symbol, PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE))
return INIT_FAILED;
// Create(symbol, timeframe, period, applied_price)
if(!rsi.Create(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE))
return INIT_FAILED;
// Create(symbol, timeframe, fast_ema, slow_ema, signal, applied_price)
if(!macd.Create(_Symbol, PERIOD_CURRENT, 12, 26, 9, PRICE_CLOSE))
return INIT_FAILED;
return INIT_SUCCEEDED;
}
void OnTick()
{
ma.Refresh(); // Must call Refresh() on each tick
rsi.Refresh();
macd.Refresh();
double maValue = ma.Main(0); // Main(index) - 0 = current bar
double maValue1 = ma.Main(1); // Previous bar
double rsiValue = rsi.Main(0);
double macdMain = macd.Main(0); // MACD main line
double macdSignal = macd.Signal(0); // MACD signal line
// For Bollinger Bands
// CiBands bands;
// bands.Create(_Symbol, PERIOD_CURRENT, 20, 0, 2.0, PRICE_CLOSE);
// bands.Refresh();
// double upper = bands.Upper(0);
// double base = bands.Base(0);
// double lower = bands.Lower(0);
}Other Standard Library Classes
| Class | Include | Purpose |
|---|---|---|
CObject | <Object.mqh> | Base class for all standard library objects. |
CArrayInt | <Arrays/ArrayInt.mqh> | Dynamic array of int. |
CArrayDouble | <Arrays/ArrayDouble.mqh> | Dynamic array of double. |
CArrayString | <Arrays/ArrayString.mqh> | Dynamic array of string. |
CArrayObj | <Arrays/ArrayObj.mqh> | Dynamic array of CObject pointers. |
CList | <Arrays/List.mqh> | Doubly-linked list of CObject pointers. |
CString | <Strings/String.mqh> | String wrapper with utility methods. |
CFile | <Files/File.mqh> | File operations (base class for CFileTxt, CFileBin). |
CCanvas | <Canvas/Canvas.mqh> | Pixel-level drawing on charts (for custom UIs). |
---
Key Enumerations
ENUM_TRADE_REQUEST_ACTIONS
| Value | Description |
|---|---|
TRADE_ACTION_DEAL | Place a market order (immediate execution). |
TRADE_ACTION_PENDING | Place a pending order. |
TRADE_ACTION_SLTP | Modify SL/TP of an existing position. |
TRADE_ACTION_MODIFY | Modify parameters of a pending order. |
TRADE_ACTION_REMOVE | Delete a pending order. |
TRADE_ACTION_CLOSE_BY | Close a position by an opposite one (hedging only). |
ENUM_ORDER_TYPE
| Value | Description |
|---|---|
ORDER_TYPE_BUY | Market buy order. |
ORDER_TYPE_SELL | Market sell order. |
ORDER_TYPE_BUY_LIMIT | Pending buy limit (below market). |
ORDER_TYPE_SELL_LIMIT | Pending sell limit (above market). |
ORDER_TYPE_BUY_STOP | Pending buy stop (above market). |
ORDER_TYPE_SELL_STOP | Pending sell stop (below market). |
ORDER_TYPE_BUY_STOP_LIMIT | Pending buy stop-limit. When price reaches stop, a buy limit is placed. |
ORDER_TYPE_SELL_STOP_LIMIT | Pending sell stop-limit. When price reaches stop, a sell limit is placed. |
ENUM_POSITION_TYPE
| Value | Description |
|---|---|
POSITION_TYPE_BUY | Long position. |
POSITION_TYPE_SELL | Short position. |
ENUM_ORDER_TYPE_FILLING
| Value | Description |
|---|---|
ORDER_FILLING_FOK | Fill or Kill. Entire volume must be filled or order is cancelled. |
ORDER_FILLING_IOC | Immediate or Cancel. Fill what's available, cancel remainder. |
ORDER_FILLING_RETURN | Return. Used for market-making; partial fills return remainder as pending. |
Important: Always detect the correct filling mode per symbol:
ENUM_ORDER_TYPE_FILLING GetFillingMode(string symbol)
{
long fillMode = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE);
if((fillMode & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK)
return ORDER_FILLING_FOK;
if((fillMode & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC)
return ORDER_FILLING_IOC;
return ORDER_FILLING_RETURN;
}Or simply use CTrade::SetTypeFillingBySymbol(_Symbol).
ENUM_SYMBOL_INFO_DOUBLE (Selected)
| Value | Description |
|---|---|
SYMBOL_BID | Current Bid price. |
SYMBOL_ASK | Current Ask price. |
SYMBOL_POINT | Point value (e.g., 0.00001 for 5-digit). |
SYMBOL_TRADE_TICK_VALUE | Value of one tick in account currency. |
SYMBOL_TRADE_TICK_SIZE | Minimum price change. |
SYMBOL_TRADE_CONTRACT_SIZE | Contract size (e.g., 100000 for standard forex lot). |
SYMBOL_VOLUME_MIN | Minimum volume for a deal (e.g., 0.01). |
SYMBOL_VOLUME_MAX | Maximum volume for a deal. |
SYMBOL_VOLUME_STEP | Volume step (e.g., 0.01). |
SYMBOL_TRADE_STOPS_LEVEL | Minimum distance in points for SL/TP from current price. |
SYMBOL_TRADE_FREEZE_LEVEL | Distance in points within which order modification/deletion is frozen. |
ENUM_ACCOUNT_INFO_DOUBLE (Selected)
| Value | Description |
|---|---|
ACCOUNT_BALANCE | Account balance. |
ACCOUNT_EQUITY | Account equity. |
ACCOUNT_PROFIT | Current floating profit/loss. |
ACCOUNT_MARGIN | Margin currently used. |
ACCOUNT_MARGIN_FREE | Free margin available. |
ACCOUNT_MARGIN_LEVEL | Margin level as percentage (Equity / Margin * 100). |
---
MQL5-Specific Features
Database (SQLite)
MQL5 has built-in SQLite database support for persistent structured storage.
int db = INVALID_HANDLE;
int OnInit()
{
// Open or create database in the common files folder
db = DatabaseOpen("my_ea_data.sqlite", DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE |
DATABASE_OPEN_COMMON);
if(db == INVALID_HANDLE)
{
Print("DB open failed: ", GetLastError());
return INIT_FAILED;
}
// Create table
if(!DatabaseExecute(db,
"CREATE TABLE IF NOT EXISTS trades ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"symbol TEXT NOT NULL,"
"type INTEGER,"
"volume REAL,"
"open_price REAL,"
"close_price REAL,"
"profit REAL,"
"open_time TEXT,"
"close_time TEXT)"))
{
Print("CREATE TABLE failed: ", GetLastError());
}
return INIT_SUCCEEDED;
}
void InsertTrade(string symbol, int type, double volume, double price)
{
string sql = StringFormat(
"INSERT INTO trades (symbol, type, volume, open_price, open_time) "
"VALUES ('%s', %d, %.2f, %.5f, '%s')",
symbol, type, volume, price, TimeToString(TimeCurrent()));
if(!DatabaseExecute(db, sql))
Print("INSERT failed: ", GetLastError());
}
void ReadTrades()
{
int request = DatabasePrepare(db, "SELECT id, symbol, profit FROM trades WHERE profit > 0");
if(request == INVALID_HANDLE)
{
Print("Prepare failed: ", GetLastError());
return;
}
int id;
string symbol;
double profit;
while(DatabaseRead(request))
{
DatabaseColumnInteger(request, 0, id);
DatabaseColumnText(request, 1, symbol);
DatabaseColumnDouble(request, 2, profit);
PrintFormat("Trade #%d: %s, Profit=%.2f", id, symbol, profit);
}
DatabaseFinalize(request);
}
// Transactions for batch operations
void BatchInsert()
{
DatabaseTransactionBegin(db);
for(int i = 0; i < 100; i++)
{
if(!DatabaseExecute(db, StringFormat("INSERT INTO trades (symbol) VALUES ('ITEM_%d')", i)))
{
DatabaseTransactionRollback(db);
return;
}
}
DatabaseTransactionCommit(db);
}
void OnDeinit(const int reason)
{
if(db != INVALID_HANDLE)
DatabaseClose(db);
}Network Sockets
Available only in EAs and scripts (NOT indicators). Allows raw TCP and TLS connections.
int OnInit()
{
// Create socket
int socket = SocketCreate();
if(socket == INVALID_HANDLE)
{
Print("SocketCreate failed: ", GetLastError());
return INIT_FAILED;
}
// Connect to server (TCP)
if(!SocketConnect(socket, "api.example.com", 443, 5000)) // host, port, timeout_ms
{
Print("SocketConnect failed: ", GetLastError());
SocketClose(socket);
return INIT_FAILED;
}
// TLS handshake for HTTPS
if(!SocketTlsHandshake(socket, "api.example.com"))
{
Print("TLS handshake failed: ", GetLastError());
SocketClose(socket);
return INIT_FAILED;
}
// Build HTTP request
string request = "GET /data HTTP/1.1\r\n"
"Host: api.example.com\r\n"
"Connection: close\r\n"
"\r\n";
// Send via TLS
uchar reqData[];
StringToCharArray(request, reqData, 0, WHOLE_ARRAY, CP_UTF8);
int sent = SocketTlsSend(socket, reqData, ArraySize(reqData) - 1); // exclude null terminator
if(sent <= 0)
{
Print("SocketTlsSend failed: ", GetLastError());
SocketClose(socket);
return INIT_FAILED;
}
// Read response
uchar response[];
string result = "";
uint timeout = 5000;
do
{
uint len = SocketTlsReadAvailable(socket, response, timeout);
if(len > 0)
result += CharArrayToString(response, 0, WHOLE_ARRAY, CP_UTF8);
}
while(SocketTlsRead(socket, response, 1024, timeout) > 0);
Print("Response: ", result);
SocketClose(socket);
return INIT_SUCCEEDED;
}
// For non-TLS (plain TCP):
// SocketSend(socket, data, dataLen);
// SocketRead(socket, response, responseLen, timeout);Resources
Embed external files (images, sounds, data) directly into the compiled EX5 file.
// Embed resource at compile time
#resource "\\Images\\logo.bmp" // Relative to source file
#resource "\\Sounds\\alert.wav"
#resource "\\Files\\config.txt"
void OnInit()
{
// Access resource as "::Images\\logo.bmp"
ObjectCreate(0, "Logo", OBJ_BITMAP_LABEL, 0, 0, 0);
ObjectSetString(0, "Logo", OBJPROP_BMPFILE, "::Images\\logo.bmp");
// Play sound from resource
PlaySound("::Sounds\\alert.wav");
// Create dynamic resource from pixel data
uint pixels[];
int width = 100, height = 100;
ArrayResize(pixels, width * height);
for(int i = 0; i < ArraySize(pixels); i++)
pixels[i] = ColorToARGB(clrBlue, 200);
ResourceCreate("::DynImage", pixels, width, height, 0, 0, width, COLOR_FORMAT_ARGB_NORMALIZE);
// Read an image resource
uint data[];
int w, h;
ResourceReadImage("::Images\\logo.bmp", data, w, h);
// Save resource to file
ResourceSave("::Images\\logo.bmp", "SavedFiles\\logo.bmp");
// Free dynamic resource
ResourceFree("::DynImage");
}OpenCL
GPU-accelerated parallel computing for intensive calculations (optimization, neural networks, etc.).
void RunOpenCL()
{
// 1. Create context (0 = default device)
int clContext = CLContextCreate(CL_USE_GPU_ONLY);
if(clContext == INVALID_HANDLE)
{
Print("CLContextCreate failed");
return;
}
// 2. Create program from OpenCL kernel source
string kernelSource =
"__kernel void multiply(__global double *a, __global double *b, __global double *c, int n) {"
" int i = get_global_id(0);"
" if(i < n) c[i] = a[i] * b[i];"
"}";
string buildLog;
int clProgram = CLProgramCreate(clContext, kernelSource, buildLog);
if(clProgram == INVALID_HANDLE)
{
Print("CLProgramCreate failed: ", buildLog);
CLContextFree(clContext);
return;
}
// 3. Create kernel
int clKernel = CLKernelCreate(clProgram, "multiply");
// 4. Prepare data
int n = 1000;
double a[], b[], c[];
ArrayResize(a, n);
ArrayResize(b, n);
ArrayResize(c, n);
for(int i = 0; i < n; i++) { a[i] = i * 1.5; b[i] = i * 2.0; }
// 5. Create buffers and write data
int bufA = CLBufferCreate(clContext, n * sizeof(double), CL_MEM_READ_ONLY);
int bufB = CLBufferCreate(clContext, n * sizeof(double), CL_MEM_READ_ONLY);
int bufC = CLBufferCreate(clContext, n * sizeof(double), CL_MEM_WRITE_ONLY);
CLBufferWrite(bufA, a);
CLBufferWrite(bufB, b);
// 6. Set kernel arguments
CLSetKernelArgMem(clKernel, 0, bufA);
CLSetKernelArgMem(clKernel, 1, bufB);
CLSetKernelArgMem(clKernel, 2, bufC);
CLSetKernelArg(clKernel, 3, n);
// 7. Execute
uint globalWorkSize[1] = {(uint)n};
CLExecute(clKernel, 1, 0, globalWorkSize);
// 8. Read results
CLBufferRead(bufC, c);
// 9. Cleanup
CLBufferFree(bufA);
CLBufferFree(bufB);
CLBufferFree(bufC);
CLKernelFree(clKernel);
CLProgramFree(clProgram);
CLContextFree(clContext);
}WebRequest (MQL5 Variant)
Two function signatures. URL must be whitelisted in Tools > Options > Expert Advisors. Only available in EAs and scripts, NOT indicators or Strategy Tester.
// Form 1: Custom headers
void WebRequestWithHeaders()
{
string url = "https://api.example.com/webhook";
string headers = "Content-Type: application/json\r\n"
"Authorization: Bearer YOUR_TOKEN\r\n";
int timeout = 5000;
// Build JSON body
string jsonBody = StringFormat(
"{\"symbol\":\"%s\",\"price\":%.5f,\"action\":\"BUY\"}",
_Symbol, SymbolInfoDouble(_Symbol, SYMBOL_ASK));
char postData[];
char resultData[];
string resultHeaders;
StringToCharArray(jsonBody, postData, 0, WHOLE_ARRAY, CP_UTF8);
// Remove null terminator
ArrayResize(postData, ArraySize(postData) - 1);
int statusCode = WebRequest(
"POST", // string method
url, // string url
headers, // string headers
timeout, // int timeout
postData, // char &data[]
resultData, // char &result[]
resultHeaders // string &result_headers
);
if(statusCode == -1)
{
Print("WebRequest error: ", GetLastError());
Print("Add URL to allowed list: Tools > Options > Expert Advisors");
return;
}
string response = CharArrayToString(resultData, 0, WHOLE_ARRAY, CP_UTF8);
PrintFormat("HTTP %d: %s", statusCode, response);
}
// Form 2: Simple headers as string (cookie, referer)
void WebRequestSimple()
{
string cookie = "", headers = "", response = "";
char postData[], resultData[];
string resultHeaders;
int statusCode = WebRequest(
"GET", // method
"https://api.example.com/data", // url
cookie, // cookie
"", // referer
5000, // timeout
postData, // data (empty for GET)
0, // data_size
resultData, // result
resultHeaders // result_headers
);
if(statusCode == 200)
{
response = CharArrayToString(resultData, 0, WHOLE_ARRAY, CP_UTF8);
Print("Response: ", response);
}
}---
MQL5 vs MQL4 Summary Table
| Feature | MQL4 | MQL5 |
|---|---|---|
| OOP | Limited (classes added later) | Full OOP: classes, inheritance, interfaces, templates, operator overloading |
| Account model | Hedging only | Netting + Hedging |
| Trade model | Orders only (OrderSend, OrderModify, OrderClose) | Orders + Deals + Positions (CTrade, OrderSend with MqlTradeRequest) |
| Standard Library | Minimal | Comprehensive (Trade, Indicators, Arrays, Canvas, etc.) |
| Event handlers | OnInit, OnDeinit, OnTick, OnTimer, OnChartEvent, OnCalculate, OnTester | All MQL4 handlers + OnTrade, OnTradeTransaction, OnBookEvent, OnTesterInit/Pass/Deinit |
| Indicator buffers | Max 8 | Max 512 |
| Draw styles | 6 basic (LINE, SECTION, HISTOGRAM, ARROW, ZIGZAG, NONE) | 18 styles including color variants (DRAW_COLOR_LINE, DRAW_FILLING, DRAW_BARS, DRAW_CANDLES, etc.) |
| OpenCL | Not available | Full OpenCL support for GPU computing |
| SQLite database | Not available | Built-in DatabaseOpen, DatabaseExecute, DatabasePrepare, etc. |
| Network sockets | Not available | TCP + TLS via SocketCreate, SocketConnect, SocketTlsHandshake, etc. |
| WebRequest | WebRequest() (same function) | WebRequest() with two signatures (custom headers or simple) |
| Multi-currency testing | Limited | Full multi-symbol/multi-timeframe backtesting |
| Cloud optimization | Not available | MQL5 Cloud Network for distributed optimization |
| Resources | #resource for images | #resource for any file type + ResourceCreate/ResourceReadImage/ResourceSave/ResourceFree |
| Custom optimization | OnTester() returns double | OnTester() + OnTesterInit/Pass/Deinit + FrameFirst/FrameNext/FrameAdd for custom criteria and frame communication |
| Timeseries access | iClose(), iOpen(), etc. with shift | CopyClose(), CopyOpen(), etc. copying into arrays, or iClose() with MQL4-compatible syntax |
| Indicator access | iMA(), iRSI() return value directly | iMA(), iRSI() return handle (int). Use CopyBuffer() to get values, or use indicator classes. |
Indicator Handle Pattern (MQL5)
int maHandle;
double maBuffer[];
int OnInit()
{
maHandle = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE);
if(maHandle == INVALID_HANDLE) return INIT_FAILED;
ArraySetAsSeries(maBuffer, true); // Index 0 = newest bar
return INIT_SUCCEEDED;
}
void OnTick()
{
if(CopyBuffer(maHandle, 0, 0, 3, maBuffer) < 3) return;
// maBuffer[0] = current bar MA value
// maBuffer[1] = previous bar MA value
// maBuffer[2] = two bars ago
}
void OnDeinit(const int reason)
{
IndicatorRelease(maHandle); // Free the indicator handle
}Security & Licensing
Table of Contents
- Account-Based Licensing
- Server-Side License Validation
- Expiration Date Checks
- Anti-Decompilation Techniques
- Distribution Best Practices
- Node.js License Server Example
- MQL4 Specific Considerations
- Complete License System Architecture
---
Account-Based Licensing
Simple Multi-Account Check
bool CheckAccountLicense() {
long currentAccount = AccountInfoInteger(ACCOUNT_LOGIN);
long authorizedAccounts[] = {12345678, 87654321, 11223344};
for(int i = 0; i < ArraySize(authorizedAccounts); i++) {
if(currentAccount == authorizedAccounts[i]) return true;
}
Alert("Account ", currentAccount, " is not authorized.");
return false;
}Account + Broker Hash Check (More Secure)
Don't store plain account numbers in code. Hash account + broker + salt:
ulong HashString(string s) {
ulong hash = 5381;
for(int i = 0; i < StringLen(s); i++)
hash = ((hash << 5) + hash) + StringGetCharacter(s, i);
return hash;
}
bool ValidateLicense() {
long account = AccountInfoInteger(ACCOUNT_LOGIN);
string server = AccountInfoString(ACCOUNT_SERVER);
string raw = IntegerToString(account) + "|" + server + "|SECRET_SALT";
ulong hash = HashString(raw);
ulong validHashes[] = {0xA1B2C3D4E5F6, 0x1234567890AB};
for(int i = 0; i < ArraySize(validHashes); i++)
if(hash == validHashes[i]) return true;
return false;
}---
Server-Side License Validation
Implementation Pattern
input string InpLicenseKey = "";
bool ValidateLicenseOnServer() {
long account = AccountInfoInteger(ACCOUNT_LOGIN);
string broker = AccountInfoString(ACCOUNT_SERVER);
string eaName = MQLInfoString(MQL_PROGRAM_NAME);
string json = "{";
json += "\"license_key\":\"" + InpLicenseKey + "\",";
json += "\"account\":" + IntegerToString(account) + ",";
json += "\"broker\":\"" + broker + "\",";
json += "\"ea\":\"" + eaName + "\"";
json += "}";
// Send to server, check response
// Handle network failures (fail-open vs fail-closed decision)
}Fail-Open vs Fail-Closed
- Fail-open: Allow trading if server unreachable (better UX, weaker security)
- Fail-closed: Block trading if server unreachable (stronger security, risk of false blocks)
- Recommended: Fail-open with cached expiry and offline grace period
Caching License Locally
Use GlobalVariableSet to cache server expiry:
GlobalVariableSet("EA_LICENSE_EXPIRY", (double)StringToTime(expiry));On next check, verify cached expiry first, re-validate with server periodically.
---
Expiration Date Checks
Hardcoded Expiration
datetime expiryDate = D'2025.12.31 23:59:59';
if(TimeCurrent() > expiryDate) {
Alert("EA has expired. Please renew.");
return INIT_FAILED;
}Server-Cached Expiration
Check GlobalVariable cache first, re-validate on timer.
Demo Mode with Limited Features
After trial period, restrict to demo accounts or limited lot sizes.
Integration in OnInit
int OnInit() {
if(!CheckAccountLicense()) return INIT_FAILED;
if(!CheckExpiration()) return INIT_FAILED;
EventSetTimer(3600); // Re-check every hour
return INIT_SUCCEEDED;
}
void OnTimer() {
if(!CheckExpiration()) ExpertRemove();
}---
Anti-Decompilation Techniques
Protection Layers Table
| Layer | Technique | Protection Level | Notes |
|---|---|---|---|
| 1 | Compile to .ex4/.ex5 | Basic | Strips variable/function names |
| 2 | Code obfuscation | Low | Complex expressions, dummy code |
| 3 | String encryption | Medium | Hide URLs, keys, constants |
| 4 | MQL5 Cloud Protector | High | Asymmetric encryption, native code |
| 5 | DLL offloading | High | Core logic in C++ DLL |
| 6 | Server-side logic | Highest | Core signals never leave server |
String Obfuscation Example
string DecryptString(int key) {
uchar encrypted[] = {104,116,116,112,115,58,47,47};
string result = "";
for(int i = 0; i < ArraySize(encrypted); i++)
result += CharToString((uchar)(encrypted[i] ^ (key % 256)));
return result;
}MQL5 Cloud Protector
- Available in MetaEditor: Tools > MQL5 Cloud Protector
- Sends compiled .ex5 to MetaQuotes cloud
- Applies asymmetric encryption + unique key signing
- Source code never leaves your machine
- Same protection as MQL5 Market store
- Files NOT bound to specific computer (unlike Market)
- Free to use
---
Distribution Best Practices
Do's
- Distribute only .ex4/.ex5 compiled files
- Use MQL5 Market for built-in DRM (hardware + account binding)
- Combine account binding + server validation + Cloud Protector
- Include version checking in licensing server to force updates
- Use unique magic numbers or comments to identify your EA
Don'ts
- Never distribute .mq4/.mq5 source files
- Never hardcode API keys or server passwords in source
- Don't rely on a single protection layer
- Don't use simple string comparisons for license keys
---
Node.js License Server Example
Endpoint: POST /api/validate
app.post('/api/validate', (req, res) => {
const { license_key, account, broker, ea, version } = req.body;
// Look up license in database
const license = db.findLicense(license_key);
if(!license) return res.json({ valid: false, message: "Invalid key" });
if(license.expired) return res.json({ valid: false, message: "License expired" });
if(license.maxAccounts && license.accounts.length >= license.maxAccounts) {
if(!license.accounts.includes(account))
return res.json({ valid: false, message: "Max accounts reached" });
}
// Register account if new
if(!license.accounts.includes(account)) {
license.accounts.push(account);
db.updateLicense(license);
}
res.json({
valid: true,
expiry: license.expiryDate,
features: license.features
});
});---
MQL4 Specific Considerations
Account Number Check (MQL4)
bool CheckLicense() {
int account = AccountNumber(); // MQL4 function
// ... same logic but with int instead of long
}Hardware ID (MQL4 via DLL)
Can use Windows API via DLL to get hardware identifiers for machine-specific licensing.
---
Complete License System Architecture
EA (MQL5) License Server (Node.js)
| |
|-- POST /validate ----------->|
| {key, account, broker} |-- Check DB
| |-- Validate
|<-- {valid, expiry, features}-|
| |
|-- Cache expiry locally |
|-- Re-check every hour |
| |
|-- POST /heartbeat ---------->| (optional)
| {account, balance, status} |-- Track usage