diff --git a/AGENTS.md b/AGENTS.md index eb821eb..68df9e6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,6 +20,7 @@ npm workspaces монорепозиторий: `apps/backend` (NestJS), `apps/fr - `apps/docs` — единственная опубликованная человекочитаемая документация проекта (Docusaurus). - Root `docs` хранит только согласованные SDD-спецификации в `docs/superpowers/specs/`. +- Все SDD spec-файлы в `docs/superpowers/specs/` пишутся на русском языке; англоязычные термины допустимы для API, кода, протоколов и официальных названий. - ADR для опубликованной документации находятся в `apps/docs/docs/adr/`. - OpenAPI source of truth — live Swagger JSON бэкенда на `/api/docs-json`; frontend generated types находятся в `apps/frontend/src/api/types.ts`. - Superpowers plans и временные execution logs не коммитить по умолчанию. Если нужен план для ревью, держать его кратким и переносить устойчивые решения в spec/ADR/docs. diff --git a/docs/superpowers/specs/2026-06-16-tbank-broker-portfolios-design.md b/docs/superpowers/specs/2026-06-16-tbank-broker-portfolios-design.md index 2f27827..fcc85a9 100644 --- a/docs/superpowers/specs/2026-06-16-tbank-broker-portfolios-design.md +++ b/docs/superpowers/specs/2026-06-16-tbank-broker-portfolios-design.md @@ -1,53 +1,53 @@ -# T-Bank Broker Portfolios Design +# Проектирование брокерских портфелей T-Bank -**Status:** Approved for implementation planning +**Статус:** одобрено для планирования реализации -**Date:** 2026-06-16 +**Дата:** 2026-06-16 -**Owner:** MoexVibe +**Владелец:** MoexVibe -## Context +## Контекст -MoexVibe already supports manually managed virtual portfolios based on MOEX market data. The next -step is a read-only integration with T-Bank Invest so the application can show real brokerage -accounts, positions, cash balances, and operation history. +MoexVibe уже поддерживает вручную управляемые виртуальные портфели на основе рыночных данных MOEX. +Следующий шаг — интеграция T-Bank Invest только для чтения, чтобы приложение показывало реальные +брокерские счета, позиции, денежные остатки и историю операций. -The initial integration is personal and server-side. The backend reads one `T_BANK_TOKEN` from -`apps/backend/.env`. Later versions may replace this with per-user token storage, but the first -implementation must not require that larger secrets model. +Первая версия интеграции персональная и серверная. Бэкенд читает один `T_BANK_TOKEN` из +`apps/backend/.env`. В будущих версиях это можно заменить хранением токенов на уровне пользователя, +но первая реализация не должна требовать более крупной модели секретов. -## External API Facts +## Факты о внешнем API -T-Bank Invest API is a gRPC API. REST is available as a proxy and WebSocket exists for clients that -need it, but gRPC is the primary protocol. Production endpoint is `invest-public-api.tbank.ru:443`; -sandbox endpoint is `sandbox-invest-public-api.tbank.ru:443`. +T-Bank Invest API — это gRPC API. REST доступен как прокси, а WebSocket существует для клиентов, +которым он нужен, но основным протоколом является gRPC. Production endpoint: +`invest-public-api.tbank.ru:443`; sandbox endpoint: `sandbox-invest-public-api.tbank.ru:443`. -Authorization is passed as gRPC metadata: +Авторизация передается как gRPC-метаданные: ```text Authorization: Bearer ``` -The relevant official services and methods are: +Нужные официальные сервисы и методы: -| Need | T-Bank method | Notes | +| Потребность | Метод T-Bank | Примечания | | --- | --- | --- | -| Accounts | `UsersService/GetAccounts` | Filter by open brokerage accounts and IIS only. | -| Current portfolio valuation | `OperationsService/GetPortfolio` | Returns totals, positions, daily yield, expected yield. | -| Cash and settled positions | `OperationsService/GetPositions` | Returns money, blocked money, securities, futures, options. | -| Operation history | `OperationsService/GetOperationsByCursor` | Cursor pagination, limit up to 1000, operation type filters. | -| Instrument metadata | `InstrumentsService/GetInstrumentBy` | Resolve `instrument_uid`, ticker, class code, lot, name, ISIN. | +| Счета | `UsersService/GetAccounts` | Фильтровать только открытые брокерские счета и ИИС. | +| Текущая оценка портфеля | `OperationsService/GetPortfolio` | Возвращает итоги, позиции, дневную доходность, ожидаемую доходность. | +| Деньги и рассчитанные позиции | `OperationsService/GetPositions` | Возвращает деньги, заблокированные деньги, бумаги, фьючерсы, опционы. | +| История операций | `OperationsService/GetOperationsByCursor` | Курсорная пагинация, лимит до 1000, фильтры по типам операций. | +| Метаданные инструмента | `InstrumentsService/GetInstrumentBy` | Получение `instrument_uid`, тикера, class code, лота, названия, ISIN. | -The official docs recommend staying below 50 requests per second across accounts and tokens. The -documented unary limits include 100 requests per minute for the accounts service and 200 requests -per minute for the operations service. T-Bank also exposes rate limit response metadata such as -`x-ratelimit-limit`, `x-ratelimit-remaining`, and `x-ratelimit-reset`. +Официальная документация рекомендует оставаться ниже 50 запросов в секунду суммарно по счетам и +токенам. Документированные лимиты unary-запросов включают 100 запросов в минуту для сервиса счетов и +200 запросов в минуту для сервиса операций. T-Bank также возвращает метаданные по лимитам запросов, +например `x-ratelimit-limit`, `x-ratelimit-remaining` и `x-ratelimit-reset`. -Stream services exist for portfolio, positions, and operations updates. They are useful later, but -the first implementation will use unary reads plus local cache/sync because it is simpler, testable, -and less likely to hold unnecessary long-lived connections. +Stream-сервисы существуют для обновлений портфеля, позиций и операций. Они пригодятся позже, но +первая реализация использует unary-чтение плюс локальные кеширование и синхронизацию: это проще, тестируемее и не +держит лишние долгоживущие соединения. -References: +Ссылки: - [T-Invest API intro](https://developer.tbank.ru/invest/intro/intro) - [gRPC protocol](https://developer.tbank.ru/invest/intro/developer/protocols/grpc/) @@ -57,38 +57,40 @@ References: - [JS SDK](https://developer.tbank.ru/invest/sdk/faq_js) - [Official proto contracts](https://opensource.tbank.ru/invest/invest-contracts) -## Goals +## Цели -- Show real T-Bank brokerage accounts and IIS accounts in a separate broker portfolios area. -- Show current positions for each account: shares, bonds, other securities if returned, and cash. -- Show operation history for each account: buys, sells, commissions, taxes, coupons, dividends, - deposits, withdrawals, repayments, corrections, and other operation types returned by T-Bank. -- Avoid excessive calls to T-Bank through backend rate limiting, short-lived read cache, and durable - local operation sync. -- Keep the frontend isolated from T-Bank. The backend remains the only external API client. -- Publish human-readable architecture and usage documentation in `apps/docs`. +- Показать реальные брокерские счета T-Bank и ИИС в отдельном разделе брокерских портфелей. +- Показать текущие позиции по каждому счету: акции, облигации, другие бумаги, если они вернулись из + API, и денежные остатки. +- Показать историю операций по каждому счету: покупки, продажи, комиссии, налоги, купоны, дивиденды, + пополнения, выводы, погашения, корректировки и другие типы операций, которые возвращает T-Bank. +- Не создавать лишнюю нагрузку на T-Bank: использовать ограничение частоты запросов на бэкенде, + короткоживущий кеш чтения и устойчивую локальную синхронизацию операций. +- Изолировать фронтенд от T-Bank. Бэкенд остается единственным клиентом внешнего API. +- Опубликовать человекочитаемую архитектурную и пользовательскую документацию в `apps/docs`. -## Non-Goals +## Не цели -- No trading, order placement, transfers, or account funding in this phase. -- No Invest Box, DFA smart account, debit account, savings account, or money market fund account in - this phase. -- No per-user T-Bank token management in this phase. -- No always-on stream workers in the first version. -- No tax calculation engine beyond showing broker-provided tax operations. +- В этой фазе нет торговли, выставления заявок, переводов или пополнения счетов. +- В этой фазе нет Инвесткопилки, ЦФА-счета, дебетового счета, накопительного счета или счета фонда + денежного рынка. +- В этой фазе нет управления T-Bank токенами на уровне пользователя. +- В первой версии нет постоянно запущенных stream workers. +- Нет собственного движка расчета налогов сверх отображения налоговых операций, которые предоставляет + брокер. -## Account Scope +## Область счетов -Use `UsersService/GetAccounts` and keep only: +Используем `UsersService/GetAccounts` и оставляем только: - `ACCOUNT_TYPE_TINKOFF` - `ACCOUNT_TYPE_TINKOFF_IIS` -Also keep only open accounts: +Также оставляем только открытые счета: - `ACCOUNT_STATUS_OPEN` -Excluded account types: +Исключенные типы счетов: - `ACCOUNT_TYPE_INVEST_BOX` - `ACCOUNT_TYPE_INVEST_FUND` @@ -97,37 +99,38 @@ Excluded account types: - `ACCOUNT_TYPE_DFA` - `ACCOUNT_TYPE_UNSPECIFIED` -## Protocol Decision +## Решение по протоколу -Use a thin backend gRPC integration instead of the REST proxy or a hard dependency on the official -JS SDK. +Используем тонкую gRPC-интеграцию на стороне бэкенда вместо REST-прокси или жесткой зависимости от +официального JS SDK. -### Why gRPC +### Почему gRPC -- It is the primary protocol of T-Bank Invest API. -- It gives direct access to unary and future stream APIs. -- It keeps request metadata, `x-tracking-id`, and rate-limit metadata visible at the transport - boundary. -- It fits the existing MoexVibe rule that backend is the only external data client. +- Это основной протокол T-Bank Invest API. +- Он дает прямой доступ к unary API и будущим stream API. +- Он оставляет метаданные запроса, `x-tracking-id` и метаданные лимитов видимыми на границе + транспорта. +- Он соответствует правилу MoexVibe: бэкенд является единственным клиентом внешних данных. -### Why Not REST First +### Почему не REST первым -REST is useful for manual debugging and Swagger examples, but it is a proxy over the same service -surface. Building on REST would add translation overhead and make future stream support a separate -design. +REST полезен для ручной отладки и Swagger-примеров, но это прокси поверх той же поверхности сервисов. +Если строиться на REST, появится лишний слой перевода, а будущая поддержка stream API потребует +отдельного дизайна. -### Why Not SDK First +### Почему не SDK первым -The official JS SDK exists and can be useful as a reference. Current npm metadata for -`@tinkoff/invest-js` shows it already depends on gRPC libraries such as `@grpc/grpc-js`, -`@grpc/proto-loader`, `nice-grpc`, and `protobufjs`. Using it directly would hide transport details -that MoexVibe needs to own: rate limiting, metadata capture, test doubles, generated type updates, -and later token sourcing. The module boundary should still allow replacing the internal transport -adapter with SDK calls if that becomes clearly cheaper. +Официальный JS SDK существует и может быть полезен как справочник. Текущие npm-метаданные +`@tinkoff/invest-js` показывают, что SDK уже зависит от gRPC-библиотек, включая `@grpc/grpc-js`, +`@grpc/proto-loader`, `nice-grpc` и `protobufjs`. Прямое использование SDK скроет транспортные +детали, которыми MoexVibe должен владеть сам: ограничение частоты запросов, сбор метаданных, +тестовые двойники, обновление сгенерированных типов и будущий источник токенов. Граница модуля при +этом должна позволять заменить внутренний транспортный адаптер на вызовы SDK, если позже это станет очевидно +дешевле. -## Backend Architecture +## Архитектура бэкенда -Create a new feature module: +Создать новый feature-модуль: ```text apps/backend/src/modules/tbank/ @@ -152,48 +155,50 @@ apps/backend/src/modules/tbank/ └── operation.mapper.ts ``` -`TBankClientService` owns: +`TBankClientService` отвечает за: -- gRPC channel creation. -- `Authorization` metadata. -- optional `x-app-name` metadata, for example `ksv741.moex-vibe`. -- request timeout. -- retry for transient gRPC failures. -- service-level rate limiting. -- capturing `x-tracking-id` and rate-limit metadata for logs and error responses. +- создание gRPC channel; +- метаданные `Authorization`; +- опциональные метаданные `x-app-name`, например `ksv741.moex-vibe`; +- таймаут запроса; +- повторные попытки для временных gRPC-сбоев; +- ограничение частоты запросов на уровне сервисов; +- сбор `x-tracking-id` и метаданных лимитов для логов и ответов об ошибках. -Domain services depend on `TBankClientService`, not directly on generated gRPC clients. This keeps -the later switch from one server token to per-user token providers local to the client layer. +Доменные сервисы зависят от `TBankClientService`, а не напрямую от сгенерированных gRPC-клиентов. +Это оставляет будущий переход от одного серверного токена к providers токенов на уровне пользователя +локальным для клиентского слоя. -## Public Backend API +## Публичный API бэкенда -All endpoints require the existing JWT authentication. In the first version, any authenticated -MoexVibe user can technically read the same server-token broker data. Deployment must treat this as -single-user/admin-only until per-user token storage is implemented. +Все endpoints требуют существующую JWT-аутентификацию. В первой версии любой аутентифицированный +пользователь MoexVibe технически сможет прочитать одни и те же брокерские данные серверного токена. +До реализации хранилища токенов на уровне пользователя deployment должен считаться +single-user/admin-only. -| Endpoint | Method | Description | +| Endpoint | Метод | Описание | | --- | --- | --- | -| `/api/v1/broker/accounts` | GET | List open T-Bank brokerage and IIS accounts. | -| `/api/v1/broker/accounts/:accountId/portfolio` | GET | Current account portfolio, positions, totals, cash. | -| `/api/v1/broker/accounts/:accountId/operations` | GET | Operation history with pagination and filters. | +| `/api/v1/broker/accounts` | GET | Список открытых брокерских счетов T-Bank и ИИС. | +| `/api/v1/broker/accounts/:accountId/portfolio` | GET | Текущий портфель счета, позиции, итоги, деньги. | +| `/api/v1/broker/accounts/:accountId/operations` | GET | История операций с пагинацией и фильтрами. | -Operations query parameters: +Параметры query для операций: -| Query | Type | Default | Notes | +| Параметр | Тип | По умолчанию | Примечания | | --- | --- | --- | --- | -| `from` | ISO datetime | start of current year | UTC in T-Bank request. | -| `to` | ISO datetime | now | UTC in T-Bank request. | -| `cursor` | string | absent | Passed to `GetOperationsByCursor`. | -| `limit` | integer | 100 | Clamp to `1..1000`. | -| `instrumentId` | string | absent | Supports figi, instrument UID, or `ticker_classCode`. | -| `operationTypes` | string list | absent | Optional T-Bank operation type enum names. | -| `state` | enum | `OPERATION_STATE_EXECUTED` | Default to executed operations for portfolio accounting views. | +| `from` | ISO datetime | начало текущего года | UTC в запросе к T-Bank. | +| `to` | ISO datetime | сейчас | UTC в запросе к T-Bank. | +| `cursor` | string | отсутствует | Передается в `GetOperationsByCursor`. | +| `limit` | integer | 100 | Ограничить до `1..1000`. | +| `instrumentId` | string | отсутствует | Поддерживает figi, instrument UID или `ticker_classCode`. | +| `operationTypes` | string list | отсутствует | Опциональные имена enum типов операций T-Bank. | +| `state` | enum | `OPERATION_STATE_EXECUTED` | По умолчанию исполненные операции для учетных представлений портфеля. | -## Response Model +## Модель ответа -Use JSON DTOs shaped for the frontend, not raw proto objects. +Используем JSON DTO, удобные для фронтенда, а не сырые proto-объекты. -Money values are normalized from T-Bank `MoneyValue { currency, units, nano }` into: +Денежные значения нормализуются из T-Bank `MoneyValue { currency, units, nano }` в: ```typescript type BrokerMoney = { @@ -204,11 +209,11 @@ type BrokerMoney = { }; ``` -`units` remains a string to preserve the original int64 value. `value` is a convenience decimal for -display and charting. Financial calculations that require exact precision should use decimal helpers, -not floating-point arithmetic. +`units` остается строкой, чтобы сохранить исходное int64-значение. `value` — удобное decimal-число +для отображения и графиков. Финансовые расчеты, которым нужна точная арифметика, должны использовать +decimal helpers, а не арифметику с плавающей точкой. -Portfolio response shape: +Форма ответа портфеля: ```typescript type BrokerPortfolio = { @@ -236,7 +241,7 @@ type BrokerPortfolio = { }; ``` -Operation response shape: +Форма ответа операций: ```typescript type BrokerOperationsPage = { @@ -248,137 +253,142 @@ type BrokerOperationsPage = { }; ``` -Each operation keeps the original T-Bank enum in `type`, plus a MoexVibe display category: +Каждая операция хранит исходный enum T-Bank в поле `type`, а также отображаемую категорию MoexVibe: -| Category | T-Bank operation examples | +| Категория | Примеры операций T-Bank | | --- | --- | -| `trade` | `OPERATION_TYPE_BUY`, `OPERATION_TYPE_SELL`, margin/delivery variants | -| `income` | `OPERATION_TYPE_DIVIDEND`, `OPERATION_TYPE_COUPON`, repayments, overnight income | -| `tax` | `OPERATION_TYPE_TAX`, dividend tax, bond tax, progressive tax variants | -| `fee` | broker fee, service fee, margin fee, success fee, cash/out/advice/other fees | -| `transfer` | input, output, securities transfer, SWIFT/acquiring/multi transfers | -| `other` | unspecified and T-Bank operation types not yet categorized | +| `trade` | `OPERATION_TYPE_BUY`, `OPERATION_TYPE_SELL`, маржинальные/поставочные варианты | +| `income` | `OPERATION_TYPE_DIVIDEND`, `OPERATION_TYPE_COUPON`, погашения, overnight income | +| `tax` | `OPERATION_TYPE_TAX`, налог на дивиденды, налог на облигации, прогрессивные налоги | +| `fee` | брокерская комиссия, сервисная комиссия, маржинальная комиссия, success fee, прочие комиссии | +| `transfer` | пополнение, вывод, перевод бумаг, SWIFT/acquiring/multi transfers | +| `other` | unspecified и еще не категоризованные типы операций T-Bank | -Never drop unknown operation types. Store and display them as `other` with the original enum. +Нельзя отбрасывать неизвестные типы операций. Их нужно хранить и показывать как `other` с исходным +enum-значением. -## Operation Sync Strategy +## Стратегия синхронизации операций -Phase 1 can read operations directly from T-Bank with a cache. Phase 2 should persist normalized -operations locally because operation history is the audit trail and should not depend on repeatedly -walking the same remote pages. +Фаза 1 может читать операции напрямую из T-Bank с кешированием. Фаза 2 должна сохранять +нормализованные операции локально, потому что история операций — это audit trail, и она не должна +зависеть от повторного обхода одних и тех же удаленных страниц. -Durable sync design: +Дизайн устойчивой синхронизации: -- Store one row per T-Bank operation item keyed by `(accountId, cursor)` when cursor is present. -- Also keep `operationId`, `parentOperationId`, `date`, `type`, `state`, `instrumentUid`, `figi`, - `ticker`, `classCode`, `payment`, `price`, `commission`, `yield`, `accruedInt`, `quantity`, - `quantityDone`, `raw`. -- Store sync state per account: last successful range, last cursor, last synced timestamp. -- Backfill historical data by date windows, for example one calendar year per run. -- Refresh a moving recent window, for example last 3 days, because broker operation IDs and parent - IDs can change according to the proto comments. -- Keep raw operation payload JSON for audit/debug while exposing normalized DTOs. +- Хранить одну строку на каждый элемент операции T-Bank с ключом `(accountId, cursor)`, если cursor + присутствует. +- Также хранить `operationId`, `parentOperationId`, `date`, `type`, `state`, `instrumentUid`, + `figi`, `ticker`, `classCode`, `payment`, `price`, `commission`, `yield`, `accruedInt`, + `quantity`, `quantityDone`, `raw`. +- Хранить состояние синхронизации по каждому счету: последний успешный диапазон, последний cursor, timestamp + последней синхронизации. +- Делать backfill исторических данных окнами по датам, например по одному календарному году за запуск. +- Обновлять скользящее недавнее окно, например последние 3 дня, потому что operation IDs и parent + IDs могут меняться согласно комментариям в proto. +- Сохранять сырой JSON payload операции для аудита/отладки и отдавать наружу нормализованные DTO. -## Cache Strategy +## Стратегия кеширования -Use existing backend `CacheService` for short-lived reads and add T-Bank-specific key prefixes. -Frontend TanStack Query should use matching or shorter stale times. +Использовать существующий backend `CacheService` для короткоживущих чтений и добавить key prefixes +для T-Bank. Frontend TanStack Query должен использовать совпадающие или более короткие stale times. -| Data | Backend TTL | Reason | +| Данные | TTL бэкенда | Причина | | --- | --- | --- | -| Accounts | 1 hour | Account list changes rarely. | -| Portfolio totals and positions | 30-60 seconds | User-facing current view, should feel fresh but not spam T-Bank. | -| Cash/withdraw limits | 30-60 seconds | Similar freshness to positions. | -| Instrument metadata | 24 hours | Instrument name, lot, ISIN, UID are stable. | -| Operation page, recent window | 60-300 seconds | Useful before durable sync exists. | -| Operation historical pages | 24 hours or DB only | History is mostly immutable outside recent correction window. | +| Счета | 1 час | Список счетов меняется редко. | +| Итоги портфеля и позиции | 30-60 секунд | Текущий пользовательский вид должен ощущаться свежим, но не спамить T-Bank. | +| Деньги/лимиты вывода | 30-60 секунд | Такая же свежесть, как у позиций. | +| Метаданные инструмента | 24 часа | Название инструмента, лот, ISIN, UID стабильны. | +| Страница операций, недавнее окно | 60-300 секунд | Полезно до появления устойчивой синхронизации. | +| Исторические страницы операций | 24 часа или только БД | История в основном неизменна вне недавнего окна корректировок. | -Add environment variables: +Добавить переменные окружения: -| Variable | Default | Description | +| Переменная | Значение по умолчанию | Описание | | --- | --- | --- | -| `T_BANK_TOKEN` | none | Server-side T-Bank Invest token. Required when integration is enabled. | +| `T_BANK_TOKEN` | нет | Серверный T-Bank Invest token. Обязателен, когда интеграция включена. | | `T_BANK_BASE_URL` | `invest-public-api.tbank.ru:443` | gRPC endpoint. | -| `T_BANK_APP_NAME` | `ksv741.moex-vibe` | Optional gRPC metadata. | -| `T_BANK_RATE_LIMIT_PER_SECOND` | `5` | Conservative local limiter across T-Bank calls. | -| `CACHE_TBANK_ACCOUNTS_TTL` | `3600` | Accounts cache TTL. | -| `CACHE_TBANK_PORTFOLIO_TTL` | `60` | Portfolio and positions cache TTL. | -| `CACHE_TBANK_OPERATIONS_TTL` | `300` | Recent operation page cache TTL. | -| `CACHE_TBANK_INSTRUMENT_TTL` | `86400` | Instrument metadata cache TTL. | +| `T_BANK_APP_NAME` | `ksv741.moex-vibe` | Опциональная gRPC metadata. | +| `T_BANK_RATE_LIMIT_PER_SECOND` | `5` | Консервативный локальный limiter для всех вызовов T-Bank. | +| `CACHE_TBANK_ACCOUNTS_TTL` | `3600` | TTL кеша счетов. | +| `CACHE_TBANK_PORTFOLIO_TTL` | `60` | TTL кеша портфеля и позиций. | +| `CACHE_TBANK_OPERATIONS_TTL` | `300` | TTL кеша страницы недавних операций. | +| `CACHE_TBANK_INSTRUMENT_TTL` | `86400` | TTL кеша метаданных инструмента. | -The rate limiter must stay below documented public limits. On 429 or exhausted rate-limit metadata, -back off until reset when metadata is available. +Rate limiter должен оставаться ниже документированных публичных лимитов. При 429 или исчерпанных +метаданных лимита нужно отступать до reset, если метаданные доступны. -## Error Handling +## Обработка ошибок -- Missing `T_BANK_TOKEN`: return a clear 503-style application error and mark integration - unavailable; do not crash the whole backend in development. -- Auth error from T-Bank: return 502/503 with a generic message; never echo token or full metadata. -- Account not found or excluded by type/status: return 404 from MoexVibe endpoints. -- T-Bank 429: return 429 or 503 with retry metadata when available. -- T-Bank transient gRPC errors: retry with small exponential backoff, then surface a typed upstream - error with `trackingId` if present. -- Mapping unknown enum values: preserve the original value and categorize as `other`. +- Отсутствует `T_BANK_TOKEN`: вернуть понятную application error в стиле 503 и пометить интеграцию + недоступной; в development не ронять весь бэкенд. +- Ошибка авторизации от T-Bank: вернуть 502/503 с общим сообщением; никогда не отдавать token или + полные метаданные наружу. +- Счет не найден или исключен по type/status: вернуть 404 из endpoints MoexVibe. +- T-Bank 429: вернуть 429 или 503 с retry metadata, если она доступна. +- Временные gRPC-ошибки T-Bank: повторные попытки с небольшим exponential backoff, затем типизированная + upstream error с `trackingId`, если он присутствует. +- Неизвестные enum values при mapping: сохранить исходное значение и категоризовать как `other`. -## Security +## Безопасность -- `T_BANK_TOKEN` must never be logged, returned in API responses, or committed. -- gRPC metadata logging must redact `Authorization`. -- The initial deployment is single-user by design. Before enabling access for multiple MoexVibe - users, add per-user encrypted token storage and authorization rules that bind each broker account - to its owner. -- The integration is read-only. Do not include order, stop-order, transfer, or pay-in clients in the - first module. +- `T_BANK_TOKEN` нельзя логировать, возвращать в API responses или коммитить. +- Логирование gRPC metadata должно редактировать `Authorization`. +- Первый deployment по дизайну single-user. Перед включением доступа для нескольких пользователей + MoexVibe нужно добавить encrypted token storage на уровне пользователя и authorization rules, которые связывают + каждый брокерский счет с владельцем. +- Интеграция только для чтения. В первый модуль нельзя включать clients для orders, stop-orders, transfers + или pay-in. -## Frontend Product Shape +## Вид продукта на фронтенде -Add a broker portfolios area separate from manually managed virtual portfolios: +Добавить раздел брокерских портфелей отдельно от вручную управляемых виртуальных портфелей: -- List page: account cards for brokerage and IIS accounts, with total value, cash, daily change, and - last refresh time. -- Account detail page: tabs for `Позиции`, `Операции`, and later `Аналитика`. -- Positions table: instrument name, ticker, type, quantity, current price, current value, expected - yield, daily yield, blocked quantity. -- Cash section: available and blocked money by currency. -- Operations table: date, type/category, instrument, quantity, payment, commission, tax/income - indicators, status, expandable trade details. +- Страница списка: карточки счетов для брокерских счетов и ИИС, с полной стоимостью, деньгами, + дневным изменением и временем последнего обновления. +- Страница счета: вкладки `Позиции`, `Операции` и позже `Аналитика`. +- Таблица позиций: название инструмента, тикер, тип, количество, текущая цена, текущая стоимость, + ожидаемая доходность, дневная доходность, заблокированное количество. +- Секция денег: доступные и заблокированные деньги по валютам. +- Таблица операций: дата, type/category, инструмент, количество, платеж, комиссия, + признаки налога/дохода, статус, раскрываемые детали сделки. -Do not merge real broker accounts into the existing manual `Portfolio` model. Keep them separate in -UI and backend API. Later, MoexVibe can add comparison views or import flows from broker operations -into virtual portfolios. +Не смешивать реальные брокерские счета с существующей ручной моделью `Portfolio`. Держать их отдельно +в UI и backend API. Позже MoexVibe сможет добавить представления сравнения или import flows из брокерских +операций в виртуальные портфели. -## Documentation Deliverables +## Документация -Publish durable documentation in `apps/docs` during implementation: +Во время реализации опубликовать устойчивую документацию в `apps/docs`: -- `apps/docs/docs/backend/tbank-invest.md`: module architecture, API methods, env vars, error - handling, and read-only scope. -- Update `apps/docs/docs/backend/modules.md`: add `TBankInvestModule`. -- Update `apps/docs/docs/backend/configuration.md`: document `T_BANK_*` and cache TTL variables. -- Update `apps/docs/docs/backend/caching.md`: add T-Bank cache strategy. -- Update `apps/docs/docs/backend/portfolio.md`: distinguish manual portfolios from broker - portfolios. -- Add ADR `apps/docs/docs/adr/ADR-011-tbank-invest-grpc.md`: record gRPC over REST/SDK decision. -- Update `apps/docs/sidebars.ts` to include the new backend page and ADR. +- `apps/docs/docs/backend/tbank-invest.md`: архитектура модуля, методы API, переменные окружения, + обработка ошибок и область только для чтения. +- Обновить `apps/docs/docs/backend/modules.md`: добавить `TBankInvestModule`. +- Обновить `apps/docs/docs/backend/configuration.md`: задокументировать `T_BANK_*` и переменные + TTL кеша. +- Обновить `apps/docs/docs/backend/caching.md`: добавить стратегию кеширования для T-Bank. +- Обновить `apps/docs/docs/backend/portfolio.md`: разделить ручные портфели и брокерские портфели. +- Добавить ADR `apps/docs/docs/adr/ADR-011-tbank-invest-grpc.md`: зафиксировать решение gRPC over + REST/SDK. +- Обновить `apps/docs/sidebars.ts`, чтобы включить новую страницу бэкенда и ADR. -## Acceptance Criteria +## Критерии приемки -- Backend lists only open `ACCOUNT_TYPE_TINKOFF` and `ACCOUNT_TYPE_TINKOFF_IIS` accounts. -- Backend exposes current account portfolio with positions and cash without exposing raw token data. -- Backend exposes paginated operation history and includes buy, sell, tax, fee, coupon, dividend, - deposit, withdrawal, and unknown operation categories. -- T-Bank calls use a conservative rate limiter and cache strategy. -- Missing or invalid token produces a clear integration-unavailable error. -- Published docs in `apps/docs` describe architecture, configuration, caching, and the gRPC decision. -- Tests cover account filtering, money mapping, operation categorization, query validation, cache - keys, and upstream error mapping. +- Бэкенд показывает только открытые счета `ACCOUNT_TYPE_TINKOFF` и `ACCOUNT_TYPE_TINKOFF_IIS`. +- Бэкенд отдает текущий портфель счета с позициями и деньгами, не раскрывая сырые данные токена. +- Бэкенд отдает историю операций с пагинацией и включает категории buy, sell, tax, fee, coupon, + dividend, deposit, withdrawal и unknown operations. +- Вызовы T-Bank используют консервативный rate limiter и стратегию кеширования. +- Отсутствующий или некорректный token дает понятную ошибку integration-unavailable. +- Опубликованная документация в `apps/docs` описывает архитектуру, конфигурацию, кеширование и + решение по gRPC. +- Тесты покрывают фильтрацию счетов, mapping денег, категоризацию операций, валидацию query, + cache keys и mapping upstream-ошибок. -## Implementation Phases - -1. Backend gRPC foundation and DTO mappers. -2. Read-only account and portfolio endpoints. -3. Operation history endpoint with cursor pagination. -4. Frontend broker account list and account detail pages. -5. Durable operation sync in database. -6. Optional stream workers for near-real-time refresh after unary sync is stable. +## Этапы реализации +1. Базовая gRPC-инфраструктура бэкенда и DTO mappers. +2. Endpoints только для чтения для счетов и портфеля. +3. Endpoint истории операций с курсорной пагинацией. +4. Список брокерских счетов и detail pages счета на фронтенде. +5. Устойчивая синхронизация операций в базе данных. +6. Опциональные stream workers для near-real-time refresh после стабилизации unary sync.