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},
26 identifiers::{AccountId, ClientOrderId, InstrumentId, VenueOrderId},
27 python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
28 types::{Price, Quantity},
29};
30use pyo3::{conversion::IntoPyObjectExt, prelude::*, types::PyList};
31
32use crate::{
33 common::{credential::KrakenCredential, enums::KrakenEnvironment},
34 http::KrakenSpotHttpClient,
35};
36
37#[pymethods]
38impl KrakenSpotHttpClient {
39 #[new]
40 #[pyo3(signature = (api_key=None, api_secret=None, base_url=None, demo=false, timeout_secs=None, max_retries=None, retry_delay_ms=None, retry_delay_max_ms=None, proxy_url=None, max_requests_per_second=None))]
41 #[allow(clippy::too_many_arguments)]
42 fn py_new(
43 api_key: Option<String>,
44 api_secret: Option<String>,
45 base_url: Option<String>,
46 demo: bool,
47 timeout_secs: Option<u64>,
48 max_retries: Option<u32>,
49 retry_delay_ms: Option<u64>,
50 retry_delay_max_ms: Option<u64>,
51 proxy_url: Option<String>,
52 max_requests_per_second: Option<u32>,
53 ) -> PyResult<Self> {
54 let timeout = timeout_secs.or(Some(60));
55
56 let environment = if demo {
57 KrakenEnvironment::Demo
58 } else {
59 KrakenEnvironment::Mainnet
60 };
61
62 if let Some(cred) = KrakenCredential::resolve_spot(api_key, api_secret) {
63 let (k, s) = cred.into_parts();
64 Self::with_credentials(
65 k,
66 s,
67 environment,
68 base_url,
69 timeout,
70 max_retries,
71 retry_delay_ms,
72 retry_delay_max_ms,
73 proxy_url,
74 max_requests_per_second,
75 )
76 .map_err(to_pyvalue_err)
77 } else {
78 Self::new(
79 environment,
80 base_url,
81 timeout,
82 max_retries,
83 retry_delay_ms,
84 retry_delay_max_ms,
85 proxy_url,
86 max_requests_per_second,
87 )
88 .map_err(to_pyvalue_err)
89 }
90 }
91
92 #[getter]
93 #[pyo3(name = "base_url")]
94 #[must_use]
95 pub fn py_base_url(&self) -> String {
96 self.inner.base_url().to_string()
97 }
98
99 #[getter]
100 #[pyo3(name = "api_key")]
101 #[must_use]
102 pub fn py_api_key(&self) -> Option<&str> {
103 self.inner.credential().map(|c| c.api_key())
104 }
105
106 #[getter]
107 #[pyo3(name = "api_key_masked")]
108 #[must_use]
109 pub fn py_api_key_masked(&self) -> Option<String> {
110 self.inner.credential().map(|c| c.api_key_masked())
111 }
112
113 #[pyo3(name = "cache_instrument")]
114 fn py_cache_instrument(&self, py: Python, instrument: Py<PyAny>) -> PyResult<()> {
115 let inst_any = pyobject_to_instrument_any(py, instrument)?;
116 self.cache_instrument(inst_any);
117 Ok(())
118 }
119
120 #[pyo3(name = "cancel_all_requests")]
121 fn py_cancel_all_requests(&self) {
122 self.cancel_all_requests();
123 }
124
125 #[pyo3(name = "set_use_spot_position_reports")]
126 fn py_set_use_spot_position_reports(&self, value: bool) {
127 self.set_use_spot_position_reports(value);
128 }
129
130 #[pyo3(name = "set_spot_positions_quote_currency")]
131 fn py_set_spot_positions_quote_currency(&self, currency: &str) {
132 self.set_spot_positions_quote_currency(currency);
133 }
134
135 #[pyo3(name = "get_server_time")]
136 fn py_get_server_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
137 let client = self.clone();
138
139 pyo3_async_runtimes::tokio::future_into_py(py, async move {
140 let server_time = client
141 .inner
142 .get_server_time()
143 .await
144 .map_err(to_pyruntime_err)?;
145
146 let json_string = serde_json::to_string(&server_time)
147 .map_err(|e| to_pyruntime_err(format!("Failed to serialize response: {e}")))?;
148
149 Ok(json_string)
150 })
151 }
152
153 #[pyo3(name = "request_instruments")]
154 #[pyo3(signature = (pairs=None))]
155 fn py_request_instruments<'py>(
156 &self,
157 py: Python<'py>,
158 pairs: Option<Vec<String>>,
159 ) -> PyResult<Bound<'py, PyAny>> {
160 let client = self.clone();
161
162 pyo3_async_runtimes::tokio::future_into_py(py, async move {
163 let instruments = client
164 .request_instruments(pairs)
165 .await
166 .map_err(to_pyruntime_err)?;
167
168 Python::attach(|py| {
169 let py_instruments: PyResult<Vec<_>> = instruments
170 .into_iter()
171 .map(|inst| instrument_any_to_pyobject(py, inst))
172 .collect();
173 let pylist = PyList::new(py, py_instruments?).unwrap();
174 Ok(pylist.unbind())
175 })
176 })
177 }
178
179 #[pyo3(name = "request_trades")]
180 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
181 fn py_request_trades<'py>(
182 &self,
183 py: Python<'py>,
184 instrument_id: InstrumentId,
185 start: Option<DateTime<Utc>>,
186 end: Option<DateTime<Utc>>,
187 limit: Option<u64>,
188 ) -> PyResult<Bound<'py, PyAny>> {
189 let client = self.clone();
190
191 pyo3_async_runtimes::tokio::future_into_py(py, async move {
192 let trades = client
193 .request_trades(instrument_id, start, end, limit)
194 .await
195 .map_err(to_pyruntime_err)?;
196
197 Python::attach(|py| {
198 let py_trades: PyResult<Vec<_>> = trades
199 .into_iter()
200 .map(|trade| trade.into_py_any(py))
201 .collect();
202 let pylist = PyList::new(py, py_trades?).unwrap().into_any().unbind();
203 Ok(pylist)
204 })
205 })
206 }
207
208 #[pyo3(name = "request_bars")]
209 #[pyo3(signature = (bar_type, start=None, end=None, limit=None))]
210 fn py_request_bars<'py>(
211 &self,
212 py: Python<'py>,
213 bar_type: BarType,
214 start: Option<DateTime<Utc>>,
215 end: Option<DateTime<Utc>>,
216 limit: Option<u64>,
217 ) -> PyResult<Bound<'py, PyAny>> {
218 let client = self.clone();
219
220 pyo3_async_runtimes::tokio::future_into_py(py, async move {
221 let bars = client
222 .request_bars(bar_type, start, end, limit)
223 .await
224 .map_err(to_pyruntime_err)?;
225
226 Python::attach(|py| {
227 let py_bars: PyResult<Vec<_>> =
228 bars.into_iter().map(|bar| bar.into_py_any(py)).collect();
229 let pylist = PyList::new(py, py_bars?).unwrap().into_any().unbind();
230 Ok(pylist)
231 })
232 })
233 }
234
235 #[pyo3(name = "request_account_state")]
236 fn py_request_account_state<'py>(
237 &self,
238 py: Python<'py>,
239 account_id: AccountId,
240 ) -> PyResult<Bound<'py, PyAny>> {
241 let client = self.clone();
242
243 pyo3_async_runtimes::tokio::future_into_py(py, async move {
244 let account_state = client
245 .request_account_state(account_id)
246 .await
247 .map_err(to_pyruntime_err)?;
248
249 Python::attach(|py| account_state.into_pyobject(py).map(|o| o.unbind()))
250 })
251 }
252
253 #[pyo3(name = "request_order_status_reports")]
254 #[pyo3(signature = (account_id, instrument_id=None, start=None, end=None, open_only=false))]
255 fn py_request_order_status_reports<'py>(
256 &self,
257 py: Python<'py>,
258 account_id: AccountId,
259 instrument_id: Option<InstrumentId>,
260 start: Option<DateTime<Utc>>,
261 end: Option<DateTime<Utc>>,
262 open_only: bool,
263 ) -> PyResult<Bound<'py, PyAny>> {
264 let client = self.clone();
265
266 pyo3_async_runtimes::tokio::future_into_py(py, async move {
267 let reports = client
268 .request_order_status_reports(account_id, instrument_id, start, end, open_only)
269 .await
270 .map_err(to_pyruntime_err)?;
271
272 Python::attach(|py| {
273 let py_reports: PyResult<Vec<_>> = reports
274 .into_iter()
275 .map(|report| report.into_py_any(py))
276 .collect();
277 let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
278 Ok(pylist)
279 })
280 })
281 }
282
283 #[pyo3(name = "request_fill_reports")]
284 #[pyo3(signature = (account_id, instrument_id=None, start=None, end=None))]
285 fn py_request_fill_reports<'py>(
286 &self,
287 py: Python<'py>,
288 account_id: AccountId,
289 instrument_id: Option<InstrumentId>,
290 start: Option<DateTime<Utc>>,
291 end: Option<DateTime<Utc>>,
292 ) -> PyResult<Bound<'py, PyAny>> {
293 let client = self.clone();
294
295 pyo3_async_runtimes::tokio::future_into_py(py, async move {
296 let reports = client
297 .request_fill_reports(account_id, instrument_id, start, end)
298 .await
299 .map_err(to_pyruntime_err)?;
300
301 Python::attach(|py| {
302 let py_reports: PyResult<Vec<_>> = reports
303 .into_iter()
304 .map(|report| report.into_py_any(py))
305 .collect();
306 let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
307 Ok(pylist)
308 })
309 })
310 }
311
312 #[pyo3(name = "request_position_status_reports")]
313 #[pyo3(signature = (account_id, instrument_id=None))]
314 fn py_request_position_status_reports<'py>(
315 &self,
316 py: Python<'py>,
317 account_id: AccountId,
318 instrument_id: Option<InstrumentId>,
319 ) -> PyResult<Bound<'py, PyAny>> {
320 let client = self.clone();
321
322 pyo3_async_runtimes::tokio::future_into_py(py, async move {
323 let reports = client
324 .request_position_status_reports(account_id, instrument_id)
325 .await
326 .map_err(to_pyruntime_err)?;
327
328 Python::attach(|py| {
329 let py_reports: PyResult<Vec<_>> = reports
330 .into_iter()
331 .map(|report| report.into_py_any(py))
332 .collect();
333 let pylist = PyList::new(py, py_reports?).unwrap().into_any().unbind();
334 Ok(pylist)
335 })
336 })
337 }
338
339 #[pyo3(name = "submit_order")]
340 #[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, reduce_only=false, post_only=false))]
341 #[allow(clippy::too_many_arguments)]
342 fn py_submit_order<'py>(
343 &self,
344 py: Python<'py>,
345 account_id: AccountId,
346 instrument_id: InstrumentId,
347 client_order_id: ClientOrderId,
348 order_side: OrderSide,
349 order_type: OrderType,
350 quantity: Quantity,
351 time_in_force: TimeInForce,
352 expire_time: Option<u64>,
353 price: Option<Price>,
354 trigger_price: Option<Price>,
355 reduce_only: bool,
356 post_only: bool,
357 ) -> PyResult<Bound<'py, PyAny>> {
358 let client = self.clone();
359 let expire_time = expire_time.map(UnixNanos::from);
360
361 pyo3_async_runtimes::tokio::future_into_py(py, async move {
362 let venue_order_id = client
363 .submit_order(
364 account_id,
365 instrument_id,
366 client_order_id,
367 order_side,
368 order_type,
369 quantity,
370 time_in_force,
371 expire_time,
372 price,
373 trigger_price,
374 reduce_only,
375 post_only,
376 )
377 .await
378 .map_err(to_pyruntime_err)?;
379
380 Python::attach(|py| venue_order_id.into_pyobject(py).map(|o| o.unbind()))
381 })
382 }
383
384 #[pyo3(name = "cancel_order")]
385 #[pyo3(signature = (account_id, instrument_id, client_order_id=None, venue_order_id=None))]
386 fn py_cancel_order<'py>(
387 &self,
388 py: Python<'py>,
389 account_id: AccountId,
390 instrument_id: InstrumentId,
391 client_order_id: Option<ClientOrderId>,
392 venue_order_id: Option<VenueOrderId>,
393 ) -> PyResult<Bound<'py, PyAny>> {
394 let client = self.clone();
395
396 pyo3_async_runtimes::tokio::future_into_py(py, async move {
397 client
398 .cancel_order(account_id, instrument_id, client_order_id, venue_order_id)
399 .await
400 .map_err(to_pyruntime_err)
401 })
402 }
403
404 #[pyo3(name = "cancel_all_orders")]
405 fn py_cancel_all_orders<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
406 let client = self.clone();
407
408 pyo3_async_runtimes::tokio::future_into_py(py, async move {
409 let response = client
410 .inner
411 .cancel_all_orders()
412 .await
413 .map_err(to_pyruntime_err)?;
414
415 Ok(response.count)
416 })
417 }
418
419 #[pyo3(name = "cancel_orders_batch")]
421 fn py_cancel_orders_batch<'py>(
422 &self,
423 py: Python<'py>,
424 venue_order_ids: Vec<VenueOrderId>,
425 ) -> PyResult<Bound<'py, PyAny>> {
426 let client = self.clone();
427
428 pyo3_async_runtimes::tokio::future_into_py(py, async move {
429 client
430 .cancel_orders_batch(venue_order_ids)
431 .await
432 .map_err(to_pyruntime_err)
433 })
434 }
435
436 #[pyo3(name = "modify_order")]
438 #[pyo3(signature = (instrument_id, client_order_id=None, venue_order_id=None, quantity=None, price=None, trigger_price=None))]
439 #[allow(clippy::too_many_arguments)]
440 fn py_modify_order<'py>(
441 &self,
442 py: Python<'py>,
443 instrument_id: InstrumentId,
444 client_order_id: Option<ClientOrderId>,
445 venue_order_id: Option<VenueOrderId>,
446 quantity: Option<Quantity>,
447 price: Option<Price>,
448 trigger_price: Option<Price>,
449 ) -> PyResult<Bound<'py, PyAny>> {
450 let client = self.clone();
451
452 pyo3_async_runtimes::tokio::future_into_py(py, async move {
453 let new_venue_order_id = client
454 .modify_order(
455 instrument_id,
456 client_order_id,
457 venue_order_id,
458 quantity,
459 price,
460 trigger_price,
461 )
462 .await
463 .map_err(to_pyruntime_err)?;
464
465 Python::attach(|py| new_venue_order_id.into_pyobject(py).map(|o| o.unbind()))
466 })
467 }
468}