beast-agent 2.63.1 → 2.63.3

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
@@ -2356,6 +2356,7 @@ function botToolSet(cfg) {
2356
2356
  'tool_request', /* CEPHANE: eksik aracı TOOL botuna yazdırma hakkı HER botta */
2357
2357
  'channel_send', /* İLK MESAJ: allow listteki kişilere kendiliğinden yazma hakkı HER botta */
2358
2358
  'agent_dm', /* BOTLAR ARASI DM: botlar birbirine iş atar/cevap verir (ör. Beast → Tool) */
2359
+ 'skill', /* SKILL KATALOĞU: botlar SKILL.md gövdesini skill() ile okur (Tool botu: tool-yazma/mql5) */
2359
2360
  ]);
2360
2361
  if (s.web_search) { set.add('web_search'); set.add('http_fetch'); set.add('webfetch'); set.add('deep_search'); }
2361
2362
  if (s.browser) {
@@ -2363,7 +2364,7 @@ function botToolSet(cfg) {
2363
2364
  }
2364
2365
  if (s.email) { set.add('email_list'); set.add('email_read'); set.add('email_send'); }
2365
2366
  if (s.run_command) {
2366
- for (const t of ['run_command', 'python_run', 'read_file', 'write_file', 'list_dir', 'computer_look', 'computer_act',
2367
+ for (const t of ['run_command', 'python_run', 'read_file', 'write_file', 'edit_file', 'grep', 'glob', 'list_dir', 'computer_look', 'computer_act',
2367
2368
  'git_commit', 'git_diff_review', 'git_pr_create', 'repo_map', 'repo_symbols', 'xlsx_read', 'xlsx_write', 'xlsx_edit']) set.add(t);
2368
2369
  }
2369
2370
  if (s.memory) { set.add('memory_write'); set.add('user_write'); set.add('memory_search'); set.add('memory_hygiene'); }
@@ -4323,6 +4324,43 @@ function detachBrowser() {
4323
4324
  browser.attached = false;
4324
4325
  }
4325
4326
 
4327
+ /* TARAYICI YENİDEN BAŞLATMA: takılan/kilitlenen sayfa için view + webContents
4328
+ tamamen kapatılır, aynı URL YENİ bir view'da açılır. persist:browser
4329
+ partition'ı silinmez → oturum/çerezler (Google, X vb.) korunur. */
4330
+ function restartBrowser() {
4331
+ if (!win || win.isDestroyed()) return { ok: false, error: 'pencere yok' };
4332
+ const wasOpen = browser.open;
4333
+ let url = '';
4334
+ try {
4335
+ if (browser.view && browser.view.webContents && !browser.view.webContents.isDestroyed()) {
4336
+ url = browser.view.webContents.getURL();
4337
+ }
4338
+ } catch {}
4339
+ detachBrowser();
4340
+ try {
4341
+ const oldWc = browser.view && browser.view.webContents;
4342
+ if (oldWc && !oldWc.isDestroyed()) {
4343
+ try { oldWc.close(); } catch { try { oldWc.destroy(); } catch {} }
4344
+ /* takılı renderer süreci close()'u geciktirebilir — güvence: kısa süre
4345
+ sonra hâlâ duruyorsa zorla yok et (yeni view'ı asla kilitlemez) */
4346
+ setTimeout(() => { try { if (!oldWc.isDestroyed()) oldWc.destroy(); } catch {} }, 1500);
4347
+ }
4348
+ } catch {}
4349
+ browser.view = null;
4350
+ browser.started = false;
4351
+ browser.lastDialog = null;
4352
+ if (wasOpen) {
4353
+ ensureBrowser();
4354
+ browser.started = true;
4355
+ browser.view.webContents.loadURL(url || BROWSER_START_URL).catch(() => {});
4356
+ layoutBrowser();
4357
+ const [w] = win.getContentSize();
4358
+ browserEmit({ open: true, width: browserShownWidth(w), url: url || BROWSER_START_URL, restarted: true });
4359
+ }
4360
+ blog('restart', 'tarayıcı yeniden başlatıldı' + (url ? ' — ' + url.slice(0, 90) : ''));
4361
+ return { ok: true, url };
4362
+ }
4363
+
4326
4364
  function setBrowserOpen(v, forceVisible) {
4327
4365
  if (!win || win.isDestroyed()) return;
4328
4366
  const want = !!v;
@@ -8399,6 +8437,7 @@ ipcMain.handle('browser:ctrl', (_e, action) => {
8399
8437
  setBrowserOpen(false);
8400
8438
  return { ok: true };
8401
8439
  }
8440
+ if (action === 'restart') return restartBrowser();
8402
8441
  const wc = browser.view && browser.view.webContents;
8403
8442
  if (!wc) return { ok: false };
8404
8443
  try {
@@ -9371,6 +9410,33 @@ function financeDir() {
9371
9410
  return d;
9372
9411
  }
9373
9412
 
9413
+ /* SEMBOL BAZLI LOT LİMİTLERİ: trader mt5_limits symbol ile koyar; anahtar =
9414
+ sembol (XAUUSD…). Genel aralık taban/tavan olarak zorlanır — sembol limiti
9415
+ onun dışına çıkamaz; boş alan genel aralığı kullanır. Her okuma/yazmada
9416
+ normalize edilir (bozuk/eski kayıtlar temizlenir). */
9417
+ function finNormalizeSymbolLimits(f) {
9418
+ const gMin = Number(f.minLot) || 0.01;
9419
+ const gMax = Number(f.maxLot) || 0.1;
9420
+ if (!f.symbolLimits || typeof f.symbolLimits !== 'object') f.symbolLimits = {};
9421
+ const clean = {};
9422
+ for (const [k, v] of Object.entries(f.symbolLimits).slice(0, 40)) {
9423
+ const sym = String(k || '').trim().toUpperCase();
9424
+ const o = v && typeof v === 'object' ? v : {};
9425
+ if (!sym) continue;
9426
+ let mn = Number(o.minLot) > 0 ? Math.round(Math.max(0.01, Math.min(100, Number(o.minLot))) * 100) / 100 : 0;
9427
+ let mx = Number(o.maxLot) > 0 ? Math.round(Math.max(0.01, Math.min(100, Number(o.maxLot))) * 100) / 100 : 0;
9428
+ if (mn && mx && mn > mx) mn = mx;
9429
+ if (mn) mn = Math.min(Math.max(mn, gMin), gMax);
9430
+ if (mx) mx = Math.max(Math.min(mx, gMax), gMin);
9431
+ if (mn && mx && mn > mx) mn = mx;
9432
+ const lim = {};
9433
+ if (mn) lim.minLot = mn;
9434
+ if (mx) lim.maxLot = mx;
9435
+ if (lim.minLot || lim.maxLot) clean[sym] = lim;
9436
+ }
9437
+ f.symbolLimits = clean;
9438
+ }
9439
+
9374
9440
  function finCfg() {
9375
9441
  if (!settings.finance || typeof settings.finance !== 'object') settings.finance = {};
9376
9442
  const f = settings.finance;
@@ -9409,8 +9475,10 @@ function finCfg() {
9409
9475
  if (!Number(f.intervalSec)) f.intervalSec = 120;
9410
9476
  if (!Number(f.minLot)) f.minLot = 0.01;
9411
9477
  if (!Number(f.maxLot)) f.maxLot = 0.1;
9412
- /* LOT ARALIĞI KILİDİ: min lot hiçbir zaman max lotu aşamaz */
9478
+ /* LOT ARALIĞI KİLİDİ: min lot hiçbir zaman max lotu aşamaz */
9413
9479
  if (f.minLot > f.maxLot) f.minLot = f.maxLot;
9480
+ /* SEMBOL BAZLI LOT LİMİTLERİ (trader kararı — mt5_limits symbol ile koyar) */
9481
+ finNormalizeSymbolLimits(f);
9414
9482
  if (f.maxPositions == null) f.maxPositions = 3;
9415
9483
  /* KODLA DİSİPLİN (trader kuralları): hepsi 0 = kural kapalı */
9416
9484
  if (!Number.isFinite(Number(f.maxTradesPerDay))) f.maxTradesPerDay = 10;
@@ -11209,6 +11277,8 @@ function finLimitsSnapshot() {
11209
11277
  maxLot: Number(f.maxLot) || 0.1,
11210
11278
  maxPositions: Number(f.maxPositions) || 3,
11211
11279
  riskPerTradePct: Number(f.riskPerTradePct) || 0,
11280
+ /* SEMBOL BAZLI LOT LİMİTLERİ (trader kararı) — genel aralığı daraltır */
11281
+ symbols: { ...(f.symbolLimits && typeof f.symbolLimits === 'object' ? f.symbolLimits : {}) },
11212
11282
  };
11213
11283
  }
11214
11284
 
@@ -11223,21 +11293,22 @@ function finLimitsSessionAllowed(sid) {
11223
11293
  return true;
11224
11294
  }
11225
11295
 
11226
- function finSyncLimitsToSessions() {
11296
+ function finSyncLimitsToSessions(extra) {
11227
11297
  const lim = finLimitsSnapshot();
11298
+ const apply = { minLot: lim.minLot, maxLot: lim.maxLot, maxPositions: lim.maxPositions, symbolLimits: lim.symbols };
11228
11299
  if (engine) {
11229
11300
  try {
11230
11301
  for (const [sid] of financeState.agents) {
11231
11302
  const s = engine.cache.get(String(sid));
11232
- if (s) s.financeLimits = { minLot: lim.minLot, maxLot: lim.maxLot, maxPositions: lim.maxPositions };
11303
+ if (s) s.financeLimits = { ...apply };
11233
11304
  }
11234
11305
  for (const [, s] of engine.cache) {
11235
11306
  if (!s || !s.finance || s.bgJob || !s.financeLimits) continue;
11236
- s.financeLimits = { minLot: lim.minLot, maxLot: lim.maxLot, maxPositions: lim.maxPositions };
11307
+ s.financeLimits = { ...apply };
11237
11308
  }
11238
11309
  } catch {}
11239
11310
  }
11240
- finPush('limits', { ...lim });
11311
+ finPush('limits', { ...lim, ...(extra && typeof extra === 'object' ? extra : {}) });
11241
11312
  }
11242
11313
 
11243
11314
  function finLimitsSet(patch, ctx) {
@@ -11247,6 +11318,54 @@ function finLimitsSet(patch, ctx) {
11247
11318
  }
11248
11319
  const f = finCfg();
11249
11320
  const notes = [];
11321
+ /* SEMBOL BAZLI LOT LİMİTİ (symbol verilirse): trader sembole göre min/max lot
11322
+ koyar — genel aralık taban/tavan olarak zorlanır. reset:true (ya da yalnız
11323
+ symbol) → sembol limiti kaldırılır, genel aralık geçerli olur. */
11324
+ const sym = String(patch.symbol || '').trim().toUpperCase();
11325
+ if (sym) {
11326
+ if (patch.maxPositions !== undefined) {
11327
+ return { ok: false, error: 'maxPositions sembol bazlı değil — symbol vermeden genel limit olarak ayarla' };
11328
+ }
11329
+ const hasMin = patch.minLot !== undefined && patch.minLot !== null && patch.minLot !== '';
11330
+ const hasMax = patch.maxLot !== undefined && patch.maxLot !== null && patch.maxLot !== '';
11331
+ const reset = patch.reset === true || String(patch.reset || '').toLowerCase() === 'true';
11332
+ if (!reset && !hasMin && !hasMax) {
11333
+ return { ok: false, error: 'sembol güncellemesi için minLot/maxLot ver ya da reset:true' };
11334
+ }
11335
+ if (!f.symbolLimits || typeof f.symbolLimits !== 'object') f.symbolLimits = {};
11336
+ if (reset) {
11337
+ if (f.symbolLimits[sym]) {
11338
+ delete f.symbolLimits[sym];
11339
+ notes.push('sembol limiti kaldırıldı → genel aralık geçerli');
11340
+ }
11341
+ } else {
11342
+ const cur = { ...(f.symbolLimits[sym] || {}) };
11343
+ if (hasMin) {
11344
+ const v = Number(patch.minLot);
11345
+ if (!(v >= 0.01) || v > 100) return { ok: false, error: 'sembol minLot 0.01-100 arasında olmalı' };
11346
+ cur.minLot = Math.round(v * 100) / 100;
11347
+ }
11348
+ if (hasMax) {
11349
+ const v = Number(patch.maxLot);
11350
+ if (!(v >= 0.01) || v > 100) return { ok: false, error: 'sembol maxLot 0.01-100 arasında olmalı' };
11351
+ cur.maxLot = Math.round(v * 100) / 100;
11352
+ }
11353
+ /* genel aralığa kelepçe: sembol limiti geneli AŞAMAZ/ALTA İNEMEZ */
11354
+ if (cur.minLot !== undefined && cur.minLot < f.minLot) { cur.minLot = f.minLot; notes.push(`minLot genel tabana kelepçelendi (${f.minLot})`); }
11355
+ if (cur.maxLot !== undefined && cur.maxLot > f.maxLot) { cur.maxLot = f.maxLot; notes.push(`maxLot genel tavana kelepçelendi (${f.maxLot})`); }
11356
+ if (cur.minLot !== undefined && cur.maxLot !== undefined && cur.minLot > cur.maxLot) { cur.minLot = cur.maxLot; notes.push(`minLot maxLot'a kelepçelendi (${cur.maxLot})`); }
11357
+ if (cur.minLot === undefined && cur.maxLot === undefined) delete f.symbolLimits[sym];
11358
+ else f.symbolLimits[sym] = cur;
11359
+ }
11360
+ try { saveSettings(); } catch {}
11361
+ const e = f.symbolLimits[sym];
11362
+ const line = `⚙️ SEMBOL LOT LİMİTİ: ${sym} → ${e ? `min ${e.minLot ?? f.minLot} · max ${e.maxLot ?? f.maxLot}` : 'genel aralığa döndü'}${sid ? ' (ajan kararı)' : ''}`;
11363
+ financeLog('[limit] ' + line);
11364
+ finSyncLimitsToSessions({ symbol: sym, note: line });
11365
+ financeNotify(line, 'limit', true);
11366
+ try { finJournal({ kind: 'limits', symbol: sym, minLot: e ? e.minLot : null, maxLot: e ? e.maxLot : null, sid }); } catch {}
11367
+ return { ok: true, symbol: sym, limits: finLimitsSnapshot(), note: notes.length ? notes.join('; ') : undefined };
11368
+ }
11250
11369
  if (patch.maxLot !== undefined) {
11251
11370
  const v = Number(patch.maxLot);
11252
11371
  if (!(v >= 0.01) || v > 100) return { ok: false, error: 'maxLot 0.01-100 arasında olmalı' };
@@ -11280,8 +11399,25 @@ function finLimitsSet(patch, ctx) {
11280
11399
  kazanç/kayıp, net, MFE/MAE, kâr geri verme) (2) ajanın yazdığı dersler.
11281
11400
  Trader turunda bu özet sistem promptuna gömülür — sistem kendi geçmişinden
11282
11401
  öğrenir; her kapanış otomatik işlenir, dersleri ajan mt5_ogrenme ile yazar. */
11283
- const FIN_LEARN_MAX_NOTES = 120;
11402
+ const FIN_LEARN_MAX_NOTES = 60; /* sembol başına ders tavanı */
11284
11403
  const FIN_LEARN_MAX_TRADES = 60;
11404
+ const FIN_LEARN_MAX_SYMBOLS = 60; /* toplam sembol tavanı — depo sınırsız büyümesin */
11405
+ const FIN_LEARN_MAX_NOTES_DAY = 3; /* sembol başına 24 saatte en fazla ders — ders SELİ depoyu zehirlemesin */
11406
+ /* DERS KALİTESİ: "günlük tutar gibi" uzun/genel metinler ders DEĞİLDİR —
11407
+ yalnız kısa, somut, tekrar kullanılabilir bilgi kaydedilir. */
11408
+ const FIN_LEARN_NOTE_MIN = 12; /* çok kısa/genel laf (ör. "iyi işlem") ders sayılmaz */
11409
+ const FIN_LEARN_NOTE_MAX = 200; /* tek kısa cümle tavanı (karakter) */
11410
+ /* SÜREKLİ UNUTMA (bellek hijyeni): eski kayıtlar otomatik düşer — depo büyüyüp
11411
+ zehirlenmez, yalnız GÜNCEL dersler ve son performans kalır. */
11412
+ const FIN_LEARN_NOTE_TTL_MS = 30 * 24 * 60 * 60 * 1000; /* 30 gün: eski ders unutulur */
11413
+ const FIN_LEARN_TRADE_TTL_MS = 90 * 24 * 60 * 60 * 1000; /* 90 gün: eski işlem kaydı düşer */
11414
+ const FIN_LEARN_SYMBOL_TTL_MS = 120 * 24 * 60 * 60 * 1000; /* 120 gün aktivite yoksa sembol kaydı silinir */
11415
+ /* ÖZETLEME (opencode tarzı compaction): ham dersler birikince ESKİ olanlar
11416
+ modele özetlettirilir → tek kalıcı özet; ham yalnız son dersler kalır. */
11417
+ const FIN_LEARN_COMPACT_AT = 24; /* not sayısı bunu aşınca arka planda otomatik özetle */
11418
+ const FIN_LEARN_KEEP_RECENT = 8; /* ham kalan son ders sayısı */
11419
+ const FIN_LEARN_SUMMARY_MAX = 800; /* kalıcı özet metin tavanı (karakter) */
11420
+ const FIN_LEARN_COMPACT_MIN = 4; /* elle özetleme için gereken en az eski ders */
11285
11421
 
11286
11422
  function finLearnEmptyStats() {
11287
11423
  return {
@@ -11291,6 +11427,84 @@ function finLearnEmptyStats() {
11291
11427
  };
11292
11428
  }
11293
11429
 
11430
+ /* Ders metni karşılaştırma anahtarı: büyük/küçük harf, noktalama ve boşluk
11431
+ farkları aynı ders sayılır — ajan aynı cümleyi kopyalayıp depoyu doldurmasın */
11432
+ function finLearnNoteKey(text) {
11433
+ return String(text || '')
11434
+ .toLowerCase()
11435
+ .replace(/[^\p{L}\p{N}\s]+/gu, ' ')
11436
+ .replace(/\s+/g, ' ')
11437
+ .trim()
11438
+ .slice(0, 140);
11439
+ }
11440
+
11441
+ /* DERS KALİTE KAPISI: gerçekten öğrenilmiş, işe yarar bilgi kısa cümleyle;
11442
+ günlük/özet tarzı metin ya da genel laf kaydedilmez. Hata metni ya da null. */
11443
+ function finLearnNoteQuality(text) {
11444
+ const t = String(text || '').replace(/\s+/g, ' ').trim();
11445
+ if (t.length < FIN_LEARN_NOTE_MIN) return 'ders çok kısa — tek cümlede somut ders yaz (ör. "Londra açılışı M5 EMA50 üstü momentum işledi")';
11446
+ if (t.length > FIN_LEARN_NOTE_MAX) return `ders çok uzun (${t.length} karakter) — en fazla ${FIN_LEARN_NOTE_MAX} karakterlik TEK kısa cümle yaz; günlük/rapor metnini ders diye kaydetme`;
11447
+ if (/^(bug[üu]n|g[üu]nl[üu]k|bu sabah|bu ak[şs]am|sabah|ak[şs]am)\b/i.test(t)) return 'günlük tarzı metin ders değildir — yalnız tekrar kullanılabilir somut ders yaz';
11448
+ /* GENEL LAFLAR: kanıtsız/tavsiye cümleleri ders değildir (somut setup/ölçüm iste) */
11449
+ if (/(dikkatli ol|dikkat et|sab[ıi]rl[ıi] ol|panik yapma|duygular[ıi]na kap[ıi]lma|disiplinli ol|temkinli ol)/i.test(t)) {
11450
+ return 'genel tavsiye ders değildir — hangi setup/koşulda ne olduğunu somut yaz (ör. sembol+periyot+seviye+sonuç)';
11451
+ }
11452
+ return null;
11453
+ }
11454
+
11455
+ /* sembolün son aktivitesi (kapanış / ders / eski istatistik) — tavan aşımında
11456
+ EN ESKİ semboller düşürülür; dokunulan (protectSym) sembol asla atılmaz */
11457
+ function finLearnActivity(e) {
11458
+ const last = (arr) => (Array.isArray(arr) && arr.length ? Number(arr[arr.length - 1].at) || 0 : 0);
11459
+ return Math.max(Number((e.stats || {}).lastAt) || 0, last(e.notes), last(e.trades));
11460
+ }
11461
+
11462
+ function finLearnPrune(learn, keep, protectSym) {
11463
+ const cap = Number.isFinite(Number(keep)) ? Number(keep) : FIN_LEARN_MAX_SYMBOLS;
11464
+ const keys = Object.keys(learn.symbols);
11465
+ if (keys.length <= cap) return 0;
11466
+ const protect = String(protectSym || '').trim().toUpperCase();
11467
+ const dropped = keys
11468
+ .sort((a, b) => {
11469
+ if (a === protect) return 1;
11470
+ if (b === protect) return -1;
11471
+ return finLearnActivity(learn.symbols[a]) - finLearnActivity(learn.symbols[b]);
11472
+ })
11473
+ .slice(0, keys.length - cap);
11474
+ for (const k of dropped) delete learn.symbols[k];
11475
+ return dropped.length;
11476
+ }
11477
+
11478
+ /* SÜREKLİ UNUTMA: TTL'i geçen ders/işlem kayıtları düşer; uzun süre hiç
11479
+ aktivite görmeyen sembol kaydı (istatistik dahil) silinir. Her yazma ve
11480
+ yüklemede çalışır → depo kendiliğinden sade kalır, birikme/zehirlenme olmaz. */
11481
+ function finLearnForget(learn, now) {
11482
+ const t = Number(now) || Date.now();
11483
+ const stat = { notes: 0, trades: 0, symbols: 0 };
11484
+ for (const [sym, e] of Object.entries(learn.symbols)) {
11485
+ if (!e || typeof e !== 'object') { delete learn.symbols[sym]; stat.symbols++; continue; }
11486
+ const notes = Array.isArray(e.notes) ? e.notes : [];
11487
+ e.notes = notes.filter((n) => n && t - (Number(n.at) || 0) <= FIN_LEARN_NOTE_TTL_MS);
11488
+ stat.notes += notes.length - e.notes.length;
11489
+ const trades = Array.isArray(e.trades) ? e.trades : [];
11490
+ e.trades = trades.filter((x) => x && t - (Number(x.at) || 0) <= FIN_LEARN_TRADE_TTL_MS);
11491
+ stat.trades += trades.length - e.trades.length;
11492
+ if (t - finLearnActivity(e) > FIN_LEARN_SYMBOL_TTL_MS) {
11493
+ delete learn.symbols[sym];
11494
+ stat.symbols += 1;
11495
+ }
11496
+ }
11497
+ return stat;
11498
+ }
11499
+
11500
+ /* TEK TEMİZLİK: önce TTL unutması, sonra sembol tavanı. protectSym (yeni
11501
+ dokunulan sembol) asla düşürülmez. */
11502
+ function finLearnTidy(learn, protectSym, now) {
11503
+ const forgot = finLearnForget(learn, now);
11504
+ const cap = finLearnPrune(learn, FIN_LEARN_MAX_SYMBOLS, protectSym);
11505
+ return { notes: forgot.notes, trades: forgot.trades, symbols: forgot.symbols + cap };
11506
+ }
11507
+
11294
11508
  function finLearnLoad() {
11295
11509
  if (financeState.learnReady) return financeState.learn;
11296
11510
  financeState.learnReady = true;
@@ -11305,21 +11519,128 @@ function finLearnLoad() {
11305
11519
  notes: Array.isArray(v.notes) ? v.notes.filter((n) => n && n.id && n.text).slice(-FIN_LEARN_MAX_NOTES) : [],
11306
11520
  trades: Array.isArray(v.trades) ? v.trades.filter(Boolean).slice(-FIN_LEARN_MAX_TRADES) : [],
11307
11521
  stats: v.stats && typeof v.stats === 'object' ? { ...finLearnEmptyStats(), ...v.stats } : finLearnEmptyStats(),
11522
+ /* KALICI ÖZET (opencode tarzı sıkıştırma): eski derslerin modele
11523
+ özetlettirilmiş hâli — ham dersler düşse de bilgi kalır */
11524
+ summary: v.summary && typeof v.summary === 'object' && String(v.summary.text || '').trim()
11525
+ ? {
11526
+ text: String(v.summary.text).replace(/\s+/g, ' ').trim().slice(0, FIN_LEARN_SUMMARY_MAX),
11527
+ at: Number(v.summary.at) || 0,
11528
+ covered: Math.max(0, Math.round(Number(v.summary.covered) || 0)),
11529
+ }
11530
+ : null,
11308
11531
  };
11309
11532
  }
11310
11533
  if (Number(raw.updatedAt)) learn.updatedAt = Number(raw.updatedAt);
11311
11534
  }
11312
11535
  } catch {}
11536
+ const dropped = finLearnTidy(learn); /* yükleme anında unut + tavan uygula */
11313
11537
  financeState.learn = learn;
11538
+ if (dropped.notes || dropped.trades || dropped.symbols) finLearnSave(); /* disk de hemen küçülsün */
11314
11539
  return learn;
11315
11540
  }
11316
11541
 
11317
11542
  function finLearnSave() {
11318
11543
  const learn = finLearnLoad();
11544
+ finLearnTidy(learn); /* her yazmada sürekli unutma */
11319
11545
  learn.updatedAt = Date.now();
11320
11546
  try { finWriteJson(finFile('ogrenme.json'), learn); } catch {}
11321
11547
  }
11322
11548
 
11549
+ /* ---------- OPENCODE TARZI ÖZETLEME (COMPACTION) ----------
11550
+ Ham dersler birikince ESKİ dersler modele özetlettirilir: çalışan desenler,
11551
+ kaçınılacak hatalar, kurallar tek KALICI ÖZET'e iner; ham olarak yalnız son
11552
+ dersler kalır. Böylece öğrenme hafızası hem küçük hem zehirsiz kalır —
11553
+ context penceresi sıkıştırmasının sembol bazlı hâli. */
11554
+ const FIN_LEARN_COMPACT_SYSTEM =
11555
+ 'Sen bir trading mentorüsün. Sana bir sembolün ESKİ ders notları ve mevcut özeti verilir. ' +
11556
+ 'Görevin: güncellenmiş KISA bir özet üretmek.\n' +
11557
+ '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 ' +
11559
+ '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. ' +
11562
+ '(6) Çıktı SADECE özet metni olsun; başlık/etiket/açıklama yazma.';
11563
+
11564
+ const finLearnCompacting = new Set();
11565
+
11566
+ function finLearnSummaryPrompt(sym, e, oldNotes) {
11567
+ const st = { ...finLearnEmptyStats(), ...(e.stats || {}) };
11568
+ const wr = st.trades ? Math.round((st.wins / st.trades) * 100) : 0;
11569
+ const lines = [];
11570
+ lines.push('SEMBOL: ' + sym);
11571
+ lines.push(
11572
+ `OTOMATİK İSTATİSTİK: ${st.trades} işlem · %${wr} kazanç · net ${st.net >= 0 ? '+' : ''}${st.net}` +
11573
+ (st.givebacks ? ` · ${st.givebacks} kez kâr geri verildi` : '') +
11574
+ (st.streak >= 2 ? ` · ${st.streak} ardışık kayıp` : '')
11575
+ );
11576
+ if (e.summary && e.summary.text) lines.push('MEVCUT ÖZET:\n' + String(e.summary.text).slice(0, 1400));
11577
+ lines.push('ESKİ DERSLER (kronolojik — en yeni EN SONDA):');
11578
+ for (const n of oldNotes.slice(-80)) {
11579
+ lines.push(`- [${String(n.kind || 'observation')}] ${String(n.text || '').slice(0, 220)}`);
11580
+ }
11581
+ lines.push('Görev: yalnız kalıcı ve güncel dersleri koruyan yeni özet metnini yaz.');
11582
+ return lines.join('\n');
11583
+ }
11584
+
11585
+ /* Bir sembolü özetle: snapshot'taki ESKİ dersleri modele özetlet, onları düş,
11586
+ yerine kalıcı özet yaz. Model çalışırken gelen YENİ dersler ham kalır. */
11587
+ async function finLearnCompactSymbol(symbol, force) {
11588
+ const sym = String(symbol || '').trim().toUpperCase();
11589
+ if (!sym) return { ok: false, error: 'symbol gerekli' };
11590
+ if (finLearnCompacting.has(sym)) return { ok: false, error: 'bu sembol şu an özetleniyor' };
11591
+ if (!engine || !engine.sel) return { ok: false, error: 'özetleme için aktif model yok' };
11592
+ const e = finLearnLoad().symbols[sym];
11593
+ if (!e) return { ok: false, error: 'sembol kaydı yok: ' + sym };
11594
+ const oldNotes = e.notes.slice(0, Math.max(0, e.notes.length - FIN_LEARN_KEEP_RECENT));
11595
+ const min = force ? FIN_LEARN_COMPACT_MIN : FIN_LEARN_COMPACT_AT - FIN_LEARN_KEEP_RECENT;
11596
+ if (oldNotes.length < min) {
11597
+ return { ok: false, error: `özetlenecek yeterli eski ders yok (${oldNotes.length}/${min})` };
11598
+ }
11599
+ finLearnCompacting.add(sym);
11600
+ try {
11601
+ const ctrl = new AbortController();
11602
+ const kill = setTimeout(() => ctrl.abort(), 60000);
11603
+ let r = null;
11604
+ try {
11605
+ r = await require('./agent/llm').chatOnce(engine.sel, {
11606
+ messages: [
11607
+ { role: 'system', content: FIN_LEARN_COMPACT_SYSTEM },
11608
+ { role: 'user', content: finLearnSummaryPrompt(sym, e, oldNotes) },
11609
+ ],
11610
+ temperature: 0.2,
11611
+ }, { signal: ctrl.signal });
11612
+ } catch (err) {
11613
+ return { ok: false, error: 'özetleme çağrısı başarısız: ' + String((err && err.message) || err).slice(0, 120) };
11614
+ } finally {
11615
+ clearTimeout(kill);
11616
+ }
11617
+ const text = String((r && r.content) || '').replace(/\s+/g, ' ').trim().slice(0, FIN_LEARN_SUMMARY_MAX);
11618
+ if (!text) return { ok: false, error: 'özet üretilemedi (model yanıtı boş)' };
11619
+ /* yarışma koruması: yalnız snapshot'taki eski dersleri düşür */
11620
+ const fresh = finLearnLoad().symbols[sym];
11621
+ if (!fresh) return { ok: false, error: 'sembol kaydı kalmadı' };
11622
+ const drop = new Set(oldNotes.map((n) => String(n.id)));
11623
+ const before = fresh.notes.length;
11624
+ fresh.notes = fresh.notes.filter((n) => !drop.has(String(n.id)));
11625
+ const covered = before - fresh.notes.length;
11626
+ fresh.summary = {
11627
+ text,
11628
+ at: Date.now(),
11629
+ covered: (Number(fresh.summary && fresh.summary.covered) || 0) + covered,
11630
+ };
11631
+ finLearnSave();
11632
+ financeLog(`[öğrenme] ${sym} özetlendi: ${covered} eski ders → kalıcı özet (${text.length} karakter)`);
11633
+ return { ok: true, symbol: sym, covered, remaining: fresh.notes.length, summary: text };
11634
+ } finally {
11635
+ finLearnCompacting.delete(sym);
11636
+ }
11637
+ }
11638
+
11639
+ /* Arka plan tetikleyici: ajanı BEKLETMEDEN özetler (notlar birikince) */
11640
+ function finLearnCompactAuto(sym) {
11641
+ try { finLearnCompactSymbol(sym, false).catch(() => {}); } catch {}
11642
+ }
11643
+
11323
11644
  function finLearnSym(symbol) {
11324
11645
  const sym = String(symbol || '').trim().toUpperCase();
11325
11646
  if (!sym) return null;
@@ -11367,6 +11688,7 @@ function finLearnRecordClose({ symbol, side, net, mfe, mae, reason }) {
11367
11688
  reason: String(reason || '').slice(0, 40),
11368
11689
  });
11369
11690
  while (e.trades.length > FIN_LEARN_MAX_TRADES) e.trades.shift();
11691
+ finLearnTidy(finLearnLoad(), symbol);
11370
11692
  finLearnSave();
11371
11693
  } catch {}
11372
11694
  }
@@ -11389,6 +11711,11 @@ function finBuildLearnDigest(symbols) {
11389
11711
  if (st.lastAt) parts.push(`son: ${st.lastNet >= 0 ? '+' : ''}${st.lastNet}`);
11390
11712
  L.push(`- ${sym}: ${parts.join(' · ')}`);
11391
11713
  }
11714
+ /* KALICI ÖZET (compaction): eski derslerin sıkıştırılmış hâli — ham ders
11715
+ sayısı azalsa da birikmiş bilgi prompta girer */
11716
+ if (e.summary && e.summary.text) {
11717
+ L.push(` kalıcı özet: "${String(e.summary.text).slice(0, 500)}"`);
11718
+ }
11392
11719
  if (e.notes.length) {
11393
11720
  const last = e.notes[e.notes.length - 1];
11394
11721
  const extra = e.notes.length > 1 ? ` (+${e.notes.length - 1} ders)` : '';
@@ -11401,19 +11728,21 @@ function finBuildLearnDigest(symbols) {
11401
11728
  function finLearnApi() {
11402
11729
  const norm = (s) => String(s || '').trim().toUpperCase();
11403
11730
  return {
11404
- list: ({ symbol } = {}) => {
11731
+ list: ({ symbol, limit } = {}) => {
11405
11732
  const learn = finLearnLoad();
11406
11733
  const sym = norm(symbol);
11734
+ const take = Math.max(1, Math.min(25, Math.round(Number(limit) || 8)));
11407
11735
  if (sym) {
11408
11736
  const e = learn.symbols[sym];
11409
- if (!e) return { ok: true, symbol: sym, count: 0, notes: [], trades: [], stats: finLearnEmptyStats() };
11737
+ if (!e) return { ok: true, symbol: sym, count: 0, notes: [], trades: [], stats: finLearnEmptyStats(), summary: null };
11410
11738
  return {
11411
11739
  ok: true,
11412
11740
  symbol: sym,
11413
11741
  count: e.notes.length,
11414
- notes: e.notes.slice(-25),
11742
+ notes: e.notes.slice(-take),
11415
11743
  trades: e.trades.slice(-12),
11416
11744
  stats: { ...finLearnEmptyStats(), ...(e.stats || {}) },
11745
+ summary: e.summary && e.summary.text ? e.summary : null,
11417
11746
  };
11418
11747
  }
11419
11748
  const rows = Object.entries(learn.symbols)
@@ -11422,14 +11751,26 @@ function finLearnApi() {
11422
11751
  notes: e.notes.length,
11423
11752
  stats: { ...finLearnEmptyStats(), ...(e.stats || {}) },
11424
11753
  lastNote: String(((e.notes[e.notes.length - 1] || {}).text) || ''),
11754
+ hasSummary: !!(e.summary && e.summary.text),
11425
11755
  }))
11426
11756
  .sort((a, b) => (b.stats.lastAt || 0) - (a.stats.lastAt || 0));
11427
- return { ok: true, count: rows.length, symbols: rows.slice(0, 40) };
11757
+ return { ok: true, count: rows.length, symbols: rows.slice(0, FIN_LEARN_MAX_SYMBOLS) };
11428
11758
  },
11429
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) */
11430
11762
  const learn = finLearnLoad();
11431
11763
  const sym = norm(symbol);
11432
- if (sym) return { ok: true, symbol: sym, stats: { ...finLearnEmptyStats(), ...((learn.symbols[sym] || {}).stats || {}) } };
11764
+ if (sym) {
11765
+ const e = learn.symbols[sym] || {};
11766
+ return {
11767
+ ok: true,
11768
+ symbol: sym,
11769
+ stats: { ...finLearnEmptyStats(), ...(e.stats || {}) },
11770
+ summary: e.summary && e.summary.text ? e.summary : null,
11771
+ notes: Array.isArray(e.notes) ? e.notes.slice(-3) : [],
11772
+ };
11773
+ }
11433
11774
  const stats = {};
11434
11775
  for (const [s, e] of Object.entries(learn.symbols)) stats[s] = { ...finLearnEmptyStats(), ...(e.stats || {}) };
11435
11776
  return { ok: true, count: Object.keys(stats).length, stats };
@@ -11438,24 +11779,45 @@ function finLearnApi() {
11438
11779
  const sym = norm(symbol);
11439
11780
  const t = String(text || '').replace(/\s+/g, ' ').trim();
11440
11781
  if (!sym || !t) return { ok: false, error: 'symbol ve text gerekli' };
11782
+ /* KALİTE KAPISI: kısa + somut + tekrar kullanılabilir — günlük tutma */
11783
+ const q = finLearnNoteQuality(t);
11784
+ if (q) return { ok: false, error: q, symbol: sym };
11785
+ const learn = finLearnLoad();
11441
11786
  const e = finLearnSym(sym);
11442
11787
  if (!e) return { ok: false, error: 'sembol yok' };
11443
- /* aynı ders tekrar yazılmasın (son 20 kayıtta aynı metin) */
11444
- const key = t.toLowerCase().slice(0, 120);
11445
- if (e.notes.slice(-20).some((n) => String(n.text || '').toLowerCase().slice(0, 120) === key)) {
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 */
11790
+ const key = finLearnNoteKey(t);
11791
+ if (key && e.notes.slice(-40).some((n) => finLearnNoteKey(n.text) === key)) {
11446
11792
  return { ok: true, duplicate: true, symbol: sym, count: e.notes.length };
11447
11793
  }
11794
+ /* GÜNLÜK DERS LİMİTİ: ajan aynı gün aynı sembole ders yağdıramaz —
11795
+ hafıza öğrenmeyle zehirlenmesin, özet son dersleri temsil etsin */
11796
+ const dayAgo = Date.now() - 24 * 60 * 60 * 1000;
11797
+ const today = e.notes.filter((n) => Number(n.at) >= dayAgo).length;
11798
+ if (today >= FIN_LEARN_MAX_NOTES_DAY) {
11799
+ return {
11800
+ ok: false,
11801
+ error: `günlük ders limiti doldu (${FIN_LEARN_MAX_NOTES_DAY}/24s) — ders kaydedilmedi`,
11802
+ symbol: sym,
11803
+ count: e.notes.length,
11804
+ };
11805
+ }
11448
11806
  const note = {
11449
11807
  id: 'l' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
11450
11808
  at: Date.now(),
11451
- text: t.slice(0, 600),
11809
+ text: t.slice(0, FIN_LEARN_NOTE_MAX),
11452
11810
  kind: ['pattern', 'mistake', 'rule', 'observation'].includes(String(kind || '')) ? String(kind) : 'observation',
11453
11811
  tags: Array.isArray(tags) ? tags.map((x) => String(x || '').slice(0, 24)).filter(Boolean).slice(0, 6) : [],
11454
11812
  sid: String(sid || ''),
11455
11813
  };
11456
11814
  e.notes.push(note);
11457
11815
  while (e.notes.length > FIN_LEARN_MAX_NOTES) e.notes.shift();
11816
+ finLearnTidy(learn, sym);
11458
11817
  finLearnSave();
11818
+ /* OPENCODE TARZI COMPACTION: notlar birikince ESKİLERİ arka planda
11819
+ özetle — ajanı bekletmez, hafıza kendiliğinden sadeleşir */
11820
+ if (e.notes.length > FIN_LEARN_COMPACT_AT) finLearnCompactAuto(sym);
11459
11821
  return { ok: true, id: note.id, symbol: sym, count: e.notes.length };
11460
11822
  },
11461
11823
  remove: ({ id, symbol } = {}) => {
@@ -11497,6 +11859,31 @@ function finLearnApi() {
11497
11859
  if (removed) finLearnSave();
11498
11860
  return { ok: true, removed };
11499
11861
  },
11862
+ drop: ({ symbol } = {}) => {
11863
+ /* sembolün TÜM kaydını (istatistik + dersler + işlemler) bırak —
11864
+ artık işlem yapılmayan/eski semboller depoda yer kaplamasın */
11865
+ const learn = finLearnLoad();
11866
+ const sym = norm(symbol);
11867
+ if (!sym || !learn.symbols[sym]) return { ok: false, error: 'sembol yok' };
11868
+ delete learn.symbols[sym];
11869
+ finLearnSave();
11870
+ return { ok: true, symbol: sym };
11871
+ },
11872
+ forget: () => {
11873
+ /* ELLE SADELEŞTİRME: TTL'i geçen ders/işlemleri ve hareketsiz sembolleri
11874
+ hemen unut (normalde her yazmada otomatik çalışır) */
11875
+ const learn = finLearnLoad();
11876
+ const r = finLearnForget(learn);
11877
+ if (r.notes || r.trades || r.symbols) finLearnSave();
11878
+ return { ok: true, ...r };
11879
+ },
11880
+ compact: ({ symbol } = {}) => {
11881
+ /* OPENCODE TARZI ÖZETLEME (elle): eski dersleri modele özetlet,
11882
+ ham yalnız son dersler kalsın */
11883
+ const sym = norm(symbol);
11884
+ if (!sym) return Promise.resolve({ ok: false, error: 'compact için symbol gerekli' });
11885
+ return finLearnCompactSymbol(sym, true);
11886
+ },
11500
11887
  };
11501
11888
  }
11502
11889
 
@@ -11883,7 +12270,10 @@ function finApplyTraderFields(s, symbolsOverride, role, isMain) {
11883
12270
  s.financeSymbols = Array.isArray(symbolsOverride) && symbolsOverride.length ? symbolsOverride : f.symbols;
11884
12271
  s.financeStrategy = String(f.strategy || '');
11885
12272
  s.financeShadow = !!f.shadowMode; /* shadow modda işlem araçları emir göndermez */
11886
- s.financeLimits = { minLot: f.minLot, maxLot: f.maxLot, maxPositions: f.maxPositions };
12273
+ {
12274
+ const lim = finLimitsSnapshot();
12275
+ s.financeLimits = { minLot: lim.minLot, maxLot: lim.maxLot, maxPositions: lim.maxPositions, symbolLimits: lim.symbols };
12276
+ }
11887
12277
  /* ZORUNLU TEK skill: rol ajanı → roleSkills[rol], ana trader → roleSkills.trader */
11888
12278
  const skillKey = r || (s.financePlaybook ? 'trader' : '');
11889
12279
  s.financeRoleSkills = (skillKey && f.roleSkills && f.roleSkills[skillKey]) || [];
@@ -11984,9 +12374,17 @@ function finTraderBrief(agent) {
11984
12374
  return [
11985
12375
  `Beast Finance ${who} başlatıldı — ilk tur: strateji çerçeveni kur ve piyasa taramasını yap.`,
11986
12376
  `Odak semboller: ${syms || '(boş — mt5_status ile terminale bak, mantıklı semboller seç)'}`,
11987
- `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}`,
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}`,
12378
+ (() => {
12379
+ const rows = Object.entries(f.symbolLimits || {}).map(([s, v]) => {
12380
+ const mn = Number(v && v.minLot) > 0 ? Number(v.minLot) : Number(f.minLot);
12381
+ const mx = Number(v && v.maxLot) > 0 ? Number(v.maxLot) : Number(f.maxLot);
12382
+ return `${s} ${mn}–${mx}`;
12383
+ });
12384
+ return rows.length ? `SEMBOL LOT LİMİTLERİN (senin kararın): ${rows.join(' · ')}` : '';
12385
+ })(),
11988
12386
  agent && agent.main
11989
- ? `LİMİT YETKİN: volatilite/performansa göre mt5_limits {action:"set", minLot?, maxLot?, maxPositions?} ile limitleri SEN güncelleyebilirsin (değişiklik anında geçerli).`
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}.`
11990
12388
  : '',
11991
12389
  roleDef
11992
12390
  ? `UZMANLIK: ${roleDef.desc} — raporlarını bu çerçevede yaz; İŞLEM AÇMA, yalnız analiz + net öneri üret.`
@@ -11999,7 +12397,7 @@ function finTraderBrief(agent) {
11999
12397
  f.shadowMode ? 'SHADOW MOD AÇIK: emir gönderilmez — kararlarını gerekçesiyle raporla, gerçek işlem açılmaz.' : '',
12000
12398
  'Bu turda: mt5_status → hesap/pozisyon/fiyat verisi → mt5_rates/mt5_indicators ile teknik okuma → değerlendirme → kararlar (veya BEKLE: sebep) → kısa rapor.',
12001
12399
  'Önemli kararların gerekçesini mt5_note ile günlüğe yaz (haftalık performans raporu bu notları kullanır).',
12002
- 'ÖĞRENME (ZORUNLU DÖNGÜ): her kapanıştan sonra aynı turda mt5_ogrenme {action:"add", symbol, text, kind} ile dersi kaydet; yeni karar öncesi mt5_ogrenme {action:"stats"/"list", symbol} ile o sembolün geçmişini oku.',
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.',
12003
12401
  '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.',
12004
12402
  '⚡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.',
12005
12403
  'Diğer finance/paralel ajanlarla koordinasyon için agent_dm aracı var (to: ajan başlığı anahtar kelimesi).',
@@ -12085,7 +12483,7 @@ async function finConsultPlan(f, agent, sid) {
12085
12483
  'Güncel piyasa bağlamı gerekiyorsa web_search kullan; kurulu bir skill konuyla ilgiliyse skill aracıyla oku.';
12086
12484
  const ctx = [
12087
12485
  `Odak semboller: ${agent && agent.symbols && agent.symbols.length ? agent.symbols.join(', ') : (f.symbols || []).join(', ') || '(boş — ajan kendi bulabilir)'}`,
12088
- `Otomatik işlem: ${auto} · lot aralığı ${f.minLot}–${f.maxLot} · max eşzamanlı pozisyon ${f.maxPositions}`,
12486
+ `Otomatik işlem: ${auto} · lot aralığı ${f.minLot}–${f.maxLot} (genel; sembol bazlı limitler daraltır) · max eşzamanlı pozisyon ${f.maxPositions}`,
12089
12487
  account
12090
12488
  ? `Hesap: bakiye ${account.balance} ${account.currency} · özkaynak ${account.equity ?? '?'} · serbest marj ${account.margin_free ?? '?'} · kaldıraç 1:${account.leverage ?? '?'}`
12091
12489
  : 'Hesap verisi alınamadı.',
@@ -12226,7 +12624,7 @@ function finAgentRound(sid, opts) {
12226
12624
  : 'İşlem açabilirsin — limitlere uy, SL\u2019siz pozisyon bırakma.';
12227
12625
  const learnTip = roleDef
12228
12626
  ? ' İlgili sembolün geçmiş derslerini (mt5_ogrenme) oku ve önerine yansıt.'
12229
- : ' Kapanan her işlemden sonra mt5_ogrenme add ile sembol dersini yaz; karar öncesi mt5_ogrenme stats/list ile o sembolün geçmişini oku' + (agent.main ? '; volatiliteye göre mt5_limits ile lot/pozisyon limitlerini güncelleyebilirsin.' : '.');
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.' : '.');
12230
12628
  const focus = agent.symbols.length ? `Odak: ${agent.symbols.join(', ')}. ` : '';
12231
12629
  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.`;
12232
12630
  const launch = (planBlock) => {
@@ -12397,6 +12795,26 @@ ipcMain.handle('finance:alerts:remove', async (_e, id) => {
12397
12795
  return { ok };
12398
12796
  });
12399
12797
 
12798
+ /* MT5 ÖĞRENME HAFIZASI (panel 🧠): sembol bazlı istatistik + ajan derslerini
12799
+ görüntüle/sil — depo tavanları (sembol/ders/işlem/günlük ders) çekirdekte uygulanır */
12800
+ ipcMain.handle('finance:learn', async (_e, payload) => {
12801
+ const p = payload && typeof payload === 'object' ? payload : {};
12802
+ const api = finLearnApi();
12803
+ const action = String(p.action || 'list');
12804
+ try {
12805
+ if (action === 'list') return api.list({ symbol: p.symbol });
12806
+ if (action === 'stats') return api.stats({ symbol: p.symbol });
12807
+ if (action === 'remove') return api.remove({ id: p.id, symbol: p.symbol });
12808
+ if (action === 'clear') return api.clear({ ids: p.ids, symbol: p.symbol, all: p.all });
12809
+ if (action === 'drop') return api.drop({ symbol: p.symbol });
12810
+ if (action === 'forget') return api.forget();
12811
+ if (action === 'compact') return await api.compact({ symbol: p.symbol });
12812
+ return { ok: false, error: 'bilinmeyen action' };
12813
+ } catch (e) {
12814
+ return { ok: false, error: String((e && e.message) || e).slice(0, 160) };
12815
+ }
12816
+ });
12817
+
12400
12818
  ipcMain.handle('finance:mode', async (_e, payload) => {
12401
12819
  financeState.mode = !!(payload && payload.on);
12402
12820
  /* SAAT DENETİMİ: pencere kapanınca ajanlar otomatik durur, açılınca geri başlar */
@@ -12512,6 +12930,8 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
12512
12930
  if (p.minLot !== undefined) f.minLot = Math.max(0.01, Math.min(100, Number(p.minLot) || 0.01));
12513
12931
  /* min lot max lotu aşamaz — hangisi sonra yazıldıysa diğerine kelepçelenir */
12514
12932
  if (f.minLot > f.maxLot) f.minLot = f.maxLot;
12933
+ /* genel aralık değişti: sembol bazlı limitler yeni taban/tavana kelepçelenir */
12934
+ if (p.minLot !== undefined || p.maxLot !== undefined) finNormalizeSymbolLimits(f);
12515
12935
  if (p.maxPositions !== undefined) f.maxPositions = Math.max(1, Math.min(20, Math.round(Number(p.maxPositions) || 3)));
12516
12936
  /* limit değişikliği koşan oturumlara ANINDA işlenir (sonraki turu beklemez) */
12517
12937
  if (p.minLot !== undefined || p.maxLot !== undefined || p.maxPositions !== undefined) {