beast-agent 2.76.6 → 2.77.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/agent/bots.js +669 -664
- package/src/agent/customtools.js +6 -19
- package/src/agent/engine.js +55 -307
- package/src/agent/seeds/tool-yazma/SKILL.md +13 -16
- package/src/agent/skills.js +14 -35
- package/src/agent/supertonic.js +622 -0
- package/src/agent/tools.js +3 -7
- package/src/main.js +77 -6164
- package/src/preload.js +2 -18
- package/src/renderer/i18n.js +14 -8
- package/src/renderer/index.html +0 -297
- package/src/renderer/renderer.js +142 -2301
- package/src/renderer/style.css +3 -825
- package/src/agent/defaulttools/mt5_barlar2/run.js +0 -188
- package/src/agent/defaulttools/mt5_barlar2/tool.json +0 -23
- package/src/agent/defaulttools/mt5_bekleyen/run.js +0 -105
- package/src/agent/defaulttools/mt5_bekleyen/tool.json +0 -18
- package/src/agent/defaulttools/mt5_durum/run.js +0 -93
- package/src/agent/defaulttools/mt5_durum/tool.json +0 -10
- package/src/agent/defaulttools/mt5_emir/run.js +0 -98
- package/src/agent/defaulttools/mt5_emir/tool.json +0 -17
- package/src/agent/defaulttools/mt5_emir_iptal/run.js +0 -90
- package/src/agent/defaulttools/mt5_emir_iptal/tool.json +0 -12
- package/src/agent/defaulttools/mt5_fiyat/run.js +0 -87
- package/src/agent/defaulttools/mt5_fiyat/tool.json +0 -16
- package/src/agent/defaulttools/mt5_gecmis/run.js +0 -87
- package/src/agent/defaulttools/mt5_gecmis/tool.json +0 -12
- package/src/agent/defaulttools/mt5_kapat/run.js +0 -108
- package/src/agent/defaulttools/mt5_kapat/tool.json +0 -15
- package/src/agent/defaulttools/mt5_m15_m5/m15m5.py +0 -60
- package/src/agent/defaulttools/mt5_m15_m5/run.js +0 -73
- package/src/agent/defaulttools/mt5_m15_m5/tool.json +0 -10
- package/src/agent/defaulttools/mt5_pozisyon_gecmis/run.js +0 -144
- package/src/agent/defaulttools/mt5_pozisyon_gecmis/tool.json +0 -12
- package/src/agent/defaulttools/mt5_shot/run.js +0 -649
- package/src/agent/defaulttools/mt5_shot/stitch.js +0 -151
- package/src/agent/defaulttools/mt5_shot/tool.json +0 -25
- package/src/agent/defaulttools/mt5_sltp/run.js +0 -92
- package/src/agent/defaulttools/mt5_sltp/tool.json +0 -14
- package/src/agent/financetools.js +0 -1025
- package/src/agent/finindicators.js +0 -254
- package/src/agent/finrisk.js +0 -289
- package/src/agent/finstats.js +0 -239
- package/src/agent/finwatch.js +0 -302
- package/src/agent/mt5/BeastFinance.mq5 +0 -498
- package/src/agent/mt5/BeastShot.tpl +0 -0
- package/src/agent/mt5setup.js +0 -542
- package/src/agent/scripts/mt5_bridge.py +0 -575
- package/src/agent/seeds/haber-duygu/SKILL.md +0 -49
- package/src/agent/seeds/mql5/SKILL.md +0 -154
- package/src/agent/seeds/price-action/SKILL.md +0 -60
- package/src/agent/seeds/risk-yonetimi/SKILL.md +0 -52
- package/src/mt5bridge.js +0 -215
package/src/agent/mt5setup.js
DELETED
|
@@ -1,542 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/* BEAST FINANCE — MT5 İLK KURULUM (otomatik)
|
|
4
|
-
Köprü ilk kez bağlandığında çalışır ve şu adımları idempotent uygular:
|
|
5
|
-
1) BeastFinance.mq5 EA'sını <data_path>\MQL5\Experts\Beast\ altına yazar
|
|
6
|
-
2) metaeditor64.exe ile derler (ex5 yok/bayat ise)
|
|
7
|
-
3) config\common.ini [Experts] bölümünü açar:
|
|
8
|
-
AllowLiveTrading=1, AllowDllImport=1, Enabled=1, Api=0
|
|
9
|
-
(AutoTrading düğmesi + Python API işlem izni terminal başlangıcında etkin olur)
|
|
10
|
-
4) Aktif profil grafiğine <expert> bloğu enjekte eder (terminal açılışta EA'yı yükler)
|
|
11
|
-
+ MQL5\Profiles\Templates\BeastFinance.tpl şablonu bırakır
|
|
12
|
-
Not: MT5 çalışırken yazılan bu ayarlar BİR SONRAKİ terminal başlangıcında etkinleşir;
|
|
13
|
-
Python API ile grafiğe EA iliştirmenin resmi yolu olmadığından profil enjeksiyonu kullanılır. */
|
|
14
|
-
|
|
15
|
-
const fs = require('fs');
|
|
16
|
-
const path = require('path');
|
|
17
|
-
const { spawnSync, execSync } = require('child_process');
|
|
18
|
-
|
|
19
|
-
const EA_NAME = 'BeastFinance';
|
|
20
|
-
const EA_REL = path.join('Beast', EA_NAME + '.mq5');
|
|
21
|
-
|
|
22
|
-
function bundledEaFile() {
|
|
23
|
-
return path.join(__dirname, 'mt5', EA_NAME + '.mq5');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function eaSource() {
|
|
27
|
-
try { return fs.readFileSync(bundledEaFile(), 'utf8'); } catch { return ''; }
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/* ---------- metin/encoding yardımcıları ---------- */
|
|
31
|
-
|
|
32
|
-
/* .chr/.tpl dosyaları UTF-16 LE (BOM'lu) olabilir; güvenli çöz/kodla */
|
|
33
|
-
function decodeText(buf) {
|
|
34
|
-
if (buf && buf.length >= 2 && buf[0] === 0xff && buf[1] === 0xfe) {
|
|
35
|
-
return { text: buf.toString('utf16le').replace(/^\uFEFF/, ''), utf16: true };
|
|
36
|
-
}
|
|
37
|
-
return { text: buf.toString('utf8').replace(/^\uFEFF/, ''), utf16: false };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function encodeText(text, utf16) {
|
|
41
|
-
return utf16 ? Buffer.from('\uFEFF' + text, 'utf16le') : Buffer.from(text, 'utf8');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/* ---------- common.ini [Experts] ---------- */
|
|
45
|
-
|
|
46
|
-
/* MT5 config ini'leri UTF-16 LE (BOM'lu/BOM'suz) olabilir; önceki sürüm
|
|
47
|
-
dosyayı UTF-8 sanıp yazınca bozulabiliyordu. Güvenli çöz: NUL yoğunluğuna
|
|
48
|
-
göre UTF-16 tespit et, baştaki/sondaki bozuk satırları temizle. */
|
|
49
|
-
function decodeIni(buf) {
|
|
50
|
-
if (!buf || !buf.length) return { text: '', utf16: false };
|
|
51
|
-
/* BİLİNEN BOZULMA ONARIMI: eski sürüm UTF-16 dosyayı UTF-8 sanıp yazınca her
|
|
52
|
-
satır sonuna fazladan 0D baytı girmişti → 0D 00 0D 0A 00 : 0D 00 0A 00 */
|
|
53
|
-
const corrupt = Buffer.from([0x0d, 0x00, 0x0d, 0x0a, 0x00]);
|
|
54
|
-
if (buf.includes(corrupt)) {
|
|
55
|
-
const out = [];
|
|
56
|
-
for (let i = 0; i < buf.length; i++) {
|
|
57
|
-
if (
|
|
58
|
-
i + 4 < buf.length &&
|
|
59
|
-
buf[i] === 0x0d && buf[i + 1] === 0x00 && buf[i + 2] === 0x0d && buf[i + 3] === 0x0a && buf[i + 4] === 0x00
|
|
60
|
-
) {
|
|
61
|
-
out.push(0x0d, 0x00, 0x0a, 0x00);
|
|
62
|
-
i += 4;
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
out.push(buf[i]);
|
|
66
|
-
}
|
|
67
|
-
buf = Buffer.from(out);
|
|
68
|
-
}
|
|
69
|
-
let utf16 = false;
|
|
70
|
-
let text = '';
|
|
71
|
-
if (buf.length >= 2 && buf[0] === 0xff && buf[1] === 0xfe) {
|
|
72
|
-
utf16 = true;
|
|
73
|
-
text = buf.toString('utf16le').replace(/^\uFEFF/, '');
|
|
74
|
-
} else {
|
|
75
|
-
let nul = 0;
|
|
76
|
-
const n = Math.min(buf.length, 512);
|
|
77
|
-
for (let i = 0; i < n; i++) if (buf[i] === 0) nul++;
|
|
78
|
-
utf16 = nul > n / 4;
|
|
79
|
-
text = utf16 ? buf.toString('utf16le') : buf.toString('utf8');
|
|
80
|
-
}
|
|
81
|
-
/* baştaki bozuk karakterleri at (ilk [ veya satır sonuna kadar) */
|
|
82
|
-
const first = text.search(/\r|\n|\[/);
|
|
83
|
-
if (first > 0) text = text.slice(first);
|
|
84
|
-
/* satır temizliği: yalnız [Bölüm] ve key=value satırları kalsın
|
|
85
|
-
(eski bozuk yazımdan kalan ASCII/CJK çöp satırları düşer) */
|
|
86
|
-
const keep = [];
|
|
87
|
-
for (const line of text.replace(/\r\n/g, '\n').split('\n')) {
|
|
88
|
-
const t = line.replace(/\uFFFD/g, '').replace(/\u0000/g, '').trim();
|
|
89
|
-
if (!t) { keep.push(''); continue; }
|
|
90
|
-
if (/^\[[^\]]+\]$/.test(t) || /^[A-Za-z0-9_]+\s*=/.test(t)) keep.push(t);
|
|
91
|
-
}
|
|
92
|
-
return { text: keep.join('\r\n'), utf16 };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function encodeIni(text, utf16) {
|
|
96
|
-
return utf16 ? Buffer.from('\uFEFF' + String(text || ''), 'utf16le') : Buffer.from(String(text || ''), 'utf8');
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const EXPERTS_WANT = { AllowLiveTrading: '1', AllowDllImport: '1', Enabled: '1', Api: '0' };
|
|
100
|
-
|
|
101
|
-
/* [Experts] bölümünü istenen değerlere çeker; bölüm yoksa ekler. */
|
|
102
|
-
function patchExpertsIni(text) {
|
|
103
|
-
const src = String(text || '').replace(/\r\n/g, '\n');
|
|
104
|
-
const lines = src.split('\n');
|
|
105
|
-
const out = [];
|
|
106
|
-
let inExperts = false;
|
|
107
|
-
let found = false;
|
|
108
|
-
const seen = new Set();
|
|
109
|
-
const flushMissing = () => {
|
|
110
|
-
for (const [k, v] of Object.entries(EXPERTS_WANT)) {
|
|
111
|
-
if (!seen.has(k)) { out.push(k + '=' + v); seen.add(k); }
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
for (const line of lines) {
|
|
115
|
-
const sec = line.match(/^\s*\[([^\]]+)\]\s*$/);
|
|
116
|
-
if (sec) {
|
|
117
|
-
if (inExperts) flushMissing();
|
|
118
|
-
inExperts = sec[1].trim().toLowerCase() === 'experts';
|
|
119
|
-
if (inExperts) found = true;
|
|
120
|
-
out.push(line);
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (inExperts) {
|
|
124
|
-
const kv = line.match(/^\s*([A-Za-z0-9_]+)\s*=/);
|
|
125
|
-
if (kv && Object.prototype.hasOwnProperty.call(EXPERTS_WANT, kv[1])) {
|
|
126
|
-
out.push(kv[1] + '=' + EXPERTS_WANT[kv[1]]);
|
|
127
|
-
seen.add(kv[1]);
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
out.push(line);
|
|
132
|
-
}
|
|
133
|
-
if (inExperts) flushMissing();
|
|
134
|
-
if (!found) {
|
|
135
|
-
if (out.length && out[out.length - 1].trim() !== '') out.push('');
|
|
136
|
-
out.push('[Experts]');
|
|
137
|
-
for (const [k, v] of Object.entries(EXPERTS_WANT)) out.push(k + '=' + v);
|
|
138
|
-
}
|
|
139
|
-
const next = out.join('\r\n');
|
|
140
|
-
return { text: next, changed: next !== String(text || '') };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function readIniValue(text, section, key) {
|
|
144
|
-
const reSec = new RegExp('^\\s*\\[' + section + '\\]\\s*$', 'im');
|
|
145
|
-
const m = reSec.exec(String(text || ''));
|
|
146
|
-
if (!m) return '';
|
|
147
|
-
const rest = String(text).slice(m.index + m[0].length).split(/\r?\n/);
|
|
148
|
-
for (const line of rest) {
|
|
149
|
-
if (/^\s*\[/.test(line)) break;
|
|
150
|
-
const kv = line.match(new RegExp('^\\s*' + key + '\\s*=\\s*(.*)$', 'i'));
|
|
151
|
-
if (kv) return String(kv[1]).trim();
|
|
152
|
-
}
|
|
153
|
-
return '';
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/* ---------- grafik .chr <expert> enjeksiyonu ----------
|
|
157
|
-
GERÇEK MT5 formatı (terminalin kendi kaydettiği tpl/chr dosyalarından):
|
|
158
|
-
blok <window>'dan ÖNCE gelir; name/path/expertmode + <inputs> kullanır.
|
|
159
|
-
<expert>
|
|
160
|
-
name=BeastFinance
|
|
161
|
-
path=Experts\Beast\BeastFinance.ex5
|
|
162
|
-
expertmode=5
|
|
163
|
-
<inputs>...</inputs>
|
|
164
|
-
</expert> */
|
|
165
|
-
|
|
166
|
-
function expertBlock(eaName) {
|
|
167
|
-
const full = String(eaName || 'BeastFinance');
|
|
168
|
-
const parts = full.split(/[\\/]/).filter(Boolean);
|
|
169
|
-
const name = parts[parts.length - 1] || 'BeastFinance';
|
|
170
|
-
const folder = parts.slice(0, -1).join('\\');
|
|
171
|
-
const rel = 'Experts\\' + (folder ? folder + '\\' : '') + name + '.ex5';
|
|
172
|
-
return (
|
|
173
|
-
'<expert>\r\n' +
|
|
174
|
-
'name=' + name + '\r\n' +
|
|
175
|
-
'path=' + rel + '\r\n' +
|
|
176
|
-
'expertmode=5\r\n' +
|
|
177
|
-
'<inputs>\r\n' +
|
|
178
|
-
'InpTag=' + name + '\r\n' +
|
|
179
|
-
'InpHeartbeat=5\r\n' +
|
|
180
|
-
'InpVerbose=true\r\n' +
|
|
181
|
-
'InpMagic=20260910\r\n' +
|
|
182
|
-
'</inputs>\r\n' +
|
|
183
|
-
'</expert>\r\n'
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/* Grafiğe EA bloğunu ekler/günceller. Başka bir EA varsa DOKUNMAZ.
|
|
188
|
-
Bizim eski yanlış format/konumdaki bloğumuz varsa düzeltir.
|
|
189
|
-
Dönüş: { text, changed, existing, ours } */
|
|
190
|
-
function injectExpertBlock(chrText, eaName) {
|
|
191
|
-
const original = String(chrText || '');
|
|
192
|
-
const block = expertBlock(eaName);
|
|
193
|
-
const norm = (s) => String(s || '').replace(/\r\n/g, '\n').replace(/[ \t]+/g, ' ').trim();
|
|
194
|
-
const m = original.match(/<expert>[\s\S]*?<\/expert>\r?\n?/i);
|
|
195
|
-
const winIdx = original.search(/<window>/i);
|
|
196
|
-
|
|
197
|
-
if (m) {
|
|
198
|
-
const isOurs = /BeastFinance/i.test(m[0]);
|
|
199
|
-
if (!isOurs) return { text: original, changed: false, existing: true, ours: false };
|
|
200
|
-
const mIdx = original.indexOf(m[0]);
|
|
201
|
-
const posOk = winIdx < 0 || mIdx < winIdx;
|
|
202
|
-
if (posOk && norm(m[0]) === norm(block)) {
|
|
203
|
-
return { text: original, changed: false, existing: false, ours: true };
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/* (varsa) eski bizim bloğu çıkar */
|
|
208
|
-
let text = m && /BeastFinance/i.test(m[0]) ? original.replace(m[0], '') : original;
|
|
209
|
-
const wi = text.search(/<window>/i);
|
|
210
|
-
if (wi >= 0) {
|
|
211
|
-
const head = text.slice(0, wi).replace(/(?:\r?\n)+$/, '\r\n');
|
|
212
|
-
text = head + block + '\r\n' + text.slice(wi);
|
|
213
|
-
} else {
|
|
214
|
-
const ci = text.lastIndexOf('</chart>');
|
|
215
|
-
if (ci < 0) return { text: original, changed: false, existing: false, ours: false };
|
|
216
|
-
text = text.slice(0, ci).replace(/(?:\r?\n)+$/, '\r\n') + block + '\r\n' + text.slice(ci);
|
|
217
|
-
}
|
|
218
|
-
return { text, changed: true, existing: false, ours: true };
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/* ---------- terminal yardımcıları ---------- */
|
|
222
|
-
|
|
223
|
-
function findMetaEditor(terminalExe, dataPath) {
|
|
224
|
-
const cands = [];
|
|
225
|
-
const add = (p) => { if (p) cands.push(p); };
|
|
226
|
-
/* 1) terminal64.exe ile aynı klasör (en güvenilir) */
|
|
227
|
-
if (terminalExe) add(path.join(path.dirname(terminalExe), 'metaeditor64.exe'));
|
|
228
|
-
/* 2) veri klasöründeki origin.txt → kurulum yolu (her broker için doğru) */
|
|
229
|
-
if (dataPath) {
|
|
230
|
-
try {
|
|
231
|
-
const origin = decodeOrigin(fs.readFileSync(path.join(dataPath, 'origin.txt')));
|
|
232
|
-
if (origin) add(path.join(origin, 'metaeditor64.exe'));
|
|
233
|
-
} catch {}
|
|
234
|
-
}
|
|
235
|
-
/* 3) çalışan terminal sürecinin klasörü */
|
|
236
|
-
try {
|
|
237
|
-
const p = execSync(
|
|
238
|
-
'powershell -NoProfile -Command "(Get-Process terminal64 -ErrorAction SilentlyContinue | Select-Object -First 1).Path"',
|
|
239
|
-
{ windowsHide: true, timeout: 8000 }
|
|
240
|
-
).toString().trim();
|
|
241
|
-
if (p && /terminal64\.exe$/i.test(p)) add(path.join(path.dirname(p), 'metaeditor64.exe'));
|
|
242
|
-
} catch {}
|
|
243
|
-
/* 4) Program Files altındaki broker klasörleri (2 seviye — her kurulum yolu için) */
|
|
244
|
-
for (const base of [process.env.ProgramFiles, process.env['ProgramFiles(x86)']].filter(Boolean)) {
|
|
245
|
-
try {
|
|
246
|
-
for (const e of fs.readdirSync(base, { withFileTypes: true })) {
|
|
247
|
-
if (!e.isDirectory()) continue;
|
|
248
|
-
add(path.join(base, e.name, 'metaeditor64.exe'));
|
|
249
|
-
try {
|
|
250
|
-
for (const e2 of fs.readdirSync(path.join(base, e.name), { withFileTypes: true })) {
|
|
251
|
-
if (e2.isDirectory()) add(path.join(base, e.name, e2.name, 'metaeditor64.exe'));
|
|
252
|
-
}
|
|
253
|
-
} catch {}
|
|
254
|
-
}
|
|
255
|
-
} catch {}
|
|
256
|
-
}
|
|
257
|
-
for (const c of cands) {
|
|
258
|
-
try { if (fs.existsSync(c)) return c; } catch {}
|
|
259
|
-
}
|
|
260
|
-
return '';
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function terminalRunning() {
|
|
264
|
-
try {
|
|
265
|
-
const out = execSync('tasklist /FI "IMAGENAME eq terminal64.exe" /NH', { windowsHide: true, timeout: 5000 }).toString();
|
|
266
|
-
return /terminal64\.exe/i.test(out);
|
|
267
|
-
} catch {
|
|
268
|
-
return false;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function decodeOrigin(buf) {
|
|
273
|
-
try {
|
|
274
|
-
const a = buf.toString('utf16le').replace(/\u0000/g, '').replace(/^\uFEFF/, '').trim();
|
|
275
|
-
if (a && /[\\/]/.test(a)) return a;
|
|
276
|
-
const b = buf.toString('utf8').replace(/\u0000/g, '').replace(/^\uFEFF/, '').trim();
|
|
277
|
-
return b;
|
|
278
|
-
} catch {
|
|
279
|
-
return '';
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/* Terminal kurulum yolunu ve veri klasörünü köprü OLMADAN bulur:
|
|
284
|
-
- çalışan terminal64.exe sürecinin yolu
|
|
285
|
-
- %APPDATA%\MetaQuotes\Terminal\<hash>\origin.txt eşleşmesi (hash → kurulum yolu)
|
|
286
|
-
En son kullanılan veri klasörünü döndürür; MT5 KAPALIYKEN de çalışır. */
|
|
287
|
-
function detectTerminal() {
|
|
288
|
-
const out = { exe: '', dataPath: '', dirs: [] };
|
|
289
|
-
try {
|
|
290
|
-
const p = execSync(
|
|
291
|
-
'powershell -NoProfile -Command "(Get-Process terminal64 -ErrorAction SilentlyContinue | Select-Object -First 1).Path"',
|
|
292
|
-
{ windowsHide: true, timeout: 8000 }
|
|
293
|
-
).toString().trim();
|
|
294
|
-
if (p && /terminal64\.exe$/i.test(p)) out.exe = p;
|
|
295
|
-
} catch {}
|
|
296
|
-
const root = process.env.APPDATA ? path.join(process.env.APPDATA, 'MetaQuotes', 'Terminal') : '';
|
|
297
|
-
try {
|
|
298
|
-
for (const e of fs.readdirSync(root, { withFileTypes: true })) {
|
|
299
|
-
if (!e.isDirectory()) continue;
|
|
300
|
-
const dir = path.join(root, e.name);
|
|
301
|
-
let origin = '';
|
|
302
|
-
try { origin = decodeOrigin(fs.readFileSync(path.join(dir, 'origin.txt'))); } catch {}
|
|
303
|
-
let at = 0;
|
|
304
|
-
try { at = fs.statSync(path.join(dir, 'config', 'common.ini')).mtimeMs; } catch {}
|
|
305
|
-
if (!origin && !at) continue;
|
|
306
|
-
out.dirs.push({ dir, origin, at });
|
|
307
|
-
}
|
|
308
|
-
} catch {}
|
|
309
|
-
out.dirs.sort((a, b) => (b.at || 0) - (a.at || 0));
|
|
310
|
-
if (!out.exe && out.dirs.length && out.dirs[0].origin) {
|
|
311
|
-
const guess = path.join(out.dirs[0].origin, 'terminal64.exe');
|
|
312
|
-
try { if (fs.existsSync(guess)) out.exe = guess; } catch {}
|
|
313
|
-
}
|
|
314
|
-
if (out.exe) {
|
|
315
|
-
const exeDir = path.dirname(out.exe).toLowerCase();
|
|
316
|
-
const hit = out.dirs.find((d) => String(d.origin || '').toLowerCase().startsWith(exeDir));
|
|
317
|
-
if (hit) out.dataPath = hit.dir;
|
|
318
|
-
}
|
|
319
|
-
if (!out.dataPath && out.dirs.length) out.dataPath = out.dirs[0].dir;
|
|
320
|
-
return out;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function newestChart(dir) {
|
|
324
|
-
let best = '';
|
|
325
|
-
let bestAt = 0;
|
|
326
|
-
try {
|
|
327
|
-
for (const f of fs.readdirSync(dir)) {
|
|
328
|
-
if (!/\.chr$/i.test(f)) continue;
|
|
329
|
-
const p = path.join(dir, f);
|
|
330
|
-
const at = fs.statSync(p).mtimeMs;
|
|
331
|
-
if (at > bestAt) { bestAt = at; best = p; }
|
|
332
|
-
}
|
|
333
|
-
} catch {}
|
|
334
|
-
return best;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/* ---------- ana akış ---------- */
|
|
338
|
-
|
|
339
|
-
function runSetup({ dataPath, terminalExe = '', force = false } = {}) {
|
|
340
|
-
const steps = [];
|
|
341
|
-
const result = { ok: false, changed: false, restartRequired: false, chartAttached: false, autoTrading: false, compiled: false, eaPath: '', error: '' };
|
|
342
|
-
try {
|
|
343
|
-
const data = String(dataPath || '').trim();
|
|
344
|
-
if (!data || !fs.existsSync(data)) {
|
|
345
|
-
result.error = 'MT5 veri klasörü bulunamadı: ' + (data || '(boş)');
|
|
346
|
-
return result;
|
|
347
|
-
}
|
|
348
|
-
const mql5 = path.join(data, 'MQL5');
|
|
349
|
-
if (!fs.existsSync(mql5)) {
|
|
350
|
-
result.error = 'MT5 MQL5 klasörü yok: ' + mql5;
|
|
351
|
-
return result;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/* 1) EA kaynağı */
|
|
355
|
-
const eaDir = path.join(mql5, 'Experts', 'Beast');
|
|
356
|
-
const eaFile = path.join(eaDir, EA_NAME + '.mq5');
|
|
357
|
-
const ex5File = path.join(eaDir, EA_NAME + '.ex5');
|
|
358
|
-
result.eaPath = eaFile;
|
|
359
|
-
const src = eaSource();
|
|
360
|
-
if (!src) {
|
|
361
|
-
result.error = 'Gömülü EA kaynağı okunamadı: ' + bundledEaFile();
|
|
362
|
-
return result;
|
|
363
|
-
}
|
|
364
|
-
let srcChanged = force;
|
|
365
|
-
try {
|
|
366
|
-
const cur = fs.existsSync(eaFile) ? fs.readFileSync(eaFile, 'utf8') : '';
|
|
367
|
-
if (cur.trim() !== src.trim()) srcChanged = true;
|
|
368
|
-
} catch { srcChanged = true; }
|
|
369
|
-
if (srcChanged) {
|
|
370
|
-
fs.mkdirSync(eaDir, { recursive: true });
|
|
371
|
-
fs.writeFileSync(eaFile, src, 'utf8');
|
|
372
|
-
steps.push('EA kaynağı yazıldı: ' + eaFile);
|
|
373
|
-
result.changed = true;
|
|
374
|
-
} else {
|
|
375
|
-
steps.push('EA kaynağı güncel: ' + eaFile);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/* 2) Derleme */
|
|
379
|
-
const needsCompile = force || srcChanged || !fs.existsSync(ex5File);
|
|
380
|
-
if (needsCompile) {
|
|
381
|
-
const me = findMetaEditor(String(terminalExe || ''), data);
|
|
382
|
-
if (!me) {
|
|
383
|
-
steps.push('MetaEditor bulunamadı — EA derlenmedi (MT5 içinden derleyin: ' + eaFile + ')');
|
|
384
|
-
} else {
|
|
385
|
-
let log = path.join(eaDir, EA_NAME + '.compile.log');
|
|
386
|
-
try { fs.rmSync(log, { force: true }); } catch {}
|
|
387
|
-
const q = (s) => '"' + String(s) + '"';
|
|
388
|
-
let r = spawnSync(me, ['/compile:' + q(eaFile), '/log:' + q(log)], { windowsHide: true, timeout: 90000 });
|
|
389
|
-
let ex5Ok = fs.existsSync(ex5File);
|
|
390
|
-
/* BOŞLUKLU YOL GÜVENLİĞİ: MetaEditor /compile yolunda boşlukta sorun
|
|
391
|
-
çıkarabiliyor → boşluksuz sahne klasöründe derleyip ex5'i geri kopyala */
|
|
392
|
-
if (!ex5Ok && /\s/.test(eaFile)) {
|
|
393
|
-
const stage = path.join(process.env.ProgramData || 'C:\\ProgramData', 'beast', 'mt5build');
|
|
394
|
-
try {
|
|
395
|
-
fs.mkdirSync(stage, { recursive: true });
|
|
396
|
-
const staged = path.join(stage, EA_NAME + '.mq5');
|
|
397
|
-
fs.copyFileSync(eaFile, staged);
|
|
398
|
-
const slog = path.join(stage, EA_NAME + '.compile.log');
|
|
399
|
-
try { fs.rmSync(slog, { force: true }); } catch {}
|
|
400
|
-
r = spawnSync(me, ['/compile:' + q(staged), '/log:' + q(slog)], { windowsHide: true, timeout: 90000 });
|
|
401
|
-
const sex5 = path.join(stage, EA_NAME + '.ex5');
|
|
402
|
-
if (fs.existsSync(sex5)) {
|
|
403
|
-
fs.copyFileSync(sex5, ex5File);
|
|
404
|
-
ex5Ok = true;
|
|
405
|
-
}
|
|
406
|
-
log = slog;
|
|
407
|
-
steps.push('EA boşluksuz klasörde derlendi (yol boşluk fallback)');
|
|
408
|
-
} catch (e) {
|
|
409
|
-
steps.push('Sahne derleme hatası: ' + String((e && e.message) || e));
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
let logText = '';
|
|
413
|
-
try { logText = decodeText(fs.readFileSync(log)).text; } catch {}
|
|
414
|
-
const errMatch = logText.match(/(\d+)\s+error/i);
|
|
415
|
-
const errCount = errMatch ? Number(errMatch[1]) : ex5Ok ? 0 : 1;
|
|
416
|
-
if (ex5Ok && errCount === 0) {
|
|
417
|
-
steps.push('EA derlendi: ' + ex5File);
|
|
418
|
-
result.compiled = true;
|
|
419
|
-
result.changed = true;
|
|
420
|
-
} else {
|
|
421
|
-
steps.push('EA derleme HATASI (' + errCount + ') — ' + me + ' · log: ' + log);
|
|
422
|
-
if (r && r.error) steps.push('MetaEditor hata: ' + String(r.error.message || r.error));
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
} else {
|
|
426
|
-
steps.push('EA derlenmiş halde: ' + ex5File);
|
|
427
|
-
result.compiled = true;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/* 3) AutoTrading izni (common.ini) — MT5 ini'leri UTF-16 olabilir */
|
|
431
|
-
const cfgFile = path.join(data, 'config', 'common.ini');
|
|
432
|
-
let cfgText = '';
|
|
433
|
-
let cfgUtf16 = false;
|
|
434
|
-
try {
|
|
435
|
-
const dec = decodeIni(fs.readFileSync(cfgFile));
|
|
436
|
-
cfgText = dec.text;
|
|
437
|
-
cfgUtf16 = dec.utf16;
|
|
438
|
-
} catch {}
|
|
439
|
-
const patched = patchExpertsIni(cfgText);
|
|
440
|
-
if (patched.changed) {
|
|
441
|
-
fs.mkdirSync(path.dirname(cfgFile), { recursive: true });
|
|
442
|
-
fs.writeFileSync(cfgFile, encodeIni(patched.text, cfgUtf16));
|
|
443
|
-
steps.push('AutoTrading izni yazıldı (config\\common.ini [Experts] Enabled/AllowLiveTrading/Api)');
|
|
444
|
-
result.changed = true;
|
|
445
|
-
} else {
|
|
446
|
-
steps.push('AutoTrading izni zaten açık (common.ini)');
|
|
447
|
-
}
|
|
448
|
-
result.autoTrading = true;
|
|
449
|
-
|
|
450
|
-
/* 4) Grafiğe EA iliştirme (aktif profil .chr) */
|
|
451
|
-
const profileLast = readIniValue(patched.text, 'Charts', 'ProfileLast') || 'Default';
|
|
452
|
-
const chartsRoot = path.join(mql5, 'Profiles', 'Charts');
|
|
453
|
-
let chartFile = path.join(chartsRoot, profileLast, 'chart01.chr');
|
|
454
|
-
if (!fs.existsSync(chartFile)) chartFile = newestChart(path.join(chartsRoot, profileLast));
|
|
455
|
-
if (!chartFile) chartFile = '';
|
|
456
|
-
if (chartFile && fs.existsSync(chartFile)) {
|
|
457
|
-
try {
|
|
458
|
-
const raw = fs.readFileSync(chartFile);
|
|
459
|
-
const dec = decodeText(raw);
|
|
460
|
-
const inj = injectExpertBlock(dec.text, 'Beast\\' + EA_NAME);
|
|
461
|
-
if (inj.changed) {
|
|
462
|
-
fs.copyFileSync(chartFile, chartFile + '.beast-bak');
|
|
463
|
-
fs.writeFileSync(chartFile, encodeText(inj.text, dec.utf16));
|
|
464
|
-
steps.push('Grafik profiline EA eklendi: ' + chartFile + ' (terminal açılışında yüklenir)');
|
|
465
|
-
result.chartAttached = true;
|
|
466
|
-
result.changed = true;
|
|
467
|
-
} else if (inj.ours) {
|
|
468
|
-
steps.push('Grafik profilinde EA zaten kayıtlı: ' + chartFile);
|
|
469
|
-
result.chartAttached = true;
|
|
470
|
-
} else if (inj.existing) {
|
|
471
|
-
steps.push('Grafikte başka bir EA var — dokunulmadı: ' + chartFile);
|
|
472
|
-
} else {
|
|
473
|
-
steps.push('Grafik profili işlendi (EA enjeksiyonu uygulanamadı): ' + chartFile);
|
|
474
|
-
}
|
|
475
|
-
/* 4b) şablon: kullanıcı elle de uygulayabilsin */
|
|
476
|
-
try {
|
|
477
|
-
const tplDir = path.join(mql5, 'Profiles', 'Templates');
|
|
478
|
-
fs.mkdirSync(tplDir, { recursive: true });
|
|
479
|
-
const tplText = inj.changed ? inj.text : (() => {
|
|
480
|
-
const raw2 = fs.readFileSync(chartFile);
|
|
481
|
-
const dec2 = decodeText(raw2);
|
|
482
|
-
return injectExpertBlock(dec2.text, 'Beast\\' + EA_NAME).text;
|
|
483
|
-
})();
|
|
484
|
-
fs.writeFileSync(path.join(tplDir, EA_NAME + '.tpl'), encodeText(tplText, dec.utf16));
|
|
485
|
-
steps.push('Şablon hazır: ' + path.join(tplDir, EA_NAME + '.tpl'));
|
|
486
|
-
} catch (e) {
|
|
487
|
-
steps.push('Şablon yazılamadı: ' + String((e && e.message) || e));
|
|
488
|
-
}
|
|
489
|
-
} catch (e) {
|
|
490
|
-
steps.push('Grafik enjeksiyonu hatası: ' + String((e && e.message) || e));
|
|
491
|
-
}
|
|
492
|
-
} else {
|
|
493
|
-
steps.push('Aktif profil grafiği bulunamadı — EA Navigator\'dan elle sürüklenebilir');
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
/* 4c) BeastShot.tpl: mt5_shot geçici grafik açtığında uyguladığı temiz açık-tema
|
|
497
|
-
şablonu (EA bloğu yok) — kaynaktan kopyalanır; mt5_shot varsayılanı bunu arar. */
|
|
498
|
-
try {
|
|
499
|
-
const tplSrc = path.join(__dirname, 'mt5', 'BeastShot.tpl');
|
|
500
|
-
if (fs.existsSync(tplSrc)) {
|
|
501
|
-
const tplDir2 = path.join(mql5, 'Profiles', 'Templates');
|
|
502
|
-
const tplDst = path.join(tplDir2, 'BeastShot.tpl');
|
|
503
|
-
fs.mkdirSync(tplDir2, { recursive: true });
|
|
504
|
-
let same = false;
|
|
505
|
-
try { same = fs.readFileSync(tplDst).equals(fs.readFileSync(tplSrc)); } catch {}
|
|
506
|
-
if (!same) {
|
|
507
|
-
fs.copyFileSync(tplSrc, tplDst);
|
|
508
|
-
steps.push('Grafik şablonu yazıldı: ' + tplDst);
|
|
509
|
-
result.changed = true;
|
|
510
|
-
} else {
|
|
511
|
-
steps.push('Grafik şablonu güncel: ' + tplDst);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
} catch (e) {
|
|
515
|
-
steps.push('Grafik şablonu yazılamadı: ' + String((e && e.message) || e));
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
const running = terminalRunning();
|
|
519
|
-
result.restartRequired = running && result.changed;
|
|
520
|
-
if (result.restartRequired) steps.push('Değişiklikler MT5 yeniden başlatılınca etkinleşir');
|
|
521
|
-
result.ok = true;
|
|
522
|
-
return result;
|
|
523
|
-
} catch (e) {
|
|
524
|
-
result.error = String((e && e.message) || e);
|
|
525
|
-
return result;
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
module.exports = {
|
|
530
|
-
runSetup,
|
|
531
|
-
eaSource,
|
|
532
|
-
patchExpertsIni,
|
|
533
|
-
injectExpertBlock,
|
|
534
|
-
decodeText,
|
|
535
|
-
encodeText,
|
|
536
|
-
decodeIni,
|
|
537
|
-
encodeIni,
|
|
538
|
-
readIniValue,
|
|
539
|
-
findMetaEditor,
|
|
540
|
-
terminalRunning,
|
|
541
|
-
detectTerminal,
|
|
542
|
-
};
|