beast-agent 2.76.4 → 2.76.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.76.4",
4
+ "version": "2.76.6",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
package/src/main.js CHANGED
@@ -10006,16 +10006,10 @@ function finCfg() {
10006
10006
  finNormalizeSymbolLimits(f);
10007
10007
  if (f.maxPositions == null) f.maxPositions = 3;
10008
10008
  /* KODLA DİSİPLİN (trader kuralları): hepsi 0 = kural kapalı */
10009
- /* GÜNLÜK İŞLEM LİMİTİ KALDIRILDI (varsayılan KAPALI/0 = sınırsız): eski
10010
- sürümün zorla koyduğu 10 değeri BİR KEZ sıfırlanır; kullanıcı bilerek
10011
- başka değer girdiyse dokunulmaz. Talimatta "günde en fazla N işlem"
10012
- yazılıysa not yine geçerlidir (bilinçli opt-in). */
10013
- if (!Number.isFinite(Number(f.maxTradesPerDay))) f.maxTradesPerDay = 0;
10014
- if (f.maxTradesPerDayDefaultCleared !== true) {
10015
- f.maxTradesPerDayDefaultCleared = true;
10016
- if (Number(f.maxTradesPerDay) === 10) f.maxTradesPerDay = 0;
10017
- try { saveSettings(); } catch {}
10018
- }
10009
+ /* GÜNLÜK İŞLEM LİMİTİ TAMAMEN KALDIRILDI: ayar değeri artık uygulanmaz
10010
+ (0'a sabitlenir). Günlük limit YALNIZ talimata "günde en fazla N işlem"
10011
+ yazılırsa (not) devreye girer — varsayılan SINIRSIZ. */
10012
+ f.maxTradesPerDay = 0;
10019
10013
  if (!Number.isFinite(Number(f.lossStreakLimit))) f.lossStreakLimit = 2;
10020
10014
  if (!Number.isFinite(Number(f.lossStreakPauseMin))) f.lossStreakPauseMin = 30;
10021
10015
  /* RE-ENTRY BEKLEME: kural VARSAYILAN KAPALI (0) — isteyen Gelişmiş
@@ -10422,18 +10416,18 @@ function finTeamDigest() {
10422
10416
  function finDisciplineError(side, symbol, positions) {
10423
10417
  try {
10424
10418
  const cfg = finCfg();
10425
- /* TALİMAT: "günde en fazla N işlem" → günlük işlem tavanını ezer */
10426
- if (Number(cfg.maxTradesPerDayNote) > 0) {
10427
- return finrisk.disciplineError(
10428
- finJournalTail(400),
10429
- Date.now(),
10430
- { ...cfg, maxTradesPerDay: Number(cfg.maxTradesPerDayNote) },
10431
- side,
10432
- symbol,
10433
- positions
10434
- );
10435
- }
10436
- return finrisk.disciplineError(finJournalTail(400), Date.now(), cfg, side, symbol, positions);
10419
+ /* GÜNLÜK İŞLEM LİMİTİ: yalnız TALİMAT notu uygulanır ("günde en fazla N
10420
+ işlem"); ayarlardaki değer KALDIRILDI. Not yoksa kural kapalı = sınırsız
10421
+ (günlük işlem sayısı asla yeni girişi engellemez). */
10422
+ const dayNote = Number(cfg.maxTradesPerDayNote) > 0 ? Number(cfg.maxTradesPerDayNote) : 0;
10423
+ return finrisk.disciplineError(
10424
+ finJournalTail(400),
10425
+ Date.now(),
10426
+ { ...cfg, maxTradesPerDay: dayNote },
10427
+ side,
10428
+ symbol,
10429
+ positions
10430
+ );
10437
10431
  } catch {
10438
10432
  return null;
10439
10433
  }
@@ -10932,23 +10926,42 @@ async function finStopAllFinanceAgents(reason) {
10932
10926
  return sids.length;
10933
10927
  }
10934
10928
 
