beast-agent 2.63.3 → 2.63.5

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/src/main.js CHANGED
@@ -31,6 +31,7 @@ const fsguard = require('./agent/fsguard');
31
31
  const log = require('./agent/logger');
32
32
  const squeeze = require('./agent/squeeze');
33
33
  const chansessions = require('./agent/channelsessions');
34
+ const typesafeMod = require('./agent/typesafe');
34
35
  const QRCode = require('qrcode'); /* Expo Go QR (bc-expurl) — whatsapp ile aynı paket */
35
36
 
36
37
  /* Renderer'a sır gönderirken kullanılan maske; kaydederken aynen geri gelirse
@@ -317,6 +318,13 @@ ensureBeastCode();
317
318
  startHealthServer(); /* splash/boot aşamasından itibaren /health ayakta */
318
319
  try { setSearchChain(settings.searchChain); } catch {}
319
320
  try { setTinyfishKey(settings.tinyfishKey || null); } catch {}
321
+ /* TypeSafe (System One/Jev) köprüsü: ajan araçları ayarlardaki anahtarı canlı okur */
322
+ try {
323
+ typesafeMod.setConfig(() => ({
324
+ apiKey: settings.typesafe && settings.typesafe.apiKey,
325
+ model: settings.typesafe && settings.typesafe.model,
326
+ }));
327
+ } catch {}
320
328
  let wa = null;
321
329
  let waChats = new Map(); // jid (numara/grup) -> KENDİ oturumu: her sohbet AYRI (gizlilik)
322
330
  let waCronSid = null; // WhatsApp CRON TABAN OTURUMU: otomatik işler; hiçbir kişiye bağlı DEĞİL
@@ -503,6 +511,12 @@ function parseSettingsText(text) {
503
511
  if (!parsed.supermemory || typeof parsed.supermemory !== 'object') {
504
512
  parsed.supermemory = { enabled: true, baseUrl: 'http://localhost:6767', apiKey: '', containerTag: 'beast' };
505
513
  }
514
+ /* TypeSafe (System One/Jev) — anahtar Ayarlar → TypeSafe sekmesinden girilir;
515
+ boşken typesafe_decision aracı anlaşılır hata döner */
516
+ if (!parsed.typesafe || typeof parsed.typesafe !== 'object') {
517
+ parsed.typesafe = { apiKey: '', model: 'jev-latest' };
518
+ }
519
+ if (typeof parsed.typesafe.model !== 'string' || !parsed.typesafe.model.trim()) parsed.typesafe.model = 'jev-latest';
506
520
  return parsed;
507
521
  }
508
522
 
