docs: reorganize system knowledge by domain
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
# Портфель по всем счетам
|
||||
|
||||
Режим «Все счета» объединяет открытые поддерживаемые инвестиционные счета,
|
||||
доступные через текущее подключение T-Bank. Состав совпадает с политикой
|
||||
[списка счетов](../accounts/overview.md); закрытые и недоступные счета не включаются.
|
||||
`all` — зарезервированное обозначение общего режима, которое не передаётся
|
||||
брокеру как идентификатор счёта.
|
||||
|
||||
## Маршруты и навигация
|
||||
|
||||
| Маршрут | Содержание |
|
||||
| --- | --- |
|
||||
| `/accounts` | Список отдельных счетов и действие открытия общего портфеля |
|
||||
| `/accounts/all` | Общий портфель и превью операций, дохода и будущих событий |
|
||||
| `/accounts/all/operations` | Общий журнал исполненных операций |
|
||||
| `/accounts/all/income` | Купоны и дивиденды за календарный период |
|
||||
| `/accounts/all/events` | Будущие события всех доступных счетов |
|
||||
|
||||
Общий режим использует те же страницы разделов с обозначением `all`.
|
||||
На страницах счёта и общего режима переключатель «Счёт»
|
||||
сохраняет текущий раздел и применённые URL-фильтры. При переключении пагинация
|
||||
начинается заново. Журнал хранит применённые `from`, `to` и `categories` в URL;
|
||||
черновые значения применяются кнопкой. Раздел дохода сохраняет свой прежний
|
||||
календарный контракт. Хлебные крошки называют общий режим «Все счета».
|
||||
|
||||
## Данные и вычисления
|
||||
|
||||
Backend публикует статические маршруты под `/api/v1/brokerage-accounts/all`:
|
||||
`/portfolio`, `/operations`, `/income-summary`, `/income-analytics`, `/events`.
|
||||
У них те же формы данных и фильтры, что у соответствующих разделов отдельного
|
||||
счёта. Позиции, операции (включая выплаты дохода) и события сохраняют источник
|
||||
в поле `account: { id, name }`; в ответах одного счёта это поле отсутствует.
|
||||
Валютные итоги и деньги агрегированы. Точную форму транспорта задаёт
|
||||
[OpenAPI backend](../../architecture/http-api.md).
|
||||
|
||||
- Портфель и доступные денежные остатки суммируются сервером точно, по исходной
|
||||
валюте, с точной десятичной арифметикой. FX-конвертации нет; правила
|
||||
неизвестных значений и состава сумм наследуются от [портфеля](data-reference.md).
|
||||
- Одинаковые бумаги разных счетов показаны одной карточкой с общим названием
|
||||
и идентификатором. Внутри всегда видны строки счетов: ссылка на портфель,
|
||||
количество, доступная стоимость, результат, средняя цена и доля позиции.
|
||||
Денежные показатели и количества остаются исходными для каждого счёта;
|
||||
общая средняя цена и суммарное количество карточки не рассчитываются.
|
||||
Группировка выполняется по типу инструмента и первому доступному
|
||||
идентификатору: UID, затем FIGI, затем ISIN. Разные выбранные идентификаторы
|
||||
или их отсутствие, а также одно лишь совпадение имени или тикера не
|
||||
объединяют позиции. Повторные строки одного счёта остаются отдельными.
|
||||
API сохраняет позиции каждого счёта; объединяется только их представление.
|
||||
Порядок карточек задаёт первое вхождение
|
||||
бумаги; порядок счетов внутри карточки сохраняется. Одиночные позиции
|
||||
используют прежнюю карточку. Доля относится к позициям той же валюты
|
||||
во всём показанном портфеле, без денежных остатков.
|
||||
- Журнал объединяет операции по убыванию времени, при равном времени использует
|
||||
порядок идентификаторов счетов, сохраняя порядок внутри одного счёта. ID
|
||||
операций разных счетов не считаются дублями. Переводы не схлопываются.
|
||||
- Доходы собираются из всех страниц операций каждого счёта за общие периоды.
|
||||
Итоги, сравнения, временные интервалы и принадлежность выплат рассчитывает
|
||||
сервис дохода.
|
||||
Для сравнения с годом ранее используется текущий состав счетов; новый счёт
|
||||
добавляет только реально полученные выплаты. При известных датах открытия всех счетов
|
||||
доступность сравнения определяется самым ранним открытием в этом составе.
|
||||
Закрытые счета в историческое сравнение не добавляются.
|
||||
- Будущие события сортируются по дате и сохраняют отдельную строку каждого счёта.
|
||||
Прогноз не включается в реализованные доходы.
|
||||
|
||||
## Полнота, свежесть и доступ
|
||||
|
||||
Для каждого общего запроса backend определяет состав доступных счетов
|
||||
и единое поколение подключения. Получение данных использует эту же границу подключения;
|
||||
проверки сессии, доступа и существующий лимитер внешних запросов сохраняются.
|
||||
Финансовые данные не сохраняются в БД; снимки и продолжения хранятся
|
||||
в памяти процесса.
|
||||
|
||||
Ошибка источника не превращается в частичный успешный ответ раздела.
|
||||
Портфель может использовать разрешённый существующей политикой запасной снимок
|
||||
отдельного счёта. Тогда общий `isStale` равен `true`, а `lastSuccessfulAt`
|
||||
показывает самое раннее время среди включённых снимков. Для пустого состава
|
||||
возвращается корректный пустой результат. Общий запрос не является атомарным
|
||||
наблюдением всех счетов в один момент времени.
|
||||
|
||||
Ошибка блока операций, доходов или будущих событий не скрывает основной
|
||||
портфель. При фоновом обновлении страницы уже полученный результат сохраняется
|
||||
с индикатором или предупреждением и повторной попыткой. Смена сессии или
|
||||
подключения сбрасывает общий режим вместе с остальными финансовыми запросами.
|
||||
|
||||
## Общая пагинация операций
|
||||
|
||||
Сервис объединяет потоки страниц каждого счёта и сохраняет непрочитанные строки
|
||||
между запросами. Фильтрованные пустые страницы не обрывают поток. Повреждённая
|
||||
цепочка курсоров и нарушенный порядок дат возвращают ошибку без частичной страницы.
|
||||
|
||||
Курсор общего журнала — случайный непрозрачный идентификатор продолжения,
|
||||
привязанного к пользователю, поколению подключения, составу счетов, фильтрам
|
||||
и размеру страницы. Верхняя граница периода фиксируется первым запросом.
|
||||
Состояние ограничено 100 продолжениями на процесс со сроком жизни пять минут;
|
||||
повторный запрос того же продолжения возвращает тот же успешный результат.
|
||||
|
||||
Неизвестный, вытесненный, истёкший либо несовместимый курсор возвращает
|
||||
`400 INVALID_OPERATIONS_CURSOR`. Действие «Повторить» сбрасывает пагинацию,
|
||||
обновляет первую страницу и сохраняет фильтры. Перезапуск backend также
|
||||
инвалидирует продолжения. Раздел рассчитан на один экземпляр backend, как
|
||||
существующий финансовый кэш.
|
||||
|
||||
## Совместимость и границы требований
|
||||
|
||||
Общий режим добавляет маршруты, сохраняя маршруты отдельных счетов.
|
||||
При совместном обновлении сначала нужен backend, затем frontend с
|
||||
соответствующим сгенерированным контрактом; прежний клиент продолжает
|
||||
работать с отдельными счетами. При откате сначала возвращается клиент.
|
||||
Этот механизм не требует изменения схемы SQLite и миграции финансовых данных.
|
||||
|
||||
Основные спецификации [портфеля](../../../specs/openspec/specs/portfolio-view/spec.md),
|
||||
[операций](../../../specs/openspec/specs/brokerage-account-ledger/spec.md),
|
||||
[дохода](../../../specs/openspec/specs/brokerage-account-income-analytics/spec.md)
|
||||
и [событий](../../../specs/openspec/specs/brokerage-account-upcoming-events/spec.md)
|
||||
описывают отдельный счёт. Общий режим подтверждён действующими маршрутизаторами,
|
||||
сервисами и [проверками агрегации](../../../backend/src/tbank/all-accounts.spec.ts),
|
||||
но не выделен в самостоятельную основную спецификацию. Не следует считать
|
||||
описание общего режима изменением требований к отдельному счёту.
|
||||
@@ -0,0 +1,98 @@
|
||||
# Данные и показатели портфеля
|
||||
|
||||
Этот справочник объясняет значения снимка одного счёта. Он нужен при чтении
|
||||
экрана, API-ответа или разборе расхождения сумм. Для
|
||||
[общего портфеля](all-accounts.md) действуют те же денежные правила,
|
||||
но данные счетов получаются не одновременно.
|
||||
|
||||
## Точные значения и неизвестность
|
||||
|
||||
Деньги представлены значением Money: ISO-код валюты и десятичная строка,
|
||||
например `{ "currency": "RUB", "value": "12500.5" }` — условный пример.
|
||||
Количества также передаются десятичными строками. Backend выполняет
|
||||
десятичную арифметику без промежуточного `Number`, нормализует отрицательные
|
||||
целую и дробную части и убирает незначащие конечные нули. Экспоненциальной
|
||||
записи нет. Frontend форматирует полученные строки в русской локали без
|
||||
сокращения, округления сумм и потери присланных дробных цифр.
|
||||
|
||||
`null` означает, что показатель нельзя определить из доступных данных.
|
||||
Это не ноль. Известная нулевая цена отличается от отсутствующей;
|
||||
позиция с ненулевым количеством и неизвестной стоимостью остаётся в составе.
|
||||
Несколько валют никогда не складываются в одну общую стоимость.
|
||||
|
||||
## Состав и денежные показатели
|
||||
|
||||
| Понятие или показатель | Смысл и источник | Ограничение |
|
||||
| --- | --- | --- |
|
||||
| Позиция | Ненулевое количество обычного инструмента из `GetPortfolio.positions` | Виртуальные позиции не включаются; порядок источника сохраняется |
|
||||
| `quantity` | Количество из текущей позиции брокера | Допустимы дробные и отрицательные количества; это не число лотов |
|
||||
| `currentValue` | Количество × текущая цена из `GetPortfolio` | `null`, если цена или её валюта недоступны |
|
||||
| `averagePurchasePrice` | Средняя цена позиции, переданная брокером | Это цена единицы, а не сумма затрат или рассчитанная приложением история покупок |
|
||||
| `absoluteReturn` | Количество × (текущая цена − средняя цена) | Требует обеих цен в одной известной валюте; не включает продажи, купоны и дивиденды |
|
||||
| Cash Balance | Суммы из `GetPositions.money`, объединённые по валюте | Заблокированные деньги не прибавляются; торгуемый инструмент `CURRENCY` остаётся позицией |
|
||||
| `totalsByCurrency` | Стоимости позиций + доступные деньги той же валюты | Не включает невычислимую стоимость; не обещает полную оценку портфеля |
|
||||
| `lastSuccessfulAt` | Время формирования успешного снимка, UTC | Не является временем каждой котировки; запасной снимок сохраняет исходное время |
|
||||
| `isStale` | Backend вернул предыдущий полный снимок после временной ошибки | Не описывает состояние клиентского кэша или ошибку фонового запроса |
|
||||
|
||||
Ненулевые денежные остатки одной валюты суммируются. Позиции одинаковых
|
||||
инструментов в ответе отдельного счёта не объединяются; визуальная группировка
|
||||
между счетами описана [отдельно](all-accounts.md#данные-и-вычисления).
|
||||
Сводка уже включает Cash Balance: прибавление денег к ней второй раз неверно.
|
||||
|
||||
При отсутствии вычислимой стоимости у части позиций известный итог остаётся
|
||||
полезен, но представляет только оценённую часть. Известные нулевые итоги
|
||||
отдельного счёта не создают валютные карточки. Если `totalsByCurrency` пуст,
|
||||
но позиции или деньги присутствуют, это не пустой портфель.
|
||||
|
||||
## Идентификация и оформление инструмента
|
||||
|
||||
UID определяет конкретный инструмент брокера и используется для перехода
|
||||
на его страницу. FIGI, ISIN и тикер сохраняются как доступные идентификаторы;
|
||||
одно название или совпадение тикера не доказывает тождество инструментов.
|
||||
Для получения метаданных backend сначала использует UID, затем FIGI.
|
||||
Отображаемое имя выбирается в порядке: полное имя, тикер, FIGI.
|
||||
Если нет и FIGI, текущая реализация использует `Unknown instrument`.
|
||||
|
||||
ISIN, тикер и `logoName` могут быть `null`. `logoName` — имя файла бренда,
|
||||
а не URL: frontend строит ссылку на публичный CDN. Ошибка изображения
|
||||
или метаданных не меняет количество, суммы и свежесть снимка и не скрывает
|
||||
финансовые данные. Имя ожидается перед завершением нового снимка;
|
||||
ошибка поиска оставляет доступный идентификатор.
|
||||
|
||||
## Структура и доли
|
||||
|
||||
`structure` распределяет стоимость по четырём категориям: акции (`STOCK`),
|
||||
облигации (`BOND`), прочие инструменты (`OTHER`) и деньги (`CASH`).
|
||||
Торгуемые валютные инструменты входят в `OTHER`; денежные остатки — в `CASH`.
|
||||
Сервер строит структуру из позиций и денег, без повторного добавления
|
||||
`totalsByCurrency`.
|
||||
|
||||
Доля категории — её стоимость / известная стоимость позиций и денег
|
||||
в выбранной валюте × 100. Backend округляет процент до шести знаков;
|
||||
frontend показывает до сотых. Положительное значение меньше 0,01%
|
||||
отображается как «< 0,01 %», если серверное округление не превратило его в ноль.
|
||||
В геометрию диаграммы передаются только числовые проценты; суммы в списке
|
||||
и подсказке форматируются из исходных точных строк.
|
||||
|
||||
Доля на карточке отдельной позиции использует другой знаменатель:
|
||||
сумму стоимостей всех позиций той же валюты **без денег**. Это приблизительный
|
||||
показатель для отображения, рассчитанный frontend. Он не изменяет Money.
|
||||
В режиме всех счетов знаменатель охватывает все показанные счета.
|
||||
|
||||
| Состояние | Доступное представление |
|
||||
| --- | --- |
|
||||
| Все составляющие оценены, неотрицательны, итог положителен | Суммы, доли и кольцевая диаграмма выбранной валюты |
|
||||
| Есть позиция без оценки | `unvaluedCategories` содержит её категорию и количество; суммы известной части остаются, доли и кольцо недоступны для всех валют |
|
||||
| Есть отрицательная составляющая | Суммы со знаком остаются; доли и кольцо отключены для её валюты, даже если итог категории положителен |
|
||||
| Состав пуст или имеет нулевую стоимость | Текстовое состояние вместо кольца |
|
||||
|
||||
Неоценённой позиции нельзя достоверно назначить валюту по её стоимости,
|
||||
поэтому ограничение долей действует на все валюты. В общем портфеле проверка
|
||||
отрицательных денег выполняется по остаткам счетов до взаимозачёта.
|
||||
|
||||
Нормативные основы: [нормализованный состав](../../../specs/openspec/specs/portfolio-view/spec.md#requirement-нормализованный-состав-portfolio)
|
||||
и [точные значения и валютные агрегаты](../../../specs/openspec/specs/portfolio-view/spec.md#requirement-точные-значения-и-валютные-агрегаты).
|
||||
Структура и общий режим подтверждены реализацией и её проверками;
|
||||
отдельных требований к ним в основной спецификации портфеля пока нет.
|
||||
Поля транспорта следует сверять с [HTTP API](../../architecture/http-api.md),
|
||||
а свежесть — с [потоком загрузки](flows/portfolio-loading.md).
|
||||
@@ -0,0 +1,126 @@
|
||||
# Загрузка портфеля
|
||||
|
||||
Поток начинается при открытии `/accounts/:accountId` пользователем
|
||||
с действующей сессией. Его результат — полный нормализованный снимок
|
||||
доступного счёта либо объяснимая ошибка. Браузер обращается к backend;
|
||||
доступ к брокерскому токену и внешние запросы остаются на сервере.
|
||||
|
||||
## Основной обмен
|
||||
|
||||
Сначала frontend проверяет свой успешный кэш: в течение 30 секунд повторное
|
||||
открытие может показать ответ без нового HTTP-запроса. Следующая схема
|
||||
описывает запрос, который действительно дошёл до backend.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant F as Frontend
|
||||
participant B as Backend API
|
||||
participant D as SQLite
|
||||
participant T as T-Bank
|
||||
F->>B: Запрос портфеля счёта с Bearer token
|
||||
B->>B: Проверяет сессию
|
||||
B->>D: Читает текущее подключение пользователя
|
||||
D-->>B: Подключение либо отсутствие
|
||||
alt Подключения нет
|
||||
B-->>F: 409 TBANK_NOT_CONNECTED
|
||||
else Подключение доступно
|
||||
B->>B: Определяет поколение подключения
|
||||
alt Снимок того же поколения не старше 30 секунд
|
||||
B-->>F: Полный снимок, isStale=false
|
||||
else Нужен новый снимок
|
||||
B->>T: Получает открытые счета
|
||||
T-->>B: Состав доступных счетов
|
||||
alt Счёт недоступен
|
||||
B-->>F: 404
|
||||
else Счёт доступен
|
||||
par Финансовые позиции
|
||||
B->>T: GetPortfolio
|
||||
T-->>B: Позиции и цены
|
||||
and Доступные деньги
|
||||
B->>T: GetPositions
|
||||
T-->>B: Денежные остатки
|
||||
end
|
||||
B->>T: Метаданные отсутствующих инструментов по UID, затем FIGI
|
||||
T-->>B: Метаданные, отсутствие или ошибка
|
||||
B->>B: Нормализует состав и точно вычисляет суммы
|
||||
B-->>F: Полный новый снимок, isStale=false
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Свежий серверный снимок выдаётся после разрешения текущего сохранённого
|
||||
подключения, но без нового `GetAccounts`. После истечения свежего кэша
|
||||
проверка доступности счёта предшествует финансовым RPC. SQLite хранит
|
||||
подключение, а не снимок или право владения счётом вне этого подключения.
|
||||
|
||||
Для нового снимка требуются оба финансовых ответа. Backend не выдаёт
|
||||
позиции без денег или деньги без позиций как полный портфель.
|
||||
Метаданные дополняют имя и идентификаторы, но их ошибка не срывает
|
||||
финансовый результат. Общий серверный клиент обеспечивает TLS, очередь,
|
||||
ограничение частоты, deadline RPC и разрешённые повторные попытки —
|
||||
[граница интеграции](../../../integrations/tbank.md).
|
||||
|
||||
## Время и изменение подключения
|
||||
|
||||
| Слой | Что сохраняется | Условие повторного использования |
|
||||
| --- | --- | --- |
|
||||
| Frontend | Последний успешный HTTP-ответ для выбранного счёта и сессии | Свежим считается 30 секунд; при обычном обновлении уже показанный ответ остаётся видимым |
|
||||
| Backend | Полный снимок пары пользователь–счёт в памяти процесса | До 30 секунд, только в том же поколении текущего подключения |
|
||||
| Backend при временной ошибке | Тот же полный снимок | Не старше пяти минут с момента успешного сохранения, только в том же поколении |
|
||||
| Метаданные инструмента | Успешный поиск или подтверждённое отсутствие | До 24 часов; общий ограниченный кэш на 500 ключей UID/FIGI, ошибки не сохраняются |
|
||||
|
||||
Это параметры текущей реализации, а не самостоятельная политика этого
|
||||
документа: [снимки](../../../../backend/src/tbank/portfolio.service.ts),
|
||||
[метаданные](../../../../backend/src/tbank/instrument-metadata.service.ts) и
|
||||
[клиентский запрос](../../../../frontend/src/entities/portfolio/api/usePortfolio.ts).
|
||||
|
||||
Пять минут — предельный возраст успешного снимка, а не дополнительные
|
||||
пять минут после окончания свежих 30 секунд. Запасной ответ сохраняет
|
||||
исходное `lastSuccessfulAt` и получает `isStale: true`.
|
||||
|
||||
Успешное создание, замена или удаление подключения меняет границу
|
||||
повторного использования финансовых данных. Старое или поздно завершившееся
|
||||
получение снимка не делает его доступным новым запросам другого поколения.
|
||||
Явная очистка памяти при изменении подключения дополняет эту проверку.
|
||||
Frontend сбрасывает всё поддерево финансовых запросов при изменении
|
||||
подключения и общий кэш сессии при выходе; старый портфель не служит
|
||||
заглушкой загрузки нового подключения.
|
||||
|
||||
В общем режиме сначала определяется состав доступных счетов одного
|
||||
подключения, затем получаются их снимки. Это
|
||||
[не атомарное наблюдение нескольких счетов](../all-accounts.md#полнота-свежесть-и-доступ).
|
||||
|
||||
## Ошибки и результат для пользователя
|
||||
|
||||
| Причина | Ответ backend | Действие на экране |
|
||||
| --- | --- | --- |
|
||||
| Нет подключения | `409 TBANK_NOT_CONNECTED` | Открыть настройки T-Bank |
|
||||
| Брокер отклонил токен | `400 INVALID_TBANK_TOKEN` | Открыть настройки; токен автоматически не удаляется |
|
||||
| Счёт недоступен через текущее подключение | `404` | Вернуться к списку счетов |
|
||||
| Временная ошибка, пригодный предыдущий снимок есть | `200`, `isStale: true` | Показать снимок и его исходное время с предупреждением |
|
||||
| Временная ошибка, пригодного снимка нет | `503 TBANK_UNAVAILABLE` | Повторить загрузку |
|
||||
| Неизвестная ошибка | Безопасная ошибка API | Повторить загрузку |
|
||||
|
||||
`INVALID_TBANK_TOKEN` и `404` не заменяются серверным запасным снимком.
|
||||
Временная ошибка определения доступных счетов также может использовать
|
||||
пригодный снимок того же поколения. Сбой метаданных использует идентификатор
|
||||
инструмента и не устанавливает `isStale`.
|
||||
|
||||
На клиенте нет polling, автоматического повтора неуспешного запроса или
|
||||
обновления при возврате фокуса. При обновлении того же уже показанного
|
||||
результата клиент оставляет его на экране, добавляя индикатор загрузки
|
||||
или предупреждение с доступной с клавиатуры повторной попыткой.
|
||||
Это клиентский сохранённый ответ, независимо от серверного `isStale`.
|
||||
|
||||
Первое обращение к крупному портфелю может потребовать много метаданных.
|
||||
Переполнение общей очереди RPC означает временную недоступность конкретного
|
||||
поиска. После успешного поиска метаданные используются повторно до истечения
|
||||
кэша. Значение `TBANK_RATE_LIMIT_PER_SECOND` следует соотносить с квотой
|
||||
источника; его увеличение не является гарантированным исправлением
|
||||
ограничения самого T-Bank.
|
||||
|
||||
Проверяемые правила: [свежесть и запасной снимок](../../../../specs/openspec/specs/portfolio-view/spec.md#requirement-свежесть-и-fallback-полного-portfolio-snapshot),
|
||||
[клиентские состояния](../../../../specs/openspec/specs/portfolio-view/spec.md#requirement-состояния-и-ошибки-portfolio-на-клиенте).
|
||||
Для расследования конкретного значения используйте
|
||||
[порядок диагностики](../how-to/investigate-discrepancy.md).
|
||||
@@ -0,0 +1,56 @@
|
||||
# Разобрать расхождение суммы портфеля
|
||||
|
||||
Используйте этот порядок, когда итог, позиция или доля на экране отличаются
|
||||
от ожидаемого значения. Он помогает определить слой расхождения без изменения
|
||||
данных. Нужны адрес экрана и уже доступный ответ API либо синтетический пример
|
||||
из существующей проверки. Реальные токены, персональные выгрузки и содержимое
|
||||
базы для разбора не нужны; не переносите их в документацию или отчёт.
|
||||
|
||||
1. **Определите предмет сравнения.** Запишите, открыт отдельный счёт или
|
||||
«Все счета», какая валюта и какой показатель расходятся. Текущая стоимость,
|
||||
свободные деньги, результат открытой позиции и полученный доход — разные
|
||||
величины. Убедитесь, что сравниваемые значения относятся к одному составу
|
||||
счетов и инструменту. Название или тикер сами по себе не доказывают
|
||||
совпадение выпуска; используйте доступные UID, FIGI и ISIN.
|
||||
2. **Сопоставьте время.** Проверьте `lastSuccessfulAt`, `isStale` и наличие
|
||||
предупреждения о клиентском обновлении. Серверный снимок не обещает время
|
||||
каждой котировки, а общий портфель может соединять разные моменты.
|
||||
Если запрос завершился ошибкой, доступное действие «Повторить» должно
|
||||
обновить результат или оставить объяснимую ошибку. Перезагрузка backend
|
||||
удаляет кэш и не восстанавливает историю стоимости.
|
||||
3. **Проверьте состав суммы.** Для одной валюты итог складывается из
|
||||
вычислимых стоимостей позиций и доступных денег. Не добавляйте деньги
|
||||
второй раз, не складывайте валюты и не включайте заблокированные средства.
|
||||
Ненулевая позиция с `currentValue: null` делает оценку неполной, но не
|
||||
даёт нулевой вклад в смысле полной стоимости. `CURRENCY` участвует
|
||||
в суммах, хотя текущий интерфейс скрывает её карточку —
|
||||
[известное расхождение со спецификацией](../overview.md#как-читать-экран).
|
||||
4. **Проверьте показатель по его определению.** Используйте
|
||||
[справочник](../data-reference.md#состав-и-денежные-показатели).
|
||||
Условный пример: 2 бумаги по 100 RUB и 50 RUB свободных денег дают
|
||||
250 RUB стоимости. Доля позиции среди позиций равна 100%, а доля
|
||||
соответствующей категории в структуре с деньгами — 80%.
|
||||
Купоны и дивиденды не включаются в результат открытой позиции.
|
||||
Для проверки десятичных сумм сохраняйте исходные строки;
|
||||
арифметика с плавающей точкой может сама создать расхождение.
|
||||
5. **Найдите ответственную границу.** Если ответ API корректен, а экран
|
||||
теряет цифры, валюту или показатель, исследуйте frontend.
|
||||
Если исходные безопасные данные и нормализованный ответ расходятся,
|
||||
исследуйте backend. Если не совпадают ожидаемое и описанное поведение,
|
||||
сначала сопоставьте [спецификацию портфеля](../../../../specs/openspec/specs/portfolio-view/spec.md)
|
||||
и ограничения [потока загрузки](../flows/portfolio-loading.md).
|
||||
Исправление документации само по себе не разрешает конфликт требований
|
||||
и реализации.
|
||||
|
||||
Для адресной проверки исходников владельцами служат
|
||||
[сервис снимков](../../../../backend/src/tbank/portfolio.service.ts),
|
||||
[валютные итоги](../../../../backend/src/tbank/portfolio-totals.ts),
|
||||
[структура](../../../../backend/src/tbank/portfolio-structure.ts) и
|
||||
[подготовка экрана](../../../../frontend/src/pages/account/ui/AccountPage.vm.ts).
|
||||
Это точки продолжения расследования; смысл вычислений изложен выше.
|
||||
|
||||
Разбор завершён, когда различие объяснено составом, временем или определением
|
||||
показателя либо получен воспроизводимый пример дефекта с ожидаемым и
|
||||
фактическим результатом. В последнем случае сохраните минимальный синтетический
|
||||
пример и адрес ответственного слоя. Ошибки источника и неизвестные значения
|
||||
не исправляются подстановкой нулей или обходом проверки доступа.
|
||||
@@ -0,0 +1,99 @@
|
||||
# Портфель
|
||||
|
||||
Портфель показывает, какими активами и доступными деньгами располагает
|
||||
брокерский счёт по последнему успешному наблюдению T-Bank. Это снимок состава
|
||||
и его текущей оценки, а не локальная история владения или расчёт полной
|
||||
инвестиционной доходности. Пользователь открывает его на
|
||||
`/accounts/:accountId`; [режим «Все счета»](all-accounts.md) объединяет
|
||||
доступные счета текущего подключения.
|
||||
|
||||
## Снимок и связанные области
|
||||
|
||||
Снимок состоит из позиций по инструментам, доступных денежных остатков,
|
||||
валютных итогов и структуры активов. Позиция означает ненулевое количество
|
||||
обычного инструмента в брокерском портфеле. Торгуемая валютная позиция
|
||||
`CURRENCY` и свободные деньги имеют разный смысл и передаются раздельно.
|
||||
Точный смысл сумм и неизвестных значений раскрыт в
|
||||
[справочнике данных](data-reference.md).
|
||||
|
||||
Доступность счёта определяется [текущим подключением и списком счетов](../accounts/overview.md).
|
||||
SQLite не хранит сам портфель: backend получает данные через серверную
|
||||
[интеграцию T-Bank](../../integrations/tbank.md) и временно сохраняет успешный
|
||||
снимок в памяти. Frontend получает нормализованный результат через HTTP API.
|
||||
[Поток загрузки](flows/portfolio-loading.md) объясняет границы доступа,
|
||||
свежий кэш и восстановление после временной ошибки.
|
||||
|
||||
Обзор счёта также показывает пять последних [операций](../operations/overview.md),
|
||||
[полученный доход](../income/overview.md) текущего месяца и до пяти
|
||||
[будущих событий](../income/upcoming-events.md). Каждый блок загружается
|
||||
самостоятельно: его ошибка или отсутствие данных не скрывает основной
|
||||
портфель. Полученные выплаты и прогноз не добавляются к текущей стоимости
|
||||
повторно и не превращают результат открытой позиции в полную доходность.
|
||||
|
||||
## Как читать экран
|
||||
|
||||
Валютная сводка показывает отдельную карточку для каждого полученного итога.
|
||||
Деньги уже включены в итог своей валюты. RUB, USD и EUR обозначены ₽, $ и €;
|
||||
другие валюты сохраняют ISO-код. Вспомогательным технологиям доступны полное
|
||||
значение и название валюты. Суммы сохраняют все исходные цифры, знак и
|
||||
дробные позиции, включая `-0,5`; длинные значения переносятся без обрезания.
|
||||
Карточки не интерактивны и занимают одну колонку на узком экране,
|
||||
две или три на широком. Отсутствующая валюта не получает искусственную
|
||||
нулевую карточку. Пустые итоги не скрывают доступный состав портфеля.
|
||||
|
||||
Структура после сводки распределяет известную стоимость по акциям,
|
||||
облигациям, другим активам и свободным деньгам. При нескольких валютах
|
||||
пользователь выбирает одну; при одной отдельного переключателя нет.
|
||||
Кольцевая диаграмма дополнена текстовым списком сумм и долей и подсказкой;
|
||||
на узком экране список расположен под ней. Неизвестная оценка, отрицательные
|
||||
составляющие или нулевой итог ограничивают возможность показать доли —
|
||||
[почему кольцо бывает недоступно](data-reference.md#структура-и-доли).
|
||||
|
||||
Акции и облигации показаны отдельными секциями, по одной карточке в ряду
|
||||
при любой ширине. Внутри сохраняется порядок источника. Слева находятся
|
||||
название, логотип, количество и идентификатор, справа — стоимость и результат.
|
||||
Средняя цена и доля размещены ниже. Логотип размером 40 px при недоступности
|
||||
изображения заменяется первой буквой имени. Акция показывает тикер, а при его
|
||||
отсутствии ISIN; облигация — ISIN. Название и логотип ведут на страницы
|
||||
[акции](../instruments/stocks.md) или [облигации](../instruments/bonds.md),
|
||||
когда доступен точный UID инструмента.
|
||||
|
||||
Результат открытой позиции имеет явный знак: плюс при положительном,
|
||||
минус при отрицательном, без знака при нуле. Цвет дополняет знак.
|
||||
Иные отображаемые активы используют компактные плитки в адаптивной сетке
|
||||
без логотипа и с подписью результата. Отсутствующие показатели скрываются,
|
||||
а не подменяются нулём или заглушкой.
|
||||
|
||||
Существует расхождение: [согласованная спецификация представления позиций](../../../specs/openspec/specs/portfolio-view/spec.md#requirement-представление-карточек-позиций-на-клиенте)
|
||||
требует доступности всех иных типов в отдельной секции, а текущие frontend
|
||||
и его проверка исключают `CURRENCY` только из списка карточек. Такие позиции
|
||||
остаются в ответе, валютных итогах, структуре и знаменателе долей остальных
|
||||
позиций. Это описание фактического ограничения; выбор между изменением
|
||||
интерфейса и согласованием требований остаётся отдельной задачей.
|
||||
|
||||
## Свежесть и границы
|
||||
|
||||
При первом запросе экран показывает каркас заголовка, сводки и разделов.
|
||||
Три формы валютных карточек в каркасе не обещают наличие трёх валют.
|
||||
Загрузка обозначена для вспомогательных технологий; декоративные элементы
|
||||
не содержат данных и не получают фокус. При обычном фоновом обновлении
|
||||
сохраняются предыдущие суммы и время успешного получения в UTC.
|
||||
|
||||
Серверный `isStale` обозначает разрешённый запасной снимок backend.
|
||||
Клиентское предупреждение означает неудачную попытку обновить уже показанный
|
||||
ответ. Эти состояния независимы и могут появиться одновременно.
|
||||
Успешный повторный запрос обновляет суммы и время, снимает клиентскую ошибку;
|
||||
серверное предупреждение определяется новым `isStale`.
|
||||
Правила времени и ошибок приведены в [потоке загрузки](flows/portfolio-loading.md).
|
||||
|
||||
В текущем решении нет долговременной истории снимков, графиков стоимости
|
||||
портфеля, валютной конвертации, учёта виртуальных позиций или заблокированных
|
||||
средств. Кэш теряется при перезапуске и рассчитан на локальную работу одного
|
||||
экземпляра backend. Отдельной команды принудительного обновления на экране
|
||||
нет; повторная попытка доступна после ошибки.
|
||||
|
||||
Для проверки конкретной суммы используйте
|
||||
[руководство по расхождениям](how-to/investigate-discrepancy.md).
|
||||
Проверяемое поведение отдельного счёта описано в
|
||||
[спецификации портфеля](../../../specs/openspec/specs/portfolio-view/spec.md);
|
||||
точную форму HTTP-ответа задаёт [OpenAPI backend](../../architecture/http-api.md).
|
||||
Reference in New Issue
Block a user