From 8eadff5a27c7e64e1e506b94c0eeee6ca01e6947 Mon Sep 17 00:00:00 2001 From: Brian Li Date: Fri, 18 Apr 2025 22:43:36 -0400 Subject: [PATCH 1/3] Add WsBbo --- hyperliquid/utils/types.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hyperliquid/utils/types.py b/hyperliquid/utils/types.py index 0c901c10..3e7f59c5 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,7 @@ 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]]}) L2BookData = TypedDict("L2BookData", {"coin": str, "levels": Tuple[List[L2Level]], "time": int}) L2BookMsg = TypedDict("L2BookMsg", {"channel": Literal["l2Book"], "data": L2BookData}) PongMsg = TypedDict("PongMsg", {"channel": Literal["pong"]}) From ab920d978769c8d42b5f9536ca0851834020f896 Mon Sep 17 00:00:00 2001 From: Brian Li Date: Sun, 4 May 2025 09:09:01 -0400 Subject: [PATCH 2/3] Fix bbo --- examples/basic_ws.py | 1 + hyperliquid/info.py | 7 ++++++- hyperliquid/utils/types.py | 3 ++- hyperliquid/websocket_manager.py | 4 ++++ 4 files changed, 13 insertions(+), 2 deletions(-) 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 73bdbc42..baac7a0f 100644 --- a/hyperliquid/info.py +++ b/hyperliquid/info.py @@ -575,7 +575,12 @@ 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": + if ( + subscription["type"] == "l2Book" + or subscription["type"] == "trades" + or subscription["type"] == "candle" + or subscription["type"] == "bbo" + ): subscription["coin"] = self.name_to_coin[subscription["coin"]] if self.ws_manager is None: raise RuntimeError("Cannot call subscribe since skip_ws was used") diff --git a/hyperliquid/utils/types.py b/hyperliquid/utils/types.py index 3e7f59c5..e7c28fb4 100644 --- a/hyperliquid/utils/types.py +++ b/hyperliquid/utils/types.py @@ -68,6 +68,7 @@ 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]], "time": int}) L2BookMsg = TypedDict("L2BookMsg", {"channel": Literal["l2Book"], "data": L2BookData}) PongMsg = TypedDict("PongMsg", {"channel": Literal["pong"]}) @@ -111,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): From fca13d254157365ce3d04988da947a15df298f54 Mon Sep 17 00:00:00 2001 From: Brian Li Date: Wed, 14 May 2025 09:34:40 -0400 Subject: [PATCH 3/3] Address comment --- hyperliquid/info.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/hyperliquid/info.py b/hyperliquid/info.py index 2f75775a..d007af0c 100644 --- a/hyperliquid/info.py +++ b/hyperliquid/info.py @@ -598,7 +598,7 @@ 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: + def _validate_subscription(self, subscription: Subscription) -> None: if ( subscription["type"] == "l2Book" or subscription["type"] == "trades" @@ -606,14 +606,16 @@ def subscribe(self, subscription: Subscription, callback: Callable[[Any], None]) 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: