beast-agent 2.76.6 → 2.77.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.
Files changed (54) hide show
  1. package/package.json +1 -1
  2. package/src/agent/bots.js +669 -664
  3. package/src/agent/customtools.js +6 -19
  4. package/src/agent/engine.js +55 -307
  5. package/src/agent/seeds/tool-yazma/SKILL.md +13 -16
  6. package/src/agent/skills.js +14 -35
  7. package/src/agent/supertonic.js +622 -0
  8. package/src/agent/tools.js +3 -7
  9. package/src/main.js +77 -6164
  10. package/src/preload.js +2 -18
  11. package/src/renderer/i18n.js +14 -8
  12. package/src/renderer/index.html +0 -297
  13. package/src/renderer/renderer.js +142 -2301
  14. package/src/renderer/style.css +3 -825
  15. package/src/agent/defaulttools/mt5_barlar2/run.js +0 -188
  16. package/src/agent/defaulttools/mt5_barlar2/tool.json +0 -23
  17. package/src/agent/defaulttools/mt5_bekleyen/run.js +0 -105
  18. package/src/agent/defaulttools/mt5_bekleyen/tool.json +0 -18
  19. package/src/agent/defaulttools/mt5_durum/run.js +0 -93
  20. package/src/agent/defaulttools/mt5_durum/tool.json +0 -10
  21. package/src/agent/defaulttools/mt5_emir/run.js +0 -98
  22. package/src/agent/defaulttools/mt5_emir/tool.json +0 -17
  23. package/src/agent/defaulttools/mt5_emir_iptal/run.js +0 -90
  24. package/src/agent/defaulttools/mt5_emir_iptal/tool.json +0 -12
  25. package/src/agent/defaulttools/mt5_fiyat/run.js +0 -87
  26. package/src/agent/defaulttools/mt5_fiyat/tool.json +0 -16
  27. package/src/agent/defaulttools/mt5_gecmis/run.js +0 -87
  28. package/src/agent/defaulttools/mt5_gecmis/tool.json +0 -12
  29. package/src/agent/defaulttools/mt5_kapat/run.js +0 -108
  30. package/src/agent/defaulttools/mt5_kapat/tool.json +0 -15
  31. package/src/agent/defaulttools/mt5_m15_m5/m15m5.py +0 -60
  32. package/src/agent/defaulttools/mt5_m15_m5/run.js +0 -73
  33. package/src/agent/defaulttools/mt5_m15_m5/tool.json +0 -10
  34. package/src/agent/defaulttools/mt5_pozisyon_gecmis/run.js +0 -144
  35. package/src/agent/defaulttools/mt5_pozisyon_gecmis/tool.json +0 -12
  36. package/src/agent/defaulttools/mt5_shot/run.js +0 -649
  37. package/src/agent/defaulttools/mt5_shot/stitch.js +0 -151
  38. package/src/agent/defaulttools/mt5_shot/tool.json +0 -25
  39. package/src/agent/defaulttools/mt5_sltp/run.js +0 -92
  40. package/src/agent/defaulttools/mt5_sltp/tool.json +0 -14
  41. package/src/agent/financetools.js +0 -1025
  42. package/src/agent/finindicators.js +0 -254
  43. package/src/agent/finrisk.js +0 -289
  44. package/src/agent/finstats.js +0 -239
  45. package/src/agent/finwatch.js +0 -302
  46. package/src/agent/mt5/BeastFinance.mq5 +0 -498
  47. package/src/agent/mt5/BeastShot.tpl +0 -0
  48. package/src/agent/mt5setup.js +0 -542
  49. package/src/agent/scripts/mt5_bridge.py +0 -575
  50. package/src/agent/seeds/haber-duygu/SKILL.md +0 -49
  51. package/src/agent/seeds/mql5/SKILL.md +0 -154
  52. package/src/agent/seeds/price-action/SKILL.md +0 -60
  53. package/src/agent/seeds/risk-yonetimi/SKILL.md +0 -52
  54. package/src/mt5bridge.js +0 -215
