beast-agent 2.60.0 → 2.62.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 +1 -1
- package/src/agent/engine.js +9 -1
- package/src/agent/financetools.js +9 -7
- package/src/agent/finrisk.js +28 -5
- package/src/main.js +128 -6
- package/src/renderer/index.html +6 -2
- package/src/renderer/renderer.js +16 -0
- package/src/renderer/style.css +2 -0
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -2057,7 +2057,15 @@ class Engine {
|
|
|
2057
2057
|
'VERİ AKIŞI (her değerlendirmede): mt5_account + mt5_positions + mt5_market çağrılarını AYNI turda PARALEL ver; gerekiyorsa mt5_history ile son işlemleri gör.\n' +
|
|
2058
2058
|
'PARALEL + KOORDİNASYON: uzun araştırma/işleri run_background ile paralel finance işçisine devret (parent finance olduğu için işçi mt5 okuma araçlarını görür); koşan ajanlarla konuşmak için agent_dm (to: ajan başlığındaki anahtar kelime, örn "GOLD"; ortak karar için group: "İSİM" ile grup sohbeti kur — mesaj tüm üyelere düşer). Görevin bitince DM/grup sohbetleri otomatik KAPANIR (geçmiş panelde kalır).\n' +
|
|
2059
2059
|
(symbols ? `İZLEME LİSTESİ: ${symbols}\n` : '') +
|
|
2060
|
-
(lim.
|
|
2060
|
+
(lim.minLot || lim.maxLot
|
|
2061
|
+
? 'LİMİTLER: ' +
|
|
2062
|
+
[
|
|
2063
|
+
lim.minLot ? `min lot ${lim.minLot}` : null,
|
|
2064
|
+
lim.maxLot ? `max lot ${lim.maxLot}` : null,
|
|
2065
|
+
lim.maxPositions ? `max eşzamanlı pozisyon ${lim.maxPositions}` : null,
|
|
2066
|
+
].filter(Boolean).join(' · ') +
|
|
2067
|
+
' — bunları aşıp araç kullanma; sistem zaten reddeder (lot min lot altına inemez, max lot üstüne çıkamaz).\n'
|
|
2068
|
+
: '') +
|
|
2061
2069
|
'RİSK DİSİPLİNİ:\n' +
|
|
2062
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' +
|
|
2063
2071
|
'- Martingale/kademeli lot artışı YASAK; kaybı geri kovalama (revenge trade) YASAK.\n' +
|
|
@@ -31,7 +31,7 @@ const NAMES = [
|
|
|
31
31
|
'mt5_cancel',
|
|
32
32
|
];
|
|
33
33
|
|
|
34
|
-
let getCfg = () => ({ allowTrading: false, maxLot: 0.1, maxPositions: 3, symbols: [] });
|
|
34
|
+
let getCfg = () => ({ allowTrading: false, minLot: 0.01, maxLot: 0.1, maxPositions: 3, symbols: [] });
|
|
35
35
|
let notify = () => {};
|
|
36
36
|
/* DİSİPLİN KANCASI (main enjekte eder): kodla zorlanan kurallar — günlük işlem
|
|
37
37
|
limiti, kayıp serisi molası, re-entry beklemesi, kur maruziyeti.
|
|
@@ -273,7 +273,7 @@ const definitions = NAMES.map((name) => {
|
|
|
273
273
|
enum: ['market', 'buy_market', 'sell_market', 'buy_limit', 'sell_limit', 'buy_stop', 'sell_stop'],
|
|
274
274
|
description: 'Emir tipi. Boş/side verilirse piyasa (anlık); limit/stop verilirse bekleyen emir (price zorunlu olur).',
|
|
275
275
|
},
|
|
276
|
-
volume: { type: 'number', description: 'Lot (max lot sınırına tabi) — boşsa riskPct + sl ile OTOMATİK hesaplanır' },
|
|
276
|
+
volume: { type: 'number', description: 'Lot (min/max lot sınırına tabi) — boşsa riskPct + sl ile OTOMATİK hesaplanır' },
|
|
277
277
|
riskPct: { type: 'number', description: 'İşlem riski % (ör. 0.5-2) — volume yerine ver: SL mesafesinden lot hesaplanır (sl zorunlu)' },
|
|
278
278
|
price: { type: 'number', description: 'Limit/stop emirlerinde tetik fiyatı (piyasa emrinde gerekmez)' },
|
|
279
279
|
sl: { type: 'number', description: 'Stop loss fiyatı (0 = yok) — şiddetle önerilir' },
|
|
@@ -565,7 +565,7 @@ const handlers = {
|
|
|
565
565
|
const balance = Number(acct && acct.account && acct.account.balance) || 0;
|
|
566
566
|
if (!(balance > 0)) return { ok: false, error: 'bakiye okunamadı' };
|
|
567
567
|
const riskAmount = (balance * riskPct) / 100;
|
|
568
|
-
const res = finrisk.calcRiskLot(info, entry, sl, riskAmount, cfg.maxLot);
|
|
568
|
+
const res = finrisk.calcRiskLot(info, entry, sl, riskAmount, cfg.maxLot, cfg.minLot);
|
|
569
569
|
if (res.error) return { ok: false, error: res.error, raw: res.raw, lossPerLot: res.lossPerLot, riskAmount: Math.round(riskAmount * 100) / 100 };
|
|
570
570
|
const digits = Number(info.digits) || 5;
|
|
571
571
|
const dist = Math.abs(entry - sl);
|
|
@@ -584,7 +584,9 @@ const handlers = {
|
|
|
584
584
|
atr: atr != null ? Math.round(atr * 1e6) / 1e6 : null,
|
|
585
585
|
timeframe: timeframe || null,
|
|
586
586
|
capped: !!res.capped,
|
|
587
|
+
raised: !!res.raised,
|
|
587
588
|
warning: closeErr || undefined,
|
|
589
|
+
minLot: Number(cfg.minLot) || 0.01,
|
|
588
590
|
maxLot: Number(cfg.maxLot) || 0.1,
|
|
589
591
|
};
|
|
590
592
|
},
|
|
@@ -623,12 +625,12 @@ const handlers = {
|
|
|
623
625
|
const acct = await bcall('account', {}, 8000);
|
|
624
626
|
const balance = Number(acct && acct.account && acct.account.balance) || 0;
|
|
625
627
|
const riskAmount = (balance * riskPct) / 100;
|
|
626
|
-
const rr = finrisk.calcRiskLot(info, price, sl, riskAmount, maxLot);
|
|
628
|
+
const rr = finrisk.calcRiskLot(info, price, sl, riskAmount, maxLot, cfg.minLot);
|
|
627
629
|
if (rr.error) return { ok: false, error: rr.error };
|
|
628
630
|
vol = rr.volume;
|
|
629
|
-
riskInfo = { riskPct, riskAmount: Math.round(riskAmount * 100) / 100, lossPerLot: rr.lossPerLot };
|
|
631
|
+
riskInfo = { riskPct, riskAmount: Math.round(riskAmount * 100) / 100, lossPerLot: rr.lossPerLot, raised: !!rr.raised };
|
|
630
632
|
} else {
|
|
631
|
-
const norm = finrisk.normalizeVolume(info, wantVolume, maxLot);
|
|
633
|
+
const norm = finrisk.normalizeVolume(info, wantVolume, maxLot, cfg.minLot);
|
|
632
634
|
if (norm.error) return { ok: false, error: norm.error };
|
|
633
635
|
vol = norm.volume;
|
|
634
636
|
}
|
|
@@ -745,7 +747,7 @@ const handlers = {
|
|
|
745
747
|
if (!symbol) return { ok: false, error: 'symbol gerekli' };
|
|
746
748
|
const info = await symInfoRow(symbol);
|
|
747
749
|
if (!info) return { ok: false, error: 'sembol bulunamadı: ' + symbol + ' (MT5 Market Watch?)' };
|
|
748
|
-
const norm = finrisk.normalizeVolume(info, args.volume, Number(cfg.maxLot) || 0.1);
|
|
750
|
+
const norm = finrisk.normalizeVolume(info, args.volume, Number(cfg.maxLot) || 0.1, cfg.minLot);
|
|
749
751
|
if (norm.error) return { ok: false, error: norm.error };
|
|
750
752
|
const vol = norm.volume;
|
|
751
753
|
const ptype = t.type;
|
package/src/agent/finrisk.js
CHANGED
|
@@ -16,24 +16,46 @@ function stepFloor(v, step) {
|
|
|
16
16
|
return Math.floor(v / s + 1e-9) * s;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
function stepCeil(v, step) {
|
|
20
|
+
const s = num(step, 0.01);
|
|
21
|
+
if (!(s > 0)) return v;
|
|
22
|
+
return Math.ceil(v / s - 1e-9) * s;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* info: MT5 symbol_info satırı. wanted: istenen lot. hardMax: kullanıcı üst
|
|
26
|
+
limiti (max lot). hardMin: kullanıcı alt limiti (min lot) — istenen lot bu
|
|
27
|
+
tabanın altındaysa adıma yukarı yuvarlanır (raised). */
|
|
28
|
+
function normalizeVolume(info, wanted, hardMax, hardMin) {
|
|
21
29
|
const vmin = num(info && info.volume_min, 0.01);
|
|
22
30
|
const vmax = num(info && info.volume_max, 100);
|
|
23
31
|
const step = num(info && info.volume_step, 0.01);
|
|
24
32
|
let v = num(wanted, 0);
|
|
25
33
|
if (!(v > 0)) return { error: 'volume gerekli (pozitif sayı)' };
|
|
26
34
|
const cap = Math.min(vmax > 0 ? vmax : Infinity, num(hardMax, 0) > 0 ? num(hardMax, 0) : Infinity);
|
|
35
|
+
const floor = num(hardMin, 0) > 0 ? num(hardMin, 0) : 0;
|
|
27
36
|
let capped = false;
|
|
37
|
+
let raised = false;
|
|
28
38
|
if (v > cap) {
|
|
29
39
|
v = stepFloor(cap, step);
|
|
30
40
|
capped = true;
|
|
31
41
|
}
|
|
42
|
+
if (v < floor - 1e-9) {
|
|
43
|
+
v = stepCeil(floor, step);
|
|
44
|
+
raised = true;
|
|
45
|
+
}
|
|
32
46
|
v = Math.round(stepFloor(v, step) * 1e8) / 1e8;
|
|
33
47
|
if (!(v > 0) || v < vmin - 1e-9) {
|
|
34
48
|
return { error: `hacim broker minimumunun altında (min ${vmin} lot)` };
|
|
35
49
|
}
|
|
36
|
-
|
|
50
|
+
if (v > cap + 1e-9) {
|
|
51
|
+
/* min lot tabanı broker adımıyla üst limite sığmadı — üst limitte kal */
|
|
52
|
+
v = Math.round(stepFloor(cap, step) * 1e8) / 1e8;
|
|
53
|
+
capped = true;
|
|
54
|
+
if (!(v > 0) || v < vmin - 1e-9) {
|
|
55
|
+
return { error: `hacim broker minimumunun altında (min ${vmin} lot)` };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { volume: v, capped, raised };
|
|
37
59
|
}
|
|
38
60
|
|
|
39
61
|
/* 1 lot için SL mesafesi kaybı (hesap para birimi): tick değeri, yoksa kontrat */
|
|
@@ -49,11 +71,11 @@ function lossPerLot(info, entry, sl) {
|
|
|
49
71
|
}
|
|
50
72
|
|
|
51
73
|
/* riskAmount: hesap para birimi cinsinden kaybedilecek tutar */
|
|
52
|
-
function calcRiskLot(info, entry, sl, riskAmount, hardMax) {
|
|
74
|
+
function calcRiskLot(info, entry, sl, riskAmount, hardMax, hardMin) {
|
|
53
75
|
const lpl = lossPerLot(info, entry, sl);
|
|
54
76
|
if (!(lpl > 0)) return { error: 'risk hesaplanamadı (tick değeri/kontrat bilgisi yok)' };
|
|
55
77
|
const raw = num(riskAmount, 0) / lpl;
|
|
56
|
-
const norm = normalizeVolume(info, raw, hardMax);
|
|
78
|
+
const norm = normalizeVolume(info, raw, hardMax, hardMin);
|
|
57
79
|
if (norm.error) return { error: norm.error, raw, lossPerLot: lpl };
|
|
58
80
|
return {
|
|
59
81
|
volume: norm.volume,
|
|
@@ -61,6 +83,7 @@ function calcRiskLot(info, entry, sl, riskAmount, hardMax) {
|
|
|
61
83
|
lossPerLot: Math.round(lpl * 100) / 100,
|
|
62
84
|
riskAmount: Math.round(raw * lpl * 100) / 100,
|
|
63
85
|
capped: !!norm.capped,
|
|
86
|
+
raised: !!norm.raised,
|
|
64
87
|
};
|
|
65
88
|
}
|
|
66
89
|
|
package/src/main.js
CHANGED
|
@@ -9387,7 +9387,10 @@ function finCfg() {
|
|
|
9387
9387
|
f.roleSkills.trader = cur.map((s) => String(s || '').trim()).filter(Boolean).slice(0, 1);
|
|
9388
9388
|
}
|
|
9389
9389
|
if (!Number(f.intervalSec)) f.intervalSec = 120;
|
|
9390
|
+
if (!Number(f.minLot)) f.minLot = 0.01;
|
|
9390
9391
|
if (!Number(f.maxLot)) f.maxLot = 0.1;
|
|
9392
|
+
/* LOT ARALIĞI KILİDİ: min lot hiçbir zaman max lotu aşamaz */
|
|
9393
|
+
if (f.minLot > f.maxLot) f.minLot = f.maxLot;
|
|
9391
9394
|
if (f.maxPositions == null) f.maxPositions = 3;
|
|
9392
9395
|
/* KODLA DİSİPLİN (trader kuralları): hepsi 0 = kural kapalı */
|
|
9393
9396
|
if (!Number.isFinite(Number(f.maxTradesPerDay))) f.maxTradesPerDay = 10;
|
|
@@ -11403,7 +11406,7 @@ function finApplyTraderFields(s, symbolsOverride, role, isMain) {
|
|
|
11403
11406
|
s.financeSymbols = Array.isArray(symbolsOverride) && symbolsOverride.length ? symbolsOverride : f.symbols;
|
|
11404
11407
|
s.financeStrategy = String(f.strategy || '');
|
|
11405
11408
|
s.financeShadow = !!f.shadowMode; /* shadow modda işlem araçları emir göndermez */
|
|
11406
|
-
s.financeLimits = { maxLot: f.maxLot, maxPositions: f.maxPositions };
|
|
11409
|
+
s.financeLimits = { minLot: f.minLot, maxLot: f.maxLot, maxPositions: f.maxPositions };
|
|
11407
11410
|
/* ZORUNLU TEK skill: rol ajanı → roleSkills[rol], ana trader → roleSkills.trader */
|
|
11408
11411
|
const skillKey = r || (s.financePlaybook ? 'trader' : '');
|
|
11409
11412
|
s.financeRoleSkills = (skillKey && f.roleSkills && f.roleSkills[skillKey]) || [];
|
|
@@ -11446,6 +11449,9 @@ function finAgentStop(sid, reason) {
|
|
|
11446
11449
|
const agent = financeState.agents.get(sid);
|
|
11447
11450
|
if (!agent) return false;
|
|
11448
11451
|
clearTimeout(agent.timer);
|
|
11452
|
+
if (agent.waitPoll) { clearTimeout(agent.waitPoll); agent.waitPoll = null; }
|
|
11453
|
+
agent.waitingTeam = false;
|
|
11454
|
+
agent.consultPromise = null;
|
|
11449
11455
|
financeState.agents.delete(sid);
|
|
11450
11456
|
if (sid === String(financeState.traderSid || '')) financeState.traderSid = null;
|
|
11451
11457
|
try {
|
|
@@ -11465,6 +11471,22 @@ function finAgentStop(sid, reason) {
|
|
|
11465
11471
|
/* AJAN DM: duran ajanın birebir sohbetleri kapanır; grupta KOŞAN üye
|
|
11466
11472
|
kalmadıysa ekip sohbeti de GEÇMİŞE düşer (aktif listede kalmaz) */
|
|
11467
11473
|
try { if (engine && engine._agentDmClose) engine._agentDmClose(sid); } catch {}
|
|
11474
|
+
/* EKİP AJANI DURDU: bekleyen trader varsa hemen değerlendir (deadlock yok) */
|
|
11475
|
+
if (agent.role) {
|
|
11476
|
+
for (const [msid, ma] of financeState.agents) {
|
|
11477
|
+
if (ma && ma.main && ma.waitingTeam) { try { finTeamWaitTick(msid); } catch {} break; }
|
|
11478
|
+
}
|
|
11479
|
+
}
|
|
11480
|
+
/* ANA TRADER DURDU: ekip cycle'sız kaldı — kendi timer'ıyla dönmeye devam etsin */
|
|
11481
|
+
if (agent.main) {
|
|
11482
|
+
const iv = Math.max(30, Number(finCfg().intervalSec) || 120) * 1000;
|
|
11483
|
+
for (const [tsid, ta] of financeState.agents) {
|
|
11484
|
+
if (!ta || !ta.role) continue;
|
|
11485
|
+
if (engine && engine.isBusy(tsid)) continue;
|
|
11486
|
+
clearTimeout(ta.timer);
|
|
11487
|
+
ta.timer = setTimeout(() => { try { finAgentRound(String(tsid)); } catch {} }, iv);
|
|
11488
|
+
}
|
|
11489
|
+
}
|
|
11468
11490
|
finPush('trader', { state: 'idle', rounds: financeState.traderRounds });
|
|
11469
11491
|
return true;
|
|
11470
11492
|
}
|
|
@@ -11480,7 +11502,7 @@ function finTraderBrief(agent) {
|
|
|
11480
11502
|
return [
|
|
11481
11503
|
`Beast Finance ${who} başlatıldı — ilk tur: strateji çerçeveni kur ve piyasa taramasını yap.`,
|
|
11482
11504
|
`Odak semboller: ${syms || '(boş — mt5_status ile terminale bak, mantıklı semboller seç)'}`,
|
|
11483
|
-
`Tur aralığı: ${f.intervalSec} sn ·
|
|
11505
|
+
`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}`,
|
|
11484
11506
|
roleDef
|
|
11485
11507
|
? `UZMANLIK: ${roleDef.desc} — raporlarını bu çerçevede yaz; İŞLEM AÇMA, yalnız analiz + net öneri üret.`
|
|
11486
11508
|
: '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ı.',
|
|
@@ -11577,7 +11599,7 @@ async function finConsultPlan(f, agent, sid) {
|
|
|
11577
11599
|
'Güncel piyasa bağlamı gerekiyorsa web_search kullan; kurulu bir skill konuyla ilgiliyse skill aracıyla oku.';
|
|
11578
11600
|
const ctx = [
|
|
11579
11601
|
`Odak semboller: ${agent && agent.symbols && agent.symbols.length ? agent.symbols.join(', ') : (f.symbols || []).join(', ') || '(boş — ajan kendi bulabilir)'}`,
|
|
11580
|
-
`Otomatik işlem: ${auto} ·
|
|
11602
|
+
`Otomatik işlem: ${auto} · lot aralığı ${f.minLot}–${f.maxLot} · max eşzamanlı pozisyon ${f.maxPositions}`,
|
|
11581
11603
|
account
|
|
11582
11604
|
? `Hesap: bakiye ${account.balance} ${account.currency} · özkaynak ${account.equity ?? '?'} · serbest marj ${account.margin_free ?? '?'} · kaldıraç 1:${account.leverage ?? '?'}`
|
|
11583
11605
|
: 'Hesap verisi alınamadı.',
|
|
@@ -11604,8 +11626,57 @@ async function finConsultPlan(f, agent, sid) {
|
|
|
11604
11626
|
}
|
|
11605
11627
|
}
|
|
11606
11628
|
|
|
11629
|
+
/* ---------- CYCLE ORKESTRASYONU: trader turu ekip raporlarını BEKLER ----------
|
|
11630
|
+
Ana trader kendi turuna başlamadan önce koşan analiz ekibi ajanlarını
|
|
11631
|
+
(teknik/risk/macro/visual) tura sokar ve O ANKİ turları bitene kadar bekler —
|
|
11632
|
+
böylece finTeamDigest her zaman TAZE rapor taşır. Sembol işçileri beklenmez.
|
|
11633
|
+
Ekip ajanları ana trader koşarken kendi başına tur planlamaz
|
|
11634
|
+
(finFlushOnDone); sıradaki turlarını cycle başlatır. */
|
|
11635
|
+
function finTeamAgents() {
|
|
11636
|
+
const out = [];
|
|
11637
|
+
try {
|
|
11638
|
+
for (const [sid, a] of financeState.agents) {
|
|
11639
|
+
if (a && a.role) out.push({ sid: String(sid), agent: a, def: finRoleDef(a.role) });
|
|
11640
|
+
}
|
|
11641
|
+
} catch {}
|
|
11642
|
+
return out;
|
|
11643
|
+
}
|
|
11644
|
+
|
|
11645
|
+
function finTeamHasMain() {
|
|
11646
|
+
try {
|
|
11647
|
+
for (const [, a] of financeState.agents) if (a && a.main) return true;
|
|
11648
|
+
} catch {}
|
|
11649
|
+
return false;
|
|
11650
|
+
}
|
|
11651
|
+
|
|
11652
|
+
/* Takılma valfi: ekip turu bu süreyi aşarsa trader eski raporla başlar */
|
|
11653
|
+
function finTeamWaitMaxMs() {
|
|
11654
|
+
const iSec = Math.max(30, Number(finCfg().intervalSec) || 120);
|
|
11655
|
+
return Math.max(60, Math.min(300, iSec)) * 1000;
|
|
11656
|
+
}
|
|
11657
|
+
|
|
11658
|
+
/* Ekip ajanı bittiğinde/durduğunda ya da 2 sn'lik yoklamada çağrılır:
|
|
11659
|
+
bekleyen trader'ın turunu açar (rapor taze) ya da beklemeye devam eder. */
|
|
11660
|
+
function finTeamWaitTick(sid) {
|
|
11661
|
+
const agent = financeState.agents.get(String(sid));
|
|
11662
|
+
if (!agent || !agent.main || !agent.waitingTeam) return;
|
|
11663
|
+
if (agent.waitPoll) { clearTimeout(agent.waitPoll); agent.waitPoll = null; }
|
|
11664
|
+
const busy = finTeamAgents().filter((t) => engine && engine.isBusy(t.sid));
|
|
11665
|
+
const expired = Date.now() >= Number(agent.waitDeadline || 0);
|
|
11666
|
+
if (busy.length && !expired) {
|
|
11667
|
+
agent.waitPoll = setTimeout(() => { try { finTeamWaitTick(sid); } catch {} }, 2000);
|
|
11668
|
+
return;
|
|
11669
|
+
}
|
|
11670
|
+
agent.waitingTeam = false;
|
|
11671
|
+
/* MAKS BEKLEME AŞILDI: geç kalanlar rapor prefix'ine açıkça notlanır */
|
|
11672
|
+
agent.waitLateNames = expired && busy.length
|
|
11673
|
+
? busy.map((t) => (t.def ? t.def.label : t.agent.role || t.sid))
|
|
11674
|
+
: [];
|
|
11675
|
+
finAgentRound(sid, { noTeamWait: true });
|
|
11676
|
+
}
|
|
11677
|
+
|
|
11607
11678
|
/* Bir ajanın TEK turu: (opsiyonel) ana ajan planı + tur emri */
|
|
11608
|
-
function finAgentRound(sid) {
|
|
11679
|
+
function finAgentRound(sid, opts) {
|
|
11609
11680
|
const agent = financeState.agents.get(String(sid));
|
|
11610
11681
|
if (!agent || !engine) return;
|
|
11611
11682
|
/* TRADE SAATLERİ KAPISI: aralık dışında YENİ TUR BAŞLATILMAZ — ajan askıya
|
|
@@ -11621,9 +11692,36 @@ function finAgentRound(sid) {
|
|
|
11621
11692
|
/* hâlâ çalışıyor — done eventinde tekrar planlanır */
|
|
11622
11693
|
return;
|
|
11623
11694
|
}
|
|
11695
|
+
const f = finCfg();
|
|
11696
|
+
/* EKİP BEKLEMESİ SÜRÜYOR: DM uyandırması vb. turu erkene çekemez — tick açar */
|
|
11697
|
+
if (agent.main && agent.waitingTeam && !(opts && opts.noTeamWait)) return;
|
|
11698
|
+
/* CYCLE KAPISI: karar turundan ÖNCE ekip ajanlarını tura sok, O ANKİ
|
|
11699
|
+
turları bitmeden trader'ı başlatma (rapor tazeliği garantisi). */
|
|
11700
|
+
if (agent.main && !(opts && opts.noTeamWait)) {
|
|
11701
|
+
const team = finTeamAgents();
|
|
11702
|
+
if (team.length) {
|
|
11703
|
+
for (const t of team) {
|
|
11704
|
+
if (engine.isBusy(t.sid)) continue;
|
|
11705
|
+
clearTimeout(t.agent.timer);
|
|
11706
|
+
t.agent.timer = null;
|
|
11707
|
+
try { finAgentRound(t.sid); } catch {}
|
|
11708
|
+
}
|
|
11709
|
+
const busy = team.filter((t) => engine.isBusy(t.sid));
|
|
11710
|
+
if (busy.length) {
|
|
11711
|
+
agent.waitingTeam = true;
|
|
11712
|
+
agent.waitDeadline = Date.now() + finTeamWaitMaxMs();
|
|
11713
|
+
/* danışma planı bekleme SIRASINDA üretilir — tur gecikmesi gizlenir */
|
|
11714
|
+
if (!agent.consultPromise && f.consultChat !== false) {
|
|
11715
|
+
agent.consultPromise = finConsultPlan(f, agent, String(sid)).catch(() => '');
|
|
11716
|
+
}
|
|
11717
|
+
finPush('trader', { state: 'team', round: (Number(agent.round) || 0) + 1, waiting: busy.length, maxSec: Math.round(finTeamWaitMaxMs() / 1000) });
|
|
11718
|
+
agent.waitPoll = setTimeout(() => { try { finTeamWaitTick(sid); } catch {} }, 2000);
|
|
11719
|
+
return;
|
|
11720
|
+
}
|
|
11721
|
+
}
|
|
11722
|
+
}
|
|
11624
11723
|
agent.round += 1;
|
|
11625
11724
|
if (agent.main) financeState.traderRounds = agent.round;
|
|
11626
|
-
const f = finCfg();
|
|
11627
11725
|
const roleDef = finRoleDef(agent.role);
|
|
11628
11726
|
let shadow = false;
|
|
11629
11727
|
try {
|
|
@@ -11660,14 +11758,23 @@ function finAgentRound(sid) {
|
|
|
11660
11758
|
if (agent.main) {
|
|
11661
11759
|
const team = finTeamDigest();
|
|
11662
11760
|
if (team) teamPrefix = '[EKİP RAPORLARI — koşan uzman ajanların son raporları; kararında dikkate al]\n' + team + '\n\n';
|
|
11761
|
+
/* MAKS BEKLEME AŞILDI: geç kalan ajanlar açıkça işaretlenir */
|
|
11762
|
+
const late = Array.isArray(agent.waitLateNames) ? agent.waitLateNames.filter(Boolean) : [];
|
|
11763
|
+
if (late.length) {
|
|
11764
|
+
teamPrefix = `[EKİP NOTU] Şu ajanlar hâlâ analizde — raporları BİR ÖNCEKİ turdan: ${late.join(', ')}. Gecikmeyi kararında dikkate al.\n\n` + teamPrefix;
|
|
11765
|
+
agent.waitLateNames = [];
|
|
11766
|
+
}
|
|
11663
11767
|
}
|
|
11664
11768
|
if (f.consultChat === false || roleDef) {
|
|
11665
11769
|
/* danışma kapalı ya da rol ajanı (plan trader'a yöneliktir): tur doğrudan başlar */
|
|
11666
11770
|
launch(teamPrefix);
|
|
11667
11771
|
return;
|
|
11668
11772
|
}
|
|
11773
|
+
/* Danışma planı ekip beklemesi SIRASINDA üretildiyse hazır olanı kullan */
|
|
11774
|
+
const consult = agent.consultPromise || finConsultPlan(f, agent, String(sid));
|
|
11775
|
+
agent.consultPromise = null;
|
|
11669
11776
|
if (agent.main) finPush('trader', { state: 'consult', round: agent.round });
|
|
11670
|
-
|
|
11777
|
+
consult
|
|
11671
11778
|
.then((plan) => launch(teamPrefix + (plan ? `[ANA AJAN PLANI — bu turun varsayılan stratejisi; strateji notuyla çelişirse not önceliklidir]\n${plan}\n\n` : '')))
|
|
11672
11779
|
.catch(() => launch(teamPrefix));
|
|
11673
11780
|
}
|
|
@@ -11706,6 +11813,12 @@ function finFlushOnDone(ev) {
|
|
|
11706
11813
|
finAgentStop(sid, String(ev.reason || 'kullanıcı durdurdu'));
|
|
11707
11814
|
return;
|
|
11708
11815
|
}
|
|
11816
|
+
/* EKİP TURU BİTTİ: bekleyen trader varsa ANINDA değerlendir (rapor taze) */
|
|
11817
|
+
if (agent.role) {
|
|
11818
|
+
for (const [msid, ma] of financeState.agents) {
|
|
11819
|
+
if (ma && ma.main && ma.waitingTeam) { try { finTeamWaitTick(msid); } catch {} break; }
|
|
11820
|
+
}
|
|
11821
|
+
}
|
|
11709
11822
|
clearTimeout(agent.timer);
|
|
11710
11823
|
/* TUR SIRASINDA GELEN OLAY/DM: tur bitti → interval BEKLENMEDEN yeni tur
|
|
11711
11824
|
(dmInbox'taki olay mesajı bu turda okunur) */
|
|
@@ -11715,6 +11828,10 @@ function finFlushOnDone(ev) {
|
|
|
11715
11828
|
agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, 800);
|
|
11716
11829
|
return;
|
|
11717
11830
|
}
|
|
11831
|
+
/* CYCLE MODU: ana trader koşarken ekip ajanı KENDİ turunu planlamaz —
|
|
11832
|
+
sıradaki turu trader'ın cycle'ı başlatır (rapor tazeliği). Trader yoksa
|
|
11833
|
+
ekip kendi timer'ıyla dönmeye devam eder. */
|
|
11834
|
+
if (agent.role && finTeamHasMain()) return;
|
|
11718
11835
|
const f = finCfg();
|
|
11719
11836
|
const iv = Math.max(30, Number(f.intervalSec) || 120) * 1000;
|
|
11720
11837
|
financeState.lastRoundAt = Date.now();
|
|
@@ -11884,13 +12001,18 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
|
|
|
11884
12001
|
koşan tur bitince zaten finFlushOnDone yeni aralığı okur. */
|
|
11885
12002
|
if (p.intervalSec !== undefined && financeState.agents) {
|
|
11886
12003
|
const iv = Math.max(30, Math.round(Number(f.intervalSec) || 120)) * 1000;
|
|
12004
|
+
const hasMain = finTeamHasMain();
|
|
11887
12005
|
for (const [sid, a] of financeState.agents) {
|
|
11888
12006
|
if (!a || !engine || engine.isBusy(sid)) continue;
|
|
12007
|
+
if (a.role && hasMain) continue; /* cycle modu: ekip turunu trader başlatır */
|
|
11889
12008
|
clearTimeout(a.timer);
|
|
11890
12009
|
a.timer = setTimeout(() => { try { finAgentRound(String(sid)); } catch {} }, iv);
|
|
11891
12010
|
}
|
|
11892
12011
|
}
|
|
11893
12012
|
if (p.maxLot !== undefined) f.maxLot = Math.max(0.01, Math.min(100, Number(p.maxLot) || 0.1));
|
|
12013
|
+
if (p.minLot !== undefined) f.minLot = Math.max(0.01, Math.min(100, Number(p.minLot) || 0.01));
|
|
12014
|
+
/* min lot max lotu aşamaz — hangisi sonra yazıldıysa diğerine kelepçelenir */
|
|
12015
|
+
if (f.minLot > f.maxLot) f.minLot = f.maxLot;
|
|
11894
12016
|
if (p.maxPositions !== undefined) f.maxPositions = Math.max(1, Math.min(20, Math.round(Number(p.maxPositions) || 3)));
|
|
11895
12017
|
/* RİSK OTOMASYONU + BİLDİRİM ayarları */
|
|
11896
12018
|
if (p.watchdog !== undefined) f.watchdog = !!p.watchdog;
|
package/src/renderer/index.html
CHANGED
|
@@ -169,14 +169,18 @@
|
|
|
169
169
|
<span>Model</span>
|
|
170
170
|
<select id="finTraderModel" title="Trader ajanının modeli — farklı API/model seçilebilir"></select>
|
|
171
171
|
</div>
|
|
172
|
-
<div class="fin-
|
|
172
|
+
<div class="fin-grid3" title="Değişiklik anında kaydedilir — ajanı DURDURMADAN sonraki tura uygulanır">
|
|
173
173
|
<div class="fin-field">
|
|
174
174
|
<span>Tur (sn)</span>
|
|
175
175
|
<input id="finInterval" type="number" min="30" max="3600" step="10" />
|
|
176
176
|
</div>
|
|
177
|
+
<div class="fin-field">
|
|
178
|
+
<span>Min lot</span>
|
|
179
|
+
<input id="finMinLot" type="number" min="0.01" max="100" step="0.01" title="Alt sınır: lot hesapları bu tabanın altına inemez — taban altındaki lotlar adıma yukarı yuvarlanır (broker minimumunun üzerinde bir taban koyar)" />
|
|
180
|
+
</div>
|
|
177
181
|
<div class="fin-field">
|
|
178
182
|
<span>Max lot</span>
|
|
179
|
-
<input id="finMaxLot" type="number" min="0.01" max="100" step="0.01" />
|
|
183
|
+
<input id="finMaxLot" type="number" min="0.01" max="100" step="0.01" title="Üst sınır: lot hesapları bu tavanın üzerine çıkamaz (min lotun altına inemez)" />
|
|
180
184
|
</div>
|
|
181
185
|
</div>
|
|
182
186
|
<div class="fin-field">
|
package/src/renderer/renderer.js
CHANGED
|
@@ -223,6 +223,7 @@ const els = {
|
|
|
223
223
|
finTraderModel: $('#finTraderModel'),
|
|
224
224
|
finModelRefreshBtn: $('#finModelRefreshBtn'),
|
|
225
225
|
finInterval: $('#finInterval'),
|
|
226
|
+
finMinLot: $('#finMinLot'),
|
|
226
227
|
finMaxLot: $('#finMaxLot'),
|
|
227
228
|
finStrategy: $('#finStrategy'),
|
|
228
229
|
finMaxTradesDay: $('#finMaxTradesDay'),
|
|
@@ -9803,6 +9804,7 @@ function finTraderInputsSet(cfg) {
|
|
|
9803
9804
|
: [];
|
|
9804
9805
|
const ae = document.activeElement;
|
|
9805
9806
|
if (els.finInterval && ae !== els.finInterval) els.finInterval.value = cfg.intervalSec || 120;
|
|
9807
|
+
if (els.finMinLot && ae !== els.finMinLot) els.finMinLot.value = cfg.minLot || 0.01;
|
|
9806
9808
|
if (els.finMaxLot && ae !== els.finMaxLot) els.finMaxLot.value = cfg.maxLot || 0.1;
|
|
9807
9809
|
if (els.finStrategy && ae !== els.finStrategy && !finStrategyDirty) els.finStrategy.value = cfg.strategy || '';
|
|
9808
9810
|
if (els.finMaxTradesDay && ae !== els.finMaxTradesDay) els.finMaxTradesDay.value = Number(cfg.maxTradesPerDay) || 0;
|
|
@@ -10162,6 +10164,7 @@ function finOnEvent(ev) {
|
|
|
10162
10164
|
finSetDot(ev.connected ? true : 'busy', { error: ev.error });
|
|
10163
10165
|
} else if (ev.fn === 'trader') {
|
|
10164
10166
|
if (ev.state === 'running') finLogLine('[trader] tur #' + (ev.round || 0) + ' başladı');
|
|
10167
|
+
else if (ev.state === 'team') finLogLine('[trader] ekip raporları bekleniyor — ' + (ev.waiting || 0) + ' ajan turunu bitirsin (max ' + (ev.maxSec || '?') + ' sn)');
|
|
10165
10168
|
else if (ev.state === 'idle') finLogLine('[trader] tur #' + (ev.round || 0) + ' bitti — sıradaki tur ~' + (ev.nextInSec || '?') + ' sn');
|
|
10166
10169
|
else if (ev.state === 'hours') finLogLine('[trader] trade saatleri dışı — ajan askıda (kontrol ~' + (ev.nextInSec || 60) + ' sn)');
|
|
10167
10170
|
else if (ev.state === 'stopped') finLogLine('[trader] durduruldu');
|
|
@@ -10528,9 +10531,22 @@ if (els.finInterval) els.finInterval.addEventListener('change', () => {
|
|
|
10528
10531
|
if (els.finMaxLot) els.finMaxLot.addEventListener('change', () => {
|
|
10529
10532
|
const v = Math.max(0.01, Math.min(100, Number(els.finMaxLot.value) || 0.1));
|
|
10530
10533
|
els.finMaxLot.value = v;
|
|
10534
|
+
/* min lot max lotu aşamaz — görünürde de kelepçele (sunucu ayrıca zorlar) */
|
|
10535
|
+
if (els.finMinLot && Number(els.finMinLot.value) > v) els.finMinLot.value = v;
|
|
10531
10536
|
finSaveCfg({ maxLot: v });
|
|
10532
10537
|
toast('Max lot: ' + v + ' — sonraki turdan itibaren geçerli');
|
|
10533
10538
|
});
|
|
10539
|
+
if (els.finMinLot) els.finMinLot.addEventListener('change', () => {
|
|
10540
|
+
const maxV = Math.max(0.01, Math.min(100, Number(els.finMaxLot && els.finMaxLot.value) || 0.1));
|
|
10541
|
+
let v = Math.max(0.01, Math.min(100, Number(els.finMinLot.value) || 0.01));
|
|
10542
|
+
if (v > maxV) {
|
|
10543
|
+
v = maxV;
|
|
10544
|
+
toast('Min lot max lotu aşamaz — ' + v + ' olarak ayarlandı');
|
|
10545
|
+
}
|
|
10546
|
+
els.finMinLot.value = v;
|
|
10547
|
+
finSaveCfg({ minLot: v });
|
|
10548
|
+
toast('Min lot: ' + v + ' — lot hesapları bu tabanın altına inmez');
|
|
10549
|
+
});
|
|
10534
10550
|
if (els.finSymBtn) els.finSymBtn.addEventListener('click', finSymPickerOpen);
|
|
10535
10551
|
|
|
10536
10552
|
/* RİSK OTOMASYONU kontrolleri: değişiklik anında kaydedilir (ajan turunu beklemez) */
|
package/src/renderer/style.css
CHANGED
|
@@ -5447,6 +5447,8 @@ body.finance-mode #composerWrap { margin-right: var(--finW, 400px); }
|
|
|
5447
5447
|
.fin-field .fin-sym-select { width: 100%; }
|
|
5448
5448
|
.fin-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
|
5449
5449
|
.fin-grid2 .fin-field { margin-bottom: 8px; }
|
|
5450
|
+
.fin-grid3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
|
|
5451
|
+
.fin-grid3 .fin-field { margin-bottom: 8px; }
|
|
5450
5452
|
.fin-hint { font-style: normal; opacity: 0.75; }
|
|
5451
5453
|
/* RİSK OTOMASYONU + PERFORMANS kartları */
|
|
5452
5454
|
.fin-check label { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text); cursor: pointer; }
|