mt5-mac 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.
mt5_mac/__init__.py ADDED
@@ -0,0 +1,277 @@
1
+ from __future__ import annotations
2
+
3
+ __version__ = "0.1.0"
4
+ __author__ = "Abdul Rafay Jiwani"
5
+ __email__ = "abdulrafayjiwani@proton.me"
6
+ __license__ = "MIT"
7
+
8
+ from mt5_mac.backend import MT5Backend
9
+ from mt5_mac.types import (
10
+ MqlTick, MqlRates, MqlAccountInfo, MqlSymbolInfo,
11
+ MqlTradeRequest, MqlTradeResult, MqlPosition, MqlOrder, MqlDeal,
12
+ )
13
+
14
+ _backend: MT5Backend | None = None
15
+
16
+ # Timeframes
17
+ TIMEFRAME_M1 = 1
18
+ TIMEFRAME_M2 = 2
19
+ TIMEFRAME_M3 = 3
20
+ TIMEFRAME_M4 = 4
21
+ TIMEFRAME_M5 = 5
22
+ TIMEFRAME_M6 = 6
23
+ TIMEFRAME_M10 = 10
24
+ TIMEFRAME_M12 = 12
25
+ TIMEFRAME_M15 = 15
26
+ TIMEFRAME_M20 = 20
27
+ TIMEFRAME_M30 = 30
28
+ TIMEFRAME_H1 = 60
29
+ TIMEFRAME_H2 = 120
30
+ TIMEFRAME_H3 = 180
31
+ TIMEFRAME_H4 = 240
32
+ TIMEFRAME_H6 = 360
33
+ TIMEFRAME_H8 = 480
34
+ TIMEFRAME_H12 = 720
35
+ TIMEFRAME_D1 = 1440
36
+ TIMEFRAME_W1 = 10080
37
+ TIMEFRAME_MN1 = 43200
38
+
39
+ # Trade actions
40
+ TRADE_ACTION_DEAL = 1
41
+ TRADE_ACTION_PENDING = 5
42
+ TRADE_ACTION_SLTP = 2
43
+ TRADE_ACTION_MODIFY = 7
44
+ TRADE_ACTION_REMOVE = 6
45
+ TRADE_ACTION_CLOSE_BY = 3
46
+
47
+ # Order types
48
+ ORDER_TYPE_BUY = 0
49
+ ORDER_TYPE_SELL = 1
50
+ ORDER_TYPE_BUY_LIMIT = 2
51
+ ORDER_TYPE_SELL_LIMIT = 3
52
+ ORDER_TYPE_BUY_STOP = 4
53
+ ORDER_TYPE_SELL_STOP = 5
54
+ ORDER_TYPE_BUY_STOP_LIMIT = 6
55
+ ORDER_TYPE_SELL_STOP_LIMIT = 7
56
+ ORDER_TYPE_CLOSE_BY = 8
57
+
58
+ # Order filling
59
+ ORDER_FILLING_FOK = 0
60
+ ORDER_FILLING_IOC = 1
61
+ ORDER_FILLING_RETURN = 2
62
+
63
+ # Order time
64
+ ORDER_TIME_GTC = 0
65
+ ORDER_TIME_DAY = 1
66
+ ORDER_TIME_SPECIFIED = 2
67
+ ORDER_TIME_SPECIFIED_DAY = 3
68
+
69
+ # Position types
70
+ POSITION_TYPE_BUY = 0
71
+ POSITION_TYPE_SELL = 1
72
+
73
+ # Deal types
74
+ DEAL_TYPE_BUY = 0
75
+ DEAL_TYPE_SELL = 1
76
+ DEAL_TYPE_BALANCE = 2
77
+ DEAL_TYPE_CREDIT = 3
78
+ DEAL_TYPE_CHARGE = 4
79
+ DEAL_TYPE_CORRECTION = 5
80
+ DEAL_TYPE_BONUS = 6
81
+ DEAL_TYPE_COMMISSION = 7
82
+ DEAL_TYPE_COMMISSION_DAILY = 8
83
+ DEAL_TYPE_COMMISSION_MONTHLY = 9
84
+ DEAL_TYPE_COMMISSION_AGENT_DAILY = 10
85
+ DEAL_TYPE_COMMISSION_AGENT_MONTHLY = 11
86
+ DEAL_TYPE_INTEREST = 12
87
+ DEAL_TYPE_BUY_CANCELED = 13
88
+ DEAL_TYPE_SELL_CANCELED = 14
89
+ DEAL_DIVIDEND = 15
90
+ DEAL_DIVIDEND_FRANKFURT = 16
91
+ DEAL_TAX = 17
92
+
93
+ # Deal entries
94
+ DEAL_ENTRY_IN = 0
95
+ DEAL_ENTRY_OUT = 1
96
+ DEAL_ENTRY_INOUT = 2
97
+ DEAL_ENTRY_OUT_BY = 3
98
+
99
+ # Trade modes
100
+ ACCOUNT_TRADE_MODE_DEMO = 0
101
+ ACCOUNT_TRADE_MODE_CONTEST = 1
102
+ ACCOUNT_TRADE_MODE_REAL = 2
103
+
104
+ # Margin modes
105
+ ACCOUNT_MARGIN_MODE_RETAIL_NETTING = 0
106
+ ACCOUNT_MARGIN_MODE_EXCHANGE = 1
107
+ ACCOUNT_MARGIN_MODE_RETAIL_HEDGING = 2
108
+
109
+ # Order reasons
110
+ ORDER_REASON_CLIENT = 0
111
+ ORDER_REASON_MOBILE = 1
112
+ ORDER_REASON_WEB = 2
113
+ ORDER_REASON_EXPERT = 3
114
+ ORDER_REASON_SL = 4
115
+ ORDER_REASON_TP = 5
116
+ ORDER_REASON_SO = 6
117
+
118
+ # Reason for positions/deals
119
+ POSITION_REASON_CLIENT = 0
120
+ POSITION_REASON_MOBILE = 1
121
+ POSITION_REASON_WEB = 2
122
+ POSITION_REASON_EXPERT = 3
123
+
124
+ # Symbol trade modes
125
+ SYMBOL_TRADE_MODE_DISABLED = 0
126
+ SYMBOL_TRADE_MODE_LONG = 1
127
+ SYMBOL_TRADE_MODE_SHORT = 2
128
+ SYMBOL_TRADE_MODE_CLOSE = 4
129
+
130
+ # Symbol trade execution
131
+ SYMBOL_TRADE_EXECUTION_REQUEST = 0
132
+ SYMBOL_TRADE_EXECUTION_INSTANT = 1
133
+ SYMBOL_TRADE_EXECUTION_MARKET = 2
134
+ SYMBOL_TRADE_EXECUTION_EXCHANGE = 3
135
+
136
+ # Trade calc modes
137
+ SYMBOL_TRADE_CALC_MODE_FOREX = 0
138
+ SYMBOL_TRADE_CALC_MODE_CFD = 1
139
+ SYMBOL_TRADE_CALC_MODE_FUTURES = 2
140
+ SYMBOL_TRADE_CALC_MODE_CFDINDEX = 3
141
+ SYMBOL_TRADE_CALC_MODE_CFDLEVERAGE = 4
142
+
143
+ # Error codes
144
+ RES_E_SUCCESS = 10009
145
+ RES_E_FAIL = -1
146
+ RES_E_INVALID_PARAMS = -2
147
+ RES_E_NO_MONEY = -3
148
+ RES_E_NOT_FOUND = -4
149
+ RES_E_MARKET_CLOSED = -5
150
+ RES_E_TRADE_DISABLED = -6
151
+ RES_E_PRICE_LIMIT = -7
152
+ RES_E_TOO_MANY_REQUESTS = -8
153
+ RES_E_SERVER_BUSY = -9
154
+ RES_E_NO_CONNECTION = -10
155
+
156
+
157
+ def initialize(
158
+ path: str | None = None,
159
+ login: int | str | None = None,
160
+ password: str | None = None,
161
+ server: str = "",
162
+ timeout: int | None = None,
163
+ portable: bool | None = None,
164
+ quiet: bool = False,
165
+ **kwargs,
166
+ ) -> bool:
167
+ global _backend
168
+ if _backend is not None:
169
+ _backend.stop()
170
+ _backend = MT5Backend(terminal_path=path or "", quiet=quiet)
171
+ ok = _backend.start(path=path)
172
+ if ok and login is not None and password is not None:
173
+ ok = _backend.login(str(login), password, server)
174
+ return ok
175
+
176
+
177
+ def shutdown() -> None:
178
+ global _backend
179
+ if _backend is not None:
180
+ _backend.stop()
181
+ _backend = None
182
+
183
+
184
+ def login(login: int | str, password: str, server: str = "", timeout: int = 30000) -> bool:
185
+ _require_backend()
186
+ return _backend.login(str(login), password, server)
187
+
188
+
189
+ def account_info() -> MqlAccountInfo | None:
190
+ _require_backend()
191
+ return _backend.account_info()
192
+
193
+
194
+ def symbols_get(symbol: str | None = None) -> tuple[MqlSymbolInfo, ...] | None:
195
+ _require_backend()
196
+ return _backend.symbols_get(symbol)
197
+
198
+
199
+ def symbol_info(symbol: str) -> MqlSymbolInfo | None:
200
+ _require_backend()
201
+ return _backend.symbol_info(symbol)
202
+
203
+
204
+ def symbol_info_tick(symbol: str) -> MqlTick | None:
205
+ _require_backend()
206
+ return _backend.symbol_info_tick(symbol)
207
+
208
+
209
+ def symbol_select(symbol: str, enable: bool = True) -> bool:
210
+ _require_backend()
211
+ return _backend.symbol_select(symbol, enable)
212
+
213
+
214
+ def copy_rates_from_pos(
215
+ symbol: str,
216
+ timeframe: int,
217
+ start_pos: int,
218
+ count: int,
219
+ ) -> tuple[MqlRates, ...] | None:
220
+ _require_backend()
221
+ return _backend.copy_rates_from_pos(symbol, timeframe, start_pos, count)
222
+
223
+
224
+ def copy_rates_from(
225
+ symbol: str,
226
+ timeframe: int,
227
+ date_from: int,
228
+ count: int,
229
+ ) -> tuple[MqlRates, ...] | None:
230
+ _require_backend()
231
+ return _backend.copy_rates_from(symbol, timeframe, date_from, count)
232
+
233
+
234
+ def copy_rates_range(
235
+ symbol: str,
236
+ timeframe: int,
237
+ date_from: int,
238
+ date_to: int,
239
+ ) -> tuple[MqlRates, ...] | None:
240
+ _require_backend()
241
+ return _backend.copy_rates_range(symbol, timeframe, date_from, date_to)
242
+
243
+
244
+ def order_send(request: dict) -> MqlTradeResult:
245
+ _require_backend()
246
+ return _backend.order_send(request)
247
+
248
+
249
+ def positions_get(symbol: str = "") -> tuple[MqlPosition, ...] | None:
250
+ _require_backend()
251
+ return _backend.positions_get(symbol)
252
+
253
+
254
+ def orders_get(symbol: str = "") -> tuple[MqlOrder, ...] | None:
255
+ _require_backend()
256
+ return _backend.orders_get(symbol)
257
+
258
+
259
+ def history_deals_get(date_from: int = 0, date_to: int = 0) -> tuple[MqlDeal, ...] | None:
260
+ _require_backend()
261
+ return _backend.history_deals_get(date_from, date_to)
262
+
263
+
264
+ def history_orders_get(date_from: int = 0, date_to: int = 0) -> tuple[MqlOrder, ...] | None:
265
+ _require_backend()
266
+ return _backend.history_orders_get(date_from, date_to)
267
+
268
+
269
+ def last_error() -> int:
270
+ return 0
271
+
272
+
273
+ def _require_backend() -> None:
274
+ if _backend is None:
275
+ raise RuntimeError(
276
+ "MT5 not initialized. Call mt5_mac.initialize() first."
277
+ )
mt5_mac/agent.py ADDED
@@ -0,0 +1,174 @@
1
+ """
2
+ Agent script for mt5_mac library.
3
+ Runs inside Wine on macOS, using the official MetaTrader5 Python package.
4
+ Communicates via JSON over stdin/stdout.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import os
11
+ import sys
12
+ import traceback
13
+ from typing import Any
14
+
15
+ try:
16
+ import MetaTrader5 as mt5
17
+ except ImportError:
18
+ print(json.dumps({"error": "MetaTrader5 not available in this Wine Python environment"}))
19
+ sys.exit(1)
20
+
21
+ TERMINAL_PATH = os.environ.get(
22
+ "MT5_TERMINAL_PATH",
23
+ r"C:\Program Files\MetaTrader 5\terminal64.exe",
24
+ )
25
+
26
+
27
+ def _serialize(obj: Any) -> Any:
28
+ if obj is None:
29
+ return None
30
+ if hasattr(obj, "_asdict"):
31
+ return _serialize(obj._asdict())
32
+ if isinstance(obj, dict):
33
+ return {k: _serialize(v) for k, v in obj.items()}
34
+ if isinstance(obj, (list, tuple)):
35
+ return [_serialize(v) for v in obj]
36
+ if hasattr(obj, "dtype"):
37
+ return _serialize_array(obj)
38
+ if hasattr(obj, "tolist"):
39
+ return _serialize(obj.tolist())
40
+ if isinstance(obj, bytes):
41
+ return obj.decode("utf-8", errors="replace")
42
+ if isinstance(obj, float):
43
+ if obj != obj:
44
+ return None
45
+ return obj
46
+ if hasattr(obj, "item"):
47
+ return obj.item()
48
+ return obj
49
+
50
+
51
+ def _serialize_array(arr: Any) -> list[dict]:
52
+ names = arr.dtype.names
53
+ result = []
54
+ for row in arr:
55
+ item = {}
56
+ for name in names:
57
+ val = row[name]
58
+ if isinstance(val, bytes):
59
+ val = val.decode("utf-8", errors="replace")
60
+ elif hasattr(val, "item"):
61
+ val = val.item()
62
+ item[name] = val
63
+ result.append(item)
64
+ return result
65
+
66
+
67
+ def handle(cmd: dict) -> dict:
68
+ action = cmd.get("action", "")
69
+
70
+ if action == "initialize":
71
+ path = cmd.get("path", TERMINAL_PATH)
72
+ success = mt5.initialize(path=path)
73
+ return {"success": success}
74
+
75
+ if action == "login":
76
+ success = mt5.login(int(cmd["login"]), cmd["password"], cmd.get("server", ""))
77
+ return {"success": success}
78
+
79
+ if action == "shutdown":
80
+ mt5.shutdown()
81
+ return {"success": True}
82
+
83
+ if action == "account_info":
84
+ info = mt5.account_info()
85
+ return _serialize(info) if info else {}
86
+
87
+ if action == "symbols_get":
88
+ symbol = cmd.get("symbol")
89
+ symbols = mt5.symbols_get(symbol) if symbol else mt5.symbols_get()
90
+ if symbols:
91
+ return {"symbols": _serialize(symbols)}
92
+ return {"symbols": []}
93
+
94
+ if action == "symbol_info":
95
+ info = mt5.symbol_info(cmd["symbol"])
96
+ return _serialize(info) if info else {}
97
+
98
+ if action == "symbol_select":
99
+ return {"success": mt5.symbol_select(cmd["symbol"], cmd.get("enable", True))}
100
+
101
+ if action == "tick_info":
102
+ tick = mt5.symbol_info_tick(cmd["symbol"])
103
+ if tick is None:
104
+ mt5.symbol_select(cmd["symbol"], True)
105
+ tick = mt5.symbol_info_tick(cmd["symbol"])
106
+ return _serialize(tick) if tick else {}
107
+
108
+ if action == "rates_get":
109
+ rates = mt5.copy_rates_from_pos(
110
+ cmd["symbol"],
111
+ cmd.get("timeframe", 1),
112
+ cmd.get("start_pos", 0),
113
+ cmd.get("count", 100),
114
+ )
115
+ return {"rates": _serialize(rates)}
116
+
117
+ if action == "copy_rates_from":
118
+ rates = mt5.copy_rates_from(
119
+ cmd["symbol"],
120
+ cmd["timeframe"],
121
+ cmd["date_from"],
122
+ cmd["count"],
123
+ )
124
+ return {"rates": _serialize(rates)}
125
+
126
+ if action == "copy_rates_range":
127
+ rates = mt5.copy_rates_range(
128
+ cmd["symbol"],
129
+ cmd["timeframe"],
130
+ cmd["date_from"],
131
+ cmd["date_to"],
132
+ )
133
+ return {"rates": _serialize(rates)}
134
+
135
+ if action == "order_send":
136
+ result = mt5.order_send(cmd["request"])
137
+ return _serialize(result) if result else {}
138
+
139
+ if action == "positions_get":
140
+ symbol = cmd.get("symbol", "")
141
+ positions = mt5.positions_get(symbol=symbol) if symbol else mt5.positions_get()
142
+ return {"positions": _serialize(positions)}
143
+
144
+ if action == "orders_get":
145
+ symbol = cmd.get("symbol", "")
146
+ orders = mt5.orders_get(symbol=symbol) if symbol else mt5.orders_get()
147
+ return {"orders": _serialize(orders)}
148
+
149
+ if action == "history_deals":
150
+ deals = mt5.history_deals_get(cmd.get("from", 0), cmd.get("to", 0))
151
+ return {"deals": _serialize(deals)}
152
+
153
+ if action == "history_orders":
154
+ orders = mt5.history_orders_get(cmd.get("from", 0), cmd.get("to", 0))
155
+ return {"orders": _serialize(orders)}
156
+
157
+ return {"error": f"Unknown action: {action}"}
158
+
159
+
160
+ def main():
161
+ for line in sys.stdin:
162
+ line = line.strip()
163
+ if not line:
164
+ continue
165
+ try:
166
+ result = handle(json.loads(line))
167
+ except Exception as e:
168
+ result = {"error": str(e), "traceback": traceback.format_exc()}
169
+ sys.stdout.write(json.dumps(result) + "\n")
170
+ sys.stdout.flush()
171
+
172
+
173
+ if __name__ == "__main__":
174
+ main()