PythonMetaTrader5 1.0.9__py3-none-any.whl → 2.1__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.
@@ -1,58 +1,77 @@
1
- # internal order send
2
1
  import time
3
2
  from MetaTrader5 import *
4
- import colorama
3
+ from loguru import logger
5
4
  import pyautogui
6
5
 
7
- colorama.init()
8
-
9
- def SpacerStart():
10
- print(colorama.Style.RESET_ALL + "//////////////////////////////")
11
-
12
- def SpacerEnd():
13
- print(colorama.Style.RESET_ALL + "//////////////////////////////")
14
- print()
15
6
 
16
7
  class Broker:
17
- RetCodes = {10027: "Enable Algo Trading in MetaTrader5 app", 10018: "Market closed", 10016: "Wrong SL"}
8
+ RetCodes = {
9
+ 10027: "Enable Algo Trading in MetaTrader5 app",
10
+ 10018: "Market closed",
11
+ 10016: "Wrong SL",
12
+ }
13
+
18
14
  log = None
19
15
  password = None
20
16
  server = None
17
+
21
18
  def __init__(self, log, password, server):
19
+ self.log = log
20
+ self.password = str(password)
21
+ self.server = str(server)
22
+
22
23
  initialize()
23
- if login(log, str(password), server=str(server)):
24
- print(colorama.Fore.GREEN + "Login succes")
24
+ if login(self.log, self.password, server=self.server):
25
+ logger.success("Login succes")
25
26
  acc_info = account_info()
26
- print(colorama.Style.RESET_ALL + str(acc_info))
27
- self.log = log
28
- self.password = str(password)
29
- self.server = str(server)
30
-
27
+ logger.info(str(acc_info))
31
28
  else:
32
- print(colorama.Fore.RED + "Login failed: ", str(self.log))
29
+ logger.error(f"Login failed: {self.log}")
33
30
 
34
- def login(self, ToEffect = False):
31
+ def login(self, ToEffect=False):
35
32
  while True:
36
- print(colorama.Fore.YELLOW + "Login on: ", str(self.log))
37
- print(self.log)
33
+ logger.info(f"Login on: {self.log}")
38
34
  if login(self.log, self.password, server=self.server):
39
- print(colorama.Fore.GREEN + "OK")
35
+ logger.success("OK")
40
36
  time.sleep(0.5)
41
37
  return True
42
- if ToEffect==False:
43
- print(colorama.Fore.RED + "ERROR")
38
+ if ToEffect is False:
39
+ logger.error("ERROR")
44
40
  return False
45
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 --------------------
46
65
 
47
66
  def _RawOrderClose(self, order_type, symbol, volume, price, comment=None, ticket=None):
48
67
  order = {
49
68
  "action": TRADE_ACTION_DEAL,
50
69
  "symbol": symbol,
51
- "volume": volume,
70
+ "volume": float(volume),
52
71
  "type": order_type,
53
- "price": price,
72
+ "price": float(price),
54
73
  "deviation": 10,
55
- "type_filling": ORDER_FILLING_IOC
74
+ "type_filling": ORDER_FILLING_IOC,
56
75
  }
57
76
  if comment is not None:
58
77
  order["comment"] = comment
@@ -60,9 +79,17 @@ class Broker:
60
79
  order["position"] = ticket
61
80
  return order_send(order)
62
81
 
63
-
64
- def _RawOrder(self, order_type, symbol, volume, price, sl=None, comment=None, ticket=None):
82
+ def _RawOrder(self, order_type, symbol, volume, price, sl=None, tp=None, comment=None, ticket=None):
65
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
+
66
93
  order = {
67
94
  "action": TRADE_ACTION_DEAL,
68
95
  "symbol": symbol,
@@ -70,394 +97,439 @@ class Broker:
70
97
  "type": order_type,
71
98
  "price": price,
72
99
  "deviation": 10,
73
- "type_filling": ORDER_FILLING_IOC
100
+ "type_filling": ORDER_FILLING_FOK,
74
101
  }
75
102
  if comment is not None:
76
103
  order["comment"] = comment
77
104
  if ticket is not None:
78
105
  order["position"] = ticket
79
-
80
106
  if sl is not None:
81
- sl = float(sl)
82
- order["sl"] = sl
83
-
107
+ order["sl"] = float(sl)
108
+ if tp is not None:
109
+ order["tp"] = float(tp)
84
110
  return order_send(order)
85
111
 
86
-
87
112
  def _RawOrderLimit(self, order_type, symbol, volume, price, limit, comment=None, ticket=None):
88
- limit = float(limit)
89
- volume = float(volume)
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
+
90
118
  order = {
91
119
  "action": TRADE_ACTION_PENDING,
92
120
  "symbol": symbol,
93
- "volume": volume,
121
+ "volume": float(volume),
94
122
  "type": order_type,
95
- "price": limit,
123
+ "price": float(limit),
96
124
  "deviation": 10,
97
- "type_filling": ORDER_FILLING_IOC
125
+ "type_filling": ORDER_FILLING_IOC,
98
126
  }
99
127
  if comment is not None:
100
128
  order["comment"] = comment
101
129
  if ticket is not None:
102
130
  order["position"] = ticket
103
-
104
131
  return order_send(order)
105
132
 
106
- # Close all specific orders
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
+
107
354
  def Close(self, symbol, *, comment=None, ticket=None):
108
355
  close = False
109
356
  if ticket is not None:
110
357
  positions = positions_get(ticket=ticket)
111
358
  else:
112
359
  positions = positions_get(symbol=symbol)
113
- tried = 0
114
- done = 0
360
+
115
361
  lenpositions = len(positions)
116
362
  if positions:
117
363
  while True:
118
364
  for pos in positions:
119
- # process only simple buy, sell, without limit or stop
120
365
  if pos.type == ORDER_TYPE_BUY or pos.type == ORDER_TYPE_SELL:
121
- tried += 1
122
- for tries in range(10):
366
+ for _ in range(10):
123
367
  info = symbol_info_tick(symbol)
124
368
  if info is None:
125
369
  return None
126
370
  if pos.type == ORDER_TYPE_BUY:
127
- r = self._RawOrderClose(ORDER_TYPE_SELL, symbol, pos.volume, info.bid, comment, pos.ticket)
371
+ r = self._RawOrderClose(
372
+ ORDER_TYPE_SELL, symbol, pos.volume, info.bid, comment, pos.ticket
373
+ )
128
374
  close = True
129
375
  else:
130
- r = self._RawOrderClose(ORDER_TYPE_BUY, symbol, pos.volume, info.ask, comment, pos.ticket)
376
+ r = self._RawOrderClose(
377
+ ORDER_TYPE_BUY, symbol, pos.volume, info.ask, comment, pos.ticket
378
+ )
131
379
  close = True
132
- # check results
133
380
  if r is None:
134
381
  return None
135
382
  if r.retcode != TRADE_RETCODE_REQUOTE and r.retcode != TRADE_RETCODE_PRICE_OFF:
136
383
  if r.retcode == TRADE_RETCODE_DONE:
137
- SpacerStart()
138
- print(colorama.Fore.BLUE + str(self.log), ": closing all ", symbol)
139
- SpacerEnd()
140
- done += 1
384
+ logger.info(f"{self.log}: closing all {symbol}")
141
385
  lenpositions -= 1
142
386
  break
143
387
  if r.retcode in self.RetCodes:
144
- print(colorama.Fore.RED + "ERROR CAN NOT CLOSE", str(self.log), symbol)
145
- print(self.RetCodes[r.retcode])
146
- print(colorama.Style.RESET_ALL)
388
+ logger.error(f"ERROR CAN NOT CLOSE {self.log} {symbol}")
389
+ logger.error(self.RetCodes[r.retcode])
147
390
  time.sleep(0.5)
148
- if not(lenpositions):
391
+ if not lenpositions:
149
392
  break
150
393
  if r.retcode == 10027:
151
- print(colorama.Fore.GREEN + "Enabling algotrading", str(self.log), symbol)
152
- print(colorama.Style.RESET_ALL)
394
+ logger.info(f"Enabling algotrading {self.log} {symbol}")
153
395
  time.sleep(1)
154
- pyautogui.hotkey('ctrl', 'e')
396
+ pyautogui.hotkey("ctrl", "e")
155
397
 
