beast-agent 2.24.3 → 2.30.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 +121 -29
- package/src/main.js +144 -44
- package/src/preload.js +2 -0
- package/src/renderer/index.html +11 -3
- package/src/renderer/renderer.js +224 -33
- package/src/renderer/style.css +91 -9
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -1180,9 +1180,11 @@ class Engine {
|
|
|
1180
1180
|
/* Beast Finance oturumu: kalıcı izolasyon etiketi (finoff kapatır) */
|
|
1181
1181
|
session.finance = true;
|
|
1182
1182
|
session.financeTrader = !!rec.trader;
|
|
1183
|
+
if (rec.role) session.financeRole = String(rec.role);
|
|
1183
1184
|
} else if (rec.t === 'finoff') {
|
|
1184
1185
|
session.finance = false;
|
|
1185
1186
|
session.financeTrader = false;
|
|
1187
|
+
delete session.financeRole;
|
|
1186
1188
|
} else if (rec.t === 'msg') {
|
|
1187
1189
|
delete rec.t;
|
|
1188
1190
|
session.messages.push(rec);
|
|
@@ -1333,12 +1335,17 @@ class Engine {
|
|
|
1333
1335
|
|
|
1334
1336
|
/* Beast Finance etiketi: oturumu finance sohbeti olarak KALICI işaretler
|
|
1335
1337
|
(jsonl'e 'fin' satırı düşer — restart sonrası da hatırlanır) */
|
|
1336
|
-
markFinance(id, trader = false) {
|
|
1338
|
+
markFinance(id, trader = false, role = '') {
|
|
1337
1339
|
const s = this._load(String(id));
|
|
1338
1340
|
s.finance = true;
|
|
1339
1341
|
s.financeTrader = !!trader;
|
|
1342
|
+
if (role) s.financeRole = String(role);
|
|
1343
|
+
else delete s.financeRole;
|
|
1340
1344
|
try {
|
|
1341
|
-
fs.appendFileSync(
|
|
1345
|
+
fs.appendFileSync(
|
|
1346
|
+
this._file(s.id),
|
|
1347
|
+
JSON.stringify({ t: 'fin', trader: !!trader, role: String(role || ''), at: nowIso() }) + '\n'
|
|
1348
|
+
);
|
|
1342
1349
|
} catch {}
|
|
1343
1350
|
this.cache.set(String(s.id), s);
|
|
1344
1351
|
return s;
|
|
@@ -1349,6 +1356,7 @@ class Engine {
|
|
|
1349
1356
|
const s = this._load(String(id));
|
|
1350
1357
|
s.finance = false;
|
|
1351
1358
|
s.financeTrader = false;
|
|
1359
|
+
delete s.financeRole;
|
|
1352
1360
|
try {
|
|
1353
1361
|
fs.appendFileSync(this._file(s.id), JSON.stringify({ t: 'finoff', at: nowIso() }) + '\n');
|
|
1354
1362
|
} catch {}
|
|
@@ -1848,7 +1856,7 @@ class Engine {
|
|
|
1848
1856
|
|
|
1849
1857
|
/* Beast Finance: MT5 trading ajanı — chat copilot'ı VEYA otonom trader.
|
|
1850
1858
|
Oturum alanları (main enjekte eder): financeTrader, financeAuto,
|
|
1851
|
-
financeSymbols, financeStrategy, financeLimits */
|
|
1859
|
+
financeSymbols, financeStrategy, financeLimits, financeRole */
|
|
1852
1860
|
buildFinanceSystem(session) {
|
|
1853
1861
|
const nowD = new Date();
|
|
1854
1862
|
const localDate = nowD.toLocaleDateString('tr-TR');
|
|
@@ -1875,6 +1883,21 @@ class Engine {
|
|
|
1875
1883
|
const modeBlock2 = isWorker
|
|
1876
1884
|
? 'ROL: FİNANS İŞÇİSİ 🔎 — TEK bir görev için piyasa verisi toplar, analiz eder, RAPOR döndürürsün. mt5_trade/mt5_pending/mt5_close KULLANMA — yalnız okuma araçları (mt5_status/account/market/positions/orders/history). İş bitince kısa final raporu yaz.'
|
|
1877
1885
|
: modeBlock;
|
|
1886
|
+
/* ANALİZ EKİBİ rolü: trader'ın yanında koşan uzman ajanlar (risk/teknik/
|
|
1887
|
+
makro) — hepsi İşlem AÇMAZ, kendi uzmanlık çerçevesinde rapor yazar */
|
|
1888
|
+
const roleBlock =
|
|
1889
|
+
({
|
|
1890
|
+
risk:
|
|
1891
|
+
'ROL: RİSK AJANI 🛡 — portföyün RİSK GÖZCÜSÜSÜN, İşlem AÇMAZSIN (mt5_trade/mt5_pending/mt5_close KULLANMA). Her turda: hesap + pozisyonlar + marj kullanımı → kaldıraç/exposure değerlendirmesi; SL\u2019siz pozisyon, limit aşımı, tek yönlü birikme, günlük kayıp hızı risklerini raporla; net düzeltme önerisi ver (hangi pozisyon küçültülmeli/kapatılmalı).',
|
|
1892
|
+
technic:
|
|
1893
|
+
'ROL: TEKNİK ANALİZ AJANI 📊 — fiyat yapısı uzmanısın, İşlem AÇMAZSIN (mt5_trade/mt5_pending/mt5_close KULLANMA). Her turda odak semboller için: trend/yapı, destek-direnç bölgeleri, momentum (mt5_market + mt5_history verisiyle); sembol başına AL/SAT/BEKLE + giriş/SL/TP fikri üret — trader bu öneriyi işleme çevirir.',
|
|
1894
|
+
macro:
|
|
1895
|
+
'ROL: MAKRO AJANI 🌍 — büyük resim uzmanısın, İşlem AÇMAZSIN (mt5_trade/mt5_pending/mt5_close KULLANMA). Her turda web_search ile güncel makro manşetleri + ekonomik takvim riskleri (faiz, CPI, jeopolitik); DXY/altın/petrol bağıntılarını odak sembollere çevir; sembol başına yön eğilimi + TEMKİN/BEKLE notu ver.',
|
|
1896
|
+
})[String((session && session.financeRole) || '')] || '';
|
|
1897
|
+
/* FİNANS EKİBİ: tüm finance ajanları tek DM grubundadır — sohbet grup
|
|
1898
|
+
thread'inde toplanır (ayrı ayrı 1:1 thread'lere dağılmaz) */
|
|
1899
|
+
const finJob = this._bgJobs && this._bgJobs.get(String(session && session.id));
|
|
1900
|
+
const teamLine = finJob && finJob.teamGid ? this._agentTeamPromptLine(finJob.teamGid) : '';
|
|
1878
1901
|
return (
|
|
1879
1902
|
(mem.soul ? mem.soul.trim() + '\n\n' : '') +
|
|
1880
1903
|
'Sen BEAST FİNANS\u2019sın — bilgisayardaki MetaTrader 5 (MT5) terminaline köprüyle bağlı trading ajanı.\n' +
|
|
@@ -1882,6 +1905,8 @@ class Engine {
|
|
|
1882
1905
|
'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' +
|
|
1883
1906
|
'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' +
|
|
1884
1907
|
modeBlock2 + '\n' +
|
|
1908
|
+
(roleBlock ? roleBlock + '\n' : '') +
|
|
1909
|
+
(teamLine || '') +
|
|
1885
1910
|
'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' +
|
|
1886
1911
|
'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' +
|
|
1887
1912
|
'PARALEL + KOORDİNASYON: uzun araştırma/işleri run_background ile paralel finance işçisine devret (parent finance olduğu için işçi mt5 okuma araçlarını görür); koşan ajanlarla konuşmak için agent_dm (to: ajan başlığındaki anahtar kelime, örn "GOLD"; ortak karar için group: "İSİM" ile grup sohbeti kur — mesaj tüm üyelere düşer). Görevin bitince DM/grup sohbetleri otomatik KAPANIR (geçmiş panelde kalır).\n' +
|
|
@@ -6308,25 +6333,36 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
|
|
|
6308
6333
|
const at = new Date().toISOString();
|
|
6309
6334
|
|
|
6310
6335
|
/* GRUP SOHBETİ: kur/yeniden kullan, hedefi üye yap, mesaj HER ÜYEYE düşer.
|
|
6311
|
-
Ajanlar ortak kararları burada tartışır; iş bitince sohbet kapanır.
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6336
|
+
Ajanlar ortak kararları burada tartışır; iş bitince sohbet kapanır.
|
|
6337
|
+
groupName verilmemişse ama iki taraf da AYNI AÇIK grubun üyesiyse mesaj
|
|
6338
|
+
YİNE GRUBA düşer — üye üyeye 1:1 thread'lere ayrı ayrı dağılmaz; ekibin
|
|
6339
|
+
tüm iç trafiği TEK grup sohbetinde canlı toplanır. */
|
|
6340
|
+
const shared = groupName || !target ? null : this._agentDmSharedGroup(fromSid, target);
|
|
6341
|
+
let gid = '';
|
|
6342
|
+
if (groupName || shared) {
|
|
6343
|
+
let g;
|
|
6344
|
+
if (groupName) {
|
|
6345
|
+
/* gid Türkçe-kararsız normalize: ALTIN/Altın/altın aynı grup */
|
|
6346
|
+
gid =
|
|
6347
|
+
'grp:' +
|
|
6348
|
+
groupName
|
|
6349
|
+
.toLowerCase()
|
|
6350
|
+
.replace(/\u0307/g, '')
|
|
6351
|
+
.replace(/ı/g, 'i')
|
|
6352
|
+
.replace(/ğ/g, 'g')
|
|
6353
|
+
.replace(/ü/g, 'u')
|
|
6354
|
+
.replace(/ş/g, 's')
|
|
6355
|
+
.replace(/ö/g, 'o')
|
|
6356
|
+
.replace(/ç/g, 'c');
|
|
6357
|
+
g = this._agentGroups.get(gid);
|
|
6358
|
+
if (!g) {
|
|
6359
|
+
if (!target) return { ok: false, error: 'yeni grup için to ile en az bir ajan belirt' };
|
|
6360
|
+
g = { id: gid, title: groupName, members: [], titles: {}, createdAt: at, closed: false };
|
|
6361
|
+
this._agentGroups.set(gid, g);
|
|
6362
|
+
}
|
|
6363
|
+
} else {
|
|
6364
|
+
g = shared;
|
|
6365
|
+
gid = String(g.id || '');
|
|
6330
6366
|
}
|
|
6331
6367
|
g.titles = g.titles || {};
|
|
6332
6368
|
const addMember = (sid, title) => {
|
|
@@ -6393,6 +6429,20 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
|
|
|
6393
6429
|
}
|
|
6394
6430
|
};
|
|
6395
6431
|
|
|
6432
|
+
/* İki ajanın ORTAK üyesi olduğu AÇIK grup var mı — 1:1 DM'leri grup içine
|
|
6433
|
+
yönlendirirken kullanılır (ekip içi trafik tek thread'de toplanır). */
|
|
6434
|
+
Engine.prototype._agentDmSharedGroup = function (a, b) {
|
|
6435
|
+
a = String(a || '');
|
|
6436
|
+
b = String(b || '');
|
|
6437
|
+
if (!a || !b) return null;
|
|
6438
|
+
for (const [, g] of this._agentGroups || new Map()) {
|
|
6439
|
+
if (g.closed) continue;
|
|
6440
|
+
const mem = g.members || [];
|
|
6441
|
+
if (mem.includes(a) && mem.includes(b)) return g;
|
|
6442
|
+
}
|
|
6443
|
+
return null;
|
|
6444
|
+
};
|
|
6445
|
+
|
|
6396
6446
|
Engine.prototype._pushAgentDm = function (dm) {
|
|
6397
6447
|
this._agentDms = this._agentDms || [];
|
|
6398
6448
|
this._agentDms.push(dm);
|
|
@@ -6403,18 +6453,30 @@ Engine.prototype._pushAgentDm = function (dm) {
|
|
|
6403
6453
|
ortak DM grubuna girer — tartışma ZORUNLU (sistem promptuyla). ---------- */
|
|
6404
6454
|
|
|
6405
6455
|
/* İş başlarken çağrılır: aynı parent / bg-gruptan başka ajan varsa (varsa
|
|
6406
|
-
önceden kurulmuştur) hepsini TEK ekip grubuna katar + katılım postu düşer.
|
|
6456
|
+
önceden kurulmuştur) hepsini TEK ekip grubuna katar + katılım postu düşer.
|
|
6457
|
+
dmGroupId verilirse (finans ekibi gibi hazır grup) parent şartı aranmaz —
|
|
6458
|
+
aynı dmGroupId'li TÜM ajanlar tek gruba toplanır. */
|
|
6407
6459
|
Engine.prototype._agentTeamJoin = function (job) {
|
|
6408
6460
|
try {
|
|
6409
6461
|
if (!job || !job.id) return;
|
|
6410
6462
|
const parent = String(job.parentId || '');
|
|
6411
|
-
|
|
6412
|
-
|
|
6463
|
+
const explicit = String(job.dmGroupId || '');
|
|
6464
|
+
if (!parent && !explicit) return;
|
|
6465
|
+
const gid = explicit || 'team:' + (job.groupId || parent);
|
|
6413
6466
|
const base =
|
|
6414
|
-
String(job.task || job.title || 'ortak görev').replace(/\s+/g, ' ').trim().slice(0,
|
|
6467
|
+
String(job.dmGroupTitle || job.task || job.title || 'ortak görev').replace(/\s+/g, ' ').trim().slice(0, 40) ||
|
|
6468
|
+
'ortak görev';
|
|
6415
6469
|
let g = this._agentGroups.get(gid);
|
|
6416
6470
|
if (!g) {
|
|
6417
|
-
g = {
|
|
6471
|
+
g = {
|
|
6472
|
+
id: gid,
|
|
6473
|
+
title: job.dmGroupTitle ? base : base + ' EKİP',
|
|
6474
|
+
members: [],
|
|
6475
|
+
titles: {},
|
|
6476
|
+
createdAt: nowIso(),
|
|
6477
|
+
closed: false,
|
|
6478
|
+
team: true,
|
|
6479
|
+
};
|
|
6418
6480
|
this._agentGroups.set(gid, g);
|
|
6419
6481
|
}
|
|
6420
6482
|
g.closed = false;
|
|
@@ -6425,9 +6487,13 @@ Engine.prototype._agentTeamJoin = function (job) {
|
|
|
6425
6487
|
if (!g.members.includes(id)) g.members.push(id);
|
|
6426
6488
|
if (title) g.titles[id] = String(title);
|
|
6427
6489
|
};
|
|
6428
|
-
/* aynı ortak görevdeki DİĞER ajanlar (aborted
|
|
6490
|
+
/* aynı ortak görevdeki / aynı hazır gruptaki DİĞER ajanlar (aborted hariç) */
|
|
6429
6491
|
const mates = [...this._bgJobs.values()].filter(
|
|
6430
|
-
(j) =>
|
|
6492
|
+
(j) =>
|
|
6493
|
+
j &&
|
|
6494
|
+
String(j.id) !== String(job.id) &&
|
|
6495
|
+
j.status !== 'aborted' &&
|
|
6496
|
+
(explicit ? String(j.dmGroupId || '') === explicit : String(j.parentId) === parent)
|
|
6431
6497
|
);
|
|
6432
6498
|
add(job.id, job.title);
|
|
6433
6499
|
for (const m of mates) add(m.id, m.title);
|
|
@@ -6569,6 +6635,32 @@ Engine.prototype.agentDmsClear = function () {
|
|
|
6569
6635
|
return { ok: true };
|
|
6570
6636
|
};
|
|
6571
6637
|
|
|
6638
|
+
/* TEK OTURUM SİLME — renderer'daki dmThreadOf() ile BİREBİR AYNI anahtar
|
|
6639
|
+
hesabı: verilen key'e düşen TÜM DM'ler silinir. Grup sohbetiyse ve grup
|
|
6640
|
+
KAPALIYSA grup kaydı da kalkar; koşan ekip grubunun kaydı korunur ki
|
|
6641
|
+
süren işin zorunlu grup akışı bozulmasın. */
|
|
6642
|
+
Engine.prototype._agentDmKeyOf = function (dm) {
|
|
6643
|
+
if (dm && dm.group) return 'G|' + String(dm.group);
|
|
6644
|
+
const a = String((dm && dm.from) || '');
|
|
6645
|
+
const b = String((dm && dm.to) || '');
|
|
6646
|
+
const pair = a < b ? a + '|' + b : b + '|' + a;
|
|
6647
|
+
return pair + '|' + String((dm && dm.topic) || '(genel)').toLowerCase();
|
|
6648
|
+
};
|
|
6649
|
+
|
|
6650
|
+
Engine.prototype.agentDmDeleteThread = function (key) {
|
|
6651
|
+
key = String(key || '');
|
|
6652
|
+
if (!key) return { ok: false, error: 'key gerekli' };
|
|
6653
|
+
const before = (this._agentDms || []).length;
|
|
6654
|
+
this._agentDms = (this._agentDms || []).filter((dm) => this._agentDmKeyOf(dm) !== key);
|
|
6655
|
+
const removed = before - this._agentDms.length;
|
|
6656
|
+
if (key.startsWith('G|')) {
|
|
6657
|
+
const g = this._agentGroups && this._agentGroups.get(key.slice(2));
|
|
6658
|
+
if (g && g.closed) this._agentGroups.delete(g.id);
|
|
6659
|
+
}
|
|
6660
|
+
if (removed) this._persistAgentDms();
|
|
6661
|
+
return { ok: true, removed };
|
|
6662
|
+
};
|
|
6663
|
+
|
|
6572
6664
|
module.exports = Engine;
|
|
6573
6665
|
module.exports.Engine = Engine;
|
|
6574
6666
|
module.exports.sanitizeTodoItems = sanitizeTodoItems;
|
package/src/main.js
CHANGED
|
@@ -5890,6 +5890,7 @@ ipcMain.handle('agents:cancel', (_e, id) => {
|
|
|
5890
5890
|
/* AJAN DM paneli: ajanlar arası mesaj arşivi */
|
|
5891
5891
|
ipcMain.handle('agent-dms:list', () => (engine ? engine.agentDmsList() : []));
|
|
5892
5892
|
ipcMain.handle('agent-dms:clear', () => (engine ? engine.agentDmsClear() : { ok: false }));
|
|
5893
|
+
ipcMain.handle('agent-dms:delete-thread', (_e, key) => (engine ? engine.agentDmDeleteThread(key) : { ok: false }));
|
|
5893
5894
|
|
|
5894
5895
|
/* ---------- KİŞİSEL TOOLLAR (%APPDATA%\beast\tools\) ---------- */
|
|
5895
5896
|
ipcMain.handle('tools:list', () => customtools.list());
|
|
@@ -8318,13 +8319,37 @@ function finCfg() {
|
|
|
8318
8319
|
(eski davranış EURUSD/XAUUSD/GBPUSD/BTCUSD'yi geri geri getiriyordu) */
|
|
8319
8320
|
if (!Array.isArray(f.symbols)) f.symbols = [];
|
|
8320
8321
|
if (typeof f.consultChat !== 'boolean') f.consultChat = true; /* her tur öncesi chat ajanından plan */
|
|
8322
|
+
if (!Array.isArray(f.analysisTeam)) f.analysisTeam = []; /* trader yanında koşacak uzman roller */
|
|
8323
|
+
/* OTOMATİK İŞLEM hep açık — kullanıcı onay kutusu KALDIRILDI: trade ajanının
|
|
8324
|
+
amacı zaten işlem açmak; onay sorulmaz, limitler (max lot/pozisyon) korur */
|
|
8325
|
+
f.allowTrading = true;
|
|
8326
|
+
if (typeof f.analysisAuto !== 'boolean') f.analysisAuto = true; /* sayıya göre otomatik atama */
|
|
8327
|
+
if (!Number(f.analysisCount)) f.analysisCount = 2; /* varsayılan: 2 uzman ajan */
|
|
8321
8328
|
if (!Number(f.intervalSec)) f.intervalSec = 120;
|
|
8322
8329
|
if (!Number(f.maxLot)) f.maxLot = 0.1;
|
|
8323
8330
|
if (f.maxPositions == null) f.maxPositions = 3;
|
|
8324
|
-
if (typeof f.allowTrading !== 'boolean') f.allowTrading = false;
|
|
8325
8331
|
return f;
|
|
8326
8332
|
}
|
|
8327
8333
|
|
|
8334
|
+
/* ANALİZ EKİBİ: trader'ın yanında koşan uzman ajan rolleri — seçilen her rol
|
|
8335
|
+
AYRI bir sürekli finance ajanı açar. Hepsi tüm SKILL'lere + mt5 okuma
|
|
8336
|
+
araçlarına erişir (finance oturumu oldukları için), İŞLEM AÇMAZLAR.
|
|
8337
|
+
AUTO_ORDER: "sadece sayı seç" modunda gereklı ajanlar bu sırayla atanır. */
|
|
8338
|
+
const FIN_ROLES = [
|
|
8339
|
+
{ id: 'risk', label: 'Risk Ajanı', desc: 'marj/kaldıraç/SL disiplini, exposure ve günlük kayıp hızı denetimi' },
|
|
8340
|
+
{ id: 'technic', label: 'Teknik Analiz', desc: 'trend/yapı/destek-direnç/momentum okuma, AL-SAT-BEKLE önerileri' },
|
|
8341
|
+
{ id: 'macro', label: 'Haber / Makro', desc: 'haber akışı + ekonomik takvim, DXY/emtia bağıntıları, yön eğilimi' },
|
|
8342
|
+
];
|
|
8343
|
+
const FIN_ROLES_AUTO = ['technic', 'risk', 'macro'];
|
|
8344
|
+
function finRolesValid(list) {
|
|
8345
|
+
return (Array.isArray(list) ? list : [])
|
|
8346
|
+
.map((r) => String(r || '').trim())
|
|
8347
|
+
.filter((r) => FIN_ROLES.some((d) => d.id === r));
|
|
8348
|
+
}
|
|
8349
|
+
function finRoleDef(role) {
|
|
8350
|
+
return FIN_ROLES.find((d) => d.id === String(role || '')) || null;
|
|
8351
|
+
}
|
|
8352
|
+
|
|
8328
8353
|
function financeLog(line) {
|
|
8329
8354
|
try {
|
|
8330
8355
|
fs.appendFileSync(path.join(financeDir(), 'finance.log'), `[${new Date().toISOString()}] ${line}\n`);
|
|
@@ -8451,7 +8476,8 @@ mt5bridge.on('log', (m) => {
|
|
|
8451
8476
|
ajanlar). Döngüyü main yönetir; engine 'done'/'error'da sürekli işi
|
|
8452
8477
|
KAPATMAZ (_bgFinish continuous dalı), yalnız kullanıcı iptali bitirir. */
|
|
8453
8478
|
|
|
8454
|
-
function finAgentSession(symbols, isMain) {
|
|
8479
|
+
function finAgentSession(symbols, isMain, role) {
|
|
8480
|
+
role = String(role || '');
|
|
8455
8481
|
if (isMain) {
|
|
8456
8482
|
const sid = financeState.traderSid;
|
|
8457
8483
|
if (sid) {
|
|
@@ -8465,7 +8491,12 @@ function finAgentSession(symbols, isMain) {
|
|
|
8465
8491
|
const s = engine._load(engine.createSession().id);
|
|
8466
8492
|
s.messages = s.messages || [];
|
|
8467
8493
|
const symLabel = symbols && symbols.length ? symbols.join(', ').slice(0, 40) : 'tüm liste';
|
|
8468
|
-
const
|
|
8494
|
+
const roleDef = finRoleDef(role);
|
|
8495
|
+
const title = isMain
|
|
8496
|
+
? 'Beast Finance · Trader'
|
|
8497
|
+
: roleDef
|
|
8498
|
+
? 'Finance · ' + roleDef.label + ' · ' + symLabel
|
|
8499
|
+
: 'Finance · ' + symLabel;
|
|
8469
8500
|
s.bgTitle = title; /* _view.isBg → sohbet geçmişinde gizli */
|
|
8470
8501
|
try {
|
|
8471
8502
|
fs.appendFileSync(
|
|
@@ -8474,7 +8505,7 @@ function finAgentSession(symbols, isMain) {
|
|
|
8474
8505
|
);
|
|
8475
8506
|
} catch {}
|
|
8476
8507
|
engine.cache.set(s.id, s);
|
|
8477
|
-
engine.markFinance(s.id, true); /* kalıcı finance etiketi (mt5_* araçları) */
|
|
8508
|
+
engine.markFinance(s.id, true, role); /* kalıcı finance etiketi + rol (mt5_* araçları) */
|
|
8478
8509
|
s.workspace = financeDir(); /* ajan kendi klasöründe çalışır */
|
|
8479
8510
|
if (isMain) financeState.traderSid = s.id;
|
|
8480
8511
|
return s;
|
|
@@ -8487,14 +8518,21 @@ function finAgentRegisterBg(s, symbols) {
|
|
|
8487
8518
|
if (!engine || !engine._bgJobs) return;
|
|
8488
8519
|
const f = finCfg();
|
|
8489
8520
|
const symLabel = symbols && symbols.length ? symbols.join(', ').slice(0, 40) : 'tüm izleme listesi';
|
|
8490
|
-
|
|
8521
|
+
const roleDef = finRoleDef(s.financeRole);
|
|
8522
|
+
const job = {
|
|
8491
8523
|
id: s.id,
|
|
8492
8524
|
code: s.code,
|
|
8493
8525
|
title: s.bgTitle || 'Finance Ajanı',
|
|
8494
|
-
task:
|
|
8526
|
+
task:
|
|
8527
|
+
`Beast Finance ${roleDef ? roleDef.label + ' (İşlem AÇMAZ — analiz ekibi)' : 'ajanı'} (${symLabel}) — ` +
|
|
8528
|
+
`${roleDef ? 'analiz modunda tur tur tarama' : 'otonom tur tur işlem yönetimi'}`,
|
|
8495
8529
|
agent: null,
|
|
8496
8530
|
parentId: '',
|
|
8497
8531
|
groupId: null,
|
|
8532
|
+
/* FİNANS EKİBİ DM GRUBU: trader + analiz ekibi + sembol işçileri TEK
|
|
8533
|
+
gruba girer — mesajlar ayrı ayrı 1:1 thread'lere dağılmaz */
|
|
8534
|
+
dmGroupId: 'team:finance',
|
|
8535
|
+
dmGroupTitle: 'Beast Finance EKİP',
|
|
8498
8536
|
status: 'running',
|
|
8499
8537
|
slot: false, /* eşzamanlılık slotu TÜKETMEZ — ana işleri bloklamaz */
|
|
8500
8538
|
continuous: true, /* tur sonları işi KAPATMAZ (_bgFinish) */
|
|
@@ -8505,34 +8543,42 @@ function finAgentRegisterBg(s, symbols) {
|
|
|
8505
8543
|
fixes: 0,
|
|
8506
8544
|
endedAt: null,
|
|
8507
8545
|
error: null,
|
|
8508
|
-
}
|
|
8546
|
+
};
|
|
8547
|
+
engine._bgJobs.set(String(s.id), job);
|
|
8509
8548
|
engine._bgEmit();
|
|
8549
|
+
/* otomatik ekip grubuna katıl: grup thread'i ilk ajanla kurulur,
|
|
8550
|
+
sonraki her finance ajanı aynı gruba eklenir (katılım postu düşer) */
|
|
8551
|
+
engine._agentTeamJoin(job);
|
|
8510
8552
|
} catch {}
|
|
8511
8553
|
}
|
|
8512
8554
|
|
|
8513
|
-
function finApplyTraderFields(s, symbolsOverride) {
|
|
8555
|
+
function finApplyTraderFields(s, symbolsOverride, role) {
|
|
8514
8556
|
const f = finCfg();
|
|
8557
|
+
const r = String(role || s.financeRole || '');
|
|
8515
8558
|
s.finance = true;
|
|
8516
8559
|
s.financeTrader = true;
|
|
8517
|
-
s.
|
|
8560
|
+
s.financeRole = r;
|
|
8561
|
+
/* rol ajanları ASLA işlem açmaz — trade ajanı ve işçileri DAIMA açabilir */
|
|
8562
|
+
s.financeAuto = !r;
|
|
8518
8563
|
s.financeSymbols = Array.isArray(symbolsOverride) && symbolsOverride.length ? symbolsOverride : f.symbols;
|
|
8519
8564
|
s.financeStrategy = String(f.strategy || '');
|
|
8520
8565
|
s.financeLimits = { maxLot: f.maxLot, maxPositions: f.maxPositions };
|
|
8521
8566
|
engine.cache.set(String(s.id), s);
|
|
8522
8567
|
}
|
|
8523
8568
|
|
|
8524
|
-
/* Yeni finance ajanı aç (ana trader YA DA sembol işçisi) */
|
|
8525
|
-
function finAgentCreate(symbols, isMain) {
|
|
8569
|
+
/* Yeni finance ajanı aç (ana trader YA DA sembol işçisi YA DA analiz ekibi rolü) */
|
|
8570
|
+
function finAgentCreate(symbols, isMain, role) {
|
|
8526
8571
|
const f = finCfg();
|
|
8527
|
-
const s = finAgentSession(symbols, isMain);
|
|
8572
|
+
const s = finAgentSession(symbols, isMain, role);
|
|
8528
8573
|
const record = {
|
|
8529
8574
|
symbols: symbols && symbols.length ? symbols.slice(0, 10) : [...(f.symbols || [])],
|
|
8530
8575
|
main: !!isMain,
|
|
8576
|
+
role: String(role || ''),
|
|
8531
8577
|
round: 0,
|
|
8532
8578
|
timer: null,
|
|
8533
8579
|
};
|
|
8534
8580
|
financeState.agents.set(String(s.id), record);
|
|
8535
|
-
finApplyTraderFields(s, record.symbols);
|
|
8581
|
+
finApplyTraderFields(s, record.symbols, record.role);
|
|
8536
8582
|
finAgentRegisterBg(s, record.symbols);
|
|
8537
8583
|
return { s, agent: record };
|
|
8538
8584
|
}
|
|
@@ -8566,18 +8612,22 @@ function finAgentStop(sid, reason) {
|
|
|
8566
8612
|
function finTraderBrief(agent) {
|
|
8567
8613
|
const f = finCfg();
|
|
8568
8614
|
const syms = agent && agent.symbols && agent.symbols.length ? agent.symbols.join(', ') : (f.symbols || []).join(', ');
|
|
8569
|
-
const
|
|
8615
|
+
const roleDef = finRoleDef(agent && agent.role);
|
|
8616
|
+
const who = agent && agent.main ? 'TRADER' : roleDef ? roleDef.label.toUpperCase() : 'FINANCE AJANI';
|
|
8570
8617
|
return [
|
|
8571
8618
|
`Beast Finance ${who} başlatıldı — ilk tur: strateji çerçeveni kur ve piyasa taramasını yap.`,
|
|
8572
8619
|
`Odak semboller: ${syms || '(boş — mt5_status ile terminale bak, mantıklı semboller seç)'}`,
|
|
8573
8620
|
`Tur aralığı: ${f.intervalSec} sn · Max lot: ${f.maxLot} · Max eşzamanlı pozisyon: ${f.maxPositions}`,
|
|
8574
|
-
|
|
8575
|
-
?
|
|
8576
|
-
: 'Otomatik işlem
|
|
8577
|
-
|
|
8621
|
+
roleDef
|
|
8622
|
+
? `UZMANLIK: ${roleDef.desc} — raporlarını bu çerçevede yaz; İŞLEM AÇMA, yalnız analiz + net öneri üret.`
|
|
8623
|
+
: 'Otomatik işlem AÇIK (daima): mt5_trade/mt5_close/mt5_modify/mt5_pending kullanabilirsin (limitler sistemce zorlanır).',
|
|
8624
|
+
roleDef
|
|
8625
|
+
? 'Bulgularını agent_dm ile ANA TRADER\u2019a bildir (to: "Trader" ya da ajan başlığı anahtarı); teknik/öneri çelişkisi varsa gerekçenle yaz.'
|
|
8626
|
+
: f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : 'Strateji notu yok: trend + destek/direnç + momentum ile temel okuma yap.',
|
|
8627
|
+
!roleDef && f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : '',
|
|
8578
8628
|
'Bu turda: mt5_status → hesap/pozisyon/fiyat verisi → değerlendirme → kararlar (veya BEKLE: sebep) → kısa rapor.',
|
|
8579
8629
|
'Diğer finance/paralel ajanlarla koordinasyon için agent_dm aracı var (to: ajan başlığı anahtar kelimesi).',
|
|
8580
|
-
].join('\n');
|
|
8630
|
+
].filter(Boolean).join('\n');
|
|
8581
8631
|
}
|
|
8582
8632
|
|
|
8583
8633
|
/* Her tur öncesi CHAT AJANINDAN plan iste: aynı motorun odaklı alt-ajanı
|
|
@@ -8597,7 +8647,7 @@ async function finConsultPlan(f, agent, sid) {
|
|
|
8597
8647
|
if (ps && ps.ok) positions = (ps.data && ps.data.positions) || [];
|
|
8598
8648
|
}
|
|
8599
8649
|
} catch {}
|
|
8600
|
-
const auto =
|
|
8650
|
+
const auto = 'AÇIK (işlem açabilir — limitler zorlanır)';
|
|
8601
8651
|
const task =
|
|
8602
8652
|
'Beast Finance ajanı yeni tura giriyor. SEN mt5_* araçlarını KULLANMA, işlem AÇMA — ' +
|
|
8603
8653
|
'sana düşen iş: semboller için bu turun kısa işlem PLANINI üretmek ' +
|
|
@@ -8643,15 +8693,16 @@ function finAgentRound(sid) {
|
|
|
8643
8693
|
agent.round += 1;
|
|
8644
8694
|
if (agent.main) financeState.traderRounds = agent.round;
|
|
8645
8695
|
const f = finCfg();
|
|
8696
|
+
const roleDef = finRoleDef(agent.role);
|
|
8646
8697
|
try {
|
|
8647
8698
|
const s = engine.cache.get(String(sid));
|
|
8648
|
-
if (s) finApplyTraderFields(s, agent.symbols);
|
|
8699
|
+
if (s) finApplyTraderFields(s, agent.symbols, agent.role);
|
|
8649
8700
|
} catch {}
|
|
8650
|
-
const auto =
|
|
8651
|
-
?
|
|
8652
|
-
: '
|
|
8701
|
+
const auto = roleDef
|
|
8702
|
+
? `UZMANLIK: ${roleDef.desc} — İŞLEM AÇMA, yalnız analiz + net öneri.`
|
|
8703
|
+
: 'İşlem açabilirsin — limitlere uy, SL\u2019siz pozisyon bırakma.';
|
|
8653
8704
|
const focus = agent.symbols.length ? `Odak: ${agent.symbols.join(', ')}. ` : '';
|
|
8654
|
-
const round = `FINANCE TUR #${agent.round}: ${focus}hesap + pozisyonlar + fiyatları çek; açık pozisyonları yönet (SL/TP güncelle, hedefe ulaşanı kapat); stratejine göre yeni fırsatları değerlendir. ${auto} Kısa rapor ver.`;
|
|
8705
|
+
const round = `FINANCE TUR #${agent.round}: ${focus}hesap + pozisyonlar + fiyatları çek; ${roleDef ? 'rolüne uygun analiz yap ve öneri ver.' : 'açık pozisyonları yönet (SL/TP güncelle, hedefe ulaşanı kapat); stratejine göre yeni fırsatları değerlendir.'} ${auto} Kısa rapor ver.`;
|
|
8655
8706
|
const launch = (planBlock) => {
|
|
8656
8707
|
if (!financeState.agents.has(String(sid))) return;
|
|
8657
8708
|
const ok = engine.send(sid, planBlock + round, { userAction: false });
|
|
@@ -8664,8 +8715,8 @@ function finAgentRound(sid) {
|
|
|
8664
8715
|
agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, 30000);
|
|
8665
8716
|
}
|
|
8666
8717
|
};
|
|
8667
|
-
if (f.consultChat === false) {
|
|
8668
|
-
/* danışma
|
|
8718
|
+
if (f.consultChat === false || roleDef) {
|
|
8719
|
+
/* danışma kapalı ya da rol ajanı (plan trader'a yöneliktir): tur doğrudan başlar */
|
|
8669
8720
|
launch('');
|
|
8670
8721
|
return;
|
|
8671
8722
|
}
|
|
@@ -8703,6 +8754,7 @@ ipcMain.handle('finance:state', async () => {
|
|
|
8703
8754
|
ok: true,
|
|
8704
8755
|
mode: financeState.mode,
|
|
8705
8756
|
cfg: f,
|
|
8757
|
+
roles: FIN_ROLES,
|
|
8706
8758
|
bridge: mt5bridge.status(),
|
|
8707
8759
|
trader: { on: financeState.traderOn, busy, rounds: financeState.traderRounds, lastAt: financeState.lastRoundAt, sid: financeState.traderSid },
|
|
8708
8760
|
models: engine ? engine.publicState().models : [],
|
|
@@ -8714,15 +8766,18 @@ ipcMain.handle('finance:snapshot', async () => {
|
|
|
8714
8766
|
const st = financeEnsureBridge();
|
|
8715
8767
|
let account = st.account || null;
|
|
8716
8768
|
let positions = [];
|
|
8769
|
+
let orders = [];
|
|
8717
8770
|
let symbols = [];
|
|
8718
8771
|
if (st.running) {
|
|
8719
|
-
const [a, p, sy] = await Promise.all([
|
|
8772
|
+
const [a, p, o, sy] = await Promise.all([
|
|
8720
8773
|
mt5bridge.call('account', {}, 8000).catch(() => null),
|
|
8721
8774
|
mt5bridge.call('positions', {}, 8000).catch(() => null),
|
|
8775
|
+
mt5bridge.call('orders', {}, 8000).catch(() => null),
|
|
8722
8776
|
mt5bridge.call('symbols', { symbols: f.symbols || [] }, 10000).catch(() => null),
|
|
8723
8777
|
]);
|
|
8724
8778
|
if (a && a.ok) account = a.data && a.data.account;
|
|
8725
8779
|
if (p && p.ok) positions = (p.data && p.data.positions) || [];
|
|
8780
|
+
if (o && o.ok) orders = (o.data && o.data.orders) || [];
|
|
8726
8781
|
if (sy && sy.ok) symbols = (sy.data && sy.data.symbols) || [];
|
|
8727
8782
|
}
|
|
8728
8783
|
const busy = financeState.traderSid && engine ? engine.isBusy(financeState.traderSid) : false;
|
|
@@ -8731,8 +8786,10 @@ ipcMain.handle('finance:snapshot', async () => {
|
|
|
8731
8786
|
bridge: { running: st.running, connected: st.connected, error: st.error, terminal: st.terminal, python: st.python },
|
|
8732
8787
|
account,
|
|
8733
8788
|
positions,
|
|
8789
|
+
orders,
|
|
8734
8790
|
symbols,
|
|
8735
8791
|
cfg: f,
|
|
8792
|
+
roles: FIN_ROLES,
|
|
8736
8793
|
trader: { on: financeState.traderOn, busy, rounds: financeState.traderRounds, lastAt: financeState.lastRoundAt },
|
|
8737
8794
|
};
|
|
8738
8795
|
});
|
|
@@ -8764,15 +8821,8 @@ ipcMain.handle('finance:mode', async (_e, payload) => {
|
|
|
8764
8821
|
}
|
|
8765
8822
|
} catch {}
|
|
8766
8823
|
}
|
|
8767
|
-
|
|
8768
|
-
|
|
8769
|
-
(best-effort — model/köprü yoksa sessizce atlanır, log'a düşer) */
|
|
8770
|
-
financeTraderStart()
|
|
8771
|
-
.then((r) => {
|
|
8772
|
-
if (!r || !r.ok) financeLog('[trader] otomatik başlatılamadı: ' + ((r && r.error) || 'bilinmeyen'));
|
|
8773
|
-
})
|
|
8774
|
-
.catch(() => {});
|
|
8775
|
-
}
|
|
8824
|
+
/* OTOMATİK TRADER KAPALI: finance moduna geçmek trader'ı KENDİLİĞİNDEN
|
|
8825
|
+
başlatmaz — kullanıcı TRADE AJANI kartındaki ▶ ile elle başlatır */
|
|
8776
8826
|
return { ok: true, mode: financeState.mode, needNew };
|
|
8777
8827
|
});
|
|
8778
8828
|
|
|
@@ -8802,8 +8852,11 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
|
|
|
8802
8852
|
if (p.intervalSec !== undefined) f.intervalSec = Math.max(30, Math.min(3600, Math.round(Number(p.intervalSec) || 120)));
|
|
8803
8853
|
if (p.maxLot !== undefined) f.maxLot = Math.max(0.01, Math.min(100, Number(p.maxLot) || 0.1));
|
|
8804
8854
|
if (p.maxPositions !== undefined) f.maxPositions = Math.max(1, Math.min(20, Math.round(Number(p.maxPositions) || 3)));
|
|
8805
|
-
|
|
8855
|
+
/* allowTrading artık AYARLANMAZ — daima true (finCfg zorlar) */
|
|
8806
8856
|
if (p.strategy !== undefined) f.strategy = String(p.strategy || '').slice(0, 2000);
|
|
8857
|
+
if (p.analysisTeam !== undefined) f.analysisTeam = finRolesValid(p.analysisTeam);
|
|
8858
|
+
if (p.analysisAuto !== undefined) f.analysisAuto = !!p.analysisAuto;
|
|
8859
|
+
if (p.analysisCount !== undefined) f.analysisCount = Math.max(0, Math.min(FIN_ROLES_AUTO.length, Math.round(Number(p.analysisCount) || 0)));
|
|
8807
8860
|
if (p.pythonPath !== undefined) f.pythonPath = String(p.pythonPath || '').trim();
|
|
8808
8861
|
if (p.terminalPath !== undefined) f.terminalPath = String(p.terminalPath || '').trim();
|
|
8809
8862
|
if (p.traderSel !== undefined) {
|
|
@@ -8860,24 +8913,58 @@ async function financeTraderStart() {
|
|
|
8860
8913
|
financeState.lastRoundAt = Date.now();
|
|
8861
8914
|
financeLog('[trader] başlatıldı (model: ' + (f.traderSel || 'genel aktif model') + ')');
|
|
8862
8915
|
finPush('trader', { state: 'running', round: mainAgent.round });
|
|
8916
|
+
/* ANALİZ EKİBİ: seçili roller için ayrı sürekli ajanlar (koşmıyorsa) */
|
|
8917
|
+
finTeamStart(f);
|
|
8863
8918
|
return { ok: true, sid: mainSid };
|
|
8864
8919
|
}
|
|
8865
8920
|
|
|
8921
|
+
/* ANALİZ EKİBİ: seçili her rol için AYRI sürekli finance ajanı — hepsi
|
|
8922
|
+
tüm SKILL'lere + mt5 okuma araçlarına erişir, İŞLEM AÇMAZ.
|
|
8923
|
+
Otomatik mod: kullanıcı sadece SAYI girer → gereklı ajanlar atanır. */
|
|
8924
|
+
function finTeamStart(f) {
|
|
8925
|
+
const n = Math.max(0, Math.min(FIN_ROLES_AUTO.length, Math.round(Number(f && f.analysisCount) || 0)));
|
|
8926
|
+
const roles = f && f.analysisAuto
|
|
8927
|
+
? FIN_ROLES_AUTO.slice(0, n)
|
|
8928
|
+
: finRolesValid((f && f.analysisTeam) || []);
|
|
8929
|
+
if (!roles.length) return;
|
|
8930
|
+
const running = new Set();
|
|
8931
|
+
for (const [, a] of financeState.agents) {
|
|
8932
|
+
if (a.role) running.add(a.role);
|
|
8933
|
+
}
|
|
8934
|
+
for (const roleId of roles) {
|
|
8935
|
+
if (running.has(roleId)) continue;
|
|
8936
|
+
const roleDef = finRoleDef(roleId);
|
|
8937
|
+
try {
|
|
8938
|
+
const created = finAgentCreate([], false, roleId);
|
|
8939
|
+
try { engine.setSessionModel(created.s.id, f.traderSel || null); } catch {}
|
|
8940
|
+
const ok = engine.send(created.s.id, finTraderBrief(created.agent), { userAction: true });
|
|
8941
|
+
if (!ok) {
|
|
8942
|
+
finAgentStop(String(created.s.id), 'oturum meşgul — ekip ajanı başlatılamadı');
|
|
8943
|
+
financeLog('[ekip] ' + roleDef.label + ' başlatılamadı: oturum meşgul');
|
|
8944
|
+
} else {
|
|
8945
|
+
financeLog('[ekip] ' + roleDef.label + ' başladı — analiz turu koşuyor (İşlem AÇMAZ)');
|
|
8946
|
+
}
|
|
8947
|
+
} catch (e) {
|
|
8948
|
+
financeLog('[ekip] ' + roleDef.label + ' hatası: ' + String((e && e.message) || e));
|
|
8949
|
+
}
|
|
8950
|
+
}
|
|
8951
|
+
}
|
|
8952
|
+
|
|
8866
8953
|
ipcMain.handle('finance:trader:start', () => financeTraderStart());
|
|
8867
8954
|
|
|
8868
8955
|
ipcMain.handle('finance:trader:stop', async () => {
|
|
8869
8956
|
financeState.traderOn = false;
|
|
8870
|
-
/* ANA trader
|
|
8957
|
+
/* ANA trader + ANALİZ EKİBİ durdurulur (sembol işçileri rail × ile ayrı durdurulur) */
|
|
8871
8958
|
let mainSid = '';
|
|
8959
|
+
const teamSids = [];
|
|
8872
8960
|
for (const [sid, a] of financeState.agents) {
|
|
8873
|
-
if (a.main)
|
|
8874
|
-
|
|
8875
|
-
break;
|
|
8876
|
-
}
|
|
8961
|
+
if (a.main) mainSid = sid;
|
|
8962
|
+
else if (a.role) teamSids.push(sid);
|
|
8877
8963
|
}
|
|
8878
8964
|
const stopped = mainSid ? finAgentStop(mainSid, 'kullanıcı Beast Finance trader ajanını durdurdu') : false;
|
|
8965
|
+
for (const sid of teamSids) finAgentStop(sid, 'kullanıcı Beast Finance ekip ajanını durdurdu');
|
|
8879
8966
|
const interrupted = !!stopped;
|
|
8880
|
-
financeLog('[trader] durduruldu');
|
|
8967
|
+
financeLog('[trader] durduruldu' + (teamSids.length ? ' (ekip: ' + teamSids.length + ' ajan)' : ''));
|
|
8881
8968
|
finPush('trader', { state: 'stopped' });
|
|
8882
8969
|
return { ok: true, interrupted };
|
|
8883
8970
|
});
|
|
@@ -8924,6 +9011,19 @@ ipcMain.handle('finance:close', async (_e, payload) => {
|
|
|
8924
9011
|
return r;
|
|
8925
9012
|
});
|
|
8926
9013
|
|
|
9014
|
+
/* Bekleyen emri panelden iptal et (ajanın mt5_cancel aracıyla aynı köprü) */
|
|
9015
|
+
ipcMain.handle('finance:cancel', async (_e, payload) => {
|
|
9016
|
+
const ticket = Number(payload && payload.ticket);
|
|
9017
|
+
if (!ticket) return { ok: false, error: 'ticket gerekli' };
|
|
9018
|
+
if (!mt5bridge.running) return { ok: false, error: 'MT5 köprüsü bağlı değil' };
|
|
9019
|
+
const r = await mt5bridge.call('cancel', { ticket }, 20000);
|
|
9020
|
+
if (r && r.ok) {
|
|
9021
|
+
financeLog('[panel] bekleyen emir iptal edildi: ticket ' + ticket);
|
|
9022
|
+
finPush('trade', { line: 'EMİR İPTAL (panel): ticket ' + ticket });
|
|
9023
|
+
}
|
|
9024
|
+
return r;
|
|
9025
|
+
});
|
|
9026
|
+
|
|
8927
9027
|
ipcMain.handle('finance:install', async () => {
|
|
8928
9028
|
financeState.installTried = false;
|
|
8929
9029
|
financeTryInstall();
|
package/src/preload.js
CHANGED
|
@@ -22,6 +22,7 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
22
22
|
agentsCancel: (id) => ipcRenderer.invoke('agents:cancel', id),
|
|
23
23
|
agentDmsList: () => ipcRenderer.invoke('agent-dms:list'),
|
|
24
24
|
agentDmsClear: () => ipcRenderer.invoke('agent-dms:clear'),
|
|
25
|
+
agentDmDeleteThread: (key) => ipcRenderer.invoke('agent-dms:delete-thread', key),
|
|
25
26
|
toolsList: () => ipcRenderer.invoke('tools:list'),
|
|
26
27
|
toolSave: (t) => ipcRenderer.invoke('tools:save', t),
|
|
27
28
|
toolDelete: (id) => ipcRenderer.invoke('tools:delete', id),
|
|
@@ -116,6 +117,7 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
116
117
|
financeAgentSpawn: (symbol) => ipcRenderer.invoke('finance:agent:spawn', { symbol }),
|
|
117
118
|
financeConnect: () => ipcRenderer.invoke('finance:connect'),
|
|
118
119
|
financeClose: (ticket, volume) => ipcRenderer.invoke('finance:close', { ticket, volume }),
|
|
120
|
+
financeCancel: (ticket) => ipcRenderer.invoke('finance:cancel', { ticket }),
|
|
119
121
|
financeInstall: () => ipcRenderer.invoke('finance:install'),
|
|
120
122
|
financeSymbolsList: (filter) => ipcRenderer.invoke('finance:symbols:list', filter),
|
|
121
123
|
clipRead: () => ipcRenderer.invoke('clip:read'),
|
package/src/renderer/index.html
CHANGED
|
@@ -161,7 +161,7 @@
|
|
|
161
161
|
<div class="fin-kv"><span>Serbest Marj</span><b id="finFree">—</b></div>
|
|
162
162
|
</div>
|
|
163
163
|
</div>
|
|
164
|
-
<div class="fin-card">
|
|
164
|
+
<div class="fin-card fin-card-trader">
|
|
165
165
|
<div class="fin-card-title">TRADE AJANI <span id="finTraderDot" class="fin-dot off" title="Trader durumu"></span></div>
|
|
166
166
|
<div class="fin-row"><select id="finTraderModel" title="Trader ajanının modeli — farklı API/model seçilebilir"></select></div>
|
|
167
167
|
<div class="fin-row fin-mini">
|
|
@@ -169,8 +169,11 @@
|
|
|
169
169
|
<label>Max lot</label><input id="finMaxLot" type="number" min="0.01" max="100" step="0.01" />
|
|
170
170
|
</div>
|
|
171
171
|
<div class="fin-row fin-mini"><label>Semboller</label><button id="finSymBtn" class="fin-sym-select" title="MT5 sembol listesinden seç — tıkla">…</button></div>
|
|
172
|
-
<div class="fin-row">
|
|
173
|
-
<label
|
|
172
|
+
<div class="fin-row fin-roles-row">
|
|
173
|
+
<label>Analiz ekibi</label>
|
|
174
|
+
<div id="finRoles" class="fin-roles" title="Trader'ın yanında koşacak uzman ajanlar — tüm skill + mt5 erişimli, işlem açmazlar"></div>
|
|
175
|
+
</div>
|
|
176
|
+
<div class="fin-row fin-btn-row">
|
|
174
177
|
<button id="finTraderBtn" class="fin-start">▶︎ Ajanı Başlat</button>
|
|
175
178
|
</div>
|
|
176
179
|
<div id="finTraderStatus">Kapalı</div>
|
|
@@ -179,6 +182,10 @@
|
|
|
179
182
|
<div class="fin-card-title">POZİSYONLAR <span id="finPosCount"></span></div>
|
|
180
183
|
<div id="finPosList"><div class="fin-empty">Açık pozisyon yok</div></div>
|
|
181
184
|
</div>
|
|
185
|
+
<div class="fin-card">
|
|
186
|
+
<div class="fin-card-title">BEKLEYEN EMİRLER <span id="finOrdCount"></span></div>
|
|
187
|
+
<div id="finOrdList"><div class="fin-empty">Bekleyen emir yok</div></div>
|
|
188
|
+
</div>
|
|
182
189
|
<div class="fin-card">
|
|
183
190
|
<div class="fin-card-title">
|
|
184
191
|
PİYASA
|
|
@@ -380,6 +387,7 @@
|
|
|
380
387
|
<span class="bot-bar-btns">
|
|
381
388
|
<button id="dmBackBtn" title="Oturum listesine dön" hidden>◀︎ Oturumlar</button>
|
|
382
389
|
<button id="dmColumnBtn" title="Yan sütunda canlı akış olarak da göster">▤︎</button>
|
|
390
|
+
<button id="dmModalDel" title="Bu oturumu sil" hidden><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg></button>
|
|
383
391
|
<button id="dmModalClear" title="Tüm DM geçmişini sil"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg></button>
|
|
384
392
|
<button id="dmModalClose" title="Kapat">✕︎</button>
|
|
385
393
|
</span>
|
package/src/renderer/renderer.js
CHANGED
|
@@ -49,6 +49,7 @@ const els = {
|
|
|
49
49
|
dmDialogList: $('#dmDialogList'),
|
|
50
50
|
dmModalClose: $('#dmModalClose'),
|
|
51
51
|
dmModalClear: $('#dmModalClear'),
|
|
52
|
+
dmModalDel: $('#dmModalDel'),
|
|
52
53
|
dmColumnBtn: $('#dmColumnBtn'),
|
|
53
54
|
dmBackBtn: $('#dmBackBtn'),
|
|
54
55
|
watchBtn: $('#watchBtn'),
|
|
@@ -172,13 +173,15 @@ const els = {
|
|
|
172
173
|
finInterval: $('#finInterval'),
|
|
173
174
|
finMaxLot: $('#finMaxLot'),
|
|
174
175
|
finSymBtn: $('#finSymBtn'),
|
|
176
|
+
finRoles: $('#finRoles'),
|
|
175
177
|
finWatchClear: $('#finWatchClear'),
|
|
176
|
-
finAllow: $('#finAllow'),
|
|
177
178
|
finTraderBtn: $('#finTraderBtn'),
|
|
178
179
|
finTraderDot: $('#finTraderDot'),
|
|
179
180
|
finTraderStatus: $('#finTraderStatus'),
|
|
180
181
|
finPosList: $('#finPosList'),
|
|
181
182
|
finPosCount: $('#finPosCount'),
|
|
183
|
+
finOrdList: $('#finOrdList'),
|
|
184
|
+
finOrdCount: $('#finOrdCount'),
|
|
182
185
|
finSymList: $('#finSymList'),
|
|
183
186
|
finLog: $('#finLog'),
|
|
184
187
|
finSymAdd: $('#finSymAdd'),
|
|
@@ -5035,6 +5038,7 @@ const dmState = {
|
|
|
5035
5038
|
groups: [], // { id, title, members, titles, createdAt, closed, closedAt }
|
|
5036
5039
|
modalThread: null, // modalde açık oturum key'i (null = oturum listesi)
|
|
5037
5040
|
};
|
|
5041
|
+
let dmGroupSyncTimer = null;
|
|
5038
5042
|
|
|
5039
5043
|
function dmGroupOf(id) {
|
|
5040
5044
|
return (dmState.groups || []).find((g) => String(g.id) === String(id)) || null;
|
|
@@ -5058,13 +5062,24 @@ function dmThreadClosed(msgs) {
|
|
|
5058
5062
|
return !!last.closed;
|
|
5059
5063
|
}
|
|
5060
5064
|
|
|
5065
|
+
/* ajan adının yanına KOD (oturum id) — kimin kim olduğunu netleştirir */
|
|
5066
|
+
function dmCode(code) {
|
|
5067
|
+
const c = String(code || '').trim();
|
|
5068
|
+
if (!c) return '';
|
|
5069
|
+
return `<span class="dm-code">${escapeHtml(c.length > 14 ? c.slice(0, 14) + '…' : c)}</span>`;
|
|
5070
|
+
}
|
|
5071
|
+
|
|
5072
|
+
function dmName(title, code) {
|
|
5073
|
+
return `${escapeHtml(String(title || 'Ajan'))}${dmCode(code)}`;
|
|
5074
|
+
}
|
|
5075
|
+
|
|
5061
5076
|
function dmThreadTitle(dm) {
|
|
5062
|
-
if (dm.group) return '👥 ' + String(dm.groupTitle || 'Grup');
|
|
5063
|
-
const a =
|
|
5064
|
-
const b =
|
|
5065
|
-
const pair = a
|
|
5077
|
+
if (dm.group) return '👥 ' + escapeHtml(String(dm.groupTitle || 'Grup'));
|
|
5078
|
+
const a = dmName(dm.fromTitle, dm.from);
|
|
5079
|
+
const b = dmName(dm.toTitle, dm.to);
|
|
5080
|
+
const pair = a + ' ↔ ' + b;
|
|
5066
5081
|
const topic = String(dm.topic || '(genel)');
|
|
5067
|
-
return topic === '(genel)' ? pair : pair + ' · ' + topic;
|
|
5082
|
+
return topic === '(genel)' ? pair : pair + ' · ' + escapeHtml(topic);
|
|
5068
5083
|
}
|
|
5069
5084
|
|
|
5070
5085
|
function dmThreads() {
|
|
@@ -5087,8 +5102,8 @@ function dmTime(iso) {
|
|
|
5087
5102
|
|
|
5088
5103
|
function dmMsgHtml(m) {
|
|
5089
5104
|
const who = m.group
|
|
5090
|
-
?
|
|
5091
|
-
: `${
|
|
5105
|
+
? dmName(m.fromTitle, m.from)
|
|
5106
|
+
: `${dmName(m.fromTitle, m.from)} → ${dmName(m.toTitle, m.to)}`;
|
|
5092
5107
|
return (
|
|
5093
5108
|
`<div class="dmt-msg"><span class="dm-who">${who}</span>` +
|
|
5094
5109
|
`${escapeHtml(String(m.text || ''))}<span class="dm-at">${dmTime(m.at)}</span></div>`
|
|
@@ -5106,17 +5121,20 @@ function dmThreadCard(key, msgs, onClick) {
|
|
|
5106
5121
|
if (last.group) {
|
|
5107
5122
|
const g = dmGroupOf(last.group);
|
|
5108
5123
|
if (g && Array.isArray(g.members) && g.members.length) {
|
|
5109
|
-
const names = g.members.map((m) => (g.titles && g.titles[m]) || m
|
|
5110
|
-
membersLine = `<div class="dmt-members">👥 ${
|
|
5124
|
+
const names = g.members.slice(0, 4).map((m) => dmName((g.titles && g.titles[m]) || m, m));
|
|
5125
|
+
membersLine = `<div class="dmt-members">👥 ${names.join(', ')}</div>`;
|
|
5111
5126
|
}
|
|
5112
5127
|
}
|
|
5113
5128
|
el.innerHTML =
|
|
5114
5129
|
'<div class="dmt-head">' +
|
|
5115
|
-
`<span class="dmt-title">${
|
|
5130
|
+
`<span class="dmt-title">${dmThreadTitle(last)}</span>` +
|
|
5116
5131
|
`<span class="dmt-count">${closed ? 'kapandı' : msgs.length}</span>` +
|
|
5132
|
+
`<button class="dmt-del" title="Bu oturumu sil">✕︎</button>` +
|
|
5117
5133
|
'</div>' +
|
|
5118
5134
|
membersLine +
|
|
5119
5135
|
`<div class="dmt-last">${escapeHtml(String(last.text || '').replace(/\s+/g, ' ').slice(0, 90))} · ${dmTime(last.at)}</div>`;
|
|
5136
|
+
const del = el.querySelector('.dmt-del');
|
|
5137
|
+
if (del) del.addEventListener('click', (ev) => { ev.stopPropagation(); dmDeleteThread(key); });
|
|
5120
5138
|
el.addEventListener('click', onClick);
|
|
5121
5139
|
return el;
|
|
5122
5140
|
}
|
|
@@ -5182,6 +5200,7 @@ function renderDmModal() {
|
|
|
5182
5200
|
list.innerHTML = '<div class="dm-empty">Henüz ajan DM\u2019i yok.<br>Ajanlar agent_dm aracıyla birbirine mesaj attıkça oturumlar birikir.</div>';
|
|
5183
5201
|
if (els.dmDialogTitle) els.dmDialogTitle.textContent = 'AJAN DM — OTURUMLAR';
|
|
5184
5202
|
if (els.dmBackBtn) els.dmBackBtn.hidden = true;
|
|
5203
|
+
if (els.dmModalDel) els.dmModalDel.hidden = true;
|
|
5185
5204
|
return;
|
|
5186
5205
|
}
|
|
5187
5206
|
if (dmState.modalThread) {
|
|
@@ -5191,8 +5210,9 @@ function renderDmModal() {
|
|
|
5191
5210
|
dmState.modalThread = null;
|
|
5192
5211
|
return renderDmModal();
|
|
5193
5212
|
}
|
|
5194
|
-
if (els.dmDialogTitle) els.dmDialogTitle.
|
|
5213
|
+
if (els.dmDialogTitle) els.dmDialogTitle.innerHTML = dmThreadTitle(msgs[msgs.length - 1]);
|
|
5195
5214
|
if (els.dmBackBtn) els.dmBackBtn.hidden = false;
|
|
5215
|
+
if (els.dmModalDel) els.dmModalDel.hidden = false;
|
|
5196
5216
|
list.innerHTML = '<div class="dm-thread-msgs">' + msgs.map(dmMsgHtml).join('') + '</div>';
|
|
5197
5217
|
const box = list.querySelector('.dm-thread-msgs');
|
|
5198
5218
|
if (box) box.scrollTop = box.scrollHeight;
|
|
@@ -5201,6 +5221,7 @@ function renderDmModal() {
|
|
|
5201
5221
|
/* OTURUM LİSTESİ — AKTİF + GEÇMİŞ bölümleri */
|
|
5202
5222
|
if (els.dmDialogTitle) els.dmDialogTitle.textContent = 'AJAN DM — AKTİF & GEÇMİŞ';
|
|
5203
5223
|
if (els.dmBackBtn) els.dmBackBtn.hidden = true;
|
|
5224
|
+
if (els.dmModalDel) els.dmModalDel.hidden = true;
|
|
5204
5225
|
list.innerHTML = '';
|
|
5205
5226
|
dmAppendThreadSections(list, (key) => {
|
|
5206
5227
|
dmState.modalThread = key;
|
|
@@ -5230,6 +5251,22 @@ async function dmClearAll() {
|
|
|
5230
5251
|
toast('Ajan DM geçmişi silindi');
|
|
5231
5252
|
}
|
|
5232
5253
|
|
|
5254
|
+
/* TEK OTURUM silme: yalnız bu sohbetin (ajan çifti + konu / grup) DM'leri gider */
|
|
5255
|
+
async function dmDeleteThread(key) {
|
|
5256
|
+
const msgs = (dmThreads().find(([k]) => k === key) || [null, []])[1];
|
|
5257
|
+
const last = msgs[msgs.length - 1];
|
|
5258
|
+
const label = last
|
|
5259
|
+
? last.group
|
|
5260
|
+
? String(last.groupTitle || 'Grup')
|
|
5261
|
+
: `${last.fromTitle || 'Ajan'} ↔ ${last.toTitle || 'Ajan'}`
|
|
5262
|
+
: 'bu oturum';
|
|
5263
|
+
if (!(await uiConfirm(`"${label}" oturumu silinsin mi?`, 'Sil'))) return;
|
|
5264
|
+
try { await beast.agentDmDeleteThread(key); } catch {}
|
|
5265
|
+
if (dmState.modalThread === key) dmState.modalThread = null;
|
|
5266
|
+
await refreshDmRail();
|
|
5267
|
+
toast('Oturum silindi');
|
|
5268
|
+
}
|
|
5269
|
+
|
|
5233
5270
|
if (els.dmBtn) {
|
|
5234
5271
|
els.dmBtn.addEventListener('click', () => {
|
|
5235
5272
|
/* kapalıyken (dm-hidden VAR) aç: toggle 'hide' SEMANTIĞI tersine çevrilir */
|
|
@@ -5238,6 +5275,11 @@ if (els.dmBtn) {
|
|
|
5238
5275
|
}
|
|
5239
5276
|
if (els.dmModalClose) els.dmModalClose.addEventListener('click', closeDmModal);
|
|
5240
5277
|
if (els.dmModalClear) els.dmModalClear.addEventListener('click', dmClearAll);
|
|
5278
|
+
if (els.dmModalDel) {
|
|
5279
|
+
els.dmModalDel.addEventListener('click', () => {
|
|
5280
|
+
if (dmState.modalThread) dmDeleteThread(dmState.modalThread); // açık oturumu sil
|
|
5281
|
+
});
|
|
5282
|
+
}
|
|
5241
5283
|
if (els.dmBackBtn) {
|
|
5242
5284
|
els.dmBackBtn.addEventListener('click', () => {
|
|
5243
5285
|
dmState.modalThread = null; // oturum listesine dön
|
|
@@ -5613,6 +5655,20 @@ function onEvent(ev) {
|
|
|
5613
5655
|
dmState.dms.push(rec);
|
|
5614
5656
|
if (dmState.dms.length > 400) dmState.dms.splice(0, dmState.dms.length - 400);
|
|
5615
5657
|
}
|
|
5658
|
+
/* GRUP mesajı canlı geldi: grup kaydını (üyeler/başlık/kapandı) sunucudan
|
|
5659
|
+
tazele — kart İLK mesajla birlikte tam grup görünümünde düşer, "önce
|
|
5660
|
+
ayrı ayrı görünür sonra gruba geçer" durumu olmaz */
|
|
5661
|
+
if (rec.group) {
|
|
5662
|
+
clearTimeout(dmGroupSyncTimer);
|
|
5663
|
+
dmGroupSyncTimer = setTimeout(() => {
|
|
5664
|
+
beast
|
|
5665
|
+
.agentDmsList()
|
|
5666
|
+
.then((res) => {
|
|
5667
|
+
if (res && !Array.isArray(res)) dmState.groups = res.groups || [];
|
|
5668
|
+
})
|
|
5669
|
+
.catch(() => {});
|
|
5670
|
+
}, 200);
|
|
5671
|
+
}
|
|
5616
5672
|
if (els.dmOverlay && !els.dmOverlay.hidden) renderDmModal();
|
|
5617
5673
|
if ($('#dmRail') && !$('#dmRail').classList.contains('dm-hidden')) renderDmRail();
|
|
5618
5674
|
return;
|
|
@@ -8820,6 +8876,56 @@ function finRenderPositions(list) {
|
|
|
8820
8876
|
}
|
|
8821
8877
|
}
|
|
8822
8878
|
|
|
8879
|
+
/* MT5 bekleyen emir tipi → etiket + renk sınıfı (0..7) */
|
|
8880
|
+
const FIN_ORDER_TYPES = {
|
|
8881
|
+
0: { t: 'BUY', c: 'buy' },
|
|
8882
|
+
1: { t: 'SELL', c: 'sell' },
|
|
8883
|
+
2: { t: 'BUY LIMIT', c: 'buy' },
|
|
8884
|
+
3: { t: 'SELL LIMIT', c: 'sell' },
|
|
8885
|
+
4: { t: 'BUY STOP', c: 'buy' },
|
|
8886
|
+
5: { t: 'SELL STOP', c: 'sell' },
|
|
8887
|
+
6: { t: 'BUY STP-LMT', c: 'buy' },
|
|
8888
|
+
7: { t: 'SELL STP-LMT', c: 'sell' },
|
|
8889
|
+
};
|
|
8890
|
+
|
|
8891
|
+
function finRenderOrders(list) {
|
|
8892
|
+
if (!els.finOrdList) return;
|
|
8893
|
+
els.finOrdList.textContent = '';
|
|
8894
|
+
if (els.finOrdCount) els.finOrdCount.textContent = list && list.length ? '(' + list.length + ')' : '';
|
|
8895
|
+
if (!list || !list.length) {
|
|
8896
|
+
const d = document.createElement('div');
|
|
8897
|
+
d.className = 'fin-empty';
|
|
8898
|
+
d.textContent = 'Bekleyen emir yok';
|
|
8899
|
+
els.finOrdList.appendChild(d);
|
|
8900
|
+
return;
|
|
8901
|
+
}
|
|
8902
|
+
for (const o of list) {
|
|
8903
|
+
const row = document.createElement('div');
|
|
8904
|
+
row.className = 'fin-pos fin-ord';
|
|
8905
|
+
const ot = FIN_ORDER_TYPES[Number(o.type)] || { t: String(o.type ?? '?'), c: '' };
|
|
8906
|
+
const vol = Number(o.volume_current) || Number(o.volume_initial) || 0;
|
|
8907
|
+
row.innerHTML =
|
|
8908
|
+
'<span class="fp-sym" title="ticket ' + o.ticket + '">' + escapeHtml(String(o.symbol || '?')) + '</span>' +
|
|
8909
|
+
'<span class="fp-dir ' + ot.c + '">' + ot.t + '</span>' +
|
|
8910
|
+
'<span class="fp-meta">' + vol + ' lot @ ' + (Number(o.price_open) || 0) +
|
|
8911
|
+
(o.sl ? ' · SL ' + o.sl : '') + (o.tp ? ' · TP ' + o.tp : '') + '</span>';
|
|
8912
|
+
const btn = document.createElement('button');
|
|
8913
|
+
btn.className = 'fp-close';
|
|
8914
|
+
btn.title = 'Bekleyen emri iptal et';
|
|
8915
|
+
btn.textContent = '✕';
|
|
8916
|
+
btn.addEventListener('click', async () => {
|
|
8917
|
+
const ok = await uiConfirm((o.symbol || '') + ' bekleyen emri iptal edilsin mi? (ticket ' + o.ticket + ')', 'İptal Et');
|
|
8918
|
+
if (!ok) return;
|
|
8919
|
+
const r = await beast.financeCancel(o.ticket).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
|
|
8920
|
+
if (r && r.ok) toast('Emir iptal edildi');
|
|
8921
|
+
else toast('İptal edilemedi: ' + ((r && r.error) || '?'));
|
|
8922
|
+
finSnapshot();
|
|
8923
|
+
});
|
|
8924
|
+
row.appendChild(btn);
|
|
8925
|
+
els.finOrdList.appendChild(row);
|
|
8926
|
+
}
|
|
8927
|
+
}
|
|
8928
|
+
|
|
8823
8929
|
function finRenderSymbols(list) {
|
|
8824
8930
|
if (!els.finSymList) return;
|
|
8825
8931
|
els.finSymList.textContent = '';
|
|
@@ -8885,10 +8991,94 @@ function finTraderInputsSet(cfg) {
|
|
|
8885
8991
|
if (els.finInterval && ae !== els.finInterval) els.finInterval.value = cfg.intervalSec || 120;
|
|
8886
8992
|
if (els.finMaxLot && ae !== els.finMaxLot) els.finMaxLot.value = cfg.maxLot || 0.1;
|
|
8887
8993
|
finSymBtnUpdate();
|
|
8888
|
-
if (els.finAllow && ae !== els.finAllow) els.finAllow.checked = cfg.allowTrading === true;
|
|
8889
8994
|
if (els.finTraderModel && ae !== els.finTraderModel && cfg.traderSel) els.finTraderModel.value = cfg.traderSel;
|
|
8890
8995
|
}
|
|
8891
8996
|
|
|
8997
|
+
/* ANALİZ EKİBİ: trader yanında koşacak uzman ajan rolleri — İKİ MOD:
|
|
8998
|
+
(1) OTOMATİK: sadece SAYI gir → gereklı ajanları sistem kendisi ata;
|
|
8999
|
+
(2) ELLE SEÇ: rol kutucukları (Risk / Teknik / Haber-Makro) çoklu seçilir.
|
|
9000
|
+
Katalog sunucudan (finance snapshot/state.roles) gelir, yerel yedek var. */
|
|
9001
|
+
let finRolesCatalog = [
|
|
9002
|
+
{ id: 'risk', label: 'Risk Ajanı', desc: 'marj/kaldıraç/SL disiplini, exposure ve günlük kayıp hızı denetimi' },
|
|
9003
|
+
{ id: 'technic', label: 'Teknik Analiz', desc: 'trend/yapı/destek-direnç/momentum okuma, AL-SAT-BEKLE önerileri' },
|
|
9004
|
+
{ id: 'macro', label: 'Haber / Makro', desc: 'haber akışı + ekonomik takvim, DXY/emtia bağıntıları, yön eğilimi' },
|
|
9005
|
+
];
|
|
9006
|
+
let finRolesDirty = false; /* tık→kaydet arası snapshot ezmesin */
|
|
9007
|
+
let finRolesSyncTimer = null;
|
|
9008
|
+
|
|
9009
|
+
function finRolesSel() {
|
|
9010
|
+
const box = els.finRoles;
|
|
9011
|
+
return box ? [...box.querySelectorAll('input[data-role]:checked')].map((cb) => cb.dataset.role) : [];
|
|
9012
|
+
}
|
|
9013
|
+
|
|
9014
|
+
function finRenderRoles(cfg) {
|
|
9015
|
+
const box = els.finRoles;
|
|
9016
|
+
if (!box) return;
|
|
9017
|
+
if (finRolesDirty) return; /* bekleyen kullanıcı değişikliği korunur */
|
|
9018
|
+
const auto = !!(cfg && cfg.analysisAuto);
|
|
9019
|
+
const count = Math.max(0, Math.round(Number(cfg && cfg.analysisCount) || 0));
|
|
9020
|
+
const sel = cfg && Array.isArray(cfg.analysisTeam) ? cfg.analysisTeam : [];
|
|
9021
|
+
box.innerHTML = '';
|
|
9022
|
+
|
|
9023
|
+
/* OTOMATİK çipi + sayı girişi */
|
|
9024
|
+
const autoLab = document.createElement('label');
|
|
9025
|
+
autoLab.className = 'fin-role fin-role-auto';
|
|
9026
|
+
autoLab.title = 'Rol seçmeden SAYI gir — gereklı ajanları sistem otomatik atar (teknik → risk → haber sırası)';
|
|
9027
|
+
const autoCb = document.createElement('input');
|
|
9028
|
+
autoCb.type = 'checkbox';
|
|
9029
|
+
autoCb.checked = auto;
|
|
9030
|
+
autoLab.appendChild(autoCb);
|
|
9031
|
+
autoLab.appendChild(document.createTextNode('⚙ Otomatik'));
|
|
9032
|
+
autoCb.addEventListener('change', () => {
|
|
9033
|
+
finRolesDirty = true;
|
|
9034
|
+
finSaveCfg({ analysisAuto: autoCb.checked });
|
|
9035
|
+
toast(autoCb.checked ? 'Analiz ekibi OTOMATİK — sayı kadar gereklı ajan atanır' : 'Analiz ekibi ELLE SEÇİM moduna geçti');
|
|
9036
|
+
clearTimeout(finRolesSyncTimer);
|
|
9037
|
+
finRolesSyncTimer = setTimeout(() => { finRolesDirty = false; finSnapshot(); }, 900);
|
|
9038
|
+
});
|
|
9039
|
+
box.appendChild(autoLab);
|
|
9040
|
+
|
|
9041
|
+
const cnt = document.createElement('input');
|
|
9042
|
+
cnt.type = 'number';
|
|
9043
|
+
cnt.className = 'fin-role-count';
|
|
9044
|
+
cnt.min = '0';
|
|
9045
|
+
cnt.max = String(finRolesCatalog.length);
|
|
9046
|
+
cnt.step = '1';
|
|
9047
|
+
cnt.value = String(count || 0);
|
|
9048
|
+
cnt.title = 'Otomatik mod: kaç uzman ajan koşacak (0 = ekip yok)';
|
|
9049
|
+
cnt.disabled = !auto;
|
|
9050
|
+
cnt.addEventListener('change', () => {
|
|
9051
|
+
finRolesDirty = true;
|
|
9052
|
+
finSaveCfg({ analysisCount: Math.max(0, Math.round(Number(cnt.value) || 0)) });
|
|
9053
|
+
toast('Analiz ekibi: ' + (Math.round(Number(cnt.value) || 0) || 'kapalı') + ' uzman ajan (otomatik)');
|
|
9054
|
+
clearTimeout(finRolesSyncTimer);
|
|
9055
|
+
finRolesSyncTimer = setTimeout(() => { finRolesDirty = false; finSnapshot(); }, 900);
|
|
9056
|
+
});
|
|
9057
|
+
box.appendChild(cnt);
|
|
9058
|
+
|
|
9059
|
+
/* rol kutucukları — otomatik moddayken pasif */
|
|
9060
|
+
for (const r of finRolesCatalog) {
|
|
9061
|
+
const lab = document.createElement('label');
|
|
9062
|
+
lab.className = 'fin-role';
|
|
9063
|
+
const cb = document.createElement('input');
|
|
9064
|
+
cb.type = 'checkbox';
|
|
9065
|
+
cb.dataset.role = r.id;
|
|
9066
|
+
cb.checked = sel.includes(r.id);
|
|
9067
|
+
cb.disabled = auto;
|
|
9068
|
+
cb.title = r.desc + ' — tüm skill + mt5 erişimli, işlem açmaz';
|
|
9069
|
+
lab.appendChild(cb);
|
|
9070
|
+
lab.appendChild(document.createTextNode(r.label));
|
|
9071
|
+
cb.addEventListener('change', () => {
|
|
9072
|
+
finRolesDirty = true;
|
|
9073
|
+
finSaveCfg({ analysisTeam: finRolesSel() });
|
|
9074
|
+
toast(r.label + (cb.checked ? ' ekibe eklendi — sonraki başlatışta koşar' : ' ekipten çıkarıldı'));
|
|
9075
|
+
clearTimeout(finRolesSyncTimer);
|
|
9076
|
+
finRolesSyncTimer = setTimeout(() => { finRolesDirty = false; finSnapshot(); }, 900);
|
|
9077
|
+
});
|
|
9078
|
+
box.appendChild(lab);
|
|
9079
|
+
}
|
|
9080
|
+
}
|
|
9081
|
+
|
|
8892
9082
|
function finRenderTrader(trader, cfg) {
|
|
8893
9083
|
if (!els.finTraderBtn) return;
|
|
8894
9084
|
const on = !!(trader && trader.on);
|
|
@@ -8900,15 +9090,21 @@ function finRenderTrader(trader, cfg) {
|
|
|
8900
9090
|
else els.finTraderDot.classList.add('off');
|
|
8901
9091
|
}
|
|
8902
9092
|
if (els.finTraderStatus) {
|
|
9093
|
+
els.finTraderStatus.classList.toggle('on', on);
|
|
9094
|
+
els.finTraderStatus.classList.toggle('busy', !!(on && trader && trader.busy));
|
|
8903
9095
|
let t = '';
|
|
8904
9096
|
if (trader && trader.lastAt) {
|
|
8905
9097
|
const d = new Date(trader.lastAt);
|
|
8906
9098
|
t = ' · son tur ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
|
|
8907
9099
|
}
|
|
8908
|
-
|
|
9100
|
+
/* ekip bilgisi: otomatik modda sayı, elle seçimde seçili rol sayısı */
|
|
9101
|
+
const team = cfg && cfg.analysisAuto
|
|
9102
|
+
? Math.round(Number(cfg.analysisCount) || 0)
|
|
9103
|
+
: (cfg && Array.isArray(cfg.analysisTeam) ? cfg.analysisTeam.length : 0);
|
|
9104
|
+
const teamTxt = team ? ' · ekip ' + team + ' ajan' + (cfg && cfg.analysisAuto ? ' (oto)' : '') : '';
|
|
8909
9105
|
els.finTraderStatus.textContent = on
|
|
8910
|
-
? (trader.busy ? 'Tur çalışıyor…' : 'Beklemede — sıradaki tur ' + (cfg && cfg.intervalSec ? cfg.intervalSec + ' sn' : '')) + ' · tur #' + (trader.rounds || 0) + ' · ' +
|
|
8911
|
-
: 'Kapalı · ' +
|
|
9106
|
+
? (trader.busy ? 'Tur çalışıyor…' : 'Beklemede — sıradaki tur ' + (cfg && cfg.intervalSec ? cfg.intervalSec + ' sn' : '')) + ' · tur #' + (trader.rounds || 0) + ' · OTONOM' + teamTxt + t
|
|
9107
|
+
: 'Kapalı · OTONOM' + teamTxt;
|
|
8912
9108
|
}
|
|
8913
9109
|
}
|
|
8914
9110
|
|
|
@@ -8945,8 +9141,11 @@ async function finSnapshot() {
|
|
|
8945
9141
|
finSetDot(b.connected ? true : b.running ? 'busy' : false, b);
|
|
8946
9142
|
finRenderAccount(r.account);
|
|
8947
9143
|
finRenderPositions(r.positions || []);
|
|
9144
|
+
finRenderOrders(r.orders || []);
|
|
8948
9145
|
finRenderSymbols(r.symbols || []);
|
|
9146
|
+
if (Array.isArray(r.roles) && r.roles.length) finRolesCatalog = r.roles;
|
|
8949
9147
|
finTraderInputsSet(r.cfg);
|
|
9148
|
+
finRenderRoles(r.cfg);
|
|
8950
9149
|
finRenderTrader(r.trader, r.cfg);
|
|
8951
9150
|
}
|
|
8952
9151
|
|
|
@@ -9042,22 +9241,7 @@ function finSaveCfg(patch) {
|
|
|
9042
9241
|
if (els.finInterval) els.finInterval.addEventListener('change', () => finSaveCfg({ intervalSec: Number(els.finInterval.value) || 120 }));
|
|
9043
9242
|
if (els.finMaxLot) els.finMaxLot.addEventListener('change', () => finSaveCfg({ maxLot: Number(els.finMaxLot.value) || 0.1 }));
|
|
9044
9243
|
if (els.finSymBtn) els.finSymBtn.addEventListener('click', finSymPickerOpen);
|
|
9045
|
-
|
|
9046
|
-
els.finAllow.addEventListener('change', async () => {
|
|
9047
|
-
if (els.finAllow.checked) {
|
|
9048
|
-
const ok = await uiConfirm(
|
|
9049
|
-
'OTOMATİK İŞLEM açılıyor: trade ajanı GERÇEK hesapta emir açabilir/kapatabilir (max lot + pozisyon limitleriyle). Onaylıyor musun?',
|
|
9050
|
-
'Aç',
|
|
9051
|
-
'Vazgeç'
|
|
9052
|
-
);
|
|
9053
|
-
if (!ok) {
|
|
9054
|
-
els.finAllow.checked = false;
|
|
9055
|
-
return;
|
|
9056
|
-
}
|
|
9057
|
-
}
|
|
9058
|
-
finSaveCfg({ allowTrading: els.finAllow.checked });
|
|
9059
|
-
});
|
|
9060
|
-
}
|
|
9244
|
+
/* OTOMATİK İŞLEM onay kutusu KALDIRILDI — trade ajanı daima işlem açabilir */
|
|
9061
9245
|
if (els.finTraderModel) {
|
|
9062
9246
|
els.finTraderModel.addEventListener('change', () => {
|
|
9063
9247
|
finSaveCfg({ traderSel: els.finTraderModel.value });
|
|
@@ -9071,7 +9255,14 @@ if (els.finTraderBtn) {
|
|
|
9071
9255
|
} else {
|
|
9072
9256
|
const r = await beast.financeTraderStart().catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
|
|
9073
9257
|
if (r && !r.ok) toast(r.error || 'Trader başlatılamadı');
|
|
9074
|
-
else if (r && r.ok)
|
|
9258
|
+
else if (r && r.ok) {
|
|
9259
|
+
const cfg = (await beast.financeState().catch(() => null));
|
|
9260
|
+
const c = (cfg && cfg.cfg) || {};
|
|
9261
|
+
const team = c.analysisAuto
|
|
9262
|
+
? Math.round(Number(c.analysisCount) || 0)
|
|
9263
|
+
: finRolesSel().length;
|
|
9264
|
+
toast(team ? 'Trade ajanı + analiz ekibi (' + team + ' uzman) başladı — turlar panelde izlenir' : 'Trade ajanı başladı — turlar panelde izlenir');
|
|
9265
|
+
}
|
|
9075
9266
|
}
|
|
9076
9267
|
finSnapshot();
|
|
9077
9268
|
});
|
package/src/renderer/style.css
CHANGED
|
@@ -2156,6 +2156,15 @@ body.browser-open #settingsDialog { width: min(70vw, calc(100vw - var(--bw, 480p
|
|
|
2156
2156
|
background: var(--accent-dim); color: var(--accent);
|
|
2157
2157
|
border-radius: 8px; padding: 1px 7px; font-weight: 700;
|
|
2158
2158
|
}
|
|
2159
|
+
/* oturum kartındaki TEK SİL butonu */
|
|
2160
|
+
.dmt-del {
|
|
2161
|
+
flex: none; border: none; background: transparent;
|
|
2162
|
+
color: var(--muted); font-size: 11px; line-height: 1;
|
|
2163
|
+
padding: 2px 4px; border-radius: 5px; cursor: pointer;
|
|
2164
|
+
opacity: 0; transition: opacity 0.12s ease;
|
|
2165
|
+
}
|
|
2166
|
+
.dmt:hover .dmt-del { opacity: 0.75; }
|
|
2167
|
+
.dmt-del:hover { opacity: 1; color: #e5484d; background: var(--panel2); }
|
|
2159
2168
|
.dmt-last {
|
|
2160
2169
|
font-size: 11px; margin-top: 4px;
|
|
2161
2170
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
@@ -2202,6 +2211,16 @@ body.browser-open #settingsDialog { width: min(70vw, calc(100vw - var(--bw, 480p
|
|
|
2202
2211
|
color: var(--muted);
|
|
2203
2212
|
margin-top: 2px;
|
|
2204
2213
|
}
|
|
2214
|
+
/* ajan adının yanındaki KOD rozeti (oturum id) */
|
|
2215
|
+
.dm-code {
|
|
2216
|
+
font-size: 9px; font-weight: 700; letter-spacing: 0.5px;
|
|
2217
|
+
color: var(--accent);
|
|
2218
|
+
background: var(--accent-dim);
|
|
2219
|
+
border-radius: 6px; padding: 0 4px; margin-left: 4px;
|
|
2220
|
+
vertical-align: 1px;
|
|
2221
|
+
}
|
|
2222
|
+
.dmt-title .dm-code { margin-left: 3px; }
|
|
2223
|
+
.dmt-members .dm-code { margin-left: 2px; }
|
|
2205
2224
|
|
|
2206
2225
|
/* ---------- AJAN DM modalı (✉) ---------- */
|
|
2207
2226
|
#dmOverlay {
|
|
@@ -4845,30 +4864,91 @@ body.finance-mode #composerWrap { margin-right: var(--finW, 400px); }
|
|
|
4845
4864
|
text-overflow: ellipsis;
|
|
4846
4865
|
}
|
|
4847
4866
|
.fin-check input { flex: none; width: auto; }
|
|
4867
|
+
/* ANALİZ EKİBİ: rol seçim kutuları (Risk / Teknik / Makro) */
|
|
4868
|
+
.fin-roles-row label { flex: none; font-size: 10px; color: var(--muted); min-width: 44px; }
|
|
4869
|
+
.fin-roles {
|
|
4870
|
+
flex: 1; min-width: 0;
|
|
4871
|
+
display: flex; flex-wrap: wrap; gap: 4px;
|
|
4872
|
+
}
|
|
4873
|
+
.fin-role {
|
|
4874
|
+
flex: none;
|
|
4875
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
4876
|
+
font-size: 10.5px; color: var(--text);
|
|
4877
|
+
background: var(--panel2);
|
|
4878
|
+
border: 1px solid var(--border);
|
|
4879
|
+
border-radius: 8px;
|
|
4880
|
+
padding: 2px 8px 2px 5px;
|
|
4881
|
+
cursor: pointer;
|
|
4882
|
+
user-select: none;
|
|
4883
|
+
}
|
|
4884
|
+
.fin-role:hover { border-color: var(--accent); }
|
|
4885
|
+
.fin-role:has(input:checked) { border-color: var(--accent); color: var(--accent); background: var(--accent-dim); }
|
|
4886
|
+
.fin-role input { flex: none; width: auto; margin: 0; }
|
|
4887
|
+
.fin-role input:disabled { opacity: 0.45; cursor: default; }
|
|
4888
|
+
.fin-role-auto { font-weight: 700; }
|
|
4889
|
+
/* Otomatik moddaki SAYI girişi */
|
|
4890
|
+
.fin-role-count {
|
|
4891
|
+
flex: none; width: 44px;
|
|
4892
|
+
background: var(--panel2); color: var(--text);
|
|
4893
|
+
border: 1px solid var(--border); border-radius: 8px;
|
|
4894
|
+
font-size: 11px; font-weight: 700; text-align: center;
|
|
4895
|
+
padding: 2px 3px;
|
|
4896
|
+
}
|
|
4897
|
+
.fin-role-count:focus { border-color: var(--accent); outline: none; }
|
|
4898
|
+
.fin-role-count:disabled { opacity: 0.45; }
|
|
4848
4899
|
.fin-start {
|
|
4849
4900
|
flex: none;
|
|
4850
4901
|
background: var(--accent);
|
|
4851
|
-
border:
|
|
4852
|
-
|
|
4902
|
+
border: 1px solid var(--accent);
|
|
4903
|
+
/* dark modda accent beyazımsı olur — sabit beyaz yazı butonda kaybolurdu;
|
|
4904
|
+
--accent-contrast iki temada da doğru kontrastı verir (siyah↔beyaz) */
|
|
4905
|
+
color: var(--accent-contrast);
|
|
4853
4906
|
font-size: 11px;
|
|
4854
|
-
font-weight:
|
|
4855
|
-
|
|
4907
|
+
font-weight: 800;
|
|
4908
|
+
letter-spacing: 0.2px;
|
|
4909
|
+
padding: 6px 14px;
|
|
4856
4910
|
border-radius: 7px;
|
|
4857
4911
|
cursor: pointer;
|
|
4912
|
+
transition: filter 0.12s ease, box-shadow 0.12s ease, transform 0.06s ease;
|
|
4913
|
+
}
|
|
4914
|
+
.fin-start:hover { filter: brightness(1.08); box-shadow: 0 1px 6px rgba(0, 0, 0, 0.22); }
|
|
4915
|
+
.fin-start:active { transform: translateY(1px); filter: brightness(0.96); }
|
|
4916
|
+
.fin-start.stop {
|
|
4917
|
+
background: #ef4444;
|
|
4918
|
+
border-color: #ef4444;
|
|
4919
|
+
color: #fff; /* kırmızı zeminde beyaz her iki temada da okunur */
|
|
4920
|
+
}
|
|
4921
|
+
|
|
4922
|
+
/* TRADE AJANI kartı — vurgulu görünüm (accent çerçeve + yumuşak zemin) */
|
|
4923
|
+
.fin-card-trader {
|
|
4924
|
+
border-color: var(--accent);
|
|
4925
|
+
box-shadow: 0 0 0 1px var(--accent-dim), 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
4926
|
+
background: linear-gradient(180deg, var(--accent-dim), transparent 46%), var(--panel);
|
|
4858
4927
|
}
|
|
4859
|
-
.fin-
|
|
4860
|
-
.fin-
|
|
4928
|
+
.fin-card-trader .fin-card-title { color: var(--text); }
|
|
4929
|
+
.fin-card-trader .fin-row { margin-bottom: 8px; }
|
|
4930
|
+
.fin-card-trader .fin-btn-row { margin-top: 2px; }
|
|
4931
|
+
.fin-btn-row .fin-start { flex: 1; width: 100%; padding: 7px 14px; }
|
|
4861
4932
|
|
|
4862
4933
|
#finTraderStatus {
|
|
4863
|
-
margin-top:
|
|
4864
|
-
font-size:
|
|
4934
|
+
margin-top: 8px;
|
|
4935
|
+
font-size: 10.5px;
|
|
4865
4936
|
color: var(--muted);
|
|
4937
|
+
background: var(--panel2);
|
|
4938
|
+
border: 1px solid var(--border);
|
|
4939
|
+
border-radius: 7px;
|
|
4940
|
+
padding: 5px 8px;
|
|
4866
4941
|
white-space: nowrap;
|
|
4867
4942
|
overflow: hidden;
|
|
4868
4943
|
text-overflow: ellipsis;
|
|
4869
4944
|
}
|
|
4945
|
+
#finTraderStatus::before { content: '● '; font-size: 9px; color: var(--muted); }
|
|
4946
|
+
#finTraderStatus.on { color: var(--text); border-color: var(--accent); }
|
|
4947
|
+
#finTraderStatus.on::before { color: #22c55e; }
|
|
4948
|
+
#finTraderStatus.on.busy { color: var(--accent); }
|
|
4949
|
+
#finTraderStatus.on.busy::before { color: #f59e0b; }
|
|
4870
4950
|
|
|
4871
|
-
#finPosList, #finSymList { display: flex; flex-direction: column; gap: 5px; }
|
|
4951
|
+
#finPosList, #finOrdList, #finSymList { display: flex; flex-direction: column; gap: 5px; }
|
|
4872
4952
|
.fin-empty { font-size: 11px; color: var(--muted); padding: 2px 0 4px; }
|
|
4873
4953
|
|
|
4874
4954
|
.fin-pos {
|
|
@@ -4907,6 +4987,8 @@ body.finance-mode #composerWrap { margin-right: var(--finW, 400px); }
|
|
|
4907
4987
|
border-radius: 5px;
|
|
4908
4988
|
}
|
|
4909
4989
|
.fin-pos .fp-close:hover { background: rgba(239, 68, 68, 0.15); color: #ef4444; }
|
|
4990
|
+
/* bekleyen emir satırı: amber sol şerit ile pozisyondan ayrışır */
|
|
4991
|
+
.fin-pos.fin-ord { border-left: 2px solid #f59e0b; }
|
|
4910
4992
|
|
|
4911
4993
|
.fin-sym {
|
|
4912
4994
|
display: flex;
|