Update skills: add website-creator, mql-developer, ecommerce-astro

Changes:
- Add FAL_KEY and GEMINI_API_KEY to .env.example
- Update picture-it to use ~/.config/opencode/.env (unified creds)
- Remove shodh-memory skill (no longer used)
- Remove alphaear-* skills (deprecated)
- Remove thai-frontend-dev skill (replaced by website-creator)
- Remove theme-factory skill
- Add mql-developer skill (MQL5 trading)
- Add ecommerce-astro skill (Astro e-commerce)
- Add website-creator skill (Next.js + Payload CMS)
- Update install script for new skills
This commit is contained in:
2026-04-16 17:40:27 +07:00
parent 5053ccdba2
commit b26c8199a5
562 changed files with 59030 additions and 37600 deletions

View File

@@ -0,0 +1,21 @@
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.

View File

@@ -0,0 +1,73 @@
# mql-developer
[Claude Code](https://docs.anthropic.com/en/docs/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 licensing
```
Uses 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:
```bash
# 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](https://github.com/anthropics/courses/tree/master/claude-code/09-skills) 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

View File

@@ -0,0 +1,393 @@
---
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

View File

@@ -0,0 +1,118 @@
---
name: fibo-zone-indicator
description: >
Multi-timeframe Fibonacci retracement + S/R indicator for MetaTrader 5.
Draws buy zones (61.8-80.1% retracement) on lower portion of bars with TP/SL lines,
plus support/resistance levels and psychological price levels.
Built for XAUUSD (gold) but works on any symbol.
category: mql5
---
# FiboZone Indicator — MQL5 Implementation
## Overview
Indicator สำหรับ XAUUSD — แสดง Fibonacci retracement zones จาก D1/H4/H1 พร้อม:
- Buy zone (61.8-80.1%) อยู่ lower portion ของแท่ง
- TP/SL lines
- Support/Resistance levels (จาก 20 bars ย้อนหลัง)
- Psychological price levels
## Key Code Patterns
### MQL5 Array Parameter Syntax
**CRITICAL**: When passing arrays OUT of a function (output parameters), use reference-to-array syntax:
```mql5
// WRONG — treats as reference to single element
int FindSRLevels(const double &highs[], const double &lows[], int count,
SSRLevel &srOut, int maxOut)
// CORRECT — reference to array
int FindSRLevels(const double &highs[], const double &lows[], int count,
SSRLevel &srOut[], int maxOut)
```
Every declaration, definition, and call site must use `[]` on array parameters. Mismatch causes compile errors: `cannot convert 'double[]' to 'double&'`.
### ArrayResize Before Indexing
Always `ArrayResize` on output arrays BEFORE writing to them:
```mql5
// WRONG — array out of range
for(int i = 0; i < outCount; i++)
srOut[i] = filtered[i];
// CORRECT
ArrayResize(srOut, outCount);
for(int i = 0; i < outCount; i++)
srOut[i] = filtered[i];
```
### Fibo Zone Calculation — Lower Portion
User clarified: Fibo tool 0% = TOP, 100% = BOTTOM. Zone 61.8-80.1% must be in LOWER portion of the bar.
**Bullish bar** (close > open): Zone near LOW → subtract from HIGH
```mql5
r.zoneHigh = high - barHeight * 0.618; // closer to high
r.zoneLow = high - barHeight * 0.801; // closer to low (lower portion)
```
**Bearish bar** (close < open): Zone near LOW of the bar → add from LOW
```mql5
r.zoneHigh = low + barHeight * 0.801; // closer to high
r.zoneLow = low + barHeight * 0.618; // closer to low (lower portion)
```
### Calculation Test (Manual)
Bullish bar: High=4766, Low=4664, Bar=102
```mql5
// Lower portion of bullish bar:
zoneHigh = 4766 - 102 * 0.618 = 4702.96
zoneLow = 4766 - 102 * 0.801 = 4684.30
// Zone: [4684, 4703] ✅ in lower portion (closer to low)
```
Bearish bar: High=4766, Low=4664, Bar=102
```mql5
// Lower portion of bearish bar:
zoneHigh = 4664 + 102 * 0.801 = 4745.70
zoneLow = 4664 + 102 * 0.618 = 4727.04
// Zone: [4727, 4746] ✅ in lower portion
```
### OBJ_RECTANGLE for Price Zones
`OBJ_RECTANGLE` needs distinct time1 and time2 to span chart width:
```mql5
// WRONG — time1 == time2 → zero-width (looks like vertical line)
ObjectCreate(0, name, OBJ_RECTANGLE, 0, anchorTime, zoneLow, anchorTime, zoneHigh);
// CORRECT — time2 = far future → spans full chart width
datetime rightTime = D'2099.12.31 23:59:59';
ObjectCreate(0, name, OBJ_RECTANGLE, 0, anchorTime, zoneLow, rightTime, zoneHigh);
ObjectSetInteger(0, name, OBJPROP_FILL, true);
ObjectSetInteger(0, name, OBJPROP_COLOR, ColorToARGB(zoneColor, 60));
```
### S/R Zone Width Parameter
`InpSRZoneWidth` groups similar prices into one S/R zone. Must be smaller than the actual price range:
```mql5
// Example: XAUUSD range ~100 points over 20 bars
// zoneWidth = 200 → all bars in ONE zone → only 1-3 S/R lines
// zoneWidth = 50 → ~2 zones → more S/R lines
input int InpSRZoneWidth = 50;
input int InpSRMinHits = 2; // lower from 5 to see more lines
input int InpMaxSR = 4;
```
## Common Bugs Fixed
| Bug | Fix |
|-----|-----|
| `cannot convert double[] to double&` | Change all array params to `&arr[]` syntax |
| `array out of range` at line N | Add `ArrayResize(srOut, count)` before `srOut[i] =` |
| Rectangle looks like vertical line | Set `time2 = D'2099.12.31'` not `anchorTime` |
| Fibo zone in wrong portion | Use high-subtract for bullish, low-add for bearish |
| S/R zoneWidth=200, only 3 lines | Reduce to ~50 for XAUUSD range |

View File

@@ -0,0 +1,129 @@
---
name: mql5-dashboard-survive-tf-change
description: >
ทำให้ Dashboard edit fields และ runtime values อยู่รอดเมื่อ user เปลี่ยน
timeframe (OnDeinit + OnInit re-run) โดยใช้ JSON file persistence.
รวมถึงวิธีแก้ข้อจำกัดของ MQL5 ที่ห้ามมี input declarations ใน .mqh files.
---
# MQL5 Dashboard: ทำให้ค่าบน Chart Objects อยู่รอดเมื่อเปลี่ยน Timeframe
## ปัญหา
เมื่อ user เปลี่ยน timeframe บน MetaTrader, terminal จะ:
1. เรียก `OnDeinit(reason)`
2. เรียก `OnInit()` ใหม่
ถ้า `OnInit()` อ่านค่าจาก `input` parameters หรือ global variables ที่ไม่ได้ persist, ค่าที่ user พิมพ์ผ่าน dashboard edit fields จะ **หายหมด**เมื่อเปลี่ยน TF
## วิธีแก้: Persistent Storage ด้วย JSON File
### 1. บันทึกก่อน OnDeinit
```mql5
string MakeRuntimeFilePath() {
string path = TerminalInfoString(TERMINAL_DATA_PATH);
string safeSymbol = StringReplace(g_symbol, "/", "_");
return path + "\\MQL5\\Files\\SmartEntry_" + safeSymbol + "_runtime.json";
}
bool SaveRuntimeValues() {
string path = MakeRuntimeFilePath();
int handle = FileOpen(path, FILE_WRITE | FILE_ANSI | FILE_COMMON);
if(handle == INVALID_HANDLE) return false;
string json = StringFormat(
"{\"upperZone\":%.5f,\"lowerZone\":%.5f,\"sl\":%.5f,\"tp\":%.5f,\"beOffset\":%d,\"isBuySetup\":%s}",
g_upperZone, g_lowerZone, g_SL, g_TP, (int)g_beOffset, g_isBuySetup ? "true" : "false");
FileWriteString(handle, json + "\n");
FileClose(handle);
return true;
}
void OnDeinit(const int reason) {
SaveRuntimeValues(); // ← บันทึกก่อน cleanup ทุกครั้ง
// ... release handles, destroy panel
}
```
### 2. โหลดกลับตอน OnInit
```mql5
bool LoadRuntimeValues() {
string path = MakeRuntimeFilePath();
int handle = FileOpen(path, FILE_READ | FILE_ANSI | FILE_COMMON);
if(handle == INVALID_HANDLE) return false; // ไม่มีไฟล์ = first run
string json = FileReadString(handle);
FileClose(handle);
g_upperZone = StringToDouble(ParseJsonField(json, "upperZone"));
g_lowerZone = StringToDouble(ParseJsonField(json, "lowerZone"));
// ...
return true;
}
int OnInit() {
// Load ก่อน ถ้าไม่มีไฟล์ค่อย fallback ไป input defaults
if(!LoadRuntimeValues()) {
g_upperZone = InputUpperZone;
// ...
}
// ...
}
```
### 3. Parse JSON field (MQL5 ไม่มี native JSON)
```mql5
string ParseJsonField(string json, string fieldName) {
string search = "\"" + fieldName + "\":";
int pos = StringFind(json, search);
if(pos < 0) return "0";
int start = pos + StringLen(search);
while(start < StringLen(json) && json[start] == ' ') start++;
if(json[start] == '"') {
int end = start + 1;
while(end < StringLen(json) && json[end] != '"') end++;
return StringSubstr(json, start + 1, end - start - 1);
} else {
int end = start;
while(end < StringLen(json) && json[end] != ',' && json[end] != '}' && json[end] != '\n') end++;
return StringSubstr(json, start, end - start);
}
}
```
## ข้อควรระวัง: `input` ใน .mqh File
**MQL5 ไม่อนุญาต `input` declarations ใน .mqh include files** — compile error
### ผิด (compile error):
```mql5
// Dashboard.mqh
input double InputUpperZone = 0; // ← ERROR: input not allowed in .mqh
```
### ถูก:
```mql5
// Dashboard.mqh — ไม่มี input declarations ที่นี่
// สร้าง method ให้ EA เรียกหลัง Init
void CDashboard::SetInitialEditTexts(double upperZone, double lowerZone,
double sl, double tp, int beOffset) {
if(upperZone > 0)
ObjectSetString(0, m_prefix + "EREdit", OBJPROP_TEXT, DoubleToString(upperZone, _Digits));
// ...
}
// SmartEntryEA.mq5
g_dashboard.Init(g_symbol, true);
g_dashboard.SetInitialEditTexts(g_upperZone, g_lowerZone, g_SL, g_TP, (int)g_beOffset);
```
## ข้อควรระวัง: Chart Objects หายเมื่อ RecreatePanel
`CreatePanel()` สร้าง objects ใหม่ทุกครั้งที่ `ObjectFind() < 0` check อยู่แล้ว — safe สำหรับ TF change เพราะ OnDeinit จะ `DestroyPanel()` ก่อน
## Symbols
- `g_symbol` — ต้องกำหนดก่อนเรียก `MakeRuntimeFilePath()`
- File path ใช้ `FILE_COMMON` flag เพื่อเขียนได้ทุก profile

View File

@@ -0,0 +1,62 @@
---
name: mql5-ea-troubleshooting
description: MQL5 EA runtime errors and attach failures — init failures, INVALID_HANDLE 32757, build-specific enum differences
---
# MQL5 EA Troubleshooting
## Common runtime errors (attach failed)
### Error 32757 (0x8001 = E_HANDLE / INVALID_HANDLE)
Usually means an indicator handle creation failed, or a **function call/argument mismatch** in `OnInit()`.
**Stack mismatch diagnosis:**
```mql5
// Call site passes 9 args:
g_orderManager.Init(g_symbol, InputMagicNumber, InputComment,
InputEntryPrice, InputSL, InputTP,
InputMaxFollowOrders, InputUseTrailingSL,
InputFollowDelayBars); // ← 9 args
// But declaration only accepts 8:
void Init(string symbol, int magicNumber, string comment,
double inputEntry, double inputSL, double inputTP,
int maxFollow, bool useTrailing, int followDelayBars); // ← 8 params
// ↑ stack corrupted
```
Fix: match declaration to call.
**Indicator handle failure:**
- `iATR(symbol, PERIOD_M5)` → in some builds requires 3 params: `iATR(symbol, PERIOD_M5, period)`
- `iATR(handle)` returns `INVALID_HANDLE` if symbol/timeframe unavailable
- Always check: `if(handle == INVALID_HANDLE) return INIT_FAILED;`
---
## Build-specific enums (tested on build 3570+)
| Enum | Wrong | Correct |
|------|-------|---------|
| ATR params | `iATR(sym, TF)` (2 args) | `iATR(sym, TF, period)` (3 args) |
| Account margin | `ACCOUNT_MARGIN_MODE_NETTING` | `ACCOUNT_MARGIN_MODE_RETAIL_NET` |
| Account free margin | `ACCOUNT_FREEMARGIN` (deprecated) | `ACCOUNT_MARGIN_FREE` |
| Symbol contract size | `SYMBOL_CONTRACT_SIZE` | `SYMBOL_TRADE_CONTRACT_SIZE` |
| Margin rate | `SYMBOL_MARGIN_RATE` (doesn't exist) | `SYMBOL_MARGIN_HEDGED` or use `OrderCalcMargin()` |
| TRADE_TRANSACTION | `TRADE_TRANSACTION_POSITION_CLOSE`, `_OPEN` don't exist | Use `TRADE_TRANSACTION_DEAL_ADD` + `TRADE_TRANSACTION_POSITION` |
| `CTrade.SetComment()` | doesn't exist | Pass comment string directly to `Buy()`/`Sell()` |
---
## Type coercion warnings (benign — still compile)
- `SymbolInfoInteger()` returns `long`; assign to `double` with explicit cast: `(double)SymbolInfoInteger(sym, SYMBOL_SPREAD)`
- `rates[].tick_volume` is `long`; cast: `(double)rates[0].tick_volume`
---
## Debug approach for attach/init failures
1. Check if error is an `INIT_*` code ( INIT_PARAMETERS_INCORRECT = 5, INIT_FAILED = 7)
2. If not INIT code → look for INVALID_HANDLE or E_HANDLE
3. Search all files for `IndicatorCreate`, `iATR`, `iAC`, `iADX` — ensure all handles checked
4. Count arguments in every Init/constructor call vs. declaration
5. Check global-scope code that runs before `OnInit()` (including class member constructors)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,237 @@
# Backtesting & Optimization
## Table of Contents
- [Strategy Tester Modes](#strategy-tester-modes)
- [Minimum Requirements for Valid Backtest](#minimum-requirements-for-valid-backtest)
- [OnTester() - Custom Optimization Criterion](#ontester---custom-optimization-criterion-mql5)
- [TesterStatistics() Constants](#testerstatistics-constants)
- [Key Performance Metrics](#key-performance-metrics)
- [Walk-Forward Analysis](#walk-forward-analysis)
- [Avoiding Overfitting](#avoiding-overfitting)
- [Monte Carlo Simulation](#monte-carlo-simulation)
- [Multi-Currency Testing (MQL5)](#multi-currency-testing-mql5)
- [Optimization Modes (MT5)](#optimization-modes-mt5)
- [Frame Functions (Inter-Pass Communication)](#frame-functions-inter-pass-communication)
- [Practical Backtesting Workflow](#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)
```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
```mql5
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:
```mql5
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)
```mql5
// 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,259 @@
# Security & Licensing
## Table of Contents
- [Account-Based Licensing](#account-based-licensing)
- [Server-Side License Validation](#server-side-license-validation)
- [Expiration Date Checks](#expiration-date-checks)
- [Anti-Decompilation Techniques](#anti-decompilation-techniques)
- [Distribution Best Practices](#distribution-best-practices)
- [Node.js License Server Example](#nodejs-license-server-example)
- [MQL4 Specific Considerations](#mql4-specific-considerations)
- [Complete License System Architecture](#complete-license-system-architecture)
---
## Account-Based Licensing
### Simple Multi-Account Check
```mql5
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:
```mql5
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
```mql5
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:
```mql5
GlobalVariableSet("EA_LICENSE_EXPIRY", (double)StringToTime(expiry));
```
On next check, verify cached expiry first, re-validate with server periodically.
---
## Expiration Date Checks
### Hardcoded Expiration
```mql5
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
```mql5
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
```mql5
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
```javascript
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)
```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
```

File diff suppressed because it is too large Load Diff