beast-agent 2.54.0 → 2.56.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/defaulttools/mt5_kapat/run.js +18 -2
- package/src/agent/defaulttools/mt5_kapat/tool.json +5 -3
- package/src/agent/engine.js +10 -7
- package/src/agent/financetools.js +78 -12
- package/src/agent/finwatch.js +13 -1
- package/src/agent/watchers.js +13 -4
- package/src/main.js +112 -20
- package/src/preload.js +2 -0
- package/src/renderer/index.html +6 -2
- package/src/renderer/renderer.js +90 -5
- package/src/renderer/style.css +2 -0
package/package.json
CHANGED
|
@@ -76,12 +76,28 @@ function call(requests) {
|
|
|
76
76
|
try { args = JSON.parse(fs.readFileSync(0, 'utf8') || '{}'); } catch (e) {}
|
|
77
77
|
const ticket = Number(args.ticket || 0);
|
|
78
78
|
if (!ticket) return console.log(JSON.stringify({ ok: false, error: 'ticket zorunlu' }));
|
|
79
|
+
/* KISMİ KAPATMA (kısmi TP / kısmi stop): percent verilirse pozisyon
|
|
80
|
+
hacminden hesaplanır; hacim verilmezse tamamı kapanır. Kalan kısım
|
|
81
|
+
broker minimumunun (0.01) altındaysa tamamı kapatılır. */
|
|
82
|
+
const percent = Math.max(0, Math.min(100, Number(args.percent) || 0));
|
|
83
|
+
let volume = Number(args.volume || 0);
|
|
84
|
+
let pvol = 0;
|
|
85
|
+
if (percent > 0 && !(volume > 0)) {
|
|
86
|
+
const pr = await call([{ method: 'positions', params: {} }]);
|
|
87
|
+
const list = (pr.ok && pr.data && pr.data.positions) || [];
|
|
88
|
+
const pos = list.find((p) => Number(p.ticket) === ticket);
|
|
89
|
+
if (!pos) return console.log(JSON.stringify({ ok: false, error: 'pozisyon bulunamadi: ' + ticket }));
|
|
90
|
+
pvol = Number(pos.volume) || 0;
|
|
91
|
+
volume = Math.floor(((pvol * percent) / 100) * 100) / 100;
|
|
92
|
+
if (!(volume > 0)) return console.log(JSON.stringify({ ok: false, error: 'percent sonucu hacim sifir cikti (pozisyon ' + pvol + ' lot)' }));
|
|
93
|
+
if (pvol - volume > 0 && pvol - volume < 0.01 - 1e-9) volume = pvol; /* kalan minimumun altinda → tamami */
|
|
94
|
+
}
|
|
79
95
|
const params = { ticket: ticket };
|
|
80
|
-
if (
|
|
96
|
+
if (volume > 0) params.volume = volume;
|
|
81
97
|
const r = await call([{ method: 'close', params: params }]);
|
|
82
98
|
if (r.ok && r.data && r.data.result) {
|
|
83
99
|
const res = r.data.result;
|
|
84
|
-
console.log(JSON.stringify({ ok: true, retcode: res.retcode, comment: res.comment, detay: r.data }));
|
|
100
|
+
console.log(JSON.stringify({ ok: true, retcode: res.retcode, comment: res.comment, kismi: pvol > 0 && volume < pvol, kapatilan: volume || 'tamami', detay: r.data }));
|
|
85
101
|
} else {
|
|
86
102
|
console.log(JSON.stringify({ ok: false, error: r.error || 'kapatma reddedildi' }));
|
|
87
103
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mt5_kapat",
|
|
3
|
-
"description": "Açık pozisyonu
|
|
3
|
+
"description": "Açık pozisyonu kapatır — TAM ya da KISMİ: ticket zorunlu; percent (pozisyonun % kaçı, ör. 50) ya da volume (lot) verilebilir. Kısmi kapatma = kısmi TP (kâr al) / kısmi stop (zarar kes) — kararı ajan verir, otomatik değildir. Kalan kısım SL/TP ile devam eder; kalan lot 0.01 altında kalırsa tamamı kapanır. Başarıda kapanış sonucu döner.",
|
|
4
4
|
"parameters": {
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
|
7
7
|
"ticket": { "type": "number", "description": "Kapatilacak pozisyonun ticket numarasi" },
|
|
8
|
-
"
|
|
8
|
+
"percent": { "type": "number", "description": "Kapatilacak yuzde (ör. 50) — kısmi TP/kısmi stop icin" },
|
|
9
|
+
"volume": { "type": "number", "description": "Kapatilacak lot (percent yerine; ikisi de yoksa tamami kapanir)" }
|
|
9
10
|
},
|
|
10
11
|
"required": ["ticket"]
|
|
11
12
|
},
|
|
12
|
-
"
|
|
13
|
+
"defaultsVersion": 2,
|
|
14
|
+
"updatedAt": "2026-09-15T15:00:00.000Z"
|
|
13
15
|
}
|
package/src/agent/engine.js
CHANGED
|
@@ -1957,20 +1957,20 @@ class Engine {
|
|
|
1957
1957
|
const rules = memory.listRules();
|
|
1958
1958
|
const isWorker = !!(session && session.bgJob && !isTrader);
|
|
1959
1959
|
const modeBlock2 = isWorker
|
|
1960
|
-
? 'ROL: FİNANS İŞÇİSİ 🔎 — TEK bir görev için piyasa verisi toplar, analiz eder, RAPOR döndürürsün. mt5_trade/mt5_pending
|
|
1960
|
+
? 'ROL: FİNANS İŞÇİSİ 🔎 — TEK bir görev için piyasa verisi toplar, analiz eder, RAPOR döndürürsün. mt5_trade/mt5_pending KULLANMA (yeni işlem AÇMA) — açık pozisyonlarda kısmi kapatma (mt5_close percent: kısmi TP/kısmi stop) ve SL/TP güncelleme (mt5_modify) SERBEST; okuma araçları (mt5_status/account/market/positions/orders/history) her zaman açık. İş bitince kısa final raporu yaz.'
|
|
1961
1961
|
: modeBlock;
|
|
1962
1962
|
/* ANALİZ EKİBİ rolü: trader'ın yanında koşan uzman ajanlar (risk/teknik/
|
|
1963
1963
|
makro) — hepsi İşlem AÇMAZ, kendi uzmanlık çerçevesinde rapor yazar */
|
|
1964
1964
|
const roleBlock =
|
|
1965
1965
|
({
|
|
1966
1966
|
risk:
|
|
1967
|
-
'ROL: RİSK AJANI 🛡 — portföyün RİSK GÖZCÜSÜSÜN, İşlem AÇMAZSIN (mt5_trade/mt5_pending
|
|
1967
|
+
'ROL: RİSK AJANI 🛡 — portföyün RİSK GÖZCÜSÜSÜN, İşlem AÇMAZSIN (mt5_trade/mt5_pending KULLANMA — yeni işlem AÇMAZSIN; açık pozisyonda kısmi kapatma (mt5_close percent) ve SL/TP güncelleme (mt5_modify) SERBEST). Her turda: hesap + pozisyonlar + marj kullanımı → kaldıraç/exposure değerlendirmesi; SL\u2019siz pozisyon, limit aşımı, tek yönlü birikme, günlük kayıp hızı risklerini raporla; net düzeltme önerisi ver (hangi pozisyon küçültülmeli/kapatılmalı).',
|
|
1968
1968
|
technic:
|
|
1969
|
-
'ROL: TEKNİK ANALİZ AJANI 📊 — fiyat yapısı uzmanısın, İşlem AÇMAZSIN (mt5_trade/mt5_pending
|
|
1969
|
+
'ROL: TEKNİK ANALİZ AJANI 📊 — fiyat yapısı uzmanısın, İşlem AÇMAZSIN (mt5_trade/mt5_pending KULLANMA — yeni işlem AÇMAZSIN; açık pozisyonda kısmi kapatma (mt5_close percent) ve SL/TP güncelleme (mt5_modify) SERBEST). Her turda odak semboller için: trend/yapı, destek-direnç bölgeleri, momentum (mt5_market + mt5_history verisiyle); sembol başına AL/SAT/BEKLE + giriş/SL/TP fikri üret — trader bu öneriyi işleme çevirir. Bulgularını mt5_ea action:"note" ile grafiğe ÇİZ (destek/direnç/SL/TP seviyeleri) ve tool__mt5_shot ile seviyelerin göründüğünü teyit et; önemli seviyeleri görselle birlikte agent_dm ile trader\'a/ekibe ilet — grafik üzerinde ortak karar alın.',
|
|
1970
1970
|
macro:
|
|
1971
|
-
'ROL: MAKRO AJANI 🌍 — büyük resim uzmanısın, İşlem AÇMAZSIN (mt5_trade/mt5_pending
|
|
1971
|
+
'ROL: MAKRO AJANI 🌍 — büyük resim uzmanısın, İşlem AÇMAZSIN (mt5_trade/mt5_pending KULLANMA — yeni işlem AÇMAZSIN; açık pozisyonda kısmi kapatma (mt5_close percent) ve SL/TP güncelleme (mt5_modify) SERBEST). Her turda web_search ile güncel makro manşetleri + ekonomik takvim riskleri (faiz, CPI, jeopolitik); DXY/altın/petrol bağıntılarını odak sembollere çevir; sembol başına yön eğilimi + TEMKİN/BEKLE notu ver.',
|
|
1972
1972
|
visual:
|
|
1973
|
-
'ROL: GÖRSEL ANALİZ AJANI 👁 — grafik/ekran görüntüsü uzmanısın, İşlem AÇMAZSIN (mt5_trade/mt5_pending
|
|
1973
|
+
'ROL: GÖRSEL ANALİZ AJANI 👁 — grafik/ekran görüntüsü uzmanısın, İşlem AÇMAZSIN (mt5_trade/mt5_pending KULLANMA — yeni işlem AÇMAZSIN; açık pozisyonda kısmi kapatma (mt5_close percent) ve SL/TP güncelleme (mt5_modify) SERBEST). MT5 grafiği için ÖNCE tool__mt5_shot kullan (BeastFinance EA "shot": gerçek grafik PNG + görsel SONRAKİ TURDA gözüne gelir; symbol/timeframe vererek başka sembol/periyot çekebilirsin; dönen path send_file ile gönderilebilir); gerekirse EA panosuna mt5_ea action:"note" ile plan/seviye yaz. ÇOKLU SEMBOL: symbols:["GOLD","EURUSD","BTCUSD"] ile izleme listesindeki tüm semboller TEK çağrıda çekilip etiketli TEK PNG\'de birleştirilir — ayrı ayrı 3 DM atmak yerine bu birleşik görseli gönder. web grafiklerinde browser_open + browser_screenshot; bilgisayar ekranı için computer_look; görsel göremeyen metin-model isen ocr_read (source:"screen"/"browser") kullan. Gördüğün yapıyı yorumla: trend, formasyon, mum yapısı, destek-direnç ve SL/TP çizgileri; sembol başına GÖRSEL TEYİT + AL/SAT/BEKLE notu üret. Önemli bulguyu GÖRSELİYLE paylaş: agent_dm image:true (son ekran görüntün) ya da image:"<mt5_shot path>" ile ANA TRADER\'a/ekibe gönder — karşı ajan görüntüyü GERÇEKTEN görür, AJAN DM panelinde açılır. NOT: image:true DAİMA oturumdaki EN SON görseli yollar — her sembolün fotoğrafını ayrı göndermen gerekirse dönen path\'leri kullan (image:"<path>").',
|
|
1974
1974
|
})[String((session && session.financeRole) || '')] || '';
|
|
1975
1975
|
/* ROL → SKILL eşleştirmesi (ayarlar modalı): rol başına TEK ve ZORUNLU
|
|
1976
1976
|
skill; ANA TRADER için ayrıca PLAYBOOK skill'i (roleSkills.trader).
|
|
@@ -2009,8 +2009,10 @@ class Engine {
|
|
|
2009
2009
|
modeBlock2 + '\n' +
|
|
2010
2010
|
(roleBlockFull ? roleBlockFull + '\n' : '') +
|
|
2011
2011
|
(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_ea (BeastFinance grafik panosu: status/ping/chart/note), mt5_trade (TÜM TİPLER: buy/sell market + buy/sell limit/stop), mt5_close (kapat), 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' +
|
|
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 — 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
2013
|
'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
|
+
'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' +
|
|
2015
|
+
'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' +
|
|
2014
2016
|
'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' +
|
|
2015
2017
|
'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' +
|
|
2016
2018
|
'YETKİLERİN (AÇIK — çekinmeden kullan):\n' +
|
|
@@ -6175,7 +6177,8 @@ const TOOLS = [
|
|
|
6175
6177
|
windowMin: { type: 'number', description: 'kind=logs: sliding window in minutes (default 10, max 720)' },
|
|
6176
6178
|
everyMin: { type: 'number', description: 'Check interval in minutes (default 15, min 1, max 1440)' },
|
|
6177
6179
|
everySec: { type: 'number', description: 'Check interval in SECONDS (10-8640) — overrides everyMin for fast watchers (e.g. 30 = every 30s)' },
|
|
6178
|
-
cooldownMin: { type: 'number', description: '
|
|
6180
|
+
cooldownMin: { type: 'number', description: 'İki bildirim arası en az dakika (varsayılan 60). >0 ise alarm TEKRARLIDIR: koşul sürdükçe soğuma dolduğunda yeniden bildirir (ör. 5 = 5 dakikada bir hatırlat). 0 = yalnız koşul yeniden sağlandığında (kenar tetikleme).' },
|
|
6181
|
+
once: { type: 'boolean', description: 'true = tek seferlik izleyici: ilk tetiklemede kapanır (varsayılan false = tekrarlı)' },
|
|
6179
6182
|
},
|
|
6180
6183
|
required: ['name', 'kind'],
|
|
6181
6184
|
},
|
|
@@ -200,7 +200,7 @@ 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", note?}.
|
|
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".',
|
|
204
204
|
parameters: {
|
|
205
205
|
type: 'object',
|
|
206
206
|
properties: {
|
|
@@ -208,7 +208,14 @@ const definitions = NAMES.map((name) => {
|
|
|
208
208
|
symbol: { type: 'string', description: 'set için sembol' },
|
|
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
|
+
mode: {
|
|
212
|
+
type: 'string',
|
|
213
|
+
enum: ['once', 'repeat'],
|
|
214
|
+
description: "set için ZORUNLU: 'once' tek seferlik | 'repeat' tekrarlı (kararı alarmı kuran ajan verir)",
|
|
215
|
+
},
|
|
211
216
|
note: { type: 'string', description: 'Alarm notu (bildirimde görünür)' },
|
|
217
|
+
cooldownMin: { type: 'number', description: "mode:'repeat' için iki tetik arası en az dakika (varsayılan 5, 0 = her tetiklemede, max 10080)" },
|
|
218
|
+
once: { type: 'boolean', description: "mode'a alternatif kısa yol (true = tek seferlik)" },
|
|
212
219
|
id: { type: 'string', description: 'remove için alarm id' },
|
|
213
220
|
},
|
|
214
221
|
required: ['action'],
|
|
@@ -275,12 +282,16 @@ const definitions = NAMES.map((name) => {
|
|
|
275
282
|
},
|
|
276
283
|
},
|
|
277
284
|
mt5_close: {
|
|
278
|
-
description:
|
|
285
|
+
description:
|
|
286
|
+
'Pozisyonu kapatır — TAM ya da KISMİ: mt5_close {ticket, percent?|volume?, kind?:"partial_tp"|"partial_sl"|"close", reason?}. KISMİ KAPATMA (kâr al = kısmi TP, zarar kes = kısmi stop) tamamen SENİN KARARIN — otomatik yapılmaz; gerek gördüğünde kullan. percent: pozisyonun % kaçı kapatılsın (ör. 50), volume: lot; kalan pozisyon SL/TP ile devam eder. Kalan lot broker minimumunun altında kalırsa tamamı kapanır. Risk yönetiminin temel aracıdır.',
|
|
279
287
|
parameters: {
|
|
280
288
|
type: 'object',
|
|
281
289
|
properties: {
|
|
282
290
|
ticket: { type: 'number', description: 'Pozisyon ticket no' },
|
|
283
|
-
|
|
291
|
+
percent: { type: 'number', description: 'Kapatılacak yüzde (ör. 50) — kısmi TP/kısmi stop için' },
|
|
292
|
+
volume: { type: 'number', description: 'Kapatılacak lot (percent yerine; boşsa ve percent yoksa tamamı kapanır)' },
|
|
293
|
+
kind: { type: 'string', enum: ['partial_tp', 'partial_sl', 'close'], description: 'Kısmi kapatmanın nedeni: kâr al / zarar kes (günlüğe etiket olur)' },
|
|
294
|
+
reason: { type: 'string', description: 'Kararın tezi (tek cümle, günlüğe yazılır)' },
|
|
284
295
|
},
|
|
285
296
|
required: ['ticket'],
|
|
286
297
|
},
|
|
@@ -425,9 +436,34 @@ const handlers = {
|
|
|
425
436
|
if (!symbol) return { ok: false, error: 'symbol gerekli' };
|
|
426
437
|
if (!isFinite(price) || price <= 0) return { ok: false, error: 'price gerekli (pozitif sayı)' };
|
|
427
438
|
if (direction !== 'above' && direction !== 'below') return { ok: false, error: "direction 'above' veya 'below' olmalı" };
|
|
428
|
-
|
|
439
|
+
/* ALARM MODUNU KURAN AJAN SEÇER: 'once' (tek seferlik) ya da
|
|
440
|
+
'repeat' (tekrarlı; koşul sürdükçe cooldownMin'de bir). Seçim
|
|
441
|
+
yapılmadan alarm kurulamaz — sistem varsayılanına bırakılmaz. */
|
|
442
|
+
const mRaw = String(args.mode || '').toLowerCase().trim();
|
|
443
|
+
let once;
|
|
444
|
+
if (mRaw === 'once' || mRaw === 'tek' || mRaw === 'tek_seferlik' || mRaw === 'tek-seferlik') once = true;
|
|
445
|
+
else if (mRaw === 'repeat' || mRaw === 'recurring' || mRaw === 'tekrarli' || mRaw === 'surekli' || mRaw === 'always') once = false;
|
|
446
|
+
else if (args.once !== undefined) once = args.once === true || String(args.once).toLowerCase() === 'true';
|
|
447
|
+
else {
|
|
448
|
+
return {
|
|
449
|
+
ok: false,
|
|
450
|
+
error:
|
|
451
|
+
"mode gerekli — alarmı kuran ajan karar verir: mode:'once' (tek seferlik, ilk tetiklemede kapanır) ya da mode:'repeat' (tekrarlı; koşul sürdükçe cooldownMin'de bir uyarır, varsayılan 5 dk)",
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
const cdRaw = Number(args.cooldownMin);
|
|
455
|
+
const cooldownMin = Number.isFinite(cdRaw) ? Math.min(Math.max(Math.round(cdRaw), 0), 10080) : undefined;
|
|
456
|
+
const alarm = alertsApi.set({
|
|
457
|
+
symbol,
|
|
458
|
+
price,
|
|
459
|
+
direction,
|
|
460
|
+
note: String(args.note || '').slice(0, 200),
|
|
461
|
+
sid: ctx && ctx.sessionId ? String(ctx.sessionId) : '',
|
|
462
|
+
...(cooldownMin === undefined ? {} : { cooldownMin }),
|
|
463
|
+
once,
|
|
464
|
+
});
|
|
429
465
|
if (!alarm) return { ok: false, error: 'alarm kurulamadı' };
|
|
430
|
-
noteTrade('alert-set', { symbol, price, direction, id: alarm.id, note: alarm.note }, ctx || {});
|
|
466
|
+
noteTrade('alert-set', { symbol, price, direction, id: alarm.id, note: alarm.note, cooldownMin: alarm.cooldownMin, once: alarm.once }, ctx || {});
|
|
431
467
|
return { ok: true, alert: alarm };
|
|
432
468
|
}
|
|
433
469
|
return { ok: false, error: "action: list|set|remove" };
|
|
@@ -617,18 +653,48 @@ const handlers = {
|
|
|
617
653
|
return { ok: true, opened: { symbol, side, volume: vol, sl, tp }, risk: riskInfo, result: data && data.result };
|
|
618
654
|
},
|
|
619
655
|
async mt5_close(args, ctx) {
|
|
620
|
-
const cfg = getCfg();
|
|
621
656
|
if (!mt5.running) return notConnected();
|
|
622
657
|
const ticket = Number(args.ticket);
|
|
623
658
|
if (!ticket) return { ok: false, error: 'ticket gerekli' };
|
|
659
|
+
/* KISMİ KAPATMA (kısmi TP / kısmi stop): kararı AJAN verir — otomatik
|
|
660
|
+
değildir. percent: pozisyonun % kaçı; volume: lot. Kalan pozisyon
|
|
661
|
+
SL/TP ile devam eder. */
|
|
662
|
+
const percent = Math.max(0, Math.min(100, Number(args.percent) || 0));
|
|
624
663
|
let vol = Number(args.volume) || 0;
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
664
|
+
let pos = null;
|
|
665
|
+
try {
|
|
666
|
+
const posRes = await bcall('positions', {}, 8000);
|
|
667
|
+
pos = ((posRes && posRes.positions) || []).find((x) => Number(x.ticket) === ticket) || null;
|
|
668
|
+
} catch {}
|
|
669
|
+
if (!pos) return { ok: false, error: 'pozisyon bulunamadı: ' + ticket };
|
|
670
|
+
const pvol = Number(pos.volume) || 0;
|
|
671
|
+
if (!(pvol > 0)) return { ok: false, error: 'pozisyon hacmi okunamadı: ' + ticket };
|
|
672
|
+
let info = null;
|
|
673
|
+
try { info = await symInfoRow(String(pos.symbol || '')); } catch {}
|
|
674
|
+
if (percent > 0 && !(vol > 0)) {
|
|
675
|
+
const norm = finrisk.normalizeVolume(info || {}, (pvol * percent) / 100);
|
|
676
|
+
if (norm.error) return { ok: false, error: norm.error };
|
|
677
|
+
vol = norm.volume;
|
|
628
678
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
679
|
+
if (!(vol > 0)) vol = pvol; /* hacim yok → tamamı */
|
|
680
|
+
if (vol > pvol + 1e-9) return { ok: false, error: `kapatılacak lot pozisyondan büyük (${vol} > ${pvol})` };
|
|
681
|
+
/* kalan pozisyon broker minimumunun altında kalacaksa TAMAMINI kapat */
|
|
682
|
+
const vmin = Number(info && info.volume_min) || 0.01;
|
|
683
|
+
const remain = pvol - vol;
|
|
684
|
+
if (remain > 0 && remain < vmin - 1e-9) vol = pvol;
|
|
685
|
+
const isPartial = vol < pvol - 1e-9;
|
|
686
|
+
const kind = String(args.kind || args.closeKind || '').toLowerCase();
|
|
687
|
+
const data = await bcall('close', { ticket, volume: vol }, 20000);
|
|
688
|
+
noteTrade('close', {
|
|
689
|
+
ticket,
|
|
690
|
+
volume: vol,
|
|
691
|
+
percent: percent || 0,
|
|
692
|
+
partial: isPartial,
|
|
693
|
+
closeKind: /partial_sl|kismi_stop|partial_stop/.test(kind) ? 'partial_sl' : /partial_tp|kismi_tp|partial_take/.test(kind) ? 'partial_tp' : kind,
|
|
694
|
+
reason: String(args.reason || '').slice(0, 300),
|
|
695
|
+
result: data && data.result,
|
|
696
|
+
}, ctx);
|
|
697
|
+
return { ok: true, closed: { ticket, volume: vol, partial: isPartial }, remaining: Math.round(remain * 1e8) / 1e8, result: data && data.result };
|
|
632
698
|
},
|
|
633
699
|
async mt5_modify(args, ctx) {
|
|
634
700
|
if (!mt5.running) return notConnected();
|
package/src/agent/finwatch.js
CHANGED
|
@@ -147,6 +147,18 @@ function parseOrderType(raw, fallbackSide) {
|
|
|
147
147
|
return { ok: true, side, kind: 'market', type: side + '_market' };
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
/* ---- fiyat alarmı soğuması ----
|
|
151
|
+
Tekrarlı alarm (once=false): aynı koşul sürdükçe en fazla cooldownMin
|
|
152
|
+
dakikada bir tetiklenir. once=true alarmda soğuma uygulanmaz (ilk
|
|
153
|
+
tetiklemede kapanır). */
|
|
154
|
+
function alertCooldownActive(a, now) {
|
|
155
|
+
if (!a || a.once) return false;
|
|
156
|
+
const cd = (Number(a.cooldownMin) || 0) * 60000;
|
|
157
|
+
if (!(cd > 0)) return false;
|
|
158
|
+
const last = Number(a.lastFiredAt) || 0;
|
|
159
|
+
return last > 0 && Number(now) - last < cd;
|
|
160
|
+
}
|
|
161
|
+
|
|
150
162
|
/* ---- bekleyen emir (pending) → pozisyon eşleştirme ----
|
|
151
163
|
Watchdog turu emir listesini karşılaştırır: listeden DÜŞEN emir için aynı
|
|
152
164
|
turda YENİ açılan (ya da netting hesapta hacmi artan) eşleşen bir pozisyon
|
|
@@ -216,4 +228,4 @@ function matchPendingDelta(goneOrders, freshPositions) {
|
|
|
216
228
|
return out;
|
|
217
229
|
}
|
|
218
230
|
|
|
219
|
-
module.exports = { plan, roundTo, stepRound, closeKind, parseOrderType, orderSide, orderTypeLabel, orderVolume, matchPendingDelta };
|
|
231
|
+
module.exports = { plan, roundTo, stepRound, closeKind, parseOrderType, alertCooldownActive, orderSide, orderTypeLabel, orderVolume, matchPendingDelta };
|
package/src/agent/watchers.js
CHANGED
|
@@ -172,7 +172,9 @@ function normalize(input) {
|
|
|
172
172
|
const everyMin = Math.min(Math.max(Math.round(rawMin) || 15, 1), 1440);
|
|
173
173
|
everySec = everyMin * 60;
|
|
174
174
|
}
|
|
175
|
-
/* 0 geçerlidir (cooldown yok);
|
|
175
|
+
/* 0 geçerlidir (cooldown yok → yalnız kenar tetikleme); sayılamayan değerler
|
|
176
|
+
varsayılana döner. cooldownMin > 0 ise alarm TEKRARLIDIR: koşul sürdükçe
|
|
177
|
+
soğuma dolduğunda yeniden bildirir. */
|
|
176
178
|
const cdRaw = Number(i.cooldownMin ?? i.cooldown_min ?? 60);
|
|
177
179
|
const cooldownMin = Number.isFinite(cdRaw) ? Math.min(Math.max(Math.round(cdRaw), 0), 10080) : 60;
|
|
178
180
|
const prompt = String(i.prompt || '').trim().slice(0, 2000);
|
|
@@ -192,6 +194,7 @@ function normalize(input) {
|
|
|
192
194
|
everyMin: Math.max(1, Math.round(everySec / 60)), // geriye dönük uyum
|
|
193
195
|
everySec,
|
|
194
196
|
cooldownMin,
|
|
197
|
+
once: !!i.once, /* tek seferlik: ilk tetiklemede kapanır */
|
|
195
198
|
prompt,
|
|
196
199
|
sessionId: i.sessionId || null,
|
|
197
200
|
enabled: true,
|
|
@@ -223,11 +226,15 @@ function compare(op, current, target) {
|
|
|
223
226
|
}
|
|
224
227
|
|
|
225
228
|
/* Tek kontrol uygula — saf: tetik kararı + kaydedilecek yama.
|
|
226
|
-
|
|
227
|
-
|
|
229
|
+
TEKRARLI ALARM: cooldownMin > 0 ise koşul sürdükçe, soğuma her dolduğunda
|
|
230
|
+
YENİDEN tetiklenir (tickOnce soğuma boyunca zaten kontrol etmez).
|
|
231
|
+
cooldownMin = 0 → yalnız kenar tetikleme (koşul bozulup yeniden sağlanınca).
|
|
232
|
+
'changed' her zaman kenar tetiklemelidir (değer değişimi olaydır). */
|
|
228
233
|
function applyCheck(w, value, now) {
|
|
229
234
|
const cond = compare(w.op, value, w.value);
|
|
230
|
-
const
|
|
235
|
+
const cdMin = Number(w.cooldownMin) || 0;
|
|
236
|
+
const repeat = cdMin > 0 && w.op !== 'changed' && !!w.lastTriggeredAt;
|
|
237
|
+
const triggered = !!(cond && (w.armed || repeat));
|
|
231
238
|
const patch = {
|
|
232
239
|
lastCheckAt: new Date(now).toISOString(),
|
|
233
240
|
lastValue: value === undefined ? null : value,
|
|
@@ -371,6 +378,8 @@ async function tickOnce(deps = {}) {
|
|
|
371
378
|
w.lastError = '';
|
|
372
379
|
pushLog(w.id, r.triggered ? 'trigger' : 'ok', value);
|
|
373
380
|
if (r.triggered) {
|
|
381
|
+
/* TEK SEFERLİK: ilk tetiklemede izleyici kapanır (yeniden etkinleştirilebilir) */
|
|
382
|
+
if (w.once) w.enabled = false;
|
|
374
383
|
if (typeof hooks.onTrigger === 'function') hooks.onTrigger({ ...w }, value);
|
|
375
384
|
events.push({ id: w.id, name: w.name, value });
|
|
376
385
|
}
|
package/src/main.js
CHANGED
|
@@ -9780,7 +9780,14 @@ async function channelSendFromAgent(input) {
|
|
|
9780
9780
|
};
|
|
9781
9781
|
}
|
|
9782
9782
|
|
|
9783
|
-
/* ---- fiyat alarmları (kalıcı) ----
|
|
9783
|
+
/* ---- fiyat alarmları (kalıcı) ----
|
|
9784
|
+
TEKRARLI (varsayılan): alarm kapanmaz; koşul sürdükçe en fazla
|
|
9785
|
+
cooldownMin dakikada bir tekrar tetikler. once:true → tek tetiklemede
|
|
9786
|
+
kapanır. cooldownMin'i AJAN karar verir (mt5_alerts ile). */
|
|
9787
|
+
const FIN_ALERT_CD_DEFAULT = 5; /* dk */
|
|
9788
|
+
function finAlertCooldown(a) {
|
|
9789
|
+
return Math.min(Math.max(Math.round(Number(a && a.cooldownMin) || FIN_ALERT_CD_DEFAULT), 0), 10080);
|
|
9790
|
+
}
|
|
9784
9791
|
function finAlertsLoad() {
|
|
9785
9792
|
const raw = finReadJson(finFile('alerts.json'), []);
|
|
9786
9793
|
financeState.alerts = (Array.isArray(raw) ? raw : [])
|
|
@@ -9791,6 +9798,10 @@ function finAlertsLoad() {
|
|
|
9791
9798
|
direction: String((a && a.direction) || 'above').toLowerCase(),
|
|
9792
9799
|
note: String((a && a.note) || '').slice(0, 200),
|
|
9793
9800
|
sid: String((a && a.sid) || ''), /* alarmı kuran ajan oturumu (uyandırma hedefi) */
|
|
9801
|
+
once: !!(a && a.once), /* tek seferlik mi */
|
|
9802
|
+
cooldownMin: finAlertCooldown(a),
|
|
9803
|
+
lastFiredAt: Number(a && a.lastFiredAt) || 0,
|
|
9804
|
+
fires: Number(a && a.fires) || 0,
|
|
9794
9805
|
at: Number(a && a.at) || Date.now(),
|
|
9795
9806
|
}))
|
|
9796
9807
|
.filter((a) => a.id && a.symbol && a.price > 0);
|
|
@@ -9807,18 +9818,41 @@ function finAlertApi() {
|
|
|
9807
9818
|
const symbol = String((a && a.symbol) || '').toUpperCase();
|
|
9808
9819
|
const price = Number(a && a.price);
|
|
9809
9820
|
if (!symbol || !(price > 0)) return null;
|
|
9821
|
+
const direction = String((a && a.direction) || 'above').toLowerCase() === 'below' ? 'below' : 'above';
|
|
9822
|
+
/* AYNI ALARM ZATEN AÇIKSA yenisini EKLEME — mevcut kaydı güncelle:
|
|
9823
|
+
ajan her turda aynı seviyeyi yeniden kurup alarm/tur döngüsü
|
|
9824
|
+
(ard arda tetik + uyandırma) üretmesin. lastFiredAt KORUNUR. */
|
|
9825
|
+
const existing = financeState.alerts.find(
|
|
9826
|
+
(x) => x.symbol === symbol && x.direction === direction && Math.abs(Number(x.price) - price) < 1e-9
|
|
9827
|
+
);
|
|
9828
|
+
if (existing) {
|
|
9829
|
+
if (a.note !== undefined) existing.note = String(a.note || '').slice(0, 200);
|
|
9830
|
+
if (a.sid !== undefined) existing.sid = String(a.sid || '');
|
|
9831
|
+
if (a.cooldownMin !== undefined) existing.cooldownMin = finAlertCooldown(a);
|
|
9832
|
+
if (a.once !== undefined) existing.once = !!a.once;
|
|
9833
|
+
finAlertsSave();
|
|
9834
|
+
financeLog(`[alarm] güncellendi (zaten açıktı): ${existing.symbol} ${existing.direction === 'below' ? '≤' : '≥'} ${existing.price}`);
|
|
9835
|
+
return { ...existing, updated: true };
|
|
9836
|
+
}
|
|
9810
9837
|
const alarm = {
|
|
9811
9838
|
id: 'a' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
|
|
9812
9839
|
symbol,
|
|
9813
9840
|
price,
|
|
9814
|
-
direction
|
|
9841
|
+
direction,
|
|
9815
9842
|
note: String((a && a.note) || '').slice(0, 200),
|
|
9816
9843
|
sid: String((a && a.sid) || ''),
|
|
9844
|
+
once: !!(a && a.once),
|
|
9845
|
+
cooldownMin: finAlertCooldown(a),
|
|
9846
|
+
lastFiredAt: 0,
|
|
9847
|
+
fires: 0,
|
|
9817
9848
|
at: Date.now(),
|
|
9818
9849
|
};
|
|
9819
9850
|
financeState.alerts.push(alarm);
|
|
9820
9851
|
finAlertsSave();
|
|
9821
|
-
financeLog(
|
|
9852
|
+
financeLog(
|
|
9853
|
+
`[alarm] kuruldu: ${alarm.symbol} ${alarm.direction === 'below' ? '≤' : '≥'} ${alarm.price}` +
|
|
9854
|
+
(alarm.once ? ' (tek seferlik)' : ` (tekrarlı · ${alarm.cooldownMin} dk soğuma)`)
|
|
9855
|
+
);
|
|
9822
9856
|
return alarm;
|
|
9823
9857
|
},
|
|
9824
9858
|
remove: (id) => {
|
|
@@ -10100,23 +10134,39 @@ async function finCheckAlerts() {
|
|
|
10100
10134
|
prices[String(row.symbol)] = { bid: Number(row.bid), ask: Number(row.ask) };
|
|
10101
10135
|
}
|
|
10102
10136
|
let changed = false;
|
|
10137
|
+
const now = Date.now();
|
|
10103
10138
|
for (const a of alerts.slice()) {
|
|
10104
10139
|
const p = prices[a.symbol];
|
|
10105
10140
|
if (!p || !isFinite(p.bid)) continue;
|
|
10141
|
+
/* soğuma: tekrarlı alarm koşul sürse bile cooldown dolmadan tekrar etmez */
|
|
10142
|
+
if (finwatch.alertCooldownActive(a, now)) continue;
|
|
10106
10143
|
const hit = a.direction === 'below' ? p.bid <= a.price : p.bid >= a.price;
|
|
10107
10144
|
if (!hit) continue;
|
|
10108
10145
|
changed = true;
|
|
10109
|
-
|
|
10110
|
-
|
|
10146
|
+
const fires = (Number(a.fires) || 0) + 1;
|
|
10147
|
+
if (a.once) {
|
|
10148
|
+
/* tek seferlik: tetiklendi → kapanır */
|
|
10149
|
+
financeState.alerts = financeState.alerts.filter((x) => x.id !== a.id);
|
|
10150
|
+
} else {
|
|
10151
|
+
/* tekrarlı: alarm AÇIK kalır; cooldown sonrası yeniden hatırlatır */
|
|
10152
|
+
a.lastFiredAt = now;
|
|
10153
|
+
a.fires = fires;
|
|
10154
|
+
}
|
|
10155
|
+
const tekrar = fires > 1 ? ` (tekrar #${fires - 1})` : '';
|
|
10156
|
+
const cdTxt = a.once ? ' · tek seferlik' : ` · ${finAlertCooldown(a)} dk soğuma`;
|
|
10157
|
+
const line = `🔔 ALARM${tekrar}: ${a.symbol} ${a.direction === 'below' ? '≤' : '≥'} ${a.price} (şimdi ${p.bid})${a.note ? ' — ' + a.note : ''}${cdTxt}`;
|
|
10111
10158
|
financeLog('[alarm] ' + line);
|
|
10112
10159
|
financeNotify(line, 'alert');
|
|
10113
|
-
finJournal({ kind: 'alert', symbol: a.symbol, price: a.price, direction: a.direction, note: a.note || '' });
|
|
10160
|
+
finJournal({ kind: 'alert', symbol: a.symbol, price: a.price, direction: a.direction, note: a.note || '', fires, once: !!a.once });
|
|
10114
10161
|
/* ALARM SAHİBİ AJAN: alarmı kuran koşan finance ajanı DM'den uyanır;
|
|
10115
10162
|
sahip koşmuyor/biliniyorsa koşan tüm finance ajanlarına düşer. */
|
|
10116
10163
|
const wakeTxt =
|
|
10117
|
-
`[FİNANS OLAYI — FİYAT ALARMI, TUR SANİYESİ BEKLENMEDEN İLETİLDİ]\n` +
|
|
10118
|
-
`- ${a.symbol} ${a.direction === 'below' ? '≤' : '≥'} ${a.price} tetiklendi (şimdi ${p.bid})${a.note ? ' — ' + a.note : ''}\n` +
|
|
10119
|
-
|
|
10164
|
+
`[FİNANS OLAYI — FİYAT ALARMI${tekrar}, TUR SANİYESİ BEKLENMEDEN İLETİLDİ]\n` +
|
|
10165
|
+
`- ${a.symbol} ${a.direction === 'below' ? '≤' : '≥'} ${a.price} tetiklendi (şimdi ${p.bid})${a.note ? ' — ' + a.note : ''}${cdTxt}\n` +
|
|
10166
|
+
(a.once
|
|
10167
|
+
? 'Alarm TEK SEFERLİKTİ ve kapandı. '
|
|
10168
|
+
: `Alarm TEKRARLI açık kalıyor${a.cooldownMin > 0 ? ` — koşul sürerse en erken ${a.cooldownMin} dk sonra yeniden uyarır` : ''}. `) +
|
|
10169
|
+
'Şimdi yap: alarmı kurma nedenini hatırla; fiyat seviyesini ve planını değerlendir, gerekiyorsa işlem/uyarı üret. Kısa rapor ver.';
|
|
10120
10170
|
if (!finWakeAgentDm(a.sid, wakeTxt)) finWakeAgents(wakeTxt, { kind: 'alarm', symbol: a.symbol });
|
|
10121
10171
|
}
|
|
10122
10172
|
if (changed) finAlertsSave();
|
|
@@ -10264,15 +10314,24 @@ async function finWatchTick() {
|
|
|
10264
10314
|
finJournal({ kind: 'watchdog', action: 'sl-move', ticket, symbol: p.symbol, sl: act.sl });
|
|
10265
10315
|
}
|
|
10266
10316
|
} else if (act.kind === 'partial') {
|
|
10267
|
-
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
}
|
|
10317
|
+
/* OTOMATİK KAPATMA YOK — kısmi TP/stop kararını AJAN verir: seviye
|
|
10318
|
+
gelince ajanlar TUR BEKLEMEDEN uyandırılır; uygun görürse
|
|
10319
|
+
mt5_close {percent, kind:"partial_tp"} ile kısmi kâr alır. */
|
|
10320
|
+
st.partial = true;
|
|
10321
|
+
const rNow = decision.r > 0
|
|
10322
|
+
? Math.round(((Number(p.type) === 0 ? Number(p.price_current) - Number(p.price_open) : Number(p.price_open) - Number(p.price_current)) / decision.r) * 10) / 10
|
|
10323
|
+
: 0;
|
|
10324
|
+
const pct = Number(cfg.partialPct) || 50;
|
|
10325
|
+
const line = `🎯 KISMİ TP FIRSATI: ${p.symbol} #${ticket} +${rNow}R (önerilen %${pct}) — kapatma kararı ajanın`;
|
|
10326
|
+
financeLog('[watchdog] ' + line);
|
|
10327
|
+
if (cfg.notifyWatchdog !== false) financeNotify(line, 'watchdog');
|
|
10328
|
+
finJournal({ kind: 'watchdog', action: 'partial-tp-opportunity', ticket, symbol: p.symbol, r: rNow, pct });
|
|
10329
|
+
finWakeAgents(
|
|
10330
|
+
`[FİNANS OLAYI — KISMİ TP FIRSATI, TUR BEKLEMEDEN İLETİLDİ]\n` +
|
|
10331
|
+
`- ${p.symbol}${Number(p.type) === 0 ? ' BUY' : ' SELL'} #${ticket} +${rNow}R seviyesinde (önerilen kısmi kapatma: %${pct})\n` +
|
|
10332
|
+
`Karar SENİN (otomatik kapatma yok): gerek görürsen mt5_close {ticket:${ticket}, percent:${pct}, kind:"partial_tp", reason:"..."} ile kısmi kâr al; kalan pozisyon SL/TP ile devam eder. Uygun değilse dokunma. Kısa rapor ver.`,
|
|
10333
|
+
{ kind: 'partial-tp', ticket }
|
|
10334
|
+
);
|
|
10276
10335
|
} else if (act.kind === 'warnSL') {
|
|
10277
10336
|
st.warnSL = true;
|
|
10278
10337
|
const line = `⚠️ SL yaklaşıyor: ${p.symbol} #${ticket} (SL ${p.sl}, fiyat ${p.price_current})`;
|
|
@@ -10480,6 +10539,19 @@ function finTradeEvent(kRaw, dataRaw, sidRaw) {
|
|
|
10480
10539
|
if (k === 'trade') {
|
|
10481
10540
|
finJournal({ kind: 'trade', sid, agent: who, symbol: d.symbol, side: d.side, volume: d.volume, sl: d.sl, tp: d.tp, reason: d.reason || d.comment || '' });
|
|
10482
10541
|
} else if (k === 'close') {
|
|
10542
|
+
/* KISMİ KAPATMA (kısmi TP / kısmi stop): ajan kararı; günlük + bildirim.
|
|
10543
|
+
Kesin toplam K/Z yine watchdog tarafından yazılır (çift sayım yok). */
|
|
10544
|
+
const isPartial = d.partial === true || Number(d.percent) > 0;
|
|
10545
|
+
if (isPartial) {
|
|
10546
|
+
const ck = String(d.closeKind || '');
|
|
10547
|
+
const label = ck === 'partial_sl' ? 'KISMİ STOP (zarar kes)' : ck === 'partial_tp' ? 'KISMİ TP (kâr al)' : 'KISMİ KAPATMA';
|
|
10548
|
+
finJournal({ kind: 'partial-close', sid, agent: who, ticket: d.ticket, volume: d.volume, percent: Number(d.percent) || 0, closeKind: ck, reason: d.reason || '' });
|
|
10549
|
+
const line = `✂️ ${label}: ticket ${d.ticket}${Number(d.percent) > 0 ? ` · %${Number(d.percent)}` : ''}${d.volume ? ` · ${d.volume} lot` : ''}${who ? ' · ' + who : ''}`;
|
|
10550
|
+
financeLog('[ajan] ' + line);
|
|
10551
|
+
finPush('trade', { line });
|
|
10552
|
+
if (finCfg().notifyTrades !== false) financeNotify(line, 'close', false);
|
|
10553
|
+
return;
|
|
10554
|
+
}
|
|
10483
10555
|
finJournal({ kind: 'close-req', sid, agent: who, ticket: d.ticket, volume: d.volume });
|
|
10484
10556
|
return; /* kesin kapanış K/Z'si watchdog tarafından yazılır (çift bildirim yok) */
|
|
10485
10557
|
} else if (k === 'modify') {
|
|
@@ -10532,7 +10604,16 @@ try {
|
|
|
10532
10604
|
} else if (kind === 'modify') finTradeEvent('modify', { ticket: a.ticket, sl: a.sl, tp: a.tp }, sid);
|
|
10533
10605
|
else if (kind === 'pending') finTradeEvent('pending', { symbol: a.symbol, type: a.type, volume: a.volume, reason: a.reason || '' }, sid);
|
|
10534
10606
|
else if (kind === 'cancel') finTradeEvent('cancel', { ticket: a.ticket }, sid);
|
|
10535
|
-
else if (kind === 'close')
|
|
10607
|
+
else if (kind === 'close') {
|
|
10608
|
+
finTradeEvent('close', {
|
|
10609
|
+
ticket: a.ticket,
|
|
10610
|
+
volume: a.volume || (a.percent ? 0 : 'all'),
|
|
10611
|
+
percent: Number(a.percent) || 0,
|
|
10612
|
+
partial: !!(Number(a.percent) > 0 || (a.volume && String(a.volume) !== 'all')),
|
|
10613
|
+
closeKind: String(a.kind || a.closeKind || ''),
|
|
10614
|
+
reason: String(a.reason || ''),
|
|
10615
|
+
}, sid);
|
|
10616
|
+
}
|
|
10536
10617
|
});
|
|
10537
10618
|
} catch {}
|
|
10538
10619
|
|
|
@@ -10988,7 +11069,7 @@ function finTraderBrief(agent) {
|
|
|
10988
11069
|
`Tur aralığı: ${f.intervalSec} sn · Max lot: ${f.maxLot} · Max eşzamanlı pozisyon: ${f.maxPositions}`,
|
|
10989
11070
|
roleDef
|
|
10990
11071
|
? `UZMANLIK: ${roleDef.desc} — raporlarını bu çerçevede yaz; İŞLEM AÇMA, yalnız analiz + net öneri üret.`
|
|
10991
|
-
: '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). Lot için mt5_risksize hesapla ya da mt5_trade’e riskPct ver; SL/TP broker stops_level mesafesine uymalı.',
|
|
11072
|
+
: '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ı.',
|
|
10992
11073
|
roleDef
|
|
10993
11074
|
? 'Bulgularını agent_dm ile ANA TRADER\u2019a bildir (to: "Trader" ya da ajan başlığı anahtarı); teknik/öneri çelişkisi varsa gerekçenle yaz.'
|
|
10994
11075
|
: f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : 'Strateji notu yok: trend + destek/direnç + momentum ile temel okuma yap.',
|
|
@@ -11265,6 +11346,17 @@ ipcMain.handle('finance:snapshot', async () => {
|
|
|
11265
11346
|
};
|
|
11266
11347
|
});
|
|
11267
11348
|
|
|
11349
|
+
ipcMain.handle('finance:alerts', async () => {
|
|
11350
|
+
/* İzleyiciler paneli hafif alarm listesi (hesap/pozisyon çekmez) */
|
|
11351
|
+
return { ok: true, alerts: (financeState.alerts || []).slice(0, 200) };
|
|
11352
|
+
});
|
|
11353
|
+
|
|
11354
|
+
ipcMain.handle('finance:alerts:remove', async (_e, id) => {
|
|
11355
|
+
const ok = finAlertApi().remove(String(id || ''));
|
|
11356
|
+
finPush('alert', { removed: ok, id: String(id || '') });
|
|
11357
|
+
return { ok };
|
|
11358
|
+
});
|
|
11359
|
+
|
|
11268
11360
|
ipcMain.handle('finance:mode', async (_e, payload) => {
|
|
11269
11361
|
financeState.mode = !!(payload && payload.on);
|
|
11270
11362
|
/* BEAST FINANCE = MT5 KAPISI:
|
package/src/preload.js
CHANGED
|
@@ -117,6 +117,8 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
117
117
|
|
|
118
118
|
financeState: () => ipcRenderer.invoke('finance:state'),
|
|
119
119
|
financeSnapshot: () => ipcRenderer.invoke('finance:snapshot'),
|
|
120
|
+
financeAlertsList: () => ipcRenderer.invoke('finance:alerts'),
|
|
121
|
+
financeAlertRemove: (id) => ipcRenderer.invoke('finance:alerts:remove', id),
|
|
120
122
|
financeMode: (on, sessionId) => ipcRenderer.invoke('finance:mode', { on, sessionId }),
|
|
121
123
|
financeSettings: (patch) => ipcRenderer.invoke('finance:settings', patch),
|
|
122
124
|
financeTraderStart: () => ipcRenderer.invoke('finance:trader:start'),
|
package/src/renderer/index.html
CHANGED
|
@@ -225,8 +225,8 @@
|
|
|
225
225
|
<div class="fin-field"><span>BE (R)</span><input id="finBeOnR" type="number" min="0" max="10" step="0.1" title="Kâr kaç R'ye ulaşınca SL başabaşa çekilsin (0 = kapalı)" /></div>
|
|
226
226
|
<div class="fin-field"><span>Trailing başlangıç (R)</span><input id="finTrailStartR" type="number" min="0" max="10" step="0.1" title="Kaç R kârdan sonra trailing başlasın (0 = kapalı)" /></div>
|
|
227
227
|
<div class="fin-field"><span>Trailing mesafe (R)</span><input id="finTrailR" type="number" min="0" max="5" step="0.1" title="SL fiyattan kaç R geriden gelsin" /></div>
|
|
228
|
-
<div class="fin-field"><span>Kısmi TP (R)</span><input id="finPartialR" type="number" min="0" max="10" step="0.1" title="Kaç R kârda kısmi
|
|
229
|
-
<div class="fin-field"><span>Kısmi TP (%)</span><input id="finPartialPct" type="number" min="0" max="90" step="5" title="
|
|
228
|
+
<div class="fin-field"><span>Kısmi TP (R)</span><input id="finPartialR" type="number" min="0" max="10" step="0.1" title="Kaç R kârda ajana kısmi TP fırsatı bildirilsin (0 = kapalı) — kapatma kararını AJAN verir, otomatik kapatılmaz" /></div>
|
|
229
|
+
<div class="fin-field"><span>Kısmi TP (%)</span><input id="finPartialPct" type="number" min="0" max="90" step="5" title="Fırsat bildiriminde ajana önerilecek kısmi kapatma yüzdesi (kapatma kararı ajanın)" /></div>
|
|
230
230
|
<div class="fin-field"><span>Günlük kayıp uyarı (%)</span><input id="finMaxDailyLoss" type="number" min="0" max="50" step="0.5" title="Gün içi düşüş bu yüzdeyi geçince aksiyon uygulanır (0 = kapalı)" /></div>
|
|
231
231
|
</div>
|
|
232
232
|
<div class="fin-grid2">
|
|
@@ -798,6 +798,10 @@
|
|
|
798
798
|
<input id="wAddWindow" class="inp" type="number" min="1" max="720" value="10" title="pencere (dk)" style="width:80px" />
|
|
799
799
|
<input id="wAddRe" class="inp" data-i18n-ph="w_add_re" placeholder="regex (opsiyonel)" />
|
|
800
800
|
</div>
|
|
801
|
+
<div class="mini-add-row">
|
|
802
|
+
<input id="wAddCd" class="inp" type="number" min="0" max="10080" value="60" title="soğuma (dk): koşul sürdükçe en fazla bu aralıkta bir bildirir — 0 = yalnız koşul yeniden sağlanınca" style="width:70px" />
|
|
803
|
+
<label style="display:flex;align-items:center;gap:5px;font-size:12px;color:var(--muted);white-space:nowrap"><input id="wAddOnce" type="checkbox" /> tek seferlik</label>
|
|
804
|
+
</div>
|
|
801
805
|
</div>
|
|
802
806
|
<div id="watchList" class="mini-list"></div>
|
|
803
807
|
</div>
|
package/src/renderer/renderer.js
CHANGED
|
@@ -6640,17 +6640,35 @@ function fmtWhen(iso) {
|
|
|
6640
6640
|
Kompakt liste: durum noktası + ad + bir sonraki tetik; satır → izleyici
|
|
6641
6641
|
modalı; ▶/× mini kontroller. Yalnız agent modunda görünür (CSS). */
|
|
6642
6642
|
let lastWatchPaneKey = '';
|
|
6643
|
+
/* FİYAT ALARMLARI (Beast Finance): İzleyiciler paneli + modalında watcher'larla
|
|
6644
|
+
BİRLİKTE listelenir — tüm alarmlar tek yerde görünür/silinir. */
|
|
6645
|
+
function finAlertMeta(a) {
|
|
6646
|
+
const dir = a.direction === 'below' ? '≤' : '≥';
|
|
6647
|
+
return {
|
|
6648
|
+
name: `🔔 ${a.symbol} ${dir} ${a.price}`,
|
|
6649
|
+
meta:
|
|
6650
|
+
(a.once ? 'tek seferlik' : `her ${Number(a.cooldownMin) || 5} dk`) +
|
|
6651
|
+
(a.fires ? ` · ${a.fires}× tetiklendi` : '') +
|
|
6652
|
+
(a.note ? ` · ${String(a.note).slice(0, 40)}` : ''),
|
|
6653
|
+
};
|
|
6654
|
+
}
|
|
6655
|
+
|
|
6643
6656
|
async function renderWatchersPane(force) {
|
|
6644
6657
|
const list = els.watchPaneList;
|
|
6645
6658
|
if (!list) return;
|
|
6646
6659
|
if (document.body.classList.contains('ide-mode') || document.body.classList.contains('studio-mode') || document.body.classList.contains('sandbox-mode')) return;
|
|
6647
6660
|
let rows = [];
|
|
6648
6661
|
try { rows = (await beast.watchersList()) || []; } catch {}
|
|
6649
|
-
|
|
6662
|
+
let alerts = [];
|
|
6663
|
+
try {
|
|
6664
|
+
const ar = await beast.financeAlertsList();
|
|
6665
|
+
alerts = (ar && ar.alerts) || [];
|
|
6666
|
+
} catch {}
|
|
6667
|
+
const key = JSON.stringify([rows, alerts]);
|
|
6650
6668
|
if (!force && key === lastWatchPaneKey) return;
|
|
6651
6669
|
lastWatchPaneKey = key;
|
|
6652
6670
|
list.innerHTML = '';
|
|
6653
|
-
if (!rows.length) {
|
|
6671
|
+
if (!rows.length && !alerts.length) {
|
|
6654
6672
|
list.innerHTML = '<div class="wp-empty">' + _t('w_empty') + '</div>';
|
|
6655
6673
|
return;
|
|
6656
6674
|
}
|
|
@@ -6698,18 +6716,52 @@ async function renderWatchersPane(force) {
|
|
|
6698
6716
|
row.addEventListener('click', () => openWatchModal());
|
|
6699
6717
|
list.appendChild(row);
|
|
6700
6718
|
}
|
|
6719
|
+
/* Beast Finance FİYAT ALARMLARI: aynı listede — silinebilir, canlı cd/fire bilgisi */
|
|
6720
|
+
for (const a of alerts) {
|
|
6721
|
+
const info = finAlertMeta(a);
|
|
6722
|
+
const row = document.createElement('div');
|
|
6723
|
+
row.className = 'wp-row alarm';
|
|
6724
|
+
row.title = info.name + (a.note ? ' — ' + a.note : '') + ' · ' + info.meta;
|
|
6725
|
+
row.innerHTML =
|
|
6726
|
+
`<span class="wp-dot"></span>` +
|
|
6727
|
+
`<div class="wp-main">` +
|
|
6728
|
+
`<div class="wp-name">${escapeHtml(info.name)}</div>` +
|
|
6729
|
+
`<div class="wp-meta">${escapeHtml(info.meta)}</div>` +
|
|
6730
|
+
`</div>`;
|
|
6731
|
+
const btns = document.createElement('div');
|
|
6732
|
+
btns.className = 'wp-btns';
|
|
6733
|
+
const d = document.createElement('button');
|
|
6734
|
+
d.className = 'wp-btn del';
|
|
6735
|
+
d.textContent = '×';
|
|
6736
|
+
d.title = _t('cr_del');
|
|
6737
|
+
d.addEventListener('click', async (e) => {
|
|
6738
|
+
e.stopPropagation();
|
|
6739
|
+
await beast.financeAlertRemove(a.id);
|
|
6740
|
+
lastWatchPaneKey = '';
|
|
6741
|
+
renderWatchersPane(true);
|
|
6742
|
+
renderWatchersModal(true);
|
|
6743
|
+
});
|
|
6744
|
+
btns.appendChild(d);
|
|
6745
|
+
row.appendChild(btns);
|
|
6746
|
+
list.appendChild(row);
|
|
6747
|
+
}
|
|
6701
6748
|
}
|
|
6702
6749
|
|
|
6703
6750
|
let lastWatchKey = '';
|
|
6704
6751
|
async function renderWatchersModal(force) {
|
|
6705
6752
|
let rows = []; try { rows = (await beast.watchersList()) || []; } catch {}
|
|
6753
|
+
let alerts = [];
|
|
6754
|
+
try {
|
|
6755
|
+
const ar = await beast.financeAlertsList();
|
|
6756
|
+
alerts = (ar && ar.alerts) || [];
|
|
6757
|
+
} catch {}
|
|
6706
6758
|
/* canlı yenilemede içerik değişmediyse DOM'a dokunma — buton tıklamaları
|
|
6707
6759
|
yeniden kurulurken yutulmasın */
|
|
6708
|
-
const key = JSON.stringify(rows);
|
|
6760
|
+
const key = JSON.stringify([rows, alerts]);
|
|
6709
6761
|
if (!force && key === lastWatchKey) return;
|
|
6710
6762
|
lastWatchKey = key;
|
|
6711
6763
|
els.watchList.innerHTML = '';
|
|
6712
|
-
if (!rows.length) {
|
|
6764
|
+
if (!rows.length && !alerts.length) {
|
|
6713
6765
|
els.watchList.innerHTML = '<div class="mini-empty">' + _t('w_empty') + '</div>';
|
|
6714
6766
|
return;
|
|
6715
6767
|
}
|
|
@@ -6724,7 +6776,7 @@ async function renderWatchersModal(force) {
|
|
|
6724
6776
|
`<span class="mr-dot"></span>` +
|
|
6725
6777
|
`<div class="mr-main">` +
|
|
6726
6778
|
`<div class="mr-name">${escapeHtml(w.name)}</div>` +
|
|
6727
|
-
`<div class="mr-meta">[${w.kind}] ${escapeHtml(String(target).slice(0, 60))} · <b>${opText(w)}</b> · her <b>${fmtEvery(w)}</b> ·
|
|
6779
|
+
`<div class="mr-meta">[${w.kind}] ${escapeHtml(String(target).slice(0, 60))} · <b>${opText(w)}</b> · her <b>${fmtEvery(w)}</b> · ${w.once ? '<b>tek seferlik</b>' : 'cd ' + w.cooldownMin + 'dk'}` +
|
|
6728
6780
|
(w.lastValue !== null && w.lastValue !== undefined ? ` · son: <b>${escapeHtml(String(w.lastValue).slice(0, 20))}</b>` : '') +
|
|
6729
6781
|
(w.lastError ? ` · <span style="color:var(--err)">${escapeHtml(w.lastError.slice(0, 40))}</span>` : '') +
|
|
6730
6782
|
`</div></div>`;
|
|
@@ -6749,6 +6801,34 @@ async function renderWatchersModal(force) {
|
|
|
6749
6801
|
row.appendChild(btns);
|
|
6750
6802
|
els.watchList.appendChild(row);
|
|
6751
6803
|
}
|
|
6804
|
+
/* Beast Finance FİYAT ALARMLARI: modalda da watcher'larla birlikte */
|
|
6805
|
+
for (const a of alerts) {
|
|
6806
|
+
const info = finAlertMeta(a);
|
|
6807
|
+
const row = document.createElement('div');
|
|
6808
|
+
row.className = 'mini-row alarm';
|
|
6809
|
+
row.innerHTML =
|
|
6810
|
+
`<span class="mr-dot"></span>` +
|
|
6811
|
+
`<div class="mr-main">` +
|
|
6812
|
+
`<div class="mr-name">${escapeHtml(info.name)}</div>` +
|
|
6813
|
+
`<div class="mr-meta">${escapeHtml(info.meta)}` +
|
|
6814
|
+
(a.note ? ` · ${escapeHtml(String(a.note).slice(0, 50))}` : '') +
|
|
6815
|
+
`</div></div>`;
|
|
6816
|
+
const btns = document.createElement('div');
|
|
6817
|
+
btns.style.cssText = 'display:flex;gap:6px;flex:none';
|
|
6818
|
+
const d = document.createElement('button');
|
|
6819
|
+
d.className = 'mr-btn del';
|
|
6820
|
+
d.textContent = '×';
|
|
6821
|
+
d.title = _t('cr_del');
|
|
6822
|
+
d.addEventListener('click', async () => {
|
|
6823
|
+
await beast.financeAlertRemove(a.id);
|
|
6824
|
+
lastWatchKey = '';
|
|
6825
|
+
renderWatchersModal(true);
|
|
6826
|
+
renderWatchersPane(true);
|
|
6827
|
+
});
|
|
6828
|
+
btns.appendChild(d);
|
|
6829
|
+
row.appendChild(btns);
|
|
6830
|
+
els.watchList.appendChild(row);
|
|
6831
|
+
}
|
|
6752
6832
|
}
|
|
6753
6833
|
|
|
6754
6834
|
/* ---------- İZLEYİCİ LOG MODALI ----------
|
|
@@ -7801,6 +7881,8 @@ async function init() {
|
|
|
7801
7881
|
name: $('#wAddName').value.trim(),
|
|
7802
7882
|
kind,
|
|
7803
7883
|
everyMin: Number($('#wAddEvery').value) || 15,
|
|
7884
|
+
cooldownMin: Math.max(0, Number($('#wAddCd') ? $('#wAddCd').value : 60) || 0),
|
|
7885
|
+
once: !!($('#wAddOnce') && $('#wAddOnce').checked),
|
|
7804
7886
|
};
|
|
7805
7887
|
if (kind === 'web') {
|
|
7806
7888
|
input.url = $('#wAddUrl').value.trim();
|
|
@@ -7823,6 +7905,8 @@ async function init() {
|
|
|
7823
7905
|
const el = $(id);
|
|
7824
7906
|
if (el) el.value = '';
|
|
7825
7907
|
}
|
|
7908
|
+
if ($('#wAddOnce')) $('#wAddOnce').checked = false;
|
|
7909
|
+
if ($('#wAddCd')) $('#wAddCd').value = '60';
|
|
7826
7910
|
lastWatchKey = '';
|
|
7827
7911
|
lastWatchPaneKey = '';
|
|
7828
7912
|
await renderWatchersModal(true);
|
|
@@ -9979,6 +10063,7 @@ function finRenderAlerts(list) {
|
|
|
9979
10063
|
row.innerHTML =
|
|
9980
10064
|
'<span class="fa-sym">' + escapeHtml(String(a.symbol || '?')) + '</span>' +
|
|
9981
10065
|
'<span class="fa-cond">' + (a.direction === 'below' ? '≤' : '≥') + ' ' + (Number(a.price) || 0) + '</span>' +
|
|
10066
|
+
'<span class="fa-note">' + (a.once ? 'tek seferlik' : `her ${Number(a.cooldownMin) || 5} dk`) + (a.fires ? ` · ${a.fires}×` : '') + '</span>' +
|
|
9982
10067
|
(a.note ? '<span class="fa-note">' + escapeHtml(String(a.note)) + '</span>' : '');
|
|
9983
10068
|
box.appendChild(row);
|
|
9984
10069
|
}
|
package/src/renderer/style.css
CHANGED
|
@@ -1504,6 +1504,8 @@ body.browser-open .mini-overlay { right: var(--bw, 480px); }
|
|
|
1504
1504
|
.mini-row.off .mr-name, .mini-row.off .mr-meta { opacity: 0.45; }
|
|
1505
1505
|
.mr-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok); flex: none; }
|
|
1506
1506
|
.mini-row.off .mr-dot { background: var(--muted); }
|
|
1507
|
+
/* Beast Finance fiyat alarmları: izleyici listesinde turuncu nokta ile ayrılır */
|
|
1508
|
+
.wp-row.alarm .wp-dot, .mini-row.alarm .mr-dot { background: #e6a117; }
|
|
1507
1509
|
.mr-main { flex: 1; min-width: 0; }
|
|
1508
1510
|
.mr-name { font-weight: 700; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1509
1511
|
.mr-meta { color: var(--muted); font-size: 11px; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|