Files
docs/features/stock-detail.md
T
ksv741 ead965a674 docs: describe stock pages and portfolio navigation
Document ticker aliases, instrument identity, market data and dividend history, portfolio links, and updated brokerage account summaries.
2026-09-10 21:23:33 +03:00

163 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Страница акции
Авторизованный пользователь открывает `/stocks/SBERP` по тикеру. Старые
адреса `/stocks/:uid` продолжают работать. Сервер возвращает `slug`: проверенный
однозначный тикер либо исходный UID. При открытии UUID-ссылки страница заменяет
адрес на тикер только при совпадении UID, сохраняя период и фильтр событий.
Прямое открытие и обновление страницы с тикером сначала разрешает его в UID;
цены, позиция и личные операции затем запрашиваются только по этому UID.
Ссылки находятся в названиях и логотипах акций отдельного портфеля и обзора
«Все счета». Названия сохраняют обычный цвет текста; буквенная заглушка
логотипа также ведёт на страницу акции. Карточки получают проверенный `slug`
через существующий запрос акции по UID: после его загрузки оба `href` сразу
содержат тикер. Одинаковые UID в разных счетах используют один запрос и общий
кэш страницы акции. До загрузки и при ошибке остаётся рабочая ссылка по UID;
загрузка адресов не блокирует портфель.
Раздел «Акции» в боковой и мобильной навигации ведёт на `/stocks`: поиск
по названию или тикеру через каталог Т-Банка, начиная с двух символов.
Результаты разных выпусков, включая обыкновенные и привилегированные акции,
имеют отдельные UID. Карточка результата показывает логотип, последнюю цену
с её временем, тип бумаги, площадку, код режима, ISIN и доступность торгов через
API. Поиск показывает одну карточку на нормализованный ISIN. До ограничения
в 30 результатов выбирается представитель выпуска: доступность через API,
точное совпадение тикера, затем режим TQBR; равные варианты упорядочены по UID.
Записи без ISIN сохраняются отдельно по UID. Обыкновенные и привилегированные
акции с разными ISIN не объединяются. Цена и ссылка принадлежат выбранному
UID; данные разных режимов не смешиваются. Прямые ссылки из портфеля сохраняют
исходный UID, даже если поиск выбрал другой режим этого выпуска; такая страница
сохраняет UUID в адресе. Если один точный тикер относится к разным выпускам,
сервер возвращает `409 STOCK_TICKER_AMBIGUOUS`, а страница предлагает поиск.
Поиск тикера нечувствителен к регистру, но использует точное совпадение:
SBER не заменяется на SBERP. Однобуквенные тикеры также поддерживаются.
Прямая ссылка не требует предварительного открытия портфеля. Хлебные крошки
«Акции → название» возвращают к поиску.
## Содержание и состояния
Верхний блок объединяет название, тип акции, текущую цену и показатели компании.
Денежные показатели используют символ валюты; описание компании видно сразу
под логотипом и названием, перед ценой. Далее идут график, моя позиция, предстоящие
дивиденды и личная история событий.
Логотип использует существующий CDN helper и буквенный fallback.
Позиция отсутствующей или полностью проданной бумаги не создаёт пустого блока;
рыночные данные и личная история остаются доступны.
Каждый раздел загружается независимо. Первичная загрузка показывает structural
skeleton, ошибка — локальную повторную попытку. При ошибке фонового обновления
сохраняются предыдущие данные и предупреждение. Цена содержит собственное время
UTC, которое может быть старше времени получения ответа. Статус торгов не
выводится из свежести цены. Отсутствующее значение не заменяется нулём.
## API и владельцы
Все методы требуют Bearer session и текущее подключение Т-Банка.
Новые endpoints принадлежат `StockController` / `StockService` существующего
`TbankConnectionModule`:
| Метод | Содержимое |
| --- | --- |
| `GET /api/v1/stocks?query=...` | До 30 результатов с признаками выпуска/режима, логотипом и последней ценой. |
| `GET /api/v1/stocks/:uid` | Точный инструмент: название, тикер, `slug`, тип, сектор, лот, валюта, начало дневной истории. |
| `GET /api/v1/stocks/by-ticker/:ticker` | Разрешение точного тикера и карточка выбранного выпуска с UID. |
| `GET /api/v1/stocks/:uid/quote` | Последняя цена и время, статус, изменение и явно указанная база. |
| `GET /api/v1/stocks/:uid/candles?period=...` | Дневные цены для `month`, `halfYear`, `year`, `all`. |
| `GET /api/v1/stocks/:uid/position` | Точная сводка открытой позиции и распределение по счетам. |
| `GET /api/v1/stocks/:uid/dividends` | События компании за пять прошлых лет и следующий год, личные оценки. |
| `GET /api/v1/stocks/:uid/company` | Доступное описание и финансовый срез. |
Личная история использует существующий
`GET /api/v1/brokerage-accounts/all/operations?instrumentUid=...`.
Фильтр UID валидируется и передаётся upstream как `instrumentId`; ответ также
проверяется по UID. Без `from` такой запрос начинается с самой ранней известной
даты открытия доступных счетов; если даты неполны, нижняя граница — Unix epoch.
Обычный журнал без фильтра инструмента сохраняет начало текущего года.
UID включён в ключ кэша и подпись общего курсора: курсор другой бумаги,
пользователя, поколения подключения или фильтра отклоняется. Истечение курсора
восстанавливается переходом на первую страницу с повторным запросом.
Сервис портфеля остаётся владельцем снимков и оценки позиций. Новая сводка
фильтрует `STOCK` по точному UID и использует decimal/BigInt, без FX.
Средняя цена равна сумме количеств, умноженных на средние цены, делённой на
общее количество; она округляется сервером до девяти знаков, проценты — до шести.
Неполные или разновалютные денежные итоги недоступны. Для нулевого количества
и сочетаний длинных/коротких позиций общая средняя и процент не синтезируются.
Результат открытой позиции исключает продажи и дивиденды и не называется полной
доходностью. Время и `isStale` сохраняют семантику исходного Portfolio Snapshot.
## Рыночная цена и график
Изменение рассчитывается относительно последней завершённой дневной свечи
строго до календарной даты последней цены по Москве. Поиск базы охватывает
20 дней до последней цены; если подходящей свечи нет, сравнение недоступно.
Дата базы указана рядом с изменением. `GetClosePrices` не используется:
его основное закрытие может относиться к текущему дню.
График показывает дневные `close`, сохраняет `isComplete` и предупреждает
о формирующейся свече. Окна RPC не превышают 365 дней, общие границы
дедуплицируются. «Вся история» начинается с `first_1dayCandleDate`; если источник
не сообщает начало истории, этот период недоступен, остальные работают.
Пустое окно до начала торгов не считается ошибкой. Линия текущей средней
покупки доступна только в валюте графика; шкала включает её значение.
Таблица цен раскрывается под графиком: новые даты первыми, валюта в цене,
статус завершённости свечи, закреплённая шапка и прокрутка с клавиатуры.
Стили графиков подключены на уровне приложения и каталога, поэтому оформление
подсказки не зависит от ранее посещённых страниц.
Метки старых сделок и дивидендов на графике не добавлены: соответствие шкалы
после корпоративных изменений не подтверждено. Лента использует дату операции;
количество сделки берётся из `quantityDone`, а не порученного `quantity`.
Отсутствующее исполненное количество не подменяется порученным. Сами исполнения
не превращаются в точные точки графика.
## Дивиденды и финансовые показатели
События компании отделены от личных зачислений. Известные будущие выплаты
определяются по `paymentDate`, при её отсутствии — по `recordDate`; прошедшие
и недатированные события помещены в отдельную историю компании. Отменённые
события исключаются. Личная оценка умножает сумму на акцию на текущее положительное
количество. Для уже прошедшего реестра оценка не строится. Время позиции и
предупреждение о stale/недоступной оценке показаны отдельно.
Оценка не доказывает право на выплату, не учитывает будущие сделки и не обещает
индивидуальную дату зачисления. Статусы «рекомендован/утверждён», сумма отдельного
зачисления после налога и налоговый смысл `dividend_net` не выводятся из API.
Пустой список означает отсутствие известных событий только за указанный период.
`GetAssetFundamentals` декодируется отдельным Instruments client с
`defaults: false`: отсутствующие поля не превращаются в нули. Остальные
существующие клиенты сохраняют прежнюю настройку декодирования. Финансовый сектор
показывает капитализацию, P/E, P/B, ROE и чистую прибыль; остальные — капитализацию,
P/E, выручку, EBITDA, чистую прибыль, общий долг и чистый долг / EBITDA, если
значения доступны. Денежные показатели без валюты не показываются.
TTM означает последние 12 месяцев, MRQ — последний отчётный квартал;
время получения не выдаётся за дату отчётности. Годовых финансовых рядов нет.
## Границы и проверка
Новые RPC проходят существующий `TbankClient`: TLS, rate limit, очередь,
deadline и retry. Доступ/расшифровка остаются у `TbankAccessService` и
`TbankTokenService`. Успешные ответы и совместные незавершённые запросы кэшируются
в bounded LRU на 30 секунд по пользователю, поколению подключения и параметрам.
Для поиска `FindInstrument` дополняется сведениями `ShareBy` через тот же
кэш карточки инструмента и одним пакетным `GetLastPrices` для списка. Ошибка
дополнительной загрузки или исчезнувшая карточка не скрывает исходную запись
каталога; недоступные логотипы и цены имеют fallback. Ошибки авторизации
не подавляются.
Ошибка целого запроса не кэшируется; отсутствие дополнительного источника
обозначается в ответе. Cache subtree `/stocks` сбрасывается вместе с финансовыми
данными при изменении подключения; logout использует существующий session reset.
Контракт генерируется из runtime Nest OpenAPI, затем обновляются frontend snapshot
и generated types. Unit/HTTP/MSW tests проверяют точность, разделение инструментов,
фильтрацию и курсоры, валидацию, отсутствие позиции и локальные ошибки.
Браузерная проверка использует синтетические ответы и широкий/узкий viewport.
Полные даты в интерфейсе имеют формат ДД.ММ.ГГГГ; ISO-формат транспорта и
временные зоны расчётов не меняются. Личная история использует общий с портфелем
`LedgerEntryRow`, дополненный количеством, ценой и ссылкой на счёт. История
дивидендов компании раскрывается в оформленном блоке с отдельными карточками
выплат, датой реестра, доступной датой выплаты и суммой на акцию.
Реальные ограничения источника описаны в
[результатах исследования](../../design/stock-detail-api-live-findings.md).
История открытых счетов не обещает полную историю владения, включая закрытые
счета, переводы и универсальные корпоративные события.