beast-agent 2.14.0 → 2.15.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/engine.js +85 -3
- package/src/agent/financetools.js +307 -0
- package/src/agent/scripts/mt5_bridge.py +381 -0
- package/src/agent/tools.js +10 -3
- package/src/main.js +410 -28
- package/src/mt5bridge.js +215 -0
- package/src/preload.js +11 -3
- package/src/renderer/index.html +67 -0
- package/src/renderer/renderer.js +493 -38
- package/src/renderer/style.css +328 -1
- package/src/agent/android.js +0 -265
package/src/mt5bridge.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* Beast Finance — MT5 köprü yöneticisi (main süreç).
|
|
4
|
+
Python stdio köprüsünü (mt5_bridge.py) spawn eder; JSON satır istek/yanıt
|
|
5
|
+
ve event akışını yönetir. Python bulunamazsa aday listesinde sıradakini
|
|
6
|
+
dener; süreç düşerse backoff ile yeniden başlatır. */
|
|
7
|
+
|
|
8
|
+
const { spawn } = require('child_process');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const { EventEmitter } = require('events');
|
|
12
|
+
|
|
13
|
+
const BUNDLED_SCRIPT = path.join(__dirname, 'agent', 'scripts', 'mt5_bridge.py');
|
|
14
|
+
|
|
15
|
+
/* Paketli uygulamada köprü scripti app.asar İÇİNDE kalır — harici Python
|
|
16
|
+
onu OKUYAMAZ. Her başlatmada %APPDATA%\beast\finance altına güncel haliyle
|
|
17
|
+
yazılır (Node asar içini okuyabilir) ve oradan çalıştırılır. */
|
|
18
|
+
function scriptPath() {
|
|
19
|
+
try {
|
|
20
|
+
const dest = path.join(process.env.APPDATA || process.env.TMP || process.cwd(), 'beast', 'finance', 'mt5_bridge.py');
|
|
21
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
22
|
+
fs.copyFileSync(BUNDLED_SCRIPT, dest);
|
|
23
|
+
return dest;
|
|
24
|
+
} catch {
|
|
25
|
+
return BUNDLED_SCRIPT;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class MT5Bridge extends EventEmitter {
|
|
30
|
+
constructor() {
|
|
31
|
+
super();
|
|
32
|
+
this.proc = null;
|
|
33
|
+
this._buf = '';
|
|
34
|
+
this._reqId = 1;
|
|
35
|
+
this._pending = new Map();
|
|
36
|
+
this._stopped = true;
|
|
37
|
+
this._backoff = 1500;
|
|
38
|
+
this._lastError = '';
|
|
39
|
+
this._terminal = null;
|
|
40
|
+
this._account = null;
|
|
41
|
+
this._candidates = ['python', 'py -3'];
|
|
42
|
+
this._candIdx = 0;
|
|
43
|
+
this._terminalPath = '';
|
|
44
|
+
this._everConnected = false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get running() {
|
|
48
|
+
return !!this.proc;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
status() {
|
|
52
|
+
return {
|
|
53
|
+
running: this.running,
|
|
54
|
+
connected: !!this._terminal,
|
|
55
|
+
terminal: this._terminal,
|
|
56
|
+
account: this._account,
|
|
57
|
+
error: this._lastError,
|
|
58
|
+
python: this._candidates[this._candIdx] || '',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* opts: { candidates: ['python','py -3'], terminal: 'C:\\...\\terminal64.exe' } */
|
|
63
|
+
start(opts = {}) {
|
|
64
|
+
if (Array.isArray(opts.candidates) && opts.candidates.length) {
|
|
65
|
+
const next = opts.candidates.map((c) => String(c).trim()).filter(Boolean);
|
|
66
|
+
if (next.join('|') !== this._candidates.join('|')) {
|
|
67
|
+
this._candidates = next;
|
|
68
|
+
this._candIdx = 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
this._terminalPath = String(opts.terminal || '').trim();
|
|
72
|
+
this._stopped = false;
|
|
73
|
+
if (this.running) return;
|
|
74
|
+
this._spawn();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_spawn() {
|
|
78
|
+
if (this._stopped) return;
|
|
79
|
+
const cand = this._candidates[this._candIdx] || 'python';
|
|
80
|
+
/* 'py -3' gibi çok kelimeli kısa komutlar bölünür; tam yol tek exe'dir */
|
|
81
|
+
const parts = /[\\/]/.test(cand) ? [cand] : cand.split(/\s+/);
|
|
82
|
+
const exe = parts[0];
|
|
83
|
+
const preArgs = parts.slice(1);
|
|
84
|
+
let proc;
|
|
85
|
+
try {
|
|
86
|
+
proc = spawn(exe, [...preArgs, scriptPath(), ...(this._terminalPath ? ['--terminal', this._terminalPath] : [])], {
|
|
87
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
88
|
+
windowsHide: true,
|
|
89
|
+
});
|
|
90
|
+
} catch (e) {
|
|
91
|
+
this._lastError = 'Python başlatılamadı: ' + String((e && e.message) || e);
|
|
92
|
+
this.emit('log', { level: 'error', line: this._lastError });
|
|
93
|
+
this._nextCandidateOrRetry();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.proc = proc;
|
|
97
|
+
this._buf = '';
|
|
98
|
+
proc.stdout.setEncoding('utf8');
|
|
99
|
+
proc.stdout.on('data', (chunk) => {
|
|
100
|
+
this._buf += chunk;
|
|
101
|
+
let i;
|
|
102
|
+
while ((i = this._buf.indexOf('\n')) >= 0) {
|
|
103
|
+
const line = this._buf.slice(0, i).trim();
|
|
104
|
+
this._buf = this._buf.slice(i + 1);
|
|
105
|
+
if (line) this._onLine(line);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
proc.stderr.setEncoding('utf8');
|
|
109
|
+
proc.stderr.on('data', (c) => {
|
|
110
|
+
const t = String(c).trim();
|
|
111
|
+
if (t) this.emit('log', { level: 'error', line: t.slice(0, 400) });
|
|
112
|
+
});
|
|
113
|
+
proc.on('error', (e) => {
|
|
114
|
+
this._lastError = `Python (${exe}) bulunamadı/başlatılamadı: ` + String((e && e.message) || e);
|
|
115
|
+
this.emit('log', { level: 'error', line: this._lastError });
|
|
116
|
+
try { this._failAll(this._lastError); } catch {}
|
|
117
|
+
if (this.proc === proc) this.proc = null;
|
|
118
|
+
this._nextCandidateOrRetry();
|
|
119
|
+
});
|
|
120
|
+
proc.on('exit', (code) => {
|
|
121
|
+
if (this.proc === proc) this.proc = null;
|
|
122
|
+
this._terminal = null;
|
|
123
|
+
this._account = null;
|
|
124
|
+
try { this._failAll('MT5 köprüsü kapandı (kod ' + code + ')'); } catch {}
|
|
125
|
+
this.emit('bridge', { connected: false, error: this._lastError || ('süreç kapandı (kod ' + code + ')') });
|
|
126
|
+
this._nextCandidateOrRetry();
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* aday python'ları sırayla dener; hepsi tükendiyse backoff ile yeniden dener */
|
|
131
|
+
_nextCandidateOrRetry() {
|
|
132
|
+
if (this._stopped) return;
|
|
133
|
+
if (this._candIdx < this._candidates.length - 1) {
|
|
134
|
+
this._candIdx += 1;
|
|
135
|
+
this.emit('log', { level: 'info', line: 'python adayı deneniyor: ' + this._candidates[this._candIdx] });
|
|
136
|
+
setTimeout(() => { try { this._spawn(); } catch {} }, 300);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
this._candIdx = 0;
|
|
140
|
+
setTimeout(() => { try { if (!this._stopped && !this.running) this._spawn(); } catch {} }, this._backoff);
|
|
141
|
+
this._backoff = Math.min(this._backoff * 2, 15000);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
_onLine(line) {
|
|
145
|
+
let msg;
|
|
146
|
+
try { msg = JSON.parse(line); } catch {
|
|
147
|
+
this.emit('log', { level: 'info', line: line.slice(0, 240) });
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (msg.event) {
|
|
151
|
+
if (msg.event === 'bridge') {
|
|
152
|
+
this._terminal = msg.terminal || null;
|
|
153
|
+
this._account = msg.account || null;
|
|
154
|
+
this._lastError = String(msg.error || '');
|
|
155
|
+
if (msg.connected) {
|
|
156
|
+
this._everConnected = true;
|
|
157
|
+
this._backoff = 1500;
|
|
158
|
+
}
|
|
159
|
+
this.emit('bridge', msg);
|
|
160
|
+
} else if (msg.event === 'log') {
|
|
161
|
+
this.emit('log', msg);
|
|
162
|
+
} else {
|
|
163
|
+
this.emit('event', msg);
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (msg.id != null && this._pending.has(msg.id)) {
|
|
168
|
+
const en = this._pending.get(msg.id);
|
|
169
|
+
this._pending.delete(msg.id);
|
|
170
|
+
clearTimeout(en.timer);
|
|
171
|
+
en.resolve(msg);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
call(method, params, timeoutMs = 12000) {
|
|
176
|
+
if (!this.running) return Promise.resolve({ ok: false, error: 'MT5 köprüsü çalışmıyor' });
|
|
177
|
+
const id = this._reqId++;
|
|
178
|
+
let resolveFn = null;
|
|
179
|
+
const p = new Promise((resolve) => { resolveFn = resolve; });
|
|
180
|
+
const timer = setTimeout(() => {
|
|
181
|
+
this._pending.delete(id);
|
|
182
|
+
resolveFn({ ok: false, error: 'MT5 zaman aşımı: ' + method });
|
|
183
|
+
}, Math.max(2000, Number(timeoutMs) || 12000));
|
|
184
|
+
this._pending.set(id, { timer, resolve: resolveFn });
|
|
185
|
+
try {
|
|
186
|
+
this.proc.stdin.write(JSON.stringify({ id, method, params: params || {} }) + '\n');
|
|
187
|
+
} catch (e) {
|
|
188
|
+
clearTimeout(timer);
|
|
189
|
+
this._pending.delete(id);
|
|
190
|
+
resolveFn({ ok: false, error: 'köprü yazma hatası: ' + String((e && e.message) || e) });
|
|
191
|
+
}
|
|
192
|
+
return p;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
stop() {
|
|
196
|
+
this._stopped = true;
|
|
197
|
+
try { this._failAll('köprü kapatıldı'); } catch {}
|
|
198
|
+
if (this.proc) {
|
|
199
|
+
try { this.proc.kill(); } catch {}
|
|
200
|
+
this.proc = null;
|
|
201
|
+
}
|
|
202
|
+
this._terminal = null;
|
|
203
|
+
this._account = null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
_failAll(err) {
|
|
207
|
+
for (const [, en] of this._pending) {
|
|
208
|
+
clearTimeout(en.timer);
|
|
209
|
+
en.resolve({ ok: false, error: err });
|
|
210
|
+
}
|
|
211
|
+
this._pending.clear();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
module.exports = new MT5Bridge();
|
package/src/preload.js
CHANGED
|
@@ -94,6 +94,17 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
94
94
|
studioTree: (rel) => ipcRenderer.invoke('studio:tree', rel),
|
|
95
95
|
studioPickVideo: () => ipcRenderer.invoke('studio:pickvideo'),
|
|
96
96
|
studioDelete: (rel) => ipcRenderer.invoke('studio:delete', rel),
|
|
97
|
+
|
|
98
|
+
financeState: () => ipcRenderer.invoke('finance:state'),
|
|
99
|
+
financeSnapshot: () => ipcRenderer.invoke('finance:snapshot'),
|
|
100
|
+
financeMode: (on, sessionId) => ipcRenderer.invoke('finance:mode', { on, sessionId }),
|
|
101
|
+
financeSettings: (patch) => ipcRenderer.invoke('finance:settings', patch),
|
|
102
|
+
financeTraderStart: () => ipcRenderer.invoke('finance:trader:start'),
|
|
103
|
+
financeTraderStop: () => ipcRenderer.invoke('finance:trader:stop'),
|
|
104
|
+
financeConnect: () => ipcRenderer.invoke('finance:connect'),
|
|
105
|
+
financeClose: (ticket, volume) => ipcRenderer.invoke('finance:close', { ticket, volume }),
|
|
106
|
+
financeInstall: () => ipcRenderer.invoke('finance:install'),
|
|
107
|
+
financeSymbolsList: (filter) => ipcRenderer.invoke('finance:symbols:list', filter),
|
|
97
108
|
clipRead: () => ipcRenderer.invoke('clip:read'),
|
|
98
109
|
clipWrite: (t) => ipcRenderer.invoke('clip:write', t),
|
|
99
110
|
thinkSet: (v) => ipcRenderer.invoke('think:set', v),
|
|
@@ -227,9 +238,6 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
227
238
|
headroomToggle: (on) => ipcRenderer.invoke('headroom:toggle', !!on),
|
|
228
239
|
headroomStatus: () => ipcRenderer.invoke('headroom:status'),
|
|
229
240
|
headroomStats: () => ipcRenderer.invoke('headroom:stats'),
|
|
230
|
-
androidStatus: () => ipcRenderer.invoke('android:status'),
|
|
231
|
-
androidInstall: () => ipcRenderer.invoke('android:install'),
|
|
232
|
-
androidStartAvd: (name) => ipcRenderer.invoke('android:startAvd', name),
|
|
233
241
|
agentsClearAll: () => ipcRenderer.invoke('agents:clearAll'),
|
|
234
242
|
ideDelete: (rel) => ipcRenderer.invoke('ide:delete', rel),
|
|
235
243
|
onWaEvent: (cb) => ipcRenderer.on('wa:event', (_e, ev) => cb(ev)),
|
package/src/renderer/index.html
CHANGED
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
</div>
|
|
91
91
|
</div>
|
|
92
92
|
<button id="studioBtn" class="dd-btn dd-icon-btn" title="Beast Studio — video yapma/düzenleme" data-i18n-title="tipStudio"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="13" height="12" rx="2"/><path d="M15 10l7-4v12l-7-4"/></svg></button>
|
|
93
|
+
<button id="finBtn" class="dd-btn dd-icon-btn" title="Beast Finance — MT5 trade paneli (bilgisayardaki MetaTrader 5 ile çalışır)"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18"/><path d="M7 15l4-5 3 3 5-7"/></svg></button>
|
|
93
94
|
<button id="sbBtn" class="dd-btn dd-icon-btn" title="Beast Sandbox — GitHub reposu indir & içinde çalış" data-i18n-title="tipSandbox"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg></button>
|
|
94
95
|
<button id="railBtn" class="dd-btn dd-icon-btn" title="Paralel Ajan Konsolu" data-i18n-title="tipRail">▤</button>
|
|
95
96
|
<button id="watchBtn" class="dd-btn dd-icon-btn" title="İzleyiciler (Watchers)" data-i18n-title="tipWatch">◎</button>
|
|
@@ -130,6 +131,57 @@
|
|
|
130
131
|
</div>
|
|
131
132
|
</div>
|
|
132
133
|
|
|
134
|
+
<section id="finPanel">
|
|
135
|
+
<div id="finHead">
|
|
136
|
+
<span id="finTitle">BEAST FINANCE</span>
|
|
137
|
+
<span id="finDot" class="fin-dot off" title="MT5 köprüsü"></span>
|
|
138
|
+
<span id="finTerm" title="">MT5</span>
|
|
139
|
+
<span class="file-spacer"></span>
|
|
140
|
+
<button id="finReconnect" title="MT5'e yeniden bağlan">⟳</button>
|
|
141
|
+
</div>
|
|
142
|
+
<div id="finBody">
|
|
143
|
+
<div class="fin-card">
|
|
144
|
+
<div class="fin-card-title">HESAP</div>
|
|
145
|
+
<div class="fin-grid">
|
|
146
|
+
<div class="fin-kv"><span>Bakiye</span><b id="finBalance">—</b></div>
|
|
147
|
+
<div class="fin-kv"><span>Özkaynak</span><b id="finEquity">—</b></div>
|
|
148
|
+
<div class="fin-kv"><span>Kâr/Zarar</span><b id="finProfit">—</b></div>
|
|
149
|
+
<div class="fin-kv"><span>Serbest Marj</span><b id="finFree">—</b></div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
<div class="fin-card">
|
|
153
|
+
<div class="fin-card-title">TRADE AJANI <span id="finTraderDot" class="fin-dot off" title="Trader durumu"></span></div>
|
|
154
|
+
<div class="fin-row"><select id="finTraderModel" title="Trader ajanının modeli — farklı API/model seçilebilir"></select></div>
|
|
155
|
+
<div class="fin-row fin-mini">
|
|
156
|
+
<label>Tur (sn)</label><input id="finInterval" type="number" min="30" max="3600" step="10" />
|
|
157
|
+
<label>Max lot</label><input id="finMaxLot" type="number" min="0.01" max="100" step="0.01" />
|
|
158
|
+
</div>
|
|
159
|
+
<div class="fin-row fin-mini"><label>Semboller</label><button id="finSymBtn" class="fin-sym-select" title="MT5 sembol listesinden seç — tıkla">…</button></div>
|
|
160
|
+
<div class="fin-row">
|
|
161
|
+
<label class="fin-check"><input id="finAllow" type="checkbox" /> Otomatik işlem (ajan trade açabilir)</label>
|
|
162
|
+
<button id="finTraderBtn" class="fin-start">▶︎ Ajanı Başlat</button>
|
|
163
|
+
</div>
|
|
164
|
+
<div id="finTraderStatus">Kapalı</div>
|
|
165
|
+
</div>
|
|
166
|
+
<div class="fin-card">
|
|
167
|
+
<div class="fin-card-title">POZİSYONLAR <span id="finPosCount"></span></div>
|
|
168
|
+
<div id="finPosList"><div class="fin-empty">Açık pozisyon yok</div></div>
|
|
169
|
+
</div>
|
|
170
|
+
<div class="fin-card">
|
|
171
|
+
<div class="fin-card-title">
|
|
172
|
+
PİYASA
|
|
173
|
+
<span class="file-spacer"></span>
|
|
174
|
+
<button id="finSymAdd" title="Sembol ekle/çıkar — MT5 sembol listesinden seç">+</button>
|
|
175
|
+
</div>
|
|
176
|
+
<div id="finSymList"><div class="fin-empty">—</div></div>
|
|
177
|
+
</div>
|
|
178
|
+
<div class="fin-card fin-grow">
|
|
179
|
+
<div class="fin-card-title">AKIŞ</div>
|
|
180
|
+
<div id="finLog"></div>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
</section>
|
|
184
|
+
|
|
133
185
|
<div id="ideRow">
|
|
134
186
|
<section id="codePane">
|
|
135
187
|
<div id="codeTabsBar">
|
|
@@ -476,6 +528,21 @@
|
|
|
476
528
|
</div>
|
|
477
529
|
</div>
|
|
478
530
|
|
|
531
|
+
<div id="finSymOverlay" class="mini-overlay" hidden>
|
|
532
|
+
<div class="mini-dialog fin-sym-dialog">
|
|
533
|
+
<div class="mini-head">
|
|
534
|
+
<span class="mini-title">MT5 SEMBOLLERİ</span>
|
|
535
|
+
<span id="finSymCount" class="fin-sym-count"></span>
|
|
536
|
+
<button class="mini-close" id="finSymClose" title="Kapat">×</button>
|
|
537
|
+
</div>
|
|
538
|
+
<div class="mini-body">
|
|
539
|
+
<input id="finSymSearch" placeholder="Sembol ara… (örn: XAU, BTC, EUR, US500)" spellcheck="false" autocomplete="off" />
|
|
540
|
+
<div id="finSymPick" class="mini-list"></div>
|
|
541
|
+
<div class="fin-sym-hint">Tıkla: izleme listesine ekle/çıkar — PİYASA kartına anında düşer</div>
|
|
542
|
+
</div>
|
|
543
|
+
</div>
|
|
544
|
+
</div>
|
|
545
|
+
|
|
479
546
|
<div id="cronOverlay" class="mini-overlay" hidden>
|
|
480
547
|
<div class="mini-dialog">
|
|
481
548
|
<div class="mini-head">
|