156
398
  if not close:
157
- SpacerStart()
158
- print(str(self.log), ": there is no orders to close")
159
- SpacerEnd()
399
+ logger.info(f"{self.log}: there is no orders to close")
160
400
  return False
161
401
 
162
-
163
- # Buy order
164
- def Buy(self, symbol, volume, price=None, *, comment=None, ticket=None):
165
- # with direct call
166
- if price is not None:
167
- return self._RawOrder(ORDER_TYPE_BUY, symbol, volume, price, comment, ticket)
168
- # no price, we try several times with current price
169
- while True:
170
- info = symbol_info_tick(symbol)
171
- r = r = self._RawOrder(ORDER_TYPE_BUY, symbol, volume, info.ask, comment, ticket)
172
- if r.retcode == 10009:
173
- SpacerStart()
174
- print(colorama.Fore.GREEN + str(self.log), ": long ", symbol)
175
- SpacerEnd()
176
- break
177
-
178
- else:
179
- if r.retcode in self.RetCodes:
180
- print(colorama.Fore.RED + "ERROR CAN NOT OPEN", str(self.log), ": long ", symbol)
181
- print(self.RetCodes[r.retcode])
182
- print(colorama.Style.RESET_ALL)
183
- if r.retcode == 10027:
184
- print(colorama.Fore.GREEN + "Enabling algotrading", str(self.log), symbol)
185
- print(colorama.Style.RESET_ALL)
186
- time.sleep(1)
187
- pyautogui.hotkey('ctrl', 'e')
188
- time.sleep(0.5)
189
-
190
-
191
- else:
192
- print(colorama.Fore.RED + "ERROR CAN NOT OPEN TRYING AGAIN", str(self.log), ": long ", symbol)
193
- print("RetCode: ", r.retcode)
194
- print("Comment: ", r.comment)
195
- print(colorama.Style.RESET_ALL)
196
- time.sleep(0.5)
197
-
198
-
199
- def BuySL(self, symbol, volume, sl, price=None, *, comment=None, ticket=None):
200
- # with direct call
201
- if price is not None:
202
- return self._RawOrder(ORDER_TYPE_BUY, symbol, volume, price, sl, comment, ticket)
203
- # no price, we try several times with current price
204
- while True:
205
- info = symbol_info_tick(symbol)
206
- r = self._RawOrder(ORDER_TYPE_BUY, symbol, volume, info.ask, sl, comment, ticket)
207
- if r.retcode == 10009:
208
- SpacerStart()
209
- print(colorama.Fore.GREEN + str(self.log), ": long ", symbol, "sl: ", sl)
210
- SpacerEnd()
211
- break
212
-
213
- else:
214
- if r.retcode in self.RetCodes:
215
- print(colorama.Fore.RED + "ERROR CAN NOT OPEN", str(self.log), ": long ", symbol)
216
- print(self.RetCodes[r.retcode])
217
- print(colorama.Style.RESET_ALL)
218
- if r.retcode == 10016:
219
- break
220
- if r.retcode == 10027:
221
- print(colorama.Fore.GREEN + "Enabling algotrading", str(self.log), symbol)
222
- print(colorama.Style.RESET_ALL)
223
- time.sleep(1)
224
- pyautogui.hotkey('ctrl', 'e')
225
- time.sleep(0.5)
226
-
227
-
228
- else:
229
- print(colorama.Fore.RED + "ERROR CAN NOT OPEN TRYING AGAIN", str(self.log), ": long ", symbol)
230
- print("RetCode: ", r.retcode)
231
- print("Comment: ", r.comment)
232
- print(colorama.Style.RESET_ALL)
233
- time.sleep(0.5)
234
- return r
235
-
236
-
237
- # Sell order
238
- def Sell(self, symbol, volume, price=None, *, comment=None, ticket=None):
239
- # with direct call
240
- if price is not None:
241
- return self._RawOrder(ORDER_TYPE_SELL, symbol, volume, price, comment, ticket)
242
- # no price, we try several times with current price
243
- while True:
244
- info = symbol_info_tick(symbol)
245
- r = self._RawOrder(ORDER_TYPE_SELL, symbol, volume, info.bid, comment, ticket)
246
- if r.retcode == 10009:
247
- SpacerStart()
248
- print(colorama.Fore.RED + str(self.log), ": short ", symbol)
249
- SpacerEnd()
250
- break
251
-
252
- else:
253
- if r.retcode in self.RetCodes:
254
- print(colorama.Fore.RED + "ERROR CAN NOT OPEN", str(self.log), " short ", symbol)
255
- print(self.RetCodes[r.retcode])
256
- print(colorama.Style.RESET_ALL)
257
- if r.retcode == 10027:
258
- print(colorama.Fore.GREEN + "Enabling algotrading", str(self.log), symbol)
259
- print(colorama.Style.RESET_ALL)
260
- time.sleep(1)
261
- pyautogui.hotkey('ctrl', 'e')
262
- time.sleep(0.5)
263
-
264
-
265
- else:
266
- print(colorama.Fore.RED + "ERROR CAN NOT OPEN TRYING AGAIN", str(self.log), ": short ", symbol)
267
- print("RetCode: ", r.retcode)
268
- print("Comment: ", r.comment)
269
- print(colorama.Style.RESET_ALL)
270
- time.sleep(0.5)
271
-
272
-
273
- def SellSL(self, symbol, volume, sl, price=None, *, comment=None, ticket=None):
274
-
275
- # with direct call
276
- if price is not None:
277
- return self._RawOrder(ORDER_TYPE_SELL, symbol, volume, price, float(sl), comment, ticket)
278
- # no price, we try several times with current price
279
- while True:
280
- info = symbol_info_tick(symbol)
281
- r = self._RawOrder(ORDER_TYPE_SELL, symbol, volume, info.bid, float(sl), comment, ticket)
282
- if r.retcode == 10009:
283
- SpacerStart()
284
- print(colorama.Fore.RED + str(self.log), ": short ", symbol, " sl ", sl)
285
- SpacerEnd()
286
- break
287
-
288
- else:
289
- if r.retcode in self.RetCodes:
290
- print(colorama.Fore.RED + "ERROR CAN NOT OPEN", str(self.log), " short ", symbol, " sl ", sl)
291
- print(self.RetCodes[r.retcode])
292
- print(colorama.Style.RESET_ALL)
293
- if r.retcode == 10016:
294
- break
295
- if r.retcode == 10027:
296
- print(colorama.Fore.GREEN + "Enabling algotrading", str(self.log), symbol)
297
- print(colorama.Style.RESET_ALL)
298
- time.sleep(1)
299
- pyautogui.hotkey('ctrl', 'e')
300
- time.sleep(0.5)
301
-
302
-
303
- else:
304
- print(colorama.Fore.RED + "ERROR CAN NOT OPEN TRYING AGAIN", str(self.log), ": short ", symbol, " sl ", sl)
305
- print("RetCode: ", r.retcode)
306
- print("Comment: ", r.comment)
307
- print(colorama.Style.RESET_ALL)
308
- time.sleep(0.5)
309
-
310
- return r
311
-
402
+ # -------------------- Pending orders --------------------
312
403
 