@@ -1,239 +0,0 @@
1
- 'use strict';
2
-
3
- /* Beast Finance — muhasebe matematiği (saf; köprü/IO yok).
4
- MT5 deals listesinden işlem istatistiği, equity serisinden drawdown ve
5
- haftalık rapor metni üretir. main süreç bu çıktıları dosyaya/panoya taşır. */
6
-
7
- function num(v, def) {
8
- const n = Number(v);
9
- return isFinite(n) ? n : def;
10
- }
11
-
12
- function dayKey(ms) {
13
- const d = new Date(Number(ms) || 0);
14
- const p = (x) => String(x).padStart(2, '0');
15
- return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate());
16
- }
17
-
18
- function isOut(entry) {
19
- return entry === 1 || entry === '1' || String(entry) === '1';
20
- }
21
-
22
- function once(v, d) {
23
- const n = Number(v);
24
- return isFinite(n) ? n : d;
25
- }
26
-
27
- /* deals: [{position_id, entry, profit, swap, commission, symbol, time, volume}] */
28
- function summarizeDeals(deals, opts) {
29
- const rows = Array.isArray(deals) ? deals : [];
30
- const since = opts && Number.isFinite(Number(opts.sinceMs)) ? Number(opts.sinceMs) : 0;
31
- const groups = new Map();
32
- for (const dl of rows) {
33
- const pid = String(dl.position_id != null ? dl.position_id : dl.ticket != null ? dl.ticket : '');
34
- if (!pid) continue;
35
- let g = groups.get(pid);
36
- if (!g) {
37
- g = { pid, symbol: String(dl.symbol || ''), net: 0, hasOut: false, openTime: Infinity, closeTime: 0, volume: 0 };
38
- groups.set(pid, g);
39
- }
40
- g.net += once(dl.profit, 0) + once(dl.swap, 0) + once(dl.commission, 0);
41
- const t = once(dl.time, 0) * (once(dl.time, 0) > 1e12 ? 1 : 1000);
42
- if (t > 0) {
43
- if (t < g.openTime) g.openTime = t;
44
- if (t > g.closeTime) g.closeTime = t;
45
- }
46
- if (isOut(dl.entry)) g.hasOut = true;
47
- if (!g.symbol && dl.symbol) g.symbol = String(dl.symbol);
48
- g.volume = Math.max(g.volume, once(dl.volume, 0));
49
- }
50
- const closed = [...groups.values()].filter((g) => g.hasOut && (!since || g.closeTime >= since));
51
- const total = closed.length;
52
- let wins = 0;
53
- let losses = 0;
54
- let breakeven = 0;
55
- let grossProfit = 0;
56
- let grossLoss = 0;
57
- let best = 0;
58
- let worst = 0;
59
- let bestSymbol = '';
60
- let worstSymbol = '';
61
- const bySymbol = {};
62
- const byDay = {};
63
- let lastTradeAt = 0;
64
- for (const g of closed) {
65
- const n = Math.round(g.net * 100) / 100;
66
- const k = dayKey(g.closeTime);
67
- byDay[k] = Math.round(((byDay[k] || 0) + n) * 100) / 100;
68
- if (!bySymbol[g.symbol]) bySymbol[g.symbol] = { net: 0, trades: 0, wins: 0, losses: 0 };
69
- const s = bySymbol[g.symbol];
70
- s.net = Math.round((s.net + n) * 100) / 100;
71
- s.trades++;
72
- if (n > 0) {
73
- wins++;
74
- s.wins++;
75
- grossProfit += n;
76
- if (n > best) {
77
- best = n;
78
- bestSymbol = g.symbol;
79
- }
80
- } else if (n < 0) {
81
- losses++;
82
- s.losses++;
83
- grossLoss += -n;
84
- if (n < worst) {
85
- worst = n;
86
- worstSymbol = g.symbol;
87
- }
88
- } else {
89
- breakeven++;
90
- }
91
- if (g.closeTime > lastTradeAt) lastTradeAt = g.closeTime;
92
- }
93
- const netProfit = Math.round((grossProfit - grossLoss) * 100) / 100;
94
- return {
95
- trades: total,
96
- wins,
97
- losses,
98
- breakeven,
99
- winRate: total ? Math.round((wins / total) * 1000) / 10 : 0,
100
- netProfit,
101
- grossProfit: Math.round(grossProfit * 100) / 100,
102
- grossLoss: Math.round(grossLoss * 100) / 100,
103
- profitFactor: grossLoss > 0 ? Math.round((grossProfit / grossLoss) * 100) / 100 : grossProfit > 0 ? null : 0,
104
- avgWin: wins ? Math.round((grossProfit / wins) * 100) / 100 : 0,
105
- avgLoss: losses ? Math.round((grossLoss / losses) * 100) / 100 : 0,
106
- best: Math.round(best * 100) / 100,
107
- bestSymbol,
108
- worst: Math.round(worst * 100) / 100,
109
- worstSymbol,
110
- bySymbol,
111
- byDay,
112
- lastTradeAt: lastTradeAt || 0,
113
- at: Date.now(),
114
- };
115
- }
116
-
117
- /* points: [{at, equity}] — sırasız gelebilir; zamana göre sıralanır. */
118
- function maxDrawdown(points) {
119
- const list = (Array.isArray(points) ? points : [])
120
- .map((p) => ({ at: num(p && p.at, 0), equity: num(p && p.equity, NaN) }))
121
- .filter((p) => isFinite(p.equity))
122
- .sort((a, b) => a.at - b.at);
123
- let peak = -Infinity;
124
- let peakAt = 0;
125
- let maxAbs = 0;
126
- let maxPct = 0;
127
- let troughAt = 0;
128
- for (const p of list) {
129
- if (p.equity > peak) {
130
- peak = p.equity;
131
- peakAt = p.at;
132
- }
133
- if (peak > 0) {
134
- const abs = peak - p.equity;
135
- const pct = (abs / peak) * 100;
136
- if (abs > maxAbs) {
137
- maxAbs = abs;
138
- maxPct = pct;
139
- troughAt = p.at;
140
- }
141
- }
142
- }
143
- return {
144
- maxAbs: Math.round(maxAbs * 100) / 100,
145
- maxPct: Math.round(maxPct * 100) / 100,
146
- peakAt,
147
- troughAt,
148
- samples: list.length,
149
- };
150
- }
151
-
152
- function money(v, cur) {
153
- const n = num(v, 0);
154
- const sign = n > 0 ? '+' : '';
155
- return sign + n.toFixed(2) + (cur ? ' ' + cur : '');
156
- }
157
-
158
- /* Haftalık rapor metni (markdown). review: ajan eleştirisi (opsiyonel). */
159
- function buildWeeklyReport(ctx) {
160
- const c = ctx || {};
161
- const st = c.stats || {};
162
- const cur = (c.account && c.account.currency) || '';
163
- const L = [];
164
- const from = dayKey(c.fromTs || Date.now() - 7 * 86400000);
165
- const to = dayKey(c.toTs || Date.now());
166
- L.push('# Beast Finance — Haftalık Rapor');
167
- L.push('');
168
- L.push('Dönem: **' + from + ' → ' + to + '**');
169
- L.push('');
170
- if (c.account) {
171
- L.push('## Hesap');
172
- L.push('- Bakiye: **' + num(c.account.balance, 0).toFixed(2) + ' ' + cur + '** · Özkaynak: ' + num(c.account.equity, 0).toFixed(2) + ' ' + cur);
173
- L.push('- Kaldıraç: 1:' + num(c.account.leverage, 0) + ' · Para birimi: ' + (cur || '?'));
174
- L.push('');
175
- }
176
- L.push('## Performans');
177
- L.push('| Metrik | Değer |');
178
- L.push('| --- | --- |');
179
- L.push('| Kapanan işlem | ' + num(st.trades, 0) + ' |');
180
- L.push('| Kazanç / Kayıp | ' + num(st.wins, 0) + ' / ' + num(st.losses, 0) + ' |');
181
- L.push('| Win rate | %' + num(st.winRate, 0) + ' |');
182
- L.push('| Net K/Z | ' + money(st.netProfit, cur) + ' |');
183
- L.push('| Profit factor | ' + (st.profitFactor == null ? '∞' : st.profitFactor) + ' |');
184
- if (c.drawdown) L.push('| Max drawdown | ' + num(c.drawdown.maxAbs, 0).toFixed(2) + ' ' + cur + ' (%' + num(c.drawdown.maxPct, 0) + ') |');
185
- L.push('');
186
- const days = Object.entries(st.byDay || {}).sort((a, b) => a[0].localeCompare(b[0])).slice(-7);
187
- if (days.length) {
188
- L.push('## Günlük K/Z');
189
- for (const [d, v] of days) L.push('- ' + d + ': ' + money(v, cur));
190
- L.push('');
191
- }
192
- if (c.mfe && c.mfe.n) {
193
- L.push('## Kâr Yakalama (MFE/MAE)');
194
- if (c.mfe.capture != null) {
195
- L.push('- Kazanan işlemlerde ulaşılan maksimum kârın **%' + c.mfe.capture + '**\'i realize edildi (' + c.mfe.n + ' kapanış ölçüldü).');
196
- }
197
- if (c.mfe.avgMaeLoss != null) {
198
- L.push('- Kaybedenlerde ortalama MAE (en kötü seviye): **-' + c.mfe.avgMaeLoss + '** — SL mesafesi bu veriye göre ayarlanabilir.');
199
- }
200
- L.push('');
201
- }
202
- const syms = Object.entries(st.bySymbol || {})
203
- .map(([s, v]) => ({ symbol: s, ...v }))
204
- .sort((a, b) => b.net - a.net)
205
- .slice(0, 8);
206
- if (syms.length) {
207
- L.push('## Semboller');
208
- L.push('| Sembol | İşlem | W/L | Net |');
209
- L.push('| --- | --- | --- | --- |');
210
- for (const s of syms) L.push('| ' + s.symbol + ' | ' + s.trades + ' | ' + s.wins + '/' + s.losses + ' | ' + money(s.net, cur) + ' |');
211
- L.push('');
212
- }
213
- if (c.positions && c.positions.length) {
214
- L.push('## Açık Pozisyonlar');
215
- for (const p of c.positions.slice(0, 15)) {
216
- L.push('- ' + (p.symbol || '?') + ' ' + (Number(p.type) === 0 ? 'BUY' : 'SELL') + ' ' + num(p.volume, 0) + ' lot @ ' + num(p.price_open, 0) + ' → K/Z ' + money(p.profit, cur));
217
- }
218
- L.push('');
219
- }
220
- if (Array.isArray(c.journal) && c.journal.length) {
221
- L.push('## Son İşlem Günlüğü');
222
- for (const j of c.journal.slice(-15)) {
223
- const t = new Date(num(j.at, Date.now()));
224
- const hh = String(t.getHours()).padStart(2, '0') + ':' + String(t.getMinutes()).padStart(2, '0');
225
- L.push('- ' + hh + ' · ' + String(j.kind || '') + (j.symbol ? ' ' + j.symbol : '') + (j.side ? ' ' + j.side : '') + (j.volume ? ' ' + j.volume : '') + (j.reason ? ' — ' + String(j.reason).slice(0, 160) : ''));
226
- }
227
- L.push('');
228
- }
229
- if (c.review) {
230
- L.push('## Ajan Değerlendirmesi');
231
- L.push(String(c.review).trim());
232
- L.push('');
233
- }
234
- L.push('---');
235
- L.push('_Beast Finance tarafından otomatik üretildi · ' + new Date().toISOString() + '_');
236
- return L.join('\n');
237
- }
238
-
239
- module.exports = { summarizeDeals, maxDrawdown, buildWeeklyReport, dayKey, money };
@@ -1,302 +0,0 @@
1
- 'use strict';
2
-
3
- /* Beast Finance — risk otomasyonu KARAR katmanı (saf; köprü/IO yok).
4
- Açık pozisyon + sembol meta verisi + pozisyon durumu (st) alır; uygulanacak
5
- aksiyonları döndürür:
6
- {kind:'modify', sl} → SL'yi taşı (BE / trailing)
7
- {kind:'partial', volume} → kısmi TP kapat
8
- {kind:'warnSL'} → SL'ye yaklaşma uyarısı
9
- {kind:'warnNoSL'} → SL'siz pozisyon uyarısı
10
- main süreç aksiyonları köprüye uygular; başarılı olanın bayrağını st'ye yazar. */
11
-
12
- function num(v, def) {
13
- const n = Number(v);
14
- return isFinite(n) ? n : def;
15
- }
16
-
17
- function roundTo(v, digits) {
18
- const d = Math.max(0, Math.min(8, Math.round(Number(digits) || 0)));
19
- const p = Math.pow(10, d);
20
- return Math.round(v * p) / p;
21
- }
22
-
23
- function stepRound(v, step) {
24
- const s = num(step, 0.01);
25
- if (!(s > 0)) return v;
26
- const q = Math.floor(v / s + 1e-9);
27
- return q * s;
28
- }
29
-
30
- /* meta: { digits, point, trade_stops_level, volume_min, volume_max, volume_step } */
31
- function plan(pos, meta, st, cfg) {
32
- const out = { actions: [], r: num(st && st.r, 0) };
33
- if (!pos || !meta) return out;
34
- const isBuy = Number(pos.type) === 0;
35
- const entry = num(pos.price_open, 0);
36
- const ref = num(pos.price_current, 0);
37
- const sl = num(pos.sl, 0);
38
- const volume = num(pos.volume, 0);
39
- const digits = num(meta.digits, 5);
40
- const point = num(meta.point, Math.pow(10, -digits));
41
- const stopsPoints = num(meta.trade_stops_level, 0);
42
- const minDist = stopsPoints > 0 ? (stopsPoints + 1) * point : point;
43
- if (!(entry > 0) || !(ref > 0) || !(volume > 0)) return out;
44
-
45
- /* R (ilk risk mesafesi) yalnızca pozisyon ilk görüldüğünde SL'den hesaplanır */
46
- let r = num(st && st.r, 0);
47
- if (!(r > 0) && sl > 0) r = Math.abs(entry - sl);
48
- out.r = r;
49
-
50
- const beOn = num(cfg && cfg.beOnR, 1);
51
- const beOff = num(cfg && cfg.beOffsetR, 0.05);
52
- const trailStart = num(cfg && cfg.trailStartR, 1.5);
53
- const trailDist = num(cfg && cfg.trailR, 0.5);
54
- const partialR = num(cfg && cfg.partialR, 0);
55
- const partialPct = num(cfg && cfg.partialPct, 50);
56
- /* KÂR KORUMA (erken trailing): kâr protectStartR'ye ulaşınca SL, GÖRÜLEN EN
57
- İYİ kârın protectDistR gerisine kilitlenir — spike dönüşlerinde kâr
58
- eksiye dönmez (0 = kapalı; varsayılan main finCfg'de verilir). */
59
- const protectStart = num(cfg && cfg.protectStartR, 0);
60
- const protectDist = num(cfg && cfg.protectDistR, 0);
61
-
62
- const profit = isBuy ? ref - entry : entry - ref;
63
- /* en iyi kâr: tick'ler arası spike'lar kaybolmasın — main st.bestProfit yazar */
64
- const bestProfit = Math.max(profit, num(st && st.bestProfit, profit));
65
-
66
- /* ---- BE + trailing: hedef SL'yi birleştir, asla geriye taşıma ---- */
67
- if (r > 0) {
68
- let desired = sl > 0 ? sl : 0;
69
- let want = false;
70
- if (beOn > 0 && !(st && st.be) && profit >= beOn * r) {
71
- const beSl = isBuy ? entry + beOff * r : entry - beOff * r;
72
- if (!(desired > 0) || (isBuy ? beSl > desired : beSl < desired)) desired = beSl;
73
- want = true;
74
- }
75
- if (trailStart > 0 && trailDist > 0 && profit >= trailStart * r) {
76
- const trailSl = isBuy ? ref - trailDist * r : ref + trailDist * r;
77
- if (!(desired > 0) || (isBuy ? trailSl > desired : trailSl < desired)) desired = trailSl;
78
- want = true;
79
- }
80
- /* kâr kilidi: ulaşılan EN İYİ kârın bir kısmı garantiye alınır */
81
- if (protectStart > 0 && protectDist > 0 && bestProfit >= protectStart * r) {
82
- const locked = bestProfit - protectDist * r;
83
- const lockSl = isBuy ? entry + locked : entry - locked;
84
- if (locked > 0 && (!(desired > 0) || (isBuy ? lockSl > desired : lockSl < desired))) desired = lockSl;
85
- want = true;
86
- }
87
- if (want && desired > 0) {
88
- /* broker stop mesafesi: SL fiyata çok yakınsa geçersiz — kırp */
89
- desired = isBuy ? Math.min(desired, ref - minDist) : Math.max(desired, ref + minDist);
90
- /* KIRPMA KİLİDİ BOZDU: fiyat geri çekildi, hedef SL girişin kaybına
91
- indi — kâr kilidini kaybı kilitleyen SL'e çevirme; hiç yazma
92
- (fiyat toparlayınca kilit yeniden uygulanır). */
93
- const lockBroken = isBuy ? desired < entry : desired > entry;
94
- const improved = !lockBroken && (isBuy ? desired > sl + point * 0.5 : (sl === 0 || desired < sl - point * 0.5));
95
- if (improved && desired > 0) {
96
- out.actions.push({ kind: 'modify', sl: roundTo(desired, digits) });
97
- }
98
- }
99
- }
100
-
101
- /* ---- kısmi TP ---- */
102
- /* partial = kısmi kapatma GERÇEKTEN yapıldı (Jev/kod); partialNotified =
103
- yalnız bildirim gitti — ikisi de tekrar üretimi engeller */
104
- if (partialR > 0 && partialPct > 0 && partialPct < 100 && r > 0 && !(st && (st.partial || st.partialNotified)) && profit >= partialR * r) {
105
- const step = num(meta.volume_step, 0.01);
106
- const vmin = num(meta.volume_min, 0.01);
107
- const vmax = num(meta.volume_max, 100);
108
- let vol = stepRound((volume * partialPct) / 100, step);
109
- vol = Math.min(vol, vmax);
110
- const remain = volume - vol;
111
- if (vol >= vmin && remain >= vmin - 1e-9 && vol < volume) {
112
- out.actions.push({ kind: 'partial', volume: Math.round(vol * 1e8) / 1e8 });
113
- }
114
- }
115
-
116
- /* ---- uyarılar ---- */
117
- if (sl > 0) {
118
- if (r > 0) {
119
- const dist = isBuy ? ref - sl : sl - ref;
120
- const thr = Math.max(minDist * 3, 0.2 * r);
121
- if (dist > 0 && dist <= thr && !(st && st.warnSL)) out.actions.push({ kind: 'warnSL' });
122
- out.rearmSL = dist > thr * 1.6;
123
- }
124
- } else if (!(st && st.warnNoSL)) {
125
- out.actions.push({ kind: 'warnNoSL' });
126
- }
127
- return out;
128
- }
129
-
130
- /* ---- kapanış nedeni (MT5 deal reason) ----
131
- İşlem SL/TP ile mi kapandı? Saf sınıflandırma: 4 = stop-loss,
132
- 5 = take-profit, 6 = stop-out (teminat). Diğerleri (manuel/EA/broker)
133
- BOŞ döner — bu olaylarda ajan uyandırılmaz. */
134
- function closeKind(reason) {
135
- const n = Number(reason);
136
- if (n === 4) return 'stop';
137
- if (n === 5) return 'tp';
138
- if (n === 6) return 'stopout';
139
- return '';
140
- }
141
-
142
- /* ---- emir tipi çözümleme (6 tip) ----
143
- "buy"/"sell" → piyasa (market); buy_market/sell_market → piyasa;
144
- buy_limit/sell_limit/buy_stop/sell_stop → bekleyen (pending).
145
- Esnek yazım: "sell limit", "limit_buy", "MARKET BUY", "anlık" aynı sonuca iner.
146
- fallbackSide: tip yön taşımıyorsa (ör. yalnız "market"/"limit") kullanılır.
147
- Dönüş: { ok, side:'buy'|'sell', kind:'market'|'pending', type:'buy_market'|... } */
148
- function parseOrderType(raw, fallbackSide) {
149
- const s = String(raw || '').toLowerCase().replace(/[\s-]+/g, '_');
150
- const fb = String(fallbackSide || '').toLowerCase().trim();
151
- const side = /(^|_)sell(_|$)/.test(s)
152
- ? 'sell'
153
- : /(^|_)buy(_|$)/.test(s)
154
- ? 'buy'
155
- : fb === 'sell'
156
- ? 'sell'
157
- : fb === 'buy'
158
- ? 'buy'
159
- : '';
160
- if (!side) return { ok: false };
161
- const hasLimit = /limit/.test(s);
162
- const hasStop = /stop/.test(s);
163
- const hasMarket = /market|instant|piyasa|anl[ıi]k/.test(s);
164
- if ((hasLimit || hasStop) && !hasMarket) {
165
- return { ok: true, side, kind: 'pending', type: side + '_' + (hasLimit ? 'limit' : 'stop') };
166
- }
167
- return { ok: true, side, kind: 'market', type: side + '_market' };
168
- }
169
-
170
- /* ---- fiyat alarmı soğuması ----
171
- Tekrarlı alarm (once=false): aynı koşul sürdükçe en fazla cooldownMin
172
- dakikada bir tetiklenir. once=true alarmda soğuma uygulanmaz (ilk
173
- tetiklemede kapanır). */
174
- function alertCooldownActive(a, now) {
175
- if (!a || a.once) return false;
176
- const cd = (Number(a.cooldownMin) || 0) * 60000;
177
- if (!(cd > 0)) return false;
178
- const last = Number(a.lastFiredAt) || 0;
179
- return last > 0 && Number(now) - last < cd;
180
- }
181
-
182
- /* ---- alarm temizliği (ajan kendi kurduğu gereksiz alarmı siler) ----
183
- opts:
184
- ids → yalnız bu id'ler (açıkça verilenler sahiplikten bağımsız silinir)
185
- sid → alarmı kuran ajan oturumu; scope varsayılanı "kendi alarmlarım"
186
- symbol → yalnız bu sembole daralt
187
- all → sahiplik filtresini kaldır (tüm finance alarmları)
188
- Dönüş: silinecek alarm id listesi (saf; IO yok). */
189
- function pickAlarms(alerts, opts) {
190
- const o = opts || {};
191
- const ids = Array.isArray(o.ids) ? o.ids.map((x) => String(x || '')).filter(Boolean) : [];
192
- const sid = String(o.sid || '');
193
- const symbol = String(o.symbol || '').toUpperCase();
194
- const all = !!o.all;
195
- const out = [];
196
- for (const a of Array.isArray(alerts) ? alerts : []) {
197
- if (!a || !a.id) continue;
198
- if (ids.length) {
199
- if (ids.includes(String(a.id))) out.push(String(a.id));
200
- continue;
201
- }
202
- if (symbol && String(a.symbol || '').toUpperCase() !== symbol) continue;
203
- if (all) {
204
- out.push(String(a.id));
205
- continue;
206
- }
207
- /* sid varsa YALNIZ kendi alarmları; sid yoksa (işçi oturumu) sahipsizler */
208
- const owner = String(a.sid || '');
209
- if (sid ? owner === sid : !owner) out.push(String(a.id));
210
- }
211
- return out;
212
- }
213
-
214
- /* ---- trade saatleri (makinenin YEREL saati) ----
215
- th: { on, start:'HH:MM', end:'HH:MM' }. on değilse 7/24 açık (true).
216
- Açıkken yalnız [start,end) aralığında true döner; gece aralığı desteklenir
217
- (ör. 22:00 → 06:00). start === end → 24 saat. Saf; IO yok. */
218
- function parseHM(v, defMin) {
219
- const m = /^([01]?\d|2[0-3]):([0-5]\d)$/.exec(String(v || '').trim());
220
- if (!m) return defMin;
221
- return Number(m[1]) * 60 + Number(m[2]);
222
- }
223
- function tradeHoursOpen(th, now) {
224
- if (!th || !th.on) return true;
225
- const s = parseHM(th.start, 9 * 60);
226
- const e = parseHM(th.end, 22 * 60);
227
- if (s === e) return true;
228
- const d = now instanceof Date ? now : new Date(Number(now) || Date.now());
229
- const cur = d.getHours() * 60 + d.getMinutes();
230
- return s < e ? cur >= s && cur < e : cur >= s || cur < e;
231
- }
232
-
233
- /* ---- bekleyen emir (pending) → pozisyon eşleştirme ----
234
- Watchdog turu emir listesini karşılaştırır: listeden DÜŞEN emir için aynı
235
- turda YENİ açılan (ya da netting hesapta hacmi artan) eşleşen bir pozisyon
236
- varsa emir AKTİFLEŞTİ (tetiklendi); eşleşme yoksa İPTAL/süresi doldu.
237
- Saf karar katmanı: IO ve ajan uyarısı main süreçte yapılır. */
238
-
239
- const ORDER_TYPE_LABELS = {
240
- 0: 'BUY LIMIT',
241
- 1: 'SELL LIMIT',
242
- 2: 'BUY STOP',
243
- 3: 'SELL STOP',
244
- 4: 'BUY STOP LIMIT',
245
- 5: 'SELL STOP LIMIT',
246
- };
247
-
248
- /* MT5 emir tipleri: çift = alış, tek = satış */
249
- function orderSide(o) {
250
- return Number(o && o.type) % 2 === 1 ? 'sell' : 'buy';
251
- }
252
-
253
- function orderTypeLabel(o) {
254
- const n = Number(o && o.type);
255
- return ORDER_TYPE_LABELS[n] || 'TIP ' + (isFinite(n) ? n : '?');
256
- }
257
-
258
- function orderVolume(o) {
259
- return num(o && (o.volume_current != null ? o.volume_current : o.volume_initial != null ? o.volume_initial : o.volume), 0);
260
- }
261
-
262
- /* goneOrders: önceki turda var olup şimdi listede OLMAYAN emirler
263
- freshPositions: bu tur yeni açılan / hacmi artan pozisyonlar
264
- Dönüş: { activated:[{order,position}], canceled:[order] } */
265
- function matchPendingDelta(goneOrders, freshPositions) {
266
- const out = { activated: [], canceled: [] };
267
- const free = (Array.isArray(freshPositions) ? freshPositions : []).filter(Boolean);
268
- for (const o of Array.isArray(goneOrders) ? goneOrders : []) {
269
- if (!o) continue;
270
- const side = orderSide(o);
271
- const symbol = String(o.symbol || '').toUpperCase();
272
- const ovol = orderVolume(o);
273
- const oprice = num(o.price_open, 0);
274
- const omagic = Number(o.magic) || 0;
275
- let best = -1;
276
- let bestScore = Infinity;
277
- for (let i = 0; i < free.length; i++) {
278
- const p = free[i];
279
- if (!p) continue;
280
- if (String(p.symbol || '').toUpperCase() !== symbol) continue;
281
- if ((num(p.type, -1) === 0 ? 'buy' : 'sell') !== side) continue;
282
- /* fiyat yakınlığı + hacim yakınlığı; aynı magic KESİN öncelik */
283
- let score = Math.abs(num(p.price_open, 0) - oprice);
284
- const pvol = num(p.volume, 0);
285
- if (ovol > 0 && pvol > 0) score += Math.abs(pvol - ovol) * 0.01;
286
- if (omagic !== 0 && (Number(p.magic) || 0) === omagic) score -= 1e9;
287
- if (score < bestScore) {
288
- bestScore = score;
289
- best = i;
290
- }
291
- }
292
- if (best >= 0) {
293
- out.activated.push({ order: o, position: free[best] });
294
- free.splice(best, 1);
295
- } else {
296
- out.canceled.push(o);
297
- }
298
- }
299
- return out;
300
- }
301
-
302
- module.exports = { plan, roundTo, stepRound, closeKind, parseOrderType, alertCooldownActive, pickAlarms, parseHM, tradeHoursOpen, orderSide, orderTypeLabel, orderVolume, matchPendingDelta };