beast-agent 2.24.2 → 2.24.5
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 +122 -23
- package/src/main.js +116 -41
- package/src/preload.js +1 -0
- package/src/renderer/index.html +5 -1
- package/src/renderer/renderer.js +174 -33
- package/src/renderer/style.css +66 -6
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -378,6 +378,8 @@ class Engine {
|
|
|
378
378
|
} catch {
|
|
379
379
|
this._agentDms = [];
|
|
380
380
|
}
|
|
381
|
+
/* restart barışı: koşan üyesi kalmayan ekip/DM gruplarını kapat */
|
|
382
|
+
try { this._agentDmReconcile(); } catch {}
|
|
381
383
|
/* superyorizyon döngüsü: takılan/uzun süren ajanı CEO'ya bildirir */
|
|
382
384
|
this._supTimer = setInterval(() => {
|
|
383
385
|
try { this._supervise(); } catch {}
|
|
@@ -1178,9 +1180,11 @@ class Engine {
|
|
|
1178
1180
|
/* Beast Finance oturumu: kalıcı izolasyon etiketi (finoff kapatır) */
|
|
1179
1181
|
session.finance = true;
|
|
1180
1182
|
session.financeTrader = !!rec.trader;
|
|
1183
|
+
if (rec.role) session.financeRole = String(rec.role);
|
|
1181
1184
|
} else if (rec.t === 'finoff') {
|
|
1182
1185
|
session.finance = false;
|
|
1183
1186
|
session.financeTrader = false;
|
|
1187
|
+
delete session.financeRole;
|
|
1184
1188
|
} else if (rec.t === 'msg') {
|
|
1185
1189
|
delete rec.t;
|
|
1186
1190
|
session.messages.push(rec);
|
|
@@ -1331,12 +1335,17 @@ class Engine {
|
|
|
1331
1335
|
|
|
1332
1336
|
/* Beast Finance etiketi: oturumu finance sohbeti olarak KALICI işaretler
|
|
1333
1337
|
(jsonl'e 'fin' satırı düşer — restart sonrası da hatırlanır) */
|
|
1334
|
-
markFinance(id, trader = false) {
|
|
1338
|
+
markFinance(id, trader = false, role = '') {
|
|
1335
1339
|
const s = this._load(String(id));
|
|
1336
1340
|
s.finance = true;
|
|
1337
1341
|
s.financeTrader = !!trader;
|
|
1342
|
+
if (role) s.financeRole = String(role);
|
|
1343
|
+
else delete s.financeRole;
|
|
1338
1344
|
try {
|
|
1339
|
-
fs.appendFileSync(
|
|
1345
|
+
fs.appendFileSync(
|
|
1346
|
+
this._file(s.id),
|
|
1347
|
+
JSON.stringify({ t: 'fin', trader: !!trader, role: String(role || ''), at: nowIso() }) + '\n'
|
|
1348
|
+
);
|
|
1340
1349
|
} catch {}
|
|
1341
1350
|
this.cache.set(String(s.id), s);
|
|
1342
1351
|
return s;
|
|
@@ -1347,6 +1356,7 @@ class Engine {
|
|
|
1347
1356
|
const s = this._load(String(id));
|
|
1348
1357
|
s.finance = false;
|
|
1349
1358
|
s.financeTrader = false;
|
|
1359
|
+
delete s.financeRole;
|
|
1350
1360
|
try {
|
|
1351
1361
|
fs.appendFileSync(this._file(s.id), JSON.stringify({ t: 'finoff', at: nowIso() }) + '\n');
|
|
1352
1362
|
} catch {}
|
|
@@ -1846,7 +1856,7 @@ class Engine {
|
|
|
1846
1856
|
|
|
1847
1857
|
/* Beast Finance: MT5 trading ajanı — chat copilot'ı VEYA otonom trader.
|
|
1848
1858
|
Oturum alanları (main enjekte eder): financeTrader, financeAuto,
|
|
1849
|
-
financeSymbols, financeStrategy, financeLimits */
|
|
1859
|
+
financeSymbols, financeStrategy, financeLimits, financeRole */
|
|
1850
1860
|
buildFinanceSystem(session) {
|
|
1851
1861
|
const nowD = new Date();
|
|
1852
1862
|
const localDate = nowD.toLocaleDateString('tr-TR');
|
|
@@ -1873,6 +1883,17 @@ class Engine {
|
|
|
1873
1883
|
const modeBlock2 = isWorker
|
|
1874
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.'
|
|
1875
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) || '')] || '';
|
|
1876
1897
|
return (
|
|
1877
1898
|
(mem.soul ? mem.soul.trim() + '\n\n' : '') +
|
|
1878
1899
|
'Sen BEAST FİNANS\u2019sın — bilgisayardaki MetaTrader 5 (MT5) terminaline köprüyle bağlı trading ajanı.\n' +
|
|
@@ -1880,6 +1901,7 @@ class Engine {
|
|
|
1880
1901
|
'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' +
|
|
1881
1902
|
'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' +
|
|
1882
1903
|
modeBlock2 + '\n' +
|
|
1904
|
+
(roleBlock ? roleBlock + '\n' : '') +
|
|
1883
1905
|
'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' +
|
|
1884
1906
|
'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' +
|
|
1885
1907
|
'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' +
|
|
@@ -6306,25 +6328,36 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
|
|
|
6306
6328
|
const at = new Date().toISOString();
|
|
6307
6329
|
|
|
6308
6330
|
/* GRUP SOHBETİ: kur/yeniden kullan, hedefi üye yap, mesaj HER ÜYEYE düşer.
|
|
6309
|
-
Ajanlar ortak kararları burada tartışır; iş bitince sohbet kapanır.
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6331
|
+
Ajanlar ortak kararları burada tartışır; iş bitince sohbet kapanır.
|
|
6332
|
+
groupName verilmemişse ama iki taraf da AYNI AÇIK grubun üyesiyse mesaj
|
|
6333
|
+
YİNE GRUBA düşer — üye üyeye 1:1 thread'lere ayrı ayrı dağılmaz; ekibin
|
|
6334
|
+
tüm iç trafiği TEK grup sohbetinde canlı toplanır. */
|
|
6335
|
+
const shared = groupName || !target ? null : this._agentDmSharedGroup(fromSid, target);
|
|
6336
|
+
let gid = '';
|
|
6337
|
+
if (groupName || shared) {
|
|
6338
|
+
let g;
|
|
6339
|
+
if (groupName) {
|
|
6340
|
+
/* gid Türkçe-kararsız normalize: ALTIN/Altın/altın aynı grup */
|
|
6341
|
+
gid =
|
|
6342
|
+
'grp:' +
|
|
6343
|
+
groupName
|
|
6344
|
+
.toLowerCase()
|
|
6345
|
+
.replace(/\u0307/g, '')
|
|
6346
|
+
.replace(/ı/g, 'i')
|
|
6347
|
+
.replace(/ğ/g, 'g')
|
|
6348
|
+
.replace(/ü/g, 'u')
|
|
6349
|
+
.replace(/ş/g, 's')
|
|
6350
|
+
.replace(/ö/g, 'o')
|
|
6351
|
+
.replace(/ç/g, 'c');
|
|
6352
|
+
g = this._agentGroups.get(gid);
|
|
6353
|
+
if (!g) {
|
|
6354
|
+
if (!target) return { ok: false, error: 'yeni grup için to ile en az bir ajan belirt' };
|
|
6355
|
+
g = { id: gid, title: groupName, members: [], titles: {}, createdAt: at, closed: false };
|
|
6356
|
+
this._agentGroups.set(gid, g);
|
|
6357
|
+
}
|
|
6358
|
+
} else {
|
|
6359
|
+
g = shared;
|
|
6360
|
+
gid = String(g.id || '');
|
|
6328
6361
|
}
|
|
6329
6362
|
g.titles = g.titles || {};
|
|
6330
6363
|
const addMember = (sid, title) => {
|
|
@@ -6391,6 +6424,20 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
|
|
|
6391
6424
|
}
|
|
6392
6425
|
};
|
|
6393
6426
|
|
|
6427
|
+
/* İki ajanın ORTAK üyesi olduğu AÇIK grup var mı — 1:1 DM'leri grup içine
|
|
6428
|
+
yönlendirirken kullanılır (ekip içi trafik tek thread'de toplanır). */
|
|
6429
|
+
Engine.prototype._agentDmSharedGroup = function (a, b) {
|
|
6430
|
+
a = String(a || '');
|
|
6431
|
+
b = String(b || '');
|
|
6432
|
+
if (!a || !b) return null;
|
|
6433
|
+
for (const [, g] of this._agentGroups || new Map()) {
|
|
6434
|
+
if (g.closed) continue;
|
|
6435
|
+
const mem = g.members || [];
|
|
6436
|
+
if (mem.includes(a) && mem.includes(b)) return g;
|
|
6437
|
+
}
|
|
6438
|
+
return null;
|
|
6439
|
+
};
|
|
6440
|
+
|
|
6394
6441
|
Engine.prototype._pushAgentDm = function (dm) {
|
|
6395
6442
|
this._agentDms = this._agentDms || [];
|
|
6396
6443
|
this._agentDms.push(dm);
|
|
@@ -6525,7 +6572,11 @@ Engine.prototype._agentDmClose = function (sid) {
|
|
|
6525
6572
|
changed = true;
|
|
6526
6573
|
}
|
|
6527
6574
|
}
|
|
6528
|
-
if (changed)
|
|
6575
|
+
if (changed) {
|
|
6576
|
+
this._persistAgentDms();
|
|
6577
|
+
/* panele CANLI haber: sohbet(ler) kapandı — AKTİF'ten GEÇMİŞ'e düşsün */
|
|
6578
|
+
try { this.emit({ type: 'agent-dm-closed', sessionId: sid }); } catch {}
|
|
6579
|
+
}
|
|
6529
6580
|
};
|
|
6530
6581
|
|
|
6531
6582
|
Engine.prototype.agentDmsList = function () {
|
|
@@ -6534,6 +6585,28 @@ Engine.prototype.agentDmsList = function () {
|
|
|
6534
6585
|
return { dms: [...(this._agentDms || [])].slice(-600), groups };
|
|
6535
6586
|
};
|
|
6536
6587
|
|
|
6588
|
+
/* RESTART BARİŞİ: açılışta grupları denetle — hiçbir üye ajan koşmuyorsa
|
|
6589
|
+
grup sohbeti KAPANIR (uygulama kapanırken yarım kalan ekip sohbetleri
|
|
6590
|
+
sonsuza dek "aktif" görünmesin; geçmiş panelde korunur). */
|
|
6591
|
+
Engine.prototype._agentDmReconcile = function () {
|
|
6592
|
+
const running = new Set();
|
|
6593
|
+
for (const [sid, j] of this._bgJobs || new Map()) {
|
|
6594
|
+
if (j && j.status === 'running') running.add(String(sid));
|
|
6595
|
+
}
|
|
6596
|
+
let changed = false;
|
|
6597
|
+
const now = new Date().toISOString();
|
|
6598
|
+
for (const [, g] of this._agentGroups || new Map()) {
|
|
6599
|
+
if (g.closed) continue;
|
|
6600
|
+
const anyRunning = (g.members || []).some((m) => running.has(String(m)));
|
|
6601
|
+
if (!anyRunning) {
|
|
6602
|
+
g.closed = true;
|
|
6603
|
+
g.closedAt = now;
|
|
6604
|
+
changed = true;
|
|
6605
|
+
}
|
|
6606
|
+
}
|
|
6607
|
+
if (changed) this._persistAgentDms();
|
|
6608
|
+
};
|
|
6609
|
+
|
|
6537
6610
|
Engine.prototype.agentDmsClear = function () {
|
|
6538
6611
|
this._agentDms = [];
|
|
6539
6612
|
this._agentGroups = new Map();
|
|
@@ -6541,6 +6614,32 @@ Engine.prototype.agentDmsClear = function () {
|
|
|
6541
6614
|
return { ok: true };
|
|
6542
6615
|
};
|
|
6543
6616
|
|
|
6617
|
+
/* TEK OTURUM SİLME — renderer'daki dmThreadOf() ile BİREBİR AYNI anahtar
|
|
6618
|
+
hesabı: verilen key'e düşen TÜM DM'ler silinir. Grup sohbetiyse ve grup
|
|
6619
|
+
KAPALIYSA grup kaydı da kalkar; koşan ekip grubunun kaydı korunur ki
|
|
6620
|
+
süren işin zorunlu grup akışı bozulmasın. */
|
|
6621
|
+
Engine.prototype._agentDmKeyOf = function (dm) {
|
|
6622
|
+
if (dm && dm.group) return 'G|' + String(dm.group);
|
|
6623
|
+
const a = String((dm && dm.from) || '');
|
|
6624
|
+
const b = String((dm && dm.to) || '');
|
|
6625
|
+
const pair = a < b ? a + '|' + b : b + '|' + a;
|
|
6626
|
+
return pair + '|' + String((dm && dm.topic) || '(genel)').toLowerCase();
|
|
6627
|
+
};
|
|
6628
|
+
|
|
6629
|
+
Engine.prototype.agentDmDeleteThread = function (key) {
|
|
6630
|
+
key = String(key || '');
|
|
6631
|
+
if (!key) return { ok: false, error: 'key gerekli' };
|
|
6632
|
+
const before = (this._agentDms || []).length;
|
|
6633
|
+
this._agentDms = (this._agentDms || []).filter((dm) => this._agentDmKeyOf(dm) !== key);
|
|
6634
|
+
const removed = before - this._agentDms.length;
|
|
6635
|
+
if (key.startsWith('G|')) {
|
|
6636
|
+
const g = this._agentGroups && this._agentGroups.get(key.slice(2));
|
|
6637
|
+
if (g && g.closed) this._agentGroups.delete(g.id);
|
|
6638
|
+
}
|
|
6639
|
+
if (removed) this._persistAgentDms();
|
|
6640
|
+
return { ok: true, removed };
|
|
6641
|
+
};
|
|
6642
|
+
|
|
6544
6643
|
module.exports = Engine;
|
|
6545
6644
|
module.exports.Engine = Engine;
|
|
6546
6645
|
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,11 +8518,14 @@ 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';
|
|
8521
|
+
const roleDef = finRoleDef(s.financeRole);
|
|
8490
8522
|
engine._bgJobs.set(String(s.id), {
|
|
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,
|
|
@@ -8510,29 +8544,33 @@ function finAgentRegisterBg(s, symbols) {
|
|
|
8510
8544
|
} catch {}
|
|
8511
8545
|
}
|
|
8512
8546
|
|
|
8513
|
-
function finApplyTraderFields(s, symbolsOverride) {
|
|
8547
|
+
function finApplyTraderFields(s, symbolsOverride, role) {
|
|
8514
8548
|
const f = finCfg();
|
|
8549
|
+
const r = String(role || s.financeRole || '');
|
|
8515
8550
|
s.finance = true;
|
|
8516
8551
|
s.financeTrader = true;
|
|
8517
|
-
s.
|
|
8552
|
+
s.financeRole = r;
|
|
8553
|
+
/* rol ajanları ASLA işlem açmaz — trade ajanı ve işçileri DAIMA açabilir */
|
|
8554
|
+
s.financeAuto = !r;
|
|
8518
8555
|
s.financeSymbols = Array.isArray(symbolsOverride) && symbolsOverride.length ? symbolsOverride : f.symbols;
|
|
8519
8556
|
s.financeStrategy = String(f.strategy || '');
|
|
8520
8557
|
s.financeLimits = { maxLot: f.maxLot, maxPositions: f.maxPositions };
|
|
8521
8558
|
engine.cache.set(String(s.id), s);
|
|
8522
8559
|
}
|
|
8523
8560
|
|
|
8524
|
-
/* Yeni finance ajanı aç (ana trader YA DA sembol işçisi) */
|
|
8525
|
-
function finAgentCreate(symbols, isMain) {
|
|
8561
|
+
/* Yeni finance ajanı aç (ana trader YA DA sembol işçisi YA DA analiz ekibi rolü) */
|
|
8562
|
+
function finAgentCreate(symbols, isMain, role) {
|
|
8526
8563
|
const f = finCfg();
|
|
8527
|
-
const s = finAgentSession(symbols, isMain);
|
|
8564
|
+
const s = finAgentSession(symbols, isMain, role);
|
|
8528
8565
|
const record = {
|
|
8529
8566
|
symbols: symbols && symbols.length ? symbols.slice(0, 10) : [...(f.symbols || [])],
|
|
8530
8567
|
main: !!isMain,
|
|
8568
|
+
role: String(role || ''),
|
|
8531
8569
|
round: 0,
|
|
8532
8570
|
timer: null,
|
|
8533
8571
|
};
|
|
8534
8572
|
financeState.agents.set(String(s.id), record);
|
|
8535
|
-
finApplyTraderFields(s, record.symbols);
|
|
8573
|
+
finApplyTraderFields(s, record.symbols, record.role);
|
|
8536
8574
|
finAgentRegisterBg(s, record.symbols);
|
|
8537
8575
|
return { s, agent: record };
|
|
8538
8576
|
}
|
|
@@ -8566,18 +8604,22 @@ function finAgentStop(sid, reason) {
|
|
|
8566
8604
|
function finTraderBrief(agent) {
|
|
8567
8605
|
const f = finCfg();
|
|
8568
8606
|
const syms = agent && agent.symbols && agent.symbols.length ? agent.symbols.join(', ') : (f.symbols || []).join(', ');
|
|
8569
|
-
const
|
|
8607
|
+
const roleDef = finRoleDef(agent && agent.role);
|
|
8608
|
+
const who = agent && agent.main ? 'TRADER' : roleDef ? roleDef.label.toUpperCase() : 'FINANCE AJANI';
|
|
8570
8609
|
return [
|
|
8571
8610
|
`Beast Finance ${who} başlatıldı — ilk tur: strateji çerçeveni kur ve piyasa taramasını yap.`,
|
|
8572
8611
|
`Odak semboller: ${syms || '(boş — mt5_status ile terminale bak, mantıklı semboller seç)'}`,
|
|
8573
8612
|
`Tur aralığı: ${f.intervalSec} sn · Max lot: ${f.maxLot} · Max eşzamanlı pozisyon: ${f.maxPositions}`,
|
|
8574
|
-
|
|
8575
|
-
?
|
|
8576
|
-
: 'Otomatik işlem
|
|
8577
|
-
|
|
8613
|
+
roleDef
|
|
8614
|
+
? `UZMANLIK: ${roleDef.desc} — raporlarını bu çerçevede yaz; İŞLEM AÇMA, yalnız analiz + net öneri üret.`
|
|
8615
|
+
: 'Otomatik işlem AÇIK (daima): mt5_trade/mt5_close/mt5_modify/mt5_pending kullanabilirsin (limitler sistemce zorlanır).',
|
|
8616
|
+
roleDef
|
|
8617
|
+
? '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.'
|
|
8618
|
+
: f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : 'Strateji notu yok: trend + destek/direnç + momentum ile temel okuma yap.',
|
|
8619
|
+
!roleDef && f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : '',
|
|
8578
8620
|
'Bu turda: mt5_status → hesap/pozisyon/fiyat verisi → değerlendirme → kararlar (veya BEKLE: sebep) → kısa rapor.',
|
|
8579
8621
|
'Diğer finance/paralel ajanlarla koordinasyon için agent_dm aracı var (to: ajan başlığı anahtar kelimesi).',
|
|
8580
|
-
].join('\n');
|
|
8622
|
+
].filter(Boolean).join('\n');
|
|
8581
8623
|
}
|
|
8582
8624
|
|
|
8583
8625
|
/* Her tur öncesi CHAT AJANINDAN plan iste: aynı motorun odaklı alt-ajanı
|
|
@@ -8597,7 +8639,7 @@ async function finConsultPlan(f, agent, sid) {
|
|
|
8597
8639
|
if (ps && ps.ok) positions = (ps.data && ps.data.positions) || [];
|
|
8598
8640
|
}
|
|
8599
8641
|
} catch {}
|
|
8600
|
-
const auto =
|
|
8642
|
+
const auto = 'AÇIK (işlem açabilir — limitler zorlanır)';
|
|
8601
8643
|
const task =
|
|
8602
8644
|
'Beast Finance ajanı yeni tura giriyor. SEN mt5_* araçlarını KULLANMA, işlem AÇMA — ' +
|
|
8603
8645
|
'sana düşen iş: semboller için bu turun kısa işlem PLANINI üretmek ' +
|
|
@@ -8643,15 +8685,16 @@ function finAgentRound(sid) {
|
|
|
8643
8685
|
agent.round += 1;
|
|
8644
8686
|
if (agent.main) financeState.traderRounds = agent.round;
|
|
8645
8687
|
const f = finCfg();
|
|
8688
|
+
const roleDef = finRoleDef(agent.role);
|
|
8646
8689
|
try {
|
|
8647
8690
|
const s = engine.cache.get(String(sid));
|
|
8648
|
-
if (s) finApplyTraderFields(s, agent.symbols);
|
|
8691
|
+
if (s) finApplyTraderFields(s, agent.symbols, agent.role);
|
|
8649
8692
|
} catch {}
|
|
8650
|
-
const auto =
|
|
8651
|
-
?
|
|
8652
|
-
: '
|
|
8693
|
+
const auto = roleDef
|
|
8694
|
+
? `UZMANLIK: ${roleDef.desc} — İŞLEM AÇMA, yalnız analiz + net öneri.`
|
|
8695
|
+
: 'İşlem açabilirsin — limitlere uy, SL\u2019siz pozisyon bırakma.';
|
|
8653
8696
|
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.`;
|
|
8697
|
+
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
8698
|
const launch = (planBlock) => {
|
|
8656
8699
|
if (!financeState.agents.has(String(sid))) return;
|
|
8657
8700
|
const ok = engine.send(sid, planBlock + round, { userAction: false });
|
|
@@ -8664,8 +8707,8 @@ function finAgentRound(sid) {
|
|
|
8664
8707
|
agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, 30000);
|
|
8665
8708
|
}
|
|
8666
8709
|
};
|
|
8667
|
-
if (f.consultChat === false) {
|
|
8668
|
-
/* danışma
|
|
8710
|
+
if (f.consultChat === false || roleDef) {
|
|
8711
|
+
/* danışma kapalı ya da rol ajanı (plan trader'a yöneliktir): tur doğrudan başlar */
|
|
8669
8712
|
launch('');
|
|
8670
8713
|
return;
|
|
8671
8714
|
}
|
|
@@ -8703,6 +8746,7 @@ ipcMain.handle('finance:state', async () => {
|
|
|
8703
8746
|
ok: true,
|
|
8704
8747
|
mode: financeState.mode,
|
|
8705
8748
|
cfg: f,
|
|
8749
|
+
roles: FIN_ROLES,
|
|
8706
8750
|
bridge: mt5bridge.status(),
|
|
8707
8751
|
trader: { on: financeState.traderOn, busy, rounds: financeState.traderRounds, lastAt: financeState.lastRoundAt, sid: financeState.traderSid },
|
|
8708
8752
|
models: engine ? engine.publicState().models : [],
|
|
@@ -8733,6 +8777,7 @@ ipcMain.handle('finance:snapshot', async () => {
|
|
|
8733
8777
|
positions,
|
|
8734
8778
|
symbols,
|
|
8735
8779
|
cfg: f,
|
|
8780
|
+
roles: FIN_ROLES,
|
|
8736
8781
|
trader: { on: financeState.traderOn, busy, rounds: financeState.traderRounds, lastAt: financeState.lastRoundAt },
|
|
8737
8782
|
};
|
|
8738
8783
|
});
|
|
@@ -8764,15 +8809,8 @@ ipcMain.handle('finance:mode', async (_e, payload) => {
|
|
|
8764
8809
|
}
|
|
8765
8810
|
} catch {}
|
|
8766
8811
|
}
|
|
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
|
-
}
|
|
8812
|
+
/* OTOMATİK TRADER KAPALI: finance moduna geçmek trader'ı KENDİLİĞİNDEN
|
|
8813
|
+
başlatmaz — kullanıcı TRADE AJANI kartındaki ▶ ile elle başlatır */
|
|
8776
8814
|
return { ok: true, mode: financeState.mode, needNew };
|
|
8777
8815
|
});
|
|
8778
8816
|
|
|
@@ -8802,8 +8840,11 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
|
|
|
8802
8840
|
if (p.intervalSec !== undefined) f.intervalSec = Math.max(30, Math.min(3600, Math.round(Number(p.intervalSec) || 120)));
|
|
8803
8841
|
if (p.maxLot !== undefined) f.maxLot = Math.max(0.01, Math.min(100, Number(p.maxLot) || 0.1));
|
|
8804
8842
|
if (p.maxPositions !== undefined) f.maxPositions = Math.max(1, Math.min(20, Math.round(Number(p.maxPositions) || 3)));
|
|
8805
|
-
|
|
8843
|
+
/* allowTrading artık AYARLANMAZ — daima true (finCfg zorlar) */
|
|
8806
8844
|
if (p.strategy !== undefined) f.strategy = String(p.strategy || '').slice(0, 2000);
|
|
8845
|
+
if (p.analysisTeam !== undefined) f.analysisTeam = finRolesValid(p.analysisTeam);
|
|
8846
|
+
if (p.analysisAuto !== undefined) f.analysisAuto = !!p.analysisAuto;
|
|
8847
|
+
if (p.analysisCount !== undefined) f.analysisCount = Math.max(0, Math.min(FIN_ROLES_AUTO.length, Math.round(Number(p.analysisCount) || 0)));
|
|
8807
8848
|
if (p.pythonPath !== undefined) f.pythonPath = String(p.pythonPath || '').trim();
|
|
8808
8849
|
if (p.terminalPath !== undefined) f.terminalPath = String(p.terminalPath || '').trim();
|
|
8809
8850
|
if (p.traderSel !== undefined) {
|
|
@@ -8860,24 +8901,58 @@ async function financeTraderStart() {
|
|
|
8860
8901
|
financeState.lastRoundAt = Date.now();
|
|
8861
8902
|
financeLog('[trader] başlatıldı (model: ' + (f.traderSel || 'genel aktif model') + ')');
|
|
8862
8903
|
finPush('trader', { state: 'running', round: mainAgent.round });
|
|
8904
|
+
/* ANALİZ EKİBİ: seçili roller için ayrı sürekli ajanlar (koşmıyorsa) */
|
|
8905
|
+
finTeamStart(f);
|
|
8863
8906
|
return { ok: true, sid: mainSid };
|
|
8864
8907
|
}
|
|
8865
8908
|
|
|
8909
|
+
/* ANALİZ EKİBİ: seçili her rol için AYRI sürekli finance ajanı — hepsi
|
|
8910
|
+
tüm SKILL'lere + mt5 okuma araçlarına erişir, İŞLEM AÇMAZ.
|
|
8911
|
+
Otomatik mod: kullanıcı sadece SAYI girer → gereklı ajanlar atanır. */
|
|
8912
|
+
function finTeamStart(f) {
|
|
8913
|
+
const n = Math.max(0, Math.min(FIN_ROLES_AUTO.length, Math.round(Number(f && f.analysisCount) || 0)));
|
|
8914
|
+
const roles = f && f.analysisAuto
|
|
8915
|
+
? FIN_ROLES_AUTO.slice(0, n)
|
|
8916
|
+
: finRolesValid((f && f.analysisTeam) || []);
|
|
8917
|
+
if (!roles.length) return;
|
|
8918
|
+
const running = new Set();
|
|
8919
|
+
for (const [, a] of financeState.agents) {
|
|
8920
|
+
if (a.role) running.add(a.role);
|
|
8921
|
+
}
|
|
8922
|
+
for (const roleId of roles) {
|
|
8923
|
+
if (running.has(roleId)) continue;
|
|
8924
|
+
const roleDef = finRoleDef(roleId);
|
|
8925
|
+
try {
|
|
8926
|
+
const created = finAgentCreate([], false, roleId);
|
|
8927
|
+
try { engine.setSessionModel(created.s.id, f.traderSel || null); } catch {}
|
|
8928
|
+
const ok = engine.send(created.s.id, finTraderBrief(created.agent), { userAction: true });
|
|
8929
|
+
if (!ok) {
|
|
8930
|
+
finAgentStop(String(created.s.id), 'oturum meşgul — ekip ajanı başlatılamadı');
|
|
8931
|
+
financeLog('[ekip] ' + roleDef.label + ' başlatılamadı: oturum meşgul');
|
|
8932
|
+
} else {
|
|
8933
|
+
financeLog('[ekip] ' + roleDef.label + ' başladı — analiz turu koşuyor (İşlem AÇMAZ)');
|
|
8934
|
+
}
|
|
8935
|
+
} catch (e) {
|
|
8936
|
+
financeLog('[ekip] ' + roleDef.label + ' hatası: ' + String((e && e.message) || e));
|
|
8937
|
+
}
|
|
8938
|
+
}
|
|
8939
|
+
}
|
|
8940
|
+
|
|
8866
8941
|
ipcMain.handle('finance:trader:start', () => financeTraderStart());
|
|
8867
8942
|
|
|
8868
8943
|
ipcMain.handle('finance:trader:stop', async () => {
|
|
8869
8944
|
financeState.traderOn = false;
|
|
8870
|
-
/* ANA trader
|
|
8945
|
+
/* ANA trader + ANALİZ EKİBİ durdurulur (sembol işçileri rail × ile ayrı durdurulur) */
|
|
8871
8946
|
let mainSid = '';
|
|
8947
|
+
const teamSids = [];
|
|
8872
8948
|
for (const [sid, a] of financeState.agents) {
|
|
8873
|
-
if (a.main)
|
|
8874
|
-
|
|
8875
|
-
break;
|
|
8876
|
-
}
|
|
8949
|
+
if (a.main) mainSid = sid;
|
|
8950
|
+
else if (a.role) teamSids.push(sid);
|
|
8877
8951
|
}
|
|
8878
8952
|
const stopped = mainSid ? finAgentStop(mainSid, 'kullanıcı Beast Finance trader ajanını durdurdu') : false;
|
|
8953
|
+
for (const sid of teamSids) finAgentStop(sid, 'kullanıcı Beast Finance ekip ajanını durdurdu');
|
|
8879
8954
|
const interrupted = !!stopped;
|
|
8880
|
-
financeLog('[trader] durduruldu');
|
|
8955
|
+
financeLog('[trader] durduruldu' + (teamSids.length ? ' (ekip: ' + teamSids.length + ' ajan)' : ''));
|
|
8881
8956
|
finPush('trader', { state: 'stopped' });
|
|
8882
8957
|
return { ok: true, interrupted };
|
|
8883
8958
|
});
|
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),
|
package/src/renderer/index.html
CHANGED
|
@@ -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 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>
|
|
172
176
|
<div class="fin-row">
|
|
173
|
-
<label class="fin-check"><input id="finAllow" type="checkbox" /> Otomatik işlem (ajan trade açabilir)</label>
|
|
174
177
|
<button id="finTraderBtn" class="fin-start">▶︎ Ajanı Başlat</button>
|
|
175
178
|
</div>
|
|
176
179
|
<div id="finTraderStatus">Kapalı</div>
|
|
@@ -380,6 +383,7 @@
|
|
|
380
383
|
<span class="bot-bar-btns">
|
|
381
384
|
<button id="dmBackBtn" title="Oturum listesine dön" hidden>◀︎ Oturumlar</button>
|
|
382
385
|
<button id="dmColumnBtn" title="Yan sütunda canlı akış olarak da göster">▤︎</button>
|
|
386
|
+
<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
387
|
<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
388
|
<button id="dmModalClose" title="Kapat">✕︎</button>
|
|
385
389
|
</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,8 +173,8 @@ 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'),
|
|
@@ -5035,6 +5036,7 @@ const dmState = {
|
|
|
5035
5036
|
groups: [], // { id, title, members, titles, createdAt, closed, closedAt }
|
|
5036
5037
|
modalThread: null, // modalde açık oturum key'i (null = oturum listesi)
|
|
5037
5038
|
};
|
|
5039
|
+
let dmGroupSyncTimer = null;
|
|
5038
5040
|
|
|
5039
5041
|
function dmGroupOf(id) {
|
|
5040
5042
|
return (dmState.groups || []).find((g) => String(g.id) === String(id)) || null;
|
|
@@ -5058,13 +5060,24 @@ function dmThreadClosed(msgs) {
|
|
|
5058
5060
|
return !!last.closed;
|
|
5059
5061
|
}
|
|
5060
5062
|
|
|
5063
|
+
/* ajan adının yanına KOD (oturum id) — kimin kim olduğunu netleştirir */
|
|
5064
|
+
function dmCode(code) {
|
|
5065
|
+
const c = String(code || '').trim();
|
|
5066
|
+
if (!c) return '';
|
|
5067
|
+
return `<span class="dm-code">${escapeHtml(c.length > 14 ? c.slice(0, 14) + '…' : c)}</span>`;
|
|
5068
|
+
}
|
|
5069
|
+
|
|
5070
|
+
function dmName(title, code) {
|
|
5071
|
+
return `${escapeHtml(String(title || 'Ajan'))}${dmCode(code)}`;
|
|
5072
|
+
}
|
|
5073
|
+
|
|
5061
5074
|
function dmThreadTitle(dm) {
|
|
5062
|
-
if (dm.group) return '👥 ' + String(dm.groupTitle || 'Grup');
|
|
5063
|
-
const a =
|
|
5064
|
-
const b =
|
|
5065
|
-
const pair = a
|
|
5075
|
+
if (dm.group) return '👥 ' + escapeHtml(String(dm.groupTitle || 'Grup'));
|
|
5076
|
+
const a = dmName(dm.fromTitle, dm.from);
|
|
5077
|
+
const b = dmName(dm.toTitle, dm.to);
|
|
5078
|
+
const pair = a + ' ↔ ' + b;
|
|
5066
5079
|
const topic = String(dm.topic || '(genel)');
|
|
5067
|
-
return topic === '(genel)' ? pair : pair + ' · ' + topic;
|
|
5080
|
+
return topic === '(genel)' ? pair : pair + ' · ' + escapeHtml(topic);
|
|
5068
5081
|
}
|
|
5069
5082
|
|
|
5070
5083
|
function dmThreads() {
|
|
@@ -5087,8 +5100,8 @@ function dmTime(iso) {
|
|
|
5087
5100
|
|
|
5088
5101
|
function dmMsgHtml(m) {
|
|
5089
5102
|
const who = m.group
|
|
5090
|
-
?
|
|
5091
|
-
: `${
|
|
5103
|
+
? dmName(m.fromTitle, m.from)
|
|
5104
|
+
: `${dmName(m.fromTitle, m.from)} → ${dmName(m.toTitle, m.to)}`;
|
|
5092
5105
|
return (
|
|
5093
5106
|
`<div class="dmt-msg"><span class="dm-who">${who}</span>` +
|
|
5094
5107
|
`${escapeHtml(String(m.text || ''))}<span class="dm-at">${dmTime(m.at)}</span></div>`
|
|
@@ -5106,17 +5119,20 @@ function dmThreadCard(key, msgs, onClick) {
|
|
|
5106
5119
|
if (last.group) {
|
|
5107
5120
|
const g = dmGroupOf(last.group);
|
|
5108
5121
|
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">👥 ${
|
|
5122
|
+
const names = g.members.slice(0, 4).map((m) => dmName((g.titles && g.titles[m]) || m, m));
|
|
5123
|
+
membersLine = `<div class="dmt-members">👥 ${names.join(', ')}</div>`;
|
|
5111
5124
|
}
|
|
5112
5125
|
}
|
|
5113
5126
|
el.innerHTML =
|
|
5114
5127
|
'<div class="dmt-head">' +
|
|
5115
|
-
`<span class="dmt-title">${
|
|
5128
|
+
`<span class="dmt-title">${dmThreadTitle(last)}</span>` +
|
|
5116
5129
|
`<span class="dmt-count">${closed ? 'kapandı' : msgs.length}</span>` +
|
|
5130
|
+
`<button class="dmt-del" title="Bu oturumu sil">✕︎</button>` +
|
|
5117
5131
|
'</div>' +
|
|
5118
5132
|
membersLine +
|
|
5119
5133
|
`<div class="dmt-last">${escapeHtml(String(last.text || '').replace(/\s+/g, ' ').slice(0, 90))} · ${dmTime(last.at)}</div>`;
|
|
5134
|
+
const del = el.querySelector('.dmt-del');
|
|
5135
|
+
if (del) del.addEventListener('click', (ev) => { ev.stopPropagation(); dmDeleteThread(key); });
|
|
5120
5136
|
el.addEventListener('click', onClick);
|
|
5121
5137
|
return el;
|
|
5122
5138
|
}
|
|
@@ -5182,6 +5198,7 @@ function renderDmModal() {
|
|
|
5182
5198
|
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
5199
|
if (els.dmDialogTitle) els.dmDialogTitle.textContent = 'AJAN DM — OTURUMLAR';
|
|
5184
5200
|
if (els.dmBackBtn) els.dmBackBtn.hidden = true;
|
|
5201
|
+
if (els.dmModalDel) els.dmModalDel.hidden = true;
|
|
5185
5202
|
return;
|
|
5186
5203
|
}
|
|
5187
5204
|
if (dmState.modalThread) {
|
|
@@ -5191,8 +5208,9 @@ function renderDmModal() {
|
|
|
5191
5208
|
dmState.modalThread = null;
|
|
5192
5209
|
return renderDmModal();
|
|
5193
5210
|
}
|
|
5194
|
-
if (els.dmDialogTitle) els.dmDialogTitle.
|
|
5211
|
+
if (els.dmDialogTitle) els.dmDialogTitle.innerHTML = dmThreadTitle(msgs[msgs.length - 1]);
|
|
5195
5212
|
if (els.dmBackBtn) els.dmBackBtn.hidden = false;
|
|
5213
|
+
if (els.dmModalDel) els.dmModalDel.hidden = false;
|
|
5196
5214
|
list.innerHTML = '<div class="dm-thread-msgs">' + msgs.map(dmMsgHtml).join('') + '</div>';
|
|
5197
5215
|
const box = list.querySelector('.dm-thread-msgs');
|
|
5198
5216
|
if (box) box.scrollTop = box.scrollHeight;
|
|
@@ -5201,6 +5219,7 @@ function renderDmModal() {
|
|
|
5201
5219
|
/* OTURUM LİSTESİ — AKTİF + GEÇMİŞ bölümleri */
|
|
5202
5220
|
if (els.dmDialogTitle) els.dmDialogTitle.textContent = 'AJAN DM — AKTİF & GEÇMİŞ';
|
|
5203
5221
|
if (els.dmBackBtn) els.dmBackBtn.hidden = true;
|
|
5222
|
+
if (els.dmModalDel) els.dmModalDel.hidden = true;
|
|
5204
5223
|
list.innerHTML = '';
|
|
5205
5224
|
dmAppendThreadSections(list, (key) => {
|
|
5206
5225
|
dmState.modalThread = key;
|
|
@@ -5230,6 +5249,22 @@ async function dmClearAll() {
|
|
|
5230
5249
|
toast('Ajan DM geçmişi silindi');
|
|
5231
5250
|
}
|
|
5232
5251
|
|
|
5252
|
+
/* TEK OTURUM silme: yalnız bu sohbetin (ajan çifti + konu / grup) DM'leri gider */
|
|
5253
|
+
async function dmDeleteThread(key) {
|
|
5254
|
+
const msgs = (dmThreads().find(([k]) => k === key) || [null, []])[1];
|
|
5255
|
+
const last = msgs[msgs.length - 1];
|
|
5256
|
+
const label = last
|
|
5257
|
+
? last.group
|
|
5258
|
+
? String(last.groupTitle || 'Grup')
|
|
5259
|
+
: `${last.fromTitle || 'Ajan'} ↔ ${last.toTitle || 'Ajan'}`
|
|
5260
|
+
: 'bu oturum';
|
|
5261
|
+
if (!(await uiConfirm(`"${label}" oturumu silinsin mi?`, 'Sil'))) return;
|
|
5262
|
+
try { await beast.agentDmDeleteThread(key); } catch {}
|
|
5263
|
+
if (dmState.modalThread === key) dmState.modalThread = null;
|
|
5264
|
+
await refreshDmRail();
|
|
5265
|
+
toast('Oturum silindi');
|
|
5266
|
+
}
|
|
5267
|
+
|
|
5233
5268
|
if (els.dmBtn) {
|
|
5234
5269
|
els.dmBtn.addEventListener('click', () => {
|
|
5235
5270
|
/* kapalıyken (dm-hidden VAR) aç: toggle 'hide' SEMANTIĞI tersine çevrilir */
|
|
@@ -5238,6 +5273,11 @@ if (els.dmBtn) {
|
|
|
5238
5273
|
}
|
|
5239
5274
|
if (els.dmModalClose) els.dmModalClose.addEventListener('click', closeDmModal);
|
|
5240
5275
|
if (els.dmModalClear) els.dmModalClear.addEventListener('click', dmClearAll);
|
|
5276
|
+
if (els.dmModalDel) {
|
|
5277
|
+
els.dmModalDel.addEventListener('click', () => {
|
|
5278
|
+
if (dmState.modalThread) dmDeleteThread(dmState.modalThread); // açık oturumu sil
|
|
5279
|
+
});
|
|
5280
|
+
}
|
|
5241
5281
|
if (els.dmBackBtn) {
|
|
5242
5282
|
els.dmBackBtn.addEventListener('click', () => {
|
|
5243
5283
|
dmState.modalThread = null; // oturum listesine dön
|
|
@@ -5613,10 +5653,29 @@ function onEvent(ev) {
|
|
|
5613
5653
|
dmState.dms.push(rec);
|
|
5614
5654
|
if (dmState.dms.length > 400) dmState.dms.splice(0, dmState.dms.length - 400);
|
|
5615
5655
|
}
|
|
5656
|
+
/* GRUP mesajı canlı geldi: grup kaydını (üyeler/başlık/kapandı) sunucudan
|
|
5657
|
+
tazele — kart İLK mesajla birlikte tam grup görünümünde düşer, "önce
|
|
5658
|
+
ayrı ayrı görünür sonra gruba geçer" durumu olmaz */
|
|
5659
|
+
if (rec.group) {
|
|
5660
|
+
clearTimeout(dmGroupSyncTimer);
|
|
5661
|
+
dmGroupSyncTimer = setTimeout(() => {
|
|
5662
|
+
beast
|
|
5663
|
+
.agentDmsList()
|
|
5664
|
+
.then((res) => {
|
|
5665
|
+
if (res && !Array.isArray(res)) dmState.groups = res.groups || [];
|
|
5666
|
+
})
|
|
5667
|
+
.catch(() => {});
|
|
5668
|
+
}, 200);
|
|
5669
|
+
}
|
|
5616
5670
|
if (els.dmOverlay && !els.dmOverlay.hidden) renderDmModal();
|
|
5617
5671
|
if ($('#dmRail') && !$('#dmRail').classList.contains('dm-hidden')) renderDmRail();
|
|
5618
5672
|
return;
|
|
5619
5673
|
}
|
|
5674
|
+
/* AJAN DM sohbeti kapandı (ajan işi bitti) — AKTİF/GEÇMİŞ tazele */
|
|
5675
|
+
if (ev.type === 'agent-dm-closed') {
|
|
5676
|
+
refreshDmRail().catch(() => {});
|
|
5677
|
+
return;
|
|
5678
|
+
}
|
|
5620
5679
|
/* KİŞİSEL TOOL çalışma kaydı — TOOLS konsolunun nokta göstergeleri */
|
|
5621
5680
|
if (ev.type === 'tool-log') {
|
|
5622
5681
|
toolUi.logRows.push({
|
|
@@ -8880,10 +8939,94 @@ function finTraderInputsSet(cfg) {
|
|
|
8880
8939
|
if (els.finInterval && ae !== els.finInterval) els.finInterval.value = cfg.intervalSec || 120;
|
|
8881
8940
|
if (els.finMaxLot && ae !== els.finMaxLot) els.finMaxLot.value = cfg.maxLot || 0.1;
|
|
8882
8941
|
finSymBtnUpdate();
|
|
8883
|
-
if (els.finAllow && ae !== els.finAllow) els.finAllow.checked = cfg.allowTrading === true;
|
|
8884
8942
|
if (els.finTraderModel && ae !== els.finTraderModel && cfg.traderSel) els.finTraderModel.value = cfg.traderSel;
|
|
8885
8943
|
}
|
|
8886
8944
|
|
|
8945
|
+
/* ANALİZ EKİBİ: trader yanında koşacak uzman ajan rolleri — İKİ MOD:
|
|
8946
|
+
(1) OTOMATİK: sadece SAYI gir → gereklı ajanları sistem kendisi ata;
|
|
8947
|
+
(2) ELLE SEÇ: rol kutucukları (Risk / Teknik / Haber-Makro) çoklu seçilir.
|
|
8948
|
+
Katalog sunucudan (finance snapshot/state.roles) gelir, yerel yedek var. */
|
|
8949
|
+
let finRolesCatalog = [
|
|
8950
|
+
{ id: 'risk', label: 'Risk Ajanı', desc: 'marj/kaldıraç/SL disiplini, exposure ve günlük kayıp hızı denetimi' },
|
|
8951
|
+
{ id: 'technic', label: 'Teknik Analiz', desc: 'trend/yapı/destek-direnç/momentum okuma, AL-SAT-BEKLE önerileri' },
|
|
8952
|
+
{ id: 'macro', label: 'Haber / Makro', desc: 'haber akışı + ekonomik takvim, DXY/emtia bağıntıları, yön eğilimi' },
|
|
8953
|
+
];
|
|
8954
|
+
let finRolesDirty = false; /* tık→kaydet arası snapshot ezmesin */
|
|
8955
|
+
let finRolesSyncTimer = null;
|
|
8956
|
+
|
|
8957
|
+
function finRolesSel() {
|
|
8958
|
+
const box = els.finRoles;
|
|
8959
|
+
return box ? [...box.querySelectorAll('input[data-role]:checked')].map((cb) => cb.dataset.role) : [];
|
|
8960
|
+
}
|
|
8961
|
+
|
|
8962
|
+
function finRenderRoles(cfg) {
|
|
8963
|
+
const box = els.finRoles;
|
|
8964
|
+
if (!box) return;
|
|
8965
|
+
if (finRolesDirty) return; /* bekleyen kullanıcı değişikliği korunur */
|
|
8966
|
+
const auto = !!(cfg && cfg.analysisAuto);
|
|
8967
|
+
const count = Math.max(0, Math.round(Number(cfg && cfg.analysisCount) || 0));
|
|
8968
|
+
const sel = cfg && Array.isArray(cfg.analysisTeam) ? cfg.analysisTeam : [];
|
|
8969
|
+
box.innerHTML = '';
|
|
8970
|
+
|
|
8971
|
+
/* OTOMATİK çipi + sayı girişi */
|
|
8972
|
+
const autoLab = document.createElement('label');
|
|
8973
|
+
autoLab.className = 'fin-role fin-role-auto';
|
|
8974
|
+
autoLab.title = 'Rol seçmeden SAYI gir — gereklı ajanları sistem otomatik atar (teknik → risk → haber sırası)';
|
|
8975
|
+
const autoCb = document.createElement('input');
|
|
8976
|
+
autoCb.type = 'checkbox';
|
|
8977
|
+
autoCb.checked = auto;
|
|
8978
|
+
autoLab.appendChild(autoCb);
|
|
8979
|
+
autoLab.appendChild(document.createTextNode('⚙ Otomatik'));
|
|
8980
|
+
autoCb.addEventListener('change', () => {
|
|
8981
|
+
finRolesDirty = true;
|
|
8982
|
+
finSaveCfg({ analysisAuto: autoCb.checked });
|
|
8983
|
+
toast(autoCb.checked ? 'Analiz ekibi OTOMATİK — sayı kadar gereklı ajan atanır' : 'Analiz ekibi ELLE SEÇİM moduna geçti');
|
|
8984
|
+
clearTimeout(finRolesSyncTimer);
|
|
8985
|
+
finRolesSyncTimer = setTimeout(() => { finRolesDirty = false; finSnapshot(); }, 900);
|
|
8986
|
+
});
|
|
8987
|
+
box.appendChild(autoLab);
|
|
8988
|
+
|
|
8989
|
+
const cnt = document.createElement('input');
|
|
8990
|
+
cnt.type = 'number';
|
|
8991
|
+
cnt.className = 'fin-role-count';
|
|
8992
|
+
cnt.min = '0';
|
|
8993
|
+
cnt.max = String(finRolesCatalog.length);
|
|
8994
|
+
cnt.step = '1';
|
|
8995
|
+
cnt.value = String(count || 0);
|
|
8996
|
+
cnt.title = 'Otomatik mod: kaç uzman ajan koşacak (0 = ekip yok)';
|
|
8997
|
+
cnt.disabled = !auto;
|
|
8998
|
+
cnt.addEventListener('change', () => {
|
|
8999
|
+
finRolesDirty = true;
|
|
9000
|
+
finSaveCfg({ analysisCount: Math.max(0, Math.round(Number(cnt.value) || 0)) });
|
|
9001
|
+
toast('Analiz ekibi: ' + (Math.round(Number(cnt.value) || 0) || 'kapalı') + ' uzman ajan (otomatik)');
|
|
9002
|
+
clearTimeout(finRolesSyncTimer);
|
|
9003
|
+
finRolesSyncTimer = setTimeout(() => { finRolesDirty = false; finSnapshot(); }, 900);
|
|
9004
|
+
});
|
|
9005
|
+
box.appendChild(cnt);
|
|
9006
|
+
|
|
9007
|
+
/* rol kutucukları — otomatik moddayken pasif */
|
|
9008
|
+
for (const r of finRolesCatalog) {
|
|
9009
|
+
const lab = document.createElement('label');
|
|
9010
|
+
lab.className = 'fin-role';
|
|
9011
|
+
const cb = document.createElement('input');
|
|
9012
|
+
cb.type = 'checkbox';
|
|
9013
|
+
cb.dataset.role = r.id;
|
|
9014
|
+
cb.checked = sel.includes(r.id);
|
|
9015
|
+
cb.disabled = auto;
|
|
9016
|
+
cb.title = r.desc + ' — tüm skill + mt5 erişimli, işlem açmaz';
|
|
9017
|
+
lab.appendChild(cb);
|
|
9018
|
+
lab.appendChild(document.createTextNode(r.label));
|
|
9019
|
+
cb.addEventListener('change', () => {
|
|
9020
|
+
finRolesDirty = true;
|
|
9021
|
+
finSaveCfg({ analysisTeam: finRolesSel() });
|
|
9022
|
+
toast(r.label + (cb.checked ? ' ekibe eklendi — sonraki başlatışta koşar' : ' ekipten çıkarıldı'));
|
|
9023
|
+
clearTimeout(finRolesSyncTimer);
|
|
9024
|
+
finRolesSyncTimer = setTimeout(() => { finRolesDirty = false; finSnapshot(); }, 900);
|
|
9025
|
+
});
|
|
9026
|
+
box.appendChild(lab);
|
|
9027
|
+
}
|
|
9028
|
+
}
|
|
9029
|
+
|
|
8887
9030
|
function finRenderTrader(trader, cfg) {
|
|
8888
9031
|
if (!els.finTraderBtn) return;
|
|
8889
9032
|
const on = !!(trader && trader.on);
|
|
@@ -8900,10 +9043,14 @@ function finRenderTrader(trader, cfg) {
|
|
|
8900
9043
|
const d = new Date(trader.lastAt);
|
|
8901
9044
|
t = ' · son tur ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
|
|
8902
9045
|
}
|
|
8903
|
-
|
|
9046
|
+
/* ekip bilgisi: otomatik modda sayı, elle seçimde seçili rol sayısı */
|
|
9047
|
+
const team = cfg && cfg.analysisAuto
|
|
9048
|
+
? Math.round(Number(cfg.analysisCount) || 0)
|
|
9049
|
+
: (cfg && Array.isArray(cfg.analysisTeam) ? cfg.analysisTeam.length : 0);
|
|
9050
|
+
const teamTxt = team ? ' · ekip ' + team + ' ajan' + (cfg && cfg.analysisAuto ? ' (oto)' : '') : '';
|
|
8904
9051
|
els.finTraderStatus.textContent = on
|
|
8905
|
-
? (trader.busy ? 'Tur çalışıyor…' : 'Beklemede — sıradaki tur ' + (cfg && cfg.intervalSec ? cfg.intervalSec + ' sn' : '')) + ' · tur #' + (trader.rounds || 0) + ' · ' +
|
|
8906
|
-
: 'Kapalı · ' +
|
|
9052
|
+
? (trader.busy ? 'Tur çalışıyor…' : 'Beklemede — sıradaki tur ' + (cfg && cfg.intervalSec ? cfg.intervalSec + ' sn' : '')) + ' · tur #' + (trader.rounds || 0) + ' · OTONOM' + teamTxt + t
|
|
9053
|
+
: 'Kapalı · OTONOM' + teamTxt;
|
|
8907
9054
|
}
|
|
8908
9055
|
}
|
|
8909
9056
|
|
|
@@ -8941,7 +9088,9 @@ async function finSnapshot() {
|
|
|
8941
9088
|
finRenderAccount(r.account);
|
|
8942
9089
|
finRenderPositions(r.positions || []);
|
|
8943
9090
|
finRenderSymbols(r.symbols || []);
|
|
9091
|
+
if (Array.isArray(r.roles) && r.roles.length) finRolesCatalog = r.roles;
|
|
8944
9092
|
finTraderInputsSet(r.cfg);
|
|
9093
|
+
finRenderRoles(r.cfg);
|
|
8945
9094
|
finRenderTrader(r.trader, r.cfg);
|
|
8946
9095
|
}
|
|
8947
9096
|
|
|
@@ -9037,22 +9186,7 @@ function finSaveCfg(patch) {
|
|
|
9037
9186
|
if (els.finInterval) els.finInterval.addEventListener('change', () => finSaveCfg({ intervalSec: Number(els.finInterval.value) || 120 }));
|
|
9038
9187
|
if (els.finMaxLot) els.finMaxLot.addEventListener('change', () => finSaveCfg({ maxLot: Number(els.finMaxLot.value) || 0.1 }));
|
|
9039
9188
|
if (els.finSymBtn) els.finSymBtn.addEventListener('click', finSymPickerOpen);
|
|
9040
|
-
|
|
9041
|
-
els.finAllow.addEventListener('change', async () => {
|
|
9042
|
-
if (els.finAllow.checked) {
|
|
9043
|
-
const ok = await uiConfirm(
|
|
9044
|
-
'OTOMATİK İŞLEM açılıyor: trade ajanı GERÇEK hesapta emir açabilir/kapatabilir (max lot + pozisyon limitleriyle). Onaylıyor musun?',
|
|
9045
|
-
'Aç',
|
|
9046
|
-
'Vazgeç'
|
|
9047
|
-
);
|
|
9048
|
-
if (!ok) {
|
|
9049
|
-
els.finAllow.checked = false;
|
|
9050
|
-
return;
|
|
9051
|
-
}
|
|
9052
|
-
}
|
|
9053
|
-
finSaveCfg({ allowTrading: els.finAllow.checked });
|
|
9054
|
-
});
|
|
9055
|
-
}
|
|
9189
|
+
/* OTOMATİK İŞLEM onay kutusu KALDIRILDI — trade ajanı daima işlem açabilir */
|
|
9056
9190
|
if (els.finTraderModel) {
|
|
9057
9191
|
els.finTraderModel.addEventListener('change', () => {
|
|
9058
9192
|
finSaveCfg({ traderSel: els.finTraderModel.value });
|
|
@@ -9066,7 +9200,14 @@ if (els.finTraderBtn) {
|
|
|
9066
9200
|
} else {
|
|
9067
9201
|
const r = await beast.financeTraderStart().catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
|
|
9068
9202
|
if (r && !r.ok) toast(r.error || 'Trader başlatılamadı');
|
|
9069
|
-
else if (r && r.ok)
|
|
9203
|
+
else if (r && r.ok) {
|
|
9204
|
+
const cfg = (await beast.financeState().catch(() => null));
|
|
9205
|
+
const c = (cfg && cfg.cfg) || {};
|
|
9206
|
+
const team = c.analysisAuto
|
|
9207
|
+
? Math.round(Number(c.analysisCount) || 0)
|
|
9208
|
+
: finRolesSel().length;
|
|
9209
|
+
toast(team ? 'Trade ajanı + analiz ekibi (' + team + ' uzman) başladı — turlar panelde izlenir' : 'Trade ajanı başladı — turlar panelde izlenir');
|
|
9210
|
+
}
|
|
9070
9211
|
}
|
|
9071
9212
|
finSnapshot();
|
|
9072
9213
|
});
|
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,19 +4864,60 @@ 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 */
|
|
4858
4920
|
}
|
|
4859
|
-
.fin-start:hover { filter: brightness(1.1); }
|
|
4860
|
-
.fin-start.stop { background: #ef4444; }
|
|
4861
4921
|
|
|
4862
4922
|
#finTraderStatus {
|
|
4863
4923
|
margin-top: 7px;
|