pdmt5 0.0.7__py3-none-any.whl → 0.0.8__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.
- pdmt5/__init__.py +2 -1
- pdmt5/mt5.py +133 -78
- {pdmt5-0.0.7.dist-info → pdmt5-0.0.8.dist-info}/METADATA +1 -1
- pdmt5-0.0.8.dist-info/RECORD +9 -0
- pdmt5-0.0.7.dist-info/RECORD +0 -9
- {pdmt5-0.0.7.dist-info → pdmt5-0.0.8.dist-info}/WHEEL +0 -0
- {pdmt5-0.0.7.dist-info → pdmt5-0.0.8.dist-info}/licenses/LICENSE +0 -0
pdmt5/__init__.py
CHANGED
|
@@ -4,7 +4,7 @@ from importlib.metadata import version
|
|
|
4
4
|
|
|
5
5
|
from .dataframe import Mt5Config, Mt5DataClient
|
|
6
6
|
from .mt5 import Mt5Client, Mt5RuntimeError
|
|
7
|
-
from .trading import Mt5TradingClient
|
|
7
|
+
from .trading import Mt5TradingClient, Mt5TradingError
|
|
8
8
|
|
|
9
9
|
__version__ = version(__package__) if __package__ else None
|
|
10
10
|
|
|
@@ -14,4 +14,5 @@ __all__ = [
|
|
|
14
14
|
"Mt5DataClient",
|
|
15
15
|
"Mt5RuntimeError",
|
|
16
16
|
"Mt5TradingClient",
|
|
17
|
+
"Mt5TradingError",
|
|
17
18
|
]
|
pdmt5/mt5.py
CHANGED
|
@@ -6,12 +6,14 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import importlib
|
|
8
8
|
import logging
|
|
9
|
+
from functools import wraps
|
|
9
10
|
from types import ModuleType # noqa: TC003
|
|
10
11
|
from typing import TYPE_CHECKING, Any, Self
|
|
11
12
|
|
|
12
13
|
from pydantic import BaseModel, ConfigDict, Field
|
|
13
14
|
|
|
14
15
|
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Callable
|
|
15
17
|
from datetime import datetime
|
|
16
18
|
from types import TracebackType
|
|
17
19
|
|
|
@@ -43,6 +45,37 @@ class Mt5Client(BaseModel):
|
|
|
43
45
|
)
|
|
44
46
|
_is_initialized: bool = False
|
|
45
47
|
|
|
48
|
+
@staticmethod
|
|
49
|
+
def _log_mt5_last_status_code(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
50
|
+
"""Decorator to log MetaTrader5 last status code after method execution.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
func: The method to decorate.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
The decorated method.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
@wraps(func)
|
|
60
|
+
def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
|
|
61
|
+
try:
|
|
62
|
+
result = func(self, *args, **kwargs)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
error_message = f"Mt5Client operation failed: {func.__name__}"
|
|
65
|
+
self.logger.exception(error_message)
|
|
66
|
+
raise Mt5RuntimeError(error_message) from e
|
|
67
|
+
else:
|
|
68
|
+
return result
|
|
69
|
+
finally:
|
|
70
|
+
last_error_response = self.mt5.last_error()
|
|
71
|
+
message = f"MetaTrader5 last status: {last_error_response}"
|
|
72
|
+
if last_error_response[0] != self.mt5.RES_S_OK:
|
|
73
|
+
self.logger.warning(message)
|
|
74
|
+
else:
|
|
75
|
+
self.logger.info(message)
|
|
76
|
+
|
|
77
|
+
return wrapper
|
|
78
|
+
|
|
46
79
|
def __enter__(self) -> Self:
|
|
47
80
|
"""Context manager entry.
|
|
48
81
|
|
|
@@ -61,6 +94,7 @@ class Mt5Client(BaseModel):
|
|
|
61
94
|
"""Context manager exit."""
|
|
62
95
|
self.shutdown()
|
|
63
96
|
|
|
97
|
+
@_log_mt5_last_status_code
|
|
64
98
|
def initialize(
|
|
65
99
|
self,
|
|
66
100
|
path: str | None = None,
|
|
@@ -83,9 +117,7 @@ class Mt5Client(BaseModel):
|
|
|
83
117
|
Returns:
|
|
84
118
|
True if successful, False otherwise.
|
|
85
119
|
"""
|
|
86
|
-
if
|
|
87
|
-
self.logger.warning("Skipping initialization, already initialized.")
|
|
88
|
-
elif path is not None:
|
|
120
|
+
if path is not None:
|
|
89
121
|
self.logger.info(
|
|
90
122
|
"Initializing MetaTrader5 connection with path: %s",
|
|
91
123
|
path,
|
|
@@ -109,6 +141,7 @@ class Mt5Client(BaseModel):
|
|
|
109
141
|
self._is_initialized = self.mt5.initialize()
|
|
110
142
|
return self._is_initialized
|
|
111
143
|
|
|
144
|
+
@_log_mt5_last_status_code
|
|
112
145
|
def login(
|
|
113
146
|
self,
|
|
114
147
|
login: int,
|
|
@@ -127,7 +160,7 @@ class Mt5Client(BaseModel):
|
|
|
127
160
|
Returns:
|
|
128
161
|
True if successful, False otherwise.
|
|
129
162
|
"""
|
|
130
|
-
self.
|
|
163
|
+
self._initialize_if_needed()
|
|
131
164
|
self.logger.info("Logging in to MetaTrader5 account: %d", login)
|
|
132
165
|
return self.mt5.login(
|
|
133
166
|
login,
|
|
@@ -142,24 +175,22 @@ class Mt5Client(BaseModel):
|
|
|
142
175
|
},
|
|
143
176
|
)
|
|
144
177
|
|
|
178
|
+
@_log_mt5_last_status_code
|
|
145
179
|
def shutdown(self) -> None:
|
|
146
180
|
"""Close the previously established connection to the MetaTrader 5 terminal."""
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
else:
|
|
152
|
-
self.logger.warning(
|
|
153
|
-
"MetaTrader5 connection is not initialized, nothing to shut down"
|
|
154
|
-
)
|
|
181
|
+
self.logger.info("Shutting down MetaTrader5 connection.")
|
|
182
|
+
response = self.mt5.shutdown()
|
|
183
|
+
self._is_initialized = False
|
|
184
|
+
return response
|
|
155
185
|
|
|
186
|
+
@_log_mt5_last_status_code
|
|
156
187
|
def version(self) -> tuple[int, int, str]:
|
|
157
188
|
"""Return the MetaTrader 5 terminal version.
|
|
158
189
|
|
|
159
190
|
Returns:
|
|
160
191
|
Tuple of (terminal_version, build, release_date).
|
|
161
192
|
"""
|
|
162
|
-
self.
|
|
193
|
+
self._initialize_if_needed()
|
|
163
194
|
self.logger.info("Retrieving MetaTrader5 version information.")
|
|
164
195
|
return self.mt5.version()
|
|
165
196
|
|
|
@@ -172,43 +203,49 @@ class Mt5Client(BaseModel):
|
|
|
172
203
|
self.logger.info("Retrieving last MetaTrader5 error")
|
|
173
204
|
return self.mt5.last_error()
|
|
174
205
|
|
|
206
|
+
@_log_mt5_last_status_code
|
|
175
207
|
def account_info(self) -> Any:
|
|
176
208
|
"""Get info on the current trading account.
|
|
177
209
|
|
|
178
210
|
Returns:
|
|
179
211
|
AccountInfo structure or None.
|
|
180
212
|
"""
|
|
181
|
-
self.
|
|
213
|
+
self._initialize_if_needed()
|
|
182
214
|
self.logger.info("Retrieving account information.")
|
|
183
215
|
response = self.mt5.account_info()
|
|
184
|
-
self.
|
|
216
|
+
self._validate_mt5_response_is_not_none(
|
|
217
|
+
response=response, operation="account_info"
|
|
218
|
+
)
|
|
185
219
|
return response
|
|
186
220
|
|
|
221
|
+
@_log_mt5_last_status_code
|
|
187
222
|
def terminal_info(self) -> Any:
|
|
188
223
|
"""Get the connected MetaTrader 5 client terminal status and settings.
|
|
189
224
|
|
|
190
225
|
Returns:
|
|
191
226
|
TerminalInfo structure or None.
|
|
192
227
|
"""
|
|
193
|
-
self.
|
|
228
|
+
self._initialize_if_needed()
|
|
194
229
|
self.logger.info("Retrieving terminal information.")
|
|
195
230
|
response = self.mt5.terminal_info()
|
|
196
|
-
self.
|
|
231
|
+
self._validate_mt5_response_is_not_none(
|
|
197
232
|
response=response,
|
|
198
233
|
operation="terminal_info",
|
|
199
234
|
)
|
|
200
235
|
return response
|
|
201
236
|
|
|
237
|
+
@_log_mt5_last_status_code
|
|
202
238
|
def symbols_total(self) -> int:
|
|
203
239
|
"""Get the number of all financial instruments in the terminal.
|
|
204
240
|
|
|
205
241
|
Returns:
|
|
206
242
|
Total number of symbols.
|
|
207
243
|
"""
|
|
208
|
-
self.
|
|
244
|
+
self._initialize_if_needed()
|
|
209
245
|
self.logger.info("Retrieving total number of symbols.")
|
|
210
246
|
return self.mt5.symbols_total()
|
|
211
247
|
|
|
248
|
+
@_log_mt5_last_status_code
|
|
212
249
|
def symbols_get(self, group: str | None = None) -> tuple[Any, ...]:
|
|
213
250
|
"""Get all financial instruments from the terminal.
|
|
214
251
|
|
|
@@ -218,7 +255,7 @@ class Mt5Client(BaseModel):
|
|
|
218
255
|
Returns:
|
|
219
256
|
Tuple of symbol info structures or None.
|
|
220
257
|
"""
|
|
221
|
-
self.
|
|
258
|
+
self._initialize_if_needed()
|
|
222
259
|
if group is not None:
|
|
223
260
|
self.logger.info("Retrieving symbols for group: %s", group)
|
|
224
261
|
response = self.mt5.symbols_get(group=group)
|
|
@@ -227,13 +264,14 @@ class Mt5Client(BaseModel):
|
|
|
227
264
|
self.logger.info("Retrieving all symbols.")
|
|
228
265
|
response = self.mt5.symbols_get()
|
|
229
266
|
context = None
|
|
230
|
-
self.
|
|
267
|
+
self._validate_mt5_response_is_not_none(
|
|
231
268
|
response=response,
|
|
232
269
|
operation="symbols_get",
|
|
233
270
|
context=context,
|
|
234
271
|
)
|
|
235
272
|
return response
|
|
236
273
|
|
|
274
|
+
@_log_mt5_last_status_code
|
|
237
275
|
def symbol_info(self, symbol: str) -> Any:
|
|
238
276
|
"""Get data on the specified financial instrument.
|
|
239
277
|
|
|
@@ -243,16 +281,17 @@ class Mt5Client(BaseModel):
|
|
|
243
281
|
Returns:
|
|
244
282
|
Symbol info structure or None.
|
|
245
283
|
"""
|
|
246
|
-
self.
|
|
284
|
+
self._initialize_if_needed()
|
|
247
285
|
self.logger.info("Retrieving information for symbol: %s", symbol)
|
|
248
286
|
response = self.mt5.symbol_info(symbol)
|
|
249
|
-
self.
|
|
287
|
+
self._validate_mt5_response_is_not_none(
|
|
250
288
|
response=response,
|
|
251
289
|
operation="symbol_info",
|
|
252
290
|
context=f"symbol={symbol}",
|
|
253
291
|
)
|
|
254
292
|
return response
|
|
255
293
|
|
|
294
|
+
@_log_mt5_last_status_code
|
|
256
295
|
def symbol_info_tick(self, symbol: str) -> Any:
|
|
257
296
|
"""Get the last tick for the specified financial instrument.
|
|
258
297
|
|
|
@@ -262,16 +301,17 @@ class Mt5Client(BaseModel):
|
|
|
262
301
|
Returns:
|
|
263
302
|
Tick info structure or None.
|
|
264
303
|
"""
|
|
265
|
-
self.
|
|
304
|
+
self._initialize_if_needed()
|
|
266
305
|
self.logger.info("Retrieving last tick for symbol: %s", symbol)
|
|
267
306
|
response = self.mt5.symbol_info_tick(symbol)
|
|
268
|
-
self.
|
|
307
|
+
self._validate_mt5_response_is_not_none(
|
|
269
308
|
response=response,
|
|
270
309
|
operation="symbol_info_tick",
|
|
271
310
|
context=f"symbol={symbol}",
|
|
272
311
|
)
|
|
273
312
|
return response
|
|
274
313
|
|
|
314
|
+
@_log_mt5_last_status_code
|
|
275
315
|
def symbol_select(self, symbol: str, enable: bool = True) -> bool:
|
|
276
316
|
"""Select a symbol in the MarketWatch window or remove a symbol from the window.
|
|
277
317
|
|
|
@@ -282,16 +322,17 @@ class Mt5Client(BaseModel):
|
|
|
282
322
|
Returns:
|
|
283
323
|
True if successful, False otherwise.
|
|
284
324
|
"""
|
|
285
|
-
self.
|
|
325
|
+
self._initialize_if_needed()
|
|
286
326
|
self.logger.info("Selecting symbol: %s, enable=%s", symbol, enable)
|
|
287
327
|
response = self.mt5.symbol_select(symbol, enable)
|
|
288
|
-
self.
|
|
328
|
+
self._validate_mt5_response_is_not_none(
|
|
289
329
|
response=response,
|
|
290
330
|
operation="symbol_select",
|
|
291
331
|
context=f"symbol={symbol}, enable={enable}",
|
|
292
332
|
)
|
|
293
333
|
return response
|
|
294
334
|
|
|
335
|
+
@_log_mt5_last_status_code
|
|
295
336
|
def market_book_add(self, symbol: str) -> bool:
|
|
296
337
|
"""Subscribe the terminal to the Market Depth change events for a specified symbol.
|
|
297
338
|
|
|
@@ -301,16 +342,17 @@ class Mt5Client(BaseModel):
|
|
|
301
342
|
Returns:
|
|
302
343
|
True if successful, False otherwise.
|
|
303
344
|
""" # noqa: E501
|
|
304
|
-
self.
|
|
345
|
+
self._initialize_if_needed()
|
|
305
346
|
self.logger.info("Adding market book for symbol: %s", symbol)
|
|
306
347
|
response = self.mt5.market_book_add(symbol)
|
|
307
|
-
self.
|
|
348
|
+
self._validate_mt5_response_is_not_none(
|
|
308
349
|
response=response,
|
|
309
350
|
operation="market_book_add",
|
|
310
351
|
context=f"symbol={symbol}",
|
|
311
352
|
)
|
|
312
353
|
return response
|
|
313
354
|
|
|
355
|
+
@_log_mt5_last_status_code
|
|
314
356
|
def market_book_get(self, symbol: str) -> tuple[Any, ...]:
|
|
315
357
|
"""Return a tuple from BookInfo featuring Market Depth entries for the specified symbol.
|
|
316
358
|
|
|
@@ -320,16 +362,17 @@ class Mt5Client(BaseModel):
|
|
|
320
362
|
Returns:
|
|
321
363
|
Tuple of BookInfo structures or None.
|
|
322
364
|
""" # noqa: E501
|
|
323
|
-
self.
|
|
365
|
+
self._initialize_if_needed()
|
|
324
366
|
self.logger.info("Retrieving market book for symbol: %s", symbol)
|
|
325
367
|
response = self.mt5.market_book_get(symbol)
|
|
326
|
-
self.
|
|
368
|
+
self._validate_mt5_response_is_not_none(
|
|
327
369
|
response=response,
|
|
328
370
|
operation="market_book_get",
|
|
329
371
|
context=f"symbol={symbol}",
|
|
330
372
|
)
|
|
331
373
|
return response
|
|
332
374
|
|
|
375
|
+
@_log_mt5_last_status_code
|
|
333
376
|
def market_book_release(self, symbol: str) -> bool:
|
|
334
377
|
"""Cancels subscription of the terminal to the Market Depth change events for a specified symbol.
|
|
335
378
|
|
|
@@ -339,16 +382,17 @@ class Mt5Client(BaseModel):
|
|
|
339
382
|
Returns:
|
|
340
383
|
True if successful, False otherwise.
|
|
341
384
|
""" # noqa: E501
|
|
342
|
-
self.
|
|
385
|
+
self._initialize_if_needed()
|
|
343
386
|
self.logger.info("Releasing market book for symbol: %s", symbol)
|
|
344
387
|
response = self.mt5.market_book_release(symbol)
|
|
345
|
-
self.
|
|
388
|
+
self._validate_mt5_response_is_not_none(
|
|
346
389
|
response=response,
|
|
347
390
|
operation="market_book_release",
|
|
348
391
|
context=f"symbol={symbol}",
|
|
349
392
|
)
|
|
350
393
|
return response
|
|
351
394
|
|
|
395
|
+
@_log_mt5_last_status_code
|
|
352
396
|
def copy_rates_from(
|
|
353
397
|
self,
|
|
354
398
|
symbol: str,
|
|
@@ -367,7 +411,7 @@ class Mt5Client(BaseModel):
|
|
|
367
411
|
Returns:
|
|
368
412
|
Array of rates or None.
|
|
369
413
|
"""
|
|
370
|
-
self.
|
|
414
|
+
self._initialize_if_needed()
|
|
371
415
|
self.logger.info(
|
|
372
416
|
"Copying rates from symbol: %s, timeframe: %d, date_from: %s, count: %d",
|
|
373
417
|
symbol,
|
|
@@ -376,7 +420,7 @@ class Mt5Client(BaseModel):
|
|
|
376
420
|
count,
|
|
377
421
|
)
|
|
378
422
|
response = self.mt5.copy_rates_from(symbol, timeframe, date_from, count)
|
|
379
|
-
self.
|
|
423
|
+
self._validate_mt5_response_is_not_none(
|
|
380
424
|
response=response,
|
|
381
425
|
operation="copy_rates_from",
|
|
382
426
|
context=(
|
|
@@ -386,6 +430,7 @@ class Mt5Client(BaseModel):
|
|
|
386
430
|
)
|
|
387
431
|
return response
|
|
388
432
|
|
|
433
|
+
@_log_mt5_last_status_code
|
|
389
434
|
def copy_rates_from_pos(
|
|
390
435
|
self,
|
|
391
436
|
symbol: str,
|
|
@@ -404,7 +449,7 @@ class Mt5Client(BaseModel):
|
|
|
404
449
|
Returns:
|
|
405
450
|
Array of rates or None.
|
|
406
451
|
"""
|
|
407
|
-
self.
|
|
452
|
+
self._initialize_if_needed()
|
|
408
453
|
self.logger.info(
|
|
409
454
|
(
|
|
410
455
|
"Copying rates from position:"
|
|
@@ -416,7 +461,7 @@ class Mt5Client(BaseModel):
|
|
|
416
461
|
count,
|
|
417
462
|
)
|
|
418
463
|
response = self.mt5.copy_rates_from_pos(symbol, timeframe, start_pos, count)
|
|
419
|
-
self.
|
|
464
|
+
self._validate_mt5_response_is_not_none(
|
|
420
465
|
response=response,
|
|
421
466
|
operation="copy_rates_from_pos",
|
|
422
467
|
context=(
|
|
@@ -426,6 +471,7 @@ class Mt5Client(BaseModel):
|
|
|
426
471
|
)
|
|
427
472
|
return response
|
|
428
473
|
|
|
474
|
+
@_log_mt5_last_status_code
|
|
429
475
|
def copy_rates_range(
|
|
430
476
|
self,
|
|
431
477
|
symbol: str,
|
|
@@ -444,7 +490,7 @@ class Mt5Client(BaseModel):
|
|
|
444
490
|
Returns:
|
|
445
491
|
Array of rates or None.
|
|
446
492
|
"""
|
|
447
|
-
self.
|
|
493
|
+
self._initialize_if_needed()
|
|
448
494
|
self.logger.info(
|
|
449
495
|
"Copying rates range: symbol=%s, timeframe=%d, date_from=%s, date_to=%s",
|
|
450
496
|
symbol,
|
|
@@ -453,7 +499,7 @@ class Mt5Client(BaseModel):
|
|
|
453
499
|
date_to,
|
|
454
500
|
)
|
|
455
501
|
response = self.mt5.copy_rates_range(symbol, timeframe, date_from, date_to)
|
|
456
|
-
self.
|
|
502
|
+
self._validate_mt5_response_is_not_none(
|
|
457
503
|
response=response,
|
|
458
504
|
operation="copy_rates_range",
|
|
459
505
|
context=(
|
|
@@ -463,6 +509,7 @@ class Mt5Client(BaseModel):
|
|
|
463
509
|
)
|
|
464
510
|
return response
|
|
465
511
|
|
|
512
|
+
@_log_mt5_last_status_code
|
|
466
513
|
def copy_ticks_from(
|
|
467
514
|
self,
|
|
468
515
|
symbol: str,
|
|
@@ -481,7 +528,7 @@ class Mt5Client(BaseModel):
|
|
|
481
528
|
Returns:
|
|
482
529
|
Array of ticks or None.
|
|
483
530
|
"""
|
|
484
|
-
self.
|
|
531
|
+
self._initialize_if_needed()
|
|
485
532
|
self.logger.info(
|
|
486
533
|
"Copying ticks from symbol: %s, date_from: %s, count: %d, flags: %d",
|
|
487
534
|
symbol,
|
|
@@ -490,7 +537,7 @@ class Mt5Client(BaseModel):
|
|
|
490
537
|
flags,
|
|
491
538
|
)
|
|
492
539
|
response = self.mt5.copy_ticks_from(symbol, date_from, count, flags)
|
|
493
|
-
self.
|
|
540
|
+
self._validate_mt5_response_is_not_none(
|
|
494
541
|
response=response,
|
|
495
542
|
operation="copy_ticks_from",
|
|
496
543
|
context=(
|
|
@@ -499,6 +546,7 @@ class Mt5Client(BaseModel):
|
|
|
499
546
|
)
|
|
500
547
|
return response
|
|
501
548
|
|
|
549
|
+
@_log_mt5_last_status_code
|
|
502
550
|
def copy_ticks_range(
|
|
503
551
|
self,
|
|
504
552
|
symbol: str,
|
|
@@ -517,7 +565,7 @@ class Mt5Client(BaseModel):
|
|
|
517
565
|
Returns:
|
|
518
566
|
Array of ticks or None.
|
|
519
567
|
"""
|
|
520
|
-
self.
|
|
568
|
+
self._initialize_if_needed()
|
|
521
569
|
self.logger.info(
|
|
522
570
|
"Copying ticks range: symbol=%s, date_from=%s, date_to=%s, flags=%d",
|
|
523
571
|
symbol,
|
|
@@ -526,7 +574,7 @@ class Mt5Client(BaseModel):
|
|
|
526
574
|
flags,
|
|
527
575
|
)
|
|
528
576
|
response = self.mt5.copy_ticks_range(symbol, date_from, date_to, flags)
|
|
529
|
-
self.
|
|
577
|
+
self._validate_mt5_response_is_not_none(
|
|
530
578
|
response=response,
|
|
531
579
|
operation="copy_ticks_range",
|
|
532
580
|
context=(
|
|
@@ -536,16 +584,18 @@ class Mt5Client(BaseModel):
|
|
|
536
584
|
)
|
|
537
585
|
return response
|
|
538
586
|
|
|
587
|
+
@_log_mt5_last_status_code
|
|
539
588
|
def orders_total(self) -> int:
|
|
540
589
|
"""Get the number of active orders.
|
|
541
590
|
|
|
542
591
|
Returns:
|
|
543
592
|
Number of active orders.
|
|
544
593
|
"""
|
|
545
|
-
self.
|
|
594
|
+
self._initialize_if_needed()
|
|
546
595
|
self.logger.info("Retrieving total number of active orders.")
|
|
547
596
|
return self.mt5.orders_total()
|
|
548
597
|
|
|
598
|
+
@_log_mt5_last_status_code
|
|
549
599
|
def orders_get(
|
|
550
600
|
self,
|
|
551
601
|
symbol: str | None = None,
|
|
@@ -562,7 +612,7 @@ class Mt5Client(BaseModel):
|
|
|
562
612
|
Returns:
|
|
563
613
|
Tuple of order info structures or None.
|
|
564
614
|
"""
|
|
565
|
-
self.
|
|
615
|
+
self._initialize_if_needed()
|
|
566
616
|
if ticket is not None:
|
|
567
617
|
self.logger.info("Retrieving order with ticket: %d", ticket)
|
|
568
618
|
response = self.mt5.orders_get(ticket=ticket)
|
|
@@ -579,13 +629,14 @@ class Mt5Client(BaseModel):
|
|
|
579
629
|
self.logger.info("Retrieving all active orders.")
|
|
580
630
|
response = self.mt5.orders_get()
|
|
581
631
|
context = None
|
|
582
|
-
self.
|
|
632
|
+
self._validate_mt5_response_is_not_none(
|
|
583
633
|
response=response,
|
|
584
634
|
operation="orders_get",
|
|
585
635
|
context=context,
|
|
586
636
|
)
|
|
587
637
|
return response
|
|
588
638
|
|
|
639
|
+
@_log_mt5_last_status_code
|
|
589
640
|
def order_calc_margin(
|
|
590
641
|
self,
|
|
591
642
|
action: int,
|
|
@@ -604,7 +655,7 @@ class Mt5Client(BaseModel):
|
|
|
604
655
|
Returns:
|
|
605
656
|
Required margin amount or None.
|
|
606
657
|
""" # noqa: E501
|
|
607
|
-
self.
|
|
658
|
+
self._initialize_if_needed()
|
|
608
659
|
self.logger.info(
|
|
609
660
|
"Calculating margin: action=%d, symbol=%s, volume=%.2f, price=%.5f",
|
|
610
661
|
action,
|
|
@@ -613,13 +664,14 @@ class Mt5Client(BaseModel):
|
|
|
613
664
|
price,
|
|
614
665
|
)
|
|
615
666
|
response = self.mt5.order_calc_margin(action, symbol, volume, price)
|
|
616
|
-
self.
|
|
667
|
+
self._validate_mt5_response_is_not_none(
|
|
617
668
|
response=response,
|
|
618
669
|
operation="order_calc_margin",
|
|
619
670
|
context=f"action={action}, symbol={symbol}, volume={volume}, price={price}",
|
|
620
671
|
)
|
|
621
672
|
return response
|
|
622
673
|
|
|
674
|
+
@_log_mt5_last_status_code
|
|
623
675
|
def order_calc_profit(
|
|
624
676
|
self,
|
|
625
677
|
action: int,
|
|
@@ -640,7 +692,7 @@ class Mt5Client(BaseModel):
|
|
|
640
692
|
Returns:
|
|
641
693
|
Calculated profit or None.
|
|
642
694
|
"""
|
|
643
|
-
self.
|
|
695
|
+
self._initialize_if_needed()
|
|
644
696
|
self.logger.info(
|
|
645
697
|
(
|
|
646
698
|
"Calculating profit: action=%d, symbol=%s, volume=%.2f,"
|
|
@@ -655,7 +707,7 @@ class Mt5Client(BaseModel):
|
|
|
655
707
|
response = self.mt5.order_calc_profit(
|
|
656
708
|
action, symbol, volume, price_open, price_close
|
|
657
709
|
)
|
|
658
|
-
self.
|
|
710
|
+
self._validate_mt5_response_is_not_none(
|
|
659
711
|
response=response,
|
|
660
712
|
operation="order_calc_profit",
|
|
661
713
|
context=(
|
|
@@ -665,6 +717,7 @@ class Mt5Client(BaseModel):
|
|
|
665
717
|
)
|
|
666
718
|
return response
|
|
667
719
|
|
|
720
|
+
@_log_mt5_last_status_code
|
|
668
721
|
def order_check(self, request: dict[str, Any]) -> Any:
|
|
669
722
|
"""Check funds sufficiency for performing a required trading operation.
|
|
670
723
|
|
|
@@ -674,16 +727,17 @@ class Mt5Client(BaseModel):
|
|
|
674
727
|
Returns:
|
|
675
728
|
OrderCheckResult structure or None.
|
|
676
729
|
"""
|
|
677
|
-
self.
|
|
730
|
+
self._initialize_if_needed()
|
|
678
731
|
self.logger.info("Checking order with request: %s", request)
|
|
679
732
|
response = self.mt5.order_check(request)
|
|
680
|
-
self.
|
|
733
|
+
self._validate_mt5_response_is_not_none(
|
|
681
734
|
response=response,
|
|
682
735
|
operation="order_check",
|
|
683
736
|
context=f"request={request}",
|
|
684
737
|
)
|
|
685
738
|
return response
|
|
686
739
|
|
|
740
|
+
@_log_mt5_last_status_code
|
|
687
741
|
def order_send(self, request: dict[str, Any]) -> Any:
|
|
688
742
|
"""Send a request to perform a trading operation from the terminal to the trade server.
|
|
689
743
|
|
|
@@ -693,26 +747,28 @@ class Mt5Client(BaseModel):
|
|
|
693
747
|
Returns:
|
|
694
748
|
OrderSendResult structure or None.
|
|
695
749
|
""" # noqa: E501
|
|
696
|
-
self.
|
|
750
|
+
self._initialize_if_needed()
|
|
697
751
|
self.logger.info("Sending order with request: %s", request)
|
|
698
752
|
response = self.mt5.order_send(request)
|
|
699
|
-
self.
|
|
753
|
+
self._validate_mt5_response_is_not_none(
|
|
700
754
|
response=response,
|
|
701
755
|
operation="order_send",
|
|
702
756
|
context=f"request={request}",
|
|
703
757
|
)
|
|
704
758
|
return response
|
|
705
759
|
|
|
760
|
+
@_log_mt5_last_status_code
|
|
706
761
|
def positions_total(self) -> int:
|
|
707
762
|
"""Get the number of open positions.
|
|
708
763
|
|
|
709
764
|
Returns:
|
|
710
765
|
Number of open positions.
|
|
711
766
|
"""
|
|
712
|
-
self.
|
|
767
|
+
self._initialize_if_needed()
|
|
713
768
|
self.logger.info("Retrieving total number of open positions.")
|
|
714
769
|
return self.mt5.positions_total()
|
|
715
770
|
|
|
771
|
+
@_log_mt5_last_status_code
|
|
716
772
|
def positions_get(
|
|
717
773
|
self,
|
|
718
774
|
symbol: str | None = None,
|
|
@@ -729,7 +785,7 @@ class Mt5Client(BaseModel):
|
|
|
729
785
|
Returns:
|
|
730
786
|
Tuple of position info structures or None.
|
|
731
787
|
"""
|
|
732
|
-
self.
|
|
788
|
+
self._initialize_if_needed()
|
|
733
789
|
if ticket is not None:
|
|
734
790
|
self.logger.info("Retrieving position with ticket: %d", ticket)
|
|
735
791
|
response = self.mt5.positions_get(ticket=ticket)
|
|
@@ -746,13 +802,14 @@ class Mt5Client(BaseModel):
|
|
|
746
802
|
self.logger.info("Retrieving all open positions.")
|
|
747
803
|
response = self.mt5.positions_get()
|
|
748
804
|
context = None
|
|
749
|
-
self.
|
|
805
|
+
self._validate_mt5_response_is_not_none(
|
|
750
806
|
response=response,
|
|
751
807
|
operation="positions_get",
|
|
752
808
|
context=context,
|
|
753
809
|
)
|
|
754
810
|
return response
|
|
755
811
|
|
|
812
|
+
@_log_mt5_last_status_code
|
|
756
813
|
def history_orders_total(
|
|
757
814
|
self,
|
|
758
815
|
date_from: datetime | int,
|
|
@@ -767,7 +824,7 @@ class Mt5Client(BaseModel):
|
|
|
767
824
|
Returns:
|
|
768
825
|
Number of historical orders.
|
|
769
826
|
"""
|
|
770
|
-
self.
|
|
827
|
+
self._initialize_if_needed()
|
|
771
828
|
self.logger.info(
|
|
772
829
|
"Retrieving total number of historical orders from %s to %s",
|
|
773
830
|
date_from,
|
|
@@ -775,6 +832,7 @@ class Mt5Client(BaseModel):
|
|
|
775
832
|
)
|
|
776
833
|
return self.mt5.history_orders_total(date_from, date_to)
|
|
777
834
|
|
|
835
|
+
@_log_mt5_last_status_code
|
|
778
836
|
def history_orders_get(
|
|
779
837
|
self,
|
|
780
838
|
date_from: datetime | int | None = None,
|
|
@@ -795,7 +853,7 @@ class Mt5Client(BaseModel):
|
|
|
795
853
|
Returns:
|
|
796
854
|
Tuple of historical order info structures or None.
|
|
797
855
|
""" # noqa: E501
|
|
798
|
-
self.
|
|
856
|
+
self._initialize_if_needed()
|
|
799
857
|
if ticket is not None:
|
|
800
858
|
self.logger.info("Retrieving order with ticket: %d", ticket)
|
|
801
859
|
response = self.mt5.history_orders_get(ticket=ticket)
|
|
@@ -821,13 +879,14 @@ class Mt5Client(BaseModel):
|
|
|
821
879
|
)
|
|
822
880
|
response = self.mt5.history_orders_get(date_from, date_to)
|
|
823
881
|
context = f"date_from={date_from}, date_to={date_to}"
|
|
824
|
-
self.
|
|
882
|
+
self._validate_mt5_response_is_not_none(
|
|
825
883
|
response=response,
|
|
826
884
|
operation="history_orders_get",
|
|
827
885
|
context=context,
|
|
828
886
|
)
|
|
829
887
|
return response
|
|
830
888
|
|
|
889
|
+
@_log_mt5_last_status_code
|
|
831
890
|
def history_deals_total(
|
|
832
891
|
self,
|
|
833
892
|
date_from: datetime | int,
|
|
@@ -842,7 +901,7 @@ class Mt5Client(BaseModel):
|
|
|
842
901
|
Returns:
|
|
843
902
|
Number of historical deals.
|
|
844
903
|
"""
|
|
845
|
-
self.
|
|
904
|
+
self._initialize_if_needed()
|
|
846
905
|
self.logger.info(
|
|
847
906
|
"Retrieving total number of historical deals from %s to %s",
|
|
848
907
|
date_from,
|
|
@@ -850,6 +909,7 @@ class Mt5Client(BaseModel):
|
|
|
850
909
|
)
|
|
851
910
|
return self.mt5.history_deals_total(date_from, date_to)
|
|
852
911
|
|
|
912
|
+
@_log_mt5_last_status_code
|
|
853
913
|
def history_deals_get(
|
|
854
914
|
self,
|
|
855
915
|
date_from: datetime | int | None = None,
|
|
@@ -870,7 +930,7 @@ class Mt5Client(BaseModel):
|
|
|
870
930
|
Returns:
|
|
871
931
|
Tuple of historical deal info structures or None.
|
|
872
932
|
""" # noqa: E501
|
|
873
|
-
self.
|
|
933
|
+
self._initialize_if_needed()
|
|
874
934
|
if ticket is not None:
|
|
875
935
|
self.logger.info("Retrieving deal with ticket: %d", ticket)
|
|
876
936
|
response = self.mt5.history_deals_get(ticket=ticket)
|
|
@@ -896,20 +956,25 @@ class Mt5Client(BaseModel):
|
|
|
896
956
|
)
|
|
897
957
|
response = self.mt5.history_deals_get(date_from, date_to)
|
|
898
958
|
context = f"date_from={date_from}, date_to={date_to}"
|
|
899
|
-
self.
|
|
959
|
+
self._validate_mt5_response_is_not_none(
|
|
900
960
|
response=response,
|
|
901
961
|
operation="history_deals_get",
|
|
902
962
|
context=context,
|
|
903
963
|
)
|
|
904
964
|
return response
|
|
905
965
|
|
|
906
|
-
def
|
|
966
|
+
def _initialize_if_needed(self) -> None:
|
|
967
|
+
"""Ensure the MetaTrader5 client is initialized before performing operations."""
|
|
968
|
+
if not self._is_initialized:
|
|
969
|
+
self.initialize()
|
|
970
|
+
|
|
971
|
+
def _validate_mt5_response_is_not_none(
|
|
907
972
|
self,
|
|
908
973
|
response: Any,
|
|
909
974
|
operation: str,
|
|
910
975
|
context: str | None = None,
|
|
911
976
|
) -> None:
|
|
912
|
-
"""Validate the response
|
|
977
|
+
"""Validate that the MetaTrader5 response is not None.
|
|
913
978
|
|
|
914
979
|
Args:
|
|
915
980
|
response: The response object to validate.
|
|
@@ -920,20 +985,10 @@ class Mt5Client(BaseModel):
|
|
|
920
985
|
Mt5RuntimeError: With error details from MetaTrader5.
|
|
921
986
|
"""
|
|
922
987
|
if response is None:
|
|
923
|
-
|
|
988
|
+
last_error_response = self.mt5.last_error()
|
|
924
989
|
error_message = (
|
|
925
|
-
f"{operation}
|
|
926
|
-
|
|
927
|
-
)
|
|
990
|
+
f"MetaTrader5 {operation} returned {response}:"
|
|
991
|
+
f" last_error={last_error_response}"
|
|
992
|
+
) + (f" context={context}" if context else "")
|
|
928
993
|
self.logger.error(error_message)
|
|
929
994
|
raise Mt5RuntimeError(error_message)
|
|
930
|
-
|
|
931
|
-
def _ensure_initialized(self) -> None:
|
|
932
|
-
"""Ensure MetaTrader5 is initialized.
|
|
933
|
-
|
|
934
|
-
Raises:
|
|
935
|
-
Mt5RuntimeError: If MetaTrader5 is not initialized.
|
|
936
|
-
"""
|
|
937
|
-
if not self._is_initialized:
|
|
938
|
-
error_message = "MetaTrader5 not initialized. Call initialize() first."
|
|
939
|
-
raise Mt5RuntimeError(error_message)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
pdmt5/__init__.py,sha256=QbSFrsi7_bgFzb-ma4DmmUjR90UvrqKMnRZq1wPRmoI,446
|
|
2
|
+
pdmt5/dataframe.py,sha256=808kwi_eUlpXvgvJzXxwDTvxp80z-1zh605ONyOIoU4,38369
|
|
3
|
+
pdmt5/mt5.py,sha256=NOcG4NrsgH7Jbs5keXdQgri2UNt9JKrxcf-FBxchEbE,32390
|
|
4
|
+
pdmt5/trading.py,sha256=8JagrLc9cjr9TUdkoRmyAmVUOV2SQElqsrtcwg5FqBs,5284
|
|
5
|
+
pdmt5/utils.py,sha256=Ll5Q3OE5h1A_sZ_qVEnOPGniFlT6_MmHfuu0zqeLdeU,3913
|
|
6
|
+
pdmt5-0.0.8.dist-info/METADATA,sha256=PbbFgHdYLTwcDnm3Uf8sswIUnShTl5xbw9E20bBqrrI,9029
|
|
7
|
+
pdmt5-0.0.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
8
|
+
pdmt5-0.0.8.dist-info/licenses/LICENSE,sha256=iABrdaUGOBWLYotFupB_PGe8arV5o7rVhn-_vK6P704,1073
|
|
9
|
+
pdmt5-0.0.8.dist-info/RECORD,,
|
pdmt5-0.0.7.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
pdmt5/__init__.py,sha256=mJ1gMqZ_MKYZ-53N41MvkwNdPhfcpq15NczCz74fD7w,406
|
|
2
|
-
pdmt5/dataframe.py,sha256=808kwi_eUlpXvgvJzXxwDTvxp80z-1zh605ONyOIoU4,38369
|
|
3
|
-
pdmt5/mt5.py,sha256=vZMIz7h7KWDegX23KEIryby-WC5hJ4wN7pp-FMTdeZ8,30498
|
|
4
|
-
pdmt5/trading.py,sha256=8JagrLc9cjr9TUdkoRmyAmVUOV2SQElqsrtcwg5FqBs,5284
|
|
5
|
-
pdmt5/utils.py,sha256=Ll5Q3OE5h1A_sZ_qVEnOPGniFlT6_MmHfuu0zqeLdeU,3913
|
|
6
|
-
pdmt5-0.0.7.dist-info/METADATA,sha256=w0kr0tob2G_h-u9DuVifML9Q5BIyVY5tHUD1YKyyLjk,9029
|
|
7
|
-
pdmt5-0.0.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
8
|
-
pdmt5-0.0.7.dist-info/licenses/LICENSE,sha256=iABrdaUGOBWLYotFupB_PGe8arV5o7rVhn-_vK6P704,1073
|
|
9
|
-
pdmt5-0.0.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|