import asyncio import logging from os import getenv import orjson import websockets import db # Logger setup logging.basicConfig(level=logging.INFO) log = logging.getLogger("RTS") # Environment variables MONOLITH_RTS_MEXC_API_ACCESS_KEY = getenv("MONOLITH_RTS_MEXC_API_ACCESS_KEY", None) MONOLITH_RTS_MEXC_API_SECRET_KEY = getenv("MONOLITH_RTS_MEXC_API_SECRET_KEY", None) # WebSocket endpoint MEXC_WS_URL = "wss://wbs.mexc.com/ws" { "d": { "e": "spot@public.kline.v3.api", "k": { "t": 1737901140, # TS "o": "684.4", # Open "c": "684.5", # Close "h": "684.5", # High "l": "684.4", # Low "v": "0.173", # Volume of the base "a": "118.41", # Volume of the quote (Quantity) "T": 1737901200, # ? "i": "Min1", # ? }, }, "c": "spot@public.kline.v3.api@BNBUSDT@Min1", # Channel "t": 1737901159239, "s": "BNBUSDT", # Symbol } # Scan DB for last endtime (T) # Request Kline data from last endtime (T) to now # Check Server Time # Response # { # "serverTime" : 1645539742000 # } # GET /api/v3/time # Weight(IP): 1 # Parameter: # NONE # Kline/Candlestick Data # Response # [ # [ # 1640804880000, # "47482.36", # "47482.36", # "47416.57", # "47436.1", # "3.550717", # 1640804940000, # "168387.3" # ] # ] # GET /api/v3/klines # Weight(IP): 1 # Kline/candlestick bars for a symbol. Klines are uniquely identified by their open time. # Parameters: # Name Type Mandatory Description # symbol string YES # interval ENUM YES ENUM: Kline Interval # startTime long NO # endTime long NO # limit integer NO Default 500; max 1000. # Scrub function: # For each record, ensure there are no time gaps # When the 1m window goes over, the next t is always the last T. # Check for gaps, and request all klines between those gaps to ensure a full DB, even with restarts. # Idle jitter function - compare our time with server time. # Compare ts with our time and print jitter. Add jitter warning to log and OHLC. # High jitter may prevent us from getting the correct data for trading. async def mex_handle(data): message = orjson.loads(data) # print(orjson.dumps(message, option=orjson.OPT_INDENT_2).decode("utf-8")) if "code" in message: if message["code"] == 0: log.info("Control message received") return symbol = message["s"] open = message["d"]["k"]["o"] close = message["d"]["k"]["c"] high = message["d"]["k"]["h"] low = message["d"]["k"]["l"] volume_base = message["d"]["k"]["v"] # ERROR IN API DOCS volume_quote = message["d"]["k"]["a"] # > a bigDecimal volume interval = message["d"]["k"]["i"] start_time = message["d"]["k"]["t"] # > t long stratTime end_time = message["d"]["k"]["T"] # > T long endTime event_time = message["t"] # t long eventTime index = f"mex_ohlc_{symbol.lower()}" reformatted = { "s": symbol, "o": float(open), "c": float(close), "h": float(high), "l": float(low), "v": float(volume_base), "a": float(volume_quote), "i": interval, "t": int(start_time), "t2": int(end_time), "ts": int(event_time), } await db.rts_store_message(index, reformatted) print(index) print(orjson.dumps(reformatted, option=orjson.OPT_INDENT_2).decode("utf-8")) print() # Kline WebSocket handler async def mex_main(): await db.init_mysql_pool() async with websockets.connect(MEXC_WS_URL) as websocket: log.info("WebSocket connected") # Define symbols and intervals symbols = ["BTCUSDT"] # Add more symbols as needed interval = "Min1" # Kline interval # Prepare subscription requests for Kline streams # Request: spot@public.kline.v3.api@@ subscriptions = [ f"spot@public.kline.v3.api@{symbol}@{interval}" for symbol in symbols ] # Send subscription requests subscribe_request = { "method": "SUBSCRIPTION", "params": subscriptions, # "id": 1, } await websocket.send(orjson.dumps(subscribe_request).decode("utf-8")) log.info(f"Subscribed to: {subscriptions}") # Listen for messages while True: try: message = await websocket.recv() await mex_handle(message) except websockets.exceptions.ConnectionClosed as e: log.error(f"WebSocket connection closed: {e}") break # Entry point if __name__ == "__main__": try: asyncio.run(mex_main()) except KeyboardInterrupt: log.info("RTS process terminated.")