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 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 self._is_initialized:
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._ensure_initialized()
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
- if self._is_initialized:
148
- self.logger.info("Shutting down MetaTrader5 connection.")
149
- self.mt5.shutdown()
150
- self._is_initialized = False
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._ensure_initialized()
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._ensure_initialized()
213
+ self._initialize_if_needed()
182
214
  self.logger.info("Retrieving account information.")
183
215
  response = self.mt5.account_info()
184
- self._validate_metatrader5_response(response=response, operation="account_info")
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._ensure_initialized()
228
+ self._initialize_if_needed()
194
229
  self.logger.info("Retrieving terminal information.")
195
230
  response = self.mt5.terminal_info()
196
- self._validate_metatrader5_response(
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._ensure_initialized()
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._ensure_initialized()
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._validate_metatrader5_response(
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._ensure_initialized()
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._ensure_initialized()
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._validate_metatrader5_response(
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 _validate_metatrader5_response(
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 from MetaTrader5 terminal functions.
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
- error_code, error_description = self.last_error()
988
+ last_error_response = self.mt5.last_error()
924
989
  error_message = (
925
- f"{operation} failed: {error_code} - {error_description}"
926
- + (f" (context: {context})" if context else "")
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdmt5
3
- Version: 0.0.7
3
+ Version: 0.0.8
4
4
  Summary: Pandas-based data handler for MetaTrader 5
5
5
  Project-URL: Repository, https://github.com/dceoy/pdmt5.git
6
6
  Author-email: dceoy <dceoy@users.noreply.github.com>
@@ -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,,
@@ -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