Т-Банк
Крейты: brokapi-tbank-protocol (gRPC клиент, собранный из официальных .proto) и brokapi-tbank (коннекторы).
С песочницы Т-Банка рекомендуется начинать: Перед боевым счетом.
Вход
| Что | Как |
|---|---|
| Токен | один статичный токен (TBANK_TOKEN). Обновлять нечего. Отвергнутый токен - ошибка настройки (Config). По документации Т-Банка новый токен нужно начать использовать в течение 7 дней после выпуска, иначе его отзовут. Выпускается только на сайте tbank.ru |
| Счет | account_id в настройке (TBANK_ACCOUNT_ID). Не задан: берется единственный открытый счет токена, а при нескольких счетах коннектор не запустится |
| Песочница | да. TBANK_SANDBOX=true выбирает адрес песочницы, caps().sandbox показывает это |
| Адрес | боевой и песочницы заданы, можно подменить TBANK_ENDPOINT (только https://, адрес имитации задается в коде). Свой корневой сертификат (например, национальный) добавляется TbankConfig::with_root_certificate_pem, встроенные корни остаются. HTTP/2 keep-alive идет раз в 5 минут и только пока открыт вызов или поток |
| Имя приложения | TBANK_APP_NAME (заголовок x-app-name, по соглашению Т-Банка <ник>.<репозиторий>) |
| Лимиты | brokapi::tbank::limits::default_limits(), один счетчик на пользователя |
Где взять токен и счет
Токен выпускает человек, чей это счет (правило 0). Шаги по документации Т-Банка:
- Открыть настройки профиля Т-Инвестиции на компьютере (с телефона страница может переадресовать в приложение, где раздела токенов нет)
и войти. Выпустить токен можно только на сайте
tbank.ru. Если страница не открывается, нужны сертификаты НУЦ Минцифры или Яндекс Браузер. - Убедиться, что «Подтверждение сделок кодом» выключено: по документации Т-Банка оно должно быть отключено. Сделки на сумму больше 30 000 000 рублей требуют подтверждения и через API недоступны.
- Выпустить токен T-Invest API для биржи и/или для песочницы. Система может попросить войти еще раз.
- Скопировать токен сразу: его показывают один раз, позже получить его нельзя.
Какой токен выбрать. Виды: только чтение, полный доступ, токен для переводов между счетами (его не берите), токен с доступом к одному счету (права чтения или полного доступа настраиваются) и токен песочницы. Для первого запуска возьмите токен песочницы (быстрый старт на Python). Для чтения боевого счета берите токен «только чтение». Токен с полным доступом нужен, когда человек решил поставить первую боевую заявку, и лучше с доступом к одному счету. Токен песочницы к боевому адресу не подойдет.
Номер счета. Если токен выпущен для одного счета, TBANK_ACCOUNT_ID задавать не нужно: берется его единственный открытый счет. Если токен на все счета и счетов
несколько, коннектор не запустится и просит указать счет. Для Инвесткопилки, Счета под ключ и Смарт-счета токен «на один счет» выпустить нельзя, у них только токен на все
счета. Где в кабинете смотреть номер счета, документация Т-Банка не говорит: в Rust список счетов дает TbankClient::list_accounts.
Счет песочницы. Токен песочницы сам счета не открывает. Счет создает вызов OpenSandboxAccount, пополняет SandboxPayIn (только рубли, до 30 000 000 рублей), счета
живут 3 месяца с последнего использования. В Rust это методы sandbox()-клиента (crates/tbank/tests/live_sandbox.rs), в Python и Gateway готовых команд нет, остается
raw или любой другой клиент T-Invest API.
Срок. Новый токен надо начать использовать в течение 7 дней после выпуска, иначе его отзовут. Токен живет три месяца с даты последнего использования. Ответ 40003
значит, что токен неактуален. Посмотреть и отозвать токены: настройки безопасности, блок «Приложения, у которых есть доступ к данным для входа».
Идентификатор инструмента
Коннектор принимает любую из трех форм:
| Форма | Когда использовать |
|---|---|
NativeId::Uid | основная. Ее отдает справочник, и в событиях брокер сам называет инструмент так |
NativeId::Figi | если есть figi |
NativeId::TickerClass | тикер и класс (SBER, TQBR), уходит брокеру как SBER_TQBR |
Ключ библиотеки - SBER@TQBR. Найти инструмент и его uid: md.find(InstrumentQuery::ByTicker { .. }). Событие
потока несет тот InstrumentId, который вы указали при подписке.
Единицы и стакан
| Что | Как |
|---|---|
| Количество | в библиотеке штуки (единицы инструмента), в API Т-Банка лоты для заявок, стакана, сделок рынка и свечей. Коннектор переводит в обе стороны: заявку делит на лот, стакан, сделки, свечи, статусы заявок и стоп-заявки умножает. Своих сделок (OrderTrade.quantity, история операций) и позиций это не касается: брокер отдает их штуками, они идут как есть. Лот инструмента (Instrument.lot, он же lot_size сообщения потока заявок) коннектор узнает одним запросом GetInstrumentBy при первой заявке, подписке или чтении по инструменту и запоминает. Заявка, не кратная лоту, дробная, нулевая или отрицательная - Rejected { invalid_qty } до отправки. Если лот прочитать нельзя, чтение дает Transport, а событие потока заявок с неизвестным лотом считается потерянным: придет Overflow и шлюз закроется до reconcile |
| Цена | за одну бумагу. Quotation и MoneyValue (units и nano) переводятся в Decimal и обратно точно. Цена с больше чем 9 знаками - Rejected { invalid_price } до отправки |
| Глубина стакана | 1, 10, 20, 30, 40, 50. Запрошенная глубина округляется вверх до допустимой, события режутся до нужной. Больше 50 - 50 |
| Стакан | полный снимок. Если брокер пометил стакан несогласованным (is_consistent = false), он не публикуется, вместо него Resync { SequenceGap } |
| Котировки | отдельного потока нет: котировка - лучший уровень стакана. Стакан и котировки одного инструмента идут одной подпиской брокера |
| Сделки рынка | сделка без направления в MarketTrade не помещается (сторона обязательна) и пропускается |
| Свечи | все таймфреймы. close_ts = открытие + длина таймфрейма (месяц считается как 30 дней) |
last_trades | сделки за последний час, новейшие limit штук |
Как cid уходит к брокеру
В поле order_id запроса PostOrder. По документации Т-Банка это ключ идемпотентности «в формате UID, до 36
символов». UUID с дефисами занимает ровно 36, поэтому влезает без потерь. Тот же ключ брокер возвращает как
order_request_id в ответе и потоке, а CancelOrder и GetOrderState принимают его с типом идентификатора «запрос».
Идемпотентность не используется: что сервер делает с повторным ключом, никто не измерял (LabelOnly, вопрос 1 ниже).
Библиотека place сама не повторяет в любом случае.
Заявка, поставленная не вами (с телефона, другим роботом), приходит без order_request_id. Чтобы она могла попасть в
foreign, коннектор выдает ей подставной стабильный cid, вычисленный из номера заявки на бирже.
Ответы брокера
Номер ошибки Т-Банка (пятизначный, из таблицы ошибок)
коннектор читает из текста статуса, а если там пусто, из начала заголовка message. Статус RESOURCE_EXHAUSTED у
Т-Банка не всегда означает лимит запросов: так же отвечает про лимит стоп-заявок по инструменту. Поэтому его различают
по номеру. Номер, которого нет в таблице, остается лимитом запросов.
| Ответ | Результат |
|---|---|
RESOURCE_EXHAUSTED с кодом 80002, 80003, 80006 или без известного кода | RateLimited. Время ожидания берется из заголовка x-ratelimit-reset, иначе 1 секунда (для 80003 и 80006, которые брокер считает в минуту, 60 секунд) |
RESOURCE_EXHAUSTED с кодом 80007 | Rejected { code: "stop_orders_limit" }: по инструменту набрано максимум стоп-заявок. Повтор не поможет, снимите одну из них |
RESOURCE_EXHAUSTED с кодом 80001 (открыто максимум потоков) | Rejected { code: "streams_limit" } |
RESOURCE_EXHAUSTED с кодом 80004 или 80005 (потоки) | Rejected с кодом no_subscriptions или stream_lifetime |
INVALID_ARGUMENT с кодом 30261 (слишком много стоп-заявок в одном ответе) | Rejected { code: "stop_orders_response_limit" }, см. «Стоп-заявки» ниже |
NOT_FOUND | Rejected с кодом not_found (как у остальных брокеров) |
INVALID_ARGUMENT, PERMISSION_DENIED, FAILED_PRECONDITION, ALREADY_EXISTS и подобные | Rejected с кодом статуса |
UNAUTHENTICATED | Config (токен не подошел) |
Таймаут, UNAVAILABLE, INTERNAL и все остальное на команде | Unknown (брокер мог выполнить) |
| Внутри обычного ответа статус «отклонено» | Rejected { code: "REJECTED" } |
Изменение заявки
ReplaceOrder. Нужны биржевой номер и количество исходной заявки: берутся из того, что коннектор уже знает, иначе
один запрос GetOrderState. Цена не указана - берется цена исходной. Новый cid становится ключом идемпотентности.
Биржевой номер после замены может измениться, ориентируйтесь на cid.
Свои сделки, портфель
| Что | Как |
|---|---|
| Поток исполнений | TradesStream. Комиссии в нем нет (0), cid определяется по номеру заявки. Сделка может прийти раньше ответа на PostOrder: пока заявка в пути, такая сделка ждет ответа и уходит с cid (или без него, если ее заявку никто не заявил) |
| История | GetOperationsByCursor: исполненные покупки и продажи, в том числе маржинальные (margin-call) и оплаченные картой, по сделке на Execution. Комиссия операции делится на количество, broker_order_id - номер операции, cid нет. Читается не более 20 страниц по 1000 операций: период, в котором их больше, дает ошибку Rejected с кодом range_too_long, а не обрезанный список |
| Портфель | два запроса: деньги из GetPositions, позиции из GetPortfolio. Поток из двух потоков (PortfolioStream, PositionsStream) под одним номером подписки |
| Заявки после обрыва потока | бот получает Event::Reconnected на подписки заявок и исполнений, шлюз закрывается до reconcile. Коннектор перечитывает активные заявки и присылает изменившиеся. Закрытые за время обрыва находите через order_state и executions |
Справочник
findпо тикеру и ISIN: быстрый поиск (запись короткая, полеcurrencyпустое, нужную валюту берите черезget).find(ByKind): списки акций, облигаций, ETF, фьючерсов, опционов, валют (базовый список, торгуемое через API). Запросы тяжелые, у них свое ведро лимитов (15 в минуту), кеша нет. Для опционов используется метод, помеченный как устаревший: если Т-Банк его уберет, станетUnsupported.MutualFundиOther:Unsupported.price_step_valueравен шагу цены. Для фьючерсов стоимость шага в деньгах не вычисляется.
Стоп-заявки
Отдельный сервис стоп-заявок: PostStopOrder, GetStopOrders, CancelStopOrder. Срок: «до отмены» и «до даты».
«До конца дня» нет. Количество в штуках, как у заявок (брокеру уходят лоты). Брокер не возвращает cid стоп-заявки. Связь с номером хранится в памяти
процесса, после рестарта в списке cid пустой и снимать стоп нужно по номеру брокера. Потока статусов в API нет.
Длинный список. Брокер не отдает слишком много стоп-заявок в одном ответе: ошибка 30261 «ограничьте запрос
параметрами from и to». active_stop_orders сначала просит весь список, а на 30261 делит период от 1970 до 2099
года пополам, пока каждый кусок не поместится, и склеивает ответы (повтор заявки на стыке отбрасывается по номеру).
Это тратит запросы GetStopOrders (60 в минуту, всплеск 10 в crates/tbank/limits.toml): если квота кончилась
посреди чтения, будет RateLimited, а читать придется заново. При длинном списке поднимите burst ведра
stop_orders_get. Не больше 40 запросов за чтение, дальше Rejected { code: "stop_orders_response_limit" }: снимите
лишние стоп-заявки. По какому времени заявки (создания, срока) брокер отбирает период и сколько заявок помещается
в один ответ, документация не говорит.
Потерянный ответ. Если place_stop кончился Unknown, а стоп у брокера есть, cid в списке не вернется сам: брокер его не отдает. Коннектор запоминает,
что просили (бумага, сторона, вид, лоты, цена активации, цена заявки) и когда отправил, и при следующем active_stop_orders дает стопу этот cid, если подходит
ровно один стоп без cid и создан он, пока запрос был в пути (плюс-минус 10 секунд на расхождение часов). Два одинаковых ожидания или два подходящих стопа (один может
быть чужой) - cid не назначается. Это опирается на create_date стопа в списке брокера: как Т-Банк его пишет, вживую не проверено. Если cid не вернулся,
стоп с cid: None на той же бумаге и в ту же сторону не считайте отсутствующим. Повтор запроса с тем же order_id библиотека не использует: ключ ли это идемпотентности для стопов, неизвестно.
Подробно: Стоп-заявки.
Чего Т-Банк не поддерживает или делает иначе
| Что | Как |
|---|---|
| Поток статусов стоп-заявок | в API нет, Unsupported |
Справочник MutualFund, Other | Unsupported |
| Серверный стрим рыночных данных, торговые статусы в потоке, остальное API | общего трейта нет, доступно через raw() |
| Подтверждение отписки в потоках заявок | у серверных потоков нет сообщения отписки, закрывается сам вызов |
Лимиты
Т-Банк считает лимиты на пользователя: на все счета и все токены вместе. Поэтому счетчик один на программу.
В crates/tbank/limits.toml значения из документации брокера, отмена вынесена в отдельное ведро. Реальные значения
отдает GetUserTariff и заголовки x-ratelimit-*: их можно проверить через raw(). До 11 одновременных соединений,
до 300 подписок в одном потоке рыночных данных.
Что учесть
- Повтор
order_id: что сделает Т-Банк (вернет прежний отчет, ошибку или примет вторую заявку), в документации не сказано. Коннектор повторов не делает, поэтому на вас это не влияет, но свой код на повторorder_idне закладывайте. - Единицы количества в потоке своих сделок - штуки (по описанию протокола).
- Номер биржевой заявки после
ReplaceOrderможет измениться. Ориентируйтесь наcid.
Листать можно клавишами Влево и Вправо.