beast-agent 2.13.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.
- package/package.json +1 -1
- package/src/agent/engine.js +154 -3
- package/src/agent/financetools.js +307 -0
- package/src/agent/llm.js +3 -1
- package/src/agent/scripts/mt5_bridge.py +381 -0
- package/src/agent/tools.js +10 -3
- package/src/main.js +466 -0
- package/src/mt5bridge.js +215 -0
- package/src/preload.js +11 -0
- package/src/renderer/i18n.js +3 -1
- package/src/renderer/index.html +76 -1
- package/src/renderer/renderer.js +562 -4
- package/src/renderer/style.css +393 -1
|
@@ -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()
|
package/src/agent/tools.js
CHANGED
|
@@ -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 =
|
|
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));
|