313
404
  def SellLimit(self, symbol, volume, stoplimit, price=None, comment=None, ticket=None):
314
405
  OpenedInstantly = False
315
-
316
406
  if price is not None:
317
- return print("Price have to be none")
318
- # no price, we try several times with current price
407
+ logger.error("Price have to be none")
408
+ return
409
+
319
410
  while True:
320
411
  info = symbol_info_tick(symbol)
321
-
322
- if (float(info.bid) >= float(stoplimit)):
323
- r = self._RawOrder(ORDER_TYPE_SELL, symbol, float(volume), info.bid, comment, ticket)
324
-
412
+ if float(info.bid) >= float(stoplimit):
413
+ r = self._RawOrder(ORDER_TYPE_SELL, symbol, float(volume), info.bid, comment=comment, ticket=ticket)
325
414
  OpenedInstantly = True
326
415
  else:
327
- r = self._RawOrderLimit(ORDER_TYPE_SELL_LIMIT, symbol, float(volume), info.bid, float(stoplimit),
328
- comment,
329
- ticket)
416
+ r = self._RawOrderLimit(
417
+ ORDER_TYPE_SELL_LIMIT, symbol, float(volume), info.bid, float(stoplimit), comment, ticket
418
+ )
330
419
  OpenedInstantly = False
