beast-agent 2.72.1 → 2.74.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.72.1",
4
+ "version": "2.74.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
package/src/main.js CHANGED
@@ -9904,6 +9904,9 @@ const financeState = {
9904
9904
  watchTimer: null,
9905
9905
  watchBusy: false,
9906
9906
  watchTickAt: 0,
9907
+ /* POZİSYON YÖNETİCİSİ (JEV): açık pozisyon varken 5 sn'lik watchdog turundan
9908
+ tetiklenen ayrı Jev ajanı — kapat/kısmi/SL kararlarını kendi talimatıyla verir */
9909
+ posManager: { busy: false, rounds: 0, lastAt: 0, lastErr: '', lastPosCount: 0, tsPartials: new Set() },
9907
9910
  hoursTimer: null, /* trade saatleri otomatik duraklatma denetimi (10 sn) */
9908
9911
  hoursPaused: null, /* pencere kapanınca durdurulan ajanların planı — açılınca geri gelir */
9909
9912
  alerts: [], /* fiyat alarmları (kalıcı: finance/alerts.json) */
@@ -10017,6 +10020,10 @@ function finCfg() {
10017
10020
  if (!Number.isFinite(Number(f.maxPerCurrency))) f.maxPerCurrency = 3;
10018
10021
  /* SHADOW MOD: emir gönderilmez — kararlar gerekçesiyle günlüğe yazılır */
10019
10022
  if (typeof f.shadowMode !== 'boolean') f.shadowMode = false;
10023
+ /* POZİSYON YÖNETİCİSİ: açık pozisyonları 5 sn'de bir Jev ile yöneten ayrı
10024
+ ajan — varsayılan AÇIK; kendi talimatı (posManagerNote) yalnız onu bağlar. */
10025
+ if (typeof f.posManagerEnabled !== 'boolean') f.posManagerEnabled = true;
10026
+ if (typeof f.posManagerNote !== 'string') f.posManagerNote = '';
10020
10027
  /* GÜNLÜK RUTİN: plan/review saatleri (HH:MM; boş = kapalı) — hafta içi cron */
10021
10028
  if (typeof f.planTime !== 'string') f.planTime = '';
10022
10029
  if (typeof f.reviewTime !== 'string') f.reviewTime = '';
@@ -10410,6 +10417,9 @@ function finExcFlush(force) {
10410
10417
 
10411
10418
  function finAgentInfo(sid) {
10412
10419
  try {
10420
+ if (String(sid || '') === FIN_POSMGR_ID) {
10421
+ return { main: false, role: 'posmanager', label: 'Pozisyon Yöneticisi' };
10422
+ }
10413
10423
  const a = financeState.agents.get(String(sid || ''));
10414
10424
  if (a) {
10415
10425
  const role = String(a.role || '');
@@ -10769,17 +10779,36 @@ function finEquitySample(account) {
10769
10779
  function finDailyLossCheck(point) {
10770
10780
  const cfg = finCfg();
10771
10781
  const day = finstats.dayKey(point.at);
10782
+ /* TALİMAT: "gün başı bakiye" yazıldıysa taban O'dur; "günlük zarar %X"
10783
+ yazıldıysa limit ayarın yerine talimattan gelir (kullanıcı her gün günceller). */
10784
+ let startBal = 0;
10785
+ let limitPct = Number(cfg.maxDailyLossPct) || 0;
10786
+ try {
10787
+ const ins = finParsedInstr();
10788
+ if (Number(ins.entry.startBalance) > 0) startBal = Number(ins.entry.startBalance);
10789
+ if (Number(ins.entry.dailyLossPct) > 0) limitPct = Number(ins.entry.dailyLossPct);
10790
+ } catch {}
10772
10791
  let ds = financeState.dayStart;
10773
10792
  if (!ds || ds.day !== day) {
10774
- financeState.dayStart = ds = { day, equity: Number(point.balance) > 0 ? Number(point.balance) : point.equity, warned: false, acted: false };
10793
+ financeState.dayStart = ds = {
10794
+ day,
10795
+ equity: startBal > 0 ? startBal : Number(point.balance) > 0 ? Number(point.balance) : point.equity,
10796
+ warned: false,
10797
+ acted: false,
10798
+ fromNote: startBal > 0,
10799
+ };
10800
+ }
10801
+ if (startBal > 0) {
10802
+ ds.equity = startBal;
10803
+ ds.fromNote = true;
10775
10804
  }
10776
- if (ds.warned || !(Number(cfg.maxDailyLossPct) > 0) || !(ds.equity > 0)) return;
10805
+ if (ds.warned || !(limitPct > 0) || !(ds.equity > 0)) return;
10777
10806
  const dd = ((ds.equity - point.equity) / ds.equity) * 100;
10778
- if (dd >= Number(cfg.maxDailyLossPct)) {
10807
+ if (dd >= limitPct) {
10779
10808
  ds.warned = true;
10780
10809
  const action = String(cfg.dailyLossAction || 'stop');
10781
10810
  const tail = action === 'flatten' ? ' — ajanlar durduruldu, pozisyonlar kapatılıyor' : action === 'stop' ? ' — ajanlar durduruldu' : '';
10782
- const line = `⚠️ Günlük kayıp %${dd.toFixed(2)} (limit %${cfg.maxDailyLossPct})${tail}`;
10811
+ const line = `⚠️ Günlük kayıp %${dd.toFixed(2)} (limit %${limitPct}${ds.fromNote ? ' · talimat gün başı bakiye' : ''})${tail}`;
10783
10812
  financeLog('[risk] ' + line);
10784
10813
  financeNotify(line, 'drawdown');
10785
10814
  finJournal({ kind: 'drawdown', pct: Math.round(dd * 100) / 100, action });
@@ -11595,6 +11624,11 @@ async function finWatchTick() {
11595
11624
  try { await finRecordClose(ticket, st); } catch {}
11596
11625
  }
11597
11626
  }
11627
+ /* POZİSYON YÖNETİCİSİ (JEV): açık pozisyon varken 5 sn'lik tur aynı anda
11628
+ yönetici karar turunu tetikler (ayrı MT5 sorgusu yok; meşgulse atlar) */
11629
+ if (positionsOk && positions.length) {
11630
+ try { finPosManagerMaybe(positions, account); } catch {}
11631
+ }
11598
11632
  try { await finCheckAlerts(); } catch {}
11599
11633
  if (!financeState.lastStatsAt || Date.now() - financeState.lastStatsAt > 120000) {
11600
11634
  financeState.lastStatsAt = Date.now();
@@ -13639,6 +13673,7 @@ function finTsAgentMode(agent) {
13639
13673
  }
13640
13674
 
13641
13675
  function finTsWho(agent, extra) {
13676
+ if (agent && agent.__posmgr) return 'TypeSafe · POZİSYON YÖNETİCİSİ' + (extra ? ' · ' + extra : '');
13642
13677
  const role = agent && agent.main ? 'TRADER' : ((finRoleDef(agent && agent.role) || {}).label || 'FİNANS').toUpperCase();
13643
13678
  return 'TypeSafe · ' + role + (extra ? ' · ' + extra : '');
13644
13679
  }
@@ -13682,6 +13717,115 @@ function finTsStrategyText() {
13682
13717
  }
13683
13718
  }
13684
13719
 
13720
+ /* ---- TALİMAT AYRIŞTIRICI ----
13721
+ Trade Ajanı + Pozisyon Yöneticisi notlarındaki SAYISAL kuralları koda çevirir:
13722
+ "risk %2" → giriş riski %2
13723
+ "lot x1.5" / "poz başı lot 1.5" / "lotu 2 kat" → lot çarpanı
13724
+ "martingale" / "martingale 1.5" → kayıp serisinde lot katlama (varsayılan ×2)
13725
+ "1R'de %50 kısmi kapat" / "%50 1R" / "kısmi %50" → otomatik kısmi close kuralı
13726
+ Saf metin ayrıştırma; uygulama ilgili turda kodla yapılır (LLM yok). */
13727
+ function finInstrNum(v) {
13728
+ const n = Number(String(v == null ? '' : v).replace(',', '.'));
13729
+ return isFinite(n) ? n : null;
13730
+ }
13731
+
13732
+ /* Para değeri: "10.000" / "10,000" → 10000; "10000,50" / "10000.50" → 10000.5 */
13733
+ function finInstrMoney(v) {
13734
+ const s = String(v == null ? '' : v).replace(/\s+/g, '');
13735
+ if (!s) return null;
13736
+ /* "5.000,50" / "5,000.50" → 5000.50 ; "10.000" → 10000 ; "10000,50" → 10000.5 */
13737
+ const m = s.match(/^(\d{1,3}(?:[.,]\d{3})+)(?:[.,](\d{1,2}))?$/);
13738
+ if (m) {
13739
+ const n = Number(m[1].replace(/[.,]/g, '') + (m[2] ? '.' + m[2] : ''));
13740
+ return isFinite(n) && n > 0 ? n : null;
13741
+ }
13742
+ const n2 = Number(s.replace(',', '.'));
13743
+ return isFinite(n2) && n2 > 0 ? n2 : null;
13744
+ }
13745
+
13746
+ function finParseInstructions(text) {
13747
+ const low = String(text || '').toLowerCase();
13748
+ const out = { riskPct: null, lotMult: null, martingale: null, partial: null, startBalance: null, dailyLossPct: null };
13749
+ if (!low.trim()) return out;
13750
+ /* "risk %2", "%2 risk", "risk yüzde 2", "riski 2 yap" → 2 (ilk sayı) */
13751
+ let m = low.match(
13752
+ /(?:risk\s*%\s*(\d+(?:[.,]\d+)?))|(?:%\s*(\d+(?:[.,]\d+)?)\s*risk)|(?:risk[^0-9]{0,14}(\d+(?:[.,]\d+)?))/
13753
+ );
13754
+ if (m) out.riskPct = finInstrNum(m[1] || m[2] || m[3]);
13755
+ if (out.riskPct != null) out.riskPct = Math.max(0.1, Math.min(10, out.riskPct));
13756
+ /* GÜN BAŞLANGIÇ BAKİYESİ (kullanıcı her gün talimata yazar): "başlangıç bakiye 10.000",
13757
+ "gün başı: 10000", "starting balance 10000" */
13758
+ m = low.match(/(?:ba[şs]lang[ıi][çc]\s*bakiye|g[üu]n\s*ba[şs][ıi](?:\s*bakiye)?|g[üu]nl[üu]k\s*ba[şs]lang[ıi][çc](?:\s*bakiye)?|starting\s*balance|start\s*balance)[^0-9]{0,14}(\d[\d.,\s]*)/);
13759
+ if (m) out.startBalance = finInstrMoney(m[1]);
13760
+ /* GÜNLÜK ZARAR LİMİTİ: "günlük zarar %3", "max günlük kayıp 3", "daily loss %3" */
13761
+ m = low.match(/(?:g[üu]nl[üu]k\s*(?:max(?:imum)?\s*)?(?:zarar|kay[ıi]p)|max\s*g[üu]nl[üu]k\s*kay[ıi]p|g[üu]nl[üu]k\s*limit|daily\s*(?:max\s*)?loss)[^0-9%]{0,12}%?\s*(\d+(?:[.,]\d+)?)/);
13762
+ if (m) {
13763
+ const v = finInstrNum(m[1]);
13764
+ if (v != null && v > 0) out.dailyLossPct = Math.max(0.1, Math.min(50, v));
13765
+ }
13766
+ m = low.match(/(?:lot\s*(?:çarpan[ıi]?|carpan[ıi]?)?\s*(?:x|×|\*)\s*(\d+(?:[.,]\d+)?))|(?:poz\s*ba[şs][ıi]\s*lot[^0-9]{0,12}(\d+(?:[.,]\d+)?))|(?:lot[^0-9]{0,12}(\d+(?:[.,]\d+)?)\s*kat)/);
13767
+ if (m) out.lotMult = finInstrNum(m[1] || m[2] || m[3]);
13768
+ if (out.lotMult != null) out.lotMult = Math.max(1, Math.min(5, out.lotMult));
13769
+ if (/martingale|mart[ıi]ngale/.test(low)) {
13770
+ /* faktör yalnız bitişik yazımdan okunur: "martingale 1.5", "martingale x2",
13771
+ "martingale çarpanı 1.5" — "martingale kullan, 1R..." gibi metinden sayı kapmaz */
13772
+ m = low.match(/mart[ıi]ngale\s*(?:x|×|çarpan[ıi]?|carpan[ıi]?)?\s*(\d+(?:[.,]\d+)?)/);
13773
+ out.martingale = m ? Math.max(1.1, Math.min(5, finInstrNum(m[1]) || 2)) : 2;
13774
+ }
13775
+ /* kısmi kapatma: "1R'de %50" → atR=1, pct=50; "%50 1R" → aynı; "kısmi %50" → atR=1 */
13776
+ m = low.match(/(\d+(?:[.,]\d+)?)\s*r[^%\d]{0,28}%\s*(\d+(?:[.,]\d+)?)/);
13777
+ if (m) out.partial = { atR: finInstrNum(m[1]), pct: finInstrNum(m[2]) };
13778
+ if (!out.partial) {
13779
+ m = low.match(/%\s*(\d+(?:[.,]\d+)?)[^%\d]{0,28}?(\d+(?:[.,]\d+)?)\s*r/);
13780
+ if (m) out.partial = { atR: finInstrNum(m[2]), pct: finInstrNum(m[1]) };
13781
+ }
13782
+ if (!out.partial) {
13783
+ m = low.match(/(?:k[ıi]smi|partial)[^%\d]{0,24}%\s*(\d+(?:[.,]\d+)?)/);
13784
+ if (m) out.partial = { atR: 1, pct: finInstrNum(m[1]) };
13785
+ }
13786
+ if (out.partial) {
13787
+ const p = out.partial;
13788
+ if (!(p.atR > 0)) p.atR = 1;
13789
+ p.atR = Math.min(10, Math.round(p.atR * 100) / 100);
13790
+ if (!(p.pct >= 5 && p.pct <= 90)) out.partial = null;
13791
+ else p.pct = Math.round(p.pct);
13792
+ }
13793
+ return out;
13794
+ }
13795
+
13796
+ /* İki notu birleştir: GİRİŞ kuralları trade notundan, YÖNETİM kuralları
13797
+ pozisyon yöneticisi notundan (yoksa trade notundan) gelir. */
13798
+ function finParsedInstr() {
13799
+ const trade = finParseInstructions(finTsStrategyText());
13800
+ const mgr = finParseInstructions(String(finCfg().posManagerNote || ''));
13801
+ return {
13802
+ entry: {
13803
+ riskPct: trade.riskPct != null ? trade.riskPct : mgr.riskPct,
13804
+ lotMult: trade.lotMult != null ? trade.lotMult : mgr.lotMult,
13805
+ martingale: trade.martingale != null ? trade.martingale : mgr.martingale,
13806
+ startBalance: trade.startBalance != null ? trade.startBalance : mgr.startBalance,
13807
+ dailyLossPct: trade.dailyLossPct != null ? trade.dailyLossPct : mgr.dailyLossPct,
13808
+ },
13809
+ manage: { partial: mgr.partial || trade.partial },
13810
+ };
13811
+ }
13812
+
13813
+ /* Rapor/state için tek satır özet: kod ne uygulayacak */
13814
+ function finInstrSummary(ins) {
13815
+ try {
13816
+ const p = [];
13817
+ if (ins.entry.riskPct != null) p.push(`risk %${ins.entry.riskPct}`);
13818
+ if (ins.entry.lotMult != null) p.push(`lot ×${ins.entry.lotMult}`);
13819
+ if (ins.entry.martingale != null) p.push(`martingale ×${ins.entry.martingale}`);
13820
+ if (ins.entry.startBalance != null) p.push(`gün başı ${ins.entry.startBalance}`);
13821
+ if (ins.entry.dailyLossPct != null) p.push(`günlük zarar limiti %${ins.entry.dailyLossPct}`);
13822
+ if (ins.manage.partial) p.push(`kısmi %${ins.manage.partial.pct} @${ins.manage.partial.atR}R`);
13823
+ return p.join(' · ');
13824
+ } catch {
13825
+ return '';
13826
+ }
13827
+ }
13828
+
13685
13829
  /* Talimattan ZAMAN DİLİMİ çıkar: "M15", "1m", "1 dk", "4 saat" → M15/M1/H4 */
13686
13830
  function finTsStrategyTf() {
13687
13831
  const s = String(finTsStrategyText() || '');
@@ -14022,6 +14166,206 @@ async function finTsToolRequest(sidS, agent, ans, lines) {
14022
14166
  }
14023
14167
  }
14024
14168
 
14169
+ /* ================= POZİSYON YÖNETİCİSİ (JEV · 5 sn) =================
14170
+ Açık pozisyon varken watchdog turu (5 sn) bu turu tetikler: pozisyon başına
14171
+ kapatma / kısmi kapatma / SL-koruma kararlarını JEV verir; kodu
14172
+ finTsManageOpen uygular. Kendi talimatı (posManagerNote) birincil kuraldır —
14173
+ trade ajanının talimatından bağımsızdır. LLM kullanılmaz. */
14174
+ const FIN_POSMGR_ID = 'posmanager';
14175
+ const FIN_POSMGR_AGENT = { main: false, role: '', __posmgr: true };
14176
+
14177
+ /* Watchdog turundan çağrılır: uygunSA yönetici turunu arka planda başlatır
14178
+ (MT5 pozisyon verisi watchdog turundan gelir — ikinci sorgu yok). */
14179
+ function finPosManagerMaybe(positions, account) {
14180
+ const pm = financeState.posManager;
14181
+ if (!pm || pm.busy) return;
14182
+ let f = {};
14183
+ try { f = finCfg(); } catch {}
14184
+ if (f.posManagerEnabled === false) return;
14185
+ const list = Array.isArray(positions) ? positions.filter(Boolean) : [];
14186
+ pm.lastPosCount = list.length;
14187
+ if (!list.length) return;
14188
+ if (!mt5bridge.running) return;
14189
+ if (!typesafeMod.cfg().apiKey) {
14190
+ const now = Date.now();
14191
+ if (!pm.noKeyAt || now - pm.noKeyAt > 10 * 60 * 1000) {
14192
+ pm.noKeyAt = now;
14193
+ finTsPost(FIN_POSMGR_ID, FIN_POSMGR_AGENT, '⚠ TypeSafe anahtarı yok — pozisyon yöneticisi çalışamıyor (Ayarlar → TypeSafe).', 'anahtar');
14194
+ }
14195
+ return;
14196
+ }
14197
+ finPosManagerRound(list, account).catch((e) => {
14198
+ pm.busy = false;
14199
+ pm.lastErr = String((e && e.message) || e).slice(0, 200);
14200
+ });
14201
+ }
14202
+
14203
+ async function finPosManagerRound(positions, account) {
14204
+ const pm = financeState.posManager;
14205
+ if (!pm || pm.busy) return;
14206
+ pm.busy = true;
14207
+ pm.rounds = (Number(pm.rounds) || 0) + 1;
14208
+ pm.lastAt = Date.now();
14209
+ const f = finCfg();
14210
+ /* TALİMAT: kısmi close kuralı varsa KOD uygular (Jev'e sorulmaz) */
14211
+ const ins = finParsedInstr();
14212
+ const instrTxt = finInstrSummary(ins);
14213
+ const lines = [];
14214
+ try {
14215
+ /* pozisyon sembolleri: piyasa + gösterge (tur başına tek Jev çağrısı) */
14216
+ const syms = [...new Set(positions.map((p) => String(p.symbol || '').toUpperCase()).filter(Boolean))].slice(0, 8);
14217
+ const market = {};
14218
+ const posTf = positions.map((p) => finLearnParseTf(p && p.comment)).find((t) => t) || '';
14219
+ for (const sym of syms) {
14220
+ const tf = finLearnNormTf(posTf) || finTsStrategyTf() || 'M15';
14221
+ const [mktR, indR] = await Promise.all([
14222
+ financetools.handlers.mt5_market({ symbols: [sym] }),
14223
+ financetools.handlers.mt5_indicators({ symbol: sym, timeframe: tf, count: 300, indicators: ['ATR(14)', 'EMA(50)', 'EMA(200)', 'RSI(14)'] }),
14224
+ ]);
14225
+ market[sym] = { row: (mktR && mktR.symbols && mktR.symbols[0]) || null, tf, ind: finTsIndLine(indR) };
14226
+ }
14227
+ const state = {
14228
+ rol: 'POZİSYON YÖNETİCİSİ',
14229
+ yerel_saat: new Date().getHours(),
14230
+ hesap: {
14231
+ balance: Number(account && account.balance) || 0,
14232
+ equity: Number(account && account.equity) || 0,
14233
+ margin_free: Number(account && account.margin_free) || 0,
14234
+ acik_pozisyon: positions.length,
14235
+ },
14236
+ pozisyonlar: positions.map((p) => finTsPosState(p)),
14237
+ piyasa: {},
14238
+ alarmlar: (financeState.alerts || []).slice(0, 8).map((a) => ({ sembol: a.symbol, yon: a.direction, fiyat: a.price, mod: a.once ? 'tek' : 'tekrarlı' })),
14239
+ sahip_talimati: String(f.posManagerNote || '').slice(0, 1500) || '(yok — genel disiplin: kârı koru, zararı sınırla, SL/TP aktif yönet)',
14240
+ talimat_ayarlari: instrTxt || '(sayısal kural yok)',
14241
+ gun_durumu: (() => {
14242
+ try {
14243
+ const sb = Number(ins.entry.startBalance) || 0;
14244
+ const eqNow = Number(account && account.equity) || 0;
14245
+ if (!(sb > 0) || !(eqNow > 0)) return '';
14246
+ const pct = Math.round(((eqNow - sb) / sb) * 10000) / 100;
14247
+ return `Gün başı ${sb} → %${pct >= 0 ? '+' : ''}${pct} (${pct >= 0 ? 'KÂR' : 'ZARAR'})`;
14248
+ } catch {
14249
+ return '';
14250
+ }
14251
+ })(),
14252
+ ekip_yanitlari: finTsFeedText(6),
14253
+ };
14254
+ for (const sym of syms) {
14255
+ const d = market[sym];
14256
+ const row = d.row || {};
14257
+ state.piyasa[sym] = {
14258
+ zaman_dilimi: d.tf,
14259
+ bid: Number(row.bid) || null,
14260
+ ask: Number(row.ask) || null,
14261
+ spread: Number(row.spread) || null,
14262
+ gosterge: d.ind,
14263
+ };
14264
+ }
14265
+ const questions = {};
14266
+ positions.forEach((pp, i) => {
14267
+ const sym = String(pp.symbol || '').toUpperCase();
14268
+ const ps = finTsPosState(pp);
14269
+ const kzTxt = ps.kz_r == null ? 'R bilinmiyor' : `kâr ${ps.kz_r}R`;
14270
+ const bestTxt = ps.en_iyi_r == null ? '' : ` · en iyi ${ps.en_iyi_r}R`;
14271
+ const slTxt = Number(pp.sl) > 0 ? `SL ${pp.sl}` : 'SL YOK';
14272
+ questions['kapat_' + i] = {
14273
+ type: 'noul',
14274
+ instructions: `${sym} açık pozisyon #${pp.ticket} (${ps.yon}, kâr ${Number(pp.profit) || 0}, ${kzTxt}${bestTxt}, ${slTxt}) ŞİMDİ kapatılmalı mı? Sahip talimatını, kârı korumayı ve momentum dönüşünü değerlendir.`,
14275
+ criteria: { true: 'Kapat — risk/kâr koruma', false: 'Açık kalsın — tez sürüyor' },
14276
+ };
14277
+ /* Talimatta SAYISAL kısmi kuralı varsa Jev'e sorulmaz — kodu uygular */
14278
+ if (!ins.manage.partial) {
14279
+ questions['kismi_' + i] = {
14280
+ type: 'choice',
14281
+ instructions: `${sym} #${pp.ticket} (${kzTxt}${bestTxt}${ps.be ? ' · SL takipte' : ''}) için ŞİMDİ kısmi kapatma uygun mu?${ps.kismi_alindi ? ' Bu pozisyonda kısmi kapatma ZATEN yapıldı — yok seç.' : ' Sahip talimatını ve kâr realize etmeyi değerlendir.'}`,
14282
+ criteria: {
14283
+ yok: 'Kısmi kapatma yok — pozisyon tam kalsın',
14284
+ yuzde25: 'Pozisyonun %25’i kapatılsın',
14285
+ yuzde50: 'Pozisyonun %50’si kapatılsın',
14286
+ yuzde75: 'Pozisyonun %75’i kapatılsın',
14287
+ },
14288
+ };
14289
+ }
14290
+ questions['sl_' + i] = {
14291
+ type: 'choice',
14292
+ instructions: `${sym} #${pp.ticket} için SL/koruma aksiyonu: ${ps.yon}, giriş ${Number(pp.price_open) || '?'}, şimdi ${ps.fiyat || '?'}, ${slTxt}, ${kzTxt}. Kâr yoksa ve SL varsa 'birak'; SL yoksa 'koru' ile koruma koy.`,
14293
+ criteria: {
14294
+ birak: 'Dokunma — SL yerinde kalsın',
14295
+ be: 'Breakeven — SL girişe çekilsin (kârı kilitle)',
14296
+ trail: 'Trailing — SL fiyatın gerisine taşınsın (kârı takip et)',
14297
+ sikilastir: 'Sıkılaştır — SL fiyata yaklaştırılsın (kârı daha çok koru)',
14298
+ koru: 'Koruma koy — SL’siz pozisyona ATR tabanlı stop eklensin',
14299
+ },
14300
+ };
14301
+ });
14302
+ const ans = await finTsAsk(state, questions);
14303
+ for (let i = 0; i < positions.length; i++) {
14304
+ const pp = positions[i];
14305
+ const sym = String(pp.symbol || '').toUpperCase();
14306
+ const ex = finTsNoul(ans, 'kapat_' + i);
14307
+ if (ex != null && ex >= FIN_TS_EXIT_P) {
14308
+ let r = null;
14309
+ try {
14310
+ r = await financetools.handlers.mt5_close(
14311
+ { ticket: Number(pp.ticket), reason: `TypeSafe yönetici: kapat p=${ex.toFixed(2)}` },
14312
+ { sessionId: FIN_POSMGR_ID }
14313
+ );
14314
+ } catch (e) {
14315
+ r = { ok: false, error: String((e && e.message) || e) };
14316
+ }
14317
+ lines.push(`- ${sym} #${pp.ticket}: KAPAT (p=${ex.toFixed(2)}) ${r && r.ok ? '✓ kapatıldı' : '✗ ' + ((r && r.error) || 'hata')}`);
14318
+ continue;
14319
+ }
14320
+ const psNow = finTsPosState(pp);
14321
+ lines.push(`- ${sym} #${pp.ticket}: açık (kapat=${ex == null ? 'yanıt yok' : ex.toFixed(2)} < ${FIN_TS_EXIT_P}) · ${psNow.kz_r == null ? 'kâr ?' : 'kâr ' + psNow.kz_r + 'R'}`);
14322
+ /* TALİMAT KISMİ KURALI (kod): "+atR'de %pct kapat" → otomatik uygula */
14323
+ const rule = ins.manage.partial;
14324
+ if (rule) {
14325
+ const stP = financeState.watch.get(String(pp.ticket)) || null;
14326
+ const done = !!(stP && stP.partial) || !!(pm.tsPartials && pm.tsPartials.has(String(pp.ticket)));
14327
+ if (!done && psNow.kz_r != null && psNow.kz_r >= rule.atR) {
14328
+ let rr = null;
14329
+ try {
14330
+ rr = await financetools.handlers.mt5_close(
14331
+ { ticket: Number(pp.ticket), percent: rule.pct, kind: 'partial_tp', reason: `TypeSafe yönetici: TALİMAT kısmi %${rule.pct} @${rule.atR}R (kâr ${psNow.kz_r}R)` },
14332
+ { sessionId: FIN_POSMGR_ID }
14333
+ );
14334
+ } catch (e) {
14335
+ rr = { ok: false, error: String((e && e.message) || e) };
14336
+ }
14337
+ if (rr && rr.ok) {
14338
+ if (stP) { stP.partial = true; finWatchSave(); }
14339
+ pm.tsPartials = pm.tsPartials || new Set();
14340
+ pm.tsPartials.add(String(pp.ticket));
14341
+ lines.push(`- ${sym} #${pp.ticket}: ✂️ TALİMAT kısmi %${rule.pct} TP ✓ (${psNow.kz_r}R ≥ ${rule.atR}R${rr.remaining != null ? ', kalan ' + rr.remaining + ' lot' : ''})`);
14342
+ } else {
14343
+ lines.push(`- ${sym} #${pp.ticket}: talimat kısmi reddedildi — ${(rr && rr.error) || 'hata'}`);
14344
+ }
14345
+ }
14346
+ }
14347
+ try { await finTsManageOpen(FIN_POSMGR_ID, pm, sym, pp, market[sym], ans, i, lines); } catch {}
14348
+ }
14349
+ finTsPost(
14350
+ FIN_POSMGR_ID,
14351
+ FIN_POSMGR_AGENT,
14352
+ `🛡️ Pozisyon yöneticisi turu #${pm.rounds} (LLM yok — 5 sn):\n` +
14353
+ (instrTxt ? 'TALİMAT AYARLARI (kod uygular): ' + instrTxt + '\n' : '') +
14354
+ lines.join('\n'),
14355
+ 'yönetim'
14356
+ );
14357
+ } catch (e) {
14358
+ pm.lastErr = String((e && e.message) || e).slice(0, 200);
14359
+ const now = Date.now();
14360
+ if (!pm.errAt || now - pm.errAt > 2 * 60 * 1000) {
14361
+ pm.errAt = now;
14362
+ finTsPost(FIN_POSMGR_ID, FIN_POSMGR_AGENT, '⚠ Yönetici turu hatası: ' + pm.lastErr, 'hata');
14363
+ }
14364
+ } finally {
14365
+ pm.busy = false;
14366
+ }
14367
+ }
14368
+
14025
14369
  function finTsSchedule(sid, agent, sec) {
14026
14370
  if (!agent || !financeState.agents.has(String(sid))) return;
14027
14371
  clearTimeout(agent.timer);
@@ -14094,6 +14438,25 @@ async function finTypeSafeRound(sid, agent) {
14094
14438
  const posList = (posR && posR.positions) || [];
14095
14439
  /* BEKLEYEN EMİRLER: pozisyon slotunu paylaşır (aşırı birikmeyi önler) */
14096
14440
  const pendCount = (ordR && Array.isArray(ordR.orders) ? ordR.orders.length : 0);
14441
+ /* TALİMAT AYARLARI: sayısal kurallar koda çevrildi (risk/lot/martingale/
14442
+ gün başı bakiye/günlük zarar limiti) — state ve giriş kapısı kullanır */
14443
+ const ins = finParsedInstr();
14444
+ /* GÜN BAŞI BAKİYE → günlük K/Z ve limit durumu (hesap anlık değerinden) */
14445
+ let dayPct = null;
14446
+ let dayBlocked = '';
14447
+ try {
14448
+ const sb = Number(ins.entry.startBalance) || 0;
14449
+ const eqNow = Number(account.equity) || 0;
14450
+ const balNow = Number(account.balance) || 0;
14451
+ const refNow = eqNow > 0 ? eqNow : balNow;
14452
+ if (sb > 0 && refNow > 0) {
14453
+ dayPct = Math.round(((refNow - sb) / sb) * 10000) / 100;
14454
+ const lim = Number(ins.entry.dailyLossPct) || 0;
14455
+ if (lim > 0 && dayPct <= -lim) {
14456
+ dayBlocked = `günlük zarar limiti AŞILDI (gün başı ${sb} → ${refNow}, %${dayPct} ≤ -%${lim}) — talimat gereği yeni işlem açılmaz`;
14457
+ }
14458
+ }
14459
+ } catch {}
14097
14460
  const market = {};
14098
14461
  for (const sym of symbols) {
14099
14462
  const tf = finTsPickTf(agent, sym);
@@ -14134,6 +14497,16 @@ async function finTypeSafeRound(sid, agent) {
14134
14497
  talimat "zorunlu giriş" içeriyorsa kod eşikleri devre dışı bırakır. */
14135
14498
  sahip_talimati: finTsStrategyText() || '(yok — temel teknik okuma)',
14136
14499
  talimat_modu: finTsMandatoryEntry() ? 'ZORUNLU GİRİŞ — bu turda işlem açmak zorunlu (bekle yok); yönü sen seç' : '',
14500
+ talimat_ayarlari: finInstrSummary(ins) || '(sayısal kural yok)',
14501
+ /* GÜN BAŞI BAKİYE (talimattan): günlük K/Z — Jev zarar durumunu otomatik görür */
14502
+ gun_baslangic_bakiye: ins.entry.startBalance != null ? ins.entry.startBalance : null,
14503
+ gun_net_yuzde: dayPct,
14504
+ gun_durumu: dayBlocked
14505
+ ? dayBlocked
14506
+ : ins.entry.startBalance != null && dayPct != null
14507
+ ? `Gün başı ${ins.entry.startBalance} → şimdi %${dayPct >= 0 ? '+' : ''}${dayPct} (${dayPct >= 0 ? 'KÂR' : 'ZARAR'})` +
14508
+ (ins.entry.dailyLossPct != null ? ` · limit -%${ins.entry.dailyLossPct}` : '')
14509
+ : '',
14137
14510
  };
14138
14511
  for (const sym of symbols) {
14139
14512
  const d = market[sym];
@@ -14156,6 +14529,9 @@ async function finTypeSafeRound(sid, agent) {
14156
14529
  }
14157
14530
  const questions = {};
14158
14531
  const talimatZorunlu = finTsMandatoryEntry();
14532
+ /* Yönetici açıkken açık pozisyon kararları ONA aittir (çift yönetim yok) */
14533
+ const posManagerOn = f.posManagerEnabled !== false;
14534
+ /* TALİMAT AYARLARI: yukarıda (state kurulmadan) ayrıştırıldı */
14159
14535
  symbols.forEach((sym, i) => {
14160
14536
  questions['yon_' + i] = {
14161
14537
  type: 'choice',
@@ -14211,7 +14587,7 @@ async function finTypeSafeRound(sid, agent) {
14211
14587
  asagi_atr: 'Fiyat -0.5×ATR altında alarm',
14212
14588
  },
14213
14589
  };
14214
- if (posBySym[sym]) {
14590
+ if (posBySym[sym] && !posManagerOn) {
14215
14591
  const pp = posBySym[sym];
14216
14592
  const ps = finTsPosState(pp);
14217
14593
  const kzTxt = ps.kz_r == null ? 'R bilinmiyor' : `kâr ${ps.kz_r}R`;
@@ -14283,6 +14659,11 @@ async function finTypeSafeRound(sid, agent) {
14283
14659
  const cal = finTsCalibration(sym, market[sym].tf);
14284
14660
  const thAction = cal.action;
14285
14661
  if (pos) {
14662
+ /* POZİSYON YÖNETİCİSİ açık: kapat/kısmi/SL kararları onun turunda */
14663
+ if (posManagerOn) {
14664
+ lines.push(`- ${sym}: pozisyon açık #${pos.ticket} — yönetim Pozisyon Yöneticisi'nde (5 sn Jev turu)`);
14665
+ continue;
14666
+ }
14286
14667
  const ex = finTsNoul(ans, 'kapat_' + i);
14287
14668
  if (ex != null && ex >= FIN_TS_EXIT_P) {
14288
14669
  const r = await financetools.handlers.mt5_close(
@@ -14313,8 +14694,16 @@ async function finTypeSafeRound(sid, agent) {
14313
14694
  continue;
14314
14695
  }
14315
14696
  }
14316
- if (opened >= 1 || posList.length + pendCount >= (Number(f.maxPositions) || 3)) {
14317
- lines.push(`- ${sym}: sinyal güçlü (${c.choice} p=${c.p.toFixed(2)}) ama pozisyon+bekleyen emir sınırı dolu — bu tur açılmadı`);
14697
+ /* GÜN BAŞI BAKİYE LİMİTİ (talimat): aşıldıysa YENİ İŞLEM AÇILMAZ */
14698
+ if (dayBlocked) {
14699
+ lines.push(`- ${sym}: ${dayBlocked}`);
14700
+ continue;
14701
+ }
14702
+ /* ÇOKLU GİRİŞ: aynı turda birden çok sembol açılabilir — yalnız toplam
14703
+ slot (pozisyon + bekleyen emir + bu turda açılanlar) tavanı korur */
14704
+ const slotsLeft = (Number(f.maxPositions) || 3) - pendCount - posList.length - opened;
14705
+ if (slotsLeft <= 0) {
14706
+ lines.push(`- ${sym}: sinyal güçlü (${c.choice} p=${c.p.toFixed(2)}) ama pozisyon+bekleyen emir sınırı dolu (${posList.length + pendCount}/${Number(f.maxPositions) || 3}) — bu tur açılmadı`);
14318
14707
  continue;
14319
14708
  }
14320
14709
  /* GİRİŞ TİPİ + RİSK PROFİLİ (JEV kararı) — seviyeleri KOD hesaplar */
@@ -14357,8 +14746,29 @@ async function finTypeSafeRound(sid, agent) {
14357
14746
  }
14358
14747
  const sl = rnd(isBuy ? entry - slDist : entry + slDist);
14359
14748
  const tp = rnd(isBuy ? entry + tpDist : entry - tpDist);
14749
+ /* RİSK + LOT: talimatta "risk %2" varsa AYNEN uygulanır; yoksa ayar ×
14750
+ kalibrasyon. Lot çarpanı ve MARTİNGALE (kayıp serisi × kat) burada. */
14360
14751
  const riskBase = Number(f.riskPerTradePct) > 0 ? Number(f.riskPerTradePct) : 0.5;
14361
- const riskPct = Math.max(0.1, Math.min(2, Math.round(riskBase * cal.riskMult * 100) / 100));
14752
+ let riskPct = ins.entry.riskPct != null
14753
+ ? ins.entry.riskPct
14754
+ : Math.max(0.1, Math.min(2, Math.round(riskBase * cal.riskMult * 100) / 100));
14755
+ let sizeMult = 1;
14756
+ let martNote = '';
14757
+ if (ins.entry.lotMult != null) sizeMult *= ins.entry.lotMult;
14758
+ if (ins.entry.martingale != null) {
14759
+ let streak = 0;
14760
+ try {
14761
+ const le = finLearnLoad().symbols[sym];
14762
+ streak = Number(le && le.stats && le.stats.streak) || 0;
14763
+ } catch {}
14764
+ const pow = Math.min(3, Math.max(0, Math.round(streak)));
14765
+ if (pow > 0) {
14766
+ sizeMult *= Math.pow(ins.entry.martingale, pow);
14767
+ martNote = ` · martingale ×${ins.entry.martingale}^${pow}`;
14768
+ }
14769
+ }
14770
+ if (sizeMult !== 1) riskPct = Math.max(0.1, Math.min(10, Math.round(riskPct * sizeMult * 100) / 100));
14771
+ const riskNote = ins.entry.riskPct != null ? ' · TALİMAT risk %' + ins.entry.riskPct : '';
14362
14772
  const emirLabel =
14363
14773
  orderType === 'market' ? 'MARKET' :
14364
14774
  isBuy && orderType === 'buy_limit' ? 'BUY LIMIT' :
@@ -14406,10 +14816,11 @@ async function finTypeSafeRound(sid, agent) {
14406
14816
  orderType,
14407
14817
  });
14408
14818
  } catch {}
14819
+ const riskTxt = `risk %${riskPct}${ins.entry.riskPct == null && cal.riskMult !== 1 ? ` ×${cal.riskMult}` : ''}${riskNote}${martNote}`;
14409
14820
  lines.push(
14410
14821
  (orderType === 'market'
14411
- ? `- ${sym}: ⚡ MARKET ${c.choice.toUpperCase()} açıldı (lot ${r.opened && r.opened.volume}, SL ${sl}, TP ${tp}, risk %${riskPct}${cal.riskMult !== 1 ? ` ×${cal.riskMult}` : ''})`
14412
- : `- ${sym}: ⏳ ${emirLabel} emri kondu @${entry} (${profKey} profil, SL ${sl}, TP ${tp}, risk %${riskPct})`) +
14822
+ ? `- ${sym}: ⚡ MARKET ${c.choice.toUpperCase()} açıldı (lot ${r.opened && r.opened.volume}, SL ${sl}, TP ${tp}, ${riskTxt})`
14823
+ : `- ${sym}: ⏳ ${emirLabel} emri kondu @${entry} (${profKey} profil, SL ${sl}, TP ${tp}, ${riskTxt})`) +
14413
14824
  ` · p=${c.p.toFixed(2)} teyit=${confVal.toFixed(2)} · tf=${market[sym].tf}` +
14414
14825
  (talimatZorunlu ? ' · TALİMAT: zorunlu giriş' : '') +
14415
14826
  (thAction !== FIN_TS_DEFAULT_TH.action ? ` · öğrenilmiş eşik p≥${thAction.toFixed(2)}` : '')
@@ -14422,10 +14833,12 @@ async function finTypeSafeRound(sid, agent) {
14422
14833
  }
14423
14834
  /* JEV ARAÇ İSTEĞİ (tur düzeyi): eksik araç varsa TOOL botuna asenkron yazdır */
14424
14835
  try { await finTsToolRequest(sidS, agent, ans, lines); } catch {}
14836
+ const insTxt = finInstrSummary(ins);
14425
14837
  finTsPost(
14426
14838
  sidS,
14427
14839
  agent,
14428
14840
  `🤖 TypeSafe karar turu #${agent.round} (LLM yok — girdi yalnız TypeSafe):\n` +
14841
+ (insTxt ? 'TALİMAT AYARLARI (kod uygular): ' + insTxt + '\n' : '') +
14429
14842
  'TF KARARI: ' + symbols.map((s) => `${s}=${market[s].tf}`).join(', ') + '\n' +
14430
14843
  'ÖĞRENİLMİŞ: ' +
14431
14844
  symbols
@@ -14735,6 +15148,16 @@ ipcMain.handle('finance:snapshot', async () => {
14735
15148
  equity: (financeState.equity || []).slice(-180),
14736
15149
  alerts: (financeState.alerts || []).slice(0, 50),
14737
15150
  watch: { on: !!financeState.watchTimer, managed: financeState.watch.size, lastAt: financeState.watchTickAt || 0 },
15151
+ /* POZİSYON YÖNETİCİSİ durumu (panel kartı) */
15152
+ posManager: {
15153
+ enabled: f.posManagerEnabled !== false,
15154
+ busy: !!financeState.posManager.busy,
15155
+ rounds: Number(financeState.posManager.rounds) || 0,
15156
+ lastAt: Number(financeState.posManager.lastAt) || 0,
15157
+ lastErr: String(financeState.posManager.lastErr || ''),
15158
+ positions: Number(financeState.posManager.lastPosCount) || 0,
15159
+ note: String(f.posManagerNote || ''),
15160
+ },
14738
15161
  };
14739
15162
  });
14740
15163
 
@@ -14964,6 +15387,9 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
14964
15387
  if (p.reentryCooldownMin !== undefined) f.reentryCooldownMin = Math.max(0, Math.min(1440, Math.round(Number(p.reentryCooldownMin) || 0)));
14965
15388
  if (p.maxPerCurrency !== undefined) f.maxPerCurrency = Math.max(0, Math.min(20, Math.round(Number(p.maxPerCurrency) || 0)));
14966
15389
  if (p.shadowMode !== undefined) f.shadowMode = !!p.shadowMode;
15390
+ /* POZİSYON YÖNETİCİSİ: kendi talimatı + aç/kapa */
15391
+ if (p.posManagerNote !== undefined) f.posManagerNote = String(p.posManagerNote || '').slice(0, 1500);
15392
+ if (p.posManagerEnabled !== undefined) f.posManagerEnabled = p.posManagerEnabled !== false;
14967
15393
  if (p.planTime !== undefined) f.planTime = /^([01]?\d|2[0-3]):([0-5]\d)$/.test(String(p.planTime || '').trim()) ? String(p.planTime).trim() : '';
14968
15394
  if (p.reviewTime !== undefined) f.reviewTime = /^([01]?\d|2[0-3]):([0-5]\d)$/.test(String(p.reviewTime || '').trim()) ? String(p.reviewTime).trim() : '';
14969
15395
  /* TRADE SAATLERİ: {on, start, end} — yerel saat; aralık dışında ajan turları
@@ -193,8 +193,8 @@
193
193
  <div id="finRoles" class="fin-roles" title="Trader&#39;ın yanında koşacak uzman ajanları seç — Risk / Teknik / Haber / Görsel; seçilen her rol AYRI sürekli ajan olarak koşar (işlem açmaz)"></div>
194
194
  </div>
195
195
  <div class="fin-field">
196
- <span>Ajan talimatı <i class="fin-hint">— 1-2 cümle; trader + tüm paralel ajanlara iletilir</i></span>
197
- <textarea id="finStrategy" rows="2" maxlength="2000" placeholder="örn. Her türde scalp yap — M1/M5 hızlı gir-çık, günlük kâr hedefine ulaşınca dur" title="Buraya yazdığın talimat trade ajanına ve koşan tüm paralel ajanlara (ekip + sembol işçileri) sistem promptunda öncelikli strateji notu olarak gider; kaydetmek için alandan çık"></textarea>
196
+ <span>Ajan talimatı <i class="fin-hint">— 1-2 cümle; trader + tüm paralel ajanlara iletilir</i><button id="finStrategyHelp" class="fin-help-btn" type="button" title="Neler yazabilirim? Desteklenen talimatlar ve örnekler">?</button></span>
197
+ <textarea id="finStrategy" rows="2" maxlength="2000" placeholder="örn. 1- risk yüzde 2 2- martingale kullan 3- her mumda işlem açmak zorundasın" title="Buraya yazdığın talimat trade ajanına ve koşan tüm paralel ajanlara (ekip + sembol işçileri) sistem promptunda öncelikli strateji notu olarak gider; kaydetmek için alandan çık"></textarea>
198
198
  </div>
199
199
  <div id="finAdvWrap" hidden>
200
200
  <div class="fin-field">
@@ -219,6 +219,19 @@
219
219
  </div>
220
220
  <div id="finTraderStatus">Kapalı</div>
221
221
  </div>
222
+ <div class="fin-card" id="finPosCard" data-fin-card="posmgr">
223
+ <div class="fin-card-title">POZİSYON YÖNETİCİSİ <span id="finPosDot" class="fin-dot off" title="Pozisyon yöneticisi durumu"></span><span id="finPosInfo" class="fin-hint"></span></div>
224
+ <div class="fin-field fin-check">
225
+ <label title="Açık pozisyon varken 5 saniyede bir JEV turu: kapat / kısmi kapat / SL taşı kararları. Trade ajanından bağımsız ayrı bir ajan — kendi talimatıyla çalışır; LLM kullanmaz.">
226
+ <input id="finPosOn" type="checkbox" /> Aktif — açık pozisyonları 5 sn'de bir Jev ile yönet
227
+ </label>
228
+ </div>
229
+ <div class="fin-field">
230
+ <span>Yönetici talimatı <i class="fin-hint">— yalnız bu ajanı bağlar</i><button id="finPosHelp" class="fin-help-btn" type="button" title="Neler yazabilirim? Desteklenen talimatlar ve örnekler">?</button></span>
231
+ <textarea id="finPosNote" rows="2" maxlength="1500" placeholder="örn. 1- 1R'de %50 kısmi kapat 2- kâr 2R'ye gelince kalanı kapat 3- zarar -0.5R'yi geçerse kes" title="Bu talimat YALNIZ Pozisyon Yöneticisi Jev ajanına gider (5 sn turu, kapat/kısmi/SL kararları); kaydetmek için alandan çık"></textarea>
232
+ </div>
233
+ <div id="finPosStatus">Beklemede — açık pozisyon yok</div>
234
+ </div>
222
235
  <div class="fin-card" id="finRiskCard" data-fin-card="risk" hidden>
223
236
  <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>
224
237
  <div class="fin-field fin-check">
@@ -876,6 +889,16 @@
876
889
  </div>
877
890
  </div>
878
891
 
892
+ <div id="finHelpOverlay" class="mini-overlay" hidden>
893
+ <div class="mini-dialog fin-help-dialog">
894
+ <div class="mini-head">
895
+ <span class="mini-title" id="finHelpTitle">TALİMAT KILAVUZU</span>
896
+ <button class="mini-close" id="finHelpClose" title="Kapat">&#x00D7;</button>
897
+ </div>
898
+ <div class="mini-body" id="finHelpBody"></div>
899
+ </div>
900
+ </div>
901
+
879
902
  <div id="finTeamOverlay" class="mini-overlay" hidden>
880
903
  <div class="mini-dialog fin-sym-dialog">
881
904
  <div class="mini-head">
@@ -228,6 +228,12 @@ const els = {
228
228
  finMaxLot: $('#finMaxLot'),
229
229
  finSymLimits: $('#finSymLimits'),
230
230
  finStrategy: $('#finStrategy'),
231
+ finStrategyHelp: $('#finStrategyHelp'),
232
+ finPosHelp: $('#finPosHelp'),
233
+ finHelpOverlay: $('#finHelpOverlay'),
234
+ finHelpTitle: $('#finHelpTitle'),
235
+ finHelpBody: $('#finHelpBody'),
236
+ finHelpClose: $('#finHelpClose'),
231
237
  finMaxTradesDay: $('#finMaxTradesDay'),
232
238
  finLossStreak: $('#finLossStreak'),
233
239
  finLossStreakPause: $('#finLossStreakPause'),
@@ -244,6 +250,11 @@ const els = {
244
250
  finTraderBtn: $('#finTraderBtn'),
245
251
  finTraderDot: $('#finTraderDot'),
246
252
  finTraderStatus: $('#finTraderStatus'),
253
+ finPosDot: $('#finPosDot'),
254
+ finPosInfo: $('#finPosInfo'),
255
+ finPosOn: $('#finPosOn'),
256
+ finPosNote: $('#finPosNote'),
257
+ finPosStatus: $('#finPosStatus'),
247
258
  finPosList: $('#finPosList'),
248
259
  finPosCount: $('#finPosCount'),
249
260
  finOrdList: $('#finOrdList'),
@@ -9718,6 +9729,7 @@ let finModelsFilled = false;
9718
9729
  let finLastPrices = new Map(); /* sembol → son bid (renk için) */
9719
9730
  let finCfgCache = null; /* son snapshot cfg — rol→skill modalı bundan okur */
9720
9731
  let finStrategyDirty = false; /* textarea'da kaydedilmeyi bekleyen ajan talimatı var */
9732
+ let finPosNoteDirty = false; /* kaydedilmeyi bekleyen pozisyon yöneticisi talimatı */
9721
9733
  const FIN_COLOR_UP = 'fs-up';
9722
9734
  const FIN_COLOR_DOWN = 'fs-down';
9723
9735
 
@@ -9933,6 +9945,8 @@ function finTraderInputsSet(cfg) {
9933
9945
  if (els.finMaxLot && ae !== els.finMaxLot) els.finMaxLot.value = cfg.maxLot || 0.1;
9934
9946
  finSymLimitsRender(cfg.symbolLimits);
9935
9947
  if (els.finStrategy && ae !== els.finStrategy && !finStrategyDirty) els.finStrategy.value = cfg.strategy || '';
9948
+ if (els.finPosNote && ae !== els.finPosNote && !finPosNoteDirty) els.finPosNote.value = cfg.posManagerNote || '';
9949
+ if (els.finPosOn && ae !== els.finPosOn) els.finPosOn.checked = cfg.posManagerEnabled !== false;
9936
9950
  if (els.finMaxTradesDay && ae !== els.finMaxTradesDay) els.finMaxTradesDay.value = Number(cfg.maxTradesPerDay) || 0;
9937
9951
  if (els.finLossStreak && ae !== els.finLossStreak) els.finLossStreak.value = Number(cfg.lossStreakLimit) || 0;
9938
9952
  if (els.finLossStreakPause && ae !== els.finLossStreakPause) els.finLossStreakPause.value = Number(cfg.lossStreakPauseMin) || 0;
@@ -10094,6 +10108,39 @@ function finRenderTrader(trader, cfg) {
10094
10108
  }
10095
10109
  }
10096
10110
 
10111
+ /* POZİSYON YÖNETİCİSİ kartı: açık pozisyon varken 5 sn Jev turu durumu */
10112
+ function finRenderPosManager(pm, cfg) {
10113
+ if (!pm) return;
10114
+ const enabled = cfg ? cfg.posManagerEnabled !== false : pm.enabled !== false;
10115
+ const pos = Number(pm.positions) || 0;
10116
+ if (els.finPosDot) {
10117
+ els.finPosDot.classList.remove('on', 'off', 'busy');
10118
+ els.finPosDot.classList.add(!enabled ? 'off' : pm.busy ? 'busy' : pos ? 'on' : 'off');
10119
+ els.finPosDot.title = !enabled
10120
+ ? 'Pozisyon yöneticisi kapalı'
10121
+ : pos
10122
+ ? 'İzliyor — ' + pos + ' açık pozisyon (5 sn Jev turu)'
10123
+ : 'Açık pozisyon yok — beklemede';
10124
+ }
10125
+ if (els.finPosInfo) els.finPosInfo.textContent = pm.rounds ? '· tur ' + pm.rounds : '';
10126
+ if (els.finPosStatus) {
10127
+ let t;
10128
+ if (!enabled) t = 'Kapalı — kutucuktan aç';
10129
+ else if (pm.busy) t = 'Tur çalışıyor…';
10130
+ else if (!pos) t = 'Beklemede — açık pozisyon yok';
10131
+ else t = 'İzliyor · ' + pos + ' pozisyon';
10132
+ if (pm.lastAt) {
10133
+ const d = new Date(pm.lastAt);
10134
+ const p = (x) => String(x).padStart(2, '0');
10135
+ t += ' · son tur ' + p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds());
10136
+ }
10137
+ if (pm.lastErr) t += ' · hata: ' + String(pm.lastErr).slice(0, 60);
10138
+ els.finPosStatus.textContent = t;
10139
+ els.finPosStatus.classList.toggle('on', enabled && !!pos);
10140
+ els.finPosStatus.classList.toggle('busy', !!pm.busy);
10141
+ }
10142
+ }
10143
+
10097
10144
  /* ---- RİSK OTOMASYONU + PERFORMANS panosu ---- */
10098
10145
  function finDayKey(ms) {
10099
10146
  const d = new Date(Number(ms) || 0);
@@ -10254,6 +10301,7 @@ async function finSnapshot() {
10254
10301
  finTraderInputsSet(r.cfg);
10255
10302
  finRenderRoles(r.cfg);
10256
10303
  finRenderTrader(r.trader, r.cfg);
10304
+ finRenderPosManager(r.posManager, r.cfg);
10257
10305
  finRenderAutomation(r.cfg, r.watch);
10258
10306
  /* GİZLİ RİSK OTOMASYONU AKTİF OLAMAZ: sunucu hâlâ açık diyorsa kapat */
10259
10307
  if (!finView.risk && r.cfg && r.cfg.watchdog !== false) finSaveCfg({ watchdog: false });
@@ -10392,8 +10440,10 @@ let finWatchDirty = false; /* picker/input değişikliği kaydedilmeyi bekliyor
10392
10440
  function finSaveCfg(patch) {
10393
10441
  if (patch && patch.symbols !== undefined) finWatchDirty = true;
10394
10442
  if (patch && patch.strategy !== undefined) finStrategyDirty = true;
10443
+ if (patch && patch.posManagerNote !== undefined) finPosNoteDirty = true;
10395
10444
  clearTimeout(finCfgTimer);
10396
10445
  const wasStrategy = !!(patch && patch.strategy !== undefined);
10446
+ const wasPosNote = !!(patch && patch.posManagerNote !== undefined);
10397
10447
  finCfgTimer = setTimeout(() => {
10398
10448
  beast.financeSettings(patch)
10399
10449
  .then(() => {
@@ -10401,6 +10451,10 @@ function finSaveCfg(patch) {
10401
10451
  finStrategyDirty = false;
10402
10452
  if (finCfgCache) finCfgCache.strategy = patch.strategy;
10403
10453
  }
10454
+ if (wasPosNote) {
10455
+ finPosNoteDirty = false;
10456
+ if (finCfgCache) finCfgCache.posManagerNote = patch.posManagerNote;
10457
+ }
10404
10458
  finWatchDirty = false;
10405
10459
  })
10406
10460
  .catch(() => { finWatchDirty = false; });
@@ -10838,6 +10892,113 @@ if (els.finStrategy) {
10838
10892
  toast(v.trim() ? 'Ajan talimatı kaydedildi — tüm ajanlar sonraki turda uygular' : 'Ajan talimatı temizlendi');
10839
10893
  });
10840
10894
  }
10895
+ /* TALİMAT KILAVUZU (?): her iki talimat alanı için desteklenen kalıplar.
10896
+ "kod" etiketli kalıplar AYRICALIKLI olarak kod tarafından otomatik uygulanır;
10897
+ serbest cümleler Jev'in tur state'ine girer ve Jev ona göre karar verir. */
10898
+ /* BUTONLAR: ? → kılavuz; dışına tıkla / × → kapat */
10899
+ if (els.finStrategyHelp) {
10900
+ els.finStrategyHelp.addEventListener('click', (e) => {
10901
+ e.preventDefault();
10902
+ finHelpOpen('trade');
10903
+ });
10904
+ }
10905
+ if (els.finPosHelp) {
10906
+ els.finPosHelp.addEventListener('click', (e) => {
10907
+ e.preventDefault();
10908
+ finHelpOpen('posmgr');
10909
+ });
10910
+ }
10911
+ if (els.finHelpClose) els.finHelpClose.addEventListener('click', finHelpClose);
10912
+ if (els.finHelpOverlay) {
10913
+ els.finHelpOverlay.addEventListener('click', (e) => {
10914
+ if (e.target === els.finHelpOverlay) finHelpClose();
10915
+ });
10916
+ }
10917
+ function finHelpHtml(which) {
10918
+ const posmgr = which === 'posmgr';
10919
+ const items = posmgr
10920
+ ? [
10921
+ ['<code>1R\'de %50 kısmi kapat</code><code>%50 1R</code><code>kısmi close %50</code>',
10922
+ '<b>Kod otomatik uygular.</b> Kâr belirtilen R\'ye gelince pozisyonun %50\'si kapatılır (tek sefer). "kısmi %50" tek başına yazılırsa 1R kabul edilir.'],
10923
+ ['<code>1.5R\'de %25 kısmi</code><code>3R\'de %75 kapat</code>',
10924
+ '<b>Kod otomatik uygular.</b> R ve yüzdeyi sen seç; yüzde 5-90 arası olmalı.'],
10925
+ ['<code>kâr 2R olunca kalanı kapat</code><code>kârı koru</code>',
10926
+ '<b>Jev uygular.</b> Serbest ifade; her 5 sn turunda Jev okur ve kapatma/trailing kararını verir.'],
10927
+ ['<code>SL\'yi takip et</code><code>breakeven\'a çek</code><code>zararı -0.5R\'de kes</code>',
10928
+ '<b>Jev uygular.</b> SL/koruma aksiyonu olarak değerlendirilir; kod seviyeyi ATR/R ile hesaplar, SL asla geriye gitmez.'],
10929
+ ['<code>başlangıç bakiye 10.000</code><code>gün başı: 10000</code>',
10930
+ 'Gün başı bakiyesi — günlük K/Z % otomatik hesaplanır (raporda "Gün başı → %-2.3 ZARAR").'],
10931
+ ]
10932
+ : [
10933
+ ['<code>risk %2</code><code>%2 risk</code><code>risk yüzde 2</code>',
10934
+ '<b>Kod otomatik uygular.</b> Girişler %2 risk ile açılır (kalibrasyon çarpanı devre dışı, 0.1-10 arası).'],
10935
+ ['<code>lot x1.5</code><code>poz başı lot 1.5</code><code>lotu 2 kat yap</code>',
10936
+ '<b>Kod otomatik uygular.</b> Tüm giriş riskine/lotuna çarpan.'],
10937
+ ['<code>martingale</code><code>martingale 1.5</code><code>martingale x2</code>',
10938
+ '<b>Kod otomatik uygular.</b> Kayıp serisinde lot katlanır (varsayılan ×2; seri en çok ×3, toplam risk tavanı %10 ve max lot kilidi geçerli).'],
10939
+ ['<code>her mumda işlem açmak zorundasın</code><code>her barda işlem</code><code>her zaman işlem</code>',
10940
+ '<b>ZORUNLU GİRİŞ modu.</b> "bekle" seçeneği kaldırılır, eşik/teyit kapıları atlanır, emir market olur. "zorunlu değil" yazarsan mod açılmaz.'],
10941
+ ['<code>M1</code><code>1m</code><code>5 dk</code><code>4 saat</code>',
10942
+ 'İşlem zaman dilimi. M1/M5 yazarsan tur ritmi hızlanır (M1 en hızlı 60 sn).'],
10943
+ ['<code>başlangıç bakiye 10.000</code><code>gün başı: 10000</code><code>starting balance 10000</code>',
10944
+ 'Gün başı bakiyesi — her turda günlük K/Z otomatik hesaplanır ve Jev\'e durum olarak verilir.'],
10945
+ ['<code>günlük zarar %3</code><code>max günlük kayıp 3</code>',
10946
+ '<b>Kod otomatik uygular.</b> Limit aşılırsa yeni işlem açılmaz; günlük kayıp otomasyonunun limiti bu olur.'],
10947
+ ];
10948
+ const example = posmgr
10949
+ ? `1- 1R'de %50 kısmi kapat\n2- kâr 2R'ye gelince kalanı kapat\n3- zarar -0.5R'yi geçerse kes`
10950
+ : `1- risk yüzde 2\n2- martingale kullan (x2)\n3- her mumda işlem açmak zorundasın\n4- başlangıç bakiye 10.000\n5- günlük zarar %3`;
10951
+ const head = posmgr
10952
+ ? 'Pozisyon Yöneticisi (5 sn turu) açık pozisyonları yönetir: kapat / kısmi kapat / SL taşı.'
10953
+ : 'Trade Ajanı girişleri yönetir: yön, emir tipi, risk, lot, martingale ve günlük limitler.';
10954
+ const rows = items
10955
+ .map((it) => '<div class="fin-help-item"><div class="fin-help-code">' + it[0] + '</div><div class="fin-help-desc">' + it[1] + '</div></div>')
10956
+ .join('');
10957
+ return (
10958
+ '<div class="fin-help-head">' + head + '</div>' +
10959
+ '<div class="fin-help-sec">Neler yazabilirsin?</div>' +
10960
+ rows +
10961
+ '<div class="fin-help-sec">Nasıl yazılır?</div>' +
10962
+ '<div class="fin-help-text">İstediğin gibi düz cümle yazabilirsin; birden çok kuralı <b>1- 2- 3-</b> diye numaralayarak alt alta yazman en kolayı. Kod, yukarıdaki sayısal kalıpları otomatik uygular; kalan serbest ifadeleri Jev her turda okur ve kararına katar. Talimat değişiklikleri <b>sonraki turda</b> geçerli olur.</div>' +
10963
+ '<pre class="fin-help-ex">' + example + '</pre>'
10964
+ );
10965
+ }
10966
+
10967
+ function finHelpOpen(which) {
10968
+ if (!els.finHelpOverlay) return;
10969
+ const posmgr = which === 'posmgr';
10970
+ if (els.finHelpTitle) els.finHelpTitle.textContent = posmgr ? 'POZİSYON YÖNETİCİSİ — TALİMAT KILAVUZU' : 'TRADE AJANI — TALİMAT KILAVUZU';
10971
+ if (els.finHelpBody) els.finHelpBody.innerHTML = finHelpHtml(which);
10972
+ els.finHelpOverlay.hidden = false;
10973
+ }
10974
+
10975
+ function finHelpClose() {
10976
+ if (els.finHelpOverlay) els.finHelpOverlay.hidden = true;
10977
+ }
10978
+
10979
+ /* POZİSYON YÖNETİCİSİ: 5 sn Jev turu — kendi talimatı + aç/kapa */
10980
+ if (els.finPosNote) {
10981
+ els.finPosNote.addEventListener('input', () => {
10982
+ finSaveCfg({ posManagerNote: els.finPosNote.value });
10983
+ });
10984
+ els.finPosNote.addEventListener('change', () => {
10985
+ const v = els.finPosNote.value;
10986
+ if (finCfgCache && String(finCfgCache.posManagerNote || '') === v) return;
10987
+ finSaveCfg({ posManagerNote: v });
10988
+ toast(v.trim() ? 'Yönetici talimatı kaydedildi — sonraki yönetici turunda uygular' : 'Yönetici talimatı temizlendi');
10989
+ });
10990
+ }
10991
+ if (els.finPosOn) {
10992
+ els.finPosOn.addEventListener('change', () => {
10993
+ finSaveCfg({ posManagerEnabled: els.finPosOn.checked });
10994
+ toast(
10995
+ els.finPosOn.checked
10996
+ ? 'Pozisyon yöneticisi AÇIK — açık pozisyonlar 5 sn Jev turuyla yönetilecek'
10997
+ : 'Pozisyon yöneticisi kapalı — pozisyon yönetimi trade ajanına döndü'
10998
+ );
10999
+ finSnapshot();
11000
+ });
11001
+ }
10841
11002
  /* TRADE AJANI: modelleri yeniden çek — üstteki ⟳ ile aynı mantık
10842
11003
  (models:refresh). Eksik/yeni modeller listeye girer, seçim korunur. */
10843
11004
  if (els.finModelRefreshBtn) {
@@ -5625,6 +5625,84 @@ body.finance-mode #composerWrap { margin-right: var(--finW, 400px); }
5625
5625
  #finTraderStatus.on.busy { color: var(--accent); }
5626
5626
  #finTraderStatus.on.busy::before { color: #f59e0b; }
5627
5627
 
5628
+ /* POZİSYON YÖNETİCİSİ kartı — trader durum satırıyla aynı görünüm */
5629
+ #finPosStatus {
5630
+ margin-top: 8px;
5631
+ font-size: 10.5px;
5632
+ color: var(--muted);
5633
+ background: var(--panel2);
5634
+ border: 1px solid var(--border);
5635
+ border-radius: 7px;
5636
+ padding: 5px 8px;
5637
+ white-space: nowrap;
5638
+ overflow: hidden;
5639
+ text-overflow: ellipsis;
5640
+ }
5641
+ #finPosStatus::before { content: '● '; font-size: 9px; color: var(--muted); }
5642
+ #finPosStatus.on { color: var(--text); border-color: var(--accent); }
5643
+ #finPosStatus.on::before { color: #22c55e; }
5644
+ #finPosStatus.on.busy { color: var(--accent); }
5645
+ #finPosStatus.on.busy::before { color: #f59e0b; }
5646
+
5647
+ /* TALİMAT KILAVUZU (? butonu + modal) */
5648
+ .fin-help-btn {
5649
+ width: 16px;
5650
+ height: 16px;
5651
+ margin-left: 6px;
5652
+ border-radius: 50%;
5653
+ border: 1px solid var(--border);
5654
+ background: var(--panel2);
5655
+ color: var(--muted);
5656
+ font-size: 10px;
5657
+ font-weight: 700;
5658
+ line-height: 1;
5659
+ padding: 0;
5660
+ cursor: pointer;
5661
+ vertical-align: 1px;
5662
+ }
5663
+ .fin-help-btn:hover { color: var(--text); border-color: var(--accent); }
5664
+ .fin-help-dialog { max-width: 620px; }
5665
+ #finHelpBody { max-height: 62vh; overflow: auto; }
5666
+ .fin-help-head { font-size: 11px; color: var(--muted); margin: 2px 0 10px; }
5667
+ .fin-help-sec {
5668
+ font-size: 10.5px;
5669
+ font-weight: 700;
5670
+ letter-spacing: 0.04em;
5671
+ text-transform: uppercase;
5672
+ color: var(--muted);
5673
+ margin: 12px 0 6px;
5674
+ }
5675
+ .fin-help-item {
5676
+ border: 1px solid var(--border);
5677
+ border-radius: 7px;
5678
+ padding: 6px 8px;
5679
+ margin-bottom: 6px;
5680
+ background: var(--panel2);
5681
+ }
5682
+ .fin-help-code { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 4px; }
5683
+ .fin-help-code code {
5684
+ font-size: 10.5px;
5685
+ padding: 1px 5px;
5686
+ border-radius: 4px;
5687
+ background: rgba(127, 127, 127, 0.14);
5688
+ border: 1px solid var(--border);
5689
+ }
5690
+ .fin-help-desc { font-size: 11px; color: var(--muted); line-height: 1.5; }
5691
+ .fin-help-desc b { color: var(--text); }
5692
+ .fin-help-text { font-size: 11px; color: var(--muted); line-height: 1.55; }
5693
+ .fin-help-text b { color: var(--text); }
5694
+ .fin-help-ex {
5695
+ margin: 8px 0 2px;
5696
+ padding: 8px 10px;
5697
+ border-radius: 7px;
5698
+ border: 1px dashed var(--border);
5699
+ background: var(--panel2);
5700
+ font-size: 11px;
5701
+ line-height: 1.6;
5702
+ white-space: pre-wrap;
5703
+ color: var(--text);
5704
+ }
5705
+
5628
5706
  #finPosList, #finOrdList, #finSymList { display: flex; flex-direction: column; gap: 5px; }
5629
5707
  .fin-empty { font-size: 11px; color: var(--muted); padding: 2px 0 4px; }
5630
5708