PythonMetaTrader5 2.3.2__py3-none-any.whl → 3.0.1__py3-none-any.whl

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.
Files changed (38) hide show
  1. PythonMetaTrader5/__init__.py +73 -535
  2. PythonMetaTrader5/adapters/__init__.py +1 -0
  3. PythonMetaTrader5/adapters/mt5_mapping.py +92 -0
  4. PythonMetaTrader5/adapters/mt5_session.py +48 -0
  5. PythonMetaTrader5/adapters/mt5_terminal.py +224 -0
  6. PythonMetaTrader5/adapters/system_clock.py +10 -0
  7. PythonMetaTrader5/application/__init__.py +1 -0
  8. PythonMetaTrader5/application/batch.py +60 -0
  9. PythonMetaTrader5/application/entries.py +163 -0
  10. PythonMetaTrader5/application/execution.py +108 -0
  11. PythonMetaTrader5/application/magic_scope.py +75 -0
  12. PythonMetaTrader5/application/ports.py +75 -0
  13. PythonMetaTrader5/application/trader.py +275 -0
  14. PythonMetaTrader5/connection.py +52 -0
  15. PythonMetaTrader5/domain/__init__.py +1 -0
  16. PythonMetaTrader5/domain/amounts.py +33 -0
  17. PythonMetaTrader5/domain/errors.py +53 -0
  18. PythonMetaTrader5/domain/inputs.py +106 -0
  19. PythonMetaTrader5/domain/market.py +91 -0
  20. PythonMetaTrader5/domain/orders.py +64 -0
  21. PythonMetaTrader5/domain/requests.py +32 -0
  22. PythonMetaTrader5/domain/retcodes.py +81 -0
  23. PythonMetaTrader5/domain/stops.py +85 -0
  24. PythonMetaTrader5/domain/symbol.py +158 -0
  25. PythonMetaTrader5/legacy/__init__.py +1 -0
  26. PythonMetaTrader5/legacy/arguments.py +52 -0
  27. PythonMetaTrader5/legacy/broker.py +284 -0
  28. PythonMetaTrader5/legacy/mt5_namespace.py +56 -0
  29. PythonMetaTrader5/legacy/views.py +123 -0
  30. PythonMetaTrader5/py.typed +0 -0
  31. PythonMetaTrader5/result.py +41 -0
  32. pythonmetatrader5-3.0.1.dist-info/METADATA +492 -0
  33. pythonmetatrader5-3.0.1.dist-info/RECORD +36 -0
  34. {pythonmetatrader5-2.3.2.dist-info → pythonmetatrader5-3.0.1.dist-info}/WHEEL +1 -1
  35. pythonmetatrader5-3.0.1.dist-info/licenses/LICENSE +16 -0
  36. pythonmetatrader5-2.3.2.dist-info/METADATA +0 -267
  37. pythonmetatrader5-2.3.2.dist-info/RECORD +0 -5
  38. {pythonmetatrader5-2.3.2.dist-info → pythonmetatrader5-3.0.1.dist-info}/top_level.txt +0 -0
