beast-agent 2.63.0 → 2.63.2
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/bots.js +19 -1
- package/src/agent/defaulttools/mt5_shot/run.js +2 -2
- package/src/agent/defaulttools/mt5_shot/stitch.js +7 -6
- package/src/agent/defaulttools/mt5_shot/tool.json +2 -2
- package/src/agent/engine.js +64 -12
- package/src/agent/financetools.js +14 -3
- package/src/main.js +301 -7
- package/src/preload.js +1 -0
- package/src/renderer/i18n.js +2 -0
- package/src/renderer/index.html +17 -1
- package/src/renderer/renderer.js +314 -0
- package/src/renderer/style.css +115 -0
package/package.json
CHANGED
package/src/agent/bots.js
CHANGED
|
@@ -130,7 +130,25 @@ function ensureDefaultBots() {
|
|
|
130
130
|
loadRegistry();
|
|
131
131
|
let created = false;
|
|
132
132
|
for (const def of DEFAULT_BOT_SEEDS) {
|
|
133
|
-
|
|
133
|
+
const existing = REG.bots.find((b) => b && b.id === def.id);
|
|
134
|
+
if (existing) {
|
|
135
|
+
/* VARSAYILAN PROMPT GÜNCELLEMESİ (tek seferlik ek): eski sürümde
|
|
136
|
+
oluşmuş Tool botunun promptunda mql5/edit_file/skill vurgusu yoksa
|
|
137
|
+
eksik parçalar EKLENİR — kullanıcı düzenlemesi ezilmez. */
|
|
138
|
+
if (def.id === 'tool') {
|
|
139
|
+
const p = String(existing.prompt || '');
|
|
140
|
+
const add = [];
|
|
141
|
+
if (!/skill\(["']mql5["']\)/.test(p)) add.push('MQL5/gösterge/EA işlerinde ÖNCE skill("mql5") oku ve prosedürüne uy.');
|
|
142
|
+
if (!/edit_file/.test(p)) add.push('Var olan dosyayı edit_file ile GÜNCELLE; yeniyi write_file ile yaz; içerik aramada grep, dosya adı aramada glob kullan.');
|
|
143
|
+
if (!/skill\(["']tool-yazma["']\)/.test(p)) add.push('Tool yazmadan önce skill("tool-yazma") oku ve sözleşmeye birebir uy.');
|
|
144
|
+
if (add.length) {
|
|
145
|
+
existing.prompt = (p ? p + ' ' : '') + add.join(' ');
|
|
146
|
+
created = true;
|
|
147
|
+
logChange(def.id, 'varsayılan prompt ekleri eklendi (mql5/edit_file/skill)');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
134
152
|
if (REG.bots.length >= MAX_BOTS) break;
|
|
135
153
|
const bot = {
|
|
136
154
|
id: def.id,
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
ÇOKLU PERİYOT: timeframes:["M1","M15"] (2-3 adet) verilirse her periyot ayrı
|
|
15
15
|
çekilir ve TEK PNG'de birleştirilir (layout:"h" soldan sağa — varsayılan,
|
|
16
|
-
"v" üstten alta). Panellerin
|
|
16
|
+
"v" üstten alta). Panellerin SOL ÜSTüne "SEMBOL · PERİYOT" etiketi çizilir;
|
|
17
17
|
sonuçta panels[{symbol, timeframe, position, path}] hangi tarafın hangi
|
|
18
18
|
sembol/periyot olduğunu söyler. tf2:"M15" kısayolu da (timeframe + tf2) kabul edilir.
|
|
19
19
|
|
|
@@ -324,7 +324,7 @@ function embedData(src, size) {
|
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
/* ---- ÇOKLU KARE: plan (sembol × periyot) sırayla çekilir ve TEK PNG'de
|
|
327
|
-
birleştirilir — kilit zaten alınmış (ack çakışması yok); her panelin
|
|
327
|
+
birleştirilir — kilit zaten alınmış (ack çakışması yok); her panelin SOL
|
|
328
328
|
ÜSTüne "SEMBOL · PERİYOT" etiketi çizilir; soldan sağa (layout:"v" ile
|
|
329
329
|
üstten alta). plan: [{symbol, timeframe}] — boş alan = aktif grafik. */
|
|
330
330
|
async function runPanels(plan, filesDir, cmdFile, ackFile, beatFile, timeoutSec, diag) {
|
|
@@ -97,8 +97,9 @@ async function compose(panels, opts) {
|
|
|
97
97
|
if (layout === 'h') ctx.fillRect(x - GAP, 0, GAP, H);
|
|
98
98
|
else ctx.fillRect(0, y - GAP, W, GAP);
|
|
99
99
|
}
|
|
100
|
-
/* etiket:
|
|
101
|
-
görsel ajan hangi panelin hangi periyot olduğunu bakışta okur
|
|
100
|
+
/* etiket: SOL ÜST köşede BÜYÜK zaman dilimi (üstünde küçük sembol) —
|
|
101
|
+
görsel ajan hangi panelin hangi periyot olduğunu bakışta okur;
|
|
102
|
+
son mumlar sağda olduğundan etiket oraya konmaz (mumları kapatıyordu) */
|
|
102
103
|
if (p.label) {
|
|
103
104
|
try {
|
|
104
105
|
const parts = String(p.label).split('·').map((s) => s.trim()).filter(Boolean);
|
|
@@ -110,14 +111,14 @@ async function compose(panels, opts) {
|
|
|
110
111
|
const padY = Math.round(fsTf * 0.42);
|
|
111
112
|
const lineGap = Math.round(fsSym * 0.45);
|
|
112
113
|
ctx.textBaseline = 'middle';
|
|
113
|
-
ctx.textAlign = '
|
|
114
|
+
ctx.textAlign = 'left';
|
|
114
115
|
ctx.font = pickFont(fsTf);
|
|
115
116
|
const tw = ctx.measureText(tf).width;
|
|
116
117
|
ctx.font = pickFont(fsSym);
|
|
117
118
|
const sw = sym ? ctx.measureText(sym).width : 0;
|
|
118
119
|
const boxW = Math.ceil(Math.max(tw, sw) + padX * 2);
|
|
119
120
|
const boxH = Math.ceil(padY * 2 + fsTf + (sym ? fsSym + lineGap : 0));
|
|
120
|
-
const bx = x +
|
|
121
|
+
const bx = x + 10;
|
|
121
122
|
const by = y + 10;
|
|
122
123
|
ctx.fillStyle = 'rgba(0,0,0,0.80)';
|
|
123
124
|
ctx.fillRect(bx, by, boxW, boxH);
|
|
@@ -126,11 +127,11 @@ async function compose(panels, opts) {
|
|
|
126
127
|
if (sym) {
|
|
127
128
|
ctx.font = pickFont(fsSym);
|
|
128
129
|
ctx.fillStyle = '#c9d2d9';
|
|
129
|
-
ctx.fillText(sym, bx +
|
|
130
|
+
ctx.fillText(sym, bx + padX, by + padY + fsSym / 2);
|
|
130
131
|
}
|
|
131
132
|
ctx.font = pickFont(fsTf);
|
|
132
133
|
ctx.fillStyle = '#00e676';
|
|
133
|
-
ctx.fillText(tf, bx +
|
|
134
|
+
ctx.fillText(tf, bx + padX, by + padY + (sym ? fsSym + lineGap : 0) + fsTf / 2);
|
|
134
135
|
ctx.textAlign = 'start';
|
|
135
136
|
} catch (e) {}
|
|
136
137
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mt5_shot",
|
|
3
|
-
"defaultsVersion":
|
|
4
|
-
"description": "MT5 grafiğinin GERÇEK ekran görüntüsünü alır ve GÖRSELİ ÇAĞIRAN AJANA GÖSTERİR: MT5\\MQL5\\Files\\beast_cmd.json üzerinden BeastFinance EA'ya 'shot' komutu yazar, EA ChartScreenShot ile PNG üretir; PNG hem tool klasörüne hem <kullanıcı>\\beast_shots altına kopyalanır (dönen path send_file'a verilebilir) hem de data URL olarak sonuca gömülür — engine bunu sonraki turda ajana görsel olarak enjekte eder (embed:false kapatır). ÇOKLU SEMBOL: symbols:[\"GOLD\",\"EURUSD\",\"BTCUSD\"] (2-4 sembol) verirsen her sembolden bir kare çekilip TEK PNG'de birleştirilir (izleme listesinin tamamı TEK görselle gönderilir); timeframes ile birlikte verilirse sembol × periyot ızgarası kurulur (en fazla 6 kare). ÇOKLU ZAMAN DİLİMİ: timeframes:[\"M1\",\"M15\"] verirsen aynı sembolün periyotları SIRAYLA çekilip TEK PNG'de birleştirilir (layout:\"h\" soldan sağa — varsayılan; \"v\" üstten alta; 2-3 periyot) ve her panelin
|
|
3
|
+
"defaultsVersion": 7,
|
|
4
|
+
"description": "MT5 grafiğinin GERÇEK ekran görüntüsünü alır ve GÖRSELİ ÇAĞIRAN AJANA GÖSTERİR: MT5\\MQL5\\Files\\beast_cmd.json üzerinden BeastFinance EA'ya 'shot' komutu yazar, EA ChartScreenShot ile PNG üretir; PNG hem tool klasörüne hem <kullanıcı>\\beast_shots altına kopyalanır (dönen path send_file'a verilebilir) hem de data URL olarak sonuca gömülür — engine bunu sonraki turda ajana görsel olarak enjekte eder (embed:false kapatır). ÇOKLU SEMBOL: symbols:[\"GOLD\",\"EURUSD\",\"BTCUSD\"] (2-4 sembol) verirsen her sembolden bir kare çekilip TEK PNG'de birleştirilir (izleme listesinin tamamı TEK görselle gönderilir); timeframes ile birlikte verilirse sembol × periyot ızgarası kurulur (en fazla 6 kare). ÇOKLU ZAMAN DİLİMİ: timeframes:[\"M1\",\"M15\"] verirsen aynı sembolün periyotları SIRAYLA çekilip TEK PNG'de birleştirilir (layout:\"h\" soldan sağa — varsayılan; \"v\" üstten alta; 2-3 periyot) ve her panelin SOL ÜST köşesine BÜYÜK periyot etiketi (üstünde küçük sembol) çizilir — solda/sağda hangi sembol/periyot olduğu görüntüden bakışta okunur; etiket son mumları örtmez. Aktif grafik istenen sembol/periyot ile aynıysa ondan çekilir (Beast panosu + seviye çizgileri görünür), değilse EA geçici grafik açar (ChartOpen/ChartSetSymbolPeriod + BeastShot.tpl açık tema); çoklu sembolde her sembol için sırayla. Çekim öncesi grafiğe sağ boşluk payı (chart shift %12) uygulanır — SON MUM sağ kenara yapışmaz; aktif grafikte önceki ayar çekimden sonra geri yazılır. Komut id/ts ile yazılır, ack poll edilir ve PNG MQL5\\Files'ta belirene kadar beklenir; hata halinde gövde şu kodu taşır: err = no_terminal|ea_offline|cmd_write_fail|ea_no_ack|ea_error|png_yok|timeout|stitch_fail (+ cmdId, filesDir, diagnostic, ping). Grafik analizi/raporu ve ekip paylaşımı için birincil screenshot aracıdır.",
|
|
5
5
|
"parameters": {
|
|
6
6
|
"type": "object",
|
|
7
7
|
"properties": {
|
package/src/agent/engine.js
CHANGED
|
@@ -2074,7 +2074,7 @@ class Engine {
|
|
|
2074
2074
|
'- Emin olmadığında İŞLEM YOK — "BEKLE: <sebep>" yaz. Sık işlem tek başına iyi işlem değil; ama onaylı fırsatta HIZLI gir, kârı hızlı topla.\n' +
|
|
2075
2075
|
'- 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. Sık işlem için lotu değil, TUR SIKLIĞINI ve kısmi kâr alımını kullan.\n' +
|
|
2076
2076
|
(session && session.financeLearn ? `SEMBOL BAZLI ÖĞRENME HAFIZAN (otomatik istatistik + kendi derslerin — kararlarında kullan):\n${session.financeLearn}\n` : '') +
|
|
2077
|
-
'ÖĞRENME DÖNGÜSÜ (ZORUNLU): her kapanıştan (özellikle stop/TP sonrası) hemen sonra mt5_ogrenme {action:"add", symbol, text, kind} ile o sembolün dersini yaz (ne işe yaradı/yaramadı, hata mı, kural mı); yeni işlem kararından önce mt5_ogrenme {action:"stats", symbol} ya da list ile o sembolün geçmişini oku — aynı hatayı tekrarlama, işleyen deseni kullan.
|
|
2077
|
+
'ÖĞRENME DÖNGÜSÜ (ZORUNLU): her kapanıştan (özellikle stop/TP sonrası) hemen sonra mt5_ogrenme {action:"add", symbol, text, kind} ile o sembolün dersini yaz (ne işe yaradı/yaramadı, hata mı, kural mı); yeni işlem kararından önce mt5_ogrenme {action:"stats", symbol} ya da list ile o sembolün geçmişini oku (kalıcı özet + son dersler) — aynı hatayı tekrarlama, işleyen deseni kullan. Hafıza kendini sadeleştirir: eski dersler otomatik özetlenir ve süresi geçenler unutulur; gerekirse mt5_ogrenme {action:"compact", symbol} ile hemen özetlet, geçersiz dersi remove/clear ile sil.\n' +
|
|
2078
2078
|
(session && session.financeStrategy ? `SAHİBİNİN STRATEJİ NOTU (önceliklidir):\n${session.financeStrategy}\n` : '') +
|
|
2079
2079
|
(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' : '') +
|
|
2080
2080
|
(session && session.financeDigest ? `İŞLEM PERFORMANS GEÇMİŞİN (kendi kayıtların — kararlarında ders çıkar):\n${session.financeDigest}\n` : '') +
|
|
@@ -3845,15 +3845,17 @@ class Engine {
|
|
|
3845
3845
|
/* KİŞİSEL TOOLLAR: %APPDATA%\beast\tools\<ad>\ — kullanıcı yazmışsa
|
|
3846
3846
|
TÜM oturumlara (BEAST FINANCE dahil) tool__<ad> olarak açılır */
|
|
3847
3847
|
toolsList = [...toolsList, ...customtools.definitions()];
|
|
3848
|
-
/*
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
if (session && session.finance) {
|
|
3848
|
+
/* SKILL ARACI: kurulu SKILL.md gövdesini modele açar — finance oturumları
|
|
3849
|
+
kadar BOT oturumları (Tool botu: tool-yazma/mql5) ve arka plan ajanları
|
|
3850
|
+
da promptlarında skill() kullanır. Tanım yalnız finance'a verilince bot
|
|
3851
|
+
skill çağrısı yapamıyor, tur rapor üretmeden boşa düşüyordu. */
|
|
3852
|
+
if (session && (session.finance || session.botId || session.bgJob)) {
|
|
3853
3853
|
const skillDef = (opencode.toolmap.definitions() || []).find(
|
|
3854
3854
|
(d) => d && d.function && d.function.name === 'skill'
|
|
3855
3855
|
);
|
|
3856
|
-
if (skillDef
|
|
3856
|
+
if (skillDef && !toolsList.some((t) => t && t.function && t.function.name === 'skill')) {
|
|
3857
|
+
toolsList = [...toolsList, skillDef];
|
|
3858
|
+
}
|
|
3857
3859
|
}
|
|
3858
3860
|
/* AJAN DM: koşan ajanlara (arka plan işleri + finance), ana sohbete VE
|
|
3859
3861
|
bot sohbetlerine verilir — botlar birbirine iş atar/cevap verir
|
|
@@ -4913,6 +4915,7 @@ const skills = require('./skills');
|
|
|
4913
4915
|
requesterTitle: who,
|
|
4914
4916
|
responderTitle: target.name || 'Tool botu',
|
|
4915
4917
|
task: task.slice(0, 300),
|
|
4918
|
+
text,
|
|
4916
4919
|
before,
|
|
4917
4920
|
at: Date.now(),
|
|
4918
4921
|
});
|
|
@@ -4964,14 +4967,16 @@ const skills = require('./skills');
|
|
|
4964
4967
|
}
|
|
4965
4968
|
|
|
4966
4969
|
/* Tool botunun turu bitti: bekleyen isteklerin raporunu AJAN DM paneline
|
|
4967
|
-
ve isteyen oturuma düşür. _run finally'den çağrılır (tüm bitiş yolları).
|
|
4970
|
+
ve isteyen oturuma düşür. _run finally'den çağrılır (tüm bitiş yolları).
|
|
4971
|
+
Tur metin rapor üretmeden öldüyse: (1) tur hiç iş yapmadıysa TEK kez
|
|
4972
|
+
sessizce yeniden dene (geçici model/ağ hatası), (2) yine olmazsa araç
|
|
4973
|
+
izini + hata mesajını rapora koy ki isteyen NE olduğunu anlasın. */
|
|
4968
4974
|
_toolRequestFinished(sid) {
|
|
4969
4975
|
try {
|
|
4970
4976
|
const key = String(sid || '');
|
|
4971
4977
|
if (!this._toolPending || !this._toolPending.size) return;
|
|
4972
4978
|
const list = this._toolPending.get(key);
|
|
4973
4979
|
if (!list || !list.length) return;
|
|
4974
|
-
this._toolPending.delete(key);
|
|
4975
4980
|
const s = this.cache.get(key) || this._load(key);
|
|
4976
4981
|
const reply = (() => {
|
|
4977
4982
|
for (let i = s.messages.length - 1; i >= 0; i--) {
|
|
@@ -4983,10 +4988,51 @@ const skills = require('./skills');
|
|
|
4983
4988
|
}
|
|
4984
4989
|
return '';
|
|
4985
4990
|
})();
|
|
4991
|
+
/* TEST/İZ: isteğin başladığı noktadan sonra araç çağrısı/sonucu var mı? */
|
|
4992
|
+
const activity = (from) => {
|
|
4993
|
+
let n = 0;
|
|
4994
|
+
for (let i = Math.max(0, Number(from) || 0); i < s.messages.length; i++) {
|
|
4995
|
+
const m = s.messages[i];
|
|
4996
|
+
if (m && ((m.role === 'assistant' && Array.isArray(m.tool_calls) && m.tool_calls.length) || m.role === 'tool')) n++;
|
|
4997
|
+
}
|
|
4998
|
+
return n;
|
|
4999
|
+
};
|
|
5000
|
+
/* GEÇİCİ ÖLÜM: tek istek + hiç araç izi + metin yok → tek kez yeniden dene */
|
|
5001
|
+
if (!reply && list.length === 1 && !list[0].retried && list[0].text && activity(list[0].before) === 0) {
|
|
5002
|
+
list[0].retried = true;
|
|
5003
|
+
this._toolPending.set(key, list);
|
|
5004
|
+
try {
|
|
5005
|
+
const ok = this.send(key, { text: '[YENİDEN DENEME — önceki tur iş yapmadan düştü; isteği baştan yürüt]\n' + String(list[0].text) });
|
|
5006
|
+
if (ok) return;
|
|
5007
|
+
} catch {}
|
|
5008
|
+
}
|
|
5009
|
+
this._toolPending.delete(key);
|
|
5010
|
+
/* METİN RAPOR YOKSA: son turun araç izini özetle — teşhis kaybolmasın */
|
|
5011
|
+
const failDetail = (() => {
|
|
5012
|
+
if (reply) return '';
|
|
5013
|
+
const L = [];
|
|
5014
|
+
for (let i = s.messages.length - 1; i >= 0 && L.length < 8; i--) {
|
|
5015
|
+
const m = s.messages[i];
|
|
5016
|
+
if (!m) continue;
|
|
5017
|
+
if (m.role === 'tool') {
|
|
5018
|
+
const t = typeof m.content === 'string' ? m.content : '';
|
|
5019
|
+
if (t.trim()) L.unshift('araç sonucu: ' + t.replace(/\s+/g, ' ').slice(0, 180));
|
|
5020
|
+
} else if (m.role === 'assistant' && Array.isArray(m.tool_calls) && m.tool_calls.length) {
|
|
5021
|
+
L.unshift('çağrılan araçlar: ' + m.tool_calls.map((c) => (c && c.function && c.function.name) || '?').join(', '));
|
|
5022
|
+
} else if (m.role === 'assistant' && typeof m.content === 'string' && m.content.trim()) {
|
|
5023
|
+
L.unshift('son metin: ' + stripAiDashes(m.content.trim()).replace(/\s+/g, ' ').slice(0, 180));
|
|
5024
|
+
} else if (m.role === 'user') {
|
|
5025
|
+
break;
|
|
5026
|
+
}
|
|
5027
|
+
}
|
|
5028
|
+
return L.join('\n');
|
|
5029
|
+
})();
|
|
4986
5030
|
for (const req of list) {
|
|
4987
5031
|
const body = reply
|
|
4988
5032
|
? `[TOOL BOTU RAPORU — ${req.id}]\n${reply}`
|
|
4989
|
-
: `[TOOL BOTU] ${req.id} numaralı istek tamamlanamadı (tur yarıda kesildi ya da metin rapor üretilemedi)
|
|
5033
|
+
: `[TOOL BOTU] ${req.id} numaralı istek tamamlanamadı (tur yarıda kesildi ya da metin rapor üretilemedi).\n` +
|
|
5034
|
+
(failDetail ? 'Ne oldu:\n' + failDetail + '\n' : '') +
|
|
5035
|
+
'Tool botu sohbetinden ayrıntıya bakabilirsin.';
|
|
4990
5036
|
const dm = {
|
|
4991
5037
|
at: new Date().toISOString(),
|
|
4992
5038
|
from: key,
|
|
@@ -7081,8 +7127,14 @@ Engine.prototype._agentDmDeliver = function (dm, target, opts = {}) {
|
|
|
7081
7127
|
dm: true,
|
|
7082
7128
|
wake: !!opts.wake,
|
|
7083
7129
|
};
|
|
7084
|
-
/* bot↔bot sigorta: boş bot oturumunu her DM'de yeni turla uyandırma
|
|
7085
|
-
|
|
7130
|
+
/* bot↔bot sigorta: boş bot oturumunu her DM'de yeni turla uyandırma.
|
|
7131
|
+
TOOL BOTU İSTİSNA (yalnız bot OLMAYAN gönderenden): ajan (finance/paralel
|
|
7132
|
+
sohbet) → Tool DM'i gerçek görevdir, beklemeden uyandırılır; bot↔bot
|
|
7133
|
+
ping-pong'u ise bütçeye tabidir (sonsuz tur fırtınası korunur). */
|
|
7134
|
+
const fromSess = this.cache.get(String(dm.from)) || null;
|
|
7135
|
+
const fromIsBot = !!(fromSess && fromSess.botId);
|
|
7136
|
+
const isToolBot = sess && String(sess.botId || '') === 'tool' && !fromIsBot;
|
|
7137
|
+
if (sess && sess.botId && !isToolBot && !this.isBusy(m) && !this._agentDmWakeAllowed(dm.from, m)) {
|
|
7086
7138
|
this._agentDmBacklog = this._agentDmBacklog || new Map();
|
|
7087
7139
|
const q = this._agentDmBacklog.get(m) || [];
|
|
7088
7140
|
q.push({ text, ...(dm.image ? { image: String(dm.image) } : {}) });
|
|
@@ -276,11 +276,11 @@ const definitions = NAMES.map((name) => {
|
|
|
276
276
|
},
|
|
277
277
|
mt5_ogrenme: {
|
|
278
278
|
description:
|
|
279
|
-
'SEMBOL BAZLI ÖĞRENME HAFIZASI — Beast Finance sürekli öğrenir: mt5_ogrenme {action:"list"|"add"|"remove"|"clear"|"stats", symbol?, text?, kind?, tags?, id?, ids?, all?}. Her sembolün KENDİ istatistiği (işlem/kazanç/kayıp, net, kâr yakalama, kâr geri verme) OTOMATİK birikir; ayrıca işlemlerden çıkardığın DERSLERİ sen kaydedersin. KURALLAR: (1) Her kapanıştan sonra (özellikle stop/tp sonrası) sembol için tek cümle ders yaz: hangi setup işe yaradı/yaramadı, saat/seans, SL yeri, hata mı hata yok mu — kind:"pattern"|"mistake"|"rule"|"observation". (2) Yeni işlem kararından ÖNCE ilgili sembolün list/stats çıktısını oku; aynı hatayı tekrarlama, işleyen deseni kullan. (3) Yanlış çıkan/genel geçersiz dersi remove/clear ile sil. action:"list" symbol verilmezse tüm sembollerin özetini döner. Ör: {action:"add", symbol:"XAUUSD", text:"Londra açılışında M5 EMA50 üstü momentum girişleri iyi çalışıyor", kind:"pattern"}.',
|
|
279
|
+
'SEMBOL BAZLI ÖĞRENME HAFIZASI — Beast Finance sürekli öğrenir: mt5_ogrenme {action:"list"|"add"|"remove"|"clear"|"stats"|"compact"|"forget", symbol?, text?, kind?, tags?, id?, ids?, all?}. Her sembolün KENDİ istatistiği (işlem/kazanç/kayıp, net, kâr yakalama, kâr geri verme) OTOMATİK birikir; ayrıca işlemlerden çıkardığın DERSLERİ sen kaydedersin. KURALLAR: (1) Her kapanıştan sonra (özellikle stop/tp sonrası) sembol için tek cümle ders yaz: hangi setup işe yaradı/yaramadı, saat/seans, SL yeri, hata mı hata yok mu — kind:"pattern"|"mistake"|"rule"|"observation". (2) Yeni işlem kararından ÖNCE ilgili sembolün list/stats çıktısını oku; aynı hatayı tekrarlama, işleyen deseni kullan. (3) Yanlış çıkan/genel geçersiz dersi remove/clear ile sil. Aynı ders tekrar yazılamaz ve sembol başına 24 saatte en fazla 12 ders kaydedilir. HAFIZA KENDİNİ SADELEŞTİRİR (opencode tarzı compaction): ham dersler birikince (24+) ESKİ dersler OTOMATİK olarak modele özetlettirilir ve tek KALICI ÖZETE sıkıştırılır — ham yalnız son dersler kalır; 30 günden eski dersler, 90 günden eski işlem kayıtları ve 120 gün hareketsiz semboller otomatik unutulur. Gerekirse compact {symbol} ile hemen özetlet, forget ile süresi geçenleri temizle. action:"list" symbol verilmezse tüm sembollerin özetini döner (kalıcı özet dahil). Ör: {action:"add", symbol:"XAUUSD", text:"Londra açılışında M5 EMA50 üstü momentum girişleri iyi çalışıyor", kind:"pattern"}.',
|
|
280
280
|
parameters: {
|
|
281
281
|
type: 'object',
|
|
282
282
|
properties: {
|
|
283
|
-
action: { type: 'string', enum: ['list', 'add', 'remove', 'clear', 'stats'] },
|
|
283
|
+
action: { type: 'string', enum: ['list', 'add', 'remove', 'clear', 'stats', 'compact', 'forget'] },
|
|
284
284
|
symbol: { type: 'string', description: 'Sembol (ör. XAUUSD) — öğrenme sembol bazlıdır' },
|
|
285
285
|
text: { type: 'string', description: 'add: öğrenilen ders/desen (kısa, net, tek cümle)' },
|
|
286
286
|
kind: { type: 'string', enum: ['pattern', 'mistake', 'rule', 'observation'], description: 'add: kayıt türü (varsayılan observation)' },
|
|
@@ -600,7 +600,18 @@ const handlers = {
|
|
|
600
600
|
const all = args.all === true || String(args.all || '').toLowerCase() === 'true';
|
|
601
601
|
return learningApi.clear({ ids, symbol, all });
|
|
602
602
|
}
|
|
603
|
-
|
|
603
|
+
if (action === 'compact') {
|
|
604
|
+
/* OPENCODE TARZI ÖZETLEME: eski dersler modele özetlettirilir, ham yalnız
|
|
605
|
+
son dersler kalır — bellek sadeleşir (normalde otomatik de çalışır) */
|
|
606
|
+
if (!symbol) return { ok: false, error: 'compact için symbol gerekli' };
|
|
607
|
+
if (typeof learningApi.compact !== 'function') return { ok: false, error: 'özetleme kullanılamıyor' };
|
|
608
|
+
return await learningApi.compact({ symbol });
|
|
609
|
+
}
|
|
610
|
+
if (action === 'forget') {
|
|
611
|
+
if (typeof learningApi.forget !== 'function') return { ok: false, error: 'temizleme kullanılamıyor' };
|
|
612
|
+
return learningApi.forget();
|
|
613
|
+
}
|
|
614
|
+
return { ok: false, error: 'action: list|add|remove|clear|stats|compact|forget' };
|
|
604
615
|
},
|
|
605
616
|
async mt5_ea(args) {
|
|
606
617
|
if (!mt5.running) return notConnected();
|
package/src/main.js
CHANGED
|
@@ -2356,6 +2356,7 @@ function botToolSet(cfg) {
|
|
|
2356
2356
|
'tool_request', /* CEPHANE: eksik aracı TOOL botuna yazdırma hakkı HER botta */
|
|
2357
2357
|
'channel_send', /* İLK MESAJ: allow listteki kişilere kendiliğinden yazma hakkı HER botta */
|
|
2358
2358
|
'agent_dm', /* BOTLAR ARASI DM: botlar birbirine iş atar/cevap verir (ör. Beast → Tool) */
|
|
2359
|
+
'skill', /* SKILL KATALOĞU: botlar SKILL.md gövdesini skill() ile okur (Tool botu: tool-yazma/mql5) */
|
|
2359
2360
|
]);
|
|
2360
2361
|
if (s.web_search) { set.add('web_search'); set.add('http_fetch'); set.add('webfetch'); set.add('deep_search'); }
|
|
2361
2362
|
if (s.browser) {
|
|
@@ -2363,7 +2364,7 @@ function botToolSet(cfg) {
|
|
|
2363
2364
|
}
|
|
2364
2365
|
if (s.email) { set.add('email_list'); set.add('email_read'); set.add('email_send'); }
|
|
2365
2366
|
if (s.run_command) {
|
|
2366
|
-
for (const t of ['run_command', 'python_run', 'read_file', 'write_file', 'list_dir', 'computer_look', 'computer_act',
|
|
2367
|
+
for (const t of ['run_command', 'python_run', 'read_file', 'write_file', 'edit_file', 'grep', 'glob', 'list_dir', 'computer_look', 'computer_act',
|
|
2367
2368
|
'git_commit', 'git_diff_review', 'git_pr_create', 'repo_map', 'repo_symbols', 'xlsx_read', 'xlsx_write', 'xlsx_edit']) set.add(t);
|
|
2368
2369
|
}
|
|
2369
2370
|
if (s.memory) { set.add('memory_write'); set.add('user_write'); set.add('memory_search'); set.add('memory_hygiene'); }
|
|
@@ -4323,6 +4324,43 @@ function detachBrowser() {
|
|
|
4323
4324
|
browser.attached = false;
|
|
4324
4325
|
}
|
|
4325
4326
|
|
|
4327
|
+
/* TARAYICI YENİDEN BAŞLATMA: takılan/kilitlenen sayfa için view + webContents
|
|
4328
|
+
tamamen kapatılır, aynı URL YENİ bir view'da açılır. persist:browser
|
|
4329
|
+
partition'ı silinmez → oturum/çerezler (Google, X vb.) korunur. */
|
|
4330
|
+
function restartBrowser() {
|
|
4331
|
+
if (!win || win.isDestroyed()) return { ok: false, error: 'pencere yok' };
|
|
4332
|
+
const wasOpen = browser.open;
|
|
4333
|
+
let url = '';
|
|
4334
|
+
try {
|
|
4335
|
+
if (browser.view && browser.view.webContents && !browser.view.webContents.isDestroyed()) {
|
|
4336
|
+
url = browser.view.webContents.getURL();
|
|
4337
|
+
}
|
|
4338
|
+
} catch {}
|
|
4339
|
+
detachBrowser();
|
|
4340
|
+
try {
|
|
4341
|
+
const oldWc = browser.view && browser.view.webContents;
|
|
4342
|
+
if (oldWc && !oldWc.isDestroyed()) {
|
|
4343
|
+
try { oldWc.close(); } catch { try { oldWc.destroy(); } catch {} }
|
|
4344
|
+
/* takılı renderer süreci close()'u geciktirebilir — güvence: kısa süre
|
|
4345
|
+
sonra hâlâ duruyorsa zorla yok et (yeni view'ı asla kilitlemez) */
|
|
4346
|
+
setTimeout(() => { try { if (!oldWc.isDestroyed()) oldWc.destroy(); } catch {} }, 1500);
|
|
4347
|
+
}
|
|
4348
|
+
} catch {}
|
|
4349
|
+
browser.view = null;
|
|
4350
|
+
browser.started = false;
|
|
4351
|
+
browser.lastDialog = null;
|
|
4352
|
+
if (wasOpen) {
|
|
4353
|
+
ensureBrowser();
|
|
4354
|
+
browser.started = true;
|
|
4355
|
+
browser.view.webContents.loadURL(url || BROWSER_START_URL).catch(() => {});
|
|
4356
|
+
layoutBrowser();
|
|
4357
|
+
const [w] = win.getContentSize();
|
|
4358
|
+
browserEmit({ open: true, width: browserShownWidth(w), url: url || BROWSER_START_URL, restarted: true });
|
|
4359
|
+
}
|
|
4360
|
+
blog('restart', 'tarayıcı yeniden başlatıldı' + (url ? ' — ' + url.slice(0, 90) : ''));
|
|
4361
|
+
return { ok: true, url };
|
|
4362
|
+
}
|
|
4363
|
+
|
|
4326
4364
|
function setBrowserOpen(v, forceVisible) {
|
|
4327
4365
|
if (!win || win.isDestroyed()) return;
|
|
4328
4366
|
const want = !!v;
|
|
@@ -8399,6 +8437,7 @@ ipcMain.handle('browser:ctrl', (_e, action) => {
|
|
|
8399
8437
|
setBrowserOpen(false);
|
|
8400
8438
|
return { ok: true };
|
|
8401
8439
|
}
|
|
8440
|
+
if (action === 'restart') return restartBrowser();
|
|
8402
8441
|
const wc = browser.view && browser.view.webContents;
|
|
8403
8442
|
if (!wc) return { ok: false };
|
|
8404
8443
|
try {
|
|
@@ -11280,8 +11319,21 @@ function finLimitsSet(patch, ctx) {
|
|
|
11280
11319
|
kazanç/kayıp, net, MFE/MAE, kâr geri verme) (2) ajanın yazdığı dersler.
|
|
11281
11320
|
Trader turunda bu özet sistem promptuna gömülür — sistem kendi geçmişinden
|
|
11282
11321
|
öğrenir; her kapanış otomatik işlenir, dersleri ajan mt5_ogrenme ile yazar. */
|
|
11283
|
-
const FIN_LEARN_MAX_NOTES =
|
|
11322
|
+
const FIN_LEARN_MAX_NOTES = 60; /* sembol başına ders tavanı */
|
|
11284
11323
|
const FIN_LEARN_MAX_TRADES = 60;
|
|
11324
|
+
const FIN_LEARN_MAX_SYMBOLS = 60; /* toplam sembol tavanı — depo sınırsız büyümesin */
|
|
11325
|
+
const FIN_LEARN_MAX_NOTES_DAY = 12; /* sembol başına 24 saatte en fazla ders — ajan seli depoyu zehirlemesin */
|
|
11326
|
+
/* SÜREKLİ UNUTMA (bellek hijyeni): eski kayıtlar otomatik düşer — depo büyüyüp
|
|
11327
|
+
zehirlenmez, yalnız GÜNCEL dersler ve son performans kalır. */
|
|
11328
|
+
const FIN_LEARN_NOTE_TTL_MS = 30 * 24 * 60 * 60 * 1000; /* 30 gün: eski ders unutulur */
|
|
11329
|
+
const FIN_LEARN_TRADE_TTL_MS = 90 * 24 * 60 * 60 * 1000; /* 90 gün: eski işlem kaydı düşer */
|
|
11330
|
+
const FIN_LEARN_SYMBOL_TTL_MS = 120 * 24 * 60 * 60 * 1000; /* 120 gün aktivite yoksa sembol kaydı silinir */
|
|
11331
|
+
/* ÖZETLEME (opencode tarzı compaction): ham dersler birikince ESKİ olanlar
|
|
11332
|
+
modele özetlettirilir → tek kalıcı özet; ham yalnız son dersler kalır. */
|
|
11333
|
+
const FIN_LEARN_COMPACT_AT = 24; /* not sayısı bunu aşınca arka planda otomatik özetle */
|
|
11334
|
+
const FIN_LEARN_KEEP_RECENT = 8; /* ham kalan son ders sayısı */
|
|
11335
|
+
const FIN_LEARN_SUMMARY_MAX = 800; /* kalıcı özet metin tavanı (karakter) */
|
|
11336
|
+
const FIN_LEARN_COMPACT_MIN = 4; /* elle özetleme için gereken en az eski ders */
|
|
11285
11337
|
|
|
11286
11338
|
function finLearnEmptyStats() {
|
|
11287
11339
|
return {
|
|
@@ -11291,6 +11343,70 @@ function finLearnEmptyStats() {
|
|
|
11291
11343
|
};
|
|
11292
11344
|
}
|
|
11293
11345
|
|
|
11346
|
+
/* Ders metni karşılaştırma anahtarı: büyük/küçük harf, noktalama ve boşluk
|
|
11347
|
+
farkları aynı ders sayılır — ajan aynı cümleyi kopyalayıp depoyu doldurmasın */
|
|
11348
|
+
function finLearnNoteKey(text) {
|
|
11349
|
+
return String(text || '')
|
|
11350
|
+
.toLowerCase()
|
|
11351
|
+
.replace(/[^\p{L}\p{N}\s]+/gu, ' ')
|
|
11352
|
+
.replace(/\s+/g, ' ')
|
|
11353
|
+
.trim()
|
|
11354
|
+
.slice(0, 140);
|
|
11355
|
+
}
|
|
11356
|
+
|
|
11357
|
+
/* sembolün son aktivitesi (kapanış / ders / eski istatistik) — tavan aşımında
|
|
11358
|
+
EN ESKİ semboller düşürülür; dokunulan (protectSym) sembol asla atılmaz */
|
|
11359
|
+
function finLearnActivity(e) {
|
|
11360
|
+
const last = (arr) => (Array.isArray(arr) && arr.length ? Number(arr[arr.length - 1].at) || 0 : 0);
|
|
11361
|
+
return Math.max(Number((e.stats || {}).lastAt) || 0, last(e.notes), last(e.trades));
|
|
11362
|
+
}
|
|
11363
|
+
|
|
11364
|
+
function finLearnPrune(learn, keep, protectSym) {
|
|
11365
|
+
const cap = Number.isFinite(Number(keep)) ? Number(keep) : FIN_LEARN_MAX_SYMBOLS;
|
|
11366
|
+
const keys = Object.keys(learn.symbols);
|
|
11367
|
+
if (keys.length <= cap) return 0;
|
|
11368
|
+
const protect = String(protectSym || '').trim().toUpperCase();
|
|
11369
|
+
const dropped = keys
|
|
11370
|
+
.sort((a, b) => {
|
|
11371
|
+
if (a === protect) return 1;
|
|
11372
|
+
if (b === protect) return -1;
|
|
11373
|
+
return finLearnActivity(learn.symbols[a]) - finLearnActivity(learn.symbols[b]);
|
|
11374
|
+
})
|
|
11375
|
+
.slice(0, keys.length - cap);
|
|
11376
|
+
for (const k of dropped) delete learn.symbols[k];
|
|
11377
|
+
return dropped.length;
|
|
11378
|
+
}
|
|
11379
|
+
|
|
11380
|
+
/* SÜREKLİ UNUTMA: TTL'i geçen ders/işlem kayıtları düşer; uzun süre hiç
|
|
11381
|
+
aktivite görmeyen sembol kaydı (istatistik dahil) silinir. Her yazma ve
|
|
11382
|
+
yüklemede çalışır → depo kendiliğinden sade kalır, birikme/zehirlenme olmaz. */
|
|
11383
|
+
function finLearnForget(learn, now) {
|
|
11384
|
+
const t = Number(now) || Date.now();
|
|
11385
|
+
const stat = { notes: 0, trades: 0, symbols: 0 };
|
|
11386
|
+
for (const [sym, e] of Object.entries(learn.symbols)) {
|
|
11387
|
+
if (!e || typeof e !== 'object') { delete learn.symbols[sym]; stat.symbols++; continue; }
|
|
11388
|
+
const notes = Array.isArray(e.notes) ? e.notes : [];
|
|
11389
|
+
e.notes = notes.filter((n) => n && t - (Number(n.at) || 0) <= FIN_LEARN_NOTE_TTL_MS);
|
|
11390
|
+
stat.notes += notes.length - e.notes.length;
|
|
11391
|
+
const trades = Array.isArray(e.trades) ? e.trades : [];
|
|
11392
|
+
e.trades = trades.filter((x) => x && t - (Number(x.at) || 0) <= FIN_LEARN_TRADE_TTL_MS);
|
|
11393
|
+
stat.trades += trades.length - e.trades.length;
|
|
11394
|
+
if (t - finLearnActivity(e) > FIN_LEARN_SYMBOL_TTL_MS) {
|
|
11395
|
+
delete learn.symbols[sym];
|
|
11396
|
+
stat.symbols += 1;
|
|
11397
|
+
}
|
|
11398
|
+
}
|
|
11399
|
+
return stat;
|
|
11400
|
+
}
|
|
11401
|
+
|
|
11402
|
+
/* TEK TEMİZLİK: önce TTL unutması, sonra sembol tavanı. protectSym (yeni
|
|
11403
|
+
dokunulan sembol) asla düşürülmez. */
|
|
11404
|
+
function finLearnTidy(learn, protectSym, now) {
|
|
11405
|
+
const forgot = finLearnForget(learn, now);
|
|
11406
|
+
const cap = finLearnPrune(learn, FIN_LEARN_MAX_SYMBOLS, protectSym);
|
|
11407
|
+
return { notes: forgot.notes, trades: forgot.trades, symbols: forgot.symbols + cap };
|
|
11408
|
+
}
|
|
11409
|
+
|
|
11294
11410
|
function finLearnLoad() {
|
|
11295
11411
|
if (financeState.learnReady) return financeState.learn;
|
|
11296
11412
|
financeState.learnReady = true;
|
|
@@ -11305,21 +11421,128 @@ function finLearnLoad() {
|
|
|
11305
11421
|
notes: Array.isArray(v.notes) ? v.notes.filter((n) => n && n.id && n.text).slice(-FIN_LEARN_MAX_NOTES) : [],
|
|
11306
11422
|
trades: Array.isArray(v.trades) ? v.trades.filter(Boolean).slice(-FIN_LEARN_MAX_TRADES) : [],
|
|
11307
11423
|
stats: v.stats && typeof v.stats === 'object' ? { ...finLearnEmptyStats(), ...v.stats } : finLearnEmptyStats(),
|
|
11424
|
+
/* KALICI ÖZET (opencode tarzı sıkıştırma): eski derslerin modele
|
|
11425
|
+
özetlettirilmiş hâli — ham dersler düşse de bilgi kalır */
|
|
11426
|
+
summary: v.summary && typeof v.summary === 'object' && String(v.summary.text || '').trim()
|
|
11427
|
+
? {
|
|
11428
|
+
text: String(v.summary.text).replace(/\s+/g, ' ').trim().slice(0, FIN_LEARN_SUMMARY_MAX),
|
|
11429
|
+
at: Number(v.summary.at) || 0,
|
|
11430
|
+
covered: Math.max(0, Math.round(Number(v.summary.covered) || 0)),
|
|
11431
|
+
}
|
|
11432
|
+
: null,
|
|
11308
11433
|
};
|
|
11309
11434
|
}
|
|
11310
11435
|
if (Number(raw.updatedAt)) learn.updatedAt = Number(raw.updatedAt);
|
|
11311
11436
|
}
|
|
11312
11437
|
} catch {}
|
|
11438
|
+
const dropped = finLearnTidy(learn); /* yükleme anında unut + tavan uygula */
|
|
11313
11439
|
financeState.learn = learn;
|
|
11440
|
+
if (dropped.notes || dropped.trades || dropped.symbols) finLearnSave(); /* disk de hemen küçülsün */
|
|
11314
11441
|
return learn;
|
|
11315
11442
|
}
|
|
11316
11443
|
|
|
11317
11444
|
function finLearnSave() {
|
|
11318
11445
|
const learn = finLearnLoad();
|
|
11446
|
+
finLearnTidy(learn); /* her yazmada sürekli unutma */
|
|
11319
11447
|
learn.updatedAt = Date.now();
|
|
11320
11448
|
try { finWriteJson(finFile('ogrenme.json'), learn); } catch {}
|
|
11321
11449
|
}
|
|
11322
11450
|
|
|
11451
|
+
/* ---------- OPENCODE TARZI ÖZETLEME (COMPACTION) ----------
|
|
11452
|
+
Ham dersler birikince ESKİ dersler modele özetlettirilir: çalışan desenler,
|
|
11453
|
+
kaçınılacak hatalar, kurallar tek KALICI ÖZET'e iner; ham olarak yalnız son
|
|
11454
|
+
dersler kalır. Böylece öğrenme hafızası hem küçük hem zehirsiz kalır —
|
|
11455
|
+
context penceresi sıkıştırmasının sembol bazlı hâli. */
|
|
11456
|
+
const FIN_LEARN_COMPACT_SYSTEM =
|
|
11457
|
+
'Sen bir trading mentorüsün. Sana bir sembolün ESKİ ders notları ve mevcut özeti verilir. ' +
|
|
11458
|
+
'Görevin: güncellenmiş KISA bir özet üretmek.\n' +
|
|
11459
|
+
'KURALLAR: (1) Yalnız kalıcı değeri olan bilgiyi tut: çalışan desenler, kaçınılacak hatalar, ' +
|
|
11460
|
+
'seans/setup kuralları, SL/TP davranışı. (2) Tekrarları birleştir; tarih/saat damgası, anlık ' +
|
|
11461
|
+
'haber, geçici durum gibi bayat bilgiyi at. (3) Çelişen bilgide EN YENİ ve istatistikle ' +
|
|
11462
|
+
'desteklenen kazanır. (4) Sayısal istatistikleri (işlem, kazanç %, net, kâr geri verme) koru. ' +
|
|
11463
|
+
'(5) En fazla ' + FIN_LEARN_SUMMARY_MAX + ' karakter; kısa maddeler ya da 2-4 akıcı cümle. ' +
|
|
11464
|
+
'(6) Çıktı SADECE özet metni olsun; başlık/etiket/açıklama yazma.';
|
|
11465
|
+
|
|
11466
|
+
const finLearnCompacting = new Set();
|
|
11467
|
+
|
|
11468
|
+
function finLearnSummaryPrompt(sym, e, oldNotes) {
|
|
11469
|
+
const st = { ...finLearnEmptyStats(), ...(e.stats || {}) };
|
|
11470
|
+
const wr = st.trades ? Math.round((st.wins / st.trades) * 100) : 0;
|
|
11471
|
+
const lines = [];
|
|
11472
|
+
lines.push('SEMBOL: ' + sym);
|
|
11473
|
+
lines.push(
|
|
11474
|
+
`OTOMATİK İSTATİSTİK: ${st.trades} işlem · %${wr} kazanç · net ${st.net >= 0 ? '+' : ''}${st.net}` +
|
|
11475
|
+
(st.givebacks ? ` · ${st.givebacks} kez kâr geri verildi` : '') +
|
|
11476
|
+
(st.streak >= 2 ? ` · ${st.streak} ardışık kayıp` : '')
|
|
11477
|
+
);
|
|
11478
|
+
if (e.summary && e.summary.text) lines.push('MEVCUT ÖZET:\n' + String(e.summary.text).slice(0, 1400));
|
|
11479
|
+
lines.push('ESKİ DERSLER (kronolojik — en yeni EN SONDA):');
|
|
11480
|
+
for (const n of oldNotes.slice(-80)) {
|
|
11481
|
+
lines.push(`- [${String(n.kind || 'observation')}] ${String(n.text || '').slice(0, 220)}`);
|
|
11482
|
+
}
|
|
11483
|
+
lines.push('Görev: yalnız kalıcı ve güncel dersleri koruyan yeni özet metnini yaz.');
|
|
11484
|
+
return lines.join('\n');
|
|
11485
|
+
}
|
|
11486
|
+
|
|
11487
|
+
/* Bir sembolü özetle: snapshot'taki ESKİ dersleri modele özetlet, onları düş,
|
|
11488
|
+
yerine kalıcı özet yaz. Model çalışırken gelen YENİ dersler ham kalır. */
|
|
11489
|
+
async function finLearnCompactSymbol(symbol, force) {
|
|
11490
|
+
const sym = String(symbol || '').trim().toUpperCase();
|
|
11491
|
+
if (!sym) return { ok: false, error: 'symbol gerekli' };
|
|
11492
|
+
if (finLearnCompacting.has(sym)) return { ok: false, error: 'bu sembol şu an özetleniyor' };
|
|
11493
|
+
if (!engine || !engine.sel) return { ok: false, error: 'özetleme için aktif model yok' };
|
|
11494
|
+
const e = finLearnLoad().symbols[sym];
|
|
11495
|
+
if (!e) return { ok: false, error: 'sembol kaydı yok: ' + sym };
|
|
11496
|
+
const oldNotes = e.notes.slice(0, Math.max(0, e.notes.length - FIN_LEARN_KEEP_RECENT));
|
|
11497
|
+
const min = force ? FIN_LEARN_COMPACT_MIN : FIN_LEARN_COMPACT_AT - FIN_LEARN_KEEP_RECENT;
|
|
11498
|
+
if (oldNotes.length < min) {
|
|
11499
|
+
return { ok: false, error: `özetlenecek yeterli eski ders yok (${oldNotes.length}/${min})` };
|
|
11500
|
+
}
|
|
11501
|
+
finLearnCompacting.add(sym);
|
|
11502
|
+
try {
|
|
11503
|
+
const ctrl = new AbortController();
|
|
11504
|
+
const kill = setTimeout(() => ctrl.abort(), 60000);
|
|
11505
|
+
let r = null;
|
|
11506
|
+
try {
|
|
11507
|
+
r = await require('./agent/llm').chatOnce(engine.sel, {
|
|
11508
|
+
messages: [
|
|
11509
|
+
{ role: 'system', content: FIN_LEARN_COMPACT_SYSTEM },
|
|
11510
|
+
{ role: 'user', content: finLearnSummaryPrompt(sym, e, oldNotes) },
|
|
11511
|
+
],
|
|
11512
|
+
temperature: 0.2,
|
|
11513
|
+
}, { signal: ctrl.signal });
|
|
11514
|
+
} catch (err) {
|
|
11515
|
+
return { ok: false, error: 'özetleme çağrısı başarısız: ' + String((err && err.message) || err).slice(0, 120) };
|
|
11516
|
+
} finally {
|
|
11517
|
+
clearTimeout(kill);
|
|
11518
|
+
}
|
|
11519
|
+
const text = String((r && r.content) || '').replace(/\s+/g, ' ').trim().slice(0, FIN_LEARN_SUMMARY_MAX);
|
|
11520
|
+
if (!text) return { ok: false, error: 'özet üretilemedi (model yanıtı boş)' };
|
|
11521
|
+
/* yarışma koruması: yalnız snapshot'taki eski dersleri düşür */
|
|
11522
|
+
const fresh = finLearnLoad().symbols[sym];
|
|
11523
|
+
if (!fresh) return { ok: false, error: 'sembol kaydı kalmadı' };
|
|
11524
|
+
const drop = new Set(oldNotes.map((n) => String(n.id)));
|
|
11525
|
+
const before = fresh.notes.length;
|
|
11526
|
+
fresh.notes = fresh.notes.filter((n) => !drop.has(String(n.id)));
|
|
11527
|
+
const covered = before - fresh.notes.length;
|
|
11528
|
+
fresh.summary = {
|
|
11529
|
+
text,
|
|
11530
|
+
at: Date.now(),
|
|
11531
|
+
covered: (Number(fresh.summary && fresh.summary.covered) || 0) + covered,
|
|
11532
|
+
};
|
|
11533
|
+
finLearnSave();
|
|
11534
|
+
financeLog(`[öğrenme] ${sym} özetlendi: ${covered} eski ders → kalıcı özet (${text.length} karakter)`);
|
|
11535
|
+
return { ok: true, symbol: sym, covered, remaining: fresh.notes.length, summary: text };
|
|
11536
|
+
} finally {
|
|
11537
|
+
finLearnCompacting.delete(sym);
|
|
11538
|
+
}
|
|
11539
|
+
}
|
|
11540
|
+
|
|
11541
|
+
/* Arka plan tetikleyici: ajanı BEKLETMEDEN özetler (notlar birikince) */
|
|
11542
|
+
function finLearnCompactAuto(sym) {
|
|
11543
|
+
try { finLearnCompactSymbol(sym, false).catch(() => {}); } catch {}
|
|
11544
|
+
}
|
|
11545
|
+
|
|
11323
11546
|
function finLearnSym(symbol) {
|
|
11324
11547
|
const sym = String(symbol || '').trim().toUpperCase();
|
|
11325
11548
|
if (!sym) return null;
|
|
@@ -11367,6 +11590,7 @@ function finLearnRecordClose({ symbol, side, net, mfe, mae, reason }) {
|
|
|
11367
11590
|
reason: String(reason || '').slice(0, 40),
|
|
11368
11591
|
});
|
|
11369
11592
|
while (e.trades.length > FIN_LEARN_MAX_TRADES) e.trades.shift();
|
|
11593
|
+
finLearnTidy(finLearnLoad(), symbol);
|
|
11370
11594
|
finLearnSave();
|
|
11371
11595
|
} catch {}
|
|
11372
11596
|
}
|
|
@@ -11389,6 +11613,11 @@ function finBuildLearnDigest(symbols) {
|
|
|
11389
11613
|
if (st.lastAt) parts.push(`son: ${st.lastNet >= 0 ? '+' : ''}${st.lastNet}`);
|
|
11390
11614
|
L.push(`- ${sym}: ${parts.join(' · ')}`);
|
|
11391
11615
|
}
|
|
11616
|
+
/* KALICI ÖZET (compaction): eski derslerin sıkıştırılmış hâli — ham ders
|
|
11617
|
+
sayısı azalsa da birikmiş bilgi prompta girer */
|
|
11618
|
+
if (e.summary && e.summary.text) {
|
|
11619
|
+
L.push(` kalıcı özet: "${String(e.summary.text).slice(0, 500)}"`);
|
|
11620
|
+
}
|
|
11392
11621
|
if (e.notes.length) {
|
|
11393
11622
|
const last = e.notes[e.notes.length - 1];
|
|
11394
11623
|
const extra = e.notes.length > 1 ? ` (+${e.notes.length - 1} ders)` : '';
|
|
@@ -11406,7 +11635,7 @@ function finLearnApi() {
|
|
|
11406
11635
|
const sym = norm(symbol);
|
|
11407
11636
|
if (sym) {
|
|
11408
11637
|
const e = learn.symbols[sym];
|
|
11409
|
-
if (!e) return { ok: true, symbol: sym, count: 0, notes: [], trades: [], stats: finLearnEmptyStats() };
|
|
11638
|
+
if (!e) return { ok: true, symbol: sym, count: 0, notes: [], trades: [], stats: finLearnEmptyStats(), summary: null };
|
|
11410
11639
|
return {
|
|
11411
11640
|
ok: true,
|
|
11412
11641
|
symbol: sym,
|
|
@@ -11414,6 +11643,7 @@ function finLearnApi() {
|
|
|
11414
11643
|
notes: e.notes.slice(-25),
|
|
11415
11644
|
trades: e.trades.slice(-12),
|
|
11416
11645
|
stats: { ...finLearnEmptyStats(), ...(e.stats || {}) },
|
|
11646
|
+
summary: e.summary && e.summary.text ? e.summary : null,
|
|
11417
11647
|
};
|
|
11418
11648
|
}
|
|
11419
11649
|
const rows = Object.entries(learn.symbols)
|
|
@@ -11422,9 +11652,10 @@ function finLearnApi() {
|
|
|
11422
11652
|
notes: e.notes.length,
|
|
11423
11653
|
stats: { ...finLearnEmptyStats(), ...(e.stats || {}) },
|
|
11424
11654
|
lastNote: String(((e.notes[e.notes.length - 1] || {}).text) || ''),
|
|
11655
|
+
hasSummary: !!(e.summary && e.summary.text),
|
|
11425
11656
|
}))
|
|
11426
11657
|
.sort((a, b) => (b.stats.lastAt || 0) - (a.stats.lastAt || 0));
|
|
11427
|
-
return { ok: true, count: rows.length, symbols: rows.slice(0,
|
|
11658
|
+
return { ok: true, count: rows.length, symbols: rows.slice(0, FIN_LEARN_MAX_SYMBOLS) };
|
|
11428
11659
|
},
|
|
11429
11660
|
stats: ({ symbol } = {}) => {
|
|
11430
11661
|
const learn = finLearnLoad();
|
|
@@ -11438,13 +11669,27 @@ function finLearnApi() {
|
|
|
11438
11669
|
const sym = norm(symbol);
|
|
11439
11670
|
const t = String(text || '').replace(/\s+/g, ' ').trim();
|
|
11440
11671
|
if (!sym || !t) return { ok: false, error: 'symbol ve text gerekli' };
|
|
11672
|
+
const learn = finLearnLoad();
|
|
11441
11673
|
const e = finLearnSym(sym);
|
|
11442
11674
|
if (!e) return { ok: false, error: 'sembol yok' };
|
|
11443
|
-
/* aynı ders tekrar yazılmasın (son
|
|
11444
|
-
|
|
11445
|
-
|
|
11675
|
+
/* aynı ders tekrar yazılmasın (son 40 kayıtta normalize metin) — kopyala/
|
|
11676
|
+
yapıştır ve noktalama oyunlarıyla depo şişirilemez */
|
|
11677
|
+
const key = finLearnNoteKey(t);
|
|
11678
|
+
if (key && e.notes.slice(-40).some((n) => finLearnNoteKey(n.text) === key)) {
|
|
11446
11679
|
return { ok: true, duplicate: true, symbol: sym, count: e.notes.length };
|
|
11447
11680
|
}
|
|
11681
|
+
/* GÜNLÜK DERS LİMİTİ: ajan aynı gün aynı sembole ders yağdıramaz —
|
|
11682
|
+
hafıza öğrenmeyle zehirlenmesin, özet son dersleri temsil etsin */
|
|
11683
|
+
const dayAgo = Date.now() - 24 * 60 * 60 * 1000;
|
|
11684
|
+
const today = e.notes.filter((n) => Number(n.at) >= dayAgo).length;
|
|
11685
|
+
if (today >= FIN_LEARN_MAX_NOTES_DAY) {
|
|
11686
|
+
return {
|
|
11687
|
+
ok: false,
|
|
11688
|
+
error: `günlük ders limiti doldu (${FIN_LEARN_MAX_NOTES_DAY}/24s) — ders kaydedilmedi`,
|
|
11689
|
+
symbol: sym,
|
|
11690
|
+
count: e.notes.length,
|
|
11691
|
+
};
|
|
11692
|
+
}
|
|
11448
11693
|
const note = {
|
|
11449
11694
|
id: 'l' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
|
|
11450
11695
|
at: Date.now(),
|
|
@@ -11455,7 +11700,11 @@ function finLearnApi() {
|
|
|
11455
11700
|
};
|
|
11456
11701
|
e.notes.push(note);
|
|
11457
11702
|
while (e.notes.length > FIN_LEARN_MAX_NOTES) e.notes.shift();
|
|
11703
|
+
finLearnTidy(learn, sym);
|
|
11458
11704
|
finLearnSave();
|
|
11705
|
+
/* OPENCODE TARZI COMPACTION: notlar birikince ESKİLERİ arka planda
|
|
11706
|
+
özetle — ajanı bekletmez, hafıza kendiliğinden sadeleşir */
|
|
11707
|
+
if (e.notes.length > FIN_LEARN_COMPACT_AT) finLearnCompactAuto(sym);
|
|
11459
11708
|
return { ok: true, id: note.id, symbol: sym, count: e.notes.length };
|
|
11460
11709
|
},
|
|
11461
11710
|
remove: ({ id, symbol } = {}) => {
|
|
@@ -11497,6 +11746,31 @@ function finLearnApi() {
|
|
|
11497
11746
|
if (removed) finLearnSave();
|
|
11498
11747
|
return { ok: true, removed };
|
|
11499
11748
|
},
|
|
11749
|
+
drop: ({ symbol } = {}) => {
|
|
11750
|
+
/* sembolün TÜM kaydını (istatistik + dersler + işlemler) bırak —
|
|
11751
|
+
artık işlem yapılmayan/eski semboller depoda yer kaplamasın */
|
|
11752
|
+
const learn = finLearnLoad();
|
|
11753
|
+
const sym = norm(symbol);
|
|
11754
|
+
if (!sym || !learn.symbols[sym]) return { ok: false, error: 'sembol yok' };
|
|
11755
|
+
delete learn.symbols[sym];
|
|
11756
|
+
finLearnSave();
|
|
11757
|
+
return { ok: true, symbol: sym };
|
|
11758
|
+
},
|
|
11759
|
+
forget: () => {
|
|
11760
|
+
/* ELLE SADELEŞTİRME: TTL'i geçen ders/işlemleri ve hareketsiz sembolleri
|
|
11761
|
+
hemen unut (normalde her yazmada otomatik çalışır) */
|
|
11762
|
+
const learn = finLearnLoad();
|
|
11763
|
+
const r = finLearnForget(learn);
|
|
11764
|
+
if (r.notes || r.trades || r.symbols) finLearnSave();
|
|
11765
|
+
return { ok: true, ...r };
|
|
11766
|
+
},
|
|
11767
|
+
compact: ({ symbol } = {}) => {
|
|
11768
|
+
/* OPENCODE TARZI ÖZETLEME (elle): eski dersleri modele özetlet,
|
|
11769
|
+
ham yalnız son dersler kalsın */
|
|
11770
|
+
const sym = norm(symbol);
|
|
11771
|
+
if (!sym) return Promise.resolve({ ok: false, error: 'compact için symbol gerekli' });
|
|
11772
|
+
return finLearnCompactSymbol(sym, true);
|
|
11773
|
+
},
|
|
11500
11774
|
};
|
|
11501
11775
|
}
|
|
11502
11776
|
|
|
@@ -12397,6 +12671,26 @@ ipcMain.handle('finance:alerts:remove', async (_e, id) => {
|
|
|
12397
12671
|
return { ok };
|
|
12398
12672
|
});
|
|
12399
12673
|
|
|
12674
|
+
/* MT5 ÖĞRENME HAFIZASI (panel 🧠): sembol bazlı istatistik + ajan derslerini
|
|
12675
|
+
görüntüle/sil — depo tavanları (sembol/ders/işlem/günlük ders) çekirdekte uygulanır */
|
|
12676
|
+
ipcMain.handle('finance:learn', async (_e, payload) => {
|
|
12677
|
+
const p = payload && typeof payload === 'object' ? payload : {};
|
|
12678
|
+
const api = finLearnApi();
|
|
12679
|
+
const action = String(p.action || 'list');
|
|
12680
|
+
try {
|
|
12681
|
+
if (action === 'list') return api.list({ symbol: p.symbol });
|
|
12682
|
+
if (action === 'stats') return api.stats({ symbol: p.symbol });
|
|
12683
|
+
if (action === 'remove') return api.remove({ id: p.id, symbol: p.symbol });
|
|
12684
|
+
if (action === 'clear') return api.clear({ ids: p.ids, symbol: p.symbol, all: p.all });
|
|
12685
|
+
if (action === 'drop') return api.drop({ symbol: p.symbol });
|
|
12686
|
+
if (action === 'forget') return api.forget();
|
|
12687
|
+
if (action === 'compact') return await api.compact({ symbol: p.symbol });
|
|
12688
|
+
return { ok: false, error: 'bilinmeyen action' };
|
|
12689
|
+
} catch (e) {
|
|
12690
|
+
return { ok: false, error: String((e && e.message) || e).slice(0, 160) };
|
|
12691
|
+
}
|
|
12692
|
+
});
|
|
12693
|
+
|
|
12400
12694
|
ipcMain.handle('finance:mode', async (_e, payload) => {
|
|
12401
12695
|
financeState.mode = !!(payload && payload.on);
|
|
12402
12696
|
/* SAAT DENETİMİ: pencere kapanınca ajanlar otomatik durur, açılınca geri başlar */
|
package/src/preload.js
CHANGED
|
@@ -131,6 +131,7 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
131
131
|
financeMt5Setup: () => ipcRenderer.invoke('finance:mt5:setup'),
|
|
132
132
|
financeSymbolsList: (filter) => ipcRenderer.invoke('finance:symbols:list', filter),
|
|
133
133
|
financeReport: () => ipcRenderer.invoke('finance:report'),
|
|
134
|
+
financeLearn: (payload) => ipcRenderer.invoke('finance:learn', payload),
|
|
134
135
|
clipRead: () => ipcRenderer.invoke('clip:read'),
|
|
135
136
|
clipWrite: (t) => ipcRenderer.invoke('clip:write', t),
|
|
136
137
|
thinkSet: (v) => ipcRenderer.invoke('think:set', v),
|
package/src/renderer/i18n.js
CHANGED
|
@@ -427,6 +427,7 @@
|
|
|
427
427
|
tip_bb_mobile: 'Mobil önizleme — telefon siluetinde canlı uygulama (Expo/Metro dev server)',
|
|
428
428
|
tip_bb_fwd: 'İleri',
|
|
429
429
|
tip_bb_reload: 'Yenile',
|
|
430
|
+
tip_bb_restart: 'Yeniden başlat — takılan sayfayı sıfırdan açar (oturum korunur)',
|
|
430
431
|
tip_bb_shot: 'Ekran görüntüsünü sohbete ekle',
|
|
431
432
|
tip_bb_open: 'Tarayıcıda aç',
|
|
432
433
|
tip_bb_close: 'Kapat',
|
|
@@ -1250,6 +1251,7 @@
|
|
|
1250
1251
|
tip_bb_mobile: 'Mobile preview — live app in a phone frame (Expo/Metro dev server)',
|
|
1251
1252
|
tip_bb_fwd: 'Forward',
|
|
1252
1253
|
tip_bb_reload: 'Reload',
|
|
1254
|
+
tip_bb_restart: 'Restart — reopens the stuck page from scratch (session kept)',
|
|
1253
1255
|
tip_bb_shot: 'Add screenshot to chat',
|
|
1254
1256
|
tip_bb_open: 'Open in browser',
|
|
1255
1257
|
tip_bb_close: 'Close',
|
package/src/renderer/index.html
CHANGED
|
@@ -164,7 +164,7 @@
|
|
|
164
164
|
</div>
|
|
165
165
|
</div>
|
|
166
166
|
<div class="fin-card fin-card-trader" data-fin-card="trader">
|
|
167
|
-
<div class="fin-card-title">TRADE AJANI <span id="finTraderDot" class="fin-dot off" title="Trader durumu"></span><span class="file-spacer"></span><button id="finSetupBtn" title="MT5 kurulumunu uygula — BeastFinance.mq5 derle + AutoTrading izni + grafiğe ekle">🛠</button><button id="finModelRefreshBtn" title="Modelleri yeniden çek — eksik/güncel modelleri tazeler">⟳</button><button id="finSkillsBtn" title="Rol → skill eşleştirmesi — rol başına tek skill (çöp ikonuyla kaldırılır; boş = ajan kendi seçer)">⚙</button><button id="finAdvToggle" type="button" aria-expanded="false" title="Gelişmiş — disiplin kuralları + shadow mod"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px"><path d="M4 6h9"/><circle cx="16.5" cy="6" r="2.5"/><path d="M20 6h0.01"/><path d="M4 12h3"/><circle cx="10.5" cy="12" r="2.5"/><path d="M15 12h5"/><path d="M4 18h11"/><circle cx="18.5" cy="18" r="2.5"/></svg></button></div>
|
|
167
|
+
<div class="fin-card-title">TRADE AJANI <span id="finTraderDot" class="fin-dot off" title="Trader durumu"></span><span class="file-spacer"></span><button id="finSetupBtn" title="MT5 kurulumunu uygula — BeastFinance.mq5 derle + AutoTrading izni + grafiğe ekle">🛠</button><button id="finModelRefreshBtn" title="Modelleri yeniden çek — eksik/güncel modelleri tazeler">⟳</button><button id="finLearnBtn" title="MT5 öğrenme hafızası — neler öğrendi: sembol bazlı dersler + otomatik istatistik (görüntüle/sil)">🧠</button><button id="finSkillsBtn" title="Rol → skill eşleştirmesi — rol başına tek skill (çöp ikonuyla kaldırılır; boş = ajan kendi seçer)">⚙</button><button id="finAdvToggle" type="button" aria-expanded="false" title="Gelişmiş — disiplin kuralları + shadow mod"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px"><path d="M4 6h9"/><circle cx="16.5" cy="6" r="2.5"/><path d="M20 6h0.01"/><path d="M4 12h3"/><circle cx="10.5" cy="12" r="2.5"/><path d="M15 12h5"/><path d="M4 18h11"/><circle cx="18.5" cy="18" r="2.5"/></svg></button></div>
|
|
168
168
|
<div class="fin-field">
|
|
169
169
|
<span>Model</span>
|
|
170
170
|
<select id="finTraderModel" title="Trader ajanının modeli — farklı API/model seçilebilir"></select>
|
|
@@ -858,6 +858,21 @@
|
|
|
858
858
|
</div>
|
|
859
859
|
</div>
|
|
860
860
|
|
|
861
|
+
<div id="finLearnOverlay" class="mini-overlay" hidden>
|
|
862
|
+
<div class="mini-dialog fin-learn-dialog">
|
|
863
|
+
<div class="mini-head">
|
|
864
|
+
<span class="mini-title">MT5 ÖĞRENME HAFIZASI</span>
|
|
865
|
+
<span id="finLearnCount" class="fin-sym-count"></span>
|
|
866
|
+
<button id="finLearnClear" title="TÜM sembollerin derslerini sil — otomatik istatistikler korunur (depo şişmesi/zehirlenmesi önlenir)">Tüm dersleri sil</button>
|
|
867
|
+
<button class="mini-close" id="finLearnClose" title="Kapat">×</button>
|
|
868
|
+
</div>
|
|
869
|
+
<div class="mini-body">
|
|
870
|
+
<div id="finLearnList" class="mini-list"></div>
|
|
871
|
+
<div class="fin-sym-hint">Trade ajanının çekirdekteki sembol bazlı hafızası: her işlem kapanışı otomatik istatistiğe işlenir, ajan dersleri mt5_ogrenme ile yazar. Sembol satırına tıkla → kalıcı özeti, dersleri ve son işlemleri gör; ÖZET ile eski dersleri modele sıkıştırt (24+ ders birikince otomatik), tek dersi çöp ikonuyla sil, sembol kaydını tümden bırak. Hafıza sürekli sadeleşir: 30 günden eski dersler, 90 günden eski işlem kayıtları ve 120 gün hareketsiz semboller otomatik unutulur.</div>
|
|
872
|
+
</div>
|
|
873
|
+
</div>
|
|
874
|
+
</div>
|
|
875
|
+
|
|
861
876
|
<div id="finTeamOverlay" class="mini-overlay" hidden>
|
|
862
877
|
<div class="mini-dialog fin-sym-dialog">
|
|
863
878
|
<div class="mini-head">
|
|
@@ -942,6 +957,7 @@
|
|
|
942
957
|
<button id="bbBack" title="Geri" data-i18n-title="tip_bb_back">←</button>
|
|
943
958
|
<button id="bbFwd" title="İleri" data-i18n-title="tip_bb_fwd">→</button>
|
|
944
959
|
<button id="bbReload" title="Yenile" data-i18n-title="tip_bb_reload">↻</button>
|
|
960
|
+
<button id="bbRestart" title="Yeniden başlat — takılan sayfayı sıfırdan açar (oturum korunur)" data-i18n-title="tip_bb_restart">⟳</button>
|
|
945
961
|
<input id="bbUrl" placeholder="adres yaz veya ara…" autocomplete="off" spellcheck="false" data-i18n-ph="bb_ph" />
|
|
946
962
|
<button id="bbPhone" title="Telefon modu — mobil versiyon / web versiyonu" data-i18n-title="tip_bb_phone"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="7" y="2" width="10" height="20" rx="2.5"/><line x1="11" y1="18" x2="13" y2="18"/></svg></button>
|
|
947
963
|
<button id="bbOpenExt" title="Tarayıcıda aç" data-i18n-title="tip_bb_open">↗</button>
|
package/src/renderer/renderer.js
CHANGED
|
@@ -92,6 +92,7 @@ const els = {
|
|
|
92
92
|
bbBack: $('#bbBack'),
|
|
93
93
|
bbFwd: $('#bbFwd'),
|
|
94
94
|
bbReload: $('#bbReload'),
|
|
95
|
+
bbRestart: $('#bbRestart'),
|
|
95
96
|
bbUrl: $('#bbUrl'),
|
|
96
97
|
bbOpenExt: $('#bbOpenExt'),
|
|
97
98
|
bbClose: $('#bbClose'),
|
|
@@ -260,6 +261,12 @@ const els = {
|
|
|
260
261
|
finSkillsOverlay: $('#finSkillsOverlay'),
|
|
261
262
|
finSkillsClose: $('#finSkillsClose'),
|
|
262
263
|
finSkillsList: $('#finSkillsList'),
|
|
264
|
+
finLearnBtn: $('#finLearnBtn'),
|
|
265
|
+
finLearnOverlay: $('#finLearnOverlay'),
|
|
266
|
+
finLearnClose: $('#finLearnClose'),
|
|
267
|
+
finLearnList: $('#finLearnList'),
|
|
268
|
+
finLearnCount: $('#finLearnCount'),
|
|
269
|
+
finLearnClear: $('#finLearnClear'),
|
|
263
270
|
finSymSearch: $('#finSymSearch'),
|
|
264
271
|
finSymPick: $('#finSymPick'),
|
|
265
272
|
finSymCount: $('#finSymCount'),
|
|
@@ -7813,6 +7820,16 @@ async function init() {
|
|
|
7813
7820
|
els.bbBack.addEventListener('click', () => beast.browserCtrl('back'));
|
|
7814
7821
|
els.bbFwd.addEventListener('click', () => beast.browserCtrl('forward'));
|
|
7815
7822
|
els.bbReload.addEventListener('click', () => beast.browserCtrl('reload'));
|
|
7823
|
+
if (els.bbRestart) {
|
|
7824
|
+
els.bbRestart.addEventListener('click', async () => {
|
|
7825
|
+
try {
|
|
7826
|
+
const r = await beast.browserCtrl('restart');
|
|
7827
|
+
toast(r && r.ok ? 'Tarayıcı yeniden başlatıldı' : 'Tarayıcı yeniden başlatılamadı');
|
|
7828
|
+
} catch (e) {
|
|
7829
|
+
toast('Tarayıcı yeniden başlatılamadı');
|
|
7830
|
+
}
|
|
7831
|
+
});
|
|
7832
|
+
}
|
|
7816
7833
|
if (els.bbPhone) {
|
|
7817
7834
|
els.bbPhone.addEventListener('click', () => {
|
|
7818
7835
|
/* web ↔ mobil: aynı sitenin iki versiyonu arasındaki anahtar */
|
|
@@ -11030,6 +11047,303 @@ if (els.finSkillsOverlay) {
|
|
|
11030
11047
|
});
|
|
11031
11048
|
}
|
|
11032
11049
|
|
|
11050
|
+
/* ---------- MT5 ÖĞRENME HAFIZASI modalı (TRADE AJANI 🧠) ----------
|
|
11051
|
+
Çekirdekteki (main process) sembol bazlı öğrenme deposunu gösterir:
|
|
11052
|
+
otomatik istatistik + ajanın mt5_ogrenme ile yazdığı dersler. Tek ders
|
|
11053
|
+
silinebilir, sembol kaydı tümden bırakılabilir; depo tavanları
|
|
11054
|
+
(60 sembol / 120 ders / 60 işlem / günlük 12 ders) çekirdekte uygulanır. */
|
|
11055
|
+
const FIN_LEARN_TRASH_SVG =
|
|
11056
|
+
'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px"><path d="M3 6h18"/><path d="M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg>';
|
|
11057
|
+
|
|
11058
|
+
let finLearnRows = [];
|
|
11059
|
+
let finLearnOpenSym = '';
|
|
11060
|
+
let finLearnBusy = false;
|
|
11061
|
+
|
|
11062
|
+
function finLearnCall(payload) {
|
|
11063
|
+
return beast.financeLearn(payload).catch(() => ({ ok: false }));
|
|
11064
|
+
}
|
|
11065
|
+
|
|
11066
|
+
function finLearnSign(v) {
|
|
11067
|
+
const n = Math.round((Number(v) || 0) * 100) / 100;
|
|
11068
|
+
return (n >= 0 ? '+' : '') + n;
|
|
11069
|
+
}
|
|
11070
|
+
|
|
11071
|
+
function finLearnTime(ts) {
|
|
11072
|
+
const t = Number(ts) || 0;
|
|
11073
|
+
if (!t) return '';
|
|
11074
|
+
const d = new Date(t);
|
|
11075
|
+
const p = (x) => String(x).padStart(2, '0');
|
|
11076
|
+
return p(d.getDate()) + '.' + p(d.getMonth() + 1) + ' ' + p(d.getHours()) + ':' + p(d.getMinutes());
|
|
11077
|
+
}
|
|
11078
|
+
|
|
11079
|
+
function finLearnKindLabel(kind) {
|
|
11080
|
+
return ({ pattern: 'desen', mistake: 'hata', rule: 'kural', observation: 'gözlem' })[String(kind || '')] || 'gözlem';
|
|
11081
|
+
}
|
|
11082
|
+
|
|
11083
|
+
function finLearnStatsText(st) {
|
|
11084
|
+
const trades = Number(st && st.trades) || 0;
|
|
11085
|
+
if (!trades) return '';
|
|
11086
|
+
const parts = [
|
|
11087
|
+
trades + ' işlem',
|
|
11088
|
+
'%' + Math.round(((Number(st.wins) || 0) / trades) * 100) + ' kazanç',
|
|
11089
|
+
'net ' + finLearnSign(st.net),
|
|
11090
|
+
];
|
|
11091
|
+
if (Number(st.streak) >= 2) parts.push(Number(st.streak) + ' ardışık kayıp');
|
|
11092
|
+
if (Number(st.givebacks) > 0) parts.push(Number(st.givebacks) + ' kez kâr geri verildi');
|
|
11093
|
+
if (st.lastAt) parts.push('son ' + finLearnSign(st.lastNet));
|
|
11094
|
+
return parts.join(' · ');
|
|
11095
|
+
}
|
|
11096
|
+
|
|
11097
|
+
async function finLearnOpen() {
|
|
11098
|
+
if (!els.finLearnOverlay) return;
|
|
11099
|
+
finLearnOpenSym = '';
|
|
11100
|
+
els.finLearnOverlay.hidden = false;
|
|
11101
|
+
await finLearnRefresh();
|
|
11102
|
+
}
|
|
11103
|
+
|
|
11104
|
+
function finLearnCloseModal() {
|
|
11105
|
+
if (els.finLearnOverlay) els.finLearnOverlay.hidden = true;
|
|
11106
|
+
}
|
|
11107
|
+
|
|
11108
|
+
async function finLearnRefresh() {
|
|
11109
|
+
const box = els.finLearnList;
|
|
11110
|
+
if (!box || finLearnBusy) return;
|
|
11111
|
+
finLearnBusy = true;
|
|
11112
|
+
box.innerHTML = '<div class="fin-empty">Öğrenme kayıtları yükleniyor…</div>';
|
|
11113
|
+
const r = await finLearnCall({ action: 'list' });
|
|
11114
|
+
finLearnBusy = false;
|
|
11115
|
+
finLearnRows = (r && Array.isArray(r.symbols)) ? r.symbols : [];
|
|
11116
|
+
finLearnRender();
|
|
11117
|
+
}
|
|
11118
|
+
|
|
11119
|
+
function finLearnRender() {
|
|
11120
|
+
const box = els.finLearnList;
|
|
11121
|
+
if (!box) return;
|
|
11122
|
+
box.textContent = '';
|
|
11123
|
+
if (els.finLearnCount) els.finLearnCount.textContent = finLearnRows.length ? finLearnRows.length + ' sembol' : '';
|
|
11124
|
+
if (!finLearnRows.length) {
|
|
11125
|
+
box.innerHTML = '<div class="fin-empty">Henüz öğrenilmiş bir şey yok — işlem kapandıkça istatistik, ajan ders yazdıkça kayıt burada birikir.</div>';
|
|
11126
|
+
return;
|
|
11127
|
+
}
|
|
11128
|
+
for (const row of finLearnRows) box.appendChild(finLearnSymEl(row));
|
|
11129
|
+
/* silme sonrası yeniden çizimde açık sembol detayı açık kalsın */
|
|
11130
|
+
if (finLearnOpenSym) {
|
|
11131
|
+
const el = [...box.children].find((c) => c.dataset && c.dataset.sym === finLearnOpenSym);
|
|
11132
|
+
if (el && el._finToggle) el._finToggle();
|
|
11133
|
+
}
|
|
11134
|
+
}
|
|
11135
|
+
|
|
11136
|
+
function finLearnSymEl(row) {
|
|
11137
|
+
const sym = String(row.symbol || '');
|
|
11138
|
+
const el = document.createElement('div');
|
|
11139
|
+
el.className = 'fin-learn-sym';
|
|
11140
|
+
el.dataset.sym = sym;
|
|
11141
|
+
const head = document.createElement('div');
|
|
11142
|
+
head.className = 'fin-learn-sym-head';
|
|
11143
|
+
const name = document.createElement('b');
|
|
11144
|
+
name.textContent = sym;
|
|
11145
|
+
const stats = document.createElement('span');
|
|
11146
|
+
stats.className = 'fin-learn-sym-stats';
|
|
11147
|
+
stats.textContent = finLearnStatsText(row.stats) || 'henüz kapanan işlem yok';
|
|
11148
|
+
const noteCount = document.createElement('span');
|
|
11149
|
+
noteCount.className = 'fin-learn-sym-notes';
|
|
11150
|
+
noteCount.textContent = (Number(row.notes) || 0) + ' ders' + (row.hasSummary ? ' · özet' : '');
|
|
11151
|
+
/* ÖZETLE (opencode tarzı compaction): eski dersler modele özetlettirilir */
|
|
11152
|
+
const sum = document.createElement('button');
|
|
11153
|
+
sum.type = 'button';
|
|
11154
|
+
sum.className = 'fin-learn-sum-btn';
|
|
11155
|
+
sum.title = 'Eski dersleri modele özetlet — hafıza sadeleşir (24+ ders birikince otomatik de çalışır)';
|
|
11156
|
+
sum.textContent = 'ÖZET';
|
|
11157
|
+
sum.addEventListener('click', async (e) => {
|
|
11158
|
+
e.stopPropagation();
|
|
11159
|
+
sum.disabled = true;
|
|
11160
|
+
sum.textContent = '…';
|
|
11161
|
+
const r = await finLearnCall({ action: 'compact', symbol: sym });
|
|
11162
|
+
sum.disabled = false;
|
|
11163
|
+
sum.textContent = 'ÖZET';
|
|
11164
|
+
if (r && r.ok) {
|
|
11165
|
+
toast(sym + ' özetlendi: ' + (Number(r.covered) || 0) + ' eski ders kalıcı özete sıkıştı');
|
|
11166
|
+
await finLearnRefresh();
|
|
11167
|
+
} else {
|
|
11168
|
+
toast('Özetlenemedi: ' + ((r && r.error) || '?'));
|
|
11169
|
+
}
|
|
11170
|
+
});
|
|
11171
|
+
const drop = document.createElement('button');
|
|
11172
|
+
drop.type = 'button';
|
|
11173
|
+
drop.className = 'fin-learn-drop';
|
|
11174
|
+
drop.title = sym + ' kaydını TÜMDEN sil (dersler + otomatik istatistik)';
|
|
11175
|
+
drop.innerHTML = FIN_LEARN_TRASH_SVG;
|
|
11176
|
+
drop.addEventListener('click', async (e) => {
|
|
11177
|
+
e.stopPropagation();
|
|
11178
|
+
const ok = await uiConfirm(sym + ' için TÜM öğrenme kaydı (dersler + istatistik) silinsin mi?', 'Sil', 'Vazgeç');
|
|
11179
|
+
if (!ok) return;
|
|
11180
|
+
await finLearnCall({ action: 'drop', symbol: sym });
|
|
11181
|
+
if (finLearnOpenSym === sym) finLearnOpenSym = '';
|
|
11182
|
+
toast(sym + ' öğrenme kaydı silindi');
|
|
11183
|
+
await finLearnRefresh();
|
|
11184
|
+
});
|
|
11185
|
+
head.appendChild(name);
|
|
11186
|
+
head.appendChild(stats);
|
|
11187
|
+
head.appendChild(noteCount);
|
|
11188
|
+
head.appendChild(sum);
|
|
11189
|
+
head.appendChild(drop);
|
|
11190
|
+
el.appendChild(head);
|
|
11191
|
+
|
|
11192
|
+
const body = document.createElement('div');
|
|
11193
|
+
body.className = 'fin-learn-body';
|
|
11194
|
+
body.hidden = true;
|
|
11195
|
+
el.appendChild(body);
|
|
11196
|
+
let loaded = false;
|
|
11197
|
+
let busy = false;
|
|
11198
|
+
el._finToggle = async () => {
|
|
11199
|
+
if (busy) return;
|
|
11200
|
+
if (loaded && !body.hidden) {
|
|
11201
|
+
body.hidden = true;
|
|
11202
|
+
if (finLearnOpenSym === sym) finLearnOpenSym = '';
|
|
11203
|
+
return;
|
|
11204
|
+
}
|
|
11205
|
+
if (loaded) {
|
|
11206
|
+
body.hidden = false;
|
|
11207
|
+
finLearnOpenSym = sym;
|
|
11208
|
+
return;
|
|
11209
|
+
}
|
|
11210
|
+
busy = true;
|
|
11211
|
+
body.hidden = false;
|
|
11212
|
+
finLearnOpenSym = sym;
|
|
11213
|
+
body.innerHTML = '<div class="fin-empty">yükleniyor…</div>';
|
|
11214
|
+
const d = await finLearnCall({ action: 'list', symbol: sym });
|
|
11215
|
+
if (!els.finLearnOverlay || els.finLearnOverlay.hidden) { busy = false; return; }
|
|
11216
|
+
finLearnDetail(body, sym, d);
|
|
11217
|
+
loaded = true;
|
|
11218
|
+
busy = false;
|
|
11219
|
+
};
|
|
11220
|
+
head.addEventListener('click', () => { el._finToggle(); });
|
|
11221
|
+
return el;
|
|
11222
|
+
}
|
|
11223
|
+
|
|
11224
|
+
function finLearnDetail(body, sym, d) {
|
|
11225
|
+
body.textContent = '';
|
|
11226
|
+
const st = (d && d.stats) || {};
|
|
11227
|
+
const info = document.createElement('div');
|
|
11228
|
+
info.className = 'fin-learn-detail-stats';
|
|
11229
|
+
const trades = Number(st.trades) || 0;
|
|
11230
|
+
const bits = [];
|
|
11231
|
+
if (trades) {
|
|
11232
|
+
bits.push(`${trades} işlem · ${Number(st.wins) || 0} kazanç · ${Number(st.losses) || 0} kayıp`);
|
|
11233
|
+
bits.push(`net ${finLearnSign(st.net)} · en iyi ${finLearnSign(st.bestNet)} · en kötü ${finLearnSign(st.worstNet)}`);
|
|
11234
|
+
bits.push(`MFE toplam ${finLearnSign(st.mfe)} · MAE toplam ${finLearnSign(st.mae)}`);
|
|
11235
|
+
if (Number(st.givebacks) > 0) bits.push(`${Number(st.givebacks)} kez kâr geri verildi (${finLearnSign(st.givebackAmount)})`);
|
|
11236
|
+
} else {
|
|
11237
|
+
bits.push('Bu sembolde henüz kapanan işlem yok — istatistik ilk kapanışta oluşur.');
|
|
11238
|
+
}
|
|
11239
|
+
info.textContent = bits.join(' — ');
|
|
11240
|
+
body.appendChild(info);
|
|
11241
|
+
|
|
11242
|
+
/* KALICI ÖZET (opencode tarzı compaction): eski derslerin modele
|
|
11243
|
+
özetlettirilmiş hâli — ham dersler silinse de bilgi burada kalır */
|
|
11244
|
+
const summ = d && d.summary && d.summary.text ? d.summary : null;
|
|
11245
|
+
if (summ) {
|
|
11246
|
+
const sHead = document.createElement('div');
|
|
11247
|
+
sHead.className = 'fin-learn-sec';
|
|
11248
|
+
sHead.textContent = 'KALICI ÖZET (opencode tarzı sıkıştırma' +
|
|
11249
|
+
(Number(summ.covered) ? ' · ' + Number(summ.covered) + ' eski ders' : '') + ')';
|
|
11250
|
+
body.appendChild(sHead);
|
|
11251
|
+
const sBox = document.createElement('div');
|
|
11252
|
+
sBox.className = 'fin-learn-summary';
|
|
11253
|
+
sBox.textContent = String(summ.text || '');
|
|
11254
|
+
body.appendChild(sBox);
|
|
11255
|
+
const sAt = document.createElement('div');
|
|
11256
|
+
sAt.className = 'fin-learn-summary-at';
|
|
11257
|
+
sAt.textContent = 'güncelleme: ' + finLearnTime(summ.at);
|
|
11258
|
+
body.appendChild(sAt);
|
|
11259
|
+
}
|
|
11260
|
+
|
|
11261
|
+
const notes = (d && Array.isArray(d.notes)) ? d.notes : [];
|
|
11262
|
+
const nHead = document.createElement('div');
|
|
11263
|
+
nHead.className = 'fin-learn-sec';
|
|
11264
|
+
nHead.textContent = 'DERSLER (' + notes.length + ')';
|
|
11265
|
+
body.appendChild(nHead);
|
|
11266
|
+
if (!notes.length) {
|
|
11267
|
+
const e = document.createElement('div');
|
|
11268
|
+
e.className = 'fin-empty';
|
|
11269
|
+
e.textContent = 'Ders yok — ajan her kapanıştan sonra mt5_ogrenme ile buraya yazar.';
|
|
11270
|
+
body.appendChild(e);
|
|
11271
|
+
} else {
|
|
11272
|
+
for (const n of notes.slice().reverse()) {
|
|
11273
|
+
const rowEl = document.createElement('div');
|
|
11274
|
+
rowEl.className = 'fin-learn-note';
|
|
11275
|
+
const kind = document.createElement('span');
|
|
11276
|
+
kind.className = 'fin-learn-kind k-' + String(n.kind || 'observation');
|
|
11277
|
+
kind.textContent = finLearnKindLabel(n.kind);
|
|
11278
|
+
const txt = document.createElement('span');
|
|
11279
|
+
txt.className = 'fin-learn-note-text';
|
|
11280
|
+
txt.textContent = String(n.text || '');
|
|
11281
|
+
txt.title = txt.textContent;
|
|
11282
|
+
const at = document.createElement('span');
|
|
11283
|
+
at.className = 'fin-learn-note-at';
|
|
11284
|
+
at.textContent = finLearnTime(n.at);
|
|
11285
|
+
const del = document.createElement('button');
|
|
11286
|
+
del.type = 'button';
|
|
11287
|
+
del.className = 'fin-learn-note-del';
|
|
11288
|
+
del.title = 'Bu dersi sil — yanlış/geçersiz ders hafızayı zehirlemesin';
|
|
11289
|
+
del.innerHTML = FIN_LEARN_TRASH_SVG;
|
|
11290
|
+
del.addEventListener('click', async () => {
|
|
11291
|
+
const r = await finLearnCall({ action: 'remove', id: n.id, symbol: sym });
|
|
11292
|
+
if (r && r.ok) {
|
|
11293
|
+
toast('Ders silindi');
|
|
11294
|
+
await finLearnRefresh();
|
|
11295
|
+
} else {
|
|
11296
|
+
toast('Ders silinemedi');
|
|
11297
|
+
}
|
|
11298
|
+
});
|
|
11299
|
+
rowEl.appendChild(kind);
|
|
11300
|
+
rowEl.appendChild(txt);
|
|
11301
|
+
rowEl.appendChild(at);
|
|
11302
|
+
rowEl.appendChild(del);
|
|
11303
|
+
body.appendChild(rowEl);
|
|
11304
|
+
}
|
|
11305
|
+
}
|
|
11306
|
+
|
|
11307
|
+
const tradeRows = (d && Array.isArray(d.trades)) ? d.trades : [];
|
|
11308
|
+
if (tradeRows.length) {
|
|
11309
|
+
const tHead = document.createElement('div');
|
|
11310
|
+
tHead.className = 'fin-learn-sec';
|
|
11311
|
+
tHead.textContent = 'SON İŞLEMLER (otomatik)';
|
|
11312
|
+
body.appendChild(tHead);
|
|
11313
|
+
for (const t of tradeRows.slice().reverse()) {
|
|
11314
|
+
const tr = document.createElement('div');
|
|
11315
|
+
tr.className = 'fin-learn-trade ' + (Number(t.net) < 0 ? 'loss' : 'win');
|
|
11316
|
+
const side = String(t.side || '').toLowerCase();
|
|
11317
|
+
const sideTxt = side === 'buy' ? 'AL' : side === 'sell' ? 'SAT' : (side || '?');
|
|
11318
|
+
tr.textContent = `${finLearnTime(t.at)} · ${sideTxt} · ${finLearnSign(t.net)}` +
|
|
11319
|
+
(t.mfe != null ? ` · MFE ${finLearnSign(t.mfe)}` : '') +
|
|
11320
|
+
(t.mae != null ? ` · MAE ${finLearnSign(t.mae)}` : '') +
|
|
11321
|
+
(t.reason ? ` · ${String(t.reason)}` : '');
|
|
11322
|
+
body.appendChild(tr);
|
|
11323
|
+
}
|
|
11324
|
+
}
|
|
11325
|
+
}
|
|
11326
|
+
|
|
11327
|
+
if (els.finLearnBtn) els.finLearnBtn.addEventListener('click', () => { finLearnOpen(); });
|
|
11328
|
+
if (els.finLearnClose) els.finLearnClose.addEventListener('click', finLearnCloseModal);
|
|
11329
|
+
if (els.finLearnOverlay) {
|
|
11330
|
+
els.finLearnOverlay.addEventListener('click', (e) => {
|
|
11331
|
+
if (e.target === els.finLearnOverlay) finLearnCloseModal();
|
|
11332
|
+
});
|
|
11333
|
+
els.finLearnOverlay.addEventListener('keydown', (e) => {
|
|
11334
|
+
if (e.key === 'Escape') { e.stopPropagation(); finLearnCloseModal(); }
|
|
11335
|
+
});
|
|
11336
|
+
}
|
|
11337
|
+
if (els.finLearnClear) {
|
|
11338
|
+
els.finLearnClear.addEventListener('click', async () => {
|
|
11339
|
+
const ok = await uiConfirm('TÜM sembollerin öğrenme DERSLERİ silinsin mi? Otomatik istatistikler korunur; işlem geri alınamaz.', 'Tümünü sil', 'Vazgeç');
|
|
11340
|
+
if (!ok) return;
|
|
11341
|
+
const r = await finLearnCall({ action: 'clear', all: true });
|
|
11342
|
+
toast(r && r.ok ? ('Dersler temizlendi' + (r.removed ? ' (' + r.removed + ' kayıt)' : '')) : 'Dersler temizlenemedi');
|
|
11343
|
+
await finLearnRefresh();
|
|
11344
|
+
});
|
|
11345
|
+
}
|
|
11346
|
+
|
|
11033
11347
|
if (els.finSymAdd) els.finSymAdd.addEventListener('click', finSymPickerOpen);
|
|
11034
11348
|
if (els.finWatchClear) {
|
|
11035
11349
|
els.finWatchClear.addEventListener('click', async () => {
|
package/src/renderer/style.css
CHANGED
|
@@ -5843,6 +5843,121 @@ body.finance-mode #composerWrap { margin-right: var(--finW, 400px); }
|
|
|
5843
5843
|
.fin-skill-chip.on { border-color: var(--accent); color: var(--accent); background: var(--accent-dim); }
|
|
5844
5844
|
.fin-skill-chip input { flex: none; width: auto; margin: 0; }
|
|
5845
5845
|
|
|
5846
|
+
/* MT5 ÖĞRENME HAFIZASI modalı (TRADE AJANI 🧠) */
|
|
5847
|
+
.fin-learn-dialog { width: 580px; max-width: calc(100vw - 40px); }
|
|
5848
|
+
.fin-learn-dialog .mini-body {
|
|
5849
|
+
flex: 1;
|
|
5850
|
+
min-height: 0;
|
|
5851
|
+
display: flex;
|
|
5852
|
+
flex-direction: column;
|
|
5853
|
+
overflow: hidden;
|
|
5854
|
+
}
|
|
5855
|
+
#finLearnList {
|
|
5856
|
+
flex: 1;
|
|
5857
|
+
min-height: 0;
|
|
5858
|
+
overflow-y: auto;
|
|
5859
|
+
display: flex;
|
|
5860
|
+
flex-direction: column;
|
|
5861
|
+
gap: 8px;
|
|
5862
|
+
}
|
|
5863
|
+
#finLearnClear {
|
|
5864
|
+
flex: none;
|
|
5865
|
+
border: 1px solid var(--border);
|
|
5866
|
+
background: var(--panel2);
|
|
5867
|
+
color: var(--muted);
|
|
5868
|
+
font-size: 10.5px;
|
|
5869
|
+
font-weight: 700;
|
|
5870
|
+
border-radius: 7px;
|
|
5871
|
+
padding: 4px 8px;
|
|
5872
|
+
cursor: pointer;
|
|
5873
|
+
margin-right: 6px;
|
|
5874
|
+
}
|
|
5875
|
+
#finLearnClear:hover { color: #ef4444; border-color: #ef4444; }
|
|
5876
|
+
.fin-learn-sym { border: 1px solid var(--border); border-radius: 8px; padding: 7px 9px; background: var(--panel2); }
|
|
5877
|
+
.fin-learn-sym-head { display: flex; align-items: center; gap: 7px; cursor: pointer; }
|
|
5878
|
+
.fin-learn-sym-head b { flex: none; font-size: 12px; color: var(--accent); }
|
|
5879
|
+
.fin-learn-sym-stats {
|
|
5880
|
+
flex: 1;
|
|
5881
|
+
min-width: 0;
|
|
5882
|
+
font-size: 10px;
|
|
5883
|
+
color: var(--muted);
|
|
5884
|
+
overflow: hidden;
|
|
5885
|
+
text-overflow: ellipsis;
|
|
5886
|
+
white-space: nowrap;
|
|
5887
|
+
}
|
|
5888
|
+
.fin-learn-sym-notes { flex: none; font-size: 9.5px; font-weight: 800; letter-spacing: 0.4px; color: var(--muted); }
|
|
5889
|
+
.fin-learn-sum-btn {
|
|
5890
|
+
flex: none;
|
|
5891
|
+
border: 1px solid var(--border);
|
|
5892
|
+
background: var(--panel);
|
|
5893
|
+
color: var(--muted);
|
|
5894
|
+
font-size: 9px;
|
|
5895
|
+
font-weight: 800;
|
|
5896
|
+
letter-spacing: 0.4px;
|
|
5897
|
+
border-radius: 5px;
|
|
5898
|
+
padding: 2px 6px;
|
|
5899
|
+
cursor: pointer;
|
|
5900
|
+
}
|
|
5901
|
+
.fin-learn-sum-btn:hover { color: var(--accent); border-color: var(--accent); }
|
|
5902
|
+
.fin-learn-sum-btn:disabled { opacity: 0.5; cursor: default; }
|
|
5903
|
+
.fin-learn-summary {
|
|
5904
|
+
border: 1px solid var(--border);
|
|
5905
|
+
border-left: 3px solid var(--accent);
|
|
5906
|
+
border-radius: 6px;
|
|
5907
|
+
background: var(--panel);
|
|
5908
|
+
padding: 6px 8px;
|
|
5909
|
+
font-size: 10.5px;
|
|
5910
|
+
color: var(--text);
|
|
5911
|
+
line-height: 1.45;
|
|
5912
|
+
word-break: break-word;
|
|
5913
|
+
}
|
|
5914
|
+
.fin-learn-summary-at { font-size: 9px; color: var(--muted); margin: 3px 0 2px; }
|
|
5915
|
+
.fin-learn-drop,
|
|
5916
|
+
.fin-learn-note-del {
|
|
5917
|
+
flex: none;
|
|
5918
|
+
border: none;
|
|
5919
|
+
background: none;
|
|
5920
|
+
color: var(--muted);
|
|
5921
|
+
line-height: 0;
|
|
5922
|
+
padding: 3px 4px;
|
|
5923
|
+
border-radius: 6px;
|
|
5924
|
+
cursor: pointer;
|
|
5925
|
+
}
|
|
5926
|
+
.fin-learn-drop:hover,
|
|
5927
|
+
.fin-learn-note-del:hover { color: #ef4444; background: var(--accent-dim); }
|
|
5928
|
+
.fin-learn-body { margin-top: 6px; border-top: 1px dashed var(--border); padding-top: 6px; }
|
|
5929
|
+
.fin-learn-detail-stats { font-size: 10px; color: var(--muted); margin-bottom: 6px; }
|
|
5930
|
+
.fin-learn-sec { font-size: 9.5px; font-weight: 800; letter-spacing: 0.5px; color: var(--muted); margin: 6px 0 4px; }
|
|
5931
|
+
.fin-learn-note {
|
|
5932
|
+
display: flex;
|
|
5933
|
+
align-items: flex-start;
|
|
5934
|
+
gap: 6px;
|
|
5935
|
+
padding: 4px 5px;
|
|
5936
|
+
border-radius: 6px;
|
|
5937
|
+
background: var(--panel);
|
|
5938
|
+
border: 1px solid var(--border);
|
|
5939
|
+
margin-bottom: 4px;
|
|
5940
|
+
}
|
|
5941
|
+
.fin-learn-kind {
|
|
5942
|
+
flex: none;
|
|
5943
|
+
font-size: 9px;
|
|
5944
|
+
font-weight: 800;
|
|
5945
|
+
letter-spacing: 0.4px;
|
|
5946
|
+
border-radius: 5px;
|
|
5947
|
+
padding: 2px 5px;
|
|
5948
|
+
border: 1px solid var(--border);
|
|
5949
|
+
color: var(--muted);
|
|
5950
|
+
text-transform: uppercase;
|
|
5951
|
+
}
|
|
5952
|
+
.fin-learn-kind.k-pattern { color: #22c55e; border-color: #22c55e; }
|
|
5953
|
+
.fin-learn-kind.k-mistake { color: #ef4444; border-color: #ef4444; }
|
|
5954
|
+
.fin-learn-kind.k-rule { color: #e6a117; border-color: #e6a117; }
|
|
5955
|
+
.fin-learn-note-text { flex: 1; min-width: 0; font-size: 10.5px; color: var(--text); word-break: break-word; }
|
|
5956
|
+
.fin-learn-note-at { flex: none; font-size: 9px; color: var(--muted); }
|
|
5957
|
+
.fin-learn-trade { font-size: 10px; color: var(--muted); padding: 2px 4px; border-radius: 5px; }
|
|
5958
|
+
.fin-learn-trade.win { color: #22c55e; }
|
|
5959
|
+
.fin-learn-trade.loss { color: #ef4444; }
|
|
5960
|
+
|
|
5846
5961
|
/* FINANCE GÖRÜNÜM AYARLARI modalı (başlıktaki ⚙) — isteğe bağlı bölümler */
|
|
5847
5962
|
.fin-card[hidden] { display: none; }
|
|
5848
5963
|
.fin-settings-dialog { width: min(440px, 90vw); height: auto; max-height: min(560px, 84vh); }
|