import logging import json import time import os import traceback from hyperliquid.info import Info from hyperliquid.utils import constants from logging_utils import setup_logging # --- Configuration for standalone error logging --- LOGS_DIR = "_logs" ERROR_LOG_FILE = os.path.join(LOGS_DIR, "live_market_errors.log") def log_error(error_message: str, include_traceback: bool = True): """A simple, robust file logger for any errors.""" try: if not os.path.exists(LOGS_DIR): os.makedirs(LOGS_DIR) with open(ERROR_LOG_FILE, 'a') as f: timestamp = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime()) f.write(f"--- ERROR at {timestamp} UTC ---\n") f.write(error_message + "\n") if include_traceback: f.write(traceback.format_exc() + "\n") f.write("="*50 + "\n") except Exception: print(f"CRITICAL: Failed to write to error log file: {error_message}", file=sys.stderr) def on_message(message, shared_prices_dict): """ Callback function to process incoming 'allMids' messages and update the shared memory dictionary directly. """ try: if message.get("channel") == "allMids": new_prices = message.get("data", {}).get("mids", {}) shared_prices_dict.update(new_prices) except Exception as e: log_error(f"Error in WebSocket on_message: {e}") def start_live_feed(shared_prices_dict, log_level='off'): """ Main function for the WebSocket process. It takes a shared dictionary and continuously feeds it with live market data. Includes a watchdog to auto-reconnect on failure. """ setup_logging(log_level, 'LiveMarketFeed') info = None callback = lambda msg: on_message(msg, shared_prices_dict) def connect_and_subscribe(): """Establishes a new WebSocket connection and subscribes to allMids.""" try: logging.info("Connecting to Hyperliquid WebSocket...") # Ensure skip_ws=False to create the ws_manager new_info = Info(constants.MAINNET_API_URL, skip_ws=False) subscription = {"type": "allMids"} new_info.subscribe(subscription, callback) logging.info("WebSocket connected and subscribed to 'allMids'.") return new_info except Exception as e: log_error(f"Failed to connect to WebSocket: {e}") return None info = connect_and_subscribe() logging.info("Starting live price feed process. Press Ctrl+C in main app to stop.") try: while True: # --- Watchdog Logic --- time.sleep(15) # Check the connection every 15 seconds # --- FIX: Changed 'is_running()' to the correct method 'is_alive()' --- if info is None or not info.ws_manager.is_alive(): error_msg = "WebSocket connection lost or not running. Attempting to reconnect..." logging.warning(error_msg) log_error(error_msg, include_traceback=False) # Log it to the file if info and info.ws_manager: # Check if ws_manager exists before stopping try: info.ws_manager.stop() # Clean up old manager except Exception as e: log_error(f"Error stopping old ws_manager: {e}") info = connect_and_subscribe() if info is None: logging.error("Reconnect failed, will retry in 15s.") else: logging.info("Successfully reconnected to WebSocket.") else: logging.debug("Watchdog check: WebSocket connection is active.") except KeyboardInterrupt: logging.info("Stopping WebSocket listener...") except Exception as e: log_error(f"Live Market Feed process crashed: {e}") finally: if info and info.ws_manager: info.ws_manager.stop() logging.info("Listener stopped.")