pmt5api 1.0.0__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.
Files changed (37) hide show
  1. pmt5api-1.0.0/CHANGELOG.md +30 -0
  2. pmt5api-1.0.0/LICENSE.txt +29 -0
  3. pmt5api-1.0.0/PKG-INFO +339 -0
  4. pmt5api-1.0.0/README.md +312 -0
  5. pmt5api-1.0.0/examples/bars.py +20 -0
  6. pmt5api-1.0.0/examples/connect.py +23 -0
  7. pmt5api-1.0.0/examples/pnl.py +23 -0
  8. pmt5api-1.0.0/examples/servers.py +19 -0
  9. pmt5api-1.0.0/examples/sizing.py +38 -0
  10. pmt5api-1.0.0/examples/stream_async.py +20 -0
  11. pmt5api-1.0.0/examples/stream_quotes.py +21 -0
  12. pmt5api-1.0.0/examples/trade_demo.py +29 -0
  13. pmt5api-1.0.0/hatch_build.py +12 -0
  14. pmt5api-1.0.0/pyproject.toml +49 -0
  15. pmt5api-1.0.0/src/pmt5api/__init__.py +34 -0
  16. pmt5api-1.0.0/src/pmt5api/__main__.py +78 -0
  17. pmt5api-1.0.0/src/pmt5api/_account.py +146 -0
  18. pmt5api-1.0.0/src/pmt5api/_base.py +24 -0
  19. pmt5api-1.0.0/src/pmt5api/_coverage.py +216 -0
  20. pmt5api-1.0.0/src/pmt5api/_events.py +147 -0
  21. pmt5api-1.0.0/src/pmt5api/_market.py +296 -0
  22. pmt5api-1.0.0/src/pmt5api/_risk.py +116 -0
  23. pmt5api-1.0.0/src/pmt5api/_runtime.py +154 -0
  24. pmt5api-1.0.0/src/pmt5api/_session.py +175 -0
  25. pmt5api-1.0.0/src/pmt5api/_streams.py +238 -0
  26. pmt5api-1.0.0/src/pmt5api/_trading.py +367 -0
  27. pmt5api-1.0.0/src/pmt5api/_util.py +78 -0
  28. pmt5api-1.0.0/src/pmt5api/bars.py +98 -0
  29. pmt5api-1.0.0/src/pmt5api/client.py +129 -0
  30. pmt5api-1.0.0/src/pmt5api/diagnostics.py +38 -0
  31. pmt5api-1.0.0/src/pmt5api/docs/API.md +717 -0
  32. pmt5api-1.0.0/src/pmt5api/docs/FROM-CSHARP.md +131 -0
  33. pmt5api-1.0.0/src/pmt5api/errors.py +150 -0
  34. pmt5api-1.0.0/src/pmt5api/frames.py +21 -0
  35. pmt5api-1.0.0/src/pmt5api/models.py +522 -0
  36. pmt5api-1.0.0/src/pmt5api/py.typed +0 -0
  37. pmt5api-1.0.0/src/pmt5api/servers.py +73 -0
