codex/tbank-broker-portfolios-design #15
@ -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.
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user