@@ -6754,6 +6768,7 @@ ipcMain.handle('settings:get', () => {
6754
6768
  if (out.email && out.email.pass) out.email.pass = SECRET_MASK;
6755
6769
  if (out.waTts && out.waTts.key) out.waTts.key = SECRET_MASK;
6756
6770
  if (out.supermemory && out.supermemory.apiKey) out.supermemory.apiKey = SECRET_MASK;
6771
+ if (out.typesafe && out.typesafe.apiKey) out.typesafe.apiKey = SECRET_MASK;
6757
6772
  if (out.fallout && Array.isArray(out.fallout.slots)) {
6758
6773
  for (const s of out.fallout.slots) if (s && s.key) s.key = SECRET_MASK;
6759
6774
  }
@@ -7132,6 +7147,41 @@ ipcMain.handle('supermemory:get', () => {
7132
7147
  up: engine ? !!engine._smUp : null,
7133
7148
  };
7134
7149
  });
7150
+
7151
+ /* TypeSafe (System One/Jev): anahtar masked döner; typesafe_decision aracı
7152
+ settings'ten CANLI okur (setConfig kancası) — kaydetmek yeterli. */
7153
+ ipcMain.handle('typesafe:get', () => {
7154
+ const t = settings.typesafe || {};
7155
+ return { apiKey: t.apiKey ? SECRET_MASK : '', model: t.model || 'jev-latest', set: !!t.apiKey };
7156
+ });
7157
+ ipcMain.handle('typesafe:set', (_e, cfg) => {
7158
+ try {
7159
+ const incomingKey = String((cfg && cfg.apiKey) || '').trim();
7160
+ const prevKey = settings.typesafe && settings.typesafe.apiKey ? String(settings.typesafe.apiKey) : '';
7161
+ settings.typesafe = {
7162
+ /* maskeli anahtar geri gelirse mevcut korunur */
7163
+ apiKey: incomingKey === SECRET_MASK ? prevKey : incomingKey,
7164
+ model: String((cfg && cfg.model) || 'jev-latest').trim() || 'jev-latest',
7165
+ };
7166
+ saveSettings();
7167
+ return {
7168
+ ok: true,
7169
+ typesafe: { apiKey: settings.typesafe.apiKey ? SECRET_MASK : '', model: settings.typesafe.model, set: !!settings.typesafe.apiKey },
7170
+ };
7171
+ } catch (e) {
7172
+ return { ok: false, error: String((e && e.message) || e) };
7173
+ }
7174
+ });
7175
+ ipcMain.handle('typesafe:test', async (_e, payload) => {
7176
+ try {
7177
+ const typed = String((payload && payload.apiKey) || '').trim();
7178
+ const key = !typed || typed === SECRET_MASK ? String((settings.typesafe && settings.typesafe.apiKey) || '') : typed;
7179
+ const model = String((payload && payload.model) || '').trim() || String((settings.typesafe && settings.typesafe.model) || '');
7180
+ return await typesafeMod.probe(key, model);
7181
+ } catch (e) {
7182
+ return { ok: false, error: String((e && e.message) || e) };
7183
+ }
7184
+ });
7135
7185
  ipcMain.handle('approval:respond', (_e, { id, ok, always }) => resolveApproval(id, ok, always));
7136
7186
  /* opencode permission reply (BC): UI kartı üçlü cevap verir —
7137
7187
  'once' | 'always' | 'reject' (+ opsiyonel geri bildirim) */
@@ -9402,6 +9452,11 @@ const financeState = {
9402
9452
  reportCheckAt: 0,
9403
9453
  flattening: false,
9404
9454
  breachBusy: false,
9455
+ /* TUR RİTMİ (trader kararı): sert hareket/haber anında geçici hızlanma.
9456
+ pace = { sec, until, base } — süre dolunca otomatik taban aralığa döner;
9457
+ kalıcı değişiklik settings.finance.intervalSec'e yazılır. */
9458
+ pace: { sec: 0, until: 0, base: 0 },
9459
+ paceTimer: null,
9405
9460
  };
9406
9461
 
9407
9462
  function financeDir() {
@@ -9455,9 +9510,9 @@ function finCfg() {
9455
9510
  if (!Number.isFinite(Number(f.analysisCount))) f.analysisCount = 2;
9456
9511
  f.analysisCount = Math.max(0, Math.min(FIN_ROLES_AUTO.length, Math.round(Number(f.analysisCount) || 0)));
9457
9512
  if (!f.analysisAuto) f.analysisTeam = finRolesValid(f.analysisTeam);
9458
- /* ROL → SKILL eşleştirmesi: rol başına TEK skill — modalda zorunlu tek
9459
- seçim yapılır. Eski çoklu seçimlerden yalnız İLKİ korunur (göç). Skill
9460
- adları kurulu katalogdan gelir. */
9513
+ /* ROL → SKILL eşleştirmesi: rol başına EN FAZLA 2 skill — modalda çoklu
9514
+ seçim yapılır (ör. price-action + typesafe-ai). Eski fazla seçimlerden
9515
+ yalnız İLK İKİSİ korunur (göç). Skill adları kurulu katalogdan gelir. */
9461
9516
  if (f.roleSkillsDefaultsCleared !== true) {
9462
9517
  f.roleSkills = {};
9463
9518
  f.roleSkillsDefaultsCleared = true;
@@ -9465,12 +9520,12 @@ function finCfg() {
9465
9520
  if (!f.roleSkills || typeof f.roleSkills !== 'object') f.roleSkills = {};
9466
9521
  for (const d of FIN_ROLES) {
9467
9522
  const cur = Array.isArray(f.roleSkills[d.id]) ? f.roleSkills[d.id] : [];
9468
- f.roleSkills[d.id] = cur.map((s) => String(s || '').trim()).filter(Boolean).slice(0, 1);
9523
+ f.roleSkills[d.id] = cur.map((s) => String(s || '').trim()).filter(Boolean).slice(0, 2);
9469
9524
  }
9470
- /* TRADER PLAYBOOK: asıl karar vericinin zorunlu skill'i (roleSkills.trader) */
9525
+ /* TRADER PLAYBOOK: asıl karar vericinin zorunlu skill'leri (roleSkills.trader) */
9471
9526
  {
9472
9527
  const cur = Array.isArray(f.roleSkills.trader) ? f.roleSkills.trader : [];
9473
- f.roleSkills.trader = cur.map((s) => String(s || '').trim()).filter(Boolean).slice(0, 1);
9528
+ f.roleSkills.trader = cur.map((s) => String(s || '').trim()).filter(Boolean).slice(0, 2);
9474
9529
  }
9475
9530
  if (!Number(f.intervalSec)) f.intervalSec = 120;
9476
9531
  if (!Number(f.minLot)) f.minLot = 0.01;
@@ -10712,11 +10767,13 @@ async function finRecordClose(ticket, st) {
10712
10767
  finExcSave();
10713
10768
  finExcFlush(true);
10714
10769
  const kind = finwatch.closeKind(closeReason);
10715
- finJournal({ kind: 'close', ticket, symbol, net: rounded, side: (st && st.side) || '', mfe, mae, reason: kind || '' });
10716
- /* SEMBOL BAZLI SÜREKLİ ÖĞRENME: her kapanış istatistiğe işlenir (mt5_ogrenme) */
10717
- finLearnRecordClose({ symbol, side: (st && st.side) || '', net: rounded, mfe, mae, reason: kind || '' });
10770
+ const tf = String((st && st.tf) || '') || '';
10771
+ finJournal({ kind: 'close', ticket, symbol, net: rounded, side: (st && st.side) || '', mfe, mae, reason: kind || '', timeframe: tf || '' });
10772
+ /* SEMBOL + PERİYOT BAZLI SÜREKLİ ÖĞRENME: her kapanış istatistiğe işlenir;
10773
+ zararla kapananlar yapılandırılmış hata kaydı olur (mt5_ogrenme) */
10774
+ finLearnRecordClose({ symbol, side: (st && st.side) || '', net: rounded, mfe, mae, reason: kind || '', timeframe: tf });
10718
10775
  const pl = rounded == null ? '' : ` · K/Z ${rounded >= 0 ? '+' : ''}${rounded.toFixed(2)}`;
10719
- const line = `🏁 Pozisyon kapandı: ${symbol || '?'} #${ticket}${pl}`;
10776
+ const line = `🏁 Pozisyon kapandı: ${symbol || '?'} #${ticket}${tf ? ' · ' + tf : ''}${pl}`;
10720
10777
  financeLog('[watchdog] ' + line);
10721
10778
  if (finCfg().notifyTrades !== false) financeNotify(line, 'close');
10722
10779
  if (rounded != null) finStatsRefresh(true);
@@ -10726,10 +10783,17 @@ async function finRecordClose(ticket, st) {
10726
10783
  const label = kind === 'stop' ? 'STOP OLDU' : kind === 'tp' ? 'TP OLDU' : 'STOP-OUT (teminat)';
10727
10784
  const how = kind === 'tp' ? 'take-profit' : kind === 'stop' ? 'stop-loss' : 'stop-out';
10728
10785
  const netTxt = rounded == null ? '?' : `${rounded >= 0 ? '+' : ''}${rounded.toFixed(2)}`;
10786
+ const loss = rounded != null && rounded < 0;
10787
+ /* YANLIŞTAN DERS: zararla kapanışta ajan hata dersini YAZMAKLA yükümlü —
10788
+ ders bir sonraki turun girdisine (öğrenme digest'i) otomatik girer */
10789
+ const lessonLine = loss
10790
+ ? `\nYANLIŞTAN DERS (ZORUNLU): mt5_ogrenme {action:"add", symbol:"${symbol || ''}", timeframe:"${tf || 'M15'}", kind:"mistake", text:"yanlış neydi + bundan sonra kaçınma kuralı"} ile hatayı yaz (tek kısa cümle: sebep + kaçınma; ör. "M5 destek kırılımında teyitsiz girdim; kapanış teyidi beklemeliydim"). Aynı hatayı tekrarlama — bu ders sonraki turların karar girdisidir.`
10791
+ : `\nKÂR/BAŞARI: işe yarayan desen varsa (periyot + koşul + sonuç) mt5_ogrenme {action:"add", symbol:"${symbol || ''}", timeframe:"${tf || 'M15'}", kind:"pattern", text:"..."} ile tek kısa cümle olarak kaydet — çalışan deseni tekrar kullan.`;
10729
10792
  finWakeAgents(
10730
10793
  `[FİNANS OLAYI — ${label}, TUR SANİYESİ BEKLENMEDEN İLETİLDİ]\n` +
10731
- `- ${symbol || '?'}${st && st.side ? ' ' + String(st.side).toUpperCase() : ''} #${ticket} ${how} ile kapandı · net ${netTxt}\n` +
10732
- `Ş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.`,
10794
+ `- ${symbol || '?'}${st && st.side ? ' ' + String(st.side).toUpperCase() : ''} #${ticket}${tf ? ' · ' + tf : ''} ${how} ile kapandı · net ${netTxt}\n` +
10795
+ `Şimdi yap: pozisyon kapandı — hesabı, risk durumunu (kayıp serisi/günlük limit), periyot istatistiğini ve planını HEMEN değerlendir; gerekiyorsa yeni emir aç ya da BEKLE. Kısa rapor ver.` +
10796
+ lessonLine,
10733
10797
  { kind, ticket, symbol }
10734
10798
  );
10735
10799
  }
@@ -10915,6 +10979,9 @@ async function finWatchTick() {
10915
10979
  symbol: String(p.symbol || ''),
10916
10980
  side: Number(p.type) === 0 ? 'buy' : 'sell',
10917
10981
  entry: Number(p.price_open) || 0,
10982
+ /* ZAMAN DİLİMİ: ajan emir açarken yorum yazar ("Beast M15") — kapanışta
10983
+ öğrenme bu periyoda işlenir */
10984
+ tf: finLearnParseTf(p.comment),
10918
10985
  r: 0,
10919
10986
  be: false,
10920
10987
  partial: false,
@@ -10925,9 +10992,12 @@ async function finWatchTick() {
10925
10992
  financeState.watch.set(ticket, st);
10926
10993
  const isBeast = Number(p.magic) === 20260908 || /beast/i.test(String(p.comment || ''));
10927
10994
  if (isBeast) {
10928
- finJournal({ kind: 'open', ticket, symbol: st.symbol, side: st.side, volume: Number(p.volume) || 0, entry: st.entry, sl: Number(p.sl) || 0, tp: Number(p.tp) || 0 });
10995
+ finJournal({ kind: 'open', ticket, symbol: st.symbol, side: st.side, volume: Number(p.volume) || 0, entry: st.entry, sl: Number(p.sl) || 0, tp: Number(p.tp) || 0, timeframe: st.tf || '' });
10929
10996
  }
10930
10997
  finWatchSave();
10998
+ } else if (!st.tf) {
10999
+ const tfNow = finLearnParseTf(p.comment);
11000
+ if (tfNow) { st.tf = tfNow; finWatchSave(); }
10931
11001
  }
10932
11002
  /* KÂR KORUMA: görülen EN İYİ kâr (fiyat farkı) tick aralarında da korunur */
10933
11003
  {
@@ -11171,8 +11241,8 @@ function finTradeEvent(kRaw, dataRaw, sidRaw) {
11171
11241
  }
11172
11242
  /* SEMBOL BAZLI ÖĞRENME kaydı: günlüğe + AKIŞ paneline düşer */
11173
11243
  if (k === 'learn') {
11174
- finJournal({ kind: 'learn', sid, agent: who, symbol: d.symbol, text: d.text, learnKind: d.kind || 'observation' });
11175
- const line = `🧠 ÖĞRENME: ${d.symbol || '?'} — ${String(d.text || '').replace(/\s+/g, ' ').slice(0, 160)}`;
11244
+ finJournal({ kind: 'learn', sid, agent: who, symbol: d.symbol, text: d.text, learnKind: d.kind || 'observation', timeframe: d.timeframe || '' });
11245
+ const line = `🧠 ÖĞRENME${d.timeframe ? ' [' + d.timeframe + ']' : ''}: ${d.symbol || '?'} — ${String(d.text || '').replace(/\s+/g, ' ').slice(0, 160)}`;
11176
11246
  financeLog('[ogrenme] ' + line);
11177
11247
  finPush('trade', { line });
11178
11248
  return;
@@ -11186,7 +11256,7 @@ function finTradeEvent(kRaw, dataRaw, sidRaw) {
11186
11256
  return;
11187
11257
  }
11188
11258
  if (k === 'trade') {
11189
- finJournal({ kind: 'trade', sid, agent: who, symbol: d.symbol, side: d.side, volume: d.volume, sl: d.sl, tp: d.tp, reason: d.reason || d.comment || '' });
11259
+ finJournal({ kind: 'trade', sid, agent: who, symbol: d.symbol, side: d.side, volume: d.volume, sl: d.sl, tp: d.tp, timeframe: d.timeframe || '', reason: d.reason || d.comment || '' });
11190
11260
  } else if (k === 'close') {
11191
11261
  /* KISMİ KAPATMA (kısmi TP / kısmi stop): ajan kararı; günlük + bildirim.
11192
11262
  Kesin toplam K/Z yine watchdog tarafından yazılır (çift sayım yok). */
@@ -11206,14 +11276,14 @@ function finTradeEvent(kRaw, dataRaw, sidRaw) {
11206
11276
  } else if (k === 'modify') {
11207
11277
  finJournal({ kind: 'modify', sid, agent: who, ticket: d.ticket, sl: d.sl, tp: d.tp });
11208
11278
  } else if (k === 'pending') {
11209
- finJournal({ kind: 'pending', sid, agent: who, symbol: d.symbol, type: d.type, volume: d.volume, reason: d.reason || '' });
11279
+ finJournal({ kind: 'pending', sid, agent: who, symbol: d.symbol, type: d.type, volume: d.volume, timeframe: d.timeframe || '', reason: d.reason || '' });
11210
11280
  } else if (k === 'cancel') {
11211
11281
  finJournal({ kind: 'cancel', sid, agent: who, ticket: d.ticket });
11212
11282
  }
11213
11283
  let line = '';
11214
- if (k === 'trade') line = `İŞLEM AÇILDI: ${d.side} ${d.volume} ${d.symbol}${who ? ' · ' + who : ''}`;
11284
+ if (k === 'trade') line = `İŞLEM AÇILDI: ${d.side} ${d.volume} ${d.symbol}${d.timeframe ? ' · ' + d.timeframe : ''}${who ? ' · ' + who : ''}`;
11215
11285
  else if (k === 'modify') line = `SL/TP GÜNCELLENDİ: ticket ${d.ticket} (SL ${d.sl || 0} / TP ${d.tp || 0})${who ? ' · ' + who : ''}`;
11216
- else if (k === 'pending') line = `BEKLEYEN EMİR: ${d.type} ${d.volume} ${d.symbol}${who ? ' · ' + who : ''}`;
11286
+ else if (k === 'pending') line = `BEKLEYEN EMİR: ${d.type} ${d.volume} ${d.symbol}${d.timeframe ? ' · ' + d.timeframe : ''}${who ? ' · ' + who : ''}`;
11217
11287
  else if (k === 'cancel') line = `EMİR İPTAL: ticket ${d.ticket}`;
11218
11288
  if (line) {
11219
11289
  financeLog('[ajan] ' + line);
@@ -11249,9 +11319,9 @@ try {
11249
11319
  if (!kind) return;
11250
11320
  if (kind === 'trade') {
11251
11321
  const side = a.side || (/^buy/i.test(String(a.type || '')) ? 'buy' : /^sell/i.test(String(a.type || '')) ? 'sell' : '');
11252
- finTradeEvent('trade', { symbol: a.symbol, side, volume: a.volume, sl: a.sl, tp: a.tp, reason: a.reason || a.comment || '', result: res }, sid);
11322
+ finTradeEvent('trade', { symbol: a.symbol, side, volume: a.volume, sl: a.sl, tp: a.tp, timeframe: a.timeframe || '', reason: a.reason || a.comment || '', result: res }, sid);
11253
11323
  } else if (kind === 'modify') finTradeEvent('modify', { ticket: a.ticket, sl: a.sl, tp: a.tp }, sid);
11254
- else if (kind === 'pending') finTradeEvent('pending', { symbol: a.symbol, type: a.type, volume: a.volume, reason: a.reason || '' }, sid);
11324
+ else if (kind === 'pending') finTradeEvent('pending', { symbol: a.symbol, type: a.type, volume: a.volume, timeframe: a.timeframe || '', reason: a.reason || '' }, sid);
11255
11325
  else if (kind === 'cancel') finTradeEvent('cancel', { ticket: a.ticket }, sid);
11256
11326
  else if (kind === 'close') {
11257
11327
  finTradeEvent('close', {
@@ -11270,8 +11340,51 @@ try {
11270
11340
  Ana trader / finance sohbet oturumu min lot-max lot-pozisyon tavanını
11271
11341
  güncelleyebilir; rol/işçi ajanları yalnız okur. Değişiklik ayarlara yazılır,
11272
11342
  koşan oturumlara ANINDA işlenir ve panele duyurulur. */
11343
+ /* TUR RİTMİ: trader sert harekette geçici hızlanabilir — efektif aralık (sn).
11344
+ Süre dolunca kendiliğinden taban aralığa döner (API/maliyet limiti korunur). */
11345
+ function finPaceSec() {
11346
+ const f = finCfg();
11347
+ const base = Math.max(30, Math.min(3600, Math.round(Number(f.intervalSec) || 120)));
11348
+ const p = financeState.pace;
11349
+ if (p && Number(p.until) > Date.now() && Number(p.sec) >= 30) {
11350
+ return Math.max(30, Math.min(3600, Math.round(Number(p.sec))));
11351
+ }
11352
+ return base;
11353
+ }
11354
+
11355
+ /* Koşan ajan zamanlayıcılarını efektif ritme göre yeniden kur; hız modunun
11356
+ bitişini de zamanla — süre dolunca taban aralığa OTOMATİK dönüş. */
11357
+ function finRescheduleAgents() {
11358
+ const iv = finPaceSec() * 1000;
11359
+ if (financeState.paceTimer) { clearTimeout(financeState.paceTimer); financeState.paceTimer = null; }
11360
+ const p = financeState.pace;
11361
+ if (p && Number(p.until) > Date.now() && Number(p.sec) > 0) {
11362
+ const left = Math.max(1000, Number(p.until) - Date.now() + 1000);
11363
+ financeState.paceTimer = setTimeout(() => {
11364
+ financeState.pace = { sec: 0, until: 0, base: 0 };
11365
+ try { financeLog('[ritim] hız modu bitti — taban tur aralığı ' + finPaceSec() + ' sn'); } catch {}
11366
+ try { finRescheduleAgents(); } catch {}
11367
+ try { finPush('limits', { ...finLimitsSnapshot(), note: 'hız modu bitti — taban tur aralığı ' + finPaceSec() + ' sn' }); } catch {}
11368
+ }, left);
11369
+ }
11370
+ if (!engine || !financeState.agents) return;
11371
+ const hasMain = finTeamHasMain();
11372
+ for (const [sid, a] of financeState.agents) {
11373
+ if (!a) continue;
11374
+ if (a.role && hasMain) continue; /* cycle modu: trader turu başlatır */
11375
+ if (engine.isBusy(sid)) continue; /* tur bitince finFlushOnDone yeni ritmi okur */
11376
+ clearTimeout(a.timer);
11377
+ a.timer = setTimeout(() => { try { finAgentRound(String(sid)); } catch {} }, iv);
11378
+ }
11379
+ if (financeState.traderSid) {
11380
+ const a = financeState.agents.get(String(financeState.traderSid));
11381
+ finPush('trader', { state: 'idle', round: (a && a.round) || 0, nextInSec: iv / 1000 });
11382
+ }
11383
+ }
11384
+
11273
11385
  function finLimitsSnapshot() {
11274
11386
  const f = finCfg();
11387
+ const paceOn = Number(financeState.pace && financeState.pace.until) > Date.now();
11275
11388
  return {
11276
11389
  minLot: Number(f.minLot) || 0.01,
11277
11390
  maxLot: Number(f.maxLot) || 0.1,
@@ -11279,6 +11392,10 @@ function finLimitsSnapshot() {
11279
11392
  riskPerTradePct: Number(f.riskPerTradePct) || 0,
11280
11393
  /* SEMBOL BAZLI LOT LİMİTLERİ (trader kararı) — genel aralığı daraltır */
11281
11394
  symbols: { ...(f.symbolLimits && typeof f.symbolLimits === 'object' ? f.symbolLimits : {}) },
11395
+ /* TUR RİTMİ: efektif aralık + taban + hız modu bitişi */
11396
+ intervalSec: finPaceSec(),
11397
+ intervalBase: Math.max(30, Math.min(3600, Math.round(Number(f.intervalSec) || 120))),
11398
+ paceUntil: paceOn ? Number(financeState.pace.until) : 0,
11282
11399
  };
11283
11400
  }
11284
11401
 
@@ -11295,7 +11412,7 @@ function finLimitsSessionAllowed(sid) {
11295
11412
 
11296
11413
  function finSyncLimitsToSessions(extra) {
11297
11414
  const lim = finLimitsSnapshot();
11298
- const apply = { minLot: lim.minLot, maxLot: lim.maxLot, maxPositions: lim.maxPositions, symbolLimits: lim.symbols };
11415
+ const apply = { minLot: lim.minLot, maxLot: lim.maxLot, maxPositions: lim.maxPositions, symbolLimits: lim.symbols, intervalSec: lim.intervalSec, intervalBase: lim.intervalBase, paceUntil: lim.paceUntil };
11299
11416
  if (engine) {
11300
11417
  try {
11301
11418
  for (const [sid] of financeState.agents) {
@@ -11323,8 +11440,8 @@ function finLimitsSet(patch, ctx) {
11323
11440
  symbol) → sembol limiti kaldırılır, genel aralık geçerli olur. */
11324
11441
  const sym = String(patch.symbol || '').trim().toUpperCase();
11325
11442
  if (sym) {
11326
- if (patch.maxPositions !== undefined) {
11327
- return { ok: false, error: 'maxPositions sembol bazlı değil — symbol vermeden genel limit olarak ayarla' };
11443
+ if (patch.maxPositions !== undefined || patch.intervalSec !== undefined || patch.intervalForMin !== undefined) {
11444
+ return { ok: false, error: 'maxPositions ve tur ritmi (intervalSec) sembol bazlı değil — symbol vermeden genel olarak ayarla' };
11328
11445
  }
11329
11446
  const hasMin = patch.minLot !== undefined && patch.minLot !== null && patch.minLot !== '';
11330
11447
  const hasMax = patch.maxLot !== undefined && patch.maxLot !== null && patch.maxLot !== '';
@@ -11381,16 +11498,39 @@ function finLimitsSet(patch, ctx) {
11381
11498
  if (!(v >= 1) || v > 20) return { ok: false, error: 'maxPositions 1-20 arasında olmalı' };
11382
11499
  f.maxPositions = v;
11383
11500
  }
11501
+ /* TUR RİTMİ (trader kararı): sert hareket/haber anında geçici hızlanma.
11502
+ intervalForMin > 0 → geçici (süre dolunca taban aralığa otomatik döner);
11503
+ yoksa kalıcı temel aralık güncellenir. Alt sınır 30 sn, hız modu ≤ 240 dk:
11504
+ API/maliyet limiti bilinçli — sürekli en düşük aralıkta kalmak YASAK. */
11505
+ let intervalChanged = false;
11506
+ if (patch.intervalSec !== undefined) {
11507
+ const base = Math.max(30, Math.min(3600, Math.round(Number(f.intervalSec) || 120)));
11508
+ const sec = Math.max(30, Math.min(3600, Math.round(Number(patch.intervalSec) || base)));
11509
+ const mins = Math.max(0, Math.min(240, Math.round(Number(patch.intervalForMin) || 0)));
11510
+ if (mins > 0 && sec < base) {
11511
+ financeState.pace = { sec, until: Date.now() + mins * 60 * 1000, base };
11512
+ notes.push(`hız modu: ${sec} sn × ${mins} dk (sonra taban ${base} sn'e döner)`);
11513
+ } else {
11514
+ f.intervalSec = sec;
11515
+ if (Number(financeState.pace && financeState.pace.until) > Date.now()) financeState.pace = { sec: 0, until: 0, base: 0 };
11516
+ notes.push(`temel tur aralığı: ${sec} sn`);
11517
+ }
11518
+ intervalChanged = true;
11519
+ } else if (patch.intervalForMin !== undefined) {
11520
+ notes.push('intervalForMin için intervalSec de verilmeli');
11521
+ }
11384
11522
  if (f.minLot > f.maxLot) {
11385
11523
  notes.push(`minLot (${f.minLot}) maxLot'tan (${f.maxLot}) büyüktü → minLot ${f.maxLot} yapıldı`);
11386
11524
  f.minLot = f.maxLot;
11387
11525
  }
11388
11526
  try { saveSettings(); } catch {}
11527
+ if (intervalChanged) { try { finRescheduleAgents(); } catch {} }
11389
11528
  finSyncLimitsToSessions();
11390
- const line = `⚙️ LİMİTLER GÜNCELLENDİ: min lot ${f.minLot} · max lot ${f.maxLot} · max pozisyon ${f.maxPositions}${sid ? ' (ajan kararı)' : ''}`;
11529
+ const paceOn = Number(financeState.pace && financeState.pace.until) > Date.now();
11530
+ const line = `⚙️ LİMİTLER GÜNCELLENDİ: min lot ${f.minLot} · max lot ${f.maxLot} · max pozisyon ${f.maxPositions}${intervalChanged ? ` · tur ${finPaceSec()} sn${paceOn ? ' (hız modu)' : ''}` : ''}${sid ? ' (ajan kararı)' : ''}`;
11391
11531
  financeLog('[limit] ' + line);
11392
11532
  financeNotify(line, 'limit', true);
11393
- try { finJournal({ kind: 'limits', minLot: f.minLot, maxLot: f.maxLot, maxPositions: f.maxPositions, sid }); } catch {}
11533
+ try { finJournal({ kind: 'limits', minLot: f.minLot, maxLot: f.maxLot, maxPositions: f.maxPositions, intervalSec: finPaceSec(), paceUntil: paceOn ? financeState.pace.until : 0, sid }); } catch {}
11394
11534
  return { ok: true, limits: finLimitsSnapshot(), note: notes.length ? notes.join('; ') : undefined };
11395
11535
  }
11396
11536
 
@@ -11418,15 +11558,62 @@ const FIN_LEARN_COMPACT_AT = 24; /* not sayısı bunu aşınca arka planda ot
11418
11558
  const FIN_LEARN_KEEP_RECENT = 8; /* ham kalan son ders sayısı */
11419
11559
  const FIN_LEARN_SUMMARY_MAX = 800; /* kalıcı özet metin tavanı (karakter) */
11420
11560
  const FIN_LEARN_COMPACT_MIN = 4; /* elle özetleme için gereken en az eski ders */
11561
+ /* ZAMAN DİLİMİ: ajan kendi periyodunu SEÇER (bot kararı) — işlem emri yorumuna
11562
+ yazılır ("Beast M15"), kapanışta periyot bazlı öğrenmeye işlenir. */
11563
+ const FIN_LEARN_TFS = ['M1', 'M5', 'M15', 'M30', 'H1', 'H4', 'D1', 'W1', 'MN1'];
11564
+ const FIN_LEARN_MAX_MISTAKES = 40; /* sembol başına otomatik hata kaydı tavanı */
11565
+
11566
+ function finLearnNormTf(v) {
11567
+ const s = String(v || '').trim().toUpperCase();
11568
+ if (!s) return '';
11569
+ const m = s.match(/^(M|H|D|W|MN)\s*(\d+)$/);
11570
+ if (!m) return '';
11571
+ const key = m[1] === 'MN' ? 'MN1' : m[1] + m[2];
11572
+ return FIN_LEARN_TFS.includes(key) ? key : '';
11573
+ }
11574
+
11575
+ /* MT5 pozisyon yorumundan periyot okur: "Beast M15 ..." → M15 */
11576
+ function finLearnParseTf(comment) {
11577
+ const m = String(comment || '').toUpperCase().match(/\b(M1|M5|M15|M30|H1|H4|D1|W1|MN1)\b/);
11578
+ return m ? m[1] : '';
11579
+ }
11421
11580
 
11422
11581
  function finLearnEmptyStats() {
11423
11582
  return {
11424
11583
  trades: 0, wins: 0, losses: 0, net: 0,
11425
11584
  mfe: 0, mae: 0, givebacks: 0, givebackAmount: 0,
11426
11585
  lastAt: 0, lastNet: 0, streak: 0, bestNet: 0, worstNet: 0,
11586
+ /* PERİYOT KIRILIMI: tf → {trades,wins,losses,net,mfe,mae,givebacks,lastAt,lastNet} */
11587
+ byTf: {},
11427
11588
  };
11428
11589
  }
11429
11590
 
11591
+ /* Bozuk/eski istatistik kaydını temizle: bilinen alanlar + byTf süzülür */
11592
+ function finLearnNormStats(v) {
11593
+ const raw = v && typeof v === 'object' ? v : {};
11594
+ const out = { ...finLearnEmptyStats(), ...raw };
11595
+ const src = raw.byTf && typeof raw.byTf === 'object' ? raw.byTf : {};
11596
+ const byTf = {};
11597
+ for (const [k, s] of Object.entries(src)) {
11598
+ const tf = finLearnNormTf(k);
11599
+ if (!tf || !s || typeof s !== 'object') continue;
11600
+ const t = {
11601
+ trades: Math.max(0, Math.round(Number(s.trades) || 0)),
11602
+ wins: Math.max(0, Math.round(Number(s.wins) || 0)),
11603
+ losses: Math.max(0, Math.round(Number(s.losses) || 0)),
11604
+ net: Math.round((Number(s.net) || 0) * 100) / 100,
11605
+ mfe: Math.round((Number(s.mfe) || 0) * 100) / 100,
11606
+ mae: Math.round((Number(s.mae) || 0) * 100) / 100,
11607
+ givebacks: Math.max(0, Math.round(Number(s.givebacks) || 0)),
11608
+ lastAt: Number(s.lastAt) || 0,
11609
+ lastNet: Math.round((Number(s.lastNet) || 0) * 100) / 100,
11610
+ };
11611
+ if (t.trades > 0) byTf[tf] = t;
11612
+ }
11613
+ out.byTf = byTf;
11614
+ return out;
11615
+ }
11616
+
11430
11617
  /* Ders metni karşılaştırma anahtarı: büyük/küçük harf, noktalama ve boşluk
11431
11618
  farkları aynı ders sayılır — ajan aynı cümleyi kopyalayıp depoyu doldurmasın */
11432
11619
  function finLearnNoteKey(text) {
@@ -11480,7 +11667,7 @@ function finLearnPrune(learn, keep, protectSym) {
11480
11667
  yüklemede çalışır → depo kendiliğinden sade kalır, birikme/zehirlenme olmaz. */
11481
11668
  function finLearnForget(learn, now) {
11482
11669
  const t = Number(now) || Date.now();
11483
- const stat = { notes: 0, trades: 0, symbols: 0 };
11670
+ const stat = { notes: 0, trades: 0, mistakes: 0, symbols: 0 };
11484
11671
  for (const [sym, e] of Object.entries(learn.symbols)) {
11485
11672
  if (!e || typeof e !== 'object') { delete learn.symbols[sym]; stat.symbols++; continue; }
11486
11673
  const notes = Array.isArray(e.notes) ? e.notes : [];
@@ -11489,6 +11676,10 @@ function finLearnForget(learn, now) {
11489
11676
  const trades = Array.isArray(e.trades) ? e.trades : [];
11490
11677
  e.trades = trades.filter((x) => x && t - (Number(x.at) || 0) <= FIN_LEARN_TRADE_TTL_MS);
11491
11678
  stat.trades += trades.length - e.trades.length;
11679
+ /* HATA KAYITLARI da işlem TTL'iyle unutulur — depo şişmez */
11680
+ const mistakes = Array.isArray(e.mistakes) ? e.mistakes : [];
11681
+ e.mistakes = mistakes.filter((x) => x && t - (Number(x.at) || 0) <= FIN_LEARN_TRADE_TTL_MS).slice(-FIN_LEARN_MAX_MISTAKES);
11682
+ stat.mistakes += mistakes.length - e.mistakes.length;
11492
11683
  if (t - finLearnActivity(e) > FIN_LEARN_SYMBOL_TTL_MS) {
11493
11684
  delete learn.symbols[sym];
11494
11685
  stat.symbols += 1;
@@ -11502,7 +11693,7 @@ function finLearnForget(learn, now) {
11502
11693
  function finLearnTidy(learn, protectSym, now) {
11503
11694
  const forgot = finLearnForget(learn, now);
11504
11695
  const cap = finLearnPrune(learn, FIN_LEARN_MAX_SYMBOLS, protectSym);
11505
- return { notes: forgot.notes, trades: forgot.trades, symbols: forgot.symbols + cap };
11696
+ return { notes: forgot.notes, trades: forgot.trades, mistakes: forgot.mistakes, symbols: forgot.symbols + cap };
11506
11697
  }
11507
11698
 
11508
11699
  function finLearnLoad() {
@@ -11518,7 +11709,8 @@ function finLearnLoad() {
11518
11709
  learn.symbols[sym] = {
11519
11710
  notes: Array.isArray(v.notes) ? v.notes.filter((n) => n && n.id && n.text).slice(-FIN_LEARN_MAX_NOTES) : [],
11520
11711
  trades: Array.isArray(v.trades) ? v.trades.filter(Boolean).slice(-FIN_LEARN_MAX_TRADES) : [],
11521
- stats: v.stats && typeof v.stats === 'object' ? { ...finLearnEmptyStats(), ...v.stats } : finLearnEmptyStats(),
11712
+ mistakes: Array.isArray(v.mistakes) ? v.mistakes.filter(Boolean).slice(-FIN_LEARN_MAX_MISTAKES) : [],
11713
+ stats: finLearnNormStats(v.stats),
11522
11714
  /* KALICI ÖZET (opencode tarzı sıkıştırma): eski derslerin modele
11523
11715
  özetlettirilmiş hâli — ham dersler düşse de bilgi kalır */
11524
11716
  summary: v.summary && typeof v.summary === 'object' && String(v.summary.text || '').trim()
@@ -11535,7 +11727,7 @@ function finLearnLoad() {
11535
11727
  } catch {}
11536
11728
  const dropped = finLearnTidy(learn); /* yükleme anında unut + tavan uygula */
11537
11729
  financeState.learn = learn;
11538
- if (dropped.notes || dropped.trades || dropped.symbols) finLearnSave(); /* disk de hemen küçülsün */
11730
+ if (dropped.notes || dropped.trades || dropped.mistakes || dropped.symbols) finLearnSave(); /* disk de hemen küçülsün */
11539
11731
  return learn;
11540
11732
  }
11541
11733
 
@@ -11555,16 +11747,16 @@ const FIN_LEARN_COMPACT_SYSTEM =
11555
11747
  'Sen bir trading mentorüsün. Sana bir sembolün ESKİ ders notları ve mevcut özeti verilir. ' +
11556
11748
  'Görevin: güncellenmiş KISA bir özet üretmek.\n' +
11557
11749
  'KURALLAR: (1) Yalnız kalıcı değeri olan bilgiyi tut: çalışan desenler, kaçınılacak hatalar, ' +
11558
- 'seans/setup kuralları, SL/TP davranışı. (2) Tekrarları birleştir; tarih/saat damgası, anlık ' +
11750
+ 'seans/setup/PERİYOT (M5/M15/H1…) kuralları, SL/TP davranışı. (2) Tekrarları birleştir; tarih/saat damgası, anlık ' +
11559
11751
  'haber, geçici durum gibi bayat bilgiyi at. (3) Çelişen bilgide EN YENİ ve istatistikle ' +
11560
- 'desteklenen kazanır. (4) Sayısal istatistikleri (işlem, kazanç %, net, kâr geri verme) koru. ' +
11561
- '(5) En fazla ' + FIN_LEARN_SUMMARY_MAX + ' karakter; kısa maddeler ya da 2-4 akıcı cümle. ' +
11752
+ 'desteklenen kazanır. (4) Sayısal istatistikleri (işlem, kazanç %, net, kâr geri verme, periyot bazlı net) koru; ' +
11753
+ 'hangi periyodun kazandırdığını/kaybettirdiğini açık yaz. (5) En fazla ' + FIN_LEARN_SUMMARY_MAX + ' karakter; kısa maddeler ya da 2-4 akıcı cümle. ' +
11562
11754
  '(6) Çıktı SADECE özet metni olsun; başlık/etiket/açıklama yazma.';
11563
11755
 
11564
11756
  const finLearnCompacting = new Set();
11565
11757
 
11566
11758
  function finLearnSummaryPrompt(sym, e, oldNotes) {
11567
- const st = { ...finLearnEmptyStats(), ...(e.stats || {}) };
11759
+ const st = finLearnNormStats(e.stats);
11568
11760
  const wr = st.trades ? Math.round((st.wins / st.trades) * 100) : 0;
11569
11761
  const lines = [];
11570
11762
  lines.push('SEMBOL: ' + sym);
@@ -11573,12 +11765,27 @@ function finLearnSummaryPrompt(sym, e, oldNotes) {
11573
11765
  (st.givebacks ? ` · ${st.givebacks} kez kâr geri verildi` : '') +
11574
11766
  (st.streak >= 2 ? ` · ${st.streak} ardışık kayıp` : '')
11575
11767
  );
11768
+ const tfRows = Object.entries(st.byTf || {}).filter(([, s]) => s && Number(s.trades) > 0);
11769
+ if (tfRows.length) {
11770
+ lines.push('PERİYOT İSTATİSTİĞİ:');
11771
+ for (const [tf, s] of tfRows.sort((a, b) => (Number(b[1].net) || 0) - (Number(a[1].net) || 0))) {
11772
+ const w = Math.round((Number(s.wins) / Number(s.trades)) * 100);
11773
+ lines.push(`- ${tf}: ${s.trades} işlem · %${w} kazanç · net ${Number(s.net) >= 0 ? '+' : ''}${Math.round(Number(s.net) * 100) / 100}`);
11774
+ }
11775
+ }
11776
+ const mistakes = Array.isArray(e.mistakes) ? e.mistakes.slice(-20) : [];
11777
+ if (mistakes.length) {
11778
+ lines.push('SON HATALAR (zararla kapananlar):');
11779
+ for (const m of mistakes) {
11780
+ lines.push(`- ${m.tf || '?'} ${m.side || ''} net ${Math.round(Number(m.net) * 100) / 100}${m.reason ? ' (' + m.reason + ')' : ''}${m.giveback ? ' [kâr geri verildi]' : ''}`);
11781
+ }
11782
+ }
11576
11783
  if (e.summary && e.summary.text) lines.push('MEVCUT ÖZET:\n' + String(e.summary.text).slice(0, 1400));
11577
11784
  lines.push('ESKİ DERSLER (kronolojik — en yeni EN SONDA):');
11578
11785
  for (const n of oldNotes.slice(-80)) {
11579
- lines.push(`- [${String(n.kind || 'observation')}] ${String(n.text || '').slice(0, 220)}`);
11786
+ lines.push(`- [${String(n.kind || 'observation')}${n.tf ? ' ' + String(n.tf) : ''}] ${String(n.text || '').slice(0, 220)}`);
11580
11787
  }
11581
- lines.push('Görev: yalnız kalıcı ve güncel dersleri koruyan yeni özet metnini yaz.');
11788
+ lines.push('Görev: yalnız kalıcı ve güncel dersleri (periyot bilgisi ve hata kalıplarıyla) koruyan yeni özet metnini yaz.');
11582
11789
  return lines.join('\n');
11583
11790
  }
11584
11791
 
@@ -11645,40 +11852,55 @@ function finLearnSym(symbol) {
11645
11852
  const sym = String(symbol || '').trim().toUpperCase();
11646
11853
  if (!sym) return null;
11647
11854
  const learn = finLearnLoad();
11648
- if (!learn.symbols[sym]) learn.symbols[sym] = { notes: [], trades: [], stats: finLearnEmptyStats() };
11855
+ if (!learn.symbols[sym]) learn.symbols[sym] = { notes: [], trades: [], mistakes: [], stats: finLearnEmptyStats(), summary: null };
11649
11856
  const e = learn.symbols[sym];
11650
- e.stats = { ...finLearnEmptyStats(), ...(e.stats || {}) };
11857
+ if (!Array.isArray(e.mistakes)) e.mistakes = [];
11858
+ e.stats = finLearnNormStats(e.stats);
11651
11859
  return e;
11652
11860
  }
11653
11861
 
11654
- /* Kapanan her işlem otomatik öğrenme girdisi olur (sembol bazlı) */
11655
- function finLearnRecordClose({ symbol, side, net, mfe, mae, reason }) {
11862
+ /* Kapanan her işlem otomatik öğrenme girdisi olur (sembol + periyot bazlı).
11863
+ timeframe: ajanın emir anında verdiği periyot (yoksa '' — genel istatistiğe
11864
+ yine işler). Zararla kapanan işlem AYRICA yapılandırılmış hata kaydı olur:
11865
+ hata dersleri bu kayıttan üretilir ve bir sonraki turda prompta girer. */
11866
+ function finLearnRecordClose({ symbol, side, net, mfe, mae, reason, timeframe }) {
11656
11867
  try {
11657
11868
  if (net == null) return; /* K/Z okunamadı — sahte kayıt yazma */
11658
11869
  const n = Number(net);
11659
11870
  if (!isFinite(n) || !symbol) return;
11660
11871
  const e = finLearnSym(symbol);
11661
11872
  if (!e) return;
11873
+ const tf = finLearnNormTf(timeframe);
11662
11874
  const st = e.stats;
11663
11875
  const m = Number(mfe);
11664
11876
  const ma = Number(mae);
11665
- st.trades += 1;
11666
- if (n >= 0) st.wins += 1;
11667
- else st.losses += 1;
11668
- st.net = Math.round((Number(st.net) + n) * 100) / 100;
11669
- st.lastAt = Date.now();
11670
- st.lastNet = Math.round(n * 100) / 100;
11671
- st.streak = n < 0 ? Number(st.streak || 0) + 1 : 0;
11877
+ const bump = (s) => {
11878
+ s.trades += 1;
11879
+ if (n >= 0) s.wins += 1;
11880
+ else s.losses += 1;
11881
+ s.net = Math.round((Number(s.net) + n) * 100) / 100;
11882
+ s.lastAt = Date.now();
11883
+ s.lastNet = Math.round(n * 100) / 100;
11884
+ s.streak = n < 0 ? Number(s.streak || 0) + 1 : 0;
11885
+ if (isFinite(m)) {
11886
+ s.mfe = Math.round(((Number(s.mfe) || 0) + m) * 100) / 100;
11887
+ if (m > 0 && n < 0) {
11888
+ s.givebacks = Number(s.givebacks || 0) + 1;
11889
+ s.givebackAmount = Math.round(((Number(s.givebackAmount) || 0) + m) * 100) / 100;
11890
+ }
11891
+ }
11892
+ if (isFinite(ma)) s.mae = Math.round(((Number(s.mae) || 0) + ma) * 100) / 100;
11893
+ };
11894
+ /* GENEL istatistik */
11895
+ bump(st);
11672
11896
  st.bestNet = Math.max(Number(st.bestNet) || 0, Math.round(n * 100) / 100);
11673
11897
  st.worstNet = Math.min(Number(st.worstNet) || 0, Math.round(n * 100) / 100);
11674
- if (isFinite(m)) {
11675
- st.mfe = Math.round(((Number(st.mfe) || 0) + m) * 100) / 100;
11676
- if (m > 0 && n < 0) {
11677
- st.givebacks += 1;
11678
- st.givebackAmount = Math.round(((Number(st.givebackAmount) || 0) + m) * 100) / 100;
11679
- }
11898
+ /* PERİYOT istatistiği: hangi zaman dilimi kazandırıyor/kaybettiriyor */
11899
+ if (tf) {
11900
+ if (!st.byTf || typeof st.byTf !== 'object') st.byTf = {};
11901
+ if (!st.byTf[tf]) st.byTf[tf] = { trades: 0, wins: 0, losses: 0, net: 0, mfe: 0, mae: 0, givebacks: 0, lastAt: 0, lastNet: 0 };
11902
+ bump(st.byTf[tf]);
11680
11903
  }
11681
- if (isFinite(ma)) st.mae = Math.round(((Number(st.mae) || 0) + ma) * 100) / 100;
11682
11904
  e.trades.push({
11683
11905
  at: Date.now(),
11684
11906
  side: String(side || ''),
@@ -11686,14 +11908,69 @@ function finLearnRecordClose({ symbol, side, net, mfe, mae, reason }) {
11686
11908
  mfe: isFinite(m) ? Math.round(m * 100) / 100 : null,
11687
11909
  mae: isFinite(ma) ? Math.round(ma * 100) / 100 : null,
11688
11910
  reason: String(reason || '').slice(0, 40),
11911
+ tf: tf || null,
11689
11912
  });
11690
11913
  while (e.trades.length > FIN_LEARN_MAX_TRADES) e.trades.shift();
11914
+ /* YANLIŞTAN DERS: zararla kapanan işlem yapılandırılmış hata kaydı olur —
11915
+ sonraki turlarda ajana "tekrar eden hata" olarak gösterilir */
11916
+ if (n < 0) {
11917
+ if (!Array.isArray(e.mistakes)) e.mistakes = [];
11918
+ e.mistakes.push({
11919
+ at: Date.now(),
11920
+ tf: tf || '',
11921
+ side: String(side || ''),
11922
+ net: Math.round(n * 100) / 100,
11923
+ mfe: isFinite(m) ? Math.round(m * 100) / 100 : null,
11924
+ mae: isFinite(ma) ? Math.round(ma * 100) / 100 : null,
11925
+ reason: String(reason || '').slice(0, 40),
11926
+ giveback: !!(isFinite(m) && m > 0),
11927
+ });
11928
+ while (e.mistakes.length > FIN_LEARN_MAX_MISTAKES) e.mistakes.shift();
11929
+ }
11691
11930
  finLearnTidy(finLearnLoad(), symbol);
11692
11931
  finLearnSave();
11693
11932
  } catch {}
11694
11933
  }
11695
11934
 
11696
- /* Trader promptuna gömülen sembol bazlı öğrenme özeti */
11935
+ /* Tur girişine gömülen öğrenme özeti: sembol + PERİYOT istatistiği, son
11936
+ hatalar ve dersler. Ajanın "her şeye öğrendiğiyle karar vermesi" için
11937
+ karar anında önüne konur; en iyi/en kötü periyot ve tekrar eden hata
11938
+ kalıbı açıkça işaretlenir. */
11939
+ function finBuildLearnTfLine(st) {
11940
+ const rows = Object.entries(st.byTf || {}).filter(([, s]) => s && Number(s.trades) > 0);
11941
+ if (!rows.length) return '';
11942
+ rows.sort((a, b) => (Number(b[1].net) || 0) - (Number(a[1].net) || 0));
11943
+ const fmt = ([tf, s]) => {
11944
+ const wrT = Math.round((Number(s.wins) / Number(s.trades)) * 100);
11945
+ return `${tf} ${s.trades}i %${wrT} net ${Number(s.net) >= 0 ? '+' : ''}${Math.round(Number(s.net) * 100) / 100}`;
11946
+ };
11947
+ const parts = rows.map(fmt);
11948
+ const best = rows[0];
11949
+ const worst = rows.length > 1 ? rows[rows.length - 1] : null;
11950
+ let out = `periyot: ${parts.join(' | ')}`;
11951
+ if (best) out += ` · EN İYİ: ${best[0]} (net ${Number(best[1].net) >= 0 ? '+' : ''}${Math.round(Number(best[1].net) * 100) / 100})`;
11952
+ if (worst && Number(worst[1].net) < 0) out += ` · NEGATİF: ${worst[0]} (riski düşür/teyit ara)`;
11953
+ return out;
11954
+ }
11955
+
11956
+ /* Son hatalardan kalıp çıkar: aynı periyotta tekrarlayan kayıp sayısı vb. */
11957
+ function finBuildLearnMistakeLine(e) {
11958
+ const ms = Array.isArray(e.mistakes) ? e.mistakes : [];
11959
+ if (!ms.length) return '';
11960
+ const last = ms.slice(-6);
11961
+ const byTf = {};
11962
+ for (const m of last) if (m.tf) byTf[m.tf] = (byTf[m.tf] || 0) + 1;
11963
+ const tfTxt = Object.entries(byTf).map(([tf, n]) => `${tf}×${n}`).join(', ');
11964
+ const lastMs = last[last.length - 1];
11965
+ const kinds = { stop: 'SL', tp: 'TP', stopout: 'stop-out', manual: 'manuel' };
11966
+ let out = `son hatalar (${last.length}): ` + last.map((m) => `${m.tf || '?'} ${kinds[String(m.reason)] || (m.reason || 'zarar')} ${Number(m.net) >= 0 ? '+' : ''}${Math.round(Number(m.net) * 100) / 100}${m.giveback ? ' [kâr geri verildi]' : ''}`).join(' · ');
11967
+ if (tfTxt && Object.keys(byTf).length) out += ` · periyot dağılımı: ${tfTxt}`;
11968
+ const repeated = Object.entries(byTf).find(([, n]) => n >= 3);
11969
+ if (repeated) out += ` · ⚠ TEKRAR EDEN HATA: ${repeated[0]} periyodunda ${repeated[1]} kayıp — bu periyotta riski düşür ya da teyit bekle`;
11970
+ if (lastMs && lastMs.giveback) out += ' · KÂR GERİ VERME: kârı koru (kısmi TP/BE kullan)';
11971
+ return out;
11972
+ }
11973
+
11697
11974
  function finBuildLearnDigest(symbols) {
11698
11975
  const learn = finLearnLoad();
11699
11976
  const prefer = Array.isArray(symbols) ? symbols.map((s) => String(s || '').trim().toUpperCase()).filter(Boolean) : [];
@@ -11702,7 +11979,7 @@ function finBuildLearnDigest(symbols) {
11702
11979
  const L = [];
11703
11980
  for (const sym of keys.slice(0, 10)) {
11704
11981
  const e = learn.symbols[sym];
11705
- const st = { ...finLearnEmptyStats(), ...(e.stats || {}) };
11982
+ const st = finLearnNormStats(e.stats);
11706
11983
  if (st.trades > 0) {
11707
11984
  const wr = Math.round((st.wins / st.trades) * 100);
11708
11985
  const parts = [`${st.trades} işlem`, `%${wr} kazanç`, `net ${st.net >= 0 ? '+' : ''}${st.net}`];
@@ -11711,6 +11988,13 @@ function finBuildLearnDigest(symbols) {
11711
11988
  if (st.lastAt) parts.push(`son: ${st.lastNet >= 0 ? '+' : ''}${st.lastNet}`);
11712
11989
  L.push(`- ${sym}: ${parts.join(' · ')}`);
11713
11990
  }
11991
+ /* PERİYOT KIRILIMI: hangi zaman dilimi kazandırıyor — bot işlem açarken
11992
+ periyodu buna göre SEÇER (kendi kararı) */
11993
+ const tfLine = finBuildLearnTfLine(st);
11994
+ if (tfLine) L.push(' ' + tfLine);
11995
+ /* SON HATALAR: yapılan yanlışlar + tekrar eden kalıp uyarısı */
11996
+ const mLine = finBuildLearnMistakeLine(e);
11997
+ if (mLine) L.push(' ' + mLine);
11714
11998
  /* KALICI ÖZET (compaction): eski derslerin sıkıştırılmış hâli — ham ders
11715
11999
  sayısı azalsa da birikmiş bilgi prompta girer */
11716
12000
  if (e.summary && e.summary.text) {
@@ -11722,62 +12006,78 @@ function finBuildLearnDigest(symbols) {
11722
12006
  L.push(` ders${extra}: "${String(last.text || '').slice(0, 160)}"`);
11723
12007
  }
11724
12008
  }
12009
+ if (L.length) {
12010
+ L.unshift(
12011
+ 'ZAMAN DİLİMİ & HATA DİSİPLİNİ: işlemi hangi periyodun analizine dayandırıyorsan mt5_trade/mt5_pending timeframe alanına YAZ (SEN seçersin; öğrenme o periyoda işlenir) — en iyi istatistikli periyodu tercih et, NEGATİF periyotta riski düşür ya da teyit bekle. Zarar/stop sonrası mt5_ogrenme {action:"add", symbol, timeframe, kind:"mistake", text:"yanlış neydi + kaçınma kuralı"} ile derdini çıkar; aynı hatayı tekrarlama.'
12012
+ );
12013
+ }
11725
12014
  return L.join('\n');
11726
12015
  }
11727
12016
 
11728
12017
  function finLearnApi() {
11729
12018
  const norm = (s) => String(s || '').trim().toUpperCase();
11730
12019
  return {
11731
- list: ({ symbol, limit } = {}) => {
12020
+ list: ({ symbol, timeframe, limit } = {}) => {
11732
12021
  const learn = finLearnLoad();
11733
12022
  const sym = norm(symbol);
12023
+ const tf = finLearnNormTf(timeframe);
11734
12024
  const take = Math.max(1, Math.min(25, Math.round(Number(limit) || 8)));
11735
12025
  if (sym) {
11736
12026
  const e = learn.symbols[sym];
11737
- if (!e) return { ok: true, symbol: sym, count: 0, notes: [], trades: [], stats: finLearnEmptyStats(), summary: null };
12027
+ if (!e) return { ok: true, symbol: sym, count: 0, notes: [], trades: [], stats: finLearnEmptyStats(), summary: null, mistakes: [] };
12028
+ const notes = tf ? e.notes.filter((n) => finLearnNormTf(n.tf) === tf) : e.notes;
12029
+ const mistakes = tf ? (Array.isArray(e.mistakes) ? e.mistakes.filter((m) => finLearnNormTf(m.tf) === tf) : []) : (Array.isArray(e.mistakes) ? e.mistakes : []);
11738
12030
  return {
11739
12031
  ok: true,
11740
12032
  symbol: sym,
11741
- count: e.notes.length,
11742
- notes: e.notes.slice(-take),
12033
+ timeframe: tf || '',
12034
+ count: notes.length,
12035
+ notes: notes.slice(-take),
11743
12036
  trades: e.trades.slice(-12),
11744
- stats: { ...finLearnEmptyStats(), ...(e.stats || {}) },
12037
+ stats: finLearnNormStats(e.stats),
11745
12038
  summary: e.summary && e.summary.text ? e.summary : null,
12039
+ mistakes: mistakes.slice(-8),
11746
12040
  };
11747
12041
  }
11748
12042
  const rows = Object.entries(learn.symbols)
11749
12043
  .map(([s, e]) => ({
11750
12044
  symbol: s,
11751
12045
  notes: e.notes.length,
11752
- stats: { ...finLearnEmptyStats(), ...(e.stats || {}) },
12046
+ stats: finLearnNormStats(e.stats),
11753
12047
  lastNote: String(((e.notes[e.notes.length - 1] || {}).text) || ''),
11754
12048
  hasSummary: !!(e.summary && e.summary.text),
11755
12049
  }))
11756
12050
  .sort((a, b) => (b.stats.lastAt || 0) - (a.stats.lastAt || 0));
11757
12051
  return { ok: true, count: rows.length, symbols: rows.slice(0, FIN_LEARN_MAX_SYMBOLS) };
11758
12052
  },
11759
- stats: ({ symbol } = {}) => {
11760
- /* KOMPAKT OKUMA: istatistik + kalıcı özet + son 3 ders — ajan karar
11761
- öncesi bunu okur (hafızanın TAMAMI değil, sadece güncel öz) */
12053
+ stats: ({ symbol, timeframe } = {}) => {
12054
+ /* KOMPAKT OKUMA: istatistik + PERİYOT kırılımı + kalıcı özet + son 3 ders
12055
+ + son hatalar — ajan karar öncesi bunu okur */
11762
12056
  const learn = finLearnLoad();
11763
12057
  const sym = norm(symbol);
12058
+ const tf = finLearnNormTf(timeframe);
11764
12059
  if (sym) {
11765
12060
  const e = learn.symbols[sym] || {};
12061
+ const st = finLearnNormStats(e.stats);
11766
12062
  return {
11767
12063
  ok: true,
11768
12064
  symbol: sym,
11769
- stats: { ...finLearnEmptyStats(), ...(e.stats || {}) },
12065
+ timeframe: tf || '',
12066
+ stats: st,
12067
+ byTf: tf && st.byTf[tf] ? { [tf]: st.byTf[tf] } : st.byTf,
11770
12068
  summary: e.summary && e.summary.text ? e.summary : null,
11771
- notes: Array.isArray(e.notes) ? e.notes.slice(-3) : [],
12069
+ notes: Array.isArray(e.notes) ? e.notes.filter((n) => !tf || finLearnNormTf(n.tf) === tf).slice(-3) : [],
12070
+ mistakes: Array.isArray(e.mistakes) ? e.mistakes.filter((m) => !tf || finLearnNormTf(m.tf) === tf).slice(-5) : [],
11772
12071
  };
11773
12072
  }
11774
12073
  const stats = {};
11775
- for (const [s, e] of Object.entries(learn.symbols)) stats[s] = { ...finLearnEmptyStats(), ...(e.stats || {}) };
12074
+ for (const [s, e] of Object.entries(learn.symbols)) stats[s] = finLearnNormStats(e.stats);
11776
12075
  return { ok: true, count: Object.keys(stats).length, stats };
11777
12076
  },
11778
- add: ({ symbol, text, kind, tags, sid } = {}) => {
12077
+ add: ({ symbol, text, kind, tags, timeframe, sid } = {}) => {
11779
12078
  const sym = norm(symbol);
11780
12079
  const t = String(text || '').replace(/\s+/g, ' ').trim();
12080
+ const tf = finLearnNormTf(timeframe);
11781
12081
  if (!sym || !t) return { ok: false, error: 'symbol ve text gerekli' };
11782
12082
  /* KALİTE KAPISI: kısa + somut + tekrar kullanılabilir — günlük tutma */
11783
12083
  const q = finLearnNoteQuality(t);
@@ -11785,10 +12085,10 @@ function finLearnApi() {
11785
12085
  const learn = finLearnLoad();
11786
12086
  const e = finLearnSym(sym);
11787
12087
  if (!e) return { ok: false, error: 'sembol yok' };
11788
- /* aynı ders tekrar yazılmasın (son 40 kayıtta normalize metin) — kopyala/
11789
- yapıştır ve noktalama oyunlarıyla depo şişirilemez */
12088
+ /* aynı ders tekrar yazılmasın (son 40 kayıtta normalize metin + periyot) —
12089
+ kopyala/yapıştır ve noktalama oyunlarıyla depo şişirilemez */
11790
12090
  const key = finLearnNoteKey(t);
11791
- if (key && e.notes.slice(-40).some((n) => finLearnNoteKey(n.text) === key)) {
12091
+ if (key && e.notes.slice(-40).some((n) => finLearnNoteKey(n.text) === key && finLearnNormTf(n.tf) === tf)) {
11792
12092
  return { ok: true, duplicate: true, symbol: sym, count: e.notes.length };
11793
12093
  }
11794
12094
  /* GÜNLÜK DERS LİMİTİ: ajan aynı gün aynı sembole ders yağdıramaz —
@@ -11809,6 +12109,7 @@ function finLearnApi() {
11809
12109
  text: t.slice(0, FIN_LEARN_NOTE_MAX),
11810
12110
  kind: ['pattern', 'mistake', 'rule', 'observation'].includes(String(kind || '')) ? String(kind) : 'observation',
11811
12111
  tags: Array.isArray(tags) ? tags.map((x) => String(x || '').slice(0, 24)).filter(Boolean).slice(0, 6) : [],
12112
+ tf: tf || '',
11812
12113
  sid: String(sid || ''),
11813
12114
  };
11814
12115
  e.notes.push(note);
@@ -11818,7 +12119,7 @@ function finLearnApi() {
11818
12119
  /* OPENCODE TARZI COMPACTION: notlar birikince ESKİLERİ arka planda
11819
12120
  özetle — ajanı bekletmez, hafıza kendiliğinden sadeleşir */
11820
12121
  if (e.notes.length > FIN_LEARN_COMPACT_AT) finLearnCompactAuto(sym);
11821
- return { ok: true, id: note.id, symbol: sym, count: e.notes.length };
12122
+ return { ok: true, id: note.id, symbol: sym, timeframe: tf || '', count: e.notes.length };
11822
12123
  },
11823
12124
  remove: ({ id, symbol } = {}) => {
11824
12125
  const learn = finLearnLoad();
@@ -12272,7 +12573,10 @@ function finApplyTraderFields(s, symbolsOverride, role, isMain) {
12272
12573
  s.financeShadow = !!f.shadowMode; /* shadow modda işlem araçları emir göndermez */
12273
12574
  {
12274
12575
  const lim = finLimitsSnapshot();
12275
- s.financeLimits = { minLot: lim.minLot, maxLot: lim.maxLot, maxPositions: lim.maxPositions, symbolLimits: lim.symbols };
12576
+ s.financeLimits = {
12577
+ minLot: lim.minLot, maxLot: lim.maxLot, maxPositions: lim.maxPositions,
12578
+ symbolLimits: lim.symbols, intervalSec: lim.intervalSec, intervalBase: lim.intervalBase, paceUntil: lim.paceUntil,
12579
+ };
12276
12580
  }
12277
12581
  /* ZORUNLU TEK skill: rol ajanı → roleSkills[rol], ana trader → roleSkills.trader */
12278
12582
  const skillKey = r || (s.financePlaybook ? 'trader' : '');
@@ -12351,7 +12655,7 @@ function finAgentStop(sid, reason, opts) {
12351
12655
  }
12352
12656
  /* ANA TRADER DURDU: ekip cycle'sız kaldı — kendi timer'ıyla dönmeye devam etsin */
12353
12657
  if (agent.main) {
12354
- const iv = Math.max(30, Number(finCfg().intervalSec) || 120) * 1000;
12658
+ const iv = finPaceSec() * 1000;
12355
12659
  for (const [tsid, ta] of financeState.agents) {
12356
12660
  if (!ta || !ta.role) continue;
12357
12661
  if (engine && engine.isBusy(tsid)) continue;
@@ -12368,13 +12672,13 @@ function finTraderBrief(agent) {
12368
12672
  const syms = agent && agent.symbols && agent.symbols.length ? agent.symbols.join(', ') : (f.symbols || []).join(', ');
12369
12673
  const roleDef = finRoleDef(agent && agent.role);
12370
12674
  const who = agent && agent.main ? 'TRADER' : roleDef ? roleDef.label.toUpperCase() : 'FINANCE AJANI';
12371
- const playbook = agent && agent.main && f.roleSkills && f.roleSkills.trader && f.roleSkills.trader[0]
12372
- ? f.roleSkills.trader[0]
12675
+ const playbook = agent && agent.main && f.roleSkills && Array.isArray(f.roleSkills.trader)
12676
+ ? f.roleSkills.trader.filter(Boolean).join(' + ')
12373
12677
  : '';
12374
12678
  return [
12375
12679
  `Beast Finance ${who} başlatıldı — ilk tur: strateji çerçeveni kur ve piyasa taramasını yap.`,
12376
12680
  `Odak semboller: ${syms || '(boş — mt5_status ile terminale bak, mantıklı semboller seç)'}`,
12377
- `Tur aralığı: ${f.intervalSec} sn · Lot aralığı: ${f.minLot}–${f.maxLot} (genel taban/tavan; sembol bazlı limit daraltır) · Max eşzamanlı pozisyon: ${f.maxPositions}`,
12681
+ `Tur aralığı: ${finPaceSec()} sn${Number(financeState.pace && financeState.pace.until) > Date.now() ? ` (HIZ MODU — taban ${f.intervalSec} sn)` : ''} · Lot aralığı: ${f.minLot}–${f.maxLot} (genel taban/tavan; sembol bazlı limit daraltır) · Max eşzamanlı pozisyon: ${f.maxPositions}`,
12378
12682
  (() => {
12379
12683
  const rows = Object.entries(f.symbolLimits || {}).map(([s, v]) => {
12380
12684
  const mn = Number(v && v.minLot) > 0 ? Number(v.minLot) : Number(f.minLot);
@@ -12384,20 +12688,20 @@ function finTraderBrief(agent) {
12384
12688
  return rows.length ? `SEMBOL LOT LİMİTLERİN (senin kararın): ${rows.join(' · ')}` : '';
12385
12689
  })(),
12386
12690
  agent && agent.main
12387
- ? `LİMİT YETKİN: volatilite/performansa göre mt5_limits ile SEN güncellersin (anında geçerli): genel {action:"set", minLot?, maxLot?, maxPositions?}; SEMBOL BAZLI {action:"set", symbol:"XAUUSD", minLot?, maxLot?} — her sembolün min/max lotunu kendi karakterine göre SEN belirle (volatilite, spread, marj); sembolü genele döndürmek için {action:"set", symbol:"XAUUSD", reset:true}.`
12691
+ ? `LİMİT+RİTİM YETKİN: mt5_limits ile SEN güncellersin (anında geçerli): genel {action:"set", minLot?, maxLot?, maxPositions?}; SEMBOL BAZLI {action:"set", symbol:"XAUUSD", minLot?, maxLot?} — her sembolün min/max lotunu kendi karakterine göre SEN belirle (volatilite, spread, marj); sembolü genele döndürmek için {action:"set", symbol:"XAUUSD", reset:true}. TUR RİTMİ: {action:"set", intervalSec:45, intervalForMin:15} → sert hareket/haber anında geçici hızlan, süre bitince taban aralığa OTOMATİK döner; sakinleşince geri çek (ör. 180-300 sn) — SÜREKLİ en düşük aralıkta kalma (API/maliyet limiti).`
12388
12692
  : '',
12389
12693
  roleDef
12390
12694
  ? `UZMANLIK: ${roleDef.desc} — raporlarını bu çerçevede yaz; İŞLEM AÇMA, yalnız analiz + net öneri üret.`
12391
- : '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ı.',
12695
+ : '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ı. LOT KORKUSU YOK: önemli olan lot değil SL YERİ ve risk %’sidir — yakın stoplu net setupta yüksek lot NORMALDİR (boyutu risk hesabı verir).',
12392
12696
  roleDef
12393
12697
  ? 'Bulgularını agent_dm ile ANA TRADER\u2019a bildir (to: "Trader" ya da ajan başlığı anahtarı); teknik/öneri çelişkisi varsa gerekçenle yaz.'
12394
12698
  : f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : 'Strateji notu yok: trend + destek/direnç + momentum ile temel okuma yap.',
12395
12699
  roleDef && f.strategy ? `Sahibinin strateji notu (bu çerçevede analiz et): ${f.strategy}` : '',
12396
- playbook ? `PLAYBOOK (ZORUNLU skill): skill("${playbook}") — sistem promptunda tam metni verilmiştir; her turda birebir uygula.` : '',
12700
+ playbook ? `PLAYBOOK (ZORUNLU skill): ${playbook} — sistem promptunda tam metinleri verilmiştir; her turda birebir uygula.` : '',
12397
12701
  f.shadowMode ? 'SHADOW MOD AÇIK: emir gönderilmez — kararlarını gerekçesiyle raporla, gerçek işlem açılmaz.' : '',
12398
12702
  'Bu turda: mt5_status → hesap/pozisyon/fiyat verisi → mt5_rates/mt5_indicators ile teknik okuma → değerlendirme → kararlar (veya BEKLE: sebep) → kısa rapor.',
12399
12703
  'Önemli kararların gerekçesini mt5_note ile günlüğe yaz (haftalık performans raporu bu notları kullanır).',
12400
- 'ÖĞRENME (SEÇİCİ): yalnız gerçekten öğrenilmiş, tekrar kullanılabilir içgörüyü tek KISA cümleyle mt5_ogrenme {action:"add", symbol, text, kind} ile kaydet (günde en fazla 2-3); her kapanışa ders yazma, günlük/uzun metin kaydetme; yeni karar öncesi mt5_ogrenme {action:"stats"/"list", symbol} ile geçmişi oku.',
12704
+ 'ÖĞRENME (ZORUNLU DÖNGÜ): işlem açarken timeframe ver (SEN seçersin: M1..MN1) — kapanış otomatik o periyoda işlenir. Zarar/stop olan her kapanışta mt5_ogrenme {action:"add", symbol, timeframe, kind:"mistake", text:"yanlış neydi + kaçınma kuralı"} ile hata dersini MUTLAKA yaz; işe yarayan deseni kind:"pattern" ile tek kısa cümle olarak kaydet (her kapanışa değil). Karar öncesi mt5_ogrenme {action:"stats"/"list", symbol} ile periyot istatistiğini ve son hataları oku; NEGATİF periyotta riski düşür, en iyi periyodu tercih et, aynı hatayı tekrarlama.',
12401
12705
  'Risk otomasyonu main süreçte 5 sn döngüyle çalışır (+R BE, trailing, KÂR KORUMA: kâr 0.3R\'de kilitlenir, kısmi TP) — TP için RR ≥ 1.5 şartı YOK; 0.3-0.5R\'de kısmi kâr alıp kalanı korumalı trailing ile taşı (hızlı kâr toplama), SL/TP seviyelerini yine sen aktif yönet.',
12402
12706
  '⚡HIZLI AKSİYON: onaylı fırsatta market buy/sell ile ANINDA gir (mt5_trade {symbol, side, sl, tp, riskPct}); bekleyen emir vermek ZORUNLU DEĞİL — limit/stop yalnız seviye beklemede kurulur.',
12403
12707
  'Diğer finance/paralel ajanlarla koordinasyon için agent_dm aracı var (to: ajan başlığı anahtar kelimesi).',
@@ -12535,7 +12839,7 @@ function finTeamHasMain() {
12535
12839
 
12536
12840
  /* Takılma valfi: ekip turu bu süreyi aşarsa trader eski raporla başlar */
12537
12841
  function finTeamWaitMaxMs() {
12538
- const iSec = Math.max(30, Number(finCfg().intervalSec) || 120);
12842
+ const iSec = finPaceSec();
12539
12843
  return Math.max(60, Math.min(300, iSec)) * 1000;
12540
12844
  }
12541
12845
 
@@ -12623,8 +12927,8 @@ function finAgentRound(sid, opts) {
12623
12927
  ? 'SHADOW MOD AÇIK: emir GÖNDERİLMEZ — kararını teziyle raporla (günlüğe yazılır).'
12624
12928
  : 'İşlem açabilirsin — limitlere uy, SL\u2019siz pozisyon bırakma.';
12625
12929
  const learnTip = roleDef
12626
- ? ' İlgili sembolün geçmiş derslerini (mt5_ogrenme) oku ve önerine yansıt.'
12627
- : ' Yalnız gerçekten öğretici, tekrar kullanılabilir bir içgörü varsa TEK kısa cümleyle mt5_ogrenme add ile kaydet (her kapanışa ders yazma); karar öncesi mt5_ogrenme stats/list ile o sembolün geçmişini oku' + (agent.main ? '; volatiliteye göre mt5_limits ile genel ya da SEMBOL BAZLI (symbol) lot limitlerini güncelleyebilirsin.' : '.');
12930
+ ? ' İlgili sembolün geçmiş derslerini ve PERİYOT istatistiğini (mt5_ogrenme stats/list) oku; önerine yansıt (hangi zaman dilimi kazandırıyor).'
12931
+ : ' Karar öncesi mt5_ogrenme stats/list ile sembolün PERİYOT istatistiğini ve son hatalarını oku; işlem açarken timeframe ver (SEN seçersin) — NEGATİF periyotta riski düşür/teyit ara, en iyi periyodu kullan; yalnız kalıcı içgörüyü mt5_ogrenme add ile yaz, ZARAR/stop sonrası hata dersini (kind:"mistake") mutlaka çıkar' + (agent.main ? '; mt5_limits ile lot/pozisyon limitlerini ve TUR RİTMİNİ (intervalSec+intervalForMin) güncelleyebilirsin.' : '.');
12628
12932
  const focus = agent.symbols.length ? `Odak: ${agent.symbols.join(', ')}. ` : '';
12629
12933
  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. ⚡Onaylı fırsatta market buy/sell ile ANINDA gir (bekleyen emir ZORUNLU DEĞİL); lot için volume yerine riskPct+sl yeter.'} ${auto}${learnTip} Önemli kararların gerekçesini mt5_note ile günlüğe yaz. Kısa rapor ver.`;
12630
12934
  const launch = (planBlock) => {
@@ -12719,8 +13023,7 @@ function finFlushOnDone(ev) {
12719
13023
  sıradaki turu trader'ın cycle'ı başlatır (rapor tazeliği). Trader yoksa
12720
13024
  ekip kendi timer'ıyla dönmeye devam eder. */
12721
13025
  if (agent.role && finTeamHasMain()) return;
12722
- const f = finCfg();
12723
- const iv = Math.max(30, Number(f.intervalSec) || 120) * 1000;
13026
+ const iv = finPaceSec() * 1000;
12724
13027
  financeState.lastRoundAt = Date.now();
12725
13028
  if (agent.main) {
12726
13029
  finPush('trader', { state: 'idle', round: agent.round, nextInSec: iv / 1000 });
@@ -12915,17 +13218,8 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
12915
13218
  }
12916
13219
  if (p.intervalSec !== undefined) f.intervalSec = Math.max(30, Math.min(3600, Math.round(Number(p.intervalSec) || 120)));
12917
13220
  /* TUR ARALIĞI CANLI: bekleyen zamanlayıcılar yeni aralıkla yeniden kurulur —
12918
- koşan tur bitince zaten finFlushOnDone yeni aralığı okur. */
12919
- if (p.intervalSec !== undefined && financeState.agents) {
12920
- const iv = Math.max(30, Math.round(Number(f.intervalSec) || 120)) * 1000;
12921
- const hasMain = finTeamHasMain();
12922
- for (const [sid, a] of financeState.agents) {
12923
- if (!a || !engine || engine.isBusy(sid)) continue;
12924
- if (a.role && hasMain) continue; /* cycle modu: ekip turunu trader başlatır */
12925
- clearTimeout(a.timer);
12926
- a.timer = setTimeout(() => { try { finAgentRound(String(sid)); } catch {} }, iv);
12927
- }
12928
- }
13221
+ koşan tur bitince zaten finFlushOnDone yeni ritmi okur. */
13222
+ if (p.intervalSec !== undefined) { try { finRescheduleAgents(); } catch {} }
12929
13223
  if (p.maxLot !== undefined) f.maxLot = Math.max(0.01, Math.min(100, Number(p.maxLot) || 0.1));
12930
13224
  if (p.minLot !== undefined) f.minLot = Math.max(0.01, Math.min(100, Number(p.minLot) || 0.01));
12931
13225
  /* min lot max lotu aşamaz — hangisi sonra yazıldıysa diğerine kelepçelenir */
@@ -12993,13 +13287,13 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
12993
13287
  if (p.analysisTeam !== undefined) f.analysisTeam = finRolesValid(p.analysisTeam);
12994
13288
  if (p.analysisAuto !== undefined) f.analysisAuto = !!p.analysisAuto;
12995
13289
  if (p.analysisCount !== undefined) f.analysisCount = Math.max(0, Math.min(FIN_ROLES_AUTO.length, Math.round(Number(p.analysisCount) || 0)));
12996
- /* ROL → SKILL eşleştirmesi güncellemesi (modal): rol başına TEK skill +
12997
- ana trader playbook'u (trader) */
13290
+ /* ROL → SKILL eşleştirmesi güncellemesi (modal): rol başına EN FAZLA 2
13291
+ skill + ana trader playbook'u (trader) */
12998
13292
  if (p.roleSkills !== undefined && p.roleSkills && typeof p.roleSkills === 'object') {
12999
13293
  for (const id of [...FIN_ROLES.map((d) => d.id), 'trader']) {
13000
13294
  if (p.roleSkills[id] === undefined) continue;
13001
13295
  f.roleSkills[id] = (Array.isArray(p.roleSkills[id]) ? p.roleSkills[id] : [])
13002
- .map((s) => String(s || '').trim()).filter(Boolean).slice(0, 1);
13296
+ .map((s) => String(s || '').trim()).filter(Boolean).slice(0, 2);
13003
13297
  }
13004
13298
  /* koşan rol ajanlarını da tazele — seçim sonraki turdan itibaren sistem
13005
13299
  promptuna girer (ajanı durdurmaya gerek yok) */