beast-agent 2.60.0 → 2.62.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.60.0",
4
+ "version": "2.62.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -2057,7 +2057,15 @@ class Engine {
2057
2057
  'VERİ AKIŞI (her değerlendirmede): mt5_account + mt5_positions + mt5_market çağrılarını AYNI turda PARALEL ver; gerekiyorsa mt5_history ile son işlemleri gör.\n' +
2058
2058
  'PARALEL + KOORDİNASYON: uzun araştırma/işleri run_background ile paralel finance işçisine devret (parent finance olduğu için işçi mt5 okuma araçlarını görür); koşan ajanlarla konuşmak için agent_dm (to: ajan başlığındaki anahtar kelime, örn "GOLD"; ortak karar için group: "İSİM" ile grup sohbeti kur — mesaj tüm üyelere düşer). Görevin bitince DM/grup sohbetleri otomatik KAPANIR (geçmiş panelde kalır).\n' +
2059
2059
  (symbols ? `İZLEME LİSTESİ: ${symbols}\n` : '') +
2060
- (lim.maxLot ? `LİMİTLER: max lot ${lim.maxLot}` + (lim.maxPositions ? ` · max eşzamanlı pozisyon ${lim.maxPositions}` : '') + ' — bunları aşıp araç kullanma; sistem zaten reddeder.\n' : '') +
2060
+ (lim.minLot || lim.maxLot
2061
+ ? 'LİMİTLER: ' +
2062
+ [
2063
+ lim.minLot ? `min lot ${lim.minLot}` : null,
2064
+ lim.maxLot ? `max lot ${lim.maxLot}` : null,
2065
+ lim.maxPositions ? `max eşzamanlı pozisyon ${lim.maxPositions}` : null,
2066
+ ].filter(Boolean).join(' · ') +
2067
+ ' — bunları aşıp araç kullanma; sistem zaten reddeder (lot min lot altına inemez, max lot üstüne çıkamaz).\n'
2068
+ : '') +
2061
2069
  'RİSK DİSİPLİNİ:\n' +
2062
2070
  '- SL\u2019siz pozisyon BIRAKMA: açtığın her işlemde mantıklı bir SL koy; SL\u2019siz kalan pozisyonu mt5_modify ile tamamla.\n' +
2063
2071
  '- Martingale/kademeli lot artışı YASAK; kaybı geri kovalama (revenge trade) YASAK.\n' +
@@ -31,7 +31,7 @@ const NAMES = [
31
31
  'mt5_cancel',
32
32
  ];
33
33
 
34
- let getCfg = () => ({ allowTrading: false, maxLot: 0.1, maxPositions: 3, symbols: [] });
34
+ let getCfg = () => ({ allowTrading: false, minLot: 0.01, maxLot: 0.1, maxPositions: 3, symbols: [] });
35
35
  let notify = () => {};
