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