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 +277 -0
- mt5_mac/agent.py +174 -0
- mt5_mac/backend.py +276 -0
- mt5_mac/bootstrap.py +174 -0
- mt5_mac/types.py +272 -0
- mt5_mac-0.1.0.dist-info/METADATA +369 -0
- mt5_mac-0.1.0.dist-info/RECORD +10 -0
- mt5_mac-0.1.0.dist-info/WHEEL +5 -0
- mt5_mac-0.1.0.dist-info/licenses/LICENSE +21 -0
- mt5_mac-0.1.0.dist-info/top_level.txt +1 -0
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()
|