36
36
  /* DİSİPLİN KANCASI (main enjekte eder): kodla zorlanan kurallar — günlük işlem
37
37
  limiti, kayıp serisi molası, re-entry beklemesi, kur maruziyeti.
@@ -273,7 +273,7 @@ const definitions = NAMES.map((name) => {
273
273
  enum: ['market', 'buy_market', 'sell_market', 'buy_limit', 'sell_limit', 'buy_stop', 'sell_stop'],
274
274
  description: 'Emir tipi. Boş/side verilirse piyasa (anlık); limit/stop verilirse bekleyen emir (price zorunlu olur).',
275
275
  },
276
- volume: { type: 'number', description: 'Lot (max lot sınırına tabi) — boşsa riskPct + sl ile OTOMATİK hesaplanır' },
276
+ volume: { type: 'number', description: 'Lot (min/max lot sınırına tabi) — boşsa riskPct + sl ile OTOMATİK hesaplanır' },
277
277
  riskPct: { type: 'number', description: 'İşlem riski % (ör. 0.5-2) — volume yerine ver: SL mesafesinden lot hesaplanır (sl zorunlu)' },
278
278
  price: { type: 'number', description: 'Limit/stop emirlerinde tetik fiyatı (piyasa emrinde gerekmez)' },
279
279
  sl: { type: 'number', description: 'Stop loss fiyatı (0 = yok) — şiddetle önerilir' },
@@ -565,7 +565,7 @@ const handlers = {
565
565
  const balance = Number(acct && acct.account && acct.account.balance) || 0;
566
566
  if (!(balance > 0)) return { ok: false, error: 'bakiye okunamadı' };
567
567
  const riskAmount = (balance * riskPct) / 100;
568
- const res = finrisk.calcRiskLot(info, entry, sl, riskAmount, cfg.maxLot);
568
+ const res = finrisk.calcRiskLot(info, entry, sl, riskAmount, cfg.maxLot, cfg.minLot);
569
569
  if (res.error) return { ok: false, error: res.error, raw: res.raw, lossPerLot: res.lossPerLot, riskAmount: Math.round(riskAmount * 100) / 100 };
570
570
  const digits = Number(info.digits) || 5;
571
571
  const dist = Math.abs(entry - sl);
@@ -584,7 +584,9 @@ const handlers = {
584
584
  atr: atr != null ? Math.round(atr * 1e6) / 1e6 : null,
585
585
  timeframe: timeframe || null,
586
586
  capped: !!res.capped,
587
+ raised: !!res.raised,
587
588
  warning: closeErr || undefined,
589
+ minLot: Number(cfg.minLot) || 0.01,
588
590
  maxLot: Number(cfg.maxLot) || 0.1,
589
591
  };
590
592
  },
@@ -623,12 +625,12 @@ const handlers = {
623
625
  const acct = await bcall('account', {}, 8000);
624
626
  const balance = Number(acct && acct.account && acct.account.balance) || 0;
625
627
  const riskAmount = (balance * riskPct) / 100;
626
- const rr = finrisk.calcRiskLot(info, price, sl, riskAmount, maxLot);
628
+ const rr = finrisk.calcRiskLot(info, price, sl, riskAmount, maxLot, cfg.minLot);
627
629
  if (rr.error) return { ok: false, error: rr.error };
628
630
  vol = rr.volume;
629
- riskInfo = { riskPct, riskAmount: Math.round(riskAmount * 100) / 100, lossPerLot: rr.lossPerLot };
631
+ riskInfo = { riskPct, riskAmount: Math.round(riskAmount * 100) / 100, lossPerLot: rr.lossPerLot, raised: !!rr.raised };
630
632
  } else {
631
- const norm = finrisk.normalizeVolume(info, wantVolume, maxLot);
633
+ const norm = finrisk.normalizeVolume(info, wantVolume, maxLot, cfg.minLot);
632
634
  if (norm.error) return { ok: false, error: norm.error };
633
635
  vol = norm.volume;
634
636
  }
@@ -745,7 +747,7 @@ const handlers = {
745
747
  if (!symbol) return { ok: false, error: 'symbol gerekli' };
746
748
  const info = await symInfoRow(symbol);
747
749
  if (!info) return { ok: false, error: 'sembol bulunamadı: ' + symbol + ' (MT5 Market Watch?)' };
748
- const norm = finrisk.normalizeVolume(info, args.volume, Number(cfg.maxLot) || 0.1);
750
+ const norm = finrisk.normalizeVolume(info, args.volume, Number(cfg.maxLot) || 0.1, cfg.minLot);
749
751
  if (norm.error) return { ok: false, error: norm.error };
750
752
  const vol = norm.volume;
751
753
  const ptype = t.type;
@@ -16,24 +16,46 @@ function stepFloor(v, step) {
16
16
  return Math.floor(v / s + 1e-9) * s;
17
17
  }
18
18
 
19
- /* info: MT5 symbol_info satırı. wanted: istenen lot. hardMax: kullanıcı limiti. */
20
- function normalizeVolume(info, wanted, hardMax) {
19
+ function stepCeil(v, step) {
20
+ const s = num(step, 0.01);
21
+ if (!(s > 0)) return v;
22
+ return Math.ceil(v / s - 1e-9) * s;
23
+ }
24
+
25
+ /* info: MT5 symbol_info satırı. wanted: istenen lot. hardMax: kullanıcı üst
26
+ limiti (max lot). hardMin: kullanıcı alt limiti (min lot) — istenen lot bu
27
+ tabanın altındaysa adıma yukarı yuvarlanır (raised). */
28
+ function normalizeVolume(info, wanted, hardMax, hardMin) {
21
29
  const vmin = num(info && info.volume_min, 0.01);
22
30
  const vmax = num(info && info.volume_max, 100);
23
31
  const step = num(info && info.volume_step, 0.01);
24
32
  let v = num(wanted, 0);
25
33
  if (!(v > 0)) return { error: 'volume gerekli (pozitif sayı)' };
26
34
  const cap = Math.min(vmax > 0 ? vmax : Infinity, num(hardMax, 0) > 0 ? num(hardMax, 0) : Infinity);
35
+ const floor = num(hardMin, 0) > 0 ? num(hardMin, 0) : 0;
27
36
  let capped = false;
37
+ let raised = false;
28
38
  if (v > cap) {
29
39
  v = stepFloor(cap, step);
30
40
  capped = true;
31
41
  }
42
+ if (v < floor - 1e-9) {
43
+ v = stepCeil(floor, step);
44
+ raised = true;
45
+ }
32
46
  v = Math.round(stepFloor(v, step) * 1e8) / 1e8;
33
47
  if (!(v > 0) || v < vmin - 1e-9) {
34
48
  return { error: `hacim broker minimumunun altında (min ${vmin} lot)` };
35
49
  }
36
- return { volume: v, capped };
50
+ if (v > cap + 1e-9) {
51
+ /* min lot tabanı broker adımıyla üst limite sığmadı — üst limitte kal */
52
+ v = Math.round(stepFloor(cap, step) * 1e8) / 1e8;
53
+ capped = true;
54
+ if (!(v > 0) || v < vmin - 1e-9) {
55
+ return { error: `hacim broker minimumunun altında (min ${vmin} lot)` };
56
+ }
57
+ }
58
+ return { volume: v, capped, raised };
37
59
  }
38
60
 
39
61
  /* 1 lot için SL mesafesi kaybı (hesap para birimi): tick değeri, yoksa kontrat */
@@ -49,11 +71,11 @@ function lossPerLot(info, entry, sl) {
49
71
  }
50
72
 
51
73
  /* riskAmount: hesap para birimi cinsinden kaybedilecek tutar */
52
- function calcRiskLot(info, entry, sl, riskAmount, hardMax) {
74
+ function calcRiskLot(info, entry, sl, riskAmount, hardMax, hardMin) {
53
75
  const lpl = lossPerLot(info, entry, sl);
54
76
  if (!(lpl > 0)) return { error: 'risk hesaplanamadı (tick değeri/kontrat bilgisi yok)' };
55
77
  const raw = num(riskAmount, 0) / lpl;
56
- const norm = normalizeVolume(info, raw, hardMax);
78
+ const norm = normalizeVolume(info, raw, hardMax, hardMin);
57
79
  if (norm.error) return { error: norm.error, raw, lossPerLot: lpl };
58
80
  return {
59
81
  volume: norm.volume,
@@ -61,6 +83,7 @@ function calcRiskLot(info, entry, sl, riskAmount, hardMax) {
61
83
  lossPerLot: Math.round(lpl * 100) / 100,
62
84
  riskAmount: Math.round(raw * lpl * 100) / 100,
63
85
  capped: !!norm.capped,
86
+ raised: !!norm.raised,
64
87
  };
65
88
  }
66
89
 
package/src/main.js CHANGED
@@ -9387,7 +9387,10 @@ function finCfg() {
9387
9387
  f.roleSkills.trader = cur.map((s) => String(s || '').trim()).filter(Boolean).slice(0, 1);
9388
9388
  }
9389
9389
  if (!Number(f.intervalSec)) f.intervalSec = 120;
9390
+ if (!Number(f.minLot)) f.minLot = 0.01;
9390
9391
  if (!Number(f.maxLot)) f.maxLot = 0.1;
9392
+ /* LOT ARALIĞI KILİDİ: min lot hiçbir zaman max lotu aşamaz */
9393
+ if (f.minLot > f.maxLot) f.minLot = f.maxLot;
9391
9394
  if (f.maxPositions == null) f.maxPositions = 3;
9392
9395
  /* KODLA DİSİPLİN (trader kuralları): hepsi 0 = kural kapalı */
9393
9396
  if (!Number.isFinite(Number(f.maxTradesPerDay))) f.maxTradesPerDay = 10;
@@ -11403,7 +11406,7 @@ function finApplyTraderFields(s, symbolsOverride, role, isMain) {
11403
11406
  s.financeSymbols = Array.isArray(symbolsOverride) && symbolsOverride.length ? symbolsOverride : f.symbols;
11404
11407
  s.financeStrategy = String(f.strategy || '');
11405
11408
  s.financeShadow = !!f.shadowMode; /* shadow modda işlem araçları emir göndermez */
11406
- s.financeLimits = { maxLot: f.maxLot, maxPositions: f.maxPositions };
11409
+ s.financeLimits = { minLot: f.minLot, maxLot: f.maxLot, maxPositions: f.maxPositions };
11407
11410
  /* ZORUNLU TEK skill: rol ajanı → roleSkills[rol], ana trader → roleSkills.trader */
11408
11411
  const skillKey = r || (s.financePlaybook ? 'trader' : '');
11409
11412
  s.financeRoleSkills = (skillKey && f.roleSkills && f.roleSkills[skillKey]) || [];
@@ -11446,6 +11449,9 @@ function finAgentStop(sid, reason) {
11446
11449
  const agent = financeState.agents.get(sid);
11447
11450
  if (!agent) return false;
11448
11451
  clearTimeout(agent.timer);
11452
+ if (agent.waitPoll) { clearTimeout(agent.waitPoll); agent.waitPoll = null; }
11453
+ agent.waitingTeam = false;
11454
+ agent.consultPromise = null;
11449
11455
  financeState.agents.delete(sid);
11450
11456
  if (sid === String(financeState.traderSid || '')) financeState.traderSid = null;
11451
11457
  try {
@@ -11465,6 +11471,22 @@ function finAgentStop(sid, reason) {
11465
11471
  /* AJAN DM: duran ajanın birebir sohbetleri kapanır; grupta KOŞAN üye
11466
11472
  kalmadıysa ekip sohbeti de GEÇMİŞE düşer (aktif listede kalmaz) */
11467
11473
  try { if (engine && engine._agentDmClose) engine._agentDmClose(sid); } catch {}
11474
+ /* EKİP AJANI DURDU: bekleyen trader varsa hemen değerlendir (deadlock yok) */
11475
+ if (agent.role) {
11476
+ for (const [msid, ma] of financeState.agents) {
11477
+ if (ma && ma.main && ma.waitingTeam) { try { finTeamWaitTick(msid); } catch {} break; }
11478
+ }
11479
+ }
11480
+ /* ANA TRADER DURDU: ekip cycle'sız kaldı — kendi timer'ıyla dönmeye devam etsin */
11481
+ if (agent.main) {
11482
+ const iv = Math.max(30, Number(finCfg().intervalSec) || 120) * 1000;
11483
+ for (const [tsid, ta] of financeState.agents) {
11484
+ if (!ta || !ta.role) continue;
11485
+ if (engine && engine.isBusy(tsid)) continue;
11486
+ clearTimeout(ta.timer);
11487
+ ta.timer = setTimeout(() => { try { finAgentRound(String(tsid)); } catch {} }, iv);
11488
+ }
11489
+ }
11468
11490
  finPush('trader', { state: 'idle', rounds: financeState.traderRounds });
11469
11491
  return true;
11470
11492
  }
@@ -11480,7 +11502,7 @@ function finTraderBrief(agent) {
11480
11502
  return [
11481
11503
  `Beast Finance ${who} başlatıldı — ilk tur: strateji çerçeveni kur ve piyasa taramasını yap.`,
11482
11504
  `Odak semboller: ${syms || '(boş — mt5_status ile terminale bak, mantıklı semboller seç)'}`,
11483
- `Tur aralığı: ${f.intervalSec} sn · Max lot: ${f.maxLot} · Max eşzamanlı pozisyon: ${f.maxPositions}`,
11505
+ `Tur aralığı: ${f.intervalSec} sn · Lot aralığı: ${f.minLot}–${f.maxLot} (min lot tabanı zorlanır, max lot tavanı aşılamaz) · Max eşzamanlı pozisyon: ${f.maxPositions}`,
11484
11506
  roleDef
11485
11507
  ? `UZMANLIK: ${roleDef.desc} — raporlarını bu çerçevede yaz; İŞLEM AÇMA, yalnız analiz + net öneri üret.`
11486
11508
  : '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). KISMİ KAPATMA yetkisi: kısmi TP (kâr al) ve kısmi stop (zarar kes) — mt5_close {percent, kind:"partial_tp"|"partial_sl"}; otomatik DEĞİL, gerek görürsen sen kullan. 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ı.',
@@ -11577,7 +11599,7 @@ async function finConsultPlan(f, agent, sid) {
11577
11599
  'Güncel piyasa bağlamı gerekiyorsa web_search kullan; kurulu bir skill konuyla ilgiliyse skill aracıyla oku.';
11578
11600
  const ctx = [
11579
11601
  `Odak semboller: ${agent && agent.symbols && agent.symbols.length ? agent.symbols.join(', ') : (f.symbols || []).join(', ') || '(boş — ajan kendi bulabilir)'}`,
11580
- `Otomatik işlem: ${auto} · max lot ${f.maxLot} · max eşzamanlı pozisyon ${f.maxPositions}`,
11602
+ `Otomatik işlem: ${auto} · lot aralığı ${f.minLot}–${f.maxLot} · max eşzamanlı pozisyon ${f.maxPositions}`,
11581
11603
  account
11582
11604
  ? `Hesap: bakiye ${account.balance} ${account.currency} · özkaynak ${account.equity ?? '?'} · serbest marj ${account.margin_free ?? '?'} · kaldıraç 1:${account.leverage ?? '?'}`
11583
11605
  : 'Hesap verisi alınamadı.',
@@ -11604,8 +11626,57 @@ async function finConsultPlan(f, agent, sid) {
11604
11626
  }
11605
11627
  }
11606
11628
 
11629
+ /* ---------- CYCLE ORKESTRASYONU: trader turu ekip raporlarını BEKLER ----------
11630
+ Ana trader kendi turuna başlamadan önce koşan analiz ekibi ajanlarını
11631
+ (teknik/risk/macro/visual) tura sokar ve O ANKİ turları bitene kadar bekler —
11632
+ böylece finTeamDigest her zaman TAZE rapor taşır. Sembol işçileri beklenmez.
11633
+ Ekip ajanları ana trader koşarken kendi başına tur planlamaz
11634
+ (finFlushOnDone); sıradaki turlarını cycle başlatır. */
11635
+ function finTeamAgents() {
11636
+ const out = [];
11637
+ try {
11638
+ for (const [sid, a] of financeState.agents) {
11639
+ if (a && a.role) out.push({ sid: String(sid), agent: a, def: finRoleDef(a.role) });
11640
+ }
11641
+ } catch {}
11642
+ return out;
11643
+ }
11644
+
11645
+ function finTeamHasMain() {
11646
+ try {
11647
+ for (const [, a] of financeState.agents) if (a && a.main) return true;
11648
+ } catch {}
11649
+ return false;
11650
+ }
11651
+
11652
+ /* Takılma valfi: ekip turu bu süreyi aşarsa trader eski raporla başlar */
11653
+ function finTeamWaitMaxMs() {
11654
+ const iSec = Math.max(30, Number(finCfg().intervalSec) || 120);
11655
+ return Math.max(60, Math.min(300, iSec)) * 1000;
11656
+ }
11657
+
11658
+ /* Ekip ajanı bittiğinde/durduğunda ya da 2 sn'lik yoklamada çağrılır:
11659
+ bekleyen trader'ın turunu açar (rapor taze) ya da beklemeye devam eder. */
11660
+ function finTeamWaitTick(sid) {
11661
+ const agent = financeState.agents.get(String(sid));
11662
+ if (!agent || !agent.main || !agent.waitingTeam) return;
11663
+ if (agent.waitPoll) { clearTimeout(agent.waitPoll); agent.waitPoll = null; }
11664
+ const busy = finTeamAgents().filter((t) => engine && engine.isBusy(t.sid));
11665
+ const expired = Date.now() >= Number(agent.waitDeadline || 0);
11666
+ if (busy.length && !expired) {
11667
+ agent.waitPoll = setTimeout(() => { try { finTeamWaitTick(sid); } catch {} }, 2000);
11668
+ return;
11669
+ }
11670
+ agent.waitingTeam = false;
11671
+ /* MAKS BEKLEME AŞILDI: geç kalanlar rapor prefix'ine açıkça notlanır */
11672
+ agent.waitLateNames = expired && busy.length
11673
+ ? busy.map((t) => (t.def ? t.def.label : t.agent.role || t.sid))
11674
+ : [];
11675
+ finAgentRound(sid, { noTeamWait: true });
11676
+ }
11677
+
11607
11678
  /* Bir ajanın TEK turu: (opsiyonel) ana ajan planı + tur emri */
11608
- function finAgentRound(sid) {
11679
+ function finAgentRound(sid, opts) {
11609
11680
  const agent = financeState.agents.get(String(sid));
11610
11681
  if (!agent || !engine) return;
11611
11682
  /* TRADE SAATLERİ KAPISI: aralık dışında YENİ TUR BAŞLATILMAZ — ajan askıya
@@ -11621,9 +11692,36 @@ function finAgentRound(sid) {
11621
11692
  /* hâlâ çalışıyor — done eventinde tekrar planlanır */
11622
11693
  return;
11623
11694
  }
11695
+ const f = finCfg();
11696
+ /* EKİP BEKLEMESİ SÜRÜYOR: DM uyandırması vb. turu erkene çekemez — tick açar */
11697
+ if (agent.main && agent.waitingTeam && !(opts && opts.noTeamWait)) return;
11698
+ /* CYCLE KAPISI: karar turundan ÖNCE ekip ajanlarını tura sok, O ANKİ
11699
+ turları bitmeden trader'ı başlatma (rapor tazeliği garantisi). */
11700
+ if (agent.main && !(opts && opts.noTeamWait)) {
11701
+ const team = finTeamAgents();
11702
+ if (team.length) {
11703
+ for (const t of team) {
11704
+ if (engine.isBusy(t.sid)) continue;
11705
+ clearTimeout(t.agent.timer);
11706
+ t.agent.timer = null;
11707
+ try { finAgentRound(t.sid); } catch {}
11708
+ }
11709
+ const busy = team.filter((t) => engine.isBusy(t.sid));
11710
+ if (busy.length) {
11711
+ agent.waitingTeam = true;
11712
+ agent.waitDeadline = Date.now() + finTeamWaitMaxMs();
11713
+ /* danışma planı bekleme SIRASINDA üretilir — tur gecikmesi gizlenir */
11714
+ if (!agent.consultPromise && f.consultChat !== false) {
11715
+ agent.consultPromise = finConsultPlan(f, agent, String(sid)).catch(() => '');
11716
+ }
11717
+ finPush('trader', { state: 'team', round: (Number(agent.round) || 0) + 1, waiting: busy.length, maxSec: Math.round(finTeamWaitMaxMs() / 1000) });
11718
+ agent.waitPoll = setTimeout(() => { try { finTeamWaitTick(sid); } catch {} }, 2000);
11719
+ return;
11720
+ }
11721
+ }
11722
+ }
11624
11723
  agent.round += 1;
11625
11724
  if (agent.main) financeState.traderRounds = agent.round;
11626
- const f = finCfg();
11627
11725
  const roleDef = finRoleDef(agent.role);
11628
11726
  let shadow = false;
11629
11727
  try {
@@ -11660,14 +11758,23 @@ function finAgentRound(sid) {
11660
11758
  if (agent.main) {
11661
11759
  const team = finTeamDigest();
11662
11760
  if (team) teamPrefix = '[EKİP RAPORLARI — koşan uzman ajanların son raporları; kararında dikkate al]\n' + team + '\n\n';
11761
+ /* MAKS BEKLEME AŞILDI: geç kalan ajanlar açıkça işaretlenir */
11762
+ const late = Array.isArray(agent.waitLateNames) ? agent.waitLateNames.filter(Boolean) : [];
11763
+ if (late.length) {
11764
+ teamPrefix = `[EKİP NOTU] Şu ajanlar hâlâ analizde — raporları BİR ÖNCEKİ turdan: ${late.join(', ')}. Gecikmeyi kararında dikkate al.\n\n` + teamPrefix;
11765
+ agent.waitLateNames = [];
11766
+ }
11663
11767
  }
11664
11768
  if (f.consultChat === false || roleDef) {
11665
11769
  /* danışma kapalı ya da rol ajanı (plan trader'a yöneliktir): tur doğrudan başlar */
11666
11770
  launch(teamPrefix);
11667
11771
  return;
11668
11772
  }
11773
+ /* Danışma planı ekip beklemesi SIRASINDA üretildiyse hazır olanı kullan */
11774
+ const consult = agent.consultPromise || finConsultPlan(f, agent, String(sid));
11775
+ agent.consultPromise = null;
11669
11776
  if (agent.main) finPush('trader', { state: 'consult', round: agent.round });
11670
- finConsultPlan(f, agent, String(sid))
11777
+ consult
11671
11778
  .then((plan) => launch(teamPrefix + (plan ? `[ANA AJAN PLANI — bu turun varsayılan stratejisi; strateji notuyla çelişirse not önceliklidir]\n${plan}\n\n` : '')))
11672
11779
  .catch(() => launch(teamPrefix));
11673
11780
  }
@@ -11706,6 +11813,12 @@ function finFlushOnDone(ev) {
11706
11813
  finAgentStop(sid, String(ev.reason || 'kullanıcı durdurdu'));
11707
11814
  return;
11708
11815
  }
11816
+ /* EKİP TURU BİTTİ: bekleyen trader varsa ANINDA değerlendir (rapor taze) */
11817
+ if (agent.role) {
11818
+ for (const [msid, ma] of financeState.agents) {
11819
+ if (ma && ma.main && ma.waitingTeam) { try { finTeamWaitTick(msid); } catch {} break; }
11820
+ }
11821
+ }
11709
11822
  clearTimeout(agent.timer);
11710
11823
  /* TUR SIRASINDA GELEN OLAY/DM: tur bitti → interval BEKLENMEDEN yeni tur
11711
11824
  (dmInbox'taki olay mesajı bu turda okunur) */
@@ -11715,6 +11828,10 @@ function finFlushOnDone(ev) {
11715
11828
  agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, 800);
11716
11829
  return;
11717
11830
  }
11831
+ /* CYCLE MODU: ana trader koşarken ekip ajanı KENDİ turunu planlamaz —
11832
+ sıradaki turu trader'ın cycle'ı başlatır (rapor tazeliği). Trader yoksa
11833
+ ekip kendi timer'ıyla dönmeye devam eder. */
11834
+ if (agent.role && finTeamHasMain()) return;
11718
11835
  const f = finCfg();
11719
11836
  const iv = Math.max(30, Number(f.intervalSec) || 120) * 1000;
11720
11837
  financeState.lastRoundAt = Date.now();
@@ -11884,13 +12001,18 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
11884
12001
  koşan tur bitince zaten finFlushOnDone yeni aralığı okur. */
11885
12002
  if (p.intervalSec !== undefined && financeState.agents) {
11886
12003
  const iv = Math.max(30, Math.round(Number(f.intervalSec) || 120)) * 1000;
12004
+ const hasMain = finTeamHasMain();
11887
12005
  for (const [sid, a] of financeState.agents) {
11888
12006
  if (!a || !engine || engine.isBusy(sid)) continue;
12007
+ if (a.role && hasMain) continue; /* cycle modu: ekip turunu trader başlatır */
11889
12008
  clearTimeout(a.timer);
11890
12009
  a.timer = setTimeout(() => { try { finAgentRound(String(sid)); } catch {} }, iv);
11891
12010
  }
11892
12011
  }
11893
12012
  if (p.maxLot !== undefined) f.maxLot = Math.max(0.01, Math.min(100, Number(p.maxLot) || 0.1));
12013
+ if (p.minLot !== undefined) f.minLot = Math.max(0.01, Math.min(100, Number(p.minLot) || 0.01));
12014
+ /* min lot max lotu aşamaz — hangisi sonra yazıldıysa diğerine kelepçelenir */
12015
+ if (f.minLot > f.maxLot) f.minLot = f.maxLot;
11894
12016
  if (p.maxPositions !== undefined) f.maxPositions = Math.max(1, Math.min(20, Math.round(Number(p.maxPositions) || 3)));
11895
12017
  /* RİSK OTOMASYONU + BİLDİRİM ayarları */
11896
12018
  if (p.watchdog !== undefined) f.watchdog = !!p.watchdog;
@@ -169,14 +169,18 @@
169
169
  <span>Model</span>
170
170
  <select id="finTraderModel" title="Trader ajanının modeli — farklı API/model seçilebilir"></select>
171
171
  </div>
172
- <div class="fin-grid2" title="Değişiklik anında kaydedilir — ajanı DURDURMADAN sonraki tura uygulanır">
172
+ <div class="fin-grid3" title="Değişiklik anında kaydedilir — ajanı DURDURMADAN sonraki tura uygulanır">
173
173
  <div class="fin-field">
174
174
  <span>Tur (sn)</span>
175
175
  <input id="finInterval" type="number" min="30" max="3600" step="10" />
176
176
  </div>
177
+ <div class="fin-field">
178
+ <span>Min lot</span>
179
+ <input id="finMinLot" type="number" min="0.01" max="100" step="0.01" title="Alt sınır: lot hesapları bu tabanın altına inemez — taban altındaki lotlar adıma yukarı yuvarlanır (broker minimumunun üzerinde bir taban koyar)" />
180
+ </div>
177
181
  <div class="fin-field">
178
182
  <span>Max lot</span>
179
- <input id="finMaxLot" type="number" min="0.01" max="100" step="0.01" />
183
+ <input id="finMaxLot" type="number" min="0.01" max="100" step="0.01" title="Üst sınır: lot hesapları bu tavanın üzerine çıkamaz (min lotun altına inemez)" />
180
184
  </div>
181
185
  </div>
182
186
  <div class="fin-field">
@@ -223,6 +223,7 @@ const els = {
223
223
  finTraderModel: $('#finTraderModel'),
224
224
  finModelRefreshBtn: $('#finModelRefreshBtn'),
225
225
  finInterval: $('#finInterval'),
226
+ finMinLot: $('#finMinLot'),
226
227
  finMaxLot: $('#finMaxLot'),
227
228
  finStrategy: $('#finStrategy'),
228
229
  finMaxTradesDay: $('#finMaxTradesDay'),
@@ -9803,6 +9804,7 @@ function finTraderInputsSet(cfg) {
9803
9804
  : [];
9804
9805
  const ae = document.activeElement;
9805
9806
  if (els.finInterval && ae !== els.finInterval) els.finInterval.value = cfg.intervalSec || 120;
9807
+ if (els.finMinLot && ae !== els.finMinLot) els.finMinLot.value = cfg.minLot || 0.01;
9806
9808
  if (els.finMaxLot && ae !== els.finMaxLot) els.finMaxLot.value = cfg.maxLot || 0.1;
9807
9809
  if (els.finStrategy && ae !== els.finStrategy && !finStrategyDirty) els.finStrategy.value = cfg.strategy || '';
9808
9810
  if (els.finMaxTradesDay && ae !== els.finMaxTradesDay) els.finMaxTradesDay.value = Number(cfg.maxTradesPerDay) || 0;
@@ -10162,6 +10164,7 @@ function finOnEvent(ev) {
10162
10164
  finSetDot(ev.connected ? true : 'busy', { error: ev.error });
10163
10165
  } else if (ev.fn === 'trader') {
10164
10166
  if (ev.state === 'running') finLogLine('[trader] tur #' + (ev.round || 0) + ' başladı');
10167
+ else if (ev.state === 'team') finLogLine('[trader] ekip raporları bekleniyor — ' + (ev.waiting || 0) + ' ajan turunu bitirsin (max ' + (ev.maxSec || '?') + ' sn)');
10165
10168
  else if (ev.state === 'idle') finLogLine('[trader] tur #' + (ev.round || 0) + ' bitti — sıradaki tur ~' + (ev.nextInSec || '?') + ' sn');
10166
10169
  else if (ev.state === 'hours') finLogLine('[trader] trade saatleri dışı — ajan askıda (kontrol ~' + (ev.nextInSec || 60) + ' sn)');
10167
10170
  else if (ev.state === 'stopped') finLogLine('[trader] durduruldu');
@@ -10528,9 +10531,22 @@ if (els.finInterval) els.finInterval.addEventListener('change', () => {
10528
10531
  if (els.finMaxLot) els.finMaxLot.addEventListener('change', () => {
10529
10532
  const v = Math.max(0.01, Math.min(100, Number(els.finMaxLot.value) || 0.1));
10530
10533
  els.finMaxLot.value = v;
10534
+ /* min lot max lotu aşamaz — görünürde de kelepçele (sunucu ayrıca zorlar) */
10535
+ if (els.finMinLot && Number(els.finMinLot.value) > v) els.finMinLot.value = v;
10531
10536
  finSaveCfg({ maxLot: v });
10532
10537
  toast('Max lot: ' + v + ' — sonraki turdan itibaren geçerli');
10533
10538
  });
10539
+ if (els.finMinLot) els.finMinLot.addEventListener('change', () => {
10540
+ const maxV = Math.max(0.01, Math.min(100, Number(els.finMaxLot && els.finMaxLot.value) || 0.1));
10541
+ let v = Math.max(0.01, Math.min(100, Number(els.finMinLot.value) || 0.01));
10542
+ if (v > maxV) {
10543
+ v = maxV;
10544
+ toast('Min lot max lotu aşamaz — ' + v + ' olarak ayarlandı');
10545
+ }
10546
+ els.finMinLot.value = v;
10547
+ finSaveCfg({ minLot: v });
10548
+ toast('Min lot: ' + v + ' — lot hesapları bu tabanın altına inmez');
10549
+ });
10534
10550
  if (els.finSymBtn) els.finSymBtn.addEventListener('click', finSymPickerOpen);
10535
10551
 
10536
10552
  /* RİSK OTOMASYONU kontrolleri: değişiklik anında kaydedilir (ajan turunu beklemez) */
@@ -5447,6 +5447,8 @@ body.finance-mode #composerWrap { margin-right: var(--finW, 400px); }
5447
5447
  .fin-field .fin-sym-select { width: 100%; }
5448
5448
  .fin-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
5449
5449
  .fin-grid2 .fin-field { margin-bottom: 8px; }
5450
+ .fin-grid3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
5451
+ .fin-grid3 .fin-field { margin-bottom: 8px; }
5450
5452
  .fin-hint { font-style: normal; opacity: 0.75; }
5451
5453
  /* RİSK OTOMASYONU + PERFORMANS kartları */
5452
5454
  .fin-check label { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text); cursor: pointer; }