1use chrono::{DateTime, Utc};
19use nautilus_core::{
20 nanos::UnixNanos,
21 python::{to_pyruntime_err, to_pyvalue_err},
22};
23use nautilus_model::{
24 data::BarType,
25 enums::{OrderSide, OrderType, TimeInForce, TriggerType},
26 identifiers::{AccountId, ClientOrderId, InstrumentId, VenueOrderId},
27 python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
28 types::{Price, Quantity},
29};
30use pyo3::{
31 conversion::IntoPyObjectExt,
32 prelude::*,
33 types::{PyDict, PyList},
34};
35use rust_decimal::Decimal;
36
37use crate::{
38 common::{credential::KrakenCredential, enums::KrakenEnvironment},
39 http::KrakenSpotHttpClient,
40};
41
42#[pymethods]
43#[pyo3_stub_gen::derive::gen_stub_pymethods]
44impl KrakenSpotHttpClient {
45 #[new]
51 #[pyo3(signature = (api_key=None, api_secret=None, base_url=None, demo=false, timeout_secs=60, max_retries=None, retry_delay_ms=None, retry_delay_max_ms=None, proxy_url=None, max_requests_per_second=5))]
52 #[expect(clippy::too_many_arguments)]
53 fn py_new(
54 api_key: Option<String>,
55 api_secret: Option<String>,
56 base_url: Option<String>,
57 demo: bool,
58 timeout_secs: u64,
59 max_retries: Option<u32>,
60 retry_delay_ms: Option<u64>,
61 retry_delay_max_ms: Option<u64>,
62 proxy_url: Option<String>,
63 max_requests_per_second: u32,
64 ) -> PyResult<Self> {
65 let environment = if demo {
66 KrakenEnvironment::Demo
67 } else {
68 KrakenEnvironment::Mainnet
69 };
70
71 if let Some(cred) = KrakenCredential::resolve_spot(api_key, api_secret) {
72 let (k, s) = cred.into_parts();
73 Self::with_credentials(
74 k,
75 s,
76 environment,
77 base_url,
78 timeout_secs,
79 max_retries,
80 retry_delay_ms,
81 retry_delay_max_ms,
82 proxy_url,
83 max_requests_per_second,
84 )
85 .map_err(to_pyvalue_err)
86 } else {
87 Self::new(
88 environment,
89 base_url,
90 timeout_secs,
91 max_retries,
92 retry_delay_ms,
93 retry_delay_max_ms,
94 proxy_url,
95 max_requests_per_second,
96 )
97 .map_err(to_pyvalue_err)
98 }
99 }
100
101 #[getter]
102 #[pyo3(name = "base_url")]
103 #[must_use]
104 pub fn py_base_url(&self) -> String {
105 self.inner.base_url().to_string()
106 }
107
108 #[getter]
109 #[pyo3(name = "api_key")]
110 #[must_use]
111 pub fn py_api_key(&self) -> Option<&str> {
112 self.inner.credential().map(|c| c.api_key())
113 }
114
115 #[getter]
116 #[pyo3(name = "api_key_masked")]
117 #[must_use]
118 pub fn py_api_key_masked(&self) -> Option<String> {
119 self.inner.credential().map(|c| c.api_key_masked())
120 }
121
122 #[pyo3(name = "cache_instrument")]
124 fn py_cache_instrument(&self, py: Python, instrument: Py<PyAny>) -> PyResult<()> {
125 let inst_any = pyobject_to_instrument_any(py, instrument)?;
126 self.cache_instrument(inst_any);
127 Ok(())
128 }
129
130 #[pyo3(name = "cancel_all_requests")]
132 fn py_cancel_all_requests(&self) {
133 self.cancel_all_requests();
134 }
135
136 #[pyo3(name = "set_use_spot_position_reports")]
138 fn py_set_use_spot_position_reports(&self, value: bool) {
139 self.set_use_spot_position_reports(value);
140 }
141
142 #[pyo3(name = "set_spot_positions_quote_currency")]
144 fn py_set_spot_positions_quote_currency(&self, currency: &str) {
145 self.set_spot_positions_quote_currency(currency);
146 }
147
148 #[pyo3(name = "get_server_time")]
149 fn py_get_server_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
150 let client = self.clone();
151
152 pyo3_async_runtimes::tokio::future_into_py(py, async move {
153 let server_time = client
154 .inner
155 .get_server_time()
156 .await
157 .map_err(to_pyruntime_err)?;
158
159 let json_string = serde_json::to_string(&server_time)
160 .map_err(|e| to_pyruntime_err(format!("Failed to serialize response: {e}")))?;
161
162 Ok(json_string)
163 })
164 }
165
166 #[pyo3(name = "request_instruments")]
171 #[pyo3(signature = (pairs=None))]
172 fn py_request_instruments<'py>(
173 &self,
174 py: Python<'py>,
175 pairs: Option<Vec<String>>,
176 ) -> PyResult<Bound<'py, PyAny>> {
177 let client = self.clone();
178
179 pyo3_async_runtimes::tokio::future_into_py(py, async move {
180 let instruments = client
181 .request_instruments(pairs)
182 .await
183 .map_err(to_pyruntime_err)?;
184
185 Python::attach(|py| {
186 let py_instruments: PyResult<Vec<_>> = instruments
187 .into_iter()
188 .map(|inst| instrument_any_to_pyobject(py, inst))
189 .collect();
190 let pylist = PyList::new(py, py_instruments?).unwrap();
191 Ok(pylist.unbind())
192 })
193 })
194 }
195
196 #[pyo3(name = "request_instrument_statuses")]
202 #[pyo3(signature = (pairs=None))]
203 fn py_request_instrument_statuses<'py>(
204 &self,
205 py: Python<'py>,
206 pairs: Option<Vec<String>>,
207 ) -> PyResult<Bound<'py, PyAny>> {
208 let client = self.clone();
209
210 pyo3_async_runtimes::tokio::future_into_py(py, async move {
211 let statuses = client
212 .request_instrument_statuses(pairs)
213 .await
214 .map_err(to_pyruntime_err)?;
215
216 Python::attach(|py| {
217 let dict = PyDict::new(py);
218 for (instrument_id, action) in statuses {
219 dict.set_item(
220 instrument_id.into_bound_py_any(py)?,
221 action.into_bound_py_any(py)?,
222 )?;
223 }
224 Ok(dict.into_any().unbind())
225 })
226 })
227 }
228
229 #[pyo3(name = "request_trades")]
231 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
232 fn py_request_trades<'py>(
233 &self,
234 py: Python<'py>,
235 instrument_id: InstrumentId,
236 start: Option<DateTime<Utc>>,
237 end: Option<DateTime<Utc>>,
238 limit: Option<u64>,
239 ) -> PyResult<Bound<'py, PyAny>> {
240 let client = self.clone();
241
242 pyo3_async_runtimes::tokio::future_into_py(py, async move {
243 let trades = client
244 .request_trades(instrument_id, start, end, limit)
245 .await
246 .map_err(to_pyruntime_err)?;
247
248 Python::attach(|py| {
249 let py_trades: PyResult<Vec<_>> = trades
250 .into_iter()
251 .map(|trade| trade.into_py_any(py))
252 .collect();
253 let pylist = PyList::new(py, py_trades?).unwrap().into_any().unbind();
254 Ok(pylist)
255 })
256 })
257 }
258
259 #[pyo3(name = "request_book_snapshot")]
261 #[pyo3(signature = (instrument_id, depth=None))]
262 fn py_request_book_snapshot<'py>(
263 &self,
264 py: Python<'py>,
265 instrument_id: InstrumentId,
266 depth: Option<u32>,
267 ) -> PyResult<Bound<'py, PyAny>> {
268 let client = self.clone();
269
270 pyo3_async_runtimes::tokio::future_into_py(py, async move {
271 let book = client
272 .request_book_snapshot(instrument_id, depth)
273 .await
274 .map_err(to_pyruntime_err)?;
275
276 Python::attach(|py| book.into_py_any(py))
277 })
278 }
279
280 #[pyo3(name = "request_bars")]
282 #[pyo3(signature = (bar_type, start=None, end=None, limit=None))]
283 fn py_request_bars<'py>(
284 &self,
285 py: Python<'py>,
286 bar_type: BarType,
287 start: Option<DateTime<Utc>>,
288 end: Option<DateTime<Utc>>,
289 limit: Option<u64>,
290 ) -> PyResult<Bound<'py, PyAny>> {
291 let client = self.clone();
292
293 pyo3_async_runtimes::tokio::future_into_py(py, async move {
294 let bars = client
295 .request_bars(bar_type, start, end, limit)
296 .await
297 .map_err(to_pyruntime_err)?;
298
299 Python::attach(|py| {
300 let py_bars: PyResult<Vec<_>> =
301 bars.into_iter().map(|bar| bar.into_py_any(py)).collect();
302 let pylist = PyList::new(py, py_bars?).unwrap().into_any().unbind();
303 Ok(pylist)
304 })
305 })
306 }
307
308 #[pyo3(name = "request_account_state")]
312 fn py_request_account_state<'py>(
313 &self,
314 py: Python<'py>,
315 account_id: AccountId,
316 ) -> PyResult<Bound<'py, PyAny>> {
317 let client = self.clone();
318
319 pyo3_async_runtimes::tokio::future_into_py(py, async move {
320 let account_state = client
321 .request_account_state(account_id)
322 .await
323 .map_err(to_pyruntime_err)?;
324
325 Python::attach(|py| account_state.into_pyobject(py).map(|o| o.unbind()))
326 })
327 }
328
329 #[pyo3(name = "request_order_status_reports")]
331 #[pyo3(signature = (account_id, instrument_id=None, start=None, end=None, open_only=false))]
332 fn py_request_order_status_reports<'py>(
333 &self,
334 py: Python<'py>,
335 account_id: AccountId,
336 instrument_id: Option<InstrumentId>,
337 start: Option<DateTime<Utc>>,
338 end: Option<DateTime<Utc>>,
339 open_only: bool,
340 ) -> PyResult<Bound<'py, PyAny>> {
341 let client = self.clone();
342
343 pyo3_async_runtimes::tokio::future_into_py(py, async move {
344 let reports = client
345 .request_order_status_reports(account_id, instrument_id, start, end, open_only)
346 .await
347 .map_err(to_pyruntime_err)?;
348
349 Python::attach(|py| {
350 let py_reports: PyResult<Vec<_>> = reports
351 .into_iter()
352 .map(|report| report.into_py_any(py))
353 .collect();
354 let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
355 Ok(pylist)
356 })
357 })
358 }
359
360 #[pyo3(name = "request_fill_reports")]
362 #[pyo3(signature = (account_id, instrument_id=None, start=None, end=None))]
363 fn py_request_fill_reports<'py>(
364 &self,
365 py: Python<'py>,
366 account_id: AccountId,
367 instrument_id: Option<InstrumentId>,
368 start: Option<DateTime<Utc>>,
369 end: Option<DateTime<Utc>>,
370 ) -> PyResult<Bound<'py, PyAny>> {
371 let client = self.clone();
372
373 pyo3_async_runtimes::tokio::future_into_py(py, async move {
374 let reports = client
375 .request_fill_reports(account_id, instrument_id, start, end)
376 .await
377 .map_err(to_pyruntime_err)?;
378
379 Python::attach(|py| {
380 let py_reports: PyResult<Vec<_>> = reports
381 .into_iter()
382 .map(|report| report.into_py_any(py))
383 .collect();
384 let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
385 Ok(pylist)
386 })
387 })
388 }
389
390 #[pyo3(name = "request_position_status_reports")]
395 #[pyo3(signature = (account_id, instrument_id=None))]
396 fn py_request_position_status_reports<'py>(
397 &self,
398 py: Python<'py>,
399 account_id: AccountId,
400 instrument_id: Option<InstrumentId>,
401 ) -> PyResult<Bound<'py, PyAny>> {
402 let client = self.clone();
403
404 pyo3_async_runtimes::tokio::future_into_py(py, async move {
405 let reports = client
406 .request_position_status_reports(account_id, instrument_id)
407 .await
408 .map_err(to_pyruntime_err)?;
409
410 Python::attach(|py| {
411 let py_reports: PyResult<Vec<_>> = reports
412 .into_iter()
413 .map(|report| report.into_py_any(py))
414 .collect();
415 let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
416 Ok(pylist)
417 })
418 })
419 }
420
421 #[pyo3(name = "submit_order")]
425 #[pyo3(signature = (account_id, instrument_id, client_order_id, order_side, order_type, quantity, time_in_force, expire_time=None, price=None, trigger_price=None, trigger_type=None, trailing_offset=None, limit_offset=None, reduce_only=false, post_only=false, quote_quantity=false, display_qty=None))]
426 #[expect(clippy::too_many_arguments)]
427 fn py_submit_order<'py>(
428 &self,
429 py: Python<'py>,
430 account_id: AccountId,
431 instrument_id: InstrumentId,
432 client_order_id: ClientOrderId,
433 order_side: OrderSide,
434 order_type: OrderType,
435 quantity: Quantity,
436 time_in_force: TimeInForce,
437 expire_time: Option<u64>,
438 price: Option<Price>,
439 trigger_price: Option<Price>,
440 trigger_type: Option<TriggerType>,
441 trailing_offset: Option<String>,
442 limit_offset: Option<String>,
443 reduce_only: bool,
444 post_only: bool,
445 quote_quantity: bool,
446 display_qty: Option<Quantity>,
447 ) -> PyResult<Bound<'py, PyAny>> {
448 let client = self.clone();
449 let expire_time = expire_time.map(UnixNanos::from);
450 let trailing_offset = trailing_offset
451 .map(|s| {
452 Decimal::from_str_exact(&s)
453 .map_err(|e| to_pyvalue_err(format!("invalid trailing_offset: {e}")))
454 })
455 .transpose()?;
456 let limit_offset = limit_offset
457 .map(|s| {
458 Decimal::from_str_exact(&s)
459 .map_err(|e| to_pyvalue_err(format!("invalid limit_offset: {e}")))
460 })
461 .transpose()?;
462
463 pyo3_async_runtimes::tokio::future_into_py(py, async move {
464 let venue_order_id = client
465 .submit_order(
466 account_id,
467 instrument_id,
468 client_order_id,
469 order_side,
470 order_type,
471 quantity,
472 time_in_force,
473 expire_time,
474 price,
475 trigger_price,
476 trigger_type,
477 trailing_offset,
478 limit_offset,
479 reduce_only,
480 post_only,
481 quote_quantity,
482 display_qty,
483 )
484 .await
485 .map_err(to_pyruntime_err)?;
486
487 Python::attach(|py| venue_order_id.into_pyobject(py).map(|o| o.unbind()))
488 })
489 }
490
491 #[pyo3(name = "cancel_order")]
493 #[pyo3(signature = (account_id, instrument_id, client_order_id=None, venue_order_id=None))]
494 fn py_cancel_order<'py>(
495 &self,
496 py: Python<'py>,
497 account_id: AccountId,
498 instrument_id: InstrumentId,
499 client_order_id: Option<ClientOrderId>,
500 venue_order_id: Option<VenueOrderId>,
501 ) -> PyResult<Bound<'py, PyAny>> {
502 let client = self.clone();
503
504 pyo3_async_runtimes::tokio::future_into_py(py, async move {
505 client
506 .cancel_order(account_id, instrument_id, client_order_id, venue_order_id)
507 .await
508 .map_err(to_pyruntime_err)
509 })
510 }
511
512 #[pyo3(name = "cancel_all_orders")]
513 fn py_cancel_all_orders<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
514 let client = self.clone();
515
516 pyo3_async_runtimes::tokio::future_into_py(py, async move {
517 let response = client
518 .inner
519 .cancel_all_orders()
520 .await
521 .map_err(to_pyruntime_err)?;
522
523 Ok(response.count)
524 })
525 }
526
527 #[pyo3(name = "cancel_orders_batch")]
529 fn py_cancel_orders_batch<'py>(
530 &self,
531 py: Python<'py>,
532 venue_order_ids: Vec<VenueOrderId>,
533 ) -> PyResult<Bound<'py, PyAny>> {
534 let client = self.clone();
535
536 pyo3_async_runtimes::tokio::future_into_py(py, async move {
537 client
538 .cancel_orders_batch(venue_order_ids)
539 .await
540 .map_err(to_pyruntime_err)
541 })
542 }
543
544 #[pyo3(name = "modify_order")]
549 #[pyo3(signature = (instrument_id, client_order_id=None, venue_order_id=None, quantity=None, price=None, trigger_price=None))]
550 #[expect(clippy::too_many_arguments)]
551 fn py_modify_order<'py>(
552 &self,
553 py: Python<'py>,
554 instrument_id: InstrumentId,
555 client_order_id: Option<ClientOrderId>,
556 venue_order_id: Option<VenueOrderId>,
557 quantity: Option<Quantity>,
558 price: Option<Price>,
559 trigger_price: Option<Price>,
560 ) -> PyResult<Bound<'py, PyAny>> {
561 let client = self.clone();
562
563 pyo3_async_runtimes::tokio::future_into_py(py, async move {
564 let new_venue_order_id = client
565 .modify_order(
566 instrument_id,
567 client_order_id,
568 venue_order_id,
569 quantity,
570 price,
571 trigger_price,
572 )
573 .await
574 .map_err(to_pyruntime_err)?;
575
576 Python::attach(|py| new_venue_order_id.into_pyobject(py).map(|o| o.unbind()))
577 })
578 }
579}
580
581#[pymethods]
584impl KrakenSpotHttpClient {
585 #[pyo3(name = "submit_orders_batch")]
590 #[expect(clippy::type_complexity)]
591 fn py_submit_orders_batch<'py>(
592 &self,
593 py: Python<'py>,
594 orders: Vec<(
595 InstrumentId,
596 ClientOrderId,
597 OrderSide,
598 OrderType,
599 Quantity,
600 TimeInForce,
601 Option<Price>,
602 Option<Price>,
603 Option<TriggerType>,
604 bool,
605 bool,
606 Option<Quantity>,
607 )>,
608 ) -> PyResult<Bound<'py, PyAny>> {
609 let client = self.clone();
610 let expanded_orders = orders
611 .into_iter()
612 .map(
613 |(
614 instrument_id,
615 client_order_id,
616 order_side,
617 order_type,
618 quantity,
619 time_in_force,
620 price,
621 trigger_price,
622 trigger_type,
623 post_only,
624 quote_quantity,
625 display_qty,
626 )| {
627 (
628 instrument_id,
629 client_order_id,
630 order_side,
631 order_type,
632 quantity,
633 time_in_force,
634 None,
635 price,
636 trigger_price,
637 trigger_type,
638 None,
639 None,
640 false,
641 post_only,
642 quote_quantity,
643 display_qty,
644 )
645 },
646 )
647 .collect();
648
649 pyo3_async_runtimes::tokio::future_into_py(py, async move {
650 client
651 .submit_orders_batch(expanded_orders)
652 .await
653 .map_err(to_pyruntime_err)
654 })
655 }
656}