beast-agent 2.62.2 → 2.63.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.62.2",
4
+ "version": "2.63.1",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -13,7 +13,7 @@
13
13
 
14
14
  ÇOKLU PERİYOT: timeframes:["M1","M15"] (2-3 adet) verilirse her periyot ayrı
15
15
  çekilir ve TEK PNG'de birleştirilir (layout:"h" soldan sağa — varsayılan,
16
- "v" üstten alta). Panellerin SAĞ ÜSTüne "SEMBOL · PERİYOT" etiketi çizilir;
16
+ "v" üstten alta). Panellerin SOL ÜSTüne "SEMBOL · PERİYOT" etiketi çizilir;
17
17
  sonuçta panels[{symbol, timeframe, position, path}] hangi tarafın hangi
18
18
  sembol/periyot olduğunu söyler. tf2:"M15" kısayolu da (timeframe + tf2) kabul edilir.
19
19
 
@@ -324,7 +324,7 @@ function embedData(src, size) {
324
324
  }
325
325
 
326
326
  /* ---- ÇOKLU KARE: plan (sembol × periyot) sırayla çekilir ve TEK PNG'de
327
- birleştirilir — kilit zaten alınmış (ack çakışması yok); her panelin SAĞ
327
+ birleştirilir — kilit zaten alınmış (ack çakışması yok); her panelin SOL
328
328
  ÜSTüne "SEMBOL · PERİYOT" etiketi çizilir; soldan sağa (layout:"v" ile
329
329
  üstten alta). plan: [{symbol, timeframe}] — boş alan = aktif grafik. */
