115115from app .services .live_trading .gate import GateSpotClient , GateUsdtFuturesClient
116116from app .services .live_trading .htx import HtxClient
117117from app .utils .db import get_db_connection
118+ from app .services .pending_order_loops import PendingOrderLoops
118119from app .utils .logger import get_logger
119120from app .utils .strategy_runtime_logs import append_strategy_log
120121from app .services .strategy_lifecycle import (
135136ALPACA_FILL_DELTA_EPSILON = 1e-8
136137
137138
138- class PendingOrderWorker (PendingOrderPositionSyncMixin ):
139+ class PendingOrderWorker (PendingOrderLoops , PendingOrderPositionSyncMixin ):
139140 def __init__ (self , poll_interval_sec : float = 1.0 , batch_size : int = 50 ):
140141 self .poll_interval_sec = float (poll_interval_sec )
141142 self .batch_size = int (batch_size )
142143 self ._stop_event = threading .Event ()
143144 self ._thread : Optional [threading .Thread ] = None
145+ self ._sync_thread : Optional [threading .Thread ] = None
146+ self .lease_guard = None
144147 self ._lock = threading .Lock ()
145148 self ._notifier = SignalNotifier ()
146149 self ._instrument_rules = get_instrument_rules_provider ()
@@ -177,15 +180,19 @@ def request_exchange_catchup(
177180
178181 def start (self ) -> bool :
179182 with self ._lock :
183+ if self ._thread and self ._thread .is_alive () and self ._sync_thread and self ._sync_thread .is_alive ():
184+ return True
180185 try :
181186 ensure_position_ledger_schema ()
182187 except Exception as e :
183188 logger .warning ("ensure_position_ledger_schema failed: %s" , e )
184- if self ._thread and self ._thread .is_alive ():
185- return True
186189 self ._stop_event .clear ()
187- self ._thread = threading .Thread (target = self ._run_loop , name = "PendingOrderWorker" , daemon = True )
188- self ._thread .start ()
190+ if not self ._thread or not self ._thread .is_alive ():
191+ self ._thread = threading .Thread (target = self ._run_loop , name = "PendingOrderWorker" , daemon = True )
192+ self ._thread .start ()
193+ if not self ._sync_thread or not self ._sync_thread .is_alive ():
194+ self ._sync_thread = threading .Thread (target = self ._run_sync_loop , name = "PendingOrderReconciliation" , daemon = True )
195+ self ._sync_thread .start ()
189196 logger .info ("PendingOrderWorker started" )
190197 return True
191198
@@ -195,43 +202,10 @@ def stop(self, timeout_sec: float = 5.0) -> None:
195202 th = self ._thread
196203 if th and th .is_alive ():
197204 th .join (timeout = timeout_sec )
205+ if self ._sync_thread and self ._sync_thread .is_alive ():
206+ self ._sync_thread .join (timeout = timeout_sec )
198207 logger .info ("PendingOrderWorker stopped" )
199208
200- def _run_loop (self ) -> None :
201- while not self ._stop_event .is_set ():
202- try :
203- self ._tick ()
204- except Exception as e :
205- logger .warning (f"PendingOrderWorker tick error: { e } " )
206- time .sleep (self .poll_interval_sec )
207-
208- def _tick (self ) -> None :
209- # logger.info(f"[PendingOrderWorker] _tick start. last_sync={self._last_position_sync_ts}")
210- self ._sync_quick_trade_orders ()
211- self ._sync_alpaca_sent_orders ()
212- self ._sync_live_sent_orders ()
213- orders = self ._fetch_pending_orders (limit = self .batch_size )
214- # logger.info(f"[PendingOrderWorker] orders fetched: {len(orders)}")
215- if not orders :
216- self ._maybe_sync_positions ()
217- return
218-
219- for o in orders :
220- oid = o .get ("id" )
221- if not oid :
222- continue
223-
224- # Mark processing (best-effort)
225- if not self ._mark_processing (order_id = int (oid )):
226- continue
227-
228- try :
229- self ._dispatch_one (o )
230- except Exception as e :
231- self ._mark_failed (order_id = int (oid ), error = str (e ))
232-
233- self ._maybe_sync_positions ()
234-
235209 def _sync_quick_trade_orders (self , limit : int = 50 ) -> None :
236210 """Reconcile non-terminal Quick Trade orders and protect new fills."""
237211 try :
0 commit comments