beast-agent 2.69.0 → 2.71.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/customtools.js +8 -4
- package/src/agent/defaulttools/fastbrowse_task/fastbrowse_bridge.py +385 -0
- package/src/agent/defaulttools/fastbrowse_task/run.js +119 -0
- package/src/agent/defaulttools/fastbrowse_task/tool.json +50 -0
- package/src/agent/financetools.js +28 -9
- package/src/agent/finwatch.js +3 -1
- package/src/main.js +394 -27
package/package.json
CHANGED
package/src/agent/customtools.js
CHANGED
|
@@ -15,6 +15,11 @@ const path = require('path');
|
|
|
15
15
|
const { spawn } = require('child_process');
|
|
16
16
|
const { beastRoot } = require('./memory');
|
|
17
17
|
|
|
18
|
+
/* Varsayılan 90 sn; tool.json'daki "timeoutMs" ile araç başına uzatılabilir
|
|
19
|
+
(üst sınır 15 dk) — uzun işler için (ör. fastbrowse tarayıcı ajanı). */
|
|
20
|
+
const TOOL_TIMEOUT_MS = 90 * 1000;
|
|
21
|
+
const TOOL_TIMEOUT_MAX_MS = 15 * 60 * 1000;
|
|
22
|
+
|
|
18
23
|
let notify = () => {};
|
|
19
24
|
function setNotify(fn) {
|
|
20
25
|
notify = typeof fn === 'function' ? fn : () => {};
|
|
@@ -80,6 +85,7 @@ function readTool(d) {
|
|
|
80
85
|
: { type: 'object', properties: {} },
|
|
81
86
|
script: runJs,
|
|
82
87
|
code: fs.readFileSync(runJs, 'utf8').slice(0, 20000),
|
|
88
|
+
timeoutMs: Math.max(5000, Math.min(TOOL_TIMEOUT_MAX_MS, Number(json.timeoutMs) || TOOL_TIMEOUT_MS)),
|
|
83
89
|
updatedAt: json.updatedAt || null,
|
|
84
90
|
};
|
|
85
91
|
} catch {
|
|
@@ -122,8 +128,6 @@ function names() {
|
|
|
122
128
|
return cache.map((t) => 'tool__' + t.id);
|
|
123
129
|
}
|
|
124
130
|
|
|
125
|
-
const TOOL_TIMEOUT_MS = 90 * 1000;
|
|
126
|
-
|
|
127
131
|
/* Tool'u çocuk node prosesinde koşturur — uygulama asla çökmez */
|
|
128
132
|
function call(name, args, sid) {
|
|
129
133
|
return new Promise((resolve) => {
|
|
@@ -157,8 +161,8 @@ function call(name, args, sid) {
|
|
|
157
161
|
};
|
|
158
162
|
let killer = setTimeout(() => {
|
|
159
163
|
try { child && child.kill(); } catch {}
|
|
160
|
-
finish({ ok: false, error: 'tool zaman aşımı (' + Math.round(
|
|
161
|
-
},
|
|
164
|
+
finish({ ok: false, error: 'tool zaman aşımı (' + Math.round(t.timeoutMs / 1000) + ' sn)' });
|
|
165
|
+
}, t.timeoutMs);
|
|
162
166
|
try {
|
|
163
167
|
/* Araç ortamı: BEAST_* değişkenleri — tool'lar köprü/python yolunu ve
|
|
164
168
|
argümanları buradan güvenle çözebilir (stdin yanı sıra yedek kanal).
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Beast Agent <-> fastbrowse köprüsü (JEV seçer, LLM okur, kod uygular).
|
|
4
|
+
|
|
5
|
+
stdin : JSON argümanlar (tool__fastbrowse_task ile aynı şema)
|
|
6
|
+
stdout: TEK JSON sonuç
|
|
7
|
+
|
|
8
|
+
Kimlikler args'tan GEÇMEZ: TypeSafe anahtarı ve OpenAI-uyumlu sağlayıcı
|
|
9
|
+
(DeepSeek / z.ai ...) %APPDATA%\\beast\\settings.json'dan okunur — model hiç
|
|
10
|
+
görmez. LLM istemcisi fastbrowse'un OpenAICompatibleLLM'i; response_format
|
|
11
|
+
json_schema'yı desteklemeyen sağlayıcılar için istek gövdesi httpx transport
|
|
12
|
+
katmanında json_object'e indirilir ve şema sistem mesajına eklenir.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _emit(obj: dict) -> None:
|
|
26
|
+
try:
|
|
27
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
28
|
+
except Exception:
|
|
29
|
+
pass
|
|
30
|
+
sys.stdout.write(json.dumps(obj, ensure_ascii=False) + "\n")
|
|
31
|
+
sys.stdout.flush()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _read_args() -> dict:
|
|
35
|
+
try:
|
|
36
|
+
raw = sys.stdin.read()
|
|
37
|
+
except Exception:
|
|
38
|
+
raw = ""
|
|
39
|
+
try:
|
|
40
|
+
obj = json.loads(raw or "{}")
|
|
41
|
+
return obj if isinstance(obj, dict) else {}
|
|
42
|
+
except Exception:
|
|
43
|
+
return {}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _beast_root() -> Path:
|
|
47
|
+
env = os.environ.get("BEAST_ROOT")
|
|
48
|
+
if env:
|
|
49
|
+
return Path(env)
|
|
50
|
+
return Path(os.environ.get("APPDATA") or ".") / "beast"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _load_settings() -> dict:
|
|
54
|
+
try:
|
|
55
|
+
return json.loads((_beast_root() / "settings.json").read_text(encoding="utf-8"))
|
|
56
|
+
except Exception:
|
|
57
|
+
return {}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _pick_provider(settings: dict, want: str, model: str):
|
|
61
|
+
"""Aktif modelin sağlayıcısı (modelOverride: custom:<id>::<model>) ya da
|
|
62
|
+
istenen ad/kimlik; yoksa ilk özel sağlayıcı. Anahtar yalnız burada çözülür."""
|
|
63
|
+
provs = settings.get("customProviders")
|
|
64
|
+
if not isinstance(provs, list):
|
|
65
|
+
provs = []
|
|
66
|
+
want = str(want or "").strip().lower()
|
|
67
|
+
mo = str(settings.get("modelOverride") or "")
|
|
68
|
+
m = re.match(r"custom:([^:]+)::(.+)$", mo)
|
|
69
|
+
mo_id = m.group(1) if m else ""
|
|
70
|
+
mo_model = m.group(2) if m else ""
|
|
71
|
+
chosen = None
|
|
72
|
+
if want:
|
|
73
|
+
for p in provs:
|
|
74
|
+
if not isinstance(p, dict):
|
|
75
|
+
continue
|
|
76
|
+
if want in (str(p.get("id") or "").lower(), str(p.get("name") or "").lower()):
|
|
77
|
+
chosen = p
|
|
78
|
+
break
|
|
79
|
+
if chosen is None and mo_id:
|
|
80
|
+
for p in provs:
|
|
81
|
+
if isinstance(p, dict) and str(p.get("id") or "") == mo_id:
|
|
82
|
+
chosen = p
|
|
83
|
+
break
|
|
84
|
+
if chosen is None and provs and isinstance(provs[0], dict):
|
|
85
|
+
chosen = provs[0]
|
|
86
|
+
if not chosen:
|
|
87
|
+
return None
|
|
88
|
+
base = str(chosen.get("baseUrl") or "").strip()
|
|
89
|
+
key = str(chosen.get("key") or "").strip()
|
|
90
|
+
mdl = str(model or "").strip()
|
|
91
|
+
if not mdl:
|
|
92
|
+
if mo_model and str(chosen.get("id") or "") == mo_id:
|
|
93
|
+
mdl = mo_model
|
|
94
|
+
else:
|
|
95
|
+
models = chosen.get("models")
|
|
96
|
+
if isinstance(models, list) and models:
|
|
97
|
+
mdl = str(models[0])
|
|
98
|
+
if not (base and key and mdl):
|
|
99
|
+
return None
|
|
100
|
+
return {
|
|
101
|
+
"name": str(chosen.get("name") or chosen.get("id") or "?"),
|
|
102
|
+
"baseUrl": base,
|
|
103
|
+
"apiKey": key,
|
|
104
|
+
"model": mdl,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _make_http():
|
|
109
|
+
"""httpx istemcisi: görsel soyma + json_schema indirgemesi (taşıma katmanı)."""
|
|
110
|
+
import httpx
|
|
111
|
+
|
|
112
|
+
class _SchemaDowngrade(httpx.AsyncBaseTransport):
|
|
113
|
+
def __init__(self, inner):
|
|
114
|
+
self._inner = inner
|
|
115
|
+
|
|
116
|
+
async def aclose(self):
|
|
117
|
+
await self._inner.aclose()
|
|
118
|
+
|
|
119
|
+
async def handle_async_request(self, request):
|
|
120
|
+
payload = None
|
|
121
|
+
is_llm = False
|
|
122
|
+
try:
|
|
123
|
+
if request.content and b"messages" in request.content:
|
|
124
|
+
body = json.loads(request.content.decode("utf-8"))
|
|
125
|
+
changed = False
|
|
126
|
+
msgs = body.get("messages")
|
|
127
|
+
if isinstance(msgs, list):
|
|
128
|
+
# 1) GÖRSEL SOYMA: sağlayıcı (GLM/DeepSeek) görsel kabul etmiyorsa
|
|
129
|
+
# image_url parçaları düşer, metin korunur ve kısa bir not eklenir.
|
|
130
|
+
for m in msgs:
|
|
131
|
+
if not isinstance(m, dict):
|
|
132
|
+
continue
|
|
133
|
+
c = m.get("content")
|
|
134
|
+
if isinstance(c, list):
|
|
135
|
+
texts = [
|
|
136
|
+
str(p.get("text") or "")
|
|
137
|
+
for p in c
|
|
138
|
+
if isinstance(p, dict) and p.get("type") == "text"
|
|
139
|
+
]
|
|
140
|
+
had_image = any(
|
|
141
|
+
isinstance(p, dict) and p.get("type") == "image_url" for p in c
|
|
142
|
+
)
|
|
143
|
+
joined = "\n".join(t for t in texts if t)
|
|
144
|
+
if had_image:
|
|
145
|
+
joined += "\n[ekran görüntüsü bu sağlayıcıya gönderilemedi — metinle devam et]"
|
|
146
|
+
m["content"] = joined
|
|
147
|
+
changed = True
|
|
148
|
+
# 2) JSON ŞEMA İNDİRGEMESİ: json_schema → json_object + şema sistem mesajı
|
|
149
|
+
rf = body.get("response_format")
|
|
150
|
+
if isinstance(rf, dict) and rf.get("type") == "json_schema":
|
|
151
|
+
schema = (rf.get("json_schema") or {}).get("schema")
|
|
152
|
+
body["response_format"] = {"type": "json_object"}
|
|
153
|
+
msgs.insert(
|
|
154
|
+
0,
|
|
155
|
+
{
|
|
156
|
+
"role": "system",
|
|
157
|
+
"content": "Yalnızca tek bir JSON nesnesi döndür; JSON dışında hiçbir metin yazma. Şu JSON Schema'ya uy: "
|
|
158
|
+
+ json.dumps(schema, ensure_ascii=False),
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
changed = True
|
|
162
|
+
if changed:
|
|
163
|
+
body["messages"] = msgs
|
|
164
|
+
payload = json.dumps(body, ensure_ascii=False).encode("utf-8")
|
|
165
|
+
else:
|
|
166
|
+
payload = request.content
|
|
167
|
+
is_llm = True
|
|
168
|
+
except Exception:
|
|
169
|
+
pass
|
|
170
|
+
req = request
|
|
171
|
+
if payload is not None:
|
|
172
|
+
headers = [(k, v) for (k, v) in request.headers.raw if k.lower() != b"content-length"]
|
|
173
|
+
req = httpx.Request(
|
|
174
|
+
request.method,
|
|
175
|
+
request.url,
|
|
176
|
+
headers=headers,
|
|
177
|
+
content=payload,
|
|
178
|
+
extensions=request.extensions,
|
|
179
|
+
)
|
|
180
|
+
# NOT: 429/5xx yeniden deneme fastbrowse'un post_with_retry katmanında zaten var
|
|
181
|
+
# (Retry-After'a saygılı) — burada TEKRAR denenmez, yoksa girişimler çoğalır.
|
|
182
|
+
return await self._inner.handle_async_request(req)
|
|
183
|
+
|
|
184
|
+
return httpx.AsyncClient(timeout=60, transport=_SchemaDowngrade(httpx.AsyncHTTPTransport()))
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
async def _resolve_cdp(client, args: dict) -> str:
|
|
188
|
+
"""Beast dahili Chromium CDP: DevToolsActivePort dosyası → /json/version → ws URL."""
|
|
189
|
+
url = str(args.get("cdp_url") or "").strip()
|
|
190
|
+
if url.startswith(("ws://", "wss://")):
|
|
191
|
+
return url
|
|
192
|
+
port = 0
|
|
193
|
+
try:
|
|
194
|
+
port = int(str(args.get("cdp_port") or "0") or 0)
|
|
195
|
+
except Exception:
|
|
196
|
+
port = 0
|
|
197
|
+
if not port:
|
|
198
|
+
appdata = Path(os.environ.get("APPDATA") or ".")
|
|
199
|
+
for name in ("Beast Agent", "beast-agent", "beast"):
|
|
200
|
+
f = appdata / name / "DevToolsActivePort"
|
|
201
|
+
try:
|
|
202
|
+
if f.exists():
|
|
203
|
+
port = int(f.read_text(encoding="utf-8").splitlines()[0].strip())
|
|
204
|
+
break
|
|
205
|
+
except Exception:
|
|
206
|
+
continue
|
|
207
|
+
if not port:
|
|
208
|
+
raise RuntimeError("DevToolsActivePort bulunamadı (Beast açık mı?)")
|
|
209
|
+
r = await client.get("http://127.0.0.1:%d/json/version" % port)
|
|
210
|
+
r.raise_for_status()
|
|
211
|
+
ws = (r.json() or {}).get("webSocketDebuggerUrl")
|
|
212
|
+
if not ws:
|
|
213
|
+
raise RuntimeError("CDP webSocketDebuggerUrl yok (port %d)" % port)
|
|
214
|
+
return str(ws)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
_HINTS = {
|
|
218
|
+
"needs_confirmation": "Geri alınamaz aksiyonda durdu (gönder/öde/sil). Kullanıcıdan onay al; uygunsa authorize:true ile tekrar çağır.",
|
|
219
|
+
"needs_login": "Giriş duvarı var; kayıtlı kimlik çözülemedi. Kullanıcıdan giriş bilgisi/profil iste.",
|
|
220
|
+
"needs_input": "Form alanı için değer verilmedi (model uydurmaz). Kullanıcıdan değeri al.",
|
|
221
|
+
"blocked": "Bot kontrolü (CAPTCHA) geçilemedi; kullanıcının çözmesi gerekebilir.",
|
|
222
|
+
"stuck": "İlerleme olmadan takıldı; görevi/dilimi netleştirip tekrar dene.",
|
|
223
|
+
"budget_exceeded": "Adım/süre limiti doldu; max_steps/max_seconds artır ya da görevi böl.",
|
|
224
|
+
"observation_limit": "Sayfa Jev'in girdi limitini aştı; daha dar bir görev/hedef ver.",
|
|
225
|
+
"unverified": "Bitti sanıyor ama tüm iddialar kanıtlanamadı — answer'a temkinli yaklaş, sonucu doğrula.",
|
|
226
|
+
"error": "Çalıştırma hatası; error alanına bak.",
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _result_dict(res, provider: dict, browser: str) -> dict:
|
|
231
|
+
try:
|
|
232
|
+
cost = {"known_dollars": round(float(res.cost.known_dollars), 4), "unknown": bool(res.cost.has_unknown)}
|
|
233
|
+
except Exception:
|
|
234
|
+
cost = None
|
|
235
|
+
steps = []
|
|
236
|
+
try:
|
|
237
|
+
for s in list(res.steps)[-30:]:
|
|
238
|
+
steps.append(
|
|
239
|
+
{
|
|
240
|
+
"i": s.index,
|
|
241
|
+
"op": getattr(s.operation, "value", str(s.operation)),
|
|
242
|
+
"karar": getattr(s.decided_by, "value", ""),
|
|
243
|
+
"sonuc": getattr(s.outcome, "value", ""),
|
|
244
|
+
"hedef": s.target,
|
|
245
|
+
"url": s.url,
|
|
246
|
+
}
|
|
247
|
+
)
|
|
248
|
+
except Exception:
|
|
249
|
+
pass
|
|
250
|
+
evidence = []
|
|
251
|
+
try:
|
|
252
|
+
for e in list(res.evidence)[:10]:
|
|
253
|
+
quote = " ".join(str(e.quote or "").split())
|
|
254
|
+
evidence.append({"quote": quote[:400], "url": e.url})
|
|
255
|
+
except Exception:
|
|
256
|
+
pass
|
|
257
|
+
status = getattr(res.status, "value", str(res.status))
|
|
258
|
+
return {
|
|
259
|
+
"ok": status == "complete",
|
|
260
|
+
"status": status,
|
|
261
|
+
"answer": res.answer,
|
|
262
|
+
"final_url": res.final_url,
|
|
263
|
+
"evidence": evidence,
|
|
264
|
+
"steps": steps,
|
|
265
|
+
"cost": cost,
|
|
266
|
+
"browser": browser,
|
|
267
|
+
"provider": provider["name"] + ":" + provider["model"],
|
|
268
|
+
"error": res.error,
|
|
269
|
+
"hint": _HINTS.get(status, ""),
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
async def _run(args: dict) -> dict:
|
|
274
|
+
from fastbrowse import run_task
|
|
275
|
+
from fastbrowse.clients.openai_compatible import OpenAICompatibleLLM
|
|
276
|
+
from fastbrowse.clients.typesafe import TYPESAFE_URL, TypeSafeJevClient
|
|
277
|
+
from fastbrowse.jev import JEV_MODEL
|
|
278
|
+
from fastbrowse.models import Authorization, Limits, LLMPurpose, LocalChrome
|
|
279
|
+
|
|
280
|
+
settings = _load_settings()
|
|
281
|
+
ts = settings.get("typesafe") if isinstance(settings.get("typesafe"), dict) else {}
|
|
282
|
+
ts_key = str(ts.get("apiKey") or "").strip()
|
|
283
|
+
if not ts_key:
|
|
284
|
+
return {
|
|
285
|
+
"ok": False,
|
|
286
|
+
"status": "error",
|
|
287
|
+
"error": "TypeSafe (Jev) API anahtarı yok",
|
|
288
|
+
"hint": "Ayarlar → TypeSafe sekmesinden anahtarı gir.",
|
|
289
|
+
}
|
|
290
|
+
if ts.get("enabled") is False:
|
|
291
|
+
return {
|
|
292
|
+
"ok": False,
|
|
293
|
+
"status": "error",
|
|
294
|
+
"error": "TypeSafe ayarı KAPALI",
|
|
295
|
+
"hint": "Ayarlar → TypeSafe → anahtarı aç.",
|
|
296
|
+
}
|
|
297
|
+
provider = _pick_provider(settings, str(args.get("provider") or ""), str(args.get("model") or ""))
|
|
298
|
+
if not provider:
|
|
299
|
+
return {
|
|
300
|
+
"ok": False,
|
|
301
|
+
"status": "error",
|
|
302
|
+
"error": "OpenAI-uyumlu LLM sağlayıcısı bulunamadı (customProviders boş ya da anahtarsız)",
|
|
303
|
+
"hint": "Ayarlar → Provider'dan özel sağlayıcı ekle (ör. DeepSeek / z.ai) ve model seç.",
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
def _clamp(v, lo, hi, default):
|
|
307
|
+
try:
|
|
308
|
+
return max(lo, min(hi, type(default)(v)))
|
|
309
|
+
except Exception:
|
|
310
|
+
return default
|
|
311
|
+
|
|
312
|
+
max_steps = _clamp(args.get("max_steps"), 5, 80, 40)
|
|
313
|
+
max_seconds = _clamp(args.get("max_seconds"), 30.0, 600.0, 180.0)
|
|
314
|
+
limits = Limits(max_steps=max_steps, max_seconds=max_seconds, max_llm_calls=50)
|
|
315
|
+
authorization = Authorization(irreversible_actions=bool(args.get("authorize")))
|
|
316
|
+
|
|
317
|
+
def _local_chrome():
|
|
318
|
+
binary = str(args.get("chrome") or os.environ.get("FASTBROWSE_CHROME") or "").strip()
|
|
319
|
+
return LocalChrome(binary=binary or None, headed=bool(args.get("headed")))
|
|
320
|
+
|
|
321
|
+
http = _make_http()
|
|
322
|
+
try:
|
|
323
|
+
jev = TypeSafeJevClient(
|
|
324
|
+
ts_key,
|
|
325
|
+
http=http,
|
|
326
|
+
base_url=TYPESAFE_URL,
|
|
327
|
+
model=str(ts.get("model") or "").strip() or JEV_MODEL,
|
|
328
|
+
)
|
|
329
|
+
llm = OpenAICompatibleLLM(
|
|
330
|
+
provider["apiKey"],
|
|
331
|
+
http=http,
|
|
332
|
+
base_url=provider["baseUrl"],
|
|
333
|
+
models={purpose: provider["model"] for purpose in LLMPurpose},
|
|
334
|
+
reasoning_effort=None, # sağlayıcıya özel 'reasoning' alanı gönderilmez
|
|
335
|
+
)
|
|
336
|
+
kwargs = {"jev": jev, "llm": llm, "limits": limits, "authorization": authorization, "http": http}
|
|
337
|
+
mode = str(args.get("mode") or "cdp").strip().lower()
|
|
338
|
+
if mode == "chrome":
|
|
339
|
+
kwargs["chrome"] = _local_chrome()
|
|
340
|
+
browser = "chrome"
|
|
341
|
+
else:
|
|
342
|
+
try:
|
|
343
|
+
kwargs["cdp_url"] = await _resolve_cdp(http, args)
|
|
344
|
+
browser = "cdp (Beast dahili)"
|
|
345
|
+
except Exception as e:
|
|
346
|
+
kwargs["chrome"] = _local_chrome()
|
|
347
|
+
browser = "chrome (cdp yok: %s)" % str(e)[:80]
|
|
348
|
+
res = await run_task(
|
|
349
|
+
str(args.get("task") or "").strip(),
|
|
350
|
+
start=(str(args.get("start") or "").strip() or None),
|
|
351
|
+
**kwargs,
|
|
352
|
+
)
|
|
353
|
+
finally:
|
|
354
|
+
try:
|
|
355
|
+
await http.aclose()
|
|
356
|
+
except Exception:
|
|
357
|
+
pass
|
|
358
|
+
return _result_dict(res, provider, browser)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def main() -> None:
|
|
362
|
+
args = _read_args()
|
|
363
|
+
if not str(args.get("task") or "").strip():
|
|
364
|
+
_emit({"ok": False, "status": "error", "error": "task gerekli (doğal dil hedef)"})
|
|
365
|
+
return
|
|
366
|
+
try:
|
|
367
|
+
result = asyncio.run(_run(args))
|
|
368
|
+
except ModuleNotFoundError as e:
|
|
369
|
+
_emit(
|
|
370
|
+
{
|
|
371
|
+
"ok": False,
|
|
372
|
+
"status": "error",
|
|
373
|
+
"error": "fastbrowse yüklenemedi: " + str(e),
|
|
374
|
+
"hint": "uv kurulu ve internet erişimi var mı? (uv run fastbrowse==0.4.2 ortamı kurar)",
|
|
375
|
+
}
|
|
376
|
+
)
|
|
377
|
+
return
|
|
378
|
+
except Exception as e:
|
|
379
|
+
_emit({"ok": False, "status": "error", "error": type(e).__name__ + ": " + str(e)[:400]})
|
|
380
|
+
return
|
|
381
|
+
_emit(result)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
if __name__ == "__main__":
|
|
385
|
+
main()
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// FASTBROWSE KÖPRÜSÜ — stdin: JSON args · stdout: JSON sonuç
|
|
3
|
+
// fastbrowse (Python 3.13) uv ile izole ortamda çalışır; anahtar/kimlik bilgileri
|
|
4
|
+
// args'tan GEÇMEZ — köprü %APPDATA%\beast\settings.json'dan okur (model hiç görmez).
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
function readArgs() {
|
|
10
|
+
try { return JSON.parse(fs.readFileSync(0, 'utf8') || '{}') || {}; } catch { return {}; }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* uv bul: PATH'te yoksa uv'un resmi kurulum yerleri denenir */
|
|
14
|
+
function findUv() {
|
|
15
|
+
const cands = [];
|
|
16
|
+
if (process.env.UV) cands.push(String(process.env.UV));
|
|
17
|
+
if (process.env.USERPROFILE) cands.push(path.join(process.env.USERPROFILE, '.local', 'bin', 'uv.exe'));
|
|
18
|
+
if (process.env.LOCALAPPDATA) cands.push(path.join(process.env.LOCALAPPDATA, 'Microsoft', 'WinGet', 'Links', 'uv.exe'));
|
|
19
|
+
for (const c of cands) { try { if (c && fs.existsSync(c)) return c; } catch {} }
|
|
20
|
+
return 'uv';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Chrome bul (chrome modu yedeği; köprü FASTBROWSE_CHROME'u kullanır) */
|
|
24
|
+
function findChrome() {
|
|
25
|
+
const cands = [];
|
|
26
|
+
if (process.env.LOCALAPPDATA) cands.push(path.join(process.env.LOCALAPPDATA, 'Google', 'Chrome', 'Application', 'chrome.exe'));
|
|
27
|
+
cands.push('C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe');
|
|
28
|
+
cands.push('C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe');
|
|
29
|
+
for (const c of cands) { try { if (c && fs.existsSync(c)) return c; } catch {} }
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function emit(obj) {
|
|
34
|
+
process.stdout.write(JSON.stringify(obj) + '\n');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const args = readArgs();
|
|
38
|
+
const uv = findUv();
|
|
39
|
+
const bridge = path.join(__dirname, 'fastbrowse_bridge.py');
|
|
40
|
+
if (!fs.existsSync(bridge)) {
|
|
41
|
+
emit({ ok: false, status: 'error', error: 'köprü dosyası yok: ' + bridge, hint: 'Araç klasörünü yeniden kur (uygulamayı yeniden başlat).' });
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const env = { ...process.env, PYTHONUTF8: '1', PYTHONIOENCODING: 'utf-8' };
|
|
46
|
+
if (!env.FASTBROWSE_CHROME) {
|
|
47
|
+
const chrome = findChrome();
|
|
48
|
+
if (chrome) env.FASTBROWSE_CHROME = chrome;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const UV_TIMEOUT_MS = 330000; /* customtools zaman aşımının (360 sn) altında kal */
|
|
52
|
+
let child = null;
|
|
53
|
+
let out = '';
|
|
54
|
+
let err = '';
|
|
55
|
+
let settled = false;
|
|
56
|
+
const finish = (obj) => {
|
|
57
|
+
if (settled) return;
|
|
58
|
+
settled = true;
|
|
59
|
+
clearTimeout(killer);
|
|
60
|
+
try { child && child.kill(); } catch {}
|
|
61
|
+
emit(obj);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const killer = setTimeout(() => {
|
|
65
|
+
finish({ ok: false, status: 'error', error: 'fastbrowse zaman aşımı (uv/Chrome yanıt vermedi)', stderr: String(err || '').slice(-500) });
|
|
66
|
+
}, UV_TIMEOUT_MS);
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
child = spawn(uv, ['run', '--python', '3.13', '--with', 'fastbrowse==0.4.2', 'python', bridge], {
|
|
70
|
+
cwd: __dirname,
|
|
71
|
+
env,
|
|
72
|
+
windowsHide: true,
|
|
73
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
74
|
+
});
|
|
75
|
+
} catch (e) {
|
|
76
|
+
finish({
|
|
77
|
+
ok: false,
|
|
78
|
+
status: 'error',
|
|
79
|
+
error: 'uv başlatılamadı: ' + String((e && e.message) || e),
|
|
80
|
+
hint: 'uv kur: winget install astral-sh.uv (ya da https://docs.astral.sh/uv/)',
|
|
81
|
+
});
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
child.stdout.on('data', (d) => { out += d.toString('utf8'); });
|
|
86
|
+
child.stderr.on('data', (d) => { err += d.toString('utf8'); if (err.length > 20000) err = err.slice(-12000); });
|
|
87
|
+
child.on('error', (e) => {
|
|
88
|
+
finish({
|
|
89
|
+
ok: false,
|
|
90
|
+
status: 'error',
|
|
91
|
+
error: 'uv çalıştırılamadı: ' + String((e && e.message) || e),
|
|
92
|
+
hint: 'uv kur: winget install astral-sh.uv (ya da https://docs.astral.sh/uv/)',
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
child.on('close', (code) => {
|
|
96
|
+
/* uv/python kendi log'larını stderr'e yazar; stdout'tan SON geçerli JSON satırını al */
|
|
97
|
+
let result = null;
|
|
98
|
+
for (const line of String(out || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean)) {
|
|
99
|
+
try {
|
|
100
|
+
const o = JSON.parse(line);
|
|
101
|
+
if (o && typeof o === 'object' && typeof o.ok === 'boolean') result = o;
|
|
102
|
+
} catch {}
|
|
103
|
+
}
|
|
104
|
+
if (!result) {
|
|
105
|
+
result = {
|
|
106
|
+
ok: false,
|
|
107
|
+
status: 'error',
|
|
108
|
+
error: 'fastbrowse sonuç üretemedi (çıkış kodu ' + code + ')',
|
|
109
|
+
stderr: String(err || '').slice(-800),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (!result.ok && !result.hint && code !== 0) result.hint = String(err || '').slice(-200) || 'çıkış kodu ' + code;
|
|
113
|
+
finish(result);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
child.stdin.write(JSON.stringify(args));
|
|
118
|
+
child.stdin.end();
|
|
119
|
+
} catch {}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fastbrowse_task",
|
|
3
|
+
"description": "HIZLI TARAYICI AJANI (fastbrowse · Jev seçer, LLM okur, kod doğrular): doğal dilde TEK hedef ver; sayfayı element listesine indeksler, her adımı Jev (TypeSafe) seçer ve cevabı sayfadan BİREBİR ALINTILARLA döndürür. Çok adımlı işler (arama + form + filtre + tarih seçimi + sonuç okuma) için browser_click/browser_type zinciri yerine bunu tercih et. Varsayılan mode:'cdp' Beast'in DAHİLİ Chromium'una bağlanır (kendi sekmesini açar/kapatır, senin sekmelerine dokunmaz); mode:'chrome' kendi Chrome'unu açar. Geri alınamaz aksiyonlar (gönder/öde/sil) authorize:true verilmeden DURUR ve status:'needs_confirmation' döner. status:'complete' DEĞİLSE başarıyı iddia etme: dönen answer + evidence alıntılarını kullanıcıya aktar; needs_login/needs_input/blocked durumlarında kullanıcıdan bilgi iste. Aynı iş için elle browser_click/browser_type zinciri kurma — tek çağrıda hedefi ver.",
|
|
4
|
+
"parameters": {
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"task": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"description": "Doğal dil hedef: \"Hacker News'teki en üst haberin başlığını bul\" gibi. Tek net hedef ver; gereksiz 've' ile işleri birleştirme."
|
|
10
|
+
},
|
|
11
|
+
"start": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "Başlangıç URL'i (ör. https://news.ycombinator.com/). Boşsa ilk adres görevden çıkarılır."
|
|
14
|
+
},
|
|
15
|
+
"mode": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"enum": ["cdp", "chrome"],
|
|
18
|
+
"description": "cdp: Beast dahili Chromium (varsayılan, giriş yapılmış oturumu kullanır); chrome: kendi Chrome'unu başlatır (Beast kapalıyken/yedek)."
|
|
19
|
+
},
|
|
20
|
+
"provider": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "LLM sağlayıcı adı/kimliği (Ayarlar → Provider'daki özel sağlayıcı; boşsa aktif modelin sağlayıcısı)."
|
|
23
|
+
},
|
|
24
|
+
"model": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "LLM modeli (boşsa sağlayıcının/aktif modelin varsayılanı)."
|
|
27
|
+
},
|
|
28
|
+
"authorize": {
|
|
29
|
+
"type": "boolean",
|
|
30
|
+
"description": "true: geri alınamaz aksiyonlara (gönder/öde/sil/onayla) izin ver. Varsayılan false — güvenli."
|
|
31
|
+
},
|
|
32
|
+
"headed": {
|
|
33
|
+
"type": "boolean",
|
|
34
|
+
"description": "chrome modunda pencereyi göster (varsayılan false = headless)."
|
|
35
|
+
},
|
|
36
|
+
"max_steps": {
|
|
37
|
+
"type": "number",
|
|
38
|
+
"description": "Adım tavanı (varsayılan 40, en çok 80)."
|
|
39
|
+
},
|
|
40
|
+
"max_seconds": {
|
|
41
|
+
"type": "number",
|
|
42
|
+
"description": "Süre tavanı saniye (varsayılan 180, en çok 600)."
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"required": ["task"]
|
|
46
|
+
},
|
|
47
|
+
"timeoutMs": 360000,
|
|
48
|
+
"defaultsVersion": 1,
|
|
49
|
+
"updatedAt": "2026-09-21T10:00:00.000Z"
|
|
50
|
+
}
|
|
@@ -920,34 +920,53 @@ const handlers = {
|
|
|
920
920
|
const info = await symInfoRow(symbol);
|
|
921
921
|
if (!info) return { ok: false, error: 'sembol bulunamadı: ' + symbol + ' (MT5 Market Watch?)' };
|
|
922
922
|
const range = lotRange(cfg, symbol);
|
|
923
|
-
const norm = finrisk.normalizeVolume(info, args.volume, range.maxLot, range.minLot);
|
|
924
|
-
if (norm.error) return { ok: false, error: norm.error };
|
|
925
|
-
const vol = norm.volume;
|
|
926
923
|
const ptype = t.type;
|
|
927
924
|
const pside = t.side;
|
|
928
|
-
|
|
925
|
+
const entryPrice = Number(args.price) || 0;
|
|
926
|
+
if (!(entryPrice > 0)) {
|
|
929
927
|
return { ok: false, error: 'price gerekli — limit/stop bekleyen emirleri için tetik fiyatı ver (market tipleri anlıktır, price istemez)' };
|
|
930
928
|
}
|
|
929
|
+
/* LOT: riskPct verilirse BEKLEYEN EMİR fiyatı + SL mesafesinden hesaplanır
|
|
930
|
+
(piyasa emriyle aynı risk hattı); volume verilmişse normalize edilir. */
|
|
931
|
+
const explicitRisk = Number(args.riskPct) > 0 ? Number(args.riskPct) : 0;
|
|
932
|
+
let vol = 0;
|
|
933
|
+
let riskInfo = null;
|
|
934
|
+
if (explicitRisk > 0 || (!(Number(args.volume) > 0) && Number(cfg.riskPerTradePct) > 0)) {
|
|
935
|
+
const slRef = Number(args.sl) || 0;
|
|
936
|
+
if (!(slRef > 0)) return { ok: false, error: '%risk ile lot için sl zorunlu — sl ver ya da volume kullan' };
|
|
937
|
+
const riskPct = explicitRisk > 0 ? explicitRisk : Number(cfg.riskPerTradePct);
|
|
938
|
+
const acct = await bcall('account', {}, 8000);
|
|
939
|
+
const balance = Number(acct && acct.account && acct.account.balance) || 0;
|
|
940
|
+
const riskAmount = (balance * riskPct) / 100;
|
|
941
|
+
const rr = finrisk.calcRiskLot(info, entryPrice, slRef, riskAmount, range.maxLot, range.minLot);
|
|
942
|
+
if (rr.error) return { ok: false, error: rr.error };
|
|
943
|
+
vol = rr.volume;
|
|
944
|
+
riskInfo = { riskPct, riskAmount: Math.round(riskAmount * 100) / 100, lossPerLot: rr.lossPerLot, raised: !!rr.raised };
|
|
945
|
+
} else {
|
|
946
|
+
const norm = finrisk.normalizeVolume(info, args.volume, range.maxLot, range.minLot);
|
|
947
|
+
if (norm.error) return { ok: false, error: norm.error };
|
|
948
|
+
vol = norm.volume;
|
|
949
|
+
}
|
|
931
950
|
/* KODLA DİSİPLİN: bekleyen emir de işlem sayılır */
|
|
932
951
|
let discErr = null;
|
|
933
952
|
try { discErr = discipline(pside, symbol, []); } catch {}
|
|
934
953
|
if (discErr) return { ok: false, error: discErr };
|
|
935
954
|
/* SHADOW MOD: bekleyen emir de GÖNDERİLMEZ — tez günlüğe düşer */
|
|
936
955
|
if (cfg.shadowMode) {
|
|
937
|
-
noteTrade('shadow', { symbol, type: ptype, side: pside, volume: vol, price:
|
|
938
|
-
return { ok: true, shadow: true, note: 'SHADOW MOD: bekleyen emir GÖNDERİLMEDİ, karar günlüğe yazıldı.' };
|
|
956
|
+
noteTrade('shadow', { symbol, type: ptype, side: pside, volume: vol, price: entryPrice, sl: Number(args.sl) || 0, tp: Number(args.tp) || 0, timeframe, reason: String(args.reason || '').slice(0, 500), risk: riskInfo }, ctx);
|
|
957
|
+
return { ok: true, shadow: true, planned: { symbol, type: ptype, volume: vol, price: entryPrice, sl: Number(args.sl) || 0, tp: Number(args.tp) || 0 }, risk: riskInfo, note: 'SHADOW MOD: bekleyen emir GÖNDERİLMEDİ, karar günlüğe yazıldı.' };
|
|
939
958
|
}
|
|
940
959
|
const data = await bcall('pending', {
|
|
941
960
|
symbol,
|
|
942
961
|
type: ptype,
|
|
943
962
|
volume: vol,
|
|
944
|
-
price:
|
|
963
|
+
price: entryPrice,
|
|
945
964
|
sl: Number(args.sl) || 0,
|
|
946
965
|
tp: Number(args.tp) || 0,
|
|
947
966
|
comment: orderComment,
|
|
948
967
|
}, 20000);
|
|
949
|
-
noteTrade('pending', { symbol, type: ptype, volume: vol, timeframe, reason: String(args.reason || '').slice(0, 500) }, ctx);
|
|
950
|
-
return { ok: true, result: data && data.result, timeframe: timeframe || null };
|
|
968
|
+
noteTrade('pending', { symbol, type: ptype, volume: vol, price: entryPrice, sl: Number(args.sl) || 0, tp: Number(args.tp) || 0, timeframe, reason: String(args.reason || '').slice(0, 500), risk: riskInfo }, ctx);
|
|
969
|
+
return { ok: true, ordered: { symbol, type: ptype, volume: vol, price: entryPrice, sl: Number(args.sl) || 0, tp: Number(args.tp) || 0 }, risk: riskInfo, result: data && data.result, timeframe: timeframe || null };
|
|
951
970
|
},
|
|
952
971
|
async mt5_cancel(args, ctx) {
|
|
953
972
|
if (!mt5.running) return notConnected();
|
package/src/agent/finwatch.js
CHANGED
|
@@ -99,7 +99,9 @@ function plan(pos, meta, st, cfg) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/* ---- kısmi TP ---- */
|
|
102
|
-
|
|
102
|
+
/* partial = kısmi kapatma GERÇEKTEN yapıldı (Jev/kod); partialNotified =
|
|
103
|
+
yalnız bildirim gitti — ikisi de tekrar üretimi engeller */
|
|
104
|
+
if (partialR > 0 && partialPct > 0 && partialPct < 100 && r > 0 && !(st && (st.partial || st.partialNotified)) && profit >= partialR * r) {
|
|
103
105
|
const step = num(meta.volume_step, 0.01);
|
|
104
106
|
const vmin = num(meta.volume_min, 0.01);
|
|
105
107
|
const vmax = num(meta.volume_max, 100);
|
package/src/main.js
CHANGED
|
@@ -11505,10 +11505,11 @@ async function finWatchTick() {
|
|
|
11505
11505
|
finJournal({ kind: 'watchdog', action: 'sl-move', ticket, symbol: p.symbol, sl: act.sl });
|
|
11506
11506
|
}
|
|
11507
11507
|
} else if (act.kind === 'partial') {
|
|
11508
|
-
/* OTOMATİK KAPATMA YOK — kısmi
|
|
11509
|
-
gelince
|
|
11510
|
-
|
|
11511
|
-
|
|
11508
|
+
/* OTOMATİK KAPATMA YOK — kısmi kapatma kararını JEV verir: seviye
|
|
11509
|
+
gelince Jev turu TUR BEKLEMEDEN uyanır ve tur içinde karar verir.
|
|
11510
|
+
primary flag 'partial' GERÇEK kapatmaya ayrıldı; burada yalnız
|
|
11511
|
+
bildirim tekrarını engelleyen 'partialNotified' işaretlenir. */
|
|
11512
|
+
st.partialNotified = true;
|
|
11512
11513
|
const rNow = decision.r > 0
|
|
11513
11514
|
? Math.round(((Number(p.type) === 0 ? Number(p.price_current) - Number(p.price_open) : Number(p.price_open) - Number(p.price_current)) / decision.r) * 10) / 10
|
|
11514
11515
|
: 0;
|
|
@@ -12723,6 +12724,7 @@ function finTsDecRecord(rec) {
|
|
|
12723
12724
|
rsi: Number.isFinite(Number(rec && rec.rsi)) ? Number(rec.rsi) : null,
|
|
12724
12725
|
atrPct: Number.isFinite(Number(rec && rec.atrPct)) ? Number(rec.atrPct) : null,
|
|
12725
12726
|
riskPct: Number(rec && rec.riskPct) || 0,
|
|
12727
|
+
orderType: String((rec && rec.orderType) || 'market'),
|
|
12726
12728
|
closedAt: 0, net: null, mfe: null, mae: null, reason: '',
|
|
12727
12729
|
};
|
|
12728
12730
|
d.decisions.push(item);
|
|
@@ -13588,8 +13590,10 @@ function finTeamWaitTick(sid) {
|
|
|
13588
13590
|
yanıtları) YALNIZ TypeSafe System One'a gider; yanıtlar olasılık olarak
|
|
13589
13591
|
döner, kararı KOD uygular. Yanıtlar AJAN DM ekip grubuna yazılır — tüm
|
|
13590
13592
|
ajanlar birbirinin cevabını görür (son yanıtlar sonraki turların state'ine
|
|
13591
|
-
de girer)
|
|
13592
|
-
|
|
13593
|
+
de girer). Emir tipini de Jev seçer: market / buy-sell limit / buy-sell
|
|
13594
|
+
stop; giriş seviyesi, SL/TP (sıkı-dengeli-geniş ATR profili), kısmi
|
|
13595
|
+
kapatma, SL taşıma, fiyat alarmı ve araç isteği Jev kararıyla KOD tarafından
|
|
13596
|
+
uygulanır. LLM yalnız finance SOHBET yardımcısında serbesttir. */
|
|
13593
13597
|
const FIN_TS_MARGIN_P = 0.12; /* birinci ile ikinci seçenek arası min fark */
|
|
13594
13598
|
const FIN_TS_CONFIRM_P = 0.55; /* noul teyit eşiği */
|
|
13595
13599
|
const FIN_TS_EXIT_P = 0.62; /* pozisyon kapatma eşiği */
|
|
@@ -13736,6 +13740,236 @@ function finTsIndLine(ind) {
|
|
|
13736
13740
|
};
|
|
13737
13741
|
}
|
|
13738
13742
|
|
|
13743
|
+
/* AÇIK POZİSYON ÖZETİ (TypeSafe state girdisi): R mesafesi, kâr R'si, görülen
|
|
13744
|
+
en iyi R ve koruma bayrakları — Jev'in pozisyon yönetimi kararları bunlara
|
|
13745
|
+
dayanır. Saf okuma; hiçbir IO/karar yok. */
|
|
13746
|
+
function finTsPosState(p) {
|
|
13747
|
+
const st = financeState.watch.get(String((p && p.ticket) || '')) || null;
|
|
13748
|
+
const entry = Number(p && p.price_open) || 0;
|
|
13749
|
+
const ref = Number(p && p.price_current) || 0;
|
|
13750
|
+
const sl = Number(p && p.sl) || 0;
|
|
13751
|
+
const isBuy = Number(p && p.type) === 0;
|
|
13752
|
+
let r = Number(st && st.r) || 0;
|
|
13753
|
+
if (!(r > 0) && sl > 0 && entry > 0) r = Math.abs(entry - sl);
|
|
13754
|
+
const profitDist = entry > 0 && ref > 0 ? (isBuy ? ref - entry : entry - ref) : 0;
|
|
13755
|
+
const bestDist = st && isFinite(Number(st.bestProfit)) ? Number(st.bestProfit) : profitDist;
|
|
13756
|
+
const round2 = (v) => Math.round(v * 100) / 100;
|
|
13757
|
+
return {
|
|
13758
|
+
ticket: p.ticket, sembol: p.symbol, yon: isBuy ? 'buy' : 'sell',
|
|
13759
|
+
hacim: p.volume, giris: p.price_open, sl: p.sl, tp: p.tp, kar: p.profit,
|
|
13760
|
+
fiyat: ref || null,
|
|
13761
|
+
r_mesafe: r > 0 ? Math.round(r * 1e6) / 1e6 : null,
|
|
13762
|
+
kz_r: r > 0 && entry > 0 && ref > 0 ? round2(profitDist / r) : null,
|
|
13763
|
+
en_iyi_r: r > 0 && entry > 0 && ref > 0 && isFinite(bestDist) ? round2(bestDist / r) : null,
|
|
13764
|
+
be: !!(st && st.be),
|
|
13765
|
+
kismi_alindi: !!(st && st.partial),
|
|
13766
|
+
};
|
|
13767
|
+
}
|
|
13768
|
+
|
|
13769
|
+
/* AÇIK POZİSYON YÖNETİMİ (JEV): tur yanıtındaki kısmi kapatma + SL/koruma
|
|
13770
|
+
kararını KOD uygular. Seviyeler deterministik (R/ATR tabanlı) hesaplanır,
|
|
13771
|
+
broker min mesafesine kırpılır ve SL ASLA geriye (risk yönüne) taşınmaz.
|
|
13772
|
+
Kısmi kapatma pozisyon başına BİR kez uygulanır (st.partial). */
|
|
13773
|
+
async function finTsManageOpen(sidS, agent, sym, pos, mkt, ans, qi, lines) {
|
|
13774
|
+
const st = financeState.watch.get(String(pos.ticket)) || null;
|
|
13775
|
+
/* kısmi kapatma pozisyon başına BİR kez: watchdog kaydı yoksa ajan üstünde izle */
|
|
13776
|
+
const partialDone = !!(st && st.partial) || !!(agent && agent.tsPartials && agent.tsPartials.has(String(pos.ticket)));
|
|
13777
|
+
const isBuy = Number(pos.type) === 0;
|
|
13778
|
+
const entry = Number(pos.price_open) || 0;
|
|
13779
|
+
const ref = Number(pos.price_current) || 0;
|
|
13780
|
+
const slNow = Number(pos.sl) || 0;
|
|
13781
|
+
const row = (mkt && mkt.row) || {};
|
|
13782
|
+
const digits = Math.max(0, Math.min(8, Number(row.digits) || 2));
|
|
13783
|
+
const rnd = (v) => Number(Number(v).toFixed(digits));
|
|
13784
|
+
const point = Number(row.point) > 0 ? Number(row.point) : Math.pow(10, -digits);
|
|
13785
|
+
const stopsPts = Number(row.trade_stops_level) || 0;
|
|
13786
|
+
const minDist = stopsPts > 0 ? (stopsPts + 1) * point : point;
|
|
13787
|
+
const atr = Number(mkt && mkt.ind && mkt.ind.atr14) || 0;
|
|
13788
|
+
let rDist = Number(st && st.r) || 0;
|
|
13789
|
+
if (!(rDist > 0) && slNow > 0 && entry > 0) rDist = Math.abs(entry - slNow);
|
|
13790
|
+
const base = rDist > 0 ? rDist : atr > 0 ? atr * 1.5 : ref > 0 ? ref * 0.003 : 0;
|
|
13791
|
+
const profitDist = entry > 0 && ref > 0 ? (isBuy ? ref - entry : entry - ref) : 0;
|
|
13792
|
+
|
|
13793
|
+
/* ---- KISMİ KAPATMA (kısmi TP / kısmi stop): karar Jev'in, uygulama kodun */
|
|
13794
|
+
const par = finTsChoice(ans, 'kismi_' + qi);
|
|
13795
|
+
const pct = par && par.p >= FIN_TS_CONFIRM_P && par.p - par.second >= FIN_TS_MARGIN_P
|
|
13796
|
+
? ({ yuzde25: 25, yuzde50: 50, yuzde75: 75 }[String(par.choice)] || 0)
|
|
13797
|
+
: 0;
|
|
13798
|
+
if (pct > 0 && !partialDone) {
|
|
13799
|
+
const kind = profitDist >= 0 ? 'partial_tp' : 'partial_sl';
|
|
13800
|
+
let r = null;
|
|
13801
|
+
try {
|
|
13802
|
+
r = await financetools.handlers.mt5_close(
|
|
13803
|
+
{
|
|
13804
|
+
ticket: Number(pos.ticket),
|
|
13805
|
+
percent: pct,
|
|
13806
|
+
kind,
|
|
13807
|
+
reason: `TypeSafe: kısmi %${pct} p=${par.p.toFixed(2)} (${kind === 'partial_tp' ? 'kâr al' : 'zarar kes'})`,
|
|
13808
|
+
},
|
|
13809
|
+
{ sessionId: sidS }
|
|
13810
|
+
);
|
|
13811
|
+
} catch (e) {
|
|
13812
|
+
r = { ok: false, error: String((e && e.message) || e) };
|
|
13813
|
+
}
|
|
13814
|
+
if (r && r.ok) {
|
|
13815
|
+
if (st) { st.partial = true; finWatchSave(); }
|
|
13816
|
+
if (agent) { agent.tsPartials = agent.tsPartials || new Set(); agent.tsPartials.add(String(pos.ticket)); }
|
|
13817
|
+
lines.push(
|
|
13818
|
+
`- ${sym} #${pos.ticket}: KISMİ %${pct} ${kind === 'partial_tp' ? 'TP ✓' : 'STOP ✓'}` +
|
|
13819
|
+
` (p=${par.p.toFixed(2)}${r.remaining != null ? ', kalan ' + r.remaining + ' lot' : ''})`
|
|
13820
|
+
);
|
|
13821
|
+
} else {
|
|
13822
|
+
lines.push(`- ${sym} #${pos.ticket}: kısmi %${pct} reddedildi — ${(r && r.error) || 'hata'}`);
|
|
13823
|
+
}
|
|
13824
|
+
} else if (pct > 0 && partialDone) {
|
|
13825
|
+
lines.push(`- ${sym} #${pos.ticket}: kısmi atlandı — daha önce alındı`);
|
|
13826
|
+
}
|
|
13827
|
+
|
|
13828
|
+
/* ---- SL / KORUMA: yalnız İYİLEŞTİRME yönünde, kârsızda mevcut SL'ye dokunma */
|
|
13829
|
+
const slc = finTsChoice(ans, 'sl_' + qi);
|
|
13830
|
+
if (!slc || slc.p < FIN_TS_CONFIRM_P || slc.p - slc.second < FIN_TS_MARGIN_P) return;
|
|
13831
|
+
const choice = String(slc.choice || '');
|
|
13832
|
+
if (choice === 'birak' || !(ref > 0)) return;
|
|
13833
|
+
if (profitDist <= 0 && slNow > 0) {
|
|
13834
|
+
lines.push(`- ${sym} #${pos.ticket}: SL kararı (${choice}) uygulanmadı — kâr yok`);
|
|
13835
|
+
return;
|
|
13836
|
+
}
|
|
13837
|
+
if (!(base > 0)) return;
|
|
13838
|
+
let desired = 0;
|
|
13839
|
+
if (choice === 'be' && rDist > 0) desired = isBuy ? entry + 0.05 * rDist : entry - 0.05 * rDist;
|
|
13840
|
+
else if (choice === 'trail') desired = isBuy ? ref - 0.5 * base : ref + 0.5 * base;
|
|
13841
|
+
else if (choice === 'sikilastir') desired = isBuy ? ref - Math.max(0.3 * base, minDist + point) : ref + Math.max(0.3 * base, minDist + point);
|
|
13842
|
+
else if (choice === 'koru' && slNow <= 0) desired = isBuy ? ref - Math.max(atr > 0 ? atr * 1.5 : base, minDist + point) : ref + Math.max(atr > 0 ? atr * 1.5 : base, minDist + point);
|
|
13843
|
+
if (!(desired > 0)) return;
|
|
13844
|
+
desired = isBuy ? Math.min(desired, ref - minDist) : Math.max(desired, ref + minDist);
|
|
13845
|
+
const improved = slNow <= 0 || (isBuy ? desired > slNow + point * 0.5 : desired < slNow - point * 0.5);
|
|
13846
|
+
if (!improved) {
|
|
13847
|
+
lines.push(`- ${sym} #${pos.ticket}: SL (${choice}) iyileştirme değil — dokunulmadı`);
|
|
13848
|
+
return;
|
|
13849
|
+
}
|
|
13850
|
+
let mod = null;
|
|
13851
|
+
try {
|
|
13852
|
+
mod = await financetools.handlers.mt5_modify(
|
|
13853
|
+
{ ticket: Number(pos.ticket), sl: rnd(desired), tp: Number(pos.tp) || 0 },
|
|
13854
|
+
{ sessionId: sidS }
|
|
13855
|
+
);
|
|
13856
|
+
} catch (e) {
|
|
13857
|
+
mod = { ok: false, error: String((e && e.message) || e) };
|
|
13858
|
+
}
|
|
13859
|
+
const label = { be: 'breakeven', trail: 'trailing', sikilastir: 'sıkılaştırma', koru: 'koruma' }[choice] || choice;
|
|
13860
|
+
if (mod && mod.ok) {
|
|
13861
|
+
if (st) { st.be = true; finWatchSave(); }
|
|
13862
|
+
lines.push(`- ${sym} #${pos.ticket}: SL ${label} → ${rnd(desired)} ✓ (p=${slc.p.toFixed(2)})`);
|
|
13863
|
+
} else {
|
|
13864
|
+
lines.push(`- ${sym} #${pos.ticket}: SL ${label} reddedildi — ${(mod && mod.error) || 'hata'}`);
|
|
13865
|
+
}
|
|
13866
|
+
}
|
|
13867
|
+
|
|
13868
|
+
/* JEV FİYAT ALARMI: Jev'in seçtiği seviyeye TEK SEFERLİK alarm kurar (kod).
|
|
13869
|
+
Aynı sembol/yönde yakın bir alarm zaten varsa tekrar kurmaz (alarm seli yok).
|
|
13870
|
+
Alarm tetiklenince finWakeAgents ilgili ajanı tur beklemeden uyandırır. */
|
|
13871
|
+
async function finTsSetAlarm(sidS, agent, sym, mkt, ans, i, lines) {
|
|
13872
|
+
const c = finTsChoice(ans, 'alarm_' + i);
|
|
13873
|
+
if (!c || String(c.choice) === 'yok') return;
|
|
13874
|
+
if (!(c.p >= FIN_TS_CONFIRM_P && c.p - c.second >= FIN_TS_MARGIN_P)) return;
|
|
13875
|
+
const row = (mkt && mkt.row) || {};
|
|
13876
|
+
const ind = (mkt && mkt.ind) || {};
|
|
13877
|
+
const ref = Number(row.bid) || Number(row.ask) || 0;
|
|
13878
|
+
if (!(ref > 0)) return;
|
|
13879
|
+
const atr = Number(ind.atr14) || 0;
|
|
13880
|
+
const digits = Math.max(0, Math.min(8, Number(row.digits) || 2));
|
|
13881
|
+
const rnd = (v) => Number(Number(v).toFixed(digits));
|
|
13882
|
+
const range = ind.range20 || {};
|
|
13883
|
+
const fallback = atr > 0 ? atr : ref * 0.003;
|
|
13884
|
+
const choice = String(c.choice);
|
|
13885
|
+
let direction = 'above';
|
|
13886
|
+
let price = 0;
|
|
13887
|
+
if (choice === 'ust_kirilim') { direction = 'above'; price = Number(range.high) || (ref + fallback); }
|
|
13888
|
+
else if (choice === 'alt_kirilim') { direction = 'below'; price = Number(range.low) || (ref - fallback); }
|
|
13889
|
+
else if (choice === 'yukari_atr') { direction = 'above'; price = ref + atr * 0.5; }
|
|
13890
|
+
else if (choice === 'asagi_atr') { direction = 'below'; price = ref - atr * 0.5; }
|
|
13891
|
+
else return;
|
|
13892
|
+
/* seviye yanlış tarafta kalırsa (yeni tepe/dip) alarm anında tetiklenmesin */
|
|
13893
|
+
const minAway = Math.max(atr * 0.2, ref * 0.0004);
|
|
13894
|
+
if (direction === 'above' && price <= ref + minAway) price = ref + minAway;
|
|
13895
|
+
if (direction === 'below' && price >= ref - minAway) price = ref - minAway;
|
|
13896
|
+
price = rnd(price);
|
|
13897
|
+
const tol = Math.max(atr * 0.15, price * 0.0005);
|
|
13898
|
+
try {
|
|
13899
|
+
const existing = (financeState.alerts || []).some((a) =>
|
|
13900
|
+
a && String(a.symbol || '').toUpperCase() === sym &&
|
|
13901
|
+
String(a.direction || '') === direction &&
|
|
13902
|
+
Math.abs(Number(a.price) - price) <= tol
|
|
13903
|
+
);
|
|
13904
|
+
if (existing) {
|
|
13905
|
+
lines.push(`- ${sym}: 🔔 alarm zaten var (${direction === 'above' ? '≥' : '≤'} ${price})`);
|
|
13906
|
+
return;
|
|
13907
|
+
}
|
|
13908
|
+
} catch {}
|
|
13909
|
+
let r = null;
|
|
13910
|
+
try {
|
|
13911
|
+
r = await financetools.handlers.mt5_alerts(
|
|
13912
|
+
{ action: 'set', symbol: sym, price, direction, mode: 'once', note: `TypeSafe seviye alarmı (p=${c.p.toFixed(2)})` },
|
|
13913
|
+
{ sessionId: sidS }
|
|
13914
|
+
);
|
|
13915
|
+
} catch (e) {
|
|
13916
|
+
r = { ok: false, error: String((e && e.message) || e) };
|
|
13917
|
+
}
|
|
13918
|
+
if (r && r.ok) lines.push(`- ${sym}: 🔔 ALARM kuruldu ${direction === 'above' ? '≥' : '≤'} ${price} (tek seferlik)`);
|
|
13919
|
+
else lines.push(`- ${sym}: alarm kurulamadı — ${(r && r.error) || 'hata'}`);
|
|
13920
|
+
}
|
|
13921
|
+
|
|
13922
|
+
/* JEV ARAÇ İSTEĞİ: Jev'in seçtiği ihtiyaç kategorisini KOD somut isteğe çevirir
|
|
13923
|
+
ve TOOL botuna ASENKRON yazar (engine._toolRequest). Ajan BEKLEMEZ; araç
|
|
13924
|
+
hazır olunca rapor AJAN DM/sohbete düşer. Ajan başına saatte 1 istek. */
|
|
13925
|
+
async function finTsToolRequest(sidS, agent, ans, lines) {
|
|
13926
|
+
const c = finTsChoice(ans, 'arac');
|
|
13927
|
+
if (!c || String(c.choice) === 'yok') return;
|
|
13928
|
+
if (!(c.p >= FIN_TS_CONFIRM_P && c.p - c.second >= FIN_TS_MARGIN_P)) return;
|
|
13929
|
+
const now = Date.now();
|
|
13930
|
+
if (agent.tsToolReqAt && now - agent.tsToolReqAt < 60 * 60 * 1000) {
|
|
13931
|
+
lines.push(`- araç isteği (${c.choice}) atlandı — son istek 1 saatten yeni`);
|
|
13932
|
+
return;
|
|
13933
|
+
}
|
|
13934
|
+
const syms = Array.isArray(agent.symbols) && agent.symbols.length ? agent.symbols.join(', ') : '(izleme listesi)';
|
|
13935
|
+
const specs = {
|
|
13936
|
+
haber: {
|
|
13937
|
+
name: 'fin_haber_makro',
|
|
13938
|
+
task:
|
|
13939
|
+
'fin_haber_makro adında bir tool yaz: verilen sembol listesi için son haber başlıklarını ve bugünün yüksek etkili ekonomik takvim olaylarını toplayıp ' +
|
|
13940
|
+
'JSON döndürsün: { ok, symbol, news:[{title, source, at, url}], calendar:[{time, currency, event, impact}] }. Girdi: {symbols:[...], hours:24}.',
|
|
13941
|
+
context: `Kullanım: Beast Finance Jev ajanları makro/haber bağlamını karar state'ine koyacak. Örnek semboller: ${syms}. Haber için mevcut runner: src/agent/scripts/news.py (python) ve web_search/websearch.py köprüsü; takvim için investing/forexfactory benzeri ücretsiz kaynak. Çıktı sözleşmesi yukarıdaki JSON olmalı.`,
|
|
13942
|
+
},
|
|
13943
|
+
korelasyon: {
|
|
13944
|
+
name: 'fin_korelasyon',
|
|
13945
|
+
task:
|
|
13946
|
+
'fin_korelasyon adında bir tool yaz: verilen semboller için son N mum kapanışından korelasyon matrisi + yönlü maruziyet özeti döndürsün: ' +
|
|
13947
|
+
'{ ok, tf, matrix:{A:{B:r}}, exposure:{USD:net,...} }. Girdi: {symbols:[...], timeframe:"H1", count:300}.',
|
|
13948
|
+
context: `Kullanım: aynı yönde birikmiş pozisyon riskini Jev kararlarında görmek için. MT5 köprüsü mt5_rates ile mum verisi çekilebilir (financetools.js desenine bak); saf matematik Node tarafında yapılabilir (getiri serisi → Pearson). Semboller: ${syms}.`,
|
|
13949
|
+
},
|
|
13950
|
+
rapor: {
|
|
13951
|
+
name: 'fin_sembol_rapor',
|
|
13952
|
+
task:
|
|
13953
|
+
'fin_sembol_rapor adında bir tool yaz: bir sembolün son 30/90 gün kapanan işlemlerini öğrenme deposundan (finance/ogrenme.json) okuyup ' +
|
|
13954
|
+
'{ ok, symbol, tf, trades, wins, net, avgR, bestTf, worstTf, topMistakes:[...] } özeti döndürsün. Girdi: {symbol, days:30}.',
|
|
13955
|
+
context: `Kullanım: Jev tur state'ine kompakt performans özeti koymak. Veri kaynağı: %APPDATA%\\beast\\finance\\ogrenme.json (finLearnApi stats/list ile aynı şema). Semboller: ${syms}.`,
|
|
13956
|
+
},
|
|
13957
|
+
};
|
|
13958
|
+
const spec = specs[String(c.choice)];
|
|
13959
|
+
if (!spec) return;
|
|
13960
|
+
agent.tsToolReqAt = now;
|
|
13961
|
+
try {
|
|
13962
|
+
const r = await engine._toolRequest({ task: spec.task, context: spec.context }, sidS);
|
|
13963
|
+
lines.push(
|
|
13964
|
+
r && r.ok
|
|
13965
|
+
? `- 🧰 Araç isteği TOOL botuna gönderildi: ${spec.name} (asenkron — hazır olunca rapor düşer)`
|
|
13966
|
+
: `- araç isteği başarısız — ${(r && r.error) || 'hata'}`
|
|
13967
|
+
);
|
|
13968
|
+
} catch (e) {
|
|
13969
|
+
lines.push('- araç isteği hatası: ' + String((e && e.message) || e));
|
|
13970
|
+
}
|
|
13971
|
+
}
|
|
13972
|
+
|
|
13739
13973
|
function finTsSchedule(sid, agent, sec) {
|
|
13740
13974
|
if (!agent || !financeState.agents.has(String(sid))) return;
|
|
13741
13975
|
clearTimeout(agent.timer);
|
|
@@ -13799,12 +14033,15 @@ async function finTypeSafeRound(sid, agent) {
|
|
|
13799
14033
|
}
|
|
13800
14034
|
/* VERİ: hesap + pozisyonlar + sembol fiyat/gösterge — hepsi KOD ile toplanır,
|
|
13801
14035
|
yalnız TypeSafe'e gönderilir; hiçbir LLM çağrısı yapılmaz */
|
|
13802
|
-
const [accR, posR] = await Promise.all([
|
|
14036
|
+
const [accR, posR, ordR] = await Promise.all([
|
|
13803
14037
|
financetools.handlers.mt5_account({}),
|
|
13804
14038
|
financetools.handlers.mt5_positions({}),
|
|
14039
|
+
financetools.handlers.mt5_orders({}).catch(() => null),
|
|
13805
14040
|
]);
|
|
13806
14041
|
const account = (accR && accR.account) || {};
|
|
13807
14042
|
const posList = (posR && posR.positions) || [];
|
|
14043
|
+
/* BEKLEYEN EMİRLER: pozisyon slotunu paylaşır (aşırı birikmeyi önler) */
|
|
14044
|
+
const pendCount = (ordR && Array.isArray(ordR.orders) ? ordR.orders.length : 0);
|
|
13808
14045
|
const market = {};
|
|
13809
14046
|
for (const sym of symbols) {
|
|
13810
14047
|
const tf = finTsPickTf(agent, sym);
|
|
@@ -13828,10 +14065,14 @@ async function finTypeSafeRound(sid, agent) {
|
|
|
13828
14065
|
acik_pozisyon: posList.length,
|
|
13829
14066
|
},
|
|
13830
14067
|
limitler: { max_pozisyon: Number(f.maxPositions) || 3, min_lot: Number(f.minLot) || 0.01, max_lot: Number(f.maxLot) || 0.1 },
|
|
13831
|
-
|
|
13832
|
-
|
|
13833
|
-
|
|
14068
|
+
bekleyen_emir: pendCount,
|
|
14069
|
+
/* MEVCUT ALARMLAR: Jev aynı seviyeye tekrar alarm kurmasın (kod yine kapılar) */
|
|
14070
|
+
alarmlar: (financeState.alerts || []).slice(0, 8).map((a) => ({
|
|
14071
|
+
sembol: a.symbol, yon: a.direction, fiyat: a.price, mod: a.once ? 'tek' : 'tekrarlı',
|
|
13834
14072
|
})),
|
|
14073
|
+
/* AÇIK POZİSYONLAR: R/kâr R'si/en iyi R + koruma bayrakları — Jev'in
|
|
14074
|
+
kapatma/kısmi/SL kararları bu bağlamla verilir (finTsPosState) */
|
|
14075
|
+
pozisyonlar: posList.map((p) => finTsPosState(p)),
|
|
13835
14076
|
piyasa: {},
|
|
13836
14077
|
ogrenme: finBuildLearnDigest(symbols),
|
|
13837
14078
|
/* KALİBRASYON: öğrenilmiş eşik + risk çarpanı + kazandıran kurallar */
|
|
@@ -13850,7 +14091,7 @@ async function finTypeSafeRound(sid, agent) {
|
|
|
13850
14091
|
};
|
|
13851
14092
|
}
|
|
13852
14093
|
|
|
13853
|
-
/*
|
|
14094
|
+
/* -------- TRADER: yön + teyit + açık pozisyon yönetimi (kapat/kısmi/SL) -------- */
|
|
13854
14095
|
if (isTrader) {
|
|
13855
14096
|
const posBySym = {};
|
|
13856
14097
|
for (const p of posList) {
|
|
@@ -13873,15 +14114,81 @@ async function finTypeSafeRound(sid, agent) {
|
|
|
13873
14114
|
instructions: `${sym} fiyat yapısı ve momentum, 'yon_' + ${i} kararını gerçekten destekliyor mu?`,
|
|
13874
14115
|
criteria: { true: 'Net destekliyor', false: 'Zayıf/çelişkili' },
|
|
13875
14116
|
};
|
|
14117
|
+
/* GİRİŞ TİPİ + RİSK PROFİLİ: seviyeleri KOD hesaplar (ATR/EMA), kararı Jev verir */
|
|
14118
|
+
questions['emir_' + i] = {
|
|
14119
|
+
type: 'choice',
|
|
14120
|
+
instructions: `${sym} sinyali onaylanırsa giriş tipi ne olsun? Anında mı gir (market), geri çekilme mi bekle (limit), kırılım teyidi mi bekle (stop)? Giriş seviyesini kod ATR/EMA'dan hesaplar.`,
|
|
14121
|
+
criteria: {
|
|
14122
|
+
market: 'Anında piyasadan gir — kaçırma riski yok, fiyat şimdiki',
|
|
14123
|
+
limit: 'Geri çekilmede limit emir — daha iyi fiyat bekle (ATR/EMA seviyesi)',
|
|
14124
|
+
stop: 'Kırılımda stop emir — momentum teyidi bekle (ATR seviyesi)',
|
|
14125
|
+
},
|
|
14126
|
+
};
|
|
14127
|
+
questions['risk_' + i] = {
|
|
14128
|
+
type: 'choice',
|
|
14129
|
+
instructions: `${sym} için SL/TP profili? Seviyeleri kod ATR'den hesaplar; volatilite ve öğrenme geçmişini dikkate al.`,
|
|
14130
|
+
criteria: {
|
|
14131
|
+
siki: 'Sıkı: SL 1.0×ATR · TP 2.0×ATR — hızlı çık, yüksek isabet',
|
|
14132
|
+
dengeli: 'Dengeli: SL 1.5×ATR · TP 2.5×ATR',
|
|
14133
|
+
genis: 'Geniş: SL 2.0×ATR · TP 3.5×ATR — trendi taşı, gürültüye dayan',
|
|
14134
|
+
},
|
|
14135
|
+
};
|
|
14136
|
+
questions['alarm_' + i] = {
|
|
14137
|
+
type: 'choice',
|
|
14138
|
+
instructions: `${sym} için fiyat alarmı kurulsun mu? Alarm SEVİYESİ gelince tur beklemeden uyanırsın (tek seferlik). Gereksizse yok seç.`,
|
|
14139
|
+
criteria: {
|
|
14140
|
+
yok: 'Alarm gerekmez',
|
|
14141
|
+
ust_kirilim: 'Üst kırılım (son 20 mum tepesi) üzerinde alarm',
|
|
14142
|
+
alt_kirilim: 'Alt kırılım (son 20 mum dibi) altında alarm',
|
|
14143
|
+
yukari_atr: 'Fiyat +0.5×ATR üzerinde alarm',
|
|
14144
|
+
asagi_atr: 'Fiyat -0.5×ATR altında alarm',
|
|
14145
|
+
},
|
|
14146
|
+
};
|
|
13876
14147
|
if (posBySym[sym]) {
|
|
13877
14148
|
const pp = posBySym[sym];
|
|
14149
|
+
const ps = finTsPosState(pp);
|
|
14150
|
+
const kzTxt = ps.kz_r == null ? 'R bilinmiyor' : `kâr ${ps.kz_r}R`;
|
|
14151
|
+
const bestTxt = ps.en_iyi_r == null ? '' : ` · en iyi ${ps.en_iyi_r}R`;
|
|
14152
|
+
const slTxt = Number(pp.sl) > 0 ? `SL ${pp.sl}` : 'SL YOK';
|
|
13878
14153
|
questions['kapat_' + i] = {
|
|
13879
14154
|
type: 'noul',
|
|
13880
|
-
instructions: `${sym} açık pozisyon (${
|
|
14155
|
+
instructions: `${sym} açık pozisyon (${ps.yon}, kâr ${Number(pp.profit) || 0}, ${kzTxt}${bestTxt}, ${slTxt}) ŞİMDİ kapatılmalı mı? Kârı koruma, kâr geri verme, momentum dönüşü veya SL/TP yakınlığını değerlendir.`,
|
|
13881
14156
|
criteria: { true: 'Kapat — risk/kâr koruma', false: 'Açık kalsın — tez sürüyor' },
|
|
13882
14157
|
};
|
|
14158
|
+
questions['kismi_' + i] = {
|
|
14159
|
+
type: 'choice',
|
|
14160
|
+
instructions: `${sym} açık pozisyon (${kzTxt}${bestTxt}${ps.be ? ' · SL takipte' : ''}) için ŞİMDİ kısmi kapatma uygun mu?${ps.kismi_alindi ? ' Bu pozisyonda kısmi kapatma ZATEN yapıldı — yok seç.' : ' Kârı realize etmek/riski azaltmak için değerlendir.'}`,
|
|
14161
|
+
criteria: {
|
|
14162
|
+
yok: 'Kısmi kapatma yok — pozisyon tam kalsın',
|
|
14163
|
+
yuzde25: 'Pozisyonun %25’i kapatılsın',
|
|
14164
|
+
yuzde50: 'Pozisyonun %50’si kapatılsın',
|
|
14165
|
+
yuzde75: 'Pozisyonun %75’i kapatılsın',
|
|
14166
|
+
},
|
|
14167
|
+
};
|
|
14168
|
+
questions['sl_' + i] = {
|
|
14169
|
+
type: 'choice',
|
|
14170
|
+
instructions: `${sym} açık pozisyonu için SL/koruma aksiyonu: ${ps.yon}, giriş ${Number(pp.price_open) || '?'}, şimdi ${ps.fiyat || '?'}, ${slTxt}, ${kzTxt}. Kâr yoksa ve SL varsa 'birak'; SL yoksa 'koru' ile koruma koy.`,
|
|
14171
|
+
criteria: {
|
|
14172
|
+
birak: 'Dokunma — SL yerinde kalsın',
|
|
14173
|
+
be: 'Breakeven — SL girişe çekilsin (kârı kilitle)',
|
|
14174
|
+
trail: 'Trailing — SL fiyatın gerisine taşınsın (kârı takip et)',
|
|
14175
|
+
sikilastir: 'Sıkılaştır — SL fiyata yaklaştırılsın (kârı daha çok koru)',
|
|
14176
|
+
koru: 'Koruma koy — SL’siz pozisyona ATR tabanlı stop eklensin',
|
|
14177
|
+
},
|
|
14178
|
+
};
|
|
13883
14179
|
}
|
|
13884
14180
|
});
|
|
14181
|
+
/* ARAÇ İHTİYACI (tur düzeyi): eksik araç varsa TOOL botuna yazdırılır */
|
|
14182
|
+
questions['arac'] = {
|
|
14183
|
+
type: 'choice',
|
|
14184
|
+
instructions: 'Bu turda eksik bir ARAÇ var mı? Varsa TOOL botuna yazdırılır (asenkron; rapor AJAN DM\'e düşer, ajan beklemez). İhtiyaç yoksa yok seç.',
|
|
14185
|
+
criteria: {
|
|
14186
|
+
yok: 'Araç isteği yok',
|
|
14187
|
+
haber: 'Haber/makro veri aracı (ekonomik takvim + haber akışı)',
|
|
14188
|
+
korelasyon: 'Korelasyon/maruziyet aracı (yönlü risk matrisi)',
|
|
14189
|
+
rapor: 'Sembol performans raporu aracı (öğrenme deposundan özet)',
|
|
14190
|
+
},
|
|
14191
|
+
};
|
|
13885
14192
|
let ans = null;
|
|
13886
14193
|
try {
|
|
13887
14194
|
ans = await finTsAsk(state, questions);
|
|
@@ -13901,6 +14208,8 @@ async function finTypeSafeRound(sid, agent) {
|
|
|
13901
14208
|
const c = finTsChoice(ans, 'yon_' + i);
|
|
13902
14209
|
const conf = finTsNoul(ans, 'teyit_' + i);
|
|
13903
14210
|
const pos = posBySym[sym];
|
|
14211
|
+
/* JEV FİYAT ALARMI: seçilen seviyeye tek seferlik alarm (kurulumu kod yapar) */
|
|
14212
|
+
try { await finTsSetAlarm(sidS, agent, sym, market[sym], ans, i, lines); } catch {}
|
|
13904
14213
|
/* ÖĞRENİLMİŞ EŞİK + RİSK ÇARPANI: kalibrasyon verisi biriktikçe kod
|
|
13905
14214
|
eşiği ve riski kendisi ayarlar (sistem öğrendikçe o karar verir) */
|
|
13906
14215
|
const cal = finTsCalibration(sym, market[sym].tf);
|
|
@@ -13915,6 +14224,8 @@ async function finTypeSafeRound(sid, agent) {
|
|
|
13915
14224
|
lines.push(`- ${sym}: KAPAT (p=${ex.toFixed(2)}) ${r && r.ok ? '✓ pozisyon kapatıldı' : '✗ ' + ((r && r.error) || 'hata')}`);
|
|
13916
14225
|
} else {
|
|
13917
14226
|
lines.push(`- ${sym}: pozisyon açık (kapat=${ex == null ? 'yanıt yok' : ex.toFixed(2)} < ${FIN_TS_EXIT_P}) — tez sürüyor`);
|
|
14227
|
+
/* JEV POZİSYON YÖNETİMİ: kısmi kapatma + SL/koruma kararları */
|
|
14228
|
+
try { await finTsManageOpen(sidS, agent, sym, pos, market[sym], ans, i, lines); } catch {}
|
|
13918
14229
|
}
|
|
13919
14230
|
continue;
|
|
13920
14231
|
}
|
|
@@ -13930,34 +14241,71 @@ async function finTypeSafeRound(sid, agent) {
|
|
|
13930
14241
|
lines.push(`- ${sym}: ${finTsFmtChoice(c)} · teyit=${conf == null ? 'yok' : conf.toFixed(2)} < ${FIN_TS_CONFIRM_P} → BEKLE`);
|
|
13931
14242
|
continue;
|
|
13932
14243
|
}
|
|
13933
|
-
if (opened >= 1 || posList.length >= (Number(f.maxPositions) || 3)) {
|
|
13934
|
-
lines.push(`- ${sym}: sinyal güçlü (${c.choice} p=${c.p.toFixed(2)}) ama pozisyon sınırı dolu — bu tur açılmadı`);
|
|
14244
|
+
if (opened >= 1 || posList.length + pendCount >= (Number(f.maxPositions) || 3)) {
|
|
14245
|
+
lines.push(`- ${sym}: sinyal güçlü (${c.choice} p=${c.p.toFixed(2)}) ama pozisyon+bekleyen emir sınırı dolu — bu tur açılmadı`);
|
|
13935
14246
|
continue;
|
|
13936
14247
|
}
|
|
13937
|
-
/*
|
|
14248
|
+
/* GİRİŞ TİPİ + RİSK PROFİLİ (JEV kararı) — seviyeleri KOD hesaplar */
|
|
13938
14249
|
const row = market[sym].row || {};
|
|
13939
|
-
const
|
|
13940
|
-
if (!(
|
|
14250
|
+
const entryRef = c.choice === 'buy' ? Number(row.ask) || Number(row.bid) : Number(row.bid) || Number(row.ask);
|
|
14251
|
+
if (!(entryRef > 0)) { lines.push(`- ${sym}: fiyat okunamadı`); continue; }
|
|
13941
14252
|
const atr = Number(market[sym].ind && market[sym].ind.atr14) || 0;
|
|
13942
|
-
const slDist = Math.max(atr > 0 ? atr * 1.5 : price * 0.003, price * 0.0004);
|
|
13943
|
-
const tpDist = Math.max(atr > 0 ? atr * 2.5 : price * 0.006, slDist * 1.2);
|
|
13944
14253
|
const digits = Math.max(0, Math.min(8, Number(row.digits) || 2));
|
|
13945
14254
|
const rnd = (v) => Number(Number(v).toFixed(digits));
|
|
13946
|
-
const
|
|
13947
|
-
const
|
|
14255
|
+
const point = Number(row.point) > 0 ? Number(row.point) : Math.pow(10, -digits);
|
|
14256
|
+
const stopsPts = Number(row.trade_stops_level) || 0;
|
|
14257
|
+
const minDist = stopsPts > 0 ? (stopsPts + 1) * point : point;
|
|
14258
|
+
const isBuy = c.choice === 'buy';
|
|
14259
|
+
/* RİSK PROFİLİ: SL/TP çarpanlarını Jev seçer, seviyeleri kod kurar */
|
|
14260
|
+
const profC = finTsChoice(ans, 'risk_' + i);
|
|
14261
|
+
const profKey = ['siki', 'dengeli', 'genis'].includes(String(profC && profC.choice)) ? String(profC.choice) : 'dengeli';
|
|
14262
|
+
const prof = { siki: [1.0, 2.0], dengeli: [1.5, 2.5], genis: [2.0, 3.5] }[profKey];
|
|
14263
|
+
const slDist = Math.max(atr > 0 ? atr * prof[0] : entryRef * 0.003, entryRef * 0.0004);
|
|
14264
|
+
const tpDist = Math.max(atr > 0 ? atr * prof[1] : entryRef * 0.006, slDist * 1.2);
|
|
14265
|
+
/* EMİR TİPİ: market / limit / stop — Jev seçer, giriş seviyesini kod kurar */
|
|
14266
|
+
const emirC = finTsChoice(ans, 'emir_' + i);
|
|
14267
|
+
const emirKind = emirC && emirC.p >= FIN_TS_CONFIRM_P && emirC.p - emirC.second >= FIN_TS_MARGIN_P ? String(emirC.choice) : 'market';
|
|
14268
|
+
let orderType = 'market';
|
|
14269
|
+
let entry = entryRef;
|
|
14270
|
+
if (emirKind === 'limit') {
|
|
14271
|
+
const pull = Math.max(atr > 0 ? atr * 0.5 : entryRef * 0.0015, minDist + point);
|
|
14272
|
+
const atrLvl = isBuy ? entryRef - pull : entryRef + pull;
|
|
14273
|
+
const ema = Number(market[sym].ind && market[sym].ind.ema50);
|
|
14274
|
+
const emaOk =
|
|
14275
|
+
isFinite(ema) && ema > 0 &&
|
|
14276
|
+
(isBuy ? ema < entryRef - minDist : ema > entryRef + minDist) &&
|
|
14277
|
+
Math.abs(entryRef - ema) <= Math.max(atr * 2, entryRef * 0.006);
|
|
14278
|
+
entry = rnd(emaOk ? ema : atrLvl);
|
|
14279
|
+
orderType = isBuy ? 'buy_limit' : 'sell_limit';
|
|
14280
|
+
} else if (emirKind === 'stop') {
|
|
14281
|
+
const breakDist = Math.max(atr > 0 ? atr * 0.25 : entryRef * 0.001, minDist + point);
|
|
14282
|
+
entry = rnd(isBuy ? entryRef + breakDist : entryRef - breakDist);
|
|
14283
|
+
orderType = isBuy ? 'buy_stop' : 'sell_stop';
|
|
14284
|
+
}
|
|
14285
|
+
const sl = rnd(isBuy ? entry - slDist : entry + slDist);
|
|
14286
|
+
const tp = rnd(isBuy ? entry + tpDist : entry - tpDist);
|
|
13948
14287
|
const riskBase = Number(f.riskPerTradePct) > 0 ? Number(f.riskPerTradePct) : 0.5;
|
|
13949
14288
|
const riskPct = Math.max(0.1, Math.min(2, Math.round(riskBase * cal.riskMult * 100) / 100));
|
|
14289
|
+
const emirLabel =
|
|
14290
|
+
orderType === 'market' ? 'MARKET' :
|
|
14291
|
+
isBuy && orderType === 'buy_limit' ? 'BUY LIMIT' :
|
|
14292
|
+
!isBuy && orderType === 'sell_limit' ? 'SELL LIMIT' :
|
|
14293
|
+
isBuy && orderType === 'buy_stop' ? 'BUY STOP' : 'SELL STOP';
|
|
13950
14294
|
const r = await financetools.handlers.mt5_trade(
|
|
13951
14295
|
{
|
|
13952
14296
|
symbol: sym,
|
|
13953
14297
|
side: c.choice,
|
|
13954
|
-
type:
|
|
14298
|
+
type: orderType,
|
|
14299
|
+
...(orderType === 'market' ? {} : { price: entry }),
|
|
13955
14300
|
sl,
|
|
13956
14301
|
tp,
|
|
13957
14302
|
riskPct,
|
|
13958
14303
|
timeframe: market[sym].tf,
|
|
13959
14304
|
comment: 'TS',
|
|
13960
|
-
reason:
|
|
14305
|
+
reason:
|
|
14306
|
+
`TypeSafe: yön ${c.choice} p=${c.p.toFixed(2)} fark=${(c.p - c.second).toFixed(2)} teyit=${conf.toFixed(2)}` +
|
|
14307
|
+
(orderType !== 'market' ? ` · ${orderType} @${entry}` : '') +
|
|
14308
|
+
` · ${profKey} profil`,
|
|
13961
14309
|
},
|
|
13962
14310
|
{ sessionId: sidS }
|
|
13963
14311
|
);
|
|
@@ -13979,20 +14327,26 @@ async function finTypeSafeRound(sid, agent) {
|
|
|
13979
14327
|
hour: new Date().getHours(),
|
|
13980
14328
|
emaAlign: ind.ema50 != null && ind.ema200 != null ? (ind.ema50 > ind.ema200) === (c.choice === 'buy') : null,
|
|
13981
14329
|
rsi: ind.rsi14,
|
|
13982
|
-
atrPct: atr > 0 &&
|
|
14330
|
+
atrPct: atr > 0 && entryRef > 0 ? atr / entryRef : null,
|
|
13983
14331
|
riskPct,
|
|
14332
|
+
orderType,
|
|
13984
14333
|
});
|
|
13985
14334
|
} catch {}
|
|
13986
14335
|
lines.push(
|
|
13987
|
-
|
|
14336
|
+
(orderType === 'market'
|
|
14337
|
+
? `- ${sym}: ⚡ MARKET ${c.choice.toUpperCase()} açıldı (lot ${r.opened && r.opened.volume}, SL ${sl}, TP ${tp}, risk %${riskPct}${cal.riskMult !== 1 ? ` ×${cal.riskMult}` : ''})`
|
|
14338
|
+
: `- ${sym}: ⏳ ${emirLabel} emri kondu @${entry} (${profKey} profil, SL ${sl}, TP ${tp}, risk %${riskPct})`) +
|
|
14339
|
+
` · p=${c.p.toFixed(2)} teyit=${conf.toFixed(2)} · tf=${market[sym].tf}` +
|
|
13988
14340
|
(thAction !== FIN_TS_DEFAULT_TH.action ? ` · öğrenilmiş eşik p≥${thAction.toFixed(2)}` : '')
|
|
13989
14341
|
);
|
|
13990
14342
|
} else if (r && r.ok && r.shadow) {
|
|
13991
|
-
lines.push(`- ${sym}: SHADOW mod — emir gönderilmedi (${c.choice} p=${c.p.toFixed(2)})`);
|
|
14343
|
+
lines.push(`- ${sym}: SHADOW mod — emir gönderilmedi (${emirLabel} ${c.choice} p=${c.p.toFixed(2)})`);
|
|
13992
14344
|
} else {
|
|
13993
14345
|
lines.push(`- ${sym}: ${c.choice.toUpperCase()} sinyali reddedildi — ${(r && r.error) || 'hata'}`);
|
|
13994
14346
|
}
|
|
13995
14347
|
}
|
|
14348
|
+
/* JEV ARAÇ İSTEĞİ (tur düzeyi): eksik araç varsa TOOL botuna asenkron yazdır */
|
|
14349
|
+
try { await finTsToolRequest(sidS, agent, ans, lines); } catch {}
|
|
13996
14350
|
finTsPost(
|
|
13997
14351
|
sidS,
|
|
13998
14352
|
agent,
|
|
@@ -14033,6 +14387,17 @@ async function finTypeSafeRound(sid, agent) {
|
|
|
14033
14387
|
criteria: { true: 'Risk var — dikkat/engel', false: 'Risk normal' },
|
|
14034
14388
|
};
|
|
14035
14389
|
});
|
|
14390
|
+
/* ARAÇ İHTİYACI (tur düzeyi): eksik araç varsa TOOL botuna yazdırılır */
|
|
14391
|
+
questions['arac'] = {
|
|
14392
|
+
type: 'choice',
|
|
14393
|
+
instructions: 'Bu turda rolün için eksik bir ARAÇ var mı? Varsa TOOL botuna yazdırılır (asenkron; rapor AJAN DM\'e düşer, ajan beklemez). İhtiyaç yoksa yok seç.',
|
|
14394
|
+
criteria: {
|
|
14395
|
+
yok: 'Araç isteği yok',
|
|
14396
|
+
haber: 'Haber/makro veri aracı (ekonomik takvim + haber akışı)',
|
|
14397
|
+
korelasyon: 'Korelasyon/maruziyet aracı (yönlü risk matrisi)',
|
|
14398
|
+
rapor: 'Sembol performans raporu aracı (öğrenme deposundan özet)',
|
|
14399
|
+
},
|
|
14400
|
+
};
|
|
14036
14401
|
let ans = null;
|
|
14037
14402
|
try {
|
|
14038
14403
|
ans = await finTsAsk(state, questions);
|
|
@@ -14054,6 +14419,8 @@ async function finTypeSafeRound(sid, agent) {
|
|
|
14054
14419
|
(rk == null ? '' : ` · risk=${rk.toFixed(2)}${rk >= 0.55 ? ' ⚠' : ''}`)
|
|
14055
14420
|
);
|
|
14056
14421
|
});
|
|
14422
|
+
/* JEV ARAÇ İSTEĞİ (rol ajanı): eksik araç varsa TOOL botuna asenkron yazdır */
|
|
14423
|
+
try { await finTsToolRequest(sidS, agent, ans, lines); } catch {}
|
|
14057
14424
|
finTsPost(sidS, agent, `🧠 TypeSafe analiz (LLM yok — girdi yalnız TypeSafe):\n` + lines.join('\n'), symbols.join(','));
|
|
14058
14425
|
} catch (e) {
|
|
14059
14426
|
try { financeLog('[typesafe] tur hatası (' + sidS + '): ' + String((e && e.message) || e)); } catch {}
|
|
@@ -14083,7 +14450,7 @@ function finAgentRound(sid, opts) {
|
|
|
14083
14450
|
}
|
|
14084
14451
|
/* JEV-ONLY (SABİT): finance ajanları LLM HİÇ kullanmaz — tur deterministik
|
|
14085
14452
|
TypeSafe hattıyla koşar (girdi yalnız TypeSafe'e gider, cevaplar AJAN
|
|
14086
|
-
DM'e düşer
|
|
14453
|
+
DM'e düşer; emir tipi/seviyeler/alarm/araç isteği Jev kararı + kod) */
|
|
14087
14454
|
if (finTsAgentMode(agent)) {
|
|
14088
14455
|
finTypeSafeRound(String(sid), agent).catch(() => {});
|
|
14089
14456
|
return;
|