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::{common::enums::DydxNetwork, http::client::DydxHttpClient};
35
36#[pymethods]
37#[pyo3_stub_gen::derive::gen_stub_pymethods]
38impl DydxHttpClient {
39 #[new]
55 #[pyo3(signature = (base_url=None, network=DydxNetwork::Mainnet, proxy_url=None))]
56 fn py_new(
57 base_url: Option<String>,
58 network: DydxNetwork,
59 proxy_url: Option<String>,
60 ) -> PyResult<Self> {
61 Self::new(
62 base_url, 60, proxy_url, network, None, )
65 .map_err(to_pyvalue_err)
66 }
67
68 #[pyo3(name = "is_testnet")]
70 fn py_is_testnet(&self) -> bool {
71 self.is_testnet()
72 }
73
74 #[pyo3(name = "base_url")]
76 fn py_base_url(&self) -> String {
77 self.base_url().to_string()
78 }
79
80 #[pyo3(name = "request_instruments")]
85 fn py_request_instruments<'py>(
86 &self,
87 py: Python<'py>,
88 maker_fee: Option<&str>,
89 taker_fee: Option<&str>,
90 ) -> PyResult<Bound<'py, PyAny>> {
91 let maker = maker_fee
92 .map(Decimal::from_str)
93 .transpose()
94 .map_err(to_pyvalue_err)?;
95
96 let taker = taker_fee
97 .map(Decimal::from_str)
98 .transpose()
99 .map_err(to_pyvalue_err)?;
100
101 let client = self.clone();
102
103 pyo3_async_runtimes::tokio::future_into_py(py, async move {
104 let instruments = client
105 .request_instruments(None, maker, taker)
106 .await
107 .map_err(to_pyvalue_err)?;
108
109 Python::attach(|py| {
110 let py_instruments: PyResult<Vec<Py<PyAny>>> = instruments
111 .into_iter()
112 .map(|inst| instrument_any_to_pyobject(py, inst))
113 .collect();
114 py_instruments
115 })
116 })
117 }
118
119 #[pyo3(name = "fetch_and_cache_instruments")]
131 fn py_fetch_and_cache_instruments<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
132 let client = self.clone();
133 pyo3_async_runtimes::tokio::future_into_py(py, async move {
134 client
135 .fetch_and_cache_instruments()
136 .await
137 .map_err(to_pyvalue_err)?;
138 Ok(())
139 })
140 }
141
142 #[pyo3(name = "fetch_instrument")]
149 fn py_fetch_instrument<'py>(
150 &self,
151 py: Python<'py>,
152 ticker: String,
153 ) -> PyResult<Bound<'py, PyAny>> {
154 let client = self.clone();
155 pyo3_async_runtimes::tokio::future_into_py(py, async move {
156 match client.fetch_and_cache_single_instrument(&ticker).await {
157 Ok(Some(instrument)) => {
158 Python::attach(|py| instrument_any_to_pyobject(py, instrument))
159 }
160 Ok(None) => Ok(Python::attach(|py| py.None())),
161 Err(e) => Err(to_pyvalue_err(e)),
162 }
163 })
164 }
165
166 #[pyo3(name = "get_instrument")]
168 fn py_get_instrument(&self, py: Python<'_>, symbol: &str) -> PyResult<Option<Py<PyAny>>> {
169 use nautilus_model::identifiers::{Symbol, Venue};
170 let instrument_id = InstrumentId::new(Symbol::new(symbol), Venue::new("DYDX"));
171 let instrument = self.get_instrument(&instrument_id);
172 match instrument {
173 Some(inst) => Ok(Some(instrument_any_to_pyobject(py, inst)?)),
174 None => Ok(None),
175 }
176 }
177
178 #[pyo3(name = "instrument_count")]
179 fn py_instrument_count(&self) -> usize {
180 self.cached_instruments_count()
181 }
182
183 #[pyo3(name = "instrument_symbols")]
184 fn py_instrument_symbols(&self) -> Vec<String> {
185 self.all_instrument_ids()
186 .into_iter()
187 .map(|id| id.symbol.to_string())
188 .collect()
189 }
190
191 #[pyo3(name = "cache_instruments")]
196 fn py_cache_instruments(
197 &self,
198 py: Python<'_>,
199 py_instruments: Vec<Bound<'_, PyAny>>,
200 ) -> PyResult<()> {
201 let instruments: Vec<InstrumentAny> = py_instruments
202 .into_iter()
203 .map(|py_inst| {
204 pyobject_to_instrument_any(py, py_inst.unbind())
206 })
207 .collect::<Result<Vec<_>, _>>()
208 .map_err(to_pyvalue_err)?;
209
210 self.cache_instruments(instruments);
211 Ok(())
212 }
213
214 #[pyo3(name = "get_orders")]
215 #[pyo3(signature = (address, subaccount_number, market=None, limit=None))]
216 fn py_get_orders<'py>(
217 &self,
218 py: Python<'py>,
219 address: String,
220 subaccount_number: u32,
221 market: Option<String>,
222 limit: Option<u32>,
223 ) -> PyResult<Bound<'py, PyAny>> {
224 let client = self.clone();
225 pyo3_async_runtimes::tokio::future_into_py(py, async move {
226 let response = client
227 .inner
228 .get_orders(&address, subaccount_number, market.as_deref(), limit)
229 .await
230 .map_err(to_pyvalue_err)?;
231 serde_json::to_string(&response).map_err(to_pyvalue_err)
232 })
233 }
234
235 #[pyo3(name = "get_fills")]
236 #[pyo3(signature = (address, subaccount_number, market=None, limit=None))]
237 fn py_get_fills<'py>(
238 &self,
239 py: Python<'py>,
240 address: String,
241 subaccount_number: u32,
242 market: Option<String>,
243 limit: Option<u32>,
244 ) -> PyResult<Bound<'py, PyAny>> {
245 let client = self.clone();
246 pyo3_async_runtimes::tokio::future_into_py(py, async move {
247 let response = client
248 .inner
249 .get_fills(&address, subaccount_number, market.as_deref(), limit)
250 .await
251 .map_err(to_pyvalue_err)?;
252 serde_json::to_string(&response).map_err(to_pyvalue_err)
253 })
254 }
255
256 #[pyo3(name = "get_subaccount")]
257 fn py_get_subaccount<'py>(
258 &self,
259 py: Python<'py>,
260 address: String,
261 subaccount_number: u32,
262 ) -> PyResult<Bound<'py, PyAny>> {
263 let client = self.clone();
264 pyo3_async_runtimes::tokio::future_into_py(py, async move {
265 let response = client
266 .inner
267 .get_subaccount(&address, subaccount_number)
268 .await
269 .map_err(to_pyvalue_err)?;
270 serde_json::to_string(&response).map_err(to_pyvalue_err)
271 })
272 }
273
274 #[pyo3(name = "request_order_status_reports")]
279 #[pyo3(signature = (address, subaccount_number, account_id, instrument_id=None))]
280 fn py_request_order_status_reports<'py>(
281 &self,
282 py: Python<'py>,
283 address: String,
284 subaccount_number: u32,
285 account_id: AccountId,
286 instrument_id: Option<InstrumentId>,
287 ) -> PyResult<Bound<'py, PyAny>> {
288 let client = self.clone();
289 pyo3_async_runtimes::tokio::future_into_py(py, async move {
290 let reports = client
291 .request_order_status_reports(
292 &address,
293 subaccount_number,
294 account_id,
295 instrument_id,
296 )
297 .await
298 .map_err(to_pyvalue_err)?;
299
300 Python::attach(|py| {
301 let pylist =
302 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
303 Ok(pylist.into_py_any_unwrap(py))
304 })
305 })
306 }
307
308 #[pyo3(name = "request_fill_reports")]
313 #[pyo3(signature = (address, subaccount_number, account_id, instrument_id=None))]
314 fn py_request_fill_reports<'py>(
315 &self,
316 py: Python<'py>,
317 address: String,
318 subaccount_number: u32,
319 account_id: AccountId,
320 instrument_id: Option<InstrumentId>,
321 ) -> PyResult<Bound<'py, PyAny>> {
322 let client = self.clone();
323 pyo3_async_runtimes::tokio::future_into_py(py, async move {
324 let reports = client
325 .request_fill_reports(&address, subaccount_number, account_id, instrument_id)
326 .await
327 .map_err(to_pyvalue_err)?;
328
329 Python::attach(|py| {
330 let pylist =
331 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
332 Ok(pylist.into_py_any_unwrap(py))
333 })
334 })
335 }
336
337 #[pyo3(name = "request_position_status_reports")]
342 #[pyo3(signature = (address, subaccount_number, account_id, instrument_id=None))]
343 fn py_request_position_status_reports<'py>(
344 &self,
345 py: Python<'py>,
346 address: String,
347 subaccount_number: u32,
348 account_id: AccountId,
349 instrument_id: Option<InstrumentId>,
350 ) -> PyResult<Bound<'py, PyAny>> {
351 let client = self.clone();
352 pyo3_async_runtimes::tokio::future_into_py(py, async move {
353 let reports = client
354 .request_position_status_reports(
355 &address,
356 subaccount_number,
357 account_id,
358 instrument_id,
359 )
360 .await
361 .map_err(to_pyvalue_err)?;
362
363 Python::attach(|py| {
364 let pylist =
365 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
366 Ok(pylist.into_py_any_unwrap(py))
367 })
368 })
369 }
370
371 #[pyo3(name = "request_account_state")]
376 fn py_request_account_state<'py>(
377 &self,
378 py: Python<'py>,
379 address: String,
380 subaccount_number: u32,
381 account_id: AccountId,
382 ) -> PyResult<Bound<'py, PyAny>> {
383 let client = self.clone();
384 pyo3_async_runtimes::tokio::future_into_py(py, async move {
385 let account_state = client
386 .request_account_state(&address, subaccount_number, account_id)
387 .await
388 .map_err(to_pyvalue_err)?;
389
390 Python::attach(|py| Ok(account_state.into_py_any_unwrap(py)))
391 })
392 }
393
394 #[pyo3(name = "request_bars")]
405 #[pyo3(signature = (bar_type, start=None, end=None, limit=None, timestamp_on_close=true))]
406 fn py_request_bars<'py>(
407 &self,
408 py: Python<'py>,
409 bar_type: BarType,
410 start: Option<DateTime<Utc>>,
411 end: Option<DateTime<Utc>>,
412 limit: Option<u32>,
413 timestamp_on_close: bool,
414 ) -> PyResult<Bound<'py, PyAny>> {
415 let client = self.clone();
416
417 pyo3_async_runtimes::tokio::future_into_py(py, async move {
418 let bars = client
419 .request_bars(bar_type, start, end, limit, timestamp_on_close)
420 .await
421 .map_err(to_pyvalue_err)?;
422
423 Python::attach(|py| {
424 let pylist = PyList::new(py, bars.into_iter().map(|b| b.into_py_any_unwrap(py)))?;
425 Ok(pylist.into_py_any_unwrap(py))
426 })
427 })
428 }
429
430 #[pyo3(name = "request_trade_ticks")]
438 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
439 fn py_request_trade_ticks<'py>(
440 &self,
441 py: Python<'py>,
442 instrument_id: InstrumentId,
443 start: Option<DateTime<Utc>>,
444 end: Option<DateTime<Utc>>,
445 limit: Option<u32>,
446 ) -> PyResult<Bound<'py, PyAny>> {
447 let client = self.clone();
448
449 pyo3_async_runtimes::tokio::future_into_py(py, async move {
450 let trades = client
451 .request_trade_ticks(instrument_id, start, end, limit)
452 .await
453 .map_err(to_pyvalue_err)?;
454
455 Python::attach(|py| {
456 let pylist = PyList::new(py, trades.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
457 Ok(pylist.into_py_any_unwrap(py))
458 })
459 })
460 }
461
462 #[pyo3(name = "request_orderbook_snapshot")]
468 fn py_request_orderbook_snapshot<'py>(
469 &self,
470 py: Python<'py>,
471 instrument_id: InstrumentId,
472 ) -> PyResult<Bound<'py, PyAny>> {
473 let client = self.clone();
474
475 pyo3_async_runtimes::tokio::future_into_py(py, async move {
476 let deltas = client
477 .request_orderbook_snapshot(instrument_id)
478 .await
479 .map_err(to_pyvalue_err)?;
480
481 Python::attach(|py| Ok(deltas.into_py_any_unwrap(py)))
482 })
483 }
484
485 #[pyo3(name = "get_time")]
486 fn py_get_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
487 let client = self.clone();
488 pyo3_async_runtimes::tokio::future_into_py(py, async move {
489 let response = client.inner.get_time().await.map_err(to_pyvalue_err)?;
490 Python::attach(|py| {
491 let dict = PyDict::new(py);
492 dict.set_item("iso", response.iso.to_string())?;
493 dict.set_item("epoch", response.epoch_ms)?;
494 Ok(dict.into_py_any_unwrap(py))
495 })
496 })
497 }
498
499 #[pyo3(name = "get_height")]
500 fn py_get_height<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
501 let client = self.clone();
502 pyo3_async_runtimes::tokio::future_into_py(py, async move {
503 let response = client.inner.get_height().await.map_err(to_pyvalue_err)?;
504 Python::attach(|py| {
505 let dict = PyDict::new(py);
506 dict.set_item("height", response.height)?;
507 dict.set_item("time", response.time)?;
508 Ok(dict.into_py_any_unwrap(py))
509 })
510 })
511 }
512
513 #[pyo3(name = "get_transfers")]
514 #[pyo3(signature = (address, subaccount_number, limit=None))]
515 fn py_get_transfers<'py>(
516 &self,
517 py: Python<'py>,
518 address: String,
519 subaccount_number: u32,
520 limit: Option<u32>,
521 ) -> PyResult<Bound<'py, PyAny>> {
522 let client = self.clone();
523 pyo3_async_runtimes::tokio::future_into_py(py, async move {
524 let response = client
525 .inner
526 .get_transfers(&address, subaccount_number, limit)
527 .await
528 .map_err(to_pyvalue_err)?;
529 serde_json::to_string(&response).map_err(to_pyvalue_err)
530 })
531 }
532
533 fn __repr__(&self) -> String {
534 format!(
535 "DydxHttpClient(base_url='{}', is_testnet={}, cached_instruments={})",
536 self.base_url(),
537 self.is_testnet(),
538 self.cached_instruments_count()
539 )
540 }
541}