beast-agent 2.38.1 → 2.38.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/agent/engine.js +13 -6
- package/src/agent/financetools.js +51 -7
- package/src/agent/finrisk.js +110 -1
- package/src/agent/finstats.js +10 -0
- package/src/agent/scripts/mt5_bridge.py +19 -2
- package/src/main.js +344 -25
- package/src/renderer/index.html +16 -0
- package/src/renderer/renderer.js +57 -4
- package/src/renderer/style.css +2 -0
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -1920,24 +1920,29 @@ class Engine {
|
|
|
1920
1920
|
'ROL: MAKRO AJANI 🌍 — büyük resim uzmanısın, İşlem AÇMAZSIN (mt5_trade/mt5_pending/mt5_close KULLANMA). 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.',
|
|
1921
1921
|
})[String((session && session.financeRole) || '')] || '';
|
|
1922
1922
|
/* ROL → SKILL eşleştirmesi (ayarlar modalı): rol başına TEK ve ZORUNLU
|
|
1923
|
-
skill
|
|
1924
|
-
|
|
1925
|
-
|
|
1923
|
+
skill; ANA TRADER için ayrıca PLAYBOOK skill'i (roleSkills.trader).
|
|
1924
|
+
Seçili SKILL.md'nin TAM GÖVDESİ prompta gömülür — ajan skill aracını
|
|
1925
|
+
çağırmayı atlasa bile prosedür KESİN uygulanır. */
|
|
1926
1926
|
const roleSkills = Array.isArray(session && session.financeRoleSkills)
|
|
1927
1927
|
? session.financeRoleSkills.map((s) => String(s || '').trim()).filter(Boolean).slice(0, 1)
|
|
1928
1928
|
: [];
|
|
1929
1929
|
let roleSkillBlock = '';
|
|
1930
|
-
if (
|
|
1930
|
+
if (roleSkills.length) {
|
|
1931
1931
|
const roleSkillName = roleSkills[0];
|
|
1932
1932
|
const sb = this._skillBody({ name: roleSkillName });
|
|
1933
|
+
const what = roleBlock
|
|
1934
|
+
? 'Bu rolün ZORUNLU skill\'i'
|
|
1935
|
+
: session && session.financePlaybook
|
|
1936
|
+
? 'TRADER PLAYBOOK\'un (ZORUNLU skill)'
|
|
1937
|
+
: 'BU OTURUMUN ZORUNLU skill\'i';
|
|
1933
1938
|
roleSkillBlock =
|
|
1934
|
-
`
|
|
1939
|
+
` ${what}: skill("${roleSkillName}")` +
|
|
1935
1940
|
(sb && sb.ok && sb.content
|
|
1936
1941
|
? ' — aşağıda TAM prosedürü verilmiştir; HER TURDA birebir uygula (ayrıca okumana gerek yok):\n\n' +
|
|
1937
1942
|
`===== SKILL: ${roleSkillName} =====\n${sb.content}\n===== /SKILL =====`
|
|
1938
1943
|
: ' — ilk turda bu skill\'i mutlaka oku ve prosedürüne birebir uy.');
|
|
1939
1944
|
}
|
|
1940
|
-
const roleBlockFull = roleBlock
|
|
1945
|
+
const roleBlockFull = roleBlock + roleSkillBlock;
|
|
1941
1946
|
/* FİNANS EKİBİ: tüm finance ajanları tek DM grubundadır — sohbet grup
|
|
1942
1947
|
thread'inde toplanır (ayrı ayrı 1:1 thread'lere dağılmaz) */
|
|
1943
1948
|
const finJob = this._bgJobs && this._bgJobs.get(String(session && session.id));
|
|
@@ -1967,6 +1972,8 @@ class Engine {
|
|
|
1967
1972
|
'- Emin olmadığında İŞLEM YOK — "BEKLE: <sebep>" yaz. Sık işlem > iyi işlem.\n' +
|
|
1968
1973
|
'- Hesap kaldıracı ve serbest marja göre pozisyon boyutunu düşük tut; tek işlemde serbest marjın büyük kısmını riske atma.\n' +
|
|
1969
1974
|
(session && session.financeStrategy ? `SAHİBİNİN STRATEJİ NOTU (önceliklidir):\n${session.financeStrategy}\n` : '') +
|
|
1975
|
+
(session && session.financeShadow ? 'SHADOW MOD AKTİF: mt5_trade/mt5_pending emir GÖNDERMEZ — kararını teziyle raporla; gerçek işlem açılmaz (karar günlüğe yazılır).\n' : '') +
|
|
1976
|
+
(session && session.financeDigest ? `İŞLEM PERFORMANS GEÇMİŞİN (kendi kayıtların — kararlarında ders çıkar):\n${session.financeDigest}\n` : '') +
|
|
1970
1977
|
(mem.user ? `# USER\n${mem.user}\n` : '') +
|
|
1971
1978
|
(rules.length ? `# KURALLAR (sahibinin kalıcı talimatları — daima uy)\n${rules.map((r) => '- ' + r).join('\n')}\n` : '') +
|
|
1972
1979
|
(skList.length
|
|
@@ -31,6 +31,10 @@ const NAMES = [
|
|
|
31
31
|
|
|
32
32
|
let getCfg = () => ({ allowTrading: false, maxLot: 0.1, maxPositions: 3, symbols: [] });
|
|
33
33
|
let notify = () => {};
|
|
34
|
+
/* DİSİPLİN KANCASI (main enjekte eder): kodla zorlanan kurallar — günlük işlem
|
|
35
|
+
limiti, kayıp serisi molası, re-entry beklemesi, kur maruziyeti.
|
|
36
|
+
(side, symbol, positions) → hata metni ya da null. */
|
|
37
|
+
let discipline = () => null;
|
|
34
38
|
/* alarm deposu main süreçte yaşar (dosyaya kalıcı) — buradan enjekte edilir */
|
|
35
39
|
let alertsApi = {
|
|
36
40
|
list: () => [],
|
|
@@ -50,6 +54,10 @@ function setAlerts(api) {
|
|
|
50
54
|
if (api && typeof api === 'object') alertsApi = api;
|
|
51
55
|
}
|
|
52
56
|
|
|
57
|
+
function setDiscipline(fn) {
|
|
58
|
+
if (typeof fn === 'function') discipline = fn;
|
|
59
|
+
}
|
|
60
|
+
|
|
53
61
|
async function bcall(method, params, timeoutMs) {
|
|
54
62
|
const r = await mt5.call(method, params, timeoutMs);
|
|
55
63
|
if (!r || r.ok !== true) throw new Error((r && r.error) || 'MT5 hatası');
|
|
@@ -75,6 +83,10 @@ async function preTradeCheck(cfg, side, symbol, info, price, volume, sl, tp, pos
|
|
|
75
83
|
if (closeErr) return closeErr;
|
|
76
84
|
const expErr = finrisk.exposureCheck(positions || [], side, symbol, cfg.maxPerSymbol, cfg.maxSameSide);
|
|
77
85
|
if (expErr) return expErr;
|
|
86
|
+
/* KODLA DİSİPLİN: günlük limit / kayıp serisi molası / re-entry / kur maruziyeti */
|
|
87
|
+
let discErr = null;
|
|
88
|
+
try { discErr = discipline(side, symbol, positions || []); } catch {}
|
|
89
|
+
if (discErr) return discErr;
|
|
78
90
|
const minLevel = Number(cfg.minMarginLevel) || 0;
|
|
79
91
|
let account = null;
|
|
80
92
|
let estMargin = 0;
|
|
@@ -216,7 +228,7 @@ const definitions = NAMES.map((name) => {
|
|
|
216
228
|
},
|
|
217
229
|
mt5_trade: {
|
|
218
230
|
description:
|
|
219
|
-
'PİYASA EMRİ AÇAR: mt5_trade {symbol, side:"buy"|"sell", volume, sl?, tp?, comment?}. Lot limiti ve max pozisyon sayısı sistem tarafından zorlanır. Otomatik işlem anahtarı kapalıysa reddedilir. SL/TP vermek ŞIDDETLİ önerilir.',
|
|
231
|
+
'PİYASA EMRİ AÇAR: mt5_trade {symbol, side:"buy"|"sell", volume, sl?, tp?, comment?, reason?}. Lot limiti ve max pozisyon sayısı sistem tarafından zorlanır. Otomatik işlem anahtarı kapalıysa reddedilir. SL/TP vermek ŞIDDETLİ önerilir. reason: kararın tek cümlelik tezi (günlüğe yazılır, performans değerlendirmesinde kullanılır).',
|
|
220
232
|
parameters: {
|
|
221
233
|
type: 'object',
|
|
222
234
|
properties: {
|
|
@@ -226,6 +238,7 @@ const definitions = NAMES.map((name) => {
|
|
|
226
238
|
sl: { type: 'number', description: 'Stop loss fiyatı (0 = yok)' },
|
|
227
239
|
tp: { type: 'number', description: 'Take profit fiyatı (0 = yok)' },
|
|
228
240
|
comment: { type: 'string', description: 'Kısa işlem notu' },
|
|
241
|
+
reason: { type: 'string', description: 'Kararın tezi/gerekçesi (tek cümle)' },
|
|
229
242
|
},
|
|
230
243
|
required: ['symbol', 'side', 'volume'],
|
|
231
244
|
},
|
|
@@ -255,7 +268,7 @@ const definitions = NAMES.map((name) => {
|
|
|
255
268
|
},
|
|
256
269
|
mt5_pending: {
|
|
257
270
|
description:
|
|
258
|
-
'BEKLEYEN EMİR koyar: mt5_pending {symbol, type:"buy_limit"|"sell_limit"|"buy_stop"|"sell_stop", volume, price, sl?, tp?}. Otomatik işlem anahtarına tabidir.',
|
|
271
|
+
'BEKLEYEN EMİR koyar: mt5_pending {symbol, type:"buy_limit"|"sell_limit"|"buy_stop"|"sell_stop", volume, price, sl?, tp?, reason?}. Otomatik işlem anahtarına tabidir. reason: kararın tezi (günlüğe yazılır).',
|
|
259
272
|
parameters: {
|
|
260
273
|
type: 'object',
|
|
261
274
|
properties: {
|
|
@@ -265,6 +278,7 @@ const definitions = NAMES.map((name) => {
|
|
|
265
278
|
price: { type: 'number' },
|
|
266
279
|
sl: { type: 'number' },
|
|
267
280
|
tp: { type: 'number' },
|
|
281
|
+
reason: { type: 'string', description: 'Kararın tezi/gerekçesi (tek cümle)' },
|
|
268
282
|
},
|
|
269
283
|
required: ['symbol', 'type', 'volume', 'price'],
|
|
270
284
|
},
|
|
@@ -499,9 +513,28 @@ const handlers = {
|
|
|
499
513
|
if (list.length >= maxPos) {
|
|
500
514
|
return { ok: false, error: `max eşzamanlı pozisyon dolu (${list.length}/${maxPos}) — önce bir pozisyon kapat` };
|
|
501
515
|
}
|
|
502
|
-
/* risk katmanı: stops_level + yoğunluk/net yön + marj kalkanı */
|
|
516
|
+
/* risk katmanı: stops_level + yoğunluk/net yön + marj kalkanı + disiplin */
|
|
503
517
|
const riskErr = await preTradeCheck(cfg, side, symbol, info, price, vol, sl, tp, list);
|
|
504
518
|
if (riskErr) return { ok: false, error: riskErr };
|
|
519
|
+
/* SHADOW MOD: gerçek emir GÖNDERİLMEZ — karar teziyle günlüğe yazılır */
|
|
520
|
+
if (cfg.shadowMode) {
|
|
521
|
+
noteTrade('shadow', {
|
|
522
|
+
symbol,
|
|
523
|
+
side,
|
|
524
|
+
volume: vol,
|
|
525
|
+
sl,
|
|
526
|
+
tp,
|
|
527
|
+
reason: String(args.reason || '').slice(0, 500),
|
|
528
|
+
risk: riskInfo,
|
|
529
|
+
}, ctx);
|
|
530
|
+
return {
|
|
531
|
+
ok: true,
|
|
532
|
+
shadow: true,
|
|
533
|
+
planned: { symbol, side, volume: vol, sl, tp },
|
|
534
|
+
risk: riskInfo,
|
|
535
|
+
note: 'SHADOW MOD: emir GÖNDERİLMEDİ, karar günlüğe yazıldı. Gerçek işlem için TRADE AJANI ayarlarından shadow modu kapat.',
|
|
536
|
+
};
|
|
537
|
+
}
|
|
505
538
|
const data = await bcall('market', {
|
|
506
539
|
symbol,
|
|
507
540
|
side,
|
|
@@ -511,7 +544,7 @@ const handlers = {
|
|
|
511
544
|
deviation: 20,
|
|
512
545
|
comment: String(args.comment || 'Beast').slice(0, 26),
|
|
513
546
|
}, 20000);
|
|
514
|
-
noteTrade('trade', { symbol, side, volume: vol, sl, tp, risk: riskInfo, comment: String(args.comment || ''), result: data && data.result }, ctx);
|
|
547
|
+
noteTrade('trade', { symbol, side, volume: vol, sl, tp, risk: riskInfo, comment: String(args.comment || ''), reason: String(args.reason || '').slice(0, 500), result: data && data.result }, ctx);
|
|
515
548
|
return { ok: true, opened: { symbol, side, volume: vol, sl, tp }, risk: riskInfo, result: data && data.result };
|
|
516
549
|
},
|
|
517
550
|
async mt5_close(args, ctx) {
|
|
@@ -553,16 +586,27 @@ const handlers = {
|
|
|
553
586
|
const norm = finrisk.normalizeVolume(info, args.volume, Number(cfg.maxLot) || 0.1);
|
|
554
587
|
if (norm.error) return { ok: false, error: norm.error };
|
|
555
588
|
const vol = norm.volume;
|
|
589
|
+
const ptype = String(args.type || '').toLowerCase();
|
|
590
|
+
const pside = ptype.startsWith('buy') ? 'buy' : ptype.startsWith('sell') ? 'sell' : '';
|
|
591
|
+
/* KODLA DİSİPLİN: bekleyen emir de işlem sayılır */
|
|
592
|
+
let discErr = null;
|
|
593
|
+
try { discErr = discipline(pside, symbol, []); } catch {}
|
|
594
|
+
if (discErr) return { ok: false, error: discErr };
|
|
595
|
+
/* SHADOW MOD: bekleyen emir de GÖNDERİLMEZ — tez günlüğe düşer */
|
|
596
|
+
if (cfg.shadowMode) {
|
|
597
|
+
noteTrade('shadow', { symbol, type: ptype, side: pside, volume: vol, price: Number(args.price) || 0, sl: Number(args.sl) || 0, tp: Number(args.tp) || 0, reason: String(args.reason || '').slice(0, 500) }, ctx);
|
|
598
|
+
return { ok: true, shadow: true, note: 'SHADOW MOD: bekleyen emir GÖNDERİLMEDİ, karar günlüğe yazıldı.' };
|
|
599
|
+
}
|
|
556
600
|
const data = await bcall('pending', {
|
|
557
601
|
symbol,
|
|
558
|
-
type:
|
|
602
|
+
type: ptype,
|
|
559
603
|
volume: vol,
|
|
560
604
|
price: Number(args.price) || 0,
|
|
561
605
|
sl: Number(args.sl) || 0,
|
|
562
606
|
tp: Number(args.tp) || 0,
|
|
563
607
|
comment: String(args.comment || 'Beast').slice(0, 26),
|
|
564
608
|
}, 20000);
|
|
565
|
-
noteTrade('pending', { symbol, type: args.type, volume: vol }, ctx);
|
|
609
|
+
noteTrade('pending', { symbol, type: args.type, volume: vol, reason: String(args.reason || '').slice(0, 500) }, ctx);
|
|
566
610
|
return { ok: true, result: data && data.result };
|
|
567
611
|
},
|
|
568
612
|
async mt5_cancel(args, ctx) {
|
|
@@ -575,4 +619,4 @@ const handlers = {
|
|
|
575
619
|
},
|
|
576
620
|
};
|
|
577
621
|
|
|
578
|
-
module.exports = { definitions, handlers, NAMES, setConfig, setNotify, setAlerts };
|
|
622
|
+
module.exports = { definitions, handlers, NAMES, setConfig, setNotify, setAlerts, setDiscipline };
|
package/src/agent/finrisk.js
CHANGED
|
@@ -129,4 +129,113 @@ function exposureCheck(positions, side, symbol, maxPerSymbol, maxSameSide) {
|
|
|
129
129
|
return null;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
|
|
132
|
+
/* ---------- DİSİPLİN (kodla zorlanan trader kuralları) ----------
|
|
133
|
+
Prompt ricalarına güvenmeden uygulanır: günlük işlem limiti, ardışık kayıp
|
|
134
|
+
molası, sembol re-entry beklemesi ve yönlü kur maruziyeti (korelasyon). */
|
|
135
|
+
|
|
136
|
+
const SYM_ALIAS = {
|
|
137
|
+
XAU: 'XAU', GOLD: 'XAU', XAUT: 'XAU',
|
|
138
|
+
XAG: 'XAG', SILV: 'XAG', SILVER: 'XAG',
|
|
139
|
+
WTI: 'OIL', BREN: 'OIL', BRENT: 'OIL', USOI: 'OIL', UKOI: 'OIL',
|
|
140
|
+
NATG: 'GAS', NGAS: 'GAS',
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/* Sembolün para bacakları: EURUSD → [EUR,USD]; GOLD → [XAU,USD]; bilinmezse [] */
|
|
144
|
+
function symbolLegs(symbol) {
|
|
145
|
+
const s = String(symbol || '').toUpperCase().replace(/[^A-Z]/g, '');
|
|
146
|
+
if (/^[A-Z]{6}$/.test(s)) return [s.slice(0, 3), s.slice(3)];
|
|
147
|
+
const base = SYM_ALIAS[s.slice(0, 4)] || SYM_ALIAS[s.slice(0, 3)];
|
|
148
|
+
if (base) return [base, 'USD'];
|
|
149
|
+
if (/^USD[A-Z]{3}$/.test(s)) return ['USD', s.slice(3)];
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* Yönlü kur maruziyeti: buy EURUSD = +EUR −USD. Limitsizse null. */
|
|
154
|
+
function currencyExposureError(positions, side, symbol, maxPerCurrency) {
|
|
155
|
+
const max = num(maxPerCurrency, 0);
|
|
156
|
+
if (!(max > 0)) return null;
|
|
157
|
+
const legs = symbolLegs(symbol);
|
|
158
|
+
if (!legs.length) return null;
|
|
159
|
+
const dir = String(side).toLowerCase() === 'buy' ? 1 : -1;
|
|
160
|
+
const exp = {};
|
|
161
|
+
const add = (cur, d) => { if (cur) exp[cur] = (exp[cur] || 0) + d; };
|
|
162
|
+
for (const p of (Array.isArray(positions) ? positions : [])) {
|
|
163
|
+
const l = symbolLegs(p && p.symbol);
|
|
164
|
+
if (!l.length) continue;
|
|
165
|
+
const d = Number(p.type) === 0 ? 1 : -1;
|
|
166
|
+
add(l[0], d);
|
|
167
|
+
add(l[1], -d);
|
|
168
|
+
}
|
|
169
|
+
add(legs[0], dir);
|
|
170
|
+
add(legs[1], -dir);
|
|
171
|
+
for (const [cur, v] of Object.entries(exp)) {
|
|
172
|
+
if (Math.abs(v) > max) {
|
|
173
|
+
return `${cur} yönlü maruziyet ${v > 0 ? '+' : ''}${v} (max ±${max}) — korelasyon riski; farklı parite/yöne geç`;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* Zaman/geçmiş tabanlı disiplin. entries: journal kayıtları (at, kind, symbol, net).
|
|
180
|
+
Sorun varsa Türkçe hata metni, yoksa null döner. Limit 0 = kural kapalı. */
|
|
181
|
+
function disciplineError(entries, now, cfg, side, symbol, positions) {
|
|
182
|
+
const t = num(now, Date.now());
|
|
183
|
+
/* gelecekteki kayıtlar (saat kayması) değerlendirmeye girmez */
|
|
184
|
+
const list = (Array.isArray(entries) ? entries : []).filter((e) => e && Number.isFinite(Number(e.at)) && Number(e.at) <= t);
|
|
185
|
+
const f = cfg || {};
|
|
186
|
+
const dayStart = (() => { const d = new Date(t); d.setHours(0, 0, 0, 0); return d.getTime(); })();
|
|
187
|
+
const maxDay = num(f.maxTradesPerDay, 0);
|
|
188
|
+
if (maxDay > 0) {
|
|
189
|
+
const opens = list.filter((e) => e.kind === 'open' && Number(e.at) >= dayStart).length;
|
|
190
|
+
if (opens >= maxDay) {
|
|
191
|
+
return `günlük işlem limiti dolu (${opens}/${maxDay}) — bugün yeni işlem açma`;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const streakLimit = num(f.lossStreakLimit, 0);
|
|
195
|
+
const pauseMin = num(f.lossStreakPauseMin, 0);
|
|
196
|
+
if (streakLimit > 0 && pauseMin > 0) {
|
|
197
|
+
const closes = list.filter((e) => e.kind === 'close').slice().sort((a, b) => num(b.at, 0) - num(a.at, 0));
|
|
198
|
+
let streak = 0;
|
|
199
|
+
let lastAt = 0;
|
|
200
|
+
for (const c of closes) {
|
|
201
|
+
if (num(c.net, 0) < 0) {
|
|
202
|
+
streak++;
|
|
203
|
+
if (!lastAt) lastAt = num(c.at, 0);
|
|
204
|
+
} else break;
|
|
205
|
+
}
|
|
206
|
+
if (streak >= streakLimit && lastAt) {
|
|
207
|
+
const elapsed = t - lastAt;
|
|
208
|
+
if (elapsed < pauseMin * 60000) {
|
|
209
|
+
const rem = Math.ceil((pauseMin * 60000 - elapsed) / 60000);
|
|
210
|
+
return `${streak} ardışık kayıp — seri molası ${rem} dk daha (limit ${streakLimit} kayıp → ${pauseMin} dk mola)`;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const reMin = num(f.reentryCooldownMin, 0);
|
|
215
|
+
if (reMin > 0) {
|
|
216
|
+
const sym = String(symbol || '').toUpperCase();
|
|
217
|
+
const last = list
|
|
218
|
+
.filter((e) => e.kind === 'close' && String(e.symbol || '').toUpperCase() === sym)
|
|
219
|
+
.sort((a, b) => num(b.at, 0) - num(a.at, 0))[0];
|
|
220
|
+
if (last) {
|
|
221
|
+
const elapsed = t - num(last.at, 0);
|
|
222
|
+
if (elapsed < reMin * 60000) {
|
|
223
|
+
const rem = Math.ceil((reMin * 60000 - elapsed) / 60000);
|
|
224
|
+
return `${sym} son işlemi ${Math.max(1, Math.round(elapsed / 60000))} dk önce kapandı — re-entry için ${rem} dk bekle`;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return currencyExposureError(positions, side, symbol, f.maxPerCurrency);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
module.exports = {
|
|
232
|
+
normalizeVolume,
|
|
233
|
+
lossPerLot,
|
|
234
|
+
calcRiskLot,
|
|
235
|
+
stopsTooClose,
|
|
236
|
+
marginCheck,
|
|
237
|
+
exposureCheck,
|
|
238
|
+
symbolLegs,
|
|
239
|
+
currencyExposureError,
|
|
240
|
+
disciplineError,
|
|
241
|
+
};
|
package/src/agent/finstats.js
CHANGED
|
@@ -189,6 +189,16 @@ function buildWeeklyReport(ctx) {
|
|
|
189
189
|
for (const [d, v] of days) L.push('- ' + d + ': ' + money(v, cur));
|
|
190
190
|
L.push('');
|
|
191
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
|
+
}
|
|
192
202
|
const syms = Object.entries(st.bySymbol || {})
|
|
193
203
|
.map(([s, v]) => ({ symbol: s, ...v }))
|
|
194
204
|
.sort((a, b) => b.net - a.net)
|
|
@@ -43,10 +43,13 @@ def d(obj):
|
|
|
43
43
|
|
|
44
44
|
TERMINAL_PATH = ""
|
|
45
45
|
INIT_TRIED = False
|
|
46
|
+
ALLOW_LAUNCH = True # Beast Finance kapaliyken False: MT5 kapaliysa ACILMAZ
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
def ensure_init():
|
|
49
|
-
"""Terminal baglantisi yoksa (yeniden) init dener; True/False doner.
|
|
50
|
+
"""Terminal baglantisi yoksa (yeniden) init dener; True/False doner.
|
|
51
|
+
ALLOW_LAUNCH False iken terminal kapaliysa BASLATMA denenmez — Beast
|
|
52
|
+
Finance acilmadikca MT5 kendiliginden acilmaz."""
|
|
50
53
|
global INIT_TRIED
|
|
51
54
|
if mt5 is None:
|
|
52
55
|
return False
|
|
@@ -55,6 +58,8 @@ def ensure_init():
|
|
|
55
58
|
return True
|
|
56
59
|
except Exception:
|
|
57
60
|
pass
|
|
61
|
+
if not ALLOW_LAUNCH:
|
|
62
|
+
return False
|
|
58
63
|
INIT_TRIED = True
|
|
59
64
|
if TERMINAL_PATH:
|
|
60
65
|
return bool(mt5.initialize(path=TERMINAL_PATH))
|
|
@@ -65,6 +70,8 @@ def need():
|
|
|
65
70
|
if mt5 is None:
|
|
66
71
|
raise RuntimeError("MetaTrader5 python paketi kurulu degil (pip install MetaTrader5)")
|
|
67
72
|
if not ensure_init():
|
|
73
|
+
if not ALLOW_LAUNCH:
|
|
74
|
+
raise RuntimeError("MT5 terminali kapali — Beast Finance acilinca otomatik baslatilir")
|
|
68
75
|
last = ""
|
|
69
76
|
try:
|
|
70
77
|
last = str(mt5.last_error())
|
|
@@ -76,7 +83,16 @@ def need():
|
|
|
76
83
|
# ---------------- yontemler ----------------
|
|
77
84
|
|
|
78
85
|
def h_ping(p):
|
|
79
|
-
return {"pong": True, "mt5": mt5 is not None, "connected": bool(ensure_init()) if mt5 else False}
|
|
86
|
+
return {"pong": True, "mt5": mt5 is not None, "connected": bool(ensure_init()) if mt5 else False, "launch": ALLOW_LAUNCH}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def h_policy(p):
|
|
90
|
+
"""Otomatik baslatma politikasi: {"launch": bool}. Beast Finance
|
|
91
|
+
kapatilinca launch=False gonderilir — MT5 kapaliysa bir daha ACILMAZ;
|
|
92
|
+
terminal aciksa cagrilar calismaya devam eder (koruma surer)."""
|
|
93
|
+
global ALLOW_LAUNCH
|
|
94
|
+
ALLOW_LAUNCH = p.get("launch") is not False
|
|
95
|
+
return {"launch": ALLOW_LAUNCH}
|
|
80
96
|
|
|
81
97
|
|
|
82
98
|
def h_account(p):
|
|
@@ -375,6 +391,7 @@ def h_all_symbols(p):
|
|
|
375
391
|
|
|
376
392
|
HANDLERS = {
|
|
377
393
|
"ping": h_ping,
|
|
394
|
+
"policy": h_policy,
|
|
378
395
|
"account": h_account,
|
|
379
396
|
"terminal": h_terminal,
|
|
380
397
|
"symbols": h_symbols,
|
package/src/main.js
CHANGED
|
@@ -5144,11 +5144,17 @@ ipcMain.handle('agent:send', (_e, { sessionId, text }) => {
|
|
|
5144
5144
|
const fsid = String(sessionId);
|
|
5145
5145
|
let fsess = engine.cache.get(fsid);
|
|
5146
5146
|
if (!fsess) { try { fsess = engine._load(fsid); } catch {} }
|
|
5147
|
-
if (fsess && !fsess.botId && !fsess.bgJob && financeState.mode
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5147
|
+
if (fsess && !fsess.botId && !fsess.bgJob && financeState.mode) {
|
|
5148
|
+
if (!fsess.finance) {
|
|
5149
|
+
engine.markFinance(fsid, false);
|
|
5150
|
+
finApplyTraderFields(fsess);
|
|
5151
|
+
fsess.financeTrader = false; /* chat copilot'ı — trader değil */
|
|
5152
|
+
engine.cache.set(fsid, fsess);
|
|
5153
|
+
} else if (!financeState.agents.has(fsid)) {
|
|
5154
|
+
/* finance SOHBET botu: ajan talimatı/ayarlar HER MESAJDA tazelenir —
|
|
5155
|
+
asıl bot paralel ajanlardan geri kalmaz */
|
|
5156
|
+
finApplyChatFields(fsess);
|
|
5157
|
+
}
|
|
5152
5158
|
}
|
|
5153
5159
|
}
|
|
5154
5160
|
} catch {}
|
|
@@ -6898,6 +6904,12 @@ function cronMirrorTargets(cronSid) {
|
|
|
6898
6904
|
}
|
|
6899
6905
|
|
|
6900
6906
|
function cronFire(job) {
|
|
6907
|
+
/* BEAST FINANCE günlük rutin: plan/review job'ları trader'a özel gönderilir */
|
|
6908
|
+
if (job && (job.kind === 'finance-plan' || job.kind === 'finance-review')) {
|
|
6909
|
+
try { finDailyRoutine(job.kind === 'finance-plan' ? 'plan' : 'review'); } catch {}
|
|
6910
|
+
cronEmit();
|
|
6911
|
+
return;
|
|
6912
|
+
}
|
|
6901
6913
|
try {
|
|
6902
6914
|
const sid = reuseOrLatestSession(job.sessionId);
|
|
6903
6915
|
if (sid !== String(job.sessionId || '')) {
|
|
@@ -8425,6 +8437,7 @@ function studioWatchStop() {
|
|
|
8425
8437
|
oturumu, tur tur piyasa tarayıp (farklı API/model seçilebilir) işlem kovalar. */
|
|
8426
8438
|
const mt5bridge = require('./mt5bridge');
|
|
8427
8439
|
const financetools = require('./agent/financetools');
|
|
8440
|
+
const finrisk = require('./agent/finrisk');
|
|
8428
8441
|
const customtools = require('./agent/customtools');
|
|
8429
8442
|
const finwatch = require('./agent/finwatch');
|
|
8430
8443
|
const finstats = require('./agent/finstats');
|
|
@@ -8439,6 +8452,9 @@ const financeState = {
|
|
|
8439
8452
|
installTried: false,
|
|
8440
8453
|
agents: new Map(), /* sid -> { symbols, main, round, timer } — AYNI ANDA koşan finance ajanları */
|
|
8441
8454
|
watch: new Map(), /* ticket -> risk otomasyonu durumu (R, BE, kısmi TP...) */
|
|
8455
|
+
excursions: new Map(), /* ticket -> { symbol, side, mfe, mae, at } — MAE/MFE takibi */
|
|
8456
|
+
excDirty: false,
|
|
8457
|
+
excSavedAt: 0,
|
|
8442
8458
|
watchTimer: null,
|
|
8443
8459
|
watchBusy: false,
|
|
8444
8460
|
watchTickAt: 0,
|
|
@@ -8489,9 +8505,25 @@ function finCfg() {
|
|
|
8489
8505
|
const cur = Array.isArray(f.roleSkills[d.id]) ? f.roleSkills[d.id] : [];
|
|
8490
8506
|
f.roleSkills[d.id] = cur.map((s) => String(s || '').trim()).filter(Boolean).slice(0, 1);
|
|
8491
8507
|
}
|
|
8508
|
+
/* TRADER PLAYBOOK: asıl karar vericinin zorunlu skill'i (roleSkills.trader) */
|
|
8509
|
+
{
|
|
8510
|
+
const cur = Array.isArray(f.roleSkills.trader) ? f.roleSkills.trader : [];
|
|
8511
|
+
f.roleSkills.trader = cur.map((s) => String(s || '').trim()).filter(Boolean).slice(0, 1);
|
|
8512
|
+
}
|
|
8492
8513
|
if (!Number(f.intervalSec)) f.intervalSec = 120;
|
|
8493
8514
|
if (!Number(f.maxLot)) f.maxLot = 0.1;
|
|
8494
8515
|
if (f.maxPositions == null) f.maxPositions = 3;
|
|
8516
|
+
/* KODLA DİSİPLİN (trader kuralları): hepsi 0 = kural kapalı */
|
|
8517
|
+
if (!Number.isFinite(Number(f.maxTradesPerDay))) f.maxTradesPerDay = 10;
|
|
8518
|
+
if (!Number.isFinite(Number(f.lossStreakLimit))) f.lossStreakLimit = 2;
|
|
8519
|
+
if (!Number.isFinite(Number(f.lossStreakPauseMin))) f.lossStreakPauseMin = 30;
|
|
8520
|
+
if (!Number.isFinite(Number(f.reentryCooldownMin))) f.reentryCooldownMin = 15;
|
|
8521
|
+
if (!Number.isFinite(Number(f.maxPerCurrency))) f.maxPerCurrency = 3;
|
|
8522
|
+
/* SHADOW MOD: emir gönderilmez — kararlar gerekçesiyle günlüğe yazılır */
|
|
8523
|
+
if (typeof f.shadowMode !== 'boolean') f.shadowMode = false;
|
|
8524
|
+
/* GÜNLÜK RUTİN: plan/review saatleri (HH:MM; boş = kapalı) — hafta içi cron */
|
|
8525
|
+
if (typeof f.planTime !== 'string') f.planTime = '';
|
|
8526
|
+
if (typeof f.reviewTime !== 'string') f.reviewTime = '';
|
|
8495
8527
|
/* RİSK OTOMASYONU (watchdog): +R'da BE, trailing, kısmi TP — main süreç
|
|
8496
8528
|
saniyelik döngüyle uygular; ajan turunu BEKLEMEZ. 0 = ilgili kural kapalı. */
|
|
8497
8529
|
if (typeof f.watchdog !== 'boolean') f.watchdog = true;
|
|
@@ -8599,6 +8631,121 @@ function finJournalTail(n) {
|
|
|
8599
8631
|
}
|
|
8600
8632
|
}
|
|
8601
8633
|
|
|
8634
|
+
/* ---------- GERİ BİLDİRİM DÖNGÜSÜ: ajanın kendi işlem geçmişi özeti ----------
|
|
8635
|
+
Trader her turda son kapanışlarını, bugününü, kayıp serisini, kâr yakalama
|
|
8636
|
+
oranını (MFE) ve son notlarını sistem promptunda görür. */
|
|
8637
|
+
function finBuildDigest() {
|
|
8638
|
+
let entries = [];
|
|
8639
|
+
try { entries = finJournalTail(300); } catch { return ''; }
|
|
8640
|
+
const closesAll = entries.filter((e) => e.kind === 'close');
|
|
8641
|
+
const closes = closesAll.slice(-10);
|
|
8642
|
+
const L = [];
|
|
8643
|
+
const dayStart = (() => { const d = new Date(); d.setHours(0, 0, 0, 0); return d.getTime(); })();
|
|
8644
|
+
const todayOpens = entries.filter((e) => e.kind === 'open' && Number(e.at) >= dayStart).length;
|
|
8645
|
+
const todayCloses = closesAll.filter((e) => Number(e.at) >= dayStart);
|
|
8646
|
+
const todayNet = Math.round(todayCloses.reduce((a, e) => a + (Number(e.net) || 0), 0) * 100) / 100;
|
|
8647
|
+
const f = finCfg();
|
|
8648
|
+
L.push(`Bugün: ${todayOpens} açılış · ${todayCloses.length} kapanış · net ${todayNet >= 0 ? '+' : ''}${todayNet}`);
|
|
8649
|
+
if (closes.length) {
|
|
8650
|
+
L.push('Son kapanışlar (yeni→eski): ' + closes
|
|
8651
|
+
.slice()
|
|
8652
|
+
.reverse()
|
|
8653
|
+
.map((e) => `${e.symbol || '?'}${e.side ? ' ' + String(e.side).toUpperCase() : ''} ${Number(e.net) >= 0 ? '+' : ''}${Number(e.net) || 0}`)
|
|
8654
|
+
.join(' · '));
|
|
8655
|
+
}
|
|
8656
|
+
let streak = 0;
|
|
8657
|
+
for (const c of closesAll.slice().reverse()) {
|
|
8658
|
+
if ((Number(c.net) || 0) < 0) streak++;
|
|
8659
|
+
else break;
|
|
8660
|
+
}
|
|
8661
|
+
if (streak >= 2) L.push(`DİKKAT: ${streak} ardışık kayıp — seri molası kuralı ${f.lossStreakLimit} kayıpta devreye girer`);
|
|
8662
|
+
/* MFE (maksimum kâr) yakalama oranı: kârı ne kadarını geri veriyorsun */
|
|
8663
|
+
const withMfe = closesAll.filter((e) => typeof e.mfe === 'number').slice(-30);
|
|
8664
|
+
if (withMfe.length >= 3) {
|
|
8665
|
+
const winMfe = withMfe.filter((e) => (Number(e.net) || 0) > 0 && Number(e.mfe) > 0);
|
|
8666
|
+
const sumMfe = winMfe.reduce((a, e) => a + Number(e.mfe), 0);
|
|
8667
|
+
const sumNet = winMfe.reduce((a, e) => a + (Number(e.net) || 0), 0);
|
|
8668
|
+
if (sumMfe > 0) {
|
|
8669
|
+
L.push(`Kâr yakalama: son ${winMfe.length} kazanan işlemde ulaşılan maksimum kârın %${Math.round((sumNet / sumMfe) * 100)}'i realize edildi`);
|
|
8670
|
+
}
|
|
8671
|
+
const losers = withMfe.filter((e) => (Number(e.net) || 0) < 0 && typeof e.mae === 'number');
|
|
8672
|
+
if (losers.length) {
|
|
8673
|
+
const avgMae = losers.reduce((a, e) => a + Math.abs(Number(e.mae) || 0), 0) / losers.length;
|
|
8674
|
+
L.push(`Kaybedenlerde ortalama MAE (en kötü seviye): -${Math.round(avgMae * 100) / 100}`);
|
|
8675
|
+
}
|
|
8676
|
+
}
|
|
8677
|
+
const notes = entries.filter((e) => e.kind === 'note').slice(-3);
|
|
8678
|
+
if (notes.length) {
|
|
8679
|
+
L.push('Son notların: ' + notes.map((n) => `${n.symbol || '?'}: ${String(n.note || '').replace(/\s+/g, ' ').slice(0, 90)}`).join(' | '));
|
|
8680
|
+
}
|
|
8681
|
+
/* Aktif disiplin kilidi varsa ajana açıkça söyle */
|
|
8682
|
+
try {
|
|
8683
|
+
const block = finrisk.disciplineError(entries, Date.now(), f, 'buy', '', []);
|
|
8684
|
+
if (block) L.push('AKTİF KİLİT: ' + block);
|
|
8685
|
+
} catch {}
|
|
8686
|
+
return L.join('\n');
|
|
8687
|
+
}
|
|
8688
|
+
|
|
8689
|
+
/* Koşan ekip ajanlarının son raporlarını trader turuna taşır. */
|
|
8690
|
+
function finTeamDigest() {
|
|
8691
|
+
const L = [];
|
|
8692
|
+
try {
|
|
8693
|
+
for (const [sid, a] of financeState.agents) {
|
|
8694
|
+
if (!a || !a.role || !engine) continue;
|
|
8695
|
+
const s = engine.cache.get(String(sid));
|
|
8696
|
+
const msgs = (s && s.messages) || [];
|
|
8697
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
8698
|
+
const m = msgs[i];
|
|
8699
|
+
if (m && m.role === 'assistant' && typeof m.content === 'string' && m.content.trim()) {
|
|
8700
|
+
const def = finRoleDef(a.role);
|
|
8701
|
+
L.push(`- ${def ? def.label : a.role}: ${m.content.replace(/\s+/g, ' ').slice(0, 260)}`);
|
|
8702
|
+
break;
|
|
8703
|
+
}
|
|
8704
|
+
}
|
|
8705
|
+
}
|
|
8706
|
+
} catch {}
|
|
8707
|
+
return L.join('\n');
|
|
8708
|
+
}
|
|
8709
|
+
|
|
8710
|
+
/* KODLA DİSİPLİN kancası: financetools işlem öncesi bunu çağırır. */
|
|
8711
|
+
function finDisciplineError(side, symbol, positions) {
|
|
8712
|
+
try {
|
|
8713
|
+
return finrisk.disciplineError(finJournalTail(400), Date.now(), finCfg(), side, symbol, positions);
|
|
8714
|
+
} catch {
|
|
8715
|
+
return null;
|
|
8716
|
+
}
|
|
8717
|
+
}
|
|
8718
|
+
|
|
8719
|
+
/* ---------- MAE/MFE: pozisyonun gördüğü en iyi/en kötü seviye ---------- */
|
|
8720
|
+
function finExcLoad() {
|
|
8721
|
+
try {
|
|
8722
|
+
const raw = finReadJson(finFile('excursions.json'), {});
|
|
8723
|
+
for (const [t, v] of Object.entries(raw || {})) {
|
|
8724
|
+
if (!v || typeof v !== 'object') continue;
|
|
8725
|
+
financeState.excursions.set(String(t), {
|
|
8726
|
+
symbol: String(v.symbol || ''),
|
|
8727
|
+
side: String(v.side || ''),
|
|
8728
|
+
mfe: Number(v.mfe) || 0,
|
|
8729
|
+
mae: Number(v.mae) || 0,
|
|
8730
|
+
at: Number(v.at) || Date.now(),
|
|
8731
|
+
});
|
|
8732
|
+
}
|
|
8733
|
+
} catch {}
|
|
8734
|
+
}
|
|
8735
|
+
function finExcSave() {
|
|
8736
|
+
financeState.excDirty = true;
|
|
8737
|
+
}
|
|
8738
|
+
function finExcFlush(force) {
|
|
8739
|
+
if (!financeState.excDirty) return;
|
|
8740
|
+
const now = Date.now();
|
|
8741
|
+
if (!force && now - financeState.excSavedAt < 15000) return;
|
|
8742
|
+
financeState.excSavedAt = now;
|
|
8743
|
+
financeState.excDirty = false;
|
|
8744
|
+
try {
|
|
8745
|
+
finWriteJson(finFile('excursions.json'), Object.fromEntries(financeState.excursions));
|
|
8746
|
+
} catch {}
|
|
8747
|
+
}
|
|
8748
|
+
|
|
8602
8749
|
function finAgentInfo(sid) {
|
|
8603
8750
|
try {
|
|
8604
8751
|
const a = financeState.agents.get(String(sid || ''));
|
|
@@ -8861,7 +9008,14 @@ async function finRecordClose(ticket, st) {
|
|
|
8861
9008
|
}
|
|
8862
9009
|
} catch {}
|
|
8863
9010
|
const rounded = net == null ? null : Math.round(net * 100) / 100;
|
|
8864
|
-
|
|
9011
|
+
/* MAE/MFE: pozisyonun gördüğü en iyi/en kötü seviyeler kapanışa iliştirilir */
|
|
9012
|
+
const ex = financeState.excursions.get(String(ticket)) || null;
|
|
9013
|
+
const mfe = ex ? Math.round(Number(ex.mfe) * 100) / 100 : undefined;
|
|
9014
|
+
const mae = ex ? Math.round(Number(ex.mae) * 100) / 100 : undefined;
|
|
9015
|
+
financeState.excursions.delete(String(ticket));
|
|
9016
|
+
finExcSave();
|
|
9017
|
+
finExcFlush(true);
|
|
9018
|
+
finJournal({ kind: 'close', ticket, symbol, net: rounded, side: (st && st.side) || '', mfe, mae });
|
|
8865
9019
|
const pl = rounded == null ? '' : ` · K/Z ${rounded >= 0 ? '+' : ''}${rounded.toFixed(2)}`;
|
|
8866
9020
|
const line = `🏁 Pozisyon kapandı: ${symbol || '?'} #${ticket}${pl}`;
|
|
8867
9021
|
financeLog('[watchdog] ' + line);
|
|
@@ -8939,6 +9093,19 @@ async function finWatchTick() {
|
|
|
8939
9093
|
for (const p of positions) {
|
|
8940
9094
|
const ticket = String(p.ticket);
|
|
8941
9095
|
live.add(ticket);
|
|
9096
|
+
/* MAE/MFE excursion takibi: kâr/zararın gördüğü en iyi-en kötü seviye */
|
|
9097
|
+
{
|
|
9098
|
+
let ex = financeState.excursions.get(ticket);
|
|
9099
|
+
const profit = Number(p.profit) || 0;
|
|
9100
|
+
if (!ex) {
|
|
9101
|
+
ex = { symbol: String(p.symbol || ''), side: Number(p.type) === 0 ? 'buy' : 'sell', mfe: profit, mae: profit, at: Date.now() };
|
|
9102
|
+
financeState.excursions.set(ticket, ex);
|
|
9103
|
+
} else {
|
|
9104
|
+
if (profit > ex.mfe) ex.mfe = profit;
|
|
9105
|
+
if (profit < ex.mae) ex.mae = profit;
|
|
9106
|
+
}
|
|
9107
|
+
finExcSave();
|
|
9108
|
+
}
|
|
8942
9109
|
let st = financeState.watch.get(ticket);
|
|
8943
9110
|
if (!st) {
|
|
8944
9111
|
st = {
|
|
@@ -9020,6 +9187,7 @@ async function finWatchTick() {
|
|
|
9020
9187
|
finStatsRefresh(true).catch(() => {});
|
|
9021
9188
|
}
|
|
9022
9189
|
finMaybeWeeklyReport();
|
|
9190
|
+
finExcFlush(false); /* MAE/MFE deposu 15 sn'de bir diske */
|
|
9023
9191
|
} catch {
|
|
9024
9192
|
} finally {
|
|
9025
9193
|
financeState.watchBusy = false;
|
|
@@ -9070,8 +9238,19 @@ async function finWeeklyReport(manual) {
|
|
|
9070
9238
|
const positions = posR && posR.ok ? ((posR.data && posR.data.positions) || []) : [];
|
|
9071
9239
|
const fromTs = now - 7 * 86400000;
|
|
9072
9240
|
const equity = (financeState.equity || []).filter((p) => Number(p.at) >= fromTs - 86400000);
|
|
9073
|
-
const journal = finJournalTail(
|
|
9241
|
+
const journal = finJournalTail(400);
|
|
9074
9242
|
const drawdown = finstats.maxDrawdown(equity);
|
|
9243
|
+
/* MFE/MAE özeti: kâr yakalama oranı ve kaybedenlerde ortalama en kötü seviye */
|
|
9244
|
+
const withMfe = journal.filter((e) => e.kind === 'close' && typeof e.mfe === 'number');
|
|
9245
|
+
const mfeWin = withMfe.filter((e) => (Number(e.net) || 0) > 0 && Number(e.mfe) > 0);
|
|
9246
|
+
const mfeSumMfe = mfeWin.reduce((a, e) => a + Number(e.mfe), 0);
|
|
9247
|
+
const mfeSumNet = mfeWin.reduce((a, e) => a + (Number(e.net) || 0), 0);
|
|
9248
|
+
const mfeLoss = withMfe.filter((e) => (Number(e.net) || 0) < 0 && typeof e.mae === 'number');
|
|
9249
|
+
const mfe = withMfe.length ? {
|
|
9250
|
+
n: withMfe.length,
|
|
9251
|
+
capture: mfeSumMfe > 0 ? Math.round((mfeSumNet / mfeSumMfe) * 100) : null,
|
|
9252
|
+
avgMaeLoss: mfeLoss.length ? Math.round((mfeLoss.reduce((a, e) => a + Math.abs(Number(e.mae)), 0) / mfeLoss.length) * 100) / 100 : null,
|
|
9253
|
+
} : null;
|
|
9075
9254
|
let review = '';
|
|
9076
9255
|
if (engine) {
|
|
9077
9256
|
try {
|
|
@@ -9084,7 +9263,7 @@ async function finWeeklyReport(manual) {
|
|
|
9084
9263
|
review = String(res || '').trim().slice(0, 4000);
|
|
9085
9264
|
} catch {}
|
|
9086
9265
|
}
|
|
9087
|
-
const md = finstats.buildWeeklyReport({ stats, drawdown, equity, journal, account, positions, fromTs, toTs: now, review });
|
|
9266
|
+
const md = finstats.buildWeeklyReport({ stats, drawdown, equity, journal, account, positions, fromTs, toTs: now, review, mfe });
|
|
9088
9267
|
const day = finstats.dayKey(now);
|
|
9089
9268
|
const file = path.join(financeDir(), 'reports', 'weekly-' + day + '.md');
|
|
9090
9269
|
try {
|
|
@@ -9119,6 +9298,7 @@ function finMaybeWeeklyReport() {
|
|
|
9119
9298
|
|
|
9120
9299
|
try { finAlertsLoad(); } catch {}
|
|
9121
9300
|
try { finWatchLoad(); } catch {}
|
|
9301
|
+
try { finExcLoad(); } catch {}
|
|
9122
9302
|
try { finStatsLoad(); } catch {}
|
|
9123
9303
|
try { finEquityLoad(); } catch {}
|
|
9124
9304
|
|
|
@@ -9126,6 +9306,7 @@ try { finEquityLoad(); } catch {}
|
|
|
9126
9306
|
try {
|
|
9127
9307
|
financetools.setConfig(() => finCfg());
|
|
9128
9308
|
financetools.setAlerts(finAlertApi());
|
|
9309
|
+
financetools.setDiscipline((side, symbol, positions) => finDisciplineError(side, symbol, positions));
|
|
9129
9310
|
financetools.setNotify((entry) => {
|
|
9130
9311
|
const k = String((entry && entry.kind) || '');
|
|
9131
9312
|
const d = entry && entry.data ? entry.data : {};
|
|
@@ -9136,15 +9317,23 @@ try {
|
|
|
9136
9317
|
finJournal({ kind: 'note', sid, agent: who, symbol: d.symbol, note: d.note, ticket: d.ticket || 0, side: d.side || '' });
|
|
9137
9318
|
return;
|
|
9138
9319
|
}
|
|
9320
|
+
/* SHADOW: gerçek emir yok — karar teziyle günlüğe yazılır (test/ölçüm) */
|
|
9321
|
+
if (k === 'shadow') {
|
|
9322
|
+
finJournal({ kind: 'shadow', sid, agent: who, symbol: d.symbol, side: d.side || '', volume: d.volume, sl: d.sl || 0, tp: d.tp || 0, price: d.price || 0, type: d.type || '', reason: d.reason || '' });
|
|
9323
|
+
const sline = `🧪 SHADOW: ${d.side || d.type || ''} ${d.volume || ''} ${d.symbol || ''} — emir gönderilmedi (karar günlüğe yazıldı)`;
|
|
9324
|
+
financeLog('[shadow] ' + sline);
|
|
9325
|
+
finPush('trade', { line: sline });
|
|
9326
|
+
return;
|
|
9327
|
+
}
|
|
9139
9328
|
if (k === 'trade') {
|
|
9140
|
-
finJournal({ kind: 'trade', sid, agent: who, symbol: d.symbol, side: d.side, volume: d.volume, sl: d.sl, tp: d.tp, reason: d.comment || '' });
|
|
9329
|
+
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 || '' });
|
|
9141
9330
|
} else if (k === 'close') {
|
|
9142
9331
|
finJournal({ kind: 'close-req', sid, agent: who, ticket: d.ticket, volume: d.volume });
|
|
9143
9332
|
return; /* kesin kapanış K/Z'si watchdog tarafından yazılır (çift bildirim yok) */
|
|
9144
9333
|
} else if (k === 'modify') {
|
|
9145
9334
|
finJournal({ kind: 'modify', sid, agent: who, ticket: d.ticket, sl: d.sl, tp: d.tp });
|
|
9146
9335
|
} else if (k === 'pending') {
|
|
9147
|
-
finJournal({ kind: 'pending', sid, agent: who, symbol: d.symbol, type: d.type, volume: d.volume });
|
|
9336
|
+
finJournal({ kind: 'pending', sid, agent: who, symbol: d.symbol, type: d.type, volume: d.volume, reason: d.reason || '' });
|
|
9148
9337
|
} else if (k === 'cancel') {
|
|
9149
9338
|
finJournal({ kind: 'cancel', sid, agent: who, ticket: d.ticket });
|
|
9150
9339
|
}
|
|
@@ -9190,6 +9379,10 @@ try {
|
|
|
9190
9379
|
} catch {}
|
|
9191
9380
|
|
|
9192
9381
|
function financeEnsureBridge() {
|
|
9382
|
+
/* BEAST FINANCE = MT5 KAPISI: mod KAPALIYKEN köprü HİÇ başlatılmaz —
|
|
9383
|
+
Beast, kullanıcı finance açmadıkça MT5 terminalini açmaz/yeniden
|
|
9384
|
+
başlatmaz. Mod açılınca finance:mode handler'ı burayı çağırır. */
|
|
9385
|
+
if (!financeState.mode) return mt5bridge.status();
|
|
9193
9386
|
const f = finCfg();
|
|
9194
9387
|
const candidates = [String(f.pythonPath || '').trim(), 'python', 'py -3'].filter(Boolean);
|
|
9195
9388
|
/* Beast gömülü Python runtime kuruluysa en başa ekle (makinede Python olmasa da köprü çalışır) */
|
|
@@ -9337,20 +9530,37 @@ function finAgentRegisterBg(s, symbols) {
|
|
|
9337
9530
|
} catch {}
|
|
9338
9531
|
}
|
|
9339
9532
|
|
|
9340
|
-
function finApplyTraderFields(s, symbolsOverride, role) {
|
|
9533
|
+
function finApplyTraderFields(s, symbolsOverride, role, isMain) {
|
|
9341
9534
|
const f = finCfg();
|
|
9342
9535
|
const r = String(role || s.financeRole || '');
|
|
9343
9536
|
s.finance = true;
|
|
9344
9537
|
s.financeTrader = true;
|
|
9345
9538
|
s.financeRole = r;
|
|
9539
|
+
/* ANA TRADER: playbook skill'i roleSkills.trader'dan gelir */
|
|
9540
|
+
if (typeof isMain === 'boolean') s.financePlaybook = isMain;
|
|
9346
9541
|
/* rol ajanları ASLA işlem açmaz — trade ajanı ve işçileri DAIMA açabilir */
|
|
9347
9542
|
s.financeAuto = !r;
|
|
9348
9543
|
s.financeSymbols = Array.isArray(symbolsOverride) && symbolsOverride.length ? symbolsOverride : f.symbols;
|
|
9349
9544
|
s.financeStrategy = String(f.strategy || '');
|
|
9545
|
+
s.financeShadow = !!f.shadowMode; /* shadow modda işlem araçları emir göndermez */
|
|
9350
9546
|
s.financeLimits = { maxLot: f.maxLot, maxPositions: f.maxPositions };
|
|
9351
|
-
/*
|
|
9352
|
-
|
|
9547
|
+
/* ZORUNLU TEK skill: rol ajanı → roleSkills[rol], ana trader → roleSkills.trader */
|
|
9548
|
+
const skillKey = r || (s.financePlaybook ? 'trader' : '');
|
|
9549
|
+
s.financeRoleSkills = (skillKey && f.roleSkills && f.roleSkills[skillKey]) || [];
|
|
9550
|
+
engine.cache.set(String(s.id), s);
|
|
9551
|
+
}
|
|
9552
|
+
|
|
9553
|
+
/* Finance SOHBET oturumu (chat copilot) alanlarını tazele — trader bayrağı
|
|
9554
|
+
KORUNUR. Strateji/sembol/limit/rol-skill değişiklikleri "asıl bot"a da
|
|
9555
|
+
ulaşsın; paralel ajanlar tur başında zaten tazeleniyor. */
|
|
9556
|
+
function finApplyChatFields(s) {
|
|
9557
|
+
if (!s) return;
|
|
9558
|
+
const wasTrader = !!s.financeTrader;
|
|
9559
|
+
finApplyTraderFields(s);
|
|
9560
|
+
s.financeTrader = wasTrader;
|
|
9561
|
+
if (!wasTrader) s.financeAuto = false; /* sohbet copilot'ı — otonom trader değil */
|
|
9353
9562
|
engine.cache.set(String(s.id), s);
|
|
9563
|
+
return s;
|
|
9354
9564
|
}
|
|
9355
9565
|
|
|
9356
9566
|
/* Yeni finance ajanı aç (ana trader YA DA sembol işçisi YA DA analiz ekibi rolü) */
|
|
@@ -9365,7 +9575,7 @@ function finAgentCreate(symbols, isMain, role) {
|
|
|
9365
9575
|
timer: null,
|
|
9366
9576
|
};
|
|
9367
9577
|
financeState.agents.set(String(s.id), record);
|
|
9368
|
-
finApplyTraderFields(s, record.symbols, record.role);
|
|
9578
|
+
finApplyTraderFields(s, record.symbols, record.role, !!isMain);
|
|
9369
9579
|
finAgentRegisterBg(s, record.symbols);
|
|
9370
9580
|
return { s, agent: record };
|
|
9371
9581
|
}
|
|
@@ -9404,6 +9614,9 @@ function finTraderBrief(agent) {
|
|
|
9404
9614
|
const syms = agent && agent.symbols && agent.symbols.length ? agent.symbols.join(', ') : (f.symbols || []).join(', ');
|
|
9405
9615
|
const roleDef = finRoleDef(agent && agent.role);
|
|
9406
9616
|
const who = agent && agent.main ? 'TRADER' : roleDef ? roleDef.label.toUpperCase() : 'FINANCE AJANI';
|
|
9617
|
+
const playbook = agent && agent.main && f.roleSkills && f.roleSkills.trader && f.roleSkills.trader[0]
|
|
9618
|
+
? f.roleSkills.trader[0]
|
|
9619
|
+
: '';
|
|
9407
9620
|
return [
|
|
9408
9621
|
`Beast Finance ${who} başlatıldı — ilk tur: strateji çerçeveni kur ve piyasa taramasını yap.`,
|
|
9409
9622
|
`Odak semboller: ${syms || '(boş — mt5_status ile terminale bak, mantıklı semboller seç)'}`,
|
|
@@ -9414,14 +9627,66 @@ function finTraderBrief(agent) {
|
|
|
9414
9627
|
roleDef
|
|
9415
9628
|
? '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.'
|
|
9416
9629
|
: f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : 'Strateji notu yok: trend + destek/direnç + momentum ile temel okuma yap.',
|
|
9417
|
-
roleDef && f.strategy ? `Sahibinin strateji notu (bu çerçevede analiz et): ${f.strategy}` : '',
|
|
9630
|
+
roleDef && f.strategy ? `Sahibinin strateji notu (bu çerçevede analiz et): ${f.strategy}` : '',
|
|
9631
|
+
playbook ? `PLAYBOOK (ZORUNLU skill): skill("${playbook}") — sistem promptunda tam metni verilmiştir; her turda birebir uygula.` : '',
|
|
9632
|
+
f.shadowMode ? 'SHADOW MOD AÇIK: emir gönderilmez — kararlarını gerekçesiyle raporla, gerçek işlem açılmaz.' : '',
|
|
9418
9633
|
'Bu turda: mt5_status → hesap/pozisyon/fiyat verisi → mt5_rates/mt5_indicators ile teknik okuma → değerlendirme → kararlar (veya BEKLE: sebep) → kısa rapor.',
|
|
9419
9634
|
'Önemli kararların gerekçesini mt5_note ile günlüğe yaz (haftalık performans raporu bu notları kullanır).',
|
|
9420
9635
|
'Risk otomasyonu main süreçte 5 sn döngüyle çalışır (+R BE, trailing, kısmi TP) — sen yine de SL/TP seviyelerini aktif yönet.',
|
|
9421
9636
|
'Diğer finance/paralel ajanlarla koordinasyon için agent_dm aracı var (to: ajan başlığı anahtar kelimesi).',
|
|
9637
|
+
agent && agent.main && !roleDef ? `İŞLEM GEÇMİŞİN:\n${finBuildDigest()}` : '',
|
|
9422
9638
|
].filter(Boolean).join('\n');
|
|
9423
9639
|
}
|
|
9424
9640
|
|
|
9641
|
+
/* GÜNLÜK RUTİN: plan (açılış) / review (kapanış) — cron 'finance-plan' /
|
|
9642
|
+
'finance-review' job'ları tetikler. Trader koşmuyorsa sessizce atlanır. */
|
|
9643
|
+
function finDailyRoutine(mode) {
|
|
9644
|
+
try {
|
|
9645
|
+
let mainSid = '';
|
|
9646
|
+
for (const [sid, a] of financeState.agents) {
|
|
9647
|
+
if (a && a.main) { mainSid = String(sid); break; }
|
|
9648
|
+
}
|
|
9649
|
+
if (!mainSid || !engine) {
|
|
9650
|
+
financeLog('[rutin] trader koşmuyor — günlük ' + mode + ' atlandı');
|
|
9651
|
+
return;
|
|
9652
|
+
}
|
|
9653
|
+
const agent = financeState.agents.get(mainSid);
|
|
9654
|
+
const s = engine.cache.get(mainSid);
|
|
9655
|
+
if (s) finApplyTraderFields(s, agent.symbols, agent.role, true);
|
|
9656
|
+
if (engine.isBusy(mainSid)) {
|
|
9657
|
+
financeLog('[rutin] trader meşgul — günlük ' + mode + ' atlandı');
|
|
9658
|
+
return;
|
|
9659
|
+
}
|
|
9660
|
+
const digest = finBuildDigest();
|
|
9661
|
+
const text = mode === 'plan'
|
|
9662
|
+
? 'GÜNLÜK PLAN (açılış rutini): Bugün için net bir plan yap — izleme listesini ve piyasa koşullarını tara; sembol başına yön eğilimi, izlenecek seviyeler ve risk planı (kaç işlem, hangi setup/kurulum, günlük kayıp sınırı) yaz. Zorunlu işlem yok. Planı mt5_note ile günlüğe kaydet.\n\nİŞLEM GEÇMİŞİN:\n' + digest
|
|
9663
|
+
: 'GÜNLÜK REVIEW (kapanış rutini): Bugünün işlemlerini ve performans geçmişini değerlendir — hangi kararlar işledi, hangileri hata; kâr yakalama (MFE) ve MAE verisine bak; açık pozisyonların SL/TP\'lerini kontrol et, korumasız bırakma; yarın için 2-3 somut ders çıkar ve mt5_note ile kaydet.\n\nİŞLEM GEÇMİŞİN:\n' + digest;
|
|
9664
|
+
const ok = engine.send(mainSid, text, { userAction: true });
|
|
9665
|
+
financeLog('[rutin] günlük ' + mode + (ok ? ' gönderildi' : ' gönderilemedi (meşgul)'));
|
|
9666
|
+
} catch {}
|
|
9667
|
+
}
|
|
9668
|
+
|
|
9669
|
+
/* Plan/review saatlerini hafta içi cron job'larına bağla (cron.json kalıcı). */
|
|
9670
|
+
function finSyncScheduleJobs() {
|
|
9671
|
+
try {
|
|
9672
|
+
const f = finCfg();
|
|
9673
|
+
cron.removeIf((j) => j && (j.kind === 'finance-plan' || j.kind === 'finance-review'));
|
|
9674
|
+
const add = (kind, name, time) => {
|
|
9675
|
+
const m = /^([01]?\d|2[0-3]):([0-5]\d)$/.exec(String(time || '').trim());
|
|
9676
|
+
if (!m) return;
|
|
9677
|
+
const r = cron.add({
|
|
9678
|
+
name,
|
|
9679
|
+
schedule: `${Number(m[2])} ${Number(m[1])} * * 1-5`,
|
|
9680
|
+
prompt: name,
|
|
9681
|
+
kind,
|
|
9682
|
+
});
|
|
9683
|
+
if (r && r.ok) financeLog('[rutin] ' + name + ' zamanlandı: ' + r.job.schedule + ' (hafta içi)');
|
|
9684
|
+
};
|
|
9685
|
+
add('finance-plan', 'Finance Günlük Plan', f.planTime);
|
|
9686
|
+
add('finance-review', 'Finance Günlük Review', f.reviewTime);
|
|
9687
|
+
} catch {}
|
|
9688
|
+
}
|
|
9689
|
+
|
|
9425
9690
|
/* Her tur öncesi CHAT AJANINDAN plan iste: aynı motorun odaklı alt-ajanı
|
|
9426
9691
|
(_subagent) hesap/pozisyon/strateji bağlamıyla kısa işlem planı üretir.
|
|
9427
9692
|
Plan, tur mesajına [ANA AJAN PLANI] bloğu olarak eklenir. */
|
|
@@ -9486,13 +9751,21 @@ function finAgentRound(sid) {
|
|
|
9486
9751
|
if (agent.main) financeState.traderRounds = agent.round;
|
|
9487
9752
|
const f = finCfg();
|
|
9488
9753
|
const roleDef = finRoleDef(agent.role);
|
|
9754
|
+
let shadow = false;
|
|
9489
9755
|
try {
|
|
9490
9756
|
const s = engine.cache.get(String(sid));
|
|
9491
|
-
if (s)
|
|
9757
|
+
if (s) {
|
|
9758
|
+
finApplyTraderFields(s, agent.symbols, agent.role, !!agent.main);
|
|
9759
|
+
/* Geri bildirim döngüsü: ana trader her turda kendi işlem geçmişini görür */
|
|
9760
|
+
s.financeDigest = agent.main ? finBuildDigest() : '';
|
|
9761
|
+
shadow = !!s.financeShadow;
|
|
9762
|
+
}
|
|
9492
9763
|
} catch {}
|
|
9493
9764
|
const auto = roleDef
|
|
9494
9765
|
? `UZMANLIK: ${roleDef.desc} — İŞLEM AÇMA, yalnız analiz + net öneri.`
|
|
9495
|
-
:
|
|
9766
|
+
: shadow
|
|
9767
|
+
? 'SHADOW MOD AÇIK: emir GÖNDERİLMEZ — kararını teziyle raporla (günlüğe yazılır).'
|
|
9768
|
+
: 'İşlem açabilirsin — limitlere uy, SL\u2019siz pozisyon bırakma.';
|
|
9496
9769
|
const focus = agent.symbols.length ? `Odak: ${agent.symbols.join(', ')}. ` : '';
|
|
9497
9770
|
const round = `FINANCE TUR #${agent.round}: ${focus}hesap + pozisyonlar + fiyatları çek; ${roleDef ? 'rolüne uygun analiz yap (mt5_rates/mt5_indicators ile) ve öneri ver.' : 'açık pozisyonları yönet (SL/TP güncelle, hedefe ulaşanı kapat); mt5_rates/mt5_indicators ile yeni fırsatları değerlendir.'} ${auto} Önemli kararların gerekçesini mt5_note ile günlüğe yaz. Kısa rapor ver.`;
|
|
9498
9771
|
const launch = (planBlock) => {
|
|
@@ -9507,15 +9780,22 @@ function finAgentRound(sid) {
|
|
|
9507
9780
|
agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, 30000);
|
|
9508
9781
|
}
|
|
9509
9782
|
};
|
|
9783
|
+
/* EKİP ENTEGRASYONU: ana trader turuna koşan uzman ajanların son raporları
|
|
9784
|
+
enjekte edilir — karar öncesi görülmesi garanti edilir. */
|
|
9785
|
+
let teamPrefix = '';
|
|
9786
|
+
if (agent.main) {
|
|
9787
|
+
const team = finTeamDigest();
|
|
9788
|
+
if (team) teamPrefix = '[EKİP RAPORLARI — koşan uzman ajanların son raporları; kararında dikkate al]\n' + team + '\n\n';
|
|
9789
|
+
}
|
|
9510
9790
|
if (f.consultChat === false || roleDef) {
|
|
9511
9791
|
/* danışma kapalı ya da rol ajanı (plan trader'a yöneliktir): tur doğrudan başlar */
|
|
9512
|
-
launch(
|
|
9792
|
+
launch(teamPrefix);
|
|
9513
9793
|
return;
|
|
9514
9794
|
}
|
|
9515
9795
|
if (agent.main) finPush('trader', { state: 'consult', round: agent.round });
|
|
9516
9796
|
finConsultPlan(f, agent, String(sid))
|
|
9517
|
-
.then((plan) => launch(plan ? `[ANA AJAN PLANI — bu turun varsayılan stratejisi; strateji notuyla çelişirse not önceliklidir]\n${plan}\n\n` : ''))
|
|
9518
|
-
.catch(() => launch(
|
|
9797
|
+
.then((plan) => launch(teamPrefix + (plan ? `[ANA AJAN PLANI — bu turun varsayılan stratejisi; strateji notuyla çelişirse not önceliklidir]\n${plan}\n\n` : '')))
|
|
9798
|
+
.catch(() => launch(teamPrefix));
|
|
9519
9799
|
}
|
|
9520
9800
|
|
|
9521
9801
|
/* Tur/durum sonları: sürekli ajan döngüsünü besle; kullanıcı iptalinde kapat */
|
|
@@ -9597,6 +9877,20 @@ ipcMain.handle('finance:snapshot', async () => {
|
|
|
9597
9877
|
|
|
9598
9878
|
ipcMain.handle('finance:mode', async (_e, payload) => {
|
|
9599
9879
|
financeState.mode = !!(payload && payload.on);
|
|
9880
|
+
/* BEAST FINANCE = MT5 KAPISI:
|
|
9881
|
+
- mod AÇILINCA köprü + MT5 terminali OTOMATİK başlar (watchdog dahil) —
|
|
9882
|
+
panel açılır açılmaz bağlantı kurulur, snapshot'ı beklemez.
|
|
9883
|
+
- mod KAPANINCA otomatik başlatma İZNİ kapanır: köprü/watchdog çalışmaya
|
|
9884
|
+
devam eder (açık MT5'te koruma sürer) ama MT5 kapalıysa Beast onu ARTIK
|
|
9885
|
+
açmaz/yeniden başlatmaz. Açık terminal KAPATILMAZ (manuel işlemlere ve
|
|
9886
|
+
açık pozisyonlara dokunulmaz); finance tekrar açılınca MT5 gerekirse
|
|
9887
|
+
yeniden otomatik başlar. */
|
|
9888
|
+
if (financeState.mode) {
|
|
9889
|
+
try { financeEnsureBridge(); } catch {}
|
|
9890
|
+
try { await mt5bridge.call('policy', { launch: true }, 4000); } catch {}
|
|
9891
|
+
} else {
|
|
9892
|
+
try { await mt5bridge.call('policy', { launch: false }, 4000); } catch {}
|
|
9893
|
+
}
|
|
9600
9894
|
const sid = String((payload && payload.sessionId) || '');
|
|
9601
9895
|
let needNew = false;
|
|
9602
9896
|
let resumeSid = '';
|
|
@@ -9712,11 +10006,12 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
|
|
|
9712
10006
|
if (p.analysisTeam !== undefined) f.analysisTeam = finRolesValid(p.analysisTeam);
|
|
9713
10007
|
if (p.analysisAuto !== undefined) f.analysisAuto = !!p.analysisAuto;
|
|
9714
10008
|
if (p.analysisCount !== undefined) f.analysisCount = Math.max(0, Math.min(FIN_ROLES_AUTO.length, Math.round(Number(p.analysisCount) || 0)));
|
|
9715
|
-
/* ROL → SKILL eşleştirmesi güncellemesi (modal): rol başına TEK skill
|
|
10009
|
+
/* ROL → SKILL eşleştirmesi güncellemesi (modal): rol başına TEK skill +
|
|
10010
|
+
ana trader playbook'u (trader) */
|
|
9716
10011
|
if (p.roleSkills !== undefined && p.roleSkills && typeof p.roleSkills === 'object') {
|
|
9717
|
-
for (const
|
|
9718
|
-
if (p.roleSkills[
|
|
9719
|
-
f.roleSkills[
|
|
10012
|
+
for (const id of [...FIN_ROLES.map((d) => d.id), 'trader']) {
|
|
10013
|
+
if (p.roleSkills[id] === undefined) continue;
|
|
10014
|
+
f.roleSkills[id] = (Array.isArray(p.roleSkills[id]) ? p.roleSkills[id] : [])
|
|
9720
10015
|
.map((s) => String(s || '').trim()).filter(Boolean).slice(0, 1);
|
|
9721
10016
|
}
|
|
9722
10017
|
/* koşan rol ajanlarını da tazele — seçim sonraki turdan itibaren sistem
|
|
@@ -9730,6 +10025,15 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
|
|
|
9730
10025
|
}
|
|
9731
10026
|
}
|
|
9732
10027
|
}
|
|
10028
|
+
/* KODLA DİSİPLİN + SHADOW + GÜNLÜK RUTİN ayarları */
|
|
10029
|
+
if (p.maxTradesPerDay !== undefined) f.maxTradesPerDay = Math.max(0, Math.min(50, Math.round(Number(p.maxTradesPerDay) || 0)));
|
|
10030
|
+
if (p.lossStreakLimit !== undefined) f.lossStreakLimit = Math.max(0, Math.min(10, Math.round(Number(p.lossStreakLimit) || 0)));
|
|
10031
|
+
if (p.lossStreakPauseMin !== undefined) f.lossStreakPauseMin = Math.max(0, Math.min(1440, Math.round(Number(p.lossStreakPauseMin) || 0)));
|
|
10032
|
+
if (p.reentryCooldownMin !== undefined) f.reentryCooldownMin = Math.max(0, Math.min(1440, Math.round(Number(p.reentryCooldownMin) || 0)));
|
|
10033
|
+
if (p.maxPerCurrency !== undefined) f.maxPerCurrency = Math.max(0, Math.min(20, Math.round(Number(p.maxPerCurrency) || 0)));
|
|
10034
|
+
if (p.shadowMode !== undefined) f.shadowMode = !!p.shadowMode;
|
|
10035
|
+
if (p.planTime !== undefined) f.planTime = /^([01]?\d|2[0-3]):([0-5]\d)$/.test(String(p.planTime || '').trim()) ? String(p.planTime).trim() : '';
|
|
10036
|
+
if (p.reviewTime !== undefined) f.reviewTime = /^([01]?\d|2[0-3]):([0-5]\d)$/.test(String(p.reviewTime || '').trim()) ? String(p.reviewTime).trim() : '';
|
|
9733
10037
|
if (p.pythonPath !== undefined) f.pythonPath = String(p.pythonPath || '').trim();
|
|
9734
10038
|
if (p.terminalPath !== undefined) f.terminalPath = String(p.terminalPath || '').trim();
|
|
9735
10039
|
if (p.traderSel !== undefined) {
|
|
@@ -9743,9 +10047,23 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
|
|
|
9743
10047
|
if (financeState.traderSid && engine) {
|
|
9744
10048
|
try {
|
|
9745
10049
|
const ts = engine.cache.get(financeState.traderSid);
|
|
9746
|
-
if (ts) finApplyTraderFields(ts);
|
|
10050
|
+
if (ts) finApplyTraderFields(ts, undefined, undefined, true);
|
|
10051
|
+
} catch {}
|
|
10052
|
+
}
|
|
10053
|
+
/* finance SOHBET oturumları: strateji/sembol/limit/rol-skill değişince ANINDA
|
|
10054
|
+
tazelenir — "asıl bot" (Beast Finance sohbeti) de aynı ajan talimatını görür */
|
|
10055
|
+
if (engine) {
|
|
10056
|
+
try {
|
|
10057
|
+
for (const [ssid, ss] of engine.cache) {
|
|
10058
|
+
if (!ss || !ss.finance || ss.botId || ss.bgJob) continue;
|
|
10059
|
+
if (financeState.agents.has(String(ssid))) continue; /* ajanlar tur başında tazelenir */
|
|
10060
|
+
if (ssid === String(financeState.traderSid || '')) continue;
|
|
10061
|
+
finApplyChatFields(ss);
|
|
10062
|
+
}
|
|
9747
10063
|
} catch {}
|
|
9748
10064
|
}
|
|
10065
|
+
/* günlük plan/review saatleri değiştiyse cron job'larını eşitle */
|
|
10066
|
+
if (p.planTime !== undefined || p.reviewTime !== undefined) finSyncScheduleJobs();
|
|
9749
10067
|
return { ok: true, cfg: f };
|
|
9750
10068
|
});
|
|
9751
10069
|
|
|
@@ -9755,6 +10073,7 @@ async function financeTraderStart() {
|
|
|
9755
10073
|
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
9756
10074
|
const f = finCfg();
|
|
9757
10075
|
if (!engine.publicState().hasModel && !f.traderSel) return { ok: false, error: 'model yok — Ayarlar → Provider' };
|
|
10076
|
+
try { finSyncScheduleJobs(); } catch {} /* plan/review saatleri trader açılışında garantiye alınır */
|
|
9758
10077
|
/* ANA trader: varsa aynen sürdür, yoksa yeni sürekli ajan aç */
|
|
9759
10078
|
let mainSid = '';
|
|
9760
10079
|
let mainAgent = null;
|
package/src/renderer/index.html
CHANGED
|
@@ -190,6 +190,22 @@
|
|
|
190
190
|
<span>Ajan talimatı <i class="fin-hint">— 1-2 cümle; trader + tüm paralel ajanlara iletilir</i></span>
|
|
191
191
|
<textarea id="finStrategy" rows="2" maxlength="2000" placeholder="örn. Her türde scalp yap — M1/M5 hızlı gir-çık, günlük kâr hedefine ulaşınca dur" title="Buraya yazdığın talimat trade ajanına ve koşan tüm paralel ajanlara (ekip + sembol işçileri) sistem promptunda öncelikli strateji notu olarak gider; kaydetmek için alandan çık"></textarea>
|
|
192
192
|
</div>
|
|
193
|
+
<div class="fin-field">
|
|
194
|
+
<span>Disiplin <i class="fin-hint">— kodla zorlanır; 0 = kural kapalı</i></span>
|
|
195
|
+
<div class="fin-grid2">
|
|
196
|
+
<div class="fin-field"><span>Günlük max işlem</span><input id="finMaxTradesDay" type="number" min="0" max="50" step="1" title="Bir günde en fazla kaç işlem açılabilir (0 = sınırsız)" /></div>
|
|
197
|
+
<div class="fin-field"><span>Kayıp serisi limiti</span><input id="finLossStreak" type="number" min="0" max="10" step="1" title="Üst üste bu kadar kayıptan sonra yeni işlem kilidi (0 = kapalı)" /></div>
|
|
198
|
+
<div class="fin-field"><span>Seri molası (dk)</span><input id="finLossStreakPause" type="number" min="0" max="1440" step="5" title="Kayıp serisi limitine ulaşınca kaç dakika yeni işlem açılmaz" /></div>
|
|
199
|
+
<div class="fin-field"><span>Re-entry bekleme (dk)</span><input id="finReentry" type="number" min="0" max="1440" step="5" title="Aynı sembolda bir işlem kapandıktan sonra yeni giriş için beklenecek süre (0 = kapalı)" /></div>
|
|
200
|
+
<div class="fin-field"><span>Kur başına max</span><input id="finMaxPerCurrency" type="number" min="0" max="20" step="1" title="Yönlü kur maruziyeti (korelasyon) limiti — örn. 3: aynı para biriminde en fazla ±3 net pozisyon (0 = kapalı)" /></div>
|
|
201
|
+
<div class="fin-field"><span>Plan / Review (saat)</span><div class="fin-time-row"><input id="finPlanTime" type="time" title="Hafta içi günlük plan saati (boş = kapalı)" /><input id="finReviewTime" type="time" title="Hafta içi günlük review saati (boş = kapalı)" /></div></div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
<div class="fin-field fin-check">
|
|
205
|
+
<label title="SHADOW MOD: emir gönderilmez — kararlar teziyle günlüğe yazılır, gerçek işlem açılmaz. Ajanı para riske atmadan canlı test etmek için.">
|
|
206
|
+
<input id="finShadow" type="checkbox" /> Shadow mod — emir açmadan karar kaydı (canlı test)
|
|
207
|
+
</label>
|
|
208
|
+
</div>
|
|
193
209
|
<div class="fin-row fin-btn-row">
|
|
194
210
|
<button id="finTraderBtn" class="fin-start">▶︎ Ajanı Başlat</button>
|
|
195
211
|
</div>
|
package/src/renderer/renderer.js
CHANGED
|
@@ -186,6 +186,14 @@ const els = {
|
|
|
186
186
|
finInterval: $('#finInterval'),
|
|
187
187
|
finMaxLot: $('#finMaxLot'),
|
|
188
188
|
finStrategy: $('#finStrategy'),
|
|
189
|
+
finMaxTradesDay: $('#finMaxTradesDay'),
|
|
190
|
+
finLossStreak: $('#finLossStreak'),
|
|
191
|
+
finLossStreakPause: $('#finLossStreakPause'),
|
|
192
|
+
finReentry: $('#finReentry'),
|
|
193
|
+
finMaxPerCurrency: $('#finMaxPerCurrency'),
|
|
194
|
+
finPlanTime: $('#finPlanTime'),
|
|
195
|
+
finReviewTime: $('#finReviewTime'),
|
|
196
|
+
finShadow: $('#finShadow'),
|
|
189
197
|
finSymBtn: $('#finSymBtn'),
|
|
190
198
|
finRoles: $('#finRoles'),
|
|
191
199
|
finWatchClear: $('#finWatchClear'),
|
|
@@ -7043,7 +7051,9 @@ async function init() {
|
|
|
7043
7051
|
els.input.focus();
|
|
7044
7052
|
});
|
|
7045
7053
|
|
|
7046
|
-
/* in-app confirm modal düğmeleri + Enter/Esc
|
|
7054
|
+
/* in-app confirm modal düğmeleri + Enter/Esc kısayolları.
|
|
7055
|
+
KRİTİK: Enter'da preventDefault ŞART — closeConfirm odağı sohbet input'una
|
|
7056
|
+
geri verdiği için varsayılan Enter aksiyonu input'a satır atlatıyordu. */
|
|
7047
7057
|
{
|
|
7048
7058
|
const ok = $('#confirmOk');
|
|
7049
7059
|
const cancel = $('#confirmCancel');
|
|
@@ -7052,9 +7062,16 @@ async function init() {
|
|
|
7052
7062
|
ok.addEventListener('click', () => closeConfirm(true));
|
|
7053
7063
|
cancel.addEventListener('click', () => closeConfirm(false));
|
|
7054
7064
|
ov.addEventListener('keydown', (e) => {
|
|
7055
|
-
if (e.key === 'Escape') { e.stopPropagation(); closeConfirm(false); }
|
|
7056
|
-
else if (e.key === 'Enter') { e.stopPropagation(); closeConfirm(true); }
|
|
7065
|
+
if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); closeConfirm(false); }
|
|
7066
|
+
else if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); closeConfirm(true); }
|
|
7057
7067
|
});
|
|
7068
|
+
/* modal AÇIKKEN odak nerede olursa olsun Enter/Esc modal'a gider —
|
|
7069
|
+
sohbet input'u Enter ile satır atlamaz (capture: input'a ulaşmadan kes) */
|
|
7070
|
+
document.addEventListener('keydown', (e) => {
|
|
7071
|
+
if (ov.hidden) return;
|
|
7072
|
+
if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); closeConfirm(true); }
|
|
7073
|
+
else if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); closeConfirm(false); }
|
|
7074
|
+
}, true);
|
|
7058
7075
|
}
|
|
7059
7076
|
}
|
|
7060
7077
|
|
|
@@ -9326,6 +9343,14 @@ function finTraderInputsSet(cfg) {
|
|
|
9326
9343
|
if (els.finInterval && ae !== els.finInterval) els.finInterval.value = cfg.intervalSec || 120;
|
|
9327
9344
|
if (els.finMaxLot && ae !== els.finMaxLot) els.finMaxLot.value = cfg.maxLot || 0.1;
|
|
9328
9345
|
if (els.finStrategy && ae !== els.finStrategy && !finStrategyDirty) els.finStrategy.value = cfg.strategy || '';
|
|
9346
|
+
if (els.finMaxTradesDay && ae !== els.finMaxTradesDay) els.finMaxTradesDay.value = Number(cfg.maxTradesPerDay) || 0;
|
|
9347
|
+
if (els.finLossStreak && ae !== els.finLossStreak) els.finLossStreak.value = Number(cfg.lossStreakLimit) || 0;
|
|
9348
|
+
if (els.finLossStreakPause && ae !== els.finLossStreakPause) els.finLossStreakPause.value = Number(cfg.lossStreakPauseMin) || 0;
|
|
9349
|
+
if (els.finReentry && ae !== els.finReentry) els.finReentry.value = Number(cfg.reentryCooldownMin) || 0;
|
|
9350
|
+
if (els.finMaxPerCurrency && ae !== els.finMaxPerCurrency) els.finMaxPerCurrency.value = Number(cfg.maxPerCurrency) || 0;
|
|
9351
|
+
if (els.finPlanTime && ae !== els.finPlanTime) els.finPlanTime.value = String(cfg.planTime || '');
|
|
9352
|
+
if (els.finReviewTime && ae !== els.finReviewTime) els.finReviewTime.value = String(cfg.reviewTime || '');
|
|
9353
|
+
if (els.finShadow && ae !== els.finShadow) els.finShadow.checked = !!cfg.shadowMode;
|
|
9329
9354
|
finSymBtnUpdate();
|
|
9330
9355
|
if (els.finTraderModel && ae !== els.finTraderModel && cfg.traderSel) els.finTraderModel.value = cfg.traderSel;
|
|
9331
9356
|
}
|
|
@@ -9908,6 +9933,31 @@ finAutoNum(els.finRiskPct, 'riskPerTradePct', 0, 20, 0.1);
|
|
|
9908
9933
|
finAutoNum(els.finMaxPerSymbol, 'maxPerSymbol', 0, 20, 1);
|
|
9909
9934
|
finAutoNum(els.finMaxSameSide, 'maxSameSide', 0, 20, 1);
|
|
9910
9935
|
finAutoNum(els.finMinMarginLevel, 'minMarginLevel', 0, 1000, 10);
|
|
9936
|
+
/* KODLA DİSİPLİN + shadow + günlük plan/review — anında kaydedilir */
|
|
9937
|
+
finAutoNum(els.finMaxTradesDay, 'maxTradesPerDay', 0, 50, 1);
|
|
9938
|
+
finAutoNum(els.finLossStreak, 'lossStreakLimit', 0, 10, 1);
|
|
9939
|
+
finAutoNum(els.finLossStreakPause, 'lossStreakPauseMin', 0, 1440, 5);
|
|
9940
|
+
finAutoNum(els.finReentry, 'reentryCooldownMin', 0, 1440, 5);
|
|
9941
|
+
finAutoNum(els.finMaxPerCurrency, 'maxPerCurrency', 0, 20, 1);
|
|
9942
|
+
const finTimeSave = (el, key) => {
|
|
9943
|
+
if (!el) return;
|
|
9944
|
+
el.addEventListener('change', () => {
|
|
9945
|
+
finSaveCfg({ [key]: String(el.value || '').slice(0, 5) });
|
|
9946
|
+
toast(key === 'planTime'
|
|
9947
|
+
? (el.value ? 'Günlük plan saati: ' + el.value + ' (hafta içi)' : 'Günlük plan kapatıldı')
|
|
9948
|
+
: (el.value ? 'Günlük review saati: ' + el.value + ' (hafta içi)' : 'Günlük review kapatıldı'));
|
|
9949
|
+
});
|
|
9950
|
+
};
|
|
9951
|
+
finTimeSave(els.finPlanTime, 'planTime');
|
|
9952
|
+
finTimeSave(els.finReviewTime, 'reviewTime');
|
|
9953
|
+
if (els.finShadow) {
|
|
9954
|
+
els.finShadow.addEventListener('change', () => {
|
|
9955
|
+
finSaveCfg({ shadowMode: els.finShadow.checked });
|
|
9956
|
+
toast(els.finShadow.checked
|
|
9957
|
+
? 'SHADOW MOD açık — emir gönderilmez, kararlar günlüğe yazılır'
|
|
9958
|
+
: 'Shadow mod kapalı — gerçek emirler aktif');
|
|
9959
|
+
});
|
|
9960
|
+
}
|
|
9911
9961
|
if (els.finDailyLossAction) {
|
|
9912
9962
|
els.finDailyLossAction.addEventListener('change', () => {
|
|
9913
9963
|
finSaveCfg({ dailyLossAction: els.finDailyLossAction.value });
|
|
@@ -10156,7 +10206,10 @@ function finRenderSkills() {
|
|
|
10156
10206
|
if (!box) return;
|
|
10157
10207
|
const cfg = finCfgCache || {};
|
|
10158
10208
|
const map = cfg.roleSkills && typeof cfg.roleSkills === 'object' ? cfg.roleSkills : {};
|
|
10159
|
-
|
|
10209
|
+
/* ANA KARAR VERİCİ (trader) da bir satır: playbook skill'i zorunlu tek seçim */
|
|
10210
|
+
const roles = [
|
|
10211
|
+
{ id: 'trader', label: 'TRADER (Ana Karar Verici)', desc: 'playbook: hangi setup\'lar serbest, giriş/çıkış/iptal kuralları, seans ve risk disiplini — asıl karar vericinin zorunlu skill\'i' },
|
|
10212
|
+
].concat(Array.isArray(finRolesCatalog) ? finRolesCatalog : []);
|
|
10160
10213
|
box.innerHTML = '';
|
|
10161
10214
|
if (!roles.length) {
|
|
10162
10215
|
box.innerHTML = '<div class="fin-empty">Rol kataloğu yok</div>';
|
package/src/renderer/style.css
CHANGED
|
@@ -4873,6 +4873,8 @@ body.finance-mode #composerWrap { margin-right: var(--finW, 400px); }
|
|
|
4873
4873
|
border-radius: 6px;
|
|
4874
4874
|
}
|
|
4875
4875
|
.fin-field input:focus, .fin-field select:focus, .fin-field textarea:focus { border-color: var(--accent); outline: none; }
|
|
4876
|
+
.fin-time-row { display: flex; gap: 6px; }
|
|
4877
|
+
.fin-time-row input { flex: 1; min-width: 0; }
|
|
4876
4878
|
/* Ajan talimatı: çok satırlı not alanı (dikey büyütülebilir) */
|
|
4877
4879
|
.fin-field textarea {
|
|
4878
4880
|
resize: vertical;
|