1use chrono::{DateTime, Utc};
19use nautilus_core::{datetime::datetime_to_unix_nanos, python::to_pyvalue_err};
20use nautilus_model::{
21 data::BarType,
22 enums::{OrderSide, OrderType, TimeInForce},
23 identifiers::{AccountId, ClientOrderId, InstrumentId, VenueOrderId},
24 python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
25 types::{Price, Quantity},
26};
27use pyo3::{IntoPyObjectExt, prelude::*, types::PyList};
28use rust_decimal::Decimal;
29
30use crate::{
31 common::{
32 enums::{AxCandleWidth, AxOrderSide},
33 parse::quantity_to_contracts,
34 },
35 http::{client::AxHttpClient, error::AxHttpError, models::PreviewAggressiveLimitOrderRequest},
36};
37
38#[pymethods]
39impl AxHttpClient {
40 #[new]
41 #[pyo3(signature = (
42 base_url=None,
43 orders_base_url=None,
44 timeout_secs=None,
45 max_retries=None,
46 retry_delay_ms=None,
47 retry_delay_max_ms=None,
48 proxy_url=None,
49 ))]
50 #[allow(clippy::too_many_arguments)]
51 fn py_new(
52 base_url: Option<String>,
53 orders_base_url: Option<String>,
54 timeout_secs: Option<u64>,
55 max_retries: Option<u32>,
56 retry_delay_ms: Option<u64>,
57 retry_delay_max_ms: Option<u64>,
58 proxy_url: Option<String>,
59 ) -> PyResult<Self> {
60 Self::new(
61 base_url,
62 orders_base_url,
63 timeout_secs,
64 max_retries,
65 retry_delay_ms,
66 retry_delay_max_ms,
67 proxy_url,
68 )
69 .map_err(to_pyvalue_err)
70 }
71
72 #[staticmethod]
73 #[pyo3(name = "with_credentials")]
74 #[pyo3(signature = (
75 api_key,
76 api_secret,
77 base_url=None,
78 orders_base_url=None,
79 timeout_secs=None,
80 max_retries=None,
81 retry_delay_ms=None,
82 retry_delay_max_ms=None,
83 proxy_url=None,
84 ))]
85 #[allow(clippy::too_many_arguments)]
86 fn py_with_credentials(
87 api_key: String,
88 api_secret: String,
89 base_url: Option<String>,
90 orders_base_url: Option<String>,
91 timeout_secs: Option<u64>,
92 max_retries: Option<u32>,
93 retry_delay_ms: Option<u64>,
94 retry_delay_max_ms: Option<u64>,
95 proxy_url: Option<String>,
96 ) -> PyResult<Self> {
97 Self::with_credentials(
98 api_key,
99 api_secret,
100 base_url,
101 orders_base_url,
102 timeout_secs,
103 max_retries,
104 retry_delay_ms,
105 retry_delay_max_ms,
106 proxy_url,
107 )
108 .map_err(to_pyvalue_err)
109 }
110
111 #[getter]
112 #[pyo3(name = "base_url")]
113 #[must_use]
114 pub fn py_base_url(&self) -> &str {
115 self.base_url()
116 }
117
118 #[getter]
119 #[pyo3(name = "api_key_masked")]
120 #[must_use]
121 pub fn py_api_key_masked(&self) -> String {
122 self.api_key_masked()
123 }
124
125 #[pyo3(name = "cancel_all_requests")]
126 pub fn py_cancel_all_requests(&self) {
127 self.cancel_all_requests();
128 }
129
130 #[pyo3(name = "cache_instrument")]
134 pub fn py_cache_instrument(&self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
135 self.cache_instrument(pyobject_to_instrument_any(py, instrument)?);
136 Ok(())
137 }
138
139 #[pyo3(name = "authenticate")]
140 #[pyo3(signature = (api_key, api_secret, expiration_seconds=86400))]
141 fn py_authenticate<'py>(
142 &self,
143 py: Python<'py>,
144 api_key: String,
145 api_secret: String,
146 expiration_seconds: i32,
147 ) -> PyResult<Bound<'py, PyAny>> {
148 let client = self.clone();
149
150 pyo3_async_runtimes::tokio::future_into_py(py, async move {
151 client
152 .authenticate(&api_key, &api_secret, expiration_seconds)
153 .await
154 .map_err(to_pyvalue_err)
155 })
156 }
157
158 #[pyo3(name = "authenticate_auto")]
159 #[pyo3(signature = (expiration_seconds=86400))]
160 fn py_authenticate_auto<'py>(
161 &self,
162 py: Python<'py>,
163 expiration_seconds: i32,
164 ) -> PyResult<Bound<'py, PyAny>> {
165 let client = self.clone();
166
167 pyo3_async_runtimes::tokio::future_into_py(py, async move {
168 client
169 .authenticate_auto(expiration_seconds)
170 .await
171 .map_err(to_pyvalue_err)
172 })
173 }
174
175 #[pyo3(name = "request_instruments")]
176 #[pyo3(signature = (maker_fee=None, taker_fee=None))]
177 fn py_request_instruments<'py>(
178 &self,
179 py: Python<'py>,
180 maker_fee: Option<Decimal>,
181 taker_fee: Option<Decimal>,
182 ) -> PyResult<Bound<'py, PyAny>> {
183 let client = self.clone();
184
185 pyo3_async_runtimes::tokio::future_into_py(py, async move {
186 let instruments = client
187 .request_instruments(maker_fee, taker_fee)
188 .await
189 .map_err(to_pyvalue_err)?;
190
191 Python::attach(|py| {
192 let py_instruments: PyResult<Vec<_>> = instruments
193 .into_iter()
194 .map(|inst| instrument_any_to_pyobject(py, inst))
195 .collect();
196 let pylist = PyList::new(py, py_instruments?)?.into_any().unbind();
197 Ok(pylist)
198 })
199 })
200 }
201
202 #[pyo3(name = "request_trade_ticks")]
203 #[pyo3(signature = (instrument_id, limit=None, start=None, end=None))]
204 fn py_request_trade_ticks<'py>(
205 &self,
206 py: Python<'py>,
207 instrument_id: InstrumentId,
208 limit: Option<i32>,
209 start: Option<DateTime<Utc>>,
210 end: Option<DateTime<Utc>>,
211 ) -> PyResult<Bound<'py, PyAny>> {
212 let client = self.clone();
213 let symbol = instrument_id.symbol.inner();
214 let start_nanos = datetime_to_unix_nanos(start);
215 let end_nanos = datetime_to_unix_nanos(end);
216
217 pyo3_async_runtimes::tokio::future_into_py(py, async move {
218 let trades = client
219 .request_trade_ticks(symbol, limit, start_nanos, end_nanos)
220 .await
221 .map_err(to_pyvalue_err)?;
222
223 Python::attach(|py| {
224 let py_trades: PyResult<Vec<_>> = trades
225 .into_iter()
226 .map(|trade| trade.into_py_any(py))
227 .collect();
228 let pylist = PyList::new(py, py_trades?)?.into_any().unbind();
229 Ok(pylist)
230 })
231 })
232 }
233
234 #[pyo3(name = "request_bars")]
235 #[pyo3(signature = (bar_type, start=None, end=None))]
236 fn py_request_bars<'py>(
237 &self,
238 py: Python<'py>,
239 bar_type: BarType,
240 start: Option<DateTime<Utc>>,
241 end: Option<DateTime<Utc>>,
242 ) -> PyResult<Bound<'py, PyAny>> {
243 let client = self.clone();
244 let symbol = bar_type.instrument_id().symbol.inner();
245 let width = AxCandleWidth::try_from(&bar_type.spec()).map_err(to_pyvalue_err)?;
246
247 pyo3_async_runtimes::tokio::future_into_py(py, async move {
248 let bars = client
249 .request_bars(symbol, start, end, width)
250 .await
251 .map_err(to_pyvalue_err)?;
252
253 Python::attach(|py| {
254 let py_bars: PyResult<Vec<_>> =
255 bars.into_iter().map(|bar| bar.into_py_any(py)).collect();
256 let pylist = PyList::new(py, py_bars?)?.into_any().unbind();
257 Ok(pylist)
258 })
259 })
260 }
261
262 #[pyo3(name = "request_funding_rates")]
263 #[pyo3(signature = (instrument_id, start=None, end=None))]
264 fn py_request_funding_rates<'py>(
265 &self,
266 py: Python<'py>,
267 instrument_id: InstrumentId,
268 start: Option<DateTime<Utc>>,
269 end: Option<DateTime<Utc>>,
270 ) -> PyResult<Bound<'py, PyAny>> {
271 let client = self.clone();
272
273 pyo3_async_runtimes::tokio::future_into_py(py, async move {
274 let funding_rates = client
275 .request_funding_rates(instrument_id, start, end)
276 .await
277 .map_err(to_pyvalue_err)?;
278
279 Python::attach(|py| {
280 let py_rates: PyResult<Vec<_>> = funding_rates
281 .into_iter()
282 .map(|rate| rate.into_py_any(py))
283 .collect();
284 let pylist = PyList::new(py, py_rates?)?.into_any().unbind();
285 Ok(pylist)
286 })
287 })
288 }
289
290 #[pyo3(name = "request_account_state")]
291 fn py_request_account_state<'py>(
292 &self,
293 py: Python<'py>,
294 account_id: AccountId,
295 ) -> PyResult<Bound<'py, PyAny>> {
296 let client = self.clone();
297
298 pyo3_async_runtimes::tokio::future_into_py(py, async move {
299 let account_state = client
300 .request_account_state(account_id)
301 .await
302 .map_err(to_pyvalue_err)?;
303
304 Python::attach(|py| account_state.into_py_any(py))
305 })
306 }
307
308 #[pyo3(name = "request_order_status")]
309 #[pyo3(signature = (
310 account_id,
311 instrument_id,
312 order_side,
313 order_type,
314 time_in_force,
315 client_order_id=None,
316 venue_order_id=None,
317 ))]
318 #[allow(clippy::too_many_arguments)]
319 fn py_request_order_status<'py>(
320 &self,
321 py: Python<'py>,
322 account_id: AccountId,
323 instrument_id: InstrumentId,
324 order_side: OrderSide,
325 order_type: OrderType,
326 time_in_force: TimeInForce,
327 client_order_id: Option<ClientOrderId>,
328 venue_order_id: Option<VenueOrderId>,
329 ) -> PyResult<Bound<'py, PyAny>> {
330 let client = self.clone();
331
332 pyo3_async_runtimes::tokio::future_into_py(py, async move {
333 let report = client
334 .request_order_status(
335 account_id,
336 instrument_id,
337 client_order_id,
338 venue_order_id,
339 order_side,
340 order_type,
341 time_in_force,
342 )
343 .await
344 .map_err(to_pyvalue_err)?;
345
346 Python::attach(|py| report.into_py_any(py))
347 })
348 }
349
350 #[pyo3(name = "request_order_status_reports")]
351 fn py_request_order_status_reports<'py>(
352 &self,
353 py: Python<'py>,
354 account_id: AccountId,
355 ) -> PyResult<Bound<'py, PyAny>> {
356 let client = self.clone();
357
358 pyo3_async_runtimes::tokio::future_into_py(py, async move {
359 let reports = client
360 .request_order_status_reports(account_id, None::<fn(u64) -> Option<ClientOrderId>>)
361 .await
362 .map_err(to_pyvalue_err)?;
363
364 Python::attach(|py| {
365 let py_reports: PyResult<Vec<_>> = reports
366 .into_iter()
367 .map(|report| report.into_py_any(py))
368 .collect();
369 let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
370 Ok(pylist)
371 })
372 })
373 }
374
375 #[pyo3(name = "request_fill_reports")]
376 fn py_request_fill_reports<'py>(
377 &self,
378 py: Python<'py>,
379 account_id: AccountId,
380 ) -> PyResult<Bound<'py, PyAny>> {
381 let client = self.clone();
382
383 pyo3_async_runtimes::tokio::future_into_py(py, async move {
384 let reports = client
385 .request_fill_reports(account_id)
386 .await
387 .map_err(to_pyvalue_err)?;
388
389 Python::attach(|py| {
390 let py_reports: PyResult<Vec<_>> = reports
391 .into_iter()
392 .map(|report| report.into_py_any(py))
393 .collect();
394 let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
395 Ok(pylist)
396 })
397 })
398 }
399
400 #[pyo3(name = "request_position_reports")]
401 fn py_request_position_reports<'py>(
402 &self,
403 py: Python<'py>,
404 account_id: AccountId,
405 ) -> PyResult<Bound<'py, PyAny>> {
406 let client = self.clone();
407
408 pyo3_async_runtimes::tokio::future_into_py(py, async move {
409 let reports = client
410 .request_position_reports(account_id)
411 .await
412 .map_err(to_pyvalue_err)?;
413
414 Python::attach(|py| {
415 let py_reports: PyResult<Vec<_>> = reports
416 .into_iter()
417 .map(|report| report.into_py_any(py))
418 .collect();
419 let pylist = PyList::new(py, py_reports?)?.into_any().unbind();
420 Ok(pylist)
421 })
422 })
423 }
424
425 #[pyo3(name = "preview_aggressive_limit_order")]
426 fn py_preview_aggressive_limit_order<'py>(
427 &self,
428 py: Python<'py>,
429 instrument_id: InstrumentId,
430 quantity: Quantity,
431 side: OrderSide,
432 ) -> PyResult<Bound<'py, PyAny>> {
433 let symbol = instrument_id.symbol.inner();
434 let ax_side = AxOrderSide::try_from(side).map_err(to_pyvalue_err)?;
435 let qty_contracts = quantity_to_contracts(quantity).map_err(to_pyvalue_err)?;
436
437 let client = self.clone();
438
439 pyo3_async_runtimes::tokio::future_into_py(py, async move {
440 let request = PreviewAggressiveLimitOrderRequest::new(symbol, qty_contracts, ax_side);
441 let response = client
442 .inner
443 .preview_aggressive_limit_order(&request)
444 .await
445 .map_err(to_pyvalue_err)?;
446
447 let price = response
448 .limit_price
449 .map(|p| Price::from(p.to_string().as_str()));
450
451 Python::attach(|py| price.into_py_any(py))
452 })
453 }
454}
455
456impl From<AxHttpError> for PyErr {
457 fn from(error: AxHttpError) -> Self {
458 to_pyvalue_err(error)
459 }
460}