1use std::{fmt::Debug, path::PathBuf};
19
20use nautilus_core::{
21 python::{IntoPyObjectNautilusExt, to_pyexception, to_pyvalue_err},
22 time::get_atomic_clock_realtime,
23};
24use nautilus_model::{
25 enums::BarAggregation,
26 identifiers::{InstrumentId, Symbol},
27 python::instruments::instrument_any_to_pyobject,
28};
29use pyo3::{
30 IntoPyObjectExt,
31 prelude::*,
32 types::{PyDict, PyList},
33};
34
35use crate::{
36 common::Credential,
37 historical::{DatabentoHistoricalClient as CoreDatabentoHistoricalClient, RangeQueryParams},
38};
39
40#[cfg_attr(
42 feature = "python",
43 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.databento")
44)]
45#[cfg_attr(
46 feature = "python",
47 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.databento")
48)]
49pub struct DatabentoHistoricalClient {
50 inner: CoreDatabentoHistoricalClient,
51}
52
53impl Debug for DatabentoHistoricalClient {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 f.debug_struct(stringify!(DatabentoHistoricalClient))
56 .field("inner", &self.inner)
57 .finish()
58 }
59}
60
61#[pymethods]
62#[pyo3_stub_gen::derive::gen_stub_pymethods]
63impl DatabentoHistoricalClient {
64 #[new]
69 fn py_new(
70 key: String,
71 publishers_filepath: PathBuf,
72 use_exchange_as_venue: bool,
73 ) -> PyResult<Self> {
74 let clock = get_atomic_clock_realtime();
75 let inner = CoreDatabentoHistoricalClient::new(
76 Credential::new(key),
77 publishers_filepath,
78 clock,
79 use_exchange_as_venue,
80 )
81 .map_err(to_pyvalue_err)?;
82
83 Ok(Self { inner })
84 }
85
86 #[getter]
88 #[pyo3(name = "api_key")]
89 fn py_api_key(&self) -> &str {
90 self.inner.api_key()
91 }
92
93 #[pyo3(name = "set_price_precision")]
99 fn py_set_price_precision(&self, symbol: &str, price_precision: u8) {
100 self.inner
101 .set_price_precision(Symbol::from(symbol), price_precision);
102 }
103
104 #[pyo3(name = "get_dataset_range")]
106 fn py_get_dataset_range<'py>(
107 &self,
108 py: Python<'py>,
109 dataset: String,
110 ) -> PyResult<Bound<'py, PyAny>> {
111 let inner = self.inner.clone();
112
113 pyo3_async_runtimes::tokio::future_into_py(py, async move {
114 let response = inner.get_dataset_range(&dataset).await;
115 match response {
116 Ok(res) => Python::attach(|py| {
117 let dict = PyDict::new(py);
118 dict.set_item("start", res.start)?;
119 dict.set_item("end", res.end)?;
120 dict.into_py_any(py)
121 }),
122 Err(e) => Err(to_pyexception(format!("Error handling response: {e}"))),
123 }
124 })
125 }
126
127 #[pyo3(name = "get_range_instruments")]
129 #[pyo3(signature = (dataset, instrument_ids, start, end=None, limit=None))]
130 #[expect(clippy::needless_pass_by_value)]
131 fn py_get_range_instruments<'py>(
132 &self,
133 py: Python<'py>,
134 dataset: String,
135 instrument_ids: Vec<InstrumentId>,
136 start: u64,
137 end: Option<u64>,
138 limit: Option<u64>,
139 ) -> PyResult<Bound<'py, PyAny>> {
140 let inner = self.inner.clone();
141 let symbols = inner.prepare_symbols_from_instrument_ids(&instrument_ids);
142
143 let params = RangeQueryParams {
144 dataset,
145 symbols,
146 start: start.into(),
147 end: end.map(Into::into),
148 limit,
149 price_precision: None,
150 };
151
152 pyo3_async_runtimes::tokio::future_into_py(py, async move {
153 let instruments = inner
154 .get_range_instruments(params)
155 .await
156 .map_err(to_pyvalue_err)?;
157
158 Python::attach(|py| -> PyResult<Py<PyAny>> {
159 let objs: Vec<Py<PyAny>> = instruments
160 .into_iter()
161 .map(|inst| instrument_any_to_pyobject(py, inst))
162 .collect::<PyResult<Vec<Py<PyAny>>>>()?;
163
164 let list = PyList::new(py, &objs).expect("Invalid `ExactSizeIterator`");
165 Ok(list.into_py_any_unwrap(py))
166 })
167 })
168 }
169
170 #[pyo3(name = "get_range_quotes")]
172 #[pyo3(signature = (dataset, instrument_ids, start, end=None, limit=None, price_precision=None, schema=None))]
173 #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
174 fn py_get_range_quotes<'py>(
175 &self,
176 py: Python<'py>,
177 dataset: String,
178 instrument_ids: Vec<InstrumentId>,
179 start: u64,
180 end: Option<u64>,
181 limit: Option<u64>,
182 price_precision: Option<u8>,
183 schema: Option<String>,
184 ) -> PyResult<Bound<'py, PyAny>> {
185 let inner = self.inner.clone();
186 let symbols = inner.prepare_symbols_from_instrument_ids(&instrument_ids);
187
188 let params = RangeQueryParams {
189 dataset,
190 symbols,
191 start: start.into(),
192 end: end.map(Into::into),
193 limit,
194 price_precision,
195 };
196
197 pyo3_async_runtimes::tokio::future_into_py(py, async move {
198 let quotes = inner
199 .get_range_quotes(params, schema)
200 .await
201 .map_err(to_pyvalue_err)?;
202 Python::attach(|py| quotes.into_py_any(py))
203 })
204 }
205
206 #[pyo3(name = "get_range_trades")]
208 #[pyo3(signature = (dataset, instrument_ids, start, end=None, limit=None, price_precision=None, schema=None))]
209 #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
210 fn py_get_range_trades<'py>(
211 &self,
212 py: Python<'py>,
213 dataset: String,
214 instrument_ids: Vec<InstrumentId>,
215 start: u64,
216 end: Option<u64>,
217 limit: Option<u64>,
218 price_precision: Option<u8>,
219 schema: Option<String>,
220 ) -> PyResult<Bound<'py, PyAny>> {
221 let inner = self.inner.clone();
222 let symbols = inner.prepare_symbols_from_instrument_ids(&instrument_ids);
223
224 let params = RangeQueryParams {
225 dataset,
226 symbols,
227 start: start.into(),
228 end: end.map(Into::into),
229 limit,
230 price_precision,
231 };
232
233 pyo3_async_runtimes::tokio::future_into_py(py, async move {
234 let trades = inner
235 .get_range_trades(params, schema)
236 .await
237 .map_err(to_pyvalue_err)?;
238 Python::attach(|py| trades.into_py_any(py))
239 })
240 }
241
242 #[pyo3(name = "get_range_bars")]
244 #[pyo3(signature = (dataset, instrument_ids, aggregation, start, end=None, limit=None, price_precision=None, timestamp_on_close=true))]
245 #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
246 fn py_get_range_bars<'py>(
247 &self,
248 py: Python<'py>,
249 dataset: String,
250 instrument_ids: Vec<InstrumentId>,
251 aggregation: BarAggregation,
252 start: u64,
253 end: Option<u64>,
254 limit: Option<u64>,
255 price_precision: Option<u8>,
256 timestamp_on_close: bool,
257 ) -> PyResult<Bound<'py, PyAny>> {
258 let inner = self.inner.clone();
259 let symbols = inner.prepare_symbols_from_instrument_ids(&instrument_ids);
260
261 let params = RangeQueryParams {
262 dataset,
263 symbols,
264 start: start.into(),
265 end: end.map(Into::into),
266 limit,
267 price_precision,
268 };
269
270 pyo3_async_runtimes::tokio::future_into_py(py, async move {
271 let bars = inner
272 .get_range_bars(params, aggregation, timestamp_on_close)
273 .await
274 .map_err(to_pyvalue_err)?;
275 Python::attach(|py| bars.into_py_any(py))
276 })
277 }
278
279 #[pyo3(name = "get_order_book_depth10")]
280 #[pyo3(signature = (dataset, instrument_ids, start, end=None, depth=None))]
281 #[expect(clippy::needless_pass_by_value)]
282 fn py_get_order_book_depth10<'py>(
283 &self,
284 py: Python<'py>,
285 dataset: String,
286 instrument_ids: Vec<InstrumentId>,
287 start: u64,
288 end: Option<u64>,
289 depth: Option<usize>,
290 ) -> PyResult<Bound<'py, PyAny>> {
291 let inner = self.inner.clone();
292 let symbols = inner.prepare_symbols_from_instrument_ids(&instrument_ids);
293
294 let params = RangeQueryParams {
295 dataset,
296 symbols,
297 start: start.into(),
298 end: end.map(Into::into),
299 limit: None,
300 price_precision: None,
301 };
302
303 pyo3_async_runtimes::tokio::future_into_py(py, async move {
304 let depths = inner
305 .get_range_order_book_depth10(params, depth)
306 .await
307 .map_err(to_pyvalue_err)?;
308 Python::attach(|py| depths.into_py_any(py))
309 })
310 }
311
312 #[pyo3(name = "get_range_order_book_deltas")]
314 #[pyo3(signature = (dataset, instrument_ids, start, end=None, limit=None, price_precision=None))]
315 #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
316 fn py_get_range_order_book_deltas<'py>(
317 &self,
318 py: Python<'py>,
319 dataset: String,
320 instrument_ids: Vec<InstrumentId>,
321 start: u64,
322 end: Option<u64>,
323 limit: Option<u64>,
324 price_precision: Option<u8>,
325 ) -> PyResult<Bound<'py, PyAny>> {
326 let inner = self.inner.clone();
327 let symbols = inner.prepare_symbols_from_instrument_ids(&instrument_ids);
328
329 let params = RangeQueryParams {
330 dataset,
331 symbols,
332 start: start.into(),
333 end: end.map(Into::into),
334 limit,
335 price_precision,
336 };
337
338 pyo3_async_runtimes::tokio::future_into_py(py, async move {
339 let deltas = inner
340 .get_range_order_book_deltas(params)
341 .await
342 .map_err(to_pyvalue_err)?;
343 Python::attach(|py| deltas.into_py_any(py))
344 })
345 }
346
347 #[pyo3(name = "get_range_imbalance")]
349 #[pyo3(signature = (dataset, instrument_ids, start, end=None, limit=None, price_precision=None))]
350 #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
351 fn py_get_range_imbalance<'py>(
352 &self,
353 py: Python<'py>,
354 dataset: String,
355 instrument_ids: Vec<InstrumentId>,
356 start: u64,
357 end: Option<u64>,
358 limit: Option<u64>,
359 price_precision: Option<u8>,
360 ) -> PyResult<Bound<'py, PyAny>> {
361 let inner = self.inner.clone();
362 let symbols = inner.prepare_symbols_from_instrument_ids(&instrument_ids);
363
364 let params = RangeQueryParams {
365 dataset,
366 symbols,
367 start: start.into(),
368 end: end.map(Into::into),
369 limit,
370 price_precision,
371 };
372
373 pyo3_async_runtimes::tokio::future_into_py(py, async move {
374 let imbalances = inner
375 .get_range_imbalance(params)
376 .await
377 .map_err(to_pyvalue_err)?;
378 Python::attach(|py| imbalances.into_py_any(py))
379 })
380 }
381
382 #[pyo3(name = "get_range_statistics")]
384 #[pyo3(signature = (dataset, instrument_ids, start, end=None, limit=None, price_precision=None))]
385 #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
386 fn py_get_range_statistics<'py>(
387 &self,
388 py: Python<'py>,
389 dataset: String,
390 instrument_ids: Vec<InstrumentId>,
391 start: u64,
392 end: Option<u64>,
393 limit: Option<u64>,
394 price_precision: Option<u8>,
395 ) -> PyResult<Bound<'py, PyAny>> {
396 let inner = self.inner.clone();
397 let symbols = inner.prepare_symbols_from_instrument_ids(&instrument_ids);
398
399 let params = RangeQueryParams {
400 dataset,
401 symbols,
402 start: start.into(),
403 end: end.map(Into::into),
404 limit,
405 price_precision,
406 };
407
408 pyo3_async_runtimes::tokio::future_into_py(py, async move {
409 let statistics = inner
410 .get_range_statistics(params)
411 .await
412 .map_err(to_pyvalue_err)?;
413 Python::attach(|py| statistics.into_py_any(py))
414 })
415 }
416
417 #[pyo3(name = "get_range_status")]
419 #[pyo3(signature = (dataset, instrument_ids, start, end=None, limit=None))]
420 #[expect(clippy::needless_pass_by_value)]
421 fn py_get_range_status<'py>(
422 &self,
423 py: Python<'py>,
424 dataset: String,
425 instrument_ids: Vec<InstrumentId>,
426 start: u64,
427 end: Option<u64>,
428 limit: Option<u64>,
429 ) -> PyResult<Bound<'py, PyAny>> {
430 let inner = self.inner.clone();
431 let symbols = inner.prepare_symbols_from_instrument_ids(&instrument_ids);
432
433 let params = RangeQueryParams {
434 dataset,
435 symbols,
436 start: start.into(),
437 end: end.map(Into::into),
438 limit,
439 price_precision: None,
440 };
441
442 pyo3_async_runtimes::tokio::future_into_py(py, async move {
443 let statuses = inner
444 .get_range_status(params)
445 .await
446 .map_err(to_pyvalue_err)?;
447 Python::attach(|py| statuses.into_py_any(py))
448 })
449 }
450}