1use ahash::AHashMap;
19use jiff::Timestamp;
20use nautilus_core::{
21 datetime::datetime_to_unix_nanos, python::to_pyvalue_err, string::secret::SecretString,
22};
23use nautilus_model::{
24 data::BarType,
25 enums::{OrderSide, OrderType, TimeInForce},
26 identifiers::{AccountId, ClientOrderId, InstrumentId, VenueOrderId},
27 python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
28 types::{Price, Quantity},
29};
30use pyo3::{IntoPyObjectExt, prelude::*, types::PyList};
31
32use crate::{
33 common::{
34 enums::{AxCandleWidth, AxOrderSide},
35 parse::{client_order_id_to_cid, quantity_to_contracts},
36 },
37 http::{client::AxHttpClient, error::AxHttpError, models::PreviewAggressiveLimitOrderRequest},
38};
39
40#[pymethods]
41#[pyo3_stub_gen::derive::gen_stub_pymethods]
42impl AxHttpClient {
43 #[new]
48 #[pyo3(signature = (
49 base_url=None,
50 orders_base_url=None,
51 timeout_secs=60,
52 max_retries=3,
53 retry_delay_ms=1000,
54 retry_delay_max_ms=10_000,
55 proxy_url=None,
56 ))]
57 fn py_new(
58 base_url: Option<String>,
59 orders_base_url: Option<String>,
60 timeout_secs: u64,
61 max_retries: u32,
62 retry_delay_ms: u64,
63 retry_delay_max_ms: u64,
64 proxy_url: Option<String>,
65 ) -> PyResult<Self> {
66 Self::new(
67 base_url,
68 orders_base_url,
69 timeout_secs,
70 max_retries,
71 retry_delay_ms,
72 retry_delay_max_ms,
73 proxy_url,
74 )
75 .map_err(to_pyvalue_err)
76 }
77
78 #[staticmethod]
84 #[pyo3(name = "with_credentials")]
85 #[pyo3(signature = (
86 api_key,
87 api_secret,
88 base_url=None,
89 orders_base_url=None,
90 timeout_secs=60,
91 max_retries=3,
92 retry_delay_ms=1000,
93 retry_delay_max_ms=10_000,
94 proxy_url=None,
95 ))]
96 #[expect(clippy::too_many_arguments)]
97 fn py_with_credentials(
98 api_key: String,
99 api_secret: String,
100 base_url: Option<String>,
101 orders_base_url: Option<String>,
102 timeout_secs: u64,
103 max_retries: u32,
104 retry_delay_ms: u64,
105 retry_delay_max_ms: u64,
106 proxy_url: Option<String>,
107 ) -> PyResult<Self> {
108 Self::with_credentials(
109 api_key,
110 api_secret,
111 base_url,
112 orders_base_url,
113 timeout_secs,
114 max_retries,
115 retry_delay_ms,
116 retry_delay_max_ms,
117 proxy_url,
118 )
119 .map_err(to_pyvalue_err)
120 }
121
122 #[getter]
124 #[pyo3(name = "base_url")]
125 #[must_use]
126 pub fn py_base_url(&self) -> &str {
127 self.base_url()
128 }
129
130 #[getter]
132 #[pyo3(name = "api_key_masked")]
133 #[must_use]
134 pub fn py_api_key_masked(&self) -> String {
135 self.api_key_masked()
136 }
137
138 #[pyo3(name = "cancel_all_requests")]
140 pub fn py_cancel_all_requests(&self) {
141 self.cancel_all_requests();
142 }
143
144 #[pyo3(name = "cancel_all_orders")]
150 pub fn py_cancel_all_orders<'py>(
151 &self,
152 py: Python<'py>,
153 instrument_id: InstrumentId,
154 ) -> PyResult<Bound<'py, PyAny>> {
155 let client = self.clone();
156 pyo3_async_runtimes::tokio::future_into_py(py, async move {
157 client
158 .cancel_all_orders(instrument_id)
159 .await
160 .map_err(to_pyvalue_err)
161 })
162 }
163
164 #[pyo3(name = "cache_instrument")]
168 pub fn py_cache_instrument(&self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
169 self.cache_instrument(pyobject_to_instrument_any(py, instrument)?);
170 Ok(())
171 }
172
173 #[pyo3(name = "authenticate")]
181 #[pyo3(signature = (api_key, api_secret, expiration_seconds=86400))]
182 fn py_authenticate<'py>(
183 &self,
184 py: Python<'py>,
185 api_key: String,
186 api_secret: String,
187 expiration_seconds: i32,
188 ) -> PyResult<Bound<'py, PyAny>> {
189 let client = self.clone();
190
191 pyo3_async_runtimes::tokio::future_into_py(py, async move {
192 client
193 .authenticate(&api_key, &api_secret, expiration_seconds)
194 .await
195 .map(SecretString::into_inner)
196 .map_err(to_pyvalue_err)
197 })
198 }
199
200 #[pyo3(name = "authenticate_auto")]
217 #[pyo3(signature = (expiration_seconds=86400))]
218 fn py_authenticate_auto<'py>(
219 &self,
220 py: Python<'py>,
221 expiration_seconds: i32,
222 ) -> PyResult<Bound<'py, PyAny>> {
223 let client = self.clone();
224
225 pyo3_async_runtimes::tokio::future_into_py(py, async move {
226 client
227 .authenticate_auto(expiration_seconds)
228 .await
229 .map(SecretString::into_inner)
230 .map_err(to_pyvalue_err)
231 })
232 }
233
234 #[pyo3(name = "request_instruments")]
240 fn py_request_instruments<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
241 let client = self.clone();
242
243 pyo3_async_runtimes::tokio::future_into_py(py, async move {
244 let instruments = client.request_instruments().await.map_err(to_pyvalue_err)?;
245
246 Python::attach(|py| {
247 let py_instruments: PyResult<Vec<_>> = instruments
248 .into_iter()
249 .map(|inst| instrument_any_to_pyobject(py, inst))
250 .collect();
251 let pylist = PyList::new(py, py_instruments?)?.into_any().unbind();
252 Ok(pylist)
253 })
254 })
255 }
256
257 #[pyo3(name = "request_trade_ticks")]
271 #[pyo3(signature = (instrument_id, limit=None, start=None, end=None))]
272 fn py_request_trade_ticks<'py>(
273 &self,
274 py: Python<'py>,
275 instrument_id: InstrumentId,
276 limit: Option<i32>,
277 start: Option<Timestamp>,
278 end: Option<Timestamp>,
279 ) -> PyResult<Bound<'py, PyAny>> {
280 let client = self.clone();
281 let symbol = instrument_id.symbol.inner();
282 let start_nanos = datetime_to_unix_nanos(start);
283 let end_nanos = datetime_to_unix_nanos(end);
284
285 pyo3_async_runtimes::tokio::future_into_py(py, async move {
286 let trades = client
287 .request_trade_ticks(symbol, limit, start_nanos, end_nanos)
288 .await
289 .map_err(to_pyvalue_err)?;
290
291 Python::attach(|py| {
292 let py_trades: PyResult<Vec<_>> = trades
293 .into_iter()
294 .map(|trade| trade.into_py_any(py))
295 .collect();
296 let pylist = PyList::new(py, py_trades?)?.into_any().unbind();
297 Ok(pylist)
298 })
299 })
300 }
301
302 #[pyo3(name = "request_bars")]
313 #[pyo3(signature = (bar_type, start=None, end=None))]
314 fn py_request_bars<'py>(
315 &self,
316 py: Python<'py>,
317 bar_type: BarType,
318 start: Option<Timestamp>,
319 end: Option<Timestamp>,
320 ) -> PyResult<Bound<'py, PyAny>> {
321 let client = self.clone();
322 let symbol = bar_type.instrument_id().symbol.inner();
323 let width = AxCandleWidth::try_from(&bar_type.spec()).map_err(to_pyvalue_err)?;
324
325 pyo3_async_runtimes::tokio::future_into_py(py, async move {
326 let bars = client
327 .request_bars(symbol, start, end, width)
328 .await
329 .map_err(to_pyvalue_err)?;
330
331 Python::attach(|py| {
332 let py_bars: PyResult<Vec<_>> =
333 bars.into_iter().map(|bar| bar.into_py_any(py)).collect();
334 let pylist = PyList::new(py, py_bars?)?.into_any().unbind();
335 Ok(pylist)
336 })
337 })
338 }
339
340 #[pyo3(name = "request_book_snapshot")]
350 #[pyo3(signature = (instrument_id, depth=None))]
351 fn py_request_book_snapshot<'py>(
352 &self,
353 py: Python<'py>,
354 instrument_id: InstrumentId,
355 depth: Option<u32>,
356 ) -> PyResult<Bound<'py, PyAny>> {
357 let client = self.clone();
358 let symbol = instrument_id.symbol.inner();
359
360 pyo3_async_runtimes::tokio::future_into_py(py, async move {
361 let book = client
362 .request_book_snapshot(symbol, depth.map(|value| value as usize))
363 .await
364 .map_err(to_pyvalue_err)?;
365
366 Python::attach(|py| book.into_py_any(py))
367 })
368 }
369
370 #[pyo3(name = "request_funding_rates")]
379 #[pyo3(signature = (instrument_id, start=None, end=None))]
380 fn py_request_funding_rates<'py>(
381 &self,
382 py: Python<'py>,
383 instrument_id: InstrumentId,
384 start: Option<Timestamp>,
385 end: Option<Timestamp>,
386 ) -> PyResult<Bound<'py, PyAny>> {
387 let client = self.clone();
388
389 pyo3_async_runtimes::tokio::future_into_py(py, async move {
390 let funding_rates = client
391 .request_funding_rates(instrument_id, start, end)
392 .await
393 .map_err(to_pyvalue_err)?;
394
395 Python::attach(|py| {
396 let py_rates: PyResult<Vec<_>> = funding_rates
397 .into_iter()
398 .map(|rate| rate.into_py_any(py))
399 .collect();
400 let pylist = PyList::new(py, py_rates?)?.into_any().unbind();
401 Ok(pylist)
402 })
403 })
404 }
405
406 #[pyo3(name = "request_account_state")]
412 fn py_request_account_state<'py>(
413 &self,
414 py: Python<'py>,
415 account_id: AccountId,
416 ) -> PyResult<Bound<'py, PyAny>> {
417 let client = self.clone();
418
419 pyo3_async_runtimes::tokio::future_into_py(py, async move {
420 let account_state = client
421 .request_account_state(account_id)
422 .await
423 .map_err(to_pyvalue_err)?;
424
425 Python::attach(|py| account_state.into_py_any(py))
426 })
427 }
428
429 #[pyo3(name = "request_order_status")]
444 #[pyo3(signature = (
445 account_id,
446 instrument_id,
447 order_side,
448 order_type,
449 time_in_force,
450 client_order_id=None,
451 venue_order_id=None,
452 ))]
453 #[expect(clippy::too_many_arguments)]
454 fn py_request_order_status<'py>(
455 &self,
456 py: Python<'py>,
457 account_id: AccountId,
458 instrument_id: InstrumentId,
459 order_side: OrderSide,
460 order_type: OrderType,
461 time_in_force: TimeInForce,
462 client_order_id: Option<ClientOrderId>,
463 venue_order_id: Option<VenueOrderId>,
464 ) -> PyResult<Bound<'py, PyAny>> {
465 let client = self.clone();
466
467 pyo3_async_runtimes::tokio::future_into_py(py, async move {
468 let report = client
469 .request_order_status(
470 account_id,
471 instrument_id,
472 client_order_id,
473 venue_order_id,
474 Some(order_side),
475 order_type,
476 time_in_force,
477 )
478 .await
479 .map_err(to_pyvalue_err)?;
480
481 Python::attach(|py| report.into_py_any(py))
482 })
483 }
484
485 #[pyo3(name = "request_order_status_reports", signature = (account_id, client_order_ids=None))]
499 fn py_request_order_status_reports<'py>(
500 &self,
501 py: Python<'py>,
502 account_id: AccountId,
503 client_order_ids: Option<Vec<ClientOrderId>>,
504 ) -> PyResult<Bound<'py, PyAny>> {
505 let client = self.clone();
506 let cid_map = client_order_ids
507 .unwrap_or_default()
508 .into_iter()
509 .map(|client_order_id| (client_order_id_to_cid(&client_order_id), client_order_id))
510 .collect::<AHashMap<_, _>>();
511
512 pyo3_async_runtimes::tokio::future_into_py(py, async move {
513 let cid_resolver = move |cid: u64| cid_map.get(&cid).copied();
514 let reports = client
515 .request_order_status_reports(account_id, Some(cid_resolver))
516 .await
517 .map_err(to_pyvalue_err)?;
518
519 Python::attach(|py| {
520 let py_reports: PyResult<Vec<_>> = reports
521 .into_iter()
522 .map(|report| report.into_py_any(py))
523 .collect();
524 let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
525 Ok(pylist)
526 })
527 })
528 }
529
530 #[pyo3(name = "request_fill_reports")]
543 fn py_request_fill_reports<'py>(
544 &self,
545 py: Python<'py>,
546 account_id: AccountId,
547 ) -> PyResult<Bound<'py, PyAny>> {
548 let client = self.clone();
549
550 pyo3_async_runtimes::tokio::future_into_py(py, async move {
551 let reports = client
552 .request_fill_reports(account_id, None, None)
553 .await
554 .map_err(to_pyvalue_err)?;
555
556 Python::attach(|py| {
557 let py_reports: PyResult<Vec<_>> = reports
558 .into_iter()
559 .map(|report| report.into_py_any(py))
560 .collect();
561 let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
562 Ok(pylist)
563 })
564 })
565 }
566
567 #[pyo3(name = "request_position_reports")]
581 fn py_request_position_reports<'py>(
582 &self,
583 py: Python<'py>,
584 account_id: AccountId,
585 ) -> PyResult<Bound<'py, PyAny>> {
586 let client = self.clone();
587
588 pyo3_async_runtimes::tokio::future_into_py(py, async move {
589 let reports = client
590 .request_position_reports(account_id)
591 .await
592 .map_err(to_pyvalue_err)?;
593
594 Python::attach(|py| {
595 let py_reports: PyResult<Vec<_>> = reports
596 .into_iter()
597 .map(|report| report.into_py_any(py))
598 .collect();
599 let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
600 Ok(pylist)
601 })
602 })
603 }
604
605 #[pyo3(name = "preview_aggressive_limit_order")]
606 fn py_preview_aggressive_limit_order<'py>(
607 &self,
608 py: Python<'py>,
609 instrument_id: InstrumentId,
610 quantity: Quantity,
611 side: OrderSide,
612 ) -> PyResult<Bound<'py, PyAny>> {
613 let symbol = instrument_id.symbol.inner();
614 let ax_side = AxOrderSide::from(side);
615 let qty_contracts = quantity_to_contracts(quantity).map_err(to_pyvalue_err)?;
616
617 let client = self.clone();
618
619 pyo3_async_runtimes::tokio::future_into_py(py, async move {
620 let request = PreviewAggressiveLimitOrderRequest::new(symbol, qty_contracts, ax_side);
621 let response = client
622 .inner
623 .preview_aggressive_limit_order(&request)
624 .await
625 .map_err(to_pyvalue_err)?;
626
627 let price = response
628 .limit_price
629 .map(|p| Price::from(p.to_string().as_str()));
630
631 Python::attach(|py| price.into_py_any(py))
632 })
633 }
634}
635
636impl From<AxHttpError> for PyErr {
637 fn from(error: AxHttpError) -> Self {
638 to_pyvalue_err(error)
639 }
640}