beast-agent 2.51.1 → 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.51.1",
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,4 +107,85 @@ function plan(pos, meta, st, cfg) {
107
107
  return out;
108
108
  }
109
109
 
110
- module.exports = { plan, roundTo, stepRound };
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
+
122
+ /* ---- bekleyen emir (pending) → pozisyon eşleştirme ----
123
+ Watchdog turu emir listesini karşılaştırır: listeden DÜŞEN emir için aynı
124
+ turda YENİ açılan (ya da netting hesapta hacmi artan) eşleşen bir pozisyon
125
+ varsa emir AKTİFLEŞTİ (tetiklendi); eşleşme yoksa İPTAL/süresi doldu.
126
+ Saf karar katmanı: IO ve ajan uyarısı main süreçte yapılır. */
127
+
128
+ const ORDER_TYPE_LABELS = {
129
+ 0: 'BUY LIMIT',
130
+ 1: 'SELL LIMIT',
131
+ 2: 'BUY STOP',
132
+ 3: 'SELL STOP',
133
+ 4: 'BUY STOP LIMIT',
134
+ 5: 'SELL STOP LIMIT',
135
+ };
136
+
137
+ /* MT5 emir tipleri: çift = alış, tek = satış */
138
+ function orderSide(o) {
139
+ return Number(o && o.type) % 2 === 1 ? 'sell' : 'buy';
140
+ }
141
+
142
+ function orderTypeLabel(o) {
143
+ const n = Number(o && o.type);
144
+ return ORDER_TYPE_LABELS[n] || 'TIP ' + (isFinite(n) ? n : '?');
145
+ }
146
+
147
+ function orderVolume(o) {
148
+ return num(o && (o.volume_current != null ? o.volume_current : o.volume_initial != null ? o.volume_initial : o.volume), 0);
149
+ }
150
+
151
+ /* goneOrders: önceki turda var olup şimdi listede OLMAYAN emirler
152
+ freshPositions: bu tur yeni açılan / hacmi artan pozisyonlar
153
+ Dönüş: { activated:[{order,position}], canceled:[order] } */
154
+ function matchPendingDelta(goneOrders, freshPositions) {
155
+ const out = { activated: [], canceled: [] };
156
+ const free = (Array.isArray(freshPositions) ? freshPositions : []).filter(Boolean);
157
+ for (const o of Array.isArray(goneOrders) ? goneOrders : []) {
158
+ if (!o) continue;
159
+ const side = orderSide(o);
160
+ const symbol = String(o.symbol || '').toUpperCase();
161
+ const ovol = orderVolume(o);
162
+ const oprice = num(o.price_open, 0);
163
+ const omagic = Number(o.magic) || 0;
164
+ let best = -1;
165
+ let bestScore = Infinity;
166
+ for (let i = 0; i < free.length; i++) {
167
+ const p = free[i];
168
+ if (!p) continue;
169
+ if (String(p.symbol || '').toUpperCase() !== symbol) continue;
170
+ if ((num(p.type, -1) === 0 ? 'buy' : 'sell') !== side) continue;
171
+ /* fiyat yakınlığı + hacim yakınlığı; aynı magic KESİN öncelik */
172
+ let score = Math.abs(num(p.price_open, 0) - oprice);
173
+ const pvol = num(p.volume, 0);
174
+ if (ovol > 0 && pvol > 0) score += Math.abs(pvol - ovol) * 0.01;
175
+ if (omagic !== 0 && (Number(p.magic) || 0) === omagic) score -= 1e9;
176
+ if (score < bestScore) {
177
+ bestScore = score;
178
+ best = i;
179
+ }
180
+ }
181
+ if (best >= 0) {
182
+ out.activated.push({ order: o, position: free[best] });
183
+ free.splice(best, 1);
184
+ } else {
185
+ out.canceled.push(o);
186
+ }
187
+ }
188
+ return out;
189
+ }
190
+
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}. ` +
@@ -9234,6 +9255,9 @@ const financeState = {
9234
9255
  agents: new Map(), /* sid -> { symbols, main, round, timer } — AYNI ANDA koşan finance ajanları */
9235
9256
  watch: new Map(), /* ticket -> risk otomasyonu durumu (R, BE, kısmi TP...) */
9236
9257
  excursions: new Map(), /* ticket -> { symbol, side, mfe, mae, at } — MAE/MFE takibi */
9258
+ pendingOrders: new Map(), /* ticket -> bekleyen emir (son başarılı okuma) */
9259
+ pendingReady: false, /* emir tabanı kuruldu mu (ilk tur sessiz; köprü kopunca sıfırlanır) */
9260
+ posVolume: new Map(), /* ticket -> hacim (netting hesapta artış = yeni dolum) */
9237
9261
  excDirty: false,
9238
9262
  excSavedAt: 0,
9239
9263
  watchTimer: null,
@@ -9766,6 +9790,7 @@ function finAlertsLoad() {
9766
9790
  price: Number(a && a.price) || 0,
9767
9791
  direction: String((a && a.direction) || 'above').toLowerCase(),
9768
9792
  note: String((a && a.note) || '').slice(0, 200),
9793
+ sid: String((a && a.sid) || ''), /* alarmı kuran ajan oturumu (uyandırma hedefi) */
9769
9794
  at: Number(a && a.at) || Date.now(),
9770
9795
  }))
9771
9796
  .filter((a) => a.id && a.symbol && a.price > 0);
@@ -9788,6 +9813,7 @@ function finAlertApi() {
9788
9813
  price,
9789
9814
  direction: String((a && a.direction) || 'above').toLowerCase() === 'below' ? 'below' : 'above',
9790
9815
  note: String((a && a.note) || '').slice(0, 200),
9816
+ sid: String((a && a.sid) || ''),
9791
9817
  at: Date.now(),
9792
9818
  };
9793
9819
  financeState.alerts.push(alarm);
@@ -9941,17 +9967,72 @@ async function finStatsRefresh(force) {
9941
9967
  }
9942
9968
  }
9943
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
+
9944
10021
  /* ---- işlem günlüğü (görülen/kapanan pozisyonlar) ---- */
9945
10022
  async function finRecordClose(ticket, st) {
9946
10023
  let net = null;
9947
10024
  let symbol = (st && st.symbol) || '';
10025
+ let closeReason = null; /* kapanışı yapan son deal'in nedeni (4=SL, 5=TP, 6=stop-out) */
9948
10026
  try {
9949
10027
  const r = await mt5bridge.call('deals', { days: 3 }, 12000);
9950
10028
  const deals = (r && r.ok && r.data && r.data.deals) || [];
9951
10029
  const mine = deals.filter((d) => String(d.position_id) === String(ticket));
9952
10030
  if (mine.length) {
9953
10031
  net = 0;
9954
- 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
+ }
9955
10036
  symbol = String(mine[0].symbol || symbol);
9956
10037
  }
9957
10038
  } catch {}
@@ -9963,12 +10044,26 @@ async function finRecordClose(ticket, st) {
9963
10044
  financeState.excursions.delete(String(ticket));
9964
10045
  finExcSave();
9965
10046
  finExcFlush(true);
9966
- 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 || '' });
9967
10049
  const pl = rounded == null ? '' : ` · K/Z ${rounded >= 0 ? '+' : ''}${rounded.toFixed(2)}`;
9968
10050
  const line = `🏁 Pozisyon kapandı: ${symbol || '?'} #${ticket}${pl}`;