331
420
 
332
- if r.retcode == 10009:
421
+ if r.retcode == 10009:
333
422
  if OpenedInstantly:
334
- SpacerStart()
335
- print(colorama.Fore.RED + str(self.log), ": opened short on: ", symbol,
336
- "beacues of ask is > or = limit price")
337
- SpacerEnd()
423
+ logger.success(f"{self.log}: opened short on: {symbol} beacues of ask is > or = limit price")
338
424
  else:
339
- SpacerStart()
340
- print(colorama.Fore.RED + str(self.log), ": opened short limit on: ", symbol, " price: ", stoplimit)
341
- SpacerEnd()
425
+ logger.success(f"{self.log}: opened short limit on: {symbol} price: {stoplimit}")
342
426
  break
343
-
344
427
  else:
345
428
  if r.retcode in self.RetCodes:
346
- print(colorama.Fore.RED + "ERROR CAN NOT OPEN", str(self.log), " sell limit on: ", symbol, " price: ", stoplimit)
347
- print(self.RetCodes[r.retcode])
348
- print(colorama.Style.RESET_ALL)
429
+ logger.error(f"ERROR CAN NOT OPEN {self.log} sell limit on: {symbol} price: {stoplimit}")
430
+ logger.error(self.RetCodes[r.retcode])
349
431
  if r.retcode == 10027:
350
- print(colorama.Fore.GREEN + "Enabling algotrading", str(self.log), symbol)
351
- print(colorama.Style.RESET_ALL)
432
+ logger.info(f"Enabling algotrading {self.log} {symbol}")
352
433
  time.sleep(1)
353
- pyautogui.hotkey('ctrl', 'e')
434
+ pyautogui.hotkey("ctrl", "e")
354
435
  time.sleep(0.5)
355
-
356
436
  else:
357
- print(colorama.Fore.RED + "ERROR CAN NOT OPEN TRYING AGAIN", str(self.log), " sell limit on: ", symbol, " price: ", stoplimit)
358
- print("RetCode: ", r.retcode)
359
- print("Comment: ", r.comment)
360
- print(colorama.Style.RESET_ALL)
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}")
361
442
  time.sleep(0.5)
362
443
  return r
363
444
 
364
-
365
445
  def BuyLimit(self, symbol, volume, stoplimit, price=None, comment=None, ticket=None):
366
446
  OpenedInstantly = False
367
447
  if price is not None:
368
- return print("Price have to be none")
369
- # no price, we try several times with current price
448
+ logger.error("Price have to be none")
449
+ return
450
+
370
451
  while True:
371
452
  info = symbol_info_tick(symbol)
372
-
373
- if (float(info.ask) <= float(stoplimit)):
374
- r = self._RawOrder(ORDER_TYPE_BUY, symbol, float(volume), info.ask, comment, ticket)
453
+ if float(info.ask) <= float(stoplimit):
454
+ r = self._RawOrder(ORDER_TYPE_BUY, symbol, float(volume), info.ask, comment=comment, ticket=ticket)
375
455
  OpenedInstantly = True
376
456
  else:
377
- r = self._RawOrderLimit(ORDER_TYPE_BUY_LIMIT, symbol, float(volume), info.ask, float(stoplimit), comment, ticket)
457
+ r = self._RawOrderLimit(
458
+ ORDER_TYPE_BUY_LIMIT, symbol, float(volume), info.ask, float(stoplimit), comment, ticket
459
+ )
378
460
  OpenedInstantly = False
379
461
 
380
- if r.retcode == 10009:
462
+ if r.retcode == 10009:
381
463
  if OpenedInstantly:
382
- SpacerStart()
383
- print(colorama.Fore.GREEN + str(self.log), ": opened long on: ", symbol, "beacues of ask is < or = limit price")
384
- SpacerEnd()
464
+ logger.success(f"{self.log}: opened long on: {symbol} beacues of ask is < or = limit price")
385
465
  else:
