@@ -914,6 +914,103 @@ async def _sync_orders_to_database(
914914 for order_id in orders_to_remove :
915915 connector .in_flight_orders .pop (order_id , None )
916916
917+ @staticmethod
918+ def _supports_order_status_query (connector : ConnectorBase ) -> bool :
919+ """Whether a connector can be asked the real state of a single order."""
920+ return (
921+ hasattr (connector , "_request_order_status" )
922+ and hasattr (connector , "_is_order_not_found_during_status_update_error" )
923+ and hasattr (connector , "in_flight_orders" )
924+ )
925+
926+ async def reconcile_active_orders (self ) -> Dict [str , int ]:
927+ """Reconcile persisted active orders against the exchange on startup.
928+
929+ Must be called AFTER ``initialize_all_trading_connectors`` so that each
930+ connector's persisted active orders have been reloaded into
931+ ``in_flight_orders``. For every tracked order we ask the exchange for its
932+ real state (``_request_order_status``) and:
933+
934+ - **Confirmed terminal** (filled/cancelled/failed) -> update the DB to the
935+ real status and drop it from tracking.
936+ - **Confirmed not found** on the exchange -> mark it CANCELLED in the DB
937+ (it no longer exists) and drop it from tracking.
938+ - **Still open** -> sync the DB to the live state and KEEP it tracked, so
939+ it remains visible and cancelable via the trading endpoints.
940+ - **Unverifiable** (transient error, or the connector isn't available)
941+ -> leave the order untouched. We never mark an order terminal unless
942+ the exchange confirms it, so a connector that failed to start can never
943+ cause a live order to be falsely reported as cancelled.
944+
945+ Returns a summary dict with counts for logging/telemetry.
946+ """
947+ summary = {"reconciled_terminal" : 0 , "still_open" : 0 , "unverified" : 0 , "skipped_connectors" : 0 }
948+ if not self .db_manager :
949+ return summary
950+
951+ terminal_states = {
952+ OrderState .FILLED , OrderState .CANCELED ,
953+ OrderState .FAILED , OrderState .COMPLETED ,
954+ }
955+
956+ from database import OrderRepository
957+
958+ for account_name , connectors in self ._trading_connectors .items ():
959+ for connector_name , connector in connectors .items ():
960+ if not self ._supports_order_status_query (connector ) or not connector .in_flight_orders :
961+ continue
962+
963+ # Snapshot tracked orders (the set was loaded from the DB at init).
964+ tracked_orders = list (connector .in_flight_orders .values ())
965+ for order in tracked_orders :
966+ client_order_id = order .client_order_id
967+ note = None
968+ try :
969+ order_update = await connector ._request_order_status (order )
970+ new_state = order_update .new_state
971+ except Exception as exc :
972+ if connector ._is_order_not_found_during_status_update_error (exc ):
973+ # The exchange does not know this order -> it is gone.
974+ new_state = OrderState .CANCELED
975+ note = "Reconciled on startup: order not found on exchange"
976+ else :
977+ # Transient/unknown error - do not touch the order.
978+ logger .warning (
979+ f"Could not verify order { client_order_id } on "
980+ f"{ account_name } /{ connector_name } : { exc } "
981+ )
982+ summary ["unverified" ] += 1
983+ continue
984+
985+ db_status = self ._map_order_state_to_status (new_state )
986+ try :
987+ async with self .db_manager .get_session_context () as session :
988+ order_repo = OrderRepository (session )
989+ await order_repo .update_order_status (
990+ client_order_id = client_order_id ,
991+ status = db_status ,
992+ error_message = note ,
993+ )
994+ except Exception as exc :
995+ logger .error (f"Failed to persist reconciled order { client_order_id } : { exc } " )
996+ summary ["unverified" ] += 1
997+ continue
998+
999+ if new_state in terminal_states :
1000+ connector .in_flight_orders .pop (client_order_id , None )
1001+ summary ["reconciled_terminal" ] += 1
1002+ else :
1003+ # Keep tracking so it stays cancelable via the trading endpoints.
1004+ summary ["still_open" ] += 1
1005+
1006+ logger .info (
1007+ "Order reconciliation complete: "
1008+ f"{ summary ['reconciled_terminal' ]} closed, "
1009+ f"{ summary ['still_open' ]} still open (re-tracked), "
1010+ f"{ summary ['unverified' ]} unverified"
1011+ )
1012+ return summary
1013+
9171014 async def sync_all_orders_to_database (self ):
9181015 """
9191016 Sync connector's in_flight_orders state to database for all trading connectors.
0 commit comments