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.
@@ -0,0 +1,110 @@
1
+ 'use strict';
2
+
3
+ /* Beast Finance — risk otomasyonu KARAR katmanı (saf; köprü/IO yok).
4
+ Açık pozisyon + sembol meta verisi + pozisyon durumu (st) alır; uygulanacak
5
+ aksiyonları döndürür:
6
+ {kind:'modify', sl} → SL'yi taşı (BE / trailing)
7
+ {kind:'partial', volume} → kısmi TP kapat
8
+ {kind:'warnSL'} → SL'ye yaklaşma uyarısı
9
+ {kind:'warnNoSL'} → SL'siz pozisyon uyarısı
10
+ main süreç aksiyonları köprüye uygular; başarılı olanın bayrağını st'ye yazar. */
11
+
12
+ function num(v, def) {
13
+ const n = Number(v);
14
+ return isFinite(n) ? n : def;
15
+ }
16
+
17
+ function roundTo(v, digits) {
18
+ const d = Math.max(0, Math.min(8, Math.round(Number(digits) || 0)));
19
+ const p = Math.pow(10, d);
20
+ return Math.round(v * p) / p;
21
+ }
22
+
23
+ function stepRound(v, step) {
24
+ const s = num(step, 0.01);
25
+ if (!(s > 0)) return v;
26
+ const q = Math.floor(v / s + 1e-9);
27
+ return q * s;
28
+ }
29
+
30
+ /* meta: { digits, point, trade_stops_level, volume_min, volume_max, volume_step } */
31
+ function plan(pos, meta, st, cfg) {
32
+ const out = { actions: [], r: num(st && st.r, 0) };
33
+ if (!pos || !meta) return out;
34
+ const isBuy = Number(pos.type) === 0;
35
+ const entry = num(pos.price_open, 0);
36
+ const ref = num(pos.price_current, 0);
37
+ const sl = num(pos.sl, 0);
38
+ const volume = num(pos.volume, 0);
39
+ const digits = num(meta.digits, 5);
40
+ const point = num(meta.point, Math.pow(10, -digits));
41
+ const stopsPoints = num(meta.trade_stops_level, 0);
42
+ const minDist = stopsPoints > 0 ? (stopsPoints + 1) * point : point;
43
+ if (!(entry > 0) || !(ref > 0) || !(volume > 0)) return out;
44
+
45
+ /* R (ilk risk mesafesi) yalnızca pozisyon ilk görüldüğünde SL'den hesaplanır */
46
+ let r = num(st && st.r, 0);
47
+ if (!(r > 0) && sl > 0) r = Math.abs(entry - sl);
48
+ out.r = r;
49
+
50
+ const beOn = num(cfg && cfg.beOnR, 1);
51
+ const beOff = num(cfg && cfg.beOffsetR, 0.05);
52
+ const trailStart = num(cfg && cfg.trailStartR, 1.5);
53
+ const trailDist = num(cfg && cfg.trailR, 0.5);
54
+ const partialR = num(cfg && cfg.partialR, 0);
55
+ const partialPct = num(cfg && cfg.partialPct, 50);
56
+
57
+ const profit = isBuy ? ref - entry : entry - ref;
58
+
59
+ /* ---- BE + trailing: hedef SL'yi birleştir, asla geriye taşıma ---- */
60
+ if (r > 0) {
61
+ let desired = sl > 0 ? sl : 0;
62
+ let want = false;
63
+ if (beOn > 0 && !(st && st.be) && profit >= beOn * r) {
64
+ const beSl = isBuy ? entry + beOff * r : entry - beOff * r;
65
+ if (!(desired > 0) || (isBuy ? beSl > desired : beSl < desired)) desired = beSl;
66
+ want = true;
67
+ }
68
+ if (trailStart > 0 && trailDist > 0 && profit >= trailStart * r) {
69
+ const trailSl = isBuy ? ref - trailDist * r : ref + trailDist * r;
70
+ if (!(desired > 0) || (isBuy ? trailSl > desired : trailSl < desired)) desired = trailSl;
71
+ want = true;
72
+ }
73
+ if (want && desired > 0) {
74
+ /* broker stop mesafesi: SL fiyata çok yakınsa geçersiz — kırp */
75
+ desired = isBuy ? Math.min(desired, ref - minDist) : Math.max(desired, ref + minDist);
76
+ const improved = isBuy ? desired > sl + point * 0.5 : (sl === 0 || desired < sl - point * 0.5);
77
+ if (improved && desired > 0) {
78
+ out.actions.push({ kind: 'modify', sl: roundTo(desired, digits) });
79
+ }
80
+ }
81
+ }
82
+
83
+ /* ---- kısmi TP ---- */
84
+ if (partialR > 0 && partialPct > 0 && partialPct < 100 && r > 0 && !(st && st.partial) && profit >= partialR * r) {
85
+ const step = num(meta.volume_step, 0.01);
86
+ const vmin = num(meta.volume_min, 0.01);
87
+ const vmax = num(meta.volume_max, 100);
88
+ let vol = stepRound((volume * partialPct) / 100, step);
89
+ vol = Math.min(vol, vmax);
90
+ const remain = volume - vol;
91
+ if (vol >= vmin && remain >= vmin - 1e-9 && vol < volume) {
92
+ out.actions.push({ kind: 'partial', volume: Math.round(vol * 1e8) / 1e8 });
93
+ }
94
+ }
95
+
96
+ /* ---- uyarılar ---- */
97
+ if (sl > 0) {
98
+ if (r > 0) {
99
+ const dist = isBuy ? ref - sl : sl - ref;
100
+ const thr = Math.max(minDist * 3, 0.2 * r);
101
+ if (dist > 0 && dist <= thr && !(st && st.warnSL)) out.actions.push({ kind: 'warnSL' });
102
+ out.rearmSL = dist > thr * 1.6;
103
+ }
104
+ } else if (!(st && st.warnNoSL)) {
105
+ out.actions.push({ kind: 'warnNoSL' });
106
+ }
107
+ return out;
108
+ }
109
+
110
+ module.exports = { plan, roundTo, stepRound };
@@ -291,6 +291,69 @@ def h_cancel(p):
291
291
  return {"result": _send_request(req)}