386
- SpacerStart()
387
- print(colorama.Fore.GREEN + str(self.log), ": opened long limit on: ", symbol, " price: ", stoplimit)
388
- SpacerEnd()
466
+ logger.success(f"{self.log}: opened long limit on: {symbol} price: {stoplimit}")
389
467
  break
390
-
391
468
  else:
392
469
  if r.retcode in self.RetCodes:
393
- print(colorama.Fore.RED + "ERROR CAN NOT OPEN", str(self.log), " long limit on: ", symbol, " price: ", stoplimit)
394
- print(self.RetCodes[r.retcode])
395
- print(colorama.Style.RESET_ALL)
470
+ logger.error(f"ERROR CAN NOT OPEN {self.log} long limit on: {symbol} price: {stoplimit}")
471
+ logger.error(self.RetCodes[r.retcode])
396
472
  if r.retcode == 10027:
397
- print(colorama.Fore.GREEN + "Enabling algotrading", str(self.log), symbol)
398
- print(colorama.Style.RESET_ALL)
473
+ logger.info(f"Enabling algotrading {self.log} {symbol}")
399
474
  time.sleep(1)
400
- pyautogui.hotkey('ctrl', 'e')
475
+ pyautogui.hotkey("ctrl", "e")
401
476
  time.sleep(0.5)
402
-
403
-
404
477
  else:
405
- print(colorama.Fore.RED + "ERROR CAN NOT OPEN TRYING AGAIN", str(self.log), " long limit on: ", symbol, " price: ", stoplimit)
406
- print("RetCode: ", r.retcode)
407
- print("Comment: ", r.comment)
408
- print(colorama.Style.RESET_ALL)
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}")
409
483
  time.sleep(0.5)
410
484
  return r
411
485
 
486
+ # -------------------- Cancel pending orders --------------------
412
487
 
413
488
  def _RawCancelOrder(self, order_number):
414
- # Create the request
415
489
  request = {
416
490
  "action": TRADE_ACTION_REMOVE,
417
491
  "order": order_number,
418
- "comment": "Order Removed"
492
+ "comment": "Order Removed",
419
493
  }
420
- # Send order to MT5
421
- order_result = order_send(request)
422
- return order_result
423
-
494
+ return order_send(request)
424
495
 
425
496
  def CancelOrders(self, symbol):
426
- cancel = False
427
497
  orders = orders_get()
428
- limit_orders = [order for order in orders if
429
- order.type == ORDER_TYPE_BUY_LIMIT or order.type == ORDER_TYPE_SELL_LIMIT]
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
+ ]
430
503
  lenpositions = len(limit_orders)
504
+
431
505
  while True:
506
+ r = None
432
507
  for order in limit_orders:
433
508
  if order.symbol == symbol:
434
- cancel = True
435
509
  r = self._RawCancelOrder(order.ticket)
436
510
 
437
511
  if r.retcode != TRADE_RETCODE_REQUOTE and r.retcode != TRADE_RETCODE_PRICE_OFF:
438
512
  if r.retcode == TRADE_RETCODE_DONE:
439
- SpacerStart()
440
- print(colorama.Fore.BLUE + str(self.log), ": order have been just cancelled on: ", symbol)
441
- SpacerEnd()
513
+ logger.info(f"{self.log}: order have been just cancelled on: {symbol}")
442
514
  lenpositions -= 1
443
515
  break
444
- if r.retcode in self.RetCodes:
445
- print(colorama.Fore.RED + "ERROR CAN NOT CLOSE", str(self.log), symbol)
446
- print(self.RetCodes[r.retcode])
447
- print(colorama.Style.RESET_ALL)
448
- time.sleep(0.5)
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)
449
520
 
450
521
  if not lenpositions:
451
522
  break
452
- if r.retcode == 10027:
453
- print(colorama.Fore.GREEN + "Enabling algotrading", str(self.log), symbol)
454
- print(colorama.Style.RESET_ALL)
523
+ if r is not None and r.retcode == 10027:
524
+ logger.info(f"Enabling algotrading {self.log} {symbol}")
455
525
  time.sleep(1)
456
- pyautogui.hotkey('ctrl', 'e')
526
+ pyautogui.hotkey("ctrl", "e")
457
527
 
