Keeping the internal ledger honest about the exchange
2026Two independent paths let the bot's own record of its positions drift permanently away from what the exchange actually held. Both are silent at the moment they happen and compound on every later tick.
AI Prophet turns LLM forecasts into live prediction-market orders. Ledger state is not held in memory between ticks: _live_ledger_state rebuilds it from the database each cycle, so anything the database fails to record, or records optimistically, becomes the bot's reality on the very next tick.
- Traced a state-divergence bug in the NET-flip path, which sells the opposite side before buying the new one: the BUY leg executed even when the SELL leg raised or returned an ERROR status. The old position was still open on the exchange and its cash had never come back, so the BUY over-committed available funds — and the next tick read a database that had logged the SELL as attempted, diverging from the exchange for good.
- Made the flip transactional from the caller's point of view: after catching the SELL exception, the routine now checks sell_status == ERROR and returns immediately with order_placed=False and an explanatory error string. The BUY is only ever attempted after a confirmed SELL.
- Found a second, quieter failure in _save_order(), which short-circuited whenever signal_id was None — always the case for make_trade() calls. Those orders never reached the database at all, so _live_ledger_state had no record of them and would cheerfully place the same position again on the next tick.
- Made BettingOrder.signal_id nullable so make_trade() rows persist without a parent signal, while the foreign key still constrains the normal process_forecasts path, then dropped the signal_id guard from _save_order() and left only the database-engine check.
Closed both paths by which the ledger could permanently disagree with real exchange state — the failure mode that matters most here, because a rebuilt-from-database ledger never self-corrects.
