neuroalpha 0.5.33 → 0.5.34
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 +19 -7
- package/server/news/index.js +25 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neuroalpha",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.34",
|
|
4
4
|
"description": "НейроАльфа — ИИ-автопилот для рынка акций Мосбиржи: эмбеддинги акций, самообучение правил, коннекторы Т-Банк / БКС / Финам / Алор и боты Telegram / MAX",
|
|
5
5
|
"author": "Roman A. Perepelkin",
|
|
6
6
|
"license": "UNLICENSED",
|
package/server/brokers/index.js
CHANGED
|
@@ -524,6 +524,7 @@ export function saveBrokerAccount(input = {}) {
|
|
|
524
524
|
entryTsBySymbol: acctChanged ? {} : (prev.entryTsBySymbol || {}),
|
|
525
525
|
exitCooldownBySymbol: acctChanged ? {} : (prev.exitCooldownBySymbol || {}),
|
|
526
526
|
entryFailCooldownBySymbol: acctChanged ? {} : (prev.entryFailCooldownBySymbol || {}),
|
|
527
|
+
entryFailReasonBySymbol: acctChanged ? {} : (prev.entryFailReasonBySymbol || {}),
|
|
527
528
|
peakPriceBySymbol: acctChanged ? {} : (prev.peakPriceBySymbol || {}),
|
|
528
529
|
baseRunId: nextBaseRunId,
|
|
529
530
|
// жёсткие предохранители (недоступны для изменения ИИ-Стратегом)
|
|
@@ -1837,6 +1838,7 @@ export async function prepareBrokerOrders(id, { onlyEntries = false, scheduledBa
|
|
|
1837
1838
|
const entryTsMap = acc.entryTsBySymbol || {};
|
|
1838
1839
|
const exitCooldownMap = acc.exitCooldownBySymbol || {};
|
|
1839
1840
|
const entryFailMap = acc.entryFailCooldownBySymbol || {};
|
|
1841
|
+
const entryFailReasonMap = acc.entryFailReasonBySymbol || {};
|
|
1840
1842
|
// Спред-фильтр реального стакана по эшелонам ликвидности: для 2–3 эшелона порог выше в TIER2_EDGE_MULT раз.
|
|
1841
1843
|
// Отдельного вечернего ужесточения нет: проверка 23.10.2026 (первые 70% и последние 30% истории) показала,
|
|
1842
1844
|
// что порог ×1,25 после 19:00 МСК снижает итог на обеих частях — правила совпадают с бэктестом.
|
|
@@ -1846,10 +1848,9 @@ export async function prepareBrokerOrders(id, { onlyEntries = false, scheduledBa
|
|
|
1846
1848
|
const minShortEdgePct = round(baseShortNeedPct, 3);
|
|
1847
1849
|
const reqBuyEdgeOf = (sym) => rowMap.get(sym)?.buyThresholdPct ?? round(Math.max(minBuyEdgePct * symbolEdgeMult(sym), 0.30), 3);
|
|
1848
1850
|
const reqShortEdgeOf = (sym) => rowMap.get(sym)?.shortThresholdPct ?? round(Math.max(minShortEdgePct * symbolEdgeMult(sym), 0.31), 3);
|
|
1849
|
-
const
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
);
|
|
1851
|
+
const isExitCooldown = (sym) => Boolean(autoAdapt && exitCooldownMap[sym] && (nowTs - exitCooldownMap[sym] < 60 * 60000));
|
|
1852
|
+
const isEntryFailCooldown = (sym) => Boolean(entryFailMap[sym] && (nowTs - entryFailMap[sym] < 5 * 60000));
|
|
1853
|
+
const isInCooldown = (sym) => isExitCooldown(sym) || isEntryFailCooldown(sym);
|
|
1853
1854
|
// Самообучение на истории реальных сделок счёта (адаптивный карантин убыточных бумаг, единый с research.js):
|
|
1854
1855
|
// если за последние 15 торговых дней по бумаге накоплен убыток (< -1.2% за >= 2 сделки или одиночный пролив < -2.5%),
|
|
1855
1856
|
// временно исключаем такую бумагу из новых входов!
|
|
@@ -1909,7 +1910,9 @@ export async function prepareBrokerOrders(id, { onlyEntries = false, scheduledBa
|
|
|
1909
1910
|
const rowMap = new Map((fc.rows || []).map((r) => [r.symbol, r]));
|
|
1910
1911
|
const nb = params.newsFilter ? blockingNews(6) : null;
|
|
1911
1912
|
|
|
1912
|
-
const
|
|
1913
|
+
const isRawSignalRow = (r) => (r.signal && r.expectedPct >= reqBuyEdgeOf(r.symbol)) || (rawDir !== 'long' && r.shortSignal && r.expectedPct <= -reqShortEdgeOf(r.symbol));
|
|
1914
|
+
const cooldownSkipped = fc.rows.filter((r) => isRawSignalRow(r) && isExitCooldown(r.symbol));
|
|
1915
|
+
const entryFailSkipped = fc.rows.filter((r) => isRawSignalRow(r) && !isExitCooldown(r.symbol) && isEntryFailCooldown(r.symbol));
|
|
1913
1916
|
// Дивидендный предохранитель (общие правила с бэктестом, server/model/dividends.js)
|
|
1914
1917
|
const divGuardOf = (sym, px) => liveDividendGuard(sym, Number(px) || specs[sym]?.lastPrice || 0, nowTs);
|
|
1915
1918
|
const divBlocked = [];
|
|
@@ -2334,14 +2337,17 @@ export async function prepareBrokerOrders(id, { onlyEntries = false, scheduledBa
|
|
|
2334
2337
|
diagNote = `Сигнал ${deferredByImpulse.map((d) => `${d.symbol} (${d.side === 'short' ? 'шорт' : 'покупка'}, текущая ${d.curPx} ₽)`).join(', ')} взят на прицел: ждём затухания встречного минутного импульса для входа по лучшей цене (проверка каждые 20 сек).`;
|
|
2335
2338
|
} else if (!newBuys.length && !newShorts.length) {
|
|
2336
2339
|
const noShortNote = nonShortableSkipped.length ? ` (пропущены немаржинальные бумаги, недоступные для шорта у брокера: ${nonShortableSkipped.map((r) => `${r.symbol} ${r.expectedPct}%`).join(', ')})` : '';
|
|
2340
|
+
const failNote = entryFailSkipped.length
|
|
2341
|
+
? ` Заявка по ${entryFailSkipped.map((r) => `${r.symbol} (${entryFailReasonMap[r.symbol] || 'отказ шлюза брокера'})`).join('; ')} временно отложена на 5 мин после ошибки приёма у брокера.`
|
|
2342
|
+
: '';
|
|
2337
2343
|
const cdNote = cooldownSkipped.length ? ` Сигнал ${cooldownSkipped.map((r) => `${r.symbol} (${r.expectedPct > 0 ? '+' : ''}${r.expectedPct}%)`).join(', ')} временно пропущен: действует пауза (30–60 мин) после недавнего выхода из позиции, чтобы не перезаходить в отыгранный импульс и не платить повторную комиссию.` : '';
|
|
2338
2344
|
const buyThrT2 = round(minBuyEdgePct * symbolEdgeMult('__T2__'), 2);
|
|
2339
2345
|
const shortThrT2 = round(minShortEdgePct * symbolEdgeMult('__T2__'), 2);
|
|
2340
2346
|
const thrRangeStr = dir !== 'long'
|
|
2341
2347
|
? `+${round(minBuyEdgePct, 2)}% / +${buyThrT2}% в покупку, −${round(minShortEdgePct, 2)}% / −${shortThrT2}% в шорт`
|
|
2342
2348
|
: `+${round(minBuyEdgePct, 2)}% (1-й эшелон) / +${buyThrT2}% (2–3-й эшелон)`;
|
|
2343
|
-
diagNote = cdNote
|
|
2344
|
-
? `На свече ${fc.bar} новых входов нет.${cdNote}${noShortNote}`
|
|
2349
|
+
diagNote = (failNote || cdNote)
|
|
2350
|
+
? `На свече ${fc.bar} новых входов нет.${failNote}${cdNote}${noShortNote}`
|
|
2345
2351
|
: `На свече ${fc.bar} ни одна доступная бумага не превысила индивидуальный порог выгоды (${thrRangeStr})${noShortNote}.`;
|
|
2346
2352
|
}
|
|
2347
2353
|
}
|
|
@@ -2422,6 +2428,9 @@ function formatTbankOrderError(status, rawText = '', ord = {}) {
|
|
|
2422
2428
|
if (code.includes('30052') || /short.*disabled|short.*not/i.test(code)) {
|
|
2423
2429
|
return 'По данной бумаге брокер временно запретил короткие продажи (шорт)';
|
|
2424
2430
|
}
|
|
2431
|
+
if (code.includes('30099') || /outside the limits/i.test(code)) {
|
|
2432
|
+
return 'Цена бумаги вышла за границы дневного ценового коридора у брокера (код 30099: Мосбиржа расширила планку, шлюз брокера ещё обновляет лимиты)';
|
|
2433
|
+
}
|
|
2425
2434
|
return `T-Invest PostOrder HTTP ${status}${msg ? `: ${msg}` : ''}${desc ? ` (код ${desc})` : ''}`;
|
|
2426
2435
|
}
|
|
2427
2436
|
|
|
@@ -2773,6 +2782,7 @@ export async function executeBrokerOrders(id, { _lockHeld = false } = {}) {
|
|
|
2773
2782
|
acc.entryTsBySymbol = acc.entryTsBySymbol || {};
|
|
2774
2783
|
acc.exitCooldownBySymbol = acc.exitCooldownBySymbol || {};
|
|
2775
2784
|
acc.entryFailCooldownBySymbol = acc.entryFailCooldownBySymbol || {};
|
|
2785
|
+
acc.entryFailReasonBySymbol = acc.entryFailReasonBySymbol || {};
|
|
2776
2786
|
acc.peakPriceBySymbol = acc.peakPriceBySymbol || {};
|
|
2777
2787
|
const execNowTs = Date.now();
|
|
2778
2788
|
const exitOrders = batch.orders.filter((o) => o.action === 'sell' || o.action === 'cover');
|
|
@@ -2815,6 +2825,7 @@ export async function executeBrokerOrders(id, { _lockHeld = false } = {}) {
|
|
|
2815
2825
|
acc.peakPriceBySymbol[ord.symbol] = Number(ord.limitPrice || ord.refPrice) || 0;
|
|
2816
2826
|
delete acc.exitCooldownBySymbol[ord.symbol];
|
|
2817
2827
|
delete acc.entryFailCooldownBySymbol[ord.symbol];
|
|
2828
|
+
delete acc.entryFailReasonBySymbol[ord.symbol];
|
|
2818
2829
|
} else if (ord.action === 'sell' || ord.action === 'cover') {
|
|
2819
2830
|
hadExits = true;
|
|
2820
2831
|
delete acc.entryTsBySymbol[ord.symbol];
|
|
@@ -2831,6 +2842,7 @@ export async function executeBrokerOrders(id, { _lockHeld = false } = {}) {
|
|
|
2831
2842
|
else ord.uncertainSend = true;
|
|
2832
2843
|
if (ord.action === 'buy' || ord.action === 'short') {
|
|
2833
2844
|
acc.entryFailCooldownBySymbol[ord.symbol] = execNowTs;
|
|
2845
|
+
acc.entryFailReasonBySymbol[ord.symbol] = e.message;
|
|
2834
2846
|
}
|
|
2835
2847
|
results.push({ symbol: ord.symbol, ok: false, error: e.message, uncertain: !e.definitive });
|
|
2836
2848
|
logBrokerEvent(acc, 'order_error', `${ord.action} ${ord.symbol} ${ord.lots} л.: ${e.message}`);
|
package/server/news/index.js
CHANGED
|
@@ -281,8 +281,9 @@ export function newsForSymbols(hours = 24) {
|
|
|
281
281
|
// Для живой проверки с newsFilter: самые важные свежие новости (важность ≥ 0.6, |тон| ≥ 0.4)
|
|
282
282
|
// по тикерам: symbol → { bad: { gist, imp, tone, ts }, good: {…} }
|
|
283
283
|
export function blockingNews(hours = 6, atTs = Date.now()) {
|
|
284
|
+
const sinceTs = atTs - hours * 3600000;
|
|
284
285
|
const rows = db.prepare(`SELECT ts, title, gist, imp, tags FROM news
|
|
285
|
-
WHERE marked = 1 AND imp >= 0.6 AND tags IS NOT NULL AND tags != '[]' AND ts >= ? AND ts <= ?`).all(
|
|
286
|
+
WHERE marked = 1 AND imp >= 0.6 AND tags IS NOT NULL AND tags != '[]' AND ts >= ? AND ts <= ?`).all(sinceTs, atTs);
|
|
286
287
|
const m = new Map();
|
|
287
288
|
for (const r of rows) {
|
|
288
289
|
for (const [sym, tone] of parseTags(r)) {
|
|
@@ -293,6 +294,29 @@ export function blockingNews(hours = 6, atTs = Date.now()) {
|
|
|
293
294
|
m.set(sym, e);
|
|
294
295
|
}
|
|
295
296
|
}
|
|
297
|
+
// Официальные сообщения Мосбиржи о расширении верхней/нижней границы ценового коридора акции (планки) —
|
|
298
|
+
// блокируют вход против пробоя планки даже до разметки ИИ (и защищают от отказа шлюза брокера с кодом 30099)
|
|
299
|
+
const corridorRows = db.prepare(`SELECT ts, title, tags FROM news
|
|
300
|
+
WHERE ts >= ? AND ts <= ? AND title LIKE '%границы ценового коридора%ценной бумаги%' AND title NOT LIKE '%коридора РЕПО%'`).all(sinceTs, atTs);
|
|
301
|
+
for (const r of corridorRows) {
|
|
302
|
+
const t = String(r.title || '');
|
|
303
|
+
if (/коридора\s+репо/i.test(t)) continue;
|
|
304
|
+
const isUpper = /верхней границы ценового коридора/i.test(t);
|
|
305
|
+
const isLower = /нижней границы ценового коридора/i.test(t);
|
|
306
|
+
if (!isUpper && !isLower) continue;
|
|
307
|
+
const syms = new Set(parseTags(r).map(([s]) => s).filter(Boolean));
|
|
308
|
+
const matchSym = /ценной бумаги\s+([A-Z0-9]{2,8})\b/i.exec(t);
|
|
309
|
+
if (matchSym?.[1]) syms.add(matchSym[1].toUpperCase());
|
|
310
|
+
for (const sym of syms) {
|
|
311
|
+
const e = m.get(sym) || {};
|
|
312
|
+
const side = isUpper ? 'good' : 'bad';
|
|
313
|
+
const tone = isUpper ? 0.9 : -0.9;
|
|
314
|
+
const imp = 0.95;
|
|
315
|
+
const gist = isUpper ? `Мосбиржа расширила верхнюю планку коридора ${sym}` : `Мосбиржа расширила нижнюю планку коридора ${sym}`;
|
|
316
|
+
if (!e[side] || imp >= e[side].imp) e[side] = { tone, imp, gist, ts: r.ts };
|
|
317
|
+
m.set(sym, e);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
296
320
|
return m;
|
|
297
321
|
}
|
|
298
322
|
|