beast-agent 2.62.0 → 2.62.2
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/bin/beast-agent.js +17 -12
- package/package.json +1 -1
- package/scripts/fix-electron.js +31 -3
- package/src/agent/defaulttools/mt5_shot/run.js +31 -23
- package/src/agent/defaulttools/mt5_shot/tool.json +1 -1
- package/src/agent/engine.js +156 -61
- package/src/agent/mt5/BeastFinance.mq5 +80 -23
- package/src/main.js +151 -1
- package/src/renderer/renderer.js +2 -0
package/bin/beast-agent.js
CHANGED
|
@@ -107,23 +107,28 @@ if (process.argv[2] === 'searxng') {
|
|
|
107
107
|
|
|
108
108
|
/* detached launcher + self-healing electron:
|
|
109
109
|
on fresh machines the electron binary download during npm postinstall
|
|
110
|
-
may have silently failed → it is repaired automatically here.
|
|
110
|
+
may have silently failed OR been truncated → it is repaired automatically here.
|
|
111
|
+
Varlık kontrolü YETMEZ: kesik dosya "var" görünür ama spawn EFTYPE verir —
|
|
112
|
+
fix-electron.status binary'yi `--version` ile çalıştırarak doğrular. */
|
|
111
113
|
function launchDetached(extraArgs) {
|
|
112
114
|
let electron = null;
|
|
113
115
|
try { electron = require('electron'); } catch {}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
116
|
+
let fix = null;
|
|
117
|
+
try { fix = require('../scripts/fix-electron'); } catch {}
|
|
118
|
+
const usable = () => {
|
|
119
|
+
if (typeof electron !== 'string') return false;
|
|
120
|
+
if (fix && typeof fix.status === 'function') return !!fix.status().ok;
|
|
121
|
+
try { return fs.existsSync(electron); } catch { return false; }
|
|
122
|
+
};
|
|
123
|
+
if (!usable() && fix) {
|
|
124
|
+
console.log('\u27F3 electron runtime is missing or corrupted \u2014 repairing automatically\u2026');
|
|
125
|
+
const r = fix.repair({ quiet: false });
|
|
126
|
+
if (r.ok && !r.skipped) console.log('\u2713 electron repaired');
|
|
127
|
+
if (r.ok) {
|
|
128
|
+
try { electron = require('electron'); } catch {}
|
|
124
129
|
}
|
|
125
130
|
}
|
|
126
|
-
if (
|
|
131
|
+
if (!usable()) {
|
|
127
132
|
console.log('\n\u2717 Electron runtime could not be installed.');
|
|
128
133
|
console.log(' Manual fix \u2014 run these 2 commands:');
|
|
129
134
|
console.log(' npm config set ignore-scripts false');
|
package/package.json
CHANGED
package/scripts/fix-electron.js
CHANGED
|
@@ -23,15 +23,43 @@ function electronDir() {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/* binary ÇALIŞIYOR mu? — kesik/bozuk indirmede dosya VAR görünür ama
|
|
27
|
+
CreateProcess "bad EXE format" (EFTYPE) verir; varlık kontrolü yetmez.
|
|
28
|
+
`electron --version` ile gerçekten çalıştığını doğrularız (kısa timeout).
|
|
29
|
+
Sonuç (yol+boyut+mtime) önbelleklenir — aynı süreçte tekrar spawn edilmez. */
|
|
30
|
+
let verifyCache = null;
|
|
31
|
+
function exeWorks(exeAbs) {
|
|
32
|
+
let key = '';
|
|
33
|
+
try {
|
|
34
|
+
const st = fs.statSync(exeAbs);
|
|
35
|
+
key = exeAbs + '|' + st.size + '|' + st.mtimeMs;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (verifyCache && verifyCache.key === key) return verifyCache.ok;
|
|
40
|
+
let ok = false;
|
|
41
|
+
try {
|
|
42
|
+
const r = spawnSync(exeAbs, ['--version'], { timeout: 20000, windowsHide: true, stdio: 'ignore' });
|
|
43
|
+
ok = !r.error && r.status === 0;
|
|
44
|
+
} catch {
|
|
45
|
+
ok = false;
|
|
46
|
+
}
|
|
47
|
+
verifyCache = { key, ok };
|
|
48
|
+
return ok;
|
|
49
|
+
}
|
|
50
|
+
|
|
26
51
|
/* binary sağlam mı? electron/index.js'in beklentisiyle aynı kontrol:
|
|
27
|
-
path.txt + dist/<exe> varlığı */
|
|
52
|
+
path.txt + dist/<exe> varlığı — ARTIK + exe'nin gerçekten çalışması */
|
|
28
53
|
function status() {
|
|
29
54
|
const dir = electronDir();
|
|
30
55
|
if (!dir) return { ok: false, missing: true };
|
|
31
56
|
const exe = process.platform === 'win32' ? 'electron.exe' : 'electron';
|
|
32
|
-
const
|
|
57
|
+
const exeAbs = path.join(dir, 'dist', exe);
|
|
58
|
+
const distOk = fs.existsSync(exeAbs);
|
|
33
59
|
const txtOk = fs.existsSync(path.join(dir, 'path.txt'));
|
|
34
|
-
|
|
60
|
+
if (!(distOk && txtOk)) return { ok: false, dir };
|
|
61
|
+
if (!exeWorks(exeAbs)) return { ok: false, dir, broken: true };
|
|
62
|
+
return { ok: true, dir };
|
|
35
63
|
}
|
|
36
64
|
|
|
37
65
|
/* platforma göre dist içindeki çalıştırılabilirin göreli yolu (electron/index.js ile aynı) */
|
|
@@ -20,8 +20,10 @@
|
|
|
20
20
|
Notlar:
|
|
21
21
|
- beast_cmd.json'a {id, ts, cmd, params} yazılır (UTF-16 BOM; EA FILE_UNICODE okur).
|
|
22
22
|
- Ack dosyası POLL edilir; PNG MQL5\Files altında belirene kadar beklenir.
|
|
23
|
-
- EA v1.
|
|
23
|
+
- EA v1.22 şu şablonu işler: {"id":"..","ts":..,"cmd":"shot","params":{file,width,height,symbol?,timeframe?,template?}}
|
|
24
24
|
symbol/timeframe aktif grafikten farklıysa EA ChartOpen+ChartSetSymbolPeriod ile geçici grafik açar.
|
|
25
|
+
EA ack'i PNG üretimini BEKLEMEDEN yazar (ChartScreenShot asenkron; geçici grafik kapatma
|
|
26
|
+
bir sonraki timer turuna ertelenir) — ack = "komut işlendi", PNG ayrıca beklenir.
|
|
25
27
|
- PNG, send_file'ın engellediği %APPDATA%\beast altından çıkarılıp kullanıcı klasörüne
|
|
26
28
|
(varsayılan <home>\beast_shots) kopyalanır; dönen "path" doğrudan send_file'a verilebilir.
|
|
27
29
|
- GÖRSEL ENJEKSİYONU: başarılı sonuçta PNG data URL olarak __injectImage alanına gömülür;
|
|
@@ -39,10 +41,12 @@ const LOCK_STALE_MS = 45000;
|
|
|
39
41
|
const HARD_MS = 80000;
|
|
40
42
|
const DEFAULT_TIMEOUT_SEC = 20;
|
|
41
43
|
|
|
42
|
-
/* ---------------- temel yardımcılar ----------------
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
/* ---------------- temel yardımcılar ----------------
|
|
45
|
+
ÖNEMLİ: beklemeler ASENKRON olmalı — Atomics.wait tabanlı senkron uyku ana
|
|
46
|
+
thread'i kilitler, watchdog ZAMANLAYICISI ateşlenemez ve araç 80 sn yerine
|
|
47
|
+
customtools'un 90 sn kill'ine takılırdı ("tool zaman aşımı" + ack kaybı). */
|
|
48
|
+
function sleep(ms) {
|
|
49
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
let CTX = { cmdId: null, filesDir: null, started: Date.now() };
|
|
@@ -74,7 +78,7 @@ function fail(code, msg, extra) {
|
|
|
74
78
|
/* ---------------- kilit (eşzamanlı çağrılar ack'i ezmesin) ---------------- */
|
|
75
79
|
function releaseLock() { try { fs.unlinkSync(LOCK_FILE); } catch (e) {} }
|
|
76
80
|
|
|
77
|
-
function acquireLock(waitMs) {
|
|
81
|
+
async function acquireLock(waitMs) {
|
|
78
82
|
const deadline = Date.now() + waitMs;
|
|
79
83
|
for (;;) {
|
|
80
84
|
try { fs.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, at: Date.now() }), { flag: 'wx' }); return true; }
|
|
@@ -83,7 +87,7 @@ function acquireLock(waitMs) {
|
|
|
83
87
|
try { st = fs.statSync(LOCK_FILE); } catch (x) {
|
|
84
88
|
// kilit kayboldu/kilitli kaldı: kısa bekle, sonsuz döngüye girme
|
|
85
89
|
if (Date.now() >= deadline) return false;
|
|
86
|
-
|
|
90
|
+
await sleep(200);
|
|
87
91
|
continue;
|
|
88
92
|
}
|
|
89
93
|
if (Date.now() - st.mtimeMs > LOCK_STALE_MS) { // ölü kilit: devral
|
|
@@ -91,7 +95,7 @@ function acquireLock(waitMs) {
|
|
|
91
95
|
continue;
|
|
92
96
|
}
|
|
93
97
|
if (Date.now() >= deadline) return false;
|
|
94
|
-
|
|
98
|
+
await sleep(400);
|
|
95
99
|
}
|
|
96
100
|
}
|
|
97
101
|
}
|
|
@@ -154,7 +158,7 @@ function tfName(p) {
|
|
|
154
158
|
}
|
|
155
159
|
|
|
156
160
|
/* ack yoksa: kanal sağlam mı? kısa bir ping ile ayırt et */
|
|
157
|
-
function eaPing(filesDir, budgetMs) {
|
|
161
|
+
async function eaPing(filesDir, budgetMs) {
|
|
158
162
|
const id = 'P' + Date.now().toString(36);
|
|
159
163
|
const cmdFile = path.join(filesDir, 'beast_cmd.json');
|
|
160
164
|
const ackFile = path.join(filesDir, 'beast_cmd_ack.json');
|
|
@@ -163,7 +167,7 @@ function eaPing(filesDir, budgetMs) {
|
|
|
163
167
|
catch (e) { return { ok: false, error: 'ping yazılamadı: ' + e.message }; }
|
|
164
168
|
const dl = Date.now() + budgetMs;
|
|
165
169
|
while (Date.now() < dl) {
|
|
166
|
-
|
|
170
|
+
await sleep(300);
|
|
167
171
|
const a = readJson(ackFile);
|
|
168
172
|
if (a && a.id === id) return { ok: true, ack: a, ms: Date.now() - t0 };
|
|
169
173
|
}
|
|
@@ -231,7 +235,7 @@ function sanitizeFile(name, fallback) {
|
|
|
231
235
|
}
|
|
232
236
|
|
|
233
237
|
/* tek kare: EA'ya shot komutu yaz → ack bekle → PNG'yi bekle */
|
|
234
|
-
function captureOne(o) {
|
|
238
|
+
async function captureOne(o) {
|
|
235
239
|
CTX.cmdId = o.id;
|
|
236
240
|
const payload = { id: o.id, ts: Date.now(), cmd: 'shot', params: o.params };
|
|
237
241
|
try { if (fs.existsSync(o.ackFile)) fs.unlinkSync(o.ackFile); } catch (e) {}
|
|
@@ -242,7 +246,7 @@ function captureOne(o) {
|
|
|
242
246
|
let ack = null, cmdConsumed = false;
|
|
243
247
|
const ackDeadline = Date.now() + o.timeoutSec * 1000;
|
|
244
248
|
while (Date.now() < ackDeadline) {
|
|
245
|
-
|
|
249
|
+
await sleep(250);
|
|
246
250
|
const a = readJson(o.ackFile);
|
|
247
251
|
if (a && a.id === o.id) { ack = a; break; }
|
|
248
252
|
try {
|
|
@@ -265,7 +269,7 @@ function captureOne(o) {
|
|
|
265
269
|
extra.diagnostic = cmdConsumed
|
|
266
270
|
? 'EA komutu aldı ama ack yazmadı (shot işleyicisi hata verdi / PNG üretimi bloke)'
|
|
267
271
|
: 'EA komutu hiç almadı (zamanlayıcı durmuş ya da komut dosyası okunamadı)';
|
|
268
|
-
extra.ping = eaPing(o.filesDir, 6000);
|
|
272
|
+
extra.ping = await eaPing(o.filesDir, 6000);
|
|
269
273
|
}
|
|
270
274
|
return { ok: false, err: 'ea_no_ack', error: 'EA ' + o.timeoutSec + ' sn içinde ack vermedi (id=' + o.id + ', heartbeat ' +
|
|
271
275
|
(extra.heartbeat_age_sec === null ? '?' : extra.heartbeat_age_sec) + ' sn, cmd tüketildi=' + cmdConsumed + ')', extra };
|
|
@@ -279,11 +283,11 @@ function captureOne(o) {
|
|
|
279
283
|
const shotFile = (ack.result && ack.result.file) ? path.basename(String(ack.result.file)) : o.params.file;
|
|
280
284
|
const src = path.join(o.filesDir, shotFile);
|
|
281
285
|
let size = 0;
|
|
282
|
-
const pngDeadline = Math.max(ackDeadline, Date.now() +
|
|
286
|
+
const pngDeadline = Math.max(ackDeadline, Date.now() + 10000);
|
|
283
287
|
for (;;) {
|
|
284
288
|
try { if (fs.existsSync(src)) { size = fs.statSync(src).size; if (size > 1500) break; } } catch (e) {}
|
|
285
289
|
if (Date.now() >= pngDeadline) break;
|
|
286
|
-
|
|
290
|
+
await sleep(250);
|
|
287
291
|
}
|
|
288
292
|
if (size <= 1500) {
|
|
289
293
|
return { ok: false, err: 'png_yok', error: 'PNG oluşmadı ya da çok küçük (' + size + ' byte): ' + shotFile, extra: { ack, expected_file: shotFile, bytes: size } };
|
|
@@ -353,7 +357,7 @@ async function runPanels(plan, filesDir, cmdFile, ackFile, beatFile, timeoutSec,
|
|
|
353
357
|
if (symArg) params.symbol = symArg;
|
|
354
358
|
if (ARGS.template !== undefined) params.template = String(ARGS.template);
|
|
355
359
|
cmdIds.push(id);
|
|
356
|
-
const r = captureOne({ id, file: params.file, params, filesDir, cmdFile, ackFile, beatFile, timeoutSec: perAck, diag });
|
|
360
|
+
const r = await captureOne({ id, file: params.file, params, filesDir, cmdFile, ackFile, beatFile, timeoutSec: perAck, diag });
|
|
357
361
|
if (!r.ok) {
|
|
358
362
|
return {
|
|
359
363
|
err: r.err,
|
|
@@ -452,7 +456,7 @@ async function run() {
|
|
|
452
456
|
{ heartbeat_age_sec: beatAge });
|
|
453
457
|
}
|
|
454
458
|
|
|
455
|
-
const locked = acquireLock(Math.min(15000, timeoutSec * 500));
|
|
459
|
+
const locked = await acquireLock(Math.min(15000, timeoutSec * 500));
|
|
456
460
|
if (!locked) console.error('[mt5_shot] uyarı: kilit alınamadı, yine de devam ediliyor');
|
|
457
461
|
|
|
458
462
|
/* ÇOKLU KARE: symbols:["GOLD","EURUSD"] ve/veya timeframes:["M1","M15"]
|
|
@@ -476,7 +480,7 @@ async function run() {
|
|
|
476
480
|
params.width = Number(ARGS.width) > 0 ? Math.min(10000, Math.round(Number(ARGS.width))) : 1600;
|
|
477
481
|
params.height = Number(ARGS.height) > 0 ? Math.min(10000, Math.round(Number(ARGS.height))) : 900;
|
|
478
482
|
|
|
479
|
-
const cap = captureOne({ id, file, params, filesDir, cmdFile, ackFile, beatFile, timeoutSec, diag });
|
|
483
|
+
const cap = await captureOne({ id, file, params, filesDir, cmdFile, ackFile, beatFile, timeoutSec, diag });
|
|
480
484
|
if (!cap.ok) return fail(cap.err, cap.error, cap.extra);
|
|
481
485
|
|
|
482
486
|
const cp = copyOut(cap.src, cap.shotFile, ARGS.outdir);
|
|
@@ -576,11 +580,15 @@ function loadArgs(rawStdin) {
|
|
|
576
580
|
const st = fs.fstatSync(0);
|
|
577
581
|
f0 = { isFIFO: st.isFIFO(), isCharacterDevice: st.isCharacterDevice(), isFile: st.isFile(), size: st.size };
|
|
578
582
|
} catch (e) { f0 = { error: String(e && e.message) }; }
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
583
|
+
/* Parent komut satırı teşhisi pahalıdır (PowerShell ~1-5 sn) — yalnız
|
|
584
|
+
BEAST_TOOL_DIAG=1 iken koşar; normal çekimlerde sn cinsinden hız kazandırır. */
|
|
585
|
+
if (process.env.BEAST_TOOL_DIAG === '1') {
|
|
586
|
+
try {
|
|
587
|
+
const { execSync } = require('child_process');
|
|
588
|
+
pcmd = execSync('powershell -NoProfile -Command "(Get-CimInstance Win32_Process -Filter \\"ProcessId=' +
|
|
589
|
+
process.ppid + '\\").CommandLine"', { timeout: 5000 }).toString().trim();
|
|
590
|
+
} catch (e) { pcmd = 'okunamadı: ' + String(e && e.message).slice(0, 120); }
|
|
591
|
+
}
|
|
584
592
|
const envJson = {};
|
|
585
593
|
for (const k of Object.keys(process.env)) {
|
|
586
594
|
if (/key|token|secret|pass|auth|credential/i.test(k)) continue;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mt5_shot",
|
|
3
|
-
"defaultsVersion":
|
|
3
|
+
"defaultsVersion": 6,
|
|
4
4
|
"description": "MT5 grafiğinin GERÇEK ekran görüntüsünü alır ve GÖRSELİ ÇAĞIRAN AJANA GÖSTERİR: MT5\\MQL5\\Files\\beast_cmd.json üzerinden BeastFinance EA'ya 'shot' komutu yazar, EA ChartScreenShot ile PNG üretir; PNG hem tool klasörüne hem <kullanıcı>\\beast_shots altına kopyalanır (dönen path send_file'a verilebilir) hem de data URL olarak sonuca gömülür — engine bunu sonraki turda ajana görsel olarak enjekte eder (embed:false kapatır). ÇOKLU SEMBOL: symbols:[\"GOLD\",\"EURUSD\",\"BTCUSD\"] (2-4 sembol) verirsen her sembolden bir kare çekilip TEK PNG'de birleştirilir (izleme listesinin tamamı TEK görselle gönderilir); timeframes ile birlikte verilirse sembol × periyot ızgarası kurulur (en fazla 6 kare). ÇOKLU ZAMAN DİLİMİ: timeframes:[\"M1\",\"M15\"] verirsen aynı sembolün periyotları SIRAYLA çekilip TEK PNG'de birleştirilir (layout:\"h\" soldan sağa — varsayılan; \"v\" üstten alta; 2-3 periyot) ve her panelin SAĞ ÜST köşesine BÜYÜK periyot etiketi (üstünde küçük sembol) çizilir — solda/sağda hangi sembol/periyot olduğu görüntüden bakışta okunur. Aktif grafik istenen sembol/periyot ile aynıysa ondan çekilir (Beast panosu + seviye çizgileri görünür), değilse EA geçici grafik açar (ChartOpen/ChartSetSymbolPeriod + BeastShot.tpl açık tema); çoklu sembolde her sembol için sırayla. Çekim öncesi grafiğe sağ boşluk payı (chart shift %12) uygulanır — SON MUM sağ kenara yapışmaz; aktif grafikte önceki ayar çekimden sonra geri yazılır. Komut id/ts ile yazılır, ack poll edilir ve PNG MQL5\\Files'ta belirene kadar beklenir; hata halinde gövde şu kodu taşır: err = no_terminal|ea_offline|cmd_write_fail|ea_no_ack|ea_error|png_yok|timeout|stitch_fail (+ cmdId, filesDir, diagnostic, ping). Grafik analizi/raporu ve ekip paylaşımı için birincil screenshot aracıdır.",
|
|
5
5
|
"parameters": {
|
|
6
6
|
"type": "object",
|
package/src/agent/engine.js
CHANGED
|
@@ -2858,37 +2858,45 @@ class Engine {
|
|
|
2858
2858
|
}
|
|
2859
2859
|
}
|
|
2860
2860
|
|
|
2861
|
-
/* SÜREKLİ ajan DM INBOX (bkz. flushPendingReports
|
|
2862
|
-
DM'ler yeni tur AÇMAZ; sıradaki
|
|
2863
|
-
eklenir — ajan-ajan sonsuz DM ping-pong'u
|
|
2861
|
+
/* SÜREKLİ ajan DM INBOX + BOT DM SEPETİ (bkz. flushPendingReports ve
|
|
2862
|
+
_agentDmDeliver): tur aralığında biriken DM'ler yeni tur AÇMAZ; sıradaki
|
|
2863
|
+
mesajın önüne tek blok olarak eklenir — ajan-ajan sonsuz DM ping-pong'u,
|
|
2864
|
+
tur fırtınası ve bağlam şişmesi engellenir. */
|
|
2864
2865
|
try {
|
|
2866
|
+
const pendingDm = [];
|
|
2865
2867
|
const jobRec = this._bgJobs && this._bgJobs.get(String(sessionId));
|
|
2866
2868
|
if (jobRec && Array.isArray(jobRec.dmInbox) && jobRec.dmInbox.length) {
|
|
2867
|
-
const
|
|
2868
|
-
|
|
2869
|
+
for (const x of jobRec.dmInbox.splice(0)) pendingDm.push(x);
|
|
2870
|
+
}
|
|
2871
|
+
if (this._agentDmBacklog && this._agentDmBacklog.size) {
|
|
2872
|
+
const q = this._agentDmBacklog.get(String(sessionId));
|
|
2873
|
+
if (q && q.length) for (const x of q.splice(0)) pendingDm.push(x);
|
|
2874
|
+
}
|
|
2875
|
+
if (pendingDm.length) {
|
|
2876
|
+
const boxText = pendingDm
|
|
2869
2877
|
.map((x) => (typeof x === 'string' ? x : String((x && x.text) || '')))
|
|
2870
2878
|
.join('\n\n')
|
|
2871
2879
|
.slice(0, 6000);
|
|
2872
2880
|
/* görselli DM'ler: son 2 görsel bu turun mesajına vision olarak eklenir */
|
|
2873
|
-
const
|
|
2881
|
+
const dmImgs = pendingDm
|
|
2874
2882
|
.map((x) => (x && typeof x === 'object' && /^data:image\//i.test(String(x.image || '')) ? String(x.image) : ''))
|
|
2875
2883
|
.filter(Boolean)
|
|
2876
2884
|
.slice(-2);
|
|
2877
2885
|
const prefix =
|
|
2878
|
-
`[BEKLEYEN AJAN DM'LERİ — ${
|
|
2886
|
+
`[BEKLEYEN AJAN DM'LERİ — ${pendingDm.length} mesaj]\n${boxText}\n` +
|
|
2879
2887
|
`(Tur aralığında biriken DM'ler. Bu turda değerlendir; yalnızca aksiyon/cevap GEREKİYORSA agent_dm ile TEK kısa cevap ver — teşekkür/onay/ack mesajı YAZMA.)\n\n`;
|
|
2880
2888
|
if (typeof msg.content === 'string') {
|
|
2881
|
-
msg.content =
|
|
2889
|
+
msg.content = dmImgs.length
|
|
2882
2890
|
? [
|
|
2883
2891
|
{ type: 'text', text: prefix + msg.content },
|
|
2884
|
-
...
|
|
2892
|
+
...dmImgs.map((u) => ({ type: 'image_url', image_url: { url: u } })),
|
|
2885
2893
|
]
|
|
2886
2894
|
: prefix + msg.content;
|
|
2887
2895
|
} else if (Array.isArray(msg.content)) {
|
|
2888
2896
|
if (msg.content[0] && typeof msg.content[0].text === 'string') {
|
|
2889
2897
|
msg.content[0] = { ...msg.content[0], text: prefix + msg.content[0].text };
|
|
2890
2898
|
}
|
|
2891
|
-
for (const u of
|
|
2899
|
+
for (const u of dmImgs) msg.content.push({ type: 'image_url', image_url: { url: u } });
|
|
2892
2900
|
}
|
|
2893
2901
|
}
|
|
2894
2902
|
} catch {}
|
|
@@ -3843,14 +3851,11 @@ class Engine {
|
|
|
3843
3851
|
);
|
|
3844
3852
|
if (skillDef) toolsList = [...toolsList, skillDef];
|
|
3845
3853
|
}
|
|
3846
|
-
/* AJAN DM: koşan
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
!session.isBotDm &&
|
|
3852
|
-
(session.bgJob || session.finance || !session.botId || session.botId === 'beast')
|
|
3853
|
-
) {
|
|
3854
|
+
/* AJAN DM: koşan ajanlara (arka plan işleri + finance), ana sohbete VE
|
|
3855
|
+
bot sohbetlerine verilir — botlar birbirine iş atar/cevap verir
|
|
3856
|
+
(ör. Beast → Tool botu; Tool botu da sonucu DM ile geri yazar).
|
|
3857
|
+
Yalnız botlar arası DM pair oturumunda kapalı (döngü koruması). */
|
|
3858
|
+
if (session && !session.isBotDm) {
|
|
3854
3859
|
toolsList = [...toolsList, AGENT_DM_DEF];
|
|
3855
3860
|
}
|
|
3856
3861
|
/* TOOL İSTEĞİ (cephane): tool botu hariç TÜM oturumlar — müşteri bot
|
|
@@ -3920,6 +3925,15 @@ class Engine {
|
|
|
3920
3925
|
const allow = new Set(adef.tools);
|
|
3921
3926
|
activeTools = activeTools.filter((t) => allow.has(t.function.name));
|
|
3922
3927
|
}
|
|
3928
|
+
/* KİŞİSEL/VARSAYILAN TOOLLAR (tool__*) CEPHANE KURALI: bot skill
|
|
3929
|
+
whitelist'i (toolLimit) ve ajan beyaz listesi bunları düşürmesin —
|
|
3930
|
+
Tool botu onardığı aracı kendi oturumunda test edebilsin, diğer
|
|
3931
|
+
botlar da kullanabilsin. Kısıtlı izinli (chat/web) ve Beast Code
|
|
3932
|
+
oturumları hariç; tam izinli ('all') her oturuma açılır. */
|
|
3933
|
+
if (!bcAgent && perms.includes('all')) {
|
|
3934
|
+
const have = new Set(activeTools.map((t) => t.function.name));
|
|
3935
|
+
activeTools = [...activeTools, ...customtools.definitions().filter((t) => !have.has(t.function.name))];
|
|
3936
|
+
}
|
|
3923
3937
|
if (perms.length === 1 && perms[0] === 'chat') {
|
|
3924
3938
|
system +=
|
|
3925
3939
|
'\n\n# KISITLI MOD\nTüm araçların (komut, dosya, web, tarayıcı, hafıza) kapalı. Sadece yazarak cevap ver. ' +
|
|
@@ -4929,6 +4943,7 @@ const skills = require('./skills');
|
|
|
4929
4943
|
const s = this._load(sid);
|
|
4930
4944
|
if (s && !s.isBotDm) {
|
|
4931
4945
|
if (String(s.botId || '') !== want) this.setSessionBot(sid, want);
|
|
4946
|
+
this._toolSessionId = sid;
|
|
4932
4947
|
return sid;
|
|
4933
4948
|
}
|
|
4934
4949
|
}
|
|
@@ -4937,6 +4952,7 @@ const skills = require('./skills');
|
|
|
4937
4952
|
const sid = String(created.id);
|
|
4938
4953
|
this.setSessionBot(sid, want);
|
|
4939
4954
|
this.setSessionPerm(sid, target.perm || 'all');
|
|
4955
|
+
this._toolSessionId = sid;
|
|
4940
4956
|
try {
|
|
4941
4957
|
fs.writeFileSync(this._toolSessionFile, JSON.stringify({ id: sid, botId: want, at: nowIso() }));
|
|
4942
4958
|
} catch {}
|
|
@@ -6013,11 +6029,11 @@ const AGENT_DM_DEF = {
|
|
|
6013
6029
|
function: {
|
|
6014
6030
|
name: 'agent_dm',
|
|
6015
6031
|
description:
|
|
6016
|
-
'Send a short DM to another
|
|
6032
|
+
'Send a short DM to another agent or BOT (parallel agents, finance agents, bot chats like "Tool") to coordinate: share findings, ask status, warn about risk, hand off work, and MAKE JOINT DECISIONS. `to` = target session id, a bot name/id (e.g. "Tool", "Trader"), or a keyword from the agent title (e.g. "GOLD"). `topic` = short subject label — replies to the same topic stay in the SAME conversation thread, so ALWAYS reuse the topic you were DMed with when replying. `group` = optional group-chat name (e.g. "GOLD EKIP"): creates or reuses a group conversation, adds the target agent as a member, and your message is delivered to EVERY member — use groups when a decision needs multiple agents. `image` shares a chart/screen as an attachment the receiving agent REALLY sees and the AJAN DM panel shows: true = your last screenshot (fresh capture if none); a file path (e.g. the tool__mt5_shot "path") or a data URL = that exact image.',
|
|
6017
6033
|
parameters: {
|
|
6018
6034
|
type: 'object',
|
|
6019
6035
|
properties: {
|
|
6020
|
-
to: { type: 'string', description: 'Target
|
|
6036
|
+
to: { type: 'string', description: 'Target session id, bot name/id (e.g. "Tool") or a title keyword' },
|
|
6021
6037
|
topic: { type: 'string', description: 'Short subject label for the conversation thread, e.g. "GOLD pozisyon riski"' },
|
|
6022
6038
|
message: { type: 'string', description: 'Short message (1-3 sentences)' },
|
|
6023
6039
|
group: { type: 'string', description: 'Optional group-chat name — send to ALL members of that group (creates it on first use, adds the target agent)' },
|
|
@@ -6889,7 +6905,7 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
|
|
|
6889
6905
|
fromJob && fromJob.title
|
|
6890
6906
|
? String(fromJob.title)
|
|
6891
6907
|
: fromSess
|
|
6892
|
-
?
|
|
6908
|
+
? this._agentDmSessionTitle(fromSess)
|
|
6893
6909
|
: 'Ajan';
|
|
6894
6910
|
/* hedef çöz: oturum id YA DA başlık anahtar kelimesi (koşan ajanlar +
|
|
6895
6911
|
ANA OTURUM FALLBACK: chat oturumları this.cache'te yaşar — finance
|
|
@@ -6909,19 +6925,12 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
|
|
|
6909
6925
|
}
|
|
6910
6926
|
if (!target) {
|
|
6911
6927
|
const q = to.toLowerCase();
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
const hay = [s.bgTitle, s.title, s.code, s.code ? 'beast · ' + s.code : ''].map((x) =>
|
|
6919
|
-
String(x || '').toLowerCase()
|
|
6920
|
-
);
|
|
6921
|
-
if (hay.some((x) => x && x.includes(q))) {
|
|
6922
|
-
target = sid;
|
|
6923
|
-
break;
|
|
6924
|
-
}
|
|
6928
|
+
target = this._agentDmFindSession(q, fromSid);
|
|
6929
|
+
if (!target) {
|
|
6930
|
+
/* CACHE SOĞUK OLABİLİR (uygulama yeni açıldı): oturum dosyalarını
|
|
6931
|
+
yükleyip bir kez daha ara — bot oturumu diskte olsa da bulunur. */
|
|
6932
|
+
try { this.listSessions(); } catch {}
|
|
6933
|
+
target = this._agentDmFindSession(q, fromSid);
|
|
6925
6934
|
}
|
|
6926
6935
|
}
|
|
6927
6936
|
if (String(target) === String(fromSid)) return { ok: false, error: 'kendine DM atılamaz' };
|
|
@@ -6943,7 +6952,7 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
|
|
|
6943
6952
|
const toTitle = target
|
|
6944
6953
|
? toJob
|
|
6945
6954
|
? String((toJob && toJob.title) || 'Ajan')
|
|
6946
|
-
:
|
|
6955
|
+
: this._agentDmSessionTitle(mainChatSess, 'Beast')
|
|
6947
6956
|
: '';
|
|
6948
6957
|
/* konu etiketi: aynı konudaki DM'ler panelde AYNI oturumda toplanır */
|
|
6949
6958
|
const topic = String((args && args.topic) || '').replace(/\s+/g, ' ').trim().slice(0, 60) || '(genel)';
|
|
@@ -7010,19 +7019,10 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
|
|
|
7010
7019
|
/* canlı event TEK SEFER — üye başına emit edilirse panelde çoğalır */
|
|
7011
7020
|
emitSafe(this, target || g.members[0], { type: 'agent-dm', ...dm });
|
|
7012
7021
|
for (const m of g.members) {
|
|
7013
|
-
if (m === String(fromSid)
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
`[AJAN DM (grup: "${g.title}") — ${dm.fromTitle} · konu: "${topic}"]\n${text}\n` +
|
|
7018
|
-
`(Cevap yalnızca aksiyon/karar GEREKİYORSA ver — agent_dm group: "${g.title}", to: "${dm.fromTitle}", topic: "${topic}"; teşekkür/onay yazma.)`,
|
|
7019
|
-
...(image ? { image } : {}),
|
|
7020
|
-
/* dm: AJAN DM'i; wake: gönderen koşan bir ajan DEĞİL (kullanıcı/chat) —
|
|
7021
|
-
sürekli ajan boştaysa turu beklemeden uyandırılır (ping-pong koruması) */
|
|
7022
|
-
dm: true,
|
|
7023
|
-
wake: !fromJob,
|
|
7024
|
-
});
|
|
7025
|
-
this.flushPendingReports(m);
|
|
7022
|
+
if (m === String(fromSid)) continue;
|
|
7023
|
+
/* wake: gönderen koşan bir ajan DEĞİL (kullanıcı/chat) —
|
|
7024
|
+
sürekli ajan boştaysa turu beklemeden uyandırılır */
|
|
7025
|
+
this._agentDmDeliver(dm, m, { groupTitle: g.title, wake: !fromJob });
|
|
7026
7026
|
}
|
|
7027
7027
|
this._persistAgentDms();
|
|
7028
7028
|
return {
|
|
@@ -7040,27 +7040,77 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
|
|
|
7040
7040
|
this._pushAgentDm(dm);
|
|
7041
7041
|
this._persistAgentDms();
|
|
7042
7042
|
emitSafe(this, target, { type: 'agent-dm', ...dm });
|
|
7043
|
-
|
|
7044
|
-
(this._pendingReports = this._pendingReports || []).push({
|
|
7045
|
-
parentId: target,
|
|
7046
|
-
text:
|
|
7047
|
-
`[AJAN DM — ${dm.fromTitle} · konu: "${topic}"]\n${text}\n` +
|
|
7048
|
-
`(Cevap yalnızca aksiyon/karar GEREKİYORSA ver — agent_dm to: "${dm.fromTitle}", topic: "${topic}"; teşekkür/onay yazma.)`,
|
|
7049
|
-
...(image ? { image } : {}),
|
|
7050
|
-
dm: true,
|
|
7051
|
-
wake: !fromJob,
|
|
7052
|
-
});
|
|
7053
|
-
this.flushPendingReports(target);
|
|
7054
|
-
}
|
|
7043
|
+
this._agentDmDeliver(dm, target, { wake: !fromJob });
|
|
7055
7044
|
return { ok: true, to: target, toTitle: dm.toTitle, topic };
|
|
7056
7045
|
} catch (e) {
|
|
7057
7046
|
return { ok: false, error: String((e && e.message) || e) };
|
|
7058
7047
|
}
|
|
7059
7048
|
};
|
|
7060
7049
|
|
|
7050
|
+
/* AJAN DM TESLİMİ: hedef ister koşan ajan (bg işi) ister düz/bot sohbet
|
|
7051
|
+
oturumu olsun mesaj GERÇEKTEN ulaşır. _pendingReports kuyruğu hedef
|
|
7052
|
+
meşgulse tur bitiminde düşer, boştaysa yeni tur açar. ESKİ HÂLDE yalnız
|
|
7053
|
+
bg işlerine teslim ediliyordu: bot sohbetine (ör. Tool) giden DM panelde
|
|
7054
|
+
kalıyor, bot hiç görmüyordu. Bot↔bot tur fırtınasına karşı çift başına
|
|
7055
|
+
pencere bütçesi vardır; bütçe dolunca mesaj yeni tur AÇMAZ, hedefin bir
|
|
7056
|
+
sonraki turunda önüne eklenir (sepet) — sonsuz ping-pong maliyeti olmaz. */
|
|
7057
|
+
Engine.prototype._agentDmDeliver = function (dm, target, opts = {}) {
|
|
7058
|
+
try {
|
|
7059
|
+
const m = String(target || '');
|
|
7060
|
+
if (!m || !dm) return false;
|
|
7061
|
+
const jobs = this._bgJobs || new Map();
|
|
7062
|
+
const job = jobs.get(m);
|
|
7063
|
+
const sess = job ? null : this.cache.get(m) || null;
|
|
7064
|
+
/* kayıtlı olmayan hedefe teslim yok — panelde kalır, sahte kuyruk birikmez */
|
|
7065
|
+
if (!job && !sess && !fs.existsSync(this._file(m))) return false;
|
|
7066
|
+
const groupTag = opts.groupTitle ? ` (grup: "${opts.groupTitle}")` : '';
|
|
7067
|
+
const replyTo = opts.groupTitle
|
|
7068
|
+
? `agent_dm group: "${opts.groupTitle}", to: "${dm.fromTitle}", topic: "${dm.topic}"`
|
|
7069
|
+
: `agent_dm to: "${dm.fromTitle}", topic: "${dm.topic}"`;
|
|
7070
|
+
const text =
|
|
7071
|
+
`[AJAN DM${groupTag} — ${dm.fromTitle} · konu: "${dm.topic}"]\n${dm.text}\n` +
|
|
7072
|
+
`(Cevap yalnızca aksiyon/karar GEREKİYORSA ver — ${replyTo}; teşekkür/onay yazma.)`;
|
|
7073
|
+
const rec = {
|
|
7074
|
+
parentId: m,
|
|
7075
|
+
text,
|
|
7076
|
+
...(dm.image ? { image: String(dm.image) } : {}),
|
|
7077
|
+
dm: true,
|
|
7078
|
+
wake: !!opts.wake,
|
|
7079
|
+
};
|
|
7080
|
+
/* bot↔bot sigorta: boş bot oturumunu her DM'de yeni turla uyandırma */
|
|
7081
|
+
if (sess && sess.botId && !this.isBusy(m) && !this._agentDmWakeAllowed(dm.from, m)) {
|
|
7082
|
+
this._agentDmBacklog = this._agentDmBacklog || new Map();
|
|
7083
|
+
const q = this._agentDmBacklog.get(m) || [];
|
|
7084
|
+
q.push({ text, ...(dm.image ? { image: String(dm.image) } : {}) });
|
|
7085
|
+
if (q.length > 20) q.splice(0, q.length - 20);
|
|
7086
|
+
this._agentDmBacklog.set(m, q);
|
|
7087
|
+
return true;
|
|
7088
|
+
}
|
|
7089
|
+
(this._pendingReports = this._pendingReports || []).push(rec);
|
|
7090
|
+
this.flushPendingReports(m);
|
|
7091
|
+
return true;
|
|
7092
|
+
} catch {
|
|
7093
|
+
return false;
|
|
7094
|
+
}
|
|
7095
|
+
};
|
|
7096
|
+
|
|
7097
|
+
/* Bot-ajan DM ping-pong sigortası: aynı çift 10 dk içinde 10 otomatik
|
|
7098
|
+
uyandırmayı aşarsa yeni tur AÇILMAZ (BEAST_DM_WAKE_LIMIT=0 kapatır). */
|
|
7099
|
+
Engine.prototype._agentDmWakeAllowed = function (a, b) {
|
|
7100
|
+
if (process.env.BEAST_DM_WAKE_LIMIT === '0') return true;
|
|
7101
|
+
const key = [String(a || ''), String(b || '')].sort().join('|');
|
|
7102
|
+
this._dmWakeLog = this._dmWakeLog || new Map();
|
|
7103
|
+
const now = Date.now();
|
|
7104
|
+
const arr = (this._dmWakeLog.get(key) || []).filter((t) => now - t < 10 * 60 * 1000);
|
|
7105
|
+
if (arr.length >= 10) { this._dmWakeLog.set(key, arr); return false; }
|
|
7106
|
+
arr.push(now);
|
|
7107
|
+
this._dmWakeLog.set(key, arr);
|
|
7108
|
+
return true;
|
|
7109
|
+
};
|
|
7110
|
+
|
|
7061
7111
|
/* AJAN DM görseli (dosyadan): png/jpg/webp/gif dosyasını data URL'e çevirir.
|
|
7062
7112
|
Send_file kapısına takılmadan (ör. tool__mt5_shot path'i) görsel paylaşımı;
|
|
7063
|
-
2.5MB üstü DM kaydını
|
|
7113
|
+
2.5MB üstü DM kaydını şişmesin diye atlanır. */
|
|
7064
7114
|
Engine.prototype._agentDmImageFromFile = function (p) {
|
|
7065
7115
|
try {
|
|
7066
7116
|
const fp = String(p || '').trim();
|
|
@@ -7110,6 +7160,51 @@ Engine.prototype._agentDmCaptureImage = async function (sid) {
|
|
|
7110
7160
|
return found;
|
|
7111
7161
|
};
|
|
7112
7162
|
|
|
7163
|
+
/* Oturum önbelleğinde DM hedefi ara (q küçük harf anahtar). Skor:
|
|
7164
|
+
tam id > bot adı/kimliği ya da tam kod > başlık/kod içerir.
|
|
7165
|
+
Eşitlikte KOŞAN oturum > Tool botunun kalıcı istek oturumu tercih edilir. */
|
|
7166
|
+
Engine.prototype._agentDmFindSession = function (q, fromSid) {
|
|
7167
|
+
let best = '';
|
|
7168
|
+
let bestScore = 0;
|
|
7169
|
+
for (const [sid, s] of this.cache) {
|
|
7170
|
+
if (sid === String(fromSid)) continue;
|
|
7171
|
+
if (!s || s.bgJob || s.isBotDm) continue;
|
|
7172
|
+
/* BOT OTURUMLARI DA HEDEF OLABİLİR: bot adı/kimliği ile eşleşir
|
|
7173
|
+
(ör. "Tool"); böylece Tool botuna DM gerçekten ulaşır — eski hâlde
|
|
7174
|
+
yalnız panelde kalıyordu. */
|
|
7175
|
+
const bot = s.botId && typeof this.resolveBot === 'function' ? this.resolveBot(s.botId) : null;
|
|
7176
|
+
const title = this._agentDmSessionTitle(s, '').toLowerCase();
|
|
7177
|
+
const hay = [s.bgTitle, s.title, s.code, title, bot ? bot.name : '', bot ? bot.id : '']
|
|
7178
|
+
.map((x) => String(x || '').toLowerCase())
|
|
7179
|
+
.filter(Boolean);
|
|
7180
|
+
let score = 0;
|
|
7181
|
+
if (String(sid).toLowerCase() === q) score = 4;
|
|
7182
|
+
else if (bot && (String(bot.id).toLowerCase() === q || String(bot.name).toLowerCase() === q)) score = 3;
|
|
7183
|
+
else if (s.code && String(s.code).toLowerCase() === q) score = 3;
|
|
7184
|
+
else if (hay.some((x) => x && x.includes(q))) score = 1;
|
|
7185
|
+
if (!score) continue;
|
|
7186
|
+
let pick = score * 10;
|
|
7187
|
+
if (this.ctrls.has(String(sid))) pick += 2;
|
|
7188
|
+
if (s.botId === 'tool' && String(this._toolSessionId || '') === String(sid)) pick += 1;
|
|
7189
|
+
if (pick > bestScore) { bestScore = pick; best = sid; }
|
|
7190
|
+
}
|
|
7191
|
+
return best;
|
|
7192
|
+
};
|
|
7193
|
+
|
|
7194
|
+
/* Oturumun AJAN DM başlığı: bot oturumunda bot adı (ör. "Tool"), düz sohbette
|
|
7195
|
+
"Beast · KOD". Başlık hem panelde kimin konuştuğunu netleştirir hem de cevap
|
|
7196
|
+
adreslemesinde kullanılır (karşı taraf bu başlıkla geri yazar). */
|
|
7197
|
+
Engine.prototype._agentDmSessionTitle = function (sess, fallback = 'Ajan') {
|
|
7198
|
+
if (!sess) return String(fallback);
|
|
7199
|
+
const code = String(sess.code || '');
|
|
7200
|
+
if (sess.botId && typeof this.resolveBot === 'function') {
|
|
7201
|
+
const b = this.resolveBot(sess.botId);
|
|
7202
|
+
if (b && b.name) return code ? String(b.name) + ' · ' + code : String(b.name);
|
|
7203
|
+
}
|
|
7204
|
+
const tail = String(sess.bgTitle || sess.title || code || '');
|
|
7205
|
+
return tail ? 'Beast · ' + tail : 'Beast';
|
|
7206
|
+
};
|
|
7207
|
+
|
|
7113
7208
|
/* İki ajanın ORTAK üyesi olduğu AÇIK grup var mı — 1:1 DM'leri grup içine
|
|
7114
7209
|
yönlendirirken kullanılır (ekip içi trafik tek thread'de toplanır). */
|
|
7115
7210
|
Engine.prototype._agentDmSharedGroup = function (a, b) {
|
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
//+------------------------------------------------------------------+
|
|
2
|
-
//| BeastFinance.mq5 — Beast Finance köprü EA'sı (v1.
|
|
2
|
+
//| BeastFinance.mq5 — Beast Finance köprü EA'sı (v1.22) |
|
|
3
3
|
//| - Heartbeat: MQL5\Files\beast_ea.json (durum + izinler + equity) |
|
|
4
4
|
//| - Komut köprüsü: beast_cmd.json (Beast yazar) → beast_cmd_ack.json|
|
|
5
5
|
//| - Entegrasyon/Screenshot: grafik üstü pano (Comment) + seviye |
|
|
6
6
|
//| çizgileri — Beast beast_note.json ile besler; görsel ajan |
|
|
7
7
|
//| computer_look ile chart screenshot'ında bu panoyu görür. |
|
|
8
|
+
//| NOT: Trade işlemleri Python köprüsünden yürür — bu EA emir AÇMAZ;|
|
|
9
|
+
//| bu yüzden <Trade\Trade.mqh> bağımlılığı YOKTUR (standart |
|
|
10
|
+
//| include klasörü eksik kurulumlarda EA derlenemiyordu). |
|
|
8
11
|
//+------------------------------------------------------------------+
|
|
9
12
|
#property copyright "Beast Agent"
|
|
10
13
|
#property link "https://github.com/algokodcom/beast-agent"
|
|
11
|
-
#property version "1.
|
|
14
|
+
#property version "1.22"
|
|
12
15
|
#property description "Beast Finance köprü EA'sı — heartbeat + komut köprüsü + grafik panosu."
|
|
13
16
|
|
|
14
|
-
#include <Trade\Trade.mqh>
|
|
15
|
-
|
|
16
17
|
input string InpTag = "BeastFinance"; // Etiket
|
|
17
18
|
input int InpHeartbeat = 5; // Kalp atışı (sn)
|
|
18
19
|
input bool InpVerbose = true; // Grafik panosunu yaz
|
|
19
|
-
input long InpMagic = 20260910; // Magic number
|
|
20
|
-
|
|
21
|
-
CTrade gTrade;
|
|
20
|
+
input long InpMagic = 20260910; // Magic number (Python köprüsü kullanır)
|
|
22
21
|
|
|
23
22
|
string BeatFile = "beast_ea.json";
|
|
24
23
|
string CmdFile = "beast_cmd.json";
|
|
@@ -29,10 +28,33 @@ string LvlPrefix = "BeastLvl_";
|
|
|
29
28
|
string gNoteText = "";
|
|
30
29
|
string gNoteSymbol = "";
|
|
31
30
|
|
|
31
|
+
/* BEKLEYEN GÖRSEL İŞLERİ: ChartScreenShot ASENKRON üretir — komut bitince
|
|
32
|
+
hemen grafiği kapatmak/boyamak çekimi iptal edebiliyor. Bu yüzden geçici
|
|
33
|
+
grafiğin kapatılması ve aktif grafiğin shift ayarının geri yazılması bir
|
|
34
|
+
sonraki timer turuna ertelenir (PNG'nin diske yazılmasına süre tanınır). */
|
|
35
|
+
long gPendCloseChart = 0;
|
|
36
|
+
long gPendRestoreChart = 0;
|
|
37
|
+
long gPendShiftPrev = 0;
|
|
38
|
+
double gPendShiftSizePrev = 0;
|
|
39
|
+
|
|
40
|
+
void FlushPendingShot()
|
|
41
|
+
{
|
|
42
|
+
if(gPendCloseChart != 0)
|
|
43
|
+
{
|
|
44
|
+
ChartClose(gPendCloseChart); /* kapanmışsa zararsız (false döner) */
|
|
45
|
+
gPendCloseChart = 0;
|
|
46
|
+
}
|
|
47
|
+
if(gPendRestoreChart != 0)
|
|
48
|
+
{
|
|
49
|
+
ChartSetInteger(gPendRestoreChart, CHART_SHIFT, gPendShiftPrev != 0);
|
|
50
|
+
ChartSetDouble(gPendRestoreChart, CHART_SHIFT_SIZE, gPendShiftSizePrev);
|
|
51
|
+
ChartRedraw(gPendRestoreChart);
|
|
52
|
+
gPendRestoreChart = 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
32
56
|
int OnInit()
|
|
33
57
|
{
|
|
34
|
-
gTrade.SetExpertMagicNumber(InpMagic);
|
|
35
|
-
gTrade.SetTypeFillingBySymbol(_Symbol);
|
|
36
58
|
EventSetTimer(MathMax(1, InpHeartbeat));
|
|
37
59
|
WriteBeat("init");
|
|
38
60
|
RefreshPanel();
|
|
@@ -43,6 +65,7 @@ int OnInit()
|
|
|
43
65
|
void OnDeinit(const int reason)
|
|
44
66
|
{
|
|
45
67
|
EventKillTimer();
|
|
68
|
+
FlushPendingShot();
|
|
46
69
|
WriteBeat("deinit");
|
|
47
70
|
ClearLevels();
|
|
48
71
|
Comment("");
|
|
@@ -50,6 +73,11 @@ void OnDeinit(const int reason)
|
|
|
50
73
|
|
|
51
74
|
void OnTimer()
|
|
52
75
|
{
|
|
76
|
+
/* ÖNEMLİ SIRA: önce ÖNCEKİ shot'un artıkları toparlanır (geçici grafik
|
|
77
|
+
kapatılır, aktif grafiğin shift'i geri yazılır); sonra yeni komut işlenir.
|
|
78
|
+
Ters sırada olsaydı yeni açılan geçici grafik aynı turda kapatılıp
|
|
79
|
+
screenshot kaybolurdu. */
|
|
80
|
+
FlushPendingShot();
|
|
53
81
|
ReadNote();
|
|
54
82
|
WriteBeat("beat");
|
|
55
83
|
ProcessCommands();
|
|
@@ -72,7 +100,7 @@ void WriteBeat(const string stage)
|
|
|
72
100
|
{
|
|
73
101
|
int h = FileOpen(BeatFile, FILE_WRITE|FILE_TXT|FILE_UNICODE);
|
|
74
102
|
if(h == INVALID_HANDLE) return;
|
|
75
|
-
string js = StringFormat("{\"ok\":true,\"ea\":\"BeastFinance\",\"version\":\"1.
|
|
103
|
+
string js = StringFormat("{\"ok\":true,\"ea\":\"BeastFinance\",\"version\":\"1.22\",\"tag\":\"%s\",\"stage\":\"%s\",\"time\":%d,\"server_time\":\"%s\",\"symbol\":\"%s\",\"period\":%d,\"equity\":%.2f,\"balance\":%.2f,\"terminal_trade_allowed\":%s,\"mql_trade_allowed\":%s,\"positions\":%d,\"note\":\"%s\"}",
|
|
76
104
|
JStr(InpTag), stage, (int)TimeCurrent(), TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS),
|
|
77
105
|
_Symbol, (int)Period(),
|
|
78
106
|
AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE),
|
|
@@ -93,7 +121,7 @@ void RefreshPanel()
|
|
|
93
121
|
Comment("");
|
|
94
122
|
return;
|
|
95
123
|
}
|
|
96
|
-
string lines = "Beast Finance — BeastFinance v1.
|
|
124
|
+
string lines = "Beast Finance — BeastFinance v1.22\n";
|
|
97
125
|
lines += _Symbol + " · " + EnumToString((ENUM_TIMEFRAMES)Period()) + " · " + TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS) + "\n";
|
|
98
126
|
lines += StringFormat("Equity: %.2f · Balance: %.2f · Acik pozisyon: %d\n",
|
|
99
127
|
AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE), PositionsTotal());
|
|
@@ -198,6 +226,7 @@ void ApplyLevels(const string raw)
|
|
|
198
226
|
}
|
|
199
227
|
if(price > 0)
|
|
200
228
|
{
|
|
229
|
+
if(n >= ArraySize(names)) break; /* taşma koruması: sınırı aşan seviye atlanır */
|
|
201
230
|
string name = LvlPrefix + IntegerToString(n);
|
|
202
231
|
names[n] = name;
|
|
203
232
|
if(ObjectFind(0, name) < 0) ObjectCreate(0, name, OBJ_HLINE, 0, 0, price);
|
|
@@ -240,6 +269,22 @@ void WriteAck(const string id, const string cmd, const bool ok, const string err
|
|
|
240
269
|
FileClose(h);
|
|
241
270
|
}
|
|
242
271
|
|
|
272
|
+
/* Komut dosyasını YALNIZ hâlâ bizim id'mizi taşıyorsa sil — EA işlerken
|
|
273
|
+
yazılmış YENİ bir komutun yanlışlıkla silinmesi komut kaybı (ea_no_ack)
|
|
274
|
+
üretiyordu. */
|
|
275
|
+
void DeleteCmdIfId(const string id)
|
|
276
|
+
{
|
|
277
|
+
if(!FileIsExist(CmdFile)) return;
|
|
278
|
+
int h = FileOpen(CmdFile, FILE_READ|FILE_TXT|FILE_UNICODE);
|
|
279
|
+
if(h == INVALID_HANDLE) return;
|
|
280
|
+
string raw = "";
|
|
281
|
+
while(!FileIsEnding(h)) raw += FileReadString(h);
|
|
282
|
+
FileClose(h);
|
|
283
|
+
if(StringLen(raw) == 0) return;
|
|
284
|
+
string cur = JsonGet(raw, "id");
|
|
285
|
+
if(StringLen(cur) == 0 || StringCompare(cur, id) == 0) FileDelete(CmdFile);
|
|
286
|
+
}
|
|
287
|
+
|
|
243
288
|
void ProcessCommands()
|
|
244
289
|
{
|
|
245
290
|
if(!FileIsExist(CmdFile)) return;
|
|
@@ -352,15 +397,17 @@ void ProcessCommands()
|
|
|
352
397
|
}
|
|
353
398
|
|
|
354
399
|
/* SON MUM SAĞ KENARA YAPIŞMASIN: grafik "shift" payı (%12 sağ boşluk).
|
|
400
|
+
CHART_SHIFT integer, CHART_SHIFT_SIZE ise DOUBLE özelliktir (10-50) —
|
|
401
|
+
yanlış API (ChartGet/SetInteger) EA'yı derlenmez hale getiriyordu.
|
|
355
402
|
Aktif grafikte önceki ayar çekimden SONRA geri yazılır — kullanıcının
|
|
356
|
-
grafiği kalıcı değişmez; geçici grafik
|
|
357
|
-
long
|
|
358
|
-
|
|
403
|
+
grafiği kalıcı değişmez; geçici grafik PNG yazılana kadar AÇIK kalır. */
|
|
404
|
+
long shiftPrev = ChartGetInteger(cid, CHART_SHIFT);
|
|
405
|
+
double shiftSizePrev = ChartGetDouble(cid, CHART_SHIFT_SIZE);
|
|
359
406
|
|
|
360
407
|
if(ok)
|
|
361
408
|
{
|
|
362
409
|
ChartSetInteger(cid, CHART_SHIFT, true);
|
|
363
|
-
|
|
410
|
+
ChartSetDouble(cid, CHART_SHIFT_SIZE, 12.0);
|
|
364
411
|
ChartRedraw(cid);
|
|
365
412
|
Sleep(1200); /* mumlar/olcek otursun */
|
|
366
413
|
ChartRedraw(cid);
|
|
@@ -375,14 +422,24 @@ void ProcessCommands()
|
|
|
375
422
|
result = StringFormat("{\"file\":\"%s\",\"width\":%d,\"height\":%d,\"symbol\":\"%s\",\"period\":%d,\"temp_chart\":%s,\"template_applied\":%s,\"theme_ok\":%s,\"bg\":%d}",
|
|
376
423
|
JStr(file), w, hh, symWant, (int)tfUse, (tempChart ? "true" : "false"), (tplApplied ? "true" : "false"), (themeOk ? "true" : "false"), bgNow);
|
|
377
424
|
}
|
|
378
|
-
if(!tempChart)
|
|
379
|
-
{
|
|
380
|
-
ChartSetInteger(cid, CHART_SHIFT, shiftPrev != 0);
|
|
381
|
-
ChartSetInteger(cid, CHART_SHIFT_SIZE, shiftSizePrev);
|
|
382
|
-
ChartRedraw(cid);
|
|
383
|
-
}
|
|
384
425
|
}
|
|
385
|
-
|
|
426
|
+
/* ACK GARANTİSİ: ChartScreenShot görüntüyü ASENKRON üretir; ack'i
|
|
427
|
+
geciktirmek tool tarafında sahte "ea_no_ack" hatalarına yol açıyordu.
|
|
428
|
+
Ack burada hemen yazılır; PNG'yi tool ayrıca bekler. Geçici grafik ve
|
|
429
|
+
aktif grafiğin shift ayarı bir sonraki timer turunda toparlanır. */
|
|
430
|
+
WriteAck(id, cmd, ok, err, result);
|
|
431
|
+
DeleteCmdIfId(id);
|
|
432
|
+
if(tempChart)
|
|
433
|
+
{
|
|
434
|
+
gPendCloseChart = cid;
|
|
435
|
+
}
|
|
436
|
+
else
|
|
437
|
+
{
|
|
438
|
+
gPendRestoreChart = cid;
|
|
439
|
+
gPendShiftPrev = shiftPrev;
|
|
440
|
+
gPendShiftSizePrev = shiftSizePrev;
|
|
441
|
+
}
|
|
442
|
+
return; /* ack bu yolda yazıldı — ortak kuyruğa tekrar girmesin */
|
|
386
443
|
}
|
|
387
444
|
else
|
|
388
445
|
{
|
|
@@ -390,7 +447,7 @@ void ProcessCommands()
|
|
|
390
447
|
err = "bilinmeyen komut: " + cmd;
|
|
391
448
|
}
|
|
392
449
|
WriteAck(id, cmd, ok, err, result);
|
|
393
|
-
|
|
450
|
+
DeleteCmdIfId(id);
|
|
394
451
|
}
|
|
395
452
|
|
|
396
453
|
/* BeastFinance.tpl acik tema renklerini grafige uygular (sablon bulunamazsa da garanti) */
|
package/src/main.js
CHANGED
|
@@ -2355,6 +2355,7 @@ function botToolSet(cfg) {
|
|
|
2355
2355
|
'watcher_add', 'watcher_list', 'watcher_remove',
|
|
2356
2356
|
'tool_request', /* CEPHANE: eksik aracı TOOL botuna yazdırma hakkı HER botta */
|
|
2357
2357
|
'channel_send', /* İLK MESAJ: allow listteki kişilere kendiliğinden yazma hakkı HER botta */
|
|
2358
|
+
'agent_dm', /* BOTLAR ARASI DM: botlar birbirine iş atar/cevap verir (ör. Beast → Tool) */
|
|
2358
2359
|
]);
|
|
2359
2360
|
if (s.web_search) { set.add('web_search'); set.add('http_fetch'); set.add('webfetch'); set.add('deep_search'); }
|
|
2360
2361
|
if (s.browser) {
|
|
@@ -3901,6 +3902,7 @@ app.whenReady().then(() => {
|
|
|
3901
3902
|
app.on('before-quit', () => {
|
|
3902
3903
|
app.isQuitting = true;
|
|
3903
3904
|
try { finWatchStop(); } catch {} // finance watchdog zamanlayıcısını kapat
|
|
3905
|
+
try { finHoursStop(); } catch {} // trade saatleri denetim zamanlayıcısını kapat
|
|
3904
3906
|
flushBrowserStorage(); // x.com/google oturumları (cookies) diske yazılsın
|
|
3905
3907
|
try { toolsMod.disposeShellSessions(); } catch {} // kalıcı shell oturumlarını kapat
|
|
3906
3908
|
try { require('./agent/mcp').stopAll(); } catch {} // MCP server süreçlerini kapat
|
|
@@ -9333,6 +9335,8 @@ const financeState = {
|
|
|
9333
9335
|
watchTimer: null,
|
|
9334
9336
|
watchBusy: false,
|
|
9335
9337
|
watchTickAt: 0,
|
|
9338
|
+
hoursTimer: null, /* trade saatleri otomatik duraklatma denetimi (10 sn) */
|
|
9339
|
+
hoursPaused: null, /* pencere kapanınca durdurulan ajanların planı — açılınca geri gelir */
|
|
9336
9340
|
alerts: [], /* fiyat alarmları (kalıcı: finance/alerts.json) */
|
|
9337
9341
|
stats: null, /* son performans özeti (kalıcı: finance/stats.json) */
|
|
9338
9342
|
statsAt: 0,
|
|
@@ -9476,6 +9480,130 @@ function finTradeHoursBlockedLog(what) {
|
|
|
9476
9480
|
return true;
|
|
9477
9481
|
}
|
|
9478
9482
|
|
|
9483
|
+
/* ---------- TRADE SAATLERİ OTOMATİK DURAKLATMA ----------
|
|
9484
|
+
Pencere kapanınca TÜM finance ajanları (trader + ekip + sembol işçileri)
|
|
9485
|
+
Durdur butonuna basılmış gibi otomatik durdurulur; pencere açılınca
|
|
9486
|
+
duraklatılanlar AYNEN geri başlatılır. Kullanıcı duraklama sırasında bir
|
|
9487
|
+
ajanı elle durdurursa o ajan plandan düşer; "Ajanı Durdur"a basılırsa
|
|
9488
|
+
plandan tamamen vazgeçilir (otomatik başlatma iptal). */
|
|
9489
|
+
function finHoursPlanSnapshot() {
|
|
9490
|
+
const plan = { main: false, roles: [], symbols: [] };
|
|
9491
|
+
try {
|
|
9492
|
+
for (const [, a] of financeState.agents) {
|
|
9493
|
+
if (!a) continue;
|
|
9494
|
+
if (a.main) plan.main = true;
|
|
9495
|
+
else if (a.role) plan.roles.push(String(a.role));
|
|
9496
|
+
else if (Array.isArray(a.symbols) && a.symbols.length) plan.symbols.push(a.symbols.slice(0, 8));
|
|
9497
|
+
}
|
|
9498
|
+
} catch {}
|
|
9499
|
+
return plan;
|
|
9500
|
+
}
|
|
9501
|
+
|
|
9502
|
+
function finHoursPlanEmpty(p) {
|
|
9503
|
+
return !p || (!p.main && !(p.roles || []).length && !(p.symbols || []).length);
|
|
9504
|
+
}
|
|
9505
|
+
|
|
9506
|
+
function finHoursPlanDrop(agent) {
|
|
9507
|
+
const p = financeState.hoursPaused;
|
|
9508
|
+
if (!p || !agent) return;
|
|
9509
|
+
if (agent.main) p.main = false;
|
|
9510
|
+
else if (agent.role) p.roles = (p.roles || []).filter((r) => r !== String(agent.role));
|
|
9511
|
+
else if (Array.isArray(agent.symbols) && agent.symbols.length) {
|
|
9512
|
+
const key = String(agent.symbols[0] || '').toUpperCase();
|
|
9513
|
+
p.symbols = (p.symbols || []).filter((s) => String((s && s[0]) || '').toUpperCase() !== key);
|
|
9514
|
+
}
|
|
9515
|
+
if (finHoursPlanEmpty(p)) financeState.hoursPaused = null;
|
|
9516
|
+
}
|
|
9517
|
+
|
|
9518
|
+
/* Pencere kapandı: koşan her finance ajanını durdur, planı sakla. */
|
|
9519
|
+
function finHoursPause(reason) {
|
|
9520
|
+
const plan = finHoursPlanSnapshot();
|
|
9521
|
+
if (finHoursPlanEmpty(plan)) return false;
|
|
9522
|
+
financeState.hoursPaused = plan;
|
|
9523
|
+
const sids = [];
|
|
9524
|
+
for (const [sid, a] of financeState.agents) if (a) sids.push(String(sid));
|
|
9525
|
+
for (const sid of sids) {
|
|
9526
|
+
try { finAgentStop(sid, reason, { keepHoursPlan: true }); } catch {}
|
|
9527
|
+
}
|
|
9528
|
+
financeState.traderOn = false;
|
|
9529
|
+
const th = finCfg().tradeHours || {};
|
|
9530
|
+
const n = (plan.main ? 1 : 0) + (plan.roles || []).length + (plan.symbols || []).length;
|
|
9531
|
+
financeLog(`[saat] trade saatleri kapandı (${th.start || '09:00'}-${th.end || '22:00'} yerel) — ${n} ajan otomatik durduruldu; pencere açılınca geri başlayacak`);
|
|
9532
|
+
finPush('trader', { state: 'hours-stop', count: n });
|
|
9533
|
+
return true;
|
|
9534
|
+
}
|
|
9535
|
+
|
|
9536
|
+
/* Duraklatılan tek ajanı geri aç (ekip rolü ya da sembol işçisi). */
|
|
9537
|
+
function finHoursSpawn(role, symbol) {
|
|
9538
|
+
if (!engine) return false;
|
|
9539
|
+
const f = finCfg();
|
|
9540
|
+
try {
|
|
9541
|
+
const created = role ? finAgentCreate([], false, role) : finAgentCreate([symbol], false);
|
|
9542
|
+
try { engine.setSessionModel(created.s.id, f.traderSel || null); } catch {}
|
|
9543
|
+
const ok = engine.send(created.s.id, finTraderBrief(created.agent), { userAction: true });
|
|
9544
|
+
if (!ok) {
|
|
9545
|
+
finAgentStop(String(created.s.id), 'saat açılışı — oturum meşgul');
|
|
9546
|
+
return false;
|
|
9547
|
+
}
|
|
9548
|
+
const roleDef = role ? finRoleDef(role) : null;
|
|
9549
|
+
financeLog('[saat] ' + (roleDef ? roleDef.label : symbol) + ' geri başlatıldı');
|
|
9550
|
+
return true;
|
|
9551
|
+
} catch {
|
|
9552
|
+
return false;
|
|
9553
|
+
}
|
|
9554
|
+
}
|
|
9555
|
+
|
|
9556
|
+
/* Pencere açıldı: duraklatılanları geri başlat. */
|
|
9557
|
+
function finHoursResume() {
|
|
9558
|
+
const p = financeState.hoursPaused;
|
|
9559
|
+
financeState.hoursPaused = null;
|
|
9560
|
+
if (finHoursPlanEmpty(p) || !engine) return false;
|
|
9561
|
+
const th = finCfg().tradeHours || {};
|
|
9562
|
+
financeLog(`[saat] trade saatleri açıldı (${th.start || '09:00'}-${th.end || '22:00'} yerel) — duraklatılan ajanlar geri başlatılıyor`);
|
|
9563
|
+
if (p.main) { try { financeTraderStart(); } catch {} }
|
|
9564
|
+
for (const role of p.roles || []) {
|
|
9565
|
+
if (!finRoleDef(role)) continue;
|
|
9566
|
+
let running = false;
|
|
9567
|
+
for (const [, a] of financeState.agents) if (a && a.role === role) { running = true; break; }
|
|
9568
|
+
if (!running) finHoursSpawn(role, '');
|
|
9569
|
+
}
|
|
9570
|
+
for (const syms of p.symbols || []) {
|
|
9571
|
+
const symbol = String((syms && syms[0]) || '').toUpperCase();
|
|
9572
|
+
if (!symbol) continue;
|
|
9573
|
+
let running = false;
|
|
9574
|
+
for (const [, a] of financeState.agents) {
|
|
9575
|
+
if (a && !a.main && !a.role && a.symbols.length === 1 && a.symbols[0] === symbol) { running = true; break; }
|
|
9576
|
+
}
|
|
9577
|
+
if (!running) finHoursSpawn('', symbol);
|
|
9578
|
+
}
|
|
9579
|
+
finPush('trader', { state: 'hours-resume' });
|
|
9580
|
+
return true;
|
|
9581
|
+
}
|
|
9582
|
+
|
|
9583
|
+
/* Saat geçişi denetimi: pencere dışındaysa duraklat, içindeyse geri başlat. */
|
|
9584
|
+
function finHoursSync() {
|
|
9585
|
+
if (!engine) return;
|
|
9586
|
+
if (!finTradeHoursOpen()) {
|
|
9587
|
+
if (financeState.agents.size) finHoursPause('trade saatleri kapandı — ajan otomatik durduruldu');
|
|
9588
|
+
return;
|
|
9589
|
+
}
|
|
9590
|
+
if (financeState.hoursPaused) finHoursResume();
|
|
9591
|
+
}
|
|
9592
|
+
|
|
9593
|
+
/* Otomatik saat denetimi zamanlayıcısı (10 sn; MT5 köprüsünden bağımsız) */
|
|
9594
|
+
function finHoursStart() {
|
|
9595
|
+
if (financeState.hoursTimer) return;
|
|
9596
|
+
financeState.hoursTimer = setInterval(() => { try { finHoursSync(); } catch {} }, 10000);
|
|
9597
|
+
try { finHoursSync(); } catch {}
|
|
9598
|
+
}
|
|
9599
|
+
|
|
9600
|
+
function finHoursStop() {
|
|
9601
|
+
if (financeState.hoursTimer) {
|
|
9602
|
+
clearInterval(financeState.hoursTimer);
|
|
9603
|
+
financeState.hoursTimer = null;
|
|
9604
|
+
}
|
|
9605
|
+
}
|
|
9606
|
+
|
|
9479
9607
|
/* ANALİZ EKİBİ: trader'ın yanında koşan uzman ajan rolleri — seçilen her rol
|
|
9480
9608
|
AYRI bir sürekli finance ajanı açar. Hepsi tüm SKILL'lere + mt5 okuma
|
|
9481
9609
|
araçlarına erişir (finance oturumu oldukları için), İŞLEM AÇMAZLAR.
|
|
@@ -11096,6 +11224,7 @@ async function financeEnsureBridge() {
|
|
|
11096
11224
|
terminal: String(f.terminalPath || '').trim(),
|
|
11097
11225
|
});
|
|
11098
11226
|
finWatchStart(); /* risk otomasyonu + bildirimler köprü açılırken başlar */
|
|
11227
|
+
try { finHoursStart(); } catch {} /* trade saatleri otomatik duraklatma denetimi */
|
|
11099
11228
|
return mt5bridge.status();
|
|
11100
11229
|
}
|
|
11101
11230
|
|
|
@@ -11290,6 +11419,7 @@ mt5bridge.on('bridge', (m) => {
|
|
|
11290
11419
|
financeLog('[MT5] terminal bağlı: ' + ((m.terminal && (m.terminal.name + ' @ ' + m.terminal.company)) || 'MT5'));
|
|
11291
11420
|
if (m.account) financeLog(`[MT5] hesap ${m.account.login} · bakiye ${m.account.balance} ${m.account.currency}`);
|
|
11292
11421
|
finWatchStart();
|
|
11422
|
+
try { finHoursStart(); } catch {} /* trade saatleri otomatik duraklatma denetimi */
|
|
11293
11423
|
finStatsRefresh(true).catch(() => {});
|
|
11294
11424
|
/* İLK ENTEGRASYON: EA + AutoTrading + grafik kurulumu otomatik */
|
|
11295
11425
|
finMt5SetupEnsure(false)
|
|
@@ -11444,10 +11574,13 @@ function finAgentCreate(symbols, isMain, role) {
|
|
|
11444
11574
|
}
|
|
11445
11575
|
|
|
11446
11576
|
/* Ajanı durdur: timer kapat + oturumu kes + paralel ajan kaydını 'aborted' yaz */
|
|
11447
|
-
function finAgentStop(sid, reason) {
|
|
11577
|
+
function finAgentStop(sid, reason, opts) {
|
|
11448
11578
|
sid = String(sid || '');
|
|
11449
11579
|
const agent = financeState.agents.get(sid);
|
|
11450
11580
|
if (!agent) return false;
|
|
11581
|
+
/* ELLE DURDURULDU: saat duraklama planından düş — pencere açılınca geri
|
|
11582
|
+
gelmesin (otomatik duraklatma keepHoursPlan ile planı korur) */
|
|
11583
|
+
if (!(opts && opts.keepHoursPlan)) { try { finHoursPlanDrop(agent); } catch {} }
|
|
11451
11584
|
clearTimeout(agent.timer);
|
|
11452
11585
|
if (agent.waitPoll) { clearTimeout(agent.waitPoll); agent.waitPoll = null; }
|
|
11453
11586
|
agent.waitingTeam = false;
|
|
@@ -11910,6 +12043,8 @@ ipcMain.handle('finance:alerts:remove', async (_e, id) => {
|
|
|
11910
12043
|
|
|
11911
12044
|
ipcMain.handle('finance:mode', async (_e, payload) => {
|
|
11912
12045
|
financeState.mode = !!(payload && payload.on);
|
|
12046
|
+
/* SAAT DENETİMİ: pencere kapanınca ajanlar otomatik durur, açılınca geri başlar */
|
|
12047
|
+
try { finHoursStart(); } catch {}
|
|
11913
12048
|
/* BEAST FINANCE = MT5 KAPISI:
|
|
11914
12049
|
- mod AÇILINCA köprü + MT5 terminali OTOMATİK başlar (watchdog dahil) —
|
|
11915
12050
|
panel açılır açılmaz bağlantı kurulur, snapshot'ı beklemez.
|
|
@@ -12146,6 +12281,13 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
|
|
|
12146
12281
|
async function financeTraderStart() {
|
|
12147
12282
|
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
12148
12283
|
const f = finCfg();
|
|
12284
|
+
try { finHoursStart(); } catch {}
|
|
12285
|
+
/* TRADE SAATLERİ KAPISI: pencere dışında elle başlatma da reddedilir —
|
|
12286
|
+
pencere açılınca duraklatılan ajanlar otomatik geri başlar */
|
|
12287
|
+
if (!finTradeHoursOpen()) {
|
|
12288
|
+
const th = f.tradeHours || {};
|
|
12289
|
+
return { ok: false, error: `trade saatleri dışı (${th.start || '09:00'}-${th.end || '22:00'} yerel) — pencere açılınca ajanlar otomatik başlar` };
|
|
12290
|
+
}
|
|
12149
12291
|
if (!engine.publicState().hasModel && !f.traderSel) return { ok: false, error: 'model yok — Ayarlar → Provider' };
|
|
12150
12292
|
try { finSyncScheduleJobs(); } catch {} /* plan/review saatleri trader açılışında garantiye alınır */
|
|
12151
12293
|
/* ANA trader: varsa aynen sürdür, yoksa yeni sürekli ajan aç */
|
|
@@ -12222,6 +12364,8 @@ ipcMain.handle('finance:trader:start', () => financeTraderStart());
|
|
|
12222
12364
|
|
|
12223
12365
|
ipcMain.handle('finance:trader:stop', async () => {
|
|
12224
12366
|
financeState.traderOn = false;
|
|
12367
|
+
/* ELLE DURDURMA: saat planı iptal — pencere açılınca otomatik geri başlatma yok */
|
|
12368
|
+
financeState.hoursPaused = null;
|
|
12225
12369
|
/* ANA trader + ANALİZ EKİBİ durdurulur (sembol işçileri rail × ile ayrı durdurulur) */
|
|
12226
12370
|
let mainSid = '';
|
|
12227
12371
|
const teamSids = [];
|
|
@@ -12242,6 +12386,12 @@ ipcMain.handle('finance:trader:stop', async () => {
|
|
|
12242
12386
|
ipcMain.handle('finance:agent:spawn', async (_e, payload) => {
|
|
12243
12387
|
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
12244
12388
|
const f = finCfg();
|
|
12389
|
+
try { finHoursStart(); } catch {}
|
|
12390
|
+
/* TRADE SAATLERİ: pencere dışında sembol işçisi de başlatılamaz */
|
|
12391
|
+
if (!finTradeHoursOpen()) {
|
|
12392
|
+
const th = f.tradeHours || {};
|
|
12393
|
+
return { ok: false, error: `trade saatleri dışı (${th.start || '09:00'}-${th.end || '22:00'} yerel) — pencere açılınca ajanlar otomatik başlar` };
|
|
12394
|
+
}
|
|
12245
12395
|
if (!engine.publicState().hasModel && !f.traderSel) return { ok: false, error: 'model yok — Ayarlar → Provider' };
|
|
12246
12396
|
const symbol = String((payload && payload.symbol) || '').trim().toUpperCase();
|
|
12247
12397
|
if (!symbol) return { ok: false, error: 'sembol gerekli' };
|
package/src/renderer/renderer.js
CHANGED
|
@@ -10167,6 +10167,8 @@ function finOnEvent(ev) {
|
|
|
10167
10167
|
else if (ev.state === 'team') finLogLine('[trader] ekip raporları bekleniyor — ' + (ev.waiting || 0) + ' ajan turunu bitirsin (max ' + (ev.maxSec || '?') + ' sn)');
|
|
10168
10168
|
else if (ev.state === 'idle') finLogLine('[trader] tur #' + (ev.round || 0) + ' bitti — sıradaki tur ~' + (ev.nextInSec || '?') + ' sn');
|
|
10169
10169
|
else if (ev.state === 'hours') finLogLine('[trader] trade saatleri dışı — ajan askıda (kontrol ~' + (ev.nextInSec || 60) + ' sn)');
|
|
10170
|
+
else if (ev.state === 'hours-stop') finLogLine('[trader] trade saatleri kapandı — ' + (ev.count || 0) + ' finance ajanı otomatik durduruldu; pencere açılınca geri başlar');
|
|
10171
|
+
else if (ev.state === 'hours-resume') finLogLine('[trader] trade saatleri açıldı — duraklatılan ajanlar geri başlatıldı');
|
|
10170
10172
|
else if (ev.state === 'stopped') finLogLine('[trader] durduruldu');
|
|
10171
10173
|
} else if (ev.fn === 'install') {
|
|
10172
10174
|
finLogLine('[MT5] paket kurulumu tamamlandı (kod ' + ev.code + ')');
|