beast-agent 2.54.0 → 2.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.54.0",
4
+ "version": "2.55.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -2009,8 +2009,9 @@ class Engine {
2009
2009
  modeBlock2 + '\n' +
2010
2010
  (roleBlockFull ? roleBlockFull + '\n' : '') +
2011
2011
  (teamLine || '') +
2012
- '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_ea (BeastFinance grafik panosu: status/ping/chart/note), mt5_trade (TÜM TİPLER: buy/sell market + buy/sell limit/stop), mt5_close (kapat), mt5_modify (SL/TP), mt5_pending (TÜM TİPLER: buy_limit/sell_limit/buy_stop/sell_stop bekleyen + buy_market/sell_market anlık), mt5_cancel (emir iptal).\n' +
2012
+ '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_alerts (fiyat alarmı: modu SEN seçersin — once/repeat), mt5_ea (BeastFinance grafik panosu: status/ping/chart/note), mt5_trade (TÜM TİPLER: buy/sell market + buy/sell limit/stop), mt5_close (kapat), mt5_modify (SL/TP), mt5_pending (TÜM TİPLER: buy_limit/sell_limit/buy_stop/sell_stop bekleyen + buy_market/sell_market anlık), mt5_cancel (emir iptal).\n' +
2013
2013
  'EMİR TİPLERİ (6 tip — HEPSİ AÇIK): buy_market/sell_market = anlık piyasa emri (fiyat beklenmez), buy_limit/sell_limit = bekleyen limit (price zorunlu), buy_stop/sell_stop = bekleyen stop (price zorunlu). mt5_trade ve mt5_pending İKİSİ DE 6 tipi kabul eder; emir tipini net söyle (ör. type:"sell_limit").\n' +
2014
+ 'ALARM KARARI SENDE: mt5_alerts ile alarm kurarken mode ZORUNLU — "once" (tek seferlik; ilk tetiklemede kapanır) ya da "repeat" (tekrarlı; alarm açık kalır, koşul sürdükçe cooldownMin dakikada bir tekrar uyarır + seni uyandırır; cooldownMin varsayılan 5). Tekrarlı mı tek seferlik mi olacağını sen seç, varsayılana bırakma; seviyenin önemine göre cooldownMin belirle (ör. 5/15/60).\n' +
2014
2015
  'KİŞİSEL MT5 ARAÇLARI (VARSAYILAN KURULU — tool__* tüm oturumlarda çağrılabilir): tool__mt5_shot (grafikten GERÇEK PNG + görsel ajana enjekte — grafik işlerinde BİRİNCİL; timeframes:["M1","M15"] ile çoklu periyot, symbols:["GOLD","EURUSD","BTCUSD"] ile çoklu sembol TEK karede), tool__mt5_barlar2 (OHLC mumlar, köprüsüz), tool__mt5_m15_m5 (GOLD M15+M5 hazır paket), tool__mt5_fiyat, tool__mt5_durum (hesap+pozisyon+emir tek çağrı), tool__mt5_gecmis, tool__mt5_pozisyon_gecmis, tool__mt5_sltp, tool__mt5_kapat, tool__mt5_bekleyen, tool__mt5_emir, tool__mt5_emir_iptal.\n' +
2015
2016
  'BEASTFINANCE EA (OTOMATİK GRAFİK UZMANI — ENTEGRASYON KANALI): MT5 terminaline bağlanıldığında BeastFinance uzman danışmanı İLK GRAFİĞE OTOMATİK yüklenir, AutoTrading izni açılır (kurulum sistem tarafından yapılır; elle ekleme gerekmez). Grafik panosu + seviye çizgileri + dosya köprüsü (beast_ea.json / beast_cmd.json / beast_note.json) bu EA üzerinden yürür: entegrasyon işlerinde (status/ping/chart/note) mt5_ea kullan; ekran görüntüsü işlerinde tool__mt5_shot ("shot" komutu — gerçek PNG + ajan görseli) birincildir. Yazdığın not ve çizgiler tool__mt5_shot ve computer_look screenshot\'ında GÖRÜNÜR.\n' +
2016
2017
  'YETKİLERİN (AÇIK — çekinmeden kullan):\n' +
@@ -6175,7 +6176,8 @@ const TOOLS = [
6175
6176
  windowMin: { type: 'number', description: 'kind=logs: sliding window in minutes (default 10, max 720)' },
6176
6177
  everyMin: { type: 'number', description: 'Check interval in minutes (default 15, min 1, max 1440)' },
6177
6178
  everySec: { type: 'number', description: 'Check interval in SECONDS (10-8640) — overrides everyMin for fast watchers (e.g. 30 = every 30s)' },
6178
- cooldownMin: { type: 'number', description: 'Min minutes between notifications (default 60)' },
6179
+ cooldownMin: { type: 'number', description: 'İki bildirim arası en az dakika (varsayılan 60). >0 ise alarm TEKRARLIDIR: koşul sürdükçe soğuma dolduğunda yeniden bildirir (ör. 5 = 5 dakikada bir hatırlat). 0 = yalnız koşul yeniden sağlandığında (kenar tetikleme).' },
6180
+ once: { type: 'boolean', description: 'true = tek seferlik izleyici: ilk tetiklemede kapanır (varsayılan false = tekrarlı)' },
6179
6181
  },
