|
@@ -73,11 +73,12 @@ class OrderFillProcessor:
|
|
|
# This is a quick check to see if a fill came through just before it disappeared
|
|
|
# and _check_external_trades hasn't processed it yet.
|
|
|
# This check is specific to this "disappeared orders" context.
|
|
|
- # fill_just_processed = await self._check_for_recent_fills_for_order(exchange_oid, order_in_db)
|
|
|
- # if fill_just_processed:
|
|
|
- # logger.info(f"ℹ️ Order {exchange_oid} disappeared, but a recent fill was found. Assuming filled. Main fill processing will handle lifecycle.")
|
|
|
- # # Potentially update order_in_db status here or rely on main fill processor
|
|
|
- # continue # Skip to next disappeared_order_id
|
|
|
+ fill_just_processed = await self._check_for_recent_fills_for_order(exchange_oid, order_in_db)
|
|
|
+ if fill_just_processed:
|
|
|
+ logger.info(f"ℹ️ Order {exchange_oid} disappeared, but a recent fill was found. Assuming filled. Main fill processing will handle lifecycle.")
|
|
|
+ # Update order status to filled to prevent incorrect cancellation notifications
|
|
|
+ stats.update_order_status(exchange_order_id=exchange_oid, new_status='filled')
|
|
|
+ continue # Skip to next disappeared_order_id
|
|
|
|
|
|
# If no immediate fill found by the helper, proceed with external cancellation logic
|
|
|
logger.warning(f"⚠️ EXTERNAL CANCELLATION: Order {exchange_oid} with status '{last_status}' was likely cancelled externally on Hyperliquid")
|
|
@@ -188,63 +189,29 @@ class OrderFillProcessor:
|
|
|
|
|
|
async def _check_for_recent_fills_for_order(self, exchange_oid, order_in_db):
|
|
|
"""Check for very recent fills that might match this order."""
|
|
|
- # This method checks for fills that might have occurred *just before* an order disappeared,
|
|
|
- # but *before* the main _check_external_trades might have run for the current cycle.
|
|
|
- # It uses its own tracking of last_processed_trade_time or a default if not available.
|
|
|
try:
|
|
|
recent_fills = self.trading_engine.get_recent_fills()
|
|
|
if not recent_fills:
|
|
|
return False
|
|
|
|
|
|
- # This last_processed_trade_time is for the context of this specific helper,
|
|
|
- # to avoid re-checking fills that the broader external trade monitor might have already seen.
|
|
|
- # It attempts to use the global one if available.
|
|
|
- # The key 'last_processed_trade_time' might be distinct from 'market_monitor_last_processed_trade_time'.
|
|
|
- # For safety, let's ensure this is consistently named if it's meant to be the same.
|
|
|
- # Given it's a helper within OrderFillProcessor, and external trades are separate,
|
|
|
- # we will keep its independent loading logic for now.
|
|
|
- # If MarketMonitor centralizes this timestamp, this should be updated.
|
|
|
-
|
|
|
- # Attempt to load the specific last_processed_trade_time if not already set on this instance
|
|
|
- # This implies self.last_processed_trade_time is an attribute of OrderFillProcessor
|
|
|
- if not hasattr(self, 'last_processed_trade_time_helper') or self.last_processed_trade_time_helper is None:
|
|
|
- try:
|
|
|
- # Using a distinct metadata key for this helper to avoid conflict,
|
|
|
- # or assuming it should use the global one. For now, let's assume it tries to use the global.
|
|
|
- last_time_str = self.trading_engine.stats._get_metadata('market_monitor_last_processed_trade_time')
|
|
|
- if last_time_str:
|
|
|
- self.last_processed_trade_time_helper = datetime.fromisoformat(last_time_str).replace(tzinfo=timezone.utc)
|
|
|
- else:
|
|
|
- self.last_processed_trade_time_helper = datetime.now(timezone.utc) - timedelta(hours=1)
|
|
|
- except Exception:
|
|
|
- self.last_processed_trade_time_helper = datetime.now(timezone.utc) - timedelta(hours=1)
|
|
|
-
|
|
|
-
|
|
|
- for fill in recent_fills:
|
|
|
+ # Check last 50 fills for the order ID to be thorough
|
|
|
+ for fill in recent_fills[-50:]:
|
|
|
try:
|
|
|
- trade_id = fill.get('id')
|
|
|
- timestamp_ms = fill.get('timestamp')
|
|
|
- symbol_from_fill = fill.get('symbol')
|
|
|
- side_from_fill = fill.get('side')
|
|
|
- amount_from_fill = float(fill.get('amount', 0))
|
|
|
- price_from_fill = float(fill.get('price', 0))
|
|
|
+ exchange_order_id_from_fill = fill.get('info', {}).get('oid')
|
|
|
|
|
|
- timestamp_dt = datetime.fromtimestamp(timestamp_ms / 1000, tz=timezone.utc) if timestamp_ms else datetime.now(timezone.utc)
|
|
|
-
|
|
|
- if timestamp_dt <= self.last_processed_trade_time_helper:
|
|
|
- continue
|
|
|
-
|
|
|
- if symbol_from_fill and side_from_fill and amount_from_fill > 0 and price_from_fill > 0:
|
|
|
- exchange_order_id_from_fill = fill.get('info', {}).get('oid')
|
|
|
+ # Direct order ID match is the most reliable indicator
|
|
|
+ if exchange_order_id_from_fill == exchange_oid:
|
|
|
+ symbol_from_fill = fill.get('symbol')
|
|
|
+ side_from_fill = fill.get('side')
|
|
|
+ amount_from_fill = float(fill.get('amount', 0))
|
|
|
|
|
|
- if exchange_order_id_from_fill == exchange_oid:
|
|
|
- if order_in_db.get('symbol') == symbol_from_fill and \
|
|
|
- order_in_db.get('side') == side_from_fill and \
|
|
|
- abs(float(order_in_db.get('amount_requested', 0)) - amount_from_fill) < 0.01 * amount_from_fill :
|
|
|
- logger.info(f"✅ Found recent matching fill {trade_id} for order {exchange_oid}. Not cancelling stop losses.")
|
|
|
- # This fill should be processed by the main external trade checker.
|
|
|
- # For the purpose of this helper, just confirming a fill exists is enough.
|
|
|
- return True
|
|
|
+ # Verify this fill matches our order details
|
|
|
+ if (symbol_from_fill == order_in_db.get('symbol') and
|
|
|
+ side_from_fill == order_in_db.get('side') and
|
|
|
+ amount_from_fill > 0):
|
|
|
+
|
|
|
+ logger.info(f"✅ Found matching fill {fill.get('id')} for order {exchange_oid}. Order was filled, not cancelled.")
|
|
|
+ return True
|
|
|
|
|
|
except Exception as e:
|
|
|
logger.error(f"Error processing fill {fill.get('id','N/A')} in _check_for_recent_fills_for_order: {e}")
|