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.
- package/package.json +1 -1
- package/src/agent/engine.js +85 -3
- package/src/agent/financetools.js +307 -0
- package/src/agent/scripts/mt5_bridge.py +381 -0
- package/src/agent/tools.js +10 -3
- package/src/main.js +410 -28
- package/src/mt5bridge.js +215 -0
- package/src/preload.js +11 -3
- package/src/renderer/index.html +67 -0
- package/src/renderer/renderer.js +493 -38
- package/src/renderer/style.css +328 -1
- package/src/agent/android.js +0 -265
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -10,6 +10,9 @@ const llm = require('./llm');
|
|
|
10
10
|
const { chatStream, chatStreamAuto, chatOnce } = llm;
|
|
11
11
|
const { chatCompletionsUrl, beastDir } = require('./config');
|
|
12
12
|
const tools = require('./tools');
|
|
13
|
+
let financetools = null;
|
|
14
|
+
try { financetools = require('./financetools'); } catch {}
|
|
15
|
+
const FINANCE_TOOL_SET = new Set(financetools ? financetools.NAMES : []);
|
|
13
16
|
const research = require('./research');
|
|
14
17
|
const agentdefs = require('./agentdefs');
|
|
15
18
|
const memory = require('./memory');
|
|
@@ -1137,6 +1140,13 @@ class Engine {
|
|
|
1137
1140
|
} else if (rec.t === 'bcws') {
|
|
1138
1141
|
/* Beast Code oturumu: hangi çalışma klasörüne bağlı */
|
|
1139
1142
|
session.bcWs = String(rec.ws || '');
|
|
1143
|
+
} else if (rec.t === 'fin') {
|
|
1144
|
+
/* Beast Finance oturumu: kalıcı izolasyon etiketi (finoff kapatır) */
|
|
1145
|
+
session.finance = true;
|
|
1146
|
+
session.financeTrader = !!rec.trader;
|
|
1147
|
+
} else if (rec.t === 'finoff') {
|
|
1148
|
+
session.finance = false;
|
|
1149
|
+
session.financeTrader = false;
|
|
1140
1150
|
} else if (rec.t === 'msg') {
|
|
1141
1151
|
delete rec.t;
|
|
1142
1152
|
session.messages.push(rec);
|
|
@@ -1273,10 +1283,36 @@ class Engine {
|
|
|
1273
1283
|
count: s.messages.length,
|
|
1274
1284
|
isBg,
|
|
1275
1285
|
isBotDm: !!s.isBotDm,
|
|
1286
|
+
finance: !!s.finance,
|
|
1276
1287
|
bgStatus: isBg ? (job ? job.status : null) : null,
|
|
1277
1288
|
};
|
|
1278
1289
|
}
|
|
1279
1290
|
|
|
1291
|
+
/* Beast Finance etiketi: oturumu finance sohbeti olarak KALICI işaretler
|
|
1292
|
+
(jsonl'e 'fin' satırı düşer — restart sonrası da hatırlanır) */
|
|
1293
|
+
markFinance(id, trader = false) {
|
|
1294
|
+
const s = this._load(String(id));
|
|
1295
|
+
s.finance = true;
|
|
1296
|
+
s.financeTrader = !!trader;
|
|
1297
|
+
try {
|
|
1298
|
+
fs.appendFileSync(this._file(s.id), JSON.stringify({ t: 'fin', trader: !!trader, at: nowIso() }) + '\n');
|
|
1299
|
+
} catch {}
|
|
1300
|
+
this.cache.set(String(s.id), s);
|
|
1301
|
+
return s;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/* Finance etiketini kaldır — oturum normal sohbete döner */
|
|
1305
|
+
unmarkFinance(id) {
|
|
1306
|
+
const s = this._load(String(id));
|
|
1307
|
+
s.finance = false;
|
|
1308
|
+
s.financeTrader = false;
|
|
1309
|
+
try {
|
|
1310
|
+
fs.appendFileSync(this._file(s.id), JSON.stringify({ t: 'finoff', at: nowIso() }) + '\n');
|
|
1311
|
+
} catch {}
|
|
1312
|
+
this.cache.set(String(s.id), s);
|
|
1313
|
+
return s;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1280
1316
|
/* #17 paralel ajan oturumları sol sohbet listesine KARIŞMAZ;
|
|
1281
1317
|
geçmişleri sağdaki Paralel Ajanlar panelinde izlenir */
|
|
1282
1318
|
listSessions() {
|
|
@@ -1599,6 +1635,46 @@ class Engine {
|
|
|
1599
1635
|
);
|
|
1600
1636
|
}
|
|
1601
1637
|
|
|
1638
|
+
/* Beast Finance: MT5 trading ajanı — chat copilot'ı VEYA otonom trader.
|
|
1639
|
+
Oturum alanları (main enjekte eder): financeTrader, financeAuto,
|
|
1640
|
+
financeSymbols, financeStrategy, financeLimits */
|
|
1641
|
+
buildFinanceSystem(session) {
|
|
1642
|
+
const nowD = new Date();
|
|
1643
|
+
const localDate = nowD.toLocaleDateString('tr-TR');
|
|
1644
|
+
const localTime = String(nowD.getHours()).padStart(2, '0') + ':00';
|
|
1645
|
+
const isTrader = !!(session && session.financeTrader);
|
|
1646
|
+
const auto = !!(session && session.financeAuto);
|
|
1647
|
+
const symbols = Array.isArray(session && session.financeSymbols) ? session.financeSymbols.join(', ') : String((session && session.financeSymbols) || '');
|
|
1648
|
+
const lim = (session && session.financeLimits) || {};
|
|
1649
|
+
const modeBlock = isTrader
|
|
1650
|
+
? auto
|
|
1651
|
+
? 'ROL: OTONOM TRADER ⚡ — tur tur çalışıyorsun. Her turda: veri çek → değerlendir → GEREKİRSE işlem aç/kapat/SL-TP güncelle → kısa rapor. Sistem seni turlar; sohbette konuşmazsın.'
|
|
1652
|
+
: 'ROL: GÖZLEMCİ ANALİST 🔍 — tur tur piyasa tararsın ama OTOMATİK İŞLEM KAPALI: mt5_trade/mt5_pending KULLANMA. Fırsatları net öneri olarak yaz (sembol, yön, giriş, SL, TP, sebep); sahibi onaylarsa açılır.'
|
|
1653
|
+
: 'ROL: TRADING CO-PİLOT 💬 — sahibinle sohbet ediyorsun. Veri isterse mt5_* araçlarıyla çek, analiz et; işlem istenirse limitler içinde uygula, istenmezse sadece öneri ver.';
|
|
1654
|
+
const proj = this._projectInstructions(session);
|
|
1655
|
+
return (
|
|
1656
|
+
'Sen BEAST FİNANS\u2019sın — bilgisayardaki MetaTrader 5 (MT5) terminaline köprüyle bağlı trading ajanı.\n' +
|
|
1657
|
+
'ORTAM: Beast Finance uygulamasındasın — solda sohbet geçmişi + izleyiciler, ortada bu sohbet, SAĞ panelde MT5 işlem panosu (hesap, pozisyonlar, piyasa, AKIŞ) var.\n' +
|
|
1658
|
+
'KÖPRÜ: MT5 terminaline Python stdio köprüsüyle bağlısın; panel 3 saniyede bir hesap/pozisyon/piyasa verisini tazeler. mt5_* araç çağrıların ve işlem hareketlerin (açılan/kapanan pozisyonlar) SAĞ paneldeki AKIŞ akışına canlı düşer — panelin orayı izlediğini bil.\n' +
|
|
1659
|
+
'GÖREV ALANI: piyasa verisi okuma (fiyat/hesap/pozisyon/geçmiş), teknik değerlendirme, pozisyon yönetimi (aç/kapat/SL/TP), risk disiplini ve kısa karar raporları. Kod yazma işi DEĞİLDİR.\n' +
|
|
1660
|
+
modeBlock + '\n' +
|
|
1661
|
+
'MT5 ARAÇLARI: mt5_status (bağlantı), mt5_account (hesap), mt5_market (canlı fiyat), mt5_positions (açık pozisyonlar), mt5_orders (bekleyen emirler), mt5_history (kapanan işlemler), mt5_trade (piyasa emri), mt5_close (kapat), mt5_modify (SL/TP), mt5_pending (bekleyen emir), mt5_cancel (emir iptal).\n' +
|
|
1662
|
+
'VERİ AKIŞI (her değerlendirmede): mt5_account + mt5_positions + mt5_market çağrılarını AYNI turda PARALEL ver; gerekiyorsa mt5_history ile son işlemleri gör.\n' +
|
|
1663
|
+
(symbols ? `İZLEME LİSTESİ: ${symbols}\n` : '') +
|
|
1664
|
+
(lim.maxLot ? `LİMİTLER: max lot ${lim.maxLot}` + (lim.maxPositions ? ` · max eşzamanlı pozisyon ${lim.maxPositions}` : '') + ' — bunları aşıp araç kullanma; sistem zaten reddeder.\n' : '') +
|
|
1665
|
+
'RİSK DİSİPLİNİ:\n' +
|
|
1666
|
+
'- SL\u2019siz pozisyon BIRAKMA: açtığın her işlemde mantıklı bir SL koy; SL\u2019siz kalan pozisyonu mt5_modify ile tamamla.\n' +
|
|
1667
|
+
'- Martingale/kademeli lot artışı YASAK; kaybı geri kovalama (revenge trade) YASAK.\n' +
|
|
1668
|
+
'- Emin olmadığında İŞLEM YOK — "BEKLE: <sebep>" yaz. Sık işlem > iyi işlem.\n' +
|
|
1669
|
+
'- Hesap kaldıracı ve serbest marja göre pozisyon boyutunu düşük tut; tek işlemde serbest marjın büyük kısmını riske atma.\n' +
|
|
1670
|
+
(session && session.financeStrategy ? `SAHİBİNİN STRATEJİ NOTU (önceliklidir):\n${session.financeStrategy}\n` : '') +
|
|
1671
|
+
'RAPOR DİSİPLİNİ: kısa ve sayısal — sembol, yön, lot, giriş, SL/TP, sebep tek satırda. Uzun fal açma; tablo/liste kullanabilirsin.\n' +
|
|
1672
|
+
`Yerel zaman: ${localDate} ${localTime} — piyasa saatlerine dikkat et (borsa/metal seansları kapalıysa spread genişler; hafta sonu FX kapalıdır).\n` +
|
|
1673
|
+
(proj ? '# PROJE TALİMATLARI\n' + proj + '\n' : '') +
|
|
1674
|
+
FORMAT_RULES
|
|
1675
|
+
);
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1602
1678
|
async buildSystem(queryText, session) {
|
|
1603
1679
|
/* BOT OTURUMU: non-admin bota bağlıysa global Beast hafızası HIÇ girmez —
|
|
1604
1680
|
o botun kendi SOUL/USER/MEMORY dosyaları kullanılır (tam izolasyon) */
|
|
@@ -3170,9 +3246,11 @@ class Engine {
|
|
|
3170
3246
|
? this.buildBgSystem(session)
|
|
3171
3247
|
: session.studio
|
|
3172
3248
|
? this.buildStudioSystem(session)
|
|
3173
|
-
: session.
|
|
3174
|
-
? this.
|
|
3175
|
-
:
|
|
3249
|
+
: session.finance
|
|
3250
|
+
? this.buildFinanceSystem(session)
|
|
3251
|
+
: session.bcCode
|
|
3252
|
+
? this.buildBcSystem(session)
|
|
3253
|
+
: await this.buildSystem(promptText, session);
|
|
3176
3254
|
// Granül izin: oturumun yetki seviyesine göre araç seti daraltılır.
|
|
3177
3255
|
// Çoklu izin (ör. web+read) seçiliyse kümeler BİRLEŞİR — hepsinin araçları açık olur.
|
|
3178
3256
|
const perms = this.sessionPermFor(session.id);
|
|
@@ -3182,6 +3260,10 @@ class Engine {
|
|
|
3182
3260
|
for (const p of perms) for (const t of PERM_TOOL_SETS[p] || []) allowedSet.add(t);
|
|
3183
3261
|
}
|
|
3184
3262
|
let activeTools = allowedSet ? toolsList.filter((t) => allowedSet.has(t.function.name)) : toolsList;
|
|
3263
|
+
/* Beast Finance: mt5_* araçları YALNIZ finance oturumlarında görünür */
|
|
3264
|
+
if (FINANCE_TOOL_SET.size && !(session && session.finance)) {
|
|
3265
|
+
activeTools = activeTools.filter((t) => !FINANCE_TOOL_SET.has(t.function.name));
|
|
3266
|
+
}
|
|
3185
3267
|
/* bot skill kısıtı: bota verilen yetkiye göre araç seti daraltılır */
|
|
3186
3268
|
const toolLimit = this.sessionTools.get(String(session.id));
|
|
3187
3269
|
if (toolLimit) activeTools = activeTools.filter((t) => toolLimit.has(t.function.name));
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* Beast Finance — MT5 ajan araçları.
|
|
4
|
+
Handler'lar main süreçteki mt5bridge singleton'ına bağlanır; işlem açan
|
|
5
|
+
araçlar settings.finance.allowTrading / maxLot / maxPositions ile sınırlanır.
|
|
6
|
+
main tarafı setConfig/setNotify ile ayar ve bildirim kancasını enjekte eder. */
|
|
7
|
+
|
|
8
|
+
const mt5 = require('../mt5bridge');
|
|
9
|
+
|
|
10
|
+
const NAMES = [
|
|
11
|
+
'mt5_status',
|
|
12
|
+
'mt5_account',
|
|
13
|
+
'mt5_market',
|
|
14
|
+
'mt5_positions',
|
|
15
|
+
'mt5_orders',
|
|
16
|
+
'mt5_history',
|
|
17
|
+
'mt5_trade',
|
|
18
|
+
'mt5_close',
|
|
19
|
+
'mt5_modify',
|
|
20
|
+
'mt5_pending',
|
|
21
|
+
'mt5_cancel',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
let getCfg = () => ({ allowTrading: false, maxLot: 0.1, maxPositions: 3, symbols: [] });
|
|
25
|
+
let notify = () => {};
|
|
26
|
+
|
|
27
|
+
function setConfig(fn) {
|
|
28
|
+
if (typeof fn === 'function') getCfg = fn;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function setNotify(fn) {
|
|
32
|
+
if (typeof fn === 'function') notify = fn;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function clampLot(v, maxLot) {
|
|
36
|
+
const n = Number(v);
|
|
37
|
+
if (!isFinite(n) || n <= 0) return 0;
|
|
38
|
+
return Math.min(n, maxLot);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function bcall(method, params, timeoutMs) {
|
|
42
|
+
const r = await mt5.call(method, params, timeoutMs);
|
|
43
|
+
if (!r || r.ok !== true) throw new Error((r && r.error) || 'MT5 hatası');
|
|
44
|
+
return r.data;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function noteTrade(kind, data) {
|
|
48
|
+
try { notify({ kind, data, at: Date.now() }); } catch {}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function notConnected() {
|
|
52
|
+
return { ok: false, error: 'MT5 köprüsü bağlı değil — terminal açık mı? (panelde ⟳ ile yeniden bağlan)' };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const definitions = NAMES.map((name) => {
|
|
56
|
+
const specs = {
|
|
57
|
+
mt5_status: {
|
|
58
|
+
description:
|
|
59
|
+
'MT5 (MetaTrader 5) köprü durumunu döndürür: köprü çalışıyor mu, terminale bağlı mı, terminal/hesap özeti. Her finance turunda VEYA bağlantı sorunlarında ilk çağrı.',
|
|
60
|
+
parameters: { type: 'object', properties: {} },
|
|
61
|
+
},
|
|
62
|
+
mt5_account: {
|
|
63
|
+
description:
|
|
64
|
+
'MT5 hesap özeti: bakiye, özkaynak (equity), yüzen kâr/zarar, marj, serbest marj, marj seviyesi, kaldıraç, para birimi, hesap no/sunucu.',
|
|
65
|
+
parameters: { type: 'object', properties: {} },
|
|
66
|
+
},
|
|
67
|
+
mt5_market: {
|
|
68
|
+
description:
|
|
69
|
+
'Verilen semboller için canlı fiyat: bid/ask, spread, digit, point. symbols verilmezse izleme listesi (watchlist) kullanılır. Örn: ["EURUSD","XAUUSD"].',
|
|
70
|
+
parameters: {
|
|
71
|
+
type: 'object',
|
|
72
|
+
properties: {
|
|
73
|
+
symbols: { type: 'array', items: { type: 'string' }, description: 'Sembol listesi (boşsa watchlist)' },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
mt5_positions: {
|
|
78
|
+
description: 'Açık pozisyonlar: ticket, sembol, yön, lot, açılış fiyatı, güncel fiyat, SL/TP, yüzen kâr/zarar, açılış zamanı.',
|
|
79
|
+
parameters: { type: 'object', properties: {} },
|
|
80
|
+
},
|
|
81
|
+
mt5_orders: {
|
|
82
|
+
description: 'Bekleyen emirler (pending orders): ticket, sembol, tip (limit/stop), lot, fiyat, SL/TP.',
|
|
83
|
+
parameters: { type: 'object', properties: {} },
|
|
84
|
+
},
|
|
85
|
+
mt5_history: {
|
|
86
|
+
description: 'Kapanmış işlemler (deals) özeti: son N günde kapanan pozisyonlar, kâr/zarar, komisyon, swap. days=1 → son 24 saat.',
|
|
87
|
+
parameters: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
properties: { days: { type: 'number', description: 'Kaç gün geriye bakılsın (varsayılan 1, en fazla 90)' } },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
mt5_trade: {
|
|
93
|
+
description:
|
|
94
|
+
'PİYASA EMRİ AÇAR: mt5_trade {symbol, side:"buy"|"sell", volume, sl?, tp?, comment?}. Lot limiti ve max pozisyon sayısı sistem tarafından zorlanır. Otomatik işlem anahtarı kapalıysa reddedilir. SL/TP vermek ŞIDDETLİ önerilir.',
|
|
95
|
+
parameters: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: {
|
|
98
|
+
symbol: { type: 'string', description: 'Örn: EURUSD' },
|
|
99
|
+
side: { type: 'string', enum: ['buy', 'sell'] },
|
|
100
|
+
volume: { type: 'number', description: 'Lot (max lot sınırına tabi)' },
|
|
101
|
+
sl: { type: 'number', description: 'Stop loss fiyatı (0 = yok)' },
|
|
102
|
+
tp: { type: 'number', description: 'Take profit fiyatı (0 = yok)' },
|
|
103
|
+
comment: { type: 'string', description: 'Kısa işlem notu' },
|
|
104
|
+
},
|
|
105
|
+
required: ['symbol', 'side', 'volume'],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
mt5_close: {
|
|
109
|
+
description: 'Açık pozisyonu kapatır: mt5_close {ticket, volume?}. volume verilirse kısmi kapatır. Risk yönetimi için kullanılır.',
|
|
110
|
+
parameters: {
|
|
111
|
+
type: 'object',
|
|
112
|
+
properties: {
|
|
113
|
+
ticket: { type: 'number', description: 'Pozisyon ticket no' },
|
|
114
|
+
volume: { type: 'number', description: 'Kapatılacak lot (boşsa tamamı)' },
|
|
115
|
+
},
|
|
116
|
+
required: ['ticket'],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
mt5_modify: {
|
|
120
|
+
description: 'Pozisyonun SL/TP seviyelerini günceller: mt5_modify {ticket, sl, tp}. SL’siz pozisyon bırakmamak için kullan.',
|
|
121
|
+
parameters: {
|
|
122
|
+
type: 'object',
|
|
123
|
+
properties: {
|
|
124
|
+
ticket: { type: 'number' },
|
|
125
|
+
sl: { type: 'number', description: 'Yeni SL (0 = kaldır)' },
|
|
126
|
+
tp: { type: 'number', description: 'Yeni TP (0 = kaldır)' },
|
|
127
|
+
},
|
|
128
|
+
required: ['ticket'],
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
mt5_pending: {
|
|
132
|
+
description:
|
|
133
|
+
'BEKLEYEN EMİR koyar: mt5_pending {symbol, type:"buy_limit"|"sell_limit"|"buy_stop"|"sell_stop", volume, price, sl?, tp?}. Otomatik işlem anahtarına tabidir.',
|
|
134
|
+
parameters: {
|
|
135
|
+
type: 'object',
|
|
136
|
+
properties: {
|
|
137
|
+
symbol: { type: 'string' },
|
|
138
|
+
type: { type: 'string', enum: ['buy_limit', 'sell_limit', 'buy_stop', 'sell_stop'] },
|
|
139
|
+
volume: { type: 'number' },
|
|
140
|
+
price: { type: 'number' },
|
|
141
|
+
sl: { type: 'number' },
|
|
142
|
+
tp: { type: 'number' },
|
|
143
|
+
},
|
|
144
|
+
required: ['symbol', 'type', 'volume', 'price'],
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
mt5_cancel: {
|
|
148
|
+
description: 'Bekleyen emri iptal eder: mt5_cancel {ticket}.',
|
|
149
|
+
parameters: {
|
|
150
|
+
type: 'object',
|
|
151
|
+
properties: { ticket: { type: 'number' } },
|
|
152
|
+
required: ['ticket'],
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
const s = specs[name];
|
|
157
|
+
return { type: 'function', function: { name, ...s } };
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const handlers = {
|
|
161
|
+
async mt5_status() {
|
|
162
|
+
const st = mt5.status();
|
|
163
|
+
return { ok: true, ...st };
|
|
164
|
+
},
|
|
165
|
+
async mt5_account() {
|
|
166
|
+
if (!mt5.running) return notConnected();
|
|
167
|
+
const data = await bcall('account', {}, 8000);
|
|
168
|
+
return { ok: true, ...data };
|
|
169
|
+
},
|
|
170
|
+
async mt5_market(args) {
|
|
171
|
+
if (!mt5.running) return notConnected();
|
|
172
|
+
const cfg = getCfg();
|
|
173
|
+
let syms = Array.isArray(args.symbols) ? args.symbols.map((s) => String(s).trim()).filter(Boolean) : [];
|
|
174
|
+
if (!syms.length) syms = Array.isArray(cfg.symbols) ? cfg.symbols : [];
|
|
175
|
+
const data = await bcall('symbols', { symbols: syms }, 10000);
|
|
176
|
+
return { ok: true, symbols: data && data.symbols ? data.symbols : [] };
|
|
177
|
+
},
|
|
178
|
+
async mt5_positions() {
|
|
179
|
+
if (!mt5.running) return notConnected();
|
|
180
|
+
const data = await bcall('positions', {}, 8000);
|
|
181
|
+
const list = (data && data.positions) || [];
|
|
182
|
+
return { ok: true, count: list.length, positions: list };
|
|
183
|
+
},
|
|
184
|
+
async mt5_orders() {
|
|
185
|
+
if (!mt5.running) return notConnected();
|
|
186
|
+
const data = await bcall('orders', {}, 8000);
|
|
187
|
+
const list = (data && data.orders) || [];
|
|
188
|
+
return { ok: true, count: list.length, orders: list };
|
|
189
|
+
},
|
|
190
|
+
async mt5_history(args) {
|
|
191
|
+
if (!mt5.running) return notConnected();
|
|
192
|
+
let days = Number(args.days);
|
|
193
|
+
if (!isFinite(days) || days <= 0) days = 1;
|
|
194
|
+
days = Math.min(days, 90);
|
|
195
|
+
const data = await bcall('deals', { days }, 12000);
|
|
196
|
+
const deals = (data && data.deals) || [];
|
|
197
|
+
/* hızlı özet: giriş/çıkış eşleşmesi yapmadan net realized P/L */
|
|
198
|
+
let profit = 0;
|
|
199
|
+
let wins = 0;
|
|
200
|
+
let losses = 0;
|
|
201
|
+
for (const dl of deals) {
|
|
202
|
+
const p = Number(dl.profit) || 0;
|
|
203
|
+
if (String(dl.entry) === '1' || dl.entry === 1 || p !== 0) {
|
|
204
|
+
profit += p + (Number(dl.swap) || 0) + (Number(dl.commission) || 0);
|
|
205
|
+
if (p > 0) wins++;
|
|
206
|
+
else if (p < 0) losses++;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
ok: true,
|
|
211
|
+
days,
|
|
212
|
+
count: deals.length,
|
|
213
|
+
netProfit: Math.round(profit * 100) / 100,
|
|
214
|
+
wins,
|
|
215
|
+
losses,
|
|
216
|
+
deals: deals.slice(-60),
|
|
217
|
+
};
|
|
218
|
+
},
|
|
219
|
+
async mt5_trade(args) {
|
|
220
|
+
const cfg = getCfg();
|
|
221
|
+
if (!cfg.allowTrading) {
|
|
222
|
+
return { ok: false, error: 'Otomatik işlem KAPALI — analiz/öneri modundasın. İşlem önerisini yaz, açma.' };
|
|
223
|
+
}
|
|
224
|
+
if (!mt5.running) return notConnected();
|
|
225
|
+
const side = String(args.side || '').toLowerCase();
|
|
226
|
+
if (side !== 'buy' && side !== 'sell') return { ok: false, error: "side 'buy' veya 'sell' olmalı" };
|
|
227
|
+
const symbol = String(args.symbol || '').trim().toUpperCase();
|
|
228
|
+
if (!symbol) return { ok: false, error: 'symbol gerekli' };
|
|
229
|
+
const maxLot = Number(cfg.maxLot) || 0.1;
|
|
230
|
+
const vol = clampLot(args.volume, maxLot);
|
|
231
|
+
if (!vol) return { ok: false, error: 'volume gerekli (pozitif sayı)' };
|
|
232
|
+
const pos = await bcall('positions', {}, 8000);
|
|
233
|
+
const maxPos = Number(cfg.maxPositions) || 3;
|
|
234
|
+
const openCount = ((pos && pos.positions) || []).length;
|
|
235
|
+
if (openCount >= maxPos) {
|
|
236
|
+
return { ok: false, error: `max eşzamanlı pozisyon dolu (${openCount}/${maxPos}) — önce bir pozisyon kapat` };
|
|
237
|
+
}
|
|
238
|
+
const data = await bcall('market', {
|
|
239
|
+
symbol,
|
|
240
|
+
side,
|
|
241
|
+
volume: vol,
|
|
242
|
+
sl: Number(args.sl) || 0,
|
|
243
|
+
tp: Number(args.tp) || 0,
|
|
244
|
+
deviation: 20,
|
|
245
|
+
comment: String(args.comment || 'Beast').slice(0, 26),
|
|
246
|
+
}, 20000);
|
|
247
|
+
noteTrade('trade', { symbol, side, volume: vol, result: data && data.result });
|
|
248
|
+
return { ok: true, opened: { symbol, side, volume: vol, sl: Number(args.sl) || 0, tp: Number(args.tp) || 0 }, result: data && data.result };
|
|
249
|
+
},
|
|
250
|
+
async mt5_close(args) {
|
|
251
|
+
const cfg = getCfg();
|
|
252
|
+
if (!mt5.running) return notConnected();
|
|
253
|
+
const ticket = Number(args.ticket);
|
|
254
|
+
if (!ticket) return { ok: false, error: 'ticket gerekli' };
|
|
255
|
+
let vol = Number(args.volume) || 0;
|
|
256
|
+
if (vol > 0) {
|
|
257
|
+
const maxLot = Number(cfg.maxLot) || 0.1;
|
|
258
|
+
if (vol > maxLot) return { ok: false, error: `kısmi kapatma lotu limiti aşıyor (max ${maxLot})` };
|
|
259
|
+
}
|
|
260
|
+
const data = await bcall('close', { ticket, volume: vol || 0 }, 20000);
|
|
261
|
+
noteTrade('close', { ticket, volume: vol || 'all', result: data && data.result });
|
|
262
|
+
return { ok: true, result: data && data.result };
|
|
263
|
+
},
|
|
264
|
+
async mt5_modify(args) {
|
|
265
|
+
if (!mt5.running) return notConnected();
|
|
266
|
+
const ticket = Number(args.ticket);
|
|
267
|
+
if (!ticket) return { ok: false, error: 'ticket gerekli' };
|
|
268
|
+
const data = await bcall('modify', {
|
|
269
|
+
ticket,
|
|
270
|
+
sl: Number(args.sl) || 0,
|
|
271
|
+
tp: Number(args.tp) || 0,
|
|
272
|
+
}, 20000);
|
|
273
|
+
noteTrade('modify', { ticket, sl: Number(args.sl) || 0, tp: Number(args.tp) || 0 });
|
|
274
|
+
return { ok: true, result: data && data.result };
|
|
275
|
+
},
|
|
276
|
+
async mt5_pending(args) {
|
|
277
|
+
const cfg = getCfg();
|
|
278
|
+
if (!cfg.allowTrading) {
|
|
279
|
+
return { ok: false, error: 'Otomatik işlem KAPALI — bekleyen emir de açılamaz. Öneriyi yaz.' };
|
|
280
|
+
}
|
|
281
|
+
if (!mt5.running) return notConnected();
|
|
282
|
+
const maxLot = Number(cfg.maxLot) || 0.1;
|
|
283
|
+
const vol = clampLot(args.volume, maxLot);
|
|
284
|
+
if (!vol) return { ok: false, error: 'volume gerekli' };
|
|
285
|
+
const data = await bcall('pending', {
|
|
286
|
+
symbol: String(args.symbol || '').trim().toUpperCase(),
|
|
287
|
+
type: String(args.type || '').toLowerCase(),
|
|
288
|
+
volume: vol,
|
|
289
|
+
price: Number(args.price) || 0,
|
|
290
|
+
sl: Number(args.sl) || 0,
|
|
291
|
+
tp: Number(args.tp) || 0,
|
|
292
|
+
comment: String(args.comment || 'Beast').slice(0, 26),
|
|
293
|
+
}, 20000);
|
|
294
|
+
noteTrade('pending', { symbol: args.symbol, type: args.type, volume: vol });
|
|
295
|
+
return { ok: true, result: data && data.result };
|
|
296
|
+
},
|
|
297
|
+
async mt5_cancel(args) {
|
|
298
|
+
if (!mt5.running) return notConnected();
|
|
299
|
+
const ticket = Number(args.ticket);
|
|
300
|
+
if (!ticket) return { ok: false, error: 'ticket gerekli' };
|
|
301
|
+
const data = await bcall('cancel', { ticket }, 20000);
|
|
302
|
+
noteTrade('cancel', { ticket });
|
|
303
|
+
return { ok: true, result: data && data.result };
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
module.exports = { definitions, handlers, NAMES, setConfig, setNotify };
|