beast-agent 2.14.0 → 2.15.0

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.
@@ -0,0 +1,381 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Beast Finance - MetaTrader 5 stdio koprusu.
4
+
5
+ Bilgisayardaki calisan MT5 terminaline MetaTrader5 python paketiyle baglanir.
6
+ Stdin'den JSON satirlari okur ({id, method, params}), her istegin yanitini
7
+ tek JSON satiri olarak stdout'a yazar ({id, ok, data|error}).
8
+ Baglanti olaylarini event satirlari ile bildirir ({event: 'bridge', ...}).
9
+
10
+ Gereksinim: pip install MetaTrader5 (yalnizca Windows + MT5 terminal kurulu)
11
+ """
12
+ import sys
13
+ import json
14
+ import time
15
+ import argparse
16
+
17
+ try:
18
+ import MetaTrader5 as mt5
19
+ except Exception:
20
+ mt5 = None
21
+
22
+
23
+ def out(obj):
24
+ try:
25
+ sys.stdout.write(json.dumps(obj, ensure_ascii=False, default=str) + "\n")
26
+ sys.stdout.flush()
27
+ except Exception:
28
+ pass
29
+
30
+
31
+ def d(obj):
32
+ """namedtuple/dict/list -> sade dict agaci (JSON'a hazir)."""
33
+ if obj is None:
34
+ return None
35
+ if hasattr(obj, "_asdict"):
36
+ return {k: d(v) for k, v in obj._asdict().items()}
37
+ if isinstance(obj, dict):
38
+ return {k: d(v) for k, v in obj.items()}
39
+ if isinstance(obj, (list, tuple)):
40
+ return [d(x) for x in obj]
41
+ return obj
42
+
43
+
44
+ TERMINAL_PATH = ""
45
+ INIT_TRIED = False
46
+
47
+
48
+ def ensure_init():
49
+ """Terminal baglantisi yoksa (yeniden) init dener; True/False doner."""
50
+ global INIT_TRIED
51
+ if mt5 is None:
52
+ return False
53
+ try:
54
+ if mt5.terminal_info() is not None:
55
+ return True
56
+ except Exception:
57
+ pass
58
+ INIT_TRIED = True
59
+ if TERMINAL_PATH:
60
+ return bool(mt5.initialize(path=TERMINAL_PATH))
61
+ return bool(mt5.initialize())
62
+
63
+
64
+ def need():
65
+ if mt5 is None:
66
+ raise RuntimeError("MetaTrader5 python paketi kurulu degil (pip install MetaTrader5)")
67
+ if not ensure_init():
68
+ last = ""
69
+ try:
70
+ last = str(mt5.last_error())
71
+ except Exception:
72
+ pass
73
+ raise RuntimeError("MT5 terminaline baglanilamadi (terminal acik mi?) " + last)
74
+
75
+
76
+ # ---------------- yontemler ----------------
77
+
78
+ def h_ping(p):
79
+ return {"pong": True, "mt5": mt5 is not None, "connected": bool(ensure_init()) if mt5 else False}
80
+
81
+
82
+ def h_account(p):
83
+ need()
84
+ return {"account": d(mt5.account_info())}
85
+
86
+
87
+ def h_terminal(p):
88
+ need()
89
+ return {"terminal": d(mt5.terminal_info())}
90
+
91
+
92
+ def h_symbols(p):
93
+ need()
94
+ syms = p.get("symbols") or []
95
+ res = []
96
+ for s in syms:
97
+ s = str(s).strip().upper()
98
+ if not s:
99
+ continue
100
+ info = mt5.symbol_info(s)
101
+ if info is None:
102
+ # sembol Market Watch'ta yoksa secmeyi dene
103
+ mt5.symbol_select(s, True)
104
+ info = mt5.symbol_info(s)
105
+ if info is None:
106
+ res.append({"symbol": s, "missing": True})
107
+ continue
108
+ tick = mt5.symbol_info_tick(s)
109
+ row = d(info)
110
+ row.update(d(tick) or {})
111
+ row["symbol"] = s
112
+ res.append(row)
113
+ return {"symbols": res}
114
+
115
+
116
+ def h_positions(p):
117
+ need()
118
+ return {"positions": d(mt5.positions_get()) or []}
119
+
120
+
121
+ def h_orders(p):
122
+ need()
123
+ return {"orders": d(mt5.orders_get()) or []}
124
+
125
+
126
+ def h_deals(p):
127
+ need()
128
+ days = p.get("days")
129
+ try:
130
+ days = float(days)
131
+ except Exception:
132
+ days = 1.0
133
+ days = max(0.01, min(days, 90.0))
134
+ to = time.time()
135
+ frm = to - days * 86400.0
136
+ deals = mt5.history_deals_get(frm, to) or []
137
+ rows = d(deals)
138
+ # sadece pozisyon giris/cikislari: entry 0 giris 1 cikis (DEAL_ENTRY_OUT)
139
+ return {"deals": rows[-500:], "count": len(rows)}
140
+
141
+
142
+ def _send_request(req):
143
+ res = mt5.order_send(req)
144
+ if res is None:
145
+ raise RuntimeError("order_send None dondu " + str(mt5.last_error()))
146
+ r = d(res)
147
+ # bazi brokerlar IOC desteklemez: FOK, sonra RETURN ile birer kez tekrar dene
148
+ if int(r.get("retcode") or 0) == 10030 and "type_filling" in req:
149
+ for filling in (mt5.ORDER_FILLING_FOK, mt5.ORDER_FILLING_RETURN):
150
+ if req["type_filling"] == filling:
151
+ continue
152
+ req2 = dict(req)
153
+ req2["type_filling"] = filling
154
+ res = mt5.order_send(req2)
155
+ if res is None:
156
+ continue
157
+ r = d(res)
158
+ if int(r.get("retcode") or 0) in (10008, 10009):
159
+ break
160
+ if int(r.get("retcode") or 0) not in (10008, 10009): # PLACED / DONE
161
+ raise RuntimeError("islem reddedildi retcode=%s comment=%s" % (r.get("retcode"), r.get("comment")))
162
+ return r
163
+
164
+
165
+ def h_market(p):
166
+ need()
167
+ symbol = str(p.get("symbol") or "").strip().upper()
168
+ side = str(p.get("side") or "").lower()
169
+ volume = float(p.get("volume") or 0)
170
+ if not symbol or side not in ("buy", "sell") or volume <= 0:
171
+ raise RuntimeError("symbol/side/volume zorunlu")
172
+ mt5.symbol_select(symbol, True)
173
+ tick = mt5.symbol_info_tick(symbol)
174
+ if tick is None:
175
+ raise RuntimeError("tick alinamadi: " + symbol)
176
+ price = tick.ask if side == "buy" else tick.bid
177
+ order_type = mt5.ORDER_TYPE_BUY if side == "buy" else mt5.ORDER_TYPE_SELL
178
+ req = {
179
+ "action": mt5.TRADE_ACTION_DEAL,
180
+ "symbol": symbol,
181
+ "volume": volume,
182
+ "type": order_type,
183
+ "price": price,
184
+ "deviation": int(p.get("deviation") or 20),
185
+ "magic": int(p.get("magic") or 20260908),
186
+ "comment": str(p.get("comment") or "Beast")[:26],
187
+ "type_time": mt5.ORDER_TIME_GTC,
188
+ "type_filling": mt5.ORDER_FILLING_IOC,
189
+ }
190
+ if p.get("sl"):
191
+ req["sl"] = float(p.get("sl"))
192
+ if p.get("tp"):
193
+ req["tp"] = float(p.get("tp"))
194
+ return {"result": _send_request(req), "price": price}
195
+
196
+
197
+ def h_close(p):
198
+ need()
199
+ ticket = int(p.get("ticket") or 0)
200
+ if not ticket:
201
+ raise RuntimeError("ticket zorunlu")
202
+ pos = mt5.positions_get(ticket=ticket)
203
+ if not pos:
204
+ raise RuntimeError("pozisyon bulunamadi: %s" % ticket)
205
+ pos = pos[0]
206
+ volume = float(p.get("volume") or pos.volume)
207
+ volume = min(volume, pos.volume)
208
+ symbol = pos.symbol
209
+ mt5.symbol_select(symbol, True)
210
+ tick = mt5.symbol_info_tick(symbol)
211
+ if tick is None:
212
+ raise RuntimeError("tick alinamadi: " + symbol)
213
+ is_buy = int(pos.type) == int(mt5.POSITION_TYPE_BUY)
214
+ req = {
215
+ "action": mt5.TRADE_ACTION_DEAL,
216
+ "symbol": symbol,
217
+ "volume": volume,
218
+ "type": mt5.ORDER_TYPE_SELL if is_buy else mt5.ORDER_TYPE_BUY,
219
+ "position": ticket,
220
+ "price": tick.bid if is_buy else tick.ask,
221
+ "deviation": int(p.get("deviation") or 20),
222
+ "magic": int(pos.magic or 0),
223
+ "comment": "Beast close",
224
+ "type_time": mt5.ORDER_TIME_GTC,
225
+ "type_filling": mt5.ORDER_FILLING_IOC,
226
+ }
227
+ return {"result": _send_request(req)}
228
+
229
+
230
+ def h_modify(p):
231
+ need()
232
+ ticket = int(p.get("ticket") or 0)
233
+ if not ticket:
234
+ raise RuntimeError("ticket zorunlu")
235
+ pos = mt5.positions_get(ticket=ticket)
236
+ if not pos:
237
+ raise RuntimeError("pozisyon bulunamadi: %s" % ticket)
238
+ pos = pos[0]
239
+ req = {
240
+ "action": mt5.TRADE_ACTION_SLTP,
241
+ "symbol": pos.symbol,
242
+ "position": ticket,
243
+ "sl": float(p.get("sl") or 0.0),
244
+ "tp": float(p.get("tp") or 0.0),
245
+ }
246
+ return {"result": _send_request(req)}
247
+
248
+
249
+ def h_pending(p):
250
+ need()
251
+ symbol = str(p.get("symbol") or "").strip().upper()
252
+ ptype = str(p.get("type") or "").lower()
253
+ volume = float(p.get("volume") or 0)
254
+ price = float(p.get("price") or 0)
255
+ if not symbol or volume <= 0 or price <= 0:
256
+ raise RuntimeError("symbol/volume/price zorunlu")
257
+ tmap = {
258
+ "buy_limit": mt5.ORDER_TYPE_BUY_LIMIT,
259
+ "sell_limit": mt5.ORDER_TYPE_SELL_LIMIT,
260
+ "buy_stop": mt5.ORDER_TYPE_BUY_STOP,
261
+ "sell_stop": mt5.ORDER_TYPE_SELL_STOP,
262
+ }
263
+ if ptype not in tmap:
264
+ raise RuntimeError("type: buy_limit|sell_limit|buy_stop|sell_stop")
265
+ mt5.symbol_select(symbol, True)
266
+ req = {
267
+ "action": mt5.TRADE_ACTION_PENDING,
268
+ "symbol": symbol,
269
+ "volume": volume,
270
+ "type": tmap[ptype],
271
+ "price": price,
272
+ "deviation": int(p.get("deviation") or 20),
273
+ "magic": int(p.get("magic") or 20260908),
274
+ "comment": str(p.get("comment") or "Beast")[:26],
275
+ "type_time": mt5.ORDER_TIME_GTC,
276
+ "type_filling": mt5.ORDER_FILLING_IOC,
277
+ }
278
+ if p.get("sl"):
279
+ req["sl"] = float(p.get("sl"))
280
+ if p.get("tp"):
281
+ req["tp"] = float(p.get("tp"))
282
+ return {"result": _send_request(req)}
283
+
284
+
285
+ def h_cancel(p):
286
+ need()
287
+ ticket = int(p.get("ticket") or 0)
288
+ if not ticket:
289
+ raise RuntimeError("ticket zorunlu")
290
+ req = {"action": mt5.TRADE_ACTION_REMOVE, "order": ticket}
291
+ return {"result": _send_request(req)}
292
+
293
+
294
+ def h_all_symbols(p):
295
+ """Terminaldeki tum semboller (sembol secici icin). filter: '*XAU*' gibi."""
296
+ need()
297
+ flt = str(p.get("filter") or "").strip()
298
+ if flt:
299
+ rows = mt5.symbols_get(group="*" + flt + "*") or []
300
+ else:
301
+ rows = mt5.symbols_get() or []
302
+ cap = 8000
303
+ out = []
304
+ for r in rows[:cap]:
305
+ out.append({
306
+ "name": getattr(r, "name", "") or "",
307
+ "desc": getattr(r, "description", "") or "",
308
+ "visible": bool(getattr(r, "visible", False)),
309
+ })
310
+ return {"symbols": out, "count": len(rows), "truncated": len(rows) > cap}
311
+
312
+
313
+ HANDLERS = {
314
+ "ping": h_ping,
315
+ "account": h_account,
316
+ "terminal": h_terminal,
317
+ "symbols": h_symbols,
318
+ "all_symbols": h_all_symbols,
319
+ "positions": h_positions,
320
+ "orders": h_orders,
321
+ "deals": h_deals,
322
+ "market": h_market,
323
+ "close": h_close,
324
+ "modify": h_modify,
325
+ "pending": h_pending,
326
+ "cancel": h_cancel,
327
+ }
328
+
329
+
330
+ def main():
331
+ global TERMINAL_PATH
332
+ ap = argparse.ArgumentParser()
333
+ ap.add_argument("--terminal", default="")
334
+ args, _ = ap.parse_known_args()
335
+ TERMINAL_PATH = args.terminal
336
+
337
+ for line in sys.stdin:
338
+ line = line.strip()
339
+ if not line:
340
+ continue
341
+ try:
342
+ req = json.loads(line)
343
+ except Exception as e:
344
+ out({"id": None, "ok": False, "error": "bozuk json: %s" % e})
345
+ continue
346
+ rid = req.get("id")
347
+ method = str(req.get("method") or "")
348
+ params = req.get("params") or {}
349
+ fn = HANDLERS.get(method)
350
+ if fn is None:
351
+ out({"id": rid, "ok": False, "error": "bilinmeyen yontem: " + method})
352
+ continue
353
+ try:
354
+ data = fn(params)
355
+ out({"id": rid, "ok": True, "data": data})
356
+ except Exception as e:
357
+ out({"id": rid, "ok": False, "error": str(e)})
358
+
359
+
360
+ if __name__ == "__main__":
361
+ if mt5 is None:
362
+ out({"event": "bridge", "connected": False, "error": "MetaTrader5 python paketi kurulu degil (pip install MetaTrader5)"})
363
+ else:
364
+ ok = False
365
+ err = ""
366
+ try:
367
+ if TERMINAL_PATH:
368
+ ok = bool(mt5.initialize(path=TERMINAL_PATH))
369
+ else:
370
+ ok = bool(mt5.initialize())
371
+ if not ok:
372
+ err = str(mt5.last_error())
373
+ except Exception as e:
374
+ err = str(e)
375
+ acc = None
376
+ term = None
377
+ if ok:
378
+ acc = d(mt5.account_info())
379
+ term = d(mt5.terminal_info())
380
+ out({"event": "bridge", "connected": ok, "error": "" if ok else err, "account": acc, "terminal": term})
381
+ main()
@@ -11,6 +11,8 @@ const searxng = require('./searxng');
11
11
  const gittools = require('./gittools');
12
12
  const repomap = require('./repomap');
13
13
  const xlsxtools = require('./xlsxtools');
14
+ let financetools = null;
15
+ try { financetools = require('./financetools'); } catch {}
14
16
 
15
17
  const MAX_CMD_OUTPUT = 16000;
16
18
  const MAX_FILE_CHARS = 200000;
@@ -1468,10 +1470,11 @@ function readCacheDrop(abs) {
1468
1470
  }
1469
1471
 
1470
1472
  const definitions = [
1471
- /* yerleşik modül araçları: git, repo haritası, excel */
1473
+ /* yerleşik modül araçları: git, repo haritası, excel, finance (MT5) */
1472
1474
  ...gittools.definitions,
1473
1475
  ...repomap.definitions,
1474
1476
  ...xlsxtools.definitions,
1477
+ ...(financetools ? financetools.definitions : []),
1475
1478
  {
1476
1479
  type: 'function',
1477
1480
  function: {
@@ -1736,8 +1739,12 @@ const definitions = [
1736
1739
 
1737
1740
  async function exec(name, args, ctx) {
1738
1741
  const cwd = ctx.cwd;
1739
- /* modül araçları: gittools / repomap / xlsxtools — kendi handler'larında */
1740
- const modHandler = gittools.handlers[name] || repomap.handlers[name] || xlsxtools.handlers[name];
1742
+ /* modül araçları: gittools / repomap / xlsxtools / financetools — kendi handler'larında */
1743
+ const modHandler =
1744
+ gittools.handlers[name] ||
1745
+ repomap.handlers[name] ||
1746
+ xlsxtools.handlers[name] ||
1747
+ (financetools && financetools.handlers[name]);
1741
1748
  if (modHandler) {
1742
1749
  try {
1743
1750
  return JSON.stringify(await modHandler(args, ctx));