beast-agent 2.63.4 → 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) */
@@ -9460,9 +9510,9 @@ function finCfg() {
9460
9510
  if (!Number.isFinite(Number(f.analysisCount))) f.analysisCount = 2;
9461
9511
  f.analysisCount = Math.max(0, Math.min(FIN_ROLES_AUTO.length, Math.round(Number(f.analysisCount) || 0)));
9462
9512
  if (!f.analysisAuto) f.analysisTeam = finRolesValid(f.analysisTeam);
9463
- /* ROL → SKILL eşleştirmesi: rol başına TEK skill — modalda zorunlu tek
9464
- seçim yapılır. Eski çoklu seçimlerden yalnız İLKİ korunur (göç). Skill
9465
- 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. */
9466
9516
  if (f.roleSkillsDefaultsCleared !== true) {
9467
9517
  f.roleSkills = {};
9468
9518
  f.roleSkillsDefaultsCleared = true;
@@ -9470,12 +9520,12 @@ function finCfg() {
9470
9520
  if (!f.roleSkills || typeof f.roleSkills !== 'object') f.roleSkills = {};
9471
9521
  for (const d of FIN_ROLES) {
9472
9522
  const cur = Array.isArray(f.roleSkills[d.id]) ? f.roleSkills[d.id] : [];
9473
- 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);
9474
9524
  }
9475
- /* TRADER PLAYBOOK: asıl karar vericinin zorunlu skill'i (roleSkills.trader) */
9525
+ /* TRADER PLAYBOOK: asıl karar vericinin zorunlu skill'leri (roleSkills.trader) */
9476
9526
  {
9477
9527
  const cur = Array.isArray(f.roleSkills.trader) ? f.roleSkills.trader : [];
9478
- 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);
9479
9529
  }
9480
9530
  if (!Number(f.intervalSec)) f.intervalSec = 120;
9481
9531
  if (!Number(f.minLot)) f.minLot = 0.01;
@@ -10717,11 +10767,13 @@ async function finRecordClose(ticket, st) {
10717
10767
  finExcSave();
10718
10768
  finExcFlush(true);
10719
10769
  const kind = finwatch.closeKind(closeReason);
10720
- finJournal({ kind: 'close', ticket, symbol, net: rounded, side: (st && st.side) || '', mfe, mae, reason: kind || '' });
10721
- /* SEMBOL BAZLI SÜREKLİ ÖĞRENME: her kapanış istatistiğe işlenir (mt5_ogrenme) */
10722
- 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 });
10723
10775
  const pl = rounded == null ? '' : ` · K/Z ${rounded >= 0 ? '+' : ''}${rounded.toFixed(2)}`;
10724
- const line = `🏁 Pozisyon kapandı: ${symbol || '?'} #${ticket}${pl}`;
10776
+ const line = `🏁 Pozisyon kapandı: ${symbol || '?'} #${ticket}${tf ? ' · ' + tf : ''}${pl}`;
10725
10777
  financeLog('[watchdog] ' + line);
10726
10778
  if (finCfg().notifyTrades !== false) financeNotify(line, 'close');
10727
10779
  if (rounded != null) finStatsRefresh(true);
