Audits › Controls › Position reconciliation with the exchange
Accounting and reconciliation · control 10 of 21
Position reconciliation with the exchange: does your trading bot have it?
At startup and periodically, does it compare its own positions with the exchange's and resolve the differences?
Why it matters
A position opened by hand at the exchange, or left over from a crash, is invisible to a bot that only trusts its own memory. The usual outcome is a freeze or a double position.
How to check it in the code
- Search for reconcile, sync, fetch_positions, fetch_open_orders, fetch_balance.
- Check whether the result is compared with the internal state and what happens on a mismatch.
- Check the interval: only at startup, or also while running.
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: 3 present, 5 partial, 0 absent, 0 not applicable, 2 not verified.
| Bot | Verdict | Evidence | Note |
|---|---|---|---|
| HKUDS/Vibe-Trading | Not verified | agent/backtest/binance_account_reconciliation.py exists but lives under agent/backtest/ (shadow-account/backtest verification), not under agent/src/live/ | Did not find a live position-vs-broker reconciliation routine run at startup within budget. |
| hummingbot/hummingbot | Partial | hummingbot/connector/client_order_tracker.py, restore_tracking_states() and process_order_not_found() (lost_order_count_limit=3) | On restart it restores in-flight orders from a locally saved state and re-polls the exchange for those known orders; an order not found after 3 tries is marked lost/failed. No evidence found of a full startup reconciliation that fetches ALL open exchange positions/orders and detects ones the bot never knew about (e.g. opened manually or left over from a crash). |
| Drakkar-Software/OctoBot | Present | index_trading.py IndexTradingModeProducer.ensure_index() calls _wait_for_positions_init() and _wait_for_orders_init() at startup before rebalancing, loading real exchange positions/orders; telegram_bot.py exposes /refresh_portfolio ('Forces OctoBot's real trader portfolio refresh using exchange data'). | Both an automatic startup sync and a manual on-demand resync from the exchange exist. |
| freqtrade/freqtrade | Partial | freqtrade/freqtradebot.py, FreqtradeBot.startup() calling self.startup_update_open_orders() with code comment 'Only update open orders on startup' -- https://raw.githubusercontent.com/freqtrade/freqtrade/3774521e7028fa666770e7c7a8da323af5c4cefa/freqtrade/freqtradebot.py | Open orders/positions are synced against the exchange once, at startup or on /reload_config. The main process() loop that runs every iteration does not repeat this reconciliation against exchange state. |
| jesse-ai/jesse | Not verified | jesse/store/state_positions.py is a plain in-memory dict (get_position/count_open_positions) with no method that compares stored positions against an exchange query. Population of that store from a live account happens via jesse_live (not in this repo). | No startup/periodic reconciliation code found in the public repo; cannot confirm presence or absence inside jesse_live. |
| chrisleekr/binance-trading-bot | Present | apps/worker/src/queues/pipeline-handlers/reconcile-fees.ts; docs/user-guide/account/orphan-orders.md (orphan-orders-detect cron) | A background cron diffs Binance's live open-order list against the local orders table every 10 minutes and surfaces/adopts mismatches (orphan orders); a separate worker pipeline handler reconciles fees. |
| alsk1992/CloddsBot | Partial | GET https://api.github.com/search/code?q=reconcile+repo:alsk1992/CloddsBot returns only 1 hit, inside src/feeds/betfair/index.ts. No general position-vs-exchange reconciliation module was found covering Binance/Bybit/MEXC/Hyperliquid/Solana/Polymarket/Kalshi. | Reconciliation, if it exists at all, appears limited to one feed integration, not the whole trading surface. |
| Lumiwealth/lumibot | Partial | docs/FAST_ORDER_LIFECYCLE_GUIDE.md: the order state machine includes 'a bounded exact-order reconciliation read after a missed callback, restart, reconnect, or ambiguous mutation result', confirmed implemented for Schwab ('performs an active-order reconciliation after login or reconnect'). | |
| YizhiSong/FriesTrader | Present | PHASE_B_TASK.md Step 4 ('Pull get_equity_positions -- this snapshot... is also what Step 5's stop-loss/take-profit checks use') and Step 6 ('Re-pull fresh account state... the earlier pull is now stale for any sell that actually executed above'). | No independent local position ledger is kept -- every cycle re-derives truth from the broker, both at cycle start and again after this cycle's own sells. |
| c9s/bbgo | Partial | pkg/bbgo/environment.go (BindSync -> tradeWriterCreator/orderWriterCreator persist trades/orders to DB; futuresPositionWriterCreator calls SyncService.FuturesService.QueryPositionsAndInsert after each futures trade) | The bot syncs historical trades/orders and periodically pulls exchange-side futures position risk into the DB, but no explicit startup routine comparing the strategy's in-memory position against the exchange and auto-resolving drift was found. |
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.