beast-agent 2.51.1 → 2.52.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.52.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -107,4 +107,73 @@ function plan(pos, meta, st, cfg) {
107
107
  return out;
108
108
  }
109
109
 
110
- module.exports = { plan, roundTo, stepRound };
110
+ /* ---- bekleyen emir (pending) → pozisyon eşleştirme ----
111
+ Watchdog turu emir listesini karşılaştırır: listeden DÜŞEN emir için aynı
112
+ turda YENİ açılan (ya da netting hesapta hacmi artan) eşleşen bir pozisyon
113
+ varsa emir AKTİFLEŞTİ (tetiklendi); eşleşme yoksa İPTAL/süresi doldu.
114
+ Saf karar katmanı: IO ve ajan uyarısı main süreçte yapılır. */
115
+
116
+ const ORDER_TYPE_LABELS = {
117
+ 0: 'BUY LIMIT',
118
+ 1: 'SELL LIMIT',
119
+ 2: 'BUY STOP',
120
+ 3: 'SELL STOP',
121
+ 4: 'BUY STOP LIMIT',
122
+ 5: 'SELL STOP LIMIT',
123
+ };
124
+
125
+ /* MT5 emir tipleri: çift = alış, tek = satış */
126
+ function orderSide(o) {
127
+ return Number(o && o.type) % 2 === 1 ? 'sell' : 'buy';
128
+ }
129
+
130
+ function orderTypeLabel(o) {
131
+ const n = Number(o && o.type);
132
+ return ORDER_TYPE_LABELS[n] || 'TIP ' + (isFinite(n) ? n : '?');
133
+ }
134
+
135
+ function orderVolume(o) {
136
+ return num(o && (o.volume_current != null ? o.volume_current : o.volume_initial != null ? o.volume_initial : o.volume), 0);
137
+ }
138
+
139
+ /* goneOrders: önceki turda var olup şimdi listede OLMAYAN emirler
140
+ freshPositions: bu tur yeni açılan / hacmi artan pozisyonlar
141
+ Dönüş: { activated:[{order,position}], canceled:[order] } */
142
+ function matchPendingDelta(goneOrders, freshPositions) {
143
+ const out = { activated: [], canceled: [] };
144
+ const free = (Array.isArray(freshPositions) ? freshPositions : []).filter(Boolean);
145
+ for (const o of Array.isArray(goneOrders) ? goneOrders : []) {
146
+ if (!o) continue;
147
+ const side = orderSide(o);
148
+ const symbol = String(o.symbol || '').toUpperCase();
149
+ const ovol = orderVolume(o);
150
+ const oprice = num(o.price_open, 0);
151
+ const omagic = Number(o.magic) || 0;
152
+ let best = -1;
153
+ let bestScore = Infinity;
154
+ for (let i = 0; i < free.length; i++) {
155
+ const p = free[i];
156
+ if (!p) continue;
157
+ if (String(p.symbol || '').toUpperCase() !== symbol) continue;
158
+ if ((num(p.type, -1) === 0 ? 'buy' : 'sell') !== side) continue;
159
+ /* fiyat yakınlığı + hacim yakınlığı; aynı magic KESİN öncelik */
160
+ let score = Math.abs(num(p.price_open, 0) - oprice);
161
+ const pvol = num(p.volume, 0);
162
+ if (ovol > 0 && pvol > 0) score += Math.abs(pvol - ovol) * 0.01;
163
+ if (omagic !== 0 && (Number(p.magic) || 0) === omagic) score -= 1e9;
164
+ if (score < bestScore) {
165
+ bestScore = score;
166
+ best = i;
167
+ }
168
+ }
169
+ if (best >= 0) {
170
+ out.activated.push({ order: o, position: free[best] });
171
+ free.splice(best, 1);
172
+ } else {
173
+ out.canceled.push(o);
174
+ }
175
+ }
176
+ return out;
177
+ }
178
+
179
+ module.exports = { plan, roundTo, stepRound, orderSide, orderTypeLabel, orderVolume, matchPendingDelta };
package/src/main.js CHANGED
@@ -9234,11 +9234,22 @@ const financeState = {
9234
9234
  agents: new Map(), /* sid -> { symbols, main, round, timer } — AYNI ANDA koşan finance ajanları */
9235
9235
  watch: new Map(), /* ticket -> risk otomasyonu durumu (R, BE, kısmi TP...) */
9236
9236
  excursions: new Map(), /* ticket -> { symbol, side, mfe, mae, at } — MAE/MFE takibi */
9237
+ pendingOrders: new Map(), /* ticket -> bekleyen emir (son başarılı okuma) */
9238
+ pendingReady: false, /* emir tabanı kuruldu mu (ilk tur sessiz; köprü kopunca sıfırlanır) */
9239
+ posVolume: new Map(), /* ticket -> hacim (netting hesapta artış = yeni dolum) */
9240
+ pendingWarnings: [], /* tur saniyesi beklenmeden gönderilemeyen uyarılar sıradaki tura */
9237
9241
  excDirty: false,
9238
9242
  excSavedAt: 0,
9239
9243
  watchTimer: null,
9240
9244
  watchBusy: false,
9241
9245
  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 */
9242
9253
  alerts: [], /* fiyat alarmları (kalıcı: finance/alerts.json) */
9243
9254
  stats: null, /* son performans özeti (kalıcı: finance/stats.json) */
9244
9255
  statsAt: 0,
@@ -10020,19 +10031,107 @@ async function finCheckAlerts() {
10020
10031
  if (changed) finAlertsSave();
10021
10032
  }
10022
10033
 
10034
+ /* ---- 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. */
10040
+ function finNotifyPendingActivated(list) {
10041
+ const items = (Array.isArray(list) ? list : []).filter((x) => x && x.order && x.position);
10042
+ if (!items.length) return;
10043
+ const f = finCfg();
10044
+ const heads = [];
10045
+ const detail = [];
10046
+ for (const { order: o, position: p } of items) {
10047
+ const symbol = String(o.symbol || p.symbol || '?');
10048
+ const side = finwatch.orderSide(o);
10049
+ const line =
10050
+ `⚡ BEKLEYEN EMİR AKTİFLEŞTİ: ${symbol} ${finwatch.orderTypeLabel(o)} ${finwatch.orderVolume(o)}` +
10051
+ ` → pozisyon #${String(p.ticket)} @ ${Number(p.price_open) || '?'}`;
10052
+ heads.push(line);
10053
+ detail.push(
10054
+ `- ${symbol} · ${side === 'buy' ? 'ALIŞ' : 'SATIŞ'} · emir #${String(o.ticket || '?')}` +
10055
+ ` (${finwatch.orderTypeLabel(o)} @ ${Number(o.price_open) || '?'}) → pozisyon #${String(p.ticket)}` +
10056
+ ` · ${Number(p.volume) || '?'} lot @ ${Number(p.price_open) || '?'}` +
10057
+ ` · SL ${Number(p.sl) || 0} / TP ${Number(p.tp) || 0}`
10058
+ );
10059
+ financeLog('[bekleyen] ' + line);
10060
+ finJournal({
10061
+ kind: 'pending-active',
10062
+ order: Number(o.ticket) || String(o.ticket || ''),
10063
+ ticket: Number(p.ticket) || 0,
10064
+ symbol,
10065
+ side,
10066
+ volume: Number(p.volume) || finwatch.orderVolume(o),
10067
+ price: Number(p.price_open) || Number(o.price_open) || 0,
10068
+ sl: Number(p.sl) || 0,
10069
+ tp: Number(p.tp) || 0,
10070
+ });
10071
+ finPush('trade', { line });
10072
+ if (f.notifyTrades !== false) {
10073
+ try { financeNotify(line, 'trade', false); } catch {}
10074
+ }
10075
+ }
10076
+ const body = [
10077
+ '[ACİL UYARI — BEKLEYEN EMİR AKTİFLEŞTİ, TUR SANİYESİ BEKLENMEDEN İLETİLDİ]',
10078
+ ...detail,
10079
+ 'Ş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
+ '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
+ ].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');
10101
+ }
10102
+
10103
+ /* Aktifleşmeden listeden düşen emir (iptal/süre doldu): günlük + bildirim */
10104
+ function finNotifyPendingGone(o) {
10105
+ if (!o) return;
10106
+ const line =
10107
+ `BEKLEYEN EMİR KALKTI: ${String(o.symbol || '?')} ${finwatch.orderTypeLabel(o)} ${finwatch.orderVolume(o)}` +
10108
+ ` (emir #${String(o.ticket || '?')}) — pozisyona dönüşmedi (iptal/süre doldu).`;
10109
+ financeLog('[bekleyen] ' + line);
10110
+ finJournal({ kind: 'cancel', order: Number(o.ticket) || String(o.ticket || ''), symbol: String(o.symbol || ''), note: 'aktifleşmeden kalktı (iptal/süre doldu)' });
10111
+ if (finCfg().notifyTrades !== false) {
10112
+ finPush('trade', { line });
10113
+ try { financeNotify(line, 'cancel', false); } catch {}
10114
+ }
10115
+ }
10116
+
10023
10117
  async function finWatchTick() {
10024
10118
  if (!mt5bridge.running || financeState.watchBusy) return;
10025
10119
  financeState.watchBusy = true;
10026
10120
  try {
10027
10121
  const cfg = finCfg();
10028
10122
  financeState.watchTickAt = Date.now();
10029
- const [posR, accR] = await Promise.all([
10123
+ const [posR, accR, ordR] = await Promise.all([
10030
10124
  mt5bridge.call('positions', {}, 8000).catch(() => null),
10031
10125
  mt5bridge.call('account', {}, 8000).catch(() => null),
10126
+ mt5bridge.call('orders', {}, 8000).catch(() => null),
10032
10127
  ]);
10033
10128
  const account = accR && accR.ok ? (accR.data && accR.data.account) : null;
10034
10129
  const positionsOk = !!(posR && posR.ok);
10035
10130
  const positions = positionsOk ? ((posR.data && posR.data.positions) || []) : [];
10131
+ const ordersOk = !!(ordR && ordR.ok);
10132
+ const orders = ordersOk ? ((ordR.data && ordR.data.orders) || []) : [];
10133
+ /* bekleyen emir takibi için ÖNCEKİ durum: watch (açık ticket'lar) + hacimler */
10134
+ const prevTickets = new Set(financeState.watch.keys());
10036
10135
  if (account) {
10037
10136
  financeState.account = account;
10038
10137
  finEquitySample(account);
@@ -10119,6 +10218,43 @@ async function finWatchTick() {
10119
10218
  }
10120
10219
  if (decision.rearmSL && st.warnSL) st.warnSL = false;
10121
10220
  }
10221
+ /* ---- BEKLEYEN EMİR AKTİFLİĞİ ----
10222
+ 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.) */
10226
+ try {
10227
+ if (!(ordersOk && positionsOk)) {
10228
+ financeState.pendingReady = false;
10229
+ } else {
10230
+ const nowMap = new Map();
10231
+ for (const o of orders) if (o && o.ticket != null) nowMap.set(String(o.ticket), o);
10232
+ if (!financeState.pendingReady) {
10233
+ /* ilk başarılı okuma (açılış/bağlantı sonrası): sessizce taban al */
10234
+ financeState.pendingOrders = nowMap;
10235
+ financeState.pendingReady = true;
10236
+ } else {
10237
+ const gone = [];
10238
+ for (const [t, o] of financeState.pendingOrders) if (!nowMap.has(t)) gone.push(o);
10239
+ financeState.pendingOrders = nowMap;
10240
+ if (gone.length) {
10241
+ /* bu tur YENİ açılan ya da hacmi artan pozisyonlar (netting hesap) */
10242
+ const fresh = [];
10243
+ for (const p of positions) {
10244
+ if (!p) continue;
10245
+ const t = String(p.ticket);
10246
+ const vol = Number(p.volume) || 0;
10247
+ const before = financeState.posVolume.get(t);
10248
+ if (!prevTickets.has(t) || (before != null && vol > before + 1e-9)) fresh.push(p);
10249
+ }
10250
+ const delta = finwatch.matchPendingDelta(gone, fresh);
10251
+ if (delta.activated.length) finNotifyPendingActivated(delta.activated);
10252
+ for (const o of delta.canceled) finNotifyPendingGone(o);
10253
+ }
10254
+ }
10255
+ financeState.posVolume = new Map(positions.map((p) => [String(p.ticket), Number(p.volume) || 0]));
10256
+ }
10257
+ } catch {}
10122
10258
  /* kapanan pozisyonlar: net K/Z ile günlük + bildirim (yalnız pozisyon
10123
10259
  listesi GERÇEKTEN alındıysa — köprü kesintisinde yanlış kapanış yok) */
10124
10260
  if (positionsOk) {
@@ -10571,6 +10707,13 @@ async function finEaStatusCheck() {
10571
10707
  }
10572
10708
 
10573
10709
  mt5bridge.on('bridge', (m) => {
10710
+ if (!(m && m.connected)) {
10711
+ /* köprü koptu: bekleyen emir tabanı sıfırlanır — kopukluk sırasında
10712
+ tetiklenen/iptal edilen emirler yanlış "aktifleşti" uyarısı üretmez */
10713
+ financeState.pendingReady = false;
10714
+ financeState.pendingOrders = new Map();
10715
+ financeState.posVolume = new Map();
10716
+ }
10574
10717
  if (m && m.connected) {
10575
10718
  financeLog('[MT5] terminal bağlı: ' + ((m.terminal && (m.terminal.name + ' @ ' + m.terminal.company)) || 'MT5'));
10576
10719
  if (m.account) financeLog(`[MT5] hesap ${m.account.login} · bakiye ${m.account.balance} ${m.account.currency}`);
@@ -10735,6 +10878,20 @@ function finAgentStop(sid, reason) {
10735
10878
  if (!agent) return false;
10736
10879
  clearTimeout(agent.timer);
10737
10880
  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 {}
10738
10895
  if (sid === String(financeState.traderSid || '')) financeState.traderSid = null;
10739
10896
  try {
10740
10897
  if (engine && engine.isBusy(sid)) {
@@ -10887,6 +11044,116 @@ async function finConsultPlan(f, agent, sid) {
10887
11044
  }
10888
11045
  }
10889
11046
 
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
+
10890
11157
  /* Bir ajanın TEK turu: (opsiyonel) ana ajan planı + tur emri */
10891
11158
  function finAgentRound(sid) {
10892
11159
  const agent = financeState.agents.get(String(sid));
@@ -10916,9 +11183,26 @@ function finAgentRound(sid) {
10916
11183
  : 'İşlem açabilirsin — limitlere uy, SL\u2019siz pozisyon bırakma.';
10917
11184
  const focus = agent.symbols.length ? `Odak: ${agent.symbols.join(', ')}. ` : '';
10918
11185
  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 {}
10919
11203
  const launch = (planBlock) => {
10920
11204
  if (!financeState.agents.has(String(sid))) return;
10921
- const ok = engine.send(sid, planBlock + round, { userAction: false });
11205
+ const ok = engine.send(sid, warnPrefix + planBlock + round, { userAction: false });
10922
11206
  if (ok) {
10923
11207
  financeState.lastRoundAt = Date.now();
10924
11208
  if (agent.main) finPush('trader', { state: 'running', round: agent.round });
@@ -10970,12 +11254,30 @@ function finFlushOnDone(ev) {
10970
11254
  return;
10971
11255
  }
10972
11256
  clearTimeout(agent.timer);
11257
+ agent.timer = null;
10973
11258
  const f = finCfg();
10974
11259
  const iv = Math.max(30, Number(f.intervalSec) || 120) * 1000;
10975
11260
  financeState.lastRoundAt = Date.now();
10976
11261
  if (agent.main) {
10977
11262
  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;
10978
11279
  }
11280
+ /* SEMBOL İŞÇİSİ: döngü dışı — kendi aralığıyla koşar */
10979
11281
  agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, iv);
10980
11282
  }
10981
11283
 
@@ -11036,6 +11338,7 @@ ipcMain.handle('finance:snapshot', async () => {
11036
11338
  });
11037
11339
 
11038
11340
  ipcMain.handle('finance:mode', async (_e, payload) => {
11341
+ const wasMode = financeState.mode;
11039
11342
  financeState.mode = !!(payload && payload.on);
11040
11343
  /* BEAST FINANCE = MT5 KAPISI:
11041
11344
  - mod AÇILINCA köprü + MT5 terminali OTOMATİK başlar (watchdog dahil) —
@@ -11089,8 +11392,23 @@ ipcMain.handle('finance:mode', async (_e, payload) => {
11089
11392
  }
11090
11393
  } catch {}
11091
11394
  }
11092
- /* OTOMATİK TRADER KAPALI: finance moduna geçmek trader'ı KENDİLİĞİNDEN
11093
- başlatmaz — kullanıcı TRADE AJANI kartındaki ▶ ile elle başlatır */
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
+ }
11094
11412
  return { ok: true, mode: financeState.mode, needNew, resumeSid };
11095
11413
  });
11096
11414
 
@@ -11125,14 +11443,23 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
11125
11443
  }
11126
11444
  if (p.intervalSec !== undefined) f.intervalSec = Math.max(30, Math.min(3600, Math.round(Number(p.intervalSec) || 120)));
11127
11445
  /* TUR ARALIĞI CANLI: bekleyen zamanlayıcılar yeni aralıkla yeniden kurulur —
11128
- koşan tur bitince zaten finFlushOnDone yeni aralığı okur. */
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. */
11129
11449
  if (p.intervalSec !== undefined && financeState.agents) {
11130
11450
  const iv = Math.max(30, Math.round(Number(f.intervalSec) || 120)) * 1000;
11451
+ const hasTeam = finRoleSids().length > 0;
11131
11452
  for (const [sid, a] of financeState.agents) {
11132
11453
  if (!a || !engine || engine.isBusy(sid)) continue;
11454
+ if (a.main && hasTeam) continue;
11455
+ if (a.role && finMainAgentRef()) continue;
11133
11456
  clearTimeout(a.timer);
11134
11457
  a.timer = setTimeout(() => { try { finAgentRound(String(sid)); } catch {} }, iv);
11135
11458
  }
11459
+ if (financeState.cycleTimer) {
11460
+ clearTimeout(financeState.cycleTimer);
11461
+ financeState.cycleTimer = setTimeout(() => { try { finTeamKickCycle(); } catch {} }, iv);
11462
+ }
11136
11463
  }
11137
11464
  if (p.maxLot !== undefined) f.maxLot = Math.max(0.01, Math.min(100, Number(p.maxLot) || 0.1));
11138
11465
  if (p.maxPositions !== undefined) f.maxPositions = Math.max(1, Math.min(20, Math.round(Number(p.maxPositions) || 3)));
@@ -11267,6 +11594,19 @@ async function financeTraderStart() {
11267
11594
  finPush('trader', { state: 'running', round: mainAgent.round });
11268
11595
  /* ANALİZ EKİBİ: seçili roller için ayrı sürekli ajanlar (koşmıyorsa) */
11269
11596
  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 {}
11270
11610
  return { ok: true, sid: mainSid };
11271
11611
  }
11272
11612