1use std::str::FromStr;
19
20use jiff::Timestamp;
21use nautilus_core::python::{IntoPyObjectNautilusExt, to_pyvalue_err};
22use nautilus_model::{
23 data::BarType,
24 identifiers::{AccountId, InstrumentId},
25 instruments::InstrumentAny,
26 python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
27};
28use pyo3::{
29 IntoPyObjectExt,
30 prelude::*,
31 types::{PyDict, PyList},
32};
33use rust_decimal::Decimal;
34
35use crate::{
36 common::{consts::DYDX_VENUE, enums::DydxNetwork},
37 http::client::DydxHttpClient,
38};
39
40#[pymethods]
41#[pyo3_stub_gen::derive::gen_stub_pymethods]
42impl DydxHttpClient {
43 #[new]
59 #[pyo3(signature = (base_url=None, network=DydxNetwork::Mainnet, proxy_url=None))]
60 fn py_new(
61 base_url: Option<String>,
62 network: DydxNetwork,
63 proxy_url: Option<String>,
64 ) -> PyResult<Self> {
65 Self::new(
66 base_url, 60, proxy_url, network, None, )
69 .map_err(to_pyvalue_err)
70 }
71
72 #[pyo3(name = "is_testnet")]
74 fn py_is_testnet(&self) -> bool {
75 self.is_testnet()
76 }
77
78 #[pyo3(name = "base_url")]
80 fn py_base_url(&self) -> String {
81 self.base_url().to_string()
82 }
83
84 #[pyo3(name = "request_instruments")]
94 fn py_request_instruments<'py>(
95 &self,
96 py: Python<'py>,
97 maker_fee: Option<&str>,
98 taker_fee: Option<&str>,
99 ) -> PyResult<Bound<'py, PyAny>> {
100 let maker = maker_fee
101 .map(Decimal::from_str)
102 .transpose()
103 .map_err(to_pyvalue_err)?;
104
105 let taker = taker_fee
106 .map(Decimal::from_str)
107 .transpose()
108 .map_err(to_pyvalue_err)?;
109
110 let client = self.clone();
111
112 pyo3_async_runtimes::tokio::future_into_py(py, async move {
113 let instruments = client
114 .request_instruments(None, maker, taker)
115 .await
116 .map_err(to_pyvalue_err)?;
117
118 Python::attach(|py| {
119 let py_instruments: PyResult<Vec<Py<PyAny>>> = instruments
120 .into_iter()
121 .map(|inst| instrument_any_to_pyobject(py, inst))
122 .collect();
123 py_instruments
124 })
125 })
126 }
127
128 #[pyo3(name = "fetch_and_cache_instruments")]
140 fn py_fetch_and_cache_instruments<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
141 let client = self.clone();
142 pyo3_async_runtimes::tokio::future_into_py(py, async move {
143 client
144 .fetch_and_cache_instruments()
145 .await
146 .map_err(to_pyvalue_err)?;
147 Ok(())
148 })
149 }
150
151 #[pyo3(name = "fetch_instrument")]
158 fn py_fetch_instrument<'py>(
159 &self,
160 py: Python<'py>,
161 ticker: String,
162 ) -> PyResult<Bound<'py, PyAny>> {
163 let client = self.clone();
164 pyo3_async_runtimes::tokio::future_into_py(py, async move {
165 match client.fetch_and_cache_single_instrument(&ticker).await {
166 Ok(Some(instrument)) => {
167 Python::attach(|py| instrument_any_to_pyobject(py, instrument))
168 }
169 Ok(None) => Ok(Python::attach(|py| py.None())),
170 Err(e) => Err(to_pyvalue_err(e)),
171 }
172 })
173 }
174
175 #[pyo3(name = "get_instrument")]
177 fn py_get_instrument(&self, py: Python<'_>, symbol: &str) -> PyResult<Option<Py<PyAny>>> {
178 use nautilus_model::identifiers::Symbol;
179 let instrument_id = InstrumentId::new(Symbol::new(symbol), *DYDX_VENUE);
180 let instrument = self.get_instrument(&instrument_id);
181 match instrument {
182 Some(inst) => Ok(Some(instrument_any_to_pyobject(py, inst)?)),
183 None => Ok(None),
184 }
185 }
186
187 #[pyo3(name = "instrument_count")]
188 fn py_instrument_count(&self) -> usize {
189 self.cached_instruments_count()
190 }
191
192 #[pyo3(name = "instrument_symbols")]
193 fn py_instrument_symbols(&self) -> Vec<String> {
194 self.all_instrument_ids()
195 .into_iter()
196 .map(|id| id.symbol.to_string())
197 .collect()
198 }
199
200 #[pyo3(name = "cache_instruments")]
205 fn py_cache_instruments(
206 &self,
207 py: Python<'_>,
208 py_instruments: Vec<Bound<'_, PyAny>>,
209 ) -> PyResult<()> {
210 let instruments: Vec<InstrumentAny> = py_instruments
211 .into_iter()
212 .map(|py_inst| {
213 pyobject_to_instrument_any(py, py_inst.unbind())
215 })
216 .collect::<Result<Vec<_>, _>>()
217 .map_err(to_pyvalue_err)?;
218
219 self.cache_instruments(instruments);
220 Ok(())
221 }
222
223 #[pyo3(name = "get_orders")]
224 #[pyo3(signature = (address, subaccount_number, market=None, limit=None))]
225 fn py_get_orders<'py>(
226 &self,
227 py: Python<'py>,
228 address: String,
229 subaccount_number: u32,
230 market: Option<String>,
231 limit: Option<u32>,
232 ) -> PyResult<Bound<'py, PyAny>> {
233 let client = self.clone();
234 pyo3_async_runtimes::tokio::future_into_py(py, async move {
235 let response = client
236 .inner
237 .get_orders(&address, subaccount_number, market.as_deref(), limit)
238 .await
239 .map_err(to_pyvalue_err)?;
240 serde_json::to_string(&response).map_err(to_pyvalue_err)
241 })
242 }
243
244 #[pyo3(name = "get_fills")]
245 #[pyo3(signature = (address, subaccount_number, market=None, limit=None))]
246 fn py_get_fills<'py>(
247 &self,
248 py: Python<'py>,
249 address: String,
250 subaccount_number: u32,
251 market: Option<String>,
252 limit: Option<u32>,
253 ) -> PyResult<Bound<'py, PyAny>> {
254 let client = self.clone();
255 pyo3_async_runtimes::tokio::future_into_py(py, async move {
256 let response = client
257 .inner
258 .get_fills(&address, subaccount_number, market.as_deref(), limit)
259 .await
260 .map_err(to_pyvalue_err)?;
261 serde_json::to_string(&response).map_err(to_pyvalue_err)
262 })
263 }
264
265 #[pyo3(name = "get_subaccount")]
266 fn py_get_subaccount<'py>(
267 &self,
268 py: Python<'py>,
269 address: String,
270 subaccount_number: u32,
271 ) -> PyResult<Bound<'py, PyAny>> {
272 let client = self.clone();
273 pyo3_async_runtimes::tokio::future_into_py(py, async move {
274 let response = client
275 .inner
276 .get_subaccount(&address, subaccount_number)
277 .await
278 .map_err(to_pyvalue_err)?;
279 serde_json::to_string(&response).map_err(to_pyvalue_err)
280 })
281 }
282
283 #[pyo3(name = "request_order_status_reports")]
292 #[pyo3(signature = (address, subaccount_number, account_id, instrument_id=None))]
293 fn py_request_order_status_reports<'py>(
294 &self,
295 py: Python<'py>,
296 address: String,
297 subaccount_number: u32,
298 account_id: AccountId,
299 instrument_id: Option<InstrumentId>,
300 ) -> PyResult<Bound<'py, PyAny>> {
301 let client = self.clone();
302 pyo3_async_runtimes::tokio::future_into_py(py, async move {
303 let reports = client
304 .request_order_status_reports(
305 &address,
306 subaccount_number,
307 account_id,
308 instrument_id,
309 )
310 .await
311 .map_err(to_pyvalue_err)?;
312
313 Python::attach(|py| {
314 let py_reports = reports
315 .into_iter()
316 .map(|report| report.into_py_any(py))
317 .collect::<PyResult<Vec<_>>>()?;
318 let pylist = PyList::new(py, py_reports)?;
319 Ok(pylist.into_py_any_unwrap(py))
320 })
321 })
322 }
323
324 #[pyo3(name = "request_fill_reports")]
333 #[pyo3(signature = (address, subaccount_number, account_id, instrument_id=None))]
334 fn py_request_fill_reports<'py>(
335 &self,
336 py: Python<'py>,
337 address: String,
338 subaccount_number: u32,
339 account_id: AccountId,
340 instrument_id: Option<InstrumentId>,
341 ) -> PyResult<Bound<'py, PyAny>> {
342 let client = self.clone();
343 pyo3_async_runtimes::tokio::future_into_py(py, async move {
344 let reports = client
345 .request_fill_reports(&address, subaccount_number, account_id, instrument_id)
346 .await
347 .map_err(to_pyvalue_err)?;
348
349 Python::attach(|py| {
350 let py_reports = reports
351 .into_iter()
352 .map(|report| report.into_py_any(py))
353 .collect::<PyResult<Vec<_>>>()?;
354 let pylist = PyList::new(py, py_reports)?;
355 Ok(pylist.into_py_any_unwrap(py))
356 })
357 })
358 }
359
360 #[pyo3(name = "request_position_status_reports")]
369 #[pyo3(signature = (address, subaccount_number, account_id, instrument_id=None))]
370 fn py_request_position_status_reports<'py>(
371 &self,
372 py: Python<'py>,
373 address: String,
374 subaccount_number: u32,
375 account_id: AccountId,
376 instrument_id: Option<InstrumentId>,
377 ) -> PyResult<Bound<'py, PyAny>> {
378 let client = self.clone();
379 pyo3_async_runtimes::tokio::future_into_py(py, async move {
380 let reports = client
381 .request_position_status_reports(
382 &address,
383 subaccount_number,
384 account_id,
385 instrument_id,
386 )
387 .await
388 .map_err(to_pyvalue_err)?;
389
390 Python::attach(|py| {
391 let py_reports = reports
392 .into_iter()
393 .map(|report| report.into_py_any(py))
394 .collect::<PyResult<Vec<_>>>()?;
395 let pylist = PyList::new(py, py_reports)?;
396 Ok(pylist.into_py_any_unwrap(py))
397 })
398 })
399 }
400
401 #[pyo3(name = "request_account_state")]
410 fn py_request_account_state<'py>(
411 &self,
412 py: Python<'py>,
413 address: String,
414 subaccount_number: u32,
415 account_id: AccountId,
416 ) -> PyResult<Bound<'py, PyAny>> {
417 let client = self.clone();
418 pyo3_async_runtimes::tokio::future_into_py(py, async move {
419 let account_state = client
420 .request_account_state(&address, subaccount_number, account_id)
421 .await
422 .map_err(to_pyvalue_err)?;
423
424 Python::attach(|py| account_state.into_py_any(py))
425 })
426 }
427
428 #[pyo3(name = "request_bars")]
446 #[pyo3(signature = (bar_type, start=None, end=None, limit=None, timestamp_on_close=true))]
447 fn py_request_bars<'py>(
448 &self,
449 py: Python<'py>,
450 bar_type: BarType,
451 start: Option<Timestamp>,
452 end: Option<Timestamp>,
453 limit: Option<u32>,
454 timestamp_on_close: bool,
455 ) -> PyResult<Bound<'py, PyAny>> {
456 let client = self.clone();
457
458 pyo3_async_runtimes::tokio::future_into_py(py, async move {
459 let bars = client
460 .request_bars(bar_type, start, end, limit, timestamp_on_close)
461 .await
462 .map_err(to_pyvalue_err)?;
463
464 Python::attach(|py| {
465 let py_bars = bars
466 .into_iter()
467 .map(|bar| bar.into_py_any(py))
468 .collect::<PyResult<Vec<_>>>()?;
469 let pylist = PyList::new(py, py_bars)?;
470 Ok(pylist.into_py_any_unwrap(py))
471 })
472 })
473 }
474
475 #[pyo3(name = "request_trade_ticks")]
488 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
489 fn py_request_trade_ticks<'py>(
490 &self,
491 py: Python<'py>,
492 instrument_id: InstrumentId,
493 start: Option<Timestamp>,
494 end: Option<Timestamp>,
495 limit: Option<u32>,
496 ) -> PyResult<Bound<'py, PyAny>> {
497 let client = self.clone();
498
499 pyo3_async_runtimes::tokio::future_into_py(py, async move {
500 let trades = client
501 .request_trade_ticks(instrument_id, start, end, limit)
502 .await
503 .map_err(to_pyvalue_err)?;
504
505 Python::attach(|py| {
506 let py_trades = trades
507 .into_iter()
508 .map(|trade| trade.into_py_any(py))
509 .collect::<PyResult<Vec<_>>>()?;
510 let pylist = PyList::new(py, py_trades)?;
511 Ok(pylist.into_py_any_unwrap(py))
512 })
513 })
514 }
515
516 #[pyo3(name = "request_funding_rates")]
528 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
529 fn py_request_funding_rates<'py>(
530 &self,
531 py: Python<'py>,
532 instrument_id: InstrumentId,
533 start: Option<Timestamp>,
534 end: Option<Timestamp>,
535 limit: Option<u32>,
536 ) -> PyResult<Bound<'py, PyAny>> {
537 let client = self.clone();
538
539 pyo3_async_runtimes::tokio::future_into_py(py, async move {
540 let funding_rates = client
541 .request_funding_rates(instrument_id, start, end, limit)
542 .await
543 .map_err(to_pyvalue_err)?;
544
545 Python::attach(|py| {
546 let py_rates = funding_rates
547 .into_iter()
548 .map(|rate| rate.into_py_any(py))
549 .collect::<PyResult<Vec<_>>>()?;
550 let pylist = PyList::new(py, py_rates)?;
551 Ok(pylist.into_py_any_unwrap(py))
552 })
553 })
554 }
555
556 #[pyo3(name = "request_orderbook_snapshot")]
567 fn py_request_orderbook_snapshot<'py>(
568 &self,
569 py: Python<'py>,
570 instrument_id: InstrumentId,
571 ) -> PyResult<Bound<'py, PyAny>> {
572 let client = self.clone();
573
574 pyo3_async_runtimes::tokio::future_into_py(py, async move {
575 let deltas = client
576 .request_orderbook_snapshot(instrument_id)
577 .await
578 .map_err(to_pyvalue_err)?;
579
580 Python::attach(|py| deltas.into_py_any(py))
581 })
582 }
583
584 #[pyo3(name = "get_time")]
585 fn py_get_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
586 let client = self.clone();
587 pyo3_async_runtimes::tokio::future_into_py(py, async move {
588 let response = client.inner.get_time().await.map_err(to_pyvalue_err)?;
589 Python::attach(|py| {
590 let dict = PyDict::new(py);
591 dict.set_item("iso", response.iso.to_string())?;
592 dict.set_item("epoch", response.epoch_ms)?;
593 Ok(dict.into_py_any_unwrap(py))
594 })
595 })
596 }
597
598 #[pyo3(name = "get_height")]
599 fn py_get_height<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
600 let client = self.clone();
601 pyo3_async_runtimes::tokio::future_into_py(py, async move {
602 let response = client.inner.get_height().await.map_err(to_pyvalue_err)?;
603 Python::attach(|py| {
604 let dict = PyDict::new(py);
605 dict.set_item("height", response.height)?;
606 dict.set_item("time", response.time)?;
607 Ok(dict.into_py_any_unwrap(py))
608 })
609 })
610 }
611
612 #[pyo3(name = "get_transfers")]
613 #[pyo3(signature = (address, subaccount_number, limit=None))]
614 fn py_get_transfers<'py>(
615 &self,
616 py: Python<'py>,
617 address: String,
618 subaccount_number: u32,
619 limit: Option<u32>,
620 ) -> PyResult<Bound<'py, PyAny>> {
621 let client = self.clone();
622 pyo3_async_runtimes::tokio::future_into_py(py, async move {
623 let response = client
624 .inner
625 .get_transfers(&address, subaccount_number, limit)
626 .await
627 .map_err(to_pyvalue_err)?;
628 serde_json::to_string(&response).map_err(to_pyvalue_err)
629 })
630 }
631
632 fn __repr__(&self) -> String {
633 format!(
634 "DydxHttpClient(base_url='{}', is_testnet={}, cached_instruments={})",
635 self.base_url(),
636 self.is_testnet(),
637 self.cached_instruments_count()
638 )
639 }
640}