beast-agent 2.38.2 → 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 +308 -20
- 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
|
@@ -6904,6 +6904,12 @@ function cronMirrorTargets(cronSid) {
|
|
|
6904
6904
|
}
|
|
6905
6905
|
|
|
6906
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
|
+
}
|
|
6907
6913
|
try {
|
|
6908
6914
|
const sid = reuseOrLatestSession(job.sessionId);
|
|
6909
6915
|
if (sid !== String(job.sessionId || '')) {
|
|
@@ -8431,6 +8437,7 @@ function studioWatchStop() {
|
|
|
8431
8437
|
oturumu, tur tur piyasa tarayıp (farklı API/model seçilebilir) işlem kovalar. */
|
|
8432
8438
|
const mt5bridge = require('./mt5bridge');
|
|
8433
8439
|
const financetools = require('./agent/financetools');
|
|
8440
|
+
const finrisk = require('./agent/finrisk');
|
|
8434
8441
|
const customtools = require('./agent/customtools');
|
|
8435
8442
|
const finwatch = require('./agent/finwatch');
|
|
8436
8443
|
const finstats = require('./agent/finstats');
|
|
@@ -8445,6 +8452,9 @@ const financeState = {
|
|
|
8445
8452
|
installTried: false,
|
|
8446
8453
|
agents: new Map(), /* sid -> { symbols, main, round, timer } — AYNI ANDA koşan finance ajanları */
|
|
8447
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,
|
|
8448
8458
|
watchTimer: null,
|
|
8449
8459
|
watchBusy: false,
|
|
8450
8460
|
watchTickAt: 0,
|
|
@@ -8495,9 +8505,25 @@ function finCfg() {
|
|
|
8495
8505
|
const cur = Array.isArray(f.roleSkills[d.id]) ? f.roleSkills[d.id] : [];
|
|
8496
8506
|
f.roleSkills[d.id] = cur.map((s) => String(s || '').trim()).filter(Boolean).slice(0, 1);
|
|
8497
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
|
+
}
|
|
8498
8513
|
if (!Number(f.intervalSec)) f.intervalSec = 120;
|
|
8499
8514
|
if (!Number(f.maxLot)) f.maxLot = 0.1;
|
|
8500
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 = '';
|
|
8501
8527
|
/* RİSK OTOMASYONU (watchdog): +R'da BE, trailing, kısmi TP — main süreç
|
|
8502
8528
|
saniyelik döngüyle uygular; ajan turunu BEKLEMEZ. 0 = ilgili kural kapalı. */
|
|
8503
8529
|
if (typeof f.watchdog !== 'boolean') f.watchdog = true;
|
|
@@ -8605,6 +8631,121 @@ function finJournalTail(n) {
|
|
|
8605
8631
|
}
|
|
8606
8632
|
}
|
|
8607
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
|
+
|
|
8608
8749
|
function finAgentInfo(sid) {
|
|
8609
8750
|
try {
|
|
8610
8751
|
const a = financeState.agents.get(String(sid || ''));
|
|
@@ -8867,7 +9008,14 @@ async function finRecordClose(ticket, st) {
|
|
|
8867
9008
|
}
|
|
8868
9009
|
} catch {}
|
|
8869
9010
|
const rounded = net == null ? null : Math.round(net * 100) / 100;
|
|
8870
|
-
|
|
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 });
|
|
8871
9019
|
const pl = rounded == null ? '' : ` · K/Z ${rounded >= 0 ? '+' : ''}${rounded.toFixed(2)}`;
|
|
8872
9020
|
const line = `🏁 Pozisyon kapandı: ${symbol || '?'} #${ticket}${pl}`;
|
|
8873
9021
|
financeLog('[watchdog] ' + line);
|
|
@@ -8945,6 +9093,19 @@ async function finWatchTick() {
|
|
|
8945
9093
|
for (const p of positions) {
|
|
8946
9094
|
const ticket = String(p.ticket);
|
|
8947
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
|
+
}
|
|
8948
9109
|
let st = financeState.watch.get(ticket);
|
|
8949
9110
|
if (!st) {
|
|
8950
9111
|
st = {
|
|
@@ -9026,6 +9187,7 @@ async function finWatchTick() {
|
|
|
9026
9187
|
finStatsRefresh(true).catch(() => {});
|
|
9027
9188
|
}
|
|
9028
9189
|
finMaybeWeeklyReport();
|
|
9190
|
+
finExcFlush(false); /* MAE/MFE deposu 15 sn'de bir diske */
|
|
9029
9191
|
} catch {
|
|
9030
9192
|
} finally {
|
|
9031
9193
|
financeState.watchBusy = false;
|
|
@@ -9076,8 +9238,19 @@ async function finWeeklyReport(manual) {
|
|
|
9076
9238
|
const positions = posR && posR.ok ? ((posR.data && posR.data.positions) || []) : [];
|
|
9077
9239
|
const fromTs = now - 7 * 86400000;
|
|
9078
9240
|
const equity = (financeState.equity || []).filter((p) => Number(p.at) >= fromTs - 86400000);
|
|
9079
|
-
const journal = finJournalTail(
|
|
9241
|
+
const journal = finJournalTail(400);
|
|
9080
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;
|
|
9081
9254
|
let review = '';
|
|
9082
9255
|
if (engine) {
|
|
9083
9256
|
try {
|
|
@@ -9090,7 +9263,7 @@ async function finWeeklyReport(manual) {
|
|
|
9090
9263
|
review = String(res || '').trim().slice(0, 4000);
|
|
9091
9264
|
} catch {}
|
|
9092
9265
|
}
|
|
9093
|
-
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 });
|
|
9094
9267
|
const day = finstats.dayKey(now);
|
|
9095
9268
|
const file = path.join(financeDir(), 'reports', 'weekly-' + day + '.md');
|
|
9096
9269
|
try {
|
|
@@ -9125,6 +9298,7 @@ function finMaybeWeeklyReport() {
|
|
|
9125
9298
|
|
|
9126
9299
|
try { finAlertsLoad(); } catch {}
|
|
9127
9300
|
try { finWatchLoad(); } catch {}
|
|
9301
|
+
try { finExcLoad(); } catch {}
|
|
9128
9302
|
try { finStatsLoad(); } catch {}
|
|
9129
9303
|
try { finEquityLoad(); } catch {}
|
|
9130
9304
|
|
|
@@ -9132,6 +9306,7 @@ try { finEquityLoad(); } catch {}
|
|
|
9132
9306
|
try {
|
|
9133
9307
|
financetools.setConfig(() => finCfg());
|
|
9134
9308
|
financetools.setAlerts(finAlertApi());
|
|
9309
|
+
financetools.setDiscipline((side, symbol, positions) => finDisciplineError(side, symbol, positions));
|
|
9135
9310
|
financetools.setNotify((entry) => {
|
|
9136
9311
|
const k = String((entry && entry.kind) || '');
|
|
9137
9312
|
const d = entry && entry.data ? entry.data : {};
|
|
@@ -9142,15 +9317,23 @@ try {
|
|
|
9142
9317
|
finJournal({ kind: 'note', sid, agent: who, symbol: d.symbol, note: d.note, ticket: d.ticket || 0, side: d.side || '' });
|
|
9143
9318
|
return;
|
|
9144
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
|
+
}
|
|
9145
9328
|
if (k === 'trade') {
|
|
9146
|
-
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 || '' });
|
|
9147
9330
|
} else if (k === 'close') {
|
|
9148
9331
|
finJournal({ kind: 'close-req', sid, agent: who, ticket: d.ticket, volume: d.volume });
|
|
9149
9332
|
return; /* kesin kapanış K/Z'si watchdog tarafından yazılır (çift bildirim yok) */
|
|
9150
9333
|
} else if (k === 'modify') {
|
|
9151
9334
|
finJournal({ kind: 'modify', sid, agent: who, ticket: d.ticket, sl: d.sl, tp: d.tp });
|
|
9152
9335
|
} else if (k === 'pending') {
|
|
9153
|
-
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 || '' });
|
|
9154
9337
|
} else if (k === 'cancel') {
|
|
9155
9338
|
finJournal({ kind: 'cancel', sid, agent: who, ticket: d.ticket });
|
|
9156
9339
|
}
|
|
@@ -9196,6 +9379,10 @@ try {
|
|
|
9196
9379
|
} catch {}
|
|
9197
9380
|
|
|
9198
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();
|
|
9199
9386
|
const f = finCfg();
|
|
9200
9387
|
const candidates = [String(f.pythonPath || '').trim(), 'python', 'py -3'].filter(Boolean);
|
|
9201
9388
|
/* Beast gömülü Python runtime kuruluysa en başa ekle (makinede Python olmasa da köprü çalışır) */
|
|
@@ -9343,19 +9530,23 @@ function finAgentRegisterBg(s, symbols) {
|
|
|
9343
9530
|
} catch {}
|
|
9344
9531
|
}
|
|
9345
9532
|
|
|
9346
|
-
function finApplyTraderFields(s, symbolsOverride, role) {
|
|
9533
|
+
function finApplyTraderFields(s, symbolsOverride, role, isMain) {
|
|
9347
9534
|
const f = finCfg();
|
|
9348
9535
|
const r = String(role || s.financeRole || '');
|
|
9349
9536
|
s.finance = true;
|
|
9350
9537
|
s.financeTrader = true;
|
|
9351
9538
|
s.financeRole = r;
|
|
9539
|
+
/* ANA TRADER: playbook skill'i roleSkills.trader'dan gelir */
|
|
9540
|
+
if (typeof isMain === 'boolean') s.financePlaybook = isMain;
|
|
9352
9541
|
/* rol ajanları ASLA işlem açmaz — trade ajanı ve işçileri DAIMA açabilir */
|
|
9353
9542
|
s.financeAuto = !r;
|
|
9354
9543
|
s.financeSymbols = Array.isArray(symbolsOverride) && symbolsOverride.length ? symbolsOverride : f.symbols;
|
|
9355
9544
|
s.financeStrategy = String(f.strategy || '');
|
|
9545
|
+
s.financeShadow = !!f.shadowMode; /* shadow modda işlem araçları emir göndermez */
|
|
9356
9546
|
s.financeLimits = { maxLot: f.maxLot, maxPositions: f.maxPositions };
|
|
9357
|
-
/*
|
|
9358
|
-
|
|
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]) || [];
|
|
9359
9550
|
engine.cache.set(String(s.id), s);
|
|
9360
9551
|
}
|
|
9361
9552
|
|
|
@@ -9384,7 +9575,7 @@ function finAgentCreate(symbols, isMain, role) {
|
|
|
9384
9575
|
timer: null,
|
|
9385
9576
|
};
|
|
9386
9577
|
financeState.agents.set(String(s.id), record);
|
|
9387
|
-
finApplyTraderFields(s, record.symbols, record.role);
|
|
9578
|
+
finApplyTraderFields(s, record.symbols, record.role, !!isMain);
|
|
9388
9579
|
finAgentRegisterBg(s, record.symbols);
|
|
9389
9580
|
return { s, agent: record };
|
|
9390
9581
|
}
|
|
@@ -9423,6 +9614,9 @@ function finTraderBrief(agent) {
|
|
|
9423
9614
|
const syms = agent && agent.symbols && agent.symbols.length ? agent.symbols.join(', ') : (f.symbols || []).join(', ');
|
|
9424
9615
|
const roleDef = finRoleDef(agent && agent.role);
|
|
9425
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
|
+
: '';
|
|
9426
9620
|
return [
|
|
9427
9621
|
`Beast Finance ${who} başlatıldı — ilk tur: strateji çerçeveni kur ve piyasa taramasını yap.`,
|
|
9428
9622
|
`Odak semboller: ${syms || '(boş — mt5_status ile terminale bak, mantıklı semboller seç)'}`,
|
|
@@ -9433,14 +9627,66 @@ function finTraderBrief(agent) {
|
|
|
9433
9627
|
roleDef
|
|
9434
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.'
|
|
9435
9629
|
: f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : 'Strateji notu yok: trend + destek/direnç + momentum ile temel okuma yap.',
|
|
9436
|
-
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.' : '',
|
|
9437
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.',
|
|
9438
9634
|
'Önemli kararların gerekçesini mt5_note ile günlüğe yaz (haftalık performans raporu bu notları kullanır).',
|
|
9439
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.',
|
|
9440
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()}` : '',
|
|
9441
9638
|
].filter(Boolean).join('\n');
|
|
9442
9639
|
}
|
|
9443
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
|
+
|
|
9444
9690
|
/* Her tur öncesi CHAT AJANINDAN plan iste: aynı motorun odaklı alt-ajanı
|
|
9445
9691
|
(_subagent) hesap/pozisyon/strateji bağlamıyla kısa işlem planı üretir.
|
|
9446
9692
|
Plan, tur mesajına [ANA AJAN PLANI] bloğu olarak eklenir. */
|
|
@@ -9505,13 +9751,21 @@ function finAgentRound(sid) {
|
|
|
9505
9751
|
if (agent.main) financeState.traderRounds = agent.round;
|
|
9506
9752
|
const f = finCfg();
|
|
9507
9753
|
const roleDef = finRoleDef(agent.role);
|
|
9754
|
+
let shadow = false;
|
|
9508
9755
|
try {
|
|
9509
9756
|
const s = engine.cache.get(String(sid));
|
|
9510
|
-
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
|
+
}
|
|
9511
9763
|
} catch {}
|
|
9512
9764
|
const auto = roleDef
|
|
9513
9765
|
? `UZMANLIK: ${roleDef.desc} — İŞLEM AÇMA, yalnız analiz + net öneri.`
|
|
9514
|
-
:
|
|
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.';
|
|
9515
9769
|
const focus = agent.symbols.length ? `Odak: ${agent.symbols.join(', ')}. ` : '';
|
|
9516
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.`;
|
|
9517
9771
|
const launch = (planBlock) => {
|
|
@@ -9526,15 +9780,22 @@ function finAgentRound(sid) {
|
|
|
9526
9780
|
agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, 30000);
|
|
9527
9781
|
}
|
|
9528
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
|
+
}
|
|
9529
9790
|
if (f.consultChat === false || roleDef) {
|
|
9530
9791
|
/* danışma kapalı ya da rol ajanı (plan trader'a yöneliktir): tur doğrudan başlar */
|
|
9531
|
-
launch(
|
|
9792
|
+
launch(teamPrefix);
|
|
9532
9793
|
return;
|
|
9533
9794
|
}
|
|
9534
9795
|
if (agent.main) finPush('trader', { state: 'consult', round: agent.round });
|
|
9535
9796
|
finConsultPlan(f, agent, String(sid))
|
|
9536
|
-
.then((plan) => launch(plan ? `[ANA AJAN PLANI — bu turun varsayılan stratejisi; strateji notuyla çelişirse not önceliklidir]\n${plan}\n\n` : ''))
|
|
9537
|
-
.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));
|
|
9538
9799
|
}
|
|
9539
9800
|
|
|
9540
9801
|
/* Tur/durum sonları: sürekli ajan döngüsünü besle; kullanıcı iptalinde kapat */
|
|
@@ -9616,6 +9877,20 @@ ipcMain.handle('finance:snapshot', async () => {
|
|
|
9616
9877
|
|
|
9617
9878
|
ipcMain.handle('finance:mode', async (_e, payload) => {
|
|
9618
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
|
+
}
|
|
9619
9894
|
const sid = String((payload && payload.sessionId) || '');
|
|
9620
9895
|
let needNew = false;
|
|
9621
9896
|
let resumeSid = '';
|
|
@@ -9731,11 +10006,12 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
|
|
|
9731
10006
|
if (p.analysisTeam !== undefined) f.analysisTeam = finRolesValid(p.analysisTeam);
|
|
9732
10007
|
if (p.analysisAuto !== undefined) f.analysisAuto = !!p.analysisAuto;
|
|
9733
10008
|
if (p.analysisCount !== undefined) f.analysisCount = Math.max(0, Math.min(FIN_ROLES_AUTO.length, Math.round(Number(p.analysisCount) || 0)));
|
|
9734
|
-
/* 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) */
|
|
9735
10011
|
if (p.roleSkills !== undefined && p.roleSkills && typeof p.roleSkills === 'object') {
|
|
9736
|
-
for (const
|
|
9737
|
-
if (p.roleSkills[
|
|
9738
|
-
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] : [])
|
|
9739
10015
|
.map((s) => String(s || '').trim()).filter(Boolean).slice(0, 1);
|
|
9740
10016
|
}
|
|
9741
10017
|
/* koşan rol ajanlarını da tazele — seçim sonraki turdan itibaren sistem
|
|
@@ -9749,6 +10025,15 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
|
|
|
9749
10025
|
}
|
|
9750
10026
|
}
|
|
9751
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() : '';
|
|
9752
10037
|
if (p.pythonPath !== undefined) f.pythonPath = String(p.pythonPath || '').trim();
|
|
9753
10038
|
if (p.terminalPath !== undefined) f.terminalPath = String(p.terminalPath || '').trim();
|
|
9754
10039
|
if (p.traderSel !== undefined) {
|
|
@@ -9762,7 +10047,7 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
|
|
|
9762
10047
|
if (financeState.traderSid && engine) {
|
|
9763
10048
|
try {
|
|
9764
10049
|
const ts = engine.cache.get(financeState.traderSid);
|
|
9765
|
-
if (ts) finApplyTraderFields(ts);
|
|
10050
|
+
if (ts) finApplyTraderFields(ts, undefined, undefined, true);
|
|
9766
10051
|
} catch {}
|
|
9767
10052
|
}
|
|
9768
10053
|
/* finance SOHBET oturumları: strateji/sembol/limit/rol-skill değişince ANINDA
|
|
@@ -9777,6 +10062,8 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
|
|
|
9777
10062
|
}
|
|
9778
10063
|
} catch {}
|
|
9779
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();
|
|
9780
10067
|
return { ok: true, cfg: f };
|
|
9781
10068
|
});
|
|
9782
10069
|
|
|
@@ -9786,6 +10073,7 @@ async function financeTraderStart() {
|
|
|
9786
10073
|
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
9787
10074
|
const f = finCfg();
|
|
9788
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 */
|
|
9789
10077
|
/* ANA trader: varsa aynen sürdür, yoksa yeni sürekli ajan aç */
|
|
9790
10078
|
let mainSid = '';
|
|
9791
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;
|