docs: require russian specs

This commit is contained in:
Sergey Krylov 2026-06-16 06:01:48 +03:00
parent a3817edfc7
commit a971d0c8bf
2 changed files with 222 additions and 211 deletions

View File

@ -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.

View File

@ -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 <T_BANK_TOKEN>
```
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.