Audits › Controls › Orphaned stop-loss
Brakes and stops · control 5 of 21
Orphaned stop-loss: does your trading bot have it?
If the bot restarts or loses its connection, do open positions keep a stop order at the exchange, or does the stop only exist in the bot's memory?
Why it matters
A stop that lives only in the process dies with the process. The position stays open with no protection until someone notices.
How to check it in the code
- Find where the stop is placed: as an exchange order (stop-loss, stop-limit, OCO) or as an in-memory price check.
- If it is in memory, check what happens on restart: are positions reloaded and stops re-evaluated?
- Check the exchange adapter supports native stop orders for the pair type used (spot vs futures).
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: 1 present, 3 partial, 1 absent, 0 not applicable, 5 not verified.
| Bot | Verdict | Evidence | Note |
|---|---|---|---|
| HKUDS/Vibe-Trading | Not verified | agent/src/live/mandate/model.py has no stop-loss field of any kind in HardCaps/Mandate | The system is a limits gate (notional/exposure/leverage), not a stop-loss manager. Any stop order the LLM chooses to place would be forwarded to the real broker and live on the broker's own book (not in-process memory), but no dedicated tracking/management subsystem was found to confirm this with code evidence. |
| hummingbot/hummingbot | Absent | hummingbot/strategy_v2/executors/position_executor/position_executor.py, constructor: raises ValueError unless triple_barrier_config.stop_loss_order_type == OrderType.MARKET ("Only market orders are supported for time_limit and stop_loss") | The stop-loss is a soft stop: a market order the live process sends when its own control loop detects the price crossed the level. No native resting stop order is placed on the exchange, so if the process dies or disconnects, the position is unprotected until it comes back. |
| Drakkar-Software/OctoBot | Not verified | Order type implementations live under packages/trading/octobot_trading/personal_data/orders/ (order.py, order_factory.py, and an unread 'types' subfolder); not opened due to time/budget. | Could not confirm whether stop-loss orders are placed exchange-side (native STOP order) or simulated locally by the running process. |
| freqtrade/freqtrade | Partial | docs/stoploss.md ('stoploss_on_exchange': False default; note on bot recreating a manually-cancelled exchange stop) -- https://raw.githubusercontent.com/freqtrade/freqtrade/3774521e7028fa666770e7c7a8da323af5c4cefa/docs/stoploss.md | If stoploss_on_exchange is explicitly enabled, the stop lives as a real order on the exchange and survives a bot crash/restart. It is OFF by default; with it off, the stop only exists in the bot's own loop and disappears if the process dies. |
| jesse-ai/jesse | Not verified | jesse/exchanges/sandbox/Sandbox.py shows that in backtest/paper a 'stop_order' is just an Order object kept in the in-memory store (jesse.store), not sent to any exchange. Whether the real live-trading drivers place a genuine server-side stop order on the exchange is implemented in the closed-source 'jesse_live' package (imported in jesse/controllers/live_controller.py as 'from jesse_live import live_mode'), which is not part of this repository. | Cannot verify without access to jesse_live. |
| chrisleekr/binance-trading-bot | Present | docs/user-guide/account/orphan-orders.md | Protective stops are real resting orders on Binance's book, not in-memory state; they are recoverable after a restart via a deterministic clientOrderId scheme (documented as a "derivable id" for both trailing-trade and momentum), and a background orphan-orders-detect cron (every 10 min) re-associates any order the DB lost track of back to its owning profile. |
| alsk1992/CloddsBot | Not verified | src/execution/futures.ts defines FuturesExecutionService.placeStopLoss() / placeTakeProfit(), i.e. exchange-side STOP_LOSS orders are supported (not purely in-memory). Could not confirm within this session's budget whether a stop is placed automatically on every entry, or reconciled/re-verified after a reconnect or process restart. | Capability exists at the API level; automatic wiring and restart-survival not verified. |
| Lumiwealth/lumibot | Not verified | No queried directly: would require reading each broker adapter's order-submission code (alpaca.py 84 KB, schwab.py 158 KB, tradier.py 104 KB, etc.) to see whether stop-loss orders are placed as broker-native conditional orders or simulated in-process. | |
| YizhiSong/FriesTrader | Partial | scripts/stop_loss.py + PHASE_B_TASK.md Step 5: the stop is recomputed fresh every cycle from get_equity_positions/get_equity_quotes/get_equity_historicals, never held only in a running process's memory, so a crashed session can't 'lose' it between cycles. | But it is not a resting stop order at the broker either -- it is only evaluated once per scheduled cycle (~8:35am Central weekdays, README), so a position has zero protection against a large intraday move between checks. |
| c9s/bbgo | Partial | pkg/types/order.go (OrderTypeStopMarket, OrderTypeStopLimit, OrderTypeTakeProfitMarket, OrderTypeTrailingStopMarket); pkg/risk/riskcontrol/circuit_break.go and position.go (in-memory halt/reduce logic) | The framework supports exchange-native stop order types a strategy can place, but the reviewed risk-control components compute halts/position-reduction in memory and re-submit orders live, so those specific protections stop working if the process dies unless the strategy separately placed a native stop. |
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.