neuroalpha 0.5.20 → 0.5.21
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/public/app.js +29 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neuroalpha",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.21",
|
|
4
4
|
"description": "НейроАльфа — ИИ-автопилот для рынка акций Мосбиржи: эмбеддинги акций, самообучение правил, коннекторы Т-Банк / БКС / Финам / Алор и боты Telegram / MAX",
|
|
5
5
|
"author": "Roman A. Perepelkin",
|
|
6
6
|
"license": "UNLICENSED",
|
package/public/app.js
CHANGED
|
@@ -2442,6 +2442,11 @@ function toggleBrokerSpecs(id) {
|
|
|
2442
2442
|
else hideBrokerSpecsById.add(id);
|
|
2443
2443
|
saveNavPatch({ hideBrokerSpecs: [...hideBrokerSpecsById] });
|
|
2444
2444
|
}
|
|
2445
|
+
let hideNonTradableAssets = loadSavedNav().hideNonTradableAssets !== false;
|
|
2446
|
+
function setHideNonTradableAssets(val) {
|
|
2447
|
+
hideNonTradableAssets = Boolean(val);
|
|
2448
|
+
saveNavPatch({ hideNonTradableAssets });
|
|
2449
|
+
}
|
|
2445
2450
|
let editingBrokerId = null;
|
|
2446
2451
|
let brokerPollBusy = false;
|
|
2447
2452
|
let lastBrokerPollTs = 0;
|
|
@@ -2527,7 +2532,12 @@ function renderBrokers() {
|
|
|
2527
2532
|
return p.qty;
|
|
2528
2533
|
};
|
|
2529
2534
|
|
|
2535
|
+
const isNonTradablePos = (p) => !(Number(p.price) > 0 && Math.abs(Number(p.valueRub)) > 0) || Boolean(p.blocked);
|
|
2536
|
+
|
|
2530
2537
|
const posStatus = (p) => {
|
|
2538
|
+
if (isNonTradablePos(p)) {
|
|
2539
|
+
return '<span class="tag" title="Бумага снята с торгов, проходит редомициляцию или заблокирована (рыночная оценка 0 ₽). Стратегия её не затрагивает">не торгуется (0 ₽)</span>';
|
|
2540
|
+
}
|
|
2531
2541
|
if (p.instrumentType && p.instrumentType !== 'share') {
|
|
2532
2542
|
const [kl] = INST_KIND_LABEL[p.instrumentType] || ['Актив', ''];
|
|
2533
2543
|
return `<span class="tag" title="Драгметаллы, валюта, облигации и БПИФы не затрагиваются стратегией акций">${esc(kl.toLowerCase())} (защищён)</span>`;
|
|
@@ -2577,6 +2587,9 @@ function renderBrokers() {
|
|
|
2577
2587
|
const totalPnlRub = ls?.totalPnlRub ?? (ls?.positions || []).reduce((s, p) => s + (p.pnlRub || 0), 0);
|
|
2578
2588
|
|
|
2579
2589
|
// Вкладка 1: Активы портфеля
|
|
2590
|
+
const allPositions = ls?.positions || [];
|
|
2591
|
+
const nonTradableCount = allPositions.filter(isNonTradablePos).length;
|
|
2592
|
+
const visiblePositions = hideNonTradableAssets ? allPositions.filter((p) => !isNonTradablePos(p)) : allPositions;
|
|
2580
2593
|
const fmtOwnership = (p) => {
|
|
2581
2594
|
if (!(p.issueSize > 0) || !(p.qty > 0)) return '<span class="muted small">—</span>';
|
|
2582
2595
|
const pct = (p.qty / p.issueSize) * 100;
|
|
@@ -2585,8 +2598,8 @@ function renderBrokers() {
|
|
|
2585
2598
|
const tip = `Доля владения в компании ${p.name || p.symbol}: ${fmtNum(p.qty, 0)} из ${fmtNum(p.issueSize, 0)} акций всего выпуска на Мосбирже`;
|
|
2586
2599
|
return `<span title="${esc(tip)}" style="white-space:nowrap"><b>${pctStr}%</b></span>`;
|
|
2587
2600
|
};
|
|
2588
|
-
const posHtml =
|
|
2589
|
-
? `${allocBarHtml}<table><thead><tr><th>Актив</th><th>Тип</th><th class="num">Кол-во</th><th class="num" title="Доля ваших акций в общем выпуске ценных бумаг компании-эмитента на Мосбирже (ISSUESIZE)">Доля владения</th><th class="num">Средняя → Текущая</th><th class="num">Стоимость (доля)</th><th class="num">Финрезультат</th><th>Статус и прогноз</th></tr></thead><tbody>${
|
|
2601
|
+
const posHtml = visiblePositions.length
|
|
2602
|
+
? `${allocBarHtml}<table><thead><tr><th>Актив</th><th>Тип</th><th class="num">Кол-во</th><th class="num" title="Доля ваших акций в общем выпуске ценных бумаг компании-эмитента на Мосбирже (ISSUESIZE)">Доля владения</th><th class="num">Средняя → Текущая</th><th class="num">Стоимость (доля)</th><th class="num">Финрезультат</th><th>Статус и прогноз</th></tr></thead><tbody>${visiblePositions.map((p) => {
|
|
2590
2603
|
const [kLbl, kCls] = INST_KIND_LABEL[p.instrumentType || 'share'] || ['Актив', ''];
|
|
2591
2604
|
const fcRow = fcRowMap?.get(p.symbol);
|
|
2592
2605
|
const fcBadge = fcRow ? ` · <span class="${cls(fcRow.expectedPct)}" title="Прогноз модели на ближайшие 2,5 ч">прогноз ${fcRow.expectedPct > 0 ? '+' : ''}${fmtNum(fcRow.expectedPct, 2)}%</span>` : '';
|
|
@@ -2601,7 +2614,7 @@ function renderBrokers() {
|
|
|
2601
2614
|
<td>${posStatus(p)}${fcBadge}</td>
|
|
2602
2615
|
</tr>`;
|
|
2603
2616
|
}).join('')}</tbody></table>`
|
|
2604
|
-
: `<p class="muted small">${ls?.ok ? 'Открытых позиций на счёте сейчас нет (все средства в рублях).' : 'Нажмите «Обновить портфель», чтобы загрузить баланс и активы с брокера.'}</p>`;
|
|
2617
|
+
: `<p class="muted small">${ls?.ok ? (nonTradableCount ? `Торгуемых позиций на счёте сейчас нет (скрыто неторгуемых активов с нулевой оценкой: ${nonTradableCount}).` : 'Открытых позиций на счёте сейчас нет (все средства в рублях).') : 'Нажмите «Обновить портфель», чтобы загрузить баланс и активы с брокера.'}</p>`;
|
|
2605
2618
|
|
|
2606
2619
|
// Вкладка 2: Торговля и заявки стратегии
|
|
2607
2620
|
const activeOrdHtml = ls?.activeOrders?.length
|
|
@@ -2877,16 +2890,20 @@ function renderBrokers() {
|
|
|
2877
2890
|
${a.executionAudit ? `<span class="k">Реальные издержки (сверка):</span><span class="v" title="Сверка фактических сделок счёта (после отключения автоследования) с ориентиром стресс-теста">комиссия <b>${fmtNum(a.executionAudit.commPct, 3)}%</b>${a.executionAudit.slipAllPct !== null ? ` · проскальзывание к сигналу <b>${a.executionAudit.slipAllPct > 0 ? '+' : ''}${fmtNum(a.executionAudit.slipAllPct, 3)}%</b>${a.executionAudit.slipTier1Pct !== null ? ` (1-й эш. ${a.executionAudit.slipTier1Pct > 0 ? '+' : ''}${fmtNum(a.executionAudit.slipTier1Pct, 3)}%${a.executionAudit.slipTier2Pct !== null ? `, 2–3-й эш. ${a.executionAudit.slipTier2Pct > 0 ? '+' : ''}${fmtNum(a.executionAudit.slipTier2Pct, 3)}%` : ''})` : ''}` : ''} · итого <b class="${a.executionAudit.totalCostPerSidePct <= a.executionAudit.breakEvenTargetPct ? 'pos' : 'neg'}">${fmtNum(a.executionAudit.totalCostPerSidePct, 3)}%/сторона</b> (порог безубыточности стресс-теста ≤ ${fmtNum(a.executionAudit.breakEvenTargetPct, 3)}%, закрыто ${a.executionAudit.closedCount} поз.${a.executionAudit.profitFactorGross !== null ? `, PF до ком. ${fmtNum(a.executionAudit.profitFactorGross, 2)}` : ''})</span>` : ''}
|
|
2878
2891
|
</div>
|
|
2879
2892
|
${ls?.ok ? `<div class="kpis">
|
|
2880
|
-
<div class="kpi"><div class="v">${fmtNum(ls.totalRub, 0)} ₽</div><div class="l">всего на счёте (${
|
|
2893
|
+
<div class="kpi"><div class="v">${fmtNum(ls.totalRub, 0)} ₽</div><div class="l">всего на счёте (${visiblePositions.length} поз.${hideNonTradableAssets && nonTradableCount ? `, скрыто: ${nonTradableCount}` : ''})</div>${ls.marginInfo ? `<div class="sub">марж. запас: ${fmtNum(ls.marginInfo.freeMarginRub, 0)} ₽</div>` : ''}</div>
|
|
2881
2894
|
<div class="kpi"><div class="v ${ls.cashRub > 1000 ? 'pos' : 'muted'}">${fmtNum(ls.cashRub, 0)} ₽</div><div class="l">свободные рубли для покупок</div>${ls.marginInfo?.startingMarginRub > 0 ? `<div class="sub">занято маржи: ${fmtNum(ls.marginInfo.startingMarginRub, 0)} ₽</div>` : ''}</div>
|
|
2882
2895
|
<div class="kpi"><div class="v ${cls(totalPnlRub)}">${totalPnlRub >= 0 ? '+' : ''}${fmtNum(totalPnlRub, 0)} ₽</div><div class="l">финрезультат открытых позиций</div>${a.executionAudit?.periods ? `<div class="sub">за 30 дн.: <b class="${cls(a.executionAudit.periods.month.netRub)}">${a.executionAudit.periods.month.netRub > 0 ? '+' : ''}${fmtNum(a.executionAudit.periods.month.netRub, 0)} ₽</b></div>` : ''}</div>
|
|
2883
2896
|
<div class="kpi"><div class="v">${fmtNum(strategyRub, 0)} ₽ <span class="muted small">/ ${fmtNum(protectedRub, 0)} ₽</span></div><div class="l">в стратегии / защищено</div>${a.executionAudit?.periods ? `<div class="sub">сегодня: <b class="${cls(a.executionAudit.periods.day.netRub)}">${a.executionAudit.periods.day.netRub > 0 ? '+' : ''}${fmtNum(a.executionAudit.periods.day.netRub, 0)} ₽</b></div>` : ''}</div>
|
|
2884
2897
|
</div>` : ''}
|
|
2885
2898
|
${ls && !ls.ok ? `<p class="neg small">Ошибка связи с брокером: ${esc(ls.error)}</p>` : ''}
|
|
2886
|
-
<div class="brk-subtabs">
|
|
2887
|
-
<button type="button" data-btab="assets" data-bid="${esc(a.id)}" class="${curSubTab === 'assets' ? 'active' : ''}">📦 Активы портфеля (${
|
|
2899
|
+
<div class="brk-subtabs" style="align-items:center">
|
|
2900
|
+
<button type="button" data-btab="assets" data-bid="${esc(a.id)}" class="${curSubTab === 'assets' ? 'active' : ''}">📦 Активы портфеля (${visiblePositions.length})</button>
|
|
2888
2901
|
<button type="button" data-btab="trade" data-bid="${esc(a.id)}" class="${curSubTab === 'trade' ? 'active' : ''}">⚡ Торговля и заявки${pb?.orders?.length ? ` (${pb.orders.length})` : ''}</button>
|
|
2889
2902
|
<button type="button" data-btab="history" data-bid="${esc(a.id)}" class="${curSubTab === 'history' ? 'active' : ''}">📜 История счёта${lh?.totalCount ? ` (${lh.totalCount})` : ''}</button>
|
|
2903
|
+
<label style="margin-left:auto;display:inline-flex;align-items:center;gap:6px;font-size:13px;color:var(--muted);cursor:pointer;user-select:none;padding:2px 4px" title="Скрывать из портфеля делистингованные, заблокированные и неторгуемые ценные бумаги с нулевой рыночной оценкой (0 ₽)">
|
|
2904
|
+
<input type="checkbox" data-bhide-nontradable="1" ${hideNonTradableAssets ? 'checked' : ''} style="margin:0;cursor:pointer">
|
|
2905
|
+
<span>Скрывать неторгуемые активы${nonTradableCount ? ` (${nonTradableCount})` : ''}</span>
|
|
2906
|
+
</label>
|
|
2890
2907
|
</div>
|
|
2891
2908
|
<div class="brk-subpane" ${curSubTab === 'assets' ? '' : 'hidden'}>${posHtml}</div>
|
|
2892
2909
|
<div class="brk-subpane" ${curSubTab === 'trade' ? '' : 'hidden'}>${activeOrdHtml}${batchHtml}</div>
|
|
@@ -3243,6 +3260,12 @@ brokerForm.addEventListener('submit', async (e) => {
|
|
|
3243
3260
|
});
|
|
3244
3261
|
|
|
3245
3262
|
$('#brokers-trade').addEventListener('click', async (e) => {
|
|
3263
|
+
const hideChk = e.target.closest('input[data-bhide-nontradable]');
|
|
3264
|
+
if (hideChk) {
|
|
3265
|
+
setHideNonTradableAssets(hideChk.checked);
|
|
3266
|
+
renderBrokers();
|
|
3267
|
+
return;
|
|
3268
|
+
}
|
|
3246
3269
|
const fltBtn = e.target.closest('button[data-hfilter]');
|
|
3247
3270
|
if (fltBtn) {
|
|
3248
3271
|
toggleBrokerHistFilter(fltBtn.dataset.bid, fltBtn.dataset.hfilter);
|