neuroalpha 0.2.0 → 0.3.13

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.
Files changed (52) hide show
  1. package/LICENSE +17 -0
  2. package/README.md +9 -3
  3. package/bin/cli.js +69 -69
  4. package/config/seed-run.json +1 -0
  5. package/config/strategy.json +4 -10
  6. package/deploy/install.sh +128 -0
  7. package/docs//320/224/320/225/320/237/320/233/320/236/320/231.md +103 -0
  8. package/docs//320/230/320/235/320/241/320/242/320/240/320/243/320/232/320/246/320/230/320/257.md +1 -1
  9. package/docs//320/234/320/225/320/245/320/220/320/235/320/230/320/232/320/220_/320/230_/320/237/320/220/320/240/320/220/320/234/320/225/320/242/320/240/320/253.md +171 -0
  10. package/package.json +15 -5
  11. package/public/admin.html +449 -0
  12. package/public/app.js +1182 -379
  13. package/public/index.html +87 -22
  14. package/public/landing.html +1217 -870
  15. package/public/legal.html +51 -8
  16. package/public/styles.css +71 -16
  17. package/server/api/routes.js +220 -35
  18. package/server/auth/index.js +633 -340
  19. package/server/billing/yookassa.js +1 -0
  20. package/server/brokers/index.js +1369 -645
  21. package/server/config.js +6 -4
  22. package/server/data/moex.js +1 -0
  23. package/server/db.js +69 -4
  24. package/server/hub.js +1 -0
  25. package/server/index.js +10 -2
  26. package/server/llm/client.js +1 -0
  27. package/server/llm/providers.js +50 -3
  28. package/server/model/dataset.js +362 -352
  29. package/server/model/forecast.js +202 -37
  30. package/server/model/linalg.js +1 -0
  31. package/server/model/live.js +476 -486
  32. package/server/model/metrics.js +1 -0
  33. package/server/model/models.js +31 -21
  34. package/server/model/profiles.js +1 -0
  35. package/server/model/research.js +128 -205
  36. package/server/model/vocab.js +1 -0
  37. package/server/news/index.js +1 -0
  38. package/server/notify/index.js +304 -168
  39. package/server/params-meta.js +22 -26
  40. package/server/settings.js +1 -0
  41. package/server/strategist/analytics.js +2 -1
  42. package/server/strategist/index.js +7 -7
  43. package/server/strategist/tools.js +2 -1
  44. package/server/util.js +1 -0
  45. package/server/model/gpu-worker.html +0 -11
  46. package/server/model/gpu-worker.js +0 -60
  47. package/server/model/tf-node.js +0 -26
  48. package/server/model/transformer-core.js +0 -330
  49. package/server/model/transformer-gpu.js +0 -221
  50. package/server/model/transformer-pool.js +0 -200
  51. package/server/model/transformer-worker.js +0 -28
  52. package/server/model/transformer.js +0 -57