9969
10051
  financeLog('[watchdog] ' + line);
9970
10052
  if (finCfg().notifyTrades !== false) financeNotify(line, 'close');
9971
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
+ }
9972
10067
  }
9973
10068
 
9974
10069
  /* ---- risk otomasyonu (watchdog) ---- */
@@ -10016,23 +10111,97 @@ async function finCheckAlerts() {
10016
10111
  financeLog('[alarm] ' + line);
10017
10112
  financeNotify(line, 'alert');
10018
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 });
10019
10121
  }
10020
10122
  if (changed) finAlertsSave();
10021
10123
  }
10022
10124
 
10125
+ /* ---- BEKLEYEN EMİR HABERLERİ ----
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). */
10130
+ function finNotifyPendingActivated(list) {
10131
+ const items = (Array.isArray(list) ? list : []).filter((x) => x && x.order && x.position);
10132
+ if (!items.length) return;
10133
+ const f = finCfg();
10134
+ const detail = [];
10135
+ for (const { order: o, position: p } of items) {
10136
+ const symbol = String(o.symbol || p.symbol || '?');
10137
+ const side = finwatch.orderSide(o);
10138
+ const line =
10139
+ `⚡ BEKLEYEN EMİR AKTİFLEŞTİ: ${symbol} ${finwatch.orderTypeLabel(o)} ${finwatch.orderVolume(o)}` +
10140
+ ` → pozisyon #${String(p.ticket)} @ ${Number(p.price_open) || '?'}`;
10141
+ detail.push(
10142
+ `- ${symbol} · ${side === 'buy' ? 'ALIŞ' : 'SATIŞ'} · emir #${String(o.ticket || '?')}` +
10143
+ ` (${finwatch.orderTypeLabel(o)} @ ${Number(o.price_open) || '?'}) → pozisyon #${String(p.ticket)}` +
10144
+ ` · ${Number(p.volume) || '?'} lot @ ${Number(p.price_open) || '?'}` +
10145
+ ` · SL ${Number(p.sl) || 0} / TP ${Number(p.tp) || 0}`
10146
+ );
10147
+ financeLog('[bekleyen] ' + line);
10148
+ finJournal({
10149
+ kind: 'pending-active',
10150
+ order: Number(o.ticket) || String(o.ticket || ''),
10151
+ ticket: Number(p.ticket) || 0,
10152
+ symbol,
10153
+ side,
10154
+ volume: Number(p.volume) || finwatch.orderVolume(o),
10155
+ price: Number(p.price_open) || Number(o.price_open) || 0,
10156
+ sl: Number(p.sl) || 0,
10157
+ tp: Number(p.tp) || 0,
10158
+ });
10159
+ finPush('trade', { line });
10160
+ if (f.notifyTrades !== false) {
10161
+ try { financeNotify(line, 'trade', false); } catch {}
10162
+ }
10163
+ }
10164
+ const body = [
10165
+ '[FİNANS OLAYI — BEKLEYEN EMİR AKTİFLEŞTİ, TUR SANİYESİ BEKLENMEDEN İLETİLDİ]',
10166
+ ...detail,
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.',
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).',
10169
+ ].join('\n');
10170
+ finWakeAgents(body, { kind: 'pending-active' });
10171
+ }
10172
+
10173
+ /* Aktifleşmeden listeden düşen emir (iptal/süre doldu): günlük + bildirim */
10174
+ function finNotifyPendingGone(o) {
10175
+ if (!o) return;
10176
+ const line =
10177
+ `BEKLEYEN EMİR KALKTI: ${String(o.symbol || '?')} ${finwatch.orderTypeLabel(o)} ${finwatch.orderVolume(o)}` +
10178
+ ` (emir #${String(o.ticket || '?')}) — pozisyona dönüşmedi (iptal/süre doldu).`;
10179
+ financeLog('[bekleyen] ' + line);
10180
+ finJournal({ kind: 'cancel', order: Number(o.ticket) || String(o.ticket || ''), symbol: String(o.symbol || ''), note: 'aktifleşmeden kalktı (iptal/süre doldu)' });
10181
+ if (finCfg().notifyTrades !== false) {
10182
+ finPush('trade', { line });
10183
+ try { financeNotify(line, 'cancel', false); } catch {}
10184
+ }
10185
+ }
10186
+
10023
10187
  async function finWatchTick() {
10024
10188
  if (!mt5bridge.running || financeState.watchBusy) return;
10025
10189
  financeState.watchBusy = true;
10026
10190
  try {
10027
10191
  const cfg = finCfg();
10028
10192
  financeState.watchTickAt = Date.now();
10029
- const [posR, accR] = await Promise.all([
10193
+ const [posR, accR, ordR] = await Promise.all([
10030
10194
  mt5bridge.call('positions', {}, 8000).catch(() => null),
10031
10195
  mt5bridge.call('account', {}, 8000).catch(() => null),
10196
+ mt5bridge.call('orders', {}, 8000).catch(() => null),
10032
10197
  ]);
10033
10198
  const account = accR && accR.ok ? (accR.data && accR.data.account) : null;
10034
10199
  const positionsOk = !!(posR && posR.ok);
10035
10200
  const positions = positionsOk ? ((posR.data && posR.data.positions) || []) : [];
10201
+ const ordersOk = !!(ordR && ordR.ok);
10202
+ const orders = ordersOk ? ((ordR.data && ordR.data.orders) || []) : [];
10203
+ /* bekleyen emir takibi için ÖNCEKİ durum: watch (açık ticket'lar) + hacimler */
10204
+ const prevTickets = new Set(financeState.watch.keys());
10036
10205
  if (account) {
10037
10206
  financeState.account = account;
10038
10207
  finEquitySample(account);
@@ -10119,6 +10288,44 @@ async function finWatchTick() {
10119
10288
  }
10120
10289
  if (decision.rearmSL && st.warnSL) st.warnSL = false;
10121
10290
  }
10291
+ /* ---- BEKLEYEN EMİR AKTİFLİĞİ ----
10292
+ Emir listesi her tur karşılaştırılır; listeden düşen emir için aynı anda
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.) */
10297
+ try {
10298
+ if (!(ordersOk && positionsOk)) {
10299
+ financeState.pendingReady = false;
10300
+ } else {
10301
+ const nowMap = new Map();
10302
+ for (const o of orders) if (o && o.ticket != null) nowMap.set(String(o.ticket), o);
10303
+ if (!financeState.pendingReady) {
10304
+ /* ilk başarılı okuma (açılış/bağlantı sonrası): sessizce taban al */
10305
+ financeState.pendingOrders = nowMap;
10306
+ financeState.pendingReady = true;
10307
+ } else {
10308
+ const gone = [];
10309
+ for (const [t, o] of financeState.pendingOrders) if (!nowMap.has(t)) gone.push(o);
10310
+ financeState.pendingOrders = nowMap;
10311
+ if (gone.length) {
10312
+ /* bu tur YENİ açılan ya da hacmi artan pozisyonlar (netting hesap) */
10313
+ const fresh = [];
10314
+ for (const p of positions) {
10315
+ if (!p) continue;
10316
+ const t = String(p.ticket);
10317
+ const vol = Number(p.volume) || 0;
10318
+ const before = financeState.posVolume.get(t);
10319
+ if (!prevTickets.has(t) || (before != null && vol > before + 1e-9)) fresh.push(p);
10320
+ }
10321
+ const delta = finwatch.matchPendingDelta(gone, fresh);
10322
+ if (delta.activated.length) finNotifyPendingActivated(delta.activated);
10323
+ for (const o of delta.canceled) finNotifyPendingGone(o);
10324
+ }
10325
+ }
10326
+ financeState.posVolume = new Map(positions.map((p) => [String(p.ticket), Number(p.volume) || 0]));
10327
+ }
10328
+ } catch {}
10122
10329
  /* kapanan pozisyonlar: net K/Z ile günlük + bildirim (yalnız pozisyon
10123
10330
  listesi GERÇEKTEN alındıysa — köprü kesintisinde yanlış kapanış yok) */
10124
10331
  if (positionsOk) {
@@ -10571,6 +10778,13 @@ async function finEaStatusCheck() {
10571
10778
  }
10572
10779
 
10573
10780
  mt5bridge.on('bridge', (m) => {
10781
+ if (!(m && m.connected)) {
10782
+ /* köprü koptu: bekleyen emir tabanı sıfırlanır — kopukluk sırasında
10783
+ tetiklenen/iptal edilen emirler yanlış "aktifleşti" uyarısı üretmez */
10784
+ financeState.pendingReady = false;
10785
+ financeState.pendingOrders = new Map();
10786
+ financeState.posVolume = new Map();
10787
+ }
10574
10788
  if (m && m.connected) {
10575
10789
  financeLog('[MT5] terminal bağlı: ' + ((m.terminal && (m.terminal.name + ' @ ' + m.terminal.company)) || 'MT5'));
10576
10790
  if (m.account) financeLog(`[MT5] hesap ${m.account.login} · bakiye ${m.account.balance} ${m.account.currency}`);
@@ -10953,7 +11167,12 @@ function finWakeAgent(sid) {
10953
11167
  const id = String(sid || '');
10954
11168
  const agent = financeState.agents.get(id);
10955
11169
  if (!agent || !engine) return;
10956
- 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
+ }
10957
11176
  clearTimeout(agent.timer);
10958
11177
  agent.timer = setTimeout(() => { try { finAgentRound(id); } catch {} }, 300);
10959
11178
  }
@@ -10970,6 +11189,14 @@ function finFlushOnDone(ev) {
10970
11189
  return;
10971
11190
  }
10972
11191
  clearTimeout(agent.timer);
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
+ }
10973
11200
  const f = finCfg();
10974
11201
  const iv = Math.max(30, Number(f.intervalSec) || 120) * 1000;
10975
11202
  financeState.lastRoundAt = Date.now();