info.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
  1. from hyperliquid.api import API
  2. from hyperliquid.utils.types import (
  3. Any,
  4. Callable,
  5. Cloid,
  6. List,
  7. Meta,
  8. Optional,
  9. SpotMeta,
  10. SpotMetaAndAssetCtxs,
  11. Subscription,
  12. cast,
  13. )
  14. from hyperliquid.websocket_manager import WebsocketManager
  15. class Info(API):
  16. def __init__(
  17. self,
  18. base_url: Optional[str] = None,
  19. skip_ws: Optional[bool] = False,
  20. meta: Optional[Meta] = None,
  21. spot_meta: Optional[SpotMeta] = None,
  22. # Note that when perp_dexs is None, then "" is used as the perp dex. "" represents
  23. # the original dex.
  24. perp_dexs: Optional[List[str]] = None,
  25. timeout: Optional[float] = None,
  26. ): # pylint: disable=too-many-locals
  27. super().__init__(base_url, timeout)
  28. self.ws_manager: Optional[WebsocketManager] = None
  29. if not skip_ws:
  30. self.ws_manager = WebsocketManager(self.base_url)
  31. self.ws_manager.start()
  32. if spot_meta is None:
  33. spot_meta = self.spot_meta()
  34. self.coin_to_asset = {}
  35. self.name_to_coin = {}
  36. self.asset_to_sz_decimals = {}
  37. # spot assets start at 10000
  38. for spot_info in spot_meta["universe"]:
  39. asset = spot_info["index"] + 10000
  40. self.coin_to_asset[spot_info["name"]] = asset
  41. self.name_to_coin[spot_info["name"]] = spot_info["name"]
  42. base, quote = spot_info["tokens"]
  43. base_info = spot_meta["tokens"][base]
  44. quote_info = spot_meta["tokens"][quote]
  45. self.asset_to_sz_decimals[asset] = base_info["szDecimals"]
  46. name = f'{base_info["name"]}/{quote_info["name"]}'
  47. if name not in self.name_to_coin:
  48. self.name_to_coin[name] = spot_info["name"]
  49. perp_dex_to_offset = {"": 0}
  50. if perp_dexs is None:
  51. perp_dexs = [""]
  52. else:
  53. for i, perp_dex in enumerate(self.perp_dexs()[1:]):
  54. # builder-deployed perp dexs start at 110000
  55. perp_dex_to_offset[perp_dex["name"]] = 110000 + i * 10000
  56. for perp_dex in perp_dexs:
  57. offset = perp_dex_to_offset[perp_dex]
  58. if perp_dex == "" and meta is not None:
  59. self.set_perp_meta(meta, 0)
  60. else:
  61. fresh_meta = self.meta(dex=perp_dex)
  62. self.set_perp_meta(fresh_meta, offset)
  63. def set_perp_meta(self, meta: Meta, offset: int) -> Any:
  64. for asset, asset_info in enumerate(meta["universe"]):
  65. asset += offset
  66. self.coin_to_asset[asset_info["name"]] = asset
  67. self.name_to_coin[asset_info["name"]] = asset_info["name"]
  68. self.asset_to_sz_decimals[asset] = asset_info["szDecimals"]
  69. def disconnect_websocket(self):
  70. if self.ws_manager is None:
  71. raise RuntimeError("Cannot call disconnect_websocket since skip_ws was used")
  72. else:
  73. self.ws_manager.stop()
  74. def user_state(self, address: str, dex: str = "") -> Any:
  75. """Retrieve trading details about a user.
  76. POST /info
  77. Args:
  78. address (str): Onchain address in 42-character hexadecimal format;
  79. e.g. 0x0000000000000000000000000000000000000000.
  80. Returns:
  81. {
  82. assetPositions: [
  83. {
  84. position: {
  85. coin: str,
  86. entryPx: Optional[float string]
  87. leverage: {
  88. type: "cross" | "isolated",
  89. value: int,
  90. rawUsd: float string # only if type is "isolated"
  91. },
  92. liquidationPx: Optional[float string]
  93. marginUsed: float string,
  94. positionValue: float string,
  95. returnOnEquity: float string,
  96. szi: float string,
  97. unrealizedPnl: float string
  98. },
  99. type: "oneWay"
  100. }
  101. ],
  102. crossMarginSummary: MarginSummary,
  103. marginSummary: MarginSummary,
  104. withdrawable: float string,
  105. }
  106. where MarginSummary is {
  107. accountValue: float string,
  108. totalMarginUsed: float string,
  109. totalNtlPos: float string,
  110. totalRawUsd: float string,
  111. }
  112. """
  113. return self.post("/info", {"type": "clearinghouseState", "user": address, "dex": dex})
  114. def spot_user_state(self, address: str) -> Any:
  115. return self.post("/info", {"type": "spotClearinghouseState", "user": address})
  116. def open_orders(self, address: str, dex: str = "") -> Any:
  117. """Retrieve a user's open orders.
  118. POST /info
  119. Args:
  120. address (str): Onchain address in 42-character hexadecimal format;
  121. e.g. 0x0000000000000000000000000000000000000000.
  122. Returns: [
  123. {
  124. coin: str,
  125. limitPx: float string,
  126. oid: int,
  127. side: "A" | "B",
  128. sz: float string,
  129. timestamp: int
  130. }
  131. ]
  132. """
  133. return self.post("/info", {"type": "openOrders", "user": address, "dex": dex})
  134. def frontend_open_orders(self, address: str, dex: str = "") -> Any:
  135. """Retrieve a user's open orders with additional frontend info.
  136. POST /info
  137. Args:
  138. address (str): Onchain address in 42-character hexadecimal format;
  139. e.g. 0x0000000000000000000000000000000000000000.
  140. Returns: [
  141. {
  142. children:
  143. [
  144. dict of frontend orders
  145. ]
  146. coin: str,
  147. isPositionTpsl: bool,
  148. isTrigger: bool,
  149. limitPx: float string,
  150. oid: int,
  151. orderType: str,
  152. origSz: float string,
  153. reduceOnly: bool,
  154. side: "A" | "B",
  155. sz: float string,
  156. tif: str,
  157. timestamp: int,
  158. triggerCondition: str,
  159. triggerPx: float str
  160. }
  161. ]
  162. """
  163. return self.post("/info", {"type": "frontendOpenOrders", "user": address, "dex": dex})
  164. def all_mids(self, dex: str = "") -> Any:
  165. """Retrieve all mids for all actively traded coins.
  166. POST /info
  167. Returns:
  168. {
  169. ATOM: float string,
  170. BTC: float string,
  171. any other coins which are trading: float string
  172. }
  173. """
  174. return self.post("/info", {"type": "allMids", "dex": dex})
  175. def user_fills(self, address: str) -> Any:
  176. """Retrieve a given user's fills.
  177. POST /info
  178. Args:
  179. address (str): Onchain address in 42-character hexadecimal format;
  180. e.g. 0x0000000000000000000000000000000000000000.
  181. Returns:
  182. [
  183. {
  184. closedPnl: float string,
  185. coin: str,
  186. crossed: bool,
  187. dir: str,
  188. hash: str,
  189. oid: int,
  190. px: float string,
  191. side: str,
  192. startPosition: float string,
  193. sz: float string,
  194. time: int
  195. },
  196. ...
  197. ]
  198. """
  199. return self.post("/info", {"type": "userFills", "user": address})
  200. def user_fills_by_time(
  201. self, address: str, start_time: int, end_time: Optional[int] = None, aggregate_by_time: Optional[bool] = False
  202. ) -> Any:
  203. """Retrieve a given user's fills by time.
  204. POST /info
  205. Args:
  206. address (str): Onchain address in 42-character hexadecimal format;
  207. e.g. 0x0000000000000000000000000000000000000000.
  208. start_time (int): Unix timestamp in milliseconds
  209. end_time (Optional[int]): Unix timestamp in milliseconds
  210. aggregate_by_time (Optional[bool]): When true, partial fills are combined when a crossing order gets filled by multiple different resting orders. Resting orders filled by multiple crossing orders will not be aggregated.
  211. Returns:
  212. [
  213. {
  214. closedPnl: float string,
  215. coin: str,
  216. crossed: bool,
  217. dir: str,
  218. hash: str,
  219. oid: int,
  220. px: float string,
  221. side: str,
  222. startPosition: float string,
  223. sz: float string,
  224. time: int
  225. },
  226. ...
  227. ]
  228. """
  229. return self.post(
  230. "/info",
  231. {
  232. "type": "userFillsByTime",
  233. "user": address,
  234. "startTime": start_time,
  235. "endTime": end_time,
  236. "aggregateByTime": aggregate_by_time,
  237. },
  238. )
  239. def meta(self, dex: str = "") -> Meta:
  240. """Retrieve exchange perp metadata
  241. POST /info
  242. Returns:
  243. {
  244. universe: [
  245. {
  246. name: str,
  247. szDecimals: int
  248. },
  249. ...
  250. ]
  251. }
  252. """
  253. return cast(Meta, self.post("/info", {"type": "meta", "dex": dex}))
  254. def meta_and_asset_ctxs(self) -> Any:
  255. """Retrieve exchange MetaAndAssetCtxs
  256. POST /info
  257. Returns:
  258. [
  259. {
  260. universe: [
  261. {
  262. 'name': str,
  263. 'szDecimals': int
  264. 'maxLeverage': int,
  265. 'onlyIsolated': bool,
  266. },
  267. ...
  268. ]
  269. },
  270. [
  271. {
  272. "dayNtlVlm": float string,
  273. "funding": float string,
  274. "impactPxs": Optional([float string, float string]),
  275. "markPx": Optional(float string),
  276. "midPx": Optional(float string),
  277. "openInterest": float string,
  278. "oraclePx": float string,
  279. "premium": Optional(float string),
  280. "prevDayPx": float string
  281. },
  282. ...
  283. ]
  284. """
  285. return self.post("/info", {"type": "metaAndAssetCtxs"})
  286. def perp_dexs(self) -> Any:
  287. return self.post("/info", {"type": "perpDexs"})
  288. def spot_meta(self) -> SpotMeta:
  289. """Retrieve exchange spot metadata
  290. POST /info
  291. Returns:
  292. {
  293. universe: [
  294. {
  295. tokens: [int, int],
  296. name: str,
  297. index: int,
  298. isCanonical: bool
  299. },
  300. ...
  301. ],
  302. tokens: [
  303. {
  304. name: str,
  305. szDecimals: int,
  306. weiDecimals: int,
  307. index: int,
  308. tokenId: str,
  309. isCanonical: bool
  310. },
  311. ...
  312. ]
  313. }
  314. """
  315. return cast(SpotMeta, self.post("/info", {"type": "spotMeta"}))
  316. def spot_meta_and_asset_ctxs(self) -> SpotMetaAndAssetCtxs:
  317. """Retrieve exchange spot asset contexts
  318. POST /info
  319. Returns:
  320. [
  321. {
  322. universe: [
  323. {
  324. tokens: [int, int],
  325. name: str,
  326. index: int,
  327. isCanonical: bool
  328. },
  329. ...
  330. ],
  331. tokens: [
  332. {
  333. name: str,
  334. szDecimals: int,
  335. weiDecimals: int,
  336. index: int,
  337. tokenId: str,
  338. isCanonical: bool
  339. },
  340. ...
  341. ]
  342. },
  343. [
  344. {
  345. dayNtlVlm: float string,
  346. markPx: float string,
  347. midPx: Optional(float string),
  348. prevDayPx: float string,
  349. circulatingSupply: float string,
  350. coin: str
  351. }
  352. ...
  353. ]
  354. ]
  355. """
  356. return cast(SpotMetaAndAssetCtxs, self.post("/info", {"type": "spotMetaAndAssetCtxs"}))
  357. def funding_history(self, name: str, startTime: int, endTime: Optional[int] = None) -> Any:
  358. """Retrieve funding history for a given coin
  359. POST /info
  360. Args:
  361. name (str): Coin to retrieve funding history for.
  362. startTime (int): Unix timestamp in milliseconds.
  363. endTime (int): Unix timestamp in milliseconds.
  364. Returns:
  365. [
  366. {
  367. coin: str,
  368. fundingRate: float string,
  369. premium: float string,
  370. time: int
  371. },
  372. ...
  373. ]
  374. """
  375. coin = self.name_to_coin[name]
  376. if endTime is not None:
  377. return self.post(
  378. "/info", {"type": "fundingHistory", "coin": coin, "startTime": startTime, "endTime": endTime}
  379. )
  380. return self.post("/info", {"type": "fundingHistory", "coin": coin, "startTime": startTime})
  381. def user_funding_history(self, user: str, startTime: int, endTime: Optional[int] = None) -> Any:
  382. """Retrieve a user's funding history
  383. POST /info
  384. Args:
  385. user (str): Address of the user in 42-character hexadecimal format.
  386. startTime (int): Start time in milliseconds, inclusive.
  387. endTime (int, optional): End time in milliseconds, inclusive. Defaults to current time.
  388. Returns:
  389. List[Dict]: A list of funding history records, where each record contains:
  390. - user (str): User address.
  391. - type (str): Type of the record, e.g., "userFunding".
  392. - startTime (int): Unix timestamp of the start time in milliseconds.
  393. - endTime (int): Unix timestamp of the end time in milliseconds.
  394. """
  395. if endTime is not None:
  396. return self.post("/info", {"type": "userFunding", "user": user, "startTime": startTime, "endTime": endTime})
  397. return self.post("/info", {"type": "userFunding", "user": user, "startTime": startTime})
  398. def l2_snapshot(self, name: str) -> Any:
  399. """Retrieve L2 snapshot for a given coin
  400. POST /info
  401. Args:
  402. name (str): Coin to retrieve L2 snapshot for.
  403. Returns:
  404. {
  405. coin: str,
  406. levels: [
  407. [
  408. {
  409. n: int,
  410. px: float string,
  411. sz: float string
  412. },
  413. ...
  414. ],
  415. ...
  416. ],
  417. time: int
  418. }
  419. """
  420. return self.post("/info", {"type": "l2Book", "coin": self.name_to_coin[name]})
  421. def candles_snapshot(self, name: str, interval: str, startTime: int, endTime: int) -> Any:
  422. """Retrieve candles snapshot for a given coin
  423. POST /info
  424. Args:
  425. name (str): Coin to retrieve candles snapshot for.
  426. interval (str): Candlestick interval.
  427. startTime (int): Unix timestamp in milliseconds.
  428. endTime (int): Unix timestamp in milliseconds.
  429. Returns:
  430. [
  431. {
  432. T: int,
  433. c: float string,
  434. h: float string,
  435. i: str,
  436. l: float string,
  437. n: int,
  438. o: float string,
  439. s: string,
  440. t: int,
  441. v: float string
  442. },
  443. ...
  444. ]
  445. """
  446. req = {"coin": self.name_to_coin[name], "interval": interval, "startTime": startTime, "endTime": endTime}
  447. return self.post("/info", {"type": "candleSnapshot", "req": req})
  448. def user_fees(self, address: str) -> Any:
  449. """Retrieve the volume of trading activity associated with a user.
  450. POST /info
  451. Args:
  452. address (str): Onchain address in 42-character hexadecimal format;
  453. e.g. 0x0000000000000000000000000000000000000000.
  454. Returns:
  455. {
  456. activeReferralDiscount: float string,
  457. dailyUserVlm: [
  458. {
  459. date: str,
  460. exchange: str,
  461. userAdd: float string,
  462. userCross: float string
  463. },
  464. ],
  465. feeSchedule: {
  466. add: float string,
  467. cross: float string,
  468. referralDiscount: float string,
  469. tiers: {
  470. mm: [
  471. {
  472. add: float string,
  473. makerFractionCutoff: float string
  474. },
  475. ],
  476. vip: [
  477. {
  478. add: float string,
  479. cross: float string,
  480. ntlCutoff: float string
  481. },
  482. ]
  483. }
  484. },
  485. userAddRate: float string,
  486. userCrossRate: float string
  487. }
  488. """
  489. return self.post("/info", {"type": "userFees", "user": address})
  490. def user_staking_summary(self, address: str) -> Any:
  491. """Retrieve the staking summary associated with a user.
  492. POST /info
  493. Args:
  494. address (str): Onchain address in 42-character hexadecimal format;
  495. e.g. 0x0000000000000000000000000000000000000000.
  496. Returns:
  497. {
  498. delegated: float string,
  499. undelegated: float string,
  500. totalPendingWithdrawal: float string,
  501. nPendingWithdrawals: int
  502. }
  503. """
  504. return self.post("/info", {"type": "delegatorSummary", "user": address})
  505. def user_staking_delegations(self, address: str) -> Any:
  506. """Retrieve the user's staking delegations.
  507. POST /info
  508. Args:
  509. address (str): Onchain address in 42-character hexadecimal format;
  510. e.g. 0x0000000000000000000000000000000000000000.
  511. Returns:
  512. [
  513. {
  514. validator: string,
  515. amount: float string,
  516. lockedUntilTimestamp: int
  517. },
  518. ]
  519. """
  520. return self.post("/info", {"type": "delegations", "user": address})
  521. def user_staking_rewards(self, address: str) -> Any:
  522. """Retrieve the historic staking rewards associated with a user.
  523. POST /info
  524. Args:
  525. address (str): Onchain address in 42-character hexadecimal format;
  526. e.g. 0x0000000000000000000000000000000000000000.
  527. Returns:
  528. [
  529. {
  530. time: int,
  531. source: string,
  532. totalAmount: float string
  533. },
  534. ]
  535. """
  536. return self.post("/info", {"type": "delegatorRewards", "user": address})
  537. def delegator_history(self, user: str) -> Any:
  538. """Retrieve comprehensive staking history for a user.
  539. POST /info
  540. Args:
  541. user (str): Onchain address in 42-character hexadecimal format.
  542. Returns:
  543. Comprehensive staking history including delegation and undelegation
  544. events with timestamps, transaction hashes, and detailed delta information.
  545. """
  546. return self.post("/info", {"type": "delegatorHistory", "user": user})
  547. def query_order_by_oid(self, user: str, oid: int) -> Any:
  548. return self.post("/info", {"type": "orderStatus", "user": user, "oid": oid})
  549. def query_order_by_cloid(self, user: str, cloid: Cloid) -> Any:
  550. return self.post("/info", {"type": "orderStatus", "user": user, "oid": cloid.to_raw()})
  551. def query_referral_state(self, user: str) -> Any:
  552. return self.post("/info", {"type": "referral", "user": user})
  553. def query_sub_accounts(self, user: str) -> Any:
  554. return self.post("/info", {"type": "subAccounts", "user": user})
  555. def query_user_to_multi_sig_signers(self, multi_sig_user: str) -> Any:
  556. return self.post("/info", {"type": "userToMultiSigSigners", "user": multi_sig_user})
  557. def query_perp_deploy_auction_status(self) -> Any:
  558. return self.post("/info", {"type": "perpDeployAuctionStatus"})
  559. def historical_orders(self, user: str) -> Any:
  560. """Retrieve a user's historical orders.
  561. POST /info
  562. Args:
  563. user (str): Onchain address in 42-character hexadecimal format;
  564. e.g. 0x0000000000000000000000000000000000000000.
  565. Returns:
  566. Returns at most 2000 most recent historical orders with their current
  567. status and detailed order information.
  568. """
  569. return self.post("/info", {"type": "historicalOrders", "user": user})
  570. def user_non_funding_ledger_updates(self, user: str, startTime: int, endTime: Optional[int] = None) -> Any:
  571. """Retrieve non-funding ledger updates for a user.
  572. POST /info
  573. Args:
  574. user (str): Onchain address in 42-character hexadecimal format.
  575. startTime (int): Start time in milliseconds (epoch timestamp).
  576. endTime (Optional[int]): End time in milliseconds (epoch timestamp).
  577. Returns:
  578. Comprehensive ledger updates including deposits, withdrawals, transfers,
  579. liquidations, and other account activities excluding funding payments.
  580. """
  581. return self.post(
  582. "/info",
  583. {"type": "userNonFundingLedgerUpdates", "user": user, "startTime": startTime, "endTime": endTime},
  584. )
  585. def portfolio(self, user: str) -> Any:
  586. """Retrieve comprehensive portfolio performance data.
  587. POST /info
  588. Args:
  589. user (str): Onchain address in 42-character hexadecimal format.
  590. Returns:
  591. Comprehensive portfolio performance data across different time periods,
  592. including account value history, PnL history, and volume metrics.
  593. """
  594. return self.post("/info", {"type": "portfolio", "user": user})
  595. def user_twap_slice_fills(self, user: str) -> Any:
  596. """Retrieve a user's TWAP slice fills.
  597. POST /info
  598. Args:
  599. user (str): Onchain address in 42-character hexadecimal format.
  600. Returns:
  601. Returns at most 2000 most recent TWAP slice fills with detailed
  602. execution information.
  603. """
  604. return self.post("/info", {"type": "userTwapSliceFills", "user": user})
  605. def user_vault_equities(self, user: str) -> Any:
  606. """Retrieve user's equity positions across all vaults.
  607. POST /info
  608. Args:
  609. user (str): Onchain address in 42-character hexadecimal format.
  610. Returns:
  611. Detailed information about user's equity positions across all vaults
  612. including current values, profit/loss metrics, and withdrawal details.
  613. """
  614. return self.post("/info", {"type": "userVaultEquities", "user": user})
  615. def user_role(self, user: str) -> Any:
  616. """Retrieve the role and account type information for a user.
  617. POST /info
  618. Args:
  619. user (str): Onchain address in 42-character hexadecimal format.
  620. Returns:
  621. Role and account type information including account structure,
  622. permissions, and relationships within the Hyperliquid ecosystem.
  623. """
  624. return self.post("/info", {"type": "userRole", "user": user})
  625. def user_rate_limit(self, user: str) -> Any:
  626. """Retrieve user's API rate limit configuration and usage.
  627. POST /info
  628. Args:
  629. user (str): Onchain address in 42-character hexadecimal format.
  630. Returns:
  631. Detailed information about user's API rate limit configuration
  632. and current usage for managing API usage and avoiding rate limiting.
  633. """
  634. return self.post("/info", {"type": "userRateLimit", "user": user})
  635. def query_spot_deploy_auction_status(self, user: str) -> Any:
  636. return self.post("/info", {"type": "spotDeployState", "user": user})
  637. def extra_agents(self, user: str) -> Any:
  638. """Retrieve extra agents associated with a user.
  639. POST /info
  640. Args:
  641. user (str): Onchain address in 42-character hexadecimal format;
  642. e.g. 0x0000000000000000000000000000000000000000.
  643. Returns:
  644. [
  645. {
  646. "name": str,
  647. "address": str,
  648. "validUntil": int
  649. },
  650. ...
  651. ]
  652. """
  653. return self.post("/info", {"type": "extraAgents", "user": user})
  654. def _remap_coin_subscription(self, subscription: Subscription) -> None:
  655. if (
  656. subscription["type"] == "l2Book"
  657. or subscription["type"] == "trades"
  658. or subscription["type"] == "candle"
  659. or subscription["type"] == "bbo"
  660. or subscription["type"] == "activeAssetCtx"
  661. ):
  662. subscription["coin"] = self.name_to_coin[subscription["coin"]]
  663. def subscribe(self, subscription: Subscription, callback: Callable[[Any], None]) -> int:
  664. self._remap_coin_subscription(subscription)
  665. if self.ws_manager is None:
  666. raise RuntimeError("Cannot call subscribe since skip_ws was used")
  667. else:
  668. return self.ws_manager.subscribe(subscription, callback)
  669. def unsubscribe(self, subscription: Subscription, subscription_id: int) -> bool:
  670. self._remap_coin_subscription(subscription)
  671. if self.ws_manager is None:
  672. raise RuntimeError("Cannot call unsubscribe since skip_ws was used")
  673. else:
  674. return self.ws_manager.unsubscribe(subscription, subscription_id)
  675. def name_to_asset(self, name: str) -> int:
  676. return self.coin_to_asset[self.name_to_coin[name]]