Если что-то пошло не так
Инструкции на случаи, когда бот не знает, что со счетом: у заявки нет ответа, канал событий переполнился, пропала
связь. Что означает каждая ошибка и состояние, - на странице Ошибки и что с ними делать, почему
библиотека устроена так - в Круге команд и Круге жизни. Код - куски программы
examples/recovery.rs, она запускается тестом против имитации: Unknown там настоящий, получен таймаутом на
имитации Т-Банка.
place вернул Unknown
Заявка ушла к брокеру, ответа нет. Она могла встать, могла не дойти. Не повторяйте place: если первая встала, на
счете окажутся две заявки. Вместо повтора спросите брокера по тому же cid.
flowchart TB
U[place вернул Unknown] --> S[order_state по cid]
S --> Q{Что ответил брокер}
Q -- заявка есть --> A[Работать как с обычной:<br>Acked, Filled, Cancelled...]
Q -- заявки нет --> W[Подождать несколько секунд<br>и спросить еще раз]
W --> Q2{Появилась}
Q2 -- да --> A
Q2 -- нет --> N[Заявки нет. Поставить новую<br>с новым cid, если еще нужно]
На схеме: путь от Unknown до решения. Из нее следует: «заявки нет» сразу после обрыва не окончательно, запрос,
ушедший в сеть, мог дойти позже, поэтому один отрицательный ответ ничего не доказывает.
/// `place` answered `Unknown`: ask the broker about the `cid` until it answers or `patience` runs out.
///
/// `Ok(Some(order))` - the order exists, continue with its state. `Ok(None)` - the broker still does not
/// know it after `patience`: it was not placed, and a new order needs a NEW `cid`.
pub async fn resolve_unknown<U: OrderExecutor>(
ud: &U,
cid: ClientOrderId,
patience: Duration,
) -> Result<Option<OrderSnapshot>, Error> {
let deadline = tokio::time::Instant::now() + patience;
loop {
match ud.order_state(cid).await {
Ok(order) => return Ok(Some(order)),
// "Not found" right after a cut is not final: the request may still be on its way.
Err(e) if e.is_not_found() => {}
// A limit or a network error says nothing about the order: wait and ask again.
Err(Error::RateLimited { .. } | Error::Transport(_)) => {}
Err(other) => return Err(other),
}
if tokio::time::Instant::now() >= deadline {
return Ok(None);
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
Вы должны увидеть: функция вернула Some(заявка), если брокер ее знает, и None, если не знает и после ожидания. Как
это читается:
order_state(cid)возвращает состояние заявки по вашемуcid. Если брокер такой заявки не знает, вернетсяRejected(кодnot_found, одинаковый у всех).- Брокер срок не называет, поэтому ожидание ограничивайте своим временем, а не ждите бесконечно.
- Если в игру вступает сомнение (например, заявка могла исполниться и исчезнуть из активных), смотрите
executionsиportfolio. - При следующем запуске бота
reconcileвернет такую заявку какconfirmedилиclosed_offline. - Вторую заявку ставьте только когда убедились, что первой нет. Номер нужен новый: старый библиотека не отправит
(
duplicate_place). - Для
cancelсUnknownотмену можно повторить вручную после проверкиorder_state: повторная отмена новых заявок не создает. Дляreplaceспрашивайте состояние по обоим номерам, исходному и новому.
Пришел Overflow или ошибка Degraded
Буфер подписки без потерь переполнился: бот читал канал медленнее, чем шли события. Часть событий о своих заявках могла пропасть, поэтому торговля закрыта, пока вы не сверитесь со счетом. Что делать, по порядку:
- Дочитать канал событий до пустого. То, что дошло, обработайте до сверки: это события до дыры.
- Вызвать
reconcile(&журнал). Успех снимаетDegraded, подписки снова принимают события. - Восстановить пропущенное из снимков: состояние заявок из
confirmedиorder_state, сделки изexecutions(...)с моментаlost_fromизOverflow, портфель изportfolio(). - Вернуться к работе.
/// `Event::Overflow` came, or a command returned `Degraded`: read the channel to the end, then reconcile.
///
/// `handle` gets every event that was waiting, so nothing that did arrive is lost. A successful
/// `reconcile` opens the gate again. After it, restore what was lost from the snapshots: the states in
/// `confirmed`, the fills from `executions(...)` since `lost_from`, the portfolio from `portfolio()`.
pub async fn after_overflow<U: OrderExecutor>(
ud: &U,
events: &mut mpsc::Receiver<Envelope>,
ledger: &[ClientOrderId],
mut handle: impl FnMut(Envelope),
) -> Result<ReconcileReport, Error> {
while let Ok(env) = events.try_recv() {
handle(env);
}
ud.reconcile(ledger).await
}
Вы должны увидеть: функция вернула отчет сверки, а следующий place перестал отвечать Degraded. Если reconcile
вернул ошибку, шлюз остался закрытым, повторите позже. Для рыночных подписок сверки нет, поэтому после Overflow
такую подписку снимают (unsubscribe) и оформляют заново. Чтобы переполнения не было: читайте канал отдельной задачей и
не делайте в ней долгих операций, размер буфера подбирайте с запасом на всплеск
(Круг событий).
Поток заявок оборвался
Соединения коннектор восстанавливает сам, а бот узнает об обрыве потока заявок или потока исполнений по событию
Event::Reconnected { sub, gap_from }. Оно приходит, когда поток снова открыт, а за время обрыва могли пройти события, которых
бот не видел: заявка исполнилась, снялась, была отклонена. Это то же состояние, что после Overflow: шлюз закрыт
(Degraded), команды в сеть не уходят.
- Дочитайте канал до конца. События до дыры нужны как обычно.
- Прочитайте состояние у брокера:
active_orders(),order_state(cid)по своим заявкам в пути,executions(range), гдеrangeначинается с запасом раньшеgap_from. - Вызовите
reconcile(&журнал). Успешная сверка открывает шлюз, следующийplaceпроходит.
if let Event::Reconnected { sub, gap_from } = &env.payload {
// 1. the events before the hole were handled as always
// 2. read what the broker has: orders and fills since a little before `gap_from`
let live = ud.active_orders().await?;
let since = TimeRange::new(UnixNanos::from_nanos(gap_from.as_nanos() - 60_000_000_000), UnixNanos::from_nanos(i64::MAX / 2));
let fills = ud.executions(since).await?;
// 3. the gate opens
ud.reconcile(&journal.live()).await?;
}
Если вы не слушаете события, а хотите узнать о закрытом шлюзе по ошибке, это Error::Degraded на команде, и действия те же.
Потеря связи без Reconnected (например, поток молчит, но не оборван) бывает, если сам брокер перестал присылать события.
Такое в коде не обнаруживается. Если связь была потеряна надолго или вы не уверены в журнале, зовите reconcile: он безопасен и
ничего не меняет на счете.
Что приходит сверх события, по брокерам: у Т-Банка коннектор сам перечитывает активные заявки и присылает изменившиеся, у БКС
и Финама нет. Поэтому после Reconnected читайте состояние сами, не ждите, что события наверстают.
Другие случаи, когда звать reconcile
- Не уверены в журнале. Вызов только читает, его можно делать в любой момент и сколько угодно раз.
NotReconciledна команде. После запуска не было успешной сверки: вызовитеreconcile, потом повторите команду,cidгоден (Запуск, рестарт и остановка).- Пришел
Resync. Это про стакан, а не про заявки: выбросьте хранимый стакан и дождитесь следующего снимка (Круг событий).
Листать можно клавишами Влево и Вправо.