@@ -10731,10 +10783,17 @@ async function finRecordClose(ticket, st) {
10731
10783
  const label = kind === 'stop' ? 'STOP OLDU' : kind === 'tp' ? 'TP OLDU' : 'STOP-OUT (teminat)';
10732
10784
  const how = kind === 'tp' ? 'take-profit' : kind === 'stop' ? 'stop-loss' : 'stop-out';
10733
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.`;
10734
10792
  finWakeAgents(
10735
10793
  `[FİNANS OLAYI — ${label}, TUR SANİYESİ BEKLENMEDEN İLETİLDİ]\n` +
10736
- `- ${symbol || '?'}${st && st.side ? ' ' + String(st.side).toUpperCase() : ''} #${ticket} ${how} ile kapandı · net ${netTxt}\n` +
10737
- `Ş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,
10738
10797
  { kind, ticket, symbol }
10739
10798
  );
10740
10799
  }
@@ -10920,6 +10979,9 @@ async function finWatchTick() {
10920
10979
  symbol: String(p.symbol || ''),
10921
10980
  side: Number(p.type) === 0 ? 'buy' : 'sell',
10922
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),
10923
10985
  r: 0,
10924
10986
  be: false,
10925
10987
  partial: false,
@@ -10930,9 +10992,12 @@ async function finWatchTick() {
10930
10992
  financeState.watch.set(ticket, st);
10931
10993
  const isBeast = Number(p.magic) === 20260908 || /beast/i.test(String(p.comment || ''));
10932
10994
  if (isBeast) {
10933
- 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 || '' });
10934
10996
  }
10935
10997
  finWatchSave();
10998
+ } else if (!st.tf) {
10999
+ const tfNow = finLearnParseTf(p.comment);
11000
+ if (tfNow) { st.tf = tfNow; finWatchSave(); }
10936
11001
  }
10937
11002
  /* KÂR KORUMA: görülen EN İYİ kâr (fiyat farkı) tick aralarında da korunur */
10938
11003
  {
@@ -11176,8 +11241,8 @@ function finTradeEvent(kRaw, dataRaw, sidRaw) {
11176
11241
  }
11177
11242
  /* SEMBOL BAZLI ÖĞRENME kaydı: günlüğe + AKIŞ paneline düşer */
11178
11243
  if (k === 'learn') {
11179
- finJournal({ kind: 'learn', sid, agent: who, symbol: d.symbol, text: d.text, learnKind: d.kind || 'observation' });
11180
- 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)}`;
11181
11246
  financeLog('[ogrenme] ' + line);
11182
11247
  finPush('trade', { line });
11183
11248
  return;
@@ -11191,7 +11256,7 @@ function finTradeEvent(kRaw, dataRaw, sidRaw) {
11191
11256
  return;
11192
11257
  }
11193
11258
  if (k === 'trade') {
11194
- 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 || '' });
11195
11260
  } else if (k === 'close') {
11196
11261
  /* KISMİ KAPATMA (kısmi TP / kısmi stop): ajan kararı; günlük + bildirim.
11197
11262
  Kesin toplam K/Z yine watchdog tarafından yazılır (çift sayım yok). */
@@ -11211,14 +11276,14 @@ function finTradeEvent(kRaw, dataRaw, sidRaw) {
11211
11276
  } else if (k === 'modify') {
11212
11277
  finJournal({ kind: 'modify', sid, agent: who, ticket: d.ticket, sl: d.sl, tp: d.tp });
11213
11278
  } else if (k === 'pending') {
11214
- 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 || '' });
11215
11280
  } else if (k === 'cancel') {
11216
11281
  finJournal({ kind: 'cancel', sid, agent: who, ticket: d.ticket });
11217
11282
  }
11218
11283
  let line = '';
11219
- 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 : ''}`;
11220
11285
  else if (k === 'modify') line = `SL/TP GÜNCELLENDİ: ticket ${d.ticket} (SL ${d.sl || 0} / TP ${d.tp || 0})${who ? ' · ' + who : ''}`;
11221
- 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 : ''}`;
11222
11287
  else if (k === 'cancel') line = `EMİR İPTAL: ticket ${d.ticket}`;
11223
11288
  if (line) {
11224
11289
  financeLog('[ajan] ' + line);
@@ -11254,9 +11319,9 @@ try {
11254
11319
  if (!kind) return;
11255
11320
  if (kind === 'trade') {
11256
11321
  const side = a.side || (/^buy/i.test(String(a.type || '')) ? 'buy' : /^sell/i.test(String(a.type || '')) ? 'sell' : '');
11257
- 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);
11258
11323
  } else if (kind === 'modify') finTradeEvent('modify', { ticket: a.ticket, sl: a.sl, tp: a.tp }, sid);
11259
- 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);
11260
11325
  else if (kind === 'cancel') finTradeEvent('cancel', { ticket: a.ticket }, sid);
