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