From e0b54aa2bb5aa007eb91aef28affb68da4ec83bf Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Wed, 11 Nov 2026 08:22:39 +0300 Subject: [PATCH 1/2] docs: describe RUB-only behavior (WealthLedger/main#55) --- architecture/http-api.md | 3 +- design/overview.md | 2 +- domains/accounts/overview.md | 10 +++--- domains/income/overview.md | 14 ++++---- domains/income/upcoming-events.md | 8 ++--- domains/instruments/bonds.md | 8 ++--- domains/instruments/stocks.md | 6 ++-- domains/operations/overview.md | 6 ++-- domains/portfolio/all-accounts.md | 11 +++--- domains/portfolio/concentration.md | 17 ++-------- domains/portfolio/data-reference.md | 34 +++++++++---------- .../how-to/investigate-discrepancy.md | 8 ++--- domains/portfolio/investment-performance.md | 1 - domains/portfolio/overview.md | 31 ++++++++--------- glossary.md | 19 +++++------ integrations/tbank.md | 2 +- overview.md | 8 ++--- 17 files changed, 86 insertions(+), 102 deletions(-) diff --git a/architecture/http-api.md b/architecture/http-api.md index ce8b9a7..a59000c 100644 --- a/architecture/http-api.md +++ b/architecture/http-api.md @@ -64,8 +64,7 @@ Envelope несовместим с прежними bare-array ответами: ## Income analytics `GET /api/v1/brokerage-accounts/{accountId}/income-analytics` и общий маршрут -`all` возвращают результат только выбранного периода: итоги по исходным -валютам, точные записи выплат, разбиение по инструментам и временные +`all` возвращают результат только выбранного периода: итоги в RUB, точные записи выплат, разбиение по инструментам и временные интервалы. Данных сравнения с предыдущим периодом или годом ранее в ответе нет — ни в теле, ни среди зависимостей `meta.dependencies`, роль которых ограничена значением `selected`. Публичную машинную схему задаёт diff --git a/design/overview.md b/design/overview.md index 07bcbf5..60be5d9 100644 --- a/design/overview.md +++ b/design/overview.md @@ -66,7 +66,7 @@ compatibility re-exports `shared/theme` и `shared/ui` удалены. Фоновое обновление сохраняет доступные данные с локальным индикатором. Подробные правила — в [состояниях интерфейса](../architecture/cross-cutting/ui-resilience.md). -Финансовые значения сохраняют валюту, знак и различие между нулём и +Финансовые значения сохраняют валюту (RUB), знак и различие между нулём и неизвестной суммой. Предметные представления раскрыты в [портфеле](../domains/portfolio/overview.md), [операциях](../domains/operations/overview.md) и [банковских картах](../domains/cards/overview.md). diff --git a/domains/accounts/overview.md b/domains/accounts/overview.md index 3c73766..71ab9a9 100644 --- a/domains/accounts/overview.md +++ b/domains/accounts/overview.md @@ -33,12 +33,12 @@ | Состояние `portfolioSummary` | Что оно означает | | --- | --- | -| Доступные валютные итоги | Текущие стоимости позиций и деньги сложены точно в исходной валюте | -| Доступная сводка с пустыми итогами | Портфель получен, но валютных итогов нет; это не ошибка загрузки | +| Доступный итог | Текущие стоимости позиций и деньги сложены точно в RUB | +| Доступная сводка без итога | Портфель получен, но итоговой суммы нет; это не ошибка загрузки | | `null` | Финансовая сводка этого счёта временно недоступна; метаданные и переход на счёт сохраняются | -Валюты не конвертируются и не объединяются в общую сумму. Деньги уже входят -в валютный итог; они не образуют второй самостоятельный итог. Правила +Позиции и деньги в валютах, отличных от RUB, не участвуют в итоге. Деньги уже входят +в итог; они не образуют второй самостоятельный итог. Правила неизвестных стоимостей совпадают со [справочником портфеля](../portfolio/data-reference.md). Сводка также сообщает число ненулевых обычных позиций акций и облигаций @@ -66,7 +66,7 @@ - Неизвестная ошибка: то же состояние недоступности без действия повтора. - Успешный пустой ответ: штатное состояние отсутствия инвестиционных счетов. -Карточка ведёт на `/accounts/:accountId` по всей области и доступна с клавиатуры. Карточки расположены в одну колонку при любой ширине. Карточка показывает название, локализованный тип, крупный валютный итог и вторую валюту при наличии, число остальных валют, дату открытия без времени и видимое действие открытия. Отдельные цветные блоки «Акции» и «Облигации» показывают количество наименований бумаг. Недоступная или пустая сводка отображается локально; отсутствие сумм не скрывает известные количества бумаг, а недоступная сводка не подменяет их нулями. Наведение и видимый клавиатурный фокус подчёркивают переход; настройка уменьшения движения отключает анимацию. +Карточка ведёт на `/accounts/:accountId` по всей области и доступна с клавиатуры. Карточки расположены в одну колонку при любой ширине. Карточка показывает название, локализованный тип, крупный итог в RUB, дату открытия без времени и видимое действие открытия. Отдельные цветные блоки «Акции» и «Облигации» показывают количество наименований бумаг. Недоступная или пустая сводка отображается локально; отсутствие сумм не скрывает известные количества бумаг, а недоступная сводка не подменяет их нулями. Наведение и видимый клавиатурный фокус подчёркивают переход; настройка уменьшения движения отключает анимацию. ## Свежесть и границы diff --git a/domains/income/overview.md b/domains/income/overview.md index 67fdac9..1024db4 100644 --- a/domains/income/overview.md +++ b/domains/income/overview.md @@ -4,7 +4,7 @@ купонами и дивидендами за выбранный период». Пользователь открывает `/accounts/:accountId/income` из обзора счёта или [общий доход](../portfolio/all-accounts.md) по доступным счетам. -Экран показывает итоги по валютам и отдельную карточку каждой +Экран показывает итоги в RUB и отдельную карточку каждой включённой выплаты. Источником служат [исполненные операции](../operations/overview.md), а не изменение цены портфеля и не календарь будущих выплат. @@ -51,10 +51,10 @@ ## Какие выплаты входят в результат - Backend проверяет доступ локально и читает сохранённые операции. Фоновая загрузка должна завершить все страницы; ошибка страницы или повреждённая цепочка курсоров не подтверждают полноту. -- Только точные типы купона и дивиденда источника формируют результат. Суммы остаются точными десятичными строками в исходной валюте; отрицательные корректировки сохраняют знак. Налоги, комиссии, сделки, переводы, погашения и переводы дивидендов не входят в доход. -- Итоги, временные интервалы и разбиение по инструментам не смешивают валюты и не создают общий конвертированный итог. Система не загружает другие периоды ради сравнения полученного дохода с выбранным. +- Только точные типы купона и дивиденда источника формируют результат. Суммы остаются точными десятичными строками в RUB; отрицательные корректировки сохраняют знак. Выплаты в других валютах не участвуют. Налоги, комиссии, сделки, переводы, погашения и переводы дивидендов не входят в доход. +- Итоги, временные интервалы и разбиение по инструментам считаются только в RUB. Система не загружает другие периоды ради сравнения полученного дохода с выбранным. - Временные интервалы по-прежнему приходят с backend: дни для 1–45 дней, недели для 46–180 и месяцы для более длинного периода, включая пустые интервалы. Страница дохода не строит из них график или текстовую динамику; ответ API не меняется. -- Раздел «Выплаты» показывает карточки выплат в исходном порядке `entries`. В отдельном счёте каждая выплата — отдельная карточка. В режиме «Все счета» выплаты одного инструмента, дня и точного типа образуют группу, если их две и более ([правила группировки](../operations/overview.md#группировка-в-режиме-все-счета)). Группа стоит на месте первой выплаты; каждая строка счёта показывает название счёта и свою сумму со знаком. Визуальный итог группы — точная сумма по валютам по показанным выплатам; он скрыт, если полнота периода не `complete`, или у выплаты нет суммы, или сумма равна нулю. Это клиентский расчёт и не заменяет серверные итоги аналитики дохода, которые по-прежнему приходят из ответа API и не пересчитываются. Описание, дата, доступные идентификаторы инструмента и сумма со знаком видны в каждой выплате. Дополнительный запрос за составом не нужен. +- Раздел «Выплаты» показывает карточки выплат в исходном порядке `entries`. В отдельном счёте каждая выплата — отдельная карточка. В режиме «Все счета» выплаты одного инструмента, дня и точного типа образуют группу, если их две и более ([правила группировки](../operations/overview.md#группировка-в-режиме-все-счета)). Группа стоит на месте первой выплаты; каждая строка счёта показывает название счёта и свою сумму со знаком. Визуальный итог группы — точная сумма в RUB по показанным выплатам; он скрыт, если полнота периода не `complete`, или у выплаты нет суммы, или сумма равна нулю. Это клиентский расчёт и не заменяет серверные итоги аналитики дохода, которые по-прежнему приходят из ответа API и не пересчитываются. Описание, дата, доступные идентификаторы инструмента и сумма со знаком видны в каждой выплате. Дополнительный запрос за составом не нужен. ## Полнота и принадлежность выплат @@ -71,11 +71,11 @@ Каждая запись `income-analytics.entries` содержит обязательный `instrumentKey`, равный `key` соответствующей строки `instruments`. Backend вычисляет этот ключ один раз при подготовке записей дохода и использует его же для группировки. -Приоритет UID, FIGI, ticker/type и ID операции с разделением по валюте остаётся +Приоритет UID, FIGI, ticker/type и ID операции остаётся серверным правилом; клиент воспринимает ключ как непрозрачную строку. Это поле принадлежит ответу аналитики; общий журнал операций его не требует. -Backend обеспечивает принадлежность выплат, валюту и receipt count при +Backend обеспечивает принадлежность выплат и receipt count при построении entries и instrument groups из одного набора данных. Frontend доверяет типизированному contract, не восстанавливает формулу идентификации и не выполняет второй consistency validator. Полностью пустой ответ допустим. @@ -94,7 +94,7 @@ Backend обеспечивает принадлежность выплат, ва выбранного счёта и сохраняет прежнее поведение. Будущие выплаты не включаются в итоги полученного дохода. -Аналитика читает сохранённую историю операций и не рассчитывает налоги, доходность (её показывает [результат инвестирования](../portfolio/investment-performance.md)), валютную конвертацию или общий мультивалютный итог. +Аналитика читает сохранённую историю операций и не рассчитывает налоги, доходность (её показывает [результат инвестирования](../portfolio/investment-performance.md)), или итоги по нескольким валютам. Основное проверяемое поведение — в [спецификации дохода](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-income-analytics/spec.md), точная форма ответа — в [HTTP API](../../architecture/http-api.md). diff --git a/domains/income/upcoming-events.md b/domains/income/upcoming-events.md index 76ae07b..dfee29b 100644 --- a/domains/income/upcoming-events.md +++ b/domains/income/upcoming-events.md @@ -56,8 +56,8 @@ «сумма неизвестна» и не превращается в ноль или полученные деньги. Дата дивиденда обозначена как дата реестра. Offer и погашение сохраняют своё значение без суммы выплаты. У каждой строки видны тип, -инструмент, дата и доступная сумма в исходной валюте. Список не имеет -пагинации и не конвертирует валюты. Пустой результат означает отсутствие +инструмент, дата и доступная сумма в RUB. Список не имеет +пагинации. Пустой результат означает отсутствие известных поддерживаемых событий в выбранном окне, а не доказательство отсутствия всех будущих выплат. Инструмент без поддерживаемых данных не получает искусственную строку «нет данных». @@ -70,7 +70,7 @@ несуществующая дата дают `400`; обратный период — `400 INVALID_EVENTS_PERIOD` без замены периода и без запуска личной работы. Фильтрация на сервере предшествует сортировке и вычислению -`incomeTotalsByCurrency`, включающего только купоны и дивиденды. +итога прогноза в RUB, включающего только купоны и дивиденды. Итоги остаются в ответе API; полный экран показывает список без общего итога списка. В режиме «Все счета» события одного инструмента, даты, точного типа и `dateKind` образуют @@ -78,7 +78,7 @@ поэтому дата реестра дивиденда и иная дата того же инструмента не объединяются. Группа стоит на месте первого события. Полный список группирует все полученные события; превью обзора сначала берёт пять событий и группирует только их. Каждая строка счёта остаётся отдельной и показывает название -счёта и свою прогнозную сумму. Визуальный итог группы — точная сумма по валютам; он показывается +счёта и свою прогнозную сумму. Визуальный итог группы — точная сумма в RUB; он показывается только если полнота прогноза `complete` и у каждого события группы известна прогнозная сумма. Иначе итог скрыт, а строки остаются. Этот итог не равен итогам ответа API и не относится к полученному доходу. diff --git a/domains/instruments/bonds.md b/domains/instruments/bonds.md index 340a25d..5b299e8 100644 --- a/domains/instruments/bonds.md +++ b/domains/instruments/bonds.md @@ -31,9 +31,9 @@ завершённой дневной свечи до московской даты котировки; база ищется за 20 дней. Денежная оценка без НКД равна цене в процентах, умноженной на текущий номинал и делённой на 100. Она округляется до девяти десятичных знаков. Оценка с НКД -добавляет текущий накопленный купонный доход только при совпадении валют. +добавляет текущий накопленный купонный доход. Это ориентир без комиссии: котировка может быть старше текущего номинала и НКД. -FX-конвертация не выполняется, первоначальный номинал не заменяет текущий. +Первоначальный номинал не заменяет текущий. Условия выпуска: текущий и первоначальный номинал, погашение, оферта, количество купонов в год, лот. Отдельно отмечены плавающий купон, @@ -49,7 +49,7 @@ FX-конвертация не выполняется, первоначальн Текущая купонная доходность — ближайший купон × 365 / длительность его периода в днях / денежная цена без НКД × 100 %. Decimal-расчёт округляется до четырёх знаков. Требуются известная неотрицательная выплата, положительные цена и -период и одинаковая валюта выплаты и цены. Это годовая оценка при сохранении +период. Это годовая оценка при сохранении размера купона и номинала, без налогов, комиссий и реинвестирования; она не является доходностью к погашению. Время котировки показывается отдельно. @@ -79,7 +79,7 @@ HTML-источник, общий кэш и поведение при измен Позиция агрегирует только `BOND` с точным UID по доступным счетам, используя [снимок портфеля](../portfolio/overview.md) и decimal-арифметику. Средняя покупка взвешена -по количеству. Неполные или разновалютные суммы недоступны; общая средняя +по количеству. Неполные суммы недоступны; общая средняя и процент доступны только при положительных включённых позициях. Для коротких позиций и сочетания длинных с короткими эти показатели не синтезируются. Результат открытой позиции не включает выплаченные купоны и продажи и не diff --git a/domains/instruments/stocks.md b/domains/instruments/stocks.md index fd4cd54..9741a3c 100644 --- a/domains/instruments/stocks.md +++ b/domains/instruments/stocks.md @@ -30,7 +30,7 @@ Раздел «Акции» в боковой и мобильной навигации ведёт на `/stocks`: скринер запускается кнопкой «Найти» с необязательным названием или тикером, сектором, -типом акции, валютой расчётов, площадкой, страной риска и максимальным размером +типом акции, площадкой, страной риска и максимальным размером лота. Он использует полный полученный каталог и показывает только акции, для которых каталог подтверждает доступность покупки и торговли через API. Это не гарантирует, что конкретная заявка пользователя будет исполнена прямо сейчас. @@ -90,10 +90,10 @@ UID включён в ключ кэша и подпись общего курс восстанавливается переходом на первую страницу с повторным запросом. [Портфель](../portfolio/overview.md) остаётся владельцем снимков и оценки позиций. Сводка -фильтрует `STOCK` по точному UID и использует точную десятичную арифметику, без конвертации валют. +фильтрует `STOCK` по точному UID и использует точную десятичную арифметику, в RUB. Средняя цена равна сумме количеств, умноженных на средние цены, делённой на общее количество; она округляется сервером до девяти знаков, проценты — до шести. -Неполные или разновалютные денежные итоги недоступны. Общая средняя и процент доступны только если все включённые позиции +Неполные денежные итоги недоступны. Общая средняя и процент доступны только если все включённые позиции положительны; для нулевого количества, коротких позиций и сочетания длинных с короткими они не синтезируются. Результат открытой позиции исключает продажи и дивиденды и не называется полной diff --git a/domains/operations/overview.md b/domains/operations/overview.md index 88dd087..042bdf7 100644 --- a/domains/operations/overview.md +++ b/domains/operations/overview.md @@ -13,7 +13,7 @@ Исполненная операция содержит тип события, его категорию, описание, момент совершения, доступные сведения об инструменте и денежное движение. Запросы отменённых или ожидающих исполнения поручений не образуют этот журнал. -Суммы остаются точными десятичными строками в исходной валюте. Frontend +Суммы остаются точными десятичными строками в RUB. Frontend форматирует их и считает визуальный итог группы по показанным записям (см. [группировку](#группировка-в-режиме-все-счета)). Положительное и отрицательное движение сохраняют знак. Отсутствие `payment` означает «Без движения денег», а не нулевую сумму. @@ -31,7 +31,7 @@ затем группирует только их. Полный журнал группирует страницу, уже загруженную курсором; группы не переносятся между страницами. Курсор и порядок записей не меняются. -Итог группы — точная сумма по каждой валюте, без конвертации. Он показывается только +Итог группы — точная сумма в RUB. Он показывается только если полнота журнала `complete`, у каждой записи группы есть `payment`, и ни одна сумма не равна нулю. Иначе итог скрыт, а строки счетов остаются. @@ -48,7 +48,7 @@ но их недоступность не меняет финансовое событие. Журнал не вычисляет остаток после операции, историческую стоимость портфеля, -совокупный налог или валютную конвертацию. Изменение рыночной цены не становится +совокупный налог. Изменение рыночной цены не становится операцией. Переводы между счетами не схлопываются в одну запись; одинаковые ID операций разных счетов не означают дубликат. diff --git a/domains/portfolio/all-accounts.md b/domains/portfolio/all-accounts.md index 6039389..b158699 100644 --- a/domains/portfolio/all-accounts.md +++ b/domains/portfolio/all-accounts.md @@ -30,12 +30,13 @@ Backend публикует статические маршруты под `/api/ У них те же формы данных и фильтры, что у соответствующих разделов отдельного счёта. Позиции, операции (включая выплаты дохода) и события сохраняют источник в поле `account: { id, name }`; в ответах одного счёта это поле отсутствует. -Валютные итоги и деньги агрегированы. Точную форму транспорта задаёт +Итоги и деньги агрегированы в RUB. Точную форму транспорта задаёт [OpenAPI backend](../../architecture/http-api.md). -- Портфель и доступные денежные остатки суммируются сервером точно, по исходной - валюте, с точной десятичной арифметикой. FX-конвертации нет; правила - неизвестных значений и состава сумм наследуются от [портфеля](data-reference.md). +- Портфель и доступные денежные остатки суммируются сервером точно, в RUB, + с точной десятичной арифметикой. Позиции и деньги в других валютах не + участвуют; правила неизвестных значений и состава сумм наследуются от + [портфеля](data-reference.md). - Одинаковые бумаги разных счетов показаны одной карточкой с общим названием и идентификатором. Внутри всегда видны строки счетов: ссылка на портфель, количество, доступная стоимость, результат, средняя цена и доля позиции. @@ -48,7 +49,7 @@ Backend публикует статические маршруты под `/api/ API сохраняет позиции каждого счёта; объединяется только их представление. Порядок карточек задаёт первое вхождение бумаги; порядок счетов внутри карточки сохраняется. Одиночные позиции - используют прежнюю карточку. Доля относится к позициям той же валюты + используют прежнюю карточку. Доля относится к позициям во всём показанном портфеле, без денежных остатков. - Журнал объединяет операции по убыванию времени, при равном времени использует порядок идентификаторов счетов и операций. ID diff --git a/domains/portfolio/concentration.md b/domains/portfolio/concentration.md index eed9b9c..d90e6eb 100644 --- a/domains/portfolio/concentration.md +++ b/domains/portfolio/concentration.md @@ -46,21 +46,10 @@ полный перечень остальных элементов, каждый со своей суммой и долей; топ-5 и диаграмма при этом не меняются. -Доли считаются от суммы стоимостей позиций разреза в выбранной валюте и +Доли считаются от суммы стоимостей позиций разреза и в сумме дают ровно 100 %; округление — до десятой доли процента, очень малые доли показываются как «< 0,1 %». Отрицательные и нулевые стоимости, а также -позиции без оценки в разрез не входят. Конвертации валют нет: каждая позиция -входит только в валюту своей оценки. - -## Валюта - -У вкладки свой список валют — валюты оценок позиций акций, облигаций и фондов, -отсортированные по ISO-коду, — и свой выбор, независимый от «Структуры -портфеля». По умолчанию выбрана первая валюта списка. При одной валюте -переключатель не показывается. Общего итога по нескольким валютам нет. -Выбранные разрез и валюта сохраняются, пока пользователь остаётся на странице -счёта, включая переключение её вкладок, и сбрасываются при новом заходе и при -смене счёта. +позиции без оценки в разрез не входят. ## Диаграмма и неполные данные @@ -69,7 +58,7 @@ отмечен как неполный (`completeness: "incomplete"` — часть позиций не имеет оценки), над разрезом появляется предупреждение «Данные неполные», а под списком — сноска «Не учтено позиций: N» с числом позиций, не вошедших в разрез. -Пустой разрез (нет позиций с положительной оценкой в выбранной валюте) +Пустой разрез (нет позиций с положительной оценкой) показывает текстовое состояние вместо диаграммы. ## Откуда берутся сектор и эмитент diff --git a/domains/portfolio/data-reference.md b/domains/portfolio/data-reference.md index 025046a..d7e2fdf 100644 --- a/domains/portfolio/data-reference.md +++ b/domains/portfolio/data-reference.md @@ -9,6 +9,7 @@ Деньги представлены значением Money: ISO-код валюты и десятичная строка, например `{ "currency": "RUB", "value": "12500.5" }` — условный пример. +Валюта всегда `RUB`: система работает только с рублями. Количества также передаются десятичными строками. Backend выполняет десятичную арифметику без промежуточного `Number`, нормализует отрицательные целую и дробную части и убирает незначащие конечные нули. Экспоненциальной @@ -18,7 +19,7 @@ `null` означает, что показатель нельзя определить из доступных данных. Это не ноль. Известная нулевая цена отличается от отсутствующей; позиция с ненулевым количеством и неизвестной стоимостью остаётся в составе. -Несколько валют никогда не складываются в одну общую стоимость. +Позиции и деньги в валютах, отличных от RUB, отфильтровываются при получении из T-Bank и не участвуют ни в одном итоге. ## Состав и денежные показатели @@ -26,25 +27,25 @@ | --- | --- | --- | | Позиция | Ненулевое количество обычного инструмента из `GetPortfolio.positions` | Виртуальные позиции не включаются; порядок источника сохраняется | | `quantity` | Количество из текущей позиции брокера | Допустимы дробные и отрицательные количества; это не число лотов | -| `currentValue` | Количество × текущая цена из `GetPortfolio` | `null`, если цена или её валюта недоступны | +| `currentValue` | Количество × текущая цена из `GetPortfolio` | `null`, если цена недоступна | | `averagePurchasePrice` | Средняя цена позиции, переданная брокером | Это цена единицы, а не сумма затрат или рассчитанная приложением история покупок | -| `absoluteReturn` | Количество × (текущая цена − средняя цена) | Требует обеих цен в одной известной валюте; не включает продажи, купоны и дивиденды | -| Cash Balance | Суммы из `GetPositions.money`, объединённые по валюте | Заблокированные деньги не прибавляются; торгуемый инструмент `CURRENCY` остаётся позицией | -| `totalsByCurrency` | Стоимости позиций + доступные деньги той же валюты | Не включает невычислимую стоимость; не обещает полную оценку портфеля | +| `absoluteReturn` | Количество × (текущая цена − средняя цена) | Требует обеих цен; не включает продажи, купоны и дивиденды | +| Cash Balance | Суммы RUB из `GetPositions.money`, объединённые в один остаток | Заблокированные деньги не прибавляются; торгуемый инструмент `CURRENCY` остаётся позицией | +| Итог Portfolio | Стоимости позиций + Cash Balance в RUB | Не включает невычислимую стоимость; не обещает полную оценку портфеля | | `lastSuccessfulAt` | Время формирования успешного снимка, UTC | Не является временем каждой котировки; запасной снимок сохраняет исходное время | | `isStale` | Истёк часовой срок свежести сохранённого portfolio source | Не описывает состояние клиентского кэша или ошибку фонового запроса | | `sector` | Сектор инструмента в том виде, как его определяет T-Bank, с обрезанными пробелами | Только для позиций `STOCK`, `BOND` и `FUND`; значение из одних пробел считается отсутствующим. Во всех остальных случаях — `null` | | `issuer` | Бренд актива инструмента по данным T-Bank: `{ id, name }` | Только для позиций `STOCK`, `BOND` и `FUND`, когда у актива известны uid и название бренда. Во всех остальных случаях — `null`. Не влияет на количество, стоимость и свежесть снимка; используется вкладкой [«Концентрация»](concentration.md) | -Ненулевые денежные остатки одной валюты суммируются. Позиции одинаковых +Ненулевые денежные остатки суммируются в один Cash Balance. Позиции одинаковых инструментов в ответе отдельного счёта не объединяются; визуальная группировка между счетами описана [отдельно](all-accounts.md#данные-и-вычисления). Сводка уже включает Cash Balance: прибавление денег к ней второй раз неверно. При отсутствии вычислимой стоимости у части позиций известный итог остаётся полезен, но представляет только оценённую часть. Известные нулевые итоги -отдельного счёта не создают валютные карточки. Если `totalsByCurrency` пуст, -но позиции или деньги присутствуют, это не пустой портфель. +отдельного счёта не создают отдельную итоговую карточку. Если итог Portfolio +отсутствует, но позиции или деньги присутствуют, это не пустой портфель. ## Результат инвестирования @@ -81,33 +82,32 @@ ISIN, тикер и `logoName` могут быть `null`. `logoName` — имя облигации (`BOND`), прочие инструменты (`OTHER`) и деньги (`CASH`). Торгуемые валютные инструменты входят в `OTHER`; денежные остатки — в `CASH`. Сервер строит структуру из позиций и денег, без повторного добавления -`totalsByCurrency`. +итога Portfolio. Доля категории — её стоимость / известная стоимость позиций и денег -в выбранной валюте × 100. Backend округляет процент до шести знаков; +× 100. Backend округляет процент до шести знаков; frontend показывает до сотых. Положительное значение меньше 0,01% отображается как «< 0,01 %», если серверное округление не превратило его в ноль. В геометрию диаграммы передаются только числовые проценты; суммы в списке и подсказке форматируются из исходных точных строк. Доля на карточке отдельной позиции использует другой знаменатель: -сумму стоимостей всех позиций той же валюты **без денег**. Это приблизительный +сумму стоимостей всех отображаемых позиций **без денег**. Это приблизительный показатель для отображения, рассчитанный frontend. Он не изменяет Money. В режиме всех счетов знаменатель охватывает все показанные счета. | Состояние | Доступное представление | | --- | --- | -| Все составляющие оценены, неотрицательны, итог положителен | Суммы, доли и кольцевая диаграмма выбранной валюты | -| Есть позиция без оценки | `unvaluedCategories` содержит её категорию и количество; суммы известной части остаются, доли и кольцо недоступны для всех валют | -| Есть отрицательная составляющая | Суммы со знаком остаются; доли и кольцо отключены для её валюты, даже если итог категории положителен | +| Все составляющие оценены, неотрицательны, итог положителен | Суммы, доли и кольцевая диаграмма | +| Есть позиция без оценки | `unvaluedCategories` содержит её категорию и количество; суммы известной части остаются, доли и кольцо недоступны | +| Есть отрицательная составляющая | Суммы со знаком остаются; доли и кольцо отключены, даже если итог категории положителен | | Состав пуст или имеет нулевую стоимость | Текстовое состояние вместо кольца | -Неоценённой позиции нельзя достоверно назначить валюту по её стоимости, -поэтому ограничение долей действует на все валюты. В общем портфеле проверка +В общем портфеле проверка отрицательных денег выполняется по остаткам счетов до взаимозачёта. Нормативные основы: [нормализованный состав](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-нормализованный-состав-portfolio) -и [точные значения и валютные агрегаты](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-точные-значения-и-валютные-агрегаты). +и [точные значения и итоговая сумма в RUB](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-точные-значения-и-итоговая-сумма-в-rub). Структура и общий режим подтверждены реализацией и её проверками; отдельных требований к ним в основной спецификации портфеля пока нет. Поля транспорта следует сверять с [HTTP API](../../architecture/http-api.md), diff --git a/domains/portfolio/how-to/investigate-discrepancy.md b/domains/portfolio/how-to/investigate-discrepancy.md index 5b5e446..326dfea 100644 --- a/domains/portfolio/how-to/investigate-discrepancy.md +++ b/domains/portfolio/how-to/investigate-discrepancy.md @@ -7,7 +7,7 @@ базы для разбора не нужны; не переносите их в документацию или отчёт. 1. **Определите предмет сравнения.** Запишите, открыт отдельный счёт или - «Все счета», какая валюта и какой показатель расходятся. Текущая стоимость, + «Все счета» и какой показатель расходится. Текущая стоимость, свободные деньги, результат открытой позиции и полученный доход — разные величины. Убедитесь, что сравниваемые значения относятся к одному составу счетов и инструменту. Название или тикер сами по себе не доказывают @@ -19,9 +19,9 @@ автоматически, поэтому расхождение проверяйте по показанному состоянию источника, а не по результату ручного обновления. Перезагрузка backend сохраняет последний полный снимок в SQLite, но не создаёт историю стоимости. -3. **Проверьте состав суммы.** Для одной валюты итог складывается из - вычислимых стоимостей позиций и доступных денег. Не добавляйте деньги - второй раз, не складывайте валюты и не включайте заблокированные средства. +3. **Проверьте состав суммы.** Итог складывается из вычислимых стоимостей + позиций и доступных денег в RUB. Не добавляйте деньги второй раз и не + включайте заблокированные средства. Ненулевая позиция с `currentValue: null` делает оценку неполной, но не даёт нулевой вклад в смысле полной стоимости. `CURRENCY` участвует в суммах, хотя текущий интерфейс скрывает её карточку — diff --git a/domains/portfolio/investment-performance.md b/domains/portfolio/investment-performance.md index 13c12ed..cf8bcb3 100644 --- a/domains/portfolio/investment-performance.md +++ b/domains/portfolio/investment-performance.md @@ -44,7 +44,6 @@ | `HISTORY_START_UNKNOWN` | Неизвестна дата открытия счёта | | `OPERATIONS_HISTORY_INCOMPLETE` | История операций не получена полностью до момента оценки | | `PORTFOLIO_VALUATION_INCOMPLETE` | Нет полной оценки портфеля | -| `NON_RUB_ACTIVITY` | Есть потоки или остатки не в рублях | | `UNVALUED_SECURITIES_TRANSFER` | Бумаги введены или выведены без денежной оценки | | `UNCONFIRMED_TRANSFER` | В «Все счета» есть похожие на перевод между своими счетами операции, связь которых источник не подтверждает | | `ACCOUNT_VALUATION_DATES_DIFFER` | В «Все счета» последние оценки счетов сделаны в разные московские дни | diff --git a/domains/portfolio/overview.md b/domains/portfolio/overview.md index 16c678b..f54d849 100644 --- a/domains/portfolio/overview.md +++ b/domains/portfolio/overview.md @@ -10,9 +10,10 @@ ## Снимок и связанные области Снимок состоит из позиций по инструментам, доступных денежных остатков, -валютных итогов и структуры активов. Позиция означает ненулевое количество +итоговой суммы в RUB и структуры активов. Позиция означает ненулевое количество обычного инструмента в брокерском портфеле. Торгуемая валютная позиция -`CURRENCY` и свободные деньги имеют разный смысл и передаются раздельно. +`CURRENCY` и свободные деньги имеют разный смысл и передаются раздельно; +в итоге учитываются только деньги в RUB. Точный смысл сумм и неизвестных значений раскрыт в [справочнике данных](data-reference.md). @@ -34,18 +35,15 @@ Backend получает данные через серверную ## Как читать экран -Валютная сводка показывает отдельную карточку для каждого полученного итога. -Деньги уже включены в итог своей валюты. RUB, USD и EUR обозначены ₽, $ и €; -другие валюты сохраняют ISO-код. Вспомогательным технологиям доступны полное +Сводка показывает одну карточку с итоговой суммой в RUB. Деньги уже включены +в итог. Сумма обозначена ₽; вспомогательным технологиям доступны полное значение и название валюты. Суммы сохраняют все исходные цифры, знак и дробные позиции, включая `-0,5`; длинные значения переносятся без обрезания. -Карточки не интерактивны и занимают одну колонку на узком экране, -две или три на широком. Отсутствующая валюта не получает искусственную -нулевую карточку. Пустые итоги не скрывают доступный состав портфеля. +Карточка не интерактивна. Отсутствующая итоговая сумма не получает искусственный +ноль. Пустой итог не скрывает доступный состав портфеля. Структура после сводки распределяет известную стоимость по акциям, -облигациям, другим активам и свободным деньгам. При нескольких валютах -пользователь выбирает одну; при одной отдельного переключателя нет. +облигациям, другим активам и свободным деньгам в RUB. Кольцевая диаграмма дополнена текстовым списком сумм и долей и подсказкой; на узком экране список расположен под ней. Неизвестная оценка, отрицательные составляющие или нулевой итог ограничивают возможность показать доли — @@ -73,14 +71,14 @@ Backend получает данные через серверную Существует расхождение: [согласованная спецификация представления позиций](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-представление-карточек-позиций-на-клиенте) требует доступности всех иных типов в отдельной секции, а текущие frontend и его проверка исключают `CURRENCY` только из списка карточек. Такие позиции -остаются в ответе, валютных итогах, структуре и знаменателе долей остальных -позиций. Это описание фактического ограничения; выбор между изменением -интерфейса и согласованием требований остаётся отдельной задачей. +остаются в ответе, но не входят в итоговую сумму, структуру; их учёт в знаменателе +долей остальных позиций требует проверки. Выбор между изменением интерфейса и +согласованием требований остаётся отдельной задачей. ## Свежесть и границы При первом запросе экран показывает каркас заголовка, сводки и разделов. -Три формы валютных карточек в каркасе не обещают наличие трёх валют. +Каркас итоговой карточки не обещает наличие суммы. Загрузка обозначена для вспомогательных технологий; декоративные элементы не содержат данных и не получают фокус. При обычном фоновом обновлении сохраняются предыдущие суммы и время успешного получения в UTC. @@ -95,10 +93,9 @@ Backend получает данные через серверную Правила времени и ошибок приведены в [потоке загрузки](flows/portfolio-loading.md). Отдельного чтения истории стоимости нет, но каждый полный снимок -сохраняет точку оценки по счёту и валюте для будущих поддерживаемых +сохраняет точку оценки по счёту в RUB для будущих поддерживаемых представлений; публичного графика в текущем решении нет. Нет также -валютной конвертации, учёта виртуальных позиций или заблокированных -средств. Последний полный снимок сохраняется после перезапуска; +учёта виртуальных позиций или заблокированных средств. Последний полный снимок сохраняется после перезапуска; обновлением личных данных владеет часовой обход, рассчитанный на один экземпляр backend. Действие «Обновить» выполняет только локальное перечитывание открытого экрана и не запускает работу с T-Bank. diff --git a/glossary.md b/glossary.md index e549a8e..2a1b624 100644 --- a/glossary.md +++ b/glossary.md @@ -91,7 +91,7 @@ _Не использовать_: тип аккаунта T-Bank, protobuf type. ## Portfolio Текущее нормализованное состояние инвестиций одного Brokerage Account, -включающее позиции, доступные денежные остатки и итоги по каждой валюте. +включающее позиции, доступный денежный остаток и итоговую сумму в RUB. _Не использовать_: общий портфель, баланс счёта. @@ -123,7 +123,7 @@ _Не использовать_: денежный остаток, cash balance. Вкладка страницы одного Brokerage Account, показывающая, какие акции, облигации, секторы и эмитенты занимают наибольшую часть текущей оценки -Portfolio, без валютной конвертации. Отличается от [структуры +Portfolio в RUB. Отличается от [структуры портфеля](domains/portfolio/data-reference.md#структура-и-доли): в её разрезах деньги не входят в знаменатель долей. @@ -188,8 +188,8 @@ _Не использовать_: аватар актива, иконка бум ## Cash Balance -Ненулевой доступный денежный остаток Portfolio в одной валюте; не является -Portfolio Position и передаётся отдельно от `positions` в `cashBalances`. +Ненулевой доступный денежный остаток Portfolio в RUB; не является +Portfolio Position и передаётся отдельно от `positions`. _Не использовать_: валютная позиция, заблокированные средства. @@ -202,7 +202,7 @@ _Не использовать_: история портфеля, snapshot дл ## Точка истории стоимости портфеля -Полная оценка одного Brokerage Account в одной валюте на момент успешного +Полная оценка одного Brokerage Account в RUB на момент успешного обновления Portfolio; последовательность таких точек описывает изменение оценки во времени, включая неторговые дни. Не является Portfolio Snapshot или [Результатом инвестирования](#результат-инвестирования); отсутствие цены @@ -293,8 +293,7 @@ _Не использовать_: активный refresh token, признак Прогнозируемое на ближайший период денежное или связанное корпоративное событие одного Brokerage Account, рассчитанное по текущей Portfolio Position; не является исполненной Ledger Entry. Прогнозная сумма события передаётся как -nullable Money: известные coupon, dividend и maturity содержат Money в исходной -валюте, offer или неизвестные денежные условия передаются как `null`. +nullable Money: известные coupon, dividend и maturity содержат Money в RUB, offer или неизвестные денежные условия передаются как `null`. В публичном API прогноз доступен через маршруты `/events`; в доменной документации событие называется Future Cash Event. @@ -310,12 +309,12 @@ _Не использовать_: upstream dividend type, MOEX row name. ## Realized Income Analytics Результат анализа исполненных купонов и дивидендов по счёту за календарный -период включительно в часовом поясе Europe/Moscow. Сохраняет исходные валюты -и знаки денежных выплат, переданных брокером. Это не налоговый расчёт, +период включительно в часовом поясе Europe/Moscow. Сохраняет знаки денежных выплат, +переданных брокером, в RUB. Это не налоговый расчёт, прогноз или [Результат инвестирования](#результат-инвестирования); правила расчёта объясняет [область дохода](domains/income/overview.md). -_Не использовать_: доходность выплат, общий доход в одной валюте, +_Не использовать_: доходность выплат, общий доход в нескольких валютах, гарантированный доход. ## Результат инвестирования diff --git a/integrations/tbank.md b/integrations/tbank.md index e5a1857..08977dc 100644 --- a/integrations/tbank.md +++ b/integrations/tbank.md @@ -11,7 +11,7 @@ WealthLedger обращается к Invest API на сервере по gRPC ov | Категория | Для чего используется | | --- | --- | | Открытые счета | Определить доступный состав счетов и проверить доступ к конкретному счёту | -| Портфель и денежные остатки | Показать текущий состав инвестиций и итоги по исходным валютам | +| Портфель и денежные остатки | Показать текущий состав инвестиций и итоговую сумму в RUB | | Исполненные операции | Построить журнал и выделить полученные купоны и дивиденды | | Инструменты и рынок | Имена, идентификаторы, цены, свечи, параметры акций и облигаций | | Классификация инструментов | Сектор акций, облигаций и фондов и бренд актива для разрезов [концентрации](../domains/portfolio/concentration.md) | diff --git a/overview.md b/overview.md index 19f7db6..3d16398 100644 --- a/overview.md +++ b/overview.md @@ -19,7 +19,7 @@ WealthLedger — локальное приложение для частного | --- | --- | | Найти свой инвестиционный счёт | [Список счетов](domains/accounts/overview.md), их типы и доступные сводки | | Вести банковские карты | [Ручной учёт](domains/cards/overview.md): независимые балансы, расходы, пополнения, переводы, возвраты и месячные сводки | -| Понять текущий состав инвестиций | [Портфель](domains/portfolio/overview.md): позиции, денежные остатки и итоги по валютам | +| Понять текущий состав инвестиций | [Портфель](domains/portfolio/overview.md): позиции, денежные остатки и итоговая сумма в RUB | | Увидеть, где сконцентрированы деньги | [Концентрация](domains/portfolio/concentration.md): крупнейшие бумаги, секторы и эмитенты счёта | | Разобрать уже совершённые движения | [Журнал операций](domains/operations/overview.md): исполненные операции с фильтрами и последовательной загрузкой истории | | Посчитать полученные выплаты | [Пассивный доход](domains/income/overview.md): исполненные купоны и дивиденды за календарный период | @@ -46,9 +46,9 @@ WealthLedger — локальное приложение для частного подтверждает фактическое зачисление. Полученные купоны и дивиденды сами по себе не равны доходности или результату после налогов. -Денежная сумма всегда относится к одной валюте. Недоступное значение -отличается от нуля, а объединение счетов не означает автоматическую -конвертацию всех валют. Краткие определения собраны в [глоссарии](glossary.md). +Система работает только с рублями (RUB): данные в других валютах отфильтровываются +при получении из T-Bank и MOEX и не участвуют в итогах. Недоступное значение +отличается от нуля, а объединение счетов не означает конвертацию. Краткие определения собраны в [глоссарии](glossary.md). ## Источники и границы продукта -- 2.54.0 From 400da2db9773c69c84ab62078b57033e46ba5103 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Wed, 11 Nov 2026 09:00:25 +0300 Subject: [PATCH 2/2] docs: ignore non-RUB flows in performance ADR (WealthLedger/main#55) --- adr/023-investment-performance-vs-index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adr/023-investment-performance-vs-index.md b/adr/023-investment-performance-vs-index.md index 9a40b83..afd161e 100644 --- a/adr/023-investment-performance-vs-index.md +++ b/adr/023-investment-performance-vs-index.md @@ -37,9 +37,9 @@ индексе Мосбиржи». - **IMOEX — только справочный индекс.** Он показывается рядом, но не участвует в «опережении» и не является базой сравнения. -- **Только RUB.** Счёт с потоками или стоимостью в другой валюте не - рассчитывается (причина `NON_RUB_ACTIVITY`); конвертация валют не - выполняется. +- **Только RUB.** Пополнения и выводы в другой валюте не учитываются, + нерублёвые позиции отфильтрованы на входе; конвертация валют не + выполняется (WealthLedger/main#55). - **Период** начинается с первого внешнего потока, не с открытия счёта. Короче года — «за N дн.» без annualization; `basis = annual` только если деньги вложены от 365 дней. -- 2.54.0