1use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err};
19use nautilus_model::{
20 data::{BarType, Data},
21 identifiers::InstrumentId,
22 python::{
23 data::data_to_pycapsule,
24 instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
25 },
26};
27use pyo3::{prelude::*, types::PyList};
28
29use crate::http::{client::KrakenHttpClient, error::KrakenHttpError};
30
31#[pymethods]
32impl KrakenHttpClient {
33 #[new]
34 #[pyo3(signature = (api_key=None, api_secret=None, base_url=None, timeout_secs=None, max_retries=None, retry_delay_ms=None, retry_delay_max_ms=None, proxy_url=None))]
35 #[allow(clippy::too_many_arguments)]
36 fn py_new(
37 api_key: Option<String>,
38 api_secret: Option<String>,
39 base_url: Option<String>,
40 timeout_secs: Option<u64>,
41 max_retries: Option<u32>,
42 retry_delay_ms: Option<u64>,
43 retry_delay_max_ms: Option<u64>,
44 proxy_url: Option<String>,
45 ) -> PyResult<Self> {
46 let timeout = timeout_secs.or(Some(60));
47
48 let key = api_key.or_else(|| std::env::var("KRAKEN_API_KEY").ok());
50 let secret = api_secret.or_else(|| std::env::var("KRAKEN_API_SECRET").ok());
51
52 if let (Some(k), Some(s)) = (key, secret) {
53 Self::with_credentials(
54 k,
55 s,
56 base_url,
57 timeout,
58 max_retries,
59 retry_delay_ms,
60 retry_delay_max_ms,
61 proxy_url,
62 )
63 .map_err(to_pyvalue_err)
64 } else {
65 Self::new(
66 base_url,
67 timeout,
68 max_retries,
69 retry_delay_ms,
70 retry_delay_max_ms,
71 proxy_url,
72 )
73 .map_err(to_pyvalue_err)
74 }
75 }
76
77 #[getter]
78 #[pyo3(name = "base_url")]
79 #[must_use]
80 pub fn py_base_url(&self) -> String {
81 self.inner.base_url().to_string()
82 }
83
84 #[getter]
85 #[pyo3(name = "api_key")]
86 #[must_use]
87 pub fn py_api_key(&self) -> Option<&str> {
88 self.inner.credential().map(|c| c.api_key())
89 }
90
91 #[getter]
92 #[pyo3(name = "api_key_masked")]
93 #[must_use]
94 pub fn py_api_key_masked(&self) -> Option<String> {
95 self.inner.credential().map(|c| c.api_key_masked())
96 }
97
98 #[pyo3(name = "cache_instrument")]
99 fn py_cache_instrument(&self, py: Python, instrument: Py<PyAny>) -> PyResult<()> {
100 let inst_any = pyobject_to_instrument_any(py, instrument)?;
101 self.cache_instrument(inst_any);
102 Ok(())
103 }
104
105 #[pyo3(name = "cancel_all_requests")]
106 fn py_cancel_all_requests(&self) {
107 self.cancel_all_requests();
108 }
109
110 #[pyo3(name = "get_server_time")]
111 fn py_get_server_time<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
112 let client = self.clone();
113
114 pyo3_async_runtimes::tokio::future_into_py(py, async move {
115 let server_time = client
116 .inner
117 .get_server_time()
118 .await
119 .map_err(to_pyruntime_err)?;
120
121 let json_string = serde_json::to_string(&server_time)
122 .map_err(|e| to_pyruntime_err(format!("Failed to serialize response: {e}")))?;
123
124 Ok(json_string)
125 })
126 }
127
128 #[pyo3(name = "request_mark_price")]
129 fn py_request_mark_price<'py>(
130 &self,
131 py: Python<'py>,
132 instrument_id: InstrumentId,
133 ) -> PyResult<Bound<'py, PyAny>> {
134 let client = self.clone();
135
136 pyo3_async_runtimes::tokio::future_into_py(py, async move {
137 let mark_price = client
138 .request_mark_price(instrument_id)
139 .await
140 .map_err(to_pyruntime_err)?;
141
142 Ok(mark_price)
143 })
144 }
145
146 #[pyo3(name = "request_index_price")]
147 fn py_request_index_price<'py>(
148 &self,
149 py: Python<'py>,
150 instrument_id: InstrumentId,
151 ) -> PyResult<Bound<'py, PyAny>> {
152 let client = self.clone();
153
154 pyo3_async_runtimes::tokio::future_into_py(py, async move {
155 let index_price = client
156 .request_index_price(instrument_id)
157 .await
158 .map_err(to_pyruntime_err)?;
159
160 Ok(index_price)
161 })
162 }
163
164 #[pyo3(name = "request_instruments")]
165 #[pyo3(signature = (pairs=None))]
166 fn py_request_instruments<'py>(
167 &self,
168 py: Python<'py>,
169 pairs: Option<Vec<String>>,
170 ) -> PyResult<Bound<'py, PyAny>> {
171 let client = self.clone();
172
173 pyo3_async_runtimes::tokio::future_into_py(py, async move {
174 let instruments = client
175 .request_instruments(pairs)
176 .await
177 .map_err(to_pyruntime_err)?;
178
179 Python::attach(|py| {
180 let py_instruments: PyResult<Vec<_>> = instruments
181 .into_iter()
182 .map(|inst| instrument_any_to_pyobject(py, inst))
183 .collect();
184 let pylist = PyList::new(py, py_instruments?).unwrap();
185 Ok(pylist.unbind())
186 })
187 })
188 }
189
190 #[pyo3(name = "request_trades")]
191 #[pyo3(signature = (instrument_id, start=None, end=None, limit=None))]
192 fn py_request_trades<'py>(
193 &self,
194 py: Python<'py>,
195 instrument_id: InstrumentId,
196 start: Option<u64>,
197 end: Option<u64>,
198 limit: Option<u64>,
199 ) -> PyResult<Bound<'py, PyAny>> {
200 let client = self.clone();
201
202 pyo3_async_runtimes::tokio::future_into_py(py, async move {
203 let trades = client
204 .request_trades(instrument_id, start, end, limit)
205 .await
206 .map_err(to_pyruntime_err)?;
207
208 Python::attach(|py| {
209 let py_trades: Vec<_> = trades
210 .into_iter()
211 .map(|trade| data_to_pycapsule(py, Data::Trade(trade)))
212 .collect();
213 let pylist = PyList::new(py, py_trades).unwrap();
214 Ok(pylist.unbind())
215 })
216 })
217 }
218
219 #[pyo3(name = "request_bars")]
220 #[pyo3(signature = (bar_type, start=None, end=None, limit=None))]
221 fn py_request_bars<'py>(
222 &self,
223 py: Python<'py>,
224 bar_type: BarType,
225 start: Option<u64>,
226 end: Option<u64>,
227 limit: Option<u64>,
228 ) -> PyResult<Bound<'py, PyAny>> {
229 let client = self.clone();
230
231 pyo3_async_runtimes::tokio::future_into_py(py, async move {
232 let bars = client
233 .request_bars(bar_type, start, end, limit)
234 .await
235 .map_err(to_pyruntime_err)?;
236
237 Python::attach(|py| {
238 let py_bars: Vec<_> = bars
239 .into_iter()
240 .map(|bar| data_to_pycapsule(py, Data::Bar(bar)))
241 .collect();
242 let pylist = PyList::new(py, py_bars).unwrap();
243 Ok(pylist.unbind())
244 })
245 })
246 }
247
248 #[pyo3(name = "request_bars_with_tick_type")]
249 #[pyo3(signature = (bar_type, start=None, end=None, limit=None, tick_type=None))]
250 fn py_request_bars_with_tick_type<'py>(
251 &self,
252 py: Python<'py>,
253 bar_type: BarType,
254 start: Option<u64>,
255 end: Option<u64>,
256 limit: Option<u64>,
257 tick_type: Option<String>,
258 ) -> PyResult<Bound<'py, PyAny>> {
259 let client = self.clone();
260
261 pyo3_async_runtimes::tokio::future_into_py(py, async move {
262 let tick_type_ref = tick_type.as_deref();
263 let bars = client
264 .request_bars_with_tick_type(bar_type, start, end, limit, tick_type_ref)
265 .await
266 .map_err(to_pyruntime_err)?;
267
268 Python::attach(|py| {
269 let py_bars: Vec<_> = bars
270 .into_iter()
271 .map(|bar| data_to_pycapsule(py, Data::Bar(bar)))
272 .collect();
273 let pylist = PyList::new(py, py_bars).unwrap();
274 Ok(pylist.unbind())
275 })
276 })
277 }
278}
279
280impl From<KrakenHttpError> for PyErr {
281 fn from(error: KrakenHttpError) -> Self {
282 match error {
283 KrakenHttpError::NetworkError(msg) => to_pyruntime_err(format!("Network error: {msg}")),
285 KrakenHttpError::ApiError(errors) => {
286 to_pyruntime_err(format!("API error: {}", errors.join(", ")))
287 }
288 KrakenHttpError::MissingCredentials => {
290 to_pyvalue_err("Missing credentials for authenticated request")
291 }
292 KrakenHttpError::AuthenticationError(msg) => {
293 to_pyvalue_err(format!("Authentication error: {msg}"))
294 }
295 KrakenHttpError::ParseError(msg) => to_pyvalue_err(format!("Parse error: {msg}")),
296 }
297 }
298}