Audits › Controls › Orderly shutdown
Brakes and stops · control 6 of 21
Orderly shutdown: does your trading bot have it?
When told to stop (signal, Ctrl+C, command), does it cancel open orders and record the open positions, or does it just exit?
Why it matters
Limit orders forgotten in the book fill hours later with nobody watching.
How to check it in the code
- Search for SIGINT, SIGTERM, shutdown, on_exit, cancel_all.
- Check the shutdown path cancels open orders, or documents clearly that it leaves them.
- Check positions are persisted so a restart knows what is open.
This is what the audit does for every bot in the list, on public code, without installing or running anything. Verdict values: present, partial, absent, not applicable, not verified. See the method.
Results across audited bots
Across 10 audited bots: 2 present, 5 partial, 0 absent, 1 not applicable, 2 not verified.
| Bot | Verdict | Evidence | Note |
|---|---|---|---|
| HKUDS/Vibe-Trading | Partial | agent/src/live/halt.py register_halt_action()/on_halt_action(): a registered callable (in production src.live.runtime.flatten.flatten_and_cancel) cancels resting orders and optionally flattens positions (mandate.flatten_on_halt) when a halt trip is observed | This fires only when the HALT sentinel is observed, not confirmed to also fire on a normal process stop (SIGINT/Ctrl-C/SIGTERM). No signal.signal handler wiring this cleanup was found. |
| hummingbot/hummingbot | Present | hummingbot/client/command/stop_command.py, StopCommand.stop_loop() | On stop it calls strategy.on_stop(), stop_strategy(), cancel_outstanding_orders() (unless skip_order_cancellation is set), disconnects connectors and stops the clock. |
| Drakkar-Software/OctoBot | Not verified | Telegram /stop command (telegram_bot.py) triggers AbstractBotInterface.set_command_stop in a background thread, but the shutdown sequence itself (whether it cancels open orders) was not opened due to time/budget. | Not confirmed whether SIGINT/SIGTERM/Ctrl+C or the /stop command cancel open orders before exiting. |
| freqtrade/freqtrade | Not verified | ||
| jesse-ai/jesse | Partial | jesse/services/failure.py: terminate_session() logs the error, publishes an 'unexpectedTermination' event over redis, stores the exception in the DB, and then calls os._exit(1) — there is no call to cancel_all_orders() or any position/order cleanup before the hard exit | The only shutdown path visible in the public repo does not cancel open orders. A user-initiated graceful stop (e.g. from the dashboard) may behave differently but its implementation is inside jesse_live and was not found in this repo. |
| chrisleekr/binance-trading-bot | Partial | packages/core/src/shutdown/index.ts (installGracefulShutdown); docs/operations/kill-switch.md ("What it does — and does not — do") | SIGTERM/SIGINT are handled: every process's registered shutdown runs via Promise.allSettled and the process exits only once all drains finish (no shutdown races between api/worker/study when co-located). However no shutdown path cancels resting orders on Binance — kill-switch.md states explicitly orders "stay live until they fill or you cancel them" — by deliberate crash-only design (recovery relies on the orphan-order reconciliation loop on restart, not on cancel-on-stop). |
| alsk1992/CloddsBot | Partial | src/cli/commands/gateway.ts registers process.on('SIGINT'|'SIGTERM', shutdown) which calls gateway.stop() and process.exit(0). ExecutionService exposes cancelOrder/cancelAllOrders, but no call to them was found inside the shutdown handler itself; gateway.stop() internals are in the gitignored gateway module and were not inspectable. | Signals are caught and shutdown is orderly at the process level, but explicit cancellation of open orders on shutdown is not confirmed. |
| Lumiwealth/lumibot | Partial | raw.githubusercontent.com/.../lumibot/traders/trader.py: `signal.signal(signal.SIGINT, self._stop_pool)` is registered for both live and backtest runs; `_stop_pool` calls `strategy_thread.stop()` for every non-abruptly-closed strategy. | |
| YizhiSong/FriesTrader | Not applicable | README.md 'How it works': there is no long-running daemon to send a stop signal to -- each Phase A/B run is a bounded, scheduled Claude Code session. Step 6 (PHASE_B_TASK.md) always resolves sells with a confirmed order state before any buy is considered, and a failed tool call is logged and the candidate skipped rather than left ambiguous. | Order type (market vs. limit) for place_equity_order was not found in the ~56% of PHASE_B_TASK.md read in this pass (file truncated at 20000/35765 chars) -- whether an unmonitored limit order could rest in the book is not_verificado within this pass, not ruled out. |
| c9s/bbgo | Present | pkg/cmd/run.go (cmdutil.WaitForSignal(SIGINT, SIGTERM), 30s gracefulShutdownPeriod, bbgo.Shutdown, trader.SaveState, session.MarketDataStream.Close()/UserDataStream.Close()) | On SIGINT/SIGTERM the process runs registered per-strategy Shutdown() callbacks, saves persistence state and closes streams within a 30s window; whether open orders are cancelled depends on each strategy implementing StrategyShutdown. |
If your bot does not have it
The watchdog enforces daily loss, drawdown, position size and heartbeat limits from outside the bot, with a read-only key, whatever the bot's own code does.