1use std::str::FromStr;
19
20use nautilus_core::python::{IntoPyObjectNautilusExt, to_pyvalue_err};
21use nautilus_model::{
22 identifiers::{AccountId, InstrumentId},
23 python::instruments::instrument_any_to_pyobject,
24};
25use pyo3::{prelude::*, types::PyList};
26use rust_decimal::Decimal;
27use ustr::Ustr;
28
29use crate::http::client::DydxHttpClient;
30
31#[pymethods]
32impl DydxHttpClient {
33 #[new]
35 #[pyo3(signature = (base_url=None, is_testnet=false))]
36 fn py_new(base_url: Option<String>, is_testnet: bool) -> PyResult<Self> {
37 Self::new(
39 base_url, None, None, is_testnet, None, )
43 .map_err(to_pyvalue_err)
44 }
45
46 #[pyo3(name = "is_testnet")]
48 fn py_is_testnet(&self) -> bool {
49 self.is_testnet()
50 }
51
52 #[pyo3(name = "base_url")]
54 fn py_base_url(&self) -> String {
55 self.base_url().to_string()
56 }
57
58 #[pyo3(name = "request_instruments")]
60 fn py_request_instruments<'py>(
61 &self,
62 py: Python<'py>,
63 maker_fee: Option<String>,
64 taker_fee: Option<String>,
65 ) -> PyResult<Bound<'py, PyAny>> {
66 let maker = maker_fee
67 .as_ref()
68 .map(|s| Decimal::from_str(s))
69 .transpose()
70 .map_err(to_pyvalue_err)?;
71
72 let taker = taker_fee
73 .as_ref()
74 .map(|s| Decimal::from_str(s))
75 .transpose()
76 .map_err(to_pyvalue_err)?;
77
78 let client = self.clone();
79
80 pyo3_async_runtimes::tokio::future_into_py(py, async move {
81 let instruments = client
82 .request_instruments(None, maker, taker)
83 .await
84 .map_err(to_pyvalue_err)?;
85
86 #[allow(deprecated)]
87 Python::with_gil(|py| {
88 let py_instruments: PyResult<Vec<Py<PyAny>>> = instruments
89 .into_iter()
90 .map(|inst| instrument_any_to_pyobject(py, inst))
91 .collect();
92 py_instruments
93 })
94 })
95 }
96
97 #[pyo3(name = "fetch_and_cache_instruments")]
103 fn py_fetch_and_cache_instruments<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
104 let client = self.clone();
105 pyo3_async_runtimes::tokio::future_into_py(py, async move {
106 client
107 .fetch_and_cache_instruments()
108 .await
109 .map_err(to_pyvalue_err)?;
110 Ok(())
111 })
112 }
113
114 #[pyo3(name = "get_instrument")]
116 fn py_get_instrument(&self, py: Python<'_>, symbol: &str) -> PyResult<Option<Py<PyAny>>> {
117 let symbol_ustr = Ustr::from(symbol);
118 let instrument = self.get_instrument(&symbol_ustr);
119 match instrument {
120 Some(inst) => Ok(Some(instrument_any_to_pyobject(py, inst)?)),
121 None => Ok(None),
122 }
123 }
124
125 #[pyo3(name = "instrument_count")]
127 fn py_instrument_count(&self) -> usize {
128 self.instruments().len()
129 }
130
131 #[pyo3(name = "instrument_symbols")]
133 fn py_instrument_symbols(&self) -> Vec<String> {
134 self.instruments()
135 .iter()
136 .map(|entry| entry.key().to_string())
137 .collect()
138 }
139
140 #[pyo3(name = "cache_instruments")]
145 fn py_cache_instruments(
146 &self,
147 py: Python<'_>,
148 py_instruments: Vec<Bound<'_, PyAny>>,
149 ) -> PyResult<()> {
150 use nautilus_model::{
151 instruments::InstrumentAny, python::instruments::pyobject_to_instrument_any,
152 };
153
154 let instruments: Vec<InstrumentAny> = py_instruments
155 .into_iter()
156 .map(|py_inst| {
157 pyobject_to_instrument_any(py, py_inst.unbind())
159 })
160 .collect::<Result<Vec<_>, _>>()
161 .map_err(to_pyvalue_err)?;
162
163 self.cache_instruments(instruments);
164 Ok(())
165 }
166
167 #[pyo3(name = "get_orders")]
171 #[pyo3(signature = (address, subaccount_number, market=None, limit=None))]
172 fn py_get_orders<'py>(
173 &self,
174 py: Python<'py>,
175 address: String,
176 subaccount_number: u32,
177 market: Option<String>,
178 limit: Option<u32>,
179 ) -> PyResult<Bound<'py, PyAny>> {
180 let client = self.clone();
181 pyo3_async_runtimes::tokio::future_into_py(py, async move {
182 let response = client
183 .inner
184 .get_orders(&address, subaccount_number, market.as_deref(), limit)
185 .await
186 .map_err(to_pyvalue_err)?;
187 serde_json::to_string(&response).map_err(to_pyvalue_err)
188 })
189 }
190
191 #[pyo3(name = "get_fills")]
195 #[pyo3(signature = (address, subaccount_number, market=None, limit=None))]
196 fn py_get_fills<'py>(
197 &self,
198 py: Python<'py>,
199 address: String,
200 subaccount_number: u32,
201 market: Option<String>,
202 limit: Option<u32>,
203 ) -> PyResult<Bound<'py, PyAny>> {
204 let client = self.clone();
205 pyo3_async_runtimes::tokio::future_into_py(py, async move {
206 let response = client
207 .inner
208 .get_fills(&address, subaccount_number, market.as_deref(), limit)
209 .await
210 .map_err(to_pyvalue_err)?;
211 serde_json::to_string(&response).map_err(to_pyvalue_err)
212 })
213 }
214
215 #[pyo3(name = "get_subaccount")]
219 fn py_get_subaccount<'py>(
220 &self,
221 py: Python<'py>,
222 address: String,
223 subaccount_number: u32,
224 ) -> PyResult<Bound<'py, PyAny>> {
225 let client = self.clone();
226 pyo3_async_runtimes::tokio::future_into_py(py, async move {
227 let response = client
228 .inner
229 .get_subaccount(&address, subaccount_number)
230 .await
231 .map_err(to_pyvalue_err)?;
232 serde_json::to_string(&response).map_err(to_pyvalue_err)
233 })
234 }
235
236 #[pyo3(name = "request_order_status_reports")]
240 #[pyo3(signature = (address, subaccount_number, account_id, instrument_id=None))]
241 fn py_request_order_status_reports<'py>(
242 &self,
243 py: Python<'py>,
244 address: String,
245 subaccount_number: u32,
246 account_id: AccountId,
247 instrument_id: Option<InstrumentId>,
248 ) -> PyResult<Bound<'py, PyAny>> {
249 let client = self.clone();
250 pyo3_async_runtimes::tokio::future_into_py(py, async move {
251 let reports = client
252 .request_order_status_reports(
253 &address,
254 subaccount_number,
255 account_id,
256 instrument_id,
257 )
258 .await
259 .map_err(to_pyvalue_err)?;
260
261 Python::attach(|py| {
262 let pylist =
263 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
264 Ok(pylist.into_py_any_unwrap(py))
265 })
266 })
267 }
268
269 #[pyo3(name = "request_fill_reports")]
273 #[pyo3(signature = (address, subaccount_number, account_id, instrument_id=None))]
274 fn py_request_fill_reports<'py>(
275 &self,
276 py: Python<'py>,
277 address: String,
278 subaccount_number: u32,
279 account_id: AccountId,
280 instrument_id: Option<InstrumentId>,
281 ) -> PyResult<Bound<'py, PyAny>> {
282 let client = self.clone();
283 pyo3_async_runtimes::tokio::future_into_py(py, async move {
284 let reports = client
285 .request_fill_reports(&address, subaccount_number, account_id, instrument_id)
286 .await
287 .map_err(to_pyvalue_err)?;
288
289 Python::attach(|py| {
290 let pylist =
291 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
292 Ok(pylist.into_py_any_unwrap(py))
293 })
294 })
295 }
296
297 #[pyo3(name = "request_position_status_reports")]
301 #[pyo3(signature = (address, subaccount_number, account_id, instrument_id=None))]
302 fn py_request_position_status_reports<'py>(
303 &self,
304 py: Python<'py>,
305 address: String,
306 subaccount_number: u32,
307 account_id: AccountId,
308 instrument_id: Option<InstrumentId>,
309 ) -> PyResult<Bound<'py, PyAny>> {
310 let client = self.clone();
311 pyo3_async_runtimes::tokio::future_into_py(py, async move {
312 let reports = client
313 .request_position_status_reports(
314 &address,
315 subaccount_number,
316 account_id,
317 instrument_id,
318 )
319 .await
320 .map_err(to_pyvalue_err)?;
321
322 Python::attach(|py| {
323 let pylist =
324 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
325 Ok(pylist.into_py_any_unwrap(py))
326 })
327 })
328 }
329
330 #[pyo3(name = "request_bars")]
353 #[pyo3(signature = (bar_type, resolution, limit=None, start=None, end=None))]
354 fn py_request_bars<'py>(
355 &self,
356 py: Python<'py>,
357 bar_type: String,
358 resolution: String,
359 limit: Option<u32>,
360 start: Option<String>,
361 end: Option<String>,
362 ) -> PyResult<Bound<'py, PyAny>> {
363 use std::str::FromStr;
364
365 use chrono::DateTime;
366 use nautilus_model::data::BarType;
367
368 use crate::common::enums::DydxCandleResolution;
369
370 let bar_type = BarType::from_str(&bar_type).map_err(to_pyvalue_err)?;
371 let resolution = DydxCandleResolution::from_str(&resolution).map_err(to_pyvalue_err)?;
372
373 let from_iso = start
374 .map(|s| DateTime::parse_from_rfc3339(&s).map(|dt| dt.with_timezone(&chrono::Utc)))
375 .transpose()
376 .map_err(to_pyvalue_err)?;
377
378 let to_iso = end
379 .map(|s| DateTime::parse_from_rfc3339(&s).map(|dt| dt.with_timezone(&chrono::Utc)))
380 .transpose()
381 .map_err(to_pyvalue_err)?;
382
383 let client = self.clone();
384
385 pyo3_async_runtimes::tokio::future_into_py(py, async move {
386 let bars = client
387 .request_bars(bar_type, resolution, limit, from_iso, to_iso)
388 .await
389 .map_err(to_pyvalue_err)?;
390
391 Python::attach(|py| {
392 let pylist = PyList::new(py, bars.into_iter().map(|b| b.into_py_any_unwrap(py)))?;
393 Ok(pylist.into_py_any_unwrap(py))
394 })
395 })
396 }
397
398 #[pyo3(name = "request_trade_ticks")]
415 #[pyo3(signature = (instrument_id, limit=None))]
416 fn py_request_trade_ticks<'py>(
417 &self,
418 py: Python<'py>,
419 instrument_id: InstrumentId,
420 limit: Option<u32>,
421 ) -> PyResult<Bound<'py, PyAny>> {
422 let client = self.clone();
423
424 pyo3_async_runtimes::tokio::future_into_py(py, async move {
425 let trades = client
426 .request_trade_ticks(instrument_id, limit)
427 .await
428 .map_err(to_pyvalue_err)?;
429
430 Python::attach(|py| {
431 let pylist = PyList::new(py, trades.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
432 Ok(pylist.into_py_any_unwrap(py))
433 })
434 })
435 }
436
437 #[pyo3(name = "request_orderbook_snapshot")]
452 fn py_request_orderbook_snapshot<'py>(
453 &self,
454 py: Python<'py>,
455 instrument_id: InstrumentId,
456 ) -> PyResult<Bound<'py, PyAny>> {
457 let client = self.clone();
458
459 pyo3_async_runtimes::tokio::future_into_py(py, async move {
460 let deltas = client
461 .request_orderbook_snapshot(instrument_id)
462 .await
463 .map_err(to_pyvalue_err)?;
464
465 Python::attach(|py| Ok(deltas.into_py_any_unwrap(py)))
466 })
467 }
468
469 #[pyo3(name = "get_time")]
476 fn py_get_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
477 let client = self.clone();
478 pyo3_async_runtimes::tokio::future_into_py(py, async move {
479 let response = client.inner.get_time().await.map_err(to_pyvalue_err)?;
480 Python::attach(|py| {
481 use pyo3::types::PyDict;
482 let dict = PyDict::new(py);
483 dict.set_item("iso", response.iso.to_string())?;
484 dict.set_item("epoch", response.epoch_ms)?;
485 Ok(dict.into_py_any_unwrap(py))
486 })
487 })
488 }
489
490 #[pyo3(name = "get_height")]
497 fn py_get_height<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
498 let client = self.clone();
499 pyo3_async_runtimes::tokio::future_into_py(py, async move {
500 let response = client.inner.get_height().await.map_err(to_pyvalue_err)?;
501 Python::attach(|py| {
502 use pyo3::types::PyDict;
503 let dict = PyDict::new(py);
504 dict.set_item("height", response.height)?;
505 dict.set_item("time", response.time)?;
506 Ok(dict.into_py_any_unwrap(py))
507 })
508 })
509 }
510
511 #[pyo3(name = "get_transfers")]
515 #[pyo3(signature = (address, subaccount_number, limit=None))]
516 fn py_get_transfers<'py>(
517 &self,
518 py: Python<'py>,
519 address: String,
520 subaccount_number: u32,
521 limit: Option<u32>,
522 ) -> PyResult<Bound<'py, PyAny>> {
523 let client = self.clone();
524 pyo3_async_runtimes::tokio::future_into_py(py, async move {
525 let response = client
526 .inner
527 .get_transfers(&address, subaccount_number, limit)
528 .await
529 .map_err(to_pyvalue_err)?;
530 serde_json::to_string(&response).map_err(to_pyvalue_err)
531 })
532 }
533
534 fn __repr__(&self) -> String {
535 format!(
536 "DydxHttpClient(base_url='{}', is_testnet={}, cached_instruments={})",
537 self.base_url(),
538 self.is_testnet(),
539 self.instruments().len()
540 )
541 }
542}