107 lines
4.0 KiB
Python
107 lines
4.0 KiB
Python
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.")
|
|
|