Ошибки и что с ними делать
Справочник: ошибка или состояние, что оно значит, почему бывает, что делать и куда смотреть дальше. Текст в первой
колонке - то, что печатает ошибка в коде (Display), а не пересказ: по нему можно искать страницу поиском. Тест в
репозитории проверяет, что тексты ошибок из brokapi::core::Error лежат на этой странице. Объяснение, откуда эти
исходы берутся: Круг команд, Круг жизни, Круг событий. У каждой строки таблиц есть якорь
(errors.html#unknown, errors.html#rate-limited, errors.html#duplicate_place): на нее можно сослаться из своего кода и документов.
Python. У каждой ошибки есть одноименное исключение в brokapi.errors (и на верхнем уровне пакета: brokapi.Unknown, brokapi.Rejected и так далее), текст str(error) равен тексту в первой колонке. Error::class() - это error.error_class (ErrorClass.RETRYABLE, FATAL, UNKNOWN). Поля вариантов - атрибуты: Rejected.code, Rejected.message, RateLimited.retry_after (timedelta), Unsupported.capability. Подробнее: Как Python связан с ядром.
Две вещи помогают разобраться быстрее. Метод Error::class() (тип brokapi::core::ErrorClass) отвечает на вопрос «можно ли повторить»: Retryable
(сеть, лимит), Fatal (тот же вызов упадет снова), Unknown (исход команды неизвестен). И правило для всех
команд: команда не повторяется автоматически, а повторять вручную можно только после того, как вы узнали, что
заявки нет.
Ошибки вызова (brokapi::core::Error)
| Ошибка или состояние | Что значит | Почему бывает | Что делать | Подробнее |
|---|---|---|---|---|
configuration error: <причина> (Config) | Неверная настройка или вход, который не подходит этому брокеру. Ничего не отправлено | Токен или секрет не приняты брокером (у всех трех: БКС, Т-Банк UNAUTHENTICATED, Финам), счет не найден, формат cid не вмещается, идентификатор инструмента другого брокера, маршрут лимитов указывает на несуществующее ведро | Исправить настройку или вход. Повтор не поможет, вызов упадет так же | Брокеры |
transport error: <причина> (Transport) | Сбой связи, ответа брокера нет. Для чтения - повторить можно | Обрыв, TLS, таймаут чтения, оборванный поток, нечитаемый ответ. На команде означает, что у коннектора нет признаков, что она дошла, но доказать это он не всегда может: считайте, что заявка могла дойти | Чтение: повторить позже. Команда: сначала посмотреть заявку (order_state), потом решать. Новая заявка - с новым cid | Если что-то пошло не так |
rate limited, retry after <время> (RateLimited) | Не хватило квоты: у счетчика библиотеки или брокер ответил «слишком много запросов» (у Т-Банка RESOURCE_EXHAUSTED с номером 80002, 80003, 80006 и без известного номера. Остальные номера этого статуса - Rejected, см. таблицу ниже). В сеть ничего не ушло (если отказал счетчик) | Бот шлет быстрее квоты, один счетчик делят разные коннекторы, или общее ведро исчерпано чтением | Подождать retry_after, отправить снова. Если отказал счетчик библиотеки, cid не потрачен и годится снова, но новый cid безопаснее всегда. Если ответил брокер, только новый cid. Библиотека сама не ждет и не повторяет | Лимиты запросов |
rejected: <код>: <текст> (Rejected) | Брокер (или библиотека до отправки) точно отказал, заявки нет | Цена вне коридора, нет денег, неизвестная заявка, заявка уже исполнена, не хватает цены у лимитной. Конкретная причина - в code и message | Не повторять тот же запрос. Исправить причину и отправить с новым cid. Сравнивайте code, а не message | коды отказов - таблица ниже |
command outcome unknown: do not send it again, ask the broker with order_state(cid) (Unknown) | Команда ушла, ответа нет. Заявка могла встать, могла не дойти | Таймаут, обрыв после отправки, ошибка сервера 5xx, нечитаемый ответ | Не повторять. Спросить брокера: order_state(cid), при сомнении active_orders, executions, reconcile. Новую заявку - только когда брокер подтвердил, что первой нет, и с новым cid | Если что-то пошло не так, Почему нет автоповтора |
unsupported capability: <возможность> (Unsupported) | Брокер или коннектор этого не умеет. Проверено без сети | Например, replace у Финама, свечи 10 минут у БКС, поток стоп-заявок у всех | Это не сбой. Убрать вызов или проверить caps() заранее. Библиотека не подменяет отсутствующий метод опросом | События и возможности |
not reconciled: call reconcile() before trading commands (NotReconciled) | Шлюз команд закрыт: после запуска не было успешной сверки. Ничего не отправлено | Бот сразу ставит заявку, не вызвав reconcile, или сверка упала с ошибкой | Вызвать reconcile(&журнал) (пустой список, если журнала нет), потом повторить команду. cid годен | Круг жизни, Запуск, рестарт и остановка |
connector degraded: events may be lost, read the event channel to the end and call reconcile() (Degraded) | Шлюз закрыт: переполнился буфер подписки без потерь или оборвался и открылся заново поток своих заявок или исполнений (Event::Reconnected), события о своих заявках могли пропасть. Ничего не отправлено. Верно и для команды, которая стояла в очереди по бумаге, когда шлюз закрылся | Бот читает канал медленнее, чем идут события, или оборвалась связь с потоком заявок | Дочитать канал до пустого, прочитать состояние у брокера, вызвать reconcile, после успеха шлюз открыт. Потом сделать канал быстрее | Если что-то пошло не так, Круг событий |
shutting down: the connector takes no new trading commands (ShuttingDown) | Идет остановка коннектора, шлюз закрыт навсегда. Ничего не отправлено | Вызван shutdown, команда пришла из другой задачи | Ничего не отправлять. Нужна торговля - запустить новый коннектор. Заявки на бирже остановка не снимает | Запуск, рестарт и остановка |
Коды Rejected
Коды ставит коннектор, они не общие для всех брокеров. Часть отказов библиотека возвращает до отправки (в сеть ничего не уходит), часть выводит из ответа брокера.
| Код | Что значит | Когда | Что делать |
|---|---|---|---|
duplicate_place | Этот cid уже отправлялся (place, place_stop) | Повтор после таймаута, повтор после отказа брокера. Единица квоты на этот вызов уже потрачена, в сеть ничего не ушло | Не повторять. Выяснить судьбу первой заявки, новая - с новым cid |
invalid_qty | Количество не подходит: нуль, отрицательное, дробное там, где брокер берет целые (БКС, Т-Банк), не целое число лотов (Т-Банк всегда, БКС и Финам если лот передан в price_grid()) | Проверка входа до отправки. Ничего не округляется, ничего не отправлено, cid годен | Исправить количество. Оно в единицах инструмента, не в лотах (Деньги, цены и количества) |
invalid_order | Финам: форма заявки неверна (например, не удалось собрать символ) | Проверка входа | Исправить заявку |
missing_price | Лимитная заявка без цены (у Т-Банка еще и с ценой не больше нуля) | Проверка входа | Указать цену. cid годен |
qty_required | БКС: заявка частично исполнена, в replace нужно указать количество | replace без qty на частично исполненной | Указать количество явно |
already_filled | БКС: заявка исполнилась раньше, чем дошла отмена | Гонка отмены и исполнения | Читать итог: order_state, executions |
not_found | Брокер не знает такую заявку (cancel, replace, order_state) или инструмент. Один и тот же код у всех брокеров (константа brokapi::core::NOT_FOUND, проверка Error::is_not_found()) | Заявка не ставилась, уже закрыта, неизвестный инструмент, или запрос после обрыва еще в пути | После Unknown не окончательно: подождать и спросить еще раз. См. recovery |
REJECTED | Т-Банк: брокер принял запрос, но ответил статусом «отклонено» | Решение биржи или брокера, причина в message | Исправить причину, новая заявка с новым cid |
rejected, cancel_rejected | БКС: заявка отклонена в ответе на постановку, отмена отклонена | Причина в message | Исправить причину |
validation | БКС: брокер не принял форму запроса (HTTP 400, ошибки полей в message) | Неверное поле в запросе | Исправить запрос |
subscription_quota | Финам: уже открыто максимум потоков | Слишком много подписок на соединение | Снять ненужные подписки |
unknown_subscription | Все брокеры: unsubscribe по номеру, которого нет | Подписка уже снята или номер чужой | Не снимать повторно |
stop_orders_limit | Т-Банк: по инструменту набрано максимум стоп-заявок (ответ 80007) | place_stop по инструменту, где уже есть предельное число стопов | Снять одну из стоп-заявок. Повтор не поможет. Заявки нет, cid годен |
streams_limit, no_subscriptions, stream_lifetime | Т-Банк: открыто максимум потоков (80001), в потоке нет подписок (80004), поток прожил свой срок (80005) | Ответ RESOURCE_EXHAUSTED с этими номерами. Это не лимит запросов | Закрыть ненужный поток, подписаться, подключиться заново |
stop_orders_response_limit | Т-Банк: стоп-заявок слишком много для одного ответа (30261), и чтение по периодам не помогло | active_stop_orders при очень длинном списке | Снять ненужные стоп-заявки или читать период за периодом через raw() (Т-Банк) |
range_too_long | История сделок (executions) не помещается в то, что умеет читать коннектор: Т-Банк длиннее 20 страниц по 1000 операций, БКС список упирается в предел 1000 последних сделок, Финам больше 100 страниц или больше сделок с одной временной меткой, чем помещается в ответ | Слишком большой период | Читать по частям: разбить период. Ничего из прочитанного не возвращается, чтобы обрезок не сошел за всю историю |
unsupported_expiry, missing_limit_price, invalid_price, unsupported_limit_price, unknown_stop_order | Стоп-заявки | Форма запроса или неизвестный стоп | Стоп-заявки |
События и состояния, которые не являются ошибкой вызова
| Состояние | Что значит | Почему бывает | Что делать | Подробнее |
|---|---|---|---|---|
Event::Overflow { sub, lost_from } | Буфер Lossless-подписки заполнился, события начиная с lost_from могли быть потеряны. Приходит один раз на подписку. Следом шлюз закрывается (Degraded) | Бот отстает от потока своих заявок и сделок | Дочитать канал, reconcile, восстановить пропущенное из снимков. Для рыночной подписки: снять и оформить заново | Если что-то пошло не так |
Event::Reconnected { sub, gap_from } | Поток своих заявок или своих исполнений оборвался и открыт снова, за время обрыва могли пройти события, которых бот не видел. Следом шлюз закрывается (Degraded) | Обрыв связи с потоком заявок или исполнений | Дочитать канал, прочитать состояние у брокера (active_orders, order_state, executions с запасом раньше gap_from), вызвать reconcile | Круг событий, Если что-то пошло не так |
Event::Resync { sub, instrument, reason } | Стакан мог потерять данные, прежнему верить нельзя | SubscriptionReset: поток переоткрыт после обрыва. SequenceGap: брокер пропустил данные (Т-Банк, Финам) | Выбросить хранимый стакан, не торговать по нему, дождаться следующего Event::OrderBook | Круг событий |
OrderState::Unknown | Судьба заявки неизвестна. Из него библиотека сама не выходит | Таймаут или обрыв на команде, неизвестный статус Т-Банка, у БКС статус 6 «отменяется» (заявка еще жива) | Запросить состояние (order_state) или сделать сверку | Круг команд, recovery |
Список foreign в отчете сверки | На счете есть заявки, которых нет в журнале бота | Поставлены руками, из приложения или другим роботом | Не трогать. Показать человеку | Круг жизни |
Список closed_offline в отчете сверки | Заявка из журнала не найдена среди живых у брокера | Исполнилась, снята или не была принята, пока бот был выключен | Узнать итог: order_state, executions, занести в журнал | Круг жизни |
reconcile вернул ошибку | Сверка не состоялась, шлюз не открыт | Нет связи, лимит, у БКС ошибка или пустой ответ по одному из cid (ответ «брокер не знает номер» ошибкой не считается, это closed_offline) | Подождать, вызвать еще раз. Ошибка ничего не говорит о заявках | Круг жизни |
| События пропали, ошибок нет | Поток оборван, идет переподключение | Обрыв связи | Подождать. Когда поток откроется, придет Reconnected (заявки и исполнения) или Resync (стакан). У Финама смотреть sub_status. При сомнении reconcile | Круг событий |
| Подписка не подтверждается | subscribe_* вернул transport error: the subscription was not confirmed by the broker within <время> (Transport), либо Rejected { subscription_refused } | Transport: поток не открылся за subscribe_ack_timeout (сеть, медленный брокер, обрыв). Rejected: брокер отказал (нет такого инструмента, нет прав). Текст ошибки содержит причину. У Финама по таймауту ошибки нет, вызов возвращает Ok и поток дооткрывается в фоне (sub_status) | Transport: подписка уже снята, повторить позже. Rejected: исправить запрос, повтор ничего не изменит | Круг событий |
Листать можно клавишами Влево и Вправо.