beast-agent 2.32.0 → 2.33.1
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 +1 -1
- package/src/agent/financetools.js +316 -45
- package/src/agent/finindicators.js +254 -0
- package/src/agent/finrisk.js +132 -0
- package/src/agent/finstats.js +229 -0
- package/src/agent/finwatch.js +110 -0
- package/src/agent/scripts/mt5_bridge.py +65 -0
- package/src/main.js +678 -8
- package/src/preload.js +1 -0
- package/src/renderer/index.html +80 -1
- package/src/renderer/renderer.js +280 -1
- package/src/renderer/style.css +52 -0
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -5477,7 +5477,7 @@ const skills = require('./skills');
|
|
|
5477
5477
|
) {
|
|
5478
5478
|
return JSON.stringify({ ok: false, error: `unknown tool ${name}` });
|
|
5479
5479
|
}
|
|
5480
|
-
return await tools.exec(name, args, { cwd: this._sessionWorkspace(sessionId), signal, wantDiff: true });
|
|
5480
|
+
return await tools.exec(name, args, { cwd: this._sessionWorkspace(sessionId), signal, wantDiff: true, sessionId });
|
|
5481
5481
|
} catch (e) {
|
|
5482
5482
|
return JSON.stringify({ ok: false, error: String((e && e.message) || e) });
|
|
5483
5483
|
}
|
|
@@ -6,14 +6,22 @@
|
|
|
6
6
|
main tarafı setConfig/setNotify ile ayar ve bildirim kancasını enjekte eder. */
|
|
7
7
|
|
|
8
8
|
const mt5 = require('../mt5bridge');
|
|
9
|
+
const finindicators = require('./finindicators');
|
|
10
|
+
const finstats = require('./finstats');
|
|
11
|
+
const finrisk = require('./finrisk');
|
|
9
12
|
|
|
10
13
|
const NAMES = [
|
|
11
14
|
'mt5_status',
|
|
12
15
|
'mt5_account',
|
|
13
16
|
'mt5_market',
|
|
17
|
+
'mt5_rates',
|
|
18
|
+
'mt5_indicators',
|
|
19
|
+
'mt5_risksize',
|
|
14
20
|
'mt5_positions',
|
|
15
21
|
'mt5_orders',
|
|
16
22
|
'mt5_history',
|
|
23
|
+
'mt5_alerts',
|
|
24
|
+
'mt5_note',
|
|
17
25
|
'mt5_trade',
|
|
18
26
|
'mt5_close',
|
|
19
27
|
'mt5_modify',
|
|
@@ -23,6 +31,12 @@ const NAMES = [
|
|
|
23
31
|
|
|
24
32
|
let getCfg = () => ({ allowTrading: false, maxLot: 0.1, maxPositions: 3, symbols: [] });
|
|
25
33
|
let notify = () => {};
|
|
34
|
+
/* alarm deposu main süreçte yaşar (dosyaya kalıcı) — buradan enjekte edilir */
|
|
35
|
+
let alertsApi = {
|
|
36
|
+
list: () => [],
|
|
37
|
+
set: () => null,
|
|
38
|
+
remove: () => false,
|
|
39
|
+
};
|
|
26
40
|
|
|
27
41
|
function setConfig(fn) {
|
|
28
42
|
if (typeof fn === 'function') getCfg = fn;
|
|
@@ -32,10 +46,8 @@ function setNotify(fn) {
|
|
|
32
46
|
if (typeof fn === 'function') notify = fn;
|
|
33
47
|
}
|
|
34
48
|
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
if (!isFinite(n) || n <= 0) return 0;
|
|
38
|
-
return Math.min(n, maxLot);
|
|
49
|
+
function setAlerts(api) {
|
|
50
|
+
if (api && typeof api === 'object') alertsApi = api;
|
|
39
51
|
}
|
|
40
52
|
|
|
41
53
|
async function bcall(method, params, timeoutMs) {
|
|
@@ -44,8 +56,43 @@ async function bcall(method, params, timeoutMs) {
|
|
|
44
56
|
return r.data;
|
|
45
57
|
}
|
|
46
58
|
|
|
47
|
-
function
|
|
48
|
-
|
|
59
|
+
async function symInfoRow(symbol) {
|
|
60
|
+
const data = await bcall('symbols', { symbols: [String(symbol || '').toUpperCase()] }, 10000);
|
|
61
|
+
const row = data && data.symbols && data.symbols[0];
|
|
62
|
+
return row && !row.missing ? row : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function tickPrice(row, side) {
|
|
66
|
+
const p = side === 'buy' ? Number(row && row.ask) : Number(row && row.bid);
|
|
67
|
+
if (isFinite(p) && p > 0) return p;
|
|
68
|
+
return Number(row && row.bid) || Number(row && row.ask) || 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* İşlem öncesi risk katmanı: stops_level + yoğunluk + marj kalkanı.
|
|
72
|
+
Hata metni ya da null döner. positions çağıran tarafından verilir. */
|
|
73
|
+
async function preTradeCheck(cfg, side, symbol, info, price, volume, sl, tp, positions) {
|
|
74
|
+
const closeErr = finrisk.stopsTooClose(info, price, sl, tp);
|
|
75
|
+
if (closeErr) return closeErr;
|
|
76
|
+
const expErr = finrisk.exposureCheck(positions || [], side, symbol, cfg.maxPerSymbol, cfg.maxSameSide);
|
|
77
|
+
if (expErr) return expErr;
|
|
78
|
+
const minLevel = Number(cfg.minMarginLevel) || 0;
|
|
79
|
+
let account = null;
|
|
80
|
+
let estMargin = 0;
|
|
81
|
+
try {
|
|
82
|
+
const a = await bcall('account', {}, 8000);
|
|
83
|
+
account = a && a.account;
|
|
84
|
+
} catch {}
|
|
85
|
+
try {
|
|
86
|
+
const m = await bcall('margin', { symbol, side, volume, price }, 8000);
|
|
87
|
+
estMargin = Number(m && m.margin) || 0;
|
|
88
|
+
} catch {}
|
|
89
|
+
const mErr = finrisk.marginCheck(account, estMargin, minLevel);
|
|
90
|
+
if (mErr && mErr.error) return mErr.error;
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function noteTrade(kind, data, ctx) {
|
|
95
|
+
try { notify({ kind, data, at: Date.now(), sid: ctx && ctx.sessionId ? String(ctx.sessionId) : '' }); } catch {}
|
|
49
96
|
}
|
|
50
97
|
|
|
51
98
|
function notConnected() {
|
|
@@ -66,7 +113,7 @@ const definitions = NAMES.map((name) => {
|
|
|
66
113
|
},
|
|
67
114
|
mt5_market: {
|
|
68
115
|
description:
|
|
69
|
-
'Verilen semboller için canlı fiyat: bid/ask, spread, digit, point. symbols verilmezse izleme listesi (watchlist) kullanılır. Örn: ["EURUSD","XAUUSD"].',
|
|
116
|
+
'Verilen semboller için canlı fiyat + TAM sembol sözleşme bilgisi: bid/ask, spread, digit, point, swap_long/short, komisyon, kontrat büyüklüğü, min/max/step lot, stops_level, işlem modu. symbols verilmezse izleme listesi (watchlist) kullanılır. Örn: ["EURUSD","XAUUSD"].',
|
|
70
117
|
parameters: {
|
|
71
118
|
type: 'object',
|
|
72
119
|
properties: {
|
|
@@ -74,6 +121,54 @@ const definitions = NAMES.map((name) => {
|
|
|
74
121
|
},
|
|
75
122
|
},
|
|
76
123
|
},
|
|
124
|
+
mt5_rates: {
|
|
125
|
+
description:
|
|
126
|
+
'OHLC MUM VERİSİ: mt5_rates {symbol, timeframe:"M1|M5|M15|M30|H1|H4|D1|W1|MN1", count?}. Trend/yapı/destek-direnç analizinin HAM verisi. Varsayılan M15 × 200 mum. Formasyon/price-action için İDEAL: H1/H4 yapı + M15/M5 giriş zamanlaması.',
|
|
127
|
+
parameters: {
|
|
128
|
+
type: 'object',
|
|
129
|
+
properties: {
|
|
130
|
+
symbol: { type: 'string', description: 'Örn: XAUUSD' },
|
|
131
|
+
timeframe: { type: 'string', description: 'M1|M5|M15|M30|H1|H4|D1|W1|MN1 (varsayılan M15)' },
|
|
132
|
+
count: { type: 'number', description: 'Mum sayısı 1-1000 (varsayılan 200)' },
|
|
133
|
+
},
|
|
134
|
+
required: ['symbol'],
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
mt5_indicators: {
|
|
138
|
+
description:
|
|
139
|
+
'TEKNİK GÖSTERGELER: mt5_indicators {symbol, timeframe, count?, indicators?}. indicators örn: ["EMA(50)","EMA(200)","RSI(14)","ATR(14)","MACD(12,26,9)","BB(20,2)","STOCH(14,3,3)","SMA(200)"]. Varsayılan: EMA20/EMA50/RSI14/ATR14/MACD. ATR ile SL mesafesi/lot, RSI ile aşırı alım-satım, EMA ile trend yönü oku.',
|
|
140
|
+
parameters: {
|
|
141
|
+
type: 'object',
|
|
142
|
+
properties: {
|
|
143
|
+
symbol: { type: 'string' },
|
|
144
|
+
timeframe: { type: 'string', description: 'M1|M5|M15|M30|H1|H4|D1|W1|MN1 (varsayılan H1)' },
|
|
145
|
+
count: { type: 'number', description: 'Hesaplamaya giren mum sayısı (varsayılan 300, en fazla 1000)' },
|
|
146
|
+
indicators: {
|
|
147
|
+
type: 'array',
|
|
148
|
+
items: { type: 'string' },
|
|
149
|
+
description: 'Gösterge listesi (boşsa varsayılan set)',
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
required: ['symbol'],
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
mt5_risksize: {
|
|
156
|
+
description:
|
|
157
|
+
'RİSK BAZLI LOT HESABI: mt5_risksize {symbol, sl?, entry?, side?, riskPct?, atrMult?, timeframe?}. Bakiyenin riskPct%’i kadar kayıp verecek lotu hesaplar (SL mesafesi × tick değeri). sl yoksa atrMult verilirse ATR(14) ile SL mesafesi önerir. riskPct boşsa ayardaki işlem riski (%) kullanılır. İŞLEM AÇMADAN ÖNCE çağır; çıkan lotu mt5_trade’e ver.',
|
|
158
|
+
parameters: {
|
|
159
|
+
type: 'object',
|
|
160
|
+
properties: {
|
|
161
|
+
symbol: { type: 'string' },
|
|
162
|
+
sl: { type: 'number', description: 'Stop loss fiyatı' },
|
|
163
|
+
entry: { type: 'number', description: 'Giriş fiyatı (boşsa güncel fiyat)' },
|
|
164
|
+
side: { type: 'string', enum: ['buy', 'sell'], description: 'Yön (varsayılan buy)' },
|
|
165
|
+
riskPct: { type: 'number', description: 'Hesap riski yüzdesi (boşsa ayar)' },
|
|
166
|
+
atrMult: { type: 'number', description: 'SL yoksa: SL mesafesi = atrMult × ATR(14)' },
|
|
167
|
+
timeframe: { type: 'string', description: 'ATR zaman dilimi (varsayılan H1)' },
|
|
168
|
+
},
|
|
169
|
+
required: ['symbol'],
|
|
170
|
+
},
|
|
171
|
+
},
|
|
77
172
|
mt5_positions: {
|
|
78
173
|
description: 'Açık pozisyonlar: ticket, sembol, yön, lot, açılış fiyatı, güncel fiyat, SL/TP, yüzen kâr/zarar, açılış zamanı.',
|
|
79
174
|
parameters: { type: 'object', properties: {} },
|
|
@@ -89,6 +184,36 @@ const definitions = NAMES.map((name) => {
|
|
|
89
184
|
properties: { days: { type: 'number', description: 'Kaç gün geriye bakılsın (varsayılan 1, en fazla 90)' } },
|
|
90
185
|
},
|
|
91
186
|
},
|
|
187
|
+
mt5_alerts: {
|
|
188
|
+
description:
|
|
189
|
+
'FİYAT ALARMI: mt5_alerts {action:"list"|"set"|"remove"}. set: {symbol, price, direction:"above"|"below", note?}. Fiyat tetiklenince sahibine bildirim gider + alarm kapanır. Örn: XAUUSD 3400 üstü / 3280 altı kritik seviye bildirimi kur.',
|
|
190
|
+
parameters: {
|
|
191
|
+
type: 'object',
|
|
192
|
+
properties: {
|
|
193
|
+
action: { type: 'string', enum: ['list', 'set', 'remove'] },
|
|
194
|
+
symbol: { type: 'string', description: 'set için sembol' },
|
|
195
|
+
price: { type: 'number', description: 'set için tetik fiyatı' },
|
|
196
|
+
direction: { type: 'string', enum: ['above', 'below'], description: 'above: fiyat ≥ price; below: fiyat ≤ price' },
|
|
197
|
+
note: { type: 'string', description: 'Alarm notu (bildirimde görünür)' },
|
|
198
|
+
id: { type: 'string', description: 'remove için alarm id' },
|
|
199
|
+
},
|
|
200
|
+
required: ['action'],
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
mt5_note: {
|
|
204
|
+
description:
|
|
205
|
+
'İŞLEM GÜNLÜĞÜ NOTU: mt5_note {symbol, note, ticket?, side?}. Kararın GEREKÇESİNİ kalıcı günlüğe yaz (neden girdin/çıktın, tez, iptal koşulu). Haftalık performans raporu bu notları içerir — her önemli kararda kullan.',
|
|
206
|
+
parameters: {
|
|
207
|
+
type: 'object',
|
|
208
|
+
properties: {
|
|
209
|
+
symbol: { type: 'string' },
|
|
210
|
+
note: { type: 'string', description: 'Gerekçe/tez (kısa, net)' },
|
|
211
|
+
ticket: { type: 'number', description: 'İlgili pozisyon ticket no (varsa)' },
|
|
212
|
+
side: { type: 'string', enum: ['buy', 'sell', 'wait'] },
|
|
213
|
+
},
|
|
214
|
+
required: ['symbol', 'note'],
|
|
215
|
+
},
|
|
216
|
+
},
|
|
92
217
|
mt5_trade: {
|
|
93
218
|
description:
|
|
94
219
|
'PİYASA EMRİ AÇAR: mt5_trade {symbol, side:"buy"|"sell", volume, sl?, tp?, comment?}. Lot limiti ve max pozisyon sayısı sistem tarafından zorlanır. Otomatik işlem anahtarı kapalıysa reddedilir. SL/TP vermek ŞIDDETLİ önerilir.',
|
|
@@ -175,6 +300,31 @@ const handlers = {
|
|
|
175
300
|
const data = await bcall('symbols', { symbols: syms }, 10000);
|
|
176
301
|
return { ok: true, symbols: data && data.symbols ? data.symbols : [] };
|
|
177
302
|
},
|
|
303
|
+
async mt5_rates(args) {
|
|
304
|
+
if (!mt5.running) return notConnected();
|
|
305
|
+
const symbol = String(args.symbol || '').trim().toUpperCase();
|
|
306
|
+
if (!symbol) return { ok: false, error: 'symbol gerekli' };
|
|
307
|
+
const timeframe = String(args.timeframe || 'M15').trim().toUpperCase();
|
|
308
|
+
let count = Math.round(Number(args.count) || 200);
|
|
309
|
+
count = Math.max(1, Math.min(1000, count));
|
|
310
|
+
const data = await bcall('rates', { symbol, timeframe, count }, 15000);
|
|
311
|
+
const rates = (data && data.rates) || [];
|
|
312
|
+
return { ok: true, symbol, timeframe, count: rates.length, rates };
|
|
313
|
+
},
|
|
314
|
+
async mt5_indicators(args) {
|
|
315
|
+
if (!mt5.running) return notConnected();
|
|
316
|
+
const symbol = String(args.symbol || '').trim().toUpperCase();
|
|
317
|
+
if (!symbol) return { ok: false, error: 'symbol gerekli' };
|
|
318
|
+
const timeframe = String(args.timeframe || 'H1').trim().toUpperCase();
|
|
319
|
+
let count = Math.round(Number(args.count) || 300);
|
|
320
|
+
count = Math.max(30, Math.min(1000, count));
|
|
321
|
+
const specs = Array.isArray(args.indicators) ? args.indicators.map((s) => String(s || '').trim()).filter(Boolean) : [];
|
|
322
|
+
const data = await bcall('rates', { symbol, timeframe, count }, 15000);
|
|
323
|
+
const rates = (data && data.rates) || [];
|
|
324
|
+
if (!rates.length) return { ok: false, error: 'mum verisi yok: ' + symbol + ' ' + timeframe };
|
|
325
|
+
const summary = finindicators.compute(rates, specs);
|
|
326
|
+
return { ok: true, symbol, timeframe, ...summary };
|
|
327
|
+
},
|
|
178
328
|
async mt5_positions() {
|
|
179
329
|
if (!mt5.running) return notConnected();
|
|
180
330
|
const data = await bcall('positions', {}, 8000);
|
|
@@ -194,29 +344,119 @@ const handlers = {
|
|
|
194
344
|
days = Math.min(days, 90);
|
|
195
345
|
const data = await bcall('deals', { days }, 12000);
|
|
196
346
|
const deals = (data && data.deals) || [];
|
|
197
|
-
|
|
198
|
-
let profit = 0;
|
|
199
|
-
let wins = 0;
|
|
200
|
-
let losses = 0;
|
|
201
|
-
for (const dl of deals) {
|
|
202
|
-
const p = Number(dl.profit) || 0;
|
|
203
|
-
if (String(dl.entry) === '1' || dl.entry === 1 || p !== 0) {
|
|
204
|
-
profit += p + (Number(dl.swap) || 0) + (Number(dl.commission) || 0);
|
|
205
|
-
if (p > 0) wins++;
|
|
206
|
-
else if (p < 0) losses++;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
347
|
+
const stats = finstats.summarizeDeals(deals);
|
|
209
348
|
return {
|
|
210
349
|
ok: true,
|
|
211
350
|
days,
|
|
212
351
|
count: deals.length,
|
|
213
|
-
netProfit:
|
|
214
|
-
wins,
|
|
215
|
-
losses,
|
|
352
|
+
netProfit: stats.netProfit,
|
|
353
|
+
wins: stats.wins,
|
|
354
|
+
losses: stats.losses,
|
|
355
|
+
winRate: stats.winRate,
|
|
356
|
+
profitFactor: stats.profitFactor,
|
|
357
|
+
bySymbol: stats.bySymbol,
|
|
216
358
|
deals: deals.slice(-60),
|
|
217
359
|
};
|
|
218
360
|
},
|
|
219
|
-
async
|
|
361
|
+
async mt5_alerts(args) {
|
|
362
|
+
const action = String(args.action || 'list').toLowerCase();
|
|
363
|
+
if (action === 'list') {
|
|
364
|
+
const list = alertsApi.list();
|
|
365
|
+
return { ok: true, count: list.length, alerts: list };
|
|
366
|
+
}
|
|
367
|
+
if (action === 'remove') {
|
|
368
|
+
const id = String(args.id || '').trim();
|
|
369
|
+
if (!id) return { ok: false, error: 'id gerekli' };
|
|
370
|
+
const removed = alertsApi.remove(id);
|
|
371
|
+
return { ok: !!removed, removed: !!removed, id };
|
|
372
|
+
}
|
|
373
|
+
if (action === 'set') {
|
|
374
|
+
const symbol = String(args.symbol || '').trim().toUpperCase();
|
|
375
|
+
const price = Number(args.price);
|
|
376
|
+
const direction = String(args.direction || 'above').toLowerCase();
|
|
377
|
+
if (!symbol) return { ok: false, error: 'symbol gerekli' };
|
|
378
|
+
if (!isFinite(price) || price <= 0) return { ok: false, error: 'price gerekli (pozitif sayı)' };
|
|
379
|
+
if (direction !== 'above' && direction !== 'below') return { ok: false, error: "direction 'above' veya 'below' olmalı" };
|
|
380
|
+
const alarm = alertsApi.set({ symbol, price, direction, note: String(args.note || '').slice(0, 200) });
|
|
381
|
+
if (!alarm) return { ok: false, error: 'alarm kurulamadı' };
|
|
382
|
+
noteTrade('alert-set', { symbol, price, direction, id: alarm.id, note: alarm.note }, {});
|
|
383
|
+
return { ok: true, alert: alarm };
|
|
384
|
+
}
|
|
385
|
+
return { ok: false, error: "action: list|set|remove" };
|
|
386
|
+
},
|
|
387
|
+
async mt5_note(args, ctx) {
|
|
388
|
+
const symbol = String(args.symbol || '').trim().toUpperCase();
|
|
389
|
+
const note = String(args.note || '').trim();
|
|
390
|
+
if (!symbol || !note) return { ok: false, error: 'symbol ve note gerekli' };
|
|
391
|
+
noteTrade('note', {
|
|
392
|
+
symbol,
|
|
393
|
+
note: note.slice(0, 2000),
|
|
394
|
+
ticket: Number(args.ticket) || 0,
|
|
395
|
+
side: String(args.side || '').toLowerCase(),
|
|
396
|
+
}, ctx || {});
|
|
397
|
+
return { ok: true, saved: true };
|
|
398
|
+
},
|
|
399
|
+
async mt5_risksize(args) {
|
|
400
|
+
if (!mt5.running) return notConnected();
|
|
401
|
+
const cfg = getCfg();
|
|
402
|
+
const symbol = String(args.symbol || '').trim().toUpperCase();
|
|
403
|
+
if (!symbol) return { ok: false, error: 'symbol gerekli' };
|
|
404
|
+
const info = await symInfoRow(symbol);
|
|
405
|
+
if (!info) return { ok: false, error: 'sembol bulunamadı: ' + symbol + ' (MT5 Market Watch?)' };
|
|
406
|
+
const side = String(args.side || 'buy').toLowerCase() === 'sell' ? 'sell' : 'buy';
|
|
407
|
+
const entry = Number(args.entry) || tickPrice(info, side);
|
|
408
|
+
let sl = Number(args.sl) || 0;
|
|
409
|
+
let atr = null;
|
|
410
|
+
let timeframe = '';
|
|
411
|
+
if (!(sl > 0) && Number(args.atrMult) > 0) {
|
|
412
|
+
timeframe = String(args.timeframe || 'H1').trim().toUpperCase();
|
|
413
|
+
const rd = await bcall('rates', { symbol, timeframe, count: 100 }, 15000);
|
|
414
|
+
const rates = (rd && rd.rates) || [];
|
|
415
|
+
if (rates.length >= 20) {
|
|
416
|
+
const series = finindicators.atrSeries(
|
|
417
|
+
rates.map((r) => r.high),
|
|
418
|
+
rates.map((r) => r.low),
|
|
419
|
+
rates.map((r) => r.close),
|
|
420
|
+
14
|
|
421
|
+
);
|
|
422
|
+
atr = series[series.length - 1];
|
|
423
|
+
if (atr > 0) {
|
|
424
|
+
const d = atr * Number(args.atrMult);
|
|
425
|
+
sl = side === 'buy' ? entry - d : entry + d;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (!(sl > 0)) return { ok: false, error: 'sl ya da atrMult gerekli' };
|
|
430
|
+
const riskPct = Number(args.riskPct) > 0 ? Number(args.riskPct) : Number(cfg.riskPerTradePct) || 0;
|
|
431
|
+
if (!(riskPct > 0)) return { ok: false, error: 'riskPct gerekli (ayarlarda işlem riski %0)' };
|
|
432
|
+
const acct = await bcall('account', {}, 8000);
|
|
433
|
+
const balance = Number(acct && acct.account && acct.account.balance) || 0;
|
|
434
|
+
if (!(balance > 0)) return { ok: false, error: 'bakiye okunamadı' };
|
|
435
|
+
const riskAmount = (balance * riskPct) / 100;
|
|
436
|
+
const res = finrisk.calcRiskLot(info, entry, sl, riskAmount, cfg.maxLot);
|
|
437
|
+
if (res.error) return { ok: false, error: res.error, raw: res.raw, lossPerLot: res.lossPerLot, riskAmount: Math.round(riskAmount * 100) / 100 };
|
|
438
|
+
const digits = Number(info.digits) || 5;
|
|
439
|
+
const dist = Math.abs(entry - sl);
|
|
440
|
+
const closeErr = finrisk.stopsTooClose(info, entry, sl, 0);
|
|
441
|
+
return {
|
|
442
|
+
ok: true,
|
|
443
|
+
symbol,
|
|
444
|
+
side,
|
|
445
|
+
entry: Number(entry.toFixed(digits)),
|
|
446
|
+
sl: Number(sl.toFixed(digits)),
|
|
447
|
+
volume: res.volume,
|
|
448
|
+
riskPct,
|
|
449
|
+
riskAmount: Math.round(riskAmount * 100) / 100,
|
|
450
|
+
lossPerLot: res.lossPerLot,
|
|
451
|
+
distance: Number(dist.toFixed(digits)),
|
|
452
|
+
atr: atr != null ? Math.round(atr * 1e6) / 1e6 : null,
|
|
453
|
+
timeframe: timeframe || null,
|
|
454
|
+
capped: !!res.capped,
|
|
455
|
+
warning: closeErr || undefined,
|
|
456
|
+
maxLot: Number(cfg.maxLot) || 0.1,
|
|
457
|
+
};
|
|
458
|
+
},
|
|
459
|
+
async mt5_trade(args, ctx) {
|
|
220
460
|
const cfg = getCfg();
|
|
221
461
|
if (!cfg.allowTrading) {
|
|
222
462
|
return { ok: false, error: 'Otomatik işlem KAPALI — analiz/öneri modundasın. İşlem önerisini yaz, açma.' };
|
|
@@ -227,27 +467,54 @@ const handlers = {
|
|
|
227
467
|
const symbol = String(args.symbol || '').trim().toUpperCase();
|
|
228
468
|
if (!symbol) return { ok: false, error: 'symbol gerekli' };
|
|
229
469
|
const maxLot = Number(cfg.maxLot) || 0.1;
|
|
230
|
-
const
|
|
231
|
-
|
|
470
|
+
const sl = Number(args.sl) || 0;
|
|
471
|
+
const tp = Number(args.tp) || 0;
|
|
472
|
+
const info = await symInfoRow(symbol);
|
|
473
|
+
if (!info) return { ok: false, error: 'sembol bulunamadı: ' + symbol + ' (MT5 Market Watch?)' };
|
|
474
|
+
const price = tickPrice(info, side);
|
|
475
|
+
/* LOT: riskPct verilirse (ya da volume yokken ayar riski açıksa) SL
|
|
476
|
+
mesafesinden hesaplanır; aksi halde volume broker kuralına normalize edilir */
|
|
477
|
+
const explicitRisk = Number(args.riskPct) > 0 ? Number(args.riskPct) : 0;
|
|
478
|
+
const wantVolume = Number(args.volume);
|
|
479
|
+
let vol = 0;
|
|
480
|
+
let riskInfo = null;
|
|
481
|
+
if (explicitRisk > 0 || (!(wantVolume > 0) && Number(cfg.riskPerTradePct) > 0)) {
|
|
482
|
+
if (!(sl > 0)) return { ok: false, error: '%risk ile lot için sl zorunlu — sl ver ya da volume kullan' };
|
|
483
|
+
const riskPct = explicitRisk > 0 ? explicitRisk : Number(cfg.riskPerTradePct);
|
|
484
|
+
const acct = await bcall('account', {}, 8000);
|
|
485
|
+
const balance = Number(acct && acct.account && acct.account.balance) || 0;
|
|
486
|
+
const riskAmount = (balance * riskPct) / 100;
|
|
487
|
+
const rr = finrisk.calcRiskLot(info, price, sl, riskAmount, maxLot);
|
|
488
|
+
if (rr.error) return { ok: false, error: rr.error };
|
|
489
|
+
vol = rr.volume;
|
|
490
|
+
riskInfo = { riskPct, riskAmount: Math.round(riskAmount * 100) / 100, lossPerLot: rr.lossPerLot };
|
|
491
|
+
} else {
|
|
492
|
+
const norm = finrisk.normalizeVolume(info, wantVolume, maxLot);
|
|
493
|
+
if (norm.error) return { ok: false, error: norm.error };
|
|
494
|
+
vol = norm.volume;
|
|
495
|
+
}
|
|
232
496
|
const pos = await bcall('positions', {}, 8000);
|
|
497
|
+
const list = (pos && pos.positions) || [];
|
|
233
498
|
const maxPos = Number(cfg.maxPositions) || 3;
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return { ok: false, error: `max eşzamanlı pozisyon dolu (${openCount}/${maxPos}) — önce bir pozisyon kapat` };
|
|
499
|
+
if (list.length >= maxPos) {
|
|
500
|
+
return { ok: false, error: `max eşzamanlı pozisyon dolu (${list.length}/${maxPos}) — önce bir pozisyon kapat` };
|
|
237
501
|
}
|
|
502
|
+
/* risk katmanı: stops_level + yoğunluk/net yön + marj kalkanı */
|
|
503
|
+
const riskErr = await preTradeCheck(cfg, side, symbol, info, price, vol, sl, tp, list);
|
|
504
|
+
if (riskErr) return { ok: false, error: riskErr };
|
|
238
505
|
const data = await bcall('market', {
|
|
239
506
|
symbol,
|
|
240
507
|
side,
|
|
241
508
|
volume: vol,
|
|
242
|
-
sl
|
|
243
|
-
tp
|
|
509
|
+
sl,
|
|
510
|
+
tp,
|
|
244
511
|
deviation: 20,
|
|
245
512
|
comment: String(args.comment || 'Beast').slice(0, 26),
|
|
246
513
|
}, 20000);
|
|
247
|
-
noteTrade('trade', { symbol, side, volume: vol, result: data && data.result });
|
|
248
|
-
return { ok: true, opened: { symbol, side, volume: vol, sl
|
|
514
|
+
noteTrade('trade', { symbol, side, volume: vol, sl, tp, risk: riskInfo, comment: String(args.comment || ''), result: data && data.result }, ctx);
|
|
515
|
+
return { ok: true, opened: { symbol, side, volume: vol, sl, tp }, risk: riskInfo, result: data && data.result };
|
|
249
516
|
},
|
|
250
|
-
async mt5_close(args) {
|
|
517
|
+
async mt5_close(args, ctx) {
|
|
251
518
|
const cfg = getCfg();
|
|
252
519
|
if (!mt5.running) return notConnected();
|
|
253
520
|
const ticket = Number(args.ticket);
|
|
@@ -258,10 +525,10 @@ const handlers = {
|
|
|
258
525
|
if (vol > maxLot) return { ok: false, error: `kısmi kapatma lotu limiti aşıyor (max ${maxLot})` };
|
|
259
526
|
}
|
|
260
527
|
const data = await bcall('close', { ticket, volume: vol || 0 }, 20000);
|
|
261
|
-
noteTrade('close', { ticket, volume: vol || 'all', result: data && data.result });
|
|
528
|
+
noteTrade('close', { ticket, volume: vol || 'all', result: data && data.result }, ctx);
|
|
262
529
|
return { ok: true, result: data && data.result };
|
|
263
530
|
},
|
|
264
|
-
async mt5_modify(args) {
|
|
531
|
+
async mt5_modify(args, ctx) {
|
|
265
532
|
if (!mt5.running) return notConnected();
|
|
266
533
|
const ticket = Number(args.ticket);
|
|
267
534
|
if (!ticket) return { ok: false, error: 'ticket gerekli' };
|
|
@@ -270,20 +537,24 @@ const handlers = {
|
|
|
270
537
|
sl: Number(args.sl) || 0,
|
|
271
538
|
tp: Number(args.tp) || 0,
|
|
272
539
|
}, 20000);
|
|
273
|
-
noteTrade('modify', { ticket, sl: Number(args.sl) || 0, tp: Number(args.tp) || 0 });
|
|
540
|
+
noteTrade('modify', { ticket, sl: Number(args.sl) || 0, tp: Number(args.tp) || 0 }, ctx);
|
|
274
541
|
return { ok: true, result: data && data.result };
|
|
275
542
|
},
|
|
276
|
-
async mt5_pending(args) {
|
|
543
|
+
async mt5_pending(args, ctx) {
|
|
277
544
|
const cfg = getCfg();
|
|
278
545
|
if (!cfg.allowTrading) {
|
|
279
546
|
return { ok: false, error: 'Otomatik işlem KAPALI — bekleyen emir de açılamaz. Öneriyi yaz.' };
|
|
280
547
|
}
|
|
281
548
|
if (!mt5.running) return notConnected();
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
549
|
+
const symbol = String(args.symbol || '').trim().toUpperCase();
|
|
550
|
+
if (!symbol) return { ok: false, error: 'symbol gerekli' };
|
|
551
|
+
const info = await symInfoRow(symbol);
|
|
552
|
+
if (!info) return { ok: false, error: 'sembol bulunamadı: ' + symbol + ' (MT5 Market Watch?)' };
|
|
553
|
+
const norm = finrisk.normalizeVolume(info, args.volume, Number(cfg.maxLot) || 0.1);
|
|
554
|
+
if (norm.error) return { ok: false, error: norm.error };
|
|
555
|
+
const vol = norm.volume;
|
|
285
556
|
const data = await bcall('pending', {
|
|
286
|
-
symbol
|
|
557
|
+
symbol,
|
|
287
558
|
type: String(args.type || '').toLowerCase(),
|
|
288
559
|
volume: vol,
|
|
289
560
|
price: Number(args.price) || 0,
|
|
@@ -291,17 +562,17 @@ const handlers = {
|
|
|
291
562
|
tp: Number(args.tp) || 0,
|
|
292
563
|
comment: String(args.comment || 'Beast').slice(0, 26),
|
|
293
564
|
}, 20000);
|
|
294
|
-
noteTrade('pending', { symbol
|
|
565
|
+
noteTrade('pending', { symbol, type: args.type, volume: vol }, ctx);
|
|
295
566
|
return { ok: true, result: data && data.result };
|
|
296
567
|
},
|
|
297
|
-
async mt5_cancel(args) {
|
|
568
|
+
async mt5_cancel(args, ctx) {
|
|
298
569
|
if (!mt5.running) return notConnected();
|
|
299
570
|
const ticket = Number(args.ticket);
|
|
300
571
|
if (!ticket) return { ok: false, error: 'ticket gerekli' };
|
|
301
572
|
const data = await bcall('cancel', { ticket }, 20000);
|
|
302
|
-
noteTrade('cancel', { ticket });
|
|
573
|
+
noteTrade('cancel', { ticket }, ctx);
|
|
303
574
|
return { ok: true, result: data && data.result };
|
|
304
575
|
},
|
|
305
576
|
};
|
|
306
577
|
|
|
307
|
-
module.exports = { definitions, handlers, NAMES, setConfig, setNotify };
|
|
578
|
+
module.exports = { definitions, handlers, NAMES, setConfig, setNotify, setAlerts };
|