292
292
 
293
293
 
294
+ def h_rates(p):
295
+ """OHLC mumlar: symbol + timeframe (M1..MN1) + count (1..1000)."""
296
+ need()
297
+ symbol = str(p.get("symbol") or "").strip().upper()
298
+ if not symbol:
299
+ raise RuntimeError("symbol zorunlu")
300
+ tf_name = str(p.get("timeframe") or "M15").strip().upper()
301
+ tf_map = {
302
+ "M1": mt5.TIMEFRAME_M1, "M2": mt5.TIMEFRAME_M2, "M3": mt5.TIMEFRAME_M3,
303
+ "M4": mt5.TIMEFRAME_M4, "M5": mt5.TIMEFRAME_M5, "M6": mt5.TIMEFRAME_M6,
304
+ "M10": mt5.TIMEFRAME_M10, "M12": mt5.TIMEFRAME_M12, "M15": mt5.TIMEFRAME_M15,
305
+ "M20": mt5.TIMEFRAME_M20, "M30": mt5.TIMEFRAME_M30,
306
+ "H1": mt5.TIMEFRAME_H1, "H2": mt5.TIMEFRAME_H2, "H3": mt5.TIMEFRAME_H3,
307
+ "H4": mt5.TIMEFRAME_H4, "H6": mt5.TIMEFRAME_H6, "H8": mt5.TIMEFRAME_H8,
308
+ "H12": mt5.TIMEFRAME_H12,
309
+ "D1": mt5.TIMEFRAME_D1, "W1": mt5.TIMEFRAME_W1, "MN1": mt5.TIMEFRAME_MN1,
310
+ }
311
+ if tf_name not in tf_map:
312
+ raise RuntimeError("timeframe: " + "|".join(tf_map.keys()))
313
+ try:
314
+ count = int(p.get("count") or 200)
315
+ except Exception:
316
+ count = 200
317
+ count = max(1, min(count, 1000))
318
+ mt5.symbol_select(symbol, True)
319
+ rates = mt5.copy_rates_from_pos(symbol, tf_map[tf_name], 0, count)
320
+ if rates is None:
321
+ raise RuntimeError("mum verisi alinamadi: %s %s %s" % (symbol, tf_name, mt5.last_error()))
322
+ rows = []
323
+ for r in rates:
324
+ rows.append({
325
+ "time": int(r["time"]),
326
+ "open": float(r["open"]),
327
+ "high": float(r["high"]),
328
+ "low": float(r["low"]),
329
+ "close": float(r["close"]),
330
+ "tick_volume": int(r["tick_volume"]),
331
+ "spread": int(r["spread"]),
332
+ "real_volume": int(r["real_volume"]),
333
+ })
334
+ return {"symbol": symbol, "timeframe": tf_name, "count": len(rows), "rates": rows}
335
+
336
+
337
+ def h_margin(p):
338
+ """order_calc_margin: tahmini gerekli marj (islem oncesi kalkan icin)."""
339
+ need()
340
+ symbol = str(p.get("symbol") or "").strip().upper()
341
+ side = str(p.get("side") or "buy").lower()
342
+ volume = float(p.get("volume") or 0)
343
+ if not symbol or volume <= 0 or side not in ("buy", "sell"):
344
+ raise RuntimeError("symbol/side/volume zorunlu")
345
+ mt5.symbol_select(symbol, True)
346
+ price = float(p.get("price") or 0)
347
+ if price <= 0:
348
+ tick = mt5.symbol_info_tick(symbol)
349
+ if tick is None:
350
+ raise RuntimeError("tick alinamadi: " + symbol)
351
+ price = tick.ask if side == "buy" else tick.bid
352
+ order_type = mt5.ORDER_TYPE_BUY if side == "buy" else mt5.ORDER_TYPE_SELL
353
+ m = mt5.order_calc_margin(order_type, symbol, volume, price)
354
+ return {"margin": (float(m) if m is not None else None), "price": price}
355
+
356
+
294
357
  def h_all_symbols(p):
295
358
  """Terminaldeki tum semboller (sembol secici icin). filter: '*XAU*' gibi."""
296
359
  need()
@@ -316,6 +379,7 @@ HANDLERS = {
316
379
  "terminal": h_terminal,
317
380
  "symbols": h_symbols,
318
381
  "all_symbols": h_all_symbols,
382
+ "rates": h_rates,
319
383
  "positions": h_positions,
320
384
  "orders": h_orders,
321
385
  "deals": h_deals,
@@ -324,6 +388,7 @@ HANDLERS = {
324
388
  "modify": h_modify,
325
389
  "pending": h_pending,
326
390
  "cancel": h_cancel,
391
+ "margin": h_margin,
327
392
  }
328
393
 
329
394