beast-agent 2.32.0 → 2.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/preload.js CHANGED
@@ -120,6 +120,7 @@ contextBridge.exposeInMainWorld('beast', {
120
120
  financeCancel: (ticket) => ipcRenderer.invoke('finance:cancel', { ticket }),
121
121
  financeInstall: () => ipcRenderer.invoke('finance:install'),
122
122
  financeSymbolsList: (filter) => ipcRenderer.invoke('finance:symbols:list', filter),
123
+ financeReport: () => ipcRenderer.invoke('finance:report'),
123
124
  clipRead: () => ipcRenderer.invoke('clip:read'),
124
125
  clipWrite: (t) => ipcRenderer.invoke('clip:write', t),
125
126
  thinkSet: (v) => ipcRenderer.invoke('think:set', v),
@@ -190,6 +190,59 @@
190
190
  </div>
191
191
  <div id="finTraderStatus">Kapalı</div>
192
192
  </div>
193
+ <div class="fin-card">
194
+ <div class="fin-card-title">RİSK OTOMASYONU <span id="finWatchDot" class="fin-dot off" title="Koruma döngüsü"></span><span id="finWatchInfo" class="fin-hint"></span></div>
195
+ <div class="fin-field fin-check">
196
+ <label title="5 saniyelik döngü: +R'da SL'yi başabaşa çeker, trailing uygular, kısmi TP alır — ajan turunu beklemez">
197
+ <input id="finWatchdog" type="checkbox" /> Aktif — BE + trailing + kısmi TP
198
+ </label>
199
+ </div>
200
+ <div class="fin-grid2">
201
+ <div class="fin-field"><span>BE (R)</span><input id="finBeOnR" type="number" min="0" max="10" step="0.1" title="Kâr kaç R'ye ulaşınca SL başabaşa çekilsin (0 = kapalı)" /></div>
202
+ <div class="fin-field"><span>Trailing başlangıç (R)</span><input id="finTrailStartR" type="number" min="0" max="10" step="0.1" title="Kaç R kârdan sonra trailing başlasın (0 = kapalı)" /></div>
203
+ <div class="fin-field"><span>Trailing mesafe (R)</span><input id="finTrailR" type="number" min="0" max="5" step="0.1" title="SL fiyattan kaç R geriden gelsin" /></div>
204
+ <div class="fin-field"><span>Kısmi TP (R)</span><input id="finPartialR" type="number" min="0" max="10" step="0.1" title="Kaç R kârda kısmi kapatma yapılsın (0 = kapalı)" /></div>
205
+ <div class="fin-field"><span>Kısmi TP (%)</span><input id="finPartialPct" type="number" min="0" max="90" step="5" title="Kısmi kapatmada kapanacak lot yüzdesi" /></div>
206
+ <div class="fin-field"><span>Günlük kayıp uyarı (%)</span><input id="finMaxDailyLoss" type="number" min="0" max="50" step="0.5" title="Gün içi düşüş bu yüzdeyi geçince aksiyon uygulanır (0 = kapalı)" /></div>
207
+ </div>
208
+ <div class="fin-grid2">
209
+ <div class="fin-field"><span>İşlem riski (%)</span><input id="finRiskPct" type="number" min="0" max="20" step="0.1" title="Pozisyon başına hesap riski — mt5_risksize ve riskPct ile lot bu orandan hesaplanır" /></div>
210
+ <div class="fin-field"><span>Sembol başına max</span><input id="finMaxPerSymbol" type="number" min="0" max="20" step="1" title="Aynı sembolde aynı yönde en fazla kaç pozisyon (0 = sınırsız)" /></div>
211
+ <div class="fin-field"><span>Aynı yön max</span><input id="finMaxSameSide" type="number" min="0" max="20" step="1" title="Toplam aynı yönde (buy ya da sell) en fazla pozisyon (0 = sınırsız)" /></div>
212
+ <div class="fin-field"><span>Min marj seviyesi (%)</span><input id="finMinMarginLevel" type="number" min="0" max="1000" step="10" title="İşlem sonrası marj seviyesi bunun altına düşecekse işlem reddedilir (0 = kapalı)" /></div>
213
+ </div>
214
+ <div class="fin-field">
215
+ <span>Günlük limit aksiyonu</span>
216
+ <select id="finDailyLossAction" title="Günlük kayıp limiti aşılınca ne yapılsın">
217
+ <option value="warn">Yalnız uyar</option>
218
+ <option value="stop">Ajanları durdur</option>
219
+ <option value="flatten">Durdur + hepsini kapat</option>
220
+ </select>
221
+ </div>
222
+ <div class="fin-field">
223
+ <span>Bildirim kanalı</span>
224
+ <select id="finNotifyTarget" title="İşlem/koruma/alarm bildirimleri bu kanala gider; panel ve masaüstü toast her zaman çalışır">
225
+ <option value="auto">Otomatik (bağlı olan kanallar)</option>
226
+ <option value="telegram">Telegram</option>
227
+ <option value="whatsapp">WhatsApp</option>
228
+ <option value="discord">Discord</option>
229
+ <option value="off">Kapalı (yalnız panel)</option>
230
+ </select>
231
+ </div>
232
+ <div id="finAlertList" class="fin-alerts"><div class="fin-empty">Aktif fiyat alarmı yok</div></div>
233
+ </div>
234
+ <div class="fin-card">
235
+ <div class="fin-card-title">PERFORMANS <span id="finStatsInfo" class="fin-hint"></span><span class="file-spacer"></span><button id="finReportBtn" title="Haftalık rapor üret — istatistik + işlem günlüğü + ajan değerlendirmesi">&#128196; Rapor</button></div>
236
+ <div id="finEquity" class="fin-equity"></div>
237
+ <div class="fin-grid">
238
+ <div class="fin-kv"><span>Bugün</span><b id="finStatToday">—</b></div>
239
+ <div class="fin-kv"><span>Hafta</span><b id="finStatWeek">—</b></div>
240
+ <div class="fin-kv"><span>Win rate</span><b id="finStatWin">—</b></div>
241
+ <div class="fin-kv"><span>Profit factor</span><b id="finStatPf">—</b></div>
242
+ <div class="fin-kv"><span>Max DD</span><b id="finStatDd">—</b></div>
243
+ <div class="fin-kv"><span>İşlem (90g)</span><b id="finStatTrades">—</b></div>
244
+ </div>
245
+ </div>
193
246
  <div class="fin-card">
194
247
  <div class="fin-card-title">POZİSYONLAR <span id="finPosCount"></span></div>
195
248
  <div id="finPosList"><div class="fin-empty">Açık pozisyon yok</div></div>
@@ -194,6 +194,31 @@ const els = {
194
194
  finSymSearch: $('#finSymSearch'),
195
195
  finSymPick: $('#finSymPick'),
196
196
  finSymCount: $('#finSymCount'),
197
+ finWatchdogCb: $('#finWatchdog'),
198
+ finBeOnR: $('#finBeOnR'),
199
+ finTrailStartR: $('#finTrailStartR'),
200
+ finTrailR: $('#finTrailR'),
201
+ finPartialR: $('#finPartialR'),
202
+ finPartialPct: $('#finPartialPct'),
203
+ finMaxDailyLoss: $('#finMaxDailyLoss'),
204
+ finRiskPct: $('#finRiskPct'),
205
+ finMaxPerSymbol: $('#finMaxPerSymbol'),
206
+ finMaxSameSide: $('#finMaxSameSide'),
207
+ finMinMarginLevel: $('#finMinMarginLevel'),
208
+ finDailyLossAction: $('#finDailyLossAction'),
209
+ finNotifyTarget: $('#finNotifyTarget'),
210
+ finWatchDot: $('#finWatchDot'),
211
+ finWatchInfo: $('#finWatchInfo'),
212
+ finAlertList: $('#finAlertList'),
213
+ finReportBtn: $('#finReportBtn'),
214
+ finStatsInfo: $('#finStatsInfo'),
215
+ finStatToday: $('#finStatToday'),
216
+ finStatWeek: $('#finStatWeek'),
217
+ finStatWin: $('#finStatWin'),
218
+ finStatPf: $('#finStatPf'),
219
+ finStatDd: $('#finStatDd'),
220
+ finStatTrades: $('#finStatTrades'),
221
+ finEquityBox: $('#finEquity'),
197
222
  stVideoEl: $('#stVideoEl'),
198
223
  stVideoName: $('#stVideoName'),
199
224
  stVideoOpen: $('#stVideoOpen'),
@@ -9356,6 +9381,116 @@ function finRenderTrader(trader, cfg) {
9356
9381
  }
9357
9382
  }
9358
9383
 
9384
+ /* ---- RİSK OTOMASYONU + PERFORMANS panosu ---- */
9385
+ function finDayKey(ms) {
9386
+ const d = new Date(Number(ms) || 0);
9387
+ const p = (x) => String(x).padStart(2, '0');
9388
+ return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate());
9389
+ }
9390
+
9391
+ function finRenderAutomation(cfg, watch) {
9392
+ if (!cfg) return;
9393
+ const ae = document.activeElement;
9394
+ if (els.finWatchdogCb) els.finWatchdogCb.checked = cfg.watchdog !== false;
9395
+ const setNum = (el, v) => {
9396
+ if (el && ae !== el) el.value = v;
9397
+ };
9398
+ setNum(els.finBeOnR, cfg.beOnR != null ? cfg.beOnR : 1);
9399
+ setNum(els.finTrailStartR, cfg.trailStartR != null ? cfg.trailStartR : 1.5);
9400
+ setNum(els.finTrailR, cfg.trailR != null ? cfg.trailR : 0.5);
9401
+ setNum(els.finPartialR, cfg.partialR != null ? cfg.partialR : 0);
9402
+ setNum(els.finPartialPct, cfg.partialPct != null ? cfg.partialPct : 50);
9403
+ setNum(els.finMaxDailyLoss, cfg.maxDailyLossPct != null ? cfg.maxDailyLossPct : 3);
9404
+ setNum(els.finRiskPct, cfg.riskPerTradePct != null ? cfg.riskPerTradePct : 1);
9405
+ setNum(els.finMaxPerSymbol, cfg.maxPerSymbol != null ? cfg.maxPerSymbol : 2);
9406
+ setNum(els.finMaxSameSide, cfg.maxSameSide != null ? cfg.maxSameSide : 3);
9407
+ setNum(els.finMinMarginLevel, cfg.minMarginLevel != null ? cfg.minMarginLevel : 150);
9408
+ if (els.finDailyLossAction && ae !== els.finDailyLossAction) els.finDailyLossAction.value = cfg.dailyLossAction || 'stop';
9409
+ if (els.finNotifyTarget && ae !== els.finNotifyTarget) els.finNotifyTarget.value = cfg.notifyTarget || 'auto';
9410
+ if (els.finWatchDot) {
9411
+ els.finWatchDot.classList.remove('on', 'off', 'busy');
9412
+ els.finWatchDot.classList.add(watch && watch.on ? 'on' : 'off');
9413
+ els.finWatchDot.title = watch && watch.on ? 'Koruma döngüsü çalışıyor (5 sn)' : 'Koruma döngüsü kapalı';
9414
+ }
9415
+ if (els.finWatchInfo) {
9416
+ els.finWatchInfo.textContent = watch && watch.on ? '· izlenen ' + (watch.managed || 0) : '';
9417
+ }
9418
+ }
9419
+
9420
+ function finSparkline(points) {
9421
+ const list = (Array.isArray(points) ? points : []).filter((p) => p && isFinite(Number(p.equity)));
9422
+ if (list.length < 2) return '<div class="fin-empty">Equity verisi toplanıyor…</div>';
9423
+ const w = 100;
9424
+ const h = 28;
9425
+ const vals = list.map((p) => Number(p.equity));
9426
+ let min = Math.min(...vals);
9427
+ let max = Math.max(...vals);
9428
+ if (max - min < 1e-9) max = min + 1;
9429
+ const step = w / (list.length - 1);
9430
+ const pts = vals
9431
+ .map((v, i) => (i * step).toFixed(2) + ',' + (h - 2 - ((v - min) / (max - min)) * (h - 4)).toFixed(2))
9432
+ .join(' ');
9433
+ const up = vals[vals.length - 1] >= vals[0];
9434
+ return '<svg viewBox="0 0 ' + w + ' ' + h + '" preserveAspectRatio="none" class="' + (up ? 'up' : 'down') + '">' +
9435
+ '<polyline points="' + pts + '" fill="none" stroke="currentColor" stroke-width="1.4" vector-effect="non-scaling-stroke" /></svg>';
9436
+ }
9437
+
9438
+ function finRenderStats(stats, equity, cur) {
9439
+ const s = stats || null;
9440
+ if (els.finEquityBox) {
9441
+ els.finEquityBox.innerHTML = finSparkline(equity);
9442
+ }
9443
+ if (els.finStatsInfo) {
9444
+ els.finStatsInfo.textContent = s && s.at
9445
+ ? '· son 90 gün · ' + new Date(s.at).toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit' })
9446
+ : '';
9447
+ }
9448
+ if (!s) {
9449
+ finKvSet(els.finStatToday, '—');
9450
+ finKvSet(els.finStatWeek, '—');
9451
+ finKvSet(els.finStatWin, '—');
9452
+ finKvSet(els.finStatPf, '—');
9453
+ finKvSet(els.finStatDd, '—');
9454
+ finKvSet(els.finStatTrades, '—');
9455
+ return;
9456
+ }
9457
+ const byDay = s.byDay && typeof s.byDay === 'object' ? s.byDay : {};
9458
+ const today = Number(byDay[finDayKey(Date.now())]) || 0;
9459
+ const week = Object.entries(byDay)
9460
+ .filter(([d]) => Date.now() - new Date(d + 'T00:00:00').getTime() < 7 * 86400000)
9461
+ .reduce((a, [, v]) => a + (Number(v) || 0), 0);
9462
+ const fmt = (n) => (n > 0 ? '+' : '') + fmtMoney(n, cur);
9463
+ finKvSet(els.finStatToday, fmt(today), today !== 0 ? (today > 0 ? 'pos' : 'neg') : null);
9464
+ finKvSet(els.finStatWeek, fmt(week), week !== 0 ? (week > 0 ? 'pos' : 'neg') : null);
9465
+ finKvSet(els.finStatWin, '%' + (Number(s.winRate) || 0) + ' (' + (Number(s.wins) || 0) + '/' + (Number(s.losses) || 0) + ')');
9466
+ finKvSet(els.finStatPf, s.profitFactor == null ? '∞' : String(s.profitFactor));
9467
+ const dd = s.drawdown || {};
9468
+ finKvSet(els.finStatDd, '%' + (Number(dd.maxPct) || 0), (Number(dd.maxPct) || 0) > 0 ? 'neg' : null);
9469
+ finKvSet(els.finStatTrades, String(Number(s.trades) || 0));
9470
+ }
9471
+
9472
+ function finRenderAlerts(list) {
9473
+ const box = els.finAlertList;
9474
+ if (!box) return;
9475
+ box.textContent = '';
9476
+ if (!list || !list.length) {
9477
+ const d = document.createElement('div');
9478
+ d.className = 'fin-empty';
9479
+ d.textContent = 'Aktif fiyat alarmı yok';
9480
+ box.appendChild(d);
9481
+ return;
9482
+ }
9483
+ for (const a of list) {
9484
+ const row = document.createElement('div');
9485
+ row.className = 'fin-alert';
9486
+ row.innerHTML =
9487
+ '<span class="fa-sym">' + escapeHtml(String(a.symbol || '?')) + '</span>' +
9488
+ '<span class="fa-cond">' + (a.direction === 'below' ? '≤' : '≥') + ' ' + (Number(a.price) || 0) + '</span>' +
9489
+ (a.note ? '<span class="fa-note">' + escapeHtml(String(a.note)) + '</span>' : '');
9490
+ box.appendChild(row);
9491
+ }
9492
+ }
9493
+
9359
9494
  async function finFillModels() {
9360
9495
  if (!els.finTraderModel) return;
9361
9496
  let models = [];
@@ -9397,6 +9532,9 @@ async function finSnapshot() {
9397
9532
  finTraderInputsSet(r.cfg);
9398
9533
  finRenderRoles(r.cfg);
9399
9534
  finRenderTrader(r.trader, r.cfg);
9535
+ finRenderAutomation(r.cfg, r.watch);
9536
+ finRenderStats(r.stats, r.equity, r.account && r.account.currency ? r.account.currency : '');
9537
+ finRenderAlerts(r.alerts);
9400
9538
  }
9401
9539
 
9402
9540
  function finLogLine(line) {
@@ -9414,7 +9552,11 @@ function finLogLine(line) {
9414
9552
  function finOnEvent(ev) {
9415
9553
  if (!ev || ev.type !== 'finance') return;
9416
9554
  if (ev.fn === 'log' && ev.line) finLogLine(ev.line);
9417
- else if (ev.fn === 'trade' && ev.line) {
9555
+ else if (ev.fn === 'notify' && ev.line) {
9556
+ finLogLine(ev.line);
9557
+ toast(ev.line);
9558
+ finSnapshot();
9559
+ } else if (ev.fn === 'trade' && ev.line) {
9418
9560
  finLogLine(ev.line);
9419
9561
  toast(ev.line);
9420
9562
  finSnapshot();
@@ -9501,6 +9643,60 @@ if (els.finMaxLot) els.finMaxLot.addEventListener('change', () => {
9501
9643
  toast('Max lot: ' + v + ' — sonraki turdan itibaren geçerli');
9502
9644
  });
9503
9645
  if (els.finSymBtn) els.finSymBtn.addEventListener('click', finSymPickerOpen);
9646
+
9647
+ /* RİSK OTOMASYONU kontrolleri: değişiklik anında kaydedilir (ajan turunu beklemez) */
9648
+ if (els.finWatchdogCb) {
9649
+ els.finWatchdogCb.addEventListener('change', () => {
9650
+ finSaveCfg({ watchdog: els.finWatchdogCb.checked });
9651
+ toast('Risk otomasyonu ' + (els.finWatchdogCb.checked ? 'açık — BE/trailing/kısmi TP 5 sn döngüyle uygulanır' : 'kapalı'));
9652
+ });
9653
+ }
9654
+ const finAutoNum = (el, key, min, max, step) => {
9655
+ if (!el) return;
9656
+ el.addEventListener('change', () => {
9657
+ let v = Number(el.value);
9658
+ if (!isFinite(v)) v = min;
9659
+ v = Math.max(min, Math.min(max, step ? Math.round(v / step) * step : v));
9660
+ el.value = v;
9661
+ finSaveCfg({ [key]: v });
9662
+ });
9663
+ };
9664
+ finAutoNum(els.finBeOnR, 'beOnR', 0, 10);
9665
+ finAutoNum(els.finTrailStartR, 'trailStartR', 0, 10);
9666
+ finAutoNum(els.finTrailR, 'trailR', 0, 5);
9667
+ finAutoNum(els.finPartialR, 'partialR', 0, 10);
9668
+ finAutoNum(els.finPartialPct, 'partialPct', 0, 90, 5);
9669
+ finAutoNum(els.finMaxDailyLoss, 'maxDailyLossPct', 0, 50);
9670
+ finAutoNum(els.finRiskPct, 'riskPerTradePct', 0, 20, 0.1);
9671
+ finAutoNum(els.finMaxPerSymbol, 'maxPerSymbol', 0, 20, 1);
9672
+ finAutoNum(els.finMaxSameSide, 'maxSameSide', 0, 20, 1);
9673
+ finAutoNum(els.finMinMarginLevel, 'minMarginLevel', 0, 1000, 10);
9674
+ if (els.finDailyLossAction) {
9675
+ els.finDailyLossAction.addEventListener('change', () => {
9676
+ finSaveCfg({ dailyLossAction: els.finDailyLossAction.value });
9677
+ const t = els.finDailyLossAction.options[els.finDailyLossAction.selectedIndex].textContent;
9678
+ toast('Günlük limit aksiyonu: ' + t);
9679
+ });
9680
+ }
9681
+ if (els.finNotifyTarget) {
9682
+ els.finNotifyTarget.addEventListener('change', () => {
9683
+ finSaveCfg({ notifyTarget: els.finNotifyTarget.value });
9684
+ toast('Bildirim kanalı: ' + els.finNotifyTarget.options[els.finNotifyTarget.selectedIndex].textContent);
9685
+ });
9686
+ }
9687
+ if (els.finReportBtn) {
9688
+ els.finReportBtn.addEventListener('click', async () => {
9689
+ els.finReportBtn.disabled = true;
9690
+ els.finReportBtn.textContent = '⏳ Rapor…';
9691
+ const r = await beast.financeReport().catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
9692
+ els.finReportBtn.disabled = false;
9693
+ els.finReportBtn.textContent = '📄 Rapor';
9694
+ if (r && r.ok) toast('Rapor hazır: ' + String(r.path || '').split(/[\\/]/).pop());
9695
+ else toast('Rapor üretilemedi: ' + ((r && r.error) || '?'));
9696
+ finSnapshot();
9697
+ });
9698
+ }
9699
+
9504
9700
  /* OTOMATİK İŞLEM onay kutusu KALDIRILDI — trade ajanı daima işlem açabilir */
9505
9701
  if (els.finTraderModel) {
9506
9702
  els.finTraderModel.addEventListener('change', () => {
@@ -4845,6 +4845,29 @@ body.finance-mode #composerWrap { margin-right: var(--finW, 400px); }
4845
4845
  .fin-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
4846
4846
  .fin-grid2 .fin-field { margin-bottom: 8px; }
4847
4847
  .fin-hint { font-style: normal; opacity: 0.75; }
4848
+ /* RİSK OTOMASYONU + PERFORMANS kartları */
4849
+ .fin-check label { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text); cursor: pointer; }
4850
+ .fin-check input { accent-color: var(--accent); margin: 0; }
4851
+ .fin-kv b.pos { color: #22c55e; }
4852
+ .fin-kv b.neg { color: #ef4444; }
4853
+ .fin-equity { height: 34px; margin-bottom: 8px; color: var(--muted); }
4854
+ .fin-equity svg { width: 100%; height: 100%; display: block; }
4855
+ .fin-equity svg.up { color: #22c55e; }
4856
+ .fin-equity svg.down { color: #ef4444; }
4857
+ .fin-alerts { margin-top: 6px; display: flex; flex-direction: column; gap: 4px; }
4858
+ .fin-alert {
4859
+ display: flex;
4860
+ align-items: center;
4861
+ gap: 6px;
4862
+ font-size: 11px;
4863
+ background: var(--panel2);
4864
+ border-radius: 4px;
4865
+ padding: 3px 6px;
4866
+ min-width: 0;
4867
+ }
4868
+ .fin-alert .fa-sym { font-weight: 700; }
4869
+ .fin-alert .fa-cond { color: #eab308; font-variant-numeric: tabular-nums; }
4870
+ .fin-alert .fa-note { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4848
4871
  /* ekib sayısı + otomatik atama özeti */
4849
4872
  .fin-team { display: flex; align-items: center; gap: 8px; min-width: 0; }
4850
4873
  .fin-team-hint {