Кабинет клиента: портфель как у брокера — «Онлайн» по QUIK, «Архив» на дату и операции по SPECTRE

- lkclient.go: договор → код QUIK (БР-1545 → BR1545, +OUT), договор → D_B_CONTRACTS SPECTRE
- lkonline.go: позиции DepoLimits (T2, свежая строка на счёт), средняя цена покупки, деньги
  MoneyLimits по валютам, цены и P&L; стоимость, доли
- lkarchive.go: портфель на дату по остаткам OD_RESTS (REG_3 = договор, раздел 39, счета
  50/51/52/55 и 58.01/58.02; 58.05 НКД не показываем) с откатом по OD_TURNS после даты
  (индекс REST+WIRDATE, знак TYPE_ проверен: сумма оборотов = остаток); оценка по котировке
  OD_VALUES_RATES ≤ 90 дней (в рублях за бумагу), иначе балансовая; операции из OD_WIRING
  (покупка/продажа/ввод/вывод/погашение/конвертация, продажа — по счёту ВЫРУЧКА) и
  пополнения/выводы денег; рецепт — InvestControl Hub (build_portfolio_base, deals_report)
- API: /api/lk/portfolio2?view=online|archive&date=, /api/lk/ops (сделки + выплаты дохода);
  без клиента в QUIK онлайн падает на брокерский учёт за сегодня
- кабинет: разделы Портфель (вкладки Онлайн/Архив с датой), Операции, Поручения, Документы,
  Профиль; депозитарные реестр и доходы из клиентского вида убраны (/portfolio → /,
  /income → /operations); проверено на БР-704 и БР-1545
- настройка Fancy.SpectreDatabase (по умолчанию BJ_SPECTRE_DATA)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
This commit is contained in:
zuevav
2026-09-18 15:44:53 +03:00
parent 05d02f22e6
commit 02fabb483d
9 changed files with 1268 additions and 274 deletions
+65
View File
@@ -0,0 +1,65 @@
//go:build live
package lkgateway
import (
"context"
"testing"
"time"
)
func TestLiveCabinetData(t *testing.T) {
rc, _ := NewRuntimeConfig("/var/lib/bj/.bj/setup.json")
globalRC = rc
fs := rc.Snapshot().Fancy
ctx := context.Background()
for _, c := range []string{"БР-1545", "БР-704"} {
t0 := time.Now()
on, err := lkOnlinePortfolio(ctx, rc, c)
if err != nil {
t.Logf("%s онлайн: ERR %v", c, err)
} else {
t.Logf("%s ОНЛАЙН (%v) на %s: всего %.2f ₽, бумаги %.2f, деньги %.2f, вложено %.2f, P&L %.2f (%.1f%%), позиций %d/%d", c, time.Since(t0).Round(time.Millisecond), on.AsOf, on.Total, on.Securities, on.CashRUB, on.Cost, on.PnL, on.PnLPct, on.Priced, len(on.Positions))
for i, p := range on.Positions {
if i > 5 {
break
}
t.Logf(" %-14s %-28.28s %-10s %10.0f × ср.%.4g → цена %.4g (%s) = %.2f ₽, P&L %.2f (%.1f%%) %s", p.ISIN, p.Name, p.Kind, p.Quantity, p.AvgPrice, p.Price, map[bool]string{true: "%", false: "₽"}[p.InPct], p.Value, p.PnL, p.PnLPct, p.Note)
}
for _, m := range on.Cash {
t.Logf(" деньги %s %.2f", m.Currency, m.Amount)
}
}
for _, d := range []time.Time{time.Now(), time.Date(2025, 12, 31, 0, 0, 0, 0, time.Local), time.Date(2024, 6, 30, 0, 0, 0, 0, time.Local)} {
t0 = time.Now()
ar, err := lkArchivePortfolio(ctx, fs, c, d)
if err != nil {
t.Logf("%s архив %s: ERR %v", c, d.Format("02.01.2006"), err)
continue
}
t.Logf("%s АРХИВ на %s (%v): всего %.2f ₽, бумаги %.2f, деньги %.2f, бал.стоимость %.2f, P&L %.2f, позиций %d (с котировкой %d)", c, ar.AsOf, time.Since(t0).Round(time.Millisecond), ar.Total, ar.Securities, ar.CashRUB, ar.Cost, ar.PnL, len(ar.Positions), ar.Priced)
for i, p := range ar.Positions {
if i > 6 {
break
}
t.Logf(" %-14s %-28.28s %-10s %10.2f × бал.%.4g → кот.%.4g (%s) = %.2f ₽ %s", p.ISIN, p.Name, p.Kind, p.Quantity, p.AvgPrice, p.Price, map[bool]string{true: "%", false: "₽"}[p.InPct], p.Value, p.Note)
}
for _, m := range ar.Cash {
t.Logf(" деньги %s %.2f", m.Currency, m.Amount)
}
}
t0 = time.Now()
ops, err := lkArchiveOperations(ctx, fs, c, time.Date(2026, 1, 1, 0, 0, 0, 0, time.Local), time.Now())
if err != nil {
t.Logf("%s операции: ERR %v", c, err)
continue
}
t.Logf("%s ОПЕРАЦИИ с 01.01.2026 (%v): %d", c, time.Since(t0).Round(time.Millisecond), len(ops))
for i, o := range ops {
if i > 8 {
break
}
t.Logf(" %s %-12s %-3s %-28.28s %10.0f × %.4g = %.2f %s (%.2f ₽)", o.Date, o.Kind, o.Side, o.Name, o.Quantity, o.Price, o.Amount, o.Currency, o.AmountRUB)
}
}
}