pdmt5 0.0.8__py3-none-any.whl → 0.1.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.
pdmt5/dataframe.py CHANGED
@@ -86,16 +86,16 @@ class Mt5DataClient(Mt5Client):
86
86
  for i in range(1 + max(0, self.retry_count)):
87
87
  if i:
88
88
  self.logger.warning(
89
- "Retrying MetaTrader5 initialization (%d/%d)...",
89
+ "Retrying MT5 initialization (%d/%d)...",
90
90
  i,
91
91
  self.retry_count,
92
92
  )
93
93
  time.sleep(i)
94
94
  if self.initialize(**initialize_kwargs): # type: ignore[reportArgumentType]
95
- self.logger.info("MetaTrader5 initialization successful.")
95
+ self.logger.info("MT5 initialization successful.")
96
96
  return
97
97
  error_message = (
98
- f"MetaTrader5 initialization failed after {self.retry_count} retries:"
98
+ f"MT5 initialization failed after {self.retry_count} retries:"
99
99
  f" {self.last_error()}"
100
100
  )
101
101
  raise Mt5RuntimeError(error_message)
pdmt5/mt5.py CHANGED
@@ -59,16 +59,20 @@ class Mt5Client(BaseModel):
59
59
  @wraps(func)
60
60
  def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
61
61
  try:
62
- result = func(self, *args, **kwargs)
62
+ response = func(self, *args, **kwargs)
63
63
  except Exception as e:
64
- error_message = f"Mt5Client operation failed: {func.__name__}"
65
- self.logger.exception(error_message)
64
+ error_message = f"MT5 {func.__name__} failed with error: {e}"
66
65
  raise Mt5RuntimeError(error_message) from e
67
66
  else:
68
- return result
67
+ self.logger.info(
68
+ "MT5 %s returned a response: %s",
69
+ func.__name__,
70
+ response,
71
+ )
72
+ return response
69
73
  finally:
70
74
  last_error_response = self.mt5.last_error()
71
- message = f"MetaTrader5 last status: {last_error_response}"
75
+ message = f"MT5 last status: {last_error_response}"
72
76
  if last_error_response[0] != self.mt5.RES_S_OK:
73
77
  self.logger.warning(message)
74
78
  else:
@@ -119,7 +123,7 @@ class Mt5Client(BaseModel):
119
123
  """
120
124
  if path is not None:
121
125
  self.logger.info(
122
- "Initializing MetaTrader5 connection with path: %s",
126
+ "Initializing MT5 connection with path: %s",
123
127
  path,
124
128
  )
125
129
  self._is_initialized = self.mt5.initialize(
@@ -137,7 +141,7 @@ class Mt5Client(BaseModel):
137
141
  },
138
142
  )
139
143
  else:
140
- self.logger.info("Initializing MetaTrader5 connection.")
144
+ self.logger.info("Initializing MT5 connection.")
141
145
  self._is_initialized = self.mt5.initialize()
142
146
  return self._is_initialized
143
147
 
@@ -161,7 +165,7 @@ class Mt5Client(BaseModel):
161
165
  True if successful, False otherwise.
162
166
  """
163
167
  self._initialize_if_needed()