10935
- /* Tüm açık pozisyonları kapatır (günlük limit 'flatten' aksiyonu). */
10936
- async function finFlattenAll() {
10929
+ /* Tüm açık pozisyonları kapatır (TOPLU KAPATMA).
10930
+ reason: 'risk' (günlük limit flatten) | 'manuel' (panel butonu) |
10931
+ 'kural' (talimat kâr hedefi — "karda tümünü kapat" gibi).
10932
+ opts.withPending: bekleyen emirler de iptal edilir (yeni pozisyon açılmasın). */
10933
+ async function finFlattenAll(reason, opts) {
10937
10934
  if (financeState.flattening) return 0;
10938
10935
  if (!mt5bridge.running) return 0;
10936
+ const why = ['risk', 'manuel', 'kural'].includes(String(reason)) ? String(reason) : 'risk';
10939
10937
  financeState.flattening = true;
10940
10938
  let closed = 0;
10941
10939
  try {
10940
+ /* ÖNCE bekleyen emirler (ops.): kapanırken yeni pozisyon aktifleşmesin */
10941
+ if (opts && opts.withPending) {
10942
+ try {
10943
+ const o = await mt5bridge.call('orders', {}, 10000);
10944
+ const orders = (o && o.ok && o.data && o.data.orders) || [];
10945
+ for (const ord of orders) {
10946
+ await mt5bridge.call('cancel', { ticket: ord.ticket }, 10000).catch(() => null);
10947
+ }
10948
+ if (orders.length) financeLog('[toplu] ' + orders.length + ' bekleyen emir iptal edildi');
10949
+ } catch {}
10950
+ }
10942
10951
  const r = await mt5bridge.call('positions', {}, 10000);
10943
10952
  const list = (r && r.ok && r.data && r.data.positions) || [];
10944
10953
  for (const p of list) {
10945
10954
  const cr = await mt5bridge.call('close', { ticket: p.ticket, volume: 0 }, 15000).catch(() => null);
10946
10955
  if (cr && cr.ok) closed++;
10947
10956
  }
10948
- const line = `🚨 Günlük limit: ${closed}/${list.length} pozisyon kapatıldı`;
10949
- financeLog('[risk] ' + line);
10950
- financeNotify(line, 'risk');
10951
- finJournal({ kind: 'risk-flatten', closed, total: list.length });
10957
+ const line = why === 'manuel'
10958
+ ? `🧯 Toplu kapatma (panel): ${closed}/${list.length} pozisyon kapatıldı`
10959
+ : why === 'kural'
10960
+ ? `🎯 Toplu kapatma (kâr hedefi): ${closed}/${list.length} pozisyon kapatıldı`
10961
+ : `🚨 Günlük limit: ${closed}/${list.length} pozisyon kapatıldı`;
10962
+ financeLog('[' + why + '] ' + line);
10963
+ financeNotify(line, why === 'risk' ? 'risk' : 'close');
10964
+ finJournal({ kind: why === 'risk' ? 'risk-flatten' : 'close-all', why, closed, total: list.length });
10952
10965
  finStatsRefresh(true).catch(() => {});
10953
10966
  } finally {
10954
10967
  financeState.flattening = false;
@@ -10956,6 +10969,34 @@ async function finFlattenAll() {
10956
10969
  return closed;
10957
10970
  }
10958
10971
 
10972
+ /* TOPLU KAPATMA KURALI (talimat): "karda tümünü kapat" / "toplam kâr %2 olunca
10973
+ hepsini kapat" / "50 dolar kârda tümünü kapat" → sepet (tüm pozisyonların
10974
+ toplam yüzen K/Z'si) hedefe ulaşınca HEPSİ kapatılır. Kural yoksa no-op. */
10975
+ async function finBasketCloseCheck(positions, account) {
10976
+ const rule = finParsedInstr().manage.closeAllProfit;
10977
+ if (!rule) return;
10978
+ if (financeState.flattening) return;
10979
+ const list = Array.isArray(positions) ? positions.filter(Boolean) : [];
10980
+ if (!list.length) return;
10981
+ const total = Math.round(list.reduce((a, p) => a + (Number(p.profit) || 0), 0) * 100) / 100;
10982
+ const bal = Number(account && account.balance) || 0;
10983
+ let hit = false;
10984
+ if (rule.money != null) hit = total >= Number(rule.money);
10985
+ else if (rule.pct != null && bal > 0) hit = total >= (bal * Number(rule.pct)) / 100;
10986
+ else hit = total > 0; /* "karda tümünü kapat" → herhangi bir net kâr */
10987
+ if (!hit) return;
10988
+ const closed = await finFlattenAll('kural', { withPending: true });
10989
+ if (closed > 0) {
10990
+ financeNotify(`🎯 Toplu kapatma kuralı: sepet +${total} → ${closed} pozisyon kapatıldı`, 'close');
10991
+ finWakeAgents(
10992
+ `[FİNANS OLAYI — TOPLU KAPATMA] Talimat kâr hedefi tetiklendi: tüm pozisyonların toplamı +${total} (hedef ` +
10993
+ (rule.money != null ? rule.money : rule.pct != null ? '%' + rule.pct : 'kâr') +
10994
+ ') → tüm pozisyonlar kapatıldı. Hesabı ve yeni planı değerlendir.',
10995
+ { kind: 'close-all', total }
10996
+ );
10997
+ }
10998
+ }
10999
+
10959
11000
  /* ---- performans istatistikleri (kalıcı) ---- */
10960
11001
  function finStatsLoad() {
10961
11002
  const s = finReadJson(finFile('stats.json'), null);
@@ -11726,6 +11767,10 @@ async function finWatchTick() {
11726
11767
  if (positions.length) {
11727
11768
  try { finPosManagerMaybe(positions, account); } catch {}
11728
11769
  }
11770
+ /* TOPLU KAPATMA KURALI: talimat kâr hedefi (sepet) dolduysa hepsini kapat */
11771
+ if (positions.length) {
11772
+ try { await finBasketCloseCheck(positions, account); } catch {}
11773
+ }
11729
11774
  }
11730
11775
  try { await finCheckAlerts(); } catch {}
11731
11776
  if (!financeState.lastStatsAt || Date.now() - financeState.lastStatsAt > 120000) {
@@ -13973,6 +14018,31 @@ function finParseInstructions(text) {
13973
14018
  ) {
13974
14019
  out.noLossClose = true;
13975
14020
  }
14021
+ /* TOPLU KAPATMA (kâr hedefi): "karda tümünü kapat", "toplam kâr %2 olunca
14022
+ hepsini kapat", "50 dolar kârda tümünü kapat" → sepet (tüm pozisyonların
14023
+ toplam yüzen K/Z'si) hedefe ulaşınca HEPSİ kapatılır (kod uygular).
14024
+ Zarar hedefli panik kapatma bilinçli olarak KAPSAM DIŞI. */
14025
+ {
14026
+ const allClose =
14027
+ /(?:t[üu]m[üu]n[üu](?:\s*pozisyonlar[ıi]?)?|hepsini|t[üu]m\s*pozisyonlar[ıi]?|toplu(?:\s*kapatma)?)[^.\n]{0,30}?kapat/.test(low);
14028
+ if (allClose && /k[âa]r/.test(low)) {
14029
+ let pct = null;
14030
+ let money = null;
14031
+ let mm = low.match(/k[âa]r[^0-9%]{0,16}%\s*(\d+(?:[.,]\d+)?)/) ||
14032
+ low.match(/toplam[^0-9%]{0,16}%\s*(\d+(?:[.,]\d+)?)[^.\n]{0,16}?k[âa]r/);
14033
+ if (mm) pct = finInstrNum(mm[1]);
14034
+ if (pct == null) {
14035
+ mm = low.match(/(\d+(?:[.,]\d+)?)\s*(?:dolar|usd|\$)[^.\n]{0,16}?k[âa]r/) ||
14036
+ low.match(/k[âa]r[^0-9]{0,16}\$?\s*(\d+(?:[.,]\d+)?)/);
14037
+ if (mm) money = finInstrNum(mm[1]);
14038
+ }
14039
+ out.closeAllProfit = {
14040
+ pct: pct != null && pct > 0 ? Math.min(50, pct) : null,
14041
+ money: pct == null && money != null && money > 0 ? money : null,
14042
+ any: pct == null && money == null,
14043
+ };
14044
+ }
14045
+ }
13976
14046
  return out;
13977
14047
  }
13978
14048
 
@@ -13993,6 +14063,8 @@ function finParsedInstr() {
13993
14063
  partial: mgr.partial || trade.partial,
13994
14064
  /* YÖNETİM KURALI: zararda kapatma yasağı iki nottan birinde varsa geçerli */
13995
14065
  noLossClose: !!(mgr.noLossClose || trade.noLossClose),
14066
+ /* TOPLU KAPATMA kâr hedefi (iki nottan biri) */
14067
+ closeAllProfit: mgr.closeAllProfit || trade.closeAllProfit || null,
13996
14068
  },
13997
14069
  };
13998
14070
  }
@@ -14008,6 +14080,10 @@ function finInstrSummary(ins) {
14008
14080
  if (ins.entry.dailyLossPct != null) p.push(`günlük zarar limiti %${ins.entry.dailyLossPct}`);
14009
14081
  if (ins.manage.partial) p.push(`kısmi %${ins.manage.partial.pct} @${ins.manage.partial.atR}R (yalnız kârda)`);
14010
14082
  if (ins.manage.noLossClose) p.push('zararda kapatma YOK');
14083
+ if (ins.manage.closeAllProfit) {
14084
+ const r = ins.manage.closeAllProfit;
14085
+ p.push(r.money != null ? `toplu kapatma +${r.money}` : r.pct != null ? `toplu kapatma +%${r.pct}` : 'kârda toplu kapatma');
14086
+ }
14011
14087
  try {
14012
14088
  const f = finCfg();
14013
14089
  if (Number(f.maxPositionsNote) > 0) p.push(`maks ${f.maxPositionsNote} pozisyon`);
@@ -14516,6 +14592,12 @@ async function finPosManagerRound(positions, account) {
14516
14592
  kapatma_kurali: ins.manage.noLossClose
14517
14593
  ? 'ZARARDA KAPATMA YOK (kod uygular): zarardaki pozisyonun kapatma kararı reddedilir — SL/TP kapatması serbest; kısmi kapatma YALNIZ kârda'
14518
14594
  : 'zararda kapatma serbest',
14595
+ /* SEPET: tüm pozisyonların toplam yüzen K/Z'si — toplu kapatma kuralı
14596
+ ve "tümünü kapat" kararı bu veriyle verilir */
14597
+ sepet_kz: Math.round(positions.reduce((a, p) => a + (Number(p.profit) || 0), 0) * 100) / 100,
14598
+ toplu_kapatma: ins.manage.closeAllProfit
14599
+ ? 'KURAL: sepet kâr hedefine ulaşınca TÜMÜ kapatılır (kod uygular; panelde "Tümünü Kapat" da var)'
14600
+ : '',
14519
14601
  talimat_ayarlari: instrTxt || '(sayısal kural yok)',
14520
14602
  gun_durumu: (() => {
14521
14603
  try {
@@ -15914,6 +15996,14 @@ ipcMain.handle('finance:close', async (_e, payload) => {
15914
15996
  return r;
15915
15997
  });
15916
15998
 
15999
+ /* TOPLU KAPATMA (panel butonu): tüm açık pozisyonlar + bekleyen emirler kapanır */
16000
+ ipcMain.handle('finance:closeAll', async () => {
16001
+ if (!mt5bridge.running) return { ok: false, error: 'MT5 köprüsü bağlı değil' };
16002
+ const closed = await finFlattenAll('manuel', { withPending: true });
16003
+ finPush('trade', { line: 'TOPLU KAPATMA (panel): ' + closed + ' pozisyon kapatıldı' });
16004
+ return { ok: true, closed };
16005
+ });
16006
+
15917
16007
  /* Bekleyen emri panelden iptal et (ajanın mt5_cancel aracıyla aynı köprü) */
15918
16008
  ipcMain.handle('finance:cancel', async (_e, payload) => {
15919
16009
  const ticket = Number(payload && payload.ticket);
package/src/preload.js CHANGED
@@ -130,6 +130,7 @@ contextBridge.exposeInMainWorld('beast', {
130
130
  financeAgentSpawn: (symbol) => ipcRenderer.invoke('finance:agent:spawn', { symbol }),
131
131
  financeConnect: () => ipcRenderer.invoke('finance:connect'),
132
132
  financeClose: (ticket, volume) => ipcRenderer.invoke('finance:close', { ticket, volume }),
133
+ financeCloseAll: () => ipcRenderer.invoke('finance:closeAll'),
133
134
  financeCancel: (ticket) => ipcRenderer.invoke('finance:cancel', { ticket }),
134
135
  financeInstall: () => ipcRenderer.invoke('finance:install'),
135
136
  financeMt5Setup: () => ipcRenderer.invoke('finance:mt5:setup'),
@@ -200,7 +200,7 @@
200
200
  <div class="fin-field">
201
201
  <span>Disiplin <i class="fin-hint">— kodla zorlanır; 0 = kural kapalı</i></span>
202
202
  <div class="fin-grid2">
203
- <div class="fin-field"><span>Günlük max işlem</span><input id="finMaxTradesDay" type="number" min="0" max="50" step="1" title="Bir günde en fazla kaç işlem açılabilir (0 = sınırsız)" /></div>
203
+ <div class="fin-field"><span>Günlük max işlem (kapalı)</span><input id="finMaxTradesDay" type="number" min="0" max="50" step="1" disabled title="Kaldırıldı: günlük işlem limiti yalnız talimata &quot;günde en fazla N işlem&quot; yazarsan uygulanır — varsayılan sınırsız" /></div>
204
204
  <div class="fin-field"><span>Kayıp serisi limiti</span><input id="finLossStreak" type="number" min="0" max="10" step="1" title="Üst üste bu kadar kayıptan sonra yeni işlem kilidi (0 = kapalı)" /></div>
205
205
  <div class="fin-field"><span>Seri molası (dk)</span><input id="finLossStreakPause" type="number" min="0" max="1440" step="5" title="Kayıp serisi limitine ulaşınca kaç dakika yeni işlem açılmaz" /></div>
206
206
  <div class="fin-field"><span>Re-entry bekleme (dk)</span><input id="finReentry" type="number" min="0" max="1440" step="5" title="Aynı sembolda bir işlem kapandıktan sonra yeni giriş için beklenecek süre — VARSAYILAN KAPALI (0 = kapalı)" /></div>
@@ -300,7 +300,7 @@
300
300
  </div>
301
301
  </div>
302
302
  <div class="fin-card" data-fin-card="positions">
303
- <div class="fin-card-title">POZİSYONLAR <span id="finPosCount"></span></div>
303
+ <div class="fin-card-title">POZİSYONLAR <span id="finPosCount"></span><span class="file-spacer"></span><button id="finCloseAll" title="TOPLU KAPATMA — tüm açık pozisyonları ve bekleyen emirleri kapat">Tümünü Kapat</button></div>
304
304
  <div id="finPosList"><div class="fin-empty">Açık pozisyon yok</div></div>
305
305
  <div class="fin-sect-title">BEKLEYEN EMİRLER <span id="finOrdCount"></span></div>
306
306
  <div id="finOrdList"><div class="fin-empty">Bekleyen emir yok</div></div>
@@ -258,6 +258,7 @@ const els = {
258
258
  finPosStatus: $('#finPosStatus'),
259
259
  finPosList: $('#finPosList'),
260
260
  finPosCount: $('#finPosCount'),
261
+ finCloseAll: $('#finCloseAll'),
261
262
  finOrdList: $('#finOrdList'),
262
263
  finOrdCount: $('#finOrdCount'),
263
264
  finSymList: $('#finSymList'),
@@ -9780,6 +9781,8 @@ function finRenderPositions(list) {
9780
9781
  if (!els.finPosList) return;
9781
9782
  els.finPosList.textContent = '';
9782
9783
  if (els.finPosCount) els.finPosCount.textContent = list && list.length ? '(' + list.length + ')' : '';
9784
+ /* toplu kapatma butonu: pozisyon yoksa pasif */
9785
+ if (els.finCloseAll) els.finCloseAll.disabled = !(list && list.length);
9783
9786
  if (!list || !list.length) {
9784
9787
  const d = document.createElement('div');
9785
9788
  d.className = 'fin-empty';
@@ -10949,13 +10952,15 @@ function finHelpHtml(which) {
10949
10952
  ['<code>günlük zarar %3</code><code>max günlük kayıp 3</code>',
10950
10953
  '<b>Kod otomatik uygular.</b> Limit aşılırsa yeni işlem açılmaz; günlük kayıp otomasyonunun limiti bu olur.'],
10951
10954
  ['<code>aynı anda en fazla 10 işlem</code><code>en fazla 10 pozisyon</code><code>günde en fazla 10 işlem</code>',
10952
- '<b>Kod otomatik uygular.</b> "aynı anda/en fazla pozisyon" = eşzamanlı tavan (pozisyon + bekleyen emir). <b>Günlük işlem limiti varsayılan olarak KAPALI (sınırsız)</b>; yalnız "günde en fazla N işlem" yazarsan o gün için devreye girer. Ayarlar\'daki değer 0 = sınırsız.'],
10955
+ '<b>Kod otomatik uygular.</b> "aynı anda/en fazla pozisyon" = eşzamanlı tavan (pozisyon + bekleyen emir). <b>Günlük işlem limiti kaldırıldı (varsayılan sınırsız)</b>; yalnız "günde en fazla N işlem" yazarsan o gün için devreye girer — Ayarlar\'daki günlük limit alanı artık kullanılmıyor.'],
10953
10956
  ['<code>sembol başına 5 işlem</code><code>aynı sembolde en fazla 3 pozisyon</code>',
10954
10957
  '<b>Kod otomatik uygular.</b> Aynı sembolde çoklu işlem tavanı (toplam tavanı aşamaz). Toplam tavan yazarsan ("aynı anda en fazla 10 işlem") aynı sembolde de o tavana kadar işlem açılabilir; yazmazsan Ayarlar → maks/pay değeri geçerlidir.'],
10955
10958
  ['<code>zararda işlem kapatma</code><code>sadece karda kapat</code><code>kârda değilse kapatma</code>',
10956
10959
  '<b>Kod otomatik uygular.</b> Zarardaki pozisyonun kapatma kararı REDDEDİLİR — SL/TP çalışmaya devam eder (stop = martingale serisinin parçası). Kârdaki/başabaştaki pozisyon normal kapatılır.'],
10957
10960
  ['<code>0.5R karda yarısını kapat</code><code>partial close ile yarısını kapat</code>',
10958
10961
  '<b>Kod otomatik uygular.</b> Kâr +0.5R\'ye ulaşınca pozisyonun %50\'si kapatılır — <b>yalnız kârdayken</b> uygulanır (zararda kısmi stop yapılmaz).'],
10962
+ ['<code>karda tümünü kapat</code><code>toplam kâr %2 olunca hepsini kapat</code><code>50 dolar kârda tümünü kapat</code>',
10963
+ '<b>Kod otomatik uygular (TOPLU KAPATMA).</b> Tüm pozisyonların toplamı hedefe ulaşınca (herhangi bir kâr / +%X / +X dolar) HEPSİ + bekleyen emirler kapatılır. Panelde POZİSYONLAR başlığındaki <b>Tümünü Kapat</b> butonuyla elle de yapılabilir.'],
10959
10964
  ];
10960
10965
  const example = posmgr
10961
10966
  ? `1- 1R'de %50 kısmi kapat\n2- kâr 2R'ye gelince kalanı kapat\n3- zarar -0.5R'yi geçerse kes`
@@ -11707,6 +11712,22 @@ if (els.finLearnClear) {
11707
11712
  }
11708
11713
 
11709
11714
  if (els.finSymAdd) els.finSymAdd.addEventListener('click', finSymPickerOpen);
11715
+ /* TOPLU KAPATMA: tüm pozisyonlar + bekleyen emirler tek onayla kapanır */
11716
+ if (els.finCloseAll) {
11717
+ els.finCloseAll.addEventListener('click', async () => {
11718
+ const ok = await uiConfirm(
11719
+ 'TÜM açık pozisyonlar ve bekleyen emirler kapatılsın mı?\n(TOPLU KAPATMA — geri alınamaz)',
11720
+ 'Tümünü Kapat',
11721
+ 'Vazgeç'
11722
+ );
11723
+ if (!ok) return;
11724
+ els.finCloseAll.disabled = true;
11725
+ const r = await beast.financeCloseAll().catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
11726
+ if (r && r.ok) toast('Toplu kapatma: ' + (Number(r.closed) || 0) + ' pozisyon kapatıldı');
11727
+ else toast('Toplu kapatma başarısız: ' + ((r && r.error) || '?'));
11728
+ finSnapshot();
11729
+ });
11730
+ }
11710
11731
  if (els.finWatchClear) {
11711
11732
  els.finWatchClear.addEventListener('click', async () => {
11712
11733
  if (!finWatch.length) {