neuroalpha 0.5.10 → 0.5.12
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 -8
- package/public/styles.css +1 -0
- package/server/brokers/index.js +49 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neuroalpha",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.12",
|
|
4
4
|
"description": "НейроАльфа — ИИ-автопилот для рынка акций Мосбиржи: эмбеддинги акций, самообучение правил, коннекторы Т-Банк / БКС / Финам / Алор и боты Telegram / MAX",
|
|
5
5
|
"author": "Roman A. Perepelkin",
|
|
6
6
|
"license": "UNLICENSED",
|
package/public/app.js
CHANGED
|
@@ -1393,7 +1393,10 @@ async function sendChat(text, { newSession = false, title = null } = {}) {
|
|
|
1393
1393
|
if (speechRec && speechActive) stopVoiceInput(false);
|
|
1394
1394
|
text = (text ?? $('#chat-input').value).trim();
|
|
1395
1395
|
if (!text) return;
|
|
1396
|
-
|
|
1396
|
+
setChatClosed(false);
|
|
1397
|
+
if (newSession && window.innerWidth <= 920) {
|
|
1398
|
+
document.querySelector('section.chat')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
1399
|
+
}
|
|
1397
1400
|
// новый запрос — лента снова «прилипает» к низу и показывает вопрос и потоковый ответ
|
|
1398
1401
|
toBottom(true);
|
|
1399
1402
|
try {
|
|
@@ -2013,6 +2016,12 @@ function toggleBrokerHistFilter(id, flt) {
|
|
|
2013
2016
|
}
|
|
2014
2017
|
let openBrokerId = loadSavedNav().openBrokerId ?? null;
|
|
2015
2018
|
let openBrokerCfgId = loadSavedNav().openBrokerCfgId ?? null;
|
|
2019
|
+
let hideBrokerSpecsById = new Set(Array.isArray(loadSavedNav().hideBrokerSpecs) ? loadSavedNav().hideBrokerSpecs : []);
|
|
2020
|
+
function toggleBrokerSpecs(id) {
|
|
2021
|
+
if (hideBrokerSpecsById.has(id)) hideBrokerSpecsById.delete(id);
|
|
2022
|
+
else hideBrokerSpecsById.add(id);
|
|
2023
|
+
saveNavPatch({ hideBrokerSpecs: [...hideBrokerSpecsById] });
|
|
2024
|
+
}
|
|
2016
2025
|
let editingBrokerId = null;
|
|
2017
2026
|
let brokerPollBusy = false;
|
|
2018
2027
|
let lastBrokerPollTs = 0;
|
|
@@ -2406,8 +2415,9 @@ function renderBrokers() {
|
|
|
2406
2415
|
|
|
2407
2416
|
const isPaused = a.autoSchedule === false || a.mode === 'readonly';
|
|
2408
2417
|
const isEditingThis = editingBrokerId === a.id;
|
|
2418
|
+
const isSpecsHidden = hideBrokerSpecsById.has(a.id);
|
|
2409
2419
|
const collapsedMetricsHtml = !isOpen && ls?.ok
|
|
2410
|
-
? `<span class="small muted" style="margin-left:6px">Всего: <b style="color:var(--text)">${fmtNum(ls.totalRub, 0)} ₽</b> · Свободно: <b class="${ls.cashRub > 1000 ? 'pos' : ''}">${fmtNum(ls.cashRub, 0)} ₽</b
|
|
2420
|
+
? `<span class="small muted" style="margin-left:6px">Всего: <b style="color:var(--text)">${fmtNum(ls.totalRub, 0)} ₽</b> · Свободно: <b class="${ls.cashRub > 1000 ? 'pos' : ''}">${fmtNum(ls.cashRub, 0)} ₽</b>${ls.marginInfo ? ` · Марж. запас: <b class="pos">${fmtNum(ls.marginInfo.freeMarginRub, 0)} ₽</b>` : ''} · Финрез.: <b class="${cls(totalPnlRub)}">${totalPnlRub >= 0 ? '+' : ''}${fmtNum(totalPnlRub, 0)} ₽</b>${a.executionAudit?.periods ? ` · 30 дн.: <b class="${cls(a.executionAudit.periods.month.netRub)}">${a.executionAudit.periods.month.netRub >= 0 ? '+' : ''}${fmtNum(a.executionAudit.periods.month.netRub, 0)} ₽</b>` : ''}</span>`
|
|
2411
2421
|
: '';
|
|
2412
2422
|
|
|
2413
2423
|
return `<div class="live-card${isOpen ? '' : ' collapsed'}" data-bid="${esc(a.id)}">
|
|
@@ -2424,6 +2434,7 @@ function renderBrokers() {
|
|
|
2424
2434
|
<div class="row-gap" style="align-items:center">
|
|
2425
2435
|
<button type="button" class="brk-toggle ${isPaused ? 'on' : ''}" data-btrade="pause" data-bid="${esc(a.id)}" title="${isPaused ? 'Авто-торговля на паузе. Нажмите, чтобы возобновить торговлю по расписанию' : 'Нажмите, чтобы поставить авто-торговлю на этом счёте на паузу'}">${isPaused ? '▶ Запустить' : '⏸ Пауза'}</button>
|
|
2426
2436
|
${isOpen && ls?.positions?.length ? `<button type="button" class="brk-toggle ${lMode === 'isolate' && Object.keys(baseMap).length ? 'on' : ''}" data-btrade="freeze" data-bid="${esc(a.id)}" title="${lMode === 'isolate' && Object.keys(baseMap).length ? 'Жёсткая защита включена (режим «Изолированный карман»): старые бумаги на счёте неприкосновенны. Нажмите, чтобы переключить в «Плавный переход».' : 'Нажмите, чтобы зафиксировать все текущие бумаги на счёте как неприкосновенные (включить режим «Изолированный карман»).'}">${lMode === 'isolate' && Object.keys(baseMap).length ? '🔒 Защита бумаг: ВКЛ' : '🔓 Защита бумаг: ВЫКЛ'}</button>` : ''}
|
|
2437
|
+
${isOpen ? `<button type="button" class="brk-icon-btn${!isSpecsHidden ? ' primary' : ''}" data-btrade="specs" data-bid="${esc(a.id)}" title="${isSpecsHidden ? 'Показать служебную информацию по счёту' : 'Скрыть служебную информацию по счёту'}" aria-label="Служебная информация">ℹ</button>` : ''}
|
|
2427
2438
|
${isOpen ? `<button type="button" class="brk-icon-btn" data-btrade="events" data-bid="${esc(a.id)}" title="Журнал исполнения: отправленные заявки, ошибки брокера, стопы, ручные действия" aria-label="Журнал исполнения">📋</button>` : ''}
|
|
2428
2439
|
${isOpen ? `<button type="button" class="brk-icon-btn${isEditingThis ? ' primary' : ''}" data-btrade="cfg" data-bid="${esc(a.id)}" title="${isEditingThis ? 'Закрыть настройки и вернуться к торговле' : 'Настроить счёт и предохранители'}">⚙</button>` : ''}
|
|
2429
2440
|
</div>
|
|
@@ -2431,20 +2442,25 @@ function renderBrokers() {
|
|
|
2431
2442
|
<div ${isOpen ? '' : 'hidden'}>
|
|
2432
2443
|
<div class="brk-cfg-mount" data-cfg-mount="${esc(a.id)}" ${isEditingThis ? '' : 'hidden'} style="margin-top:12px"></div>
|
|
2433
2444
|
<div class="brk-trade-view" ${isEditingThis ? 'hidden' : ''}>
|
|
2434
|
-
<div class="brk-specs">
|
|
2445
|
+
<div class="brk-specs" ${isSpecsHidden ? 'hidden' : ''}>
|
|
2435
2446
|
<span class="k">Брокер и номер счёта:</span><span class="v"><b>${esc(BROKER_LABELS[a.broker] || a.broker)}</b> · <code>${esc(a.accountId || ls?.accountId || 'авто')}</code>${ls?.at ? ` <span class="muted">(обновлён ${fmtTs(ls.at)})</span>` : ''}</span>
|
|
2436
2447
|
<span class="k">Режим работы счёта:</span><span class="v"><span class="tag ${a.mode === 'auto' ? 'mid' : a.mode === 'confirm' ? 'ok' : ''}">${esc(BROKER_MODES[a.mode] || a.mode)}</span> · авто-торговля по расписанию: <b>${a.autoSchedule !== false ? '<span class="pos">вкл.</span>' : '<span class="muted">на паузе</span>'}</b></span>
|
|
2437
2448
|
<span class="k">Начальный портфель:</span><span class="v">${esc(LEGACY_MODES[lMode] || lMode)}</span>
|
|
2438
|
-
<span class="k">Маржинальная торговля:</span><span class="v">${ls ? (ls.marginEnabled ?
|
|
2449
|
+
<span class="k">Маржинальная торговля:</span><span class="v">${ls ? (ls.marginEnabled ? `<b class="pos">вкл. (шорт доступен)</b>${ls.marginInfo ? ` · ликвидный портфель <b>${fmtNum(ls.marginInfo.liquidPortfolioRub, 0)} ₽</b> · доступный запас <b class="pos">${fmtNum(ls.marginInfo.freeMarginRub, 0)} ₽</b> · нач. маржа <b>${fmtNum(ls.marginInfo.startingMarginRub, 0)} ₽</b> (мин. ${fmtNum(ls.marginInfo.minimalMarginRub, 0)} ₽)${ls.marginInfo.fundsSufficiencyLevel > 0 ? ` · достаточность <b>${fmtNum(ls.marginInfo.fundsSufficiencyLevel, 2)}×</b>` : ''}${ls.marginInfo.missingFundsRub > 0 ? ` · <b class="neg">нехватка ${fmtNum(ls.marginInfo.missingFundsRub, 0)} ₽</b>` : ''}` : ''}` : '<b>выкл. у брокера (только в рост)</b>') : '<span class="muted">не проверено</span>'}</span>
|
|
2450
|
+
${a.executionAudit?.periods ? (() => {
|
|
2451
|
+
const pr = a.executionAudit.periods;
|
|
2452
|
+
const fmtP = (lbl, x) => `${lbl}: <b class="${cls(x.netRub)}">${x.netRub > 0 ? '+' : ''}${fmtNum(x.netRub, 0)} ₽</b> <span class="muted small">(${x.pct > 0 ? '+' : ''}${fmtNum(x.pct, 2)}%)</span>`;
|
|
2453
|
+
return `<span class="k">Доход / убыток по периодам:</span><span class="v" title="Чистый результат счёта (закрытые сделки после комиссий + дивиденды/купоны; за сегодня — с учётом дневной переоценки портфеля)">${fmtP('сегодня', pr.day)} · ${fmtP('7 дн.', pr.week)} · ${fmtP('30 дн.', pr.month)} · ${fmtP('с начала года', pr.ytd)}</span>`;
|
|
2454
|
+
})() : ''}
|
|
2439
2455
|
<span class="k">Контроль автоследования:</span><span class="v">${ls ? (ls.autoFollowDetected ? '<b class="neg">обнаружено ✕ (торговля заблокирована)</b>' : '<b class="pos">не используется ✓ (счёт допущен)</b>') : '<span class="muted">не проверено</span>'}</span>
|
|
2440
2456
|
<span class="k">Предохранители и настройки:</span><span class="v">${capSpecHtml} · дневной стоп <b>−${fmtNum(a.maxDailyDrawdownPct, 1)}%</b> · отступ лимитки <b>${fmtNum(a.limitOffsetPct, 2)}%</b>${a.broker === 'tbank' ? ` · стоп-заявки у брокера: <b>${a.brokerStops ? `<span class="pos">вкл.</span> (${Object.keys(a.brokerStopBySymbol || {}).length} активн.)` : 'выкл.'}</b> · кэш в LQDT: <b>${a.cashSweep ? '<span class="pos">вкл.</span>' : 'выкл.'}</b>` : ''}${a.baseRunLabel ? ` · эксперимент <b>${esc(a.baseRunLabel)}</b>` : ''}</span>
|
|
2441
2457
|
${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>` : ''}
|
|
2442
2458
|
</div>
|
|
2443
2459
|
${ls?.ok ? `<div class="kpis">
|
|
2444
|
-
<div class="kpi"><div class="v">${fmtNum(ls.totalRub, 0)} ₽</div><div class="l">всего на счёте (${ls.positions?.length || 0} поз.)</div
|
|
2445
|
-
<div class="kpi"><div class="v ${ls.cashRub > 1000 ? 'pos' : 'muted'}">${fmtNum(ls.cashRub, 0)} ₽</div><div class="l">свободные рубли для покупок</div
|
|
2446
|
-
<div class="kpi"><div class="v ${cls(totalPnlRub)}">${totalPnlRub >= 0 ? '+' : ''}${fmtNum(totalPnlRub, 0)} ₽</div><div class="l">финрезультат открытых позиций</div></div>
|
|
2447
|
-
<div class="kpi"><div class="v">${fmtNum(strategyRub, 0)} ₽ <span class="muted small">/ ${fmtNum(protectedRub, 0)} ₽</span></div><div class="l">в стратегии / защищено</div></div>
|
|
2460
|
+
<div class="kpi"><div class="v">${fmtNum(ls.totalRub, 0)} ₽</div><div class="l">всего на счёте (${ls.positions?.length || 0} поз.)</div>${ls.marginInfo ? `<div class="sub">марж. запас: ${fmtNum(ls.marginInfo.freeMarginRub, 0)} ₽</div>` : ''}</div>
|
|
2461
|
+
<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>
|
|
2462
|
+
<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>
|
|
2463
|
+
<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>
|
|
2448
2464
|
</div>` : ''}
|
|
2449
2465
|
${ls && !ls.ok ? `<p class="neg small">Ошибка связи с брокером: ${esc(ls.error)}</p>` : ''}
|
|
2450
2466
|
<div class="brk-subtabs">
|
|
@@ -2887,6 +2903,11 @@ $('#brokers-trade').addEventListener('click', async (e) => {
|
|
|
2887
2903
|
}
|
|
2888
2904
|
return;
|
|
2889
2905
|
}
|
|
2906
|
+
if (act === 'specs') {
|
|
2907
|
+
toggleBrokerSpecs(id);
|
|
2908
|
+
renderBrokers();
|
|
2909
|
+
return;
|
|
2910
|
+
}
|
|
2890
2911
|
if (act === 'events') {
|
|
2891
2912
|
openBrokerEvents(id);
|
|
2892
2913
|
return;
|
package/public/styles.css
CHANGED
|
@@ -414,6 +414,7 @@ table.cmp td { padding: 3px 8px; }
|
|
|
414
414
|
.brk-alloc-legend span { display: inline-flex; align-items: center; gap: 5px; }
|
|
415
415
|
.brk-alloc-legend i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }
|
|
416
416
|
.brk-specs { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 4px 14px; font-size: 13px; margin: 10px 0; line-height: 1.45; }
|
|
417
|
+
.brk-specs[hidden] { display: none; }
|
|
417
418
|
.brk-specs .k { color: var(--muted); }
|
|
418
419
|
.brk-specs .v { color: var(--text); }
|
|
419
420
|
.brk-toolbar { display: flex; flex-wrap: nowrap; align-items: center; gap: 8px; padding: 8px 0 10px; margin-bottom: 10px; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); overflow-x: auto; }
|
package/server/brokers/index.js
CHANGED
|
@@ -287,7 +287,7 @@ const executionAuditCache = new Map(); // accountId -> { sig, val }
|
|
|
287
287
|
function computeExecutionAudit(a) {
|
|
288
288
|
const ops = Array.isArray(a.lastHistory?.operations) ? a.lastHistory.operations : [];
|
|
289
289
|
if (!ops.length) return null;
|
|
290
|
-
const sig = `${a.lastHistory?.at || 0}:${ops.length}:${ops[0]?.id || ''}:${ops[ops.length - 1]?.id || ''}`;
|
|
290
|
+
const sig = `${a.lastHistory?.at || 0}:${ops.length}:${ops[0]?.id || ''}:${ops[ops.length - 1]?.id || ''}:${Math.round(Number(a.lastSync?.dailyYieldRub || 0))}:${Math.round(Number(a.lastSync?.totalRub || 0))}`;
|
|
291
291
|
const hit = executionAuditCache.get(a.id);
|
|
292
292
|
if (hit && hit.sig === sig) return hit.val;
|
|
293
293
|
const sorted = [...ops].sort((x, y) => x.date - y.date);
|
|
@@ -320,8 +320,9 @@ function computeExecutionAudit(a) {
|
|
|
320
320
|
const slipTier2Pct = rubW2 > 0 ? round(slipW2 / rubW2, 3) : null;
|
|
321
321
|
const totalCostPerSidePct = slipAllPct !== null ? round(commPct + Math.max(0, slipAllPct), 3) : commPct;
|
|
322
322
|
|
|
323
|
-
// FIFO закрытых позиций (без старого портфеля baselineQty)
|
|
323
|
+
// FIFO закрытых позиций (без старого портфеля baselineQty) + разбивка финансового результата по периодам (день, 7 дн., 30 дн., с начала года)
|
|
324
324
|
const book = new Map();
|
|
325
|
+
const closedEvents = [];
|
|
325
326
|
let winRub = 0, lossRub = 0, closedCount = 0;
|
|
326
327
|
for (const t of trades) {
|
|
327
328
|
const q = t.kind === 'buy' ? t.qty : -t.qty;
|
|
@@ -332,6 +333,7 @@ function computeExecutionAudit(a) {
|
|
|
332
333
|
const take = Math.min(Math.abs(rem), Math.abs(lots[0].qty));
|
|
333
334
|
const pnl = lots[0].qty > 0 ? take * (t.price - lots[0].price) : take * (lots[0].price - t.price);
|
|
334
335
|
closedCount++;
|
|
336
|
+
closedEvents.push({ ts: t.date || 0, pnl });
|
|
335
337
|
if (pnl >= 0) winRub += pnl; else lossRub += -pnl;
|
|
336
338
|
lots[0].qty -= Math.sign(lots[0].qty) * take;
|
|
337
339
|
rem -= Math.sign(rem) * take;
|
|
@@ -344,6 +346,31 @@ function computeExecutionAudit(a) {
|
|
|
344
346
|
const netRub = round(grossRub - feesRub, 0);
|
|
345
347
|
const profitFactorGross = lossRub > 0 ? round(winRub / lossRub, 2) : (winRub > 0 ? 3 : null);
|
|
346
348
|
|
|
349
|
+
const nowTs = Date.now();
|
|
350
|
+
const nowMsk = new Date(nowTs + MSK);
|
|
351
|
+
const todayStartTs = Date.parse(`${nowMsk.toISOString().slice(0, 10)}T00:00:00+03:00`);
|
|
352
|
+
const yearStartTs = Date.parse(`${nowMsk.getUTCFullYear()}-01-01T00:00:00+03:00`);
|
|
353
|
+
const baseCap = Math.max(1, Number(a.lastSync?.totalRub || a.initialTotalRub || a.maxCapitalRub || 100000));
|
|
354
|
+
const calcPeriodPnl = (fromTs, includeUnrealizedToday = false) => {
|
|
355
|
+
const g = closedEvents.filter((c) => c.ts >= fromTs).reduce((s, c) => s + c.pnl, 0);
|
|
356
|
+
const f = algoOps.filter((o) => o.kind === 'fee' && (o.date || 0) >= fromTs).reduce((s, o) => s + Math.abs(o.paymentRub || 0), 0);
|
|
357
|
+
const d = sorted.filter((o) => o.kind === 'div' && (o.date || 0) >= fromTs).reduce((s, o) => s + (Number(o.paymentRub) || 0), 0);
|
|
358
|
+
const cCnt = closedEvents.filter((c) => c.ts >= fromTs).length;
|
|
359
|
+
let total = round(g - f + d, 0);
|
|
360
|
+
if (includeUnrealizedToday && cCnt === 0 && Math.abs(total) < 1 && a.lastSync?.dailyYieldRub) {
|
|
361
|
+
total = round(Number(a.lastSync.dailyYieldRub) || 0, 0);
|
|
362
|
+
} else if (includeUnrealizedToday && cCnt === 0 && Math.abs(total) < 1 && a.lastSync?.dayStartRub > 0 && a.lastSync?.totalRub > 0) {
|
|
363
|
+
total = round(a.lastSync.totalRub - a.lastSync.dayStartRub, 0);
|
|
364
|
+
}
|
|
365
|
+
return { netRub: total, pct: round((total / baseCap) * 100, 2), closed: cCnt };
|
|
366
|
+
};
|
|
367
|
+
const periods = {
|
|
368
|
+
day: calcPeriodPnl(todayStartTs, true),
|
|
369
|
+
week: calcPeriodPnl(nowTs - 7 * DAY, false),
|
|
370
|
+
month: calcPeriodPnl(nowTs - 30 * DAY, false),
|
|
371
|
+
ytd: calcPeriodPnl(yearStartTs, false),
|
|
372
|
+
};
|
|
373
|
+
|
|
347
374
|
const val = {
|
|
348
375
|
fromTs: algoOps[0]?.date || null,
|
|
349
376
|
tradesCount: trades.length,
|
|
@@ -359,6 +386,7 @@ function computeExecutionAudit(a) {
|
|
|
359
386
|
feesRub: round(feesRub, 0),
|
|
360
387
|
netRub,
|
|
361
388
|
profitFactorGross,
|
|
389
|
+
periods,
|
|
362
390
|
};
|
|
363
391
|
executionAuditCache.set(a.id, { sig, val });
|
|
364
392
|
return val;
|
|
@@ -856,10 +884,28 @@ async function syncTbank(acc) {
|
|
|
856
884
|
}
|
|
857
885
|
}
|
|
858
886
|
let marginEnabled = false;
|
|
887
|
+
let marginInfo = null;
|
|
859
888
|
if (accountType === 'ACCOUNT_TYPE_TINKOFF_IIS' || accountType === 'ACCOUNT_TYPE_INVEST_BOX') {
|
|
860
889
|
marginEnabled = false;
|
|
861
890
|
} else if (rMargin?.ok) {
|
|
862
891
|
marginEnabled = true;
|
|
892
|
+
const jMar = await rMargin.json().catch(() => null);
|
|
893
|
+
if (jMar) {
|
|
894
|
+
const liquidPortfolioRub = round(qVal(jMar.liquidPortfolio), 0);
|
|
895
|
+
const startingMarginRub = round(qVal(jMar.startingMargin), 0);
|
|
896
|
+
const minimalMarginRub = round(qVal(jMar.minimalMargin), 0);
|
|
897
|
+
const missingFundsRub = round(qVal(jMar.amountOfMissingFunds), 0);
|
|
898
|
+
const fundsSufficiencyLevel = round(qVal(jMar.fundsSufficiencyLevel), 2);
|
|
899
|
+
const freeMarginRub = round(Math.max(0, liquidPortfolioRub - startingMarginRub), 0);
|
|
900
|
+
marginInfo = {
|
|
901
|
+
liquidPortfolioRub,
|
|
902
|
+
startingMarginRub,
|
|
903
|
+
minimalMarginRub,
|
|
904
|
+
freeMarginRub,
|
|
905
|
+
missingFundsRub,
|
|
906
|
+
fundsSufficiencyLevel,
|
|
907
|
+
};
|
|
908
|
+
}
|
|
863
909
|
} else if (rMargin) {
|
|
864
910
|
const mTxt = await rMargin.text().catch(() => '');
|
|
865
911
|
marginEnabled = !mTxt.includes('30051') && !/margin.*disabled/i.test(mTxt) ? false : false;
|
|
@@ -991,6 +1037,7 @@ async function syncTbank(acc) {
|
|
|
991
1037
|
accountName,
|
|
992
1038
|
accountType,
|
|
993
1039
|
marginEnabled,
|
|
1040
|
+
marginInfo,
|
|
994
1041
|
autoFollowDetected,
|
|
995
1042
|
totalRub: totalRub || cashRub,
|
|
996
1043
|
cashRub: round(cashRub, 2),
|