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