feat: SL/TP risk formulas, project docs, symbol specs
SKILL.md Section 5 rewritten with PointValue-based formulas:
- Direction A: SL points → SL price
- Direction B: Risk% + lots → SL price (with currency conversion)
- Direction C: SL price + risk% → lot size
- FindFXRate helper for profit_currency ≠ account_currency
- OrderCalcProfit verification pattern
Section 8 expanded with 6 SL/TP pitfalls (PointValue vs TICK_VALUE,
TickSize ≠ Point, currency conversion, NormalizeDouble rounding,
lot step quantization, STOPS_LEVEL check).
Section 9 EA skeleton now includes inline PointValue, FindFXRate,
CalcSLFromRisk, CalcLotsFromSL functions.
New files:
- LICENSE (MIT)
- README.md
- docs-dev/symbol-spec.md (symbol spec workflow)
- skills/mql5/references/symbol-spec/specs-{XAUUSD,USDJPY}.csv
- skills/mql5/scripts/verify_sl_tp_formulas.py (Python verification)
Updated: pyproject.toml, AGENTS.md, .gitignore, docs-dev/skill-design.md
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.log
|
||||
.extract.log
|
||||
html_cache/
|
||||
|
||||
@@ -15,6 +15,7 @@ MQL5 Agent Skills project. Creates and publishes Agent Skills conforming to the
|
||||
```
|
||||
mql5-skills/
|
||||
├── AGENTS.md # This file — project conventions
|
||||
├── LICENSE # MIT license
|
||||
├── README.md # Public readme
|
||||
├── pyproject.toml # uv project config
|
||||
├── sitemaps/ # Source sitemaps from mql5.com
|
||||
@@ -28,6 +29,8 @@ mql5-skills/
|
||||
├── skills/
|
||||
│ └── mql5/ # The MQL5 development skill
|
||||
│ ├── SKILL.md # Skill definition (agentskills.io spec)
|
||||
│ ├── scripts/
|
||||
│ │ └── verify_sl_tp_formulas.py # SL/TP risk formula verification
|
||||
│ └── references/
|
||||
│ ├── book/ # Programming book markdown (from sitemap_book_en.xml)
|
||||
│ │ ├── 0000-book.md
|
||||
@@ -35,16 +38,20 @@ mql5-skills/
|
||||
│ │ │ ├── 0001-intro.md
|
||||
│ │ │ └── pics/
|
||||
│ │ └── ...
|
||||
│ └── docs/ # API reference markdown (from sitemap_docs_en.xml)
|
||||
│ ├── 0000-docs.md
|
||||
│ ├── 01-basis/
|
||||
│ │ ├── 0001-basis.md
|
||||
│ │ └── pics/
|
||||
│ └── ...
|
||||
│ ├── docs/ # API reference markdown (from sitemap_docs_en.xml)
|
||||
│ │ ├── 0000-docs.md
|
||||
│ │ ├── 01-basis/
|
||||
│ │ │ ├── 0001-basis.md
|
||||
│ │ │ └── pics/
|
||||
│ │ └── ...
|
||||
│ └── symbol-spec/ # Broker symbol specifications (CSV)
|
||||
│ ├── specs-XAUUSD.csv
|
||||
│ └── specs-USDJPY.csv
|
||||
└── docs-dev/ # Development documentation
|
||||
├── extraction.md # Extraction workflow and script design
|
||||
├── naming.md # Folder/file naming conventions
|
||||
└── skill-design.md # SKILL.md content plan
|
||||
├── skill-design.md # SKILL.md content plan
|
||||
└── symbol-spec.md # Symbol spec workflow
|
||||
```
|
||||
|
||||
## SKILL.md Convention (skills/mql5/)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 chikui
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,115 @@
|
||||
# mql5-skills
|
||||
|
||||
MQL5 Agent Skill for MetaTrader 5 development. Conforms to the
|
||||
[AgentSkills.io Specification](https://agentskills.io/specification).
|
||||
|
||||
## What's Inside
|
||||
|
||||
An AI agent skill (`skills/mql5/SKILL.md`) that gives an LLM agent working
|
||||
knowledge of MQL5 development — positions, orders, indicators, risk management,
|
||||
backtesting — backed by the full MQL5 programming book and API reference
|
||||
extracted to Markdown.
|
||||
|
||||
```
|
||||
skills/mql5/
|
||||
├── SKILL.md # The agent skill (agentskills.io spec)
|
||||
├── scripts/
|
||||
│ ├── verify_sl_tp_formulas.py # Python SL/TP risk formula verification
|
||||
│ └── ...
|
||||
└── references/
|
||||
├── book/ # MQL5 Programming Book (581 pages)
|
||||
├── docs/ # MQL5 API Reference (4135 pages)
|
||||
└── symbol-spec/ # Broker symbol specifications (CSV)
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Use the Skill
|
||||
|
||||
Point your AI agent at `skills/mql5/SKILL.md`. The skill covers:
|
||||
|
||||
- **Trading operations**: CTrade class, OrderSend, position management
|
||||
- **Indicators**: built-in handles, multi-timeframe, custom indicators
|
||||
- **Risk management**: PointValue-based SL/TP calculation, lot sizing,
|
||||
currency conversion for cross-pair profit currencies
|
||||
- **Backtesting**: Strategy Tester, OnTester custom criteria, optimization
|
||||
- **Pitfalls**: 16 documented pitfalls including SL/TP-specific issues
|
||||
|
||||
### Extract References (Development)
|
||||
|
||||
The `references/book/` and `references/docs/` directories are generated from
|
||||
mql5.com sitemaps via a two-phase pipeline:
|
||||
|
||||
```bash
|
||||
# Phase 1: Download HTML (needs network)
|
||||
uv run scripts/extract.py download --all
|
||||
|
||||
# Phase 2: Convert to Markdown (offline)
|
||||
uv run scripts/extract.py convert --all
|
||||
```
|
||||
|
||||
See `docs-dev/extraction.md` for the full pipeline design.
|
||||
|
||||
### Verify Risk Formulas
|
||||
|
||||
```bash
|
||||
python skills/mql5/scripts/verify_sl_tp_formulas.py
|
||||
```
|
||||
|
||||
Tests SL/TP calculation against real symbol specs (XAUUSD, USDJPY) with
|
||||
verified PointValue-based formulas.
|
||||
|
||||
## Symbol Specs
|
||||
|
||||
Broker-specific symbol specifications live in `skills/mql5/references/symbol-spec/`:
|
||||
|
||||
| File | Symbol | Calc Mode | Contract Size | Digits |
|
||||
|------|--------|-----------|---------------|--------|
|
||||
| `specs-XAUUSD.csv` | XAUUSD | CFD Leverage | 100 | 2 |
|
||||
| `specs-USDJPY.csv` | USDJPY | Forex | 100,000 | 3 |
|
||||
|
||||
These are used by the verification script and can be extended for additional
|
||||
symbols.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
mql5-skills/
|
||||
├── AGENTS.md # Project conventions for AI agents
|
||||
├── LICENSE # MIT
|
||||
├── README.md # This file
|
||||
├── pyproject.toml # uv project config (Python 3.14)
|
||||
├── sitemaps/ # Source sitemaps from mql5.com
|
||||
│ ├── sitemap_book_en.xml # 581 URLs → programming book
|
||||
│ └── sitemap_docs_en.xml # 4135 URLs → API reference docs
|
||||
├── scripts/ # Extraction pipeline
|
||||
│ └── extract.py # XML → HTML → Markdown
|
||||
├── skills/mql5/ # The MQL5 agent skill
|
||||
│ ├── SKILL.md
|
||||
│ ├── scripts/
|
||||
│ └── references/
|
||||
├── docs-dev/ # Development documentation
|
||||
│ ├── extraction.md # Extraction pipeline design
|
||||
│ ├── naming.md # File/folder naming conventions
|
||||
│ ├── skill-design.md # SKILL.md content plan
|
||||
│ └── symbol-spec.md # Symbol spec workflow
|
||||
└── html_cache/ # Downloaded HTML (gitignored)
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Setup
|
||||
uv sync
|
||||
|
||||
# Run extraction
|
||||
uv run scripts/extract.py download --all
|
||||
uv run scripts/extract.py convert --all
|
||||
|
||||
# Verify formulas
|
||||
python skills/mql5/scripts/verify_sl_tp_formulas.py
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
+57
-70
@@ -9,106 +9,93 @@ conforming to the [AgentSkills.io Specification](https://agentskills.io/specific
|
||||
---
|
||||
name: mql5
|
||||
description: >
|
||||
MQL5 development skill for MetaTrader 5. Covers Expert Advisors, Indicators,
|
||||
Scripts, and Services. Focus on positions, orders, indicators, ticks, and bars.
|
||||
Includes programming book and API reference documentation.
|
||||
MQL5 development skill for MetaTrader 5 Expert Advisors, Indicators, Scripts,
|
||||
and Services. Focus on positions, orders, indicators, ticks, bars, risk
|
||||
management, backtesting, and multi-instance MT5 operations. Includes
|
||||
programming book and API reference documentation.
|
||||
version: "0.1"
|
||||
license: MIT
|
||||
compatibility: >
|
||||
Target: MetaTrader 5 platform. Language: MQL5 (C++-like syntax).
|
||||
File extensions: *.mq5 (source), *.mqh (headers).
|
||||
References: mql5.com/en/book (programming), mql5.com/en/docs (API).
|
||||
Run time: Windows native, Linux via Wine, macOS via Wine.
|
||||
metadata:
|
||||
project-version: "0.1.0"
|
||||
sources:
|
||||
book: sitemaps/sitemap_book_en.xml (581 URLs)
|
||||
docs: sitemaps/sitemap_docs_en.xml (4135 URLs)
|
||||
focus-areas:
|
||||
- positions
|
||||
- orders
|
||||
- indicators
|
||||
- ticks
|
||||
- bars
|
||||
- risk-management
|
||||
- backtesting
|
||||
---
|
||||
```
|
||||
|
||||
## Body Content Structure
|
||||
|
||||
The body should be structured as follows:
|
||||
### 1. MQL5 Fundamentals
|
||||
- Language and file types (.mq5, .mqh, .ex5)
|
||||
- Program types: EA, Indicator, Script, Service
|
||||
- MQL5 directory structure (Windows, Linux/Wine)
|
||||
- Multi-instance MT5 operations
|
||||
|
||||
### 1. Overview
|
||||
### 2. Trading Operations
|
||||
- Core concepts: Order, Deal, Position
|
||||
- CTrade class usage patterns
|
||||
- Position queries (Hedging vs Netting)
|
||||
- Order execution pattern with error handling
|
||||
|
||||
Brief description of MQL5 and MetaTrader 5:
|
||||
- MQL5 is the programming language for MetaTrader 5
|
||||
- Syntax similar to C++
|
||||
- File types: `.mq5` (source), `.mqh` (headers)
|
||||
- Program types: Expert Advisors, Indicators, Scripts, Services
|
||||
### 3. Indicators and Multi-Timeframe
|
||||
- Built-in indicator handles (iMA, iRSI, iMACD, iBands)
|
||||
- Reading indicator values via CopyBuffer
|
||||
- Multi-timeframe analysis pattern
|
||||
- New bar detection
|
||||
|
||||
### 2. Quick Reference — Key Operations
|
||||
### 4. Ticks and Bars
|
||||
- Timeseries access (MqlRates, ArraySetAsSeries)
|
||||
- Tick data (MqlTick, SymbolInfoTick)
|
||||
- Key functions table
|
||||
|
||||
Focus areas with concise API patterns:
|
||||
### 5. Risk Management and Lot Sizing
|
||||
- **PointValue concept**: profit-currency per 1-point move for 1 lot
|
||||
- Forex/CFD: `point × ContractSize`
|
||||
- Futures: `point × TickValue / TickSize`
|
||||
- **Direction A**: SL distance points → SL price
|
||||
- **Direction B**: Risk% + fixed lots → SL price (with currency conversion)
|
||||
- **Direction C**: SL price + risk% → lot size
|
||||
- **Profit verification**: OrderCalcProfit + manual formula
|
||||
- Risk-to-Reward ratio
|
||||
- Position sizing rules (7 rules)
|
||||
|
||||
#### Positions
|
||||
- `CTrade` class for position management
|
||||
- `PositionGetSymbol()`, `PositionSelect()`, `PositionGetDouble()`
|
||||
- `CTrade::PositionOpen()`, `CTrade::PositionClose()`
|
||||
### 6. Backtesting and Optimization
|
||||
- Strategy Tester concepts
|
||||
- OnTester custom optimization criterion
|
||||
- Key statistics table
|
||||
- Backtesting workflow
|
||||
|
||||
#### Orders
|
||||
- `CTrade::OrderSend()` for pending orders
|
||||
- `ORDER_TYPE_BUY_LIMIT`, `ORDER_TYPE_SELL_LIMIT`, etc.
|
||||
- `OrderGetTicket()`, `OrderSelect()`
|
||||
### 7. Event Handlers Reference
|
||||
- Handler table (OnInit through OnTesterPass)
|
||||
|
||||
#### Indicators
|
||||
- `iMA()`, `iRSI()`, `iMACD()`, `iBands()` — built-in indicators
|
||||
- `CopyBuffer()` to read indicator values
|
||||
- `IndicatorCreate()` for custom indicators
|
||||
### 8. Common Pitfalls
|
||||
- **General** (10 items): ResultRetcode, MagicNumber, NormalizeDouble, etc.
|
||||
- **SL/TP and Risk Calculation** (6 items): PointValue vs TICK_VALUE,
|
||||
TickSize vs Point, currency conversion, NormalizeDouble rounding,
|
||||
lot step quantization, STOPS_LEVEL check
|
||||
|
||||
#### Ticks
|
||||
- `SymbolInfoTick()` — current tick data
|
||||
- `MqlTick` structure: `bid`, `ask`, `last`, `volume`, `time`
|
||||
- `OnTick()` handler for Expert Advisors
|
||||
### 9. Quick Reference — EA Skeleton
|
||||
- Complete EA template with inline risk functions:
|
||||
- `PointValue()`, `FindFXRate()`, `CalcSLFromRisk()`, `CalcLotsFromSL()`
|
||||
- OnTick example with SL calculation and loss verification
|
||||
|
||||
#### Bars
|
||||
- `Bars()`, `BarsCalculated()` — bar count
|
||||
- `CopyOpen()`, `CopyHigh()`, `CopyLow()`, `CopyClose()`, `CopyVolume()`
|
||||
- `CopyRates()`, `CopyTime()`
|
||||
- `CTerminalInfo`, `CSymbolInfo` for symbol/bar info
|
||||
|
||||
### 3. Program Types
|
||||
|
||||
| Type | Purpose | Key Handler |
|
||||
|------|---------|-------------|
|
||||
| Expert Advisor | Automated trading | `OnTick()`, `OnInit()`, `OnDeinit()` |
|
||||
| Indicator | Technical analysis | `OnCalculate()` |
|
||||
| Script | One-shot execution | `OnStart()` |
|
||||
| Service | Background task | `OnStart()`, `OnTimer()` |
|
||||
|
||||
### 4. Common Patterns
|
||||
|
||||
- Trade execution with error handling
|
||||
- Indicator buffer management
|
||||
- Timer-based operations
|
||||
- Chart object manipulation
|
||||
- File I/O for logging/data
|
||||
|
||||
### 5. References
|
||||
|
||||
Point to the extracted documentation:
|
||||
- `references/book/` — Programming book (learning path)
|
||||
- `references/docs/` — API reference (function/type lookup)
|
||||
|
||||
### 6. Pitfalls & Best Practices
|
||||
|
||||
- `RefreshRates()` before trading operations
|
||||
- `NormalizeDouble()` for price comparisons
|
||||
- Check `Retcode()` after trade operations
|
||||
- Use `CTrade` class over raw `OrderSend()`
|
||||
- Handle `OnTimer()` for periodic operations
|
||||
- Test with Strategy Tester before live deployment
|
||||
### 10. References
|
||||
- In-skill references (book/, docs/, symbol-spec/, scripts/)
|
||||
- External links (MQL5 Reference, MQL5 Book, Strategy Tester Guide)
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The SKILL.md should be concise (< 1024 chars for description, body can be longer)
|
||||
- Body is loaded as context by AI agents — prioritize actionable patterns
|
||||
- Reference files provide depth; SKILL.md provides the "what to do"
|
||||
- Risk formulas verified against real symbol specs (XAUUSD, USDJPY)
|
||||
via `scripts/verify_sl_tp_formulas.py`
|
||||
- Version 0.1: initial content, will expand as extraction completes
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# Symbol Specification Workflow
|
||||
|
||||
Broker-specific symbol specifications stored as CSV files, used by the
|
||||
SL/TP risk formula verification script.
|
||||
|
||||
## Location
|
||||
|
||||
```
|
||||
skills/mql5/references/symbol-spec/
|
||||
├── specs-XAUUSD.csv
|
||||
├── specs-USDJPY.csv
|
||||
└── ...
|
||||
```
|
||||
|
||||
## CSV Format
|
||||
|
||||
Each file is a two-column CSV (`Property,Value`) exported from the MT5
|
||||
Symbol Properties dialog. Key fields:
|
||||
|
||||
| Field | Example (XAUUSD) | Example (USDJPY) | Used By |
|
||||
|-------|-------------------|-------------------|---------|
|
||||
| Digits | 2 | 3 | NormalizeDouble, Point |
|
||||
| Contract size | 100 | 100000 | PointValue, profit formula |
|
||||
| Calculation | CFD Leverage | Forex | Formula selection |
|
||||
| Tick size | 0.01 | 0.001 | Futures formula |
|
||||
| Tick value | 1.0 | 0.618839 | Futures formula |
|
||||
| Stops level | 0 | 0 | Min SL distance check |
|
||||
| Profit currency | USD | JPY | Currency conversion |
|
||||
| Minimal volume | 0.01 | 0.01 | Lot normalization |
|
||||
| Maximal volume | 80.00 | 80.00 | Lot normalization |
|
||||
| Volume step | 0.01 | 0.01 | Lot normalization |
|
||||
|
||||
## How to Export from MT5
|
||||
|
||||
1. In MT5: Tools → Symbols (or press Ctrl+U)
|
||||
2. Select the symbol → Properties tab
|
||||
3. Right-click → "Copy" or manually record values
|
||||
4. Create `specs-{SYMBOL}.csv` with the `Property,Value` format
|
||||
|
||||
## How to Add a New Symbol
|
||||
|
||||
1. Export specs from MT5 (see above)
|
||||
2. Save as `skills/mql5/references/symbol-spec/specs-{SYMBOL}.csv`
|
||||
3. Add the symbol to `verify_sl_tp_formulas.py`:
|
||||
```python
|
||||
specs = {
|
||||
"XAUUSD": SymbolSpec.from_csv(SPEC_DIR / "specs-XAUUSD.csv"),
|
||||
"USDJPY": SymbolSpec.from_csv(SPEC_DIR / "specs-USDJPY.csv"),
|
||||
"EURUSD": SymbolSpec.from_csv(SPEC_DIR / "specs-EURUSD.csv"), # new
|
||||
}
|
||||
bids = {"XAUUSD": 4121.28, "USDJPY": 161.561, "EURUSD": 1.0850}
|
||||
fx_rates = {"XAUUSD": 1.0, "USDJPY": 161.561, "EURUSD": 1.0}
|
||||
```
|
||||
4. Run `python skills/mql5/scripts/verify_sl_tp_formulas.py` to verify
|
||||
|
||||
## Key Distinctions by Calc Mode
|
||||
|
||||
### Forex (SYMBOL_CALC_MODE_FOREX)
|
||||
- Profit = `(close - open) × ContractSize × Lots`
|
||||
- PointValue = `point × ContractSize`
|
||||
- Example: USDJPY — ContractSize=100000, PointValue=100 JPY/pt/lot
|
||||
|
||||
### CFD Leverage (SYMBOL_CALC_MODE_CFDLEVERAGE)
|
||||
- Profit = `(close - open) × ContractSize × Lots`
|
||||
- PointValue = `point × ContractSize`
|
||||
- Example: XAUUSD — ContractSize=100, PointValue=1.0 USD/pt/lot
|
||||
|
||||
### Futures (SYMBOL_CALC_MODE_FUTURES)
|
||||
- Profit = `(close - open) × TickValue / TickSize × Lots`
|
||||
- PointValue = `point × TickValue / TickSize`
|
||||
- Uses broker-supplied TickValue instead of ContractSize
|
||||
|
||||
## Currency Conversion
|
||||
|
||||
When `SYMBOL_CURRENCY_PROFIT ≠ ACCOUNT_CURRENCY`:
|
||||
|
||||
```
|
||||
risk_profcy = risk_account_cy × fx_rate
|
||||
```
|
||||
|
||||
The verification script uses the `FindFXRate` helper to locate a Forex pair
|
||||
in Market Watch that converts between the two currencies. In MQL5 code,
|
||||
this is implemented in `SKILL.md` Section 5 (Direction B/C).
|
||||
+2
-1
@@ -1,8 +1,9 @@
|
||||
[project]
|
||||
name = "mql5-skills"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
description = "MQL5 Agent Skill for MetaTrader 5 development — extraction pipeline and risk formula verification"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
"beautifulsoup4>=4.15.0",
|
||||
|
||||
+298
-22
@@ -276,35 +276,182 @@ SymbolInfoTick(_Symbol, tick);
|
||||
|
||||
## 5. Risk Management and Lot Sizing
|
||||
|
||||
### Fixed Percentage Risk
|
||||
### Core Concept: PointValue
|
||||
|
||||
`PointValue` = profit/loss in profit-currency for a 1-point price move on 1 lot.
|
||||
This is the foundation for all risk calculations.
|
||||
|
||||
```mql5
|
||||
double CalculateLotSize(double riskPercent, double slPoints) {
|
||||
double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
||||
double riskAmount = accountBalance * riskPercent / 100.0;
|
||||
double PointValue(string symbol) {
|
||||
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||
double contract = SymbolInfoDouble(symbol, SYMBOL_TRADE_CONTRACT_SIZE);
|
||||
ENUM_SYMBOL_CALC_MODE mode =
|
||||
(ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(symbol, SYMBOL_TRADE_CALC_MODE);
|
||||
|
||||
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
|
||||
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
|
||||
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
switch (mode) {
|
||||
case SYMBOL_CALC_MODE_FOREX:
|
||||
case SYMBOL_CALC_MODE_FOREX_NO_LEVERAGE:
|
||||
case SYMBOL_CALC_MODE_CFD:
|
||||
case SYMBOL_CALC_MODE_CFDINDEX:
|
||||
case SYMBOL_CALC_MODE_CFDLEVERAGE:
|
||||
case SYMBOL_CALC_MODE_EXCH_STOCKS:
|
||||
case SYMBOL_CALC_MODE_EXCH_STOCKS_MOEX:
|
||||
return point * contract;
|
||||
|
||||
if (tickValue == 0 || tickSize == 0 || slPoints == 0) return 0;
|
||||
case SYMBOL_CALC_MODE_FUTURES:
|
||||
case SYMBOL_CALC_MODE_EXCH_FUTURES:
|
||||
case SYMBOL_CALC_MODE_EXCH_FUTURES_FORTS:
|
||||
return point * SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE)
|
||||
/ SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
double slMoneyPerLot = (slPoints * point / tickSize) * tickValue;
|
||||
double lot = riskAmount / slMoneyPerLot;
|
||||
Key distinction:
|
||||
- `SYMBOL_TRADE_TICK_VALUE` = profit-currency per tick for **1 lot** (broker-supplied)
|
||||
- `PointValue` = profit-currency per **1 point** for **1 lot** (computed)
|
||||
- `loss = points × PointValue × Lots`
|
||||
|
||||
### Direction A: SL Distance Points → SL Price
|
||||
|
||||
Given a stop loss distance in points, compute the SL price level.
|
||||
|
||||
```mql5
|
||||
double CalcSLFromPoints(string symbol, double openPrice, int slPoints,
|
||||
bool isBuy) {
|
||||
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||
double slDistPrice = slPoints * point;
|
||||
|
||||
if (isBuy)
|
||||
return NormalizeDouble(openPrice - slDistPrice,
|
||||
(int)SymbolInfoInteger(symbol, SYMBOL_DIGITS));
|
||||
else
|
||||
return NormalizeDouble(openPrice + slDistPrice,
|
||||
(int)SymbolInfoInteger(symbol, SYMBOL_DIGITS));
|
||||
}
|
||||
```
|
||||
|
||||
### Direction B: Risk % → SL Price (fixed lot size)
|
||||
|
||||
Given account balance, risk %, and lot size, compute where SL must be placed.
|
||||
|
||||
**CRITICAL**: When profit_currency ≠ account_currency, convert risk amount first.
|
||||
|
||||
```mql5
|
||||
double CalcSLFromRisk(string symbol, double balance, double riskPct,
|
||||
double lots, double openPrice, bool isBuy) {
|
||||
double pv = PointValue(symbol);
|
||||
if (pv == 0 || lots == 0) return 0;
|
||||
|
||||
double riskAmount = balance * riskPct / 100.0;
|
||||
|
||||
// If profit currency differs from account currency, convert.
|
||||
// Example: USDJPY → profit=JPY, account=USD → multiply by USDJPY bid
|
||||
string profCy = SymbolInfoString(symbol, SYMBOL_CURRENCY_PROFIT);
|
||||
string accCy = SymbolInfoString(ACCOUNT_CURRENCY);
|
||||
if (profCy != accCy) {
|
||||
// Find exchange rate pair: look for a Forex symbol with
|
||||
// base=accCy, profit=profCy (or reverse)
|
||||
string rateSym = "";
|
||||
int dir = FindFXRate(accCy, profCy, rateSym);
|
||||
if (dir == 0) { Print("Cannot convert ", profCy, "→", accCy); return 0; }
|
||||
MqlTick tick;
|
||||
SymbolInfoTick(rateSym, tick);
|
||||
double rate = (dir > 0) ? tick.bid : 1.0 / tick.ask;
|
||||
riskAmount *= rate; // risk in profit currency
|
||||
}
|
||||
|
||||
double points = riskAmount / (pv * lots);
|
||||
double slPrice = points * SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||
|
||||
if (isBuy)
|
||||
return NormalizeDouble(openPrice - slPrice,
|
||||
(int)SymbolInfoInteger(symbol, SYMBOL_DIGITS));
|
||||
else
|
||||
return NormalizeDouble(openPrice + slPrice,
|
||||
(int)SymbolInfoInteger(symbol, SYMBOL_DIGITS));
|
||||
}
|
||||
|
||||
// Helper: find a Forex pair that converts from→to
|
||||
// Returns +1 if pair is from/to, -1 if to/from, 0 if not found
|
||||
int FindFXRate(string from, string to, string &result) {
|
||||
for (int i = 0; i < SymbolsTotal(true); i++) {
|
||||
string sym = SymbolName(i, true);
|
||||
ENUM_SYMBOL_CALC_MODE m =
|
||||
(ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(sym, SYMBOL_TRADE_CALC_MODE);
|
||||
if (m != SYMBOL_CALC_MODE_FOREX &&
|
||||
m != SYMBOL_CALC_MODE_FOREX_NO_LEVERAGE) continue;
|
||||
string base = SymbolInfoString(sym, SYMBOL_CURRENCY_BASE);
|
||||
string profit = SymbolInfoString(sym, SYMBOL_CURRENCY_PROFIT);
|
||||
if (base == from && profit == to) { result = sym; return +1; }
|
||||
if (base == to && profit == from) { result = sym; return -1; }
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Direction C: SL Price → Lot Size (risk-based sizing)
|
||||
|
||||
Given a fixed SL price, compute the lot size so loss matches the risk budget.
|
||||
|
||||
```mql5
|
||||
double CalcLotsFromSL(string symbol, double balance, double riskPct,
|
||||
double openPrice, double slPrice) {
|
||||
double pv = PointValue(symbol);
|
||||
if (pv == 0) return 0;
|
||||
|
||||
double riskAmount = balance * riskPct / 100.0;
|
||||
|
||||
// Currency conversion (same as Direction B above)
|
||||
string profCy = SymbolInfoString(symbol, SYMBOL_CURRENCY_PROFIT);
|
||||
string accCy = SymbolInfoString(ACCOUNT_CURRENCY);
|
||||
if (profCy != accCy) {
|
||||
string rateSym = "";
|
||||
int dir = FindFXRate(accCy, profCy, rateSym);
|
||||
if (dir == 0) return 0;
|
||||
MqlTick tick;
|
||||
SymbolInfoTick(rateSym, tick);
|
||||
double rate = (dir > 0) ? tick.bid : 1.0 / tick.ask;
|
||||
riskAmount *= rate;
|
||||
}
|
||||
|
||||
double slDistPrice = MathAbs(openPrice - slPrice);
|
||||
if (slDistPrice == 0) return 0;
|
||||
|
||||
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||
double points = slDistPrice / point;
|
||||
double rawLots = riskAmount / (pv * points);
|
||||
|
||||
// Normalize to broker constraints
|
||||
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
||||
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
|
||||
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
|
||||
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
||||
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
||||
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||||
|
||||
lot = MathFloor(lot / lotStep) * lotStep;
|
||||
double lot = MathFloor(rawLots / lotStep) * lotStep;
|
||||
lot = MathMax(lot, minLot);
|
||||
lot = MathMin(lot, maxLot);
|
||||
|
||||
return NormalizeDouble(lot, 2);
|
||||
}
|
||||
```
|
||||
|
||||
### Profit Verification
|
||||
|
||||
Use `OrderCalcProfit` (EA/scripts only) or manual formula to verify:
|
||||
|
||||
```mql5
|
||||
// Using OrderCalcProfit
|
||||
double profit;
|
||||
OrderCalcProfit(ORDER_TYPE_BUY, symbol, lots, openPrice, closePrice, profit);
|
||||
// profit is in profit currency
|
||||
|
||||
// Manual formula (Forex/CFD)
|
||||
double profit = (closePrice - openPrice) * ContractSize * Lots;
|
||||
|
||||
// Manual formula (Futures)
|
||||
double profit = (closePrice - openPrice) * TickValue / TickSize * Lots;
|
||||
```
|
||||
|
||||
### Risk-to-Reward Ratio
|
||||
|
||||
```mql5
|
||||
@@ -317,9 +464,13 @@ double tp = (orderType == ORDER_TYPE_BUY) ? price + tpDistance : price - tpDista
|
||||
### Position Sizing Rules
|
||||
|
||||
1. Never risk more than 1-2% per trade
|
||||
2. Calculate lot size from risk amount and SL distance
|
||||
3. Normalize to broker's lot step and min/max constraints
|
||||
4. Account for spread when calculating SL distance
|
||||
2. Calculate SL price from risk% and lot size (Direction B), OR
|
||||
calculate lot size from SL price and risk% (Direction C)
|
||||
3. Always verify with `OrderCalcProfit` or manual formula
|
||||
4. Normalize SL with `NormalizeDouble(price, SYMBOL_DIGITS)`
|
||||
5. Check SL distance ≥ `SYMBOL_TRADE_STOPS_LEVEL × Point`
|
||||
6. Normalize lots to `SYMBOL_VOLUME_STEP`, clamp to `[VOLUME_MIN, VOLUME_MAX]`
|
||||
7. When profit_currency ≠ account_currency, convert risk amount via FX rate
|
||||
|
||||
## 6. Backtesting and Optimization
|
||||
|
||||
@@ -407,9 +558,11 @@ EA Development Cycle:
|
||||
|
||||
## 8. Common Pitfalls
|
||||
|
||||
### General
|
||||
|
||||
1. **Always check `ResultRetcode()`** after `PositionOpen()` — success != execution
|
||||
2. **Use `SetExpertMagicNumber()`** to distinguish your EA's trades
|
||||
3. **Normalize prices** with `SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)`
|
||||
3. **Normalize prices** with `NormalizeDouble(price, SYMBOL_DIGITS)`
|
||||
4. **Check `Bars() > N`** before trading to ensure enough history
|
||||
5. **Use `ArraySetAsSeries(true)`** for timeseries arrays (index 0 = latest)
|
||||
6. **Release indicator handles** in `OnDeinit()` with `IndicatorRelease()`
|
||||
@@ -418,6 +571,15 @@ EA Development Cycle:
|
||||
9. **Spread varies**: use `SymbolInfoInteger(_Symbol, SYMBOL_SPREAD)` for live spread
|
||||
10. **Timer in tester**: use `EventSetTimer()` in `OnInit()`, not hardcoded delays
|
||||
|
||||
### SL/TP and Risk Calculation
|
||||
|
||||
11. **PointValue ≠ TICK_VALUE**: `SYMBOL_TRADE_TICK_VALUE` is per tick (broker-defined step), `PointValue = point × ContractSize` is per point (smallest price unit). For most Forex: TickSize = Point, so they coincide; for futures/metals they may differ.
|
||||
12. **TickSize ≠ Point**: Always use the correct formula for the symbol's `SYMBOL_TRADE_CALC_MODE`. Forex/CFD: `loss = delta_price × ContractSize × Lots`. Futures: `loss = delta_price × TickValue / TickSize × Lots`.
|
||||
13. **Profit currency ≠ Account currency**: USDJPY profit is JPY, not USD. Risk amount must be converted: `risk_JPY = risk_USD × USDJPY_bid`. Failing this makes risk 100×+ too small.
|
||||
14. **NormalizeDouble introduces rounding**: SL price rounded to `SYMBOL_DIGITS` causes ~0.01-0.02% deviation from target loss. Acceptable; verify with `OrderCalcProfit`.
|
||||
15. **Lot step quantization**: `MathFloor(rawLots / lotStep) * lotStep` can leave residual risk unmet. For large lot_step or small risk budgets, actual loss may differ from target by up to one lot_step worth of loss.
|
||||
16. **STOPS_LEVEL check**: SL must be ≥ `SYMBOL_TRADE_STOPS_LEVEL × Point` from current price. If stops_level ≤ 0, use a safety margin (e.g. 150 points).
|
||||
|
||||
## 9. Quick Reference — EA Skeleton
|
||||
|
||||
```mql5
|
||||
@@ -440,6 +602,100 @@ CTrade trade;
|
||||
bool IsHedging;
|
||||
datetime lastBarTime = 0;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| PointValue: profit-currency per 1-point move for 1 lot |
|
||||
//+------------------------------------------------------------------+
|
||||
double PointValue(string symbol) {
|
||||
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||
double contract = SymbolInfoDouble(symbol, SYMBOL_TRADE_CONTRACT_SIZE);
|
||||
ENUM_SYMBOL_CALC_MODE mode =
|
||||
(ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(symbol, SYMBOL_TRADE_CALC_MODE);
|
||||
if (mode == SYMBOL_CALC_MODE_FUTURES ||
|
||||
mode == SYMBOL_CALC_MODE_EXCH_FUTURES ||
|
||||
mode == SYMBOL_CALC_MODE_EXCH_FUTURES_FORTS)
|
||||
return point * SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE)
|
||||
/ SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
||||
return point * contract; // Forex, CFD, Stocks
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| FindFXRate: locate a Forex pair for currency conversion |
|
||||
//+------------------------------------------------------------------+
|
||||
int FindFXRate(string from, string to, string &result) {
|
||||
for (int i = 0; i < SymbolsTotal(true); i++) {
|
||||
string sym = SymbolName(i, true);
|
||||
ENUM_SYMBOL_CALC_MODE m =
|
||||
(ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(sym, SYMBOL_TRADE_CALC_MODE);
|
||||
if (m != SYMBOL_CALC_MODE_FOREX &&
|
||||
m != SYMBOL_CALC_MODE_FOREX_NO_LEVERAGE) continue;
|
||||
string base = SymbolInfoString(sym, SYMBOL_CURRENCY_BASE);
|
||||
string profit = SymbolInfoString(sym, SYMBOL_CURRENCY_PROFIT);
|
||||
if (base == from && profit == to) { result = sym; return +1; }
|
||||
if (base == to && profit == from) { result = sym; return -1; }
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| CalcSLFromRisk: risk% + lots → SL price |
|
||||
//+------------------------------------------------------------------+
|
||||
double CalcSLFromRisk(string symbol, double balance, double riskPct,
|
||||
double lots, double openPrice, bool isBuy) {
|
||||
double pv = PointValue(symbol);
|
||||
if (pv == 0 || lots == 0) return 0;
|
||||
double riskAmount = balance * riskPct / 100.0;
|
||||
|
||||
// Currency conversion if needed
|
||||
string profCy = SymbolInfoString(symbol, SYMBOL_CURRENCY_PROFIT);
|
||||
string accCy = SymbolInfoString(ACCOUNT_CURRENCY);
|
||||
if (profCy != accCy) {
|
||||
string rateSym = "";
|
||||
int dir = FindFXRate(accCy, profCy, rateSym);
|
||||
if (dir == 0) return 0;
|
||||
MqlTick tick; SymbolInfoTick(rateSym, tick);
|
||||
riskAmount *= (dir > 0) ? tick.bid : 1.0 / tick.ask;
|
||||
}
|
||||
|
||||
double points = riskAmount / (pv * lots);
|
||||
double slPrice = points * SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
||||
return isBuy ? NormalizeDouble(openPrice - slPrice, digits)
|
||||
: NormalizeDouble(openPrice + slPrice, digits);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| CalcLotsFromSL: SL price + risk% → lot size |
|
||||
//+------------------------------------------------------------------+
|
||||
double CalcLotsFromSL(string symbol, double balance, double riskPct,
|
||||
double openPrice, double slPrice) {
|
||||
double pv = PointValue(symbol);
|
||||
if (pv == 0) return 0;
|
||||
double riskAmount = balance * riskPct / 100.0;
|
||||
|
||||
string profCy = SymbolInfoString(symbol, SYMBOL_CURRENCY_PROFIT);
|
||||
string accCy = SymbolInfoString(ACCOUNT_CURRENCY);
|
||||
if (profCy != accCy) {
|
||||
string rateSym = "";
|
||||
int dir = FindFXRate(accCy, profCy, rateSym);
|
||||
if (dir == 0) return 0;
|
||||
MqlTick tick; SymbolInfoTick(rateSym, tick);
|
||||
riskAmount *= (dir > 0) ? tick.bid : 1.0 / tick.ask;
|
||||
}
|
||||
|
||||
double slDist = MathAbs(openPrice - slPrice);
|
||||
if (slDist == 0) return 0;
|
||||
double points = slDist / SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||||
double rawLots = riskAmount / (pv * points);
|
||||
|
||||
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
||||
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
||||
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||||
double lot = MathFloor(rawLots / lotStep) * lotStep;
|
||||
lot = MathMax(lot, minLot);
|
||||
lot = MathMin(lot, maxLot);
|
||||
return NormalizeDouble(lot, 2);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit() {
|
||||
IsHedging = ((ENUM_ACCOUNT_MARGIN_MODE)
|
||||
@@ -465,13 +721,28 @@ void OnTick() {
|
||||
if (barTime == lastBarTime) return;
|
||||
lastBarTime = barTime;
|
||||
|
||||
// Analysis and trading logic here
|
||||
// ...
|
||||
// Example: buy with 1% risk, SL at 500 points
|
||||
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
int slPts = 500;
|
||||
double sl = CalcSLFromPoints(_Symbol, bid, slPts, true);
|
||||
// Or: double sl = CalcSLFromRisk(_Symbol,
|
||||
// AccountInfoDouble(ACCOUNT_BALANCE), RiskPercent,
|
||||
// 0.10, bid, true);
|
||||
|
||||
double lots = CalcLotsFromSL(_Symbol,
|
||||
AccountInfoDouble(ACCOUNT_BALANCE), RiskPercent, bid, sl);
|
||||
|
||||
// Verify loss matches risk budget
|
||||
double profit;
|
||||
OrderCalcProfit(ORDER_TYPE_BUY, _Symbol, lots, bid, sl, profit);
|
||||
PrintFormat("SL=%.5f lots=%.2f expected_loss=%.2f",
|
||||
sl, lots, profit);
|
||||
|
||||
// trade.Buy(lots, _Symbol, 0, sl, 0, "EA Signal");
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
double OnTester() {
|
||||
// Custom optimization criterion
|
||||
double trades = TesterStatistics(STAT_TRADES);
|
||||
if (trades < 30) return 0;
|
||||
return TesterStatistics(STAT_PROFIT_FACTOR);
|
||||
@@ -497,6 +768,11 @@ double OnTester() {
|
||||
- `24-customind/` — Custom indicator creation
|
||||
- `13-event-handlers/` — Event handlers (OnTick, OnTester, etc.)
|
||||
- `34-standardlibrary/` — Standard library (CTrade, CPositionInfo, etc.)
|
||||
- `01-constants/` — Enums and structures (MqlTradeRequest, ENUM_SYMBOL_CALC_MODE)
|
||||
- `references/symbol-spec/` — Symbol specification CSVs (broker-specific)
|
||||
- `specs-XAUUSD.csv` — XAUUSD: CFD Leverage, ContractSize=100, Digits=2
|
||||
- `specs-USDJPY.csv` — USDJPY: Forex, ContractSize=100000, Digits=3
|
||||
- `scripts/verify_sl_tp_formulas.py` — Python verification of SL/TP risk formulas
|
||||
|
||||
### External
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
Property,Value
|
||||
Sector,SECTOR_CURRENCY
|
||||
Industry,INDUSTRY_UNDEFINED
|
||||
Digits,3
|
||||
Contract size,100000
|
||||
Spread,3 (floating)
|
||||
Stops level,0
|
||||
Margin currency,USD
|
||||
Profit currency,JPY
|
||||
Calculation,Forex
|
||||
Tick size,0.00100000
|
||||
Tick value,0.618839
|
||||
Hedged margin,0.00
|
||||
Chart mode,By bid price
|
||||
Trade,Full access
|
||||
Execution,Market
|
||||
GTC mode,Good till cancelled
|
||||
Filling,Immediate or Cancel
|
||||
Expiration,All
|
||||
Orders,"Market, Limit, Stop, Stop Limit, Stop Loss, Take Profit"
|
||||
Minimal volume,0.01
|
||||
Maximal volume,80.00
|
||||
Volume step,0.01
|
||||
Swap type,In points
|
||||
Swap long,5.96
|
||||
Swap short,-16.95
|
||||
Swap rates Monday,1
|
||||
Swap rates Tuesday,1
|
||||
Swap rates Wednesday,3
|
||||
Swap rates Thursday,1
|
||||
Swap rates Friday,1
|
||||
Margin rates Market buy Initial,1.0000000
|
||||
Margin rates Market buy usd / lot,~0.00
|
||||
Margin rates Market buy Maintenance,1.0000000
|
||||
Margin rates Market buy usd / lot,~0.00
|
||||
Margin rates Market sell Initial,1.0000000
|
||||
Margin rates Market sell usd / lot,~0.00
|
||||
Margin rates Market sell Maintenance,1.0000000
|
||||
Margin rates Market sell usd / lot,~0.00
|
||||
|
@@ -0,0 +1,39 @@
|
||||
Property,Value
|
||||
Sector,SECTOR_COMMODITIES
|
||||
Industry,INDUSTRY_COMMODITIES_PRECIOUS
|
||||
Digits,2
|
||||
Contract size,100
|
||||
Spread,12 (floating)
|
||||
Stops level,0
|
||||
Margin currency,USD
|
||||
Profit currency,USD
|
||||
Calculation,CFD Leverage
|
||||
Tick size,0.01000000
|
||||
Tick value,1.000000
|
||||
Hedged margin,25.00
|
||||
Chart mode,By bid price
|
||||
Trade,Full access
|
||||
Execution,Market
|
||||
GTC mode,Good till cancelled
|
||||
Filling,Immediate or Cancel
|
||||
Expiration,All
|
||||
Orders,"Market, Limit, Stop, Stop Limit, Stop Loss, Take Profit"
|
||||
Minimal volume,0.01
|
||||
Maximal volume,80.00
|
||||
Volume step,0.01
|
||||
Swap type,In points
|
||||
Swap long,-63.88
|
||||
Swap short,40.23
|
||||
Swap rates Monday,1
|
||||
Swap rates Tuesday,1
|
||||
Swap rates Wednesday,3
|
||||
Swap rates Thursday,1
|
||||
Swap rates Friday,1
|
||||
Margin rates Market buy Initial,1.0000000
|
||||
Margin rates Market buy usd / lot,~0.00
|
||||
Margin rates Market buy Maintenance,1.0000000
|
||||
Margin rates Market buy usd / lot,~0.00
|
||||
Margin rates Market sell Initial,1.0000000
|
||||
Margin rates Market sell usd / lot,~0.00
|
||||
Margin rates Market sell Maintenance,1.0000000
|
||||
Margin rates Market sell usd / lot,~0.00
|
||||
|
@@ -0,0 +1,400 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Verify SL/TP calculation formulas from MQL5 documentation.
|
||||
|
||||
Two symbol types:
|
||||
- XAUUSD: CFD Leverage mode → Profit = (close-open) * ContractSize * Lots
|
||||
- USDJPY: Forex mode → Profit = (close-open) * ContractSize * Lots
|
||||
(but profit currency = JPY, so dollar-equivalent needs conversion)
|
||||
|
||||
Bid prices used: XAUUSD=4121.28, USDJPY=161.561
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import csv
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
# ── Spec loader ──────────────────────────────────────────────────────
|
||||
|
||||
SPEC_DIR = Path(__file__).resolve().parent.parent / "references" / "symbol-spec"
|
||||
|
||||
|
||||
@dataclass
|
||||
class SymbolSpec:
|
||||
name: str
|
||||
digits: int
|
||||
contract_size: float
|
||||
calc_mode: str
|
||||
tick_size: float
|
||||
tick_value: float
|
||||
stops_level: int
|
||||
profit_currency: str
|
||||
lot_min: float
|
||||
lot_max: float
|
||||
lot_step: float
|
||||
|
||||
@property
|
||||
def point(self) -> float:
|
||||
"""SYMBOL_POINT = 10^(-digits)"""
|
||||
return 10 ** (-self.digits)
|
||||
|
||||
@classmethod
|
||||
def from_csv(cls, path: Path) -> "SymbolSpec":
|
||||
rows: dict[str, str] = {}
|
||||
with open(path, newline="") as f:
|
||||
for row in csv.reader(f):
|
||||
if len(row) >= 2:
|
||||
rows[row[0].strip()] = row[1].strip()
|
||||
return cls(
|
||||
name=path.stem.replace("specs-", ""),
|
||||
digits=int(rows["Digits"]),
|
||||
contract_size=float(rows["Contract size"]),
|
||||
calc_mode=rows["Calculation"],
|
||||
tick_size=float(rows["Tick size"]),
|
||||
tick_value=float(rows["Tick value"]),
|
||||
stops_level=int(rows["Stops level"]),
|
||||
profit_currency=rows["Profit currency"],
|
||||
lot_min=float(rows["Minimal volume"]),
|
||||
lot_max=float(rows["Maximal volume"]),
|
||||
lot_step=float(rows["Volume step"]),
|
||||
)
|
||||
|
||||
|
||||
# ── Core formulas ────────────────────────────────────────────────────
|
||||
#
|
||||
# Key distinction:
|
||||
# PointValue = value of 1 POINT move for 1 lot (in profit currency)
|
||||
# loss = number_of_points * PointValue * Lots
|
||||
#
|
||||
# Therefore:
|
||||
# number_of_points = loss / (PointValue * Lots)
|
||||
# sl_distance_price = number_of_points * point
|
||||
#
|
||||
# When profit_currency != account_currency, we must convert:
|
||||
# max_loss_profcy = balance * risk_pct / 100 * fx_rate_to_profcy
|
||||
#
|
||||
|
||||
def point_value(spec: SymbolSpec) -> float:
|
||||
"""
|
||||
MPM::PointValue: value of 1 POINT move for 1 lot, in profit currency.
|
||||
|
||||
Forex/CFD: point * ContractSize (since Profit = delta * ContractSize * Lots)
|
||||
Futures: point * TickValue / TickSize
|
||||
"""
|
||||
mode = spec.calc_mode.lower()
|
||||
if "forex" in mode or "cfd" in mode or "stock" in mode:
|
||||
return spec.point * spec.contract_size
|
||||
elif "future" in mode:
|
||||
return spec.point * spec.tick_value / spec.tick_size
|
||||
raise ValueError(f"Unsupported calc mode: {spec.calc_mode}")
|
||||
|
||||
|
||||
def risk_amount(
|
||||
balance_usd: float,
|
||||
risk_pct: float,
|
||||
spec: SymbolSpec,
|
||||
fx_rate: float,
|
||||
) -> float:
|
||||
"""
|
||||
Convert risk from account currency (USD) to profit currency.
|
||||
|
||||
fx_rate: how many units of profit_currency per 1 USD
|
||||
e.g. USDJPY=161.561 → fx_rate=161.561
|
||||
XAUUSD (USD=USD) → fx_rate=1.0
|
||||
"""
|
||||
return balance_usd * risk_pct / 100.0 * fx_rate
|
||||
|
||||
|
||||
def calc_sl_from_risk(
|
||||
spec: SymbolSpec,
|
||||
max_loss_profcy: float, # risk budget in profit currency
|
||||
lots: float,
|
||||
open_price: float,
|
||||
direction: str, # "BUY" or "SELL"
|
||||
) -> float:
|
||||
"""
|
||||
Given risk budget (in profit currency) and lot size, compute SL price.
|
||||
|
||||
points = max_loss / (PointValue * Lots) (number of points)
|
||||
sl_price = open_price ± points * point
|
||||
|
||||
BUY: SL = open - points * point
|
||||
SELL: SL = open + points * point
|
||||
"""
|
||||
pv = point_value(spec)
|
||||
points = max_loss_profcy / (pv * lots)
|
||||
sl_distance_price = points * spec.point
|
||||
|
||||
if direction == "BUY":
|
||||
sl = open_price - sl_distance_price
|
||||
else:
|
||||
sl = open_price + sl_distance_price
|
||||
return round(sl, spec.digits)
|
||||
|
||||
|
||||
def calc_lots_from_sl(
|
||||
spec: SymbolSpec,
|
||||
max_loss_profcy: float, # risk budget in profit currency
|
||||
open_price: float,
|
||||
sl_price: float,
|
||||
) -> float:
|
||||
"""
|
||||
Given a fixed SL price, compute lot size so that loss == max_loss_profcy.
|
||||
|
||||
sl_distance_price = abs(open - sl)
|
||||
points = sl_distance_price / point
|
||||
Lots = max_loss / (PointValue * points)
|
||||
|
||||
Then normalize to lot_step, clamp to [lot_min, lot_max].
|
||||
"""
|
||||
sl_distance_price = abs(open_price - sl_price)
|
||||
if sl_distance_price == 0:
|
||||
return 0.0
|
||||
pv = point_value(spec)
|
||||
points = sl_distance_price / spec.point
|
||||
raw_lots = max_loss_profcy / (pv * points)
|
||||
|
||||
# Normalize to lot_step
|
||||
normed = int(raw_lots / spec.lot_step) * spec.lot_step
|
||||
normed = max(normed, spec.lot_min)
|
||||
normed = min(normed, spec.lot_max)
|
||||
return round(normed, 8)
|
||||
|
||||
|
||||
def calc_profit(
|
||||
spec: SymbolSpec, lots: float, open_price: float, close_price: float
|
||||
) -> float:
|
||||
"""
|
||||
Profit in profit currency (from OrderCalcProfit formulas).
|
||||
"""
|
||||
mode = spec.calc_mode.lower()
|
||||
if "forex" in mode or "cfd" in mode or "stock" in mode:
|
||||
return (close_price - open_price) * spec.contract_size * lots
|
||||
elif "future" in mode:
|
||||
return (close_price - open_price) * spec.tick_value / spec.tick_size * lots
|
||||
raise ValueError(f"Unsupported: {spec.calc_mode}")
|
||||
|
||||
|
||||
# ── Display helpers ──────────────────────────────────────────────────
|
||||
|
||||
SEP = "─" * 72
|
||||
PCY = " " # profit currency suffix
|
||||
|
||||
|
||||
def fmt(v: float, d: int) -> str:
|
||||
return f"{v:,.{d}f}"
|
||||
|
||||
|
||||
def run_tests(
|
||||
spec: SymbolSpec, bid: float, account_balance: float, fx_rate: float
|
||||
):
|
||||
pc = spec.profit_currency # e.g. "JPY" or "USD"
|
||||
|
||||
print(f"\n{SEP}")
|
||||
print(f" Symbol: {spec.name} | Calc Mode: {spec.calc_mode}")
|
||||
print(f" Digits={spec.digits} ContractSize={spec.contract_size}")
|
||||
print(f" Point={spec.point} TickSize={spec.tick_size} TickValue={spec.tick_value}")
|
||||
print(f" Profit currency: {pc} | FX rate: {fx_rate} {pc}/USD")
|
||||
print(SEP)
|
||||
|
||||
pv = point_value(spec)
|
||||
print(f" PointValue = {pv} ({pc} per 1-point move, 1 lot)")
|
||||
print()
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
# Test 1: SL → Profit round-trip (verify formula correctness)
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
print(" TEST 1: SL→Profit round-trip (fixed lots=0.10)")
|
||||
lots = 0.10
|
||||
|
||||
for sl_distance_pts in [100, 500, 1000, 2000]:
|
||||
sl_dist_price = sl_distance_pts * spec.point
|
||||
sl_buy = round(bid - sl_dist_price, spec.digits)
|
||||
loss_buy = calc_profit(spec, lots, bid, sl_buy)
|
||||
|
||||
print(f" SL距离={sl_distance_pts:>5} pts "
|
||||
f"→ 价格距离={sl_dist_price} "
|
||||
f"BUY SL={fmt(sl_buy, spec.digits)} "
|
||||
f"亏损={fmt(loss_buy, spec.digits)} {pc}")
|
||||
|
||||
# Also verify with Ask = Bid + spread
|
||||
spread_pts = 12 if spec.name == "XAUUSD" else 3
|
||||
ask = round(bid + spread_pts * spec.point, spec.digits)
|
||||
print(f" (Ask={fmt(ask, spec.digits)}, spread={spread_pts} pts)")
|
||||
print()
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
# Test 2: Risk% → SL price (forward direction)
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
print(f" TEST 2: Risk%→SL (Balance={fmt(account_balance, 2)} USD, Lots=0.10)")
|
||||
for risk_pct in [0.5, 1.0, 2.0, 5.0]:
|
||||
ml = risk_amount(account_balance, risk_pct, spec, fx_rate)
|
||||
for direction in ["BUY", "SELL"]:
|
||||
price = bid if direction == "BUY" else ask
|
||||
sl = calc_sl_from_risk(spec, ml, lots, price, direction)
|
||||
|
||||
# Verify: compute actual loss at this SL
|
||||
actual_loss = calc_profit(spec, lots, price, sl)
|
||||
|
||||
print(f" Risk={risk_pct}% {direction:4s} "
|
||||
f"SL={fmt(sl, spec.digits)} "
|
||||
f"目标亏损={fmt(ml, 2)} {pc} "
|
||||
f"实际亏损={fmt(actual_loss, 2)} {pc} "
|
||||
f"差={fmt(abs(actual_loss) - ml, 6)}")
|
||||
print()
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
# Test 3: Fixed SL price → Lot size (reverse direction)
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
risk_pct = 1.0
|
||||
ml = risk_amount(account_balance, risk_pct, spec, fx_rate)
|
||||
print(f" TEST 3: Fixed SL→Lots (Balance={fmt(account_balance, 2)} USD, Risk={risk_pct}%)")
|
||||
print(f" risk budget = {fmt(ml, 2)} {pc}")
|
||||
for sl_distance_pts in [100, 500, 1000, 2000]:
|
||||
sl_dist_price = sl_distance_pts * spec.point
|
||||
sl_buy = round(bid - sl_dist_price, spec.digits)
|
||||
lots_calc = calc_lots_from_sl(spec, ml, bid, sl_buy)
|
||||
|
||||
if lots_calc > 0:
|
||||
actual_loss = calc_profit(spec, lots_calc, bid, sl_buy)
|
||||
else:
|
||||
actual_loss = 0.0
|
||||
|
||||
print(f" SL距离={sl_distance_pts:>5} pts "
|
||||
f"SL={fmt(sl_buy, spec.digits)} "
|
||||
f"计算手数={lots_calc:.4f} "
|
||||
f"实际亏损={fmt(actual_loss, 2)} {pc} "
|
||||
f"差={fmt(abs(actual_loss) - ml, 6)}")
|
||||
print()
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
# Test 4: Cross-verify — forward vs reverse should match
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
print(" TEST 4: Cross-verify forward↔reverse")
|
||||
test_cases = [
|
||||
(0.5, 500),
|
||||
(1.0, 1000),
|
||||
(2.0, 1500),
|
||||
]
|
||||
for risk_pct, sl_pts in test_cases:
|
||||
ml = risk_amount(account_balance, risk_pct, spec, fx_rate)
|
||||
sl_dist_price = sl_pts * spec.point
|
||||
sl_buy = round(bid - sl_dist_price, spec.digits)
|
||||
|
||||
# Forward: risk% → SL (with lots=0.10)
|
||||
lots_fwd = 0.10
|
||||
sl_fwd = calc_sl_from_risk(spec, ml, lots_fwd, bid, "BUY")
|
||||
|
||||
# Reverse: SL → lots
|
||||
lots_rev = calc_lots_from_sl(spec, ml, bid, sl_buy)
|
||||
|
||||
# Forward loss check
|
||||
loss_fwd = calc_profit(spec, lots_fwd, bid, sl_fwd)
|
||||
|
||||
# Reverse loss check
|
||||
loss_rev = calc_profit(spec, lots_rev, bid, sl_buy)
|
||||
|
||||
print(f" Risk={risk_pct}% SL距离={sl_pts}pts budget={fmt(ml, 2)} {pc}")
|
||||
print(f" Forward: SL={fmt(sl_fwd, spec.digits)} lots={lots_fwd:.2f} "
|
||||
f"loss={fmt(loss_fwd, 2)} {pc} (budget={fmt(ml, 2)})")
|
||||
print(f" Reverse: lots={lots_rev:.4f} "
|
||||
f"loss={fmt(loss_rev, 2)} {pc} (budget={fmt(ml, 2)})")
|
||||
print(f" Δloss = {fmt(abs(loss_fwd) - ml, 6)} "
|
||||
f"(forward vs budget)")
|
||||
print()
|
||||
|
||||
|
||||
# ── Main ─────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
BALANCE = 10000.0 # USD demo account
|
||||
|
||||
specs = {
|
||||
"XAUUSD": SymbolSpec.from_csv(SPEC_DIR / "specs-XAUUSD.csv"),
|
||||
"USDJPY": SymbolSpec.from_csv(SPEC_DIR / "specs-USDJPY.csv"),
|
||||
}
|
||||
bids = {"XAUUSD": 4121.28, "USDJPY": 161.561}
|
||||
|
||||
# FX rates: profit_currency per 1 USD
|
||||
# XAUUSD: profit=USD → rate=1.0
|
||||
# USDJPY: profit=JPY → rate=USDJPY_bid
|
||||
fx_rates = {"XAUUSD": 1.0, "USDJPY": bids["USDJPY"]}
|
||||
|
||||
print("=" * 72)
|
||||
print(" MQL5 SL/TP Formula Verification")
|
||||
print(f" Account Balance: {BALANCE:,.2f} USD")
|
||||
print("=" * 72)
|
||||
|
||||
for name in ["XAUUSD", "USDJPY"]:
|
||||
run_tests(specs[name], bids[name], BALANCE, fx_rates[name])
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
# Special: USDJPY — currency conversion walkthrough
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
print(SEP)
|
||||
print(" USDJPY: Currency Conversion Walkthrough")
|
||||
print(SEP)
|
||||
jpy_spec = specs["USDJPY"]
|
||||
jpy_rate = bids["USDJPY"]
|
||||
lots = 0.10
|
||||
risk_pct = 1.0
|
||||
|
||||
# Step 1-2: risk budget in profit currency
|
||||
target_usd = BALANCE * risk_pct / 100.0 # = 100.00 USD
|
||||
target_jpy = target_usd * jpy_rate # = 16,156.10 JPY
|
||||
|
||||
# Step 3: formula → SL
|
||||
ml_jpy = risk_amount(BALANCE, risk_pct, jpy_spec, jpy_rate)
|
||||
sl_fwd = calc_sl_from_risk(jpy_spec, ml_jpy, lots, bids["USDJPY"], "BUY")
|
||||
|
||||
# Step 4-5: verify
|
||||
loss_jpy = calc_profit(jpy_spec, lots, bids["USDJPY"], sl_fwd)
|
||||
loss_usd = loss_jpy / jpy_rate
|
||||
|
||||
print(f" Risk={risk_pct}%, Lots={lots}, Balance={fmt(BALANCE, 2)} USD")
|
||||
print()
|
||||
print(f" Step 1: risk budget (USD) = {fmt(BALANCE, 2)} × {risk_pct}% = {fmt(target_usd, 2)} USD")
|
||||
print(f" Step 2: convert to JPY = {fmt(target_usd, 2)} × {jpy_rate} = {fmt(target_jpy, 2)} JPY")
|
||||
print(f" Step 3: points = budget / (PointValue × Lots)")
|
||||
print(f" = {fmt(target_jpy, 2)} / ({point_value(jpy_spec):.1f} × {lots}) = {target_jpy / (point_value(jpy_spec) * lots):.1f} pts")
|
||||
print(f" SL距离 = {target_jpy / (point_value(jpy_spec) * lots):.1f} × {jpy_spec.point} = "
|
||||
f"{target_jpy / (point_value(jpy_spec) * lots) * jpy_spec.point:.4f} price")
|
||||
print(f" SL = {bids['USDJPY']} - {target_jpy / (point_value(jpy_spec) * lots) * jpy_spec.point:.4f} = "
|
||||
f"{fmt(sl_fwd, jpy_spec.digits)}")
|
||||
print()
|
||||
print(f" Step 4: actual loss = {fmt(loss_jpy, 2)} JPY")
|
||||
print(f" Step 5: loss in USD = {fmt(loss_jpy, 2)} / {jpy_rate} = {fmt(loss_usd, 2)} USD")
|
||||
print()
|
||||
print(f" Result: target {fmt(target_usd, 2)} USD ≈ actual {fmt(loss_usd, 2)} USD "
|
||||
f"(差={fmt(abs(loss_usd) - target_usd, 4)} USD, "
|
||||
f"来自 NormalizeDouble 四舍五入)")
|
||||
print()
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
# Special: XAUUSD lots sensitivity for 1% risk
|
||||
# ─────────────────────────────────────────────────────────────────
|
||||
print(SEP)
|
||||
print(" XAUUSD: Lots vs SL distance for 1% risk ($100 target loss)")
|
||||
print(SEP)
|
||||
xau = specs["XAUUSD"]
|
||||
ml_usd = risk_amount(BALANCE, 1.0, xau, 1.0)
|
||||
for lots in [0.01, 0.05, 0.10, 0.50, 1.00, 2.00]:
|
||||
pv = point_value(xau)
|
||||
points = ml_usd / (pv * lots)
|
||||
sl_dist_price = points * xau.point
|
||||
sl_dist_pts = int(points)
|
||||
sl = round(bids["XAUUSD"] - sl_dist_price, xau.digits)
|
||||
|
||||
# Verify
|
||||
actual_loss = calc_profit(xau, lots, bids["XAUUSD"], sl)
|
||||
|
||||
print(f" Lots={lots:>5.2f} "
|
||||
f"SL距离={sl_dist_pts:>6} pts ({sl_dist_price:.2f} price) "
|
||||
f"SL={fmt(sl, xau.digits)} "
|
||||
f"亏损={fmt(actual_loss, 2)} USD "
|
||||
f"差={fmt(abs(actual_loss) - ml_usd, 6)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user