beast-agent 2.51.1 → 2.53.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/financetools.js +3 -3
- package/src/agent/finwatch.js +82 -1
- package/src/main.js +231 -4
package/package.json
CHANGED
|
@@ -396,7 +396,7 @@ const handlers = {
|
|
|
396
396
|
deals: deals.slice(-60),
|
|
397
397
|
};
|
|
398
398
|
},
|
|
399
|
-
async mt5_alerts(args) {
|
|
399
|
+
async mt5_alerts(args, ctx) {
|
|
400
400
|
const action = String(args.action || 'list').toLowerCase();
|
|
401
401
|
if (action === 'list') {
|
|
402
402
|
const list = alertsApi.list();
|
|
@@ -415,9 +415,9 @@ const handlers = {
|
|
|
415
415
|
if (!symbol) return { ok: false, error: 'symbol gerekli' };
|
|
416
416
|
if (!isFinite(price) || price <= 0) return { ok: false, error: 'price gerekli (pozitif sayı)' };
|
|
417
417
|
if (direction !== 'above' && direction !== 'below') return { ok: false, error: "direction 'above' veya 'below' olmalı" };
|
|
418
|
-
const alarm = alertsApi.set({ symbol, price, direction, note: String(args.note || '').slice(0, 200) });
|
|
418
|
+
const alarm = alertsApi.set({ symbol, price, direction, note: String(args.note || '').slice(0, 200), sid: ctx && ctx.sessionId ? String(ctx.sessionId) : '' });
|
|
419
419
|
if (!alarm) return { ok: false, error: 'alarm kurulamadı' };
|
|
420
|
-
noteTrade('alert-set', { symbol, price, direction, id: alarm.id, note: alarm.note }, {});
|
|
420
|
+
noteTrade('alert-set', { symbol, price, direction, id: alarm.id, note: alarm.note }, ctx || {});
|
|
421
421
|
return { ok: true, alert: alarm };
|
|
422
422
|
}
|
|
423
423
|
return { ok: false, error: "action: list|set|remove" };
|
package/src/agent/finwatch.js
CHANGED
|
@@ -107,4 +107,85 @@ function plan(pos, meta, st, cfg) {
|
|
|
107
107
|
return out;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
/* ---- kapanış nedeni (MT5 deal reason) ----
|
|
111
|
+
İşlem SL/TP ile mi kapandı? Saf sınıflandırma: 4 = stop-loss,
|
|
112
|
+
5 = take-profit, 6 = stop-out (teminat). Diğerleri (manuel/EA/broker)
|
|
113
|
+
BOŞ döner — bu olaylarda ajan uyandırılmaz. */
|
|
114
|
+
function closeKind(reason) {
|
|
115
|
+
const n = Number(reason);
|
|
116
|
+
if (n === 4) return 'stop';
|
|
117
|
+
if (n === 5) return 'tp';
|
|
118
|
+
if (n === 6) return 'stopout';
|
|
119
|
+
return '';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* ---- bekleyen emir (pending) → pozisyon eşleştirme ----
|
|
123
|
+
Watchdog turu emir listesini karşılaştırır: listeden DÜŞEN emir için aynı
|
|
124
|
+
turda YENİ açılan (ya da netting hesapta hacmi artan) eşleşen bir pozisyon
|
|
125
|
+
varsa emir AKTİFLEŞTİ (tetiklendi); eşleşme yoksa İPTAL/süresi doldu.
|
|
126
|
+
Saf karar katmanı: IO ve ajan uyarısı main süreçte yapılır. */
|
|
127
|
+
|
|
128
|
+
const ORDER_TYPE_LABELS = {
|
|
129
|
+
0: 'BUY LIMIT',
|
|
130
|
+
1: 'SELL LIMIT',
|
|
131
|
+
2: 'BUY STOP',
|
|
132
|
+
3: 'SELL STOP',
|
|
133
|
+
4: 'BUY STOP LIMIT',
|
|
134
|
+
5: 'SELL STOP LIMIT',
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/* MT5 emir tipleri: çift = alış, tek = satış */
|
|
138
|
+
function orderSide(o) {
|
|
139
|
+
return Number(o && o.type) % 2 === 1 ? 'sell' : 'buy';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function orderTypeLabel(o) {
|
|
143
|
+
const n = Number(o && o.type);
|
|
144
|
+
return ORDER_TYPE_LABELS[n] || 'TIP ' + (isFinite(n) ? n : '?');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function orderVolume(o) {
|
|
148
|
+
return num(o && (o.volume_current != null ? o.volume_current : o.volume_initial != null ? o.volume_initial : o.volume), 0);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* goneOrders: önceki turda var olup şimdi listede OLMAYAN emirler
|
|
152
|
+
freshPositions: bu tur yeni açılan / hacmi artan pozisyonlar
|
|
153
|
+
Dönüş: { activated:[{order,position}], canceled:[order] } */
|
|
154
|
+
function matchPendingDelta(goneOrders, freshPositions) {
|
|
155
|
+
const out = { activated: [], canceled: [] };
|
|
156
|
+
const free = (Array.isArray(freshPositions) ? freshPositions : []).filter(Boolean);
|
|
157
|
+
for (const o of Array.isArray(goneOrders) ? goneOrders : []) {
|
|
158
|
+
if (!o) continue;
|
|
159
|
+
const side = orderSide(o);
|
|
160
|
+
const symbol = String(o.symbol || '').toUpperCase();
|
|
161
|
+
const ovol = orderVolume(o);
|
|
162
|
+
const oprice = num(o.price_open, 0);
|
|
163
|
+
const omagic = Number(o.magic) || 0;
|
|
164
|
+
let best = -1;
|
|
165
|
+
let bestScore = Infinity;
|
|
166
|
+
for (let i = 0; i < free.length; i++) {
|
|
167
|
+
const p = free[i];
|
|
168
|
+
if (!p) continue;
|
|
169
|
+
if (String(p.symbol || '').toUpperCase() !== symbol) continue;
|
|
170
|
+
if ((num(p.type, -1) === 0 ? 'buy' : 'sell') !== side) continue;
|
|
171
|
+
/* fiyat yakınlığı + hacim yakınlığı; aynı magic KESİN öncelik */
|
|
172
|
+
let score = Math.abs(num(p.price_open, 0) - oprice);
|
|
173
|
+
const pvol = num(p.volume, 0);
|
|
174
|
+
if (ovol > 0 && pvol > 0) score += Math.abs(pvol - ovol) * 0.01;
|
|
175
|
+
if (omagic !== 0 && (Number(p.magic) || 0) === omagic) score -= 1e9;
|
|
176
|
+
if (score < bestScore) {
|
|
177
|
+
bestScore = score;
|
|
178
|
+
best = i;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (best >= 0) {
|
|
182
|
+
out.activated.push({ order: o, position: free[best] });
|
|
183
|
+
free.splice(best, 1);
|
|
184
|
+
} else {
|
|
185
|
+
out.canceled.push(o);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = { plan, roundTo, stepRound, closeKind, orderSide, orderTypeLabel, orderVolume, matchPendingDelta };
|
package/src/main.js
CHANGED
|
@@ -596,6 +596,16 @@ function startEventBus() {
|
|
|
596
596
|
notify: (sub, text) => {
|
|
597
597
|
try {
|
|
598
598
|
waLog(`olay bildirimi → sid=${sub.sessionId} (${sub.type})`);
|
|
599
|
+
/* FINANCE AJANI: olay aboneliği alarmı AJAN DM'i olarak düşer ve
|
|
600
|
+
boştaki ajanı tur beklemeden uyandırır; diğer oturumlar aynı. */
|
|
601
|
+
if (financeState.agents.has(String(sub.sessionId))) {
|
|
602
|
+
finWakeAgentDm(
|
|
603
|
+
sub.sessionId,
|
|
604
|
+
`[FİNANS OLAYI — OLAY ABONELİĞİ (${sub.type})]\n${String(text || '')}\n` +
|
|
605
|
+
`Şimdi yap: aboneliği kurma nedenini hatırla; durumu değerlendir, gerekiyorsa işlem/uyarı üret. Kısa rapor ver.`
|
|
606
|
+
);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
599
609
|
engine.send(sub.sessionId, { text });
|
|
600
610
|
} catch {}
|
|
601
611
|
},
|
|
@@ -7673,6 +7683,17 @@ function watcherFire(w, value) {
|
|
|
7673
7683
|
? 'izlenen değer değişti'
|
|
7674
7684
|
: `kural sağlandı (son değer ${value}, koşul ${w.op} ${w.value ?? ''})`;
|
|
7675
7685
|
toastNotify(`İzleyici: ${w.name}`, target, 'watchers');
|
|
7686
|
+
/* FINANCE AJANI: izleyici alarmı AJAN DM'i olarak düşer ve boştaki ajanı
|
|
7687
|
+
tur beklemeden uyandırır (main.js finWakeAgentDm). Diğer oturumlarda
|
|
7688
|
+
eski davranış: mesaj doğrudan sohbete iner. */
|
|
7689
|
+
if (financeState.agents.has(String(sid))) {
|
|
7690
|
+
finWakeAgentDm(
|
|
7691
|
+
sid,
|
|
7692
|
+
`[FİNANS OLAYI — İZLEYİCİ ALARMI: ${w.name}]\n- ${target}\n` +
|
|
7693
|
+
`Şimdi yap: izleyiciyi kurma nedenini hatırla; durumu değerlendir, gerekiyorsa işlem/uyarı üret. Kısa rapor ver.`
|
|
7694
|
+
);
|
|
7695
|
+
return;
|
|
7696
|
+
}
|
|
7676
7697
|
engine.send(sid, {
|
|
7677
7698
|
text:
|
|
7678
7699
|
`[IZLEYICI: ${w.name}] ${target}. ` +
|
|
@@ -9234,6 +9255,9 @@ const financeState = {
|
|
|
9234
9255
|
agents: new Map(), /* sid -> { symbols, main, round, timer } — AYNI ANDA koşan finance ajanları */
|
|
9235
9256
|
watch: new Map(), /* ticket -> risk otomasyonu durumu (R, BE, kısmi TP...) */
|
|
9236
9257
|
excursions: new Map(), /* ticket -> { symbol, side, mfe, mae, at } — MAE/MFE takibi */
|
|
9258
|
+
pendingOrders: new Map(), /* ticket -> bekleyen emir (son başarılı okuma) */
|
|
9259
|
+
pendingReady: false, /* emir tabanı kuruldu mu (ilk tur sessiz; köprü kopunca sıfırlanır) */
|
|
9260
|
+
posVolume: new Map(), /* ticket -> hacim (netting hesapta artış = yeni dolum) */
|
|
9237
9261
|
excDirty: false,
|
|
9238
9262
|
excSavedAt: 0,
|
|
9239
9263
|
watchTimer: null,
|
|
@@ -9766,6 +9790,7 @@ function finAlertsLoad() {
|
|
|
9766
9790
|
price: Number(a && a.price) || 0,
|
|
9767
9791
|
direction: String((a && a.direction) || 'above').toLowerCase(),
|
|
9768
9792
|
note: String((a && a.note) || '').slice(0, 200),
|
|
9793
|
+
sid: String((a && a.sid) || ''), /* alarmı kuran ajan oturumu (uyandırma hedefi) */
|
|
9769
9794
|
at: Number(a && a.at) || Date.now(),
|
|
9770
9795
|
}))
|
|
9771
9796
|
.filter((a) => a.id && a.symbol && a.price > 0);
|
|
@@ -9788,6 +9813,7 @@ function finAlertApi() {
|
|
|
9788
9813
|
price,
|
|
9789
9814
|
direction: String((a && a.direction) || 'above').toLowerCase() === 'below' ? 'below' : 'above',
|
|
9790
9815
|
note: String((a && a.note) || '').slice(0, 200),
|
|
9816
|
+
sid: String((a && a.sid) || ''),
|
|
9791
9817
|
at: Date.now(),
|
|
9792
9818
|
};
|
|
9793
9819
|
financeState.alerts.push(alarm);
|
|
@@ -9941,17 +9967,72 @@ async function finStatsRefresh(force) {
|
|
|
9941
9967
|
}
|
|
9942
9968
|
}
|
|
9943
9969
|
|
|
9970
|
+
/* ---- FİNANS OLAY → AJAN DM UYANDIRMA ----
|
|
9971
|
+
Stop/TP/bekleyen emir aktivasyonu/fiyat alarmı gibi olaylar koşan finance
|
|
9972
|
+
ajanlarına AJAN DM'i olarak düşer ve BOŞTAKİ ajanı TUR SANİYESİNİ
|
|
9973
|
+
BEKLEMEDEN uyandırır: dm+wake kaydı engine._pendingReports'a girer;
|
|
9974
|
+
flushPendingReports bunu continuous ajanın dmInbox'ına yazar ve
|
|
9975
|
+
onDmQueued → finWakeAgent turu hemen başlatır. Ajan meşgulse mesaj
|
|
9976
|
+
KAYBOLMAZ — tur biter bitmez (interval beklenmeden) yeni turda okunur
|
|
9977
|
+
(finWakeAgent.wakePending → finFlushOnDone).
|
|
9978
|
+
Olay ayrıca ekip DM grubuna sistem postu düşer (panelde canlı görünür). */
|
|
9979
|
+
function finWakeAgentDm(sid, text) {
|
|
9980
|
+
const id = String(sid || '');
|
|
9981
|
+
const agent = financeState.agents.get(id);
|
|
9982
|
+
if (!agent || !engine) return false;
|
|
9983
|
+
try {
|
|
9984
|
+
const body = agent.role
|
|
9985
|
+
? String(text || '') + "\n(ROL HATIRLATMASI: işlem AÇMA — durumu analiz et; gerekiyorsa agent_dm ile ANA TRADER'a bildir.)"
|
|
9986
|
+
: String(text || '');
|
|
9987
|
+
(engine._pendingReports = engine._pendingReports || []).push({
|
|
9988
|
+
parentId: id,
|
|
9989
|
+
text: body,
|
|
9990
|
+
dm: true,
|
|
9991
|
+
wake: true,
|
|
9992
|
+
});
|
|
9993
|
+
engine.flushPendingReports(id);
|
|
9994
|
+
return true;
|
|
9995
|
+
} catch {
|
|
9996
|
+
return false;
|
|
9997
|
+
}
|
|
9998
|
+
}
|
|
9999
|
+
|
|
10000
|
+
/* Tüm koşan finance ajanlarını olayla uyandır. Dönüş: uyandırılan ajan sayısı. */
|
|
10001
|
+
function finWakeAgents(text, meta) {
|
|
10002
|
+
const body = String(text || '').trim();
|
|
10003
|
+
if (!body || !engine) return 0;
|
|
10004
|
+
if (!financeState.agents.size) {
|
|
10005
|
+
financeLog('[olay] koşan finance ajanı yok — uyarı yalnız günlük/bildirimde' + (meta && meta.kind ? ' (' + meta.kind + ')' : ''));
|
|
10006
|
+
return 0;
|
|
10007
|
+
}
|
|
10008
|
+
/* Panel görünürlüğü: olay ekip DM grubuna sistem postu olarak düşer */
|
|
10009
|
+
try {
|
|
10010
|
+
if (typeof engine._agentTeamPost === 'function') {
|
|
10011
|
+
engine._agentTeamPost('team:finance', 'finance', 'Beast Finance', body);
|
|
10012
|
+
}
|
|
10013
|
+
} catch {}
|
|
10014
|
+
let sent = 0;
|
|
10015
|
+
for (const [sid] of financeState.agents) {
|
|
10016
|
+
if (finWakeAgentDm(sid, body)) sent += 1;
|
|
10017
|
+
}
|
|
10018
|
+
return sent;
|
|
10019
|
+
}
|
|
10020
|
+
|
|
9944
10021
|
/* ---- işlem günlüğü (görülen/kapanan pozisyonlar) ---- */
|
|
9945
10022
|
async function finRecordClose(ticket, st) {
|
|
9946
10023
|
let net = null;
|
|
9947
10024
|
let symbol = (st && st.symbol) || '';
|
|
10025
|
+
let closeReason = null; /* kapanışı yapan son deal'in nedeni (4=SL, 5=TP, 6=stop-out) */
|
|
9948
10026
|
try {
|
|
9949
10027
|
const r = await mt5bridge.call('deals', { days: 3 }, 12000);
|
|
9950
10028
|
const deals = (r && r.ok && r.data && r.data.deals) || [];
|
|
9951
10029
|
const mine = deals.filter((d) => String(d.position_id) === String(ticket));
|
|
9952
10030
|
if (mine.length) {
|
|
9953
10031
|
net = 0;
|
|
9954
|
-
for (const d of mine)
|
|
10032
|
+
for (const d of mine) {
|
|
10033
|
+
net += (Number(d.profit) || 0) + (Number(d.swap) || 0) + (Number(d.commission) || 0);
|
|
10034
|
+
if (Number(d.entry) === 1) closeReason = Number(d.reason);
|
|
10035
|
+
}
|
|
9955
10036
|
symbol = String(mine[0].symbol || symbol);
|
|
9956
10037
|
}
|
|
9957
10038
|
} catch {}
|
|
@@ -9963,12 +10044,26 @@ async function finRecordClose(ticket, st) {
|
|
|
9963
10044
|
financeState.excursions.delete(String(ticket));
|
|
9964
10045
|
finExcSave();
|
|
9965
10046
|
finExcFlush(true);
|
|
9966
|
-
|
|
10047
|
+
const kind = finwatch.closeKind(closeReason);
|
|
10048
|
+
finJournal({ kind: 'close', ticket, symbol, net: rounded, side: (st && st.side) || '', mfe, mae, reason: kind || '' });
|
|
9967
10049
|
const pl = rounded == null ? '' : ` · K/Z ${rounded >= 0 ? '+' : ''}${rounded.toFixed(2)}`;
|
|
9968
10050
|
const line = `🏁 Pozisyon kapandı: ${symbol || '?'} #${ticket}${pl}`;
|
|
9969
10051
|
financeLog('[watchdog] ' + line);
|
|
9970
10052
|
if (finCfg().notifyTrades !== false) financeNotify(line, 'close');
|
|
9971
10053
|
if (rounded != null) finStatsRefresh(true);
|
|
10054
|
+
/* STOP / TP / STOP-OUT: koşan tüm finance ajanları TUR BEKLEMEDEN uyanır
|
|
10055
|
+
(ajan DM kutusu + boştaysa anında tur) — manuel kapanışta uyandırma yok. */
|
|
10056
|
+
if (kind) {
|
|
10057
|
+
const label = kind === 'stop' ? 'STOP OLDU' : kind === 'tp' ? 'TP OLDU' : 'STOP-OUT (teminat)';
|
|
10058
|
+
const how = kind === 'tp' ? 'take-profit' : kind === 'stop' ? 'stop-loss' : 'stop-out';
|
|
10059
|
+
const netTxt = rounded == null ? '?' : `${rounded >= 0 ? '+' : ''}${rounded.toFixed(2)}`;
|
|
10060
|
+
finWakeAgents(
|
|
10061
|
+
`[FİNANS OLAYI — ${label}, TUR SANİYESİ BEKLENMEDEN İLETİLDİ]\n` +
|
|
10062
|
+
`- ${symbol || '?'}${st && st.side ? ' ' + String(st.side).toUpperCase() : ''} #${ticket} ${how} ile kapandı · net ${netTxt}\n` +
|
|
10063
|
+
`Şimdi yap: pozisyon kapandı — hesabı, risk durumunu (kayıp serisi/günlük limit) ve planını HEMEN değerlendir; gerekiyorsa yeni emir aç ya da BEKLE. Kısa rapor ver.`,
|
|
10064
|
+
{ kind, ticket, symbol }
|
|
10065
|
+
);
|
|
10066
|
+
}
|
|
9972
10067
|
}
|
|
9973
10068
|
|
|
9974
10069
|
/* ---- risk otomasyonu (watchdog) ---- */
|
|
@@ -10016,23 +10111,97 @@ async function finCheckAlerts() {
|
|
|
10016
10111
|
financeLog('[alarm] ' + line);
|
|
10017
10112
|
financeNotify(line, 'alert');
|
|
10018
10113
|
finJournal({ kind: 'alert', symbol: a.symbol, price: a.price, direction: a.direction, note: a.note || '' });
|
|
10114
|
+
/* ALARM SAHİBİ AJAN: alarmı kuran koşan finance ajanı DM'den uyanır;
|
|
10115
|
+
sahip koşmuyor/biliniyorsa koşan tüm finance ajanlarına düşer. */
|
|
10116
|
+
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
|
+
`Şimdi yap: alarmı kurma nedenini hatırla; fiyat seviyesini ve planını değerlendir, gerekiyorsa işlem/uyarı üret. Kısa rapor ver.`;
|
|
10120
|
+
if (!finWakeAgentDm(a.sid, wakeTxt)) finWakeAgents(wakeTxt, { kind: 'alarm', symbol: a.symbol });
|
|
10019
10121
|
}
|
|
10020
10122
|
if (changed) finAlertsSave();
|
|
10021
10123
|
}
|
|
10022
10124
|
|
|
10125
|
+
/* ---- BEKLEYEN EMİR HABERLERİ ----
|
|
10126
|
+
Aktifleşme: günlük + panel/kanal bildirimi + koşan TÜM finance ajanlarına
|
|
10127
|
+
AJAN DM'i — boştaki ajan tur saniyesini beklemeden uyanır, meşgul olan
|
|
10128
|
+
mesajı sıradaki güvenli noktada okur (finWakeAgents). İptal/süre doldu:
|
|
10129
|
+
yalnız günlük + bildirim (uyandırma yok). */
|
|
10130
|
+
function finNotifyPendingActivated(list) {
|
|
10131
|
+
const items = (Array.isArray(list) ? list : []).filter((x) => x && x.order && x.position);
|
|
10132
|
+
if (!items.length) return;
|
|
10133
|
+
const f = finCfg();
|
|
10134
|
+
const detail = [];
|
|
10135
|
+
for (const { order: o, position: p } of items) {
|
|
10136
|
+
const symbol = String(o.symbol || p.symbol || '?');
|
|
10137
|
+
const side = finwatch.orderSide(o);
|
|
10138
|
+
const line =
|
|
10139
|
+
`⚡ BEKLEYEN EMİR AKTİFLEŞTİ: ${symbol} ${finwatch.orderTypeLabel(o)} ${finwatch.orderVolume(o)}` +
|
|
10140
|
+
` → pozisyon #${String(p.ticket)} @ ${Number(p.price_open) || '?'}`;
|
|
10141
|
+
detail.push(
|
|
10142
|
+
`- ${symbol} · ${side === 'buy' ? 'ALIŞ' : 'SATIŞ'} · emir #${String(o.ticket || '?')}` +
|
|
10143
|
+
` (${finwatch.orderTypeLabel(o)} @ ${Number(o.price_open) || '?'}) → pozisyon #${String(p.ticket)}` +
|
|
10144
|
+
` · ${Number(p.volume) || '?'} lot @ ${Number(p.price_open) || '?'}` +
|
|
10145
|
+
` · SL ${Number(p.sl) || 0} / TP ${Number(p.tp) || 0}`
|
|
10146
|
+
);
|
|
10147
|
+
financeLog('[bekleyen] ' + line);
|
|
10148
|
+
finJournal({
|
|
10149
|
+
kind: 'pending-active',
|
|
10150
|
+
order: Number(o.ticket) || String(o.ticket || ''),
|
|
10151
|
+
ticket: Number(p.ticket) || 0,
|
|
10152
|
+
symbol,
|
|
10153
|
+
side,
|
|
10154
|
+
volume: Number(p.volume) || finwatch.orderVolume(o),
|
|
10155
|
+
price: Number(p.price_open) || Number(o.price_open) || 0,
|
|
10156
|
+
sl: Number(p.sl) || 0,
|
|
10157
|
+
tp: Number(p.tp) || 0,
|
|
10158
|
+
});
|
|
10159
|
+
finPush('trade', { line });
|
|
10160
|
+
if (f.notifyTrades !== false) {
|
|
10161
|
+
try { financeNotify(line, 'trade', false); } catch {}
|
|
10162
|
+
}
|
|
10163
|
+
}
|
|
10164
|
+
const body = [
|
|
10165
|
+
'[FİNANS OLAYI — BEKLEYEN EMİR AKTİFLEŞTİ, TUR SANİYESİ BEKLENMEDEN İLETİLDİ]',
|
|
10166
|
+
...detail,
|
|
10167
|
+
'Şimdi yap: yeni pozisyonu HEMEN değerlendir — SL/TP yerinde mi, korumasız mı, planına uygun mu; gerekiyorsa SL/TP güncelle ya da kapat. Kısa rapor ver.',
|
|
10168
|
+
'NOT: Bu bir ara uyarıdır; tur düzenin bu turdan sonra normal aralığıyla devam eder (tur sayacı/aralık değişmez).',
|
|
10169
|
+
].join('\n');
|
|
10170
|
+
finWakeAgents(body, { kind: 'pending-active' });
|
|
10171
|
+
}
|
|
10172
|
+
|
|
10173
|
+
/* Aktifleşmeden listeden düşen emir (iptal/süre doldu): günlük + bildirim */
|
|
10174
|
+
function finNotifyPendingGone(o) {
|
|
10175
|
+
if (!o) return;
|
|
10176
|
+
const line =
|
|
10177
|
+
`BEKLEYEN EMİR KALKTI: ${String(o.symbol || '?')} ${finwatch.orderTypeLabel(o)} ${finwatch.orderVolume(o)}` +
|
|
10178
|
+
` (emir #${String(o.ticket || '?')}) — pozisyona dönüşmedi (iptal/süre doldu).`;
|
|
10179
|
+
financeLog('[bekleyen] ' + line);
|
|
10180
|
+
finJournal({ kind: 'cancel', order: Number(o.ticket) || String(o.ticket || ''), symbol: String(o.symbol || ''), note: 'aktifleşmeden kalktı (iptal/süre doldu)' });
|
|
10181
|
+
if (finCfg().notifyTrades !== false) {
|
|
10182
|
+
finPush('trade', { line });
|
|
10183
|
+
try { financeNotify(line, 'cancel', false); } catch {}
|
|
10184
|
+
}
|
|
10185
|
+
}
|
|
10186
|
+
|
|
10023
10187
|
async function finWatchTick() {
|
|
10024
10188
|
if (!mt5bridge.running || financeState.watchBusy) return;
|
|
10025
10189
|
financeState.watchBusy = true;
|
|
10026
10190
|
try {
|
|
10027
10191
|
const cfg = finCfg();
|
|
10028
10192
|
financeState.watchTickAt = Date.now();
|
|
10029
|
-
const [posR, accR] = await Promise.all([
|
|
10193
|
+
const [posR, accR, ordR] = await Promise.all([
|
|
10030
10194
|
mt5bridge.call('positions', {}, 8000).catch(() => null),
|
|
10031
10195
|
mt5bridge.call('account', {}, 8000).catch(() => null),
|
|
10196
|
+
mt5bridge.call('orders', {}, 8000).catch(() => null),
|
|
10032
10197
|
]);
|
|
10033
10198
|
const account = accR && accR.ok ? (accR.data && accR.data.account) : null;
|
|
10034
10199
|
const positionsOk = !!(posR && posR.ok);
|
|
10035
10200
|
const positions = positionsOk ? ((posR.data && posR.data.positions) || []) : [];
|
|
10201
|
+
const ordersOk = !!(ordR && ordR.ok);
|
|
10202
|
+
const orders = ordersOk ? ((ordR.data && ordR.data.orders) || []) : [];
|
|
10203
|
+
/* bekleyen emir takibi için ÖNCEKİ durum: watch (açık ticket'lar) + hacimler */
|
|
10204
|
+
const prevTickets = new Set(financeState.watch.keys());
|
|
10036
10205
|
if (account) {
|
|
10037
10206
|
financeState.account = account;
|
|
10038
10207
|
finEquitySample(account);
|
|
@@ -10119,6 +10288,44 @@ async function finWatchTick() {
|
|
|
10119
10288
|
}
|
|
10120
10289
|
if (decision.rearmSL && st.warnSL) st.warnSL = false;
|
|
10121
10290
|
}
|
|
10291
|
+
/* ---- BEKLEYEN EMİR AKTİFLİĞİ ----
|
|
10292
|
+
Emir listesi her tur karşılaştırılır; listeden düşen emir için aynı anda
|
|
10293
|
+
yeni/eşleşen bir pozisyon oluştuysa emir AKTİFLEŞTİ → koşan finance
|
|
10294
|
+
ajanları TUR SANİYESİNİ BEKLEMEDEN ajan DM'iyle uyandırılır; tur düzeni
|
|
10295
|
+
sonra normal aralıkla sürer. (Köprü kesintisinde taban sıfırlanır —
|
|
10296
|
+
yanlış "aktifleşti" uyarısı yok.) */
|
|
10297
|
+
try {
|
|
10298
|
+
if (!(ordersOk && positionsOk)) {
|
|
10299
|
+
financeState.pendingReady = false;
|
|
10300
|
+
} else {
|
|
10301
|
+
const nowMap = new Map();
|
|
10302
|
+
for (const o of orders) if (o && o.ticket != null) nowMap.set(String(o.ticket), o);
|
|
10303
|
+
if (!financeState.pendingReady) {
|
|
10304
|
+
/* ilk başarılı okuma (açılış/bağlantı sonrası): sessizce taban al */
|
|
10305
|
+
financeState.pendingOrders = nowMap;
|
|
10306
|
+
financeState.pendingReady = true;
|
|
10307
|
+
} else {
|
|
10308
|
+
const gone = [];
|
|
10309
|
+
for (const [t, o] of financeState.pendingOrders) if (!nowMap.has(t)) gone.push(o);
|
|
10310
|
+
financeState.pendingOrders = nowMap;
|
|
10311
|
+
if (gone.length) {
|
|
10312
|
+
/* bu tur YENİ açılan ya da hacmi artan pozisyonlar (netting hesap) */
|
|
10313
|
+
const fresh = [];
|
|
10314
|
+
for (const p of positions) {
|
|
10315
|
+
if (!p) continue;
|
|
10316
|
+
const t = String(p.ticket);
|
|
10317
|
+
const vol = Number(p.volume) || 0;
|
|
10318
|
+
const before = financeState.posVolume.get(t);
|
|
10319
|
+
if (!prevTickets.has(t) || (before != null && vol > before + 1e-9)) fresh.push(p);
|
|
10320
|
+
}
|
|
10321
|
+
const delta = finwatch.matchPendingDelta(gone, fresh);
|
|
10322
|
+
if (delta.activated.length) finNotifyPendingActivated(delta.activated);
|
|
10323
|
+
for (const o of delta.canceled) finNotifyPendingGone(o);
|
|
10324
|
+
}
|
|
10325
|
+
}
|
|
10326
|
+
financeState.posVolume = new Map(positions.map((p) => [String(p.ticket), Number(p.volume) || 0]));
|
|
10327
|
+
}
|
|
10328
|
+
} catch {}
|
|
10122
10329
|
/* kapanan pozisyonlar: net K/Z ile günlük + bildirim (yalnız pozisyon
|
|
10123
10330
|
listesi GERÇEKTEN alındıysa — köprü kesintisinde yanlış kapanış yok) */
|
|
10124
10331
|
if (positionsOk) {
|
|
@@ -10571,6 +10778,13 @@ async function finEaStatusCheck() {
|
|
|
10571
10778
|
}
|
|
10572
10779
|
|
|
10573
10780
|
mt5bridge.on('bridge', (m) => {
|
|
10781
|
+
if (!(m && m.connected)) {
|
|
10782
|
+
/* köprü koptu: bekleyen emir tabanı sıfırlanır — kopukluk sırasında
|
|
10783
|
+
tetiklenen/iptal edilen emirler yanlış "aktifleşti" uyarısı üretmez */
|
|
10784
|
+
financeState.pendingReady = false;
|
|
10785
|
+
financeState.pendingOrders = new Map();
|
|
10786
|
+
financeState.posVolume = new Map();
|
|
10787
|
+
}
|
|
10574
10788
|
if (m && m.connected) {
|
|
10575
10789
|
financeLog('[MT5] terminal bağlı: ' + ((m.terminal && (m.terminal.name + ' @ ' + m.terminal.company)) || 'MT5'));
|
|
10576
10790
|
if (m.account) financeLog(`[MT5] hesap ${m.account.login} · bakiye ${m.account.balance} ${m.account.currency}`);
|
|
@@ -10953,7 +11167,12 @@ function finWakeAgent(sid) {
|
|
|
10953
11167
|
const id = String(sid || '');
|
|
10954
11168
|
const agent = financeState.agents.get(id);
|
|
10955
11169
|
if (!agent || !engine) return;
|
|
10956
|
-
if (engine.isBusy(id))
|
|
11170
|
+
if (engine.isBusy(id)) {
|
|
11171
|
+
/* tur sürüyor: olay dmInbox'ta bekler — tur bitince finFlushOnDone
|
|
11172
|
+
BEKLEMEDEN yeni tur açar (interval beklenmez) */
|
|
11173
|
+
agent.wakePending = true;
|
|
11174
|
+
return;
|
|
11175
|
+
}
|
|
10957
11176
|
clearTimeout(agent.timer);
|
|
10958
11177
|
agent.timer = setTimeout(() => { try { finAgentRound(id); } catch {} }, 300);
|
|
10959
11178
|
}
|
|
@@ -10970,6 +11189,14 @@ function finFlushOnDone(ev) {
|
|
|
10970
11189
|
return;
|
|
10971
11190
|
}
|
|
10972
11191
|
clearTimeout(agent.timer);
|
|
11192
|
+
/* TUR SIRASINDA GELEN OLAY/DM: tur bitti → interval BEKLENMEDEN yeni tur
|
|
11193
|
+
(dmInbox'taki olay mesajı bu turda okunur) */
|
|
11194
|
+
if (agent.wakePending) {
|
|
11195
|
+
agent.wakePending = false;
|
|
11196
|
+
financeState.lastRoundAt = Date.now();
|
|
11197
|
+
agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, 800);
|
|
11198
|
+
return;
|
|
11199
|
+
}
|
|
10973
11200
|
const f = finCfg();
|
|
10974
11201
|
const iv = Math.max(30, Number(f.intervalSec) || 120) * 1000;
|
|
10975
11202
|
financeState.lastRoundAt = Date.now();
|