PythonMetaTrader5 2.3.2__tar.gz → 3.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonmetatrader5-3.0.1/LICENSE +16 -0
- pythonmetatrader5-3.0.1/PKG-INFO +492 -0
- pythonmetatrader5-3.0.1/README.md +457 -0
- pythonmetatrader5-3.0.1/pyproject.toml +87 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/__init__.py +73 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/adapters/__init__.py +1 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/adapters/mt5_mapping.py +92 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/adapters/mt5_session.py +48 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/adapters/mt5_terminal.py +224 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/adapters/system_clock.py +10 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/application/__init__.py +1 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/application/batch.py +60 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/application/entries.py +163 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/application/execution.py +108 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/application/magic_scope.py +75 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/application/ports.py +75 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/application/trader.py +275 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/connection.py +52 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/domain/__init__.py +1 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/domain/amounts.py +33 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/domain/errors.py +53 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/domain/inputs.py +106 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/domain/market.py +91 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/domain/orders.py +64 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/domain/requests.py +32 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/domain/retcodes.py +81 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/domain/stops.py +85 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/domain/symbol.py +158 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/legacy/__init__.py +1 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/legacy/arguments.py +52 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/legacy/broker.py +284 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/legacy/mt5_namespace.py +56 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/legacy/views.py +123 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/py.typed +0 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5/result.py +41 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5.egg-info/PKG-INFO +492 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5.egg-info/SOURCES.txt +39 -0
- pythonmetatrader5-3.0.1/src/PythonMetaTrader5.egg-info/requires.txt +11 -0
- pythonmetatrader5-2.3.2/PKG-INFO +0 -267
- pythonmetatrader5-2.3.2/README.md +0 -250
- pythonmetatrader5-2.3.2/pyproject.toml +0 -39
- pythonmetatrader5-2.3.2/src/PythonMetaTrader5/__init__.py +0 -535
- pythonmetatrader5-2.3.2/src/PythonMetaTrader5.egg-info/PKG-INFO +0 -267
- pythonmetatrader5-2.3.2/src/PythonMetaTrader5.egg-info/SOURCES.txt +0 -8
- pythonmetatrader5-2.3.2/src/PythonMetaTrader5.egg-info/requires.txt +0 -3
- {pythonmetatrader5-2.3.2 → pythonmetatrader5-3.0.1}/setup.cfg +0 -0
- {pythonmetatrader5-2.3.2 → pythonmetatrader5-3.0.1}/src/PythonMetaTrader5.egg-info/dependency_links.txt +0 -0
- {pythonmetatrader5-2.3.2 → pythonmetatrader5-3.0.1}/src/PythonMetaTrader5.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
PythonMetaTrader5
|
|
2
|
+
Copyright (c) Wiktor Jelen
|
|
3
|
+
|
|
4
|
+
This work is licensed under the Creative Commons Attribution-NonCommercial 4.0
|
|
5
|
+
International License (CC BY-NC 4.0).
|
|
6
|
+
|
|
7
|
+
You are free to share and adapt this work for non-commercial purposes, provided
|
|
8
|
+
you give appropriate credit, link to the license, and indicate if changes were
|
|
9
|
+
made.
|
|
10
|
+
|
|
11
|
+
Full legal code: https://creativecommons.org/licenses/by-nc/4.0/legalcode
|
|
12
|
+
Summary: https://creativecommons.org/licenses/by-nc/4.0/
|
|
13
|
+
|
|
14
|
+
THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. TRADING FINANCIAL
|
|
15
|
+
INSTRUMENTS CARRIES RISK; THE AUTHORS ARE NOT LIABLE FOR ANY LOSSES INCURRED
|
|
16
|
+
THROUGH THE USE OF THIS SOFTWARE.
|
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PythonMetaTrader5
|
|
3
|
+
Version: 3.0.1
|
|
4
|
+
Summary: Safe, typed trading layer over the official MetaTrader5 Python API
|
|
5
|
+
Author-email: Wiktor Jelen <wiktorjn@gmail.com>
|
|
6
|
+
License-Expression: CC-BY-NC-4.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/Akinzou/MetaTrader5-Python
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/Akinzou/MetaTrader5-Python/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/Akinzou/MetaTrader5-Python/blob/master/CHANGELOG.md
|
|
10
|
+
Keywords: metatrader5,mt5,trading,forex,algotrading
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: MetaTrader5; sys_platform == "win32"
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-cov>=5; extra == "dev"
|
|
30
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
31
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
32
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
33
|
+
Requires-Dist: twine>=5; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# PythonMetaTrader5
|
|
37
|
+
|
|
38
|
+
[](https://github.com/Akinzou/MetaTrader5-Python/actions/workflows/ci.yml)
|
|
39
|
+
[](https://pypi.org/project/PythonMetaTrader5/)
|
|
40
|
+

|
|
41
|
+

|
|
42
|
+
[](https://pepy.tech/projects/pythonmetatrader5)
|
|
43
|
+
[](https://discord.gg/BARYa55KS8)
|
|
44
|
+
|
|
45
|
+
A safe, typed trading layer over the official
|
|
46
|
+
[`MetaTrader5`](https://pypi.org/project/MetaTrader5/) Python package.
|
|
47
|
+
|
|
48
|
+
Built for bots (webhook executors, signal followers) where a trading call must
|
|
49
|
+
**never hang, never retry blindly, and never fail silently**.
|
|
50
|
+
|
|
51
|
+
## Contents
|
|
52
|
+
|
|
53
|
+
- [Why this library](#why-this-library)
|
|
54
|
+
- [Requirements and limits](#requirements-and-limits)
|
|
55
|
+
- [Installation](#installation)
|
|
56
|
+
- [Quick start](#quick-start)
|
|
57
|
+
- [Connecting](#connecting)
|
|
58
|
+
- [Opening positions and limit orders](#opening-positions-and-limit-orders)
|
|
59
|
+
- [Stops: SL and TP](#stops-sl-and-tp)
|
|
60
|
+
- [Volumes, prices and comments](#volumes-prices-and-comments)
|
|
61
|
+
- [Closing and cancelling](#closing-and-cancelling)
|
|
62
|
+
- [Magic numbers: whose trades a call touches](#magic-numbers-whose-trades-a-call-touches)
|
|
63
|
+
- [Symbol and ticket](#symbol-and-ticket)
|
|
64
|
+
- [One bot per account and magic 0](#one-bot-per-account-and-magic-0)
|
|
65
|
+
- [Netting accounts](#netting-accounts)
|
|
66
|
+
- [Reading positions and orders](#reading-positions-and-orders)
|
|
67
|
+
- [Results and errors](#results-and-errors)
|
|
68
|
+
- [Error kinds](#error-kinds)
|
|
69
|
+
- [Retries](#retries)
|
|
70
|
+
- [Batch failures](#batch-failures)
|
|
71
|
+
- [Logging](#logging)
|
|
72
|
+
- [Calling MetaTrader5 functions directly](#calling-metatrader5-functions-directly)
|
|
73
|
+
- [Migrating from 2.x](#migrating-from-2x)
|
|
74
|
+
- [Before trading real money](#before-trading-real-money)
|
|
75
|
+
- [Architecture and decisions (ADR)](#architecture-and-decisions-adr)
|
|
76
|
+
- [Development](#development)
|
|
77
|
+
- [License](#license)
|
|
78
|
+
|
|
79
|
+
Links to the other documents point to GitHub; on PyPI they open the files of
|
|
80
|
+
the release you are reading (ADR 0012). On PyPI the Contents entries
|
|
81
|
+
above do not jump to their sections; read this page on
|
|
82
|
+
[GitHub](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/README.md#contents) for that.
|
|
83
|
+
|
|
84
|
+
Other documents: [CHANGELOG.md](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/CHANGELOG.md) ·
|
|
85
|
+
[MIGRATION.md](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/MIGRATION.md) · [docs/architecture.md](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/architecture.md) ·
|
|
86
|
+
[docs/adr](https://github.com/Akinzou/MetaTrader5-Python/tree/v3.0.1/docs/adr)
|
|
87
|
+
|
|
88
|
+
## Why this library
|
|
89
|
+
|
|
90
|
+
- **Every trading call returns.** Requotes are retried a bounded number of times
|
|
91
|
+
with a fresh quote; everything else comes back immediately as a typed error.
|
|
92
|
+
(The deprecated `Broker.login(ToEffect=True)` is the one exception: as in
|
|
93
|
+
2.x, it keeps trying until the login works.)
|
|
94
|
+
- **No silent defaults.** Values that MT5 would quietly read as something else
|
|
95
|
+
are refused before sending: `sl=0` ("no stop"), `ticket=0` (possibly "no
|
|
96
|
+
filter"), `volume=True` (one lot), NaN and infinities.
|
|
97
|
+
- **No double fills.** Timeouts and lost connections are never auto-retried:
|
|
98
|
+
the order may already be live.
|
|
99
|
+
- **Typed results.** `Ok(receipt)` or `Err(TradeError)` with an `ErrorKind`
|
|
100
|
+
(`MARKET_CLOSED`, `NO_MONEY`, `INVALID_STOPS`, ...) instead of magic numbers.
|
|
101
|
+
- **Correct prices and lots.** Prices snap to the symbol's tick grid, volumes
|
|
102
|
+
are checked against min/max/step, the filling mode is chosen from the symbol.
|
|
103
|
+
- **Close-all touches only your bot's magic.** `close_positions()` and
|
|
104
|
+
`cancel_pending_orders()` act on the trader's own magic number unless told
|
|
105
|
+
otherwise. With the default `magic=0` that includes manual trades: set your
|
|
106
|
+
own `magic` ([details](#one-bot-per-account-and-magic-0)).
|
|
107
|
+
- **SL/TP as prices or distances in points.**
|
|
108
|
+
- **2.x compatible.** The old `Broker` class still works (deprecated).
|
|
109
|
+
|
|
110
|
+
## Requirements and limits
|
|
111
|
+
|
|
112
|
+
- **Windows** with a MetaTrader 5 terminal installed: the official
|
|
113
|
+
`MetaTrader5` package is Windows-only. Python **3.10+**.
|
|
114
|
+
- **Algo Trading** must be enabled in the terminal. Without it every order
|
|
115
|
+
fails with `ALGO_TRADING_DISABLED`. The library does not toggle it for you
|
|
116
|
+
([ADR 0004](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0004-no-gui-automation.md)).
|
|
117
|
+
- **One connection per process.** The official package keeps a single global
|
|
118
|
+
terminal session, so a second `connect()` (or its failed login) affects the
|
|
119
|
+
first. Run one process per account.
|
|
120
|
+
- **Thread-safe within the library.** Every terminal call holds one
|
|
121
|
+
process-wide lock, so a threaded webhook server can share a `Trader`
|
|
122
|
+
([ADR 0009](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0009-one-lock-per-process.md)). The MT5 functions
|
|
123
|
+
re-exported through `PythonMetaTrader5` hold it for each single call (a pair
|
|
124
|
+
such as `order_send` + `last_error` is not atomic). Calls you make to the
|
|
125
|
+
`MetaTrader5` module directly bypass it.
|
|
126
|
+
|
|
127
|
+
## Installation
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
pip install PythonMetaTrader5
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
To stay on the old API: `pip install "PythonMetaTrader5<3"`.
|
|
134
|
+
|
|
135
|
+
## Quick start
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from PythonMetaTrader5 import Err, Ok, Side, Stop, connect
|
|
139
|
+
|
|
140
|
+
match connect(12345678, "password", "MetaQuotes-Demo", magic=1001):
|
|
141
|
+
case Err(error):
|
|
142
|
+
raise SystemExit(f"cannot connect: {error}")
|
|
143
|
+
case Ok(trader):
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
with trader: # shuts the terminal connection down on exit
|
|
147
|
+
result = trader.open_position(
|
|
148
|
+
"EURUSD", Side.BUY, 0.10,
|
|
149
|
+
sl=Stop.points(1000), # 1000 points below the entry price
|
|
150
|
+
tp=1.12500, # a plain float is an absolute price
|
|
151
|
+
comment="webhook #42",
|
|
152
|
+
)
|
|
153
|
+
match result:
|
|
154
|
+
case Ok(receipt):
|
|
155
|
+
print("filled at", receipt.price, "deal", receipt.deal)
|
|
156
|
+
case Err(error):
|
|
157
|
+
print("not opened:", error.kind, error.message)
|
|
158
|
+
|
|
159
|
+
trader.close_positions("EURUSD") # only this bot's (magic 1001) positions
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Connecting
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
connect(login, password, server, *, path=None, timeout_ms=60000, magic=0,
|
|
166
|
+
deviation=10, retry=None) -> Result[Trader, TradeError]
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
| Argument | Meaning |
|
|
170
|
+
|---|---|
|
|
171
|
+
| `login` | Account number: a positive integer or a string of ASCII digits. `True`, `12345678.9` and similar values are rejected, not coerced |
|
|
172
|
+
| `password`, `server` | As in the terminal's login dialog, e.g. `"MetaQuotes-Demo"` |
|
|
173
|
+
| `path` | Path to `terminal64.exe` when more than one terminal is installed |
|
|
174
|
+
| `timeout_ms` | How long the terminal may take to start and log in |
|
|
175
|
+
| `magic` | The bot's own magic number, an integer from 0 to 2\*\*64-1. Every order is tagged with it, and close-all/cancel-all act only on it ([see below](#magic-numbers-whose-trades-a-call-touches)). Anything else, `None` included, returns `Err(INVALID_ARGUMENT)` |
|
|
176
|
+
| `deviation` | Maximum accepted slippage in points for market orders |
|
|
177
|
+
| `retry` | A `RetryPolicy` ([see Retries](#retries)) |
|
|
178
|
+
|
|
179
|
+
`connect()` never raises for a failed login: it returns
|
|
180
|
+
`Err(TradeError(CONNECTION_FAILED, ...))`. A `Trader` is a context manager;
|
|
181
|
+
`with trader:` calls `shutdown()` on exit.
|
|
182
|
+
|
|
183
|
+
## Opening positions and limit orders
|
|
184
|
+
|
|
185
|
+
| `Trader` method | Returns |
|
|
186
|
+
|---|---|
|
|
187
|
+
| `open_position(symbol, side, volume, *, sl=None, tp=None, price=None, comment=None)` | `Result[TradeReceipt, TradeError]` |
|
|
188
|
+
| `place_limit_order(symbol, side, volume, price, *, sl=None, tp=None, comment=None)` | `Result[TradeReceipt, TradeError]` |
|
|
189
|
+
| `close_positions(symbol=None, *, ticket=None, magic=<own>, comment=None)` | `Result[list[TradeReceipt], BatchFailure]` |
|
|
190
|
+
| `cancel_pending_orders(symbol=None, *, magic=<own>, kind=None)` | `Result[list[TradeReceipt], BatchFailure]` |
|
|
191
|
+
| `positions(symbol=None, *, ticket=None, magic=None)` | `Result[list[Position], TradeError]` |
|
|
192
|
+
| `pending_orders(symbol=None, *, magic=None, kind=None)` | `Result[list[PendingOrder], TradeError]` |
|
|
193
|
+
| `shutdown()` | `None` |
|
|
194
|
+
|
|
195
|
+
- `side`: `Side.BUY` / `Side.SELL`, or the strings `"buy"`, `"sell"`, `"long"`,
|
|
196
|
+
`"short"`.
|
|
197
|
+
- A market order is built from the **current** quote; `price=` sends it once at
|
|
198
|
+
that price instead.
|
|
199
|
+
- The filling mode comes from the symbol's allowed modes (FOK, then IOC, then
|
|
200
|
+
RETURN); limit orders on exchange-execution symbols use RETURN
|
|
201
|
+
([ADR 0007](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0007-filling-and-price-grid.md)).
|
|
202
|
+
|
|
203
|
+
### Stops: SL and TP
|
|
204
|
+
|
|
205
|
+
- A float is an **absolute price**; `Stop.points(n)` is a distance of `n`
|
|
206
|
+
points from the entry price (`Stop.at_price(p)` is the explicit form).
|
|
207
|
+
- For limit orders the distance is measured from the limit price, and for a
|
|
208
|
+
market order with an explicit `price` from that price.
|
|
209
|
+
- Pass `None` for no stop. A float stop that is zero, negative or not finite is
|
|
210
|
+
rejected (`INVALID_STOPS`): MT5 would read 0 as "no stop"
|
|
211
|
+
([ADR 0008](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0008-refuse-values-mt5-misreads.md)).
|
|
212
|
+
- `Stop.points()` / `Stop.at_price()` raise `ValueError` unless the value is a
|
|
213
|
+
positive finite number (bool and str included); the sign of a points distance
|
|
214
|
+
is ignored. A `kind` that is not a `StopKind` raises `TypeError`.
|
|
215
|
+
- A stop that resolves outside the tick grid's range, or to zero or below, is
|
|
216
|
+
`INVALID_STOPS` when the order is built.
|
|
217
|
+
- For a value that may be bad at runtime, such as an ATR that is NaN during
|
|
218
|
+
warm-up, use `Stop.parse(value, StopKind.POINTS)`, which returns `Ok(stop)` /
|
|
219
|
+
`Err(INVALID_STOPS)` instead of raising.
|
|
220
|
+
|
|
221
|
+
### Volumes, prices and comments
|
|
222
|
+
|
|
223
|
+
- Amounts may be `int`, `float`, `Decimal` or numpy scalars; `bool` and strings
|
|
224
|
+
are refused (the deprecated `Broker` still converts numeric strings).
|
|
225
|
+
- Volumes are checked against the symbol's min, max and step before sending
|
|
226
|
+
(`INVALID_VOLUME`). Prices snap to the symbol's tick size, not just `digits`,
|
|
227
|
+
so symbols with a tick of 0.25 work.
|
|
228
|
+
- `comment`: at most 29 UTF-16 units: 29 characters, but characters outside the
|
|
229
|
+
BMP, such as most emoji, count as 2 (combined emoji more). The `MetaTrader5`
|
|
230
|
+
package refuses 30 or more (measured on 5.0.6231; its docs say 31). Longer
|
|
231
|
+
comments, and comments that are not `str`, are rejected before sending
|
|
232
|
+
(`INVALID_ARGUMENT`), not truncated.
|
|
233
|
+
|
|
234
|
+
## Closing and cancelling
|
|
235
|
+
|
|
236
|
+
### Magic numbers: whose trades a call touches
|
|
237
|
+
|
|
238
|
+
A magic number tags every order a bot sends. MT5 gives trades opened by hand
|
|
239
|
+
magic **0**. Decision and reasoning:
|
|
240
|
+
[ADR 0011](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0011-close-all-defaults-to-own-magic.md).
|
|
241
|
+
|
|
242
|
+
| Call | Acts on |
|
|
243
|
+
|---|---|
|
|
244
|
+
| `close_positions()` / `cancel_pending_orders()` | Only the trader's own magic (the one given to `connect()`) |
|
|
245
|
+
| `...(magic=None)` | Still only the own magic: a `None` from a missing config key cannot widen the call |
|
|
246
|
+
| `...(magic=7)` | Only magic 7 |
|
|
247
|
+
| `...(magic=ANY_MAGIC)` | Every magic on the account, manual trades included |
|
|
248
|
+
| `close_positions(ticket=t)` | Position `t`, whatever its magic: you named it. With `magic=` too, `t` closes only if its magic matches |
|
|
249
|
+
| `positions()` / `pending_orders()` | Every magic by default: a read cannot close anything |
|
|
250
|
+
|
|
251
|
+
A magic must be an integer from 0 to 2\*\*64-1 (MT5's ulong). `"42"`, `True`,
|
|
252
|
+
`1.5`, `-1` and `2**64` are refused as `INVALID_ARGUMENT` instead of matching
|
|
253
|
+
nothing and reporting an empty success.
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
from PythonMetaTrader5 import ANY_MAGIC
|
|
257
|
+
|
|
258
|
+
trader.close_positions() # this bot's positions only
|
|
259
|
+
trader.close_positions(magic=ANY_MAGIC) # the whole account
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Symbol and ticket
|
|
263
|
+
|
|
264
|
+
- `symbol` limits a call to that symbol exactly: `close_positions("EURGBP")`
|
|
265
|
+
never touches a EURUSD position, also with `magic=ANY_MAGIC`. The adapter
|
|
266
|
+
re-checks the symbol of every position MT5 returns.
|
|
267
|
+
- `ticket` takes precedence over `symbol`, as in 2.x:
|
|
268
|
+
`close_positions("EURGBP", ticket=t)` closes position `t` even if it is on
|
|
269
|
+
EURUSD.
|
|
270
|
+
- `ticket` must be a positive integer; `0`, `True` or `"7"` are rejected (0 may
|
|
271
|
+
mean "no filter" to MT5). An empty string `""` as symbol is rejected too: a
|
|
272
|
+
missing webhook field must not mean "every symbol".
|
|
273
|
+
- `cancel_pending_orders(kind=PendingKind.LIMIT)` limits a cancel to one order
|
|
274
|
+
type (`LIMIT`, `STOP`, `STOP_LIMIT`).
|
|
275
|
+
- Each position is closed on its own symbol with its own spec, and a symbol
|
|
276
|
+
with broken data can still be closed.
|
|
277
|
+
|
|
278
|
+
### One bot per account and magic 0
|
|
279
|
+
|
|
280
|
+
`connect()` keeps `magic=0` as its default, so a bot that is alone on its
|
|
281
|
+
account works without setting one. The cost: manual trades share magic 0, so
|
|
282
|
+
such a bot's close-all also closes manual positions and cancels manual pending
|
|
283
|
+
orders. Whenever a defaulted close/cancel of a magic-0 trader affects
|
|
284
|
+
something, the library logs a warning:
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
trader magic is 0, which manual trades share: this close/cancel includes them; set magic at connect(), or pass magic=0 to confirm
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
- Bot alone on the account and the behaviour is intended: pass `magic=0`
|
|
291
|
+
explicitly (`close_positions(magic=0)`) and the warning is not logged.
|
|
292
|
+
- Bot next to manual trading or other bots: give it its own magic in
|
|
293
|
+
`connect(..., magic=1001)`.
|
|
294
|
+
|
|
295
|
+
### Netting accounts
|
|
296
|
+
|
|
297
|
+
On a netting account one position per symbol collects every deal and keeps the
|
|
298
|
+
magic of the deal that opened it, so merged volume cannot be told apart by
|
|
299
|
+
magic. A bot's deal that merges into a manual position is not "own"; a manual
|
|
300
|
+
deal that merges into the bot's position is. When a defaulted close/cancel
|
|
301
|
+
matches nothing while items with other magics are in scope, the library logs
|
|
302
|
+
how many it left open, so `Ok([])` never reads as "flat" in silence. If that
|
|
303
|
+
extra listing fails, the warning says it could not check.
|
|
304
|
+
|
|
305
|
+
## Reading positions and orders
|
|
306
|
+
|
|
307
|
+
- `Position`: `ticket`, `symbol`, `side`, `volume`, `price_open`, `sl`, `tp`
|
|
308
|
+
(`None` when not set), `price_current`, `profit`, `swap`, `time`, `magic`,
|
|
309
|
+
`comment`, and MT5's `identifier`, `reason`, `time_msc`, `time_update`,
|
|
310
|
+
`time_update_msc`, `external_id`.
|
|
311
|
+
- `PendingOrder`: `ticket`, `symbol`, `side`, `kind` (`PendingKind`), `volume`,
|
|
312
|
+
`price`, `magic`, `comment`.
|
|
313
|
+
- `TradeReceipt` (the broker's answer): `retcode`, `deal`, `order`, `volume`,
|
|
314
|
+
`price`, `bid`, `ask`, `comment`, `request_id`, `retcode_external`. For a
|
|
315
|
+
close, `order` / `deal` are the IDs of the closing deal, not the position
|
|
316
|
+
ticket.
|
|
317
|
+
- An empty account is `Ok([])`; a terminal failure is `Err(TERMINAL_ERROR)`,
|
|
318
|
+
never an empty list.
|
|
319
|
+
|
|
320
|
+
## Results and errors
|
|
321
|
+
|
|
322
|
+
Expected failures are values, not exceptions
|
|
323
|
+
([ADR 0002](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0002-result-at-boundaries.md)): every call returns `Ok(...)`
|
|
324
|
+
or `Err(...)`. Exceptions are reserved for programming errors such as
|
|
325
|
+
`RetryPolicy(attempts=0)` or `Stop.points("x")`.
|
|
326
|
+
|
|
327
|
+
`TradeError(kind, message, retcode=None, receipt=None, ticket=None)`. The
|
|
328
|
+
`receipt` carries the broker's raw answer when there was one; `ticket` names the
|
|
329
|
+
position/order in a batch.
|
|
330
|
+
|
|
331
|
+
### Error kinds
|
|
332
|
+
|
|
333
|
+
| `ErrorKind` | Meaning | What to do |
|
|
334
|
+
|---|---|---|
|
|
335
|
+
| `INVALID_ARGUMENT`, `INVALID_VOLUME`, `INVALID_PRICE`, `INVALID_STOPS` | Refused before sending, or by the server for the same reason | Fix the input; a resend gives the same answer |
|
|
336
|
+
| `INVALID_FILLING` | The server refused the filling mode the library chose from the symbol's flags (10030) | Nothing to fix in your call: escalate to the broker; a resend gives the same answer |
|
|
337
|
+
| `NO_MONEY`, `LIMIT_REACHED` | Margin or a broker limit | Do not resend unchanged |
|
|
338
|
+
| `MARKET_CLOSED`, `TRADE_DISABLED` | The symbol cannot be traded now | Wait or skip |
|
|
339
|
+
| `ALGO_TRADING_DISABLED` | Algo Trading is off in the terminal (10027) or on the server (10026) | 10027: enable it in the terminal. 10026: the broker or account forbids it; contact the broker |
|
|
340
|
+
| `PARTIAL_FILL` | Part of the volume executed | Check `positions()`; never re-sent |
|
|
341
|
+
| `OUTCOME_UNKNOWN` | Timeout, lost connection, or a queued market deal | **Check `positions()` before anything else**: the order may be live |
|
|
342
|
+
| `RETRIES_EXHAUSTED` | Requotes/price changes outlasted the retry policy | Retry later with a fresh decision |
|
|
343
|
+
| `REJECTED` | Any other server refusal (also unknown retcodes) | Read `message` and `retcode` |
|
|
344
|
+
| `SYMBOL_NOT_FOUND`, `NO_PRICE` | Unknown symbol, or no quote | Check the symbol name and market data |
|
|
345
|
+
| `BROKEN_SYMBOL_DATA` | The terminal's symbol data cannot price an order (point 0, NaN tick size, volume step 0) | Escalate; a resend gives the same answer. Closing still works |
|
|
346
|
+
| `TERMINAL_ERROR`, `CONNECTION_FAILED` | The terminal or the login failed | Check the terminal and credentials |
|
|
347
|
+
|
|
348
|
+
### Retries
|
|
349
|
+
|
|
350
|
+
[ADR 0003](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0003-bounded-retries.md):
|
|
351
|
+
|
|
352
|
+
| Server answer | What happens |
|
|
353
|
+
|---|---|
|
|
354
|
+
| market deal (entry or close): done | `Ok(receipt)` |
|
|
355
|
+
| pending order: placed or done | `Ok(receipt)` |
|
|
356
|
+
| market deal: partial fill | `PARTIAL_FILL` with the receipt; part of the deal exists, never re-sent |
|
|
357
|
+
| market deal: placed (queued, not executed) | `OUTCOME_UNKNOWN` with the receipt, never re-sent |
|
|
358
|
+
| requote, price changed, no quotes, too many requests | retried with a fresh quote, up to `RetryPolicy.attempts` (default 3, 0.5 s apart), then `RETRIES_EXHAUSTED` |
|
|
359
|
+
| timeout, no connection, or `order_send` lost in IPC | `OUTCOME_UNKNOWN`, never retried: check `positions()` first |
|
|
360
|
+
| anything else | the matching `ErrorKind`, returned at once |
|
|
361
|
+
| refused before sending: no quote yet (`NO_PRICE`, e.g. a symbol just added to Market Watch) | waited for and rebuilt up to `RetryPolicy.attempts`, then `NO_PRICE`; nothing was sent |
|
|
362
|
+
| refused before sending: anything else | the matching `ErrorKind`, returned at once; nothing was sent |
|
|
363
|
+
|
|
364
|
+
```python
|
|
365
|
+
from PythonMetaTrader5 import RetryPolicy, connect
|
|
366
|
+
|
|
367
|
+
connect(..., retry=RetryPolicy(attempts=5, delay_seconds=0.2))
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Batch failures
|
|
371
|
+
|
|
372
|
+
`close_positions()` and `cancel_pending_orders()` try every matching item and
|
|
373
|
+
return `Ok(receipts)` only when all succeeded (`Ok([])` when nothing matched).
|
|
374
|
+
Otherwise they return `Err(BatchFailure)`:
|
|
375
|
+
|
|
376
|
+
- `BatchFailure.succeeded`: receipts of the items that worked.
|
|
377
|
+
- `BatchFailure.errors`: one `TradeError` per item that failed, with
|
|
378
|
+
`TradeError.ticket` naming it.
|
|
379
|
+
- When the call fails before trying any item (an invalid `magic` or `comment`,
|
|
380
|
+
or the listing itself fails), `succeeded` is empty and the single error has
|
|
381
|
+
`ticket=None`.
|
|
382
|
+
- A failed item is not necessarily still open: `OUTCOME_UNKNOWN` may have
|
|
383
|
+
closed it and `PARTIAL_FILL` closed part of it. Read `positions()` before
|
|
384
|
+
sending again.
|
|
385
|
+
|
|
386
|
+
```python
|
|
387
|
+
from PythonMetaTrader5 import ErrorKind
|
|
388
|
+
|
|
389
|
+
match trader.close_positions("EURUSD"):
|
|
390
|
+
case Ok(receipts):
|
|
391
|
+
print("closed", len(receipts))
|
|
392
|
+
case Err(failure):
|
|
393
|
+
for error in failure.errors:
|
|
394
|
+
if error.ticket is None:
|
|
395
|
+
print("nothing tried:", error.kind, error.message)
|
|
396
|
+
elif error.kind in (ErrorKind.OUTCOME_UNKNOWN, ErrorKind.PARTIAL_FILL):
|
|
397
|
+
print("check positions():", error.ticket, error.kind)
|
|
398
|
+
else:
|
|
399
|
+
# REJECTED also covers "position already closed" (10036).
|
|
400
|
+
print("not closed by this call:", error.ticket, error.kind, error.message)
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## Logging
|
|
404
|
+
|
|
405
|
+
The library logs through the standard `logging` module under the
|
|
406
|
+
`PythonMetaTrader5` logger and never configures handlers itself
|
|
407
|
+
([ADR 0005](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0005-stdlib-logging.md)). Without any logging
|
|
408
|
+
configuration, warnings and errors still reach stderr. The login and server are
|
|
409
|
+
logged on connect; the account owner's name and balance are not.
|
|
410
|
+
|
|
411
|
+
## Calling MetaTrader5 functions directly
|
|
412
|
+
|
|
413
|
+
As in 2.x, the `MetaTrader5` functions and constants are reachable through this
|
|
414
|
+
package, loaded lazily ([ADR 0010](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0010-keep-the-metatrader5-re-export.md)):
|
|
415
|
+
|
|
416
|
+
```python
|
|
417
|
+
from PythonMetaTrader5 import *
|
|
418
|
+
|
|
419
|
+
info = symbol_info("EURUSD")
|
|
420
|
+
print(ORDER_TYPE_BUY)
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Names defined by this library win over MT5's (our `Tick` over MT5's struct).
|
|
424
|
+
|
|
425
|
+
## Migrating from 2.x
|
|
426
|
+
|
|
427
|
+
`from PythonMetaTrader5 import Broker` keeps working and emits a
|
|
428
|
+
`DeprecationWarning` ([ADR 0006](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0006-legacy-facade.md)). `Broker`
|
|
429
|
+
keeps 2.x behaviour where it was safe: `Close` and `CancelOrders` still act on
|
|
430
|
+
every magic. Several 2.x behaviours were unsafe and changed: endless retries,
|
|
431
|
+
`sl=0` as "no stop", Ctrl+E automation, fixed filling modes and more. Read
|
|
432
|
+
[MIGRATION.md](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/MIGRATION.md) before upgrading a bot that trades real money; it
|
|
433
|
+
also maps the main `Broker` calls to their `Trader` equivalents.
|
|
434
|
+
|
|
435
|
+
## Before trading real money
|
|
436
|
+
|
|
437
|
+
- Run your bot on a **demo** account first. The library's own demo tests need
|
|
438
|
+
a source checkout ([Development](#development)); they are not in the wheel.
|
|
439
|
+
- Give every bot its own `magic` unless it is alone on the account.
|
|
440
|
+
- Treat `OUTCOME_UNKNOWN` as "maybe filled": read `positions()` before sending
|
|
441
|
+
again.
|
|
442
|
+
- Check `Err` on every call. A refused close leaves the position open; an
|
|
443
|
+
`OUTCOME_UNKNOWN` or `PARTIAL_FILL` close may have closed all or part of it,
|
|
444
|
+
so read `positions()` before acting again.
|
|
445
|
+
- Trading carries risk; this software comes without any warranty.
|
|
446
|
+
|
|
447
|
+
## Architecture and decisions (ADR)
|
|
448
|
+
|
|
449
|
+
Clean Architecture with Ports & Adapters: `domain/` holds the trading rules,
|
|
450
|
+
`application/` the use cases (`Trader`), `adapters/` the only code that talks
|
|
451
|
+
to MetaTrader 5. Details: [docs/architecture.md](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/architecture.md).
|
|
452
|
+
|
|
453
|
+
| ADR | Decision |
|
|
454
|
+
|---|---|
|
|
455
|
+
| [0001](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0001-ports-only-at-process-boundaries.md) | Ports only at process boundaries |
|
|
456
|
+
| [0002](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0002-result-at-boundaries.md) | Result values at boundaries, exceptions for programming errors |
|
|
457
|
+
| [0003](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0003-bounded-retries.md) | Bounded retries, never on an unknown outcome |
|
|
458
|
+
| [0004](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0004-no-gui-automation.md) | No GUI automation for Algo Trading |
|
|
459
|
+
| [0005](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0005-stdlib-logging.md) | Standard logging, no NullHandler |
|
|
460
|
+
| [0006](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0006-legacy-facade.md) | Keep a 2.x `Broker` facade |
|
|
461
|
+
| [0007](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0007-filling-and-price-grid.md) | Filling mode from the symbol, prices on the tick grid |
|
|
462
|
+
| [0008](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0008-refuse-values-mt5-misreads.md) | Refuse input that MT5 would quietly read as something else |
|
|
463
|
+
| [0009](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0009-one-lock-per-process.md) | One lock per process around terminal calls |
|
|
464
|
+
| [0010](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0010-keep-the-metatrader5-re-export.md) | Keep re-exporting the MetaTrader5 namespace, lazily |
|
|
465
|
+
| [0011](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0011-close-all-defaults-to-own-magic.md) | Close-all and cancel-all default to the trader's own magic |
|
|
466
|
+
| [0012](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/docs/adr/0012-pin-readme-links-at-publish.md) | Publish from the tag and pin the README's links there |
|
|
467
|
+
|
|
468
|
+
## Development
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
python -m venv .venv && .venv\Scripts\activate
|
|
472
|
+
pip install -e ".[dev]"
|
|
473
|
+
ruff check src tests scripts && ruff format --check src tests scripts
|
|
474
|
+
mypy
|
|
475
|
+
pytest --cov
|
|
476
|
+
python scripts/check_file_length.py
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
`tests/contract` compares the unit-test fakes with the installed `MetaTrader5`
|
|
480
|
+
package and is skipped where it is not installed (anywhere but Windows).
|
|
481
|
+
Integration tests trade on a **demo** account and are skipped unless
|
|
482
|
+
`MT5_LOGIN`, `MT5_PASSWORD` and `MT5_SERVER` are set (they refuse to run on a
|
|
483
|
+
live account): `pytest -m integration`.
|
|
484
|
+
|
|
485
|
+
Releases are published from the tag `v<version>`: the publish workflow runs
|
|
486
|
+
`scripts/pin_readme_links.py`, which points the README's `master` links at that
|
|
487
|
+
tag and refuses to run from any other ref (ADR 0012).
|
|
488
|
+
|
|
489
|
+
## License
|
|
490
|
+
|
|
491
|
+
[CC BY-NC 4.0](https://github.com/Akinzou/MetaTrader5-Python/blob/v3.0.1/LICENSE). Trading carries risk; this software comes without any
|
|
492
|
+
warranty.
|