Files
2026-08-14 15:50:35 +03:00

6.8 KiB

Context

См. motivation в proposal.md. Текущие Portfolio Position и Account Ledger получаются read-through из T-Bank; календаря или сохранённого расписания событий нет. references/moex-vibe подтверждает, что MOEX предоставляет дату и величину дивиденда, ближайший купон, дату и номинал погашения, а также дату оферты. Эти данные не содержат расписания будущей амортизации.

Goals / Non-Goals

Goals:

  • Вычислять account-scoped прогноз без хранения синтетических событий.
  • Сохранять отличия между датой реестра дивиденда и датой денежного события.
  • Опубликовать единый OpenAPI-контракт, из которого frontend сгенерирует типы.

Non-Goals:

  • Расчёт налогов, конвертация валют и общий итог.
  • Учёт будущих сделок или владения на дату реестра.
  • История фактических выплат, ручное обновление и фильтры.
  • Будущие частичные погашения до появления достоверного расписания амортизации.

Decisions

Read-through projection of current positions

GET /api/v1/brokerage-accounts/:accountId/events получает актуальный Portfolio, обогащает его данными MOEX и собирает только события в rolling-окне 30 календарных дней. Ответ содержит calculatedAt; данные не сохраняются.

Это избегает отдельного хранилища и устаревших расписаний. Альтернатива с persisted calendar потребовала бы синхронизации с позициями и не повышает точность, поскольку расчёт всё равно основан на текущем количестве бумаг.

Explicit event contract

Контракт события содержит тип (coupon, dividend, maturity, offer), календарную дату, семантику даты (event или registry), инструмент, исходную валюту и nullable прогнозную сумму. Для coupon, dividend и maturity сумма равна текущему количеству позиции, умноженному на выплату или номинал на одну бумагу; offer не получает выдуманную сумму.

События возвращаются без агрегированного итога, поскольку валюты не конвертируются. Альтернатива с одним полем даты скрыла бы отличие даты реестра от даты поступления дивиденда.

Strict source completeness

Если запрос Portfolio или нужные данные MOEX не получены, endpoint отвечает ошибкой и не публикует неполный календарь. Инструмент с успешным ответом, но без поддерживаемого события, просто не добавляет строку.

Это отделяет техническую неполноту от корректного пустого результата. Альтернатива вернуть частичный список не позволяет пользователю отличить пропуск от отсутствия выплат.

Account-local interface using established list patterns

Frontend добавляет маршрут /accounts/:accountId/events, breadcrumb и действие «Будущие события» на обзоре счёта рядом с Account Ledger. Обзор запрашивает тот же endpoint отдельно от Portfolio и показывает первые пять событий в локальном блоке. Страница использует существующие состояния loading/error/empty и компактные responsive rows; она показывает все события результата без локальных фильтров или pagination.

Это продолжает account-scoped навигацию /accounts/:accountId/operations. Альтернатива с глобальным календарём смешивала бы события разных счетов и требовала бы выбора счёта.

Coverage limitation is part of the interface

UI постоянно поясняет, что суммы являются оценками до налогов по текущей позиции, а покрытие зависит от доступных данных MOEX. При пустом списке пояснение дополнительно называет отсутствие известных событий в ближайшие 30 дней. Будущее частичное погашение не имитируется датой maturity.

Это сохраняет полезный минимальный список без ложной точности. Альтернатива скрыть ограничение противоречит природе прогнозов и данным источника.

Risks / Trade-offs

  • [Позиция изменится до события] → обозначать сумму как прогноз по позиции на calculatedAt, не заявляя право на выплату.
  • [MOEX публикует дивидендную дату реестра, а не дату зачисления] → передавать и отображать отдельную семантику даты.
  • [MOEX даёт только ближайший купон] → ограничить окно 30 днями и явно раскрыть неполноту покрытия.
  • [Нет графика амортизации] → исключить будущие частичные погашения до добавления надёжного источника.

Migration Plan

  1. Развернуть backend endpoint и обновлённый OpenAPI.
  2. Сгенерировать и закоммитить frontend API-контракт, затем выпустить страницу.
  3. При откате удалить действие и маршрут frontend; endpoint можно удалить без миграции данных, поскольку состояние не сохраняется.