rekryt / tbank-lib
Асинхронная библиотека для T-Invest API (T-Bank Investments) на AMPHP v3 и Revolt Event Loop
Requires
- php: >=8.2
- ext-json: *
- ext-mbstring: *
- amphp/amp: ^3.0
- amphp/http-client: ^5.1
- amphp/pipeline: ^1.2
- amphp/sync: ^2.2
- amphp/websocket-client: ^2.0
- psr/event-dispatcher: ^1.0
- psr/log: ^3.0
- revolt/event-loop: ^1.0
Requires (Dev)
- ext-simplexml: *
- ext-zlib: *
- friendsofphp/php-cs-fixer: ^3.64
- phpstan/phpstan: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpunit/phpunit: ^10.5 || ^11.0
- psalm/plugin-phpunit: ^0.19.7
- vimeo/psalm: ^6.0
- vlucas/phpdotenv: ^5.6
Suggests
- opencck/server: HTTP/WS-сервер на AMPHP v3 — нужен только для примера examples/recipes/opencck-exporter
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-12 17:35:25 UTC
README
Асинхронная PHP-библиотека для T-Invest API на AMPHP v3 и Revolt Event Loop.
Весь публичный API брокера — 109 unary-методов и 7 стримов — доступен типизированными вызовами.
Заявка, портфель и котировка приезжают не массивом, а объектом: PostOrderResponse, PortfolioResponse,
Quotation. Деньги считаются целыми числами в нано-долях, без float.
$client = Client::create(new ClientConfig(token: $token)); $portfolio = $client->operations()->getPortfolio($accountId); echo $portfolio->totalAmountPortfolio?->toString(); // 101405.12 RUB
Возможности
| Unary-методы | 9 сервисов, 109 методов с развёрнутыми параметрами и именованными аргументами |
| Стримы | биржевой, заявки и портфель по WebSocket: переподключение, восстановление подписок, сторожевой таймер |
| Справочник | InstrumentRegistry — поиск по uid, figi, тикеру и positionUid за O(1) с ленивой догрузкой |
| Лимиты | иерархические token bucket-ы «метод → сервис → глобальные 50/с», без sleep |
| Повторы | экспоненциальный backoff с джиттером; торговые поручения повторяются только с ключом идемпотентности |
| Ошибки | каталог из 175 кодов T-Invest API, разложенный по дереву исключений |
| Деньги | Quotation и MoneyValue с целочисленной нано-арифметикой и округлением до шага цены |
| Асинхронность | всё работает в Event Loop: сотня подписок и десяток методов одновременно — один процесс |
Требования
- PHP 8.2 или новее
ext-json,ext-mbstring- расширения для gRPC не нужны: библиотека ходит по REST и WebSocket (см. ADR 0002)
Установка
composer require rekryt/tbank-lib
Токен
Токен выпускается в личном кабинете: Инвестиции → Настройки → Токены для API. Подробно — в документации брокера.
Для начала берите токен только на чтение: он не даст выставить заявку даже по ошибке.
Токен — это доступ к деньгам, поэтому в репозитории ему не место: держите его в .env
и в переменных окружения.
cp examples/.env.example examples/.env
# впишите API_TOKEN в examples/.env
Быстрый старт
<?php declare(strict_types=1); use OpenCCK\Client; use OpenCCK\ClientConfig; require __DIR__ . '/vendor/autoload.php'; $client = Client::create(new ClientConfig(token: OpenCCK\env('API_TOKEN') ?? '')); $accounts = $client->users()->getAccounts(); $accountId = $accounts->accounts[0]->id; $portfolio = $client->operations()->getPortfolio($accountId); printf("Счёт %s: %s\n", $accountId, $portfolio->totalAmountPortfolio?->toString() ?? '—'); foreach ($portfolio->positions as $position) { printf( " %-12s %s шт., доходность %s\n", $position->figi, $position->quantity?->toString() ?? '0', $position->expectedYield?->toString() ?? '0' ); }
Вызов выглядит синхронным, но не блокирует процесс: под ним Event Loop, и пока ответ в пути,
работают остальные волокна. Отдельного await не нужно — библиотека сама живёт в цикле.
Обратите внимание: getAccounts() вернёт пустой список, если у токена нет доступа ни к одному
счёту, — проверяйте результат перед обращением по индексу.
Стрим
Соединение открывается по первой подписке. Разрыв, переподписку и сторожевой таймер библиотека берёт на себя: приложению остаётся разобрать события.
$stream = $client->marketDataStream(); $stream->subscribeCandles([$instrumentUid], SubscriptionInterval::OneMinute); $stream->subscribeLastPrice([$instrumentUid]); foreach ($stream->events() as $event) { if ($event instanceof PayloadEvent && $event->payload instanceof Candle) { $candle = $event->payload; printf("%s: закрытие %s\n", $candle->time?->toString() ?? '', $candle->close?->toString() ?? ''); } // за время разрыва биржа торговала: пропуск добирают unary-методами if ($event instanceof StreamDisconnectedEvent) { printf("разрыв, попытка %d через %.1f с\n", $event->attempt, $event->retryIn); } }
Подписок больше трёхсот на соединение библиотека не сложит — это лимит брокера; сверх него она
сама открывает следующее соединение. StreamMode::Raw отдаёт разобранный JSON без гидрации
в DTO: на потоках в сотни тысяч сообщений в секунду это заметно дешевле.
Разворачивать примеры целиком — в examples/streams/.
Справочник инструментов
$registry = $client->registry(); $registry->warmup(); // весь список одним запросом $sber = $registry->byTicker('SBER', 'TQBR'); echo $sber?->uid;
Промах догружается точечно, одновременные запросы одного инструмента схлопываются в один сетевой
вызов, записи живут 12 часов. toArray() и fill() переживают перезапуск процесса.
Примеры
examples/README.md — таблица всех 116 методов API: на каждый метод
запускаемый .php и описание .md с входными данными, кодом, настоящим ответом биржи
и разбором возможных ошибок.
php examples/quickstart/first-request.php # счета и портфель php examples/market-data/get-candles.php # один метод API php examples/streams/market-data-stream.php # стрим котировок
Рядом с примерами на каждый метод — рукописные сценарии:
quickstart/ для первого знакомства и recipes/ для
задач, которые одним методом не решаются: лимиты, повторы, снимок справочника, добор данных после
разрыва стрима, несколько токенов, экспортер метрик для Prometheus.
Примеры, меняющие состояние счёта — выставление и отмена заявок, пополнение песочницы, перевод
валюты, — без ключа --live останавливаются, не дойдя до сети.
Архитектура
src/
├── Domain/ деньги, идентификаторы, enum-ы, справочник — про предметную область
├── App/ DTO, сервисы, стримы, события — про API брокера
└── Infrastructure/ транспорт, лимиты, повторы, ошибки, часы — про то, как это доехало
Зависимости идут в одну сторону: Infrastructure → App → Domain. Доменное ядро ничего не знает
ни про HTTP, ни про amphp — котировку можно считать в тесте без сети и без Event Loop.
DTO, enum-ы, сервисы и примеры генерируются из снимков OpenAPI в resources/openapi/
(composer generate). Сгенерированный код лежит в репозитории, а CI проверяет, что он не разошёлся
со спецификацией. Правьте генератор или снимок, а не выходные файлы.
Решения, которые дороже всего переигрывать, записаны в adr/:
REST и WebSocket вместо gRPC,
целочисленные деньги,
иерархические token bucket-ы,
повторы только идемпотентных вызовов.
Устройство целиком, границы и обоснования — в BRIEF.md.
Разработка
composer qa # всё, что гоняет CI composer test # 1696 тестов composer coverage # покрытие src/ с порогом 85 % composer bench # бюджеты производительности composer generate:check # код не разошёлся со спецификацией composer check:secrets # в рабочем дереве нет токенов composer check:links # ссылки в документации и примерах живые
| Гейт | Требование |
|---|---|
| PHPStan | level 9, без baseline, 0 ошибок |
| Psalm | errorLevel=1, 0 ошибок |
| PHP-CS-Fixer | PSR-12 + правила .editorconfig |
| PHPUnit | покрытие src/ ≥ 85 % (сейчас 98.8 %) |
| Бенчмарки | бюджеты §7 BRIEF.md |
Интеграционный прогон по песочнице (composer test:integration) запускается только при заданном
TBANK_SANDBOX_TOKEN — он открывает счёт, пополняет его, выставляет и отменяет заявку и закрывает
счёт за собой.
Лицензия
MIT — см. LICENSE.
Библиотека не связана с ПАО «Т-Банк» и разрабатывается независимо. Торговля на бирже сопряжена с риском потери средств; автор не несёт ответственности за решения, принятые вашим кодом.