beast-agent 2.58.1 → 2.59.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 +29 -5
- package/src/agent/financetools.js +23 -5
- package/src/agent/finwatch.js +52 -1
- package/src/agent/llm.js +54 -14
- package/src/main.js +84 -2
- package/src/renderer/index.html +14 -0
- package/src/renderer/renderer.js +50 -0
- package/src/renderer/style.css +2 -0
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -811,9 +811,13 @@ class Engine {
|
|
|
811
811
|
payload = squeeze.apply(payload, { cacheKey: String(session.id || '') });
|
|
812
812
|
}
|
|
813
813
|
const cands = this._chatCandidates(sel, wasVision);
|
|
814
|
+
/* AYLIK KOTASI DOLAN SAĞLAYICILAR: aynı sağlayıcının diğer modellerini
|
|
815
|
+
denemek boşuna (kota hesap/workspace bazlı) — bu turda atlanır. */
|
|
816
|
+
const quotaOut = new Set();
|
|
814
817
|
let lastErr = null;
|
|
815
818
|
for (let i = 0; i < cands.length; i++) {
|
|
816
819
|
const c = cands[i];
|
|
820
|
+
if (quotaOut.has(String(c.providerId))) continue;
|
|
817
821
|
let retried413 = false;
|
|
818
822
|
try {
|
|
819
823
|
/* provider bazlı girdi limiti: payload aşıyorsa sıkıştırılıp öyle gönderilir.
|
|
@@ -903,6 +907,15 @@ class Engine {
|
|
|
903
907
|
if (e && (e.name === 'AbortError' || (signal && signal.aborted))) throw e;
|
|
904
908
|
lastErr = e;
|
|
905
909
|
const msgStr = String((e && e.message) || '');
|
|
910
|
+
/* AYLIK KOTA: bu sağlayıcı tur boyunca kapalı — kullanıcıya net söyle,
|
|
911
|
+
FALLOUT varsa farklı sağlayıcıya geç */
|
|
912
|
+
if (e && e.usageLimit) {
|
|
913
|
+
quotaOut.add(String(c.providerId));
|
|
914
|
+
emitSafe(this, session.id, {
|
|
915
|
+
type: 'status',
|
|
916
|
+
status: `⛔ ${c.providerName}: aylık kullanım limiti doldu — aynı sağlayıcının diğer modelleri atlanıyor`,
|
|
917
|
+
});
|
|
918
|
+
}
|
|
906
919
|
/* 413 TPM: sağlayıcı gerçek istek boyutunu söylüyor → kalibre et,
|
|
907
920
|
aynı modeli yeniden sıkıştırıp BİR KEZ daha dene */
|
|
908
921
|
const m413 = /Requested\s+([\d,]+)/i.exec(msgStr) || (e && e.status === 413 ? [, 0] : null);
|
|
@@ -925,7 +938,11 @@ class Engine {
|
|
|
925
938
|
([400, 404, 422].includes(e.status) ||
|
|
926
939
|
/\bno endpoints\b/i.test(msgStr) ||
|
|
927
940
|
/\bimage input\b/i.test(msgStr));
|
|
928
|
-
|
|
941
|
+
/* sıradaki aday: kotası dolmuş sağlayıcılar atlanır */
|
|
942
|
+
let next = null;
|
|
943
|
+
for (let j = i + 1; j < cands.length; j++) {
|
|
944
|
+
if (!quotaOut.has(String(cands[j].providerId))) { next = cands[j]; break; }
|
|
945
|
+
}
|
|
929
946
|
if (!next) break; // zincir bitti — hatayı yukarı fırlat
|
|
930
947
|
if (imgIssue) {
|
|
931
948
|
emitSafe(this, session.id, {
|
|
@@ -1417,15 +1434,21 @@ class Engine {
|
|
|
1417
1434
|
files = fs.readdirSync(this.sessionsDir).filter((f) => f.endsWith('.jsonl'));
|
|
1418
1435
|
} catch {}
|
|
1419
1436
|
const out = [];
|
|
1437
|
+
const fin = [];
|
|
1420
1438
|
for (const f of files) {
|
|
1421
1439
|
const id = f.replace(/\.jsonl$/, '');
|
|
1422
1440
|
const v = this._view(this._load(id));
|
|
1423
1441
|
if (v.isBg) continue;
|
|
1424
1442
|
if (v.isBotDm) continue; // botlar arası DM — yalnız admin DM Log ekranında
|
|
1425
|
-
|
|
1443
|
+
/* FİNANS GEÇMİŞİ KAYBOLMAZ: normal sohbet listesi 100 ile sınırlı olsa da
|
|
1444
|
+
finance sohbetleri AYRICA listelenir — 100+ oturumu olan kullanıcıda
|
|
1445
|
+
eski finance sohbetleri listeden düşüp "kaybolmuş" görünmez. */
|
|
1446
|
+
(v.finance ? fin : out).push(v);
|
|
1426
1447
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1448
|
+
const byNew = (a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt));
|
|
1449
|
+
out.sort(byNew);
|
|
1450
|
+
fin.sort(byNew);
|
|
1451
|
+
return out.slice(0, 100).concat(fin.slice(0, 60)).sort(byNew);
|
|
1429
1452
|
}
|
|
1430
1453
|
|
|
1431
1454
|
/* Panel oturumları: belirli bir bgTitle taşıyan gizli oturumlar (Beast Code,
|
|
@@ -2009,9 +2032,10 @@ class Engine {
|
|
|
2009
2032
|
modeBlock2 + '\n' +
|
|
2010
2033
|
(roleBlockFull ? roleBlockFull + '\n' : '') +
|
|
2011
2034
|
(teamLine || '') +
|
|
2012
|
-
'MT5 ARAÇLARI: mt5_status (bağlantı), mt5_account (hesap), mt5_market (canlı fiyat), mt5_positions (açık pozisyonlar), mt5_orders (bekleyen emirler), mt5_history (kapanan işlemler), mt5_alerts (fiyat alarmı: modu SEN seçersin
|
|
2035
|
+
'MT5 ARAÇLARI: mt5_status (bağlantı), mt5_account (hesap), mt5_market (canlı fiyat), mt5_positions (açık pozisyonlar), mt5_orders (bekleyen emirler), mt5_history (kapanan işlemler), mt5_alerts (fiyat alarmı: kur/listele/sil — modu SEN seçersin: once/repeat), mt5_ea (BeastFinance grafik panosu: status/ping/chart/note), mt5_trade (TÜM TİPLER: buy/sell market + buy/sell limit/stop), mt5_close (TAM ya da KISMİ kapat: percent ile kısmi TP & kısmi stop — karar ajanın), mt5_modify (SL/TP), mt5_pending (TÜM TİPLER: buy_limit/sell_limit/buy_stop/sell_stop bekleyen + buy_market/sell_market anlık), mt5_cancel (emir iptal).\n' +
|
|
2013
2036
|
'EMİR TİPLERİ (6 tip — HEPSİ AÇIK): buy_market/sell_market = anlık piyasa emri (fiyat beklenmez), buy_limit/sell_limit = bekleyen limit (price zorunlu), buy_stop/sell_stop = bekleyen stop (price zorunlu). mt5_trade ve mt5_pending İKİSİ DE 6 tipi kabul eder; emir tipini net söyle (ör. type:"sell_limit").\n' +
|
|
2014
2037
|
'ALARM KARARI SENDE: mt5_alerts ile alarm kurarken mode ZORUNLU — "once" (tek seferlik; ilk tetiklemede kapanır) ya da "repeat" (tekrarlı; alarm açık kalır, koşul sürdükçe cooldownMin dakikada bir tekrar uyarır + seni uyandırır; cooldownMin varsayılan 5). Tekrarlı mı tek seferlik mi olacağını sen seç, varsayılana bırakma; seviyenin önemine göre cooldownMin belirle (ör. 5/15/60).\n' +
|
|
2038
|
+
'ALARM TEMİZLİĞİ (ZORUNLU DİSİPLİN): alarm bir BEKÇİDİR, süs değil — gereksiz alarm biriktirme. Her turda (ya da en az birkaç turda bir) mt5_alerts action:"list" ile alarmları gör; listede "mine:true" senin kurduklarındır. Tezi/geçerliliği biten alarmı SİL: seviye kırıldı ve artık anlamsız, pozisyon kapandı, plan/hedef değişti, tetiklendi ve işlevi kalmadı. Silme: tekil kayıt action:"remove" {id} · kendi gereksizlerini toplu action:"clear" (varsayılan yalnız SENİN alarmların; symbol ile daralt; tümünü temizlemek için all:true — bunu başka ajanların alarmlarını da sileceği için YALNIZ açıkça gerekliyse kullan). Akan fiyatta işe yaramayan, üst üste aynı bölgeye kurulmuş alarmları temiz tut.\n' +
|
|
2015
2039
|
'KISMİ KAPATMA YETKİSİ (TÜM finance ajanlarında): açık pozisyonda kısmi kapatma — kısmi TP (kâr al) ve kısmi stop (zarar kes) — TAMAMEN SENİN KARARIN, OTOMATİK DEĞİLDİR; mt5_close {ticket, percent, kind:"partial_tp"|"partial_sl", reason}. Gerek gördüğünde kullan; kalan pozisyon SL/TP ile devam eder. Kısmi TP fırsatı bildirimi gelirse (fiyat seviyeye ulaştı) kapatıp kapatmamak sana bağlı.\n' +
|
|
2016
2040
|
'KİŞİSEL MT5 ARAÇLARI (VARSAYILAN KURULU — tool__* tüm oturumlarda çağrılabilir): tool__mt5_shot (grafikten GERÇEK PNG + görsel ajana enjekte — grafik işlerinde BİRİNCİL; timeframes:["M1","M15"] ile çoklu periyot, symbols:["GOLD","EURUSD","BTCUSD"] ile çoklu sembol TEK karede), tool__mt5_barlar2 (OHLC mumlar, köprüsüz), tool__mt5_m15_m5 (GOLD M15+M5 hazır paket), tool__mt5_fiyat, tool__mt5_durum (hesap+pozisyon+emir tek çağrı), tool__mt5_gecmis, tool__mt5_pozisyon_gecmis, tool__mt5_sltp, tool__mt5_kapat, tool__mt5_bekleyen, tool__mt5_emir, tool__mt5_emir_iptal.\n' +
|
|
2017
2041
|
'BEASTFINANCE EA (OTOMATİK GRAFİK UZMANI — ENTEGRASYON KANALI): MT5 terminaline bağlanıldığında BeastFinance uzman danışmanı İLK GRAFİĞE OTOMATİK yüklenir, AutoTrading izni açılır (kurulum sistem tarafından yapılır; elle ekleme gerekmez). Grafik panosu + seviye çizgileri + dosya köprüsü (beast_ea.json / beast_cmd.json / beast_note.json) bu EA üzerinden yürür: entegrasyon işlerinde (status/ping/chart/note) mt5_ea kullan; ekran görüntüsü işlerinde tool__mt5_shot ("shot" komutu — gerçek PNG + ajan görseli) birincildir. Yazdığın not ve çizgiler tool__mt5_shot ve computer_look screenshot\'ında GÖRÜNÜR.\n' +
|
|
@@ -200,12 +200,12 @@ const definitions = NAMES.map((name) => {
|
|
|
200
200
|
},
|
|
201
201
|
mt5_alerts: {
|
|
202
202
|
description:
|
|
203
|
-
'FİYAT ALARMI: mt5_alerts {action:"list"|"set"|"remove"}. set: {symbol, price, direction:"above"|"below", mode:"once"|"repeat", note?, cooldownMin?}. ALARM MODUNU KURAN AJAN (sen) SEÇERSİN — mode zorunlu: "once" tek seferlik (ilk tetiklemede kapanır) ya da "repeat" tekrarlı (alarm açık kalır, koşul sürdükçe cooldownMin dakikada bir tekrar bildirir + sahibi ajanı uyandırır; cooldownMin varsayılan 5). Örn kritik seviye: XAUUSD 3400 üstü → mode:"repeat", cooldownMin:15; tek kere haber: mode:"once".',
|
|
203
|
+
'FİYAT ALARMI: mt5_alerts {action:"list"|"set"|"remove"|"clear"}. set: {symbol, price, direction:"above"|"below", mode:"once"|"repeat", note?, cooldownMin?}. ALARM MODUNU KURAN AJAN (sen) SEÇERSİN — mode zorunlu: "once" tek seferlik (ilk tetiklemede kapanır) ya da "repeat" tekrarlı (alarm açık kalır, koşul sürdükçe cooldownMin dakikada bir tekrar bildirir + sahibi ajanı uyandırır; cooldownMin varsayılan 5). Örn kritik seviye: XAUUSD 3400 üstü → mode:"repeat", cooldownMin:15; tek kere haber: mode:"once". TEMİZLİK: alarm bir bekçidir, süs değil — gereksiz alarm biriktirme; her turda list ile KENDİ alarmlarını gör, işi bitenleri (tez geçersiz, pozisyon kapandı, seviye anlamsız) clear ile SİL. clear varsayılanı YALNIZ senin kurduğun alarmlar; symbol ile daralt; ids:[...] belirli alarmlar; all:true tüm finance alarmları.',
|
|
204
204
|
parameters: {
|
|
205
205
|
type: 'object',
|
|
206
206
|
properties: {
|
|
207
|
-
action: { type: 'string', enum: ['list', 'set', 'remove'] },
|
|
208
|
-
symbol: { type: 'string', description: 'set için sembol' },
|
|
207
|
+
action: { type: 'string', enum: ['list', 'set', 'remove', 'clear'] },
|
|
208
|
+
symbol: { type: 'string', description: 'set/clear için sembol (clear: yalnız bu sembole daralt)' },
|
|
209
209
|
price: { type: 'number', description: 'set için tetik fiyatı' },
|
|
210
210
|
direction: { type: 'string', enum: ['above', 'below'], description: 'above: fiyat ≥ price; below: fiyat ≤ price' },
|
|
211
211
|
mode: {
|
|
@@ -217,6 +217,8 @@ const definitions = NAMES.map((name) => {
|
|
|
217
217
|
cooldownMin: { type: 'number', description: "mode:'repeat' için iki tetik arası en az dakika (varsayılan 5, 0 = her tetiklemede, max 10080)" },
|
|
218
218
|
once: { type: 'boolean', description: "mode'a alternatif kısa yol (true = tek seferlik)" },
|
|
219
219
|
id: { type: 'string', description: 'remove için alarm id' },
|
|
220
|
+
ids: { type: 'array', items: { type: 'string' }, description: 'clear için: silinecek alarm id listesi (verilirse sahiplik aranmaz)' },
|
|
221
|
+
all: { type: 'boolean', description: 'clear için: true → sahiplik filtresini kaldır, kalan TÜM finance alarmlarını sil (varsayılan: yalnız kendi kurdukların)' },
|
|
220
222
|
},
|
|
221
223
|
required: ['action'],
|
|
222
224
|
},
|
|
@@ -420,7 +422,9 @@ const handlers = {
|
|
|
420
422
|
async mt5_alerts(args, ctx) {
|
|
421
423
|
const action = String(args.action || 'list').toLowerCase();
|
|
422
424
|
if (action === 'list') {
|
|
423
|
-
|
|
425
|
+
/* mine:true → bu oturumun (senin) kurduğu alarm — temizlik kararı için */
|
|
426
|
+
const sid = ctx && ctx.sessionId ? String(ctx.sessionId) : '';
|
|
427
|
+
const list = alertsApi.list().map((a) => ({ ...a, mine: sid ? String(a.sid || '') === sid : !a.sid }));
|
|
424
428
|
return { ok: true, count: list.length, alerts: list };
|
|
425
429
|
}
|
|
426
430
|
if (action === 'remove') {
|
|
@@ -429,6 +433,20 @@ const handlers = {
|
|
|
429
433
|
const removed = alertsApi.remove(id);
|
|
430
434
|
return { ok: !!removed, removed: !!removed, id };
|
|
431
435
|
}
|
|
436
|
+
if (action === 'clear') {
|
|
437
|
+
/* ALARM TEMİZLİĞİ: alarmı kuran ajan kendi gereksiz alarmlarını siler.
|
|
438
|
+
Varsayılan kapsam: bu oturumun kurdukları (ctx.sessionId); symbol ile
|
|
439
|
+
daralt; ids açıkça verilirse sahiplik aranmaz; all:true → tümü. */
|
|
440
|
+
if (typeof alertsApi.clear !== 'function') return { ok: false, error: 'toplu alarm temizliği desteklenmiyor' };
|
|
441
|
+
const ids = Array.isArray(args.ids) ? args.ids.map((x) => String(x || '').trim()).filter(Boolean) : [];
|
|
442
|
+
const all = args.all === true || String(args.all || '').toLowerCase() === 'true' || String(args.scope || '').toLowerCase() === 'all';
|
|
443
|
+
const symbol = String(args.symbol || '').trim().toUpperCase();
|
|
444
|
+
const sid = ctx && ctx.sessionId ? String(ctx.sessionId) : '';
|
|
445
|
+
const res = alertsApi.clear({ ids, sid, all, symbol }) || {};
|
|
446
|
+
const removed = Number(res.removed) || 0;
|
|
447
|
+
if (removed > 0) noteTrade('alert-clear', { removed, ids: res.ids || [], symbol, all }, ctx || {});
|
|
448
|
+
return { ok: true, removed, ids: res.ids || [] };
|
|
449
|
+
}
|
|
432
450
|
if (action === 'set') {
|
|
433
451
|
const symbol = String(args.symbol || '').trim().toUpperCase();
|
|
434
452
|
const price = Number(args.price);
|
|
@@ -466,7 +484,7 @@ const handlers = {
|
|
|
466
484
|
noteTrade('alert-set', { symbol, price, direction, id: alarm.id, note: alarm.note, cooldownMin: alarm.cooldownMin, once: alarm.once }, ctx || {});
|
|
467
485
|
return { ok: true, alert: alarm };
|
|
468
486
|
}
|
|
469
|
-
return { ok: false, error: "action: list|set|remove" };
|
|
487
|
+
return { ok: false, error: "action: list|set|remove|clear" };
|
|
470
488
|
},
|
|
471
489
|
async mt5_note(args, ctx) {
|
|
472
490
|
const symbol = String(args.symbol || '').trim().toUpperCase();
|
package/src/agent/finwatch.js
CHANGED
|
@@ -159,6 +159,57 @@ function alertCooldownActive(a, now) {
|
|
|
159
159
|
return last > 0 && Number(now) - last < cd;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
/* ---- alarm temizliği (ajan kendi kurduğu gereksiz alarmı siler) ----
|
|
163
|
+
opts:
|
|
164
|
+
ids → yalnız bu id'ler (açıkça verilenler sahiplikten bağımsız silinir)
|
|
165
|
+
sid → alarmı kuran ajan oturumu; scope varsayılanı "kendi alarmlarım"
|
|
166
|
+
symbol → yalnız bu sembole daralt
|
|
167
|
+
all → sahiplik filtresini kaldır (tüm finance alarmları)
|
|
168
|
+
Dönüş: silinecek alarm id listesi (saf; IO yok). */
|
|
169
|
+
function pickAlarms(alerts, opts) {
|
|
170
|
+
const o = opts || {};
|
|
171
|
+
const ids = Array.isArray(o.ids) ? o.ids.map((x) => String(x || '')).filter(Boolean) : [];
|
|
172
|
+
const sid = String(o.sid || '');
|
|
173
|
+
const symbol = String(o.symbol || '').toUpperCase();
|
|
174
|
+
const all = !!o.all;
|
|
175
|
+
const out = [];
|
|
176
|
+
for (const a of Array.isArray(alerts) ? alerts : []) {
|
|
177
|
+
if (!a || !a.id) continue;
|
|
178
|
+
if (ids.length) {
|
|
179
|
+
if (ids.includes(String(a.id))) out.push(String(a.id));
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (symbol && String(a.symbol || '').toUpperCase() !== symbol) continue;
|
|
183
|
+
if (all) {
|
|
184
|
+
out.push(String(a.id));
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
/* sid varsa YALNIZ kendi alarmları; sid yoksa (işçi oturumu) sahipsizler */
|
|
188
|
+
const owner = String(a.sid || '');
|
|
189
|
+
if (sid ? owner === sid : !owner) out.push(String(a.id));
|
|
190
|
+
}
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* ---- trade saatleri (makinenin YEREL saati) ----
|
|
195
|
+
th: { on, start:'HH:MM', end:'HH:MM' }. on değilse 7/24 açık (true).
|
|
196
|
+
Açıkken yalnız [start,end) aralığında true döner; gece aralığı desteklenir
|
|
197
|
+
(ör. 22:00 → 06:00). start === end → 24 saat. Saf; IO yok. */
|
|
198
|
+
function parseHM(v, defMin) {
|
|
199
|
+
const m = /^([01]?\d|2[0-3]):([0-5]\d)$/.exec(String(v || '').trim());
|
|
200
|
+
if (!m) return defMin;
|
|
201
|
+
return Number(m[1]) * 60 + Number(m[2]);
|
|
202
|
+
}
|
|
203
|
+
function tradeHoursOpen(th, now) {
|
|
204
|
+
if (!th || !th.on) return true;
|
|
205
|
+
const s = parseHM(th.start, 9 * 60);
|
|
206
|
+
const e = parseHM(th.end, 22 * 60);
|
|
207
|
+
if (s === e) return true;
|
|
208
|
+
const d = now instanceof Date ? now : new Date(Number(now) || Date.now());
|
|
209
|
+
const cur = d.getHours() * 60 + d.getMinutes();
|
|
210
|
+
return s < e ? cur >= s && cur < e : cur >= s || cur < e;
|
|
211
|
+
}
|
|
212
|
+
|
|
162
213
|
/* ---- bekleyen emir (pending) → pozisyon eşleştirme ----
|
|
163
214
|
Watchdog turu emir listesini karşılaştırır: listeden DÜŞEN emir için aynı
|
|
164
215
|
turda YENİ açılan (ya da netting hesapta hacmi artan) eşleşen bir pozisyon
|
|
@@ -228,4 +279,4 @@ function matchPendingDelta(goneOrders, freshPositions) {
|
|
|
228
279
|
return out;
|
|
229
280
|
}
|
|
230
281
|
|
|
231
|
-
module.exports = { plan, roundTo, stepRound, closeKind, parseOrderType, alertCooldownActive, orderSide, orderTypeLabel, orderVolume, matchPendingDelta };
|
|
282
|
+
module.exports = { plan, roundTo, stepRound, closeKind, parseOrderType, alertCooldownActive, pickAlarms, parseHM, tradeHoursOpen, orderSide, orderTypeLabel, orderVolume, matchPendingDelta };
|
package/src/agent/llm.js
CHANGED
|
@@ -64,7 +64,27 @@ const HINTS = {
|
|
|
64
64
|
504: 'sağlayıcı zaman aşımı',
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
+
/* AYLIK KOTA / KULLANIM LİMİTİ: 429 gövdesindeki "usage limit / quota" sinyali
|
|
68
|
+
hız limitinden FARKLI — tekrar denemek çözmez, aynı sağlayıcının diğer
|
|
69
|
+
modelleri de kapalıdır. Bu ayrım motorun FALLOUT kararını da besler. */
|
|
70
|
+
function isUsageLimitDetail(detail) {
|
|
71
|
+
return /GoUsageLimit|usage[_ ]?limit|insufficient_quota|monthly limit|kullanım limiti/i.test(
|
|
72
|
+
String(detail || '')
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
function friendlyError(status, statusText, detail) {
|
|
77
|
+
const d = String(detail || '');
|
|
78
|
+
if (status === 429 && isUsageLimitDetail(d)) {
|
|
79
|
+
let msg = 'HTTP 429 — AYLIK KULLANIM LİMİTİ DOLDU (hız limiti değil)';
|
|
80
|
+
const reset = /Resets?\s+in\s+([^."}]+)/i.exec(d);
|
|
81
|
+
if (reset) msg += ` — sıfırlanma: ${reset[1].trim()} sonra`;
|
|
82
|
+
const link = /(https?:\/\/[^\s"}]+)/.exec(d);
|
|
83
|
+
if (link) msg += `\nDevam için: ${link[1]}`;
|
|
84
|
+
msg +=
|
|
85
|
+
'\n(çözüm: sağlayıcı panelinden bakiyeden kullanımı aç, başka model/sağlayıcı seç ya da sıfırlanmayı bekle)';
|
|
86
|
+
return msg;
|
|
87
|
+
}
|
|
68
88
|
const hint = HINTS[status];
|
|
69
89
|
let msg = `HTTP ${status} ${statusText}`;
|
|
70
90
|
if (hint) msg += ` — ${hint}`;
|
|
@@ -72,9 +92,33 @@ function friendlyError(status, statusText, detail) {
|
|
|
72
92
|
if ([500, 502, 503, 504].includes(status)) {
|
|
73
93
|
msg += '\n(geçici olabilir: birkaç saniye sonra tekrar dene ya da model seçiciyi açıp başka bir model dene)';
|
|
74
94
|
}
|
|
95
|
+
if (isTransientDetail(status, d)) {
|
|
96
|
+
msg += '\n(sağlayıcı geçici hatası — otomatik tekrar denenir; sürerse başka model/sağlayıcı dene)';
|
|
97
|
+
}
|
|
75
98
|
return msg;
|
|
76
99
|
}
|
|
77
100
|
|
|
101
|
+
/* SAĞLAYICI GEÇİCİ HATASI: bazı sağlayıcılar upstream arızasını HTTP 400
|
|
102
|
+
gövdesinde "server_error" olarak döner — istemci hatası değildir, tekrar
|
|
103
|
+
denenmeli. Bu bayrak olmadan 400 kalıcı sayılıp model boşuna "çökmüş" olur. */
|
|
104
|
+
function isTransientDetail(status, detail) {
|
|
105
|
+
if (status !== 400 && status !== 500) return false;
|
|
106
|
+
return /"(type|code)"\s*:\s*"server_error"|Error from provider|Upstream (request failed|response was not valid JSON)|temporar/i.test(
|
|
107
|
+
String(detail || '')
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* HTTP hatasını zengin bayraklarla kur — usageLimit/transient bilgisi retry ve
|
|
112
|
+
FALLOUT katmanlarına taşınır (boşa tekrar/boşa çökme olmaz). */
|
|
113
|
+
function httpError(status, statusText, detail, headers) {
|
|
114
|
+
const err = new Error(friendlyError(status, statusText, detail));
|
|
115
|
+
err.status = status;
|
|
116
|
+
if (isUsageLimitDetail(detail)) err.usageLimit = true;
|
|
117
|
+
if (isTransientDetail(status, detail)) err.transient = true;
|
|
118
|
+
err.retryAfterMs = parseRetryAfter(headers && (headers.get('retry-after-ms') || headers.get('retry-after')));
|
|
119
|
+
return err;
|
|
120
|
+
}
|
|
121
|
+
|
|
78
122
|
/* opencode cache disiplini (provider/transform.ts:1262-1276 port): destekleyen
|
|
79
123
|
sağlayıcılara oturum-sabit önbellek anahtarı gönderilir — aynı önek tekrar
|
|
80
124
|
kullanıldığında girdi tokenleri cache'ten okunur (~%90 ucuz + hızlı TTFT) */
|
|
@@ -302,9 +346,15 @@ async function withRetries(fn, { signal, onRetry } = {}) {
|
|
|
302
346
|
return await fn();
|
|
303
347
|
} catch (e) {
|
|
304
348
|
const aborted = e && (e.name === 'AbortError' || (signal && signal.aborted));
|
|
349
|
+
if (aborted) throw e;
|
|
350
|
+
/* AYLIK KOTA DOLDU: tekrar denemek boşa — hemen fırlat ki motor
|
|
351
|
+
FALLOUT ile sıradaki sağlayıcıya/modele geçebilsin. */
|
|
352
|
+
if (e && e.usageLimit) throw e;
|
|
305
353
|
const status = e && e.status;
|
|
306
|
-
const netErr =
|
|
307
|
-
|
|
354
|
+
const netErr = status === undefined;
|
|
355
|
+
/* 400 gövdesinde "server_error/upstream" diyen sağlayıcı hataları geçicidir
|
|
356
|
+
(transient) — kalıcı 400 gibi davranıp modeli boşuna "çökertme". */
|
|
357
|
+
const retriable = status === undefined || RETRYABLE_STATUS.has(status) || !!(e && e.transient);
|
|
308
358
|
if (!retriable) throw e;
|
|
309
359
|
const cap = netErr ? NET_MAX_RETRIES : MAX_RETRIES;
|
|
310
360
|
if (attempt >= cap) throw e;
|
|
@@ -409,12 +459,7 @@ async function streamOnce(sel, body, { signal, onDelta, onRetry } = {}, drops =
|
|
|
409
459
|
try {
|
|
410
460
|
detail = (await r.text()).slice(0, 300);
|
|
411
461
|
} catch {}
|
|
412
|
-
|
|
413
|
-
err.status = r.status;
|
|
414
|
-
err.retryAfterMs = parseRetryAfter(
|
|
415
|
-
r.headers && (r.headers.get('retry-after-ms') || r.headers.get('retry-after'))
|
|
416
|
-
);
|
|
417
|
-
throw err;
|
|
462
|
+
throw httpError(r.status, r.statusText, detail, r.headers);
|
|
418
463
|
}
|
|
419
464
|
return r;
|
|
420
465
|
},
|
|
@@ -590,12 +635,7 @@ async function chatOnce(sel, body, { signal, onRetry, onParamDrop, onEffortDowng
|
|
|
590
635
|
try {
|
|
591
636
|
detail = (await r.text()).slice(0, 300);
|
|
592
637
|
} catch {}
|
|
593
|
-
|
|
594
|
-
err.status = r.status;
|
|
595
|
-
err.retryAfterMs = parseRetryAfter(
|
|
596
|
-
r.headers && (r.headers.get('retry-after-ms') || r.headers.get('retry-after'))
|
|
597
|
-
);
|
|
598
|
-
throw err;
|
|
638
|
+
throw httpError(r.status, r.statusText, detail, r.headers);
|
|
599
639
|
}
|
|
600
640
|
return r;
|
|
601
641
|
},
|
package/src/main.js
CHANGED
|
@@ -9400,6 +9400,13 @@ function finCfg() {
|
|
|
9400
9400
|
/* GÜNLÜK RUTİN: plan/review saatleri (HH:MM; boş = kapalı) — hafta içi cron */
|
|
9401
9401
|
if (typeof f.planTime !== 'string') f.planTime = '';
|
|
9402
9402
|
if (typeof f.reviewTime !== 'string') f.reviewTime = '';
|
|
9403
|
+
/* TRADE SAATLERİ: varsayılan KAPALI (7/24). Açıkken Beast Finance ve tüm
|
|
9404
|
+
ajanları YALNIZ [start,end) aralığında çalışır — makinenin YEREL saati;
|
|
9405
|
+
varsayılan aralık 09:00-22:00; gece aralığı desteklenir (22:00 → 06:00). */
|
|
9406
|
+
if (!f.tradeHours || typeof f.tradeHours !== 'object') f.tradeHours = {};
|
|
9407
|
+
if (typeof f.tradeHours.on !== 'boolean') f.tradeHours.on = false;
|
|
9408
|
+
if (!/^([01]?\d|2[0-3]):([0-5]\d)$/.test(String(f.tradeHours.start || '').trim())) f.tradeHours.start = '09:00';
|
|
9409
|
+
if (!/^([01]?\d|2[0-3]):([0-5]\d)$/.test(String(f.tradeHours.end || '').trim())) f.tradeHours.end = '22:00';
|
|
9403
9410
|
/* RİSK OTOMASYONU (watchdog): +R'da BE, trailing, kısmi TP — main süreç
|
|
9404
9411
|
saniyelik döngüyle uygular; ajan turunu BEKLEMEZ. 0 = ilgili kural kapalı. */
|
|
9405
9412
|
if (typeof f.watchdog !== 'boolean') f.watchdog = true;
|
|
@@ -9446,6 +9453,26 @@ function finCfg() {
|
|
|
9446
9453
|
return f;
|
|
9447
9454
|
}
|
|
9448
9455
|
|
|
9456
|
+
/* ---- TRADE SAATLERİ KAPISI (makinenin YEREL saati) ----
|
|
9457
|
+
tradeHours.on true iken ajan turları ve olay uyandırmaları yalnız
|
|
9458
|
+
[start,end) aralığında çalışır; gece aralığı desteklenir (ör. 22:00 →
|
|
9459
|
+
06:00). Kapalıyken (varsayılan) 7/24 serbesttir. Açık pozisyon koruması
|
|
9460
|
+
(watchdog: BE/trailing/kısmi TP) bu kapıdan ETKİLENMEZ — risk güvenliği
|
|
9461
|
+
her saat sürer; kapı yalnız ajan ÜRETİMİNİ (tur/karar/uyandırma) kısar.
|
|
9462
|
+
Karar katmanı saf fonksiyondur: finwatch.tradeHoursOpen (birim testli). */
|
|
9463
|
+
function finTradeHoursOpen(now) {
|
|
9464
|
+
return finwatch.tradeHoursOpen(finCfg().tradeHours, now);
|
|
9465
|
+
}
|
|
9466
|
+
/* Kapı kaynaklı ertelemeler günlüğe SEYREK düşer (10 dk'da en fazla 1 satır) */
|
|
9467
|
+
function finTradeHoursBlockedLog(what) {
|
|
9468
|
+
const now = Date.now();
|
|
9469
|
+
if (now - Number(financeState.tradeHoursLogAt || 0) < 10 * 60 * 1000) return false;
|
|
9470
|
+
financeState.tradeHoursLogAt = now;
|
|
9471
|
+
const th = finCfg().tradeHours || {};
|
|
9472
|
+
financeLog(`[saat] trade saatleri dışı (${th.start || '09:00'}-${th.end || '22:00'} yerel) — ${what} ertelendi`);
|
|
9473
|
+
return true;
|
|
9474
|
+
}
|
|
9475
|
+
|
|
9449
9476
|
/* ANALİZ EKİBİ: trader'ın yanında koşan uzman ajan rolleri — seçilen her rol
|
|
9450
9477
|
AYRI bir sürekli finance ajanı açar. Hepsi tüm SKILL'lere + mt5 okuma
|
|
9451
9478
|
araçlarına erişir (finance oturumu oldukları için), İŞLEM AÇMAZLAR.
|
|
@@ -9943,6 +9970,21 @@ function finAlertApi() {
|
|
|
9943
9970
|
}
|
|
9944
9971
|
return false;
|
|
9945
9972
|
},
|
|
9973
|
+
/* TOPLU TEMİZLİK: alarmı KURAN ajan kendi gereksiz alarmlarını siler.
|
|
9974
|
+
Varsayılan kapsam: yalnız bu oturumun (sid) kurduğu alarmlar; symbol
|
|
9975
|
+
verilirse sembole daralır; ids açıkça verilirse sahiplik aranmaz;
|
|
9976
|
+
all:true → sahiplik filtresi kalkar (tüm finance alarmları). */
|
|
9977
|
+
clear: (opts) => {
|
|
9978
|
+
const o = opts || {};
|
|
9979
|
+
const ids = finwatch.pickAlarms(financeState.alerts, o);
|
|
9980
|
+
if (!ids.length) return { removed: 0, ids: [] };
|
|
9981
|
+
const drop = new Set(ids);
|
|
9982
|
+
financeState.alerts = financeState.alerts.filter((a) => !drop.has(String(a.id)));
|
|
9983
|
+
finAlertsSave();
|
|
9984
|
+
const scope = Array.isArray(o.ids) && o.ids.length ? 'verilen id\'ler' : o.all ? 'tüm alarmlar' : 'ajanın kendi alarmları';
|
|
9985
|
+
financeLog(`[alarm] temizlendi: ${ids.length} adet (${scope}${o.symbol ? ' · ' + String(o.symbol).toUpperCase() : ''})`);
|
|
9986
|
+
return { removed: ids.length, ids };
|
|
9987
|
+
},
|
|
9946
9988
|
};
|
|
9947
9989
|
}
|
|
9948
9990
|
|
|
@@ -10367,6 +10409,12 @@ function finWakeAgentDm(sid, text) {
|
|
|
10367
10409
|
const id = String(sid || '');
|
|
10368
10410
|
const agent = financeState.agents.get(id);
|
|
10369
10411
|
if (!agent || !engine) return false;
|
|
10412
|
+
/* TRADE SAATLERİ KAPISI: aralık dışında olay ajanı UYANDIRMAZ (panel/kanal
|
|
10413
|
+
bildirimi düşmeye devam eder; tur pencere açılınca kendiliğinden başlar) */
|
|
10414
|
+
if (!finTradeHoursOpen()) {
|
|
10415
|
+
finTradeHoursBlockedLog('olay uyandırması');
|
|
10416
|
+
return false;
|
|
10417
|
+
}
|
|
10370
10418
|
try {
|
|
10371
10419
|
const body = agent.role
|
|
10372
10420
|
? String(text || '') + "\n(ROL HATIRLATMASI: işlem AÇMA — durumu analiz et; gerekiyorsa agent_dm ile ANA TRADER'a bildir.)"
|
|
@@ -10527,11 +10575,12 @@ async function finCheckAlerts() {
|
|
|
10527
10575
|
sahip koşmuyor/biliniyorsa koşan tüm finance ajanlarına düşer. */
|
|
10528
10576
|
const wakeTxt =
|
|
10529
10577
|
`[FİNANS OLAYI — FİYAT ALARMI${tekrar}, TUR SANİYESİ BEKLENMEDEN İLETİLDİ]\n` +
|
|
10530
|
-
`- ${a.symbol} ${a.direction === 'below' ? '≤' : '≥'} ${a.price} tetiklendi (şimdi ${p.bid})${a.note ? ' — ' + a.note : ''}${cdTxt}\n` +
|
|
10578
|
+
`- ${a.symbol} ${a.direction === 'below' ? '≤' : '≥'} ${a.price} tetiklendi (şimdi ${p.bid})${a.note ? ' — ' + a.note : ''}${cdTxt} (id: ${a.id})\n` +
|
|
10531
10579
|
(a.once
|
|
10532
10580
|
? 'Alarm TEK SEFERLİKTİ ve kapandı. '
|
|
10533
10581
|
: `Alarm TEKRARLI açık kalıyor${a.cooldownMin > 0 ? ` — koşul sürerse en erken ${a.cooldownMin} dk sonra yeniden uyarır` : ''}. `) +
|
|
10534
|
-
'Şimdi yap: alarmı kurma nedenini hatırla; fiyat seviyesini ve planını değerlendir, gerekiyorsa işlem/uyarı üret.
|
|
10582
|
+
'Şimdi yap: alarmı kurma nedenini hatırla; fiyat seviyesini ve planını değerlendir, gerekiyorsa işlem/uyarı üret. ' +
|
|
10583
|
+
'TEMİZLİK: alarmın görevi bittiyse (tez geçersiz, pozisyon kapandı, seviye anlamsızlaştı) mt5_alerts action:"remove" {id} ya da action:"clear" ile SİL — gereksiz alarm biriktirme. Kısa rapor ver.';
|
|
10535
10584
|
if (!finWakeAgentDm(a.sid, wakeTxt)) finWakeAgents(wakeTxt, { kind: 'alarm', symbol: a.symbol });
|
|
10536
10585
|
}
|
|
10537
10586
|
if (changed) finAlertsSave();
|
|
@@ -11453,6 +11502,10 @@ function finTraderBrief(agent) {
|
|
|
11453
11502
|
'finance-review' job'ları tetikler. Trader koşmuyorsa sessizce atlanır. */
|
|
11454
11503
|
function finDailyRoutine(mode) {
|
|
11455
11504
|
try {
|
|
11505
|
+
if (!finTradeHoursOpen()) {
|
|
11506
|
+
financeLog('[rutin] trade saatleri dışı — günlük ' + mode + ' atlandı');
|
|
11507
|
+
return;
|
|
11508
|
+
}
|
|
11456
11509
|
let mainSid = '';
|
|
11457
11510
|
for (const [sid, a] of financeState.agents) {
|
|
11458
11511
|
if (a && a.main) { mainSid = String(sid); break; }
|
|
@@ -11554,6 +11607,15 @@ async function finConsultPlan(f, agent, sid) {
|
|
|
11554
11607
|
function finAgentRound(sid) {
|
|
11555
11608
|
const agent = financeState.agents.get(String(sid));
|
|
11556
11609
|
if (!agent || !engine) return;
|
|
11610
|
+
/* TRADE SAATLERİ KAPISI: aralık dışında YENİ TUR BAŞLATILMAZ — ajan askıya
|
|
11611
|
+
alınır; pencere açılınca 60 sn içinde kaldığı yerden devam eder */
|
|
11612
|
+
if (!finTradeHoursOpen()) {
|
|
11613
|
+
finTradeHoursBlockedLog(`tur #${(Number(agent.round) || 0) + 1}`);
|
|
11614
|
+
clearTimeout(agent.timer);
|
|
11615
|
+
agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, 60000);
|
|
11616
|
+
if (agent.main) finPush('trader', { state: 'hours', round: agent.round, nextInSec: 60 });
|
|
11617
|
+
return;
|
|
11618
|
+
}
|
|
11557
11619
|
if (engine.isBusy(sid)) {
|
|
11558
11620
|
/* hâlâ çalışıyor — done eventinde tekrar planlanır */
|
|
11559
11621
|
return;
|
|
@@ -11616,6 +11678,12 @@ function finWakeAgent(sid) {
|
|
|
11616
11678
|
const id = String(sid || '');
|
|
11617
11679
|
const agent = financeState.agents.get(id);
|
|
11618
11680
|
if (!agent || !engine) return;
|
|
11681
|
+
/* TRADE SAATLERİ KAPISI: aralık dışında DM uyandırması tur AÇMAZ; mesaj
|
|
11682
|
+
inbox'ta bekler, pencere açılınca okunur */
|
|
11683
|
+
if (!finTradeHoursOpen()) {
|
|
11684
|
+
finTradeHoursBlockedLog('DM uyandırması');
|
|
11685
|
+
return;
|
|
11686
|
+
}
|
|
11619
11687
|
if (engine.isBusy(id)) {
|
|
11620
11688
|
/* tur sürüyor: olay dmInbox'ta bekler — tur bitince finFlushOnDone
|
|
11621
11689
|
BEKLEMEDEN yeni tur açar (interval beklenmez) */
|
|
@@ -11903,6 +11971,20 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
|
|
|
11903
11971
|
if (p.shadowMode !== undefined) f.shadowMode = !!p.shadowMode;
|
|
11904
11972
|
if (p.planTime !== undefined) f.planTime = /^([01]?\d|2[0-3]):([0-5]\d)$/.test(String(p.planTime || '').trim()) ? String(p.planTime).trim() : '';
|
|
11905
11973
|
if (p.reviewTime !== undefined) f.reviewTime = /^([01]?\d|2[0-3]):([0-5]\d)$/.test(String(p.reviewTime || '').trim()) ? String(p.reviewTime).trim() : '';
|
|
11974
|
+
/* TRADE SAATLERİ: {on, start, end} — yerel saat; aralık dışında ajan turları
|
|
11975
|
+
ve olay uyandırmaları durur (varsayılan kapalı, aralık 09:00-22:00) */
|
|
11976
|
+
if (p.tradeHours !== undefined && p.tradeHours && typeof p.tradeHours === 'object') {
|
|
11977
|
+
const th = f.tradeHours && typeof f.tradeHours === 'object' ? f.tradeHours : {};
|
|
11978
|
+
if (typeof p.tradeHours.on === 'boolean') th.on = p.tradeHours.on;
|
|
11979
|
+
for (const k of ['start', 'end']) {
|
|
11980
|
+
if (p.tradeHours[k] !== undefined) {
|
|
11981
|
+
const v = String(p.tradeHours[k] || '').trim().slice(0, 5);
|
|
11982
|
+
if (/^([01]?\d|2[0-3]):([0-5]\d)$/.test(v)) th[k] = v;
|
|
11983
|
+
}
|
|
11984
|
+
}
|
|
11985
|
+
f.tradeHours = th;
|
|
11986
|
+
if (th.on && !finTradeHoursOpen()) financeLog('[saat] trade saatleri açık — ajanlar ' + th.start + '-' + th.end + ' aralığında çalışacak (yerel saat)');
|
|
11987
|
+
}
|
|
11906
11988
|
if (p.pythonPath !== undefined) f.pythonPath = String(p.pythonPath || '').trim();
|
|
11907
11989
|
if (p.terminalPath !== undefined) f.terminalPath = String(p.terminalPath || '').trim();
|
|
11908
11990
|
if (p.traderSel !== undefined) {
|
package/src/renderer/index.html
CHANGED
|
@@ -892,6 +892,20 @@
|
|
|
892
892
|
</label>
|
|
893
893
|
<div class="fin-set-note">RİSK OTOMASYONU gizliyken koruma döngüsü (watchdog) kapatılır; tekrar gösterdiğinde ilgili kutucuktan açabilirsin.</div>
|
|
894
894
|
<div class="fin-set-sep"></div>
|
|
895
|
+
<label class="fin-set-row" title="Varsayılan KAPALI: kapalıyken ajanlar 7/24 çalışır. Açıkken Beast Finance ve tüm ajanları YALNIZ belirlenen saat aralığında çalışır — aralık dışında yeni tur başlatılmaz ve olaylar ajanı uyandırmaz (makinenin YEREL saati). Açık pozisyon koruması (BE/trailing/kısmi TP) her saat çalışmaya devam eder.">
|
|
896
|
+
<input id="finTradeHoursOn" type="checkbox" />
|
|
897
|
+
<span class="fsr-main"><b>TRADE SAATLERİ</b><i>Kapalıyken 7/24 · açıkken ajanlar yalnız bu aralıkta</i></span>
|
|
898
|
+
</label>
|
|
899
|
+
<div id="finTradeHoursWrap" class="fin-set-sub" hidden>
|
|
900
|
+
<span class="fin-set-lbl">BAŞLANGIÇ / BİTİŞ (YEREL SAAT)</span>
|
|
901
|
+
<div class="fin-th-row">
|
|
902
|
+
<input id="finTradeHoursStart" type="time" step="60" value="09:00" />
|
|
903
|
+
<span class="fin-set-lbl">→</span>
|
|
904
|
+
<input id="finTradeHoursEnd" type="time" step="60" value="22:00" />
|
|
905
|
+
</div>
|
|
906
|
+
<div class="fin-set-note" style="margin:0">Aralık dışında ajan turları askıya alınır, olay alarmları ajanı uyandırmaz; pencere açılıp kapanınca kaldığı yerden devam eder. Gece aralığı da verilebilir (ör. 22:00 → 06:00).</div>
|
|
907
|
+
</div>
|
|
908
|
+
<div class="fin-set-sep"></div>
|
|
895
909
|
<label class="fin-set-row" title="Açıkken botu eklediğin Telegram grubuna yazılan ilk mesaj o grubu AJAN DM aynası yapar. Her AJAN DM grubu Telegram'da AYRI BAŞLIK (topic) olarak canlı akar; başlığa yazdıkların ilgili AJAN DM grubunda görünür ve o grubun ajanları uyanır. Grup/Konu seçmene gerek yok.">
|
|
896
910
|
<input id="finDmTgOn" type="checkbox" />
|
|
897
911
|
<span class="fsr-main"><b>AJAN DM ↔ TELEGRAM</b><i>Her ajan grubu Telegram'da ayrı başlık — canlı</i></span>
|
package/src/renderer/renderer.js
CHANGED
|
@@ -208,6 +208,10 @@ const els = {
|
|
|
208
208
|
finDmTgState: $('#finDmTgState'),
|
|
209
209
|
finDmTgUnbind: $('#finDmTgUnbind'),
|
|
210
210
|
finDmTgHint: $('#finDmTgHint'),
|
|
211
|
+
finTradeHoursOn: $('#finTradeHoursOn'),
|
|
212
|
+
finTradeHoursWrap: $('#finTradeHoursWrap'),
|
|
213
|
+
finTradeHoursStart: $('#finTradeHoursStart'),
|
|
214
|
+
finTradeHoursEnd: $('#finTradeHoursEnd'),
|
|
211
215
|
finRiskCard: $('#finRiskCard'),
|
|
212
216
|
finNotifyCard: $('#finNotifyCard'),
|
|
213
217
|
finPerfCard: $('#finPerfCard'),
|
|
@@ -9997,6 +10001,7 @@ function finRenderAutomation(cfg, watch) {
|
|
|
9997
10001
|
if (els.finWatchInfo) {
|
|
9998
10002
|
els.finWatchInfo.textContent = watch && watch.on ? '· izlenen ' + (watch.managed || 0) : '';
|
|
9999
10003
|
}
|
|
10004
|
+
finTradeHoursPaint();
|
|
10000
10005
|
}
|
|
10001
10006
|
|
|
10002
10007
|
function finSparkline(points) {
|
|
@@ -10158,6 +10163,7 @@ function finOnEvent(ev) {
|
|
|
10158
10163
|
} else if (ev.fn === 'trader') {
|
|
10159
10164
|
if (ev.state === 'running') finLogLine('[trader] tur #' + (ev.round || 0) + ' başladı');
|
|
10160
10165
|
else if (ev.state === 'idle') finLogLine('[trader] tur #' + (ev.round || 0) + ' bitti — sıradaki tur ~' + (ev.nextInSec || '?') + ' sn');
|
|
10166
|
+
else if (ev.state === 'hours') finLogLine('[trader] trade saatleri dışı — ajan askıda (kontrol ~' + (ev.nextInSec || 60) + ' sn)');
|
|
10161
10167
|
else if (ev.state === 'stopped') finLogLine('[trader] durduruldu');
|
|
10162
10168
|
} else if (ev.fn === 'install') {
|
|
10163
10169
|
finLogLine('[MT5] paket kurulumu tamamlandı (kod ' + ev.code + ')');
|
|
@@ -10319,11 +10325,37 @@ function finDmTgPaint() {
|
|
|
10319
10325
|
function finSettingsOpen() {
|
|
10320
10326
|
if (els.finSettingsOverlay) els.finSettingsOverlay.hidden = false;
|
|
10321
10327
|
finDmTgPaint();
|
|
10328
|
+
finTradeHoursPaint();
|
|
10322
10329
|
}
|
|
10323
10330
|
function finSettingsCloseModal() {
|
|
10324
10331
|
if (els.finSettingsOverlay) els.finSettingsOverlay.hidden = true;
|
|
10325
10332
|
}
|
|
10326
10333
|
|
|
10334
|
+
/* ---------- TRADE SAATLERİ (görünüm ayarları modalı) ----------
|
|
10335
|
+
Ayarlar sunucuda (settings.finance.tradeHours) yaşar: on + start/end.
|
|
10336
|
+
Kapalıyken ajanlar 7/24; açıkken yalnız [start,end) aralığında çalışır
|
|
10337
|
+
(makinenin yerel saati). Açık pozisyon koruması her saat sürer. */
|
|
10338
|
+
function finTradeHoursPaint() {
|
|
10339
|
+
if (!els.finTradeHoursOn) return;
|
|
10340
|
+
const th = (finCfgCache && finCfgCache.tradeHours) || {};
|
|
10341
|
+
const on = th.on === true;
|
|
10342
|
+
els.finTradeHoursOn.checked = on;
|
|
10343
|
+
const row = els.finTradeHoursOn.closest('.fin-set-row');
|
|
10344
|
+
if (row) row.classList.toggle('on', on);
|
|
10345
|
+
if (els.finTradeHoursWrap) els.finTradeHoursWrap.hidden = !on;
|
|
10346
|
+
const ae = document.activeElement;
|
|
10347
|
+
if (els.finTradeHoursStart && ae !== els.finTradeHoursStart) els.finTradeHoursStart.value = th.start || '09:00';
|
|
10348
|
+
if (els.finTradeHoursEnd && ae !== els.finTradeHoursEnd) els.finTradeHoursEnd.value = th.end || '22:00';
|
|
10349
|
+
}
|
|
10350
|
+
function finTradeHoursSave() {
|
|
10351
|
+
const on = !!(els.finTradeHoursOn && els.finTradeHoursOn.checked);
|
|
10352
|
+
const start = String((els.finTradeHoursStart && els.finTradeHoursStart.value) || '09:00').slice(0, 5) || '09:00';
|
|
10353
|
+
const end = String((els.finTradeHoursEnd && els.finTradeHoursEnd.value) || '22:00').slice(0, 5) || '22:00';
|
|
10354
|
+
if (finCfgCache) finCfgCache.tradeHours = { on, start, end };
|
|
10355
|
+
finSaveCfg({ tradeHours: { on, start, end } });
|
|
10356
|
+
return { on, start, end };
|
|
10357
|
+
}
|
|
10358
|
+
|
|
10327
10359
|
function finViewToggle(key, on) {
|
|
10328
10360
|
finView[key] = !!on;
|
|
10329
10361
|
finViewApply();
|
|
@@ -10373,6 +10405,24 @@ if (els.finDmTgUnbind) {
|
|
|
10373
10405
|
toast('Telegram grubu koparıldı — yeni grupta /start yazınca bağlanır');
|
|
10374
10406
|
});
|
|
10375
10407
|
}
|
|
10408
|
+
/* TRADE SAATLERİ kontrolleri — anında kaydedilir (yerel saat) */
|
|
10409
|
+
if (els.finTradeHoursOn) {
|
|
10410
|
+
els.finTradeHoursOn.addEventListener('change', () => {
|
|
10411
|
+
const { on, start, end } = finTradeHoursSave();
|
|
10412
|
+
finTradeHoursPaint();
|
|
10413
|
+
toast(on
|
|
10414
|
+
? 'Trade saatleri AÇIK — Beast Finance ve ajanları yalnız ' + start + '–' + end + ' arasında çalışır (yerel saat)'
|
|
10415
|
+
: 'Trade saatleri kapalı — ajanlar 7/24 serbest');
|
|
10416
|
+
});
|
|
10417
|
+
}
|
|
10418
|
+
for (const el of [els.finTradeHoursStart, els.finTradeHoursEnd]) {
|
|
10419
|
+
if (!el) continue;
|
|
10420
|
+
el.addEventListener('change', () => {
|
|
10421
|
+
const { on, start, end } = finTradeHoursSave();
|
|
10422
|
+
if (on) toast('Trade saatleri: ' + start + '–' + end + ' (yerel saat)');
|
|
10423
|
+
});
|
|
10424
|
+
}
|
|
10425
|
+
finTradeHoursPaint();
|
|
10376
10426
|
finViewApply();
|
|
10377
10427
|
|
|
10378
10428
|
/* ---------- BÖLÜM SIRALAMA (sürükle-bırak) ----------
|
package/src/renderer/style.css
CHANGED
|
@@ -5882,6 +5882,8 @@ body.finance-mode #composerWrap { margin-right: var(--finW, 400px); }
|
|
|
5882
5882
|
}
|
|
5883
5883
|
.fin-set-sub[hidden] { display: none; }
|
|
5884
5884
|
.fin-set-lbl { font-size: 10.5px; color: var(--muted); letter-spacing: 0.5px; }
|
|
5885
|
+
.fin-th-row { display: flex; align-items: center; gap: 8px; }
|
|
5886
|
+
.fin-th-row input[type="time"] { flex: 1; min-width: 0; }
|
|
5885
5887
|
.fin-dm-tg-state { font-size: 11.5px; color: var(--text); line-height: 1.4; word-break: break-word; }
|
|
5886
5888
|
.fin-set-btn {
|
|
5887
5889
|
align-self: flex-start;
|