11261
11326
  else if (kind === 'close') {
11262
11327
  finTradeEvent('close', {
@@ -11493,15 +11558,62 @@ const FIN_LEARN_COMPACT_AT = 24; /* not sayısı bunu aşınca arka planda ot
11493
11558
  const FIN_LEARN_KEEP_RECENT = 8; /* ham kalan son ders sayısı */
11494
11559
  const FIN_LEARN_SUMMARY_MAX = 800; /* kalıcı özet metin tavanı (karakter) */
11495
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
+ }
11496
11580
 
11497
11581
  function finLearnEmptyStats() {
11498
11582
  return {
11499
11583
  trades: 0, wins: 0, losses: 0, net: 0,
11500
11584
  mfe: 0, mae: 0, givebacks: 0, givebackAmount: 0,
11501
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: {},
11502
11588
  };
11503
11589
  }
11504
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
+
11505
11617
  /* Ders metni karşılaştırma anahtarı: büyük/küçük harf, noktalama ve boşluk
11506
11618
  farkları aynı ders sayılır — ajan aynı cümleyi kopyalayıp depoyu doldurmasın */
11507
11619
  function finLearnNoteKey(text) {
@@ -11555,7 +11667,7 @@ function finLearnPrune(learn, keep, protectSym) {
11555
11667
  yüklemede çalışır → depo kendiliğinden sade kalır, birikme/zehirlenme olmaz. */
11556
11668
  function finLearnForget(learn, now) {
11557
11669
  const t = Number(now) || Date.now();
11558
- const stat = { notes: 0, trades: 0, symbols: 0 };
11670
+ const stat = { notes: 0, trades: 0, mistakes: 0, symbols: 0 };
11559
11671
  for (const [sym, e] of Object.entries(learn.symbols)) {
11560
11672
  if (!e || typeof e !== 'object') { delete learn.symbols[sym]; stat.symbols++; continue; }
11561
11673
  const notes = Array.isArray(e.notes) ? e.notes : [];
@@ -11564,6 +11676,10 @@ function finLearnForget(learn, now) {
11564
11676
  const trades = Array.isArray(e.trades) ? e.trades : [];
11565
11677
  e.trades = trades.filter((x) => x && t - (Number(x.at) || 0) <= FIN_LEARN_TRADE_TTL_MS);
11566
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;
11567
11683
  if (t - finLearnActivity(e) > FIN_LEARN_SYMBOL_TTL_MS) {
11568
11684
  delete learn.symbols[sym];
11569
11685
  stat.symbols += 1;
@@ -11577,7 +11693,7 @@ function finLearnForget(learn, now) {
11577
11693
  function finLearnTidy(learn, protectSym, now) {
11578
11694
  const forgot = finLearnForget(learn, now);
11579
11695
  const cap = finLearnPrune(learn, FIN_LEARN_MAX_SYMBOLS, protectSym);
11580
- 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 };
11581
11697
  }
11582
11698
 
11583
11699
  function finLearnLoad() {
@@ -11593,7 +11709,8 @@ function finLearnLoad() {
11593
11709
  learn.symbols[sym] = {
11594
11710
  notes: Array.isArray(v.notes) ? v.notes.filter((n) => n && n.id && n.text).slice(-FIN_LEARN_MAX_NOTES) : [],
11595
11711
  trades: Array.isArray(v.trades) ? v.trades.filter(Boolean).slice(-FIN_LEARN_MAX_TRADES) : [],
11596
- 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),
11597
11714
  /* KALICI ÖZET (opencode tarzı sıkıştırma): eski derslerin modele
11598
11715
  özetlettirilmiş hâli — ham dersler düşse de bilgi kalır */
11599
11716
  summary: v.summary && typeof v.summary === 'object' && String(v.summary.text || '').trim()
@@ -11610,7 +11727,7 @@ function finLearnLoad() {
11610
11727
  } catch {}
11611
11728
  const dropped = finLearnTidy(learn); /* yükleme anında unut + tavan uygula */
11612
11729
  financeState.learn = learn;
11613
- 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 */
11614
11731
  return learn;
11615
11732
  }
11616
11733
 
@@ -11630,16 +11747,16 @@ const FIN_LEARN_COMPACT_SYSTEM =
11630
11747
  'Sen bir trading mentorüsün. Sana bir sembolün ESKİ ders notları ve mevcut özeti verilir. ' +
11631
11748
  'Görevin: güncellenmiş KISA bir özet üretmek.\n' +
11632
11749
  'KURALLAR: (1) Yalnız kalıcı değeri olan bilgiyi tut: çalışan desenler, kaçınılacak hatalar, ' +
11633
- '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 ' +
11634
11751
  'haber, geçici durum gibi bayat bilgiyi at. (3) Çelişen bilgide EN YENİ ve istatistikle ' +
11635
- 'desteklenen kazanır. (4) Sayısal istatistikleri (işlem, kazanç %, net, kâr geri verme) koru. ' +
11636
- '(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. ' +
11637
11754
  '(6) Çıktı SADECE özet metni olsun; başlık/etiket/açıklama yazma.';
11638
11755
 
11639
11756
  const finLearnCompacting = new Set();
11640
11757
 
11641
11758
  function finLearnSummaryPrompt(sym, e, oldNotes) {
11642
- const st = { ...finLearnEmptyStats(), ...(e.stats || {}) };
11759
+ const st = finLearnNormStats(e.stats);
11643
11760
  const wr = st.trades ? Math.round((st.wins / st.trades) * 100) : 0;
11644
11761
  const lines = [];
11645
11762
  lines.push('SEMBOL: ' + sym);
@@ -11648,12 +11765,27 @@ function finLearnSummaryPrompt(sym, e, oldNotes) {
11648
11765
  (st.givebacks ? ` · ${st.givebacks} kez kâr geri verildi` : '') +
11649
11766
  (st.streak >= 2 ? ` · ${st.streak} ardışık kayıp` : '')
11650
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
+ }
11651
11783
  if (e.summary && e.summary.text) lines.push('MEVCUT ÖZET:\n' + String(e.summary.text).slice(0, 1400));
11652
11784
  lines.push('ESKİ DERSLER (kronolojik — en yeni EN SONDA):');
11653
11785
  for (const n of oldNotes.slice(-80)) {
11654
- 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)}`);
11655
11787
  }
11656
- 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.');
11657
11789
  return lines.join('\n');
11658
11790
  }
11659
11791
 
@@ -11720,40 +11852,55 @@ function finLearnSym(symbol) {
11720
11852
  const sym = String(symbol || '').trim().toUpperCase();
11721
11853
  if (!sym) return null;
11722
11854
  const learn = finLearnLoad();
11723
- 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 };
11724
11856
  const e = learn.symbols[sym];
11725
- e.stats = { ...finLearnEmptyStats(), ...(e.stats || {}) };
11857
+ if (!Array.isArray(e.mistakes)) e.mistakes = [];
11858
+ e.stats = finLearnNormStats(e.stats);
11726
11859
  return e;
11727
11860
  }
11728
11861
 
11729
- /* Kapanan her işlem otomatik öğrenme girdisi olur (sembol bazlı) */
11730
- 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 }) {
11731
11867
  try {
11732
11868
  if (net == null) return; /* K/Z okunamadı — sahte kayıt yazma */
11733
11869
  const n = Number(net);
11734
11870
  if (!isFinite(n) || !symbol) return;
11735
11871
  const e = finLearnSym(symbol);
11736
11872
  if (!e) return;
11873
+ const tf = finLearnNormTf(timeframe);
11737
11874
  const st = e.stats;
11738
11875
  const m = Number(mfe);
11739
11876
  const ma = Number(mae);
11740
- st.trades += 1;
11741
- if (n >= 0) st.wins += 1;
11742
- else st.losses += 1;
11743
- st.net = Math.round((Number(st.net) + n) * 100) / 100;
11744
- st.lastAt = Date.now();
11745
- st.lastNet = Math.round(n * 100) / 100;
11746
- 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);
11747
11896
  st.bestNet = Math.max(Number(st.bestNet) || 0, Math.round(n * 100) / 100);
11748
11897
  st.worstNet = Math.min(Number(st.worstNet) || 0, Math.round(n * 100) / 100);
11749
- if (isFinite(m)) {
11750
- st.mfe = Math.round(((Number(st.mfe) || 0) + m) * 100) / 100;
11751
- if (m > 0 && n < 0) {
11752
- st.givebacks += 1;
11753
- st.givebackAmount = Math.round(((Number(st.givebackAmount) || 0) + m) * 100) / 100;
11754
- }
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]);
11755
11903
  }
11756
- if (isFinite(ma)) st.mae = Math.round(((Number(st.mae) || 0) + ma) * 100) / 100;
11757
11904
  e.trades.push({
11758
11905
  at: Date.now(),
11759
11906
  side: String(side || ''),
@@ -11761,14 +11908,69 @@ function finLearnRecordClose({ symbol, side, net, mfe, mae, reason }) {
11761
11908
  mfe: isFinite(m) ? Math.round(m * 100) / 100 : null,
11762
11909
  mae: isFinite(ma) ? Math.round(ma * 100) / 100 : null,
11763
11910
  reason: String(reason || '').slice(0, 40),
11911
+ tf: tf || null,
11764
11912
  });
11765
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
+ }
11766
11930
  finLearnTidy(finLearnLoad(), symbol);
11767
11931
  finLearnSave();
11768
11932
  } catch {}
11769
11933
  }
11770
11934
 
11771
- /* 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
+
11772
11974
  function finBuildLearnDigest(symbols) {
11773
11975
  const learn = finLearnLoad();
11774
11976
  const prefer = Array.isArray(symbols) ? symbols.map((s) => String(s || '').trim().toUpperCase()).filter(Boolean) : [];
@@ -11777,7 +11979,7 @@ function finBuildLearnDigest(symbols) {
11777
11979
  const L = [];
11778
11980
  for (const sym of keys.slice(0, 10)) {
11779
11981
  const e = learn.symbols[sym];
11780
- const st = { ...finLearnEmptyStats(), ...(e.stats || {}) };
11982
+ const st = finLearnNormStats(e.stats);
11781
11983
  if (st.trades > 0) {
11782
11984
  const wr = Math.round((st.wins / st.trades) * 100);
11783
11985
  const parts = [`${st.trades} işlem`, `%${wr} kazanç`, `net ${st.net >= 0 ? '+' : ''}${st.net}`];
@@ -11786,6 +11988,13 @@ function finBuildLearnDigest(symbols) {
11786
11988
  if (st.lastAt) parts.push(`son: ${st.lastNet >= 0 ? '+' : ''}${st.lastNet}`);
11787
11989
  L.push(`- ${sym}: ${parts.join(' · ')}`);
11788
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);
11789
11998
  /* KALICI ÖZET (compaction): eski derslerin sıkıştırılmış hâli — ham ders
11790
11999
  sayısı azalsa da birikmiş bilgi prompta girer */
11791
12000
  if (e.summary && e.summary.text) {
@@ -11797,62 +12006,78 @@ function finBuildLearnDigest(symbols) {
11797
12006
  L.push(` ders${extra}: "${String(last.text || '').slice(0, 160)}"`);
11798
12007
  }
11799
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
+ }
11800
12014
  return L.join('\n');
11801
12015
  }
11802
12016
 
11803
12017
  function finLearnApi() {
11804
12018
  const norm = (s) => String(s || '').trim().toUpperCase();
11805
12019
  return {
11806
- list: ({ symbol, limit } = {}) => {
12020
+ list: ({ symbol, timeframe, limit } = {}) => {
11807
12021
  const learn = finLearnLoad();
11808
12022
  const sym = norm(symbol);
12023
+ const tf = finLearnNormTf(timeframe);
11809
12024
  const take = Math.max(1, Math.min(25, Math.round(Number(limit) || 8)));
11810
12025
  if (sym) {
11811
12026
  const e = learn.symbols[sym];
11812
- 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 : []);
11813
12030
  return {
11814
12031
  ok: true,
11815
12032
  symbol: sym,
11816
- count: e.notes.length,
11817
- notes: e.notes.slice(-take),
12033
+ timeframe: tf || '',
12034
+ count: notes.length,
12035
+ notes: notes.slice(-take),
11818
12036
  trades: e.trades.slice(-12),
11819
- stats: { ...finLearnEmptyStats(), ...(e.stats || {}) },
12037
+ stats: finLearnNormStats(e.stats),
11820
12038
  summary: e.summary && e.summary.text ? e.summary : null,
12039
+ mistakes: mistakes.slice(-8),
11821
12040
  };
11822
12041
  }
11823
12042
  const rows = Object.entries(learn.symbols)
11824
12043
  .map(([s, e]) => ({
11825
12044
  symbol: s,
11826
12045
  notes: e.notes.length,
11827
- stats: { ...finLearnEmptyStats(), ...(e.stats || {}) },
12046
+ stats: finLearnNormStats(e.stats),
11828
12047
  lastNote: String(((e.notes[e.notes.length - 1] || {}).text) || ''),
11829
12048
  hasSummary: !!(e.summary && e.summary.text),
11830
12049
  }))
11831
12050
  .sort((a, b) => (b.stats.lastAt || 0) - (a.stats.lastAt || 0));
11832
12051
  return { ok: true, count: rows.length, symbols: rows.slice(0, FIN_LEARN_MAX_SYMBOLS) };
11833
12052
  },
11834
- stats: ({ symbol } = {}) => {
11835
- /* KOMPAKT OKUMA: istatistik + kalıcı özet + son 3 ders — ajan karar
11836
- ö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 */
11837
12056
  const learn = finLearnLoad();
11838
12057
  const sym = norm(symbol);
12058
+ const tf = finLearnNormTf(timeframe);
11839
12059
  if (sym) {
11840
12060
  const e = learn.symbols[sym] || {};
12061
+ const st = finLearnNormStats(e.stats);
11841
12062
  return {
11842
12063
  ok: true,
11843
12064
  symbol: sym,
11844
- stats: { ...finLearnEmptyStats(), ...(e.stats || {}) },
12065
+ timeframe: tf || '',
12066
+ stats: st,
12067
+ byTf: tf && st.byTf[tf] ? { [tf]: st.byTf[tf] } : st.byTf,
11845
12068
  summary: e.summary && e.summary.text ? e.summary : null,
11846
- 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) : [],
11847
12071
  };
11848
12072
  }
11849
12073
  const stats = {};
11850
- 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);
11851
12075
  return { ok: true, count: Object.keys(stats).length, stats };
11852
12076
  },
11853
- add: ({ symbol, text, kind, tags, sid } = {}) => {
12077
+ add: ({ symbol, text, kind, tags, timeframe, sid } = {}) => {
11854
12078
  const sym = norm(symbol);
11855
12079
  const t = String(text || '').replace(/\s+/g, ' ').trim();
12080
+ const tf = finLearnNormTf(timeframe);
11856
12081
  if (!sym || !t) return { ok: false, error: 'symbol ve text gerekli' };
11857
12082
  /* KALİTE KAPISI: kısa + somut + tekrar kullanılabilir — günlük tutma */
11858
12083
  const q = finLearnNoteQuality(t);
@@ -11860,10 +12085,10 @@ function finLearnApi() {
11860
12085
  const learn = finLearnLoad();
11861
12086
  const e = finLearnSym(sym);
11862
12087
  if (!e) return { ok: false, error: 'sembol yok' };
11863
- /* aynı ders tekrar yazılmasın (son 40 kayıtta normalize metin) — kopyala/
11864
- 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 */
11865
12090
  const key = finLearnNoteKey(t);
11866
- 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)) {
11867
12092
  return { ok: true, duplicate: true, symbol: sym, count: e.notes.length };
11868
12093
  }
11869
12094
  /* GÜNLÜK DERS LİMİTİ: ajan aynı gün aynı sembole ders yağdıramaz —
@@ -11884,6 +12109,7 @@ function finLearnApi() {
11884
12109
  text: t.slice(0, FIN_LEARN_NOTE_MAX),
11885
12110
  kind: ['pattern', 'mistake', 'rule', 'observation'].includes(String(kind || '')) ? String(kind) : 'observation',
11886
12111
  tags: Array.isArray(tags) ? tags.map((x) => String(x || '').slice(0, 24)).filter(Boolean).slice(0, 6) : [],
12112
+ tf: tf || '',
11887
12113
  sid: String(sid || ''),
11888
12114
  };
11889
12115
  e.notes.push(note);
@@ -11893,7 +12119,7 @@ function finLearnApi() {
11893
12119
  /* OPENCODE TARZI COMPACTION: notlar birikince ESKİLERİ arka planda
11894
12120
  özetle — ajanı bekletmez, hafıza kendiliğinden sadeleşir */
11895
12121
  if (e.notes.length > FIN_LEARN_COMPACT_AT) finLearnCompactAuto(sym);
11896
- 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 };
11897
12123
  },
11898
12124
  remove: ({ id, symbol } = {}) => {
11899
12125
  const learn = finLearnLoad();
@@ -12446,8 +12672,8 @@ function finTraderBrief(agent) {
12446
12672
  const syms = agent && agent.symbols && agent.symbols.length ? agent.symbols.join(', ') : (f.symbols || []).join(', ');
12447
12673
  const roleDef = finRoleDef(agent && agent.role);
12448
12674
  const who = agent && agent.main ? 'TRADER' : roleDef ? roleDef.label.toUpperCase() : 'FINANCE AJANI';
12449
- const playbook = agent && agent.main && f.roleSkills && f.roleSkills.trader && f.roleSkills.trader[0]
12450
- ? f.roleSkills.trader[0]
12675
+ const playbook = agent && agent.main && f.roleSkills && Array.isArray(f.roleSkills.trader)
12676
+ ? f.roleSkills.trader.filter(Boolean).join(' + ')
12451
12677
  : '';
12452
12678
  return [
12453
12679
  `Beast Finance ${who} başlatıldı — ilk tur: strateji çerçeveni kur ve piyasa taramasını yap.`,
@@ -12471,11 +12697,11 @@ function finTraderBrief(agent) {
12471
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.'
12472
12698
  : f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : 'Strateji notu yok: trend + destek/direnç + momentum ile temel okuma yap.',
12473
12699
  roleDef && f.strategy ? `Sahibinin strateji notu (bu çerçevede analiz et): ${f.strategy}` : '',
12474
- 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.` : '',
12475
12701
  f.shadowMode ? 'SHADOW MOD AÇIK: emir gönderilmez — kararlarını gerekçesiyle raporla, gerçek işlem açılmaz.' : '',
12476
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.',
12477
12703
  'Önemli kararların gerekçesini mt5_note ile günlüğe yaz (haftalık performans raporu bu notları kullanır).',
12478
- 'ÖĞ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.',
12479
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.',
12480
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.',
12481
12707
  'Diğer finance/paralel ajanlarla koordinasyon için agent_dm aracı var (to: ajan başlığı anahtar kelimesi).',
@@ -12701,8 +12927,8 @@ function finAgentRound(sid, opts) {
12701
12927
  ? 'SHADOW MOD AÇIK: emir GÖNDERİLMEZ — kararını teziyle raporla (günlüğe yazılır).'
12702
12928
  : 'İşlem açabilirsin — limitlere uy, SL\u2019siz pozisyon bırakma.';
12703
12929
  const learnTip = roleDef
12704
- ? ' İlgili sembolün geçmiş derslerini (mt5_ogrenme) oku ve önerine yansıt.'
12705
- : ' 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 ? '; mt5_limits ile lot/pozisyon limitlerini ve TUR RİTMİNİ (intervalSec+intervalForMin) 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.' : '.');
12706
12932
  const focus = agent.symbols.length ? `Odak: ${agent.symbols.join(', ')}. ` : '';
12707
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.`;
12708
12934
  const launch = (planBlock) => {
@@ -13061,13 +13287,13 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
13061
13287
  if (p.analysisTeam !== undefined) f.analysisTeam = finRolesValid(p.analysisTeam);
13062
13288
  if (p.analysisAuto !== undefined) f.analysisAuto = !!p.analysisAuto;
13063
13289
  if (p.analysisCount !== undefined) f.analysisCount = Math.max(0, Math.min(FIN_ROLES_AUTO.length, Math.round(Number(p.analysisCount) || 0)));
13064
- /* ROL → SKILL eşleştirmesi güncellemesi (modal): rol başına TEK skill +
13065
- 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) */
13066
13292
  if (p.roleSkills !== undefined && p.roleSkills && typeof p.roleSkills === 'object') {
13067
13293
  for (const id of [...FIN_ROLES.map((d) => d.id), 'trader']) {
13068
13294
  if (p.roleSkills[id] === undefined) continue;
13069
13295
  f.roleSkills[id] = (Array.isArray(p.roleSkills[id]) ? p.roleSkills[id] : [])
13070
- .map((s) => String(s || '').trim()).filter(Boolean).slice(0, 1);
13296
+ .map((s) => String(s || '').trim()).filter(Boolean).slice(0, 2);
13071
13297
  }
13072
13298
  /* koşan rol ajanlarını da tazele — seçim sonraki turdan itibaren sistem
13073
13299
  promptuna girer (ajanı durdurmaya gerek yok) */