1use std::collections::HashMap;
17
18use nautilus_core::python::{IntoPyObjectNautilusExt, to_pyvalue_err};
19use nautilus_model::{
20 data::BarType,
21 enums::{OrderSide, OrderType, TimeInForce},
22 identifiers::{AccountId, ClientOrderId, InstrumentId, VenueOrderId},
23 instruments::Instrument,
24 orders::OrderAny,
25 python::{
26 instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
27 orders::pyobject_to_order_any,
28 },
29 types::{Price, Quantity},
30};
31use pyo3::{prelude::*, types::PyList};
32use serde_json::to_string;
33
34use crate::http::{client::HyperliquidHttpClient, parse::HyperliquidMarketType};
35
36#[pymethods]
37#[pyo3_stub_gen::derive::gen_stub_pymethods]
38impl HyperliquidHttpClient {
39 #[new]
45 #[pyo3(signature = (private_key=None, vault_address=None, account_address=None, is_testnet=false, timeout_secs=60, proxy_url=None, normalize_prices=true))]
46 fn py_new(
47 private_key: Option<String>,
48 vault_address: Option<String>,
49 account_address: Option<String>,
50 is_testnet: bool,
51 timeout_secs: u64,
52 proxy_url: Option<String>,
53 normalize_prices: bool,
54 ) -> PyResult<Self> {
55 let mut client = Self::with_credentials(
56 private_key,
57 vault_address,
58 account_address,
59 is_testnet,
60 timeout_secs,
61 proxy_url,
62 )
63 .map_err(to_pyvalue_err)?;
64 client.set_normalize_prices(normalize_prices);
65 Ok(client)
66 }
67
68 #[staticmethod]
74 #[pyo3(name = "from_env", signature = (is_testnet=false))]
75 fn py_from_env(is_testnet: bool) -> PyResult<Self> {
76 Self::from_env(is_testnet).map_err(to_pyvalue_err)
77 }
78
79 #[staticmethod]
81 #[pyo3(name = "from_credentials", signature = (private_key, vault_address=None, is_testnet=false, timeout_secs=60, proxy_url=None))]
82 fn py_from_credentials(
83 private_key: &str,
84 vault_address: Option<&str>,
85 is_testnet: bool,
86 timeout_secs: u64,
87 proxy_url: Option<String>,
88 ) -> PyResult<Self> {
89 Self::from_credentials(
90 private_key,
91 vault_address,
92 is_testnet,
93 timeout_secs,
94 proxy_url,
95 )
96 .map_err(to_pyvalue_err)
97 }
98
99 #[pyo3(name = "cache_instrument")]
104 fn py_cache_instrument(&self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
105 self.cache_instrument(&pyobject_to_instrument_any(py, instrument)?);
106 Ok(())
107 }
108
109 #[pyo3(name = "set_account_id")]
113 fn py_set_account_id(&mut self, account_id: &str) {
114 let account_id = AccountId::from(account_id);
115 self.set_account_id(account_id);
116 }
117
118 #[pyo3(name = "get_user_address")]
124 fn py_get_user_address(&self) -> PyResult<String> {
125 self.get_user_address().map_err(to_pyvalue_err)
126 }
127
128 #[pyo3(name = "get_spot_fill_coin_mapping")]
136 fn py_get_spot_fill_coin_mapping(&self) -> HashMap<String, String> {
137 self.get_spot_fill_coin_mapping()
138 .into_iter()
139 .map(|(k, v)| (k.to_string(), v.to_string()))
140 .collect()
141 }
142
143 #[pyo3(name = "get_spot_meta")]
145 fn py_get_spot_meta<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
146 let client = self.clone();
147 pyo3_async_runtimes::tokio::future_into_py(py, async move {
148 let meta = client.get_spot_meta().await.map_err(to_pyvalue_err)?;
149 to_string(&meta).map_err(to_pyvalue_err)
150 })
151 }
152
153 #[pyo3(name = "get_perp_meta")]
154 fn py_get_perp_meta<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
155 let client = self.clone();
156 pyo3_async_runtimes::tokio::future_into_py(py, async move {
157 let meta = client.load_perp_meta().await.map_err(to_pyvalue_err)?;
158 to_string(&meta).map_err(to_pyvalue_err)
159 })
160 }
161
162 #[pyo3(name = "load_instrument_definitions", signature = (include_spot=true, include_perps=true, include_perps_hip3=false))]
163 fn py_load_instrument_definitions<'py>(
164 &self,
165 py: Python<'py>,
166 include_spot: bool,
167 include_perps: bool,
168 include_perps_hip3: bool,
169 ) -> PyResult<Bound<'py, PyAny>> {
170 let client = self.clone();
171
172 pyo3_async_runtimes::tokio::future_into_py(py, async move {
173 let mut defs = client
174 .request_instrument_defs()
175 .await
176 .map_err(to_pyvalue_err)?;
177
178 defs.retain(|def| match def.market_type {
179 HyperliquidMarketType::Perp => {
180 if def.is_hip3 {
181 include_perps_hip3
182 } else {
183 include_perps
184 }
185 }
186 HyperliquidMarketType::Spot => include_spot,
187 });
188
189 let mut instruments = client.convert_defs(defs);
190 instruments.sort_by_key(|instrument| instrument.id());
191
192 Python::attach(|py| {
193 let mut py_instruments = Vec::with_capacity(instruments.len());
194 for instrument in instruments {
195 py_instruments.push(instrument_any_to_pyobject(py, instrument)?);
196 }
197
198 let py_list = PyList::new(py, &py_instruments)?;
199 Ok(py_list.into_any().unbind())
200 })
201 })
202 }
203
204 #[pyo3(name = "request_quote_ticks", signature = (instrument_id, start=None, end=None, limit=None))]
205 fn py_request_quote_ticks<'py>(
206 &self,
207 py: Python<'py>,
208 instrument_id: InstrumentId,
209 start: Option<chrono::DateTime<chrono::Utc>>,
210 end: Option<chrono::DateTime<chrono::Utc>>,
211 limit: Option<u32>,
212 ) -> PyResult<Bound<'py, PyAny>> {
213 let _ = (instrument_id, start, end, limit);
214 pyo3_async_runtimes::tokio::future_into_py(py, async move {
215 Err::<Vec<u8>, _>(to_pyvalue_err(anyhow::anyhow!(
216 "Hyperliquid does not provide historical quotes via HTTP API"
217 )))
218 })
219 }
220
221 #[pyo3(name = "request_trade_ticks", signature = (instrument_id, start=None, end=None, limit=None))]
222 fn py_request_trade_ticks<'py>(
223 &self,
224 py: Python<'py>,
225 instrument_id: InstrumentId,
226 start: Option<chrono::DateTime<chrono::Utc>>,
227 end: Option<chrono::DateTime<chrono::Utc>>,
228 limit: Option<u32>,
229 ) -> PyResult<Bound<'py, PyAny>> {
230 let _ = (instrument_id, start, end, limit);
231 pyo3_async_runtimes::tokio::future_into_py(py, async move {
232 Err::<Vec<u8>, _>(to_pyvalue_err(anyhow::anyhow!(
233 "Hyperliquid does not provide historical market trades via HTTP API"
234 )))
235 })
236 }
237
238 #[pyo3(name = "request_bars", signature = (bar_type, start=None, end=None, limit=None))]
247 fn py_request_bars<'py>(
248 &self,
249 py: Python<'py>,
250 bar_type: BarType,
251 start: Option<chrono::DateTime<chrono::Utc>>,
252 end: Option<chrono::DateTime<chrono::Utc>>,
253 limit: Option<u32>,
254 ) -> PyResult<Bound<'py, PyAny>> {
255 let client = self.clone();
256
257 pyo3_async_runtimes::tokio::future_into_py(py, async move {
258 let bars = client
259 .request_bars(bar_type, start, end, limit)
260 .await
261 .map_err(to_pyvalue_err)?;
262
263 Python::attach(|py| {
264 let pylist = PyList::new(py, bars.into_iter().map(|b| b.into_py_any_unwrap(py)))?;
265 Ok(pylist.into_py_any_unwrap(py))
266 })
267 })
268 }
269
270 #[pyo3(name = "submit_order", signature = (
272 instrument_id,
273 client_order_id,
274 order_side,
275 order_type,
276 quantity,
277 time_in_force,
278 price=None,
279 trigger_price=None,
280 post_only=false,
281 reduce_only=false,
282 ))]
283 #[allow(clippy::too_many_arguments)]
284 fn py_submit_order<'py>(
285 &self,
286 py: Python<'py>,
287 instrument_id: InstrumentId,
288 client_order_id: ClientOrderId,
289 order_side: OrderSide,
290 order_type: OrderType,
291 quantity: Quantity,
292 time_in_force: TimeInForce,
293 price: Option<Price>,
294 trigger_price: Option<Price>,
295 post_only: bool,
296 reduce_only: bool,
297 ) -> PyResult<Bound<'py, PyAny>> {
298 let client = self.clone();
299
300 pyo3_async_runtimes::tokio::future_into_py(py, async move {
301 let report = client
302 .submit_order(
303 instrument_id,
304 client_order_id,
305 order_side,
306 order_type,
307 quantity,
308 time_in_force,
309 price,
310 trigger_price,
311 post_only,
312 reduce_only,
313 )
314 .await
315 .map_err(to_pyvalue_err)?;
316
317 Python::attach(|py| Ok(report.into_py_any_unwrap(py)))
318 })
319 }
320
321 #[pyo3(name = "cancel_order", signature = (
326 instrument_id,
327 client_order_id=None,
328 venue_order_id=None,
329 ))]
330 fn py_cancel_order<'py>(
331 &self,
332 py: Python<'py>,
333 instrument_id: InstrumentId,
334 client_order_id: Option<ClientOrderId>,
335 venue_order_id: Option<VenueOrderId>,
336 ) -> PyResult<Bound<'py, PyAny>> {
337 let client = self.clone();
338
339 pyo3_async_runtimes::tokio::future_into_py(py, async move {
340 client
341 .cancel_order(instrument_id, client_order_id, venue_order_id)
342 .await
343 .map_err(to_pyvalue_err)?;
344 Ok(())
345 })
346 }
347
348 #[pyo3(name = "modify_order")]
353 #[allow(clippy::too_many_arguments)]
354 fn py_modify_order<'py>(
355 &self,
356 py: Python<'py>,
357 instrument_id: InstrumentId,
358 venue_order_id: VenueOrderId,
359 order_side: OrderSide,
360 order_type: OrderType,
361 price: Price,
362 quantity: Quantity,
363 trigger_price: Option<Price>,
364 reduce_only: bool,
365 post_only: bool,
366 time_in_force: TimeInForce,
367 client_order_id: Option<ClientOrderId>,
368 ) -> PyResult<Bound<'py, PyAny>> {
369 let client = self.clone();
370
371 pyo3_async_runtimes::tokio::future_into_py(py, async move {
372 client
373 .modify_order(
374 instrument_id,
375 venue_order_id,
376 order_side,
377 order_type,
378 price,
379 quantity,
380 trigger_price,
381 reduce_only,
382 post_only,
383 time_in_force,
384 client_order_id,
385 )
386 .await
387 .map_err(to_pyvalue_err)?;
388 Ok(())
389 })
390 }
391
392 #[pyo3(name = "submit_orders")]
394 fn py_submit_orders<'py>(
395 &self,
396 py: Python<'py>,
397 orders: Vec<Py<PyAny>>,
398 ) -> PyResult<Bound<'py, PyAny>> {
399 let client = self.clone();
400
401 pyo3_async_runtimes::tokio::future_into_py(py, async move {
402 let order_anys: Vec<OrderAny> = Python::attach(|py| {
403 orders
404 .into_iter()
405 .map(|order| pyobject_to_order_any(py, order))
406 .collect::<PyResult<Vec<_>>>()
407 .map_err(to_pyvalue_err)
408 })?;
409
410 let order_refs: Vec<&OrderAny> = order_anys.iter().collect();
411
412 let reports = client
413 .submit_orders(&order_refs)
414 .await
415 .map_err(to_pyvalue_err)?;
416
417 Python::attach(|py| {
418 let pylist =
419 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
420 Ok(pylist.into_py_any_unwrap(py))
421 })
422 })
423 }
424
425 #[pyo3(name = "request_order_status_reports")]
433 fn py_request_order_status_reports<'py>(
434 &self,
435 py: Python<'py>,
436 instrument_id: Option<&str>,
437 ) -> PyResult<Bound<'py, PyAny>> {
438 let client = self.clone();
439 let instrument_id = instrument_id.map(InstrumentId::from);
440
441 pyo3_async_runtimes::tokio::future_into_py(py, async move {
442 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
443 let reports = client
444 .request_order_status_reports(&account_address, instrument_id)
445 .await
446 .map_err(to_pyvalue_err)?;
447
448 Python::attach(|py| {
449 let pylist =
450 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
451 Ok(pylist.into_py_any_unwrap(py))
452 })
453 })
454 }
455
456 #[pyo3(name = "request_fill_reports")]
464 fn py_request_fill_reports<'py>(
465 &self,
466 py: Python<'py>,
467 instrument_id: Option<&str>,
468 ) -> PyResult<Bound<'py, PyAny>> {
469 let client = self.clone();
470 let instrument_id = instrument_id.map(InstrumentId::from);
471
472 pyo3_async_runtimes::tokio::future_into_py(py, async move {
473 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
474 let reports = client
475 .request_fill_reports(&account_address, instrument_id)
476 .await
477 .map_err(to_pyvalue_err)?;
478
479 Python::attach(|py| {
480 let pylist =
481 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
482 Ok(pylist.into_py_any_unwrap(py))
483 })
484 })
485 }
486
487 #[pyo3(name = "request_position_status_reports")]
495 fn py_request_position_status_reports<'py>(
496 &self,
497 py: Python<'py>,
498 instrument_id: Option<&str>,
499 ) -> PyResult<Bound<'py, PyAny>> {
500 let client = self.clone();
501 let instrument_id = instrument_id.map(InstrumentId::from);
502
503 pyo3_async_runtimes::tokio::future_into_py(py, async move {
504 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
505 let reports = client
506 .request_position_status_reports(&account_address, instrument_id)
507 .await
508 .map_err(to_pyvalue_err)?;
509
510 Python::attach(|py| {
511 let pylist =
512 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
513 Ok(pylist.into_py_any_unwrap(py))
514 })
515 })
516 }
517
518 #[pyo3(name = "request_account_state")]
526 fn py_request_account_state<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
527 let client = self.clone();
528
529 pyo3_async_runtimes::tokio::future_into_py(py, async move {
530 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
531 let account_state = client
532 .request_account_state(&account_address)
533 .await
534 .map_err(to_pyvalue_err)?;
535
536 Python::attach(|py| Ok(account_state.into_py_any_unwrap(py)))
537 })
538 }
539
540 #[pyo3(name = "info_user_fees")]
542 fn py_info_user_fees<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
543 let client = self.clone();
544
545 pyo3_async_runtimes::tokio::future_into_py(py, async move {
546 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
547 let json = client
548 .info_user_fees(&account_address)
549 .await
550 .map_err(to_pyvalue_err)?;
551 to_string(&json).map_err(to_pyvalue_err)
552 })
553 }
554}