diff --git a/examples/basic_ws.py b/examples/basic_ws.py index 3ca62d6a..eb7f7f3e 100644 --- a/examples/basic_ws.py +++ b/examples/basic_ws.py @@ -17,6 +17,7 @@ def main(): info.subscribe({"type": "userFundings", "user": address}, print) info.subscribe({"type": "userNonFundingLedgerUpdates", "user": address}, print) info.subscribe({"type": "webData2", "user": address}, print) + info.subscribe({"type": "bbo", "coin": "ETH"}, print) if __name__ == "__main__": diff --git a/hyperliquid/info.py b/hyperliquid/info.py index 3ba23bd6..d007af0c 100644 --- a/hyperliquid/info.py +++ b/hyperliquid/info.py @@ -598,17 +598,24 @@ def query_sub_accounts(self, user: str) -> Any: def query_user_to_multi_sig_signers(self, multi_sig_user: str) -> Any: return self.post("/info", {"type": "userToMultiSigSigners", "user": multi_sig_user}) - def subscribe(self, subscription: Subscription, callback: Callable[[Any], None]) -> int: - if subscription["type"] == "l2Book" or subscription["type"] == "trades" or subscription["type"] == "candle": + def _validate_subscription(self, subscription: Subscription) -> None: + if ( + subscription["type"] == "l2Book" + or subscription["type"] == "trades" + or subscription["type"] == "candle" + or subscription["type"] == "bbo" + ): subscription["coin"] = self.name_to_coin[subscription["coin"]] + + def subscribe(self, subscription: Subscription, callback: Callable[[Any], None]) -> int: + self._validate_subscription(subscription) if self.ws_manager is None: raise RuntimeError("Cannot call subscribe since skip_ws was used") else: return self.ws_manager.subscribe(subscription, callback) def unsubscribe(self, subscription: Subscription, subscription_id: int) -> bool: - if subscription["type"] == "l2Book" or subscription["type"] == "trades" or subscription["type"] == "candle": - subscription["coin"] = self.name_to_coin[subscription["coin"]] + self._validate_subscription(subscription) if self.ws_manager is None: raise RuntimeError("Cannot call unsubscribe since skip_ws was used") else: diff --git a/hyperliquid/utils/types.py b/hyperliquid/utils/types.py index f3565671..8519237d 100644 --- a/hyperliquid/utils/types.py +++ b/hyperliquid/utils/types.py @@ -37,6 +37,7 @@ SpotMetaAndAssetCtxs = Tuple[SpotMeta, List[SpotAssetCtx]] AllMidsSubscription = TypedDict("AllMidsSubscription", {"type": Literal["allMids"]}) +BboSubscription = TypedDict("BboSubscription", {"type": Literal["bbo"], "coin": str}) L2BookSubscription = TypedDict("L2BookSubscription", {"type": Literal["l2Book"], "coin": str}) TradesSubscription = TypedDict("TradesSubscription", {"type": Literal["trades"], "coin": str}) UserEventsSubscription = TypedDict("UserEventsSubscription", {"type": Literal["userEvents"], "user": str}) @@ -51,6 +52,7 @@ # If adding new subscription types that contain coin's don't forget to handle automatically rewrite name to coin in info.subscribe Subscription = Union[ AllMidsSubscription, + BboSubscription, L2BookSubscription, TradesSubscription, UserEventsSubscription, @@ -65,6 +67,8 @@ AllMidsData = TypedDict("AllMidsData", {"mids": Dict[str, str]}) AllMidsMsg = TypedDict("AllMidsMsg", {"channel": Literal["allMids"], "data": AllMidsData}) L2Level = TypedDict("L2Level", {"px": str, "sz": str, "n": int}) +BboData = TypedDict("BboData", {"coin": str, "time": int, "bbo": Tuple[Optional[L2Level], Optional[L2Level]]}) +BboMsg = TypedDict("BboMsg", {"channel": Literal["bbo"], "data": BboData}) L2BookData = TypedDict("L2BookData", {"coin": str, "levels": Tuple[List[L2Level], List[L2Level]], "time": int}) L2BookMsg = TypedDict("L2BookMsg", {"channel": Literal["l2Book"], "data": L2BookData}) PongMsg = TypedDict("PongMsg", {"channel": Literal["pong"]}) @@ -108,7 +112,7 @@ }, total=False, ) -WsMsg = Union[AllMidsMsg, L2BookMsg, TradesMsg, UserEventsMsg, PongMsg, UserFillsMsg, OtherWsMsg] +WsMsg = Union[AllMidsMsg, BboMsg, L2BookMsg, TradesMsg, UserEventsMsg, PongMsg, UserFillsMsg, OtherWsMsg] # b is the public address of the builder, f is the amount of the fee in tenths of basis points. e.g. 10 means 1 basis point BuilderInfo = TypedDict("BuilderInfo", {"b": str, "f": int}) diff --git a/hyperliquid/websocket_manager.py b/hyperliquid/websocket_manager.py index e2f9cdb6..8c7ca7ac 100644 --- a/hyperliquid/websocket_manager.py +++ b/hyperliquid/websocket_manager.py @@ -31,6 +31,8 @@ def subscription_to_identifier(subscription: Subscription) -> str: return f'userNonFundingLedgerUpdates:{subscription["user"].lower()}' elif subscription["type"] == "webData2": return f'webData2:{subscription["user"].lower()}' + elif subscription["type"] == "bbo": + return f'bbo:{subscription["coin"].lower()}' def ws_msg_to_identifier(ws_msg: WsMsg) -> Optional[str]: @@ -60,6 +62,8 @@ def ws_msg_to_identifier(ws_msg: WsMsg) -> Optional[str]: return f'userNonFundingLedgerUpdates:{ws_msg["data"]["user"].lower()}' elif ws_msg["channel"] == "webData2": return f'webData2:{ws_msg["data"]["user"].lower()}' + elif ws_msg["channel"] == "bbo": + return f'bbo:{ws_msg["data"]["coin"].lower()}' class WebsocketManager(threading.Thread):