beast-agent 2.76.2 → 2.76.4

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.76.2",
4
+ "version": "2.76.4",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -126,6 +126,20 @@ function lotRange(cfg, symbol) {
126
126
  return { minLot, maxLot };
127
127
  }
128
128
 
129
+ /* LOT ÇARPANI (martingale/kademe): risk lotu hesaplandıktan SONRA hacim
130
+ çarpılır ve broker adımına normalize edilir; maxLot ile kırpılır.
131
+ 1. işlem 0.01 lot ise martingale kademesi 0.012'ye çıkar (adıma yuvarlanır)
132
+ — kâra geçince çarpan kalkar, taban lota dönülür. */
133
+ function scaleVolume(info, vol, mult, minLot, maxLot) {
134
+ const m = Number(mult);
135
+ if (!(m > 0) || m === 1) return vol;
136
+ const lo = Number(minLot) > 0 ? Number(minLot) : 0.01;
137
+ const hi = Number(maxLot) > 0 ? Number(maxLot) : vol * m;
138
+ const want = Math.max(lo, Math.min(hi, vol * m));
139
+ const n = finrisk.normalizeVolume(info, want, maxLot, minLot);
140
+ return !n.error && Number(n.volume) > 0 ? Number(n.volume) : vol;
141
+ }
142
+
129
143
  /* İşlem öncesi risk katmanı: stops_level + yoğunluk + marj kalkanı.
130
144
  Hata metni ya da null döner. positions çağıran tarafından verilir. */
