beast-agent 2.76.5 → 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 -6068
- package/src/preload.js +2 -17
- package/src/renderer/i18n.js +14 -8
- package/src/renderer/index.html +0 -297
- package/src/renderer/renderer.js +142 -2280
- 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
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/* Beast Finance — teknik gösterge matematiği (saf JS, bağımlılıksız).
|
|
4
|
-
mt5_rates'in döndürdüğü OHLC dizilerinden EMA/SMA/RSI/ATR/MACD/BB/Stoch
|
|
5
|
-
hesaplar. UI veya köprü gerektirmez; birim testleri doğrudan bu modülü çağırır. */
|
|
6
|
-
|
|
7
|
-
function nums(arr) {
|
|
8
|
-
return (Array.isArray(arr) ? arr : []).map((v) => {
|
|
9
|
-
const n = Number(v);
|
|
10
|
-
return isFinite(n) ? n : NaN;
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function smaSeries(values, period) {
|
|
15
|
-
const v = nums(values);
|
|
16
|
-
const out = new Array(v.length).fill(null);
|
|
17
|
-
const p = Math.max(1, Math.round(Number(period) || 1));
|
|
18
|
-
if (v.length < p) return out;
|
|
19
|
-
let sum = 0;
|
|
20
|
-
for (let i = 0; i < v.length; i++) {
|
|
21
|
-
sum += v[i];
|
|
22
|
-
if (i >= p) sum -= v[i - p];
|
|
23
|
-
if (i >= p - 1) out[i] = sum / p;
|
|
24
|
-
}
|
|
25
|
-
return out;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function emaSeries(values, period) {
|
|
29
|
-
const v = nums(values);
|
|
30
|
-
const out = new Array(v.length).fill(null);
|
|
31
|
-
const p = Math.max(1, Math.round(Number(period) || 1));
|
|
32
|
-
if (v.length < p) return out;
|
|
33
|
-
const k = 2 / (p + 1);
|
|
34
|
-
let sum = 0;
|
|
35
|
-
for (let i = 0; i < p; i++) sum += v[i];
|
|
36
|
-
let prev = sum / p;
|
|
37
|
-
out[p - 1] = prev;
|
|
38
|
-
for (let i = p; i < v.length; i++) {
|
|
39
|
-
prev = v[i] * k + prev * (1 - k);
|
|
40
|
-
out[i] = prev;
|
|
41
|
-
}
|
|
42
|
-
return out;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/* Wilder RSI — ilk ortalama SMA, sonrası Wilder yumuşatması */
|
|
46
|
-
function rsiSeries(closes, period) {
|
|
47
|
-
const c = nums(closes);
|
|
48
|
-
const p = Math.max(2, Math.round(Number(period) || 14));
|
|
49
|
-
const out = new Array(c.length).fill(null);
|
|
50
|
-
if (c.length <= p) return out;
|
|
51
|
-
let gain = 0;
|
|
52
|
-
let loss = 0;
|
|
53
|
-
for (let i = 1; i <= p; i++) {
|
|
54
|
-
const d = c[i] - c[i - 1];
|
|
55
|
-
if (d >= 0) gain += d;
|
|
56
|
-
else loss -= d;
|
|
57
|
-
}
|
|
58
|
-
let avgGain = gain / p;
|
|
59
|
-
let avgLoss = loss / p;
|
|
60
|
-
out[p] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
|
|
61
|
-
for (let i = p + 1; i < c.length; i++) {
|
|
62
|
-
const d = c[i] - c[i - 1];
|
|
63
|
-
const g = d > 0 ? d : 0;
|
|
64
|
-
const l = d < 0 ? -d : 0;
|
|
65
|
-
avgGain = (avgGain * (p - 1) + g) / p;
|
|
66
|
-
avgLoss = (avgLoss * (p - 1) + l) / p;
|
|
67
|
-
out[i] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
|
|
68
|
-
}
|
|
69
|
-
return out;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function trueRanges(highs, lows, closes) {
|
|
73
|
-
const h = nums(highs);
|
|
74
|
-
const l = nums(lows);
|
|
75
|
-
const c = nums(closes);
|
|
76
|
-
const out = new Array(c.length).fill(NaN);
|
|
77
|
-
for (let i = 0; i < c.length; i++) {
|
|
78
|
-
const prev = i > 0 ? c[i - 1] : c[i];
|
|
79
|
-
out[i] = Math.max(h[i] - l[i], Math.abs(h[i] - prev), Math.abs(l[i] - prev));
|
|
80
|
-
}
|
|
81
|
-
return out;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/* Wilder ATR */
|
|
85
|
-
function atrSeries(highs, lows, closes, period) {
|
|
86
|
-
const tr = trueRanges(highs, lows, closes);
|
|
87
|
-
const p = Math.max(1, Math.round(Number(period) || 14));
|
|
88
|
-
const out = new Array(tr.length).fill(null);
|
|
89
|
-
if (tr.length < p) return out;
|
|
90
|
-
let sum = 0;
|
|
91
|
-
for (let i = 0; i < p; i++) sum += tr[i];
|
|
92
|
-
let prev = sum / p;
|
|
93
|
-
out[p - 1] = prev;
|
|
94
|
-
for (let i = p; i < tr.length; i++) {
|
|
95
|
-
prev = (prev * (p - 1) + tr[i]) / p;
|
|
96
|
-
out[i] = prev;
|
|
97
|
-
}
|
|
98
|
-
return out;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function macdSeries(closes, fast, slow, signalPeriod) {
|
|
102
|
-
const f = Math.max(1, Math.round(Number(fast) || 12));
|
|
103
|
-
const s = Math.max(2, Math.round(Number(slow) || 26));
|
|
104
|
-
const sig = Math.max(1, Math.round(Number(signalPeriod) || 9));
|
|
105
|
-
const emaFast = emaSeries(closes, f);
|
|
106
|
-
const emaSlow = emaSeries(closes, s);
|
|
107
|
-
const macd = emaFast.map((v, i) => (v == null || emaSlow[i] == null ? null : v - emaSlow[i]));
|
|
108
|
-
const start = macd.findIndex((v) => v != null);
|
|
109
|
-
const compact = start >= 0 ? macd.slice(start) : [];
|
|
110
|
-
const signalCompact = emaSeries(compact, sig);
|
|
111
|
-
const signal = new Array(macd.length).fill(null);
|
|
112
|
-
if (start >= 0) {
|
|
113
|
-
for (let i = 0; i < signalCompact.length; i++) signal[start + i] = signalCompact[i];
|
|
114
|
-
}
|
|
115
|
-
const hist = macd.map((v, i) => (v == null || signal[i] == null ? null : v - signal[i]));
|
|
116
|
-
return { macd, signal, hist };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function bollingerSeries(closes, period, mult) {
|
|
120
|
-
const c = nums(closes);
|
|
121
|
-
const p = Math.max(2, Math.round(Number(period) || 20));
|
|
122
|
-
const m = Number(mult) > 0 ? Number(mult) : 2;
|
|
123
|
-
const mid = smaSeries(c, p);
|
|
124
|
-
const upper = new Array(c.length).fill(null);
|
|
125
|
-
const lower = new Array(c.length).fill(null);
|
|
126
|
-
for (let i = p - 1; i < c.length; i++) {
|
|
127
|
-
let sum = 0;
|
|
128
|
-
for (let j = i - p + 1; j <= i; j++) sum += Math.pow(c[j] - mid[i], 2);
|
|
129
|
-
const sd = Math.sqrt(sum / p);
|
|
130
|
-
upper[i] = mid[i] + m * sd;
|
|
131
|
-
lower[i] = mid[i] - m * sd;
|
|
132
|
-
}
|
|
133
|
-
return { mid, upper, lower };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function stochSeries(highs, lows, closes, kPeriod, kSmooth, dPeriod) {
|
|
137
|
-
const h = nums(highs);
|
|
138
|
-
const l = nums(lows);
|
|
139
|
-
const c = nums(closes);
|
|
140
|
-
const kp = Math.max(1, Math.round(Number(kPeriod) || 14));
|
|
141
|
-
const ks = Math.max(1, Math.round(Number(kSmooth) || 3));
|
|
142
|
-
const dp = Math.max(1, Math.round(Number(dPeriod) || 3));
|
|
143
|
-
const raw = new Array(c.length).fill(null);
|
|
144
|
-
for (let i = kp - 1; i < c.length; i++) {
|
|
145
|
-
let hh = -Infinity;
|
|
146
|
-
let ll = Infinity;
|
|
147
|
-
for (let j = i - kp + 1; j <= i; j++) {
|
|
148
|
-
if (h[j] > hh) hh = h[j];
|
|
149
|
-
if (l[j] < ll) ll = l[j];
|
|
150
|
-
}
|
|
151
|
-
raw[i] = hh === ll ? 50 : ((c[i] - ll) / (hh - ll)) * 100;
|
|
152
|
-
}
|
|
153
|
-
const k = smaSeries(raw, ks);
|
|
154
|
-
const d = smaSeries(k, dp);
|
|
155
|
-
return { k, d };
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/* "EMA(50)" | "ema50" | "RSI(14)" | "ATR(14)" | "MACD(12,26,9)" |
|
|
159
|
-
"BB(20,2)" | "SMA(200)" | "STOCH(14,3,3)" → {type, params} */
|
|
160
|
-
function parseSpec(spec) {
|
|
161
|
-
const s = String(spec || '').trim().toUpperCase().replace(/\s+/g, '');
|
|
162
|
-
const m = /^([A-Z]+)\(?([0-9,.\s]*)\)?$/.exec(s);
|
|
163
|
-
if (!m) return null;
|
|
164
|
-
const type = m[1];
|
|
165
|
-
const params = (m[2] ? m[2].split(',').map((x) => Number(x)) : []).filter((x) => isFinite(x));
|
|
166
|
-
if (!['EMA', 'SMA', 'RSI', 'ATR', 'MACD', 'BB', 'STOCH'].includes(type)) return null;
|
|
167
|
-
return { type, params };
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function lastN(series, n) {
|
|
171
|
-
const v = (Array.isArray(series) ? series : []).filter((x) => x != null);
|
|
172
|
-
const size = Math.max(1, Math.min(20, Number(n) || 1));
|
|
173
|
-
return v.slice(-size).map((x) => Math.round(x * 1e6) / 1e6);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/* Ham seriyi hesaplar; dönen değerler tam uzunlukta (warmup null). */
|
|
177
|
-
function seriesFor(spec, rates) {
|
|
178
|
-
const closes = rates.map((r) => Number(r.close));
|
|
179
|
-
const highs = rates.map((r) => Number(r.high));
|
|
180
|
-
const lows = rates.map((r) => Number(r.low));
|
|
181
|
-
const p = spec.params;
|
|
182
|
-
switch (spec.type) {
|
|
183
|
-
case 'EMA':
|
|
184
|
-
return { name: 'EMA', params: [p[0] || 20], series: emaSeries(closes, p[0] || 20) };
|
|
185
|
-
case 'SMA':
|
|
186
|
-
return { name: 'SMA', params: [p[0] || 20], series: smaSeries(closes, p[0] || 20) };
|
|
187
|
-
case 'RSI':
|
|
188
|
-
return { name: 'RSI', params: [p[0] || 14], series: rsiSeries(closes, p[0] || 14) };
|
|
189
|
-
case 'ATR':
|
|
190
|
-
return { name: 'ATR', params: [p[0] || 14], series: atrSeries(highs, lows, closes, p[0] || 14) };
|
|
191
|
-
case 'MACD': {
|
|
192
|
-
const r = macdSeries(closes, p[0] || 12, p[1] || 26, p[2] || 9);
|
|
193
|
-
return { name: 'MACD', params: [p[0] || 12, p[1] || 26, p[2] || 9], series: r.macd, extra: { signal: r.signal, hist: r.hist } };
|
|
194
|
-
}
|
|
195
|
-
case 'BB': {
|
|
196
|
-
const r = bollingerSeries(closes, p[0] || 20, p[1] || 2);
|
|
197
|
-
return { name: 'BB', params: [p[0] || 20, p[1] || 2], series: r.mid, extra: { upper: r.upper, lower: r.lower } };
|
|
198
|
-
}
|
|
199
|
-
case 'STOCH': {
|
|
200
|
-
const r = stochSeries(highs, lows, closes, p[0] || 14, p[1] || 3, p[2] || 3);
|
|
201
|
-
return { name: 'STOCH', params: [p[0] || 14, p[1] || 3, p[2] || 3], series: r.k, extra: { d: r.d } };
|
|
202
|
-
}
|
|
203
|
-
default:
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const DEFAULT_SPECS = ['EMA(20)', 'EMA(50)', 'RSI(14)', 'ATR(14)', 'MACD(12,26,9)'];
|
|
209
|
-
|
|
210
|
-
/* rates: [{time, open, high, low, close, ...}] → ajan dostu özet.
|
|
211
|
-
Her gösterge için son 3 değer + (varsa) ek seriler. */
|
|
212
|
-
function compute(rates, specs) {
|
|
213
|
-
const rows = Array.isArray(rates) ? rates : [];
|
|
214
|
-
const list = (Array.isArray(specs) && specs.length ? specs : DEFAULT_SPECS)
|
|
215
|
-
.map((s) => (typeof s === 'string' ? parseSpec(s) : s))
|
|
216
|
-
.filter(Boolean)
|
|
217
|
-
.slice(0, 12);
|
|
218
|
-
const close = rows.length ? Number(rows[rows.length - 1].close) : null;
|
|
219
|
-
const out = { bars: rows.length, close, indicators: {} };
|
|
220
|
-
for (const spec of list) {
|
|
221
|
-
const r = seriesFor(spec, rows);
|
|
222
|
-
if (!r) continue;
|
|
223
|
-
const key = r.name + '(' + r.params.join(',') + ')';
|
|
224
|
-
const entry = { last: lastN(r.series, 3) };
|
|
225
|
-
if (r.extra) {
|
|
226
|
-
entry.extra = {};
|
|
227
|
-
for (const [k, v] of Object.entries(r.extra)) entry.extra[k] = lastN(v, 3);
|
|
228
|
-
}
|
|
229
|
-
out.indicators[key] = entry;
|
|
230
|
-
}
|
|
231
|
-
const highs = rows.map((x) => Number(x.high));
|
|
232
|
-
const lows = rows.map((x) => Number(x.low));
|
|
233
|
-
if (highs.length) {
|
|
234
|
-
const window = Math.min(20, rows.length);
|
|
235
|
-
out.range20 = {
|
|
236
|
-
high: Math.max(...highs.slice(-window)),
|
|
237
|
-
low: Math.min(...lows.slice(-window)),
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
return out;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
module.exports = {
|
|
244
|
-
parseSpec,
|
|
245
|
-
compute,
|
|
246
|
-
smaSeries,
|
|
247
|
-
emaSeries,
|
|
248
|
-
rsiSeries,
|
|
249
|
-
atrSeries,
|
|
250
|
-
macdSeries,
|
|
251
|
-
bollingerSeries,
|
|
252
|
-
stochSeries,
|
|
253
|
-
DEFAULT_SPECS,
|
|
254
|
-
};
|
package/src/agent/finrisk.js
DELETED
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/* Beast Finance — işlem öncesi risk katmanı (saf; köprü/IO yok).
|
|
4
|
-
Lot normalizasyonu (broker min/max/step), %risk ile lot hesabı (SL mesafesi
|
|
5
|
-
veya ATR), stops_level doğrulaması, marj kalkanı ve yoğunluk (net yön)
|
|
6
|
-
kontrolü. financetools bu kararları uygular; birim testleri doğrudan çağırır. */
|
|
7
|
-
|
|
8
|
-
function num(v, def) {
|
|
9
|
-
const n = Number(v);
|
|
10
|
-
return isFinite(n) ? n : def;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function stepFloor(v, step) {
|
|
14
|
-
const s = num(step, 0.01);
|
|
15
|
-
if (!(s > 0)) return v;
|
|
16
|
-
return Math.floor(v / s + 1e-9) * s;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function stepCeil(v, step) {
|
|
20
|
-
const s = num(step, 0.01);
|
|
21
|
-
if (!(s > 0)) return v;
|
|
22
|
-
return Math.ceil(v / s - 1e-9) * s;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/* info: MT5 symbol_info satırı. wanted: istenen lot. hardMax: kullanıcı üst
|
|
26
|
-
limiti (max lot). hardMin: kullanıcı alt limiti (min lot) — istenen lot bu
|
|
27
|
-
tabanın altındaysa adıma yukarı yuvarlanır (raised). Taban/tavan broker
|
|
28
|
-
adımına OTURMUYORSA (ör. min 0.15–max 0.19, adım 0.1) sessizce limit dışına
|
|
29
|
-
düşmek yerine net hata döner — aralığı mt5_limits ile düzelt. */
|
|
30
|
-
function normalizeVolume(info, wanted, hardMax, hardMin) {
|
|
31
|
-
const vmin = num(info && info.volume_min, 0.01);
|
|
32
|
-
const vmax = num(info && info.volume_max, 100);
|
|
33
|
-
const step = num(info && info.volume_step, 0.01);
|
|
34
|
-
let v = num(wanted, 0);
|
|
35
|
-
if (!(v > 0)) return { error: 'volume gerekli (pozitif sayı)' };
|
|
36
|
-
const cap = Math.min(vmax > 0 ? vmax : Infinity, num(hardMax, 0) > 0 ? num(hardMax, 0) : Infinity);
|
|
37
|
-
const floor = num(hardMin, 0) > 0 ? num(hardMin, 0) : 0;
|
|
38
|
-
const align = (x) => Math.round(x * 1e8) / 1e8;
|
|
39
|
-
const mismatch = () => ({
|
|
40
|
-
error: `lot aralığı broker adımıyla uyuşmuyor (min ${floor}, max ${cap === Infinity ? '∞' : cap}, adım ${step}) — mt5_limits ile aralığı düzelt`,
|
|
41
|
-
});
|
|
42
|
-
if (floor > cap + 1e-9) return mismatch();
|
|
43
|
-
let capped = false;
|
|
44
|
-
let raised = false;
|
|
45
|
-
if (v > cap) {
|
|
46
|
-
v = align(stepFloor(cap, step));
|
|
47
|
-
capped = true;
|
|
48
|
-
if (floor > 0 && v < floor - 1e-9) return mismatch();
|
|
49
|
-
}
|
|
50
|
-
if (v < floor - 1e-9) {
|
|
51
|
-
v = align(stepCeil(floor, step));
|
|
52
|
-
raised = true;
|
|
53
|
-
if (v > cap + 1e-9) return mismatch();
|
|
54
|
-
}
|
|
55
|
-
v = align(stepFloor(v, step));
|
|
56
|
-
if (floor > 0 && v < floor - 1e-9) { v = align(stepCeil(floor, step)); raised = true; }
|
|
57
|
-
if (!(v > 0) || v < vmin - 1e-9) {
|
|
58
|
-
return { error: `hacim broker minimumunun altında (min ${vmin} lot)` };
|
|
59
|
-
}
|
|
60
|
-
if (v > cap + 1e-9) {
|
|
61
|
-
v = align(stepFloor(cap, step));
|
|
62
|
-
capped = true;
|
|
63
|
-
if (floor > 0 && v < floor - 1e-9) return mismatch();
|
|
64
|
-
if (!(v > 0) || v < vmin - 1e-9) {
|
|
65
|
-
return { error: `hacim broker minimumunun altında (min ${vmin} lot)` };
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return { volume: v, capped, raised };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/* 1 lot için SL mesafesi kaybı (hesap para birimi): tick değeri, yoksa kontrat */
|
|
72
|
-
function lossPerLot(info, entry, sl) {
|
|
73
|
-
const dist = Math.abs(num(entry, 0) - num(sl, 0));
|
|
74
|
-
if (!(dist > 0)) return 0;
|
|
75
|
-
const tickSize = num(info && info.trade_tick_size, 0) || num(info && info.point, 0);
|
|
76
|
-
const tickValue = num(info && info.trade_tick_value, 0);
|
|
77
|
-
if (tickSize > 0 && tickValue > 0) return (dist / tickSize) * tickValue;
|
|
78
|
-
const contract = num(info && info.trade_contract_size, 0);
|
|
79
|
-
if (contract > 0) return dist * contract;
|
|
80
|
-
return 0;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/* riskAmount: hesap para birimi cinsinden kaybedilecek tutar */
|
|
84
|
-
function calcRiskLot(info, entry, sl, riskAmount, hardMax, hardMin) {
|
|
85
|
-
const lpl = lossPerLot(info, entry, sl);
|
|
86
|
-
if (!(lpl > 0)) return { error: 'risk hesaplanamadı (tick değeri/kontrat bilgisi yok)' };
|
|
87
|
-
const raw = num(riskAmount, 0) / lpl;
|
|
88
|
-
const norm = normalizeVolume(info, raw, hardMax, hardMin);
|
|
89
|
-
if (norm.error) return { error: norm.error, raw, lossPerLot: lpl };
|
|
90
|
-
return {
|
|
91
|
-
volume: norm.volume,
|
|
92
|
-
raw: Math.round(raw * 1e4) / 1e4,
|
|
93
|
-
lossPerLot: Math.round(lpl * 100) / 100,
|
|
94
|
-
riskAmount: Math.round(raw * lpl * 100) / 100,
|
|
95
|
-
capped: !!norm.capped,
|
|
96
|
-
raised: !!norm.raised,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/* stops_level ihlali: SL/TP fiyata çok yakın mı? (broker reddini önler)
|
|
101
|
-
Yakınsa Türkçe hata metni, sorun yoksa null döner. */
|
|
102
|
-
function stopsTooClose(info, price, sl, tp) {
|
|
103
|
-
const stops = num(info && info.trade_stops_level, 0);
|
|
104
|
-
if (!(stops > 0)) return null;
|
|
105
|
-
const point = num(info && info.point, 0);
|
|
106
|
-
if (!(point > 0)) return null;
|
|
107
|
-
const minDist = stops * point;
|
|
108
|
-
const p = num(price, 0);
|
|
109
|
-
const s = num(sl, 0);
|
|
110
|
-
const t = num(tp, 0);
|
|
111
|
-
if (p > 0 && s > 0 && Math.abs(p - s) < minDist - 1e-12) {
|
|
112
|
-
return `SL fiyata çok yakın — broker en az ${minDist.toFixed(point < 0.01 ? 3 : 2)} mesafe istiyor`;
|
|
113
|
-
}
|
|
114
|
-
if (p > 0 && t > 0 && Math.abs(p - t) < minDist - 1e-12) {
|
|
115
|
-
return `TP fiyata çok yakın — broker en az ${minDist.toFixed(point < 0.01 ? 3 : 2)} mesafe istiyor`;
|
|
116
|
-
}
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/* Marj kalkanı: mevcut serbest marj + işlem sonrası tahmini marj seviyesi.
|
|
121
|
-
Sorun varsa hata metni, yoksa null. */
|
|
122
|
-
function marginCheck(account, estMargin, minLevel) {
|
|
123
|
-
const equity = num(account && account.equity, 0);
|
|
124
|
-
const used = num(account && account.margin, 0);
|
|
125
|
-
const free = num(account && account.margin_free, 0);
|
|
126
|
-
const current = num(account && account.margin_level, 0);
|
|
127
|
-
const m = num(estMargin, 0);
|
|
128
|
-
const minL = num(minLevel, 0);
|
|
129
|
-
if (m > 0 && free > 0 && m > free) {
|
|
130
|
-
return { error: `yetersiz serbest marj (gerekli ~${m.toFixed(2)}, serbest ${free.toFixed(2)})` };
|
|
131
|
-
}
|
|
132
|
-
if (minL > 0 && current > 0 && current < minL) {
|
|
133
|
-
return { error: `marj seviyesi kalkanı: mevcut %${current.toFixed(0)} < min %${minL}` };
|
|
134
|
-
}
|
|
135
|
-
if (minL > 0 && equity > 0 && (used > 0 || m > 0)) {
|
|
136
|
-
const after = (equity / (used + m)) * 100;
|
|
137
|
-
if (after < minL) {
|
|
138
|
-
return { error: `marj seviyesi kalkanı: işlem sonrası tahmini %${after.toFixed(0)} < min %${minL}`, level: Math.round(after * 100) / 100 };
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/* Yoğunluk: aynı yöne birikme + sembol başına aynı yön limiti.
|
|
145
|
-
Sorun varsa hata metni, yoksa null. 0 = ilgili kural kapalı. */
|
|
146
|
-
function exposureCheck(positions, side, symbol, maxPerSymbol, maxSameSide) {
|
|
147
|
-
const list = Array.isArray(positions) ? positions : [];
|
|
148
|
-
const isBuy = String(side).toLowerCase() === 'buy';
|
|
149
|
-
const sym = String(symbol || '').toUpperCase();
|
|
150
|
-
const maxSame = num(maxSameSide, 0);
|
|
151
|
-
const maxSym = num(maxPerSymbol, 0);
|
|
152
|
-
if (maxSame > 0) {
|
|
153
|
-
const sameDir = list.filter((p) => (Number(p.type) === 0) === isBuy).length;
|
|
154
|
-
if (sameDir >= maxSame) return `aynı yönde ${sameDir} pozisyon var (max ${maxSame}) — çeşitlendir`;
|
|
155
|
-
}
|
|
156
|
-
if (maxSym > 0) {
|
|
157
|
-
const sameSym = list.filter(
|
|
158
|
-
(p) => String(p.symbol || '').toUpperCase() === sym && (Number(p.type) === 0) === isBuy
|
|
159
|
-
).length;
|
|
160
|
-
if (sameSym >= maxSym) return `${sym} ${isBuy ? 'BUY' : 'SELL'} için ${sameSym} pozisyon var (max ${maxSym})`;
|
|
161
|
-
}
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/* ---------- DİSİPLİN (kodla zorlanan trader kuralları) ----------
|
|
166
|
-
Prompt ricalarına güvenmeden uygulanır: günlük işlem limiti, ardışık kayıp
|
|
167
|
-
molası, sembol re-entry beklemesi ve yönlü kur maruziyeti (korelasyon). */
|
|
168
|
-
|
|
169
|
-
const SYM_ALIAS = {
|
|
170
|
-
XAU: 'XAU', GOLD: 'XAU', XAUT: 'XAU',
|
|
171
|
-
XAG: 'XAG', SILV: 'XAG', SILVER: 'XAG',
|
|
172
|
-
WTI: 'OIL', BREN: 'OIL', BRENT: 'OIL', USOI: 'OIL', UKOI: 'OIL',
|
|
173
|
-
NATG: 'GAS', NGAS: 'GAS',
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
/* Sembolün para bacakları: EURUSD → [EUR,USD]; GOLD → [XAU,USD]; bilinmezse [] */
|
|
177
|
-
function symbolLegs(symbol) {
|
|
178
|
-
const s = String(symbol || '').toUpperCase().replace(/[^A-Z]/g, '');
|
|
179
|
-
if (/^[A-Z]{6}$/.test(s)) return [s.slice(0, 3), s.slice(3)];
|
|
180
|
-
const base = SYM_ALIAS[s.slice(0, 4)] || SYM_ALIAS[s.slice(0, 3)];
|
|
181
|
-
if (base) return [base, 'USD'];
|
|
182
|
-
if (/^USD[A-Z]{3}$/.test(s)) return ['USD', s.slice(3)];
|
|
183
|
-
return [];
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/* Yönlü kur maruziyeti: buy EURUSD = +EUR −USD. Limitsizse null. */
|
|
187
|
-
function currencyExposureError(positions, side, symbol, maxPerCurrency) {
|
|
188
|
-
const max = num(maxPerCurrency, 0);
|
|
189
|
-
if (!(max > 0)) return null;
|
|
190
|
-
const legs = symbolLegs(symbol);
|
|
191
|
-
if (!legs.length) return null;
|
|
192
|
-
const dir = String(side).toLowerCase() === 'buy' ? 1 : -1;
|
|
193
|
-
const exp = {};
|
|
194
|
-
const add = (cur, d) => { if (cur) exp[cur] = (exp[cur] || 0) + d; };
|
|
195
|
-
for (const p of (Array.isArray(positions) ? positions : [])) {
|
|
196
|
-
const l = symbolLegs(p && p.symbol);
|
|
197
|
-
if (!l.length) continue;
|
|
198
|
-
const d = Number(p.type) === 0 ? 1 : -1;
|
|
199
|
-
add(l[0], d);
|
|
200
|
-
add(l[1], -d);
|
|
201
|
-
}
|
|
202
|
-
add(legs[0], dir);
|
|
203
|
-
add(legs[1], -dir);
|
|
204
|
-
for (const [cur, v] of Object.entries(exp)) {
|
|
205
|
-
if (Math.abs(v) > max) {
|
|
206
|
-
return `${cur} yönlü maruziyet ${v > 0 ? '+' : ''}${v} (max ±${max}) — korelasyon riski; farklı parite/yöne geç`;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/* Zaman/geçmiş tabanlı disiplin. entries: journal kayıtları (at, kind, symbol, net).
|
|
213
|
-
Sorun varsa Türkçe hata metni, yoksa null döner. Limit 0 = kural kapalı. */
|
|
214
|
-
function disciplineError(entries, now, cfg, side, symbol, positions) {
|
|
215
|
-
const t = num(now, Date.now());
|
|
216
|
-
/* gelecekteki kayıtlar (saat kayması) değerlendirmeye girmez */
|
|
217
|
-
const list = (Array.isArray(entries) ? entries : []).filter((e) => e && Number.isFinite(Number(e.at)) && Number(e.at) <= t);
|
|
218
|
-
const f = cfg || {};
|
|
219
|
-
const dayStart = (() => { const d = new Date(t); d.setHours(0, 0, 0, 0); return d.getTime(); })();
|
|
220
|
-
const maxDay = num(f.maxTradesPerDay, 0);
|
|
221
|
-
if (maxDay > 0) {
|
|
222
|
-
/* TEK İŞLEM = AÇILIŞ + KAPANIŞ: gün içindeki açılışlar TICKET bazında
|
|
223
|
-
sayılır; aynı pozisyonun kapanış kaydı AYRICA sayılmaz (çift sayım yok,
|
|
224
|
-
watchdog yeniden açılış yazsa bile ticket tekrarı düşülür). Ticket'sız
|
|
225
|
-
eski kayıtlar tek tek sayılır (geriye dönük uyum). */
|
|
226
|
-
const seen = new Set();
|
|
227
|
-
let opens = 0;
|
|
228
|
-
for (const e of list) {
|
|
229
|
-
if (Number(e.at) < dayStart) continue;
|
|
230
|
-
if (e.kind !== 'open') continue;
|
|
231
|
-
const tk = e.ticket != null ? String(e.ticket) : '';
|
|
232
|
-
if (tk && tk !== '0') {
|
|
233
|
-
if (seen.has(tk)) continue;
|
|
234
|
-
seen.add(tk);
|
|
235
|
-
}
|
|
236
|
-
opens += 1;
|
|
237
|
-
}
|
|
238
|
-
if (opens >= maxDay) {
|
|
239
|
-
return `günlük işlem limiti dolu (${opens}/${maxDay}) — bugün yeni işlem açma`;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
const streakLimit = num(f.lossStreakLimit, 0);
|
|
243
|
-
const pauseMin = num(f.lossStreakPauseMin, 0);
|
|
244
|
-
if (streakLimit > 0 && pauseMin > 0) {
|
|
245
|
-
const closes = list.filter((e) => e.kind === 'close').slice().sort((a, b) => num(b.at, 0) - num(a.at, 0));
|
|
246
|
-
let streak = 0;
|
|
247
|
-
let lastAt = 0;
|
|
248
|
-
for (const c of closes) {
|
|
249
|
-
if (num(c.net, 0) < 0) {
|
|
250
|
-
streak++;
|
|
251
|
-
if (!lastAt) lastAt = num(c.at, 0);
|
|
252
|
-
} else break;
|
|
253
|
-
}
|
|
254
|
-
if (streak >= streakLimit && lastAt) {
|
|
255
|
-
const elapsed = t - lastAt;
|
|
256
|
-
if (elapsed < pauseMin * 60000) {
|
|
257
|
-
const rem = Math.ceil((pauseMin * 60000 - elapsed) / 60000);
|
|
258
|
-
return `${streak} ardışık kayıp — seri molası ${rem} dk daha (limit ${streakLimit} kayıp → ${pauseMin} dk mola)`;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
const reMin = num(f.reentryCooldownMin, 0);
|
|
263
|
-
if (reMin > 0) {
|
|
264
|
-
const sym = String(symbol || '').toUpperCase();
|
|
265
|
-
const last = list
|
|
266
|
-
.filter((e) => e.kind === 'close' && String(e.symbol || '').toUpperCase() === sym)
|
|
267
|
-
.sort((a, b) => num(b.at, 0) - num(a.at, 0))[0];
|
|
268
|
-
if (last) {
|
|
269
|
-
const elapsed = t - num(last.at, 0);
|
|
270
|
-
if (elapsed < reMin * 60000) {
|
|
271
|
-
const rem = Math.ceil((reMin * 60000 - elapsed) / 60000);
|
|
272
|
-
return `${sym} son işlemi ${Math.max(1, Math.round(elapsed / 60000))} dk önce kapandı — re-entry için ${rem} dk bekle`;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
return currencyExposureError(positions, side, symbol, f.maxPerCurrency);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
module.exports = {
|
|
280
|
-
normalizeVolume,
|
|
281
|
-
lossPerLot,
|
|
282
|
-
calcRiskLot,
|
|
283
|
-
stopsTooClose,
|
|
284
|
-
marginCheck,
|
|
285
|
-
exposureCheck,
|
|
286
|
-
symbolLegs,
|
|
287
|
-
currencyExposureError,
|
|
288
|
-
disciplineError,
|
|
289
|
-
};
|