@@ -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
+ [![CI](https://github.com/Akinzou/MetaTrader5-Python/actions/workflows/ci.yml/badge.svg)](https://github.com/Akinzou/MetaTrader5-Python/actions/workflows/ci.yml)
39
+ [![PyPI](https://img.shields.io/pypi/v/pythonmetatrader5)](https://pypi.org/project/PythonMetaTrader5/)
40
+ ![Python](https://img.shields.io/badge/python-3.10%2B-blue?logo=python&logoColor=white)
41
+ ![License](https://img.shields.io/badge/license-CC_BY--NC_4.0-lightgrey.svg)
42
+ [![PyPI Downloads](https://static.pepy.tech/badge/pythonmetatrader5)](https://pepy.tech/projects/pythonmetatrader5)
43
+ [![Discord](https://img.shields.io/badge/Discord-join-5865F2?logo=discord&logoColor=white)](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.
@@ -0,0 +1,36 @@
1
+ PythonMetaTrader5/__init__.py,sha256=bALjGPZAZJwaduG6zQ_2ifF1yYSlSUwug6iRujTnw58,2761
2
+ PythonMetaTrader5/connection.py,sha256=xA6VhhqNM-oXGh3ieWS8bCr9yAb4aEVtontgDsHwrQ4,1898
3
+ PythonMetaTrader5/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ PythonMetaTrader5/result.py,sha256=Q3RpcspZgb8nEf8Tnm_W4cx9IldyUDIN_DiCwZg5nEQ,956
5
+ PythonMetaTrader5/adapters/__init__.py,sha256=SfTHYRiDD8Qyi9zAtjh1WQJbT2XKL9C58zDM7P-RU0c,67
6
+ PythonMetaTrader5/adapters/mt5_mapping.py,sha256=YYQO_pFSbavKpeir2zGbZyYmMcp_tqssJa0la_VaB2Y,3089
7
+ PythonMetaTrader5/adapters/mt5_session.py,sha256=hoXlaKMdM1INqOE8Cc4bo3S8vvWNivXq_d1jgA0v9YE,1837
8
+ PythonMetaTrader5/adapters/mt5_terminal.py,sha256=OlpWUIR8wYAb7slKcCulni3JiwsQB8574FcOq6oQPpw,9476
9
+ PythonMetaTrader5/adapters/system_clock.py,sha256=h5dDN-xHNREghu0pCfbO5wM7BvawZQ9YDDAm36KpTkM,188
10
+ PythonMetaTrader5/application/__init__.py,sha256=sAbMrgqvIrwVA1TMdDfkVyh3dO9matXRVTiihb91ns4,64
11
+ PythonMetaTrader5/application/batch.py,sha256=krKHiBXU49aaBGmtC82O8L2oNKjUny5hqdC8YNBPR1o,1940
12
+ PythonMetaTrader5/application/entries.py,sha256=8uL4rI7h4iZjEh4nGVpi5UJQBL2Z7pdpXKN7cNKjWuQ,6025
13
+ PythonMetaTrader5/application/execution.py,sha256=uCJlMnvJKCteAvFw8mLBgndoYU_oooBn91VTpWS2P_Y,4208
14
+ PythonMetaTrader5/application/magic_scope.py,sha256=LwUoY4L4CAUM0mPwAkrD9dVCRoQwB3ByehJNwV6g1rQ,2811
15
+ PythonMetaTrader5/application/ports.py,sha256=yeJrWLbdh1ncdYxn8c9cNGMdwppReTrigCPPc8OIKoA,2642
16
+ PythonMetaTrader5/application/trader.py,sha256=Ahw1WEleyYGs3V0b59pmBLdf878pHiSJ31r7dyN0PFM,10203
17
+ PythonMetaTrader5/domain/__init__.py,sha256=ZZ7iypJWkoG-nK2y_Dj0CG4IRZETi7IGJ90jY06cak8,57
18
+ PythonMetaTrader5/domain/amounts.py,sha256=ab_shtLiqn1I__9R7LtG_Vrz2hjrKwFqUZ1JE5F4tTo,1475
19
+ PythonMetaTrader5/domain/errors.py,sha256=Or7wDSlBO6Yo6miQiQOnXsInUXR67aMRhLhtZXKSVz8,2013
20
+ PythonMetaTrader5/domain/inputs.py,sha256=g_Z3s3e0H-VlKqNvvBTnXdavkVcKXt9kzE-PYryfcB0,4075
21
+ PythonMetaTrader5/domain/market.py,sha256=rBMc17-Uki3boAvPcDZNNPbYRtFxVFWGrGxoPZaBYrE,2051
22
+ PythonMetaTrader5/domain/orders.py,sha256=QA_AlOENgEZSD0ssP0WYJeJRdOC4zZ4z8-xtyRr0RTI,2399
23
+ PythonMetaTrader5/domain/requests.py,sha256=WngSUCpcSlQGBn8a7pMBV-TKVwNtFFqzRQNMuWZ-F9A,857
24
+ PythonMetaTrader5/domain/retcodes.py,sha256=Zstjn2Z-fbl4LZY7EacvSnYnxKJLxj5tzKoNF2YXDTI,3825
25
+ PythonMetaTrader5/domain/stops.py,sha256=G-eKU1z_LiKTugkfuPbMfoyUpCww1NhB7GqrNDKjTKU,3325
26
+ PythonMetaTrader5/domain/symbol.py,sha256=UnNJETCa7JXXqZrEWXVk07AxSBpOPsptV-aKtbPlJH8,7039
27
+ PythonMetaTrader5/legacy/__init__.py,sha256=g-HN0OW4CQ2xmYPdzjsIKoFJlZQquypd_wbairdEWiw,72
28
+ PythonMetaTrader5/legacy/arguments.py,sha256=bkklwl-9e6-JSY3bdPztwaElLXz8ro0qLieVkqozlbI,1802
29
+ PythonMetaTrader5/legacy/broker.py,sha256=qQC9gCsNQqjpH-AvX6PG6uVxU8Sm5bnziyDChoG_1sg,11168
30
+ PythonMetaTrader5/legacy/mt5_namespace.py,sha256=z-Woo4SnWu7ACk4j9zZ62h5K41cwhAae4ZAwtl4PKRQ,2411
31
+ PythonMetaTrader5/legacy/views.py,sha256=O_vDxru28cIn6j0kqNJerDcyVbRY4oLEnTtSAlA1H08,3260
32
+ pythonmetatrader5-3.0.1.dist-info/licenses/LICENSE,sha256=XDOg33_Bini4gyXwSu3XzYs5Gf3Han6tAojB-dZD5DQ,659
33
+ pythonmetatrader5-3.0.1.dist-info/METADATA,sha256=vOJjtEwUkwLMuDgbdJp2Tvp551Y6sVUysgPPhZ3C700,26306
34
+ pythonmetatrader5-3.0.1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
35
+ pythonmetatrader5-3.0.1.dist-info/top_level.txt,sha256=r8xFh3BXAc7RVgR1j6wCYfyk_mm_DaDoGUYFk2IqBLE,18
36
+ pythonmetatrader5-3.0.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (84.2.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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.