330
330
  async function runPanels(plan, filesDir, cmdFile, ackFile, beatFile, timeoutSec, diag) {
@@ -97,8 +97,9 @@ async function compose(panels, opts) {
97
97
  if (layout === 'h') ctx.fillRect(x - GAP, 0, GAP, H);
98
98
  else ctx.fillRect(0, y - GAP, W, GAP);
99
99
  }
100
- /* etiket: SAĞ ÜST köşede BÜYÜK zaman dilimi (üstünde küçük sembol) —
101
- görsel ajan hangi panelin hangi periyot olduğunu bakışta okur */
100
+ /* etiket: SOL ÜST köşede BÜYÜK zaman dilimi (üstünde küçük sembol) —
101
+ görsel ajan hangi panelin hangi periyot olduğunu bakışta okur;
102
+ son mumlar sağda olduğundan etiket oraya konmaz (mumları kapatıyordu) */
102
103
  if (p.label) {
103
104
  try {
104
105
  const parts = String(p.label).split('·').map((s) => s.trim()).filter(Boolean);
@@ -110,14 +111,14 @@ async function compose(panels, opts) {
110
111
  const padY = Math.round(fsTf * 0.42);
111
112
  const lineGap = Math.round(fsSym * 0.45);
112
113
  ctx.textBaseline = 'middle';
113
- ctx.textAlign = 'right';
114
+ ctx.textAlign = 'left';
114
115
  ctx.font = pickFont(fsTf);
115
116
  const tw = ctx.measureText(tf).width;
116
117
  ctx.font = pickFont(fsSym);
117
118
  const sw = sym ? ctx.measureText(sym).width : 0;
118
119
  const boxW = Math.ceil(Math.max(tw, sw) + padX * 2);
119
120
  const boxH = Math.ceil(padY * 2 + fsTf + (sym ? fsSym + lineGap : 0));
120
- const bx = x + cellW - boxW - 10;
121
+ const bx = x + 10;
121
122
  const by = y + 10;
122
123
  ctx.fillStyle = 'rgba(0,0,0,0.80)';
123
124
  ctx.fillRect(bx, by, boxW, boxH);
@@ -126,11 +127,11 @@ async function compose(panels, opts) {
126
127
  if (sym) {
127
128
  ctx.font = pickFont(fsSym);
128
129
  ctx.fillStyle = '#c9d2d9';
129
- ctx.fillText(sym, bx + boxW - padX, by + padY + fsSym / 2);
130
+ ctx.fillText(sym, bx + padX, by + padY + fsSym / 2);
130
131
  }
131
132
  ctx.font = pickFont(fsTf);
132
133
  ctx.fillStyle = '#00e676';
133
- ctx.fillText(tf, bx + boxW - padX, by + padY + (sym ? fsSym + lineGap : 0) + fsTf / 2);
134
+ ctx.fillText(tf, bx + padX, by + padY + (sym ? fsSym + lineGap : 0) + fsTf / 2);
134
135
  ctx.textAlign = 'start';
135
136
  } catch (e) {}
136
137
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mt5_shot",
3
- "defaultsVersion": 6,
4
- "description": "MT5 grafiğinin GERÇEK ekran görüntüsünü alır ve GÖRSELİ ÇAĞIRAN AJANA GÖSTERİR: MT5\\MQL5\\Files\\beast_cmd.json üzerinden BeastFinance EA'ya 'shot' komutu yazar, EA ChartScreenShot ile PNG üretir; PNG hem tool klasörüne hem <kullanıcı>\\beast_shots altına kopyalanır (dönen path send_file'a verilebilir) hem de data URL olarak sonuca gömülür — engine bunu sonraki turda ajana görsel olarak enjekte eder (embed:false kapatır). ÇOKLU SEMBOL: symbols:[\"GOLD\",\"EURUSD\",\"BTCUSD\"] (2-4 sembol) verirsen her sembolden bir kare çekilip TEK PNG'de birleştirilir (izleme listesinin tamamı TEK görselle gönderilir); timeframes ile birlikte verilirse sembol × periyot ızgarası kurulur (en fazla 6 kare). ÇOKLU ZAMAN DİLİMİ: timeframes:[\"M1\",\"M15\"] verirsen aynı sembolün periyotları SIRAYLA çekilip TEK PNG'de birleştirilir (layout:\"h\" soldan sağa — varsayılan; \"v\" üstten alta; 2-3 periyot) ve her panelin SAĞ ÜST köşesine BÜYÜK periyot etiketi (üstünde küçük sembol) çizilir — solda/sağda hangi sembol/periyot olduğu görüntüden bakışta okunur. Aktif grafik istenen sembol/periyot ile aynıysa ondan çekilir (Beast panosu + seviye çizgileri görünür), değilse EA geçici grafik açar (ChartOpen/ChartSetSymbolPeriod + BeastShot.tpl açık tema); çoklu sembolde her sembol için sırayla. Çekim öncesi grafiğe sağ boşluk payı (chart shift %12) uygulanır — SON MUM sağ kenara yapışmaz; aktif grafikte önceki ayar çekimden sonra geri yazılır. Komut id/ts ile yazılır, ack poll edilir ve PNG MQL5\\Files'ta belirene kadar beklenir; hata halinde gövde şu kodu taşır: err = no_terminal|ea_offline|cmd_write_fail|ea_no_ack|ea_error|png_yok|timeout|stitch_fail (+ cmdId, filesDir, diagnostic, ping). Grafik analizi/raporu ve ekip paylaşımı için birincil screenshot aracıdır.",
3
+ "defaultsVersion": 7,
4
+ "description": "MT5 grafiğinin GERÇEK ekran görüntüsünü alır ve GÖRSELİ ÇAĞIRAN AJANA GÖSTERİR: MT5\\MQL5\\Files\\beast_cmd.json üzerinden BeastFinance EA'ya 'shot' komutu yazar, EA ChartScreenShot ile PNG üretir; PNG hem tool klasörüne hem <kullanıcı>\\beast_shots altına kopyalanır (dönen path send_file'a verilebilir) hem de data URL olarak sonuca gömülür — engine bunu sonraki turda ajana görsel olarak enjekte eder (embed:false kapatır). ÇOKLU SEMBOL: symbols:[\"GOLD\",\"EURUSD\",\"BTCUSD\"] (2-4 sembol) verirsen her sembolden bir kare çekilip TEK PNG'de birleştirilir (izleme listesinin tamamı TEK görselle gönderilir); timeframes ile birlikte verilirse sembol × periyot ızgarası kurulur (en fazla 6 kare). ÇOKLU ZAMAN DİLİMİ: timeframes:[\"M1\",\"M15\"] verirsen aynı sembolün periyotları SIRAYLA çekilip TEK PNG'de birleştirilir (layout:\"h\" soldan sağa — varsayılan; \"v\" üstten alta; 2-3 periyot) ve her panelin SOL ÜST köşesine BÜYÜK periyot etiketi (üstünde küçük sembol) çizilir — solda/sağda hangi sembol/periyot olduğu görüntüden bakışta okunur; etiket son mumları örtmez. Aktif grafik istenen sembol/periyot ile aynıysa ondan çekilir (Beast panosu + seviye çizgileri görünür), değilse EA geçici grafik açar (ChartOpen/ChartSetSymbolPeriod + BeastShot.tpl açık tema); çoklu sembolde her sembol için sırayla. Çekim öncesi grafiğe sağ boşluk payı (chart shift %12) uygulanır — SON MUM sağ kenara yapışmaz; aktif grafikte önceki ayar çekimden sonra geri yazılır. Komut id/ts ile yazılır, ack poll edilir ve PNG MQL5\\Files'ta belirene kadar beklenir; hata halinde gövde şu kodu taşır: err = no_terminal|ea_offline|cmd_write_fail|ea_no_ack|ea_error|png_yok|timeout|stitch_fail (+ cmdId, filesDir, diagnostic, ping). Grafik analizi/raporu ve ekip paylaşımı için birincil screenshot aracıdır.",
5
5
  "parameters": {
6
6
  "type": "object",
7
7
  "properties": {
@@ -2032,7 +2032,7 @@ class Engine {
2032
2032
  modeBlock2 + '\n' +
2033
2033
  (roleBlockFull ? roleBlockFull + '\n' : '') +
2034
2034
  (teamLine || '') +
2035
- 'MT5 ARAÇLARI: mt5_status (bağlantı), mt5_account (hesap), mt5_market (canlı fiyat), mt5_positions (açık pozisyonlar), mt5_orders (bekleyen emirler), mt5_history (kapanan işlemler), mt5_alerts (fiyat alarmı: kur/listele/sil — modu SEN seçersin: once/repeat), mt5_ea (BeastFinance grafik panosu: status/ping/chart/note), mt5_trade (⚡PİYASA EMRİ: side:"buy"|"sell" ile ANLIK gir — bekleyen emir ZORUNLU DEĞİL; lot için volume yerine riskPct+sl ver, sistem hesaplar; limit/stop tipleri de kabul eder), mt5_close (TAM ya da KISMİ kapat: percent ile kısmi TP & kısmi stop — karar ajanın), mt5_modify (SL/TP), mt5_pending (BEKLEYEN emir: limit/stop + price — fiyatın seviyeye gelmesini beklemek için; anlık giriş için mt5_trade kullan), mt5_cancel (emir iptal).\n' +
2035
+ 'MT5 ARAÇLARI: mt5_status (bağlantı), mt5_account (hesap), mt5_market (canlı fiyat), mt5_positions (açık pozisyonlar), mt5_orders (bekleyen emirler), mt5_history (kapanan işlemler), mt5_alerts (fiyat alarmı: kur/listele/sil — modu SEN seçersin: once/repeat), mt5_ogrenme (SEMBOL BAZLI ÖĞRENME: işlem istatistiği + dersler — her kapanıştan sonra ders yaz, karar öncesi oku), mt5_limits (lot/pozisyon limitlerini gör/GÜNCELLE — yalnız trader), mt5_ea (BeastFinance grafik panosu: status/ping/chart/note), mt5_trade (⚡PİYASA EMRİ: side:"buy"|"sell" ile ANLIK gir — bekleyen emir ZORUNLU DEĞİL; lot için volume yerine riskPct+sl ver, sistem hesaplar; limit/stop tipleri de kabul eder), mt5_close (TAM ya da KISMİ kapat: percent ile kısmi TP & kısmi stop — karar ajanın), mt5_modify (SL/TP), mt5_pending (BEKLEYEN emir: limit/stop + price — fiyatın seviyeye gelmesini beklemek için; anlık giriş için mt5_trade kullan), mt5_cancel (emir iptal).\n' +
2036
2036
  'EMİR TİPLERİ (6 tip — HEPSİ AÇIK): buy_market/sell_market = anlık piyasa emri (fiyat beklenmez), buy_limit/sell_limit = bekleyen limit (price zorunlu), buy_stop/sell_stop = bekleyen stop (price zorunlu). mt5_trade ve mt5_pending İKİSİ DE 6 tipi kabul eder; emir tipini net söyle (ör. type:"sell_limit").\n' +
2037
2037
  '⚡HIZLI AKSİYON (ZORUNLU ZİHNİYET): piyasa emri (market buy/sell) HER ZAMAN elinde ve bekleyen emir vermek ZORUNDA DEĞİLSİN. Fırsat anıksa / teyitli kırılım, momentum, haber veya seviye reddi anında mt5_trade {symbol, side:"buy"|"sell", sl, tp, riskPct ya da volume, reason} ile ANINDA market gir — type bile yazmana gerek yok. Bekleyen emir YALNIZ fiyatın seviyeye gelmesini beklemek gerçekten mantıklıysa kurulur (ör. geri çekilme limiti); "emir" diye her şeyi bekleyen emirle yapma. Hızlı girişte tek çağrı yeter: volume yerine riskPct+sl ver → lot otomatik hesaplanır.\n' +
2038
2038
  'ALARM KARARI SENDE: mt5_alerts ile alarm kurarken mode ZORUNLU — "once" (tek seferlik; ilk tetiklemede kapanır) ya da "repeat" (tekrarlı; alarm açık kalır, koşul sürdükçe cooldownMin dakikada bir tekrar uyarır + seni uyandırır; cooldownMin varsayılan 5). Tekrarlı mı tek seferlik mi olacağını sen seç, varsayılana bırakma; seviyenin önemine göre cooldownMin belirle (ör. 5/15/60).\n' +
@@ -2068,9 +2068,13 @@ class Engine {
2068
2068
  : '') +
2069
2069
  'RİSK DİSİPLİNİ:\n' +
2070
2070
  '- SL\u2019siz pozisyon BIRAKMA: açtığın her işlemde mantıklı bir SL koy; SL\u2019siz kalan pozisyonu mt5_modify ile tamamla.\n' +
2071
+ '- KÂR KORUMA (SİSTEM OTOMATİK): kâr 0.3R\'ye ulaşınca SL, görülen en iyi kârın 0.15R gerisine kilitlenir — kâr eksiye dönmez. Bu yüzden kârı erken koru; hedefe ulaşmayan işlemde bile kârı geri verme.\n' +
2072
+ '- TP İÇİN RR ≥ 1.5 ŞARTI YOK: scalp/hızlı işlemlerde TP 0.3R-0.5R olabilir. Kısmi kâr alımı SERBEST ve TEŞVİK EDİLİR: 0.3-0.5R\'de pozisyonun bir kısmını mt5_close {percent, kind:"partial_tp"} ile realize et, kalanı korumalı trailing ile taşı. Amaç: sık işlem + hızlı kâr toplama; kârı masada bekletme.\n' +
2071
2073
  '- Martingale/kademeli lot artışı YASAK; kaybı geri kovalama (revenge trade) YASAK.\n' +
2072
- '- Emin olmadığında İŞLEM YOK — "BEKLE: <sebep>" yaz. Sık işlem > iyi işlem.\n' +
2073
- '- Hesap kaldıracı ve serbest marja göre pozisyon boyutunu düşük tut; tek işlemde serbest marjın büyük kısmını riske atma.\n' +
2074
+ '- Emin olmadığında İŞLEM YOK — "BEKLE: <sebep>" yaz. Sık işlem tek başına iyi işlem değil; ama onaylı fırsatta HIZLI gir, kârı hızlı topla.\n' +
2075
+ '- Hesap kaldıracı ve serbest marja göre pozisyon boyutunu düşük tut; tek işlemde serbest marjın büyük kısmını riske atma. Sık işlem için lotu değil, TUR SIKLIĞINI ve kısmi kâr alımını kullan.\n' +
2076
+ (session && session.financeLearn ? `SEMBOL BAZLI ÖĞRENME HAFIZAN (otomatik istatistik + kendi derslerin — kararlarında kullan):\n${session.financeLearn}\n` : '') +
2077
+ 'ÖĞRENME DÖNGÜSÜ (ZORUNLU): her kapanıştan (özellikle stop/TP sonrası) hemen sonra mt5_ogrenme {action:"add", symbol, text, kind} ile o sembolün dersini yaz (ne işe yaradı/yaramadı, hata mı, kural mı); yeni işlem kararından önce mt5_ogrenme {action:"stats", symbol} ya da list ile o sembolün geçmişini oku — aynı hatayı tekrarlama, işleyen deseni kullan. Geçersiz dersi remove/clear ile sil.\n' +
2074
2078
  (session && session.financeStrategy ? `SAHİBİNİN STRATEJİ NOTU (önceliklidir):\n${session.financeStrategy}\n` : '') +
2075
2079
  (session && session.financeShadow ? 'SHADOW MOD AKTİF: mt5_trade/mt5_pending emir GÖNDERMEZ — kararını teziyle raporla; gerçek işlem açılmaz (karar günlüğe yazılır).\n' : '') +
2076
2080
  (session && session.financeDigest ? `İŞLEM PERFORMANS GEÇMİŞİN (kendi kayıtların — kararlarında ders çıkar):\n${session.financeDigest}\n` : '') +
@@ -23,6 +23,8 @@ const NAMES = [
23
23
  'mt5_history',
24
24
  'mt5_alerts',
25
25
  'mt5_note',
26
+ 'mt5_ogrenme',
27
+ 'mt5_limits',
26
28
  'mt5_ea',
27
29
  'mt5_trade',
28
30
  'mt5_close',
@@ -43,6 +45,19 @@ let alertsApi = {
43
45
  set: () => null,
44
46
  remove: () => false,
45
47
  };
48
+ /* LIMIT API (main enjekte eder): trader lot/pozisyon limitlerini okuyup yazar */
49
+ let limitsApi = {
50
+ get: () => ({ minLot: 0.01, maxLot: 0.1, maxPositions: 3 }),
51
+ set: () => ({ ok: false, error: 'limit güncelleme kullanılamıyor' }),
52
+ };
53
+ /* ÖĞRENME API (main enjekte eder): sembol bazlı öğrenme deposu */
54
+ let learningApi = {
55
+ list: () => ({ ok: false, error: 'öğrenme deposu kullanılamıyor' }),
56
+ add: () => ({ ok: false, error: 'öğrenme deposu kullanılamıyor' }),
57
+ remove: () => ({ ok: false, error: 'öğrenme deposu kullanılamıyor' }),
58
+ clear: () => ({ ok: false, error: 'öğrenme deposu kullanılamıyor' }),
59
+ stats: () => ({ ok: false, error: 'öğrenme deposu kullanılamıyor' }),
60
+ };
46
61
 
47
62
  function setConfig(fn) {
48
63
  if (typeof fn === 'function') getCfg = fn;
@@ -60,6 +75,14 @@ function setDiscipline(fn) {
60
75
  if (typeof fn === 'function') discipline = fn;
61
76
  }
62
77
 
78
+ function setLimits(api) {
79
+ if (api && typeof api === 'object') limitsApi = { ...limitsApi, ...api };
80
+ }
81
+
82
+ function setLearning(api) {
83
+ if (api && typeof api === 'object') learningApi = { ...learningApi, ...api };
84
+ }
85
+
63
86
  async function bcall(method, params, timeoutMs) {
64
87
  const r = await mt5.call(method, params, timeoutMs);
65
88
  if (!r || r.ok !== true) throw new Error((r && r.error) || 'MT5 hatası');
@@ -237,6 +260,38 @@ const definitions = NAMES.map((name) => {
237
260
  required: ['symbol', 'note'],
238
261
  },
239
262
  },
263
+ mt5_limits: {
264
+ description:
265
+ 'LOT / POZİSYON LİMİTLERİNİ oku ve GÜNCELLE — trader karar mercii: mt5_limits {action:"get"|"set", minLot?, maxLot?, maxPositions?}. get: yürürlükteki min lot / max lot / max eşzamanlı pozisyon ve işlem riski %. set: yalnız ana trader (ve finance sohbet copilot\'ı) kullanabilir — rol/işçi ajanları değiştiremez. Volatilite rejimine göre limitleri sen ayarla (ör. sakin piyasada max lot artır, haber anında düşür); değişiklik ANINDA panelde ve sonraki turda geçerli olur. Kural: minLot maxLot\'u aşamaz.',
266
+ parameters: {
267
+ type: 'object',
268
+ properties: {
269
+ action: { type: 'string', enum: ['get', 'set'], description: 'get: mevcut limitler · set: güncelle' },
270
+ minLot: { type: 'number', description: 'set: yeni alt sınır (0.01-100)' },
271
+ maxLot: { type: 'number', description: 'set: yeni üst sınır (0.01-100)' },
272
+ maxPositions: { type: 'number', description: 'set: eşzamanlı pozisyon tavanı (1-20)' },
273
+ },
274
+ required: ['action'],
275
+ },
276
+ },
277
+ mt5_ogrenme: {
278
+ description:
279
+ 'SEMBOL BAZLI ÖĞRENME HAFIZASI — Beast Finance sürekli öğrenir: mt5_ogrenme {action:"list"|"add"|"remove"|"clear"|"stats", symbol?, text?, kind?, tags?, id?, ids?, all?}. Her sembolün KENDİ istatistiği (işlem/kazanç/kayıp, net, kâr yakalama, kâr geri verme) OTOMATİK birikir; ayrıca işlemlerden çıkardığın DERSLERİ sen kaydedersin. KURALLAR: (1) Her kapanıştan sonra (özellikle stop/tp sonrası) sembol için tek cümle ders yaz: hangi setup işe yaradı/yaramadı, saat/seans, SL yeri, hata mı hata yok mu — kind:"pattern"|"mistake"|"rule"|"observation". (2) Yeni işlem kararından ÖNCE ilgili sembolün list/stats çıktısını oku; aynı hatayı tekrarlama, işleyen deseni kullan. (3) Yanlış çıkan/genel geçersiz dersi remove/clear ile sil. action:"list" symbol verilmezse tüm sembollerin özetini döner. Ör: {action:"add", symbol:"XAUUSD", text:"Londra açılışında M5 EMA50 üstü momentum girişleri iyi çalışıyor", kind:"pattern"}.',
280
+ parameters: {
281
+ type: 'object',
282
+ properties: {
283
+ action: { type: 'string', enum: ['list', 'add', 'remove', 'clear', 'stats'] },
284
+ symbol: { type: 'string', description: 'Sembol (ör. XAUUSD) — öğrenme sembol bazlıdır' },
285
+ text: { type: 'string', description: 'add: öğrenilen ders/desen (kısa, net, tek cümle)' },
286
+ kind: { type: 'string', enum: ['pattern', 'mistake', 'rule', 'observation'], description: 'add: kayıt türü (varsayılan observation)' },
287
+ tags: { type: 'array', items: { type: 'string' }, description: 'add: etiketler (ör. ["scalp","london"])' },
288
+ id: { type: 'string', description: 'remove: kayıt id' },
289
+ ids: { type: 'array', items: { type: 'string' }, description: 'clear: silinecek kayıt id listesi' },
290
+ all: { type: 'boolean', description: 'clear: true → tüm öğrenme kayıtları (dikkatli)' },
291
+ },
292
+ required: ['action'],
293
+ },
294
+ },
240
295
  mt5_ea: {
241
296
  description:
242
297
  'BEASTFINANCE EA KÖPRÜSÜ (grafikte çalışan uzman danışman): action:"status" → EA heartbeat (yüklü mü, AutoTrading/EA izni, equity, pozisyon, son ack); "ping" → EA canlı yanıt; "chart" → aktif grafiğin sembol/periyot/fiyat/spread bilgisi; "note" → {symbol?, text, levels:[{price,label}]} grafik panosuna not + yatay seviye çizgileri yazar. NOT: yazdığın pano ve çizgiler görsel ajanın screenshot\'ında GÖRÜNÜR — GERÇEK grafik PNG\'si için varsayılan kurulu tool__mt5_shot kullan (EA "shot" komutu; görsel ajanın gözüne gelir), seviyeleri çizdikten sonra tool__mt5_shot ile görsel doğrula; grafiği agent_dm image ile ekibe paylaş.',
@@ -500,6 +555,53 @@ const handlers = {
500
555
  }, ctx || {});
501
556
  return { ok: true, saved: true };
502
557
  },
558
+ async mt5_limits(args, ctx) {
559
+ const action = String(args.action || 'get').toLowerCase();
560
+ if (action === 'get') {
561
+ return { ok: true, ...limitsApi.get() };
562
+ }
563
+ if (action === 'set') {
564
+ const patch = {};
565
+ for (const k of ['minLot', 'maxLot', 'maxPositions']) {
566
+ if (args[k] !== undefined && args[k] !== null && args[k] !== '') patch[k] = Number(args[k]);
567
+ }
568
+ if (!Object.keys(patch).length) {
569
+ return { ok: false, error: 'set için en az bir alan ver: minLot / maxLot / maxPositions' };
570
+ }
571
+ return await limitsApi.set(patch, ctx || {});
572
+ }
573
+ return { ok: false, error: 'action: get|set' };
574
+ },
575
+ async mt5_ogrenme(args, ctx) {
576
+ const action = String(args.action || 'list').toLowerCase();
577
+ const symbol = String(args.symbol || '').trim().toUpperCase();
578
+ if (action === 'list') return learningApi.list({ symbol });
579
+ if (action === 'stats') return learningApi.stats({ symbol });
580
+ if (action === 'add') {
581
+ const text = String(args.text || '').replace(/\s+/g, ' ').trim();
582
+ if (!symbol) return { ok: false, error: 'symbol gerekli — öğrenme sembol bazlıdır' };
583
+ if (!text) return { ok: false, error: 'text gerekli (öğrenilen ders)' };
584
+ const kindRaw = String(args.kind || 'observation').toLowerCase();
585
+ const kind = ['pattern', 'mistake', 'rule', 'observation'].includes(kindRaw) ? kindRaw : 'observation';
586
+ const tags = Array.isArray(args.tags) ? args.tags.map((t) => String(t || '').trim()).filter(Boolean).slice(0, 6) : [];
587
+ const res = learningApi.add({ symbol, text: text.slice(0, 600), kind, tags, sid: (ctx && ctx.sessionId) || '' });
588
+ if (res && res.ok) {
589
+ noteTrade('learn', { symbol, text: text.slice(0, 300), kind }, ctx || {});
590
+ }
591
+ return res;
592
+ }
593
+ if (action === 'remove') {
594
+ const id = String(args.id || '').trim();
595
+ if (!id) return { ok: false, error: 'remove için id gerekli' };
596
+ return learningApi.remove({ id, symbol });
597
+ }
598
+ if (action === 'clear') {
599
+ const ids = Array.isArray(args.ids) ? args.ids.map((x) => String(x || '').trim()).filter(Boolean) : [];
600
+ const all = args.all === true || String(args.all || '').toLowerCase() === 'true';
601
+ return learningApi.clear({ ids, symbol, all });
602
+ }
603
+ return { ok: false, error: 'action: list|add|remove|clear|stats' };
604
+ },
503
605
  async mt5_ea(args) {
504
606
  if (!mt5.running) return notConnected();
505
607
  const action = String(args.action || 'status').toLowerCase();
@@ -786,4 +888,4 @@ const handlers = {
786
888
  },
787
889
  };
788
890
 
789
- module.exports = { definitions, handlers, NAMES, setConfig, setNotify, setAlerts, setDiscipline };
891
+ module.exports = { definitions, handlers, NAMES, setConfig, setNotify, setAlerts, setDiscipline, setLimits, setLearning };
@@ -53,8 +53,15 @@ function plan(pos, meta, st, cfg) {
53
53
  const trailDist = num(cfg && cfg.trailR, 0.5);
54
54
  const partialR = num(cfg && cfg.partialR, 0);
55
55
  const partialPct = num(cfg && cfg.partialPct, 50);
56
+ /* KÂR KORUMA (erken trailing): kâr protectStartR'ye ulaşınca SL, GÖRÜLEN EN
57
+ İYİ kârın protectDistR gerisine kilitlenir — spike dönüşlerinde kâr
58
+ eksiye dönmez (0 = kapalı; varsayılan main finCfg'de verilir). */
59
+ const protectStart = num(cfg && cfg.protectStartR, 0);
60
+ const protectDist = num(cfg && cfg.protectDistR, 0);
56
61
 
57
62
  const profit = isBuy ? ref - entry : entry - ref;
63
+ /* en iyi kâr: tick'ler arası spike'lar kaybolmasın — main st.bestProfit yazar */
64
+ const bestProfit = Math.max(profit, num(st && st.bestProfit, profit));
58
65
 
59
66
  /* ---- BE + trailing: hedef SL'yi birleştir, asla geriye taşıma ---- */
60
67
  if (r > 0) {
@@ -70,10 +77,21 @@ function plan(pos, meta, st, cfg) {
70
77
  if (!(desired > 0) || (isBuy ? trailSl > desired : trailSl < desired)) desired = trailSl;
71
78
  want = true;
72
79
  }
80
+ /* kâr kilidi: ulaşılan EN İYİ kârın bir kısmı garantiye alınır */
81
+ if (protectStart > 0 && protectDist > 0 && bestProfit >= protectStart * r) {
82
+ const locked = bestProfit - protectDist * r;
83
+ const lockSl = isBuy ? entry + locked : entry - locked;
84
+ if (locked > 0 && (!(desired > 0) || (isBuy ? lockSl > desired : lockSl < desired))) desired = lockSl;
85
+ want = true;
86
+ }
73
87
  if (want && desired > 0) {
74
88
  /* broker stop mesafesi: SL fiyata çok yakınsa geçersiz — kırp */
75
89
  desired = isBuy ? Math.min(desired, ref - minDist) : Math.max(desired, ref + minDist);
76
- const improved = isBuy ? desired > sl + point * 0.5 : (sl === 0 || desired < sl - point * 0.5);
90
+ /* KIRPMA KİLİDİ BOZDU: fiyat geri çekildi, hedef SL girişin kaybına
91
+ indi — kâr kilidini kaybı kilitleyen SL'e çevirme; hiç yazma
92
+ (fiyat toparlayınca kilit yeniden uygulanır). */
93
+ const lockBroken = isBuy ? desired < entry : desired > entry;
94
+ const improved = !lockBroken && (isBuy ? desired > sl + point * 0.5 : (sl === 0 || desired < sl - point * 0.5));
77
95
  if (improved && desired > 0) {
78
96
  out.actions.push({ kind: 'modify', sl: roundTo(desired, digits) });
79
97
  }
@@ -43,7 +43,8 @@ Kural: LTF sinyali HTF yönüne tersse işlem açma.
43
43
  - Giriş: BOS sonrası geri çekilme (pullback) + retest teyidi; kovalama yok.
44
44
  - SL: yapının arkasına (son swing low/high'ın ötesi) + spread/ATR payı. Yapının İÇİNE SL koyma.
45
45
  - Geniş SL tercih ediliyorsa: ~4×ATR SL, ~2×ATR'de BE (break-even) taşıma.
46
- - TP: bir sonraki HTF bölgesi; R:R en az 1.5–2. Kısmi kâr alımı serbest.
46
+ - TP: bir sonraki HTF bölgesi. R:R ESNEKTİR — scalp/hızlı işlemlerde TP 0.3R–0.5R olabilir; R:R ≥ 1.5 ŞART DEĞİL. Kısmi kâr alımı serbest ve teşvik edilir: 0.3-0.5R'de pozisyonun bir kısmını realize et, kalanı korumalı trailing ile taşı.
47
+ - KÂR KORUMA: +0.3R'de sistem SL'yi kilitler (en iyi kârın 0.15R gerisi); kârın eksiye dönmesine izin verme — gerekiyorsa 0.3-0.5R'de kısmi TP ile kârı cebe al.
47
48
  - BE'e çekmeden SL'siz/işlemsiz kalma; her pozisyonun SL'si olsun.
48
49
 
49
50
  ## 6) YASAKLAR
@@ -30,6 +30,8 @@ version: 1.0.0
30
30
  - **Korelasyon**: XAUUSD/EURUSD ile DXY ters koreledir; aynı yönde iki korelasyonlu pozisyon riski katlar.
31
31
  - SL'siz pozisyon varsa ilk iş: SL tamamla veya kapat.
32
32
  - Hedefine ulaşan/kâra geçen pozisyonda BE veya kısmi kâr; trailing ATR bazlı.
33
+ - **Kâr koruma:** +0.3R'de sistem SL'yi en iyi kârın 0.15R gerisine kilitler; +0.3-0.5R'de kısmi TP (ör. %50) ile kârı cebe al, kalanı trailing'e bırak. TP için R:R ≥ 1.5 şartı YOK — hızlı kâr toplama (scalp) serbest; ama kârı kale verme.
34
+ - **Sembol bazlı öğrenme:** her kapanıştan sonra mt5_ogrenme add ile dersi yaz (ne işe yaradı/yaramadı); yeni işlem öncesi o sembolün stats/list geçmişini oku — aynı hatayı tekrarlama.
33
35
 
34
36
  ## 4) KAYIP LİMİTLERİ
35
37
 
package/src/main.js CHANGED
@@ -5578,6 +5578,10 @@ ipcMain.handle('sessions:create', () => {
5578
5578
  try {
5579
5579
  engine.markFinance(v.id, false);
5580
5580
  v.finance = true;
5581
+ /* finance sohbeti TAM araç setiyle koşar (trader botu dahil):
5582
+ bot skill kısıtı finance araçlarını (mt5_*) düşürmesin */
5583
+ engine.setSessionTools(v.id, null);
5584
+ engine.setSessionPerm(v.id, 'all');
5581
5585
  } catch {}
5582
5586
  }
5583
5587
  return v;
@@ -5639,7 +5643,17 @@ ipcMain.handle('agent:send', (_e, { sessionId, text }) => {
5639
5643
  const fsid = String(sessionId);
5640
5644
  let fsess = engine.cache.get(fsid);
5641
5645
  if (!fsess) { try { fsess = engine._load(fsid); } catch {} }
5642
- if (fsess && !fsess.botId && !fsess.bgJob && financeState.mode) {
5646
+ /* botIdsiz sohbetler + Trader botu (finance'ın kendi botu) finance'a girer;
5647
+ diğer müşteri botlarının kanalları finance izolasyonuna karışmaz */
5648
+ const finBot = !fsess || !fsess.botId || fsess.botId === 'trader';
5649
+ if (fsess && finBot && !fsess.bgJob && financeState.mode) {
5650
+ /* Trader botu finance sohbetinde tam araç setiyle koşar (mt5_* açık) */
5651
+ if (fsess.botId === 'trader') {
5652
+ try {
5653
+ engine.setSessionTools(fsid, null);
5654
+ engine.setSessionPerm(fsid, 'all');
5655
+ } catch {}
5656
+ }
5643
5657
  if (!fsess.finance) {
5644
5658
  engine.markFinance(fsid, false);
5645
5659
  finApplyTraderFields(fsess);
@@ -9338,6 +9352,8 @@ const financeState = {
9338
9352
  hoursTimer: null, /* trade saatleri otomatik duraklatma denetimi (10 sn) */
9339
9353
  hoursPaused: null, /* pencere kapanınca durdurulan ajanların planı — açılınca geri gelir */
9340
9354
  alerts: [], /* fiyat alarmları (kalıcı: finance/alerts.json) */
9355
+ learn: null, /* sembol bazlı öğrenme deposu (kalıcı: finance/ogrenme.json) */
9356
+ learnReady: false, /* depo diskten yüklendi mi (tembel yükleme) */
9341
9357
  stats: null, /* son performans özeti (kalıcı: finance/stats.json) */
9342
9358
  statsAt: 0,
9343
9359
  lastStatsAt: 0,
@@ -9421,6 +9437,11 @@ function finCfg() {
9421
9437
  if (!Number.isFinite(Number(f.beOffsetR))) f.beOffsetR = 0.05;
9422
9438
  if (!Number.isFinite(Number(f.trailStartR))) f.trailStartR = 1.5;
9423
9439
  if (!Number.isFinite(Number(f.trailR))) f.trailR = 0.5;
9440
+ /* KÂR KORUMA (erken trailing): kâr bu R'ye ulaşınca SL, görülen en iyi
9441
+ kârın protectDistR gerisine kilitlenir — 0.3R'de başlar, kâr eksiye
9442
+ dönmez (0 = kapalı) */
9443
+ if (!Number.isFinite(Number(f.protectStartR))) f.protectStartR = 0.3;
9444
+ if (!Number.isFinite(Number(f.protectDistR))) f.protectDistR = 0.15;
9424
9445
  if (!Number.isFinite(Number(f.partialR))) f.partialR = 0;
9425
9446
  if (!Number.isFinite(Number(f.partialPct))) f.partialPct = 50;
9426
9447
  /* BİLDİRİM: işlem/koruma/alarm olayları bağlı kanallara (TG/WA/Discord)
@@ -10624,6 +10645,8 @@ async function finRecordClose(ticket, st) {
10624
10645
  finExcFlush(true);
10625
10646
  const kind = finwatch.closeKind(closeReason);
10626
10647
  finJournal({ kind: 'close', ticket, symbol, net: rounded, side: (st && st.side) || '', mfe, mae, reason: kind || '' });
10648
+ /* SEMBOL BAZLI SÜREKLİ ÖĞRENME: her kapanış istatistiğe işlenir (mt5_ogrenme) */
10649
+ finLearnRecordClose({ symbol, side: (st && st.side) || '', net: rounded, mfe, mae, reason: kind || '' });
10627
10650
  const pl = rounded == null ? '' : ` · K/Z ${rounded >= 0 ? '+' : ''}${rounded.toFixed(2)}`;
10628
10651
  const line = `🏁 Pozisyon kapandı: ${symbol || '?'} #${ticket}${pl}`;
10629
10652
  financeLog('[watchdog] ' + line);
@@ -10838,6 +10861,11 @@ async function finWatchTick() {
10838
10861
  }
10839
10862
  finWatchSave();
10840
10863
  }
10864
+ /* KÂR KORUMA: görülen EN İYİ kâr (fiyat farkı) tick aralarında da korunur */
10865
+ {
10866
+ const dist = st.side === 'buy' ? (Number(p.price_current) || 0) - st.entry : st.entry - (Number(p.price_current) || 0);
10867
+ if (isFinite(dist) && !(Number(st.bestProfit) >= dist)) st.bestProfit = dist;
10868
+ }
10841
10869
  if (cfg.watchdog === false) continue;
10842
10870
  let meta = null;
10843
10871
  try { meta = await finSymbolMetaGet(p.symbol); } catch {}
@@ -11073,6 +11101,14 @@ function finTradeEvent(kRaw, dataRaw, sidRaw) {
11073
11101
  finJournal({ kind: 'note', sid, agent: who, symbol: d.symbol, note: d.note, ticket: d.ticket || 0, side: d.side || '' });
11074
11102
  return;
11075
11103
  }
11104
+ /* SEMBOL BAZLI ÖĞRENME kaydı: günlüğe + AKIŞ paneline düşer */
11105
+ if (k === 'learn') {
11106
+ finJournal({ kind: 'learn', sid, agent: who, symbol: d.symbol, text: d.text, learnKind: d.kind || 'observation' });
11107
+ const line = `🧠 ÖĞRENME: ${d.symbol || '?'} — ${String(d.text || '').replace(/\s+/g, ' ').slice(0, 160)}`;
11108
+ financeLog('[ogrenme] ' + line);
11109
+ finPush('trade', { line });
11110
+ return;
11111
+ }
11076
11112
  /* SHADOW: gerçek emir yok — karar teziyle günlüğe yazılır (test/ölçüm) */
11077
11113
  if (k === 'shadow') {
11078
11114
  finJournal({ kind: 'shadow', sid, agent: who, symbol: d.symbol, side: d.side || '', volume: d.volume, sl: d.sl || 0, tp: d.tp || 0, price: d.price || 0, type: d.type || '', reason: d.reason || '' });
@@ -11162,6 +11198,317 @@ try {
11162
11198
  });
11163
11199
  } catch {}
11164
11200
 
11201
+ /* ---------- LIMIT API (mt5_limits) ----------
11202
+ Ana trader / finance sohbet oturumu min lot-max lot-pozisyon tavanını
11203
+ güncelleyebilir; rol/işçi ajanları yalnız okur. Değişiklik ayarlara yazılır,
11204
+ koşan oturumlara ANINDA işlenir ve panele duyurulur. */
11205
+ function finLimitsSnapshot() {
11206
+ const f = finCfg();
11207
+ return {
11208
+ minLot: Number(f.minLot) || 0.01,
11209
+ maxLot: Number(f.maxLot) || 0.1,
11210
+ maxPositions: Number(f.maxPositions) || 3,
11211
+ riskPerTradePct: Number(f.riskPerTradePct) || 0,
11212
+ };
11213
+ }
11214
+
11215
+ function finLimitsSessionAllowed(sid) {
11216
+ const id = String(sid || '');
11217
+ if (!id || !engine) return false;
11218
+ let s = null;
11219
+ try { s = engine.cache.get(id) || engine._load(id); } catch {}
11220
+ if (!s || !s.finance) return false;
11221
+ if (s.financeRole) return false; /* analiz ekibi rol ajanı — limit değiştiremez */
11222
+ if (s.bgJob) return false; /* arka plan işçisi — limit değiştiremez */
11223
+ return true;
11224
+ }
11225
+
11226
+ function finSyncLimitsToSessions() {
11227
+ const lim = finLimitsSnapshot();
11228
+ if (engine) {
11229
+ try {
11230
+ for (const [sid] of financeState.agents) {
11231
+ const s = engine.cache.get(String(sid));
11232
+ if (s) s.financeLimits = { minLot: lim.minLot, maxLot: lim.maxLot, maxPositions: lim.maxPositions };
11233
+ }
11234
+ for (const [, s] of engine.cache) {
11235
+ if (!s || !s.finance || s.bgJob || !s.financeLimits) continue;
11236
+ s.financeLimits = { minLot: lim.minLot, maxLot: lim.maxLot, maxPositions: lim.maxPositions };
11237
+ }
11238
+ } catch {}
11239
+ }
11240
+ finPush('limits', { ...lim });
11241
+ }
11242
+
11243
+ function finLimitsSet(patch, ctx) {
11244
+ const sid = String((ctx && ctx.sessionId) || '');
11245
+ if (!finLimitsSessionAllowed(sid)) {
11246
+ return { ok: false, error: 'limit değiştirme yetkisi yalnız ana trader / finance sohbet oturumunda — rol ve işçi ajanları limit değiştiremez' };
11247
+ }
11248
+ const f = finCfg();
11249
+ const notes = [];
11250
+ if (patch.maxLot !== undefined) {
11251
+ const v = Number(patch.maxLot);
11252
+ if (!(v >= 0.01) || v > 100) return { ok: false, error: 'maxLot 0.01-100 arasında olmalı' };
11253
+ f.maxLot = Math.round(v * 100) / 100;
11254
+ }
11255
+ if (patch.minLot !== undefined) {
11256
+ const v = Number(patch.minLot);
11257
+ if (!(v >= 0.01) || v > 100) return { ok: false, error: 'minLot 0.01-100 arasında olmalı' };
11258
+ f.minLot = Math.round(v * 100) / 100;
11259
+ }
11260
+ if (patch.maxPositions !== undefined) {
11261
+ const v = Math.round(Number(patch.maxPositions));
11262
+ if (!(v >= 1) || v > 20) return { ok: false, error: 'maxPositions 1-20 arasında olmalı' };
11263
+ f.maxPositions = v;
11264
+ }
11265
+ if (f.minLot > f.maxLot) {
11266
+ notes.push(`minLot (${f.minLot}) maxLot'tan (${f.maxLot}) büyüktü → minLot ${f.maxLot} yapıldı`);
11267
+ f.minLot = f.maxLot;
11268
+ }
11269
+ try { saveSettings(); } catch {}
11270
+ finSyncLimitsToSessions();
11271
+ const line = `⚙️ LİMİTLER GÜNCELLENDİ: min lot ${f.minLot} · max lot ${f.maxLot} · max pozisyon ${f.maxPositions}${sid ? ' (ajan kararı)' : ''}`;
11272
+ financeLog('[limit] ' + line);
11273
+ financeNotify(line, 'limit', true);
11274
+ try { finJournal({ kind: 'limits', minLot: f.minLot, maxLot: f.maxLot, maxPositions: f.maxPositions, sid }); } catch {}
11275
+ return { ok: true, limits: finLimitsSnapshot(), note: notes.length ? notes.join('; ') : undefined };
11276
+ }
11277
+
11278
+ /* ---------- MT5 ÖĞRENME (mt5_ogrenme) — SEMBOL BAZLI SÜREKLİ ÖĞRENME ----------
11279
+ finance/ogrenme.json: her sembol için (1) OTOMATİK istatistik (işlem sayısı,
11280
+ kazanç/kayıp, net, MFE/MAE, kâr geri verme) (2) ajanın yazdığı dersler.
11281
+ Trader turunda bu özet sistem promptuna gömülür — sistem kendi geçmişinden
11282
+ öğrenir; her kapanış otomatik işlenir, dersleri ajan mt5_ogrenme ile yazar. */
11283
+ const FIN_LEARN_MAX_NOTES = 120;
11284
+ const FIN_LEARN_MAX_TRADES = 60;
11285
+
11286
+ function finLearnEmptyStats() {
11287
+ return {
11288
+ trades: 0, wins: 0, losses: 0, net: 0,
11289
+ mfe: 0, mae: 0, givebacks: 0, givebackAmount: 0,
11290
+ lastAt: 0, lastNet: 0, streak: 0, bestNet: 0, worstNet: 0,
11291
+ };
11292
+ }
11293
+
11294
+ function finLearnLoad() {
11295
+ if (financeState.learnReady) return financeState.learn;
11296
+ financeState.learnReady = true;
11297
+ const learn = { symbols: {}, updatedAt: 0 };
11298
+ try {
11299
+ const raw = finReadJson(finFile('ogrenme.json'), null);
11300
+ if (raw && typeof raw === 'object' && raw.symbols && typeof raw.symbols === 'object') {
11301
+ for (const [k, v] of Object.entries(raw.symbols)) {
11302
+ const sym = String(k || '').trim().toUpperCase();
11303
+ if (!sym || !v || typeof v !== 'object') continue;
11304
+ learn.symbols[sym] = {
11305
+ notes: Array.isArray(v.notes) ? v.notes.filter((n) => n && n.id && n.text).slice(-FIN_LEARN_MAX_NOTES) : [],
11306
+ trades: Array.isArray(v.trades) ? v.trades.filter(Boolean).slice(-FIN_LEARN_MAX_TRADES) : [],
11307
+ stats: v.stats && typeof v.stats === 'object' ? { ...finLearnEmptyStats(), ...v.stats } : finLearnEmptyStats(),
11308
+ };
11309
+ }
11310
+ if (Number(raw.updatedAt)) learn.updatedAt = Number(raw.updatedAt);
11311
+ }
11312
+ } catch {}
11313
+ financeState.learn = learn;
11314
+ return learn;
11315
+ }
11316
+
11317
+ function finLearnSave() {
11318
+ const learn = finLearnLoad();
11319
+ learn.updatedAt = Date.now();
11320
+ try { finWriteJson(finFile('ogrenme.json'), learn); } catch {}
11321
+ }
11322
+
11323
+ function finLearnSym(symbol) {
11324
+ const sym = String(symbol || '').trim().toUpperCase();
11325
+ if (!sym) return null;
11326
+ const learn = finLearnLoad();
11327
+ if (!learn.symbols[sym]) learn.symbols[sym] = { notes: [], trades: [], stats: finLearnEmptyStats() };
11328
+ const e = learn.symbols[sym];
11329
+ e.stats = { ...finLearnEmptyStats(), ...(e.stats || {}) };
11330
+ return e;
11331
+ }
11332
+
11333
+ /* Kapanan her işlem otomatik öğrenme girdisi olur (sembol bazlı) */
11334
+ function finLearnRecordClose({ symbol, side, net, mfe, mae, reason }) {
11335
+ try {
11336
+ if (net == null) return; /* K/Z okunamadı — sahte kayıt yazma */
11337
+ const n = Number(net);
11338
+ if (!isFinite(n) || !symbol) return;
11339
+ const e = finLearnSym(symbol);
11340
+ if (!e) return;
11341
+ const st = e.stats;
11342
+ const m = Number(mfe);
11343
+ const ma = Number(mae);
11344
+ st.trades += 1;
11345
+ if (n >= 0) st.wins += 1;
11346
+ else st.losses += 1;
11347
+ st.net = Math.round((Number(st.net) + n) * 100) / 100;
11348
+ st.lastAt = Date.now();
11349
+ st.lastNet = Math.round(n * 100) / 100;
11350
+ st.streak = n < 0 ? Number(st.streak || 0) + 1 : 0;
11351
+ st.bestNet = Math.max(Number(st.bestNet) || 0, Math.round(n * 100) / 100);
11352
+ st.worstNet = Math.min(Number(st.worstNet) || 0, Math.round(n * 100) / 100);
11353
+ if (isFinite(m)) {
11354
+ st.mfe = Math.round(((Number(st.mfe) || 0) + m) * 100) / 100;
11355
+ if (m > 0 && n < 0) {
11356
+ st.givebacks += 1;
11357
+ st.givebackAmount = Math.round(((Number(st.givebackAmount) || 0) + m) * 100) / 100;
11358
+ }
11359
+ }
11360
+ if (isFinite(ma)) st.mae = Math.round(((Number(st.mae) || 0) + ma) * 100) / 100;
11361
+ e.trades.push({
11362
+ at: Date.now(),
11363
+ side: String(side || ''),
11364
+ net: Math.round(n * 100) / 100,
11365
+ mfe: isFinite(m) ? Math.round(m * 100) / 100 : null,
11366
+ mae: isFinite(ma) ? Math.round(ma * 100) / 100 : null,
11367
+ reason: String(reason || '').slice(0, 40),
11368
+ });
11369
+ while (e.trades.length > FIN_LEARN_MAX_TRADES) e.trades.shift();
11370
+ finLearnSave();
11371
+ } catch {}
11372
+ }
11373
+
11374
+ /* Trader promptuna gömülen sembol bazlı öğrenme özeti */
11375
+ function finBuildLearnDigest(symbols) {
11376
+ const learn = finLearnLoad();
11377
+ const prefer = Array.isArray(symbols) ? symbols.map((s) => String(s || '').trim().toUpperCase()).filter(Boolean) : [];
11378
+ const keys = [...new Set([...prefer, ...Object.keys(learn.symbols)])].filter((k) => learn.symbols[k]);
11379
+ if (!keys.length) return '';
11380
+ const L = [];
11381
+ for (const sym of keys.slice(0, 10)) {
11382
+ const e = learn.symbols[sym];
11383
+ const st = { ...finLearnEmptyStats(), ...(e.stats || {}) };
11384
+ if (st.trades > 0) {
11385
+ const wr = Math.round((st.wins / st.trades) * 100);
11386
+ const parts = [`${st.trades} işlem`, `%${wr} kazanç`, `net ${st.net >= 0 ? '+' : ''}${st.net}`];
11387
+ if (st.streak >= 2) parts.push(`${st.streak} ardışık kayıp`);
11388
+ if (st.givebacks > 0) parts.push(`${st.givebacks} kez kâr geri verildi (tepe kâr ${Math.round(st.givebackAmount * 100) / 100} eksiye döndü)`);
11389
+ if (st.lastAt) parts.push(`son: ${st.lastNet >= 0 ? '+' : ''}${st.lastNet}`);
11390
+ L.push(`- ${sym}: ${parts.join(' · ')}`);
11391
+ }
11392
+ if (e.notes.length) {
11393
+ const last = e.notes[e.notes.length - 1];
11394
+ const extra = e.notes.length > 1 ? ` (+${e.notes.length - 1} ders)` : '';
11395
+ L.push(` ders${extra}: "${String(last.text || '').slice(0, 160)}"`);
11396
+ }
11397
+ }
11398
+ return L.join('\n');
11399
+ }
11400
+
11401
+ function finLearnApi() {
11402
+ const norm = (s) => String(s || '').trim().toUpperCase();
11403
+ return {
11404
+ list: ({ symbol } = {}) => {
11405
+ const learn = finLearnLoad();
11406
+ const sym = norm(symbol);
11407
+ if (sym) {
11408
+ const e = learn.symbols[sym];
11409
+ if (!e) return { ok: true, symbol: sym, count: 0, notes: [], trades: [], stats: finLearnEmptyStats() };
11410
+ return {
11411
+ ok: true,
11412
+ symbol: sym,
11413
+ count: e.notes.length,
11414
+ notes: e.notes.slice(-25),
11415
+ trades: e.trades.slice(-12),
11416
+ stats: { ...finLearnEmptyStats(), ...(e.stats || {}) },
11417
+ };
11418
+ }
11419
+ const rows = Object.entries(learn.symbols)
11420
+ .map(([s, e]) => ({
11421
+ symbol: s,
11422
+ notes: e.notes.length,
11423
+ stats: { ...finLearnEmptyStats(), ...(e.stats || {}) },
11424
+ lastNote: String(((e.notes[e.notes.length - 1] || {}).text) || ''),
11425
+ }))
11426
+ .sort((a, b) => (b.stats.lastAt || 0) - (a.stats.lastAt || 0));
11427
+ return { ok: true, count: rows.length, symbols: rows.slice(0, 40) };
11428
+ },
11429
+ stats: ({ symbol } = {}) => {
11430
+ const learn = finLearnLoad();
11431
+ const sym = norm(symbol);
11432
+ if (sym) return { ok: true, symbol: sym, stats: { ...finLearnEmptyStats(), ...((learn.symbols[sym] || {}).stats || {}) } };
11433
+ const stats = {};
11434
+ for (const [s, e] of Object.entries(learn.symbols)) stats[s] = { ...finLearnEmptyStats(), ...(e.stats || {}) };
11435
+ return { ok: true, count: Object.keys(stats).length, stats };
11436
+ },
11437
+ add: ({ symbol, text, kind, tags, sid } = {}) => {
11438
+ const sym = norm(symbol);
11439
+ const t = String(text || '').replace(/\s+/g, ' ').trim();
11440
+ if (!sym || !t) return { ok: false, error: 'symbol ve text gerekli' };
11441
+ const e = finLearnSym(sym);
11442
+ 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)) {
11446
+ return { ok: true, duplicate: true, symbol: sym, count: e.notes.length };
11447
+ }
11448
+ const note = {
11449
+ id: 'l' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
11450
+ at: Date.now(),
11451
+ text: t.slice(0, 600),
11452
+ kind: ['pattern', 'mistake', 'rule', 'observation'].includes(String(kind || '')) ? String(kind) : 'observation',
11453
+ tags: Array.isArray(tags) ? tags.map((x) => String(x || '').slice(0, 24)).filter(Boolean).slice(0, 6) : [],
11454
+ sid: String(sid || ''),
11455
+ };
11456
+ e.notes.push(note);
11457
+ while (e.notes.length > FIN_LEARN_MAX_NOTES) e.notes.shift();
11458
+ finLearnSave();
11459
+ return { ok: true, id: note.id, symbol: sym, count: e.notes.length };
11460
+ },
11461
+ remove: ({ id, symbol } = {}) => {
11462
+ const learn = finLearnLoad();
11463
+ const sym = norm(symbol);
11464
+ const key = String(id || '');
11465
+ if (!key) return { ok: false, error: 'id gerekli' };
11466
+ let removed = 0;
11467
+ const targets = sym ? [sym] : Object.keys(learn.symbols);
11468
+ for (const s of targets) {
11469
+ const e = learn.symbols[s];
11470
+ if (!e) continue;
11471
+ const before = e.notes.length;
11472
+ e.notes = e.notes.filter((n) => String(n.id) !== key);
11473
+ removed += before - e.notes.length;
11474
+ }
11475
+ if (removed) finLearnSave();
11476
+ return { ok: true, removed, id: key };
11477
+ },
11478
+ clear: ({ ids = [], symbol, all } = {}) => {
11479
+ const learn = finLearnLoad();
11480
+ const sym = norm(symbol);
11481
+ const list = Array.isArray(ids) ? ids.map((x) => String(x || '').trim()).filter(Boolean) : [];
11482
+ let removed = 0;
11483
+ const wipe = (e) => {
11484
+ if (!e) return;
11485
+ if (list.length) {
11486
+ const before = e.notes.length;
11487
+ e.notes = e.notes.filter((n) => !list.includes(String(n.id)));
11488
+ removed += before - e.notes.length;
11489
+ } else {
11490
+ removed += e.notes.length;
11491
+ e.notes = [];
11492
+ }
11493
+ };
11494
+ if (all) for (const e of Object.values(learn.symbols)) wipe(e);
11495
+ else if (sym) wipe(learn.symbols[sym]);
11496
+ else return { ok: false, error: 'clear için symbol ya da all:true ver (ids de verilebilir)' };
11497
+ if (removed) finLearnSave();
11498
+ return { ok: true, removed };
11499
+ },
11500
+ };
11501
+ }
11502
+
11503
+ /* LIMIT + ÖĞRENME kancalarını financetools'a bağla */
11504
+ try {
11505
+ financetools.setLimits({
11506
+ get: () => finLimitsSnapshot(),
11507
+ set: (patch, ctx) => finLimitsSet(patch, ctx),
11508
+ });
11509
+ financetools.setLearning(finLearnApi());
11510
+ } catch {}
11511
+
11165
11512
  /* KİŞİSEL TOOLLAR: çalışma kaydı renderer'a (TOOLS konsolu buradan beslenir) */
11166
11513
  try {
11167
11514
  customtools.setNotify((entry) => {
@@ -11540,6 +11887,8 @@ function finApplyTraderFields(s, symbolsOverride, role, isMain) {
11540
11887
  /* ZORUNLU TEK skill: rol ajanı → roleSkills[rol], ana trader → roleSkills.trader */
11541
11888
  const skillKey = r || (s.financePlaybook ? 'trader' : '');
11542
11889
  s.financeRoleSkills = (skillKey && f.roleSkills && f.roleSkills[skillKey]) || [];
11890
+ /* SEMBOL BAZLI ÖĞRENME ÖZETİ: tur promptuna gömülür (kendi geçmişinden öğren) */
11891
+ try { s.financeLearn = finBuildLearnDigest(s.financeSymbols); } catch { s.financeLearn = ''; }
11543
11892
  engine.cache.set(String(s.id), s);
11544
11893
  }
11545
11894
 
@@ -11636,6 +11985,9 @@ function finTraderBrief(agent) {
11636
11985
  `Beast Finance ${who} başlatıldı — ilk tur: strateji çerçeveni kur ve piyasa taramasını yap.`,
11637
11986
  `Odak semboller: ${syms || '(boş — mt5_status ile terminale bak, mantıklı semboller seç)'}`,
11638
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}`,
11988
+ 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).`
11990
+ : '',
11639
11991
  roleDef
11640
11992
  ? `UZMANLIK: ${roleDef.desc} — raporlarını bu çerçevede yaz; İŞLEM AÇMA, yalnız analiz + net öneri üret.`
11641
11993
  : '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ı.',
@@ -11647,7 +11999,8 @@ function finTraderBrief(agent) {
11647
11999
  f.shadowMode ? 'SHADOW MOD AÇIK: emir gönderilmez — kararlarını gerekçesiyle raporla, gerçek işlem açılmaz.' : '',
11648
12000
  'Bu turda: mt5_status → hesap/pozisyon/fiyat verisi → mt5_rates/mt5_indicators ile teknik okuma → değerlendirme → kararlar (veya BEKLE: sebep) → kısa rapor.',
11649
12001
  'Önemli kararların gerekçesini mt5_note ile günlüğe yaz (haftalık performans raporu bu notları kullanır).',
11650
- 'Risk otomasyonu main süreçte 5 sn döngüyle çalışır (+R BE, trailing, kısmi TP) — sen yine de SL/TP seviyelerini aktif yönet.',
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.',
12003
+ '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.',
11651
12004
  '⚡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.',
11652
12005
  'Diğer finance/paralel ajanlarla koordinasyon için agent_dm aracı var (to: ajan başlığı anahtar kelimesi).',
11653
12006
  agent && agent.main && !roleDef ? `İŞLEM GEÇMİŞİN:\n${finBuildDigest()}` : '',
@@ -11871,8 +12224,11 @@ function finAgentRound(sid, opts) {
11871
12224
  : shadow
11872
12225
  ? 'SHADOW MOD AÇIK: emir GÖNDERİLMEZ — kararını teziyle raporla (günlüğe yazılır).'
11873
12226
  : 'İşlem açabilirsin — limitlere uy, SL\u2019siz pozisyon bırakma.';
12227
+ const learnTip = roleDef
12228
+ ? ' İ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.' : '.');
11874
12230
  const focus = agent.symbols.length ? `Odak: ${agent.symbols.join(', ')}. ` : '';
11875
- 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} Önemli kararların gerekçesini mt5_note ile günlüğe yaz. Kısa rapor ver.`;
12231
+ 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.`;
11876
12232
  const launch = (planBlock) => {
11877
12233
  if (!financeState.agents.has(String(sid))) return;
11878
12234
  const ok = engine.send(sid, planBlock + round, { userAction: false });
@@ -12066,7 +12422,9 @@ ipcMain.handle('finance:mode', async (_e, payload) => {
12066
12422
  try {
12067
12423
  let s = engine.cache.get(sid);
12068
12424
  if (!s) { try { s = engine._load(sid); } catch {} }
12069
- if (s && !s.bgJob && (!s.botId || s.botId === 'beast')) {
12425
+ /* 'trader' botu finance'ın kendi botudur (mod açılınca UI o bota döner) —
12426
+ onun sohbeti de finance olarak işlenir; diğer müşteri botları karışmaz */
12427
+ if (s && !s.bgJob && (!s.botId || s.botId === 'beast' || s.botId === 'trader')) {
12070
12428
  if (financeState.mode) {
12071
12429
  if (s.finance) {
12072
12430
  /* zaten finance oturumu — aynen sürdür */
@@ -12090,7 +12448,13 @@ ipcMain.handle('finance:mode', async (_e, payload) => {
12090
12448
  sohbet oturumu önerilir; hiç yoksa yeni oturum açılır. */
12091
12449
  if (financeState.mode && needNew && engine) {
12092
12450
  try {
12093
- const prev = (engine.listSessions() || []).find((v) => v && v.finance && !v.isBg && v.id);
12451
+ const list = engine.listSessions() || [];
12452
+ /* önce AKTİF BOTUN finance sohbeti (mod açılınca bot Trader'a döner),
12453
+ yoksa en son finance sohbeti */
12454
+ const actBot = String(settings.activeBotId || '');
12455
+ const prev =
12456
+ (actBot && list.find((v) => v && v.finance && !v.isBg && v.id && String(v.botId || '') === actBot)) ||
12457
+ list.find((v) => v && v.finance && !v.isBg && v.id);
12094
12458
  if (prev) {
12095
12459
  resumeSid = String(prev.id);
12096
12460
  needNew = false;
@@ -12149,12 +12513,20 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
12149
12513
  /* min lot max lotu aşamaz — hangisi sonra yazıldıysa diğerine kelepçelenir */
12150
12514
  if (f.minLot > f.maxLot) f.minLot = f.maxLot;
12151
12515
  if (p.maxPositions !== undefined) f.maxPositions = Math.max(1, Math.min(20, Math.round(Number(p.maxPositions) || 3)));
12516
+ /* limit değişikliği koşan oturumlara ANINDA işlenir (sonraki turu beklemez) */
12517
+ if (p.minLot !== undefined || p.maxLot !== undefined || p.maxPositions !== undefined) {
12518
+ try { finSyncLimitsToSessions(); } catch {}
12519
+ }
12152
12520
  /* RİSK OTOMASYONU + BİLDİRİM ayarları */
12153
12521
  if (p.watchdog !== undefined) f.watchdog = !!p.watchdog;
12154
12522
  if (p.beOnR !== undefined) f.beOnR = Math.max(0, Math.min(10, Number(p.beOnR) || 0));
12155
12523
  if (p.beOffsetR !== undefined) f.beOffsetR = Math.max(0, Math.min(1, Number(p.beOffsetR) || 0));
12156
12524
  if (p.trailStartR !== undefined) f.trailStartR = Math.max(0, Math.min(10, Number(p.trailStartR) || 0));
12157
12525
  if (p.trailR !== undefined) f.trailR = Math.max(0, Math.min(5, Number(p.trailR) || 0));
12526
+ /* KÂR KORUMA: kâr protectStartR'ye ulaşınca SL, en iyi kârın protectDistR
12527
+ gerisine kilitlenir (0 = kapalı) */
12528
+ if (p.protectStartR !== undefined) f.protectStartR = Math.max(0, Math.min(10, Number(p.protectStartR) || 0));
12529
+ if (p.protectDistR !== undefined) f.protectDistR = Math.max(0, Math.min(5, Number(p.protectDistR) || 0));
12158
12530
  if (p.partialR !== undefined) f.partialR = Math.max(0, Math.min(10, Number(p.partialR) || 0));
12159
12531
  if (p.partialPct !== undefined) f.partialPct = Math.max(0, Math.min(90, Number(p.partialPct) || 0));
12160
12532
  if (p.notifyTarget !== undefined) {
@@ -222,13 +222,15 @@
222
222
  <div class="fin-card-title">RİSK OTOMASYONU <span id="finWatchDot" class="fin-dot off" title="Koruma döngüsü"></span><span id="finWatchInfo" class="fin-hint"></span></div>
223
223
  <div class="fin-field fin-check">
224
224
  <label title="5 saniyelik döngü: +R'da SL'yi başabaşa çeker, trailing uygular, kısmi TP alır — ajan turunu beklemez">
225
- <input id="finWatchdog" type="checkbox" /> Aktif — BE + trailing + kısmi TP
225
+ <input id="finWatchdog" type="checkbox" /> Aktif — BE + trailing + kâr koruma + kısmi TP
226
226
  </label>
227
227
  </div>
228
228
  <div class="fin-grid2">
229
229
  <div class="fin-field"><span>BE (R)</span><input id="finBeOnR" type="number" min="0" max="10" step="0.1" title="Kâr kaç R'ye ulaşınca SL başabaşa çekilsin (0 = kapalı)" /></div>
230
230
  <div class="fin-field"><span>Trailing başlangıç (R)</span><input id="finTrailStartR" type="number" min="0" max="10" step="0.1" title="Kaç R kârdan sonra trailing başlasın (0 = kapalı)" /></div>
231
231
  <div class="fin-field"><span>Trailing mesafe (R)</span><input id="finTrailR" type="number" min="0" max="5" step="0.1" title="SL fiyattan kaç R geriden gelsin" /></div>
232
+ <div class="fin-field"><span>Kâr koruma başlangıç (R)</span><input id="finProtectStartR" type="number" min="0" max="10" step="0.1" title="Kâr bu R'ye ulaşınca SL kilitlenir: görülen EN İYİ kârın bir kısmı garantiye alınır — kâr eksiye dönmez (0 = kapalı, önerilen 0.3)" /></div>
233
+ <div class="fin-field"><span>Kâr koruma payı (R)</span><input id="finProtectDistR" type="number" min="0" max="5" step="0.05" title="Kilitlenen SL, görülen en iyi kârın kaç R gerisinde kalsın (ör. 0.15: +0.5R görüldüyse SL +0.35R'ye kilitlenir)" /></div>
232
234
  <div class="fin-field"><span>Kısmi TP (R)</span><input id="finPartialR" type="number" min="0" max="10" step="0.1" title="Kaç R kârda ajana kısmi TP fırsatı bildirilsin (0 = kapalı) — kapatma kararını AJAN verir, otomatik kapatılmaz" /></div>
233
235
  <div class="fin-field"><span>Kısmi TP (%)</span><input id="finPartialPct" type="number" min="0" max="90" step="5" title="Fırsat bildiriminde ajana önerilecek kısmi kapatma yüzdesi (kapatma kararı ajanın)" /></div>
234
236
  <div class="fin-field"><span>Günlük kayıp uyarı (%)</span><input id="finMaxDailyLoss" type="number" min="0" max="50" step="0.5" title="Gün içi düşüş bu yüzdeyi geçince aksiyon uygulanır (0 = kapalı)" /></div>
@@ -267,6 +267,8 @@ const els = {
267
267
  finBeOnR: $('#finBeOnR'),
268
268
  finTrailStartR: $('#finTrailStartR'),
269
269
  finTrailR: $('#finTrailR'),
270
+ finProtectStartR: $('#finProtectStartR'),
271
+ finProtectDistR: $('#finProtectDistR'),
270
272
  finPartialR: $('#finPartialR'),
271
273
  finPartialPct: $('#finPartialPct'),
272
274
  finMaxDailyLoss: $('#finMaxDailyLoss'),
@@ -1061,16 +1063,19 @@ async function renderSessions(list) {
1061
1063
  els.sessList.innerHTML = '';
1062
1064
  /* BEAST FINANCE izolasyonu: finance modunda YALNIZ finance sohbetleri,
1063
1065
  normal modda finance olmayanlar listelenir */
1064
- list = (list || []).filter((s) => (financeModeOn() ? !!s.finance : !s.finance));
1066
+ const finMode = financeModeOn();
1067
+ list = (list || []).filter((s) => (finMode ? !!s.finance : !s.finance));
1065
1068
  /* elle sıra: sessionOrder'daki id'ler önde (o sırayla), diğerleri engine sırasıyla arkada */
1066
1069
  const rank = new Map(sessionOrder.map((id, i) => [id, i]));
1067
1070
  const withRank = list.map((s, i) => ({ s, r: rank.has(s.id) ? rank.get(s.id) : 1e6 + i }));
1068
1071
  withRank.sort((a, b) => a.r - b.r);
1069
1072
  /* aktif botun oturumları — botlar arası geçişte liste de o bota göre değişir.
1070
1073
  KATI KURAL: her oturum yalnız BAĞLI OLDUĞU botun listesinde görünür;
1071
- bot kaydı olmayan (eski) oturumlar Beast (varsayılan sahip) altında görünür. */
1074
+ bot kaydı olmayan (eski) oturumlar Beast (varsayılan sahip) altında görünür.
1075
+ İSTİSNA — finance modu: mod Trader botuna döner; TÜM finance sohbetleri
1076
+ (eski Beast finance'ları dahil) listede kalır, kaybolmaz. */
1072
1077
  for (const { s } of withRank) {
1073
- if ((s.botId || 'beast') !== activeBotId) continue;
1078
+ if (!finMode && (s.botId || 'beast') !== activeBotId) continue;
1074
1079
  const row = document.createElement('div');
1075
1080
  row.className = 'sess' + (s.id === activeId ? ' active' : '');
1076
1081
  row.dataset.sid = s.id; /* en üstteki sohbeti otomatik aktif etmek için */
@@ -4607,7 +4612,7 @@ function updateBotChip() {
4607
4612
  /* BOTLAR ARASI GEÇİŞ: kart tıklaması SADECE aktif botu değiştirir —
4608
4613
  yönetim sayfası (Ayarlar/Memory/Log/Watcher/İstatistik) bot satırındaki ⚙
4609
4614
  butonundan veya üstteki bot rozetinden (botChip) açılır. */
4610
- async function switchBot(id) {
4615
+ async function switchBot(id, opts) {
4611
4616
  const b = botsCache.find((x) => x.id === id);
4612
4617
  if (!b) return;
4613
4618
  if (id === activeBotId) return;
@@ -4625,15 +4630,19 @@ async function switchBot(id) {
4625
4630
  }
4626
4631
  } catch {}
4627
4632
  try { localStorage.setItem('beast.activeBot', id); } catch {}
4628
- /* o botun en son oturumuna geç; hiç yoksa o bot için yeni sohbet aç */
4629
- try {
4630
- const list = (await beast.listSessions()).filter((s) => (s.botId || 'beast') === id);
4631
- if (list.length) await openSession(list[0].id);
4632
- else { const v = await beast.createSession(); await openSession(v.id); }
4633
- } catch {}
4633
+ /* noOpen: oturum açma çağırana bırakılır (finance modu kendi finance
4634
+ sohbetini açar/sürdürür — bot geçişi sohbeti değiştirmez) */
4635
+ if (!(opts && opts.noOpen)) {
4636
+ /* o botun en son oturumuna geç; hiç yoksa o bot için yeni sohbet aç */
4637
+ try {
4638
+ const list = (await beast.listSessions()).filter((s) => (s.botId || 'beast') === id);
4639
+ if (list.length) await openSession(list[0].id);
4640
+ else { const v = await beast.createSession(); await openSession(v.id); }
4641
+ } catch {}
4642
+ }
4634
4643
  /* picker, aktif botun kendi modelini göstersin */
4635
4644
  applyState();
4636
- toast(b.name + (b.admin ? '' : ' — ' + _t('bot_switched')));
4645
+ if (!(opts && opts.silent)) toast(b.name + (b.admin ? '' : ' — ' + _t('bot_switched')));
4637
4646
  }
4638
4647
 
4639
4648
  function botOverlaySetOpen(open) {
@@ -9985,6 +9994,8 @@ function finRenderAutomation(cfg, watch) {
9985
9994
  setNum(els.finBeOnR, cfg.beOnR != null ? cfg.beOnR : 1);
9986
9995
  setNum(els.finTrailStartR, cfg.trailStartR != null ? cfg.trailStartR : 1.5);
9987
9996
  setNum(els.finTrailR, cfg.trailR != null ? cfg.trailR : 0.5);
9997
+ setNum(els.finProtectStartR, cfg.protectStartR != null ? cfg.protectStartR : 0.3);
9998
+ setNum(els.finProtectDistR, cfg.protectDistR != null ? cfg.protectDistR : 0.15);
9988
9999
  setNum(els.finPartialR, cfg.partialR != null ? cfg.partialR : 0);
9989
10000
  setNum(els.finPartialPct, cfg.partialPct != null ? cfg.partialPct : 50);
9990
10001
  setNum(els.finMaxDailyLoss, cfg.maxDailyLossPct != null ? cfg.maxDailyLossPct : 3);
@@ -10170,6 +10181,20 @@ function finOnEvent(ev) {
10170
10181
  else if (ev.state === 'hours-stop') finLogLine('[trader] trade saatleri kapandı — ' + (ev.count || 0) + ' finance ajanı otomatik durduruldu; pencere açılınca geri başlar');
10171
10182
  else if (ev.state === 'hours-resume') finLogLine('[trader] trade saatleri açıldı — duraklatılan ajanlar geri başlatıldı');
10172
10183
  else if (ev.state === 'stopped') finLogLine('[trader] durduruldu');
10184
+ } else if (ev.fn === 'limits') {
10185
+ /* trader mt5_limits ile limitleri değiştirdi — panel alanları ANINDA tazelenir */
10186
+ if (finCfgCache) {
10187
+ if (ev.minLot !== undefined) finCfgCache.minLot = ev.minLot;
10188
+ if (ev.maxLot !== undefined) finCfgCache.maxLot = ev.maxLot;
10189
+ if (ev.maxPositions !== undefined) finCfgCache.maxPositions = ev.maxPositions;
10190
+ finTraderInputsSet(finCfgCache);
10191
+ } else {
10192
+ const ae2 = document.activeElement;
10193
+ if (els.finMinLot && ae2 !== els.finMinLot && ev.minLot !== undefined) els.finMinLot.value = ev.minLot;
10194
+ if (els.finMaxLot && ae2 !== els.finMaxLot && ev.maxLot !== undefined) els.finMaxLot.value = ev.maxLot;
10195
+ }
10196
+ finLogLine('[limit] ' + (ev.note || ('min lot ' + ev.minLot + ' · max lot ' + ev.maxLot + ' · max pozisyon ' + ev.maxPositions)));
10197
+ toast('Trader limitleri güncelledi: min ' + ev.minLot + ' / max ' + ev.maxLot);
10173
10198
  } else if (ev.fn === 'install') {
10174
10199
  finLogLine('[MT5] paket kurulumu tamamlandı (kod ' + ev.code + ')');
10175
10200
  }
@@ -10186,6 +10211,16 @@ async function setFinanceMode(on) {
10186
10211
  const brandSub = document.querySelector('#brand .brand-sub');
10187
10212
  if (brandSub) brandSub.textContent = on ? 'Finance' : 'Agent';
10188
10213
  try { localStorage.setItem('beast.financeMode', on ? '1' : '0'); } catch {}
10214
+ /* BEAST TRADER MODU: finance moduna geçilince aktif bot OTOMATİK Trader
10215
+ botuna döner — oturum açma finance akışına bırakılır (noOpen) */
10216
+ if (on) {
10217
+ try {
10218
+ if (!botsCache.length) await refreshBots();
10219
+ if (activeBotId !== 'trader' && botsCache.some((b) => b.id === 'trader')) {
10220
+ await switchBot('trader', { noOpen: true });
10221
+ }
10222
+ } catch {}
10223
+ }
10189
10224
  const fm = await beast.financeMode(!!on, activeId).catch(() => ({}));
10190
10225
  /* finance modu açıldı ama aktif oturum NORMAL (örn. WhatsApp sohbeti) ise
10191
10226
  ASLA o oturumu finance'e çevirme — önce EN SON finance sohbetinden devam
@@ -10571,6 +10606,8 @@ const finAutoNum = (el, key, min, max, step) => {
10571
10606
  finAutoNum(els.finBeOnR, 'beOnR', 0, 10);
10572
10607
  finAutoNum(els.finTrailStartR, 'trailStartR', 0, 10);
10573
10608
  finAutoNum(els.finTrailR, 'trailR', 0, 5);
10609
+ finAutoNum(els.finProtectStartR, 'protectStartR', 0, 10);
10610
+ finAutoNum(els.finProtectDistR, 'protectDistR', 0, 5);
10574
10611
  finAutoNum(els.finPartialR, 'partialR', 0, 10);
10575
10612
  finAutoNum(els.finPartialPct, 'partialPct', 0, 90, 5);
10576
10613
  finAutoNum(els.finMaxDailyLoss, 'maxDailyLossPct', 0, 50);