1#![allow(clippy::missing_errors_doc)]
19
20use std::str::FromStr;
21
22use chrono::DateTime;
23use nautilus_core::python::{IntoPyObjectNautilusExt, to_pyvalue_err};
24use nautilus_model::{
25 data::BarType,
26 identifiers::{AccountId, InstrumentId},
27 instruments::InstrumentAny,
28 python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
29};
30use pyo3::{
31 prelude::*,
32 types::{PyDict, PyList},
33};
34use rust_decimal::Decimal;
35use ustr::Ustr;
36
37use crate::{common::enums::DydxCandleResolution, http::client::DydxHttpClient};
38
39#[pymethods]
40impl DydxHttpClient {
41 #[new]
42 #[pyo3(signature = (base_url=None, is_testnet=false))]
43 fn py_new(base_url: Option<String>, is_testnet: bool) -> PyResult<Self> {
44 Self::new(
46 base_url, None, None, is_testnet, None, )
50 .map_err(to_pyvalue_err)
51 }
52
53 #[pyo3(name = "is_testnet")]
54 fn py_is_testnet(&self) -> bool {
55 self.is_testnet()
56 }
57
58 #[pyo3(name = "base_url")]
59 fn py_base_url(&self) -> String {
60 self.base_url().to_string()
61 }
62
63 #[pyo3(name = "request_instruments")]
64 fn py_request_instruments<'py>(
65 &self,
66 py: Python<'py>,
67 maker_fee: Option<String>,
68 taker_fee: Option<String>,
69 ) -> PyResult<Bound<'py, PyAny>> {
70 let maker = maker_fee
71 .as_ref()
72 .map(|s| Decimal::from_str(s))
73 .transpose()
74 .map_err(to_pyvalue_err)?;
75
76 let taker = taker_fee
77 .as_ref()
78 .map(|s| Decimal::from_str(s))
79 .transpose()
80 .map_err(to_pyvalue_err)?;
81
82 let client = self.clone();
83
84 pyo3_async_runtimes::tokio::future_into_py(py, async move {
85 let instruments = client
86 .request_instruments(None, maker, taker)
87 .await
88 .map_err(to_pyvalue_err)?;
89
90 #[allow(deprecated)]
91 Python::with_gil(|py| {
92 let py_instruments: PyResult<Vec<Py<PyAny>>> = instruments
93 .into_iter()
94 .map(|inst| instrument_any_to_pyobject(py, inst))
95 .collect();
96 py_instruments
97 })
98 })
99 }
100
101 #[pyo3(name = "fetch_and_cache_instruments")]
102 fn py_fetch_and_cache_instruments<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
103 let client = self.clone();
104 pyo3_async_runtimes::tokio::future_into_py(py, async move {
105 client
106 .fetch_and_cache_instruments()
107 .await
108 .map_err(to_pyvalue_err)?;
109 Ok(())
110 })
111 }
112
113 #[pyo3(name = "get_instrument")]
114 fn py_get_instrument(&self, py: Python<'_>, symbol: &str) -> PyResult<Option<Py<PyAny>>> {
115 let symbol_ustr = Ustr::from(symbol);
116 let instrument = self.get_instrument(&symbol_ustr);
117 match instrument {
118 Some(inst) => Ok(Some(instrument_any_to_pyobject(py, inst)?)),
119 None => Ok(None),
120 }
121 }
122
123 #[pyo3(name = "instrument_count")]
124 fn py_instrument_count(&self) -> usize {
125 self.instruments().len()
126 }
127
128 #[pyo3(name = "instrument_symbols")]
129 fn py_instrument_symbols(&self) -> Vec<String> {
130 self.instruments()
131 .iter()
132 .map(|entry| entry.key().to_string())
133 .collect()
134 }
135
136 #[pyo3(name = "cache_instruments")]
137 fn py_cache_instruments(
138 &self,
139 py: Python<'_>,
140 py_instruments: Vec<Bound<'_, PyAny>>,
141 ) -> PyResult<()> {
142 let instruments: Vec<InstrumentAny> = py_instruments
143 .into_iter()
144 .map(|py_inst| {
145 pyobject_to_instrument_any(py, py_inst.unbind())
147 })
148 .collect::<Result<Vec<_>, _>>()
149 .map_err(to_pyvalue_err)?;
150
151 self.cache_instruments(instruments);
152 Ok(())
153 }
154
155 #[pyo3(name = "get_orders")]
156 #[pyo3(signature = (address, subaccount_number, market=None, limit=None))]
157 fn py_get_orders<'py>(
158 &self,
159 py: Python<'py>,
160 address: String,
161 subaccount_number: u32,
162 market: Option<String>,
163 limit: Option<u32>,
164 ) -> PyResult<Bound<'py, PyAny>> {
165 let client = self.clone();
166 pyo3_async_runtimes::tokio::future_into_py(py, async move {
167 let response = client
168 .inner
169 .get_orders(&address, subaccount_number, market.as_deref(), limit)
170 .await
171 .map_err(to_pyvalue_err)?;
172 serde_json::to_string(&response).map_err(to_pyvalue_err)
173 })
174 }
175
176 #[pyo3(name = "get_fills")]
177 #[pyo3(signature = (address, subaccount_number, market=None, limit=None))]
178 fn py_get_fills<'py>(
179 &self,
180 py: Python<'py>,
181 address: String,
182 subaccount_number: u32,
183 market: Option<String>,
184 limit: Option<u32>,
185 ) -> PyResult<Bound<'py, PyAny>> {
186 let client = self.clone();
187 pyo3_async_runtimes::tokio::future_into_py(py, async move {
188 let response = client
189 .inner
190 .get_fills(&address, subaccount_number, market.as_deref(), limit)
191 .await
192 .map_err(to_pyvalue_err)?;
193 serde_json::to_string(&response).map_err(to_pyvalue_err)
194 })
195 }
196
197 #[pyo3(name = "get_subaccount")]
198 fn py_get_subaccount<'py>(
199 &self,
200 py: Python<'py>,
201 address: String,
202 subaccount_number: 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_subaccount(&address, subaccount_number)
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 = "request_order_status_reports")]
216 #[pyo3(signature = (address, subaccount_number, account_id, instrument_id=None))]
217 fn py_request_order_status_reports<'py>(
218 &self,
219 py: Python<'py>,
220 address: String,
221 subaccount_number: u32,
222 account_id: AccountId,
223 instrument_id: Option<InstrumentId>,
224 ) -> PyResult<Bound<'py, PyAny>> {
225 let client = self.clone();
226 pyo3_async_runtimes::tokio::future_into_py(py, async move {
227 let reports = client
228 .request_order_status_reports(
229 &address,
230 subaccount_number,
231 account_id,
232 instrument_id,
233 )
234 .await
235 .map_err(to_pyvalue_err)?;
236
237 Python::attach(|py| {
238 let pylist =
239 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
240 Ok(pylist.into_py_any_unwrap(py))
241 })
242 })
243 }
244
245 #[pyo3(name = "request_fill_reports")]
246 #[pyo3(signature = (address, subaccount_number, account_id, instrument_id=None))]
247 fn py_request_fill_reports<'py>(
248 &self,
249 py: Python<'py>,
250 address: String,
251 subaccount_number: u32,
252 account_id: AccountId,
253 instrument_id: Option<InstrumentId>,
254 ) -> PyResult<Bound<'py, PyAny>> {
255 let client = self.clone();
256 pyo3_async_runtimes::tokio::future_into_py(py, async move {
257 let reports = client
258 .request_fill_reports(&address, subaccount_number, account_id, instrument_id)
259 .await
260 .map_err(to_pyvalue_err)?;
261
262 Python::attach(|py| {
263 let pylist =
264 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
265 Ok(pylist.into_py_any_unwrap(py))
266 })
267 })
268 }
269
270 #[pyo3(name = "request_position_status_reports")]
271 #[pyo3(signature = (address, subaccount_number, account_id, instrument_id=None))]
272 fn py_request_position_status_reports<'py>(
273 &self,
274 py: Python<'py>,
275 address: String,
276 subaccount_number: u32,
277 account_id: AccountId,
278 instrument_id: Option<InstrumentId>,
279 ) -> PyResult<Bound<'py, PyAny>> {
280 let client = self.clone();
281 pyo3_async_runtimes::tokio::future_into_py(py, async move {
282 let reports = client
283 .request_position_status_reports(
284 &address,
285 subaccount_number,
286 account_id,
287 instrument_id,
288 )
289 .await
290 .map_err(to_pyvalue_err)?;
291
292 Python::attach(|py| {
293 let pylist =
294 PyList::new(py, reports.into_iter().map(|r| r.into_py_any_unwrap(py)))?;
295 Ok(pylist.into_py_any_unwrap(py))
296 })
297 })
298 }
299
300 #[pyo3(name = "request_bars")]
301 #[pyo3(signature = (bar_type, resolution, limit=None, start=None, end=None))]
302 fn py_request_bars<'py>(
303 &self,
304 py: Python<'py>,
305 bar_type: String,
306 resolution: String,
307 limit: Option<u32>,
308 start: Option<String>,
309 end: Option<String>,
310 ) -> PyResult<Bound<'py, PyAny>> {
311 let bar_type = BarType::from_str(&bar_type).map_err(to_pyvalue_err)?;
312 let resolution = DydxCandleResolution::from_str(&resolution).map_err(to_pyvalue_err)?;
313
314 let from_iso = start
315 .map(|s| DateTime::parse_from_rfc3339(&s).map(|dt| dt.with_timezone(&chrono::Utc)))
316 .transpose()
317 .map_err(to_pyvalue_err)?;
318
319 let to_iso = end
320 .map(|s| DateTime::parse_from_rfc3339(&s).map(|dt| dt.with_timezone(&chrono::Utc)))
321 .transpose()
322 .map_err(to_pyvalue_err)?;
323
324 let client = self.clone();
325
326 pyo3_async_runtimes::tokio::future_into_py(py, async move {
327 let bars = client
328 .request_bars(bar_type, resolution, limit, from_iso, to_iso)
329 .await
330 .map_err(to_pyvalue_err)?;
331
332 Python::attach(|py| {
333 let pylist = PyList::new(py, bars.into_iter().map(|b| b.into_py_any_unwrap(py)))?;
334 Ok(pylist.into_py_any_unwrap(py))
335 })
336 })
337 }
338
339 #[pyo3(name = "request_trade_ticks")]
340 #[pyo3(signature = (instrument_id, limit=None))]
341 fn py_request_trade_ticks<'py>(
342 &self,
343 py: Python<'py>,
344 instrument_id: InstrumentId,
345 limit: Option<u32>,
346 ) -> PyResult<Bound<'py, PyAny>> {
347 let client = self.clone();
348
349 pyo3_async_runtimes::tokio::future_into_py(py, async move {
350 let trades = client
351 .request_trade_ticks(instrument_id, limit)
352 .await
353 .map_err(to_pyvalue_err)?;
354
355 Python::attach(|py| {
356 let pylist = PyList::new(py, trades.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
357 Ok(pylist.into_py_any_unwrap(py))
358 })
359 })
360 }
361
362 #[pyo3(name = "request_orderbook_snapshot")]
363 fn py_request_orderbook_snapshot<'py>(
364 &self,
365 py: Python<'py>,
366 instrument_id: InstrumentId,
367 ) -> PyResult<Bound<'py, PyAny>> {
368 let client = self.clone();
369
370 pyo3_async_runtimes::tokio::future_into_py(py, async move {
371 let deltas = client
372 .request_orderbook_snapshot(instrument_id)
373 .await
374 .map_err(to_pyvalue_err)?;
375
376 Python::attach(|py| Ok(deltas.into_py_any_unwrap(py)))
377 })
378 }
379
380 #[pyo3(name = "get_time")]
381 fn py_get_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
382 let client = self.clone();
383 pyo3_async_runtimes::tokio::future_into_py(py, async move {
384 let response = client.inner.get_time().await.map_err(to_pyvalue_err)?;
385 Python::attach(|py| {
386 let dict = PyDict::new(py);
387 dict.set_item("iso", response.iso.to_string())?;
388 dict.set_item("epoch", response.epoch_ms)?;
389 Ok(dict.into_py_any_unwrap(py))
390 })
391 })
392 }
393
394 #[pyo3(name = "get_height")]
395 fn py_get_height<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
396 let client = self.clone();
397 pyo3_async_runtimes::tokio::future_into_py(py, async move {
398 let response = client.inner.get_height().await.map_err(to_pyvalue_err)?;
399 Python::attach(|py| {
400 let dict = PyDict::new(py);
401 dict.set_item("height", response.height)?;
402 dict.set_item("time", response.time)?;
403 Ok(dict.into_py_any_unwrap(py))
404 })
405 })
406 }
407
408 #[pyo3(name = "get_transfers")]
409 #[pyo3(signature = (address, subaccount_number, limit=None))]
410 fn py_get_transfers<'py>(
411 &self,
412 py: Python<'py>,
413 address: String,
414 subaccount_number: u32,
415 limit: Option<u32>,
416 ) -> PyResult<Bound<'py, PyAny>> {
417 let client = self.clone();
418 pyo3_async_runtimes::tokio::future_into_py(py, async move {
419 let response = client
420 .inner
421 .get_transfers(&address, subaccount_number, limit)
422 .await
423 .map_err(to_pyvalue_err)?;
424 serde_json::to_string(&response).map_err(to_pyvalue_err)
425 })
426 }
427
428 fn __repr__(&self) -> String {
429 format!(
430 "DydxHttpClient(base_url='{}', is_testnet={}, cached_instruments={})",
431 self.base_url(),
432 self.is_testnet(),
433 self.instruments().len()
434 )
435 }
436}