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_bars")]
324 #[pyo3(signature = (bar_type, start=None, end=None, limit=None, timestamp_on_close=true))]
325 fn py_request_bars<'py>(
326 &self,
327 py: Python<'py>,
328 bar_type: BarType,
329 start: Option<DateTime<Utc>>,
330 end: Option<DateTime<Utc>>,
331 limit: Option<u32>,
332 timestamp_on_close: bool,
333 ) -> PyResult<Bound<'py, PyAny>> {
334 let client = self.clone();
335
336 pyo3_async_runtimes::tokio::future_into_py(py, async move {
337 let bars = client
338 .request_bars(bar_type, start, end, limit, timestamp_on_close)
339 .await
340 .map_err(to_pyvalue_err)?;
341
342 Python::attach(|py| {
343 let pylist = PyList::new(py, bars.into_iter().map(|b| b.into_py_any_unwrap(py)))?;
344 Ok(pylist.into_py_any_unwrap(py))
345 })
346 })
347 }
348
349 #[pyo3(name = "request_trade_ticks")]
350 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
351 fn py_request_trade_ticks<'py>(
352 &self,
353 py: Python<'py>,
354 instrument_id: InstrumentId,
355 start: Option<DateTime<Utc>>,
356 end: Option<DateTime<Utc>>,
357 limit: Option<u32>,
358 ) -> PyResult<Bound<'py, PyAny>> {
359 let client = self.clone();
360
361 pyo3_async_runtimes::tokio::future_into_py(py, async move {
362 let trades = client
363 .request_trade_ticks(instrument_id, start, end, limit)
364 .await
365 .map_err(to_pyvalue_err)?;
366
367 Python::attach(|py| {
368 let pylist = PyList::new(py, trades.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
369 Ok(pylist.into_py_any_unwrap(py))
370 })
371 })
372 }
373
374 #[pyo3(name = "request_orderbook_snapshot")]
375 fn py_request_orderbook_snapshot<'py>(
376 &self,
377 py: Python<'py>,
378 instrument_id: InstrumentId,
379 ) -> PyResult<Bound<'py, PyAny>> {
380 let client = self.clone();
381
382 pyo3_async_runtimes::tokio::future_into_py(py, async move {
383 let deltas = client
384 .request_orderbook_snapshot(instrument_id)
385 .await
386 .map_err(to_pyvalue_err)?;
387
388 Python::attach(|py| Ok(deltas.into_py_any_unwrap(py)))
389 })
390 }
391
392 #[pyo3(name = "get_time")]
393 fn py_get_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
394 let client = self.clone();
395 pyo3_async_runtimes::tokio::future_into_py(py, async move {
396 let response = client.inner.get_time().await.map_err(to_pyvalue_err)?;
397 Python::attach(|py| {
398 let dict = PyDict::new(py);
399 dict.set_item("iso", response.iso.to_string())?;
400 dict.set_item("epoch", response.epoch_ms)?;
401 Ok(dict.into_py_any_unwrap(py))
402 })
403 })
404 }
405
406 #[pyo3(name = "get_height")]
407 fn py_get_height<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
408 let client = self.clone();
409 pyo3_async_runtimes::tokio::future_into_py(py, async move {
410 let response = client.inner.get_height().await.map_err(to_pyvalue_err)?;
411 Python::attach(|py| {
412 let dict = PyDict::new(py);
413 dict.set_item("height", response.height)?;
414 dict.set_item("time", response.time)?;
415 Ok(dict.into_py_any_unwrap(py))
416 })
417 })
418 }
419
420 #[pyo3(name = "get_transfers")]
421 #[pyo3(signature = (address, subaccount_number, limit=None))]
422 fn py_get_transfers<'py>(
423 &self,
424 py: Python<'py>,
425 address: String,
426 subaccount_number: u32,
427 limit: Option<u32>,
428 ) -> PyResult<Bound<'py, PyAny>> {
429 let client = self.clone();
430 pyo3_async_runtimes::tokio::future_into_py(py, async move {
431 let response = client
432 .inner
433 .get_transfers(&address, subaccount_number, limit)
434 .await
435 .map_err(to_pyvalue_err)?;
436 serde_json::to_string(&response).map_err(to_pyvalue_err)
437 })
438 }
439
440 fn __repr__(&self) -> String {
441 format!(
442 "DydxHttpClient(base_url='{}', is_testnet={}, cached_instruments={})",
443 self.base_url(),
444 self.is_testnet(),
445 self.cached_instruments_count()
446 )
447 }
448}