Запуск, рестарт и остановка бота
Инструкция: что делать боту при старте, после рестарта и при остановке, в каком порядке и что при этом должно быть
видно. Почему порядок именно такой, объяснено на странице Круг жизни. Код
ниже - куски программы examples/lifecycle.rs: она запускается тестом против имитации БКС, поэтому текст и
проверенный код не расходятся. Программа играет два запуска бота: первый ставит заявку и уходит, не снимая ее,
второй стартует, сверяется с журналом и находит эту заявку.
Старт и рестарт
Старт и рестарт - одна процедура. На первом запуске журнал бота пуст, на втором в нем лежат cid заявок, которые бот
считал живыми.
- Поднять коннекторы.
start(конфиг, счетчик_лимитов, канал_событий). Шлюз команд после этого закрыт. - Подписаться на статусы заявок и исполнения. Подписки при закрытом шлюзе разрешены.
// Second run, after a restart. The gate is closed after `start`: commands return NotReconciled.
let (ud, mut events) = start_ud(cfg).await?;
// Subscribe BEFORE the reconcile: an event between the snapshot and the subscription would be lost.
ud.subscribe_order_state().await?;
Вы должны увидеть: start вернул коннектор без ошибки. Если вернулась ошибка, токен не подошел (у БКС и Финама
start сразу авторизуется, у Т-Банка при заданном счете первая проверка будет при первом запросе). Любая
торговая команда сейчас вернет NotReconciled, и это правильно.
- Достать из журнала
cidзаявок, считавшихся живыми, и вызватьreconcile. - Разобрать три списка.
// The journal says which orders the bot thinks are alive.
let report = ud.reconcile(&journal).await?;
for order in &report.confirmed {
println!(
"confirmed {}: still at the broker, filled {}",
order.cid,
order.filled.decimal()
);
}
for cid in &report.closed_offline {
// Find out how it ended: order_state(cid) and executions(...), then write it to the journal.
println!("closed offline {cid}: ask order_state and write the result");
}
for order in &report.foreign {
// Not ours. Do not touch: a person decides.
println!("foreign {}: shown to a person", order.cid);
}
Вы должны увидеть строку confirmed ... для каждой заявки, которая стоит у брокера. Если reconcile вернул ошибку,
шлюз не открыт: подождите и вызовите еще раз. Ошибка ничего не говорит о заявках. Что делать с каждым списком:
confirmed: сверить с журналом состояние и исполненный объем, поправить. Продолжать вести.closed_offline: для каждого запросить итог (order_state,executions) и записать в журнал.foreign: показать человеку или записать в журнал как чужие. Не снимать и не менять: бот не знает, зачем заявку поставили. Если такую заявку снять, можно сломать чужую стратегию.
- Для стоп-заявок отдельно вызвать
active_stop_orders(): обычная сверка их не показывает (Стоп-заявки). - Работать. Шлюз открыт.
// The gate is open now. The bot trades. Here it takes its own order off.
ud.cancel(cid).await?;
journal.clear();
Если пропустить шаги 2-3 и сразу ставить заявки, получите NotReconciled. Если подписаться после сверки, события,
пришедшие между снимком и подпиской, потеряются.
Остановка
shutdown(режим) останавливает коннектор. Он забирает коннектор по значению, после вызова пользоваться им нельзя.
Режимы:
Graceful { deadline }- аккуратно: закрыть подписки штатным способом брокера и дождаться подтверждений не дольшеdeadline.Immediate- сразу, без отписок.
// Stop: new commands get ShuttingDown, subscriptions are closed, the report says what was left.
let report = ud
.shutdown(ShutdownMode::Graceful {
deadline: Duration::from_secs(5),
})
.await;
println!(
"stopped: {} subscription(s) closed, {} failed, {} command(s) without an answer",
report.unsubscribed,
report.unsubscribe_failed,
report.inflight_abandoned.len()
);
Что происходит по шагам:
- Новые торговые команды получают
ShuttingDown. Шлюз закрывается навсегда. - (
Graceful) Подписки закрываются. У gRPC-брокеров закрытие вызова и есть отписка, у MD БКС отправляется сообщение отписки. - Заявки, у которых еще нет ответа, не ждутся и не повторяются: они локально переводятся в
Unknown, а список возвращается в отчете (ShutdownReport.inflight_abandoned). У Т-Банка на каждую такую заявку еще уходит событие. Почему не ждутся: повторить их нельзя, а ждать вечно нельзя. - Задачи останавливаются.
Вы должны увидеть отчет: сколько подписок закрыто (unsubscribed), сколько закрыть не удалось
(unsubscribe_failed), какие заявки остались без ответа (inflight_abandoned). Если список не пуст, при
следующем запуске эти cid должны быть в журнале как «проверить»: reconcile вернет их как confirmed или
closed_offline.
Заявки на бирже при остановке не затрагиваются. Если до остановки заявка была принята, она останется на бирже, а при следующем запуске ее подхватит сверка. Снять заявки перед выходом - решение бота: библиотека этого не делает, потому что «снять все» может быть неверным решением (например, защитный стоп должен пережить бота). Порядок остановки нескольких коннекторов тоже за ботом: обычно сначала UD (чтобы не принимать команды), потом MD.
Листать можно клавишами Влево и Вправо.