164
- self.logger.info("Logging in to MetaTrader5 account: %d", login)
168
+ self.logger.info("Logging in to MT5 account: %d", login)
165
169
  return self.mt5.login(
166
170
  login,
167
171
  **{
@@ -178,7 +182,7 @@ class Mt5Client(BaseModel):
178
182
  @_log_mt5_last_status_code
179
183
  def shutdown(self) -> None:
180
184
  """Close the previously established connection to the MetaTrader 5 terminal."""
181
- self.logger.info("Shutting down MetaTrader5 connection.")
185
+ self.logger.info("Shutting down MT5 connection.")
182
186
  response = self.mt5.shutdown()
183
187
  self._is_initialized = False
184
188
  return response
@@ -191,16 +195,17 @@ class Mt5Client(BaseModel):
191
195
  Tuple of (terminal_version, build, release_date).
192
196
  """
193
197
  self._initialize_if_needed()
194
- self.logger.info("Retrieving MetaTrader5 version information.")
198
+ self.logger.info("Retrieving MT5 version information.")
195
199
  return self.mt5.version()
196
200
 
201
+ @_log_mt5_last_status_code
197
202
  def last_error(self) -> tuple[int, str]:
198
203
  """Return data on the last error.
199
204
 
200
205
  Returns:
201
206
  Tuple of (error_code, error_description).
202
207
  """
203
- self.logger.info("Retrieving last MetaTrader5 error")
208
+ self.logger.info("Retrieving last MT5 error")
204
209
  return self.mt5.last_error()
205
210
 
206
211
  @_log_mt5_last_status_code
@@ -985,10 +990,8 @@ class Mt5Client(BaseModel):
985
990
  Mt5RuntimeError: With error details from MetaTrader5.
986
991
  """
987
992
  if response is None:
988
- last_error_response = self.mt5.last_error()
989
993
  error_message = (
990
- f"MetaTrader5 {operation} returned {response}:"
991
- f" last_error={last_error_response}"
994
+ f"MT5 {operation} returned {response}:"
995
+ f" last_error={self.mt5.last_error()}"
992
996
  ) + (f" context={context}" if context else "")
993
- self.logger.error(error_message)
994
997
  raise Mt5RuntimeError(error_message)
pdmt5/trading.py CHANGED
@@ -31,7 +31,7 @@ class Mt5TradingClient(Mt5DataClient):
31
31
 
32
32
  def close_open_positions(
33
33
  self,
34
- symbols: str | list[str] | tuple[str] | None = None,
34
+ symbols: str | list[str] | tuple[str, ...] | None = None,
35
35
  **kwargs: Any, # noqa: ANN401
36
36
  ) -> dict[str, list[dict[str, Any]]]:
37
37
  """Close all open positions for specified symbols.
@@ -51,6 +51,7 @@ class Mt5TradingClient(Mt5DataClient):
51
51
  symbol_list = symbols
52
52
  else:
53
53
  symbol_list = self.symbols_get()
54
+ self.logger.info("Fetching and closing positions for symbols: %s", symbol_list)
54
55
  return {
55
56
  s: self._fetch_and_close_position(symbol=s, **kwargs) for s in symbol_list
56
57
  }
@@ -99,11 +100,17 @@ class Mt5TradingClient(Mt5DataClient):
99
100
  for p in positions_dict
100
101
  ]
101
102
 
102
- def send_or_check_order(self, request: dict[str, Any]) -> dict[str, Any]:
103
+ def send_or_check_order(
104
+ self,
105
+ request: dict[str, Any],
106
+ dry_run: bool | None = None,
107
+ ) -> dict[str, Any]:
103
108
  """Send or check an order request.
104
109
 
105
110
  Args:
106
111
  request: Order request dictionary.
112
+ dry_run: Optional flag to enable dry run mode. If None, uses the instance's
113
+ `dry_run` attribute.
107
114
 
108
115
  Returns:
109
116
  Dictionary with operation result.
@@ -112,29 +119,66 @@ class Mt5TradingClient(Mt5DataClient):
112
119
  Mt5TradingError: If the order operation fails.
113
120
  """
114
121
  self.logger.debug("request: %s", request)
115
- if self.dry_run:
122
+ is_dry_run = dry_run if dry_run is not None else self.dry_run
123
+ self.logger.debug("is_dry_run: %s", is_dry_run)
124
+ if is_dry_run:
116
125
  response = self.order_check_as_dict(request=request)
117
126
  order_func = "order_check"
118
127
  else:
119
128
  response = self.order_send_as_dict(request=request)
120
129
  order_func = "order_send"
121
130
  retcode = response.get("retcode")
122
- if ((not self.dry_run) and retcode == self.mt5.TRADE_RETCODE_DONE) or (
123
- self.dry_run and retcode == 0
131
+ if ((not is_dry_run) and retcode == self.mt5.TRADE_RETCODE_DONE) or (
132
+ is_dry_run and retcode == 0
124
133
  ):
125
- self.logger.info("retcode: %s, response: %s", retcode, response)
134
+ self.logger.info("response: %s", response)
126
135
  return response
127
136
  elif retcode in {
128
137
  self.mt5.TRADE_RETCODE_TRADE_DISABLED,
129
138
  self.mt5.TRADE_RETCODE_MARKET_CLOSED,
130
139
  }:
131
- self.logger.info("retcode: %s, response: %s", retcode, response)
140
+ self.logger.info("response: %s", response)
132
141
  comment = response.get("comment", "Unknown error")
133
142
  self.logger.warning("%s() failed and skipped. <= `%s`", order_func, comment)
134
143
  return response
135
144
  else:
136
- self.logger.error("retcode: %s, response: %s", retcode, response)
145
+ self.logger.error("response: %s", response)
137
146
  comment = response.get("comment", "Unknown error")
138
147
  error_message = f"{order_func}() failed and aborted. <= `{comment}`"
139
- self.logger.error(error_message)
148
+ raise Mt5TradingError(error_message)
149
+
150
+ def calculate_minimum_order_margins(self, symbol: str) -> dict[str, float]:
151
+ """Calculate minimum order margins for a given symbol.
152
+
153
+ Args:
154
+ symbol: Symbol for which to calculate minimum order margins.
155
+
156
+ Returns:
157
+ Dictionary with margin information.
158
+
159
+ Raises:
160
+ Mt5TradingError: If margin calculation fails.
161
+ """
162
+ symbol_info = self.symbol_info_as_dict(symbol=symbol)
163
+ symbol_info_tick = self.symbol_info_tick_as_dict(symbol=symbol)
164
+ min_ask_order_margin = self.order_calc_margin(
165
+ action=self.mt5.ORDER_TYPE_BUY,
166
+ symbol=symbol,
167
+ volume=symbol_info["volume_min"],
168
+ price=symbol_info_tick["ask"],
169
+ )
170
+ min_bid_order_margin = self.order_calc_margin(
171
+ action=self.mt5.ORDER_TYPE_SELL,
172
+ symbol=symbol,
173
+ volume=symbol_info["volume_min"],
174
+ price=symbol_info_tick["bid"],
175
+ )
176
+ min_order_margins = {"ask": min_ask_order_margin, "bid": min_bid_order_margin}
177
+ self.logger.info("Minimum order margins for %s: %s", symbol, min_order_margins)
178
+ if all(min_order_margins.values()):
179
+ return min_order_margins
180
+ else:
181
+ error_message = (
182
+ f"Failed to calculate minimum order margins for symbol: {symbol}."
183
+ )
140
184
  raise Mt5TradingError(error_message)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdmt5
3
- Version: 0.0.8
3
+ Version: 0.1.0
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=MumdFp72ZN_394X_viMkovfyb1c8C-LxLFTdymLEMYM,38345
3
+ pdmt5/mt5.py,sha256=rAY7MQalobUWZtMfC_xyTFCDTuU3EkFAyY_geBl6cyg,32379
4
+ pdmt5/trading.py,sha256=4rT0WgK0JyLMv8XatXur8-NWqG6gh_8XQ-g5sVX9xq0,6987
5
+ pdmt5/utils.py,sha256=Ll5Q3OE5h1A_sZ_qVEnOPGniFlT6_MmHfuu0zqeLdeU,3913
6
+ pdmt5-0.1.0.dist-info/METADATA,sha256=MJKmvQctS6kaU_OhqtXOYJNNa1uUutr2smvmUfnUD5g,9029
7
+ pdmt5-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
+ pdmt5-0.1.0.dist-info/licenses/LICENSE,sha256=iABrdaUGOBWLYotFupB_PGe8arV5o7rVhn-_vK6P704,1073
9
+ pdmt5-0.1.0.dist-info/RECORD,,
@@ -1,9 +0,0 @@
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,,
File without changes