@@ -0,0 +1,30 @@
1
+ # Changelog — pmt5api
2
+
3
+ ## [1.0.0] — 2026-10-12
4
+
5
+ First release. `pmt5api` exposes every public capability of the wmt5api 3.x C# library to Python.
6
+
7
+ ### Capabilities
8
+ - Connection and session: licensing, failover, `auto_reconnect`, proxies (SOCKS4/SOCKS5/HTTPS), client certificate,
9
+ all timeouts, connect-progress events, server time and account mode.
10
+ - Market data: symbols with trading rules and sessions, commissions, quotes and subscriptions (callbacks, iterators,
11
+ asyncio), bars M1–D1 with tick volume, tick value and contract size.
12
+ - Account: balances and margins (with and without swap/commission), orders, positions, closed orders, history and
13
+ deal history (blocking and event-driven), realized summary, balance/equity curve.
14
+ - Sizing and risk: money-scale calibration, lots-for-risk with evidence, required margin, order profit.
15
+ - Order entry: market, pending (limit, stop, stop-limit), expiration, fill policy, modify, partial close, close-by,
16
+ cancel — synchronous, with request ids and progress events, and awaitable; magic number on orders and deals.
17
+ - Offline helpers and administration: `servers.dat` read and write, demo account request, password change.
18
+ - Diagnostics: the core's log into Python `logging`.
19
+ - Parity manifest `pmt5api.COVERAGE`, checked against the DLL by the test suite; generated API reference and a
20
+ C#-to-Python table.
21
+
22
+ ### Packaging
23
+ - Linux x86-64 wheel carrying the core and the .NET 9 runtime (`manylinux_2_28`); a pure wheel carrying the core;
24
+ an sdist.
25
+
26
+ ### Verified
27
+ Against real demo accounts, from clean containers without .NET (Python 3.9, 3.12, 3.13; glibc 2.28 and 2.41):
28
+ read-only and trading suites, reconnects (drops, a refused window, a silent link), every shipped example, and a
29
+ three-hour unattended run (4 recovered drops, no errors, memory flat after warm-up).
30
+ Not verified: Windows, macOS, arm64, musl; netting accounts; client certificates.
@@ -0,0 +1,29 @@
1
+ wmt5api — Commercial SDK License
2
+ ================================
3
+
4
+ Copyright © Sen Trading (mtapi.one). All rights reserved.
5
+
6
+ This software (the "SDK": wmt5api.dll, its documentation, and the accompanying
7
+ sample) is licensed, not sold. Your use is governed by the commercial license
8
+ agreement under which you obtained your API key.
9
+
10
+ Subject to that agreement and a valid, active license key, you are granted a
11
+ non-exclusive, non-transferable right to integrate and use the SDK in your own
12
+ applications.
13
+
14
+ You may NOT, except as expressly permitted by your agreement:
15
+ • disassemble the SDK, or attempt to discover its source code, internal structure, or algorithms by any means;
16
+ • redistribute, sublicense, rent, or resell the SDK or your API key;
17
+ • remove or alter any proprietary notices.
18
+
19
+ The SDK connects to a hosted licensing service to authorize each connection.
20
+ A license is valid for your subscription period; it stops authorizing new
21
+ connections when the subscription lapses or is cancelled.
22
+
23
+ THE SDK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
24
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25
+ A PARTICULAR PURPOSE. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM,
26
+ DAMAGES, OR OTHER LIABILITY ARISING FROM THE USE OF THE SDK.
27
+
28
+ For license terms, support, or a key: contact your account representative at
29
+ mtapi.one.
pmt5api-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,339 @@
1
+ Metadata-Version: 2.5
2
+ Name: pmt5api
3
+ Version: 1.0.0
4
+ Summary: Complete Python interface to the wmt5api MetaTrader 5 client library (market data, account, sizing, order entry, events).
5
+ Project-URL: Homepage, https://mtapi.one
6
+ Author: Sen Trading
7
+ License-File: LICENSE.txt
8
+ Keywords: market-data,metatrader,mt5,trading,wmt5api
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Intended Audience :: Financial and Insurance Industry
12
+ Classifier: License :: Other/Proprietary License
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.9
20
+ Requires-Dist: pythonnet>=3.0.3
21
+ Provides-Extra: pandas
22
+ Requires-Dist: pandas>=1.5; extra == 'pandas'
23
+ Provides-Extra: test
24
+ Requires-Dist: pandas>=1.5; extra == 'test'
25
+ Requires-Dist: pytest>=8; extra == 'test'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # pmt5api — Python for wmt5api
29
+
30
+ `pmt5api` is the complete Python interface to **wmt5api**, the MetaTrader 5 client library from
31
+ [mtapi.one](https://mtapi.one). The protocol, licensing and trading logic stay in the licensed core
32
+ (`wmt5api.dll`), which this package loads in-process; every public capability of the C# library is available here
33
+ under a Pythonic name, with plain data types, a small error hierarchy, context managers, and sync, async and
34
+ callback styles.
35
+
36
+ No MetaTrader terminal is needed — the core talks to your broker directly.
37
+
38
+ **Parity with the C# library is measured, not claimed:** `pmt5api.COVERAGE` maps every public member of the C#
39
+ `MT5API` class to its Python counterpart (or states why it has none), and the test suite fails if the core gains a
40
+ member that is not accounted for. [`docs/FROM-CSHARP.md`](src/pmt5api/docs/FROM-CSHARP.md) is that table;
41
+ [`docs/API.md`](src/pmt5api/docs/API.md) is the reference.
42
+
43
+ ## Install
44
+
45
+ The **Linux x86-64 wheel** carries everything — the core and a .NET runtime — so there is nothing else to install:
46
+
47
+ ```
48
+ pip install pmt5api-<version>-py3-none-manylinux_2_28_x86_64.whl
49
+ ```
50
+
51
+ On any other platform use the `py3-none-any` wheel, which carries the core and needs a .NET 8 (or later) runtime on
52
+ the machine (`dotnet --list-runtimes` should list `Microsoft.NETCore.App`).
53
+
54
+ You also need your **API key** (`wk_…`, the same key you use with wmt5api):
55
+
56
+ ```
57
+ export WMTAPI_KEY=wk_... # or pass api_key=... to Client(...)
58
+ ```
59
+
60
+ | | |
61
+ |---|---|
62
+ | Python | 3.9, 3.12 and 3.13 verified; 3.9 or newer required |
63
+ | Platform | **Linux x86-64 verified** (glibc 2.28 or newer). Windows and macOS are not verified yet — see *Known limitations* |
64
+ | Optional | `pip install pandas` for `to_frame` |
65
+
66
+ ## Quick start
67
+
68
+ ```python
69
+ from pmt5api import Client
70
+
71
+ with Client(login=12345678, password="investor-password", host="broker-access-host", port=443) as c:
72
+ acct = c.account()
73
+ print(acct.name, acct.balance, acct.currency, "equity", acct.equity)
74
+
75
+ print(c.quote("EURUSD")) # Quote(symbol='EURUSD', bid=..., ask=..., time=...)
76
+
77
+ for p in c.positions():
78
+ print(p.ticket, p.symbol, p.order_type, p.lots, p.profit)
79
+ ```
80
+
81
+ Use an investor (read-only) password for anything that does not trade.
82
+
83
+ ## Market data
84
+
85
+ ```python
86
+ from datetime import datetime
87
+ import pmt5api
88
+
89
+ c.symbols() # dict[str, SymbolInfo]: digits, contract size, min/max/step lots, swaps, ...
90
+ c.symbol("EURUSD") # one symbol; find_symbol("EURUSD") resolves a suffixed name such as EURUSDm
91
+ c.sessions("EURUSD") # when it is quoted and traded, per weekday
92
+ c.is_trade_session("EURUSD") # inside a trade session right now?
93
+
94
+ h1 = c.bars("EURUSD", "H1", count=500) # the latest 500 H1 bars, oldest first
95
+ d1 = c.bars("XAUUSD", "D1", start=datetime(2026, 1, 1)) # everything since a date
96
+ df = pmt5api.to_frame(h1) # pandas DataFrame indexed by time
97
+ ```
98
+
99
+ Timeframes: `M1 M2 M3 M4 M5 M6 M10 M12 M15 M20 M30 H1 H2 H3 H4 H6 H8 H12 D1 W1 MN1`. The server delivers one-minute
100
+ bars; higher timeframes are built from them, aligned to the server's calendar (a day starts at server midnight, an H4
101
+ bar at 00, 04, 08, … server time, a week on Sunday, a month on the 1st), with `tick_volume` summed. Minutes after
102
+ `end` are not used; the last bar may still be forming. `spread` is present on M1 bars only.
103
+
104
+ ### Live quotes
105
+
106
+ ```python
107
+ with Client(...) as c:
108
+ for q in c.quotes("EURUSD", "XAUUSD", timeout=30): # ends after 30 s without a tick
109
+ print(q.symbol, q.bid, q.ask)
110
+ ```
111
+
112
+ ```python
113
+ async with Client(...) as c: # connects without blocking the event loop
114
+ async with c.aquotes("EURUSD") as quotes: # attached on entry, detached on exit
115
+ async for q in quotes:
116
+ print(q)
117
+ ```
118
+
119
+ ```python
120
+ remove = c.on_quote(lambda q: print(q)) # callback style
121
+ c.subscribe("EURUSD")
122
+ ...
123
+ remove()
124
+ ```
125
+
126
+ Callbacks are delivered by **one dispatcher thread per client**, one at a time, in arrival order — you need no locks
127
+ between callbacks, and a slow callback does not stall the connection. If a callback falls more than 20,000 events
128
+ behind, the oldest events are dropped, a warning is logged and `c.events_dropped` counts them. An exception raised
129
+ inside a callback is logged (`logging.getLogger("pmt5api")`) and swallowed. Order updates keep the state they had at
130
+ the moment they arrived (they are snapshots). The iterators attach when you create them, drop the oldest item when the
131
+ consumer falls behind, and detach on `close()` / `async with` / garbage collection.
132
+
133
+ ## Trading
134
+
135
+ ```python
136
+ o = c.buy("EURUSD", 0.10, sl=1.0950, tp=1.1100, magic=424242) # market order at the ask; sell() at the bid
137
+ c.modify(o.ticket, sl=1.0970) # change stops; what you leave out keeps its value
138
+ c.close(o.ticket, 0.05) # close half; close(ticket) closes it all
139
+
140
+ p = c.place_order("EURUSD", "buy_stop_limit", 0.10, 1.1200, stop_limit=1.1190,
141
+ expiration=datetime(2026, 12, 31, 23, 0)) # also buy_limit sell_limit buy_stop sell_stop
142
+ c.modify(p.ticket, expiration="gtc")
143
+ c.cancel(p.ticket)
144
+
145
+ c.close(a.ticket, close_by=b.ticket) # close one position against an opposite one (hedging accounts)
146
+ ```
147
+
148
+ `magic` is your own number for the order: it comes back on `Order.magic` and `Deal.magic` (including the deals
149
+ that close the position), so a bot can find its own orders and history after a restart.
150
+
151
+ **Without waiting** — a request id now, the outcome through events:
152
+
153
+ ```python
154
+ c.on_order_progress(lambda p: print(p.request_id, p.status, p.ticket)) # REQUEST_ACCEPTED, REQUEST_EXECUTED, ...
155
+ rid = c.buy_async("EURUSD", 0.10)
156
+ ```
157
+
158
+ **In asyncio** — the synchronous calls on a worker thread: `await c.abuy(...)`, `asell`, `aplace_order`, `amodify`,
159
+ `aclose`, `acancel`, `aquote`. Cancelling the `await` (a `wait_for` timeout, a cancelled task) does not recall a request
160
+ that is already on its way: the order may still be sent, so check `orders()` afterwards.
161
+
162
+ **Test on a demo account first.** Nothing here is ever retried or resent for you: if the server does not confirm in
163
+ time the call raises `RequestTimeout` and the order **may have been accepted** — look at `orders()` before sending
164
+ again. `NoQuoteError` is different: no price was available, so nothing was sent. `orders()` can lag a moment behind
165
+ a call that has just returned; `close()` and `modify()` read the position's volume and stops from it, so wait for a
166
+ partial close to show up before acting on the same position again. `positions()` lists open positions only (orders of
167
+ type `Buy` or `Sell`); `orders()` also lists pending orders. A market order needs a price, so even the `*_async`
168
+ calls first wait (up to `quote_timeout`) for the symbol's first quote.
169
+
170
+ ## Sizing and risk
171
+
172
+ Do not size a trade from a one-line formula: what a price unit is worth depends on how *your* broker configured
173
+ *your* account (two accounts in the same cent currency can differ by a factor of one hundred). The core measures it
174
+ from your own closed deals and refuses to size without that evidence:
175
+
176
+ ```python
177
+ if c.calibrate_money_scale(datetime(2025, 1, 1)): # read-only; places no order
178
+ r = c.lots_for_risk("EURUSD", entry_price=1.1000, stop_price=1.0950, risk_amount=50.0)
179
+ for w in r.warnings:
180
+ print("warning:", w) # read these: ok can still be True with a caveat
181
+ if r.ok and r.expected_loss_at_stop <= 50.0 * 1.05:
182
+ c.buy("EURUSD", r.lots, sl=1.0950)
183
+ else:
184
+ print("not sizing:", r.reason)
185
+ else:
186
+ print(c.money_scale_evidence)
187
+ ```
188
+
189
+ `ok` is true even when the smallest allowed volume risks more than you asked for (up to `max_ratio`, default 2×), or
190
+ when nothing independent corroborated the price unit — which is why `warnings` and `expected_loss_at_stop` are there
191
+ to be checked. Also: `required_margin(symbol, lots)`, `order_profit(symbol, open, close, lots)`, `tick_value(symbol)`,
192
+ `ledger_value_per_lot(symbol)`, `money_scale_from_deal(deal)`.
193
+
194
+ ## Account, history and curves
195
+
196
+ | Call | Returns |
197
+ |---|---|
198
+ | `account(refresh=True)` | `AccountInfo` — balance, equity, floating P/L (with and without swap/commission), margin, free margin, margin level, leverage, account mode, contact details |
199
+ | `orders()` / `positions()` / `closed_orders()` | `list[Order]` — working / open positions only / what this session saw close |
200
+ | `history(start, end)` / `deal_history(year, month)` | `History` — `.orders` and `.deals` (the ledger, including deposits and withdrawals) |
201
+ | `summary(start=None, end=None)` | `AccountSummary` — realized profit, deposits, withdrawals, from the deal ledger |
202
+ | `equity_history(start, "D1")` | `list[EquityPoint]` — balance (exact) and equity (reconstructed) curve with drawdowns |
203
+ | `request_history(...)`, `request_bars(...)` | without waiting; the data arrives through `on_history` / `on_quote_history` |
204
+
205
+ Times are naive `datetime` values in the **broker's server time**, as the server reports them; an unset time is `None`.
206
+
207
+ `account()` first quotes each open position's symbol once and recalculates profits, so equity, profit and margin
208
+ level are current (a cold read would otherwise show equity equal to balance). Pass `refresh=False` to skip that.
209
+
210
+ ## Events
211
+
212
+ `on_quote`, `on_order_update`, `on_order_progress`, `on_connect_progress`, `on_history`, `on_quote_history`,
213
+ `on_symbols_update`, `on_symbol_update` — each returns a function that removes the callback. `quotes()` and
214
+ `order_updates()` (and the `a…` async versions) turn a stream into an iterator.
215
+
216
+ ## Staying connected
217
+
218
+ Pass `auto_reconnect=True` to let the core re-establish a dropped session (exponential backoff with jitter, failover to
219
+ the broker's cluster members, re-subscription). Watch `on_connect_progress` to tell a permanent failure from a
220
+ transient one, and bound it with `reconnect_max_attempts`. Do not poll `connected` and call `connect()` in a loop —
221
+ brokers rate-limit logins that hammer their access servers. After a reconnect, re-query `account()` and `orders()`;
222
+ subscriptions are restored, cached snapshots are not.
223
+
224
+ ## Configuration
225
+
226
+ | Setting | Argument | Environment variable |
227
+ |---|---|---|
228
+ | API key | `api_key=` | `WMTAPI_KEY` |
229
+ | Licence service URL (leave at its default) | `token_url=` | `WMTAPI_TOKEN_URL` |
230
+ | Core library (only if you do not use a wheel that carries it) | `dll_path=` | `WMTAPI_DLL` |
231
+ | Timeouts (seconds, each > 0) | `connect_timeout` (one attempt on one server), `cluster_member_timeout` (per server while failing over), `token_timeout`, `execution_timeout`, `quote_timeout` / `history_timeout` / `bars_timeout` (the defaults of `quote`, `history`, `bars`), `ping_interval`, `no_message_timeout` | — |
232
+ | Reconnect | `auto_reconnect`, `reconnect_backoff`, `reconnect_max_backoff`, `reconnect_max_attempts` | — |
233
+ | Proxy | `proxy=Proxy("socks5" \| "socks4" \| "https", host, port, user, password)` — `host` must be an IP address | — |
234
+ | Client certificate / common key | `certificate=`, `certificate_password=`, `common_key=` | — |
235
+ | .NET runtime location | — | `DOTNET_ROOT` |
236
+
237
+ A core path you give (argument or `WMTAPI_DLL`) is used exactly as given and is an error if it does not exist. The
238
+ .NET runtime is taken from `DOTNET_ROOT` if you set it, else the runtime bundled in the wheel, else an installed one.
239
+ The bundled runtime runs in invariant-culture mode on machines without ICU.
240
+
241
+ ### Diagnostics
242
+
243
+ ```python
244
+ import logging
245
+ logging.basicConfig(level=logging.DEBUG)
246
+ pmt5api.enable_core_logging() # the core's own messages -> logging.getLogger("pmt5api.core")
247
+ ```
248
+
249
+ ### Offline helpers and account administration
250
+
251
+ `load_servers_dat(path_or_bytes)` / `save_servers_dat(records)` read and write a terminal's `servers.dat`;
252
+ `request_demo_account(DemoAccountRequest(...), host, port)` asks a broker for a demo account (it creates a real
253
+ account at that broker: use it deliberately); `c.change_password(new, investor=False)`.
254
+
255
+ ## Errors
256
+
257
+ Everything the core raises reaches you as one of these, all subclasses of `pmt5api.WmtError`; the original
258
+ exception stays available as `__cause__`.
259
+
260
+ | Exception | Meaning |
261
+ |---|---|
262
+ | `LicenseError` | The licence service refused the key. `.reason` is one of `NoKey`, `Invalid`, `Expired`, `Revoked`, `RateLimited`, `LoginNotAllowed`, `AccountLimitReached`, `Unknown`. |
263
+ | `TokenServiceError` | The licence service could not be reached or answered unusably — retry, don't re-prompt for a key. |
264
+ | `ConnectError` | The broker's access server (or the network, or a proxy) could not be reached. |
265
+ | `ConnectionLostError` | An established connection dropped (the core's `Disconnected`). |
266
+ | `ServerError` / `InvestorModeError` | The broker refused the request (wrong login or password, a rejected order / a read-only login tried to trade). `.code` is the server's return code by name (`REQUOTE`, `NO_MONEY`, `INVALID_PRICE`, …). |
267
+ | `InvalidRequestError` | The request was rejected before it was sent, e.g. an unknown symbol (also a `ValueError`). |
268
+ | `BusyError` | The core allows one request of this kind at a time and one is still running (a history or bars download). |
269
+ | `ProxyError` | The configured proxy failed. |
270
+ | `RequestTimeout` | No answer in time (also a built-in `TimeoutError`). For a trade call the request **was sent**: the order may have been accepted. |
271
+ | `NoQuoteError` | No quote arrived in time to read or to trade against. Nothing was sent. |
272
+ | `NotConnectedError` | The call needs a connection and the client is not connected. |
273
+ | `CoreNotFoundError` / `RuntimeNotFoundError` | `wmt5api.dll` or the .NET runtime was not found; the message says where it looked. |
274
+
275
+ Calls made through `client.raw` bypass this translation and raise the core's own exceptions.
276
+
277
+ ```python
278
+ from pmt5api import Client, LicenseError, TokenServiceError
279
+
280
+ try:
281
+ c = Client(...).connect()
282
+ except LicenseError as e:
283
+ print("licence refused:", e.reason)
284
+ except TokenServiceError as e:
285
+ print("licence service unreachable:", e)
286
+ ```
287
+
288
+ ## What has been verified
289
+
290
+ Each of these was run against real demo accounts (not only unit-tested), from a clean container with no .NET
291
+ installed: connect and licensing (including the production HTTPS licence path); account, symbols and trading rules;
292
+ quotes and streams; bars (H1 identical to the core's own conversion on every period compared); history; the sizing
293
+ chain (calibration, sizing, refusal of a zero-distance stop, cross-check from a closed deal); summary reconciled
294
+ against the deal ledger and an equity curve ending at the account's balance; order entry — market, pending,
295
+ stop-limit with expiration, modify, partial close, close-by, cancel, asynchronous with progress events — with the
296
+ magic number round-tripping through every deal; a rejected order; connecting through SOCKS5 and HTTPS proxies;
297
+ dropping the connection twice mid-stream and recovering with subscriptions restored; a broker that refuses connections
298
+ for a while; a link that goes silent without closing (noticed at `no_message_timeout`, then recovered); and a
299
+ three-hour unattended run on a demo account — five symbols streamed (147 732 ticks), the account polled every minute
300
+ (179 reads), 17 bar and 17 history downloads, 17 far-away pending orders placed and cancelled, the connection cut
301
+ four times (each recovered in 2.0–2.5 s with the subscriptions restored) — which finished with no error. Resident
302
+ memory settled at about 200 MB (it rises to ~190 MB in the first half hour; between the two halves of the run it
303
+ moved by 4 MB), threads and file descriptors stayed flat. Every shipped example was run against a real account too.
304
+
305
+ ## Known limitations
306
+
307
+ - **Linux x86-64 is the verified platform.** Windows, macOS, arm64 and musl-based Linux have not been verified.
308
+ - Netting accounts have not been verified (the available demo accounts are all hedging).
309
+ - Client certificates (`certificate=`), `common_key=` and `request_demo_account` have been unit-tested but not run
310
+ against a broker that requires or serves them.
311
+ - One `Client` is one broker connection. Its methods can be called from several threads, but a trading call is
312
+ neither queued nor serialized for you; two threads acting on the same ticket is your responsibility.
313
+ - The `state` of an order is the core's value and is not a reliable way to tell a position from a pending order (a
314
+ freshly opened position can stay `Started`). Use `positions()`, or the order's type, for that.
315
+ - A folder named `wmt5api` in your working directory is handled automatically (it would otherwise hide the core's
316
+ .NET namespace).
317
+
318
+ ## Licensing and security
319
+
320
+ Your connection is authorised per session by the hosted licence service, using your API key; the core talks to your
321
+ broker directly. What is sent to the licence service and what is not, and the security notes, are in the core's own
322
+ documents, which travel with the wheel: `pmt5api/_native/docs/LICENSING.md`, `SECURITY.md` and `ERRORS.md`
323
+ (and the core's `CHANGELOG.md` next to them). `python -m pmt5api` prints where they are.
324
+
325
+ ## Third-party components
326
+
327
+ The Linux wheel includes the Microsoft .NET runtime (MIT licence; its notices are in `pmt5api/_dotnet/`) and the
328
+ wmt5api core with its own notices (`pmt5api/_native/THIRD-PARTY-NOTICES.txt`).
329
+
330
+ ## Trademarks
331
+
332
+ MetaTrader, MT4 and MT5 are trademarks of MetaQuotes Ltd. wmt4api/wmt5api/pmt5api are independent products of
333
+ Sen Trading ([mtapi.one](https://mtapi.one)); they are not affiliated with, endorsed by or sponsored by MetaQuotes
334
+ Ltd. All other names are the property of their respective owners.
335
+
336
+ ## Licence and support
337
+
338
+ See `LICENSE.txt`. Use of the core library is governed by the wmt5api commercial SDK licence. For keys, support
339
+ and licence terms contact mtapi.one.