1use std::str::FromStr;
19
20use chrono::{DateTime, Utc};
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 prelude::*,
30 types::{PyDict, PyList},
31};
32use rust_decimal::Decimal;
33
34use crate::{
35 common::{consts::DYDX_VENUE, enums::DydxNetwork},
36 http::client::DydxHttpClient,
37};
38
39#[pymethods]
40#[pyo3_stub_gen::derive::gen_stub_pymethods]
41impl DydxHttpClient {
42 #[new]
58 #[pyo3(signature = (base_url=None, network=DydxNetwork::Mainnet, proxy_url=None))]
59 fn py_new(
60 base_url: Option<String>,
61 network: DydxNetwork,
62 proxy_url: Option<String>,
63 ) -> PyResult<Self> {
64 Self::new(
65 base_url, 60, proxy_url, network, None, )
68 .map_err(to_pyvalue_err)
69 }
70
71 #[pyo3(name = "is_testnet")]
73 fn py_is_testnet(&self) -> bool {
74 self.is_testnet()
75 }
76
77 #[pyo3(name = "base_url")]
79 fn py_base_url(&self) -> String {
80 self.base_url().to_string()
81 }
82
83 #[pyo3(name = "request_instruments")]
88 fn py_request_instruments<'py>(
89 &self,
90 py: Python<'py>,
91 maker_fee: Option<&str>,
92 taker_fee: Option<&str>,
93 ) -> PyResult<Bound<'py, PyAny>> {
94 let maker = maker_fee
95 .map(Decimal::from_str)
96 .transpose()
97 .map_err(to_pyvalue_err)?;
98
99 let taker = taker_fee
100 .map(Decimal::from_str)
101 .transpose()
102 .map_err(to_pyvalue_err)?;
103
104 let client = self.clone();
105
106 pyo3_async_runtimes::tokio::future_into_py(py, async move {
107 let instruments = client
108 .request_instruments(None, maker, taker)
109 .await
110 .map_err(to_pyvalue_err)?;
111
112 Python::attach(|py| {
113 let py_instruments: PyResult<Vec<Py<PyAny>>> = instruments
114 .into_iter()
115 .map(|inst| instrument_any_to_pyobject(py, inst))
116 .collect();
117 py_instruments
118 })
119 })
120 }
121
122 #[pyo3(name = "fetch_and_cache_instruments")]
134 fn py_fetch_and_cache_instruments<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
135 let client = self.clone();
136 pyo3_async_runtimes::tokio::future_into_py(py, async move {
137 client
138 .fetch_and_cache_instruments()
139 .await
140 .map_err(to_pyvalue_err)?;
141 Ok(())
142 })
143 }
144
145 #[pyo3(name = "fetch_instrument")]
152 fn py_fetch_instrument<'py>(
153 &self,
154 py: Python<'py>,
155 ticker: String,
156 ) -> PyResult<Bound<'py, PyAny>> {
157 let client = self.clone();
158 pyo3_async_runtimes::tokio::future_into_py(py, async move {
159 match client.fetch_and_cache_single_instrument(&ticker).await {
160 Ok(Some(instrument)) => {
161 Python::attach(|py| instrument_any_to_pyobject(py, instrument))
162 }
163 Ok(None) => Ok(Python::attach(|py| py.None())),
164 Err(e) => Err(to_pyvalue_err(e)),
165 }
166 })
167 }
168
169 #[pyo3(name = "get_instrument")]
171 fn py_get_instrument(&self, py: Python<'_>, symbol: &str) -> PyResult<Option<Py<PyAny>>> {
172 use nautilus_model::identifiers::Symbol;
173 let instrument_id = InstrumentId::new(Symbol::new(symbol), *DYDX_VENUE);
174 let instrument = self.get_instrument(&instrument_id);
175 match instrument {
176 Some(inst) => Ok(Some(instrument_any_to_pyobject(py, inst)?)),
177 None => Ok(None),
178 }
179 }
180
181 #[pyo3(name = "instrument_count")]
182 fn py_instrument_count(&self) -> usize {
183 self.cached_instruments_count()
184 }
185
186 #[pyo3(name = "instrument_symbols")]
187 fn py_instrument_symbols(&self) -> Vec<String> {
188 self.all_instrument_ids()
189 .into_iter()
190 .map(|id| id.symbol.to_string())
191 .collect()
192 }
193
194 #[pyo3(name = "cache_instruments")]
199 fn py_cache_instruments(
200 &self,
201 py: Python<'_>,
202 py_instruments: Vec<Bound<'_, PyAny>>,
203 ) -> PyResult<()> {
204 let instruments: Vec<InstrumentAny> = py_instruments
205 .into_iter()
206 .map(|py_inst| {
207 pyobject_to_instrument_any(py, py_inst.unbind())
209 })
210 .collect::<Result<Vec<_>, _>>()
211 .map_err(to_pyvalue_err)?;
212
213 self.cache_instruments(instruments);
214 Ok(())
215 }
216
217 #[pyo3(name = "get_orders")]
218 #[pyo3(signature = (address, subaccount_number, market=None, limit=None))]
219 fn py_get_orders<'py>(
220 &self,
221 py: Python<'py>,
222 address: String,
223 subaccount_number: u32,
224 market: Option<String>,
225 limit: Option<u32>,
226 ) -> PyResult<Bound<'py, PyAny>> {
227 let client = self.clone();
228 pyo3_async_runtimes::tokio::future_into_py(py, async move {
229 let response = client
230 .inner
231 .get_orders(&address, subaccount_number, market.as_deref(), limit)
232 .await
233 .map_err(to_pyvalue_err)?;
234 serde_json::to_string(&response).map_err(to_pyvalue_err)
235 })
236 }
237
238 #[pyo3(name = "get_fills")]
239 #[pyo3(signature = (address, subaccount_number, market=None, limit=None))]
240 fn py_get_fills<'py>(
241 &self,
242 py: Python<'py>,
243 address: String,
244 subaccount_number: u32,
245 market: Option<String>,
246 limit: Option<u32>,
247 ) -> PyResult<Bound<'py, PyAny>> {
248 let client = self.clone();
249 pyo3_async_runtimes::tokio::future_into_py(py, async move {
250 let response = client
251 .inner
252 .get_fills(&address, subaccount_number, market.as_deref(), limit)
253 .await
254 .map_err(to_pyvalue_err)?;
255 serde_json::to_string(&response).map_err(to_pyvalue_err)
256 })
257 }
258
259 #[pyo3(name = "get_subaccount")]
260 fn py_get_subaccount<'py>(
261 &self,
262 py: Python<'py>,
263 address: String,
264 subaccount_number: u32,
265 ) -> PyResult<Bound<'py, PyAny>> {
266 let client = self.clone();
267 pyo3_async_runtimes::tokio::future_into_py(py, async move {
268 let response = client
269 .inner
270 .get_subaccount(&address, subaccount_number)
271 .await
272 .map_err(to_pyvalue_err)?;
273 serde_json::to_string(&response).map_err(to_pyvalue_err)
274 })
275 }
276
277 #[pyo3(name = "request_order_status_reports")]
282 #[pyo3(signature = (address, subaccount_number, account_id, instrument_id=None))]
283 fn py_request_order_status_reports<'py>(
284 &self,
285 py: Python<'py>,
286 address: String,
287 subaccount_number: u32,
288 account_id: AccountId,
289 instrument_id: Option<InstrumentId>,
290 ) -> PyResult<Bound<'py, PyAny>> {
291 let client = self.clone();
292 pyo3_async_runtimes::tokio::future_into_py(py, async move {
293 let reports = client
294 .request_order_status_reports(
295 &address,
296 subaccount_number,
297 account_id,
298 instrument_id,
299 )
300 .await
301 .map_err(to_pyvalue_err)?;
302
303 Python::attach(|py| {
304 let pylist =
305 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
306 Ok(pylist.into_py_any_unwrap(py))
307 })
308 })
309 }
310
311 #[pyo3(name = "request_fill_reports")]
316 #[pyo3(signature = (address, subaccount_number, account_id, instrument_id=None))]
317 fn py_request_fill_reports<'py>(
318 &self,
319 py: Python<'py>,
320 address: String,
321 subaccount_number: u32,
322 account_id: AccountId,
323 instrument_id: Option<InstrumentId>,
324 ) -> PyResult<Bound<'py, PyAny>> {
325 let client = self.clone();
326 pyo3_async_runtimes::tokio::future_into_py(py, async move {
327 let reports = client
328 .request_fill_reports(&address, subaccount_number, account_id, instrument_id)
329 .await
330 .map_err(to_pyvalue_err)?;
331
332 Python::attach(|py| {
333 let pylist =
334 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
335 Ok(pylist.into_py_any_unwrap(py))
336 })
337 })
338 }
339
340 #[pyo3(name = "request_position_status_reports")]
345 #[pyo3(signature = (address, subaccount_number, account_id, instrument_id=None))]
346 fn py_request_position_status_reports<'py>(
347 &self,
348 py: Python<'py>,
349 address: String,
350 subaccount_number: u32,
351 account_id: AccountId,
352 instrument_id: Option<InstrumentId>,
353 ) -> PyResult<Bound<'py, PyAny>> {
354 let client = self.clone();
355 pyo3_async_runtimes::tokio::future_into_py(py, async move {
356 let reports = client
357 .request_position_status_reports(
358 &address,
359 subaccount_number,
360 account_id,
361 instrument_id,
362 )
363 .await
364 .map_err(to_pyvalue_err)?;
365
366 Python::attach(|py| {
367 let pylist =
368 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
369 Ok(pylist.into_py_any_unwrap(py))
370 })
371 })
372 }
373
374 #[pyo3(name = "request_account_state")]
379 fn py_request_account_state<'py>(
380 &self,
381 py: Python<'py>,
382 address: String,
383 subaccount_number: u32,
384 account_id: AccountId,
385 ) -> PyResult<Bound<'py, PyAny>> {
386 let client = self.clone();
387 pyo3_async_runtimes::tokio::future_into_py(py, async move {
388 let account_state = client
389 .request_account_state(&address, subaccount_number, account_id)
390 .await
391 .map_err(to_pyvalue_err)?;
392
393 Python::attach(|py| Ok(account_state.into_py_any_unwrap(py)))
394 })
395 }
396
397 #[pyo3(name = "request_bars")]
408 #[pyo3(signature = (bar_type, start=None, end=None, limit=None, timestamp_on_close=true))]
409 fn py_request_bars<'py>(
410 &self,
411 py: Python<'py>,
412 bar_type: BarType,
413 start: Option<DateTime<Utc>>,
414 end: Option<DateTime<Utc>>,
415 limit: Option<u32>,
416 timestamp_on_close: bool,
417 ) -> PyResult<Bound<'py, PyAny>> {
418 let client = self.clone();
419
420 pyo3_async_runtimes::tokio::future_into_py(py, async move {
421 let bars = client
422 .request_bars(bar_type, start, end, limit, timestamp_on_close)
423 .await
424 .map_err(to_pyvalue_err)?;
425
426 Python::attach(|py| {
427 let pylist = PyList::new(py, bars.into_iter().map(|b| b.into_py_any_unwrap(py)))?;
428 Ok(pylist.into_py_any_unwrap(py))
429 })
430 })
431 }
432
433 #[pyo3(name = "request_trade_ticks")]
441 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
442 fn py_request_trade_ticks<'py>(
443 &self,
444 py: Python<'py>,
445 instrument_id: InstrumentId,
446 start: Option<DateTime<Utc>>,
447 end: Option<DateTime<Utc>>,
448 limit: Option<u32>,
449 ) -> PyResult<Bound<'py, PyAny>> {
450 let client = self.clone();
451
452 pyo3_async_runtimes::tokio::future_into_py(py, async move {
453 let trades = client
454 .request_trade_ticks(instrument_id, start, end, limit)
455 .await
456 .map_err(to_pyvalue_err)?;
457
458 Python::attach(|py| {
459 let pylist = PyList::new(py, trades.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
460 Ok(pylist.into_py_any_unwrap(py))
461 })
462 })
463 }
464
465 #[pyo3(name = "request_funding_rates")]
473 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
474 fn py_request_funding_rates<'py>(
475 &self,
476 py: Python<'py>,
477 instrument_id: InstrumentId,
478 start: Option<DateTime<Utc>>,
479 end: Option<DateTime<Utc>>,
480 limit: Option<u32>,
481 ) -> PyResult<Bound<'py, PyAny>> {
482 let client = self.clone();
483
484 pyo3_async_runtimes::tokio::future_into_py(py, async move {
485 let funding_rates = client
486 .request_funding_rates(instrument_id, start, end, limit)
487 .await
488 .map_err(to_pyvalue_err)?;
489
490 Python::attach(|py| {
491 let pylist = PyList::new(
492 py,
493 funding_rates
494 .into_iter()
495 .map(|rate| rate.into_py_any_unwrap(py)),
496 )?;
497 Ok(pylist.into_py_any_unwrap(py))
498 })
499 })
500 }
501
502 #[pyo3(name = "request_orderbook_snapshot")]
508 fn py_request_orderbook_snapshot<'py>(
509 &self,
510 py: Python<'py>,
511 instrument_id: InstrumentId,
512 ) -> PyResult<Bound<'py, PyAny>> {
513 let client = self.clone();
514
515 pyo3_async_runtimes::tokio::future_into_py(py, async move {
516 let deltas = client
517 .request_orderbook_snapshot(instrument_id)
518 .await
519 .map_err(to_pyvalue_err)?;
520
521 Python::attach(|py| Ok(deltas.into_py_any_unwrap(py)))
522 })
523 }
524
525 #[pyo3(name = "get_time")]
526 fn py_get_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
527 let client = self.clone();
528 pyo3_async_runtimes::tokio::future_into_py(py, async move {
529 let response = client.inner.get_time().await.map_err(to_pyvalue_err)?;
530 Python::attach(|py| {
531 let dict = PyDict::new(py);
532 dict.set_item("iso", response.iso.to_string())?;
533 dict.set_item("epoch", response.epoch_ms)?;
534 Ok(dict.into_py_any_unwrap(py))
535 })
536 })
537 }
538
539 #[pyo3(name = "get_height")]
540 fn py_get_height<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
541 let client = self.clone();
542 pyo3_async_runtimes::tokio::future_into_py(py, async move {
543 let response = client.inner.get_height().await.map_err(to_pyvalue_err)?;
544 Python::attach(|py| {
545 let dict = PyDict::new(py);
546 dict.set_item("height", response.height)?;
547 dict.set_item("time", response.time)?;
548 Ok(dict.into_py_any_unwrap(py))
549 })
550 })
551 }
552
553 #[pyo3(name = "get_transfers")]
554 #[pyo3(signature = (address, subaccount_number, limit=None))]
555 fn py_get_transfers<'py>(
556 &self,
557 py: Python<'py>,
558 address: String,
559 subaccount_number: u32,
560 limit: Option<u32>,
561 ) -> PyResult<Bound<'py, PyAny>> {
562 let client = self.clone();
563 pyo3_async_runtimes::tokio::future_into_py(py, async move {
564 let response = client
565 .inner
566 .get_transfers(&address, subaccount_number, limit)
567 .await
568 .map_err(to_pyvalue_err)?;
569 serde_json::to_string(&response).map_err(to_pyvalue_err)
570 })
571 }
572
573 fn __repr__(&self) -> String {
574 format!(
575 "DydxHttpClient(base_url='{}', is_testnet={}, cached_instruments={})",
576 self.base_url(),
577 self.is_testnet(),
578 self.cached_instruments_count()
579 )
580 }
581}