nautilus_kraken/python/
http_spot.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Python bindings for the Kraken Spot HTTP client.
17
18use 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    /// Cancel multiple orders in a single batch request.
420    #[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    /// Modify an existing order on the Kraken Spot exchange.
437    #[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}