6180
6182
  required: ['name', 'kind'],
6181
6183
  },
@@ -200,7 +200,7 @@ const definitions = NAMES.map((name) => {
200
200
  },
201
201
  mt5_alerts: {
202
202
  description:
203
- 'FİYAT ALARMI: mt5_alerts {action:"list"|"set"|"remove"}. set: {symbol, price, direction:"above"|"below", note?}. Fiyat tetiklenince sahibine bildirim gider + alarm kapanır. Örn: XAUUSD 3400 üstü / 3280 altı kritik seviye bildirimi kur.',
203
+ 'FİYAT ALARMI: mt5_alerts {action:"list"|"set"|"remove"}. set: {symbol, price, direction:"above"|"below", mode:"once"|"repeat", note?, cooldownMin?}. ALARM MODUNU KURAN AJAN (sen) SEÇERSİN — mode zorunlu: "once" tek seferlik (ilk tetiklemede kapanır) ya da "repeat" tekrarlı (alarm açık kalır, koşul sürdükçe cooldownMin dakikada bir tekrar bildirir + sahibi ajanı uyandırır; cooldownMin varsayılan 5). Örn kritik seviye: XAUUSD 3400 üstü → mode:"repeat", cooldownMin:15; tek kere haber: mode:"once".',
204
204
  parameters: {
205
205
  type: 'object',
206
206
  properties: {
@@ -208,7 +208,14 @@ const definitions = NAMES.map((name) => {
208
208
  symbol: { type: 'string', description: 'set için sembol' },
209
209
  price: { type: 'number', description: 'set için tetik fiyatı' },
210
210
  direction: { type: 'string', enum: ['above', 'below'], description: 'above: fiyat ≥ price; below: fiyat ≤ price' },
211
+ mode: {
212
+ type: 'string',
213
+ enum: ['once', 'repeat'],
214
+ description: "set için ZORUNLU: 'once' tek seferlik | 'repeat' tekrarlı (kararı alarmı kuran ajan verir)",
215
+ },
211
216
  note: { type: 'string', description: 'Alarm notu (bildirimde görünür)' },
217
+ cooldownMin: { type: 'number', description: "mode:'repeat' için iki tetik arası en az dakika (varsayılan 5, 0 = her tetiklemede, max 10080)" },
218
+ once: { type: 'boolean', description: "mode'a alternatif kısa yol (true = tek seferlik)" },
212
219
  id: { type: 'string', description: 'remove için alarm id' },
213
220
  },
214
221
  required: ['action'],
@@ -425,9 +432,34 @@ const handlers = {
425
432
  if (!symbol) return { ok: false, error: 'symbol gerekli' };
426
433
  if (!isFinite(price) || price <= 0) return { ok: false, error: 'price gerekli (pozitif sayı)' };
427
434
  if (direction !== 'above' && direction !== 'below') return { ok: false, error: "direction 'above' veya 'below' olmalı" };
428
- const alarm = alertsApi.set({ symbol, price, direction, note: String(args.note || '').slice(0, 200), sid: ctx && ctx.sessionId ? String(ctx.sessionId) : '' });
435
+ /* ALARM MODUNU KURAN AJAN SEÇER: 'once' (tek seferlik) ya da
436
+ 'repeat' (tekrarlı; koşul sürdükçe cooldownMin'de bir). Seçim
437
+ yapılmadan alarm kurulamaz — sistem varsayılanına bırakılmaz. */
438
+ const mRaw = String(args.mode || '').toLowerCase().trim();
439
+ let once;
440
+ if (mRaw === 'once' || mRaw === 'tek' || mRaw === 'tek_seferlik' || mRaw === 'tek-seferlik') once = true;
441
+ else if (mRaw === 'repeat' || mRaw === 'recurring' || mRaw === 'tekrarli' || mRaw === 'surekli' || mRaw === 'always') once = false;
442
+ else if (args.once !== undefined) once = args.once === true || String(args.once).toLowerCase() === 'true';
443
+ else {
444
+ return {
445
+ ok: false,
446
+ error:
447
+ "mode gerekli — alarmı kuran ajan karar verir: mode:'once' (tek seferlik, ilk tetiklemede kapanır) ya da mode:'repeat' (tekrarlı; koşul sürdükçe cooldownMin'de bir uyarır, varsayılan 5 dk)",
448
+ };
449
+ }
450
+ const cdRaw = Number(args.cooldownMin);
451
+ const cooldownMin = Number.isFinite(cdRaw) ? Math.min(Math.max(Math.round(cdRaw), 0), 10080) : undefined;
452
+ const alarm = alertsApi.set({
453
+ symbol,
454
+ price,
455
+ direction,
456
+ note: String(args.note || '').slice(0, 200),
457
+ sid: ctx && ctx.sessionId ? String(ctx.sessionId) : '',
458
+ ...(cooldownMin === undefined ? {} : { cooldownMin }),
459
+ once,
460
+ });
429
461
  if (!alarm) return { ok: false, error: 'alarm kurulamadı' };
430
- noteTrade('alert-set', { symbol, price, direction, id: alarm.id, note: alarm.note }, ctx || {});
462
+ noteTrade('alert-set', { symbol, price, direction, id: alarm.id, note: alarm.note, cooldownMin: alarm.cooldownMin, once: alarm.once }, ctx || {});
431
463
  return { ok: true, alert: alarm };
432
464
  }
433
465
  return { ok: false, error: "action: list|set|remove" };
@@ -147,6 +147,18 @@ function parseOrderType(raw, fallbackSide) {
147
147
  return { ok: true, side, kind: 'market', type: side + '_market' };
148
148
  }
149
149
 
150
+ /* ---- fiyat alarmı soğuması ----
151
+ Tekrarlı alarm (once=false): aynı koşul sürdükçe en fazla cooldownMin
152
+ dakikada bir tetiklenir. once=true alarmda soğuma uygulanmaz (ilk
153
+ tetiklemede kapanır). */
154
+ function alertCooldownActive(a, now) {
155
+ if (!a || a.once) return false;
156
+ const cd = (Number(a.cooldownMin) || 0) * 60000;
157
+ if (!(cd > 0)) return false;
158
+ const last = Number(a.lastFiredAt) || 0;
159
+ return last > 0 && Number(now) - last < cd;
160
+ }
161
+
150
162
  /* ---- bekleyen emir (pending) → pozisyon eşleştirme ----
151
163
  Watchdog turu emir listesini karşılaştırır: listeden DÜŞEN emir için aynı
152
164
  turda YENİ açılan (ya da netting hesapta hacmi artan) eşleşen bir pozisyon
@@ -216,4 +228,4 @@ function matchPendingDelta(goneOrders, freshPositions) {
216
228
  return out;
217
229
  }
218
230
 
219
- module.exports = { plan, roundTo, stepRound, closeKind, parseOrderType, orderSide, orderTypeLabel, orderVolume, matchPendingDelta };
231
+ module.exports = { plan, roundTo, stepRound, closeKind, parseOrderType, alertCooldownActive, orderSide, orderTypeLabel, orderVolume, matchPendingDelta };
@@ -172,7 +172,9 @@ function normalize(input) {
172
172
  const everyMin = Math.min(Math.max(Math.round(rawMin) || 15, 1), 1440);
173
173
  everySec = everyMin * 60;
174
174
  }
175
- /* 0 geçerlidir (cooldown yok); sadece sayılamayan değerler varsayılana döner */
175
+ /* 0 geçerlidir (cooldown yok → yalnız kenar tetikleme); sayılamayan değerler
176
+ varsayılana döner. cooldownMin > 0 ise alarm TEKRARLIDIR: koşul sürdükçe
177
+ soğuma dolduğunda yeniden bildirir. */
176
178
  const cdRaw = Number(i.cooldownMin ?? i.cooldown_min ?? 60);
177
179
  const cooldownMin = Number.isFinite(cdRaw) ? Math.min(Math.max(Math.round(cdRaw), 0), 10080) : 60;
178
180
  const prompt = String(i.prompt || '').trim().slice(0, 2000);
@@ -192,6 +194,7 @@ function normalize(input) {
192
194
  everyMin: Math.max(1, Math.round(everySec / 60)), // geriye dönük uyum
193
195
  everySec,
194
196
  cooldownMin,
197
+ once: !!i.once, /* tek seferlik: ilk tetiklemede kapanır */
195
198
  prompt,
196
199
  sessionId: i.sessionId || null,
197
200
  enabled: true,
@@ -223,11 +226,15 @@ function compare(op, current, target) {
223
226
  }
224
227
 
225
228
  /* Tek kontrol uygula — saf: tetik kararı + kaydedilecek yama.
226
- Kenar-tetiklemeli: koşul doğruyken tek kez ateşlenir, koşul normale
227
- dönünce yeniden kurulur (armed). */
229
+ TEKRARLI ALARM: cooldownMin > 0 ise koşul sürdükçe, soğuma her dolduğunda
230
+ YENİDEN tetiklenir (tickOnce soğuma boyunca zaten kontrol etmez).
231
+ cooldownMin = 0 → yalnız kenar tetikleme (koşul bozulup yeniden sağlanınca).
232
+ 'changed' her zaman kenar tetiklemelidir (değer değişimi olaydır). */
228
233
  function applyCheck(w, value, now) {
229
234
  const cond = compare(w.op, value, w.value);
230
- const triggered = cond && w.armed;
235
+ const cdMin = Number(w.cooldownMin) || 0;
236
+ const repeat = cdMin > 0 && w.op !== 'changed' && !!w.lastTriggeredAt;
237
+ const triggered = !!(cond && (w.armed || repeat));
231
238
  const patch = {
232
239
  lastCheckAt: new Date(now).toISOString(),
233
240
  lastValue: value === undefined ? null : value,
@@ -371,6 +378,8 @@ async function tickOnce(deps = {}) {
371
378
  w.lastError = '';
372
379
  pushLog(w.id, r.triggered ? 'trigger' : 'ok', value);
373
380
  if (r.triggered) {
381
+ /* TEK SEFERLİK: ilk tetiklemede izleyici kapanır (yeniden etkinleştirilebilir) */
382
+ if (w.once) w.enabled = false;
374
383
  if (typeof hooks.onTrigger === 'function') hooks.onTrigger({ ...w }, value);
375
384
  events.push({ id: w.id, name: w.name, value });
376
385
  }
package/src/main.js CHANGED
@@ -9780,7 +9780,14 @@ async function channelSendFromAgent(input) {
9780
9780
  };
9781
9781
  }
9782
9782
 
9783
- /* ---- fiyat alarmları (kalıcı) ---- */
9783
+ /* ---- fiyat alarmları (kalıcı) ----
9784
+ TEKRARLI (varsayılan): alarm kapanmaz; koşul sürdükçe en fazla
9785
+ cooldownMin dakikada bir tekrar tetikler. once:true → tek tetiklemede
9786
+ kapanır. cooldownMin'i AJAN karar verir (mt5_alerts ile). */
9787
+ const FIN_ALERT_CD_DEFAULT = 5; /* dk */
9788
+ function finAlertCooldown(a) {
9789
+ return Math.min(Math.max(Math.round(Number(a && a.cooldownMin) || FIN_ALERT_CD_DEFAULT), 0), 10080);
9790
+ }
9784
9791
  function finAlertsLoad() {
9785
9792
  const raw = finReadJson(finFile('alerts.json'), []);
9786
9793
  financeState.alerts = (Array.isArray(raw) ? raw : [])
@@ -9791,6 +9798,10 @@ function finAlertsLoad() {
9791
9798
  direction: String((a && a.direction) || 'above').toLowerCase(),
9792
9799
  note: String((a && a.note) || '').slice(0, 200),
9793
9800
  sid: String((a && a.sid) || ''), /* alarmı kuran ajan oturumu (uyandırma hedefi) */
9801
+ once: !!(a && a.once), /* tek seferlik mi */
9802
+ cooldownMin: finAlertCooldown(a),
9803
+ lastFiredAt: Number(a && a.lastFiredAt) || 0,
9804
+ fires: Number(a && a.fires) || 0,
9794
9805
  at: Number(a && a.at) || Date.now(),
9795
9806
  }))
9796
9807
  .filter((a) => a.id && a.symbol && a.price > 0);
@@ -9807,18 +9818,41 @@ function finAlertApi() {
9807
9818
  const symbol = String((a && a.symbol) || '').toUpperCase();
9808
9819
  const price = Number(a && a.price);
9809
9820
  if (!symbol || !(price > 0)) return null;
9821
+ const direction = String((a && a.direction) || 'above').toLowerCase() === 'below' ? 'below' : 'above';
9822
+ /* AYNI ALARM ZATEN AÇIKSA yenisini EKLEME — mevcut kaydı güncelle:
9823
+ ajan her turda aynı seviyeyi yeniden kurup alarm/tur döngüsü
9824
+ (ard arda tetik + uyandırma) üretmesin. lastFiredAt KORUNUR. */
9825
+ const existing = financeState.alerts.find(
9826
+ (x) => x.symbol === symbol && x.direction === direction && Math.abs(Number(x.price) - price) < 1e-9
9827
+ );
9828
+ if (existing) {
9829
+ if (a.note !== undefined) existing.note = String(a.note || '').slice(0, 200);
9830
+ if (a.sid !== undefined) existing.sid = String(a.sid || '');
9831
+ if (a.cooldownMin !== undefined) existing.cooldownMin = finAlertCooldown(a);
9832
+ if (a.once !== undefined) existing.once = !!a.once;
9833
+ finAlertsSave();
9834
+ financeLog(`[alarm] güncellendi (zaten açıktı): ${existing.symbol} ${existing.direction === 'below' ? '≤' : '≥'} ${existing.price}`);
9835
+ return { ...existing, updated: true };
9836
+ }
9810
9837
  const alarm = {
9811
9838
  id: 'a' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
9812
9839
  symbol,
9813
9840
  price,
9814
- direction: String((a && a.direction) || 'above').toLowerCase() === 'below' ? 'below' : 'above',
9841
+ direction,
9815
9842
  note: String((a && a.note) || '').slice(0, 200),
9816
9843
  sid: String((a && a.sid) || ''),
9844
+ once: !!(a && a.once),
9845
+ cooldownMin: finAlertCooldown(a),
9846
+ lastFiredAt: 0,
9847
+ fires: 0,
9817
9848
  at: Date.now(),
9818
9849
  };
9819
9850
  financeState.alerts.push(alarm);
9820
9851
  finAlertsSave();
9821
- financeLog(`[alarm] kuruldu: ${alarm.symbol} ${alarm.direction === 'below' ? '≤' : '≥'} ${alarm.price}`);
9852
+ financeLog(
9853
+ `[alarm] kuruldu: ${alarm.symbol} ${alarm.direction === 'below' ? '≤' : '≥'} ${alarm.price}` +
9854
+ (alarm.once ? ' (tek seferlik)' : ` (tekrarlı · ${alarm.cooldownMin} dk soğuma)`)
9855
+ );
9822
9856
  return alarm;
9823
9857
  },
9824
9858
  remove: (id) => {
@@ -10100,23 +10134,39 @@ async function finCheckAlerts() {
10100
10134
  prices[String(row.symbol)] = { bid: Number(row.bid), ask: Number(row.ask) };
10101
10135
  }
10102
10136
  let changed = false;
10137
+ const now = Date.now();
10103
10138
  for (const a of alerts.slice()) {
10104
10139
  const p = prices[a.symbol];
10105
10140
  if (!p || !isFinite(p.bid)) continue;
10141
+ /* soğuma: tekrarlı alarm koşul sürse bile cooldown dolmadan tekrar etmez */
10142
+ if (finwatch.alertCooldownActive(a, now)) continue;
10106
10143
  const hit = a.direction === 'below' ? p.bid <= a.price : p.bid >= a.price;
10107
10144
  if (!hit) continue;
10108
10145
  changed = true;
10109
- financeState.alerts = financeState.alerts.filter((x) => x.id !== a.id);
10110
- const line = `🔔 ALARM: ${a.symbol} ${a.direction === 'below' ? '≤' : '≥'} ${a.price} (şimdi ${p.bid})${a.note ? ' — ' + a.note : ''}`;
10146
+ const fires = (Number(a.fires) || 0) + 1;
10147
+ if (a.once) {
10148
+ /* tek seferlik: tetiklendi → kapanır */
10149
+ financeState.alerts = financeState.alerts.filter((x) => x.id !== a.id);
10150
+ } else {
10151
+ /* tekrarlı: alarm AÇIK kalır; cooldown sonrası yeniden hatırlatır */
10152
+ a.lastFiredAt = now;
10153
+ a.fires = fires;
10154
+ }
10155
+ const tekrar = fires > 1 ? ` (tekrar #${fires - 1})` : '';
10156
+ const cdTxt = a.once ? ' · tek seferlik' : ` · ${finAlertCooldown(a)} dk soğuma`;
10157
+ const line = `🔔 ALARM${tekrar}: ${a.symbol} ${a.direction === 'below' ? '≤' : '≥'} ${a.price} (şimdi ${p.bid})${a.note ? ' — ' + a.note : ''}${cdTxt}`;
10111
10158
  financeLog('[alarm] ' + line);
10112
10159
  financeNotify(line, 'alert');
10113
- finJournal({ kind: 'alert', symbol: a.symbol, price: a.price, direction: a.direction, note: a.note || '' });
10160
+ finJournal({ kind: 'alert', symbol: a.symbol, price: a.price, direction: a.direction, note: a.note || '', fires, once: !!a.once });
10114
10161
  /* ALARM SAHİBİ AJAN: alarmı kuran koşan finance ajanı DM'den uyanır;
10115
10162
  sahip koşmuyor/biliniyorsa koşan tüm finance ajanlarına düşer. */
10116
10163
  const wakeTxt =
10117
- `[FİNANS OLAYI — FİYAT ALARMI, TUR SANİYESİ BEKLENMEDEN İLETİLDİ]\n` +
10118
- `- ${a.symbol} ${a.direction === 'below' ? '≤' : '≥'} ${a.price} tetiklendi (şimdi ${p.bid})${a.note ? ' — ' + a.note : ''}\n` +
10119
- `Şimdi yap: alarmı kurma nedenini hatırla; fiyat seviyesini ve planını değerlendir, gerekiyorsa işlem/uyarı üret. Kısa rapor ver.`;
10164
+ `[FİNANS OLAYI — FİYAT ALARMI${tekrar}, TUR SANİYESİ BEKLENMEDEN İLETİLDİ]\n` +
10165
+ `- ${a.symbol} ${a.direction === 'below' ? '≤' : '≥'} ${a.price} tetiklendi (şimdi ${p.bid})${a.note ? ' — ' + a.note : ''}${cdTxt}\n` +
10166
+ (a.once
10167
+ ? 'Alarm TEK SEFERLİKTİ ve kapandı. '
10168
+ : `Alarm TEKRARLI açık kalıyor${a.cooldownMin > 0 ? ` — koşul sürerse en erken ${a.cooldownMin} dk sonra yeniden uyarır` : ''}. `) +
10169
+ 'Şimdi yap: alarmı kurma nedenini hatırla; fiyat seviyesini ve planını değerlendir, gerekiyorsa işlem/uyarı üret. Kısa rapor ver.';
10120
10170
  if (!finWakeAgentDm(a.sid, wakeTxt)) finWakeAgents(wakeTxt, { kind: 'alarm', symbol: a.symbol });
10121
10171
  }
10122
10172
  if (changed) finAlertsSave();
@@ -10988,7 +11038,7 @@ function finTraderBrief(agent) {
10988
11038
  `Tur aralığı: ${f.intervalSec} sn · Max lot: ${f.maxLot} · Max eşzamanlı pozisyon: ${f.maxPositions}`,
10989
11039
  roleDef
10990
11040
  ? `UZMANLIK: ${roleDef.desc} — raporlarını bu çerçevede yaz; İŞLEM AÇMA, yalnız analiz + net öneri üret.`
10991
- : 'Otomatik işlem AÇIK (daima): 6 emir tipinin HEPSİ açık — buy_market/sell_market (anlık piyasa), buy_limit/sell_limit ve buy_stop/sell_stop (bekleyen; price zorunlu). mt5_trade ya da mt5_pending İKİSİ de tüm tipleri kabul eder; ayrıca mt5_close/mt5_modify/mt5_cancel açık (limitler sistemce zorlanır). Lot için mt5_risksize hesapla ya da mt5_trade’e riskPct ver; SL/TP broker stops_level mesafesine uymalı.',
11041
+ : 'Otomatik işlem AÇIK (daima): 6 emir tipinin HEPSİ açık — buy_market/sell_market (anlık piyasa), buy_limit/sell_limit ve buy_stop/sell_stop (bekleyen; price zorunlu). mt5_trade ya da mt5_pending İKİSİ de tüm tipleri kabul eder; ayrıca mt5_close/mt5_modify/mt5_cancel açık (limitler sistemce zorlanır). ALARM: mt5_alerts ile kurarken modu SEN seç — mode:"once" tek seferlik, mode:"repeat" + cooldownMin (dk) tekrarlı. Lot için mt5_risksize hesapla ya da mt5_trade’e riskPct ver; SL/TP broker stops_level mesafesine uymalı.',
10992
11042
  roleDef
10993
11043
  ? '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.'
10994
11044
  : f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : 'Strateji notu yok: trend + destek/direnç + momentum ile temel okuma yap.',
@@ -11265,6 +11315,17 @@ ipcMain.handle('finance:snapshot', async () => {
11265
11315
  };
11266
11316
  });
11267
11317
 
11318
+ ipcMain.handle('finance:alerts', async () => {
11319
+ /* İzleyiciler paneli hafif alarm listesi (hesap/pozisyon çekmez) */
11320
+ return { ok: true, alerts: (financeState.alerts || []).slice(0, 200) };
11321
+ });
11322
+
11323
+ ipcMain.handle('finance:alerts:remove', async (_e, id) => {
11324
+ const ok = finAlertApi().remove(String(id || ''));
11325
+ finPush('alert', { removed: ok, id: String(id || '') });
11326
+ return { ok };
11327
+ });
11328
+
11268
11329
  ipcMain.handle('finance:mode', async (_e, payload) => {
11269
11330
  financeState.mode = !!(payload && payload.on);
11270
11331
  /* BEAST FINANCE = MT5 KAPISI:
package/src/preload.js CHANGED
@@ -117,6 +117,8 @@ contextBridge.exposeInMainWorld('beast', {
117
117
 
118
118
  financeState: () => ipcRenderer.invoke('finance:state'),
119
119
  financeSnapshot: () => ipcRenderer.invoke('finance:snapshot'),
120
+ financeAlertsList: () => ipcRenderer.invoke('finance:alerts'),
121
+ financeAlertRemove: (id) => ipcRenderer.invoke('finance:alerts:remove', id),
120
122
  financeMode: (on, sessionId) => ipcRenderer.invoke('finance:mode', { on, sessionId }),
121
123
  financeSettings: (patch) => ipcRenderer.invoke('finance:settings', patch),
122
124
  financeTraderStart: () => ipcRenderer.invoke('finance:trader:start'),
@@ -798,6 +798,10 @@
798
798
  <input id="wAddWindow" class="inp" type="number" min="1" max="720" value="10" title="pencere (dk)" style="width:80px" />
799
799
  <input id="wAddRe" class="inp" data-i18n-ph="w_add_re" placeholder="regex (opsiyonel)" />
800
800
  </div>
801
+ <div class="mini-add-row">
802
+ <input id="wAddCd" class="inp" type="number" min="0" max="10080" value="60" title="soğuma (dk): koşul sürdükçe en fazla bu aralıkta bir bildirir — 0 = yalnız koşul yeniden sağlanınca" style="width:70px" />
803
+ <label style="display:flex;align-items:center;gap:5px;font-size:12px;color:var(--muted);white-space:nowrap"><input id="wAddOnce" type="checkbox" /> tek seferlik</label>
804
+ </div>
801
805
  </div>
802
806
  <div id="watchList" class="mini-list"></div>
803
807
  </div>
@@ -6640,17 +6640,35 @@ function fmtWhen(iso) {
6640
6640
  Kompakt liste: durum noktası + ad + bir sonraki tetik; satır → izleyici
6641
6641
  modalı; ▶/× mini kontroller. Yalnız agent modunda görünür (CSS). */
6642
6642
  let lastWatchPaneKey = '';
6643
+ /* FİYAT ALARMLARI (Beast Finance): İzleyiciler paneli + modalında watcher'larla
6644
+ BİRLİKTE listelenir — tüm alarmlar tek yerde görünür/silinir. */
6645
+ function finAlertMeta(a) {
6646
+ const dir = a.direction === 'below' ? '≤' : '≥';
6647
+ return {
6648
+ name: `🔔 ${a.symbol} ${dir} ${a.price}`,
6649
+ meta:
6650
+ (a.once ? 'tek seferlik' : `her ${Number(a.cooldownMin) || 5} dk`) +
6651
+ (a.fires ? ` · ${a.fires}× tetiklendi` : '') +
6652
+ (a.note ? ` · ${String(a.note).slice(0, 40)}` : ''),
6653
+ };
6654
+ }
6655
+
6643
6656
  async function renderWatchersPane(force) {
6644
6657
  const list = els.watchPaneList;
6645
6658
  if (!list) return;
6646
6659
  if (document.body.classList.contains('ide-mode') || document.body.classList.contains('studio-mode') || document.body.classList.contains('sandbox-mode')) return;
6647
6660
  let rows = [];
6648
6661
  try { rows = (await beast.watchersList()) || []; } catch {}
6649
- const key = JSON.stringify(rows);
6662
+ let alerts = [];
6663
+ try {
6664
+ const ar = await beast.financeAlertsList();
6665
+ alerts = (ar && ar.alerts) || [];
6666
+ } catch {}
6667
+ const key = JSON.stringify([rows, alerts]);
6650
6668
  if (!force && key === lastWatchPaneKey) return;
6651
6669
  lastWatchPaneKey = key;
6652
6670
  list.innerHTML = '';
6653
- if (!rows.length) {
6671
+ if (!rows.length && !alerts.length) {
6654
6672
  list.innerHTML = '<div class="wp-empty">' + _t('w_empty') + '</div>';
6655
6673
  return;
6656
6674
  }
@@ -6698,18 +6716,52 @@ async function renderWatchersPane(force) {
6698
6716
  row.addEventListener('click', () => openWatchModal());
6699
6717
  list.appendChild(row);
6700
6718
  }
6719
+ /* Beast Finance FİYAT ALARMLARI: aynı listede — silinebilir, canlı cd/fire bilgisi */
6720
+ for (const a of alerts) {
6721
+ const info = finAlertMeta(a);
6722
+ const row = document.createElement('div');
6723
+ row.className = 'wp-row alarm';
6724
+ row.title = info.name + (a.note ? ' — ' + a.note : '') + ' · ' + info.meta;
6725
+ row.innerHTML =
6726
+ `<span class="wp-dot"></span>` +
6727
+ `<div class="wp-main">` +
6728
+ `<div class="wp-name">${escapeHtml(info.name)}</div>` +
6729
+ `<div class="wp-meta">${escapeHtml(info.meta)}</div>` +
6730
+ `</div>`;
6731
+ const btns = document.createElement('div');
6732
+ btns.className = 'wp-btns';
6733
+ const d = document.createElement('button');
6734
+ d.className = 'wp-btn del';
6735
+ d.textContent = '×';
6736
+ d.title = _t('cr_del');
6737
+ d.addEventListener('click', async (e) => {
6738
+ e.stopPropagation();
6739
+ await beast.financeAlertRemove(a.id);
6740
+ lastWatchPaneKey = '';
6741
+ renderWatchersPane(true);
6742
+ renderWatchersModal(true);
6743
+ });
6744
+ btns.appendChild(d);
6745
+ row.appendChild(btns);
6746
+ list.appendChild(row);
6747
+ }
6701
6748
  }
6702
6749
 
6703
6750
  let lastWatchKey = '';
6704
6751
  async function renderWatchersModal(force) {
6705
6752
  let rows = []; try { rows = (await beast.watchersList()) || []; } catch {}
6753
+ let alerts = [];
6754
+ try {
6755
+ const ar = await beast.financeAlertsList();
6756
+ alerts = (ar && ar.alerts) || [];
6757
+ } catch {}
6706
6758
  /* canlı yenilemede içerik değişmediyse DOM'a dokunma — buton tıklamaları
6707
6759
  yeniden kurulurken yutulmasın */
6708
- const key = JSON.stringify(rows);
6760
+ const key = JSON.stringify([rows, alerts]);
6709
6761
  if (!force && key === lastWatchKey) return;
6710
6762
  lastWatchKey = key;
6711
6763
  els.watchList.innerHTML = '';
6712
- if (!rows.length) {
6764
+ if (!rows.length && !alerts.length) {
6713
6765
  els.watchList.innerHTML = '<div class="mini-empty">' + _t('w_empty') + '</div>';
6714
6766
  return;
6715
6767
  }
@@ -6724,7 +6776,7 @@ async function renderWatchersModal(force) {
6724
6776
  `<span class="mr-dot"></span>` +
6725
6777
  `<div class="mr-main">` +
6726
6778
  `<div class="mr-name">${escapeHtml(w.name)}</div>` +
6727
- `<div class="mr-meta">[${w.kind}] ${escapeHtml(String(target).slice(0, 60))} · <b>${opText(w)}</b> · her <b>${fmtEvery(w)}</b> · cd ${w.cooldownMin}dk` +
6779
+ `<div class="mr-meta">[${w.kind}] ${escapeHtml(String(target).slice(0, 60))} · <b>${opText(w)}</b> · her <b>${fmtEvery(w)}</b> · ${w.once ? '<b>tek seferlik</b>' : 'cd ' + w.cooldownMin + 'dk'}` +
6728
6780
  (w.lastValue !== null && w.lastValue !== undefined ? ` · son: <b>${escapeHtml(String(w.lastValue).slice(0, 20))}</b>` : '') +
6729
6781
  (w.lastError ? ` · <span style="color:var(--err)">${escapeHtml(w.lastError.slice(0, 40))}</span>` : '') +
6730
6782
  `</div></div>`;
@@ -6749,6 +6801,34 @@ async function renderWatchersModal(force) {
6749
6801
  row.appendChild(btns);
6750
6802
  els.watchList.appendChild(row);
6751
6803
  }
6804
+ /* Beast Finance FİYAT ALARMLARI: modalda da watcher'larla birlikte */
6805
+ for (const a of alerts) {
6806
+ const info = finAlertMeta(a);
6807
+ const row = document.createElement('div');
6808
+ row.className = 'mini-row alarm';
6809
+ row.innerHTML =
6810
+ `<span class="mr-dot"></span>` +
6811
+ `<div class="mr-main">` +
6812
+ `<div class="mr-name">${escapeHtml(info.name)}</div>` +
6813
+ `<div class="mr-meta">${escapeHtml(info.meta)}` +
6814
+ (a.note ? ` · ${escapeHtml(String(a.note).slice(0, 50))}` : '') +
6815
+ `</div></div>`;
6816
+ const btns = document.createElement('div');
6817
+ btns.style.cssText = 'display:flex;gap:6px;flex:none';
6818
+ const d = document.createElement('button');
6819
+ d.className = 'mr-btn del';
6820
+ d.textContent = '×';
6821
+ d.title = _t('cr_del');
6822
+ d.addEventListener('click', async () => {
6823
+ await beast.financeAlertRemove(a.id);
6824
+ lastWatchKey = '';
6825
+ renderWatchersModal(true);
6826
+ renderWatchersPane(true);
6827
+ });
6828
+ btns.appendChild(d);
6829
+ row.appendChild(btns);
6830
+ els.watchList.appendChild(row);
6831
+ }
6752
6832
  }
6753
6833
 
6754
6834
  /* ---------- İZLEYİCİ LOG MODALI ----------
@@ -7801,6 +7881,8 @@ async function init() {
7801
7881
  name: $('#wAddName').value.trim(),
7802
7882
  kind,
7803
7883
  everyMin: Number($('#wAddEvery').value) || 15,
7884
+ cooldownMin: Math.max(0, Number($('#wAddCd') ? $('#wAddCd').value : 60) || 0),
7885
+ once: !!($('#wAddOnce') && $('#wAddOnce').checked),
7804
7886
  };
7805
7887
  if (kind === 'web') {
7806
7888
  input.url = $('#wAddUrl').value.trim();
@@ -7823,6 +7905,8 @@ async function init() {
7823
7905
  const el = $(id);
7824
7906
  if (el) el.value = '';
7825
7907
  }
7908
+ if ($('#wAddOnce')) $('#wAddOnce').checked = false;
7909
+ if ($('#wAddCd')) $('#wAddCd').value = '60';
7826
7910
  lastWatchKey = '';
7827
7911
  lastWatchPaneKey = '';
7828
7912
  await renderWatchersModal(true);
@@ -9979,6 +10063,7 @@ function finRenderAlerts(list) {
9979
10063
  row.innerHTML =
9980
10064
  '<span class="fa-sym">' + escapeHtml(String(a.symbol || '?')) + '</span>' +
9981
10065
  '<span class="fa-cond">' + (a.direction === 'below' ? '≤' : '≥') + ' ' + (Number(a.price) || 0) + '</span>' +
10066
+ '<span class="fa-note">' + (a.once ? 'tek seferlik' : `her ${Number(a.cooldownMin) || 5} dk`) + (a.fires ? ` · ${a.fires}×` : '') + '</span>' +
9982
10067
  (a.note ? '<span class="fa-note">' + escapeHtml(String(a.note)) + '</span>' : '');
9983
10068
  box.appendChild(row);
9984
10069
  }
@@ -1504,6 +1504,8 @@ body.browser-open .mini-overlay { right: var(--bw, 480px); }
1504
1504
  .mini-row.off .mr-name, .mini-row.off .mr-meta { opacity: 0.45; }
1505
1505
  .mr-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok); flex: none; }
1506
1506
  .mini-row.off .mr-dot { background: var(--muted); }
1507
+ /* Beast Finance fiyat alarmları: izleyici listesinde turuncu nokta ile ayrılır */
1508
+ .wp-row.alarm .wp-dot, .mini-row.alarm .mr-dot { background: #e6a117; }
1507
1509
  .mr-main { flex: 1; min-width: 0; }
1508
1510
  .mr-name { font-weight: 700; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1509
1511
  .mr-meta { color: var(--muted); font-size: 11px; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }