beast-agent 2.32.0 → 2.33.1
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 +1 -1
- package/src/agent/financetools.js +316 -45
- package/src/agent/finindicators.js +254 -0
- package/src/agent/finrisk.js +132 -0
- package/src/agent/finstats.js +229 -0
- package/src/agent/finwatch.js +110 -0
- package/src/agent/scripts/mt5_bridge.py +65 -0
- package/src/main.js +678 -8
- package/src/preload.js +1 -0
- package/src/renderer/index.html +80 -1
- package/src/renderer/renderer.js +280 -1
- package/src/renderer/style.css +52 -0
package/src/main.js
CHANGED
|
@@ -3714,6 +3714,7 @@ app.whenReady().then(() => {
|
|
|
3714
3714
|
|
|
3715
3715
|
app.on('before-quit', () => {
|
|
3716
3716
|
app.isQuitting = true;
|
|
3717
|
+
try { finWatchStop(); } catch {} // finance watchdog zamanlayıcısını kapat
|
|
3717
3718
|
flushBrowserStorage(); // x.com/google oturumları (cookies) diske yazılsın
|
|
3718
3719
|
try { toolsMod.disposeShellSessions(); } catch {} // kalıcı shell oturumlarını kapat
|
|
3719
3720
|
try { require('./agent/mcp').stopAll(); } catch {} // MCP server süreçlerini kapat
|
|
@@ -8417,6 +8418,8 @@ function studioWatchStop() {
|
|
|
8417
8418
|
const mt5bridge = require('./mt5bridge');
|
|
8418
8419
|
const financetools = require('./agent/financetools');
|
|
8419
8420
|
const customtools = require('./agent/customtools');
|
|
8421
|
+
const finwatch = require('./agent/finwatch');
|
|
8422
|
+
const finstats = require('./agent/finstats');
|
|
8420
8423
|
|
|
8421
8424
|
const financeState = {
|
|
8422
8425
|
mode: false, /* chat oturumları finance bayrağı alıyor mu */
|
|
@@ -8427,6 +8430,20 @@ const financeState = {
|
|
|
8427
8430
|
installing: false,
|
|
8428
8431
|
installTried: false,
|
|
8429
8432
|
agents: new Map(), /* sid -> { symbols, main, round, timer } — AYNI ANDA koşan finance ajanları */
|
|
8433
|
+
watch: new Map(), /* ticket -> risk otomasyonu durumu (R, BE, kısmi TP...) */
|
|
8434
|
+
watchTimer: null,
|
|
8435
|
+
watchBusy: false,
|
|
8436
|
+
watchTickAt: 0,
|
|
8437
|
+
alerts: [], /* fiyat alarmları (kalıcı: finance/alerts.json) */
|
|
8438
|
+
stats: null, /* son performans özeti (kalıcı: finance/stats.json) */
|
|
8439
|
+
statsAt: 0,
|
|
8440
|
+
lastStatsAt: 0,
|
|
8441
|
+
equity: [], /* equity örnekleri (kalıcı: finance/equity.jsonl) */
|
|
8442
|
+
account: null,
|
|
8443
|
+
dayStart: null,
|
|
8444
|
+
reportCheckAt: 0,
|
|
8445
|
+
flattening: false,
|
|
8446
|
+
breachBusy: false,
|
|
8430
8447
|
};
|
|
8431
8448
|
|
|
8432
8449
|
function financeDir() {
|
|
@@ -8468,6 +8485,30 @@ function finCfg() {
|
|
|
8468
8485
|
if (!Number(f.intervalSec)) f.intervalSec = 120;
|
|
8469
8486
|
if (!Number(f.maxLot)) f.maxLot = 0.1;
|
|
8470
8487
|
if (f.maxPositions == null) f.maxPositions = 3;
|
|
8488
|
+
/* RİSK OTOMASYONU (watchdog): +R'da BE, trailing, kısmi TP — main süreç
|
|
8489
|
+
saniyelik döngüyle uygular; ajan turunu BEKLEMEZ. 0 = ilgili kural kapalı. */
|
|
8490
|
+
if (typeof f.watchdog !== 'boolean') f.watchdog = true;
|
|
8491
|
+
if (!Number.isFinite(Number(f.beOnR))) f.beOnR = 1;
|
|
8492
|
+
if (!Number.isFinite(Number(f.beOffsetR))) f.beOffsetR = 0.05;
|
|
8493
|
+
if (!Number.isFinite(Number(f.trailStartR))) f.trailStartR = 1.5;
|
|
8494
|
+
if (!Number.isFinite(Number(f.trailR))) f.trailR = 0.5;
|
|
8495
|
+
if (!Number.isFinite(Number(f.partialR))) f.partialR = 0;
|
|
8496
|
+
if (!Number.isFinite(Number(f.partialPct))) f.partialPct = 50;
|
|
8497
|
+
/* BİLDİRİM: işlem/koruma/alarm olayları bağlı kanallara (TG/WA/Discord)
|
|
8498
|
+
gider; 'auto' = bağlı olanlar, 'off' = yalnız panel/masaüstü */
|
|
8499
|
+
if (typeof f.notifyTarget !== 'string') f.notifyTarget = 'auto';
|
|
8500
|
+
if (typeof f.notifyTrades !== 'boolean') f.notifyTrades = true;
|
|
8501
|
+
if (typeof f.notifyWatchdog !== 'boolean') f.notifyWatchdog = true;
|
|
8502
|
+
if (!Number.isFinite(Number(f.maxDailyLossPct))) f.maxDailyLossPct = 3;
|
|
8503
|
+
/* GÜNLÜK LİMİT AKSİYONU: warn = yalnız uyarı; stop = tüm finance ajanlarını
|
|
8504
|
+
durdur; flatten = durdur + tüm pozisyonları kapat */
|
|
8505
|
+
if (!['warn', 'stop', 'flatten'].includes(String(f.dailyLossAction))) f.dailyLossAction = 'stop';
|
|
8506
|
+
/* İŞLEM ÖNCESİ RİSK KATMANI: %risk lot + yoğunluk + marj kalkanı */
|
|
8507
|
+
if (!Number.isFinite(Number(f.riskPerTradePct))) f.riskPerTradePct = 1;
|
|
8508
|
+
if (!Number.isFinite(Number(f.maxPerSymbol))) f.maxPerSymbol = 2;
|
|
8509
|
+
if (!Number.isFinite(Number(f.maxSameSide))) f.maxSameSide = 3;
|
|
8510
|
+
if (!Number.isFinite(Number(f.minMarginLevel))) f.minMarginLevel = 150;
|
|
8511
|
+
if (f.weeklyReport == null) f.weeklyReport = true;
|
|
8471
8512
|
return f;
|
|
8472
8513
|
}
|
|
8473
8514
|
|
|
@@ -8503,21 +8544,605 @@ function finPush(fn, data) {
|
|
|
8503
8544
|
} catch {}
|
|
8504
8545
|
}
|
|
8505
8546
|
|
|
8506
|
-
/*
|
|
8547
|
+
/* ---------- FINANCE KALICILIK + BİLDİRİM + RİSK OTOMASYONU ---------- */
|
|
8548
|
+
|
|
8549
|
+
function finFile(name) {
|
|
8550
|
+
return path.join(financeDir(), String(name || ''));
|
|
8551
|
+
}
|
|
8552
|
+
|
|
8553
|
+
function finReadJson(file, def) {
|
|
8554
|
+
try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return def; }
|
|
8555
|
+
}
|
|
8556
|
+
|
|
8557
|
+
function finWriteJson(file, obj) {
|
|
8558
|
+
try {
|
|
8559
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
8560
|
+
fs.writeFileSync(file, JSON.stringify(obj, null, 2));
|
|
8561
|
+
} catch {}
|
|
8562
|
+
}
|
|
8563
|
+
|
|
8564
|
+
/* İşlem günlüğü: her karar (gerekçe notu, açılış, koruma, alarm, kapanış)
|
|
8565
|
+
finance/journal.jsonl'a düşer; haftalık rapor buradan beslenir. */
|
|
8566
|
+
function finJournal(entry) {
|
|
8567
|
+
const e = { at: Date.now(), ...entry };
|
|
8568
|
+
try {
|
|
8569
|
+
const p = finFile('journal.jsonl');
|
|
8570
|
+
try {
|
|
8571
|
+
if (fs.statSync(p).size > 3 * 1024 * 1024) fs.renameSync(p, finFile('journal-old.jsonl'));
|
|
8572
|
+
} catch {}
|
|
8573
|
+
fs.appendFileSync(p, JSON.stringify(e) + '\n');
|
|
8574
|
+
} catch {}
|
|
8575
|
+
return e;
|
|
8576
|
+
}
|
|
8577
|
+
|
|
8578
|
+
function finJournalTail(n) {
|
|
8579
|
+
try {
|
|
8580
|
+
const lines = fs.readFileSync(finFile('journal.jsonl'), 'utf8').trim().split('\n');
|
|
8581
|
+
return lines
|
|
8582
|
+
.slice(-Math.max(1, Math.min(500, Number(n) || 20)))
|
|
8583
|
+
.map((l) => { try { return JSON.parse(l); } catch { return null; } })
|
|
8584
|
+
.filter(Boolean);
|
|
8585
|
+
} catch {
|
|
8586
|
+
return [];
|
|
8587
|
+
}
|
|
8588
|
+
}
|
|
8589
|
+
|
|
8590
|
+
function finAgentInfo(sid) {
|
|
8591
|
+
try {
|
|
8592
|
+
const a = financeState.agents.get(String(sid || ''));
|
|
8593
|
+
if (a) {
|
|
8594
|
+
const role = String(a.role || '');
|
|
8595
|
+
const def = role ? finRoleDef(role) : null;
|
|
8596
|
+
return { main: !!a.main, role: role || (a.main ? 'trader' : 'worker'), label: def ? def.label : a.main ? 'Trader' : 'Sembol Ajanı' };
|
|
8597
|
+
}
|
|
8598
|
+
/* işçi kaydı yoksa (chat copilot'ı vb.) oturumdan başlık/rol oku */
|
|
8599
|
+
if (sid && engine) {
|
|
8600
|
+
const s = engine.cache.get(String(sid)) || engine._load(String(sid));
|
|
8601
|
+
if (s && s.finance) {
|
|
8602
|
+
const def = s.financeRole ? finRoleDef(s.financeRole) : null;
|
|
8603
|
+
return { main: false, role: String(s.financeRole || 'chat'), label: def ? def.label : s.bgTitle ? String(s.bgTitle) : 'Finance Chat' };
|
|
8604
|
+
}
|
|
8605
|
+
}
|
|
8606
|
+
} catch {}
|
|
8607
|
+
return {};
|
|
8608
|
+
}
|
|
8609
|
+
|
|
8610
|
+
/* Bildirim: panel/masaüstü toast + istenen kanal (Telegram/WhatsApp/Discord).
|
|
8611
|
+
Kanal seçimi finCfg().notifyTarget: auto|whatsapp|telegram|discord|off
|
|
8612
|
+
panel=false → panel toast'ı atla (çağıran zaten finPush('trade') yaptıysa). */
|
|
8613
|
+
function financeNotify(text, kind, panel) {
|
|
8614
|
+
const line = String(text || '').trim();
|
|
8615
|
+
if (!line) return;
|
|
8616
|
+
if (panel !== false) finPush('notify', { line, kind: kind || '' });
|
|
8617
|
+
let cfg;
|
|
8618
|
+
try { cfg = finCfg(); } catch { cfg = {}; }
|
|
8619
|
+
const target = String((cfg && cfg.notifyTarget) || 'auto');
|
|
8620
|
+
if (target === 'off') return;
|
|
8621
|
+
const body = '💼 *Beast Finance*\n' + line;
|
|
8622
|
+
const senders = [];
|
|
8623
|
+
try {
|
|
8624
|
+
if ((target === 'whatsapp' || target === 'auto') && wa && wa.connected) {
|
|
8625
|
+
const own = waOwnerNum();
|
|
8626
|
+
if (own) {
|
|
8627
|
+
const jid = own + '@s.whatsapp.net';
|
|
8628
|
+
senders.push(() => sendWaSafe(jid, body));
|
|
8629
|
+
}
|
|
8630
|
+
}
|
|
8631
|
+
} catch {}
|
|
8632
|
+
try {
|
|
8633
|
+
if ((target === 'telegram' || target === 'auto') && tg && tg.connected) {
|
|
8634
|
+
for (const id of tgOwnerIds()) senders.push(() => sendTgSafe(id, body));
|
|
8635
|
+
}
|
|
8636
|
+
} catch {}
|
|
8637
|
+
try {
|
|
8638
|
+
if ((target === 'discord' || target === 'auto') && dc && dc.connected) {
|
|
8639
|
+
for (const id of dcOwnerIds()) senders.push(() => sendDcSafe(id, body));
|
|
8640
|
+
}
|
|
8641
|
+
} catch {}
|
|
8642
|
+
for (const fn of senders) {
|
|
8643
|
+
try { Promise.resolve(fn()).catch(() => {}); } catch {}
|
|
8644
|
+
}
|
|
8645
|
+
}
|
|
8646
|
+
|
|
8647
|
+
/* ---- fiyat alarmları (kalıcı) ---- */
|
|
8648
|
+
function finAlertsLoad() {
|
|
8649
|
+
const raw = finReadJson(finFile('alerts.json'), []);
|
|
8650
|
+
financeState.alerts = (Array.isArray(raw) ? raw : [])
|
|
8651
|
+
.map((a) => ({
|
|
8652
|
+
id: String(a && a.id ? a.id : ''),
|
|
8653
|
+
symbol: String((a && a.symbol) || '').toUpperCase(),
|
|
8654
|
+
price: Number(a && a.price) || 0,
|
|
8655
|
+
direction: String((a && a.direction) || 'above').toLowerCase(),
|
|
8656
|
+
note: String((a && a.note) || '').slice(0, 200),
|
|
8657
|
+
at: Number(a && a.at) || Date.now(),
|
|
8658
|
+
}))
|
|
8659
|
+
.filter((a) => a.id && a.symbol && a.price > 0);
|
|
8660
|
+
}
|
|
8661
|
+
|
|
8662
|
+
function finAlertsSave() {
|
|
8663
|
+
finWriteJson(finFile('alerts.json'), financeState.alerts);
|
|
8664
|
+
}
|
|
8665
|
+
|
|
8666
|
+
function finAlertApi() {
|
|
8667
|
+
return {
|
|
8668
|
+
list: () => financeState.alerts.slice(),
|
|
8669
|
+
set: (a) => {
|
|
8670
|
+
const symbol = String((a && a.symbol) || '').toUpperCase();
|
|
8671
|
+
const price = Number(a && a.price);
|
|
8672
|
+
if (!symbol || !(price > 0)) return null;
|
|
8673
|
+
const alarm = {
|
|
8674
|
+
id: 'a' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
|
|
8675
|
+
symbol,
|
|
8676
|
+
price,
|
|
8677
|
+
direction: String((a && a.direction) || 'above').toLowerCase() === 'below' ? 'below' : 'above',
|
|
8678
|
+
note: String((a && a.note) || '').slice(0, 200),
|
|
8679
|
+
at: Date.now(),
|
|
8680
|
+
};
|
|
8681
|
+
financeState.alerts.push(alarm);
|
|
8682
|
+
finAlertsSave();
|
|
8683
|
+
financeLog(`[alarm] kuruldu: ${alarm.symbol} ${alarm.direction === 'below' ? '≤' : '≥'} ${alarm.price}`);
|
|
8684
|
+
return alarm;
|
|
8685
|
+
},
|
|
8686
|
+
remove: (id) => {
|
|
8687
|
+
const before = financeState.alerts.length;
|
|
8688
|
+
financeState.alerts = financeState.alerts.filter((a) => a.id !== String(id));
|
|
8689
|
+
if (financeState.alerts.length !== before) {
|
|
8690
|
+
finAlertsSave();
|
|
8691
|
+
financeLog('[alarm] silindi: ' + id);
|
|
8692
|
+
return true;
|
|
8693
|
+
}
|
|
8694
|
+
return false;
|
|
8695
|
+
},
|
|
8696
|
+
};
|
|
8697
|
+
}
|
|
8698
|
+
|
|
8699
|
+
/* ---- equity serisi + günlük kayıp uyarısı ---- */
|
|
8700
|
+
function finEquityLoad() {
|
|
8701
|
+
if (financeState.equity && financeState.equity.length) return financeState.equity;
|
|
8702
|
+
try {
|
|
8703
|
+
const lines = fs.readFileSync(finFile('equity.jsonl'), 'utf8').trim().split('\n');
|
|
8704
|
+
financeState.equity = lines
|
|
8705
|
+
.slice(-4000)
|
|
8706
|
+
.map((l) => { try { return JSON.parse(l); } catch { return null; } })
|
|
8707
|
+
.filter((p) => p && isFinite(Number(p.equity)));
|
|
8708
|
+
} catch {
|
|
8709
|
+
financeState.equity = [];
|
|
8710
|
+
}
|
|
8711
|
+
return financeState.equity;
|
|
8712
|
+
}
|
|
8713
|
+
|
|
8714
|
+
function finEquitySample(account) {
|
|
8715
|
+
const eq = Number(account && account.equity);
|
|
8716
|
+
if (!isFinite(eq)) return;
|
|
8717
|
+
finEquityLoad();
|
|
8718
|
+
const last = financeState.equity.length ? financeState.equity[financeState.equity.length - 1] : null;
|
|
8719
|
+
const now = Date.now();
|
|
8720
|
+
if (last && now - Number(last.at) < 30000) return;
|
|
8721
|
+
const point = { at: now, equity: eq, balance: Number(account.balance) || 0 };
|
|
8722
|
+
financeState.equity.push(point);
|
|
8723
|
+
try {
|
|
8724
|
+
fs.appendFileSync(finFile('equity.jsonl'), JSON.stringify(point) + '\n');
|
|
8725
|
+
if (financeState.equity.length > 4000) {
|
|
8726
|
+
financeState.equity = financeState.equity.slice(-2500);
|
|
8727
|
+
fs.writeFileSync(finFile('equity.jsonl'), financeState.equity.map((p) => JSON.stringify(p)).join('\n') + '\n');
|
|
8728
|
+
}
|
|
8729
|
+
} catch {}
|
|
8730
|
+
finDailyLossCheck(point);
|
|
8731
|
+
}
|
|
8732
|
+
|
|
8733
|
+
function finDailyLossCheck(point) {
|
|
8734
|
+
const cfg = finCfg();
|
|
8735
|
+
const day = finstats.dayKey(point.at);
|
|
8736
|
+
let ds = financeState.dayStart;
|
|
8737
|
+
if (!ds || ds.day !== day) {
|
|
8738
|
+
financeState.dayStart = ds = { day, equity: Number(point.balance) > 0 ? Number(point.balance) : point.equity, warned: false, acted: false };
|
|
8739
|
+
}
|
|
8740
|
+
if (ds.warned || !(Number(cfg.maxDailyLossPct) > 0) || !(ds.equity > 0)) return;
|
|
8741
|
+
const dd = ((ds.equity - point.equity) / ds.equity) * 100;
|
|
8742
|
+
if (dd >= Number(cfg.maxDailyLossPct)) {
|
|
8743
|
+
ds.warned = true;
|
|
8744
|
+
const action = String(cfg.dailyLossAction || 'stop');
|
|
8745
|
+
const tail = action === 'flatten' ? ' — ajanlar durduruldu, pozisyonlar kapatılıyor' : action === 'stop' ? ' — ajanlar durduruldu' : '';
|
|
8746
|
+
const line = `⚠️ Günlük kayıp %${dd.toFixed(2)} (limit %${cfg.maxDailyLossPct})${tail}`;
|
|
8747
|
+
financeLog('[risk] ' + line);
|
|
8748
|
+
financeNotify(line, 'drawdown');
|
|
8749
|
+
finJournal({ kind: 'drawdown', pct: Math.round(dd * 100) / 100, action });
|
|
8750
|
+
if (action === 'stop' || action === 'flatten') {
|
|
8751
|
+
finDailyLossBreach(action).catch(() => {});
|
|
8752
|
+
}
|
|
8753
|
+
}
|
|
8754
|
+
}
|
|
8755
|
+
|
|
8756
|
+
/* Günlük limit aşımı: ajanları durdur (+ istenirse tüm pozisyonları kapat).
|
|
8757
|
+
Ajanlar önce durur ki flatten sırasında yeni işlem açılmasın. */
|
|
8758
|
+
async function finDailyLossBreach(action) {
|
|
8759
|
+
if (financeState.breachBusy) return;
|
|
8760
|
+
financeState.breachBusy = true;
|
|
8761
|
+
try {
|
|
8762
|
+
await finStopAllFinanceAgents('günlük kayıp limiti aşıldı — risk katmanı durdurdu');
|
|
8763
|
+
const stopLine = '🛑 Risk limiti: tüm finance ajanları durduruldu';
|
|
8764
|
+
financeLog('[risk] ' + stopLine);
|
|
8765
|
+
financeNotify(stopLine, 'risk');
|
|
8766
|
+
finJournal({ kind: 'risk-stop', action });
|
|
8767
|
+
if (action === 'flatten') await finFlattenAll();
|
|
8768
|
+
} finally {
|
|
8769
|
+
financeState.breachBusy = false;
|
|
8770
|
+
}
|
|
8771
|
+
}
|
|
8772
|
+
|
|
8773
|
+
async function finStopAllFinanceAgents(reason) {
|
|
8774
|
+
financeState.traderOn = false;
|
|
8775
|
+
const sids = [...financeState.agents.keys()];
|
|
8776
|
+
for (const sid of sids) {
|
|
8777
|
+
try { finAgentStop(String(sid), reason); } catch {}
|
|
8778
|
+
}
|
|
8779
|
+
try { finPush('trader', { state: 'stopped', reason }); } catch {}
|
|
8780
|
+
try { financeLog('[risk] finance ajanları durduruldu: ' + sids.length + ' ajan'); } catch {}
|
|
8781
|
+
return sids.length;
|
|
8782
|
+
}
|
|
8783
|
+
|
|
8784
|
+
/* Tüm açık pozisyonları kapatır (günlük limit 'flatten' aksiyonu). */
|
|
8785
|
+
async function finFlattenAll() {
|
|
8786
|
+
if (financeState.flattening) return 0;
|
|
8787
|
+
if (!mt5bridge.running) return 0;
|
|
8788
|
+
financeState.flattening = true;
|
|
8789
|
+
let closed = 0;
|
|
8790
|
+
try {
|
|
8791
|
+
const r = await mt5bridge.call('positions', {}, 10000);
|
|
8792
|
+
const list = (r && r.ok && r.data && r.data.positions) || [];
|
|
8793
|
+
for (const p of list) {
|
|
8794
|
+
const cr = await mt5bridge.call('close', { ticket: p.ticket, volume: 0 }, 15000).catch(() => null);
|
|
8795
|
+
if (cr && cr.ok) closed++;
|
|
8796
|
+
}
|
|
8797
|
+
const line = `🚨 Günlük limit: ${closed}/${list.length} pozisyon kapatıldı`;
|
|
8798
|
+
financeLog('[risk] ' + line);
|
|
8799
|
+
financeNotify(line, 'risk');
|
|
8800
|
+
finJournal({ kind: 'risk-flatten', closed, total: list.length });
|
|
8801
|
+
finStatsRefresh(true).catch(() => {});
|
|
8802
|
+
} finally {
|
|
8803
|
+
financeState.flattening = false;
|
|
8804
|
+
}
|
|
8805
|
+
return closed;
|
|
8806
|
+
}
|
|
8807
|
+
|
|
8808
|
+
/* ---- performans istatistikleri (kalıcı) ---- */
|
|
8809
|
+
function finStatsLoad() {
|
|
8810
|
+
const s = finReadJson(finFile('stats.json'), null);
|
|
8811
|
+
if (s && typeof s === 'object') financeState.stats = s;
|
|
8812
|
+
}
|
|
8813
|
+
|
|
8814
|
+
async function finStatsRefresh(force) {
|
|
8815
|
+
if (!mt5bridge.running) return financeState.stats;
|
|
8816
|
+
const now = Date.now();
|
|
8817
|
+
if (!force && financeState.statsAt && now - financeState.statsAt < 60000) return financeState.stats;
|
|
8818
|
+
financeState.statsAt = now;
|
|
8819
|
+
try {
|
|
8820
|
+
const r = await mt5bridge.call('deals', { days: 90 }, 15000);
|
|
8821
|
+
const deals = (r && r.ok && r.data && r.data.deals) || [];
|
|
8822
|
+
const stats = finstats.summarizeDeals(deals);
|
|
8823
|
+
stats.drawdown = finstats.maxDrawdown(finEquityLoad());
|
|
8824
|
+
financeState.stats = stats;
|
|
8825
|
+
finWriteJson(finFile('stats.json'), stats);
|
|
8826
|
+
return stats;
|
|
8827
|
+
} catch {
|
|
8828
|
+
return financeState.stats;
|
|
8829
|
+
}
|
|
8830
|
+
}
|
|
8831
|
+
|
|
8832
|
+
/* ---- işlem günlüğü (görülen/kapanan pozisyonlar) ---- */
|
|
8833
|
+
async function finRecordClose(ticket, st) {
|
|
8834
|
+
let net = null;
|
|
8835
|
+
let symbol = (st && st.symbol) || '';
|
|
8836
|
+
try {
|
|
8837
|
+
const r = await mt5bridge.call('deals', { days: 3 }, 12000);
|
|
8838
|
+
const deals = (r && r.ok && r.data && r.data.deals) || [];
|
|
8839
|
+
const mine = deals.filter((d) => String(d.position_id) === String(ticket));
|
|
8840
|
+
if (mine.length) {
|
|
8841
|
+
net = 0;
|
|
8842
|
+
for (const d of mine) net += (Number(d.profit) || 0) + (Number(d.swap) || 0) + (Number(d.commission) || 0);
|
|
8843
|
+
symbol = String(mine[0].symbol || symbol);
|
|
8844
|
+
}
|
|
8845
|
+
} catch {}
|
|
8846
|
+
const rounded = net == null ? null : Math.round(net * 100) / 100;
|
|
8847
|
+
finJournal({ kind: 'close', ticket, symbol, net: rounded, side: (st && st.side) || '' });
|
|
8848
|
+
const pl = rounded == null ? '' : ` · K/Z ${rounded >= 0 ? '+' : ''}${rounded.toFixed(2)}`;
|
|
8849
|
+
const line = `🏁 Pozisyon kapandı: ${symbol || '?'} #${ticket}${pl}`;
|
|
8850
|
+
financeLog('[watchdog] ' + line);
|
|
8851
|
+
if (finCfg().notifyTrades !== false) financeNotify(line, 'close');
|
|
8852
|
+
if (rounded != null) finStatsRefresh(true);
|
|
8853
|
+
}
|
|
8854
|
+
|
|
8855
|
+
/* ---- risk otomasyonu (watchdog) ---- */
|
|
8856
|
+
const finSymMetaCache = new Map(); /* symbol -> { at, row } */
|
|
8857
|
+
|
|
8858
|
+
async function finSymbolMetaGet(symbol) {
|
|
8859
|
+
const sym = String(symbol || '');
|
|
8860
|
+
if (!sym) return null;
|
|
8861
|
+
const hit = finSymMetaCache.get(sym);
|
|
8862
|
+
if (hit && Date.now() - hit.at < 60000) return hit.row;
|
|
8863
|
+
try {
|
|
8864
|
+
const r = await mt5bridge.call('symbols', { symbols: [sym] }, 10000);
|
|
8865
|
+
const row = r && r.ok && r.data && r.data.symbols && r.data.symbols[0];
|
|
8866
|
+
if (row && !row.missing) {
|
|
8867
|
+
finSymMetaCache.set(sym, { at: Date.now(), row });
|
|
8868
|
+
return row;
|
|
8869
|
+
}
|
|
8870
|
+
} catch {}
|
|
8871
|
+
return hit ? hit.row : null;
|
|
8872
|
+
}
|
|
8873
|
+
|
|
8874
|
+
async function finCheckAlerts() {
|
|
8875
|
+
const alerts = financeState.alerts || [];
|
|
8876
|
+
if (!alerts.length) return;
|
|
8877
|
+
const syms = [...new Set(alerts.map((a) => a.symbol))];
|
|
8878
|
+
let rows = [];
|
|
8879
|
+
try {
|
|
8880
|
+
const r = await mt5bridge.call('symbols', { symbols: syms }, 10000);
|
|
8881
|
+
if (r && r.ok) rows = (r.data && r.data.symbols) || [];
|
|
8882
|
+
} catch {}
|
|
8883
|
+
const prices = {};
|
|
8884
|
+
for (const row of rows) {
|
|
8885
|
+
if (!row || row.missing) continue;
|
|
8886
|
+
prices[String(row.symbol)] = { bid: Number(row.bid), ask: Number(row.ask) };
|
|
8887
|
+
}
|
|
8888
|
+
let changed = false;
|
|
8889
|
+
for (const a of alerts.slice()) {
|
|
8890
|
+
const p = prices[a.symbol];
|
|
8891
|
+
if (!p || !isFinite(p.bid)) continue;
|
|
8892
|
+
const hit = a.direction === 'below' ? p.bid <= a.price : p.bid >= a.price;
|
|
8893
|
+
if (!hit) continue;
|
|
8894
|
+
changed = true;
|
|
8895
|
+
financeState.alerts = financeState.alerts.filter((x) => x.id !== a.id);
|
|
8896
|
+
const line = `🔔 ALARM: ${a.symbol} ${a.direction === 'below' ? '≤' : '≥'} ${a.price} (şimdi ${p.bid})${a.note ? ' — ' + a.note : ''}`;
|
|
8897
|
+
financeLog('[alarm] ' + line);
|
|
8898
|
+
financeNotify(line, 'alert');
|
|
8899
|
+
finJournal({ kind: 'alert', symbol: a.symbol, price: a.price, direction: a.direction, note: a.note || '' });
|
|
8900
|
+
}
|
|
8901
|
+
if (changed) finAlertsSave();
|
|
8902
|
+
}
|
|
8903
|
+
|
|
8904
|
+
async function finWatchTick() {
|
|
8905
|
+
if (!mt5bridge.running || financeState.watchBusy) return;
|
|
8906
|
+
financeState.watchBusy = true;
|
|
8907
|
+
try {
|
|
8908
|
+
const cfg = finCfg();
|
|
8909
|
+
financeState.watchTickAt = Date.now();
|
|
8910
|
+
const [posR, accR] = await Promise.all([
|
|
8911
|
+
mt5bridge.call('positions', {}, 8000).catch(() => null),
|
|
8912
|
+
mt5bridge.call('account', {}, 8000).catch(() => null),
|
|
8913
|
+
]);
|
|
8914
|
+
const account = accR && accR.ok ? (accR.data && accR.data.account) : null;
|
|
8915
|
+
const positionsOk = !!(posR && posR.ok);
|
|
8916
|
+
const positions = positionsOk ? ((posR.data && posR.data.positions) || []) : [];
|
|
8917
|
+
if (account) {
|
|
8918
|
+
financeState.account = account;
|
|
8919
|
+
finEquitySample(account);
|
|
8920
|
+
}
|
|
8921
|
+
const live = new Set();
|
|
8922
|
+
for (const p of positions) {
|
|
8923
|
+
const ticket = String(p.ticket);
|
|
8924
|
+
live.add(ticket);
|
|
8925
|
+
let st = financeState.watch.get(ticket);
|
|
8926
|
+
if (!st) {
|
|
8927
|
+
st = {
|
|
8928
|
+
symbol: String(p.symbol || ''),
|
|
8929
|
+
side: Number(p.type) === 0 ? 'buy' : 'sell',
|
|
8930
|
+
entry: Number(p.price_open) || 0,
|
|
8931
|
+
r: 0,
|
|
8932
|
+
be: false,
|
|
8933
|
+
partial: false,
|
|
8934
|
+
warnNoSL: false,
|
|
8935
|
+
warnSL: false,
|
|
8936
|
+
seenAt: Date.now(),
|
|
8937
|
+
};
|
|
8938
|
+
financeState.watch.set(ticket, st);
|
|
8939
|
+
const isBeast = Number(p.magic) === 20260908 || /beast/i.test(String(p.comment || ''));
|
|
8940
|
+
if (isBeast) {
|
|
8941
|
+
finJournal({ kind: 'open', ticket, symbol: st.symbol, side: st.side, volume: Number(p.volume) || 0, entry: st.entry, sl: Number(p.sl) || 0, tp: Number(p.tp) || 0 });
|
|
8942
|
+
}
|
|
8943
|
+
finWatchSave();
|
|
8944
|
+
}
|
|
8945
|
+
if (cfg.watchdog === false) continue;
|
|
8946
|
+
let meta = null;
|
|
8947
|
+
try { meta = await finSymbolMetaGet(p.symbol); } catch {}
|
|
8948
|
+
if (!meta) continue;
|
|
8949
|
+
let decision;
|
|
8950
|
+
try { decision = finwatch.plan(p, meta, st, cfg); } catch { continue; }
|
|
8951
|
+
if (decision.r > 0 && !(st.r > 0)) {
|
|
8952
|
+
st.r = decision.r;
|
|
8953
|
+
finWatchSave();
|
|
8954
|
+
}
|
|
8955
|
+
for (const act of decision.actions) {
|
|
8956
|
+
if (act.kind === 'modify') {
|
|
8957
|
+
const r = await mt5bridge.call('modify', { ticket: p.ticket, sl: act.sl, tp: Number(p.tp) || 0 }, 15000).catch(() => null);
|
|
8958
|
+
if (r && r.ok) {
|
|
8959
|
+
st.be = true;
|
|
8960
|
+
const line = `🛡️ SL taşındı: ${p.symbol} #${ticket} → ${act.sl}`;
|
|
8961
|
+
financeLog('[watchdog] ' + line);
|
|
8962
|
+
if (cfg.notifyWatchdog !== false) financeNotify(line, 'watchdog');
|
|
8963
|
+
finJournal({ kind: 'watchdog', action: 'sl-move', ticket, symbol: p.symbol, sl: act.sl });
|
|
8964
|
+
}
|
|
8965
|
+
} else if (act.kind === 'partial') {
|
|
8966
|
+
const r = await mt5bridge.call('close', { ticket: p.ticket, volume: act.volume }, 15000).catch(() => null);
|
|
8967
|
+
if (r && r.ok) {
|
|
8968
|
+
st.partial = true;
|
|
8969
|
+
const line = `🎯 Kısmi TP: ${p.symbol} #${ticket} ${act.volume} lot kapatıldı`;
|
|
8970
|
+
financeLog('[watchdog] ' + line);
|
|
8971
|
+
if (cfg.notifyWatchdog !== false) financeNotify(line, 'watchdog');
|
|
8972
|
+
finJournal({ kind: 'watchdog', action: 'partial-tp', ticket, symbol: p.symbol, volume: act.volume });
|
|
8973
|
+
finStatsRefresh(true);
|
|
8974
|
+
}
|
|
8975
|
+
} else if (act.kind === 'warnSL') {
|
|
8976
|
+
st.warnSL = true;
|
|
8977
|
+
const line = `⚠️ SL yaklaşıyor: ${p.symbol} #${ticket} (SL ${p.sl}, fiyat ${p.price_current})`;
|
|
8978
|
+
financeLog('[watchdog] ' + line);
|
|
8979
|
+
if (cfg.notifyWatchdog !== false) financeNotify(line, 'watchdog');
|
|
8980
|
+
finJournal({ kind: 'watchdog', action: 'sl-near', ticket, symbol: p.symbol, sl: Number(p.sl) || 0 });
|
|
8981
|
+
} else if (act.kind === 'warnNoSL') {
|
|
8982
|
+
st.warnNoSL = true;
|
|
8983
|
+
const line = `⚠️ SL'siz pozisyon: ${p.symbol} #${ticket} — risk koruması yok`;
|
|
8984
|
+
financeLog('[watchdog] ' + line);
|
|
8985
|
+
if (cfg.notifyWatchdog !== false) financeNotify(line, 'watchdog');
|
|
8986
|
+
}
|
|
8987
|
+
}
|
|
8988
|
+
if (decision.rearmSL && st.warnSL) st.warnSL = false;
|
|
8989
|
+
}
|
|
8990
|
+
/* kapanan pozisyonlar: net K/Z ile günlük + bildirim (yalnız pozisyon
|
|
8991
|
+
listesi GERÇEKTEN alındıysa — köprü kesintisinde yanlış kapanış yok) */
|
|
8992
|
+
if (positionsOk) {
|
|
8993
|
+
for (const [ticket, st] of [...financeState.watch]) {
|
|
8994
|
+
if (live.has(ticket)) continue;
|
|
8995
|
+
financeState.watch.delete(ticket);
|
|
8996
|
+
finWatchSave();
|
|
8997
|
+
try { await finRecordClose(ticket, st); } catch {}
|
|
8998
|
+
}
|
|
8999
|
+
}
|
|
9000
|
+
try { await finCheckAlerts(); } catch {}
|
|
9001
|
+
if (!financeState.lastStatsAt || Date.now() - financeState.lastStatsAt > 120000) {
|
|
9002
|
+
financeState.lastStatsAt = Date.now();
|
|
9003
|
+
finStatsRefresh(true).catch(() => {});
|
|
9004
|
+
}
|
|
9005
|
+
finMaybeWeeklyReport();
|
|
9006
|
+
} catch {
|
|
9007
|
+
} finally {
|
|
9008
|
+
financeState.watchBusy = false;
|
|
9009
|
+
}
|
|
9010
|
+
}
|
|
9011
|
+
|
|
9012
|
+
function finWatchStart() {
|
|
9013
|
+
if (financeState.watchTimer) return;
|
|
9014
|
+
financeState.watchTimer = setInterval(() => { finWatchTick().catch(() => {}); }, 5000);
|
|
9015
|
+
finWatchTick().catch(() => {});
|
|
9016
|
+
}
|
|
9017
|
+
|
|
9018
|
+
function finWatchStop() {
|
|
9019
|
+
if (financeState.watchTimer) {
|
|
9020
|
+
clearInterval(financeState.watchTimer);
|
|
9021
|
+
financeState.watchTimer = null;
|
|
9022
|
+
}
|
|
9023
|
+
}
|
|
9024
|
+
|
|
9025
|
+
function finWatchLoad() {
|
|
9026
|
+
const raw = finReadJson(finFile('watchdog.json'), {});
|
|
9027
|
+
try {
|
|
9028
|
+
for (const [k, v] of Object.entries(raw || {})) {
|
|
9029
|
+
if (!v || typeof v !== 'object') continue;
|
|
9030
|
+
financeState.watch.set(String(k), v);
|
|
9031
|
+
}
|
|
9032
|
+
} catch {}
|
|
9033
|
+
}
|
|
9034
|
+
|
|
9035
|
+
function finWatchSave() {
|
|
9036
|
+
try { finWriteJson(finFile('watchdog.json'), Object.fromEntries(financeState.watch)); } catch {}
|
|
9037
|
+
}
|
|
9038
|
+
|
|
9039
|
+
/* ---- haftalık rapor (ajan değerlendirmesi + istatistik + günlük) ---- */
|
|
9040
|
+
async function finWeeklyReport(manual) {
|
|
9041
|
+
const cfg = finCfg();
|
|
9042
|
+
const now = Date.now();
|
|
9043
|
+
if (!manual && cfg.weeklyReport === false) return null;
|
|
9044
|
+
if (!manual && Number(cfg.lastReportAt) && now - Number(cfg.lastReportAt) < 7 * 86400000) return null;
|
|
9045
|
+
let stats = financeState.stats;
|
|
9046
|
+
try { stats = (await finStatsRefresh(true)) || stats; } catch {}
|
|
9047
|
+
let account = financeState.account;
|
|
9048
|
+
const [accR, posR] = await Promise.all([
|
|
9049
|
+
account ? Promise.resolve(null) : mt5bridge.call('account', {}, 8000).catch(() => null),
|
|
9050
|
+
mt5bridge.call('positions', {}, 8000).catch(() => null),
|
|
9051
|
+
]);
|
|
9052
|
+
if (accR && accR.ok) account = accR.data && accR.data.account;
|
|
9053
|
+
const positions = posR && posR.ok ? ((posR.data && posR.data.positions) || []) : [];
|
|
9054
|
+
const fromTs = now - 7 * 86400000;
|
|
9055
|
+
const equity = (financeState.equity || []).filter((p) => Number(p.at) >= fromTs - 86400000);
|
|
9056
|
+
const journal = finJournalTail(40);
|
|
9057
|
+
const drawdown = finstats.maxDrawdown(equity);
|
|
9058
|
+
let review = '';
|
|
9059
|
+
if (engine) {
|
|
9060
|
+
try {
|
|
9061
|
+
const task =
|
|
9062
|
+
'Beast Finance haftalık performans değerlendirmesi: aşağıdaki istatistik ve işlem günlüğünü ELEŞTİREL yorumla. ' +
|
|
9063
|
+
'Ne iyi gitti, hangi hatalar tekrarlandı, gelecek hafta ne değişmeli? En fazla 12 satır, madde madde, Türkçe.';
|
|
9064
|
+
const ctx = JSON.stringify({ stats, drawdown, journal: journal.slice(-20) }).slice(0, 12000);
|
|
9065
|
+
const signal = typeof AbortSignal !== 'undefined' && AbortSignal.timeout ? AbortSignal.timeout(120000) : undefined;
|
|
9066
|
+
const res = await engine._subagent(task, ctx, signal, null, '');
|
|
9067
|
+
review = String(res || '').trim().slice(0, 4000);
|
|
9068
|
+
} catch {}
|
|
9069
|
+
}
|
|
9070
|
+
const md = finstats.buildWeeklyReport({ stats, drawdown, equity, journal, account, positions, fromTs, toTs: now, review });
|
|
9071
|
+
const day = finstats.dayKey(now);
|
|
9072
|
+
const file = path.join(financeDir(), 'reports', 'weekly-' + day + '.md');
|
|
9073
|
+
try {
|
|
9074
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
9075
|
+
fs.writeFileSync(file, md);
|
|
9076
|
+
} catch {}
|
|
9077
|
+
cfg.lastReportAt = now;
|
|
9078
|
+
try { saveSettings(); } catch {}
|
|
9079
|
+
const n = stats ? (Number(stats.netProfit) >= 0 ? '+' : '') + stats.netProfit : '?';
|
|
9080
|
+
const line = `📊 Haftalık rapor hazır (net ${n}) — ${path.basename(file)}`;
|
|
9081
|
+
financeLog('[rapor] ' + line);
|
|
9082
|
+
financeNotify(line, 'report');
|
|
9083
|
+
return { ok: true, path: file, text: md, stats };
|
|
9084
|
+
}
|
|
9085
|
+
|
|
9086
|
+
function finMaybeWeeklyReport() {
|
|
9087
|
+
try {
|
|
9088
|
+
const cfg = finCfg();
|
|
9089
|
+
if (cfg.weeklyReport === false) return;
|
|
9090
|
+
if (!Number(cfg.lastReportAt)) {
|
|
9091
|
+
/* ilk kurulum: temel al — rapor 7 gün sonra üretilir (boş rapor atma) */
|
|
9092
|
+
cfg.lastReportAt = Date.now();
|
|
9093
|
+
try { saveSettings(); } catch {}
|
|
9094
|
+
return;
|
|
9095
|
+
}
|
|
9096
|
+
if (Date.now() - Number(cfg.lastReportAt) < 7 * 86400000) return;
|
|
9097
|
+
if (Date.now() - Number(financeState.reportCheckAt || 0) < 3600000) return;
|
|
9098
|
+
financeState.reportCheckAt = Date.now();
|
|
9099
|
+
finWeeklyReport(false).catch(() => {});
|
|
9100
|
+
} catch {}
|
|
9101
|
+
}
|
|
9102
|
+
|
|
9103
|
+
try { finAlertsLoad(); } catch {}
|
|
9104
|
+
try { finWatchLoad(); } catch {}
|
|
9105
|
+
try { finStatsLoad(); } catch {}
|
|
9106
|
+
try { finEquityLoad(); } catch {}
|
|
9107
|
+
|
|
9108
|
+
/* financetools ayar + bildirim kancası: her olay kalıcı günlüğe + bildirime */
|
|
8507
9109
|
try {
|
|
8508
9110
|
financetools.setConfig(() => finCfg());
|
|
9111
|
+
financetools.setAlerts(finAlertApi());
|
|
8509
9112
|
financetools.setNotify((entry) => {
|
|
8510
9113
|
const k = String((entry && entry.kind) || '');
|
|
8511
9114
|
const d = entry && entry.data ? entry.data : {};
|
|
9115
|
+
const ai = finAgentInfo(entry && entry.sid);
|
|
9116
|
+
const who = ai.label || '';
|
|
9117
|
+
const sid = String((entry && entry.sid) || '');
|
|
9118
|
+
if (k === 'note') {
|
|
9119
|
+
finJournal({ kind: 'note', sid, agent: who, symbol: d.symbol, note: d.note, ticket: d.ticket || 0, side: d.side || '' });
|
|
9120
|
+
return;
|
|
9121
|
+
}
|
|
9122
|
+
if (k === 'trade') {
|
|
9123
|
+
finJournal({ kind: 'trade', sid, agent: who, symbol: d.symbol, side: d.side, volume: d.volume, sl: d.sl, tp: d.tp, reason: d.comment || '' });
|
|
9124
|
+
} else if (k === 'close') {
|
|
9125
|
+
finJournal({ kind: 'close-req', sid, agent: who, ticket: d.ticket, volume: d.volume });
|
|
9126
|
+
return; /* kesin kapanış K/Z'si watchdog tarafından yazılır (çift bildirim yok) */
|
|
9127
|
+
} else if (k === 'modify') {
|
|
9128
|
+
finJournal({ kind: 'modify', sid, agent: who, ticket: d.ticket, sl: d.sl, tp: d.tp });
|
|
9129
|
+
} else if (k === 'pending') {
|
|
9130
|
+
finJournal({ kind: 'pending', sid, agent: who, symbol: d.symbol, type: d.type, volume: d.volume });
|
|
9131
|
+
} else if (k === 'cancel') {
|
|
9132
|
+
finJournal({ kind: 'cancel', sid, agent: who, ticket: d.ticket });
|
|
9133
|
+
}
|
|
8512
9134
|
let line = '';
|
|
8513
|
-
if (k === 'trade') line = `İŞLEM AÇILDI: ${d.side} ${d.volume} ${d.symbol}`;
|
|
8514
|
-
else if (k === '
|
|
8515
|
-
else if (k === '
|
|
8516
|
-
else if (k === 'pending') line = `BEKLEYEN EMİR: ${d.type} ${d.volume} ${d.symbol}`;
|
|
9135
|
+
if (k === 'trade') line = `İŞLEM AÇILDI: ${d.side} ${d.volume} ${d.symbol}${who ? ' · ' + who : ''}`;
|
|
9136
|
+
else if (k === 'modify') line = `SL/TP GÜNCELLENDİ: ticket ${d.ticket} (SL ${d.sl || 0} / TP ${d.tp || 0})${who ? ' · ' + who : ''}`;
|
|
9137
|
+
else if (k === 'pending') line = `BEKLEYEN EMİR: ${d.type} ${d.volume} ${d.symbol}${who ? ' · ' + who : ''}`;
|
|
8517
9138
|
else if (k === 'cancel') line = `EMİR İPTAL: ticket ${d.ticket}`;
|
|
8518
9139
|
if (line) {
|
|
8519
9140
|
financeLog('[ajan] ' + line);
|
|
8520
9141
|
finPush('trade', { line });
|
|
9142
|
+
if (finCfg().notifyTrades !== false && (k === 'trade' || k === 'pending' || k === 'cancel')) {
|
|
9143
|
+
financeNotify(line, k, false); /* panel toast'ı yukarıda — kanallara gönder */
|
|
9144
|
+
}
|
|
9145
|
+
if (k === 'trade') finStatsRefresh(true).catch(() => {});
|
|
8521
9146
|
}
|
|
8522
9147
|
});
|
|
8523
9148
|
} catch {}
|
|
@@ -8560,6 +9185,7 @@ function financeEnsureBridge() {
|
|
|
8560
9185
|
candidates,
|
|
8561
9186
|
terminal: String(f.terminalPath || '').trim(),
|
|
8562
9187
|
});
|
|
9188
|
+
finWatchStart(); /* risk otomasyonu + bildirimler köprü açılırken başlar */
|
|
8563
9189
|
return mt5bridge.status();
|
|
8564
9190
|
}
|
|
8565
9191
|
|
|
@@ -8597,6 +9223,8 @@ mt5bridge.on('bridge', (m) => {
|
|
|
8597
9223
|
if (m && m.connected) {
|
|
8598
9224
|
financeLog('[MT5] terminal bağlı: ' + ((m.terminal && (m.terminal.name + ' @ ' + m.terminal.company)) || 'MT5'));
|
|
8599
9225
|
if (m.account) financeLog(`[MT5] hesap ${m.account.login} · bakiye ${m.account.balance} ${m.account.currency}`);
|
|
9226
|
+
finWatchStart();
|
|
9227
|
+
finStatsRefresh(true).catch(() => {});
|
|
8600
9228
|
} else if (m && m.error) {
|
|
8601
9229
|
financeLog('[MT5] bağlantı yok — ' + m.error);
|
|
8602
9230
|
if (/paket/i.test(m.error)) financeTryInstall();
|
|
@@ -8765,12 +9393,14 @@ function finTraderBrief(agent) {
|
|
|
8765
9393
|
`Tur aralığı: ${f.intervalSec} sn · Max lot: ${f.maxLot} · Max eşzamanlı pozisyon: ${f.maxPositions}`,
|
|
8766
9394
|
roleDef
|
|
8767
9395
|
? `UZMANLIK: ${roleDef.desc} — raporlarını bu çerçevede yaz; İŞLEM AÇMA, yalnız analiz + net öneri üret.`
|
|
8768
|
-
: 'Otomatik işlem AÇIK (daima): mt5_trade/mt5_close/mt5_modify/mt5_pending kullanabilirsin (limitler sistemce zorlanır).',
|
|
9396
|
+
: 'Otomatik işlem AÇIK (daima): mt5_trade/mt5_close/mt5_modify/mt5_pending kullanabilirsin (limitler sistemce zorlanır). Lot için mt5_risksize hesapla ya da mt5_trade’e riskPct ver; SL/TP broker stops_level mesafesine uymalı.',
|
|
8769
9397
|
roleDef
|
|
8770
9398
|
? '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.'
|
|
8771
9399
|
: f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : 'Strateji notu yok: trend + destek/direnç + momentum ile temel okuma yap.',
|
|
8772
9400
|
!roleDef && f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : '',
|
|
8773
|
-
'Bu turda: mt5_status → hesap/pozisyon/fiyat verisi → değerlendirme → kararlar (veya BEKLE: sebep) → kısa rapor.',
|
|
9401
|
+
'Bu turda: mt5_status → hesap/pozisyon/fiyat verisi → mt5_rates/mt5_indicators ile teknik okuma → değerlendirme → kararlar (veya BEKLE: sebep) → kısa rapor.',
|
|
9402
|
+
'Önemli kararların gerekçesini mt5_note ile günlüğe yaz (haftalık performans raporu bu notları kullanır).',
|
|
9403
|
+
'Risk otomasyonu main süreçte 5 sn döngüyle çalışır (+R BE, trailing, kısmi TP) — sen yine de SL/TP seviyelerini aktif yönet.',
|
|
8774
9404
|
'Diğer finance/paralel ajanlarla koordinasyon için agent_dm aracı var (to: ajan başlığı anahtar kelimesi).',
|
|
8775
9405
|
].filter(Boolean).join('\n');
|
|
8776
9406
|
}
|
|
@@ -8847,7 +9477,7 @@ function finAgentRound(sid) {
|
|
|
8847
9477
|
? `UZMANLIK: ${roleDef.desc} — İŞLEM AÇMA, yalnız analiz + net öneri.`
|
|
8848
9478
|
: 'İşlem açabilirsin — limitlere uy, SL\u2019siz pozisyon bırakma.';
|
|
8849
9479
|
const focus = agent.symbols.length ? `Odak: ${agent.symbols.join(', ')}. ` : '';
|
|
8850
|
-
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);
|
|
9480
|
+
const round = `FINANCE TUR #${agent.round}: ${focus}hesap + pozisyonlar + fiyatları çek; ${roleDef ? 'rolüne uygun analiz yap (mt5_rates/mt5_indicators ile) ve öneri ver.' : 'açık pozisyonları yönet (SL/TP güncelle, hedefe ulaşanı kapat); mt5_rates/mt5_indicators ile yeni fırsatları değerlendir.'} ${auto} Önemli kararların gerekçesini mt5_note ile günlüğe yaz. Kısa rapor ver.`;
|
|
8851
9481
|
const launch = (planBlock) => {
|
|
8852
9482
|
if (!financeState.agents.has(String(sid))) return;
|
|
8853
9483
|
const ok = engine.send(sid, planBlock + round, { userAction: false });
|
|
@@ -8940,6 +9570,11 @@ ipcMain.handle('finance:snapshot', async () => {
|
|
|
8940
9570
|
roles: FIN_ROLES,
|
|
8941
9571
|
rolesAuto: FIN_ROLES_AUTO,
|
|
8942
9572
|
trader: { on: financeState.traderOn, busy, rounds: financeState.traderRounds, lastAt: financeState.lastRoundAt },
|
|
9573
|
+
/* performans + risk otomasyonu görünümü */
|
|
9574
|
+
stats: financeState.stats || null,
|
|
9575
|
+
equity: (financeState.equity || []).slice(-180),
|
|
9576
|
+
alerts: (financeState.alerts || []).slice(0, 50),
|
|
9577
|
+
watch: { on: !!financeState.watchTimer, managed: financeState.watch.size, lastAt: financeState.watchTickAt || 0 },
|
|
8943
9578
|
};
|
|
8944
9579
|
});
|
|
8945
9580
|
|
|
@@ -9017,6 +9652,30 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
|
|
|
9017
9652
|
}
|
|
9018
9653
|
if (p.maxLot !== undefined) f.maxLot = Math.max(0.01, Math.min(100, Number(p.maxLot) || 0.1));
|
|
9019
9654
|
if (p.maxPositions !== undefined) f.maxPositions = Math.max(1, Math.min(20, Math.round(Number(p.maxPositions) || 3)));
|
|
9655
|
+
/* RİSK OTOMASYONU + BİLDİRİM ayarları */
|
|
9656
|
+
if (p.watchdog !== undefined) f.watchdog = !!p.watchdog;
|
|
9657
|
+
if (p.beOnR !== undefined) f.beOnR = Math.max(0, Math.min(10, Number(p.beOnR) || 0));
|
|
9658
|
+
if (p.beOffsetR !== undefined) f.beOffsetR = Math.max(0, Math.min(1, Number(p.beOffsetR) || 0));
|
|
9659
|
+
if (p.trailStartR !== undefined) f.trailStartR = Math.max(0, Math.min(10, Number(p.trailStartR) || 0));
|
|
9660
|
+
if (p.trailR !== undefined) f.trailR = Math.max(0, Math.min(5, Number(p.trailR) || 0));
|
|
9661
|
+
if (p.partialR !== undefined) f.partialR = Math.max(0, Math.min(10, Number(p.partialR) || 0));
|
|
9662
|
+
if (p.partialPct !== undefined) f.partialPct = Math.max(0, Math.min(90, Number(p.partialPct) || 0));
|
|
9663
|
+
if (p.notifyTarget !== undefined) {
|
|
9664
|
+
const t = String(p.notifyTarget || 'auto').toLowerCase();
|
|
9665
|
+
f.notifyTarget = ['auto', 'whatsapp', 'telegram', 'discord', 'off'].includes(t) ? t : 'auto';
|
|
9666
|
+
}
|
|
9667
|
+
if (p.notifyTrades !== undefined) f.notifyTrades = !!p.notifyTrades;
|
|
9668
|
+
if (p.notifyWatchdog !== undefined) f.notifyWatchdog = !!p.notifyWatchdog;
|
|
9669
|
+
if (p.maxDailyLossPct !== undefined) f.maxDailyLossPct = Math.max(0, Math.min(50, Number(p.maxDailyLossPct) || 0));
|
|
9670
|
+
if (p.dailyLossAction !== undefined) {
|
|
9671
|
+
const v = String(p.dailyLossAction || 'warn').toLowerCase();
|
|
9672
|
+
f.dailyLossAction = ['warn', 'stop', 'flatten'].includes(v) ? v : 'warn';
|
|
9673
|
+
}
|
|
9674
|
+
if (p.riskPerTradePct !== undefined) f.riskPerTradePct = Math.max(0, Math.min(20, Number(p.riskPerTradePct) || 0));
|
|
9675
|
+
if (p.maxPerSymbol !== undefined) f.maxPerSymbol = Math.max(0, Math.min(20, Math.round(Number(p.maxPerSymbol) || 0)));
|
|
9676
|
+
if (p.maxSameSide !== undefined) f.maxSameSide = Math.max(0, Math.min(20, Math.round(Number(p.maxSameSide) || 0)));
|
|
9677
|
+
if (p.minMarginLevel !== undefined) f.minMarginLevel = Math.max(0, Math.min(1000, Number(p.minMarginLevel) || 0));
|
|
9678
|
+
if (p.weeklyReport !== undefined) f.weeklyReport = !!p.weeklyReport;
|
|
9020
9679
|
/* allowTrading artık AYARLANMAZ — daima true (finCfg zorlar) */
|
|
9021
9680
|
if (p.strategy !== undefined) f.strategy = String(p.strategy || '').slice(0, 2000);
|
|
9022
9681
|
if (p.analysisTeam !== undefined) f.analysisTeam = finRolesValid(p.analysisTeam);
|
|
@@ -9203,6 +9862,17 @@ ipcMain.handle('finance:install', async () => {
|
|
|
9203
9862
|
return { ok: true, installing: true };
|
|
9204
9863
|
});
|
|
9205
9864
|
|
|
9865
|
+
/* Haftalık raporu elle üret (panel butonu) — istatistik + günlük + ajan yorumu */
|
|
9866
|
+
ipcMain.handle('finance:report', async () => {
|
|
9867
|
+
try {
|
|
9868
|
+
const r = await finWeeklyReport(true);
|
|
9869
|
+
if (!r) return { ok: false, error: 'rapor üretilemedi' };
|
|
9870
|
+
return { ok: true, path: r.path, stats: r.stats || null };
|
|
9871
|
+
} catch (e) {
|
|
9872
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
9873
|
+
}
|
|
9874
|
+
});
|
|
9875
|
+
|
|
9206
9876
|
|
|
9207
9877
|
/* düşünme (reasoning) seviyesi */
|
|
9208
9878
|
ipcMain.handle('think:set', (_e, v) => {
|