458
528
  if not len(limit_orders):
459
- SpacerStart()
460
- print(str(self.log), ": there is no orders to cancel")
461
- SpacerEnd()
529
+ logger.info(f"{self.log}: there is no orders to cancel")
530
+
531
+ # -------------------- Disconnect --------------------
532
+
462
533
  def Disconnect(self):
463
534
  shutdown()
535
+ logger.info("Disconnected from MT5")
@@ -0,0 +1,29 @@
1
+ Metadata-Version: 2.4
2
+ Name: PythonMetaTrader5
3
+ Version: 2.1
4
+ Summary: Connect MT5 with Python
5
+ Author-email: Wiktor Jelen <wiktorjn@gmail.com>
6
+ License: CC BY-NC 4.0
7
+ Project-URL: Homepage, https://github.com/Akinzou/MetaTrader5-Python
8
+ Project-URL: Bug Tracker, https://github.com/Akinzou/MetaTrader5-Python/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.6
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: MetaTrader5
15
+ Requires-Dist: loguru
16
+ Requires-Dist: pyautogui
17
+
18
+ # PythonMetaTrader5 [![PyPI Publish](https://github.com/Akinzou/MetaTrader5-Python/actions/workflows/python-publish.yml/badge.svg)](https://github.com/Akinzou/MetaTrader5-Python/actions/workflows/python-publish.yml) ![PyPI](https://img.shields.io/pypi/v/pythonmetatrader5) ![Python](https://img.shields.io/badge/python-3.6%2B-blue?logo=python&logoColor=white) ![License](https://img.shields.io/badge/license-CC_BY--NC_4.0-lightgrey.svg) [![PyPI Downloads](https://static.pepy.tech/badge/pythonmetatrader5)](https://pepy.tech/projects/pythonmetatrader5)
19
+
20
+ [![Discord](https://img.shields.io/badge/Join_us_on-Discord-5865F2?logo=discord&logoColor=white&style=for-the-badge)](https://discord.gg/BARYa55KS8)
21
+
22
+
23
+ MetaTrader 5
24
+ This Python library enables you to connect with the MetaTrader 5 trading platform and interact with its features using Python. It allows executing trading operations, fetching market data, receiving notifications, and more.
25
+
26
+ Soon -> huge update
27
+
28
+
29
+
@@ -0,0 +1,5 @@
1
+ PythonMetaTrader5/__init__.py,sha256=FQYk4BheVnCjEDqIKFw_i5bR6tTOquydzNtxbKkO9F8,21318
2
+ pythonmetatrader5-2.1.dist-info/METADATA,sha256=7lJr9jvAYIfS3NLSsWputOFnqimh-sxgMgmYSGIj3io,1565
3
+ pythonmetatrader5-2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
4
+ pythonmetatrader5-2.1.dist-info/top_level.txt,sha256=r8xFh3BXAc7RVgR1j6wCYfyk_mm_DaDoGUYFk2IqBLE,18
5
+ pythonmetatrader5-2.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,111 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: PythonMetaTrader5
3
- Version: 1.0.9
4
- Summary: Connect MT5 with Python
5
- Home-page: https://github.com/Akinzou/MetaTrader5-Python
6
- Author: Wiktor Jelen
7
- Author-email: wiktorjn@gmail.com
8
- Project-URL: Bug Tracker, https://github.com/Akinzou/MetaTrader5-Python/issues
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Operating System :: OS Independent
12
- Requires-Python: >=3.6
13
- Description-Content-Type: text/markdown
14
-
15
- # Python Library for MetaTrader 5 Connection
16
-
17
- ![MetaTrader 5]
18
- This Python library enables you to connect with the MetaTrader 5 trading platform and interact with its features using Python. It allows executing trading operations, fetching market data, receiving notifications, and more.
19
-
20
- ## Requirements
21
-
22
- To use this library, you need:
23
-
24
- Python 3.x
25
- MetaTrader 5 trading platform installed
26
- MetaTrader 5 Python API (MetaTrader5 module)
27
- colorama
28
-
29
- ## Installation
30
-
31
- To install the library, use `pip`:
32
-
33
- ```bash
34
- pip install MetaTrader5
35
- pip install PythonMetaTrader5
36
- ```
37
- # Getting Started
38
-
39
- Import the library in your Python script:
40
-
41
- ```
42
- from MetaTrader5 import *
43
- ```
44
-
45
- Initialize the MetaTrader 5 library by calling the initialize() function.
46
-
47
- Create an instance of the Broker class by providing your MetaTrader 5 login credentials and server:
48
-
49
- ```
50
- broker = Broker(log=your_mt5_login, password="your_mt5_password", server="your_mt5_server")
51
- ```
52
- ### After successful login, you can perform trading operations such as placing orders and managing positions.
53
-
54
-
55
- ## Available Functions:
56
- ### Placing Orders:
57
-
58
- ``Buy(symbol, volume, price=None, *, comment=None, ticket=None):`` Places a Buy market order or limit order if price is specified.
59
-
60
- ``BuySL(symbol, volume, sl, price=None, *, comment=None, ticket=None):`` Places a Buy market order or limit order with a Stop Loss (SL) price if price is specified.
61
-
62
- ``Sell(symbol, volume, price=None, *, comment=None, ticket=None):`` Places a Sell market order or limit order if price is specified.
63
-
64
- ``SellSL(symbol, volume, sl, price=None, *, comment=None, ticket=None):`` Places a Sell market order or limit order with a Stop Loss (SL) price if price is specified.
65
-
66
- ``BuyLimit(symbol, volume, stoplimit, price=None, comment=None, ticket=None):`` Places a Buy limit order with the specified stoplimit price.
67
-
68
- ``SellLimit(symbol, volume, stoplimit, price=None, comment=None, ticket=None):`` Places a Sell limit order with the specified stoplimit price.
69
-
70
-
71
- ### Managing Positions:
72
- ``Close(symbol, *, comment=None, ticket=None):`` Closes all positions for the specified symbol or a specific position indicated by ticket.
73
-
74
- ``CancelOrders(symbol):`` Cancels all pending orders for the specified symbol.
75
-
76
-
77
- ### Miscellaneous:
78
- ``Disconnect():`` Closes the connection to the MetaTrader 5 trading platform.
79
-
80
-
81
- ## Example Usage:
82
- ```
83
- from MetaTrader5 import *
84
-
85
- # Initialize the MetaTrader 5 library
86
- initialize()
87
-
88
- # Create a broker instance and log in
89
- broker = Broker(log=your_mt5_login, password="your_mt5_password", server="your_mt5_server")
90
-
91
- # Place a Buy market order for 0.1 lot of EURUSD
92
- broker.Buy("EURUSD", 0.1)
93
-
94
- # Place a limit order for gold (if price is lower than 1900 it will be execute immediately
95
- broker.BuyLimit("XAUUSD", 0.01, 1900)
96
-
97
- # Close all positions for EURUSD
98
- broker.Close("EURUSD")
99
-
100
- # Cancel all pending orders for EURUSD
101
- broker.CancelOrders("EURUSD")
102
-
103
- # Disconnect from the MetaTrader 5 platform
104
- broker.Disconnect()
105
- ```
106
-
107
-
108
- ## Note:
109
- It is important to have the MetaTrader 5 trading platform running and logged in with the provided credentials before using this library.
110
- This library is provided "as is" without any warranty. Use it at your own risk.
111
- Before using this library in a live trading environment, thoroughly test it on a demo account to ensure its correctness and reliability.
@@ -1,5 +0,0 @@
1
- PythonMetaTrader5/__init__.py,sha256=HvV8KhWHGUf7yhYxuSfS-tUMJIM_jsTsq_8wVbtNJDY,19015
2
- PythonMetaTrader5-1.0.9.dist-info/METADATA,sha256=oEbrX8zr7-ETOO9NXwTn8669yl98e5yztGpCOfoSq3Y,3925
3
- PythonMetaTrader5-1.0.9.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
4
- PythonMetaTrader5-1.0.9.dist-info/top_level.txt,sha256=r8xFh3BXAc7RVgR1j6wCYfyk_mm_DaDoGUYFk2IqBLE,18
5
- PythonMetaTrader5-1.0.9.dist-info/RECORD,,