neuroalpha 0.6.12 → 0.6.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.
- package/package.json +1 -1
- package/server/brokers/index.js +0 -2
- package/server/index.js +33 -8
- package/server/model/dataset.js +2 -4
- package/server/model/forecast.js +1 -3
- package/server/news/index.js +2 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neuroalpha",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.13",
|
|
4
4
|
"description": "НейроАльфа — ИИ-автопилот для рынка акций Мосбиржи: эмбеддинги акций, самообучение правил, коннекторы Т-Банк / БКС / Финам / Алор и боты Telegram / MAX",
|
|
5
5
|
"author": "Roman A. Perepelkin",
|
|
6
6
|
"license": "UNLICENSED",
|
package/server/brokers/index.js
CHANGED
|
@@ -191,7 +191,6 @@ function logBrokerEvent(acc, kind, text) {
|
|
|
191
191
|
const uid = currentUserId();
|
|
192
192
|
db.prepare('INSERT INTO broker_events (ts, user_id, account_id, kind, text) VALUES (?, ?, ?, ?, ?)')
|
|
193
193
|
.run(Date.now(), uid, String(acc?.id || ''), String(kind), String(text || '').slice(0, 500));
|
|
194
|
-
if (/error|stop|halt/.test(kind)) console.warn(`[брокер ${acc?.name || acc?.id}] ${kind}: ${text}`);
|
|
195
194
|
// храним последние 5000 событий на счёт
|
|
196
195
|
if (Math.random() < 0.02) {
|
|
197
196
|
db.prepare(`DELETE FROM broker_events WHERE user_id = ? AND account_id = ? AND id NOT IN
|
|
@@ -275,7 +274,6 @@ function migrateKvAccounts(uid) {
|
|
|
275
274
|
db.exec('COMMIT');
|
|
276
275
|
} catch (e) { db.exec('ROLLBACK'); throw e; }
|
|
277
276
|
// прежняя запись не удаляется: при откате на версию до 0.4.1 счета останутся на месте (в состоянии на момент переноса)
|
|
278
|
-
console.log(`✓ Брокерские счета пользователя ${uid} перенесены в отдельные записи (${old.length})`);
|
|
279
277
|
}
|
|
280
278
|
userKvSet('brokerAccountsMigrated', true, uid);
|
|
281
279
|
}
|
package/server/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Copyright (c) 2026 Roman A. Perepelkin. All rights reserved. Proprietary software.
|
|
2
2
|
import http from 'node:http';
|
|
3
|
+
import fs from 'node:fs';
|
|
3
4
|
import os from 'node:os';
|
|
4
5
|
import { spawn } from 'node:child_process';
|
|
5
6
|
import path from 'node:path';
|
|
@@ -141,11 +142,26 @@ app.use('/docs', express.static(path.join(ROOT, 'docs')));
|
|
|
141
142
|
app.use(express.static(path.join(ROOT, 'public')));
|
|
142
143
|
|
|
143
144
|
function lanUrls() {
|
|
144
|
-
return Object.
|
|
145
|
+
return Object.entries(os.networkInterfaces())
|
|
146
|
+
.filter(([name]) => !/vethernet|wsl|docker|hyper-v|virtualbox|vmware/i.test(name))
|
|
147
|
+
.flatMap(([, list]) => list || [])
|
|
145
148
|
.filter((a) => a && a.family === 'IPv4' && !a.internal)
|
|
146
149
|
.map((a) => `http://${a.address}:${PORT}`);
|
|
147
150
|
}
|
|
148
151
|
|
|
152
|
+
function fmtClickableUrl(url, out = process.stdout) {
|
|
153
|
+
if (!out?.isTTY) return url;
|
|
154
|
+
const osc8 = Boolean(
|
|
155
|
+
process.env.WT_SESSION
|
|
156
|
+
|| process.env.TERM_PROGRAM
|
|
157
|
+
|| process.env.COLORTERM
|
|
158
|
+
|| process.env.FORCE_HYPERLINK === '1'
|
|
159
|
+
|| (process.platform !== 'win32' && process.env.TERM && process.env.TERM !== 'dumb'),
|
|
160
|
+
);
|
|
161
|
+
const styled = `\x1b[4;96m${url}\x1b[0m`;
|
|
162
|
+
return osc8 ? `\x1b]8;;${url}\x1b\\${styled}\x1b]8;;\x1b\\` : styled;
|
|
163
|
+
}
|
|
164
|
+
|
|
149
165
|
const shouldOpenBrowser = process.env.OPEN_BROWSER === '1'
|
|
150
166
|
|| (process.env.OPEN_BROWSER !== '0'
|
|
151
167
|
&& process.env.NO_BROWSER !== '1'
|
|
@@ -157,9 +173,10 @@ const shouldOpenBrowser = process.env.OPEN_BROWSER === '1'
|
|
|
157
173
|
|
|
158
174
|
wss.on('error', () => {});
|
|
159
175
|
server.on('error', (e) => {
|
|
176
|
+
const clientUrl = `http://localhost:${PORT}`;
|
|
160
177
|
if (e.code === 'EADDRINUSE') {
|
|
161
|
-
console.error(`Порт ${PORT} занят: сервер уже запущен? Клиент:
|
|
162
|
-
if (shouldOpenBrowser) openBrowser(
|
|
178
|
+
console.error(`Порт ${PORT} занят: сервер уже запущен? Клиент: ${fmtClickableUrl(clientUrl, process.stderr)}`);
|
|
179
|
+
if (shouldOpenBrowser) openBrowser(clientUrl);
|
|
163
180
|
} else {
|
|
164
181
|
console.error(e);
|
|
165
182
|
}
|
|
@@ -168,12 +185,20 @@ server.on('error', (e) => {
|
|
|
168
185
|
|
|
169
186
|
server.listen(PORT, LISTEN_HOST, () => {
|
|
170
187
|
const ident = IS_INSTANCE ? getIdentity() : null;
|
|
171
|
-
|
|
188
|
+
let ver = '';
|
|
189
|
+
try { ver = ` v${JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8')).version}`; } catch { /* ignore */ }
|
|
190
|
+
console.log(`◈ НейроАльфа${ver} запущена${IS_INSTANCE ? ` (${INSTANCE_KIND}) · машина ${ident.id || 'не активирована'} · центр ${ident.centralUrl}` : (IS_CENTRAL ? ' · центр' : '')}`);
|
|
172
191
|
const llm = activeModelInfo();
|
|
173
|
-
console.log(
|
|
174
|
-
if (!LISTEN_HOST || LISTEN_HOST === '0.0.0.0') for (const url of lanUrls()) console.log(` В
|
|
175
|
-
|
|
176
|
-
|
|
192
|
+
console.log(` ИИ-стратег: ${llm.enabled ? `${llm.provider} · ${llm.model}` : 'выключен (Настройки → Модели ИИ)'}`);
|
|
193
|
+
if (!LISTEN_HOST || LISTEN_HOST === '0.0.0.0') for (const url of lanUrls()) console.log(` В сети: ${fmtClickableUrl(url)}`);
|
|
194
|
+
const clientUrl = `http://localhost:${PORT}`;
|
|
195
|
+
const enterHint = process.stdin?.isTTY && !IS_CENTRAL ? ' (Enter — открыть в браузере)' : '';
|
|
196
|
+
console.log(` Клиент: ${fmtClickableUrl(clientUrl)}${enterHint}`);
|
|
197
|
+
if (shouldOpenBrowser) openBrowser(clientUrl);
|
|
198
|
+
if (process.stdin?.isTTY && !IS_CENTRAL) {
|
|
199
|
+
process.stdin.on('data', () => openBrowser(clientUrl));
|
|
200
|
+
process.stdin.unref?.();
|
|
201
|
+
}
|
|
177
202
|
});
|
|
178
203
|
|
|
179
204
|
function openBrowser(url) {
|
package/server/model/dataset.js
CHANGED
|
@@ -295,10 +295,9 @@ export async function loadDataset({ interval = BAR_INTERVAL, fromTs, tillTs, use
|
|
|
295
295
|
killer.unref?.();
|
|
296
296
|
}),
|
|
297
297
|
]);
|
|
298
|
-
} catch
|
|
298
|
+
} catch {
|
|
299
299
|
if (signal?.aborted) throw new Error('Отменено');
|
|
300
300
|
raw[s] = EMPTY();
|
|
301
|
-
console.warn(`[dataset] ${s}: ${e.message} — акция пропущена`);
|
|
302
301
|
} finally {
|
|
303
302
|
clearTimeout(killer);
|
|
304
303
|
onProgress({ symbol: s, done: ++done, total: all.length });
|
|
@@ -309,10 +308,9 @@ export async function loadDataset({ interval = BAR_INTERVAL, fromTs, tillTs, use
|
|
|
309
308
|
try {
|
|
310
309
|
onProgress({ symbol: 'BR', done: done, total: all.length });
|
|
311
310
|
raw.BR = await loadBrent(fromTs, tillTs, signal, { localOnly });
|
|
312
|
-
} catch
|
|
311
|
+
} catch {
|
|
313
312
|
if (signal?.aborted) throw new Error('Отменено');
|
|
314
313
|
raw.BR = EMPTY();
|
|
315
|
-
console.warn(`[dataset] BR: ${e.message} — нефть пропущена`);
|
|
316
314
|
}
|
|
317
315
|
}
|
|
318
316
|
if (!raw.IMOEX?.n) throw new Error('Нет данных индекса МосБиржи');
|
package/server/model/forecast.js
CHANGED
|
@@ -966,9 +966,7 @@ export async function forecastNow(input, { force = false, onProgress } = {}) {
|
|
|
966
966
|
if (saved?.result && saved.key === key && now - (saved.result.at || 0) < 4 * 3600000) {
|
|
967
967
|
if (!inflight.has(key)) {
|
|
968
968
|
setImmediate(() => {
|
|
969
|
-
refreshDatasetStateInBackground(p, key, { force: false }).catch((
|
|
970
|
-
console.warn('[forecast] background warm-up error:', err?.message || err);
|
|
971
|
-
});
|
|
969
|
+
refreshDatasetStateInBackground(p, key, { force: false }).catch(() => {});
|
|
972
970
|
});
|
|
973
971
|
}
|
|
974
972
|
const liveSnap = await fetchLiveMoexSnapshot().catch(() => null);
|
package/server/news/index.js
CHANGED
|
@@ -146,7 +146,7 @@ async function collectOnce() {
|
|
|
146
146
|
const perSource = {};
|
|
147
147
|
let fresh = 0;
|
|
148
148
|
results.forEach((r, i) => {
|
|
149
|
-
if (r.status !== 'fulfilled')
|
|
149
|
+
if (r.status !== 'fulfilled') return;
|
|
150
150
|
let n = 0;
|
|
151
151
|
for (const it of r.value) {
|
|
152
152
|
if (it.ts < Date.now() - 3 * DAY || it.ts > Date.now() + 3600000) continue; // старьё и будущее не берём
|
|
@@ -157,7 +157,6 @@ async function collectOnce() {
|
|
|
157
157
|
});
|
|
158
158
|
fresh = Object.values(perSource).reduce((a, b) => a + b, 0);
|
|
159
159
|
if (fresh) {
|
|
160
|
-
console.log(`Новости: +${fresh} (${Object.entries(perSource).map(([k, v]) => `${k} ${v}`).join(', ')})`);
|
|
161
160
|
hub.emit({ type: 'news:update', fresh });
|
|
162
161
|
}
|
|
163
162
|
await markPending(vocab, m);
|
|
@@ -240,7 +239,7 @@ function schedule(ms) {
|
|
|
240
239
|
timer = setTimeout(async () => {
|
|
241
240
|
if (!busy) {
|
|
242
241
|
busy = true;
|
|
243
|
-
try { await collectOnce(); } catch
|
|
242
|
+
try { await collectOnce(); } catch { /* ошибки источников не выводятся в консоль */ } finally { busy = false; }
|
|
244
243
|
}
|
|
245
244
|
schedule(isTradingTime() ? 10 * 60000 : 30 * 60000);
|
|
246
245
|
}, ms);
|