7.2 KiB
Портфель
Авторизованный пользователь просматривает текущий состав одного доступного брокерского счёта на /accounts/:accountId. Frontend обращается только к backend API; T-Bank остаётся server-side интеграцией.
sequenceDiagram
participant U as User
participant F as Frontend
participant B as Backend
participant D as SQLite
participant T as T-Bank gRPC
U->>F: Открывает /accounts/:accountId
F->>B: GET /api/v1/brokerage-accounts/:accountId/portfolio
B->>D: Читает T-Bank connection
B->>T: GetAccounts(OPEN)
alt Счёт недоступен
B-->>F: 404
F-->>U: Состояние недоступного счёта
else Свежий Snapshot
B-->>F: 200 Portfolio isStale=false
else Требуется обновление
par Получение финансовых данных
B->>T: GetPortfolio
and
B->>T: GetPositions
end
B->>T: GetInstrumentBy(FIGI), при необходимости
B-->>F: Полный Portfolio или Problem Details
end
API
GET /api/v1/brokerage-accounts/:accountId/portfolio требует Bearer access token. Успешный ответ содержит lastSuccessfulAt в RFC 3339 UTC, isStale, totalsByCurrency, positions и cashBalances.
Все количества и суммы передаются decimal strings без экспоненциальной записи. Деньги содержат ISO-код валюты. Backend выполняет точные операции через BigInt; frontend форматирует строки для русской локали и не выполняет финансовую арифметику кроме display-only оценки доли позиции.
Portfolio Position -- ненулевой обычный актив из T-Bank portfolio. currentValue равен количеству, умноженному на текущую цену, когда оба значения и валюта доступны. absoluteReturn доступен только при одинаковой известной валюте текущей и средней цены. isin берётся из metadata Instrument и передаётся nullable-строкой. Невычислимые значения передаются null, а не нулём. Позиции остаются в upstream-порядке; виртуальные позиции не входят в ответ.
Cash Balance -- доступный ненулевой денежный остаток. Несколько остатков одной валюты суммируются; заблокированные средства не включаются. Итоги группируются по валюте и включают стоимость позиций и Cash Balance без конвертации.
Metadata инструмента ищется сначала по instrument UID, затем по FIGI. Отображение использует полное name, затем ticker, затем FIGI. Для нового Snapshot backend ожидает отсутствующие metadata в пределах gRPC deadline, чтобы показать name при доступном ответе T-Bank. Ошибка, отсутствие инструмента или пустые name/ticker используют FIGI как fallback и не скрывают финансовые данные.
Первый запрос крупного Portfolio может выполнить много metadata lookup. Если настроенный per-instance лимит T-Bank RPC недостаточен и локальная очередь переполняется, увеличьте TBANK_RATE_LIMIT_PER_SECOND в рамках upstream-квоты; после успешного lookup metadata остаётся в 24-часовом in-memory cache.
Свежесть и ошибки
- Backend хранит успешный полный Snapshot для пары User и Brokerage Account свежим 30 секунд.
- После истечения fresh TTL последний Snapshot можно вернуть как fallback не более пяти минут только при
TBANK_UNAVAILABLE; такой ответ имеетisStale: trueи исходныйlastSuccessfulAt. INVALID_TBANK_TOKENникогда не маскируется fallback-данными.- Успешная замена или удаление T-Bank connection очищает Portfolio Snapshot User.
- Metadata по FIGI и подтверждённое отсутствие инструмента кэшируются в памяти 24 часа глобально; ошибки lookup не кэшируются.
409 TBANK_NOT_CONNECTEDи400 INVALID_TBANK_TOKENведут в настройки;404показывает возврат к списку счетов;503 TBANK_UNAVAILABLEи неизвестная ошибка позволяют повторить запрос, если нет fallback Snapshot.
Клиент использует успешный React Query-кэш 30 секунд, без polling, automatic retry и refetch при возврате фокуса. После устаревания предыдущий результат скрывается до следующего ответа. Акции и облигации отображаются отдельными секциями с upstream-порядком внутри каждой; остальные активы остаются в отдельной секции. Position Card -- компактная плитка в адаптивном grid: одна колонка на узком экране, 2-3 на широком. Плитка показывает идентификатор, количество, текущую стоимость, абсолютный доход с цветовой индикацией знака (зелёный при положительном, красный при отрицательном), среднюю цену покупки, долю среди позиций той же валюты и ISIN. Доля -- display-only оценка, вычисляемая на клиенте как currentValue позиции к сумме currentValue позиций той же валюты, без Cash Balance; отсутствующие метрики скрываются, а не отображаются заглушкой. RUB форматируется как ₽. Loaded view сохраняет адаптивное одноколоночное отображение на узком экране.
Ограничения MVP
In-memory cache теряется после перезапуска и рассчитан на одного User. Нет истории Snapshot, операций, графиков, валютной конвертации, виртуальных позиций, заблокированных средств, ручного обновления и поддержки нескольких backend instances.