---
name: mql-developer
description: >
  Comprehensive MQL4/MQL5 development for MetaTrader 4 and MetaTrader 5 platforms.
  Use when writing, reviewing, debugging, or architecting: Expert Advisors (EAs),
  custom indicators, scripts, libraries (.mqh), graphical panels, or any MQL code.
  Also use for: order/position management, risk management, backtesting strategies,
  communication with external APIs (WebRequest, REST, JSON), inter-program communication,
  code protection/licensing, and MQL4-to-MQL5 migration. Covers the full MQL ecosystem
  including trading automation, technical analysis, UI panels, and server integration.
---

# 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](references/mql4-reference.md) |
| MQL5 syntax, OOP, CTrade, Standard Library | [references/mql5-reference.md](references/mql5-reference.md) |
| Project structure, EA architecture, design patterns | [references/architecture-patterns.md](references/architecture-patterns.md) |
| Orders, positions, risk management, trailing stops | [references/trading-operations.md](references/trading-operations.md) |
| Custom indicators, UI panels, scripts, chart objects | [references/indicators-and-ui.md](references/indicators-and-ui.md) |
| WebRequest, JSON, REST API, Node.js integration | [references/external-communication.md](references/external-communication.md) |
| Strategy Tester, optimization, walk-forward, Monte Carlo | [references/backtesting.md](references/backtesting.md) |
| Code protection, licensing, anti-decompilation | [references/security-licensing.md](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. Use `NormalizeDouble()` 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()-1` down to `0` when closing orders (MQL4)
- **ECN brokers**: Some require two-step: `OrderSend()` without SL/TP, then `OrderModify()`
- **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

### 16. Non-Existent `ACCOUNT_MARGIN_MODE_NETTING` in MT5
**Bug pattern:** Using `ACCOUNT_MARGIN_MODE_NETTING` or `ACCOUNT_MARGIN_MODE_RETAIL_NET` in MT5 code — these constants **do not exist** in the MT5 API.
**Root cause:** `ACCOUNT_MARGIN_MODE_NETTING = 1` exists in MT4 but NOT in MT5. The MT5 equivalent is `(ENUM_ACCOUNT_MARGIN_MODE)1` (ACCOUNT_MARGIN_MODE_RETAIL_NETTING).
**Wrong:**
```mql5
bool isNetting = (marginMode == ACCOUNT_MARGIN_MODE_NETTING);     // COMPILE ERROR in MT5
bool isNetting = (marginMode == ACCOUNT_MARGIN_MODE_RETAIL_NET); // COMPILE ERROR in MT5
```
**Correct:**
```mql5
ENUM_ACCOUNT_MARGIN_MODE marginMode = (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
bool isNetting = (marginMode == (ENUM_ACCOUNT_MARGIN_MODE)1);  // 1 = retail netting account
```
**Also:** When replacing a magic number with an enum constant, always verify the constant actually exists in MT5 documentation — many MT4 enum values differ from MT5.

### 17. Dashboard UI Row Overlap When Adding New Sections
**Bug pattern:** Dashboard has existing static info rows (State, Pattern, Direction, Zone, SL, Trail, Spread, Age). Adding a new "INPUT" edit section below row 7 without adjusting the start position — the edit fields overlap and cover the static rows at the same Y coordinates.
**Example of wrong layout:**
```
y=30..184  Static rows 0-7 (8 rows × 22px spacing)
y=96       Edit rows START HERE ← CONFLICT: edit rows 0-2 overlap static rows 3-5
```
**Correct pattern:** When adding a new INPUT section to a dashboard that already has static rows, compute `editStartY` as the Y AFTER the last static row:
```mql5
// Static rows: 8 rows × 22px starting at y=30 → last static row ends at y=30+7*22=184
// INPUT section must start AFTER y=184
int editStartY = m_y + 232;  // safe gap after last static row (184 + 48px buffer)
```
**Also:** When Dashboard uses `UpdateDisplay` loop, ensure the loop count matches the values array size. If Age row was previously handled as a separate update outside the loop, merging it INTO the loop requires adding `values[7] = ageStr` and changing `for(i=0; i<7)` to `for(i=0; i<8)` — missing this causes Val6 (Spread) to be written twice and Val7 (Age) to overwrite Val6.

### 18. Dashboard Display Loop Writing Wrong Values (Spread/Age Same)
**Bug pattern:** Dashboard has a loop `for(i=0; i<7)` writing Val0-Val6, but a separate update block after the loop also writes `Val7 = spreadStr`, and ANOTHER separate block writes `Val8 = ageStr`. Since Val7 was already set to Spread by the first overwrite and then overwritten by Age (Val8 overwrites Val7 position), the final display shows Age in both Spread and Age positions, while Spread stays at Val6.
**Root cause:** Adding new rows (Age, new edit fields) without updating the loop bounds — old separate update code overwrites newer loop-assigned values.
**Fix:** Consolidate all row value updates into one loop with correct bounds:
```mql5
string values[8];  // all 8 rows including age
values[6] = spreadStr;
values[7] = ageStr;
for(int i = 0; i < 8; i++)  // NOT 7
   ObjectSetString(0, "Val" + IntegerToString(i), OBJPROP_TEXT, values[i]);
```
**Also:** When the static row count changes (e.g., 7→8), update BOTH the `CreatePanel` row loop AND the `UpdateDisplay` value array + loop.

## Common AI-Generated MQL5 Bugs (Subagent Review Checklist)

### 21. FiboZone Persistence — Keep Zone Until New Qualified Bar
When implementing "keep zone until new zone arrives" logic: use `g_lastFiboZone` + `g_lastFiboAnchorTime` globals to persist the last valid FiboZone. On each bar: if qualified → compute new zone + persist it; if NOT qualified → redraw the persisted zone (don't delete). Always `DeleteAllObjects()` at bar start then redraw accordingly.
**MQL5 global struct gotcha:** `SFiboResult g_lastFiboZone = {};` at global scope is INVALID — `{}` not allowed at global scope. Use `SFiboResult g_lastFiboZone;` (zero-initialized automatically) or initialize in `OnInit()`.
**Struct declaration order:** MQL5 requires struct types be declared BEFORE any global variable using that type. Structs must appear above globals in the file.

### 22. ColorToARGB Return Type
`ColorToARGB()` returns `uint`, NOT `color`. Assigning directly to a `color` variable produces a compiler warning. Use `uint` for the variable holding `ColorToARGB` result, then cast if needed.

When reviewing any AI-generated MQL5 EA code (especially from subagents), check ALL of these before compiling:

### 1. Indicator Handle Leaks
**Bug pattern:** Using `ChartIndicatorDelete(0, ChartWindowFind(), handle)` to release handles — this is wrong and causes handle leaks.
**Correct pattern:** Use ONLY `IndicatorRelease(handle)` to free indicator handles. `ChartIndicatorDelete` deletes the chart indicator from a subwindow, not the handle itself.
**Code that leaks:**
```mql5
int handle = iATR(_Symbol, PERIOD_M5);
// ... use handle ...
ChartIndicatorDelete(0, ChartWindowFind(), handle); // WRONG
IndicatorRelease(handle);
```
**Always check:** Every `iATR`, `iMA`, `iRSI`, `iCustom` handle creation must have exactly one `IndicatorRelease()` call, and NO `ChartIndicatorDelete` calls on the handle.

### 2. Parameters Passed But Never Used in Calculation
**Bug pattern:** A function accepts a parameter (e.g., `engulfThreshold`) but never uses it in the body — the hardcoded value is used instead.
**Example:**
```mql5
// Function signature says engulfThreshold is used
bool DetectEngulfingBullish(string symbol, double engulfThreshold, int shift = 0)
   {
   // ...
   bool condition3 = (currBody >= prevBody); // engulfThreshold IGNORED!
   }
```
**Always check:** For every function parameter, grep for its name in the function body — verify it's actually used in a calculation or condition.

### 3. ATR Multiplier / ATR Period Not Stored
**Bug pattern:** `CTrailStop::Init()` receives `atrMultiplier` and `atrPeriod` parameters but never stores them as member variables, so `CalculateTrailDistance()` uses a hardcoded default instead.
**Correct pattern:**
```mql5
void CTrailStop::Init(...) {
   m_atrMultiplier = atrMultiplier;  // MUST store
   m_atrPeriod     = atrPeriod;      // MUST store
}

void CTrailStop::CalculateTrailDistance() {
   // Use stored values
   m_trailDistance = m_atrValue * m_atrMultiplier;
}
```
**Always check:** When a class has an `Init()` that receives parameters meant for later use, verify those parameters are assigned to member variables.

### 4. PositionSelect() in Hedging Mode
**Bug pattern:** Using `PositionSelect(symbol)` which in hedging mode selects the **first** position found — not necessarily the one the EA just opened.
**Correct pattern:** Always use `PositionSelectByTicket(ticket)` for precise selection. If ticket is unknown yet, scan all positions with magic number filter:
```mql5
for(int i = PositionsTotal() - 1; i >= 0; i--) {
   if(PositionGetSymbol(i) != _Symbol) continue;
   if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
   // Found our position
   ticket = PositionGetTicket(i);
}
```
**Always check:** Any `PositionSelect(_Symbol)` call in an EA that manages multiple positions or uses hedging mode is a bug.

### 5. Hardcoded Filling Policy
**Bug pattern:** `m_trade.SetTypeFilling(ORDER_FILLING_FOK)` hardcoded — this will cause order rejection on IOC-only brokers.
**Correct pattern:** Auto-detect per symbol:
```mql5
ENUM_ORDER_TYPE_FILLING DetectFillingMode(string symbol) {
   long filling = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE);
   if((filling & SYMBOL_FILLING_FOK) != 0) return ORDER_FILLING_FOK;
   if((filling & SYMBOL_FILLING_IOC) != 0) return ORDER_FILLING_IOC;
   return ORDER_FILLING_RETURN;
}
```
**Always check:** Any hardcoded `ORDER_FILLING_FOK` or `ORDER_FILLING_IOC` without detection.

### 6. Missing New Bar Check
**Bug pattern:** `OnTick()` processes on every single tick — this causes duplicate signal processing, especially for pattern-based EAs that check bar patterns.
**Correct pattern:** Always gate `OnTick()` with a new bar check:
```mql5
static datetime lastBarTime = 0;
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
if(currentBarTime == lastBarTime) return;
lastBarTime = currentBarTime;
// ... rest of OnTick logic
```
**Always check:** If the EA uses bar patterns (engulfing, pin bar, etc.) and doesn't have a new bar gate, it's a bug.

### 7. Hardcoded Magic Number or Lot Size
**Bug pattern:** `0.1` hardcoded as lot size, or `12345` as magic without an input parameter.
**Always check:** All trade-affecting values should be `input` parameters — lot size, magic number, slippage.

### 8. State Machine One-Shot Guards (TS, Breakeven, etc.)
**Bug pattern:** State machine activates a one-shot action (e.g., trailing stop, breakeven) inside a state handler that runs every tick — without an `IsActive()` or `IsActivated()` guard, the action re-triggers on every tick.
**Example:**
```mql5
// HandleTSActiveState runs every tick while in STATE_TS_ACTIVE
void HandleTSActiveState(double currentPrice) {
    // WRONG: no guard — re-activates every tick!
    if(moveDistance >= trailDist * 0.5) {
        g_trailStop.ActivateTS(...);
    }
    // ...
}
```
**Correct pattern:** Check `IsActive()` before activating:
```mql5
if(!g_trailStop.IsActive() && moveDistance >= trailDist * 0.5) {
    g_trailStop.ActivateTS(...);
}
```
**Also check:** For classes with `CalculateXxx()` methods that populate member variables — verify `CalculateXxx()` is actually called before `Activate()` uses those members. E.g., `CTrailStop.CalculateTrailDistance()` must be called before `ActivateTS()` so `m_trailDistance` is populated.
**Always check:** Every one-shot action in a state handler must have a guard (`IsActive()`, `IsEnabled()`, etc.) preventing re-trigger.

### 9. Unused Global Variables
**Bug pattern:** Global variables are declared but never referenced anywhere — indicates dead code or broken wiring.
**Example:** `int g_atrHandle = INVALID_HANDLE;` declared globally but all indicator handles are created/released locally inside functions.
**Always check:** `grep` every global `g_` variable name back through the entire file — if it's only in its declaration and `OnInit`/`OnDeinit`, it's likely unused. Also check for globals that ARE set in `OnInit` but never read anywhere.

### 10. Dead Code After Early Return
**Bug pattern:** Code after a conditional block that always returns — unreachable dead code that compiles but never executes.
**Example:**
```mql5
if(isNetting) {
    // ... do stuff ...
    return true;   // always returns here
}
// DEAD CODE BELOW — this block is unreachable
if(m_primaryTicket == 0) {
    // ...
}
```
**Always check:** After a patch that adds `return` inside an if-block, verify the else/after block is still reachable. Also check for if/else chains where every branch has a return — the final `else` block is fine, but intermediate `else if` branches that all return make subsequent branches unreachable.

### 11. Dashboard Background Appears Transparent (OBJPROP_BACK)
**Bug pattern:** Dashboard panel background appears invisible/transparent — you can see candlesticks through it and text is hard to read. The BG color looks correct but renders as "see-through."
**Root cause:** `OBJPROP_BACK = true` on a `OBJ_RECTANGLE_LABEL` places the object **behind** the chart, so candles show through and the solid BG color is hidden.
**Fix:** Set `OBJPROP_BACK = false` so the panel sits on top of the chart as a proper foreground UI element:
```mql5
ObjectSetInteger(0, bgName, OBJPROP_BACK, false);  // false = foreground, true = behind chart
```


### 12. Init Parameter Validation for Semi-Automatic EAs
**Bug pattern:** Validation in `OnInit` rejects `0` as invalid for entry/SL parameters, but for semi-automatic EAs (where levels are set via inputs or dashboard after attach), `0` means "unset — will be set later" and is a valid state.
**Wrong pattern:**
```mql5
if(InputEntryPrice <= 0 || InputSL <= 0)  // Blocks semi-auto operation!
   return INIT_PARAMETERS_INCORRECT;
```
**Correct pattern:** Only validate what's truly required at attach time. For semi-auto EAs, entry/SL can be 0:
```mql5
// Only reject if BOTH are set AND equal (ambiguous direction)
if(InputEntryPrice > 0 && InputSL > 0 && InputEntryPrice == InputSL)
   return INIT_PARAMETERS_INCORRECT;
```

### 13. Class Method Init Signature Mismatch (Declaration vs Definition vs Call)
**Bug pattern:** `COrderManager::Init()` declaration says 8 params, definition says 8 params, but call site passes 9 — or vice versa. The compiler only sees the declaration at the call site. Result: stack frame mismatch → garbage parameter values → crash or silent wrong behavior.
**Always check:** When a class `Init()` method is involved, count the actual arguments at the call site against the parameter list in the declaration. If you add a parameter to `Init()`, update BOTH declaration AND definition AND all call sites.

### 14. Dashboard Panel Appears Transparent / Candles Show Through
**Bug pattern:** `OBJPROP_BACK = true` on `OBJ_RECTANGLE_LABEL` or button makes the object render *behind* the chart — appearing invisible or semi-transparent, candles show through it.
**Fix:** Always set `OBJPROP_BACK = false` for dashboard/panel backgrounds that should overlay the chart:
```mql5
ObjectSetInteger(0, bgName, OBJPROP_BACK, false);  // false = on top (foreground)
```

### 15. Indicator Handle Creation in OnInit on Non-Current Timeframe
**Bug pattern:** EA is attached to H1 chart but creates an ATR handle on M5 (`iATR(_Symbol, PERIOD_M5, 14)`). During `OnInit`, M5 data may not be loaded into the terminal cache yet → `CopyBuffer` returns 0 → ATR distance = 0 → trailing stop never activates.
**Also:** If ATR handle creation fails during init, subsequent `CopyBuffer` calls silently return 0 instead of failing.
**Correct pattern:**
```mql5
double GetTrailActivateDistance() {
   if(InputAutoTrailDistance) {
      double dist = CalculateTrailDistance();
      if(dist <= 0)
         return InputManualTrailDistance * _Point * 0.5;  // safe fallback
      return dist * 0.5;
   }
   return InputManualTrailDistance * _Point * 0.5;
}
```
Also always check `CopyBuffer` return value and have a fallback. Recalculate ATR on first tick if init failed.

### Review Checklist for MQL5 EA Code
When reviewing any MQL5 EA code (especially from subagents), systematically check:
1. Every `iATR`/`iMA`/`iRSI`/`iCustom` → has `IndicatorRelease()` and NO `ChartIndicatorDelete` on handle
2. Every function parameter → grep body to confirm it's actually used
3. Every class `Init()` → verify params are stored to members if used later
4. Every `PositionSelect(symbol)` → replace with `PositionSelectByTicket()` or scan loop
5. Every `SetTypeFilling()` → verify auto-detect instead of hardcoded
6. `OnTick()` → verify new bar gate for bar-pattern strategies
7. All magic/lot/slippage values → must be `input` parameters, not literals
8. One-shot actions in state handlers → must have `IsActive()` guard
9. `CalculateXxx()` called before `Activate()` — member vars populated before use
10. Unused globals → grep `g_` variables, confirm they're read after being set
11. Dead code after return → verify else/after blocks are still reachable
12. Dashboard panel BG → verify `OBJPROP_BACK = false` (true = behind chart, appears transparent)
13. Init validation for semi-auto EAs → 0 is valid "unset" state for entry/SL (not an error)
14. Class `Init()` signature → count call-site args vs declaration args (stack mismatch = subtle crash)
16. Indicator handles in `OnInit` on non-current timeframe → `CopyBuffer` may return 0 before data loads, use fallback
17. ACCOUNT_MARGIN_MODE enum → verify constant exists in MT5 (ACCOUNT_MARGIN_MODE_NETTING does NOT exist in MT5)
18. ACCOUNT_MARGIN_MODE enum → verify constant exists in MT5 (ACCOUNT_MARGIN_MODE_NETTING does NOT exist in MT5)
19. Dashboard layout → when adding new UI sections, verify edit row Y positions don't overlap existing static rows
20. Dashboard UpdateDisplay loop → loop bounds must match values array size; separate post-loop updates that write to ValN can overwrite loop-assigned values

## 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 `input` for user parameters, not `extern`
- Always use `#property strict` in 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