131
145
  async function preTradeCheck(cfg, side, symbol, info, price, volume, sl, tp, positions) {
@@ -792,6 +806,8 @@ const handlers = {
792
806
  mesafesinden hesaplanır; aksi halde volume broker kuralına normalize edilir */
793
807
  const explicitRisk = Number(args.riskPct) > 0 ? Number(args.riskPct) : 0;
794
808
  const wantVolume = Number(args.volume);
809
+ /* MARTİNGALE/KADEME ÇARPANI: risk lotu hesaplandıktan sonra hacme uygulanır */
810
+ const sizeMult = Number(args.sizeMult) > 0 ? Math.min(20, Number(args.sizeMult)) : 1;
795
811
  let vol = 0;
796
812
  let riskInfo = null;
797
813
  if (explicitRisk > 0 || (!(wantVolume > 0) && Number(cfg.riskPerTradePct) > 0)) {
@@ -803,7 +819,15 @@ const handlers = {
803
819
  const rr = finrisk.calcRiskLot(info, price, sl, riskAmount, range.maxLot, range.minLot);
804
820
  if (rr.error) return { ok: false, error: rr.error };
805
821
  vol = rr.volume;
806
- riskInfo = { riskPct, riskAmount: Math.round(riskAmount * 100) / 100, lossPerLot: rr.lossPerLot, raised: !!rr.raised };
822
+ if (sizeMult !== 1) vol = scaleVolume(info, vol, sizeMult, range.minLot, range.maxLot);
823
+ const perLot = Number(rr.lossPerLot) || 0;
824
+ riskInfo = {
825
+ riskPct,
826
+ riskAmount: Math.round((perLot > 0 ? perLot * vol : riskAmount) * 100) / 100,
827
+ lossPerLot: rr.lossPerLot,
828
+ raised: !!rr.raised,
829
+ sizeMult,
830
+ };
807
831
  } else {
808
832
  const norm = finrisk.normalizeVolume(info, wantVolume, range.maxLot, range.minLot);
809
833
  if (norm.error) return { ok: false, error: norm.error };
@@ -939,6 +963,8 @@ const handlers = {
939
963
  /* LOT: riskPct verilirse BEKLEYEN EMİR fiyatı + SL mesafesinden hesaplanır
940
964
  (piyasa emriyle aynı risk hattı); volume verilmişse normalize edilir. */
941
965
  const explicitRisk = Number(args.riskPct) > 0 ? Number(args.riskPct) : 0;
966
+ /* MARTİNGALE/KADEME ÇARPANI: risk lotu hesaplandıktan sonra hacme uygulanır */
967
+ const sizeMult = Number(args.sizeMult) > 0 ? Math.min(20, Number(args.sizeMult)) : 1;
942
968
  let vol = 0;
943
969
  let riskInfo = null;
944
970
  if (explicitRisk > 0 || (!(Number(args.volume) > 0) && Number(cfg.riskPerTradePct) > 0)) {
@@ -951,7 +977,15 @@ const handlers = {
951
977
  const rr = finrisk.calcRiskLot(info, entryPrice, slRef, riskAmount, range.maxLot, range.minLot);
952
978
  if (rr.error) return { ok: false, error: rr.error };
953
979
  vol = rr.volume;
954
- riskInfo = { riskPct, riskAmount: Math.round(riskAmount * 100) / 100, lossPerLot: rr.lossPerLot, raised: !!rr.raised };
980
+ if (sizeMult !== 1) vol = scaleVolume(info, vol, sizeMult, range.minLot, range.maxLot);
981
+ const perLot = Number(rr.lossPerLot) || 0;
982
+ riskInfo = {
983
+ riskPct,
984
+ riskAmount: Math.round((perLot > 0 ? perLot * vol : riskAmount) * 100) / 100,
985
+ lossPerLot: rr.lossPerLot,
986
+ raised: !!rr.raised,
987
+ sizeMult,
988
+ };
955
989
  } else {
956
990
  const norm = finrisk.normalizeVolume(info, args.volume, range.maxLot, range.minLot);
957
991
  if (norm.error) return { ok: false, error: norm.error };
package/src/main.js CHANGED
@@ -10006,7 +10006,16 @@ function finCfg() {
10006
10006
  finNormalizeSymbolLimits(f);
10007
10007
  if (f.maxPositions == null) f.maxPositions = 3;
10008
10008
  /* KODLA DİSİPLİN (trader kuralları): hepsi 0 = kural kapalı */
10009
- if (!Number.isFinite(Number(f.maxTradesPerDay))) f.maxTradesPerDay = 10;
10009
+ /* GÜNLÜK İŞLEM LİMİTİ KALDIRILDI (varsayılan KAPALI/0 = sınırsız): eski
10010
+ sürümün zorla koyduğu 10 değeri BİR KEZ sıfırlanır; kullanıcı bilerek
10011
+ başka değer girdiyse dokunulmaz. Talimatta "günde en fazla N işlem"
10012
+ yazılıysa not yine geçerlidir (bilinçli opt-in). */
10013
+ if (!Number.isFinite(Number(f.maxTradesPerDay))) f.maxTradesPerDay = 0;
10014
+ if (f.maxTradesPerDayDefaultCleared !== true) {
10015
+ f.maxTradesPerDayDefaultCleared = true;
10016
+ if (Number(f.maxTradesPerDay) === 10) f.maxTradesPerDay = 0;
10017
+ try { saveSettings(); } catch {}
10018
+ }
10010
10019
  if (!Number.isFinite(Number(f.lossStreakLimit))) f.lossStreakLimit = 2;
10011
10020
  if (!Number.isFinite(Number(f.lossStreakPauseMin))) f.lossStreakPauseMin = 30;
10012
10021
  /* RE-ENTRY BEKLEME: kural VARSAYILAN KAPALI (0) — isteyen Gelişmiş
@@ -11333,19 +11342,28 @@ async function finRecordClose(ticket, st) {
11333
11342
  let net = null;
11334
11343
  let symbol = (st && st.symbol) || '';
11335
11344
  let closeReason = null; /* kapanışı yapan son deal'in nedeni (4=SL, 5=TP, 6=stop-out) */
11336
- try {
11337
- const r = await mt5bridge.call('deals', { days: 3 }, 12000);
11338
- const deals = (r && r.ok && r.data && r.data.deals) || [];
11339
- const mine = deals.filter((d) => String(d.position_id) === String(ticket));
11340
- if (mine.length) {
11341
- net = 0;
11342
- for (const d of mine) {
11343
- net += (Number(d.profit) || 0) + (Number(d.swap) || 0) + (Number(d.commission) || 0);
11344
- if (Number(d.entry) === 1) closeReason = Number(d.reason);
11345
+ /* DEAL GECİKMESİ: kapanıştan hemen sonra deal listesi boş dönebilir — kısa
11346
+ gecikmeyle 1 kez daha dene. K/Z kaydı kaçarsa martingale kayıp serisi,
11347
+ öğrenme istatistiği ve kalibrasyon boş kalır (martingale çalışmaz). */
11348
+ for (let attempt = 0; attempt < 2 && net == null; attempt++) {
11349
+ if (attempt > 0) await new Promise((res) => setTimeout(res, 2000));
11350
+ try {
11351
+ const r = await mt5bridge.call('deals', { days: 3 }, 12000);
11352
+ const deals = (r && r.ok && r.data && r.data.deals) || [];
11353
+ const mine = deals.filter((d) => String(d.position_id) === String(ticket));
11354
+ if (mine.length) {
11355
+ net = 0;
11356
+ for (const d of mine) {
11357
+ net += (Number(d.profit) || 0) + (Number(d.swap) || 0) + (Number(d.commission) || 0);
11358
+ if (Number(d.entry) === 1) closeReason = Number(d.reason);
11359
+ }
11360
+ symbol = String(mine[0].symbol || symbol);
11345
11361
  }
11346
- symbol = String(mine[0].symbol || symbol);
11347
- }
11348
- } catch {}
11362
+ } catch {}
11363
+ }
11364
+ if (net == null) {
11365
+ try { financeLog(`[watchdog] kapanış K/Z okunamadı (ticket ${ticket}, ${symbol || '?'}) — deal kaydı yok`); } catch {}
11366
+ }
11349
11367
  const rounded = net == null ? null : Math.round(net * 100) / 100;
11350
11368
  /* MAE/MFE: pozisyonun gördüğü en iyi/en kötü seviyeler kapanışa iliştirilir */
11351
11369
  const ex = financeState.excursions.get(String(ticket)) || null;
@@ -13868,7 +13886,7 @@ function finInstrMoney(v) {
13868
13886
 
13869
13887
  function finParseInstructions(text) {
13870
13888
  const low = String(text || '').toLowerCase();
13871
- const out = { riskPct: null, lotMult: null, martingale: null, partial: null, startBalance: null, dailyLossPct: null };
13889
+ const out = { riskPct: null, lotMult: null, martingale: null, partial: null, startBalance: null, dailyLossPct: null, noLossClose: false };
13872
13890
  if (!low.trim()) return out;
13873
13891
  /* "risk %2", "%2 risk", "risk yüzde 2", "riski 2 yap" → 2 (ilk sayı) */
13874
13892
  let m = low.match(
@@ -13898,7 +13916,9 @@ function finParseInstructions(text) {
13898
13916
  sayılar faktör sayılmaz (varsayılan ×2 kalır). Geçerli aralık 1.01-5. */
13899
13917
  const pats = [
13900
13918
  /mart[ıi]ngale\s*(?:x|×|çarpan[ıi]?|carpan[ıi]?|oran[ıi]?|fakt[öo]r[üu]?|kat[ıi]?)?\s*(\d+(?:[.,]\d+)?)(?!\s*r\b)/,
13901
- /(\d+(?:[.,]\d+)?)\s*(?:x|×|kat[ıi]?)?\s*mart[ıi]ngale/,
13919
+ /* "1.2x martingale" — çarpan kelimesi (x/kat) ZORUNLU: yoksa "risk %2
13920
+ martingale" cümlesindeki 2 yanlışlıkla faktör sanılırdı */
13921
+ /(\d+(?:[.,]\d+)?)\s*(?:x|×|kat[ıi]?)\s*mart[ıi]ngale/,
13902
13922
  /mart[ıi]ngale[^0-9%\n]{0,24}?(\d+(?:[.,]\d+)?)\s*(?:x|×|kat[ıi]?)/,
13903
13923
  /mart[ıi]ngale[^0-9%\n]{0,24}?(\d+[.,]\d+)(?!\s*r\b)/,
13904
13924
  ];
@@ -13924,6 +13944,16 @@ function finParseInstructions(text) {
13924
13944
  m = low.match(/(?:k[ıi]smi|partial)[^%\d]{0,24}%\s*(\d+(?:[.,]\d+)?)/);
13925
13945
  if (m) out.partial = { atR: 1, pct: finInstrNum(m[1]) };
13926
13946
  }
13947
+ if (!out.partial) {
13948
+ /* "0.5R'de yarısını kapat", "0.5R karda partial close ile yarısını kapat" */
13949
+ m = low.match(/(\d+(?:[.,]\d+)?)\s*r[^.\n]{0,40}?(?:yar[ıi]s[ıi](?:n[ıi])?|half)/);
13950
+ if (m) out.partial = { atR: finInstrNum(m[1]), pct: 50 };
13951
+ }
13952
+ if (!out.partial) {
13953
+ /* R yazılmamış: "partial close ile yarısını kapat" → 1R varsayılan */
13954
+ m = low.match(/(?:k[ıi]smi|partial|yar[ıi]s[ıi](?:n[ıi])?)[^.\n]{0,30}?(?:yar[ıi]s[ıi](?:n[ıi])?|%\s*50)/);
13955
+ if (m) out.partial = { atR: 1, pct: 50 };
13956
+ }
13927
13957
  if (out.partial) {
13928
13958
  const p = out.partial;
13929
13959
  if (!(p.atR > 0)) p.atR = 1;
@@ -13931,6 +13961,18 @@ function finParseInstructions(text) {
13931
13961
  if (!(p.pct >= 5 && p.pct <= 90)) out.partial = null;
13932
13962
  else p.pct = Math.round(p.pct);
13933
13963
  }
13964
+ /* ZARARDA KAPATMA YASAĞI (KOD uygular): "zararda işlem kapatma",
13965
+ "zararına kapatma", "sadece karda kapat", "kârda değilse kapatma" →
13966
+ kapatma kararı zarardayken REDDEDİLİR (SL/TP hariç — stop çalışır). */
13967
+ if (
13968
+ /(?:zarar(?:da|ına|dayken|day[ıi]m)?|eksi(?:de|ye))[^.\n]{0,24}?kapatma/.test(low) ||
13969
+ /kapatma[^.\n]{0,24}?(?:zarar|eksi)/.test(low) ||
13970
+ /(?:sadece|yaln[ıi]z)[^.\n]{0,24}?k[âa]r(?:da|daysa|dayken)?\b/.test(low) ||
13971
+ /k[âa]rda de[ğg]il(?:se)?[^.\n]{0,20}?kapatma/.test(low) ||
13972
+ /k[âa]r olmadan[^.\n]{0,16}?kapatma/.test(low)
13973
+ ) {
13974
+ out.noLossClose = true;
13975
+ }
13934
13976
  return out;
13935
13977
  }
13936
13978
 
@@ -13947,7 +13989,11 @@ function finParsedInstr() {
13947
13989
  startBalance: trade.startBalance != null ? trade.startBalance : mgr.startBalance,
13948
13990
  dailyLossPct: trade.dailyLossPct != null ? trade.dailyLossPct : mgr.dailyLossPct,
13949
13991
  },
13950
- manage: { partial: mgr.partial || trade.partial },
13992
+ manage: {
13993
+ partial: mgr.partial || trade.partial,
13994
+ /* YÖNETİM KURALI: zararda kapatma yasağı iki nottan birinde varsa geçerli */
13995
+ noLossClose: !!(mgr.noLossClose || trade.noLossClose),
13996
+ },
13951
13997
  };
13952
13998
  }
13953
13999
 
@@ -13960,7 +14006,8 @@ function finInstrSummary(ins) {
13960
14006
  if (ins.entry.martingale != null) p.push(`martingale ×${ins.entry.martingale}`);
13961
14007
  if (ins.entry.startBalance != null) p.push(`gün başı ${ins.entry.startBalance}`);
13962
14008
  if (ins.entry.dailyLossPct != null) p.push(`günlük zarar limiti %${ins.entry.dailyLossPct}`);
13963
- if (ins.manage.partial) p.push(`kısmi %${ins.manage.partial.pct} @${ins.manage.partial.atR}R`);
14009
+ if (ins.manage.partial) p.push(`kısmi %${ins.manage.partial.pct} @${ins.manage.partial.atR}R (yalnız kârda)`);
14010
+ if (ins.manage.noLossClose) p.push('zararda kapatma YOK');
13964
14011
  try {
13965
14012
  const f = finCfg();
13966
14013
  if (Number(f.maxPositionsNote) > 0) p.push(`maks ${f.maxPositionsNote} pozisyon`);
@@ -13986,12 +14033,25 @@ function finTsStrategyTf() {
13986
14033
  }
13987
14034
 
13988
14035
  /* Talimat "zorunlu giriş" istiyor mu? ("her mumda işlem açmak zorundasın" gibi)
13989
- Olumsuz yazım ("zorunlu değil") modu AÇMAZ. */
14036
+ SADECE açık emir cümleleri modu açar; teyit cümleleri ("her mum kapanışını
14037
+ bekle", "her mumda alarm kur") modu AÇMAZ → girişler İSTEĞE BAĞLI kalır
14038
+ (eşik + teyit kapıları çalışır). Olumsuz yazım ("zorunlu değil / zorunda
14039
+ değil / mecbur değil") modu kapatır. */
13990
14040
  function finTsMandatoryEntry() {
13991
- const s = String(finTsStrategyText() || '').toLowerCase();
14041
+ const s = String(finTsStrategyText() || '')
14042
+ .toLocaleLowerCase('tr')
14043
+ .replace(/\u0307/g, ''); /* İ → i̇ (birleşik nokta) temizliği */
13992
14044
  if (!s) return false;
13993
14045
  if (/zorunlu değil|mecbur değil|zorunda değil/.test(s)) return false;
13994
- return /her mum|her barda?|her mumda|işlem açmak zorunda|her zaman işlem|sürekli işlem|durmadan işlem|zorunlu (?:olarak )?(?:işlem|giriş|al|sat)|mecbur/.test(s);
14046
+ const trade = '(?:i[şs]lem|trade|giri[şs]|pozisyon|emir|al\\b|sat\\b|a[çc]\\b)';
14047
+ return (
14048
+ new RegExp('her\\s*(?:mum(?:da|de)?|bar(?:da|de)?)[^.\\n]{0,24}?' + trade).test(s) ||
14049
+ new RegExp('(?:i[şs]lem|giri[şs]|pozisyon|emir)\\s*(?:a[çc]mak|a[çc]ma)?\\s*zorunda').test(s) ||
14050
+ new RegExp('her\\s*zaman\\s*' + trade).test(s) ||
14051
+ new RegExp('(?:s[üu]rekli|durmadan)\\s*' + trade).test(s) ||
14052
+ new RegExp('zorunlu\\s*(?:olarak\\s*)?' + trade).test(s) ||
14053
+ /\bmecbur\b/.test(s)
14054
+ );
13995
14055
  }
13996
14056
 
13997
14057
  /* Ritim: talimatta M1 → işlem ajanı turları en hızlı 60 sn, M5 → 300 sn
@@ -14138,8 +14198,10 @@ async function finTsManageOpen(sidS, agent, sym, pos, mkt, ans, qi, lines) {
14138
14198
  const pct = par && par.p >= FIN_TS_CONFIRM_P && par.p - par.second >= FIN_TS_MARGIN_P
14139
14199
  ? ({ yuzde25: 25, yuzde50: 50, yuzde75: 75 }[String(par.choice)] || 0)
14140
14200
  : 0;
14141
- if (pct > 0 && !partialDone) {
14142
- const kind = profitDist >= 0 ? 'partial_tp' : 'partial_sl';
14201
+ if (pct > 0 && profitDist > 0 && !partialDone) {
14202
+ /* YALNIZ KÂRDA: sahip kuralı "kısmi kapatma sadece kardaysa" — zararda
14203
+ kısmi kapatma (partial_sl) UYGULANMAZ; zarar kesme kararı SL'ye aittir. */
14204
+ const kind = 'partial_tp';
14143
14205
  let r = null;
14144
14206
  try {
14145
14207
  r = await financetools.handlers.mt5_close(
@@ -14147,7 +14209,7 @@ async function finTsManageOpen(sidS, agent, sym, pos, mkt, ans, qi, lines) {
14147
14209
  ticket: Number(pos.ticket),
14148
14210
  percent: pct,
14149
14211
  kind,
14150
- reason: `TypeSafe: kısmi %${pct} p=${par.p.toFixed(2)} (${kind === 'partial_tp' ? 'kâr al' : 'zarar kes'})`,
14212
+ reason: `TypeSafe: kısmi %${pct} p=${par.p.toFixed(2)} (kâr al)`,
14151
14213
  },
14152
14214
  { sessionId: sidS }
14153
14215
  );
@@ -14158,12 +14220,14 @@ async function finTsManageOpen(sidS, agent, sym, pos, mkt, ans, qi, lines) {
14158
14220
  if (st) { st.partial = true; finWatchSave(); }
14159
14221
  if (agent) { agent.tsPartials = agent.tsPartials || new Set(); agent.tsPartials.add(String(pos.ticket)); }
14160
14222
  lines.push(
14161
- `- ${sym} #${pos.ticket}: KISMİ %${pct} ${kind === 'partial_tp' ? 'TP ✓' : 'STOP ✓'}` +
14223
+ `- ${sym} #${pos.ticket}: KISMİ %${pct} TP ✓` +
14162
14224
  ` (p=${par.p.toFixed(2)}${r.remaining != null ? ', kalan ' + r.remaining + ' lot' : ''})`
14163
14225
  );
14164
14226
  } else {
14165
14227
  lines.push(`- ${sym} #${pos.ticket}: kısmi %${pct} reddedildi — ${(r && r.error) || 'hata'}`);
14166
14228
  }
14229
+ } else if (pct > 0 && profitDist <= 0) {
14230
+ lines.push(`- ${sym} #${pos.ticket}: kısmi atlandı — kârda değil (talimat: sadece kârda)`);
14167
14231
  } else if (pct > 0 && partialDone) {
14168
14232
  lines.push(`- ${sym} #${pos.ticket}: kısmi atlandı — daha önce alındı`);
14169
14233
  }
@@ -14446,6 +14510,12 @@ async function finPosManagerRound(positions, account) {
14446
14510
  piyasa: {},
14447
14511
  alarmlar: (financeState.alerts || []).slice(0, 8).map((a) => ({ sembol: a.symbol, yon: a.direction, fiyat: a.price, mod: a.once ? 'tek' : 'tekrarlı' })),
14448
14512
  sahip_talimati: String(f.posManagerNote || '').slice(0, 1500) || '(yok — genel disiplin: kârı koru, zararı sınırla, SL/TP aktif yönet)',
14513
+ /* TRADE TALİMATI da yöneticiye görünür: iki kartın kuralları çelişmesin */
14514
+ trader_talimati: finTsStrategyText() ? String(finTsStrategyText()).slice(0, 1000) : '',
14515
+ /* KOD KURALI ÖZETİ: Jev bunları ihlal eden seçim yapmasın */
14516
+ kapatma_kurali: ins.manage.noLossClose
14517
+ ? 'ZARARDA KAPATMA YOK (kod uygular): zarardaki pozisyonun kapatma kararı reddedilir — SL/TP kapatması serbest; kısmi kapatma YALNIZ kârda'
14518
+ : 'zararda kapatma serbest',
14449
14519
  talimat_ayarlari: instrTxt || '(sayısal kural yok)',
14450
14520
  gun_durumu: (() => {
14451
14521
  try {
@@ -14513,7 +14583,16 @@ async function finPosManagerRound(positions, account) {
14513
14583
  const pp = positions[i];
14514
14584
  const sym = String(pp.symbol || '').toUpperCase();
14515
14585
  const ex = finTsNoul(ans, 'kapat_' + i);
14516
- if (ex != null && ex >= FIN_TS_EXIT_P) {
14586
+ const psNow = finTsPosState(pp);
14587
+ /* ZARARDA KAPATMA YASAĞI (talimat — KOD uygular): zarardaki pozisyonun
14588
+ kapatma kararı REDDEDİLİR. SL/TP kapatması bundan etkilenmez. */
14589
+ const inLoss = Number(pp.profit) < 0 || (psNow.kz_r != null && psNow.kz_r < 0);
14590
+ if (ex != null && ex >= FIN_TS_EXIT_P && ins.manage.noLossClose && inLoss) {
14591
+ lines.push(
14592
+ `- ${sym} #${pp.ticket}: KAPATILMADI — talimat "zararda kapatma" ` +
14593
+ `(K/Z ${Math.round((Number(pp.profit) || 0) * 100) / 100}${psNow.kz_r != null ? ' · ' + psNow.kz_r + 'R' : ''})`
14594
+ );
14595
+ } else if (ex != null && ex >= FIN_TS_EXIT_P) {
14517
14596
  let r = null;
14518
14597
  try {
14519
14598
  r = await financetools.handlers.mt5_close(
@@ -14525,9 +14604,9 @@ async function finPosManagerRound(positions, account) {
14525
14604
  }
14526
14605
  lines.push(`- ${sym} #${pp.ticket}: KAPAT (p=${ex.toFixed(2)}) ${r && r.ok ? '✓ kapatıldı' : '✗ ' + ((r && r.error) || 'hata')}`);
14527
14606
  continue;
14607
+ } else {
14608
+ 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'}`);
14528
14609
  }
14529
- const psNow = finTsPosState(pp);
14530
- 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'}`);
14531
14610
  /* TALİMAT KISMİ KURALI (kod): "+atR'de %pct kapat" → otomatik uygula */
14532
14611
  const rule = ins.manage.partial;
14533
14612
  if (rule) {
@@ -14876,7 +14955,13 @@ async function finTypeSafeRound(sid, agent) {
14876
14955
  lines.push(`- ${sym}: pozisyon açık #${pos.ticket} — yönetim Pozisyon Yöneticisi'nde (${finPosMgrSec()} sn Jev turu)`);
14877
14956
  } else {
14878
14957
  const ex = finTsNoul(ans, 'kapat_' + i);
14879
- if (ex != null && ex >= FIN_TS_EXIT_P) {
14958
+ const psT = finTsPosState(pos);
14959
+ const inLossT = Number(pos.profit) < 0 || (psT.kz_r != null && psT.kz_r < 0);
14960
+ if (ex != null && ex >= FIN_TS_EXIT_P && ins.manage.noLossClose && inLossT) {
14961
+ lines.push(`- ${sym}: kapat REDDEDİLDİ — talimat "zararda kapatma" (K/Z ${Math.round((Number(pos.profit) || 0) * 100) / 100})`);
14962
+ /* yönetim kararları (kısmi/SL) yine değerlendirilir */
14963
+ try { await finTsManageOpen(sidS, agent, sym, pos, market[sym], ans, i, lines); } catch {}
14964
+ } else if (ex != null && ex >= FIN_TS_EXIT_P) {
14880
14965
  const r = await financetools.handlers.mt5_close(
14881
14966
  { ticket: Number(pos.ticket), reason: `TypeSafe: kapat p=${ex.toFixed(2)}` },
14882
14967
  { sessionId: sidS }
@@ -14986,7 +15071,10 @@ async function finTypeSafeRound(sid, agent) {
14986
15071
  martNote = ` · martingale ×${ins.entry.martingale}^${pow} (${pow}. ardışık kayıp sonrası)`;
14987
15072
  }
14988
15073
  }
14989
- if (sizeMult !== 1) riskPct = Math.max(0.1, Math.min(10, Math.round(riskPct * sizeMult * 100) / 100));
15074
+ /* MARTİNGALE/LOT ÇARPANI: riskPct TABAN kalır; çarpan LOTA uygulanır
15075
+ (araç: risk lotu × sizeMult, broker adımı + maxLot kelepçesi).
15076
+ Gösterim ve karar kaydı için efektif risk yüzdesi hesaplanır. */
15077
+ const effRiskPct = Math.max(0.1, Math.min(30, Math.round(riskPct * sizeMult * 100) / 100));
14990
15078
  const riskNote = ins.entry.riskPct != null ? ' · TALİMAT risk %' + ins.entry.riskPct : '';
14991
15079
  const emirLabel =
14992
15080
  orderType === 'market' ? 'MARKET' :
@@ -15002,6 +15090,7 @@ async function finTypeSafeRound(sid, agent) {
15002
15090
  sl,
15003
15091
  tp,
15004
15092
  riskPct,
15093
+ sizeMult,
15005
15094
  timeframe: market[sym].tf,
15006
15095
  comment: 'TS',
15007
15096
  reason:
@@ -15038,11 +15127,11 @@ async function finTypeSafeRound(sid, agent) {
15038
15127
  emaAlign: ind.ema50 != null && ind.ema200 != null ? (ind.ema50 > ind.ema200) === (c.choice === 'buy') : null,
15039
15128
  rsi: ind.rsi14,
15040
15129
  atrPct: atr > 0 && entryRef > 0 ? atr / entryRef : null,
15041
- riskPct,
15130
+ riskPct: effRiskPct,
15042
15131
  orderType,
15043
15132
  });
15044
15133
  } catch {}
15045
- const riskTxt = `risk %${riskPct}${ins.entry.riskPct == null && cal.riskMult !== 1 ? ` ×${cal.riskMult}` : ''}${riskNote}${martNote}`;
15134
+ const riskTxt = `risk %${effRiskPct}${ins.entry.riskPct == null && cal.riskMult !== 1 ? ` ×${cal.riskMult}` : ''}${riskNote}${martNote}`;
15046
15135
  lines.push(
15047
15136
  (orderType === 'market'
15048
15137
  ? `- ${sym}: ⚡ MARKET ${c.choice.toUpperCase()} açıldı (lot ${r.opened && r.opened.volume}, SL ${sl}, TP ${tp}, ${riskTxt})`
@@ -15072,6 +15161,11 @@ async function finTypeSafeRound(sid, agent) {
15072
15161
  agent,
15073
15162
  `🤖 TypeSafe karar turu #${agent.round} (LLM yok — girdi yalnız TypeSafe):\n` +
15074
15163
  (insTxt ? 'TALİMAT AYARLARI (kod uygular): ' + insTxt + '\n' : '') +
15164
+ /* giriş serbestliği görünür olsun: zorunlu mod yalnız talimat açıkça
15165
+ isterse; yoksa girişler eşik/teyit kapılı (isteğe bağlı) */
15166
+ (talimatZorunlu
15167
+ ? 'TALİMAT MODU: ZORUNLU GİRİŞ — talimat gereği bu turda giriş zorunlu\n'
15168
+ : 'GİRİŞ MODU: İSTEĞE BAĞLI — yalnız eşik/teyit geçen sinyalde işlem açılır\n') +
15075
15169
  'TF KARARI: ' + symbols.map((s) => `${s}=${market[s].tf}`).join(', ') + '\n' +
15076
15170
  'ÖĞRENİLMİŞ: ' +
15077
15171
  symbols
@@ -10941,7 +10941,7 @@ function finHelpHtml(which) {
10941
10941
  ['<code>martingale</code><code>martingale 1.2</code><code>martingale x1.20</code><code>1.2 kat martingale</code>',
10942
10942
  '<b>Kod otomatik uygular.</b> Kayıp serisi KAPANAN işlemlerden sayılır (aç+kapa = 1 işlem): 1. işlem stop → 2. işlem ×1.2, o da stop → ×1.44… Kâra geçen ilk işlemde seri sıfırlanır ve taban lota dönülür. Faktör serbest (ör. 1.2); varsayılan ×2, 1.01–5 arası geçerli, seri en çok ×3, toplam risk tavanı %10 ve max lot kilidi geçerli.'],
10943
10943
  ['<code>her mumda işlem açmak zorundasın</code><code>her barda işlem</code><code>her zaman işlem</code>',
10944
- '<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.'],
10944
+ '<b>ZORUNLU GİRİŞ modu (isteğe bağlı).</b> Bu ifadeyi <b>yazmazsan girişler isteğe bağlıdır</b>: yalnız eşik + teyit geçen sinyalde işlem açılır. Yazarsan "bekle" kaldırılır, eşik/teyit atlanır, emir market olur. Teyit cümleleri ("her mum kapanışını bekle") modu açmaz; "zorunlu değil" yazarsan kapanır.'],
10945
10945
  ['<code>M1</code><code>1m</code><code>M15</code><code>5 dk</code><code>4 saat</code>',
10946
10946
  'İşlem zaman dilimi. Yazdığın TF (ör. <b>M15</b>) tüm girişlerde birincil kuraldır: analiz, bildirim ve pozisyon yorumu o periyodu kullanır — talimatta M1 yazmadıkça M1\'de işlem açılmaz. M1/M5 yazarsan tur ritmi hızlanır (M1 en hızlı 60 sn).'],
10947
10947
  ['<code>başlangıç bakiye 10.000</code><code>gün başı: 10000</code><code>starting balance 10000</code>',
@@ -10949,13 +10949,17 @@ function finHelpHtml(which) {
10949
10949
  ['<code>günlük zarar %3</code><code>max günlük kayıp 3</code>',
10950
10950
  '<b>Kod otomatik uygular.</b> Limit aşılırsa yeni işlem açılmaz; günlük kayıp otomasyonunun limiti bu olur.'],
10951
10951
  ['<code>aynı anda en fazla 10 işlem</code><code>en fazla 10 pozisyon</code><code>günde en fazla 10 işlem</code>',
10952
- '<b>Kod otomatik uygular.</b> Eşzamanlı tavan (pozisyon + bekleyen emir) ya da günlük işlem tavanı. Talimatta yazdığın sürece Ayarlar\'daki değeri ezer; metinden kaldırınca ayar geçerli olur.'],
10952
+ '<b>Kod otomatik uygular.</b> "aynı anda/en fazla pozisyon" = eşzamanlı tavan (pozisyon + bekleyen emir). <b>Günlük işlem limiti varsayılan olarak KAPALI (sınırsız)</b>; yalnız "günde en fazla N işlem" yazarsan o gün için devreye girer. Ayarlar\'daki değer 0 = sınırsız.'],
10953
10953
  ['<code>sembol başına 5 işlem</code><code>aynı sembolde en fazla 3 pozisyon</code>',
10954
10954
  '<b>Kod otomatik uygular.</b> Aynı sembolde çoklu işlem tavanı (toplam tavanı aşamaz). Toplam tavan yazarsan ("aynı anda en fazla 10 işlem") aynı sembolde de o tavana kadar işlem açılabilir; yazmazsan Ayarlar → maks/pay değeri geçerlidir.'],
10955
+ ['<code>zararda işlem kapatma</code><code>sadece karda kapat</code><code>kârda değilse kapatma</code>',
10956
+ '<b>Kod otomatik uygular.</b> Zarardaki pozisyonun kapatma kararı REDDEDİLİR — SL/TP çalışmaya devam eder (stop = martingale serisinin parçası). Kârdaki/başabaştaki pozisyon normal kapatılır.'],
10957
+ ['<code>0.5R karda yarısını kapat</code><code>partial close ile yarısını kapat</code>',
10958
+ '<b>Kod otomatik uygular.</b> Kâr +0.5R\'ye ulaşınca pozisyonun %50\'si kapatılır — <b>yalnız kârdayken</b> uygulanır (zararda kısmi stop yapılmaz).'],
10955
10959
  ];
10956
10960
  const example = posmgr
10957
10961
  ? `1- 1R'de %50 kısmi kapat\n2- kâr 2R'ye gelince kalanı kapat\n3- zarar -0.5R'yi geçerse kes`
10958
- : `1- M15'te işlem aç\n2- risk yüzde 2\n3- martingale kullan (x1.2)\n4- her mumda işlem açmak zorundasın\n5- aynı anda en fazla 10 işlem\n6- başlangıç bakiye 10.000\n7- günlük zarar %3`;
10962
+ : `1- zaman dilimi M15\n2- risk yüzde 2\n3- martingale kullan (x1.2)\n4- sadece uygun sinyalde işlem aç (isteğe bağlı)\n5- aynı anda en fazla 10 işlem\n6- başlangıç bakiye 10.000\n7- günlük zarar %3`;
10959
10963
  const head = posmgr
10960
10964
  ? 'Pozisyon Yöneticisi (ayarlı sıklık — varsayılan 5 sn) açık pozisyonları yönetir: kapat / kısmi kapat / SL taşı. Trade Ajanı pozisyon açıkken de tur atmaya devam eder ve yeni girişleri değerlendirir.'
10961
10965
  : 'Trade Ajanı girişleri yönetir: yön, emir tipi, risk, lot, martingale ve günlük limitler.';