diff --git a/openspec/changes/archive/2026-08-19-source-share-dividends-from-tbank/design.md b/openspec/changes/archive/2026-08-19-source-share-dividends-from-tbank/design.md new file mode 100644 index 0000000..f8c82eb --- /dev/null +++ b/openspec/changes/archive/2026-08-19-source-share-dividends-from-tbank/design.md @@ -0,0 +1,44 @@ +## Context + +См. motivation в `proposal.md`. Прогноз будущих событий (`GET /api/v1/brokerage-account-upcoming-events`) строится read-through: текущие Portfolio Position из T-Bank обогащаются данными источников. Дивиденды акций брались из MOEX `dividends.json`, который больше не возвращает блок `dividends`. T-Bank gRPC уже используется для Portfolio и Instruments, а `GetDividends` описан в `instruments.proto` и фильтруется по `record_date` — это прямой заменяющий источник для текущих позиций, поскольку токен владельца счёта уже есть в системе. + +## Goals / Non-Goals + +**Goals:** +- Восстановить прогноз дивидендов по акциям через источник, доступный тем же токеном, что и Portfolio. +- Сохранить семантику события (`dateKind: registry` по дате фиксации реестра) и расчёт суммы до налогов по текущему количеству. +- Убрать ставший мёртвым MOEX-путь дивидендов. + +**Non-Goals:** +- Изменение API-контракта и frontend. +- Замена источника событий облигаций. +- Учёт налогов, удержаний и будущих сделок. + +## Decisions + +### Share dividends via T-Bank GetDividends + +Ветка акций в `UpcomingEventsService` вызывает `TbankClient.getDividends(token, instrumentId, from, to)` с `instrumentId = instrumentUid ?? figi` позиции и окном 30 календарных дней. Запись превращается в событие `dividend`: дата = `record_date`, `dateKind: registry`, валюта и сумма на бумагу из `dividend_net`, итоговая сумма = `dividend_net × quantity`. Выплаты с `dividend_type: Cancelled` пропускаются. + +Это использует уже имеющийся токен и покрывает именно инструменты портфеля, тогда как публичный MOEX-источник дивидендов сейчас недоступен. Альтернатива (новый `cci/corp-actions` MOEX) требует авторизации и не подходит для анонимного серверного вызова. + +### Strict source completeness unchanged + +Недоступность T-Bank или MOEX по-прежнему завершает запрос ошибкой без частичного списка; инструмент без поддерживаемых событий просто пропускается. Смена источника не меняет семантику ошибок endpoint. + +### MOEX client keeps bond events only + +Из `moex.client.ts` удаляются `dividends()` и `MoexDividend`; остаётся `bondEvents()` с блоками `coupon`, `offer`, `description`. Отсутствующий блок в успешном ответе MOEX трактуется как «нет данных», а не сбой. + +## Risks / Trade-offs + +- [T-Bank возвращает только известные ему выплаты] → покрытие дивидендов зависит от данных T-Bank, что согласуется с ограничением «прогноз может быть неполным из-за доступности источника». +- [`GetDividends` лимитируется rate limiter] → запросы уже проходят через общий `RateLimiter` T-Bank. +- [Отменённые выплаты в истории] → фильтр `dividend_type: Cancelled` исключает их из прогноза. + +## Migration Plan + +1. Добавить `TbankClient.getDividends` и тип `TbankDividend`. +2. Переключить ветку акций в `UpcomingEventsService`; удалить MOEX-метод дивидендов. +3. Обновить тесты и `docs/features/brokerage-account-upcoming-events.md`. +4. Откат: вернуть ветку акций на MOEX не требует миграции данных, состояние не сохраняется. diff --git a/openspec/changes/archive/2026-08-19-source-share-dividends-from-tbank/proposal.md b/openspec/changes/archive/2026-08-19-source-share-dividends-from-tbank/proposal.md new file mode 100644 index 0000000..e116c45 --- /dev/null +++ b/openspec/changes/archive/2026-08-19-source-share-dividends-from-tbank/proposal.md @@ -0,0 +1,26 @@ +## Why + +MOEX ISS перестал отдавать блок `dividends` в `securities/{secid}/dividends.json`: endpoint отвечает `200` только с блоками `description` и `boards`. Дивиденды по акциям из MOEX стали недоступны, поэтому прогноз будущих событий не может опираться на этот источник. Подтверждено живыми запросами; блок `bondization` (купон/оферта/погашение) при этом продолжает работать. + +## What Changes + +- Сменить источник дивидендов по акциям с MOEX на T-Bank gRPC `GetDividends`: запрос по `instrument_id` позиции (uid или figi), фильтрация по `record_date` в окне 30 дней, сумма = `dividend_net` на одну бумагу, умноженная на текущее количество. +- Удалить из MOEX-клиента неиспользуемый метод `dividends` и тип `MoexDividend`; MOEX остаётся источником событий облигаций (`coupon`, `maturity`, `offer`). +- Сохранить контракт API без изменений: дивиденд по-прежнему возвращает `type: dividend`, `dateKind: registry`, `currency` и `amount`. +- Зафиксировать смену источника в постоянной документации `docs/features`. + +## Capabilities + +### New Capabilities + +- Нет. + +### Modified Capabilities + +- `brokerage-account-upcoming-events`: источник дивидендных событий для акций меняется с MOEX ISS на T-Bank `GetDividends`. + +## Impact + +- Backend: `TbankClient.getDividends`, `UpcomingEventsService` (ветка акций), удаление MOEX-метода дивидендов, тесты. +- Frontend: без изменений — контракт и форматы событий не меняются. +- Docs: обновление `docs/features/brokerage-account-upcoming-events.md`. diff --git a/openspec/changes/archive/2026-08-19-source-share-dividends-from-tbank/specs/brokerage-account-upcoming-events/spec.md b/openspec/changes/archive/2026-08-19-source-share-dividends-from-tbank/specs/brokerage-account-upcoming-events/spec.md new file mode 100644 index 0000000..a45fe9e --- /dev/null +++ b/openspec/changes/archive/2026-08-19-source-share-dividends-from-tbank/specs/brokerage-account-upcoming-events/spec.md @@ -0,0 +1,43 @@ +## Purpose + +Дивиденды по акциям берутся из T-Bank `GetDividends`, а не из MOEX ISS, который перестал отдавать блок `dividends`. События облигаций продолжают поставляться из MOEX. + +## MODIFIED Requirements + +### Requirement: Forecasts preserve event meaning and uncertainty +Система MUST передавать для каждого события его календарную дату, тип, доступные идентификаторы и название инструмента, исходную валюту и, если она известна, прогнозную сумму до налогов. Прогнозная сумма MUST рассчитываться по текущему количеству бумаг на момент запроса. Дивиденд MUST обозначать переданную дату как дату реестра, а offer MUST передаваться без вымышленной суммы. + +#### Scenario: Прогноз купона по текущей позиции +- **WHEN** поддерживаемая облигация в текущей Portfolio Position имеет известный купон в пределах периода +- **THEN** система возвращает событие coupon с суммой до налогов в исходной валюте, рассчитанной по текущему количеству облигаций + +#### Scenario: Прогноз дивиденда +- **WHEN** поддерживаемая акция в текущей Portfolio Position имеет объявленный дивиденд с датой фиксации реестра в пределах периода +- **THEN** система возвращает событие dividend с датой, обозначенной как дата реестра (`record_date`), и суммой до налогов, равной `dividend_net` на одну бумагу, умноженной на текущее количество акций + +#### Scenario: Отменённый дивиденд не попадает в прогноз +- **WHEN** запись дивиденда T-Bank имеет тип выплаты `Cancelled` +- **THEN** система не добавляет такое событие в результат + +#### Scenario: Отображение оферты без суммы +- **WHEN** для поддерживаемой облигации известна оферта в пределах периода, но её денежные условия недоступны +- **THEN** система возвращает событие offer без прогнозной суммы + +#### Scenario: Неподдерживаемое частичное погашение +- **WHEN** источник не предоставляет расписание будущих частичных погашений или амортизации облигации +- **THEN** система НЕ создаёт синтетическое событие частичного погашения + +### Requirement: Upcoming events use complete source results +Система MUST завершать запрос ошибкой, если необходимые для прогноза данные брокера или MOEX недоступны, и MUST NOT возвращать частичный список как полный результат. Система MUST пропускать инструменты, для которых источник не предоставляет поддерживаемые события, не создавая строки «нет данных». + +#### Scenario: Недоступность источника +- **WHEN** необходимый источник данных брокера или MOEX временно недоступен +- **THEN** система возвращает ошибку запроса будущих событий без частичного списка + +#### Scenario: Инструмент без поддерживаемых данных +- **WHEN** Portfolio содержит инструмент, для которого источник не предоставляет поддерживаемые будущие события +- **THEN** система не добавляет для него синтетическую строку в результат + +#### Scenario: MOEX отвечает без запрошенного блока +- **WHEN** MOEX отвечает успешно, но не содержит запрошенного блока данных (например, дивидендов) +- **THEN** система трактует ответ как отсутствие данных по инструменту и не завершает запрос ошибкой diff --git a/openspec/changes/archive/2026-08-19-source-share-dividends-from-tbank/tasks.md b/openspec/changes/archive/2026-08-19-source-share-dividends-from-tbank/tasks.md new file mode 100644 index 0000000..8529331 --- /dev/null +++ b/openspec/changes/archive/2026-08-19-source-share-dividends-from-tbank/tasks.md @@ -0,0 +1,15 @@ +## 1. Источник дивидендов через T-Bank + +- [x] 1.1 Добавить в `TbankClient` тип `TbankDividend` и метод `getDividends(token, instrumentId, from, to)`, вызывающий `InstrumentsService.GetDividends`, и unit-тест передачи запроса. +- [x] 1.2 Переключить ветку акций в `UpcomingEventsService` с `moex.dividends` на `tbank.getDividends`: `instrumentId = instrumentUid ?? figi`, окно по `record_date`, сумма `dividend_net × quantity`, пропуск `dividend_type: Cancelled`. +- [x] 1.3 Удалить из `MoexClient` метод `dividends` и тип `MoexDividend` вместе с их тестами; оставить `bondEvents` для облигаций. + +## 2. Тесты + +- [x] 2.1 Обновить `upcoming-events.service.spec.ts`: дивиденд формируется из `GetDividends`, отменённая выплата пропускается, сумма и дата реестра корректны. +- [x] 2.2 Убедиться, что backend-проверки (`npm run lint`, `npm test`, `npm run build`) зелёные. + +## 3. Документация и проверка + +- [x] 3.1 Обновить `docs/features/brokerage-account-upcoming-events.md`: источник дивидендов — T-Bank `GetDividends`, события облигаций — MOEX. +- [x] 3.2 Прогнать `openspec validate` и `opsx-verify`, затем архивировать change. diff --git a/openspec/specs/brokerage-account-upcoming-events/spec.md b/openspec/specs/brokerage-account-upcoming-events/spec.md index 698cfb8..c1be28b 100644 --- a/openspec/specs/brokerage-account-upcoming-events/spec.md +++ b/openspec/specs/brokerage-account-upcoming-events/spec.md @@ -29,8 +29,12 @@ - **THEN** система возвращает событие coupon с суммой до налогов в исходной валюте, рассчитанной по текущему количеству облигаций #### Scenario: Прогноз дивиденда -- **WHEN** поддерживаемая акция в текущей Portfolio Position имеет известный дивиденд в пределах периода -- **THEN** система возвращает событие dividend с датой, обозначенной как дата реестра, и суммой до налогов, рассчитанной по текущему количеству акций +- **WHEN** поддерживаемая акция в текущей Portfolio Position имеет объявленный дивиденд с датой фиксации реестра в пределах периода +- **THEN** система возвращает событие dividend с датой, обозначенной как дата реестра (`record_date`), и суммой до налогов, равной `dividend_net` на одну бумагу, умноженной на текущее количество акций + +#### Scenario: Отменённый дивиденд не попадает в прогноз +- **WHEN** запись дивиденда T-Bank имеет тип выплаты `Cancelled` +- **THEN** система не добавляет такое событие в результат #### Scenario: Отображение оферты без суммы - **WHEN** для поддерживаемой облигации известна оферта в пределах периода, но её денежные условия недоступны @@ -51,6 +55,10 @@ - **WHEN** Portfolio содержит инструмент, для которого источник не предоставляет поддерживаемые будущие события - **THEN** система не добавляет для него синтетическую строку в результат +#### Scenario: MOEX отвечает без запрошенного блока +- **WHEN** MOEX отвечает успешно, но не содержит запрошенного блока данных (например, дивидендов) +- **THEN** система трактует ответ как отсутствие данных по инструменту и не завершает запрос ошибкой + ### Requirement: Account page presents an upcoming events list Страница Brokerage Account MUST предоставлять действие перехода на отдельную страницу «Будущие события». Эта страница MUST показывать все полученные события компактным списком без фильтров, пагинации и суммарного итога, с датой, типом, инструментом и суммой в исходной валюте, если она известна. Страница MUST объяснять, что прогноз основан на текущей позиции, указан до налогов и может быть неполным из-за доступности данных источника.