PythonMetaTrader5 2.2__py3-none-any.whl → 3.0.0__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 +65 -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.0.dist-info/METADATA +231 -0
  33. pythonmetatrader5-3.0.0.dist-info/RECORD +36 -0
  34. {pythonmetatrader5-2.2.dist-info → pythonmetatrader5-3.0.0.dist-info}/WHEEL +1 -1
  35. pythonmetatrader5-3.0.0.dist-info/licenses/LICENSE +16 -0
  36. pythonmetatrader5-2.2.dist-info/METADATA +0 -29
  37. pythonmetatrader5-2.2.dist-info/RECORD +0 -5
  38. {pythonmetatrader5-2.2.dist-info → pythonmetatrader5-3.0.0.dist-info}/top_level.txt +0 -0
@@ -1,535 +1,73 @@
1
- import time
2
- from MetaTrader5 import *
3
- from loguru import logger
4
- import pyautogui
5
-
6
-
7
- class Broker:
8
- RetCodes = {
9
- 10027: "Enable Algo Trading in MetaTrader5 app",
10
- 10018: "Market closed",
11
- 10016: "Wrong SL",
12
- }
13
-
14
- log = None
15
- password = None
16
- server = None
17
-
18
- def __init__(self, log, password, server):
19
- self.log = log
20
- self.password = str(password)
21
- self.server = str(server)
22
-
23
- initialize()
24
- if login(self.log, self.password, server=self.server):
25
- logger.success("Login succes")
26
- acc_info = account_info()
27
- logger.info(str(acc_info))
28
- else:
29
- logger.error(f"Login failed: {self.log}")
30
-
31
- def login(self, ToEffect=False):
32
- while True:
33
- logger.info(f"Login on: {self.log}")
34
- if login(self.log, self.password, server=self.server):
35
- logger.success("OK")
36
- time.sleep(0.5)
37
- return True
38
- if ToEffect is False:
39
- logger.error("ERROR")
40
- return False
41
-
42
- # -------------------- Symbol meta & normalization --------------------
43
-
44
- def _symbol_meta(self, symbol):
45
- """
46
- Returns (point, digits).
47
- point -> minimal price step (MT5)
48
- digits -> number of decimal places
49
- """
50
- info = symbol_info(symbol)
51
- if info is None:
52
- logger.error(f"{self.log}: symbol_info({symbol}) returned None")
53
- return None, None
54
- point = float(info.point)
55
- digits = int(info.digits)
56
- return point, digits
57
-
58
- def _normalize(self, price, digits):
59
- if price is None:
60
- return None
61
- step = 10 ** (-digits)
62
- return round(float(price) / step) * step
63
-
64
- # -------------------- Low-level senders --------------------
65
-
66
- def _RawOrderClose(self, order_type, symbol, volume, price, comment=None, ticket=None):
67
- order = {
68
- "action": TRADE_ACTION_DEAL,
69
- "symbol": symbol,
70
- "volume": float(volume),
71
- "type": order_type,
72
- "price": float(price),
73
- "deviation": 10,
74
- "type_filling": ORDER_FILLING_IOC,
75
- }
76
- if comment is not None:
77
- order["comment"] = comment
78
- if ticket is not None:
79
- order["position"] = ticket
80
- return order_send(order)
81
-
82
- def _RawOrder(self, order_type, symbol, volume, price, sl=None, tp=None, comment=None, ticket=None):
83
- volume = float(volume)
84
-
85
- # normalize prices to instrument digits before sending
86
- info = symbol_info(symbol)
87
- if info:
88
- digits = int(info.digits)
89
- price = self._normalize(price, digits)
90
- sl = self._normalize(sl, digits) if sl is not None else None
91
- tp = self._normalize(tp, digits) if tp is not None else None
92
-
93
- order = {
94
- "action": TRADE_ACTION_DEAL,
95
- "symbol": symbol,
96
- "volume": volume,
97
- "type": order_type,
98
- "price": price,
99
- "deviation": 10,
100
- "type_filling": ORDER_FILLING_FOK,
101
- }
102
- if comment is not None:
103
- order["comment"] = comment
104
- if ticket is not None:
105
- order["position"] = ticket
106
- if sl is not None:
107
- order["sl"] = float(sl)
108
- if tp is not None:
109
- order["tp"] = float(tp)
110
- return order_send(order)
111
-
112
- def _RawOrderLimit(self, order_type, symbol, volume, price, limit, comment=None, ticket=None):
113
- # MT5 uses 'price' for the pending level; keep signature consistent
114
- info = symbol_info(symbol)
115
- digits = int(info.digits) if info else 5
116
- limit = self._normalize(limit, digits)
117
-
118
- order = {
119
- "action": TRADE_ACTION_PENDING,
120
- "symbol": symbol,
121
- "volume": float(volume),
122
- "type": order_type,
123
- "price": float(limit),
124
- "deviation": 10,
125
- "type_filling": ORDER_FILLING_IOC,
126
- }
127
- if comment is not None:
128
- order["comment"] = comment
129
- if ticket is not None:
130
- order["position"] = ticket
131
- return order_send(order)
132
-
133
- # -------------------- Positions I/O --------------------
134
-
135
- def _pos_to_dict(self, p):
136
- """Serialize MT5 position to a dict (for as_dict=True)."""
137
- if p is None:
138
- return None
139
- return {
140
- "ticket": p.ticket,
141
- "symbol": p.symbol,
142
- "type": p.type, # ORDER_TYPE_BUY / ORDER_TYPE_SELL
143
- "volume": float(p.volume),
144
- "price_open": float(p.price_open),
145
- "sl": float(p.sl) if p.sl else None,
146
- "tp": float(p.tp) if p.tp else None,
147
- "price_current": float(p.price_current),
148
- "profit": float(p.profit),
149
- "time": int(p.time), # epoch
150
- "magic": int(p.magic),
151
- "comment": p.comment,
152
- }
153
-
154
- # -------------------- Positions by symbol --------------------
155
-
156
- def GetPositionsBySymbol(self, symbol: str, as_dict: bool = False):
157
- """
158
- Return all open positions for the given symbol.
159
- If as_dict=True -> list of dictionaries.
160
- """
161
- try:
162
- positions = positions_get(symbol=symbol)
163
- except Exception as e:
164
- logger.error(f"{self.log}: GetPositionsBySymbol exception: {e}")
165
- return []
166
-
167
- positions = list(positions) if positions else []
168
- if as_dict:
169
- return [self._pos_to_dict(p) for p in positions]
170
- return positions
171
-
172
- def GetPositions(self, symbol=None, *, ticket=None, as_dict: bool = False):
173
- """
174
- Return positions with the same filtering semantics as Close():
175
- - if ticket is provided -> only that position
176
- - elif symbol is provided -> all positions for that symbol
177
- - else -> all open positions
178
- """
179
- try:
180
- if ticket is not None:
181
- pos = positions_get(ticket=ticket)
182
- elif symbol is not None:
183
- pos = positions_get(symbol=symbol)
184
- else:
185
- pos = positions_get()
186
- except Exception as e:
187
- logger.error(f"{self.log}: GetPositions exception: {e}")
188
- return []
189
-
190
- pos = list(pos) if pos else []
191
- if as_dict:
192
- return [self._pos_to_dict(p) for p in pos]
193
- return pos
194
-
195
- def GetPosition(self, symbol=None, *, ticket=None, as_dict: bool = False):
196
- """
197
- Return the first matching position or None.
198
- Convenient when you expect at most one.
199
- """
200
- pos_list = self.GetPositions(symbol=symbol, ticket=ticket, as_dict=False)
201
- if not pos_list:
202
- return None if not as_dict else None
203
- return self._pos_to_dict(pos_list[0]) if as_dict else pos_list[0]
204
-
205
- # -------------------- SL/TP computation: absolute or offset IN POINTS --------------------
206
-
207
- def _compute_sl_tp(self, order_type, symbol, sl, tp, sl_type: str, tp_type: str):
208
- """
209
- Return (sl_val, tp_val) as absolute prices.
210
- - 'absolute' -> values are prices (e.g., 1.12345), only normalized
211
- - 'offset' -> values are POINTS away from the current price:
212
- BUY : SL = ask - |sl|*point, TP = ask + |tp|*point
213
- SELL: SL = bid + |sl|*point, TP = bid - |tp|*point
214
- """
215
- sl_val = float(sl) if sl is not None else None
216
- tp_val = float(tp) if tp is not None else None
217
- sl_type = (sl_type or "absolute").lower()
218
- tp_type = (tp_type or "absolute").lower()
219
-
220
- info_tick = symbol_info_tick(symbol)
221
- if info_tick is None:
222
- logger.error(f"{self.log}: symbol_info_tick({symbol}) returned None (SL/TP compute)")
223
- return None, None
224
-
225
- point, digits = self._symbol_meta(symbol)
226
- if point is None:
227
- return None, None
228
-
229
- is_buy = (order_type == ORDER_TYPE_BUY)
230
- ref_price = info_tick.ask if is_buy else info_tick.bid
231
-
232
- if sl_val is not None:
233
- if sl_type == "absolute":
234
- pass
235
- elif sl_type == "offset":
236
- sl_val = ref_price - abs(sl_val) * point if is_buy else ref_price + abs(sl_val) * point
237
- else:
238
- logger.error(f"{self.log}: invalid sl_type '{sl_type}' (use 'absolute'|'offset')")
239
- return None, None
240
-
241
- if tp_val is not None:
242
- if tp_type == "absolute":
243
- pass
244
- elif tp_type == "offset":
245
- tp_val = ref_price + abs(tp_val) * point if is_buy else ref_price - abs(tp_val) * point
246
- else:
247
- logger.error(f"{self.log}: invalid tp_type '{tp_type}' (use 'absolute'|'offset')")
248
- return None, None
249
-
250
- sl_val = self._normalize(sl_val, digits) if sl_val is not None else None
251
- tp_val = self._normalize(tp_val, digits) if tp_val is not None else None
252
- return sl_val, tp_val
253
-
254
- # -------------------- Single market-entry function --------------------
255
-
256
- def OpenPosition(
257
- self,
258
- symbol,
259
- side,
260
- volume,
261
- *,
262
- sl=None,
263
- tp=None,
264
- price=None,
265
- comment=None,
266
- ticket=None,
267
- sl_type: str = "absolute", # 'absolute' (price) or 'offset' (POINTS)
268
- tp_type: str = "absolute", # 'absolute' (price) or 'offset' (POINTS)
269
- ):
270
- """
271
- Open a market position.
272
- side: "buy"/"sell" (also accepts "long"/"short")
273
- SL/TP:
274
- - sl_type/tp_type == 'absolute': pass absolute prices
275
- - sl_type/tp_type == 'offset' : pass POINTS from current price
276
- price: if None -> use current ask/bid and retry on known transient errors
277
- """
278
- side = str(side).lower()
279
- if side not in ("buy", "sell", "long", "short"):
280
- logger.error(f"{self.log}: invalid side '{side}' (use 'buy'/'sell')")
281
- return None
282
-
283
- order_type = ORDER_TYPE_BUY if side in ("buy", "long") else ORDER_TYPE_SELL
284
-
285
- sl_val, tp_val = self._compute_sl_tp(order_type, symbol, sl, tp, sl_type, tp_type)
286
-
287
- # direct-price mode
288
- if price is not None:
289
- return self._RawOrder(order_type, symbol, volume, price, sl_val, tp_val, comment, ticket)
290
-
291
- # current-tick mode with retries
292
- while True:
293
- info = symbol_info_tick(symbol)
294
- if info is None:
295
- logger.error(f"{self.log}: symbol_info_tick({symbol}) returned None")
296
- return None
297
-
298
- curr_price = info.ask if order_type == ORDER_TYPE_BUY else info.bid
299
- r = self._RawOrder(order_type, symbol, volume, curr_price, sl_val, tp_val, comment, ticket)
300
-
301
- if r is None:
302
- logger.error(f"{self.log}: order_send returned None")
303
- return None
304
-
305
- if r.retcode == TRADE_RETCODE_DONE or r.retcode == 10009:
306
- action = "long" if order_type == ORDER_TYPE_BUY else "short"
307
- details = []
308
- if sl_val is not None:
309
- details.append(f"SL {sl_val}")
310
- if tp_val is not None:
311
- details.append(f"TP {tp_val}")
312
- suffix = f" ({', '.join(details)})" if details else ""
313
- logger.success(f"{self.log}: {action} {symbol}{suffix}")
314
- return r
315
-
316
- if r.retcode in self.RetCodes:
317
- action = "long" if order_type == ORDER_TYPE_BUY else "short"
318
- logger.error(f"ERROR CAN NOT OPEN {self.log}: {action} {symbol}")
319
- logger.error(self.RetCodes[r.retcode])
320
- if r.retcode == 10027:
321
- logger.info(f"Enabling algotrading {self.log} {symbol}")
322
- time.sleep(1)
323
- pyautogui.hotkey("ctrl", "e")
324
- if r.retcode == 10016:
325
- return r
326
- time.sleep(0.5)
327
- else:
328
- action = "long" if order_type == ORDER_TYPE_BUY else "short"
329
- logger.error(f"ERROR CAN NOT OPEN TRYING AGAIN {self.log}: {action} {symbol}")
330
- logger.info(f"RetCode: {r.retcode}")
331
- logger.info(f"Comment: {r.comment}")
332
- time.sleep(0.5)
333
-
334
- # -------------------- Backward-compat wrappers --------------------
335
-
336
- def Buy(self, symbol, volume, price=None, *, comment=None, ticket=None):
337
- return self.OpenPosition(symbol, "buy", volume, price=price, comment=comment, ticket=ticket)
338
-
339
- def BuySL(self, symbol, volume, sl, price=None, *, comment=None, ticket=None, sl_type: str = "absolute"):
340
- return self.OpenPosition(
341
- symbol, "buy", volume, sl=sl, price=price, comment=comment, ticket=ticket, sl_type=sl_type
342
- )
343
-
344
- def Sell(self, symbol, volume, price=None, *, comment=None, ticket=None):
345
- return self.OpenPosition(symbol, "sell", volume, price=price, comment=comment, ticket=ticket)
346
-
347
- def SellSL(self, symbol, volume, sl, price=None, *, comment=None, ticket=None, sl_type: str = "absolute"):
348
- return self.OpenPosition(
349
- symbol, "sell", volume, sl=sl, price=price, comment=comment, ticket=ticket, sl_type=sl_type
350
- )
351
-
352
- # -------------------- Closing logic --------------------
353
-
354
- def Close(self, symbol, *, comment=None, ticket=None):
355
- close = False
356
- if ticket is not None:
357
- positions = positions_get(ticket=ticket)
358
- else:
359
- positions = positions_get(symbol=symbol)
360
-
361
- lenpositions = len(positions)
362
- if positions:
363
- while True:
364
- for pos in positions:
365
- if pos.type == ORDER_TYPE_BUY or pos.type == ORDER_TYPE_SELL:
366
- for _ in range(10):
367
- info = symbol_info_tick(symbol)
368
- if info is None:
369
- return None
370
- if pos.type == ORDER_TYPE_BUY:
371
- r = self._RawOrderClose(
372
- ORDER_TYPE_SELL, symbol, pos.volume, info.bid, comment, pos.ticket
373
- )
374
- close = True
375
- else:
376
- r = self._RawOrderClose(
377
- ORDER_TYPE_BUY, symbol, pos.volume, info.ask, comment, pos.ticket
378
- )
379
- close = True
380
- if r is None:
381
- return None
382
- if r.retcode != TRADE_RETCODE_REQUOTE and r.retcode != TRADE_RETCODE_PRICE_OFF:
383
- if r.retcode == TRADE_RETCODE_DONE:
384
- logger.info(f"{self.log}: closing all {symbol}")
385
- lenpositions -= 1
386
- break
387
- if r.retcode in self.RetCodes:
388
- logger.error(f"ERROR CAN NOT CLOSE {self.log} {symbol}")
389
- logger.error(self.RetCodes[r.retcode])
390
- time.sleep(0.5)
391
- if not lenpositions:
392
- break
393
- if r.retcode == 10027:
394
- logger.info(f"Enabling algotrading {self.log} {symbol}")
395
- time.sleep(1)
396
- pyautogui.hotkey("ctrl", "e")
397
-
398
- if not close:
399
- logger.info(f"{self.log}: there is no orders to close")
400
- return False
401
-
402
- # -------------------- Pending orders --------------------
403
-
404
- def SellLimit(self, symbol, volume, stoplimit, price=None, comment=None, ticket=None):
405
- OpenedInstantly = False
406
- if price is not None:
407
- logger.error("Price have to be none")
408
- return
409
-
410
- while True:
411
- info = symbol_info_tick(symbol)
412
- if float(info.bid) >= float(stoplimit):
413
- r = self._RawOrder(ORDER_TYPE_SELL, symbol, float(volume), info.bid, comment=comment, ticket=ticket)
414
- OpenedInstantly = True
415
- else:
416
- r = self._RawOrderLimit(
417
- ORDER_TYPE_SELL_LIMIT, symbol, float(volume), info.bid, float(stoplimit), comment, ticket
418
- )
419
- OpenedInstantly = False
420
-
421
- if r.retcode == 10009:
422
- if OpenedInstantly:
423
- logger.success(f"{self.log}: opened short on: {symbol} beacues of ask is > or = limit price")
424
- else:
425
- logger.success(f"{self.log}: opened short limit on: {symbol} price: {stoplimit}")
426
- break
427
- else:
428
- if r.retcode in self.RetCodes:
429
- logger.error(f"ERROR CAN NOT OPEN {self.log} sell limit on: {symbol} price: {stoplimit}")
430
- logger.error(self.RetCodes[r.retcode])
431
- if r.retcode == 10027:
432
- logger.info(f"Enabling algotrading {self.log} {symbol}")
433
- time.sleep(1)
434
- pyautogui.hotkey("ctrl", "e")
435
- time.sleep(0.5)
436
- else:
437
- logger.error(
438
- f"ERROR CAN NOT OPEN TRYING AGAIN {self.log} sell limit on: {symbol} price: {stoplimit}"
439
- )
440
- logger.info(f"RetCode: {r.retcode}")
441
- logger.info(f"Comment: {r.comment}")
442
- time.sleep(0.5)
443
- return r
444
-
445
- def BuyLimit(self, symbol, volume, stoplimit, price=None, comment=None, ticket=None):
446
- OpenedInstantly = False
447
- if price is not None:
448
- logger.error("Price have to be none")
449
- return
450
-
451
- while True:
452
- info = symbol_info_tick(symbol)
453
- if float(info.ask) <= float(stoplimit):
454
- r = self._RawOrder(ORDER_TYPE_BUY, symbol, float(volume), info.ask, comment=comment, ticket=ticket)
455
- OpenedInstantly = True
456
- else:
457
- r = self._RawOrderLimit(
458
- ORDER_TYPE_BUY_LIMIT, symbol, float(volume), info.ask, float(stoplimit), comment, ticket
459
- )
460
- OpenedInstantly = False
461
-
462
- if r.retcode == 10009:
463
- if OpenedInstantly:
464
- logger.success(f"{self.log}: opened long on: {symbol} beacues of ask is < or = limit price")
465
- else:
466
- logger.success(f"{self.log}: opened long limit on: {symbol} price: {stoplimit}")
467
- break
468
- else:
469
- if r.retcode in self.RetCodes:
470
- logger.error(f"ERROR CAN NOT OPEN {self.log} long limit on: {symbol} price: {stoplimit}")
471
- logger.error(self.RetCodes[r.retcode])
472
- if r.retcode == 10027:
473
- logger.info(f"Enabling algotrading {self.log} {symbol}")
474
- time.sleep(1)
475
- pyautogui.hotkey("ctrl", "e")
476
- time.sleep(0.5)
477
- else:
478
- logger.error(
479
- f"ERROR CAN NOT OPEN TRYING AGAIN {self.log} long limit on: {symbol} price: {stoplimit}"
480
- )
481
- logger.info(f"RetCode: {r.retcode}")
482
- logger.info(f"Comment: {r.comment}")
483
- time.sleep(0.5)
484
- return r
485
-
486
- # -------------------- Cancel pending orders --------------------
487
-
488
- def _RawCancelOrder(self, order_number):
489
- request = {
490
- "action": TRADE_ACTION_REMOVE,
491
- "order": order_number,
492
- "comment": "Order Removed",
493
- }
494
- return order_send(request)
495
-
496
- def CancelOrders(self, symbol):
497
- orders = orders_get()
498
- limit_orders = [
499
- order
500
- for order in orders
501
- if order.type == ORDER_TYPE_BUY_LIMIT or order.type == ORDER_TYPE_SELL_LIMIT
502
- ]
503
- lenpositions = len(limit_orders)
504
-
505
- while True:
506
- r = None
507
- for order in limit_orders:
508
- if order.symbol == symbol:
509
- r = self._RawCancelOrder(order.ticket)
510
-
511
- if r.retcode != TRADE_RETCODE_REQUOTE and r.retcode != TRADE_RETCODE_PRICE_OFF:
512
- if r.retcode == TRADE_RETCODE_DONE:
513
- logger.info(f"{self.log}: order have been just cancelled on: {symbol}")
514
- lenpositions -= 1
515
- break
516
- if r.retcode in self.RetCodes:
517
- logger.error(f"ERROR CAN NOT CLOSE {self.log} {symbol}")
518
- logger.error(self.RetCodes[r.retcode])
519
- time.sleep(0.5)
520
-
521
- if not lenpositions:
522
- break
523
- if r is not None and r.retcode == 10027:
524
- logger.info(f"Enabling algotrading {self.log} {symbol}")
525
- time.sleep(1)
526
- pyautogui.hotkey("ctrl", "e")
527
-
528
- if not len(limit_orders):
529
- logger.info(f"{self.log}: there is no orders to cancel")
530
-
531
- # -------------------- Disconnect --------------------
532
-
533
- def Disconnect(self):
534
- shutdown()
535
- logger.info("Disconnected from MT5")
1
+ """PythonMetaTrader5: a safe, typed trading layer over the MetaTrader5 package.
2
+
3
+ from PythonMetaTrader5 import connect, Stop, Ok, Err
4
+
5
+ match connect(12345678, "password", "MetaQuotes-Demo", magic=1001):
6
+ case Ok(trader):
7
+ trader.open_position("EURUSD", "buy", 0.1, sl=Stop.points(1000))
8
+ case Err(error):
9
+ print(error)
10
+ """
11
+
12
+ from typing import Any
13
+
14
+ # `X as X`: explicit re-exports, because __all__ is computed (see __getattr__).
15
+ from PythonMetaTrader5.application.batch import BatchFailure as BatchFailure
16
+ from PythonMetaTrader5.application.execution import RetryPolicy as RetryPolicy
17
+ from PythonMetaTrader5.application.trader import Trader as Trader
18
+ from PythonMetaTrader5.connection import connect as connect
19
+ from PythonMetaTrader5.domain.errors import ErrorKind as ErrorKind
20
+ from PythonMetaTrader5.domain.errors import TradeError as TradeError
21
+ from PythonMetaTrader5.domain.inputs import ANY_MAGIC as ANY_MAGIC
22
+ from PythonMetaTrader5.domain.inputs import AnyMagic as AnyMagic
23
+ from PythonMetaTrader5.domain.market import PendingKind as PendingKind
24
+ from PythonMetaTrader5.domain.market import PendingOrder as PendingOrder
25
+ from PythonMetaTrader5.domain.market import Position as Position
26
+ from PythonMetaTrader5.domain.market import Tick as Tick
27
+ from PythonMetaTrader5.domain.market import TradeReceipt as TradeReceipt
28
+ from PythonMetaTrader5.domain.orders import Side as Side
29
+ from PythonMetaTrader5.domain.stops import Stop as Stop
30
+ from PythonMetaTrader5.domain.stops import StopKind as StopKind
31
+ from PythonMetaTrader5.legacy import mt5_namespace as _mt5_namespace
32
+ from PythonMetaTrader5.legacy.broker import Broker as Broker
33
+ from PythonMetaTrader5.result import Err as Err
34
+ from PythonMetaTrader5.result import Ok as Ok
35
+ from PythonMetaTrader5.result import Result as Result
36
+
37
+ _OWN_NAMES = (
38
+ "ANY_MAGIC",
39
+ "AnyMagic",
40
+ "BatchFailure",
41
+ "Broker",
42
+ "Err",
43
+ "ErrorKind",
44
+ "Ok",
45
+ "PendingKind",
46
+ "PendingOrder",
47
+ "Position",
48
+ "Result",
49
+ "RetryPolicy",
50
+ "Side",
51
+ "Stop",
52
+ "StopKind",
53
+ "Tick",
54
+ "TradeError",
55
+ "TradeReceipt",
56
+ "Trader",
57
+ "connect",
58
+ )
59
+
60
+
61
+ def __getattr__(name: str) -> Any:
62
+ # 2.x re-exported the whole MetaTrader5 namespace and its README relied on it
63
+ # (`symbol_info("EURUSD")` after a star import), so those names resolve here,
64
+ # lazily (ADR 0010). A star import asks for __all__ through this hook too.
65
+ # Names defined in this module always win, e.g. our Tick over MT5's struct.
66
+ if name == "__all__":
67
+ mt5_names = _mt5_namespace.public_names()
68
+ return [*_OWN_NAMES, *(n for n in mt5_names if n not in globals())]
69
+ return _mt5_namespace.resolve(name)
70
+
71
+
72
+ def __dir__() -> list[str]:
73
+ return sorted({*globals(), *__getattr__("__all__")})
@@ -0,0 +1 @@
1
+ """Implementations of the application ports for the real world."""