beast-agent 2.61.0 → 2.62.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/beast-agent.js +17 -12
- package/package.json +1 -1
- package/scripts/fix-electron.js +31 -3
- 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 +159 -4
- package/src/renderer/index.html +6 -2
- package/src/renderer/renderer.js +17 -0
- package/src/renderer/style.css +2 -0
package/bin/beast-agent.js
CHANGED
|
@@ -107,23 +107,28 @@ if (process.argv[2] === 'searxng') {
|
|
|
107
107
|
|
|
108
108
|
/* detached launcher + self-healing electron:
|
|
109
109
|
on fresh machines the electron binary download during npm postinstall
|
|
110
|
-
may have silently failed → it is repaired automatically here.
|
|
110
|
+
may have silently failed OR been truncated → it is repaired automatically here.
|
|
111
|
+
Varlık kontrolü YETMEZ: kesik dosya "var" görünür ama spawn EFTYPE verir —
|
|
112
|
+
fix-electron.status binary'yi `--version` ile çalıştırarak doğrular. */
|
|
111
113
|
function launchDetached(extraArgs) {
|
|
112
114
|
let electron = null;
|
|
113
115
|
try { electron = require('electron'); } catch {}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
116
|
+
let fix = null;
|
|
117
|
+
try { fix = require('../scripts/fix-electron'); } catch {}
|
|
118
|
+
const usable = () => {
|
|
119
|
+
if (typeof electron !== 'string') return false;
|
|
120
|
+
if (fix && typeof fix.status === 'function') return !!fix.status().ok;
|
|
121
|
+
try { return fs.existsSync(electron); } catch { return false; }
|
|
122
|
+
};
|
|
123
|
+
if (!usable() && fix) {
|
|
124
|
+
console.log('\u27F3 electron runtime is missing or corrupted \u2014 repairing automatically\u2026');
|
|
125
|
+
const r = fix.repair({ quiet: false });
|
|
126
|
+
if (r.ok && !r.skipped) console.log('\u2713 electron repaired');
|
|
127
|
+
if (r.ok) {
|
|
128
|
+
try { electron = require('electron'); } catch {}
|
|
124
129
|
}
|
|
125
130
|
}
|
|
126
|
-
if (
|
|
131
|
+
if (!usable()) {
|
|
127
132
|
console.log('\n\u2717 Electron runtime could not be installed.');
|
|
128
133
|
console.log(' Manual fix \u2014 run these 2 commands:');
|
|
129
134
|
console.log(' npm config set ignore-scripts false');
|
package/package.json
CHANGED
package/scripts/fix-electron.js
CHANGED
|
@@ -23,15 +23,43 @@ function electronDir() {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/* binary ÇALIŞIYOR mu? — kesik/bozuk indirmede dosya VAR görünür ama
|
|
27
|
+
CreateProcess "bad EXE format" (EFTYPE) verir; varlık kontrolü yetmez.
|
|
28
|
+
`electron --version` ile gerçekten çalıştığını doğrularız (kısa timeout).
|
|
29
|
+
Sonuç (yol+boyut+mtime) önbelleklenir — aynı süreçte tekrar spawn edilmez. */
|
|
30
|
+
let verifyCache = null;
|
|
31
|
+
function exeWorks(exeAbs) {
|
|
32
|
+
let key = '';
|
|
33
|
+
try {
|
|
34
|
+
const st = fs.statSync(exeAbs);
|
|
35
|
+
key = exeAbs + '|' + st.size + '|' + st.mtimeMs;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (verifyCache && verifyCache.key === key) return verifyCache.ok;
|
|
40
|
+
let ok = false;
|
|
41
|
+
try {
|
|
42
|
+
const r = spawnSync(exeAbs, ['--version'], { timeout: 20000, windowsHide: true, stdio: 'ignore' });
|
|
43
|
+
ok = !r.error && r.status === 0;
|
|
44
|
+
} catch {
|
|
45
|
+
ok = false;
|
|
46
|
+
}
|
|
47
|
+
verifyCache = { key, ok };
|
|
48
|
+
return ok;
|
|
49
|
+
}
|
|
50
|
+
|
|
26
51
|
/* binary sağlam mı? electron/index.js'in beklentisiyle aynı kontrol:
|
|
27
|
-
path.txt + dist/<exe> varlığı */
|
|
52
|
+
path.txt + dist/<exe> varlığı — ARTIK + exe'nin gerçekten çalışması */
|
|
28
53
|
function status() {
|
|
29
54
|
const dir = electronDir();
|
|
30
55
|
if (!dir) return { ok: false, missing: true };
|
|
31
56
|
const exe = process.platform === 'win32' ? 'electron.exe' : 'electron';
|
|
32
|
-
const
|
|
57
|
+
const exeAbs = path.join(dir, 'dist', exe);
|
|
58
|
+
const distOk = fs.existsSync(exeAbs);
|
|
33
59
|
const txtOk = fs.existsSync(path.join(dir, 'path.txt'));
|
|
34
|
-
|
|
60
|
+
if (!(distOk && txtOk)) return { ok: false, dir };
|
|
61
|
+
if (!exeWorks(exeAbs)) return { ok: false, dir, broken: true };
|
|
62
|
+
return { ok: true, dir };
|
|
35
63
|
}
|
|
36
64
|
|
|
37
65
|
/* platforma göre dist içindeki çalıştırılabilirin göreli yolu (electron/index.js ile aynı) */
|
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
|
@@ -3901,6 +3901,7 @@ app.whenReady().then(() => {
|
|
|
3901
3901
|
app.on('before-quit', () => {
|
|
3902
3902
|
app.isQuitting = true;
|
|
3903
3903
|
try { finWatchStop(); } catch {} // finance watchdog zamanlayıcısını kapat
|
|
3904
|
+
try { finHoursStop(); } catch {} // trade saatleri denetim zamanlayıcısını kapat
|
|
3904
3905
|
flushBrowserStorage(); // x.com/google oturumları (cookies) diske yazılsın
|
|
3905
3906
|
try { toolsMod.disposeShellSessions(); } catch {} // kalıcı shell oturumlarını kapat
|
|
3906
3907
|
try { require('./agent/mcp').stopAll(); } catch {} // MCP server süreçlerini kapat
|
|
@@ -9333,6 +9334,8 @@ const financeState = {
|
|
|
9333
9334
|
watchTimer: null,
|
|
9334
9335
|
watchBusy: false,
|
|
9335
9336
|
watchTickAt: 0,
|
|
9337
|
+
hoursTimer: null, /* trade saatleri otomatik duraklatma denetimi (10 sn) */
|
|
9338
|
+
hoursPaused: null, /* pencere kapanınca durdurulan ajanların planı — açılınca geri gelir */
|
|
9336
9339
|
alerts: [], /* fiyat alarmları (kalıcı: finance/alerts.json) */
|
|
9337
9340
|
stats: null, /* son performans özeti (kalıcı: finance/stats.json) */
|
|
9338
9341
|
statsAt: 0,
|
|
@@ -9387,7 +9390,10 @@ function finCfg() {
|
|
|
9387
9390
|
f.roleSkills.trader = cur.map((s) => String(s || '').trim()).filter(Boolean).slice(0, 1);
|
|
9388
9391
|
}
|
|
9389
9392
|
if (!Number(f.intervalSec)) f.intervalSec = 120;
|
|
9393
|
+
if (!Number(f.minLot)) f.minLot = 0.01;
|
|
9390
9394
|
if (!Number(f.maxLot)) f.maxLot = 0.1;
|
|
9395
|
+
/* LOT ARALIĞI KILİDİ: min lot hiçbir zaman max lotu aşamaz */
|
|
9396
|
+
if (f.minLot > f.maxLot) f.minLot = f.maxLot;
|
|
9391
9397
|
if (f.maxPositions == null) f.maxPositions = 3;
|
|
9392
9398
|
/* KODLA DİSİPLİN (trader kuralları): hepsi 0 = kural kapalı */
|
|
9393
9399
|
if (!Number.isFinite(Number(f.maxTradesPerDay))) f.maxTradesPerDay = 10;
|
|
@@ -9473,6 +9479,130 @@ function finTradeHoursBlockedLog(what) {
|
|
|
9473
9479
|
return true;
|
|
9474
9480
|
}
|
|
9475
9481
|
|
|
9482
|
+
/* ---------- TRADE SAATLERİ OTOMATİK DURAKLATMA ----------
|
|
9483
|
+
Pencere kapanınca TÜM finance ajanları (trader + ekip + sembol işçileri)
|
|
9484
|
+
Durdur butonuna basılmış gibi otomatik durdurulur; pencere açılınca
|
|
9485
|
+
duraklatılanlar AYNEN geri başlatılır. Kullanıcı duraklama sırasında bir
|
|
9486
|
+
ajanı elle durdurursa o ajan plandan düşer; "Ajanı Durdur"a basılırsa
|
|
9487
|
+
plandan tamamen vazgeçilir (otomatik başlatma iptal). */
|
|
9488
|
+
function finHoursPlanSnapshot() {
|
|
9489
|
+
const plan = { main: false, roles: [], symbols: [] };
|
|
9490
|
+
try {
|
|
9491
|
+
for (const [, a] of financeState.agents) {
|
|
9492
|
+
if (!a) continue;
|
|
9493
|
+
if (a.main) plan.main = true;
|
|
9494
|
+
else if (a.role) plan.roles.push(String(a.role));
|
|
9495
|
+
else if (Array.isArray(a.symbols) && a.symbols.length) plan.symbols.push(a.symbols.slice(0, 8));
|
|
9496
|
+
}
|
|
9497
|
+
} catch {}
|
|
9498
|
+
return plan;
|
|
9499
|
+
}
|
|
9500
|
+
|
|
9501
|
+
function finHoursPlanEmpty(p) {
|
|
9502
|
+
return !p || (!p.main && !(p.roles || []).length && !(p.symbols || []).length);
|
|
9503
|
+
}
|
|
9504
|
+
|
|
9505
|
+
function finHoursPlanDrop(agent) {
|
|
9506
|
+
const p = financeState.hoursPaused;
|
|
9507
|
+
if (!p || !agent) return;
|
|
9508
|
+
if (agent.main) p.main = false;
|
|
9509
|
+
else if (agent.role) p.roles = (p.roles || []).filter((r) => r !== String(agent.role));
|
|
9510
|
+
else if (Array.isArray(agent.symbols) && agent.symbols.length) {
|
|
9511
|
+
const key = String(agent.symbols[0] || '').toUpperCase();
|
|
9512
|
+
p.symbols = (p.symbols || []).filter((s) => String((s && s[0]) || '').toUpperCase() !== key);
|
|
9513
|
+
}
|
|
9514
|
+
if (finHoursPlanEmpty(p)) financeState.hoursPaused = null;
|
|
9515
|
+
}
|
|
9516
|
+
|
|
9517
|
+
/* Pencere kapandı: koşan her finance ajanını durdur, planı sakla. */
|
|
9518
|
+
function finHoursPause(reason) {
|
|
9519
|
+
const plan = finHoursPlanSnapshot();
|
|
9520
|
+
if (finHoursPlanEmpty(plan)) return false;
|
|
9521
|
+
financeState.hoursPaused = plan;
|
|
9522
|
+
const sids = [];
|
|
9523
|
+
for (const [sid, a] of financeState.agents) if (a) sids.push(String(sid));
|
|
9524
|
+
for (const sid of sids) {
|
|
9525
|
+
try { finAgentStop(sid, reason, { keepHoursPlan: true }); } catch {}
|
|
9526
|
+
}
|
|
9527
|
+
financeState.traderOn = false;
|
|
9528
|
+
const th = finCfg().tradeHours || {};
|
|
9529
|
+
const n = (plan.main ? 1 : 0) + (plan.roles || []).length + (plan.symbols || []).length;
|
|
9530
|
+
financeLog(`[saat] trade saatleri kapandı (${th.start || '09:00'}-${th.end || '22:00'} yerel) — ${n} ajan otomatik durduruldu; pencere açılınca geri başlayacak`);
|
|
9531
|
+
finPush('trader', { state: 'hours-stop', count: n });
|
|
9532
|
+
return true;
|
|
9533
|
+
}
|
|
9534
|
+
|
|
9535
|
+
/* Duraklatılan tek ajanı geri aç (ekip rolü ya da sembol işçisi). */
|
|
9536
|
+
function finHoursSpawn(role, symbol) {
|
|
9537
|
+
if (!engine) return false;
|
|
9538
|
+
const f = finCfg();
|
|
9539
|
+
try {
|
|
9540
|
+
const created = role ? finAgentCreate([], false, role) : finAgentCreate([symbol], false);
|
|
9541
|
+
try { engine.setSessionModel(created.s.id, f.traderSel || null); } catch {}
|
|
9542
|
+
const ok = engine.send(created.s.id, finTraderBrief(created.agent), { userAction: true });
|
|
9543
|
+
if (!ok) {
|
|
9544
|
+
finAgentStop(String(created.s.id), 'saat açılışı — oturum meşgul');
|
|
9545
|
+
return false;
|
|
9546
|
+
}
|
|
9547
|
+
const roleDef = role ? finRoleDef(role) : null;
|
|
9548
|
+
financeLog('[saat] ' + (roleDef ? roleDef.label : symbol) + ' geri başlatıldı');
|
|
9549
|
+
return true;
|
|
9550
|
+
} catch {
|
|
9551
|
+
return false;
|
|
9552
|
+
}
|
|
9553
|
+
}
|
|
9554
|
+
|
|
9555
|
+
/* Pencere açıldı: duraklatılanları geri başlat. */
|
|
9556
|
+
function finHoursResume() {
|
|
9557
|
+
const p = financeState.hoursPaused;
|
|
9558
|
+
financeState.hoursPaused = null;
|
|
9559
|
+
if (finHoursPlanEmpty(p) || !engine) return false;
|
|
9560
|
+
const th = finCfg().tradeHours || {};
|
|
9561
|
+
financeLog(`[saat] trade saatleri açıldı (${th.start || '09:00'}-${th.end || '22:00'} yerel) — duraklatılan ajanlar geri başlatılıyor`);
|
|
9562
|
+
if (p.main) { try { financeTraderStart(); } catch {} }
|
|
9563
|
+
for (const role of p.roles || []) {
|
|
9564
|
+
if (!finRoleDef(role)) continue;
|
|
9565
|
+
let running = false;
|
|
9566
|
+
for (const [, a] of financeState.agents) if (a && a.role === role) { running = true; break; }
|
|
9567
|
+
if (!running) finHoursSpawn(role, '');
|
|
9568
|
+
}
|
|
9569
|
+
for (const syms of p.symbols || []) {
|
|
9570
|
+
const symbol = String((syms && syms[0]) || '').toUpperCase();
|
|
9571
|
+
if (!symbol) continue;
|
|
9572
|
+
let running = false;
|
|
9573
|
+
for (const [, a] of financeState.agents) {
|
|
9574
|
+
if (a && !a.main && !a.role && a.symbols.length === 1 && a.symbols[0] === symbol) { running = true; break; }
|
|
9575
|
+
}
|
|
9576
|
+
if (!running) finHoursSpawn('', symbol);
|
|
9577
|
+
}
|
|
9578
|
+
finPush('trader', { state: 'hours-resume' });
|
|
9579
|
+
return true;
|
|
9580
|
+
}
|
|
9581
|
+
|
|
9582
|
+
/* Saat geçişi denetimi: pencere dışındaysa duraklat, içindeyse geri başlat. */
|
|
9583
|
+
function finHoursSync() {
|
|
9584
|
+
if (!engine) return;
|
|
9585
|
+
if (!finTradeHoursOpen()) {
|
|
9586
|
+
if (financeState.agents.size) finHoursPause('trade saatleri kapandı — ajan otomatik durduruldu');
|
|
9587
|
+
return;
|
|
9588
|
+
}
|
|
9589
|
+
if (financeState.hoursPaused) finHoursResume();
|
|
9590
|
+
}
|
|
9591
|
+
|
|
9592
|
+
/* Otomatik saat denetimi zamanlayıcısı (10 sn; MT5 köprüsünden bağımsız) */
|
|
9593
|
+
function finHoursStart() {
|
|
9594
|
+
if (financeState.hoursTimer) return;
|
|
9595
|
+
financeState.hoursTimer = setInterval(() => { try { finHoursSync(); } catch {} }, 10000);
|
|
9596
|
+
try { finHoursSync(); } catch {}
|
|
9597
|
+
}
|
|
9598
|
+
|
|
9599
|
+
function finHoursStop() {
|
|
9600
|
+
if (financeState.hoursTimer) {
|
|
9601
|
+
clearInterval(financeState.hoursTimer);
|
|
9602
|
+
financeState.hoursTimer = null;
|
|
9603
|
+
}
|
|
9604
|
+
}
|
|
9605
|
+
|
|
9476
9606
|
/* ANALİZ EKİBİ: trader'ın yanında koşan uzman ajan rolleri — seçilen her rol
|
|
9477
9607
|
AYRI bir sürekli finance ajanı açar. Hepsi tüm SKILL'lere + mt5 okuma
|
|
9478
9608
|
araçlarına erişir (finance oturumu oldukları için), İŞLEM AÇMAZLAR.
|
|
@@ -11093,6 +11223,7 @@ async function financeEnsureBridge() {
|
|
|
11093
11223
|
terminal: String(f.terminalPath || '').trim(),
|
|
11094
11224
|
});
|
|
11095
11225
|
finWatchStart(); /* risk otomasyonu + bildirimler köprü açılırken başlar */
|
|
11226
|
+
try { finHoursStart(); } catch {} /* trade saatleri otomatik duraklatma denetimi */
|
|
11096
11227
|
return mt5bridge.status();
|
|
11097
11228
|
}
|
|
11098
11229
|
|
|
@@ -11287,6 +11418,7 @@ mt5bridge.on('bridge', (m) => {
|
|
|
11287
11418
|
financeLog('[MT5] terminal bağlı: ' + ((m.terminal && (m.terminal.name + ' @ ' + m.terminal.company)) || 'MT5'));
|
|
11288
11419
|
if (m.account) financeLog(`[MT5] hesap ${m.account.login} · bakiye ${m.account.balance} ${m.account.currency}`);
|
|
11289
11420
|
finWatchStart();
|
|
11421
|
+
try { finHoursStart(); } catch {} /* trade saatleri otomatik duraklatma denetimi */
|
|
11290
11422
|
finStatsRefresh(true).catch(() => {});
|
|
11291
11423
|
/* İLK ENTEGRASYON: EA + AutoTrading + grafik kurulumu otomatik */
|
|
11292
11424
|
finMt5SetupEnsure(false)
|
|
@@ -11403,7 +11535,7 @@ function finApplyTraderFields(s, symbolsOverride, role, isMain) {
|
|
|
11403
11535
|
s.financeSymbols = Array.isArray(symbolsOverride) && symbolsOverride.length ? symbolsOverride : f.symbols;
|
|
11404
11536
|
s.financeStrategy = String(f.strategy || '');
|
|
11405
11537
|
s.financeShadow = !!f.shadowMode; /* shadow modda işlem araçları emir göndermez */
|
|
11406
|
-
s.financeLimits = { maxLot: f.maxLot, maxPositions: f.maxPositions };
|
|
11538
|
+
s.financeLimits = { minLot: f.minLot, maxLot: f.maxLot, maxPositions: f.maxPositions };
|
|
11407
11539
|
/* ZORUNLU TEK skill: rol ajanı → roleSkills[rol], ana trader → roleSkills.trader */
|
|
11408
11540
|
const skillKey = r || (s.financePlaybook ? 'trader' : '');
|
|
11409
11541
|
s.financeRoleSkills = (skillKey && f.roleSkills && f.roleSkills[skillKey]) || [];
|
|
@@ -11441,10 +11573,13 @@ function finAgentCreate(symbols, isMain, role) {
|
|
|
11441
11573
|
}
|
|
11442
11574
|
|
|
11443
11575
|
/* Ajanı durdur: timer kapat + oturumu kes + paralel ajan kaydını 'aborted' yaz */
|
|
11444
|
-
function finAgentStop(sid, reason) {
|
|
11576
|
+
function finAgentStop(sid, reason, opts) {
|
|
11445
11577
|
sid = String(sid || '');
|
|
11446
11578
|
const agent = financeState.agents.get(sid);
|
|
11447
11579
|
if (!agent) return false;
|
|
11580
|
+
/* ELLE DURDURULDU: saat duraklama planından düş — pencere açılınca geri
|
|
11581
|
+
gelmesin (otomatik duraklatma keepHoursPlan ile planı korur) */
|
|
11582
|
+
if (!(opts && opts.keepHoursPlan)) { try { finHoursPlanDrop(agent); } catch {} }
|
|
11448
11583
|
clearTimeout(agent.timer);
|
|
11449
11584
|
if (agent.waitPoll) { clearTimeout(agent.waitPoll); agent.waitPoll = null; }
|
|
11450
11585
|
agent.waitingTeam = false;
|
|
@@ -11499,7 +11634,7 @@ function finTraderBrief(agent) {
|
|
|
11499
11634
|
return [
|
|
11500
11635
|
`Beast Finance ${who} başlatıldı — ilk tur: strateji çerçeveni kur ve piyasa taramasını yap.`,
|
|
11501
11636
|
`Odak semboller: ${syms || '(boş — mt5_status ile terminale bak, mantıklı semboller seç)'}`,
|
|
11502
|
-
`Tur aralığı: ${f.intervalSec} sn ·
|
|
11637
|
+
`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}`,
|
|
11503
11638
|
roleDef
|
|
11504
11639
|
? `UZMANLIK: ${roleDef.desc} — raporlarını bu çerçevede yaz; İŞLEM AÇMA, yalnız analiz + net öneri üret.`
|
|
11505
11640
|
: '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ı.',
|
|
@@ -11596,7 +11731,7 @@ async function finConsultPlan(f, agent, sid) {
|
|
|
11596
11731
|
'Güncel piyasa bağlamı gerekiyorsa web_search kullan; kurulu bir skill konuyla ilgiliyse skill aracıyla oku.';
|
|
11597
11732
|
const ctx = [
|
|
11598
11733
|
`Odak semboller: ${agent && agent.symbols && agent.symbols.length ? agent.symbols.join(', ') : (f.symbols || []).join(', ') || '(boş — ajan kendi bulabilir)'}`,
|
|
11599
|
-
`Otomatik işlem: ${auto} ·
|
|
11734
|
+
`Otomatik işlem: ${auto} · lot aralığı ${f.minLot}–${f.maxLot} · max eşzamanlı pozisyon ${f.maxPositions}`,
|
|
11600
11735
|
account
|
|
11601
11736
|
? `Hesap: bakiye ${account.balance} ${account.currency} · özkaynak ${account.equity ?? '?'} · serbest marj ${account.margin_free ?? '?'} · kaldıraç 1:${account.leverage ?? '?'}`
|
|
11602
11737
|
: 'Hesap verisi alınamadı.',
|
|
@@ -11907,6 +12042,8 @@ ipcMain.handle('finance:alerts:remove', async (_e, id) => {
|
|
|
11907
12042
|
|
|
11908
12043
|
ipcMain.handle('finance:mode', async (_e, payload) => {
|
|
11909
12044
|
financeState.mode = !!(payload && payload.on);
|
|
12045
|
+
/* SAAT DENETİMİ: pencere kapanınca ajanlar otomatik durur, açılınca geri başlar */
|
|
12046
|
+
try { finHoursStart(); } catch {}
|
|
11910
12047
|
/* BEAST FINANCE = MT5 KAPISI:
|
|
11911
12048
|
- mod AÇILINCA köprü + MT5 terminali OTOMATİK başlar (watchdog dahil) —
|
|
11912
12049
|
panel açılır açılmaz bağlantı kurulur, snapshot'ı beklemez.
|
|
@@ -12007,6 +12144,9 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
|
|
|
12007
12144
|
}
|
|
12008
12145
|
}
|
|
12009
12146
|
if (p.maxLot !== undefined) f.maxLot = Math.max(0.01, Math.min(100, Number(p.maxLot) || 0.1));
|
|
12147
|
+
if (p.minLot !== undefined) f.minLot = Math.max(0.01, Math.min(100, Number(p.minLot) || 0.01));
|
|
12148
|
+
/* min lot max lotu aşamaz — hangisi sonra yazıldıysa diğerine kelepçelenir */
|
|
12149
|
+
if (f.minLot > f.maxLot) f.minLot = f.maxLot;
|
|
12010
12150
|
if (p.maxPositions !== undefined) f.maxPositions = Math.max(1, Math.min(20, Math.round(Number(p.maxPositions) || 3)));
|
|
12011
12151
|
/* RİSK OTOMASYONU + BİLDİRİM ayarları */
|
|
12012
12152
|
if (p.watchdog !== undefined) f.watchdog = !!p.watchdog;
|
|
@@ -12140,6 +12280,13 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
|
|
|
12140
12280
|
async function financeTraderStart() {
|
|
12141
12281
|
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
12142
12282
|
const f = finCfg();
|
|
12283
|
+
try { finHoursStart(); } catch {}
|
|
12284
|
+
/* TRADE SAATLERİ KAPISI: pencere dışında elle başlatma da reddedilir —
|
|
12285
|
+
pencere açılınca duraklatılan ajanlar otomatik geri başlar */
|
|
12286
|
+
if (!finTradeHoursOpen()) {
|
|
12287
|
+
const th = f.tradeHours || {};
|
|
12288
|
+
return { ok: false, error: `trade saatleri dışı (${th.start || '09:00'}-${th.end || '22:00'} yerel) — pencere açılınca ajanlar otomatik başlar` };
|
|
12289
|
+
}
|
|
12143
12290
|
if (!engine.publicState().hasModel && !f.traderSel) return { ok: false, error: 'model yok — Ayarlar → Provider' };
|
|
12144
12291
|
try { finSyncScheduleJobs(); } catch {} /* plan/review saatleri trader açılışında garantiye alınır */
|
|
12145
12292
|
/* ANA trader: varsa aynen sürdür, yoksa yeni sürekli ajan aç */
|
|
@@ -12216,6 +12363,8 @@ ipcMain.handle('finance:trader:start', () => financeTraderStart());
|
|
|
12216
12363
|
|
|
12217
12364
|
ipcMain.handle('finance:trader:stop', async () => {
|
|
12218
12365
|
financeState.traderOn = false;
|
|
12366
|
+
/* ELLE DURDURMA: saat planı iptal — pencere açılınca otomatik geri başlatma yok */
|
|
12367
|
+
financeState.hoursPaused = null;
|
|
12219
12368
|
/* ANA trader + ANALİZ EKİBİ durdurulur (sembol işçileri rail × ile ayrı durdurulur) */
|
|
12220
12369
|
let mainSid = '';
|
|
12221
12370
|
const teamSids = [];
|
|
@@ -12236,6 +12385,12 @@ ipcMain.handle('finance:trader:stop', async () => {
|
|
|
12236
12385
|
ipcMain.handle('finance:agent:spawn', async (_e, payload) => {
|
|
12237
12386
|
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
12238
12387
|
const f = finCfg();
|
|
12388
|
+
try { finHoursStart(); } catch {}
|
|
12389
|
+
/* TRADE SAATLERİ: pencere dışında sembol işçisi de başlatılamaz */
|
|
12390
|
+
if (!finTradeHoursOpen()) {
|
|
12391
|
+
const th = f.tradeHours || {};
|
|
12392
|
+
return { ok: false, error: `trade saatleri dışı (${th.start || '09:00'}-${th.end || '22:00'} yerel) — pencere açılınca ajanlar otomatik başlar` };
|
|
12393
|
+
}
|
|
12239
12394
|
if (!engine.publicState().hasModel && !f.traderSel) return { ok: false, error: 'model yok — Ayarlar → Provider' };
|
|
12240
12395
|
const symbol = String((payload && payload.symbol) || '').trim().toUpperCase();
|
|
12241
12396
|
if (!symbol) return { ok: false, error: 'sembol gerekli' };
|
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;
|
|
@@ -10165,6 +10167,8 @@ function finOnEvent(ev) {
|
|
|
10165
10167
|
else if (ev.state === 'team') finLogLine('[trader] ekip raporları bekleniyor — ' + (ev.waiting || 0) + ' ajan turunu bitirsin (max ' + (ev.maxSec || '?') + ' sn)');
|
|
10166
10168
|
else if (ev.state === 'idle') finLogLine('[trader] tur #' + (ev.round || 0) + ' bitti — sıradaki tur ~' + (ev.nextInSec || '?') + ' sn');
|
|
10167
10169
|
else if (ev.state === 'hours') finLogLine('[trader] trade saatleri dışı — ajan askıda (kontrol ~' + (ev.nextInSec || 60) + ' sn)');
|
|
10170
|
+
else if (ev.state === 'hours-stop') finLogLine('[trader] trade saatleri kapandı — ' + (ev.count || 0) + ' finance ajanı otomatik durduruldu; pencere açılınca geri başlar');
|
|
10171
|
+
else if (ev.state === 'hours-resume') finLogLine('[trader] trade saatleri açıldı — duraklatılan ajanlar geri başlatıldı');
|
|
10168
10172
|
else if (ev.state === 'stopped') finLogLine('[trader] durduruldu');
|
|
10169
10173
|
} else if (ev.fn === 'install') {
|
|
10170
10174
|
finLogLine('[MT5] paket kurulumu tamamlandı (kod ' + ev.code + ')');
|
|
@@ -10529,9 +10533,22 @@ if (els.finInterval) els.finInterval.addEventListener('change', () => {
|
|
|
10529
10533
|
if (els.finMaxLot) els.finMaxLot.addEventListener('change', () => {
|
|
10530
10534
|
const v = Math.max(0.01, Math.min(100, Number(els.finMaxLot.value) || 0.1));
|
|
10531
10535
|
els.finMaxLot.value = v;
|
|
10536
|
+
/* min lot max lotu aşamaz — görünürde de kelepçele (sunucu ayrıca zorlar) */
|
|
10537
|
+
if (els.finMinLot && Number(els.finMinLot.value) > v) els.finMinLot.value = v;
|
|
10532
10538
|
finSaveCfg({ maxLot: v });
|
|
10533
10539
|
toast('Max lot: ' + v + ' — sonraki turdan itibaren geçerli');
|
|
10534
10540
|
});
|
|
10541
|
+
if (els.finMinLot) els.finMinLot.addEventListener('change', () => {
|
|
10542
|
+
const maxV = Math.max(0.01, Math.min(100, Number(els.finMaxLot && els.finMaxLot.value) || 0.1));
|
|
10543
|
+
let v = Math.max(0.01, Math.min(100, Number(els.finMinLot.value) || 0.01));
|
|
10544
|
+
if (v > maxV) {
|
|
10545
|
+
v = maxV;
|
|
10546
|
+
toast('Min lot max lotu aşamaz — ' + v + ' olarak ayarlandı');
|
|
10547
|
+
}
|
|
10548
|
+
els.finMinLot.value = v;
|
|
10549
|
+
finSaveCfg({ minLot: v });
|
|
10550
|
+
toast('Min lot: ' + v + ' — lot hesapları bu tabanın altına inmez');
|
|
10551
|
+
});
|
|
10535
10552
|
if (els.finSymBtn) els.finSymBtn.addEventListener('click', finSymPickerOpen);
|
|
10536
10553
|
|
|
10537
10554
|
/* 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; }
|