7 Commits
Author SHA1 Message Date
codex-bot f504a0f566 docs: investment performance vs Moscow Exchange index (WealthLedger/main#54) (#67)
Docs security / security (push) Successful in 7s
Docs verify / verify (push) Successful in 11s
2026-11-08 16:49:39 +03:00
ksv741 364f6bd8d0 chore(release): 0.1.8
Conventional commits / commits (pull_request) Successful in 10s
Docs security / security (pull_request) Successful in 6s
Docs verify / verify (pull_request) Successful in 9s
Docs security / security (push) Successful in 6s
Docs verify / verify (push) Successful in 10s
2026-11-06 07:47:24 +03:00
codex-bot 6d7d4c7b28 chore(deps): set Renovate release age, PR limit and update groups (WealthLedger/main#6) (#64)
Docs security / security (push) Successful in 6s
Docs verify / verify (push) Successful in 10s
2026-11-03 14:07:41 +03:00
ksv741 40ea47a7c2 chore(release): 0.1.7
Conventional commits / commits (pull_request) Successful in 10s
Docs security / security (pull_request) Successful in 7s
Docs verify / verify (pull_request) Successful in 12s
Docs security / security (push) Successful in 8s
Docs verify / verify (push) Successful in 11s
2026-11-01 19:33:26 +03:00
codex-bot 37430da4c9 docs: describe concentration tab and stored instrument classification (WealthLedger/main#48) (#60)
Docs security / security (push) Successful in 7s
Docs verify / verify (push) Successful in 11s
2026-11-01 19:29:01 +03:00
codex-bot abe5b16241 chore(deps): resolve markdownlint audit advisory (WealthLedger/main#49) (#61)
Docs security / security (push) Successful in 7s
Docs verify / verify (push) Successful in 11s
2026-11-01 19:13:01 +03:00
codex-bot dcb528fa9f docs: describe all-account row grouping (WealthLedger/main#47) (#59)
Docs security / security (push) Successful in 6s
Docs verify / verify (push) Successful in 9s
2026-10-30 07:28:25 +03:00
26 changed files with 617 additions and 35 deletions
+17
View File
@@ -1,5 +1,22 @@
# Changelog # Changelog
## [0.1.8](https://git.personal.ksv741.ru/WealthLedger/docs/compare/v0.1.7...v0.1.8) (2026-11-06)
### Maintenance
* **deps:** set Renovate release age, PR limit and update groups (WealthLedger/main[#6](https://git.personal.ksv741.ru/WealthLedger/docs/issues/6)) ([#64](https://git.personal.ksv741.ru/WealthLedger/docs/issues/64)) ([6d7d4c7](https://git.personal.ksv741.ru/WealthLedger/docs/commit/6d7d4c7b286535b7338d7e398d2daf7d8137a501))
## [0.1.7](https://git.personal.ksv741.ru/WealthLedger/docs/compare/v0.1.6...v0.1.7) (2026-11-01)
### Documentation
* describe all-account row grouping (WealthLedger/main[#47](https://git.personal.ksv741.ru/WealthLedger/docs/issues/47)) ([#59](https://git.personal.ksv741.ru/WealthLedger/docs/issues/59)) ([dcb528f](https://git.personal.ksv741.ru/WealthLedger/docs/commit/dcb528fa9f76f0d7aa98bd827cc7ee39024dbb13))
* describe concentration tab and stored instrument classification (WealthLedger/main[#48](https://git.personal.ksv741.ru/WealthLedger/docs/issues/48)) ([#60](https://git.personal.ksv741.ru/WealthLedger/docs/issues/60)) ([37430da](https://git.personal.ksv741.ru/WealthLedger/docs/commit/37430da4c99cb571ef5bc0676cad7b3a47bcb6f0))
### Maintenance
* **deps:** resolve markdownlint audit advisory (WealthLedger/main[#49](https://git.personal.ksv741.ru/WealthLedger/docs/issues/49)) ([#61](https://git.personal.ksv741.ru/WealthLedger/docs/issues/61)) ([abe5b16](https://git.personal.ksv741.ru/WealthLedger/docs/commit/abe5b16241d3002baa5d39c2a12949f23a75b968))
## [0.1.6](https://git.personal.ksv741.ru/WealthLedger/docs/compare/v0.1.5...v0.1.6) (2026-10-24) ## [0.1.6](https://git.personal.ksv741.ru/WealthLedger/docs/compare/v0.1.5...v0.1.6) (2026-10-24)
### Maintenance ### Maintenance
+1 -1
View File
@@ -30,7 +30,7 @@ WealthLedger помогает частному инвестору понимат
| [Аутентификация](domains/authentication/overview.md) | Регистрация, вход, восстановление и завершение сессии | | [Аутентификация](domains/authentication/overview.md) | Регистрация, вход, восстановление и завершение сессии |
| [Счета](domains/accounts/overview.md) | Доступные счета, сводки и состояния каждого счёта | | [Счета](domains/accounts/overview.md) | Доступные счета, сводки и состояния каждого счёта |
| [Банковские карты](domains/cards/overview.md) | Ручные остатки, операции, категории, возвраты, месячные сводки и исправления | | [Банковские карты](domains/cards/overview.md) | Ручные остатки, операции, категории, возвраты, месячные сводки и исправления |
| [Портфель](domains/portfolio/overview.md) | Позиции, остатки, суммы, структура и свежесть | | [Портфель](domains/portfolio/overview.md) | Позиции, остатки, суммы, структура, [концентрация](domains/portfolio/concentration.md) и свежесть |
| [Все счета](domains/portfolio/all-accounts.md) | Общий портфель, происхождение строк и сводные разделы | | [Все счета](domains/portfolio/all-accounts.md) | Общий портфель, происхождение строк и сводные разделы |
| [Операции](domains/operations/overview.md) | Исполненные операции, фильтры и пагинация | | [Операции](domains/operations/overview.md) | Исполненные операции, фильтры и пагинация |
| [Доход](domains/income/overview.md) | Полученные купоны и дивиденды, периоды и состав расчёта | | [Доход](domains/income/overview.md) | Полученные купоны и дивиденды, периоды и состав расчёта |
@@ -2,7 +2,10 @@
**Status:** Accepted. Заменяет request-driven, manual-refresh и **Status:** Accepted. Заменяет request-driven, manual-refresh и
no-periodic-walk части [ADR 017](017-local-tbank-reads-and-background-sync.md) no-periodic-walk части [ADR 017](017-local-tbank-reads-and-background-sync.md)
и дополняет [ADR 018](018-minimal-runtime-coordination.md). и дополняет [ADR 018](018-minimal-runtime-coordination.md). Правило «справочные
сведения запрашиваются онлайн» дополнено исключением для сектора и бренда
актива позиции — [ADR
022](022-stored-instrument-classification.md).
## Decision ## Decision
@@ -0,0 +1,56 @@
# 022. Хранимая классификация инструментов T-Bank
**Status:** Accepted. Дополняет [ADR
019](019-hourly-reads-and-online-instrument-reference.md) исключением из
правила «справочные сведения запрашиваются онлайн». Решение утверждено
владельцем в [Issue
WealthLedger/main#48](https://git.personal.ksv741.ru/WealthLedger/main/issues/48).
## Context
ADR 019 установил, что справочные сведения инструментов (detail, lookup,
каталоги) запрашиваются онлайн и не сохраняются как долговременный справочник:
«старые справочные строки не являются источником актуальности». Вкладка
«Концентрация» требует для каждой позиции её сектора и эмитента — бренда
актива по данным T-Bank. Сектор есть только в описаниях отдельных типов
инструментов (`share`, `bond`, `etf`), а эмитент — в описании актива по
`asset_uid` инструмента; из названия, тикера, ISIN или `logoName` его вывести
нельзя. Запрашивать эти поля онлайн при каждом открытии экрана нельзя:
ответ Portfolio не ждёт T-Bank, а разрезы должны считаться по уже сохранённому
снимку.
Существующий metadata-кэш (`TbankInstrument`) не подходит: общий ответ
`GetInstrumentBy` не содержит `sector`, а его `brand` — данные логотипа,
а не `{uid, name}` бренда актива.
## Decision
- Сектор и бренд актива позиции хранятся как последнее известное значение в
глобальной таблице `TbankInstrumentClassification` (провайдер, ключ
инструмента, сектор и время его получения, uid актива и uid/название бренда
с временем получения). Данные публичные, без привязки к пользователю или
подключению; ограждение по connection revision к ним не применяется, публикация
Snapshot ограждается как прежде.
- Каждое поле обновляется в часовом обходе при его первой попытке, начавшейся
не ранее чем через 24 часа после последнего успешного получения этого поля;
пока значение не получено, попытка повторяется в каждом обходе. Сектор и
бренд обновляются независимо.
- Неуспешное получение не мешает публикации Portfolio Snapshot и не влияет на
его свежесть; сохранённое значение остаётся доступным. Отказ token
обрабатывается как отказ token при обходе Portfolio. Ответ «инструмент не
найден» сохраняет прежнее значение и перезапускает отсчёт 24 часов.
- Классификация не входит в `meta.dependencies` Portfolio и не меняет
`isStale` и `lastSuccessfulAt`.
## Consequences
- Это первый долговременный справочник T-Bank в системе: ADR 019 теряет
характер «только онлайн» для этой области и дополняется, а не заменяется.
- Разрезы концентрации считаются по снимку без обращений к T-Bank; число
вызовов растёт ограниченно — не более одной попытки на поле в сутки на
позицию, при первом обходе после выката возможен разовый всплеск, сглаженный
общим лимитером и очередью.
- Значение может быть устаревшим до 24 часов; это приемлемо для группировки
позиций, но классификация не является источником актуальных данных брокера.
- Второй потребитель хранимой классификации — условие пересмотра решения
(выделение независимого процесса обновления, отдельные сроки).
@@ -0,0 +1,93 @@
# 023. Результат инвестирования относительно индекса Мосбиржи
**Status:** Accepted. **Date:** 2026-11-07. Дополняет [ADR
019](019-hourly-reads-and-online-instrument-reference.md) исключением для
публичной истории закрытий индексов MOEX (по образцу
[ADR 022](022-stored-instrument-classification.md)). Решение оформлено в
[Issue WealthLedger/main#54](https://git.personal.ksv741.ru/WealthLedger/main/issues/54)
и утверждённом change `specs/openspec/changes/2026-11-07-investment-performance-vs-index/`
в репозитории specs.
## Context
Вкладка «Результат» должна показать, как счёт или «Все счета» справились с
деньгами, которые в него внесли, и сравнить это с вложением тех же денег в
индекс. Простая доходность «стоимость / вложено − 1» искажает оценку, когда
пополнения и выводы распределены во времени. Доходность по времени (TWR)
требует истории стоимости на каждую дату потока, а её в системе нет: хранится
только последний Portfolio.
Сравнение с ценовым IMOEX не отвечает на вопрос пользователя: IMOEX не
включает дивиденды и купоны, а эталон должен учитывать их так же, как
портфель. ADR 019 запрещает хранить справочные данные как долговременный
источник; индексы — публичные временные ряды, для которых онлайн-запрос при
каждом открытии вкладки неприемлем по задержке и лимитам внешнего сервиса.
## Decision
- **«Доходность» для «Результата инвестирования» — money-weighted return (XIRR)**
по внешним денежным потокам счёта и текущей оценке Portfolio. Термин
«доходность» снимается с запрета только для этого понятия; прочие понятия
«не доходность» сохраняют прежний текст.
- **Сравнение — теневой портфель в индексе.** Те же внешние потоки
конвертируются в единицы индекса по закрытию дня потока (или ближайшего
предыдущего торгового дня) и оцениваются по закрытию на дату расчёта.
Эталон — полный доходный индекс MCFTRR; XIRR индекса считается на тех же
потоках. Это идея PME по Long–Nickels; в интерфейсе — «те же деньги в
индексе Мосбиржи».
- **IMOEX — только справочный индекс.** Он показывается рядом, но не
участвует в «опережении» и не является базой сравнения.
- **Только RUB.** Счёт с потоками или стоимостью в другой валюте не
рассчитывается (причина `NON_RUB_ACTIVITY`); конвертация валют не
выполняется.
- **Период** начинается с первого внешнего потока, не с открытия счёта.
Короче года — «за N дн.» без annualization; `basis = annual` только если
деньги вложены от 365 дней.
- **Хранение закрытий индексов.** Пара (индекс, торговый день по Москве) →
CLOSE хранится локально с upsert. Это исключение из ADR 019: публичная
история индекса, не привязанная к пользователю или подключению, хранится
как долговременные данные. Остальные справочные сведения ADR 019 не
меняются.
### Отклонённые варианты
- **Простой ROI** (`стоимость / вложено − 1`): искажён моментами пополнений
и выводов, поэтому не сравнивается с индексом на тех же деньгах.
- **TWR (time-weighted return):** требует стоимости портфеля на каждую дату
потока; история оценок в системе отсутствует. Сохранение такой истории
вне этого решения.
- **Ценовой IMOEX как ориентир:** не учитывает дивиденды и купоны, поэтому
сравнение с портфелем, где они учтены, смещено.
- **Конвертация валют (FX):** потребовала бы исторических курсов и
отдельного решения о методике; не нужна для личного RUB-учёта.
## Consequences
- Результат зависит от сохранённых операций T-Bank и последнего полного
Portfolio: неполная история или неполная оценка дают `data: null` или
`completeness: incomplete` по правилам change, а не подменяются оценкой.
- Первая загрузка индекса догружает историю от `from − 10 дней`; при
недостатке закрытий появляется `INDEX_HISTORY_UNAVAILABLE`. Фоновая
синхронизация индексов не вводится.
- Исключение из ADR 019 ограничено закрытиями индексов MOEX.
### Оговорки и условия источника
- **Налоги.** MCFTRR при расчёте дивидендов и купонов использует ставки для
организаций, а не НДФЛ физического лица. Индекс — ориентир альтернативы,
а не точная модель налогов пользователя. Оговорка фиксируется в методике
(«Как считали»).
- **Данные MOEX ISS.** ISS бесплатно отдаёт данные «для ознакомления».
Приложение личное и локальное, данные не распространяются; источник
указывается в «Как считали».
- **Переводы между счетами.** Совпадение суммы и времени операций ещё не
доказывает внутренний перевод. Пока нет подтверждённой связи, такие счета
исключаются из рассчитанной части «Все счета» с явной причиной неполноты.
- **Несколько решений XIRR.** Процент не показывается: нельзя выбрать
произвольную ставку. Прибыль в рублях остаётся видна.
## Links
- Issue: [WealthLedger/main#54](https://git.personal.ksv741.ru/WealthLedger/main/issues/54)
- Change: `specs/openspec/changes/2026-11-07-investment-performance-vs-index/`
(proposal, system-analysis «Расчёт» и «Индексы MOEX», design, Decisions backend 3, 5)
+2
View File
@@ -26,3 +26,5 @@
- [019 — Часовой обход и онлайн-справочник инструментов](019-hourly-reads-and-online-instrument-reference.md) - [019 — Часовой обход и онлайн-справочник инструментов](019-hourly-reads-and-online-instrument-reference.md)
- [020 — Evidence for hourly sync read boundaries](020-hourly-sync-verification-evidence.md) - [020 — Evidence for hourly sync read boundaries](020-hourly-sync-verification-evidence.md)
- [021 — Тихое чтение и редкие подтверждения](021-quiet-reading-and-rare-confirmations.md) - [021 — Тихое чтение и редкие подтверждения](021-quiet-reading-and-rare-confirmations.md)
- [022 — Хранимая классификация инструментов T-Bank](022-stored-instrument-classification.md)
- [023 — Результат инвестирования относительно индекса Мосбиржи](023-investment-performance-vs-index.md)
+6 -1
View File
@@ -141,13 +141,18 @@ membership текущей revision перед каждым финансовым
SQLite хранит полный последний portfolio, операции и их подтверждённые SQLite хранит полный последний portfolio, операции и их подтверждённые
диапазоны, private прогнозы, сохранённые точки истории оценки, общие диапазоны, private прогнозы, сохранённые точки истории оценки, общие
instrument/market sources и intent/staging. instrument/market sources, классификацию инструментов (сектор и бренд актива
как последнее известное значение с временем каждого получения,
`TbankInstrumentClassification`) и intent/staging.
Нормализованные финансовые значения сохраняются decimal-строками. Полнота Нормализованные финансовые значения сохраняются decimal-строками. Полнота
отделена от свежести и пагинации. Ответ не ждёт внешних RPC; обновлением отделена от свежести и пагинации. Ответ не ждёт внешних RPC; обновлением
личных данных владеет часовой обход в том же backend process через личных данных владеет часовой обход в том же backend process через
существующий лимитер, недостающие общие источники он же ставит в фоновую существующий лимитер, недостающие общие источники он же ставит в фоновую
очередь. На startup обход начинает следующую попытку по обычному часовому очередь. На startup обход начинает следующую попытку по обычному часовому
сроку; после нового подключения первая попытка начинается сразу. сроку; после нового подключения первая попытка начинается сразу.
Классификация — публичные данные без привязки к пользователю и подключению;
её поля обновляются в том же обходе не чаще раза в сутки
([ADR 022](../adr/022-stored-instrument-classification.md)).
Подробности — [модель свежести](cross-cutting/data-freshness.md). Подробности — [модель свежести](cross-cutting/data-freshness.md).
PUT подключения сначала проверяет token у T-Bank, затем одной транзакцией PUT подключения сначала проверяет token у T-Bank, затем одной транзакцией
+2 -1
View File
@@ -31,7 +31,8 @@ WealthLedger нормализует их для просмотра и сопос
информацией об инструментах. [Интеграция T-Bank](../integrations/tbank.md) информацией об инструментах. [Интеграция T-Bank](../integrations/tbank.md)
объясняет полномочия подключения и ограничения внешней зависимости. объясняет полномочия подключения и ограничения внешней зависимости.
[MOEX ISS](../integrations/moex.md) поставляет публичные параметры и календарь выпусков облигаций. [MOEX ISS](../integrations/moex.md) поставляет публичные параметры и календарь выпусков облигаций,
а также дневные закрытия индексов Мосбиржи для сравнения результата.
[Реестр НКР](../integrations/nkr.md) используется для поиска кредитного рейтинга конкретного выпуска; [Реестр НКР](../integrations/nkr.md) используется для поиска кредитного рейтинга конкретного выпуска;
рейтинг эмитента не подставляется вместо него. Отсутствие подходящей записи рейтинг эмитента не подставляется вместо него. Отсутствие подходящей записи
и недоступность источника имеют разный смысл. Правила интерпретации этих и недоступность источника имеют разный смысл. Правила интерпретации этих
+10 -3
View File
@@ -8,8 +8,12 @@ T-Bank. Нормальным владельцем обновления служ
простоя выполняется не более одна актуальная попытка, а перекрывающийся срок простоя выполняется не более одна актуальная попытка, а перекрывающийся срок
пропускается, не ставится в очередь. Общие справочные данные инструментов пропускается, не ставится в очередь. Общие справочные данные инструментов
запрашиваются онлайн, когда это требуется карточке; финансовый HTTP-ответ не запрашиваются онлайн, когда это требуется карточке; финансовый HTTP-ответ не
ждёт T-Bank. Модель принята в ждёт T-Bank. Единственное исключение — сектор и бренд актива позиции, которые
[ADR 019](../../adr/019-hourly-reads-and-online-instrument-reference.md). хранятся как последнее известное значение с обновлением не чаще раза в сутки;
свежесть и полнота Portfolio от них не зависят. Модель принята в
[ADR 019](../../adr/019-hourly-reads-and-online-instrument-reference.md),
исключение — в
[ADR 022](../../adr/022-stored-instrument-classification.md).
## Клиентский и серверный снимки ## Клиентский и серверный снимки
@@ -18,7 +22,10 @@ T-Bank. Нормальным владельцем обновления служ
HTTP-перечитывания в той же границе доступа. Это не серверный fallback и не HTTP-перечитывания в той же границе доступа. Это не серверный fallback и не
состояние часового обхода. SQLite независимо от браузера хранит membership, состояние часового обхода. SQLite независимо от браузера хранит membership,
последний полный portfolio, исполненные операции, прогнозы, общие сведения об последний полный portfolio, исполненные операции, прогнозы, общие сведения об
инструментах, котировки, свечи и календари. инструментах и классификацию инструментов (сектор и бренд актива как
последнее известное значение, [ADR
022](../../adr/022-stored-instrument-classification.md)), котировки, свечи и
календари.
[Полнота периода](../../glossary.md#полнота-истории-счёта) и свежесть различаются. [Полнота периода](../../glossary.md#полнота-истории-счёта) и свежесть различаются.
Успешно проверенный пустой результат полон и доступен; последняя страница Успешно проверенный пустой результат полон и доступен; последняя страница
+10
View File
@@ -72,6 +72,16 @@ Envelope несовместим с прежними bare-array ответами:
backend-generated OpenAPI; frontend получает её обновлённый snapshot отдельно. backend-generated OpenAPI; frontend получает её обновлённый snapshot отдельно.
Точную семантику поведения описывает [доход](../domains/income/overview.md). Точную семантику поведения описывает [доход](../domains/income/overview.md).
## Investment performance
`GET /api/v1/brokerage-accounts/{accountId}/performance` и общий маршрут
`all` возвращают [результат инвестирования](../domains/portfolio/investment-performance.md)
за всё время. Недоступность расчёта — не HTTP-ошибка: ответ `200` с
`data: null` и причинами в `meta.unavailable`. Неполная сводка «Все счета»
имеет `completeness: incomplete` и `excludedAccounts`. Доли передаются
десятичной строкой (`0.151` = 15,1 %), `return.basis` различает годовую
ставку и ставку за период. Схему задаёт backend-generated OpenAPI.
## Validation и identity ## Validation и identity
Backend преобразует и проверяет входные данные на общей HTTP-границе. Backend преобразует и проверяет входные данные на общей HTTP-границе.
+3 -3
View File
@@ -54,7 +54,7 @@
- Только точные типы купона и дивиденда источника формируют результат. Суммы остаются точными десятичными строками в исходной валюте; отрицательные корректировки сохраняют знак. Налоги, комиссии, сделки, переводы, погашения и переводы дивидендов не входят в доход. - Только точные типы купона и дивиденда источника формируют результат. Суммы остаются точными десятичными строками в исходной валюте; отрицательные корректировки сохраняют знак. Налоги, комиссии, сделки, переводы, погашения и переводы дивидендов не входят в доход.
- Итоги, временные интервалы и разбиение по инструментам не смешивают валюты и не создают общий конвертированный итог. Система не загружает другие периоды ради сравнения полученного дохода с выбранным. - Итоги, временные интервалы и разбиение по инструментам не смешивают валюты и не создают общий конвертированный итог. Система не загружает другие периоды ради сравнения полученного дохода с выбранным.
- Временные интервалы по-прежнему приходят с backend: дни для 1–45 дней, недели для 46–180 и месяцы для более длинного периода, включая пустые интервалы. Страница дохода не строит из них график или текстовую динамику; ответ API не меняется. - Временные интервалы по-прежнему приходят с backend: дни для 1–45 дней, недели для 46–180 и месяцы для более длинного периода, включая пустые интервалы. Страница дохода не строит из них график или текстовую динамику; ответ API не меняется.
- Раздел «Выплаты» показывает единый список карточек выплат в исходном порядке `entries`: одна карточка на каждую выплату. Внешних групп по инструментам или валютам, итогов и количества выплат инструмента нет. Описание, дата, доступные идентификаторы инструмента и сумма со знаком видны в каждой карточке; валюты разделены в итогах. Дополнительный запрос за составом не нужен. - Раздел «Выплаты» показывает карточки выплат в исходном порядке `entries`. В отдельном счёте каждая выплата — отдельная карточка. В режиме «Все счета» выплаты одного инструмента, дня и точного типа образуют группу, если их две и более ([правила группировки](../operations/overview.md#группировка-в-режиме-все-счета)). Группа стоит на месте первой выплаты; каждая строка счёта показывает название счёта и свою сумму со знаком. Визуальный итог группы — точная сумма по валютам по показанным выплатам; он скрыт, если полнота периода не `complete`, или у выплаты нет суммы, или сумма равна нулю. Это клиентский расчёт и не заменяет серверные итоги аналитики дохода, которые по-прежнему приходят из ответа API и не пересчитываются. Описание, дата, доступные идентификаторы инструмента и сумма со знаком видны в каждой выплате. Дополнительный запрос за составом не нужен.
## Полнота и принадлежность выплат ## Полнота и принадлежность выплат
@@ -80,7 +80,7 @@ Backend обеспечивает принадлежность выплат, ва
доверяет типизированному contract, не восстанавливает формулу идентификации доверяет типизированному contract, не восстанавливает формулу идентификации
и не выполняет второй consistency validator. Полностью пустой ответ допустим. и не выполняет второй consistency validator. Полностью пустой ответ допустим.
Состав и исходный серверный порядок записей и знак сумм Состав и исходный серверный порядок записей и знак сумм
сохраняются, frontend не пересчитывает денежные итоги. Стабильный ID в пределах подключения и счёта определяет одну операцию: сохраняются; серверные денежные итоги frontend не пересчитывает (визуальный итог группы в режиме «Все счета» отдельный, см. выше). Стабильный ID в пределах подключения и счёта определяет одну операцию:
повторная сверка обновляет её без двойного учёта. Одинаковые ID разных счетов повторная сверка обновляет её без двойного учёта. Одинаковые ID разных счетов
остаются разными выплатами. Изменение источника логотипа позволяет заново остаются разными выплатами. Изменение источника логотипа позволяет заново
показать изображение после буквенной заглушки. показать изображение после буквенной заглушки.
@@ -94,7 +94,7 @@ Backend обеспечивает принадлежность выплат, ва
выбранного счёта и сохраняет прежнее поведение. Будущие выплаты выбранного счёта и сохраняет прежнее поведение. Будущие выплаты
не включаются в итоги полученного дохода. не включаются в итоги полученного дохода.
Аналитика читает сохранённую историю операций и не рассчитывает налоги, инвестиционную эффективность, доходность, валютную конвертацию или общий мультивалютный итог. Аналитика читает сохранённую историю операций и не рассчитывает налоги, доходность (её показывает [результат инвестирования](../portfolio/investment-performance.md)), валютную конвертацию или общий мультивалютный итог.
Основное проверяемое поведение — в [спецификации дохода](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-income-analytics/spec.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). точная форма ответа — в [HTTP API](../../architecture/http-api.md).
+10 -1
View File
@@ -71,7 +71,16 @@
без замены периода и без запуска личной работы. без замены периода и без запуска личной работы.
Фильтрация на сервере предшествует сортировке и вычислению Фильтрация на сервере предшествует сортировке и вычислению
`incomeTotalsByCurrency`, включающего только купоны и дивиденды. `incomeTotalsByCurrency`, включающего только купоны и дивиденды.
Итоги остаются в ответе API; полный экран показывает список без общего итога. Итоги остаются в ответе API; полный экран показывает список без общего итога списка.
В режиме «Все счета» события одного инструмента, даты, точного типа и `dateKind` образуют
группу, если их две и более, в том числе одного счёта. Ключ дополнительно включает `dateKind`,
поэтому дата реестра дивиденда и иная дата того же инструмента не объединяются. Группа стоит на месте первого
события. Полный список группирует все полученные события; превью обзора сначала берёт пять
событий и группирует только их. Каждая строка счёта остаётся отдельной и показывает название
счёта и свою прогнозную сумму. Визуальный итог группы — точная сумма по валютам; он показывается
только если полнота прогноза `complete` и у каждого события группы известна прогнозная сумма.
Иначе итог скрыт, а строки остаются. Этот итог не равен итогам ответа API и не относится к полученному доходу.
## Получение и отказы ## Получение и отказы
+21 -3
View File
@@ -13,9 +13,27 @@
Исполненная операция содержит тип события, его категорию, описание, Исполненная операция содержит тип события, его категорию, описание,
момент совершения, доступные сведения об инструменте и денежное движение. момент совершения, доступные сведения об инструменте и денежное движение.
Запросы отменённых или ожидающих исполнения поручений не образуют этот журнал. Запросы отменённых или ожидающих исполнения поручений не образуют этот журнал.
Суммы остаются точными десятичными строками в исходной валюте; frontend Суммы остаются точными десятичными строками в исходной валюте. Frontend
только форматирует их. Положительное и отрицательное движение сохраняют форматирует их и считает визуальный итог группы по показанным записям
знак. Отсутствие `payment` означает «Без движения денег», а не нулевую сумму. (см. [группировку](#группировка-в-режиме-все-счета)). Положительное и отрицательное
движение сохраняют знак. Отсутствие `payment` означает «Без движения денег», а не нулевую сумму.
## Группировка в режиме «Все счета»
Только в режиме `all` записи журнала и превью обзора, которые относятся к одному
дню экрана, одному инструменту и точному типу операции, объединяются в группу.
Ключом служат день, `instrumentUid` (иначе `figi`) и тип; группа образуется при
двух и более записях, в том числе одного счёта. Группа стоит на месте первой записи.
Каждая запись остаётся отдельной строкой с названием счёта простым текстом и своей суммой.
Одиночный счёт не группируется.
Группировка выполняется после лимита: превью обзора сначала берёт пять записей,
затем группирует только их. Полный журнал группирует страницу, уже загруженную
курсором; группы не переносятся между страницами. Курсор и порядок записей не меняются.
Итог группы — точная сумма по каждой валюте, без конвертации. Он показывается только
если полнота журнала `complete`, у каждой записи группы есть `payment`, и ни одна сумма
не равна нулю. Иначе итог скрыт, а строки счетов остаются.
Категории объединяют разные события для фильтрации: сделка (`trade`), Категории объединяют разные события для фильтрации: сделка (`trade`),
доход (`income`), налог (`tax`), комиссия (`fee`), перевод (`transfer`) доход (`income`), налог (`tax`), комиссия (`fee`), перевод (`transfer`)
+12
View File
@@ -59,6 +59,12 @@ Backend публикует статические маршруты под `/api/
состав счетов влияет только на фактические выплаты выбранного периода. состав счетов влияет только на фактические выплаты выбранного периода.
- Будущие события сортируются по дате и сохраняют отдельную строку каждого счёта. - Будущие события сортируются по дате и сохраняют отдельную строку каждого счёта.
Прогноз не включается в реализованные доходы. Прогноз не включается в реализованные доходы.
- [Результат инвестирования](investment-performance.md) складывает потоки и
стоимость только рублёвых счетов с полной историей. Счёт с причиной
недоступности исключается, сводка помечается неполной. Похожие на перевод
между своими счетами операции без подтверждённой связи исключают оба счёта
(`UNCONFIRMED_TRANSFER`). Если оценки оставшихся счетов сделаны в разные
московские дни, сводка недоступна: стоимости разных дат не складываются.
## Полнота, свежесть и доступ ## Полнота, свежесть и доступ
@@ -81,6 +87,12 @@ Backend публикует статические маршруты под `/api/
применённые фильтры. Coverage/status без изменения строк не ломают cursor. применённые фильтры. Coverage/status без изменения строк не ломают cursor.
Продолжение не зависит от RAM-кэша или перезапуска. Продолжение не зависит от RAM-кэша или перезапуска.
Визуальная группировка строк одного инструмента в общих списках выполняется только
в пределах уже полученного списка: в журнале — на загруженной странице, без переноса группы
на следующую; в полном списке будущих событий и выплатах дохода — по всем полученным строкам
периода. Группировка не меняет порядок, cursor или ответ API. Протокол курсора прежний.
Правила группировки — в [журнале](../operations/overview.md#группировка-в-режиме-все-счета).
Повторная сверка полного показанного периода публикует все счета и интервалы Повторная сверка полного показанного периода публикует все счета и интервалы
одной транзакцией; успешное первое окно не смешивается с прежними остальными. одной транзакцией; успешное первое окно не смешивается с прежними остальными.
Подробнее — [журнал](../operations/overview.md#получение-и-полнота). Подробнее — [журнал](../operations/overview.md#получение-и-полнота).
+81
View File
@@ -0,0 +1,81 @@
# Концентрация портфеля
Вкладка «Концентрация» на странице счёта `/accounts/:accountId` показывает,
какие акции, облигации, секторы и эмитенты занимают наибольшую часть текущей
оценки Portfolio. Она отвечает на вопрос «где сконцентрированы деньги»,
который не решают карточки позиций и [структура
активов](overview.md#как-читать-экран). Точный смысл полей и долей — в
[справочнике данных](data-reference.md).
## Вкладка и разрезы
Вкладка есть только у отдельного счёта: в режиме [«Все счета»](all-accounts.md)
её нет. Появление вкладки не меняет вкладку, открытую по умолчанию («Позиции»),
и содержание остальных вкладок. Пока Portfolio загружается, при его ошибке или
штатном пустом составе вкладки нет; при Portfolio хотя бы с одной позицией или
денежным остатком вкладка показывается.
Вкладка содержит четыре разреза и показывает один из них одновременно:
| Разрез | Что ранжирует | Типы позиций |
| --- | --- | --- |
| «Акции» | Пять крупнейших позиций акций | `STOCK` |
| «Облигации» | Пять крупнейших позиций облигаций | `BOND` |
| «Секторы» | Пять крупнейших секторов по данным T-Bank | `STOCK`, `BOND`, `FUND` |
| «Эмитенты» | Пять крупнейших эмитентов (брендов актива) | `STOCK`, `BOND` |
Фонды участвуют только в разрезе «Секторы»: их эмитент — управляющая компания,
а не компания внутри фонда. Позиции типов `CURRENCY`, `FUTURE`, `OPTION`,
`STRUCTURED_NOTE`, `DIGITAL_FINANCIAL_ASSET` и `OTHER` во вкладку не входят.
Позиции без сектора образуют явную группу «Сектор не указан», без бренда —
«Эмитент не указан»; обе группы показываются с видимой долей. Государственные
облигации определяются значением сектора T-Bank `government` с подписью
«Государственный сектор»; других собственных классификаций WealthLedger
не вводит. Название элемента в разрезе эмитентов — название бренда; если у
одного бренда разные названия, показывается название позиции с наибольшей
стоимостью.
## Топ-5 и «Прочее»
В каждом разрезе показаны пять крупнейших элементов по текущей денежной оценке,
каждый с суммой и долей. Рядом показан суммарный показатель «Топ-5 · N %».
Если в разрезе семь и более элементов, все остальные объединены в одну строку
«Прочее» с числом элементов, суммой и долей. При шести и меньшем числе
элементов каждый показан своей строкой. По нажатию на «Прочее» раскрывается
полный перечень остальных элементов, каждый со своей суммой и долей; топ-5
и диаграмма при этом не меняются.
Доли считаются от суммы стоимостей позиций разреза в выбранной валюте и
в сумме дают ровно 100 %; округление — до десятой доли процента, очень малые
доли показываются как «< 0,1 %». Отрицательные и нулевые стоимости, а также
позиции без оценки в разрез не входят. Конвертации валют нет: каждая позиция
входит только в валюту своей оценки.
## Валюта
У вкладки свой список валют — валюты оценок позиций акций, облигаций и фондов,
отсортированные по ISO-коду, — и свой выбор, независимый от «Структуры
портфеля». По умолчанию выбрана первая валюта списка. При одной валюте
переключатель не показывается. Общего итога по нескольким валютам нет.
Выбранные разрез и валюта сохраняются, пока пользователь остаётся на странице
счёта, включая переключение её вкладок, и сбрасываются при новом заходе и при
смене счёта.
## Диаграмма и неполные данные
Рядом с перечнем показана кольцевая диаграмма, согласованная с перечнем;
информация передаётся не только цветом, есть текстовый эквивалент. Если ответ
отмечен как неполный (`completeness: "incomplete"` — часть позиций не имеет
оценки), над разрезом появляется предупреждение «Данные неполные», а под
списком — сноска «Не учтено позиций: N» с числом позиций, не вошедших в разрез.
Пустой разрез (нет позиций с положительной оценкой в выбранной валюте)
показывает текстовое состояние вместо диаграммы.
## Откуда берутся сектор и эмитент
Сектор и эмитент — часть данных Portfolio: их получает backend в часовом
обходе и сохраняет в снимке вместе с остальными полями позиций
([поток загрузки](flows/portfolio-loading.md#обновление-сектора-и-эмитента),
[интеграция T-Bank](../../integrations/tbank.md)). Вкладка считается по уже
загруженному снимку и не обращается к T-Bank.
+16
View File
@@ -33,6 +33,8 @@
| `totalsByCurrency` | Стоимости позиций + доступные деньги той же валюты | Не включает невычислимую стоимость; не обещает полную оценку портфеля | | `totalsByCurrency` | Стоимости позиций + доступные деньги той же валюты | Не включает невычислимую стоимость; не обещает полную оценку портфеля |
| `lastSuccessfulAt` | Время формирования успешного снимка, UTC | Не является временем каждой котировки; запасной снимок сохраняет исходное время | | `lastSuccessfulAt` | Время формирования успешного снимка, UTC | Не является временем каждой котировки; запасной снимок сохраняет исходное время |
| `isStale` | Истёк часовой срок свежести сохранённого portfolio source | Не описывает состояние клиентского кэша или ошибку фонового запроса | | `isStale` | Истёк часовой срок свежести сохранённого portfolio source | Не описывает состояние клиентского кэша или ошибку фонового запроса |
| `sector` | Сектор инструмента в том виде, как его определяет T-Bank, с обрезанными пробелами | Только для позиций `STOCK`, `BOND` и `FUND`; значение из одних пробел считается отсутствующим. Во всех остальных случаях — `null` |
| `issuer` | Бренд актива инструмента по данным T-Bank: `{ id, name }` | Только для позиций `STOCK`, `BOND` и `FUND`, когда у актива известны uid и название бренда. Во всех остальных случаях — `null`. Не влияет на количество, стоимость и свежесть снимка; используется вкладкой [«Концентрация»](concentration.md) |
Ненулевые денежные остатки одной валюты суммируются. Позиции одинаковых Ненулевые денежные остатки одной валюты суммируются. Позиции одинаковых
инструментов в ответе отдельного счёта не объединяются; визуальная группировка инструментов в ответе отдельного счёта не объединяются; визуальная группировка
@@ -44,6 +46,18 @@
отдельного счёта не создают валютные карточки. Если `totalsByCurrency` пуст, отдельного счёта не создают валютные карточки. Если `totalsByCurrency` пуст,
но позиции или деньги присутствуют, это не пустой портфель. но позиции или деньги присутствуют, это не пустой портфель.
## Результат инвестирования
| Поле | Смысл | Ограничение |
| --- | --- | --- |
| `netInvested`, `currentValue`, `profit` | Чистые внешние потоки, сумма RUB-оценок, их разность | Только RUB, копейки |
| `return.value` | Доля XIRR (`0.151` = 15,1 %) | `null` с `unavailableReason`; при `basis = period` — за весь период |
| `benchmark`, `reference` | Те же потоки в MCFTRR и IMOEX | IMOEX только справка |
| `outperformance` | Разница долей и стоимостей с MCFTRR | `null`, если любая сторона недоступна |
| `series` | Точки торговых дней MCFTRR | Пусто без истории индекса |
Подробнее — [результат инвестирования](investment-performance.md).
## Идентификация и оформление инструмента ## Идентификация и оформление инструмента
UID определяет конкретный инструмент брокера и используется для перехода UID определяет конкретный инструмент брокера и используется для перехода
@@ -58,6 +72,8 @@ ISIN, тикер и `logoName` могут быть `null`. `logoName` — имя
или метаданных не меняет количество, суммы и свежесть снимка и не скрывает или метаданных не меняет количество, суммы и свежесть снимка и не скрывает
финансовые данные. Metadata обновляется независимо; отсутствие имени не задерживает выдачу финансовые данные. Metadata обновляется независимо; отсутствие имени не задерживает выдачу
финансового снимка и оставляет доступный идентификатор. финансового снимка и оставляет доступный идентификатор.
`logoName` не равен эмитенту из вкладки «Концентрация»: он берётся из metadata
инструмента, а эмитент — из [классификации инструмента](flows/portfolio-loading.md#обновление-сектора-и-эмитента).
## Структура и доли ## Структура и доли
+22 -3
View File
@@ -11,9 +11,28 @@
они не ждут и не вызывают T-Bank и не предлагают ручную синхронизацию. Часовой они не ждут и не вызывают T-Bank и не предлагают ручную синхронизацию. Часовой
обход текущего подключения обновляет Portfolio независимо от экрана. Для нового обход текущего подключения обновляет Portfolio независимо от экрана. Для нового
полного snapshot ему нужны оба финансовых ответа `GetPortfolio` и полного snapshot ему нужны оба финансовых ответа `GetPortfolio` и
`GetPositions`; суммы и позиции публикуются вместе. Metadata инструмента `GetPositions`; суммы и позиции публикуются вместе. Metadata инструмента и
дополняет представление независимо, с доступным UID/FIGI вместо имени до её классификация (сектор и эмитент) дополняют представление независимо,
онлайн-загрузки. с доступным UID/FIGI вместо имени до их загрузки.
## Обновление сектора и эмитента
Часовой обход также обновляет сектор и эмитента позиций типов `share`, `bond`
и `etf`. Значения хранятся как последнее известное ([ADR
022](../../../adr/022-stored-instrument-classification.md)): сектор — как его
определяет T-Bank, эмитент — бренд актива (`uid` и `name`). Каждое поле
обновляется при первом обходе, начавшемся не ранее чем через 24 часа после его
последнего успешного получения; пока значение не получено, попытка повторяется
в каждом обходе. Сектор и эмитент обновляются независимо.
Неуспешное получение не мешает публикации snapshot и не меняет
`lastSuccessfulAt` и `isStale`; сохранённое значение остаётся доступным.
Исключение — отказ token: он обрабатывается так же, как отказ token при
обходе Portfolio. Инструмент ищется по UID, затем по FIGI; ответ «не найдено»
сохраняет прежнее значение и отсчёт 24 часов начинается заново. Позиции без
полученных значений участвуют в разрезах вкладки
[«Концентрация»](../concentration.md) как «Сектор не указан» и
«Эмитент не указан».
Открытый экран выполняет локальное перечитывание не чаще одного раза в Открытый экран выполняет локальное перечитывание не чаще одного раза в
30 минут; retry также повторяет только HTTP GET. `data: null` означает, что 30 минут; retry также повторяет только HTTP GET. `data: null` означает, что
@@ -0,0 +1,68 @@
# Результат инвестирования
Вкладка «Результат» на странице брокерского счёта и в режиме
[«Все счета»](all-accounts.md) отвечает на два вопроса:
- сколько заработали вложенные деньги;
- обогнал ли пользователь рынок.
Результат считается за всё время истории счёта, только в рублях. Решение и
отклонённые альтернативы описаны в
[ADR 023](../../adr/023-investment-performance-vs-index.md).
## Показатели
| Показатель | Смысл |
| --- | --- |
| Вложено (нетто) | Сумма пополнений минус сумма выводов рублей |
| Прибыль | Текущая стоимость портфеля минус чистые вложения |
| Доходность | Ставка XIRR с учётом сумм и дат пополнений и выводов |
| Те же деньги в индексе Мосбиржи | Стоимость и доходность тех же пополнений и выводов, вложенных в индекс полной доходности MCFTRR (с дивидендами после налогов) |
| Опережение или отставание рынка | Разница доходностей в процентных пунктах и разница стоимостей в рублях |
Ценовой индекс IMOEX не учитывает дивиденды. Он показан на графике только
для справки и в сравнении не участвует.
Период начинается с первого пополнения, а не с открытия счёта. Если деньги
вложены меньше 365 дней, проценты показаны за фактическое число дней и не
пересчитываются в годовые.
## Какие операции считаются потоками
Внешние потоки — только пополнения и выводы рублей (типы T-Bank `INPUT`,
`INPUT_ACQUIRING`, `INPUT_SWIFT`, `INP_MULTI` и соответствующие `OUTPUT`).
Сделки, купоны, дивиденды, погашения, налоги и комиссии уже отражены в
текущей стоимости и потоками не являются. Учитываются только операции до
момента последней полной оценки портфеля.
## Когда результата нет
Число не подменяется нулём: вместо него показывается причина.
| Причина | Когда возникает |
| --- | --- |
| `HISTORY_START_UNKNOWN` | Неизвестна дата открытия счёта |
| `OPERATIONS_HISTORY_INCOMPLETE` | История операций не получена полностью до момента оценки |
| `PORTFOLIO_VALUATION_INCOMPLETE` | Нет полной оценки портфеля |
| `NON_RUB_ACTIVITY` | Есть потоки или остатки не в рублях |
| `UNVALUED_SECURITIES_TRANSFER` | Бумаги введены или выведены без денежной оценки |
| `UNCONFIRMED_TRANSFER` | В «Все счета» есть похожие на перевод между своими счетами операции, связь которых источник не подтверждает |
| `ACCOUNT_VALUATION_DATES_DIFFER` | В «Все счета» последние оценки счетов сделаны в разные московские дни |
| `NO_SOLUTION` | Уравнение доходности не имеет решения; прибыль остаётся |
| `AMBIGUOUS_RETURN` | Потоки меняют знак больше одного раза, и единственность ставки не доказана; прибыль остаётся |
| `INDEX_HISTORY_UNAVAILABLE` | Нет истории индекса за период; прибыль и доходность остаются, сравнения нет |
В «Все счета» счёт с причиной исключается, а результат помечается
неполным со списком исключённых счетов. Если считать не по чему, результат
недоступен целиком.
## График
На графике показаны ступенчатая линия чистых вложений, стоимость тех же
денег в индексе по закрытиям торговых дней, справочная линия индекса без
дивидендов и отметка текущей стоимости портфеля на конец периода. Линии
прошлой стоимости портфеля нет: такая история пока не хранится.
Нормативное поведение задаёт change
`2026-11-07-investment-performance-vs-index` в репозитории specs, форму
ответа — [HTTP API](../../architecture/http-api.md#investment-performance).
+6
View File
@@ -29,6 +29,8 @@ Backend получает данные через серверную
самостоятельно: его ошибка или отсутствие данных не скрывает основной самостоятельно: его ошибка или отсутствие данных не скрывает основной
портфель. Полученные выплаты и прогноз не добавляются к текущей стоимости портфель. Полученные выплаты и прогноз не добавляются к текущей стоимости
повторно и не превращают результат открытой позиции в полную доходность. повторно и не превращают результат открытой позиции в полную доходность.
Полную доходность с учётом пополнений и выводов показывает вкладка
[«Результат»](investment-performance.md).
## Как читать экран ## Как читать экран
@@ -64,6 +66,10 @@ Backend получает данные через серверную
без логотипа и с подписью результата. Отсутствующие показатели скрываются, без логотипа и с подписью результата. Отсутствующие показатели скрываются,
а не подменяются нулём или заглушкой. а не подменяются нулём или заглушкой.
Рядом с вкладкой «Позиции» на странице отдельного счёта есть вкладка
[«Концентрация»](concentration.md): она показывает, где сконцентрированы
деньги — крупнейшие бумаги, секторы и эмитенты. В режиме «Все счета» её нет.
Существует расхождение: [согласованная спецификация представления позиций](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-представление-карточек-позиций-на-клиенте) Существует расхождение: [согласованная спецификация представления позиций](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-представление-карточек-позиций-на-клиенте)
требует доступности всех иных типов в отдельной секции, а текущие frontend требует доступности всех иных типов в отдельной секции, а текущие frontend
и его проверка исключают `CURRENCY` только из списка карточек. Такие позиции и его проверка исключают `CURRENCY` только из списка карточек. Такие позиции
+86 -6
View File
@@ -119,6 +119,46 @@ Instrument. Она остаётся в `positions` и не объединяет
_Не использовать_: денежный остаток, cash balance. _Не использовать_: денежный остаток, cash balance.
## Концентрация
Вкладка страницы одного Brokerage Account, показывающая, какие акции,
облигации, секторы и эмитенты занимают наибольшую часть текущей оценки
Portfolio, без валютной конвертации. Отличается от [структуры
портфеля](domains/portfolio/data-reference.md#структура-и-доли): в её разрезах
деньги не входят в знаменатель долей.
_Не использовать_: структура портфеля, диверсификация.
## Разрез
Одна из четырёх группировок вкладки «Концентрация»: «Акции», «Облигации»,
«Секторы», «Эмитенты». Элемент разреза — единица ранжирования; сектор
агрегирует позиции `STOCK`, `BOND` и `FUND`, эмитенты — `STOCK` и `BOND`.
_Не использовать_: фильтр, категория структуры.
## Сектор
Значение сектора инструмента в том виде, как его определяет T-Bank, без
перекодирования и собственной классификации WealthLedger. Заполняется для
позиций `STOCK`, `BOND` и `FUND`; государственные облигации определяются
значением `government` с подписью «Государственный сектор». Позиция без
сектора попадает в явную группу «Сектор не указан».
_Не использовать_: отрасль, собственная классификация приложения.
## Эмитент
В разрезе «Эмитенты» вкладки «Концентрация» — бренд актива по данным T-Bank,
объединяющий акции и облигации одной компании; фонды в разрез не входят.
Название эмитента — название бренда. **Оговорка:** в этом разрезе слово
означает бренд актива T-Bank, а не юридического эмитента ценной бумаги;
[рейтинг выпуска](integrations/nkr.md) не подставляется вместо рейтинга
эмитента и наоборот. Позиция без бренда попадает в явную группу
«Эмитент не указан».
_Не использовать_: юридический эмитент, эмитент рейтинга.
## Instrument ## Instrument
Ценная бумага или иной торгуемый актив T-Bank, на который ссылается Portfolio Ценная бумага или иной торгуемый актив T-Bank, на который ссылается Portfolio
@@ -165,9 +205,10 @@ _Не использовать_: история портфеля, snapshot дл
Полная оценка одного Brokerage Account в одной валюте на момент успешного Полная оценка одного Brokerage Account в одной валюте на момент успешного
обновления Portfolio; последовательность таких точек описывает изменение обновления Portfolio; последовательность таких точек описывает изменение
оценки во времени, включая неторговые дни. Не является Portfolio Snapshot оценки во времени, включая неторговые дни. Не является Portfolio Snapshot
или инвестиционной доходностью; отсутствие цены позиции не приравнивается к нулю. или [Результатом инвестирования](#результат-инвестирования); отсутствие цены
позиции не приравнивается к нулю.
_Не использовать_: снимок состава портфеля, доходность. _Не использовать_: снимок состава портфеля.
## Client Cached Snapshot ## Client Cached Snapshot
@@ -271,8 +312,47 @@ _Не использовать_: upstream dividend type, MOEX row name.
Результат анализа исполненных купонов и дивидендов по счёту за календарный Результат анализа исполненных купонов и дивидендов по счёту за календарный
период включительно в часовом поясе Europe/Moscow. Сохраняет исходные валюты период включительно в часовом поясе Europe/Moscow. Сохраняет исходные валюты
и знаки денежных выплат, переданных брокером. Это не налоговый расчёт, и знаки денежных выплат, переданных брокером. Это не налоговый расчёт,
прогноз или инвестиционная доходность; правила расчёта объясняет прогноз или [Результат инвестирования](#результат-инвестирования); правила
[область дохода](domains/income/overview.md). расчёта объясняет [область дохода](domains/income/overview.md).
_Не использовать_: доходность, общий доход в одной валюте, гарантированный _Не использовать_: доходность выплат, общий доход в одной валюте,
доход. гарантированный доход.
## Результат инвестирования
Прибыль, доходность и сравнение с индексом Мосбиржи для счёта или «Все
счета» за всё время в рублях. Правила описаны в
[результате инвестирования](domains/portfolio/investment-performance.md),
решение — в [ADR 023](adr/023-investment-performance-vs-index.md).
_Не использовать_: нереализованный результат, доход.
## Внешний денежный поток
Пополнение (положительный поток) или вывод (отрицательный поток) рублей со
счёта. Сделки, выплаты, налоги и комиссии внешними потоками не являются.
_Не использовать_: операция, выплата.
## Доходность (XIRR)
Годовая ставка, при которой дисконтированные по фактическим дням внешние
потоки и текущая стоимость в сумме дают ноль. При истории короче года
показывается доходность за период. Единственное значение термина
«доходность» для личного результата.
_Не использовать_: доходность к погашению, рост цены позиции.
## Те же деньги в индексе Мосбиржи
Стоимость и доходность тех же внешних потоков, вложенных в индекс полной
доходности MCFTRR по закрытию дня потока. Ценовой IMOEX — только справка.
_Не использовать_: бенчмарк IMOEX, теневой портфель (в интерфейсе).
## Опережение рынка
Разница доходности пользователя и тех же денег в индексе в процентных пунктах
и разница стоимостей в рублях; отрицательное значение называется отставанием.
_Не использовать_: альфа.
+28 -1
View File
@@ -1,6 +1,7 @@
# Интеграция с MOEX ISS # Интеграция с MOEX ISS
MOEX ISS дополняет данные T-Bank публичной информацией об облигациях. MOEX ISS дополняет данные T-Bank публичной информацией об облигациях и
дневными закрытиями индексов Мосбиржи.
Backend получает её по HTTPS в JSON с `iss.moex.com`; запросы содержат Backend получает её по HTTPS в JSON с `iss.moex.com`; запросы содержат
идентификаторы выпуска, без token T-Bank и идентификатора брокерского счёта. идентификаторы выпуска, без token T-Bank и идентификатора брокерского счёта.
MOEX не определяет доступ пользователя к инвестициям и не подтверждает MOEX не определяет доступ пользователя к инвестициям и не подтверждает
@@ -47,6 +48,32 @@ HTTP-запрос ограничен таймаутом. Сеть, неуспе
[странице облигации](../domains/instruments/bonds.md#получение-данных-и-восстановление). [странице облигации](../domains/instruments/bonds.md#получение-данных-и-восстановление).
Наличие успешного HTTP-ответа не гарантирует котировку без задержки. Наличие успешного HTTP-ответа не гарантирует котировку без задержки.
## Индексы
Для [результата инвестирования](../domains/portfolio/investment-performance.md)
backend читает дневные закрытия MCFTRR (полная доходность после налогов) и
IMOEX (ценовой, справка) из
`/iss/history/engines/stock/markets/index/securities/{SECID}.json` с
`history.columns=TRADEDATE,CLOSE`, постранично по `history.cursor`.
Любая некорректная страница считается сбоем: частичная история не
сохраняется.
Закрытия и подтверждённый диапазон покрытия хранятся локально в SQLite
(исключение из ADR 019, см. [ADR 023](../adr/023-investment-performance-vs-index.md)).
Догрузка выполняется при чтении результата одной одновременной загрузкой на
индекс: расширение диапазона назад — сразу, проверка ещё не опубликованного
закрытия и повтор после сбоя — не чаще раза в час. Недоступность
MOEX не возвращается как HTTP-ошибка: при достаточной сохранённой истории
результат считается по ней, иначе сравнение помечается
`INDEX_HISTORY_UNAVAILABLE`. События лога —
`integration.moex.index_history.succeeded` и `.failed`, без данных
пользователя.
ISS бесплатно отдаёт данные для ознакомления; приложение личное и не
распространяет их, источник указан в пояснении «Как считали».
## Ссылки
Условия обработки источника в прогнозе закреплены в Условия обработки источника в прогнозе закреплены в
[спецификации будущих событий](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-upcoming-events/spec.md#requirement-upcoming-events-use-complete-source-results). [спецификации будущих событий](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/brokerage-account-upcoming-events/spec.md#requirement-upcoming-events-use-complete-source-results).
Точный механизм транспорта, разбора и сопоставления реализован в Точный механизм транспорта, разбора и сопоставления реализован в
+12 -1
View File
@@ -14,8 +14,17 @@ WealthLedger обращается к Invest API на сервере по gRPC ov
| Портфель и денежные остатки | Показать текущий состав инвестиций и итоги по исходным валютам | | Портфель и денежные остатки | Показать текущий состав инвестиций и итоги по исходным валютам |
| Исполненные операции | Построить журнал и выделить полученные купоны и дивиденды | | Исполненные операции | Построить журнал и выделить полученные купоны и дивиденды |
| Инструменты и рынок | Имена, идентификаторы, цены, свечи, параметры акций и облигаций | | Инструменты и рынок | Имена, идентификаторы, цены, свечи, параметры акций и облигаций |
| Классификация инструментов | Сектор акций, облигаций и фондов и бренд актива для разрезов [концентрации](../domains/portfolio/concentration.md) |
| Купоны и дивиденды | Построить будущие события по текущим позициям и календарь инструмента | | Купоны и дивиденды | Построить будущие события по текущим позициям и календарь инструмента |
Сектор получается из описаний инструмента (отдельные описания для акций,
облигаций и фондов, поиск по UID, затем по FIGI); эмитент — из описания актива
по `asset_uid` инструмента: его uid и название бренда. Эмитент не выводится из
названия, тикера или ISIN. Классификация хранится как последнее известное
значение с обновлением не чаще раза в сутки ([ADR
022](../adr/022-stored-instrument-classification.md)); все вызовы проходят
через общий лимитер запросов T-Bank.
T-Bank определяет исходные данные и права token. Backend проверяет локальную T-Bank определяет исходные данные и права token. Backend проверяет локальную
сессию, выбирает подключение её пользователя и устанавливает доступ к счёту. сессию, выбирает подключение её пользователя и устанавливает доступ к счёту.
Наличие идентификатора счёта в URL этого доступа не подтверждает. Наличие идентификатора счёта в URL этого доступа не подтверждает.
@@ -41,7 +50,9 @@ Backend передаёт имя файла `logoName` из метаданных
Логотипы используются в портфеле, операциях, будущих событиях, поиске и Логотипы используются в портфеле, операциях, будущих событиях, поиске и
страницах инструментов. Отсутствующее имя или ошибка изображения оставляют страницах инструментов. Отсутствующее имя или ошибка изображения оставляют
буквенную заглушку и не меняют финансовые значения. Правило построения URL буквенную заглушку и не меняют финансовые значения. Логотип — это metadata
инструмента; он не равен эмитенту из [концентрации](../domains/portfolio/concentration.md),
который берётся из бренда актива. Правило построения URL
принадлежит [форматированию frontend](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/src/shared/lib/display.ts). принадлежит [форматированию frontend](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/src/shared/lib/display.ts).
## Проверка и сохранение подключения ## Проверка и сохранение подключения
+5 -2
View File
@@ -20,6 +20,7 @@ WealthLedger — локальное приложение для частного
| Найти свой инвестиционный счёт | [Список счетов](domains/accounts/overview.md), их типы и доступные сводки | | Найти свой инвестиционный счёт | [Список счетов](domains/accounts/overview.md), их типы и доступные сводки |
| Вести банковские карты | [Ручной учёт](domains/cards/overview.md): независимые балансы, расходы, пополнения, переводы, возвраты и месячные сводки | | Вести банковские карты | [Ручной учёт](domains/cards/overview.md): независимые балансы, расходы, пополнения, переводы, возвраты и месячные сводки |
| Понять текущий состав инвестиций | [Портфель](domains/portfolio/overview.md): позиции, денежные остатки и итоги по валютам | | Понять текущий состав инвестиций | [Портфель](domains/portfolio/overview.md): позиции, денежные остатки и итоги по валютам |
| Увидеть, где сконцентрированы деньги | [Концентрация](domains/portfolio/concentration.md): крупнейшие бумаги, секторы и эмитенты счёта |
| Разобрать уже совершённые движения | [Журнал операций](domains/operations/overview.md): исполненные операции с фильтрами и последовательной загрузкой истории | | Разобрать уже совершённые движения | [Журнал операций](domains/operations/overview.md): исполненные операции с фильтрами и последовательной загрузкой истории |
| Посчитать полученные выплаты | [Пассивный доход](domains/income/overview.md): исполненные купоны и дивиденды за календарный период | | Посчитать полученные выплаты | [Пассивный доход](domains/income/overview.md): исполненные купоны и дивиденды за календарный период |
| Посмотреть ожидаемые события | [Будущие выплаты](domains/income/upcoming-events.md): купоны, дивиденды, погашения и оферты по текущим позициям | | Посмотреть ожидаемые события | [Будущие выплаты](domains/income/upcoming-events.md): купоны, дивиденды, погашения и оферты по текущим позициям |
@@ -40,8 +41,10 @@ WealthLedger — локальное приложение для частного
Портфель отвечает на вопрос «что есть сейчас», журнал — «что исполнено», Портфель отвечает на вопрос «что есть сейчас», журнал — «что исполнено»,
аналитика дохода — «какие выплаты уже получены», будущие события — «что аналитика дохода — «какие выплаты уже получены», будущие события — «что
ожидается». Прогноз не подтверждает фактическое зачисление. Полученные купоны ожидается», [результат инвестирования](domains/portfolio/investment-performance.md) —
и дивиденды не равны инвестиционной доходности или результату после налогов. «как сработали вложенные деньги и обогнал ли я индекс Мосбиржи». Прогноз не
подтверждает фактическое зачисление. Полученные купоны и дивиденды сами по
себе не равны доходности или результату после налогов.
Денежная сумма всегда относится к одной валюте. Недоступное значение Денежная сумма всегда относится к одной валюте. Недоступное значение
отличается от нуля, а объединение счетов не означает автоматическую отличается от нуля, а объединение счетов не означает автоматическую
+14 -6
View File
@@ -1,12 +1,12 @@
{ {
"name": "wealth-ledger-docs", "name": "wealth-ledger-docs",
"version": "0.1.6", "version": "0.1.8",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "wealth-ledger-docs", "name": "wealth-ledger-docs",
"version": "0.1.6", "version": "0.1.8",
"devDependencies": { "devDependencies": {
"@commitlint/cli": "21.2.4", "@commitlint/cli": "21.2.4",
"@commitlint/config-conventional": "21.2.4", "@commitlint/config-conventional": "21.2.4",
@@ -677,6 +677,13 @@
} }
} }
}, },
"node_modules/@isaacs/nonrecursive-glob-match": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@isaacs/nonrecursive-glob-match/-/nonrecursive-glob-match-1.0.3.tgz",
"integrity": "sha512-SI7kyKHq4XXDRozw+r9GlRZkia1ZkFrpLW98r2IC2Bpr8YkFGE3fvjzmxCG0x78A++69HmDNrDvSMYBZmHGpKA==",
"dev": true,
"license": "BlueOak-1.0.0"
},
"node_modules/@octokit/auth-token": { "node_modules/@octokit/auth-token": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz",
@@ -3249,13 +3256,14 @@
} }
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "10.2.6", "version": "11.0.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-11.0.2.tgz",
"integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "integrity": "sha512-pWTgz5CN3tjqyZNSK2tePlSmu9IwM41q8yn8Usdqqwe9TIHh+9dEpLLjF4uApsfPEAZY71qtSiA2B2APEr+1Yw==",
"dev": true, "dev": true,
"license": "BlueOak-1.0.0", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
"brace-expansion": "^5.0.8" "@isaacs/nonrecursive-glob-match": "^1.0.2",
"brace-expansion": "^5.0.12"
}, },
"engines": { "engines": {
"node": "18 || 20 || >=22" "node": "18 || 20 || >=22"
+2 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "wealth-ledger-docs", "name": "wealth-ledger-docs",
"version": "0.1.6", "version": "0.1.8",
"private": true, "private": true,
"engines": { "engines": {
"node": "24.22.1" "node": "24.22.1"
@@ -26,6 +26,7 @@
"katex": "0.19.1", "katex": "0.19.1",
"markdownlint-cli@0.49.1": { "markdownlint-cli@0.49.1": {
"js-yaml": "5.4.2", "js-yaml": "5.4.2",
"minimatch": "11.0.2",
"smol-toml": "1.9.1" "smol-toml": "1.9.1"
}, },
"release-it": { "release-it": {
+30 -1
View File
@@ -2,7 +2,8 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json", "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [ "extends": [
"config:recommended", "config:recommended",
":semanticCommitTypeAll(chore)" ":semanticCommitTypeAll(chore)",
"security:minimumReleaseAgeNpm"
], ],
"timezone": "Europe/Moscow", "timezone": "Europe/Moscow",
"labels": [ "labels": [
@@ -10,6 +11,13 @@
], ],
"dependencyDashboard": true, "dependencyDashboard": true,
"rangeStrategy": "pin", "rangeStrategy": "pin",
"prConcurrentLimit": 5,
"lockFileMaintenance": {
"enabled": true,
"schedule": [
"before 4am on the first day of the month"
]
},
"packageRules": [ "packageRules": [
{ {
"matchUpdateTypes": [ "matchUpdateTypes": [
@@ -18,6 +26,27 @@
], ],
"groupName": "non-major dependencies" "groupName": "non-major dependencies"
}, },
{
"matchManagers": [
"github-actions"
],
"matchUpdateTypes": [
"minor",
"patch"
],
"groupName": "non-major github actions"
},
{
"matchManagers": [
"docker-compose",
"dockerfile"
],
"matchUpdateTypes": [
"minor",
"patch"
],
"groupName": "non-major container images"
},
{ {
"matchUpdateTypes": [ "matchUpdateTypes": [
"major" "major"