@@ -1,221 +0,0 @@
1
- // Обучение трансформера на видеокарте без нативных сборок: TensorFlow.js (WebGL) в безголовом браузере
2
- // на базе Chromium (Chrome, Edge, Chromium, Brave). Браузер запускается со своим временным профилем,
3
- // подключается к служебному мини-серверу (только 127.0.0.1) по WebSocket, получает данные окна двоично
4
- // и обучает тем же кодом, что и в Node (transformer-core.js).
5
- // Если браузера нет, WebGL недоступен или отрисовка программная — ошибка, вызывающий откатывается на процессор.
6
- import { spawn, spawnSync } from 'node:child_process';
7
- import fs from 'node:fs';
8
- import os from 'node:os';
9
- import path from 'node:path';
10
- import http from 'node:http';
11
- import crypto from 'node:crypto';
12
- import { fileURLToPath } from 'node:url';
13
- import express from 'express';
14
- import { WebSocketServer } from 'ws';
15
- import { pack, unpack } from './transformer-core.js';
16
-
17
- const HERE = path.dirname(fileURLToPath(import.meta.url));
18
- const ROOT = path.resolve(HERE, '../..');
19
- // программная отрисовка вместо видеокарты
20
- const SOFTWARE = /swiftshader|llvmpipe|softpipe|software|basic render|mesa offscreen|lavapipe/i;
21
-
22
- const exists = (p) => { try { return fs.statSync(p).isFile(); } catch { return false; } };
23
-
24
- // Поиск браузера: BROWSER_PATH → стандартные места установки для Windows / macOS / Linux
25
- export function findBrowser() {
26
- if (process.env.BROWSER_PATH) return exists(process.env.BROWSER_PATH) ? process.env.BROWSER_PATH : null;
27
- const list = [];
28
- if (process.platform === 'win32') {
29
- const bases = [process.env.PROGRAMFILES, process.env['PROGRAMFILES(X86)'], process.env.LOCALAPPDATA].filter(Boolean);
30
- const rel = ['Google/Chrome/Application/chrome.exe', 'Microsoft/Edge/Application/msedge.exe', 'Chromium/Application/chrome.exe', 'BraveSoftware/Brave-Browser/Application/brave.exe'];
31
- for (const r of rel) for (const b of bases) list.push(path.join(b, r));
32
- } else if (process.platform === 'darwin') {
33
- const apps = [['Google Chrome', 'Google Chrome'], ['Microsoft Edge', 'Microsoft Edge'], ['Chromium', 'Chromium'], ['Brave Browser', 'Brave Browser']];
34
- for (const dir of ['/Applications', path.join(os.homedir(), 'Applications')]) for (const [app, bin] of apps) list.push(`${dir}/${app}.app/Contents/MacOS/${bin}`);
35
- } else {
36
- const names = ['google-chrome', 'google-chrome-stable', 'chromium', 'chromium-browser', 'microsoft-edge', 'microsoft-edge-stable', 'brave-browser'];
37
- const dirs = (process.env.PATH || '').split(path.delimiter).filter(Boolean);
38
- for (const n of names) for (const d of [...dirs, '/usr/bin', '/usr/local/bin', '/snap/bin']) list.push(path.join(d, n));
39
- }
40
- return list.find(exists) || null;
41
- }
42
-
43
- // Короткое название видеокарты из строки WebGL («ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 (0x…) Direct3D11 …)»)
44
- function gpuName(renderer) {
45
- const m = /^ANGLE \(([^,]*),\s*(.+?)(?:\s*\(0x[0-9a-f]+\))?(?:\s+Direct3D.*|\s+OpenGL.*|\s+Metal.*|,.*)?\)?$/i.exec(renderer || '');
46
- return (m ? m[2] : renderer || 'неизвестно').trim();
47
- }
48
-
49
- // Завершение браузера со всеми его процессами — строго по своему PID (не по имени: у пользователя открыт свой браузер)
50
- function killTree(pid) {
51
- if (!pid) return;
52
- try {
53
- if (process.platform === 'win32') spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore', windowsHide: true });
54
- else process.kill(-pid, 'SIGKILL'); // отдельная группа процессов (detached)
55
- } catch {
56
- try { process.kill(pid, 'SIGKILL'); } catch { /* уже завершён */ }
57
- }
58
- }
59
-
60
- // при выходе сервера браузеры не должны остаться висеть
61
- const alive = new Set();
62
- process.once('exit', () => { for (const r of alive) killTree(r.proc?.pid); });
63
-
64
- export class GpuRunner {
65
- threads = 1;
66
- where = 'видеокарта';
67
- closed = false;
68
- job = null;
69
- chain = Promise.resolve();
70
-
71
- async start({ timeoutMs = 60000 } = {}) {
72
- const exe = findBrowser();
73
- if (!exe) throw new Error(process.env.BROWSER_PATH ? `нет браузера по пути BROWSER_PATH: ${process.env.BROWSER_PATH}` : 'не найден Chrome / Edge / Chromium / Brave (путь можно указать в BROWSER_PATH)');
74
- try {
75
- await this.#serve();
76
- const hello = new Promise((resolve, reject) => { this.onHello = resolve; this.onStartFail = reject; });
77
- this.dir = fs.mkdtempSync(path.join(os.tmpdir(), 'trading-gpu-'));
78
- const args = [
79
- '--headless=new', `--user-data-dir=${this.dir}`,
80
- '--no-first-run', '--no-default-browser-check', '--disable-extensions', '--disable-component-extensions-with-background-pages',
81
- '--disable-sync', '--disable-component-update', '--disable-default-apps', '--disable-background-networking',
82
- '--mute-audio', '--password-store=basic', '--use-mock-keychain',
83
- '--ignore-gpu-blocklist', '--enable-gpu', '--enable-webgl', '--enable-gpu-rasterization',
84
- '--disable-background-timer-throttling', '--disable-renderer-backgrounding', '--disable-backgrounding-occluded-windows',
85
- '--disable-features=Translate,MediaRouter,OptimizationHints',
86
- ...(process.getuid?.() === 0 ? ['--no-sandbox'] : []),
87
- `http://127.0.0.1:${this.port}/gpu-worker.html?token=${this.token}`,
88
- ];
89
- this.proc = spawn(exe, args, { stdio: 'ignore', detached: process.platform !== 'win32', windowsHide: true });
90
- alive.add(this);
91
- this.exited = new Promise((r) => this.proc.once('exit', r));
92
- this.proc.once('error', (e) => this.onStartFail?.(e));
93
- this.proc.once('exit', (code) => {
94
- alive.delete(this);
95
- this.onStartFail?.(new Error(`браузер завершился (код ${code})`));
96
- this.#failJob(new Error('браузер завершился'));
97
- });
98
- let timer;
99
- const h = await Promise.race([hello, new Promise((_, rej) => { timer = setTimeout(() => rej(new Error('браузер не ответил за минуту')), timeoutMs); })]).finally(() => clearTimeout(timer));
100
- this.onStartFail = null;
101
- if (!h.webgl) throw new Error('WebGL недоступен');
102
- if (SOFTWARE.test(h.renderer || '')) throw new Error(`нет видеокарты, программная отрисовка: ${h.renderer}`);
103
- if (h.backend !== 'webgl') throw new Error(`TensorFlow.js не включил WebGL${h.error ? `: ${h.error}` : ''}`);
104
- if (!h.float32) throw new Error('видеокарта не умеет считать в 32-битных числах');
105
- this.renderer = h.renderer;
106
- this.where = `видеокарта: ${gpuName(h.renderer)}`;
107
- return this;
108
- } catch (e) {
109
- await this.close();
110
- throw e;
111
- }
112
- }
113
-
114
- // Служебный мини-сервер: страница обучения, общий код модели, tfjs из node_modules и WebSocket
115
- async #serve() {
116
- this.token = crypto.randomBytes(16).toString('hex');
117
- const app = express();
118
- const file = (url, p) => app.get(url, (req, res) => res.type(path.extname(p)).sendFile(p));
119
- file('/gpu-worker.html', path.join(HERE, 'gpu-worker.html'));
120
- file('/gpu/gpu-worker.js', path.join(HERE, 'gpu-worker.js'));
121
- file('/gpu/transformer-core.js', path.join(HERE, 'transformer-core.js'));
122
- file('/gpu/linalg.js', path.join(HERE, 'linalg.js'));
123
- app.use('/tf', express.static(path.join(ROOT, 'node_modules/@tensorflow/tfjs/dist')));
124
- this.server = http.createServer(app);
125
- this.wss = new WebSocketServer({ server: this.server, path: '/gpu-ws', maxPayload: 1 << 30 });
126
- this.wss.on('connection', (ws, req) => {
127
- if (new URL(req.url, 'http://x').searchParams.get('token') !== this.token || this.ws) { ws.close(); return; }
128
- this.ws = ws;
129
- ws.on('message', (data, isBinary) => this.#onMessage(data, isBinary));
130
- ws.on('close', () => { this.ws = null; this.#failJob(new Error('связь с браузером потеряна')); });
131
- });
132
- this.wss.on('error', () => {});
133
- await new Promise((resolve, reject) => {
134
- this.server.once('error', reject);
135
- this.server.listen(0, '127.0.0.1', () => { this.port = this.server.address().port; resolve(); });
136
- });
137
- }
138
-
139
- #onMessage(data, isBinary) {
140
- if (!isBinary) {
141
- const m = JSON.parse(String(data));
142
- if (m.type === 'hello') { this.onHello?.(m); this.onHello = null; return; }
143
- const job = this.job;
144
- if (!job || m.id !== job.id) return;
145
- if (m.type === 'progress') job.onProgress?.(m);
146
- else if (m.type === 'error') { this.job = null; job.reject(m.cancelled || this.closed ? new Error('Отменено') : new Error(m.error)); }
147
- return;
148
- }
149
- const m = unpack(data);
150
- const job = this.job;
151
- if (!job || m.id !== job.id) return;
152
- this.job = null;
153
- const preds = new Map();
154
- Array.from(m.us).forEach((u, k) => preds.set(u, m.flat.subarray(k * job.N, (k + 1) * job.N)));
155
- job.resolve({ preds, info: { ...m.info, device: this.where } });
156
- }
157
-
158
- #failJob(e) {
159
- const job = this.job;
160
- if (!job) return;
161
- this.job = null;
162
- job.reject(this.closed ? new Error('Отменено') : e);
163
- }
164
-
165
- get backlog() { return this.pending || 0; }
166
-
167
- // Окна — строго по очереди (видеокарта одна)
168
- submit(input, us, cfg, { onProgress } = {}) {
169
- this.pending = (this.pending || 0) + 1;
170
- const run = () => new Promise((resolve, reject) => {
171
- if (this.closed || !this.ws) { reject(new Error(this.closed ? 'Отменено' : 'связь с браузером потеряна')); return; }
172
- this.job = { id: crypto.randomUUID(), N: input.N, onProgress, resolve, reject };
173
- this.ws.send(pack({ id: this.job.id, input, us: Int32Array.from(us), cfg }));
174
- });
175
- const p = this.chain.then(run, run).finally(() => { this.pending--; this.touch?.(); });
176
- this.chain = p.catch(() => {});
177
- return p;
178
- }
179
-
180
- // Отмена — просто закрываем браузер
181
- cancel() { this.close(); }
182
-
183
- async close() {
184
- if (this.closed) return;
185
- this.closed = true;
186
- this.#failJob(new Error('Отменено'));
187
- this.onStartFail = null;
188
- if (this.proc && this.proc.exitCode === null) {
189
- killTree(this.proc.pid);
190
- await Promise.race([this.exited, new Promise((r) => setTimeout(r, 5000))]);
191
- }
192
- alive.delete(this);
193
- try { this.wss?.close(); } catch { /* уже закрыт */ }
194
- for (const c of this.wss?.clients || []) c.terminate();
195
- await new Promise((r) => (this.server?.listening ? this.server.close(() => r()) : r()));
196
- if (this.dir) fs.rm(this.dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 300 }, () => {});
197
- }
198
- }
199
-
200
- // Общий браузер для прогноза «сейчас» и живой проверки: закрывается после 10 минут простоя
201
- let shared = null, lastFail = null;
202
- const IDLE_MS = 10 * 60000;
203
- export async function sharedGpu() {
204
- if (shared) {
205
- const r = await shared;
206
- if (!r.closed && r.ws) return r;
207
- shared = null;
208
- }
209
- // недавно не получилось — не запускаем браузер на каждый прогноз заново
210
- if (lastFail && Date.now() - lastFail.at < IDLE_MS) throw lastFail.error;
211
- shared = new GpuRunner().start().catch((e) => { shared = null; lastFail = { at: Date.now(), error: e }; throw e; });
212
- const r = await shared;
213
- let timer = null;
214
- r.touch = () => {
215
- clearTimeout(timer);
216
- timer = setTimeout(() => { if (!r.backlog) { r.close(); if (shared) shared.then((x) => { if (x === r) shared = null; }); } }, IDLE_MS);
217
- timer.unref();
218
- };
219
- r.touch();
220
- return r;
221
- }
@@ -1,200 +0,0 @@
1
- // Обучение трансформера вне главного потока сервера.
2
- // • CpuPool — пул рабочих потоков (worker_threads): окна обучаются одновременно, каждый поток — по очереди своё окно.
3
- // • Видеокарта — через безголовый браузер (transformer-gpu.js), окна по очереди.
4
- // • fitPredictIsolated — одно окно (прогноз «сейчас», живая проверка): в отдельном потоке или на видеокарте.
5
- // Если потоки или видеокарта недоступны — тихий откат: на процессор / в главный поток.
6
- import os from 'node:os';
7
- import { Worker } from 'node:worker_threads';
8
- import { TF_CFG, fitPredictCore } from './transformer-core.js';
9
- import { loadTf } from './tf-node.js';
10
-
11
- const WORKER_URL = new URL('./transformer-worker.js', import.meta.url);
12
- // память одного потока сверх данных окна: TensorFlow.js + WASM, с запасом
13
- const BASE_MEM = 250 * 1024 * 1024;
14
-
15
- // Сколько потоков: ядер − 1 (одно — главному потоку), не больше числа окон и не больше, чем помещается
16
- // в половину свободной памяти. TRANSFORMER_THREADS — ручное переопределение.
17
- export function planThreads(jobs, bytes) {
18
- const env = Math.floor(Number(process.env.TRANSFORMER_THREADS));
19
- if (env >= 1) return { n: Math.max(1, Math.min(env, jobs)), source: 'TRANSFORMER_THREADS' };
20
- const cpu = Math.max(1, (os.availableParallelism?.() ?? os.cpus().length) - 1);
21
- const mem = Math.max(1, Math.floor((os.freemem() * 0.5) / (BASE_MEM + 3 * bytes)));
22
- return { n: Math.max(1, Math.min(cpu, jobs, mem)), cpu, mem, source: 'auto' };
23
- }
24
-
25
- const cancelError = () => new Error('Отменено');
26
-
27
- // Все уникальные буферы массивов объекта — для передачи в поток без копирования
28
- function buffersOf(x, out = new Set()) {
29
- if (ArrayBuffer.isView(x)) out.add(x.buffer);
30
- else if (Array.isArray(x)) x.forEach((v) => buffersOf(v, out));
31
- else if (x && typeof x === 'object') Object.values(x).forEach((v) => buffersOf(v, out));
32
- return out;
33
- }
34
-
35
- const toPreds = (us, flat, N) => {
36
- const preds = new Map();
37
- us.forEach((u, k) => preds.set(u, flat.subarray(k * N, (k + 1) * N)));
38
- return preds;
39
- };
40
-
41
- let seq = 0;
42
-
43
- export class CpuPool {
44
- constructor(n) {
45
- this.threads = n;
46
- this.where = 'процессор';
47
- this.slots = [];
48
- this.queue = [];
49
- this.closed = false;
50
- }
51
-
52
- // Запуск первого потока и ожидание его готовности: если потоки не запускаются — ошибка сразу
53
- async start(timeoutMs = 15000) {
54
- const slot = this.#spawn();
55
- await new Promise((resolve, reject) => {
56
- const t = setTimeout(() => reject(new Error('поток не запустился')), timeoutMs);
57
- slot.w.once('online', () => { clearTimeout(t); resolve(); });
58
- slot.w.once('error', (e) => { clearTimeout(t); reject(e); });
59
- });
60
- return this;
61
- }
62
-
63
- #spawn() {
64
- const w = new Worker(WORKER_URL);
65
- const slot = { w, job: null };
66
- w.on('message', (m) => this.#onMessage(slot, m));
67
- w.on('error', (e) => this.#onFail(slot, e));
68
- w.on('exit', (code) => this.#onFail(slot, new Error(`поток трансформера завершился (код ${code})`)));
69
- this.slots.push(slot);
70
- return slot;
71
- }
72
-
73
- // Окно в очередь. transfer — передать массивы окна в поток без копирования (у вызывающего они станут пустыми)
74
- submit(input, us, cfg, { onProgress, transfer = false } = {}) {
75
- if (this.closed) return Promise.reject(cancelError());
76
- return new Promise((resolve, reject) => {
77
- this.queue.push({ input, us: Int32Array.from(us), cfg, onProgress, transfer, resolve, reject, N: input.N });
78
- this.#pump();
79
- });
80
- }
81
-
82
- // Сколько окон ещё не готово (в очереди и в работе)
83
- get backlog() {
84
- return this.queue.length + this.slots.filter((s) => s.job).length;
85
- }
86
-
87
- #pump() {
88
- while (!this.closed && this.queue.length) {
89
- let slot = this.slots.find((s) => !s.job);
90
- if (!slot && this.slots.length < this.threads) slot = this.#spawn();
91
- if (!slot) break;
92
- const job = this.queue.shift();
93
- job.id = ++seq;
94
- slot.job = job;
95
- const msg = { type: 'fit', id: job.id, input: job.input, us: job.us, cfg: job.cfg };
96
- slot.w.postMessage(msg, job.transfer ? [...buffersOf(msg)] : []);
97
- // копия (или сами массивы) ушла в поток — у себя не держим
98
- job.input = null;
99
- job.us = null;
100
- }
101
- }
102
-
103
- #onMessage(slot, m) {
104
- const job = slot.job;
105
- if (!job || m.id !== job.id) return;
106
- if (m.type === 'progress') { job.onProgress?.(m); return; }
107
- slot.job = null;
108
- if (m.type === 'done') job.resolve({ preds: toPreds(m.us, m.flat, job.N), info: m.info });
109
- else job.reject(m.cancelled || this.closed ? cancelError() : new Error(m.error));
110
- this.#pump();
111
- }
112
-
113
- #onFail(slot, e) {
114
- const k = this.slots.indexOf(slot);
115
- if (k < 0) return;
116
- this.slots.splice(k, 1);
117
- if (slot.job) {
118
- const err = this.closed ? cancelError() : Object.assign(new Error(`Трансформер: ${e.message}`), { workerFailed: true });
119
- slot.job.reject(err);
120
- slot.job = null;
121
- }
122
- if (!this.closed) this.#pump();
123
- }
124
-
125
- // Отмена: просим потоки остановиться между батчами; через 3 с — принудительно
126
- cancel() {
127
- if (this.closed) return;
128
- for (const s of this.slots) s.w.postMessage({ type: 'cancel' });
129
- for (const j of this.queue.splice(0)) j.reject(cancelError());
130
- setTimeout(() => this.close(), 3000).unref();
131
- }
132
-
133
- async close() {
134
- if (this.closed && !this.slots.length) return;
135
- this.closed = true;
136
- for (const j of this.queue.splice(0)) j.reject(cancelError());
137
- const slots = this.slots.splice(0);
138
- for (const s of slots) if (s.job) { s.job.reject(cancelError()); s.job = null; }
139
- await Promise.all(slots.map((s) => s.w.terminate().catch(() => {})));
140
- }
141
- }
142
-
143
- const gpuModule = () => import('./transformer-gpu.js');
144
-
145
- // Исполнитель для исследования: видеокарта (если включена и доступна) → пул потоков (если включены) → null (последовательно).
146
- // note — почему пришлось откатиться (для прогресса и журнала)
147
- export async function openTransformerRunner({ gpu = false, parallel = true, jobs = 1, bytes = 0 } = {}) {
148
- let note = '';
149
- if (gpu) {
150
- try {
151
- const { GpuRunner } = await gpuModule();
152
- return { runner: await new GpuRunner().start(), note };
153
- } catch (e) {
154
- note = `видеокарта недоступна (${e.message}) — обучаем на процессоре`;
155
- console.warn(`Трансформер: ${note}`);
156
- }
157
- }
158
- if (!parallel) return { runner: null, note };
159
- const plan = planThreads(jobs, bytes);
160
- const pool = new CpuPool(plan.n);
161
- try {
162
- await pool.start();
163
- pool.plan = plan;
164
- return { runner: pool, note };
165
- } catch (e) {
166
- await pool.close();
167
- const msg = `потоки не запустились (${e.message}) — обучаем последовательно`;
168
- console.warn(`Трансформер: ${msg}`);
169
- return { runner: null, note: note ? `${note}; ${msg}` : msg };
170
- }
171
- }
172
-
173
- // Одно окно: { preds, info, where }. Видеокарта (общий браузер с таймаутом простоя) → отдельный поток → главный поток
174
- export async function fitPredictIsolated(input, us, cfg = {}, { gpu = false } = {}) {
175
- if (gpu) {
176
- try {
177
- const { sharedGpu } = await gpuModule();
178
- const g = await sharedGpu();
179
- return { ...(await g.submit(input, us, cfg)), where: g.where };
180
- } catch (e) {
181
- if (e.message === 'Отменено') throw e;
182
- console.warn(`Трансформер: видеокарта недоступна (${e.message}) — обучаем на процессоре`);
183
- }
184
- }
185
- const pool = new CpuPool(1);
186
- let started = false;
187
- try {
188
- await pool.start();
189
- started = true;
190
- return { ...(await pool.submit(input, us, cfg)), where: 'процессор' };
191
- } catch (e) {
192
- if (started && !e.workerFailed) throw e;
193
- // потоки недоступны — обучаем прямо здесь (сервер на это время занят)
194
- console.warn(`Трансформер: отдельный поток недоступен (${e.message}) — обучаем в главном потоке`);
195
- const tf = await loadTf();
196
- return { ...(await fitPredictCore(tf, input, us, { cfg: { ...TF_CFG, ...cfg } })), where: 'процессор' };
197
- } finally {
198
- await pool.close();
199
- }
200
- }
@@ -1,28 +0,0 @@
1
- // Рабочий поток: грузит TensorFlow.js один раз и обучает трансформер по окнам по очереди.
2
- // Сообщения: { type: 'fit', id, input, us, cfg } → { type: 'progress' | 'done' | 'error', id, ... }; { type: 'cancel' }.
3
- import { parentPort } from 'node:worker_threads';
4
- import { loadTf } from './tf-node.js';
5
- import { fitPredictCore } from './transformer-core.js';
6
-
7
- let cancelled = false;
8
-
9
- parentPort.on('message', async (m) => {
10
- if (m.type === 'cancel') { cancelled = true; return; }
11
- if (m.type !== 'fit') return;
12
- const { id } = m;
13
- try {
14
- const tf = await loadTf();
15
- const r = await fitPredictCore(tf, m.input, m.us, {
16
- cfg: m.cfg,
17
- isCancelled: () => cancelled,
18
- onProgress: (x) => parentPort.postMessage({ type: 'progress', id, ...x }),
19
- });
20
- // прогнозы одним плоским массивом — передаём без копирования
21
- const N = m.input.N, us = Int32Array.from(r.preds.keys());
22
- const flat = new Float64Array(us.length * N);
23
- us.forEach((u, k) => flat.set(r.preds.get(u), k * N));
24
- parentPort.postMessage({ type: 'done', id, us, flat, info: { ...r.info, tensors: tf.memory().numTensors, backend: tf.getBackend() } }, [us.buffer, flat.buffer]);
25
- } catch (e) {
26
- parentPort.postMessage({ type: 'error', id, error: e.message, cancelled });
27
- }
28
- });
@@ -1,57 +0,0 @@
1
- // Ступень 3: небольшой трансформер с вниманием МЕЖДУ АКЦИЯМИ.
2
- // Само обучение — в transformer-core.js (общий код для Node и браузера); здесь — обёртка для главного потока:
3
- // подготовка «облегчённого окна» и настройки из параметров.
4
- // Обучается в каждом окне с нуля, только на данных окна обучения (как остальные модели).
5
- import { LIQ_MIN, trainUs } from './models.js';
6
- import { TF_CFG, fitCore, predictCore } from './transformer-core.js';
7
- import { loadTf } from './tf-node.js';
8
-
9
- export { TF_CFG, loadTf };
10
-
11
- // Настройки обучения из параметров стратегии (эпохи и шаг свечей); остальное — TF_CFG
12
- export const transformerCfg = (p) => ({
13
- epochs: Math.round(p.transformerEpochs ?? TF_CFG.epochs),
14
- stride: Math.round(p.transformerStride ?? TF_CFG.stride),
15
- });
16
-
17
- // Из полного окна — только то, что нужно трансформеру (без карты рынка, сырых доходностей и т.п.)
18
- export function transformerInput(f) {
19
- return {
20
- N: f.N, W: f.W, H: f.H, liqMin: LIQ_MIN,
21
- X: f.X, liq: f.liq, has: f.has, yz: f.yz,
22
- macro: f.macro ? { list: f.macro.list.map((m) => ({ b: m.b, zs: m.zs })) } : null,
23
- tr: Int32Array.from(trainUs(f)),
24
- };
25
- }
26
-
27
- // Объём массивов окна в байтах (для оценки памяти потоков)
28
- export function inputBytes(x) {
29
- let s = 0;
30
- const walk = (v) => {
31
- if (ArrayBuffer.isView(v)) s += v.byteLength;
32
- else if (Array.isArray(v)) v.forEach(walk);
33
- else if (v && typeof v === 'object') Object.values(v).forEach(walk);
34
- };
35
- walk(x);
36
- return s;
37
- }
38
-
39
- // Обучение в окне в главном потоке. onProgress({ epoch, epochs }) — в начале каждой эпохи; isCancelled() → прерывание между батчами
40
- export async function fitTransformer(f, { onProgress = () => {}, isCancelled = () => false, cfg = {} } = {}) {
41
- const tf = await loadTf();
42
- const x = transformerInput(f);
43
- const model = await fitCore(tf, x, { onProgress, isCancelled, cfg });
44
- model.tf = tf;
45
- model.input = x;
46
- model.predictOne = predictTransformer;
47
- model.predictMany = predictManyTransformer;
48
- return model;
49
- }
50
-
51
- // Прогноз сразу по многим моментам: Map(u → Float64Array(N)) в единицах yz
52
- export function predictManyTransformer(model, f, us) {
53
- return predictCore(model.tf, model, model.input, us);
54
- }
55
- export function predictTransformer(model, f, u) {
56
- return predictCore(model.tf, model, model.input, [u]).get(u);
57
- }