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, InstrumentAny},
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;
35
36#[pymethods]
37impl HyperliquidHttpClient {
38 #[new]
47 #[pyo3(signature = (private_key=None, vault_address=None, is_testnet=false, timeout_secs=None, proxy_url=None))]
48 fn py_new(
49 private_key: Option<String>,
50 vault_address: Option<String>,
51 is_testnet: bool,
52 timeout_secs: Option<u64>,
53 proxy_url: Option<String>,
54 ) -> PyResult<Self> {
55 Self::with_credentials(
56 private_key,
57 vault_address,
58 is_testnet,
59 timeout_secs,
60 proxy_url,
61 )
62 .map_err(to_pyvalue_err)
63 }
64
65 #[staticmethod]
66 #[pyo3(name = "from_env", signature = (is_testnet=false))]
67 fn py_from_env(is_testnet: bool) -> PyResult<Self> {
68 Self::from_env(is_testnet).map_err(to_pyvalue_err)
69 }
70
71 #[staticmethod]
72 #[pyo3(name = "from_credentials", signature = (private_key, vault_address=None, is_testnet=false, timeout_secs=None, proxy_url=None))]
73 fn py_from_credentials(
74 private_key: &str,
75 vault_address: Option<&str>,
76 is_testnet: bool,
77 timeout_secs: Option<u64>,
78 proxy_url: Option<String>,
79 ) -> PyResult<Self> {
80 Self::from_credentials(
81 private_key,
82 vault_address,
83 is_testnet,
84 timeout_secs,
85 proxy_url,
86 )
87 .map_err(to_pyvalue_err)
88 }
89
90 #[pyo3(name = "cache_instrument")]
91 fn py_cache_instrument(&self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
92 self.cache_instrument(pyobject_to_instrument_any(py, instrument)?);
93 Ok(())
94 }
95
96 #[pyo3(name = "set_account_id")]
97 fn py_set_account_id(&mut self, account_id: &str) -> PyResult<()> {
98 let account_id = AccountId::from(account_id);
99 self.set_account_id(account_id);
100 Ok(())
101 }
102
103 #[pyo3(name = "get_user_address")]
104 fn py_get_user_address(&self) -> PyResult<String> {
105 self.get_user_address().map_err(to_pyvalue_err)
106 }
107
108 #[pyo3(name = "get_spot_fill_coin_mapping")]
109 fn py_get_spot_fill_coin_mapping(&self) -> HashMap<String, String> {
110 self.get_spot_fill_coin_mapping()
111 .into_iter()
112 .map(|(k, v)| (k.to_string(), v.to_string()))
113 .collect()
114 }
115
116 #[pyo3(name = "get_spot_meta")]
117 fn py_get_spot_meta<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
118 let client = self.clone();
119 pyo3_async_runtimes::tokio::future_into_py(py, async move {
120 let meta = client.get_spot_meta().await.map_err(to_pyvalue_err)?;
121 to_string(&meta).map_err(to_pyvalue_err)
122 })
123 }
124
125 #[pyo3(name = "get_perp_meta")]
126 fn py_get_perp_meta<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
127 let client = self.clone();
128 pyo3_async_runtimes::tokio::future_into_py(py, async move {
129 let meta = client.load_perp_meta().await.map_err(to_pyvalue_err)?;
130 to_string(&meta).map_err(to_pyvalue_err)
131 })
132 }
133
134 #[pyo3(name = "load_instrument_definitions", signature = (include_perp=true, include_spot=true))]
135 fn py_load_instrument_definitions<'py>(
136 &self,
137 py: Python<'py>,
138 include_perp: bool,
139 include_spot: bool,
140 ) -> PyResult<Bound<'py, PyAny>> {
141 let client = self.clone();
142
143 pyo3_async_runtimes::tokio::future_into_py(py, async move {
144 let mut instruments = client.request_instruments().await.map_err(to_pyvalue_err)?;
145
146 if !include_perp || !include_spot {
147 instruments.retain(|instrument| match instrument {
148 InstrumentAny::CryptoPerpetual(_) => include_perp,
149 InstrumentAny::CurrencyPair(_) => include_spot,
150 _ => true,
151 });
152 }
153
154 instruments.sort_by_key(|instrument| instrument.id());
155
156 Python::attach(|py| {
157 let mut py_instruments = Vec::with_capacity(instruments.len());
158 for instrument in instruments {
159 py_instruments.push(instrument_any_to_pyobject(py, instrument)?);
160 }
161
162 let py_list = PyList::new(py, &py_instruments)?;
163 Ok(py_list.into_any().unbind())
164 })
165 })
166 }
167
168 #[pyo3(name = "request_quote_ticks", signature = (instrument_id, start=None, end=None, limit=None))]
169 fn py_request_quote_ticks<'py>(
170 &self,
171 py: Python<'py>,
172 instrument_id: InstrumentId,
173 start: Option<chrono::DateTime<chrono::Utc>>,
174 end: Option<chrono::DateTime<chrono::Utc>>,
175 limit: Option<u32>,
176 ) -> PyResult<Bound<'py, PyAny>> {
177 let _ = (instrument_id, start, end, limit);
178 pyo3_async_runtimes::tokio::future_into_py(py, async move {
179 Err::<Vec<u8>, _>(to_pyvalue_err(anyhow::anyhow!(
180 "Hyperliquid does not provide historical quotes via HTTP API"
181 )))
182 })
183 }
184
185 #[pyo3(name = "request_trade_ticks", signature = (instrument_id, start=None, end=None, limit=None))]
186 fn py_request_trade_ticks<'py>(
187 &self,
188 py: Python<'py>,
189 instrument_id: InstrumentId,
190 start: Option<chrono::DateTime<chrono::Utc>>,
191 end: Option<chrono::DateTime<chrono::Utc>>,
192 limit: Option<u32>,
193 ) -> PyResult<Bound<'py, PyAny>> {
194 let _ = (instrument_id, start, end, limit);
195 pyo3_async_runtimes::tokio::future_into_py(py, async move {
196 Err::<Vec<u8>, _>(to_pyvalue_err(anyhow::anyhow!(
197 "Hyperliquid does not provide historical market trades via HTTP API"
198 )))
199 })
200 }
201
202 #[pyo3(name = "request_bars", signature = (bar_type, start=None, end=None, limit=None))]
203 fn py_request_bars<'py>(
204 &self,
205 py: Python<'py>,
206 bar_type: BarType,
207 start: Option<chrono::DateTime<chrono::Utc>>,
208 end: Option<chrono::DateTime<chrono::Utc>>,
209 limit: Option<u32>,
210 ) -> PyResult<Bound<'py, PyAny>> {
211 let client = self.clone();
212
213 pyo3_async_runtimes::tokio::future_into_py(py, async move {
214 let bars = client
215 .request_bars(bar_type, start, end, limit)
216 .await
217 .map_err(to_pyvalue_err)?;
218
219 Python::attach(|py| {
220 let pylist = PyList::new(py, bars.into_iter().map(|b| b.into_py_any_unwrap(py)))?;
221 Ok(pylist.into_py_any_unwrap(py))
222 })
223 })
224 }
225
226 #[pyo3(name = "submit_order", signature = (
227 instrument_id,
228 client_order_id,
229 order_side,
230 order_type,
231 quantity,
232 time_in_force,
233 price=None,
234 trigger_price=None,
235 post_only=false,
236 reduce_only=false,
237 ))]
238 #[allow(clippy::too_many_arguments)]
239 fn py_submit_order<'py>(
240 &self,
241 py: Python<'py>,
242 instrument_id: InstrumentId,
243 client_order_id: ClientOrderId,
244 order_side: OrderSide,
245 order_type: OrderType,
246 quantity: Quantity,
247 time_in_force: TimeInForce,
248 price: Option<Price>,
249 trigger_price: Option<Price>,
250 post_only: bool,
251 reduce_only: bool,
252 ) -> PyResult<Bound<'py, PyAny>> {
253 let client = self.clone();
254
255 pyo3_async_runtimes::tokio::future_into_py(py, async move {
256 let report = client
257 .submit_order(
258 instrument_id,
259 client_order_id,
260 order_side,
261 order_type,
262 quantity,
263 time_in_force,
264 price,
265 trigger_price,
266 post_only,
267 reduce_only,
268 )
269 .await
270 .map_err(to_pyvalue_err)?;
271
272 Python::attach(|py| Ok(report.into_py_any_unwrap(py)))
273 })
274 }
275
276 #[pyo3(name = "cancel_order", signature = (
277 instrument_id,
278 client_order_id=None,
279 venue_order_id=None,
280 ))]
281 fn py_cancel_order<'py>(
282 &self,
283 py: Python<'py>,
284 instrument_id: InstrumentId,
285 client_order_id: Option<ClientOrderId>,
286 venue_order_id: Option<VenueOrderId>,
287 ) -> PyResult<Bound<'py, PyAny>> {
288 let client = self.clone();
289
290 pyo3_async_runtimes::tokio::future_into_py(py, async move {
291 client
292 .cancel_order(instrument_id, client_order_id, venue_order_id)
293 .await
294 .map_err(to_pyvalue_err)?;
295 Ok(())
296 })
297 }
298
299 #[pyo3(name = "submit_orders")]
300 fn py_submit_orders<'py>(
301 &self,
302 py: Python<'py>,
303 orders: Vec<Py<PyAny>>,
304 ) -> PyResult<Bound<'py, PyAny>> {
305 let client = self.clone();
306
307 pyo3_async_runtimes::tokio::future_into_py(py, async move {
308 let order_anys: Vec<OrderAny> = Python::attach(|py| {
309 orders
310 .into_iter()
311 .map(|order| pyobject_to_order_any(py, order))
312 .collect::<PyResult<Vec<_>>>()
313 .map_err(to_pyvalue_err)
314 })?;
315
316 let order_refs: Vec<&OrderAny> = order_anys.iter().collect();
317
318 let reports = client
319 .submit_orders(&order_refs)
320 .await
321 .map_err(to_pyvalue_err)?;
322
323 Python::attach(|py| {
324 let pylist =
325 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
326 Ok(pylist.into_py_any_unwrap(py))
327 })
328 })
329 }
330
331 #[pyo3(name = "request_order_status_reports")]
332 fn py_request_order_status_reports<'py>(
333 &self,
334 py: Python<'py>,
335 instrument_id: Option<&str>,
336 ) -> PyResult<Bound<'py, PyAny>> {
337 let client = self.clone();
338 let instrument_id = instrument_id.map(InstrumentId::from);
339
340 pyo3_async_runtimes::tokio::future_into_py(py, async move {
341 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
342 let reports = client
343 .request_order_status_reports(&account_address, instrument_id)
344 .await
345 .map_err(to_pyvalue_err)?;
346
347 Python::attach(|py| {
348 let pylist =
349 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
350 Ok(pylist.into_py_any_unwrap(py))
351 })
352 })
353 }
354
355 #[pyo3(name = "request_fill_reports")]
356 fn py_request_fill_reports<'py>(
357 &self,
358 py: Python<'py>,
359 instrument_id: Option<&str>,
360 ) -> PyResult<Bound<'py, PyAny>> {
361 let client = self.clone();
362 let instrument_id = instrument_id.map(InstrumentId::from);
363
364 pyo3_async_runtimes::tokio::future_into_py(py, async move {
365 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
366 let reports = client
367 .request_fill_reports(&account_address, instrument_id)
368 .await
369 .map_err(to_pyvalue_err)?;
370
371 Python::attach(|py| {
372 let pylist =
373 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
374 Ok(pylist.into_py_any_unwrap(py))
375 })
376 })
377 }
378
379 #[pyo3(name = "request_position_status_reports")]
380 fn py_request_position_status_reports<'py>(
381 &self,
382 py: Python<'py>,
383 instrument_id: Option<&str>,
384 ) -> PyResult<Bound<'py, PyAny>> {
385 let client = self.clone();
386 let instrument_id = instrument_id.map(InstrumentId::from);
387
388 pyo3_async_runtimes::tokio::future_into_py(py, async move {
389 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
390 let reports = client
391 .request_position_status_reports(&account_address, instrument_id)
392 .await
393 .map_err(to_pyvalue_err)?;
394
395 Python::attach(|py| {
396 let pylist =
397 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
398 Ok(pylist.into_py_any_unwrap(py))
399 })
400 })
401 }
402
403 #[pyo3(name = "request_account_state")]
404 fn py_request_account_state<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
405 let client = self.clone();
406
407 pyo3_async_runtimes::tokio::future_into_py(py, async move {
408 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
409 let account_state = client
410 .request_account_state(&account_address)
411 .await
412 .map_err(to_pyvalue_err)?;
413
414 Python::attach(|py| Ok(account_state.into_py_any_unwrap(py)))
415 })
416 }
417}