nautilus_hyperliquid/python/
http.rs1use nautilus_core::python::{IntoPyObjectNautilusExt, to_pyvalue_err};
17use nautilus_model::{
18 instruments::{Instrument, InstrumentAny},
19 python::{
20 instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
21 orders::pyobject_to_order_any,
22 },
23};
24use pyo3::{prelude::*, types::PyList};
25use serde_json::to_string;
26
27use crate::http::client::HyperliquidHttpClient;
28
29#[pymethods]
30impl HyperliquidHttpClient {
31 #[new]
32 #[pyo3(signature = (private_key=None, vault_address=None, is_testnet=false, timeout_secs=None))]
33 fn py_new(
34 private_key: Option<String>,
35 vault_address: Option<String>,
36 is_testnet: bool,
37 timeout_secs: Option<u64>,
38 ) -> PyResult<Self> {
39 let pk = private_key.or_else(|| {
41 if is_testnet {
42 std::env::var("HYPERLIQUID_TESTNET_PK").ok()
43 } else {
44 std::env::var("HYPERLIQUID_PK").ok()
45 }
46 });
47
48 let vault = vault_address.or_else(|| {
49 if is_testnet {
50 std::env::var("HYPERLIQUID_TESTNET_VAULT").ok()
51 } else {
52 std::env::var("HYPERLIQUID_VAULT").ok()
53 }
54 });
55
56 if let Some(key) = pk {
57 Self::from_credentials(&key, vault.as_deref(), is_testnet, timeout_secs)
58 .map_err(to_pyvalue_err)
59 } else {
60 Ok(Self::new(is_testnet, timeout_secs))
61 }
62 }
63
64 #[staticmethod]
72 #[pyo3(name = "from_env")]
73 fn py_from_env() -> PyResult<Self> {
74 Self::from_env().map_err(to_pyvalue_err)
75 }
76
77 #[staticmethod]
87 #[pyo3(name = "from_credentials", signature = (private_key, vault_address=None, is_testnet=false, timeout_secs=None))]
88 fn py_from_credentials(
89 private_key: &str,
90 vault_address: Option<&str>,
91 is_testnet: bool,
92 timeout_secs: Option<u64>,
93 ) -> PyResult<Self> {
94 Self::from_credentials(private_key, vault_address, is_testnet, timeout_secs)
95 .map_err(to_pyvalue_err)
96 }
97
98 #[pyo3(name = "get_perp_meta")]
100 fn py_get_perp_meta<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
101 let client = self.clone();
102 pyo3_async_runtimes::tokio::future_into_py(py, async move {
103 let meta = client.load_perp_meta().await.map_err(to_pyvalue_err)?;
104 to_string(&meta).map_err(to_pyvalue_err)
105 })
106 }
107
108 #[pyo3(name = "get_spot_meta")]
110 fn py_get_spot_meta<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
111 let client = self.clone();
112 pyo3_async_runtimes::tokio::future_into_py(py, async move {
113 let meta = client.get_spot_meta().await.map_err(to_pyvalue_err)?;
114 to_string(&meta).map_err(to_pyvalue_err)
115 })
116 }
117
118 #[pyo3(name = "get_l2_book")]
125 fn py_get_l2_book<'py>(&self, py: Python<'py>, coin: &str) -> PyResult<Bound<'py, PyAny>> {
126 let client = self.clone();
127 let coin = coin.to_string();
128 pyo3_async_runtimes::tokio::future_into_py(py, async move {
129 let book = client.info_l2_book(&coin).await.map_err(to_pyvalue_err)?;
130 to_string(&book).map_err(to_pyvalue_err)
131 })
132 }
133
134 #[pyo3(name = "load_instrument_definitions", signature = (include_perp=true, include_spot=true))]
136 fn py_load_instrument_definitions<'py>(
137 &self,
138 py: Python<'py>,
139 include_perp: bool,
140 include_spot: bool,
141 ) -> PyResult<Bound<'py, PyAny>> {
142 let client = self.clone();
143
144 pyo3_async_runtimes::tokio::future_into_py(py, async move {
145 let mut instruments = client.request_instruments().await.map_err(to_pyvalue_err)?;
146
147 if !include_perp || !include_spot {
148 instruments.retain(|instrument| match instrument {
149 InstrumentAny::CryptoPerpetual(_) => include_perp,
150 InstrumentAny::CurrencyPair(_) => include_spot,
151 _ => true,
152 });
153 }
154
155 instruments.sort_by_key(|instrument| instrument.id());
156
157 Python::attach(|py| {
158 let mut py_instruments = Vec::with_capacity(instruments.len());
159 for instrument in instruments {
160 py_instruments.push(instrument_any_to_pyobject(py, instrument)?);
161 }
162
163 let py_list = PyList::new(py, &py_instruments)?;
164 Ok(py_list.into_any().unbind())
165 })
166 })
167 }
168
169 #[pyo3(name = "submit_order")]
176 fn py_submit_order<'py>(
177 &self,
178 py: Python<'py>,
179 order: Py<PyAny>,
180 ) -> PyResult<Bound<'py, PyAny>> {
181 let client = self.clone();
182
183 pyo3_async_runtimes::tokio::future_into_py(py, async move {
184 let order_any =
186 Python::attach(|py| pyobject_to_order_any(py, order).map_err(to_pyvalue_err))?;
187
188 let report = client
189 .submit_order(&order_any)
190 .await
191 .map_err(to_pyvalue_err)?;
192
193 Python::attach(|py| Ok(report.into_py_any_unwrap(py)))
194 })
195 }
196
197 #[pyo3(name = "submit_orders")]
204 fn py_submit_orders<'py>(
205 &self,
206 py: Python<'py>,
207 orders: Vec<Py<PyAny>>,
208 ) -> PyResult<Bound<'py, PyAny>> {
209 let client = self.clone();
210
211 pyo3_async_runtimes::tokio::future_into_py(py, async move {
212 let order_anys: Vec<nautilus_model::orders::any::OrderAny> = Python::attach(|py| {
214 orders
215 .into_iter()
216 .map(|order| pyobject_to_order_any(py, order))
217 .collect::<PyResult<Vec<_>>>()
218 .map_err(to_pyvalue_err)
219 })?;
220
221 let order_refs: Vec<&nautilus_model::orders::any::OrderAny> =
223 order_anys.iter().collect();
224
225 let reports = client
226 .submit_orders(&order_refs)
227 .await
228 .map_err(to_pyvalue_err)?;
229
230 Python::attach(|py| {
231 let pylist =
232 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
233 Ok(pylist.into_py_any_unwrap(py))
234 })
235 })
236 }
237
238 #[pyo3(name = "get_open_orders")]
242 fn py_get_open_orders<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
243 let client = self.clone();
244
245 pyo3_async_runtimes::tokio::future_into_py(py, async move {
246 let user_address = client.get_user_address().map_err(to_pyvalue_err)?;
247 let response = client
248 .info_open_orders(&user_address)
249 .await
250 .map_err(to_pyvalue_err)?;
251 to_string(&response).map_err(to_pyvalue_err)
252 })
253 }
254
255 #[pyo3(name = "get_clearinghouse_state")]
259 fn py_get_clearinghouse_state<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
260 let client = self.clone();
261
262 pyo3_async_runtimes::tokio::future_into_py(py, async move {
263 let user_address = client.get_user_address().map_err(to_pyvalue_err)?;
264 let response = client
265 .info_clearinghouse_state(&user_address)
266 .await
267 .map_err(to_pyvalue_err)?;
268 to_string(&response).map_err(to_pyvalue_err)
269 })
270 }
271
272 #[pyo3(name = "add_instrument")]
276 fn py_add_instrument(&self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
277 self.add_instrument(pyobject_to_instrument_any(py, instrument)?);
278 Ok(())
279 }
280
281 #[pyo3(name = "set_account_id")]
285 fn py_set_account_id(&mut self, account_id: &str) -> PyResult<()> {
286 let account_id = nautilus_model::identifiers::AccountId::from(account_id);
287 self.set_account_id(account_id);
288 Ok(())
289 }
290
291 #[pyo3(name = "get_user_address")]
295 fn py_get_user_address(&self) -> PyResult<String> {
296 self.get_user_address().map_err(to_pyvalue_err)
297 }
298
299 #[pyo3(name = "request_order_status_reports")]
303 fn py_request_order_status_reports<'py>(
304 &self,
305 py: Python<'py>,
306 instrument_id: Option<&str>,
307 ) -> PyResult<Bound<'py, PyAny>> {
308 let client = self.clone();
309 let instrument_id = instrument_id.map(nautilus_model::identifiers::InstrumentId::from);
310
311 pyo3_async_runtimes::tokio::future_into_py(py, async move {
312 let user_address = client.get_user_address().map_err(to_pyvalue_err)?;
313 let reports = client
314 .request_order_status_reports(&user_address, instrument_id)
315 .await
316 .map_err(to_pyvalue_err)?;
317
318 Python::attach(|py| {
319 let pylist =
320 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
321 Ok(pylist.into_py_any_unwrap(py))
322 })
323 })
324 }
325
326 #[pyo3(name = "request_fill_reports")]
330 fn py_request_fill_reports<'py>(
331 &self,
332 py: Python<'py>,
333 instrument_id: Option<&str>,
334 ) -> PyResult<Bound<'py, PyAny>> {
335 let client = self.clone();
336 let instrument_id = instrument_id.map(nautilus_model::identifiers::InstrumentId::from);
337
338 pyo3_async_runtimes::tokio::future_into_py(py, async move {
339 let user_address = client.get_user_address().map_err(to_pyvalue_err)?;
340 let reports = client
341 .request_fill_reports(&user_address, instrument_id)
342 .await
343 .map_err(to_pyvalue_err)?;
344
345 Python::attach(|py| {
346 let pylist =
347 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
348 Ok(pylist.into_py_any_unwrap(py))
349 })
350 })
351 }
352
353 #[pyo3(name = "request_position_status_reports")]
357 fn py_request_position_status_reports<'py>(
358 &self,
359 py: Python<'py>,
360 instrument_id: Option<&str>,
361 ) -> PyResult<Bound<'py, PyAny>> {
362 let client = self.clone();
363 let instrument_id = instrument_id.map(nautilus_model::identifiers::InstrumentId::from);
364
365 pyo3_async_runtimes::tokio::future_into_py(py, async move {
366 let user_address = client.get_user_address().map_err(to_pyvalue_err)?;
367 let reports = client
368 .request_position_status_reports(&user_address, instrument_id)
369 .await
370 .map_err(to_pyvalue_err)?;
371
372 Python::attach(|py| {
373 let pylist =
374 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
375 Ok(pylist.into_py_any_unwrap(py))
376 })
377 })
378 }
379}