beast-agent 2.52.0 → 2.53.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.52.0",
4
+ "version": "2.53.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -396,7 +396,7 @@ const handlers = {
396
396
  deals: deals.slice(-60),
397
397
  };
398
398
  },
399
- async mt5_alerts(args) {
399
+ async mt5_alerts(args, ctx) {
400
400
  const action = String(args.action || 'list').toLowerCase();
401
401
  if (action === 'list') {
402
402
  const list = alertsApi.list();
@@ -415,9 +415,9 @@ const handlers = {
415
415
  if (!symbol) return { ok: false, error: 'symbol gerekli' };
416
416
  if (!isFinite(price) || price <= 0) return { ok: false, error: 'price gerekli (pozitif sayı)' };
417
417
  if (direction !== 'above' && direction !== 'below') return { ok: false, error: "direction 'above' veya 'below' olmalı" };
418
- const alarm = alertsApi.set({ symbol, price, direction, note: String(args.note || '').slice(0, 200) });
418
+ const alarm = alertsApi.set({ symbol, price, direction, note: String(args.note || '').slice(0, 200), sid: ctx && ctx.sessionId ? String(ctx.sessionId) : '' });
419
419
  if (!alarm) return { ok: false, error: 'alarm kurulamadı' };
420
- noteTrade('alert-set', { symbol, price, direction, id: alarm.id, note: alarm.note }, {});
420
+ noteTrade('alert-set', { symbol, price, direction, id: alarm.id, note: alarm.note }, ctx || {});
421
421
  return { ok: true, alert: alarm };
422
422
  }
423
423
  return { ok: false, error: "action: list|set|remove" };
@@ -107,6 +107,18 @@ function plan(pos, meta, st, cfg) {
107
107
  return out;
108
108
  }
109
109
 
110
+ /* ---- kapanış nedeni (MT5 deal reason) ----
111
+ İşlem SL/TP ile mi kapandı? Saf sınıflandırma: 4 = stop-loss,
112
+ 5 = take-profit, 6 = stop-out (teminat). Diğerleri (manuel/EA/broker)
113
+ BOŞ döner — bu olaylarda ajan uyandırılmaz. */
114
+ function closeKind(reason) {
115
+ const n = Number(reason);
116
+ if (n === 4) return 'stop';
117
+ if (n === 5) return 'tp';
118
+ if (n === 6) return 'stopout';
119
+ return '';
120
+ }
121
+
110
122
  /* ---- bekleyen emir (pending) → pozisyon eşleştirme ----
111
123
  Watchdog turu emir listesini karşılaştırır: listeden DÜŞEN emir için aynı
112
124
  turda YENİ açılan (ya da netting hesapta hacmi artan) eşleşen bir pozisyon
@@ -176,4 +188,4 @@ function matchPendingDelta(goneOrders, freshPositions) {
176
188
  return out;
177
189
  }
178
190
 
179
- module.exports = { plan, roundTo, stepRound, orderSide, orderTypeLabel, orderVolume, matchPendingDelta };
191
+ module.exports = { plan, roundTo, stepRound, closeKind, orderSide, orderTypeLabel, orderVolume, matchPendingDelta };
package/src/main.js CHANGED
@@ -596,6 +596,16 @@ function startEventBus() {
596
596
  notify: (sub, text) => {
597
597
  try {
598
598
  waLog(`olay bildirimi → sid=${sub.sessionId} (${sub.type})`);
599
+ /* FINANCE AJANI: olay aboneliği alarmı AJAN DM'i olarak düşer ve
600
+ boştaki ajanı tur beklemeden uyandırır; diğer oturumlar aynı. */
601
+ if (financeState.agents.has(String(sub.sessionId))) {
602
+ finWakeAgentDm(
603
+ sub.sessionId,
604
+ `[FİNANS OLAYI — OLAY ABONELİĞİ (${sub.type})]\n${String(text || '')}\n` +
605
+ `Şimdi yap: aboneliği kurma nedenini hatırla; durumu değerlendir, gerekiyorsa işlem/uyarı üret. Kısa rapor ver.`
606
+ );
607
+ return;
608
+ }
599
609
  engine.send(sub.sessionId, { text });
600
610
  } catch {}
601
611
  },
@@ -7673,6 +7683,17 @@ function watcherFire(w, value) {
7673
7683
  ? 'izlenen değer değişti'
7674
7684
  : `kural sağlandı (son değer ${value}, koşul ${w.op} ${w.value ?? ''})`;
7675
7685
  toastNotify(`İzleyici: ${w.name}`, target, 'watchers');
7686
+ /* FINANCE AJANI: izleyici alarmı AJAN DM'i olarak düşer ve boştaki ajanı
7687
+ tur beklemeden uyandırır (main.js finWakeAgentDm). Diğer oturumlarda
7688
+ eski davranış: mesaj doğrudan sohbete iner. */
7689
+ if (financeState.agents.has(String(sid))) {
7690
+ finWakeAgentDm(
7691
+ sid,
7692
+ `[FİNANS OLAYI — İZLEYİCİ ALARMI: ${w.name}]\n- ${target}\n` +
7693
+ `Şimdi yap: izleyiciyi kurma nedenini hatırla; durumu değerlendir, gerekiyorsa işlem/uyarı üret. Kısa rapor ver.`
7694
+ );
7695
+ return;
7696
+ }
7676
7697
  engine.send(sid, {
7677
7698
  text:
7678
7699
  `[IZLEYICI: ${w.name}] ${target}. ` +
@@ -9237,19 +9258,11 @@ const financeState = {
9237
9258
  pendingOrders: new Map(), /* ticket -> bekleyen emir (son başarılı okuma) */
9238
9259
  pendingReady: false, /* emir tabanı kuruldu mu (ilk tur sessiz; köprü kopunca sıfırlanır) */
9239
9260
  posVolume: new Map(), /* ticket -> hacim (netting hesapta artış = yeni dolum) */
9240
- pendingWarnings: [], /* tur saniyesi beklenmeden gönderilemeyen uyarılar sıradaki tura */
9241
9261
  excDirty: false,
9242
9262
  excSavedAt: 0,
9243
9263
  watchTimer: null,
9244
9264
  watchBusy: false,
9245
9265
  watchTickAt: 0,
9246
- /* TUR DÖNGÜSÜ (ekip → trader): rol ajanlarının turları bitince asıl trader
9247
- tetiklenir; trader turu bitince interval sonra yeni ekip turu başlar */
9248
- teamPending: new Set(), /* bu döngüde rapor beklenen rol ajanları */
9249
- cyclePhase: 'idle', /* idle: sıradaki ekip turu bekleniyor · team: raporlar toplanıyor · trader: trader turda */
9250
- cycleTimer: null, /* trader turundan sonra ekip turlarını başlatan zamanlayıcı */
9251
- cycleGuardTimer: null, /* ekip raporları takılırsa trader'ı zorlayan emniyet zamanlayıcısı */
9252
- traderWaitTimer: null, /* ekip bitince trader'ı tetikleyen kısa gecikme */
9253
9266
  alerts: [], /* fiyat alarmları (kalıcı: finance/alerts.json) */
9254
9267
  stats: null, /* son performans özeti (kalıcı: finance/stats.json) */
9255
9268
  statsAt: 0,
@@ -9777,6 +9790,7 @@ function finAlertsLoad() {
9777
9790
  price: Number(a && a.price) || 0,
9778
9791
  direction: String((a && a.direction) || 'above').toLowerCase(),
9779
9792
  note: String((a && a.note) || '').slice(0, 200),
9793
+ sid: String((a && a.sid) || ''), /* alarmı kuran ajan oturumu (uyandırma hedefi) */
9780
9794
  at: Number(a && a.at) || Date.now(),
9781
9795
  }))
9782
9796
  .filter((a) => a.id && a.symbol && a.price > 0);
@@ -9799,6 +9813,7 @@ function finAlertApi() {
9799
9813
  price,
9800
9814
  direction: String((a && a.direction) || 'above').toLowerCase() === 'below' ? 'below' : 'above',
9801
9815
  note: String((a && a.note) || '').slice(0, 200),
9816
+ sid: String((a && a.sid) || ''),
9802
9817
  at: Date.now(),
9803
9818
  };
9804
9819
  financeState.alerts.push(alarm);
@@ -9952,17 +9967,72 @@ async function finStatsRefresh(force) {
9952
9967
  }
9953
9968
  }
9954
9969
 
9970
+ /* ---- FİNANS OLAY → AJAN DM UYANDIRMA ----
9971
+ Stop/TP/bekleyen emir aktivasyonu/fiyat alarmı gibi olaylar koşan finance
9972
+ ajanlarına AJAN DM'i olarak düşer ve BOŞTAKİ ajanı TUR SANİYESİNİ
9973
+ BEKLEMEDEN uyandırır: dm+wake kaydı engine._pendingReports'a girer;
9974
+ flushPendingReports bunu continuous ajanın dmInbox'ına yazar ve
9975
+ onDmQueued → finWakeAgent turu hemen başlatır. Ajan meşgulse mesaj
9976
+ KAYBOLMAZ — tur biter bitmez (interval beklenmeden) yeni turda okunur
9977
+ (finWakeAgent.wakePending → finFlushOnDone).
9978
+ Olay ayrıca ekip DM grubuna sistem postu düşer (panelde canlı görünür). */
9979
+ function finWakeAgentDm(sid, text) {
9980
+ const id = String(sid || '');
9981
+ const agent = financeState.agents.get(id);
9982
+ if (!agent || !engine) return false;
9983
+ try {
9984
+ const body = agent.role
9985
+ ? String(text || '') + "\n(ROL HATIRLATMASI: işlem AÇMA — durumu analiz et; gerekiyorsa agent_dm ile ANA TRADER'a bildir.)"
9986
+ : String(text || '');
9987
+ (engine._pendingReports = engine._pendingReports || []).push({
9988
+ parentId: id,
9989
+ text: body,
9990
+ dm: true,
9991
+ wake: true,
9992
+ });
9993
+ engine.flushPendingReports(id);
9994
+ return true;
9995
+ } catch {
9996
+ return false;
9997
+ }
9998
+ }
9999
+
10000
+ /* Tüm koşan finance ajanlarını olayla uyandır. Dönüş: uyandırılan ajan sayısı. */
10001
+ function finWakeAgents(text, meta) {
10002
+ const body = String(text || '').trim();
10003
+ if (!body || !engine) return 0;
10004
+ if (!financeState.agents.size) {
10005
+ financeLog('[olay] koşan finance ajanı yok — uyarı yalnız günlük/bildirimde' + (meta && meta.kind ? ' (' + meta.kind + ')' : ''));
10006
+ return 0;
10007
+ }
10008
+ /* Panel görünürlüğü: olay ekip DM grubuna sistem postu olarak düşer */
10009
+ try {
10010
+ if (typeof engine._agentTeamPost === 'function') {
10011
+ engine._agentTeamPost('team:finance', 'finance', 'Beast Finance', body);
10012
+ }
10013
+ } catch {}
10014
+ let sent = 0;
10015
+ for (const [sid] of financeState.agents) {
10016
+ if (finWakeAgentDm(sid, body)) sent += 1;
10017
+ }
10018
+ return sent;
10019
+ }
10020
+
9955
10021
  /* ---- işlem günlüğü (görülen/kapanan pozisyonlar) ---- */
9956
10022
  async function finRecordClose(ticket, st) {
9957
10023
  let net = null;
9958
10024
  let symbol = (st && st.symbol) || '';
10025
+ let closeReason = null; /* kapanışı yapan son deal'in nedeni (4=SL, 5=TP, 6=stop-out) */
9959
10026
  try {
9960
10027
  const r = await mt5bridge.call('deals', { days: 3 }, 12000);
9961
10028
  const deals = (r && r.ok && r.data && r.data.deals) || [];
9962
10029
  const mine = deals.filter((d) => String(d.position_id) === String(ticket));
9963
10030
  if (mine.length) {
9964
10031
  net = 0;
9965
- for (const d of mine) net += (Number(d.profit) || 0) + (Number(d.swap) || 0) + (Number(d.commission) || 0);
10032
+ for (const d of mine) {
10033
+ net += (Number(d.profit) || 0) + (Number(d.swap) || 0) + (Number(d.commission) || 0);
10034
+ if (Number(d.entry) === 1) closeReason = Number(d.reason);
10035
+ }
9966
10036
  symbol = String(mine[0].symbol || symbol);
9967
10037
  }
9968
10038
  } catch {}
@@ -9974,12 +10044,26 @@ async function finRecordClose(ticket, st) {
9974
10044
  financeState.excursions.delete(String(ticket));
9975
10045
  finExcSave();
9976
10046
  finExcFlush(true);
9977
- finJournal({ kind: 'close', ticket, symbol, net: rounded, side: (st && st.side) || '', mfe, mae });
10047
+ const kind = finwatch.closeKind(closeReason);
10048
+ finJournal({ kind: 'close', ticket, symbol, net: rounded, side: (st && st.side) || '', mfe, mae, reason: kind || '' });
9978
10049
  const pl = rounded == null ? '' : ` · K/Z ${rounded >= 0 ? '+' : ''}${rounded.toFixed(2)}`;
9979
10050
  const line = `🏁 Pozisyon kapandı: ${symbol || '?'} #${ticket}${pl}`;
9980
10051
  financeLog('[watchdog] ' + line);
9981
10052
  if (finCfg().notifyTrades !== false) financeNotify(line, 'close');
9982
10053
  if (rounded != null) finStatsRefresh(true);
10054
+ /* STOP / TP / STOP-OUT: koşan tüm finance ajanları TUR BEKLEMEDEN uyanır
10055
+ (ajan DM kutusu + boştaysa anında tur) — manuel kapanışta uyandırma yok. */
10056
+ if (kind) {
10057
+ const label = kind === 'stop' ? 'STOP OLDU' : kind === 'tp' ? 'TP OLDU' : 'STOP-OUT (teminat)';
10058
+ const how = kind === 'tp' ? 'take-profit' : kind === 'stop' ? 'stop-loss' : 'stop-out';
10059
+ const netTxt = rounded == null ? '?' : `${rounded >= 0 ? '+' : ''}${rounded.toFixed(2)}`;
10060
+ finWakeAgents(
10061
+ `[FİNANS OLAYI — ${label}, TUR SANİYESİ BEKLENMEDEN İLETİLDİ]\n` +
10062
+ `- ${symbol || '?'}${st && st.side ? ' ' + String(st.side).toUpperCase() : ''} #${ticket} ${how} ile kapandı · net ${netTxt}\n` +
10063
+ `Şimdi yap: pozisyon kapandı — hesabı, risk durumunu (kayıp serisi/günlük limit) ve planını HEMEN değerlendir; gerekiyorsa yeni emir aç ya da BEKLE. Kısa rapor ver.`,
10064
+ { kind, ticket, symbol }
10065
+ );
10066
+ }
9983
10067
  }
9984
10068
 
9985
10069
  /* ---- risk otomasyonu (watchdog) ---- */
@@ -10027,21 +10111,26 @@ async function finCheckAlerts() {
10027
10111
  financeLog('[alarm] ' + line);
10028
10112
  financeNotify(line, 'alert');
10029
10113
  finJournal({ kind: 'alert', symbol: a.symbol, price: a.price, direction: a.direction, note: a.note || '' });
10114
+ /* ALARM SAHİBİ AJAN: alarmı kuran koşan finance ajanı DM'den uyanır;
10115
+ sahip koşmuyor/biliniyorsa koşan tüm finance ajanlarına düşer. */
10116
+ 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.`;
10120
+ if (!finWakeAgentDm(a.sid, wakeTxt)) finWakeAgents(wakeTxt, { kind: 'alarm', symbol: a.symbol });
10030
10121
  }
10031
10122
  if (changed) finAlertsSave();
10032
10123
  }
10033
10124
 
10034
10125
  /* ---- BEKLEYEN EMİR HABERLERİ ----
10035
- Aktifleşme uyarısı: günlük + panel/kanal bildirimi + koşan TÜM finance
10036
- ajanlarına TUR SANİYESİ BEKLEMEDEN mesaj. Oturum koşuyorsa engine steer
10037
- tamponuna girer (koşan turun güvenli noktasında okunur), boştaysa anında
10038
- yeni tur başlatır; sonraki turlar normal aralıkla devam eder. Gönderim
10039
- başarısızsa (durdurma kilidi/meşguliyet) uyarı sıradaki tur mesajına eklenir. */
10126
+ Aktifleşme: günlük + panel/kanal bildirimi + koşan TÜM finance ajanlarına
10127
+ AJAN DM'i — boştaki ajan tur saniyesini beklemeden uyanır, meşgul olan
10128
+ mesajı sıradaki güvenli noktada okur (finWakeAgents). İptal/süre doldu:
10129
+ yalnız günlük + bildirim (uyandırma yok). */
10040
10130
  function finNotifyPendingActivated(list) {
10041
10131
  const items = (Array.isArray(list) ? list : []).filter((x) => x && x.order && x.position);
10042
10132
  if (!items.length) return;
10043
10133
  const f = finCfg();
10044
- const heads = [];
10045
10134
  const detail = [];
10046
10135
  for (const { order: o, position: p } of items) {
10047
10136
  const symbol = String(o.symbol || p.symbol || '?');
@@ -10049,7 +10138,6 @@ function finNotifyPendingActivated(list) {
10049
10138
  const line =
10050
10139
  `⚡ BEKLEYEN EMİR AKTİFLEŞTİ: ${symbol} ${finwatch.orderTypeLabel(o)} ${finwatch.orderVolume(o)}` +
10051
10140
  ` → pozisyon #${String(p.ticket)} @ ${Number(p.price_open) || '?'}`;
10052
- heads.push(line);
10053
10141
  detail.push(
10054
10142
  `- ${symbol} · ${side === 'buy' ? 'ALIŞ' : 'SATIŞ'} · emir #${String(o.ticket || '?')}` +
10055
10143
  ` (${finwatch.orderTypeLabel(o)} @ ${Number(o.price_open) || '?'}) → pozisyon #${String(p.ticket)}` +
@@ -10074,30 +10162,12 @@ function finNotifyPendingActivated(list) {
10074
10162
  }
10075
10163
  }
10076
10164
  const body = [
10077
- '[ACİL UYARI — BEKLEYEN EMİR AKTİFLEŞTİ, TUR SANİYESİ BEKLENMEDEN İLETİLDİ]',
10165
+ '[FİNANS OLAYI — BEKLEYEN EMİR AKTİFLEŞTİ, TUR SANİYESİ BEKLENMEDEN İLETİLDİ]',
10078
10166
  ...detail,
10079
10167
  'Şimdi yap: yeni pozisyonu HEMEN değerlendir — SL/TP yerinde mi, korumasız mı, planına uygun mu; gerekiyorsa SL/TP güncelle ya da kapat. Kısa rapor ver.',
10080
10168
  'NOT: Bu bir ara uyarıdır; tur düzenin bu turdan sonra normal aralığıyla devam eder (tur sayacı/aralık değişmez).',
10081
10169
  ].join('\n');
10082
- let sent = 0;
10083
- for (const [sid, a] of financeState.agents) {
10084
- if (!a || !engine) continue;
10085
- const text = a.role
10086
- ? body + '\n(ROL HATIRLATMASI: işlem AÇMA — pozisyonu analiz et; gerekiyorsa agent_dm ile ANA TRADER\u2019a bildir.)'
10087
- : body;
10088
- try {
10089
- if (engine.send(String(sid), text, { userAction: false })) {
10090
- sent += 1;
10091
- } else {
10092
- financeState.pendingWarnings.push({ sid: String(sid), text, at: Date.now() });
10093
- if (financeState.pendingWarnings.length > 20) {
10094
- financeState.pendingWarnings.splice(0, financeState.pendingWarnings.length - 20);
10095
- }
10096
- }
10097
- } catch {}
10098
- }
10099
- if (!financeState.agents.size) financeLog('[bekleyen] koşan finance ajanı yok — uyarı yalnız günlük/bildirimde');
10100
- else if (!sent) financeLog('[bekleyen] uyarı hemen gönderilemedi — sıradaki turlara iliştirildi');
10170
+ finWakeAgents(body, { kind: 'pending-active' });
10101
10171
  }
10102
10172
 
10103
10173
  /* Aktifleşmeden listeden düşen emir (iptal/süre doldu): günlük + bildirim */
@@ -10220,9 +10290,10 @@ async function finWatchTick() {
10220
10290
  }
10221
10291
  /* ---- BEKLEYEN EMİR AKTİFLİĞİ ----
10222
10292
  Emir listesi her tur karşılaştırılır; listeden düşen emir için aynı anda
10223
- yeni/eşleşen bir pozisyon oluştuysa emir AKTİFLEŞTİ → ajanlar TUR
10224
- SANİYESİNİ BEKLEMEDEN uyarılır; tur döngüsü sonra normal aralıkla sürer.
10225
- (Köprü kesintisinde taban sıfırlanır — yanlış "aktifleşti" uyarısı yok.) */
10293
+ yeni/eşleşen bir pozisyon oluştuysa emir AKTİFLEŞTİ → koşan finance
10294
+ ajanları TUR SANİYESİNİ BEKLEMEDEN ajan DM'iyle uyandırılır; tur düzeni
10295
+ sonra normal aralıkla sürer. (Köprü kesintisinde taban sıfırlanır —
10296
+ yanlış "aktifleşti" uyarısı yok.) */
10226
10297
  try {
10227
10298
  if (!(ordersOk && positionsOk)) {
10228
10299
  financeState.pendingReady = false;
@@ -10878,20 +10949,6 @@ function finAgentStop(sid, reason) {
10878
10949
  if (!agent) return false;
10879
10950
  clearTimeout(agent.timer);
10880
10951
  financeState.agents.delete(sid);
10881
- /* TUR DÖNGÜSÜ temizliği: trader gidince döngü tamamen durur; ekip ajanı
10882
- gidince raporu bekleyen döngü kalanlarla tamamlanır (bekleme boşa düşmez) */
10883
- try {
10884
- if (agent.main) {
10885
- clearTimeout(financeState.cycleTimer); financeState.cycleTimer = null;
10886
- clearTimeout(financeState.cycleGuardTimer); financeState.cycleGuardTimer = null;
10887
- clearTimeout(financeState.traderWaitTimer); financeState.traderWaitTimer = null;
10888
- financeState.teamPending = new Set();
10889
- financeState.cyclePhase = 'idle';
10890
- } else if (agent.role) {
10891
- financeState.teamPending.delete(sid);
10892
- finMaybeTraderAfterTeam(false);
10893
- }
10894
- } catch {}
10895
10952
  if (sid === String(financeState.traderSid || '')) financeState.traderSid = null;
10896
10953
  try {
10897
10954
  if (engine && engine.isBusy(sid)) {
@@ -11044,116 +11101,6 @@ async function finConsultPlan(f, agent, sid) {
11044
11101
  }
11045
11102
  }
11046
11103
 
11047
- /* ---- TUR DÖNGÜSÜ: EKİP TURU → ASIL TRADER ----
11048
- Ekip (rol) ajanları sırayla değil AYNI ANDA turlar; HEPSİ bitince asıl
11049
- trader'ın turu tetiklenir — böylece teknik/görsel/haber raporları TAZEYKEN
11050
- karar verir (finTeamDigest aynı turda enjekte edilir). Trader turu bitince
11051
- `intervalSec` sonra yeni ekip turu başlar. Ekip yoksa trader kendi
11052
- aralığıyla koşar (eski davranış). Sembol işçileri döngüye girmez. */
11053
-
11054
- function finRoleSids() {
11055
- const out = [];
11056
- for (const [sid, a] of financeState.agents) if (a && a.role) out.push(String(sid));
11057
- return out;
11058
- }
11059
-
11060
- function finMainAgentRef() {
11061
- for (const [sid, a] of financeState.agents) if (a && a.main) return { sid: String(sid), agent: a };
11062
- return null;
11063
- }
11064
-
11065
- /* Ekip turları için EMNİYET: raporlar takılır/döngü koparsa trader zorlanır */
11066
- function finArmCycleGuard() {
11067
- clearTimeout(financeState.cycleGuardTimer);
11068
- const iv = Math.max(30, Number(finCfg().intervalSec) || 120) * 1000;
11069
- const guard = Math.max(10 * 60 * 1000, iv * 5);
11070
- financeState.cycleGuardTimer = setTimeout(() => {
11071
- financeState.cycleGuardTimer = null;
11072
- if (financeState.cyclePhase !== 'team') return;
11073
- financeLog('[tur] ekip raporları zaman aşımı — trader turu zorlanıyor');
11074
- financeState.teamPending = new Set();
11075
- financeState.cyclePhase = 'idle';
11076
- finMaybeTraderAfterTeam(true);
11077
- }, guard);
11078
- }
11079
-
11080
- /* Ekip turlarını başlat (yeni döngü): rol ajanları hafif gecikmeyle sıraya girer */
11081
- function finTeamKickCycle() {
11082
- clearTimeout(financeState.cycleTimer);
11083
- financeState.cycleTimer = null;
11084
- clearTimeout(financeState.traderWaitTimer);
11085
- financeState.traderWaitTimer = null;
11086
- const roles = finRoleSids();
11087
- if (!roles.length) {
11088
- financeState.cyclePhase = 'idle';
11089
- return;
11090
- }
11091
- financeState.teamPending = new Set(roles);
11092
- financeState.cyclePhase = 'team';
11093
- finArmCycleGuard();
11094
- roles.forEach((sid, i) => {
11095
- const a = financeState.agents.get(sid);
11096
- if (!a) return;
11097
- clearTimeout(a.timer);
11098
- a.timer = setTimeout(() => {
11099
- a.timer = null;
11100
- try { finAgentRound(sid); } catch {}
11101
- }, 400 + i * 1200);
11102
- });
11103
- financeLog('[tur] ekip turları başladı (' + roles.length + ' ajan) — bitince trader tetiklenecek');
11104
- }
11105
-
11106
- /* Ekip ajanı turu bitti: HEPSİ bittiyse asıl trader'ı tetikle.
11107
- force=true (emniyet): bekleyen raporlar boş sayılır. */
11108
- function finMaybeTraderAfterTeam(force) {
11109
- if (!force && financeState.cyclePhase !== 'team') return;
11110
- const p = financeState.teamPending || new Set();
11111
- financeState.teamPending = p;
11112
- for (const s of [...p]) if (!financeState.agents.has(s)) p.delete(s);
11113
- if (!force && p.size) return;
11114
- financeState.teamPending = new Set();
11115
- const main = finMainAgentRef();
11116
- if (!main || !engine) {
11117
- financeState.cyclePhase = 'idle';
11118
- return;
11119
- }
11120
- financeState.cyclePhase = 'trader';
11121
- clearTimeout(financeState.cycleGuardTimer);
11122
- financeState.cycleGuardTimer = null;
11123
- clearTimeout(financeState.traderWaitTimer);
11124
- const fire = () => {
11125
- financeState.traderWaitTimer = null;
11126
- if (!financeState.agents.has(main.sid)) return;
11127
- if (engine.isBusy(main.sid)) {
11128
- /* trader başka turda (DM uyandırması vb.) — kısa süre sonra yeniden dene */
11129
- financeState.traderWaitTimer = setTimeout(fire, 5000);
11130
- return;
11131
- }
11132
- financeLog('[tur] ekip raporları tamam — trader turu tetiklendi');
11133
- finAgentRound(main.sid);
11134
- };
11135
- financeState.traderWaitTimer = setTimeout(fire, 1200);
11136
- }
11137
-
11138
- /* Trader turu bitti → interval sonra yeni ekip turu (ekip varsa) */
11139
- function finCycleAfterTrader() {
11140
- const f = finCfg();
11141
- const iv = Math.max(30, Number(f.intervalSec) || 120) * 1000;
11142
- clearTimeout(financeState.cycleTimer);
11143
- financeState.cycleTimer = null;
11144
- clearTimeout(financeState.traderWaitTimer);
11145
- financeState.traderWaitTimer = null;
11146
- clearTimeout(financeState.cycleGuardTimer);
11147
- financeState.cycleGuardTimer = null;
11148
- financeState.teamPending = new Set();
11149
- financeState.cyclePhase = 'idle';
11150
- if (!finRoleSids().length) return;
11151
- financeState.cycleTimer = setTimeout(() => {
11152
- financeState.cycleTimer = null;
11153
- try { finTeamKickCycle(); } catch {}
11154
- }, iv);
11155
- }
11156
-
11157
11104
  /* Bir ajanın TEK turu: (opsiyonel) ana ajan planı + tur emri */
11158
11105
  function finAgentRound(sid) {
11159
11106
  const agent = financeState.agents.get(String(sid));
@@ -11183,26 +11130,9 @@ function finAgentRound(sid) {
11183
11130
  : 'İşlem açabilirsin — limitlere uy, SL\u2019siz pozisyon bırakma.';
11184
11131
  const focus = agent.symbols.length ? `Odak: ${agent.symbols.join(', ')}. ` : '';
11185
11132
  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.`;
11186
- /* TUR SANİYESİNİ BEKLEMEDEN gönderilemeyen uyarılar (bekleyen emir
11187
- aktivasyonu): sıradaki tur mesajına en başta iliştirilir. */
11188
- let warnPrefix = '';
11189
- try {
11190
- const q = financeState.pendingWarnings || [];
11191
- if (q.length) {
11192
- const mine = q.filter((w) => w && String(w.sid) === String(sid));
11193
- if (mine.length) {
11194
- financeState.pendingWarnings = q.filter((w) => mine.indexOf(w) === -1);
11195
- warnPrefix = mine.map((w) => String(w.text || '')).join('\n\n') + '\n\n';
11196
- } else {
11197
- /* yaşlı kayıtları buda (duran/silinmiş ajanlar için birikmesin) */
11198
- const now = Date.now();
11199
- financeState.pendingWarnings = q.filter((w) => w && now - Number(w.at || 0) < 15 * 60000);
11200
- }
11201
- }
11202
- } catch {}
11203
11133
  const launch = (planBlock) => {
11204
11134
  if (!financeState.agents.has(String(sid))) return;
11205
- const ok = engine.send(sid, warnPrefix + planBlock + round, { userAction: false });
11135
+ const ok = engine.send(sid, planBlock + round, { userAction: false });
11206
11136
  if (ok) {
11207
11137
  financeState.lastRoundAt = Date.now();
11208
11138
  if (agent.main) finPush('trader', { state: 'running', round: agent.round });
@@ -11237,7 +11167,12 @@ function finWakeAgent(sid) {
11237
11167
  const id = String(sid || '');
11238
11168
  const agent = financeState.agents.get(id);
11239
11169
  if (!agent || !engine) return;
11240
- if (engine.isBusy(id)) return;
11170
+ if (engine.isBusy(id)) {
11171
+ /* tur sürüyor: olay dmInbox'ta bekler — tur bitince finFlushOnDone
11172
+ BEKLEMEDEN yeni tur açar (interval beklenmez) */
11173
+ agent.wakePending = true;
11174
+ return;
11175
+ }
11241
11176
  clearTimeout(agent.timer);
11242
11177
  agent.timer = setTimeout(() => { try { finAgentRound(id); } catch {} }, 300);
11243
11178
  }
@@ -11254,30 +11189,20 @@ function finFlushOnDone(ev) {
11254
11189
  return;
11255
11190
  }
11256
11191
  clearTimeout(agent.timer);
11257
- agent.timer = null;
11192
+ /* TUR SIRASINDA GELEN OLAY/DM: tur bitti → interval BEKLENMEDEN yeni tur
11193
+ (dmInbox'taki olay mesajı bu turda okunur) */
11194
+ if (agent.wakePending) {
11195
+ agent.wakePending = false;
11196
+ financeState.lastRoundAt = Date.now();
11197
+ agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, 800);
11198
+ return;
11199
+ }
11258
11200
  const f = finCfg();
11259
11201
  const iv = Math.max(30, Number(f.intervalSec) || 120) * 1000;
11260
11202
  financeState.lastRoundAt = Date.now();
11261
11203
  if (agent.main) {
11262
11204
  finPush('trader', { state: 'idle', round: agent.round, nextInSec: iv / 1000 });
11263
- /* TUR DÖNGÜSÜ: ekip varsa trader KENDİ KENDİNE zamanlanmaz — interval
11264
- sonra ekip turları başlar, hepsi bitince trader yeniden tetiklenir.
11265
- (Ekip turu hâlâ sürüyorsa veya trader tetiği bekliyorsa dokunma.) */
11266
- if (finRoleSids().length) {
11267
- if (financeState.cyclePhase !== 'team' && !financeState.traderWaitTimer) finCycleAfterTrader();
11268
- } else {
11269
- agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, iv);
11270
- }
11271
- return;
11272
- }
11273
- if (agent.role) {
11274
- /* EKİP AJANI turu bitti → raporlar tamamlanınca asıl trader tetiklenir.
11275
- Trader koşmuyorsa (nadir) rol ajanı kendi aralığıyla devam eder. */
11276
- finMaybeTraderAfterTeam(false);
11277
- if (!finMainAgentRef()) agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, iv);
11278
- return;
11279
11205
  }
11280
- /* SEMBOL İŞÇİSİ: döngü dışı — kendi aralığıyla koşar */
11281
11206
  agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, iv);
11282
11207
  }
11283
11208
 
@@ -11338,7 +11263,6 @@ ipcMain.handle('finance:snapshot', async () => {
11338
11263
  });
11339
11264
 
11340
11265
  ipcMain.handle('finance:mode', async (_e, payload) => {
11341
- const wasMode = financeState.mode;
11342
11266
  financeState.mode = !!(payload && payload.on);
11343
11267
  /* BEAST FINANCE = MT5 KAPISI:
11344
11268
  - mod AÇILINCA köprü + MT5 terminali OTOMATİK başlar (watchdog dahil) —
@@ -11392,23 +11316,8 @@ ipcMain.handle('finance:mode', async (_e, payload) => {
11392
11316
  }
11393
11317
  } catch {}
11394
11318
  }
11395
- /* TRADER OTOMATİK BAŞLAT: Beast Finance moduna GEÇİLİNCE (kapalıyken
11396
- açılınca) TRADE AJANI kendiliğinden koşar — ▶ Ajanı Başlat beklenmez;
11397
- analiz ekibi de aynı akışta başlar. Zaten koşuyorsa sessizce onaylanır.
11398
- /stop kilidi açıkken kendiliğinden başlatılmaz (kilit yalnız açık bir
11399
- başlat eylemiyle — /start ya da ▶ — açılır). */
11400
- if (financeState.mode && !wasMode && engine && !engine._stopped) {
11401
- financeTraderStart()
11402
- .then((r) => {
11403
- if (r && r.ok) {
11404
- if (!r.already) financeLog('[trader] otomatik başlatıldı (finance modu açıldı)');
11405
- } else if (r && r.error) {
11406
- financeLog('[trader] otomatik başlatılamadı: ' + r.error);
11407
- try { financeNotify('⚠️ Trader otomatik başlatılamadı: ' + r.error, 'risk'); } catch {}
11408
- }
11409
- })
11410
- .catch(() => {});
11411
- }
11319
+ /* OTOMATİK TRADER KAPALI: finance moduna geçmek trader'ı KENDİLİĞİNDEN
11320
+ başlatmaz — kullanıcı TRADE AJANI kartındaki ▶ ile elle başlatır */
11412
11321
  return { ok: true, mode: financeState.mode, needNew, resumeSid };
11413
11322
  });
11414
11323
 
@@ -11443,23 +11352,14 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
11443
11352
  }
11444
11353
  if (p.intervalSec !== undefined) f.intervalSec = Math.max(30, Math.min(3600, Math.round(Number(p.intervalSec) || 120)));
11445
11354
  /* TUR ARALIĞI CANLI: bekleyen zamanlayıcılar yeni aralıkla yeniden kurulur —
11446
- koşan tur bitince zaten finFlushOnDone yeni aralığı okur. TUR DÖNGÜSÜ
11447
- yönetimindeki trader/rol ajanları kendi zamanlayıcısını KULLANMAZ —
11448
- döngü zamanlayıcısı aşağıda yeni aralıkla kurulur. */
11355
+ koşan tur bitince zaten finFlushOnDone yeni aralığı okur. */
11449
11356
  if (p.intervalSec !== undefined && financeState.agents) {
11450
11357
  const iv = Math.max(30, Math.round(Number(f.intervalSec) || 120)) * 1000;
11451
- const hasTeam = finRoleSids().length > 0;
11452
11358
  for (const [sid, a] of financeState.agents) {
11453
11359
  if (!a || !engine || engine.isBusy(sid)) continue;
11454
- if (a.main && hasTeam) continue;
11455
- if (a.role && finMainAgentRef()) continue;
11456
11360
  clearTimeout(a.timer);
11457
11361
  a.timer = setTimeout(() => { try { finAgentRound(String(sid)); } catch {} }, iv);
11458
11362
  }
11459
- if (financeState.cycleTimer) {
11460
- clearTimeout(financeState.cycleTimer);
11461
- financeState.cycleTimer = setTimeout(() => { try { finTeamKickCycle(); } catch {} }, iv);
11462
- }
11463
11363
  }
11464
11364
  if (p.maxLot !== undefined) f.maxLot = Math.max(0.01, Math.min(100, Number(p.maxLot) || 0.1));
11465
11365
  if (p.maxPositions !== undefined) f.maxPositions = Math.max(1, Math.min(20, Math.round(Number(p.maxPositions) || 3)));
@@ -11594,19 +11494,6 @@ async function financeTraderStart() {
11594
11494
  finPush('trader', { state: 'running', round: mainAgent.round });
11595
11495
  /* ANALİZ EKİBİ: seçili roller için ayrı sürekli ajanlar (koşmıyorsa) */
11596
11496
  finTeamStart(f);
11597
- /* TUR DÖNGÜSÜ başlangıcı: ilk ekip raporları bitince trader karar turuna
11598
- geçer; trader brief turu bitince de yeni ekip turu interval ile başlar */
11599
- try {
11600
- const roles = finRoleSids();
11601
- if (roles.length) {
11602
- financeState.teamPending = new Set(roles);
11603
- financeState.cyclePhase = 'team';
11604
- finArmCycleGuard();
11605
- } else {
11606
- financeState.teamPending = new Set();
11607
- financeState.cyclePhase = 'idle';
11608
- }
11609
- } catch {}
11610
11497
  return { ok: true, sid: mainSid };
11611
11498
  }
11612
11499