Skip to main content

nautilus_tardis/python/
http.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 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
16use nautilus_core::{
17    UnixNanos,
18    python::{enums::parse_enum, to_pyruntime_err},
19};
20use nautilus_model::python::instruments::instrument_any_to_pyobject;
21use pyo3::{IntoPyObjectExt, prelude::*};
22
23use crate::{
24    common::enums::TardisExchange,
25    http::{TardisHttpClient, query::InstrumentFilterBuilder},
26};
27
28#[pymethods]
29#[pyo3_stub_gen::derive::gen_stub_pymethods]
30impl TardisHttpClient {
31    /// A Tardis HTTP API client.
32    /// See <https://docs.tardis.dev/api/http>.
33    #[new]
34    #[pyo3(signature = (api_key=None, base_url=None, timeout_secs=None, normalize_symbols=true, proxy_url=None))]
35    fn py_new(
36        api_key: Option<&str>,
37        base_url: Option<&str>,
38        timeout_secs: Option<u64>,
39        normalize_symbols: bool,
40        proxy_url: Option<String>,
41    ) -> PyResult<Self> {
42        Self::new(
43            api_key,
44            base_url,
45            timeout_secs,
46            normalize_symbols,
47            proxy_url,
48        )
49        .map_err(to_pyruntime_err)
50    }
51
52    #[getter]
53    #[pyo3(name = "api_key")]
54    fn py_api_key(&self) -> Option<&str> {
55        self.credential().map(|c| c.api_key())
56    }
57
58    #[getter]
59    #[pyo3(name = "api_key_masked")]
60    fn py_api_key_masked(&self) -> Option<String> {
61        self.credential().map(|c| c.api_key_masked())
62    }
63
64    /// Returns all Nautilus instrument definitions for the given `exchange`, and filter params.
65    ///
66    /// # Errors
67    ///
68    /// Returns an error if fetching instrument info or parsing into domain types fails.
69    ///
70    /// See <https://docs.tardis.dev/api/instruments-metadata-api>.
71    #[expect(clippy::too_many_arguments)]
72    #[pyo3(name = "instruments")]
73    #[pyo3(signature = (exchange, symbol=None, base_currency=None, quote_currency=None, instrument_type=None, contract_type=None, active=None, start=None, end=None, available_offset=None, effective=None, ts_init=None))]
74    fn py_instruments<'py>(
75        &self,
76        exchange: &str,
77        symbol: Option<String>,
78        base_currency: Option<Vec<String>>,
79        quote_currency: Option<Vec<String>>,
80        instrument_type: Option<Vec<String>>,
81        contract_type: Option<Vec<String>>,
82        active: Option<bool>,
83        start: Option<u64>,
84        end: Option<u64>,
85        available_offset: Option<u64>,
86        effective: Option<u64>,
87        ts_init: Option<u64>,
88        py: Python<'py>,
89    ) -> PyResult<Bound<'py, PyAny>> {
90        let exchange: TardisExchange = parse_enum(exchange, stringify!(exchange))?;
91
92        let filter = InstrumentFilterBuilder::default()
93            .base_currency(base_currency)
94            .quote_currency(quote_currency)
95            .instrument_type(instrument_type)
96            .contract_type(contract_type)
97            .active(active)
98            // NOTE: The Tardis instruments metadata API does not function correctly when using
99            // the `availableSince` and `availableTo` params.
100            // .available_since(start.and_then(|x| Timestamp::from_nanosecond(x.into()).ok()))
101            // .available_to(end.and_then(|x| Timestamp::from_nanosecond(x.into()).ok()))
102            .build()
103            .unwrap(); // All fields are Option, so build cannot fail
104
105        let self_clone = self.clone();
106
107        pyo3_async_runtimes::tokio::future_into_py(py, async move {
108            let instruments = self_clone
109                .instruments(
110                    exchange,
111                    symbol.as_deref(),
112                    Some(&filter),
113                    start.map(UnixNanos::from),
114                    end.map(UnixNanos::from),
115                    available_offset.map(UnixNanos::from),
116                    effective.map(UnixNanos::from),
117                    ts_init.map(UnixNanos::from),
118                )
119                .await
120                .map_err(to_pyruntime_err)?;
121
122            Python::attach(|py| {
123                let mut py_instruments = Vec::new();
124                for inst in instruments {
125                    py_instruments.push(instrument_any_to_pyobject(py, inst)?);
126                }
127                py_instruments.into_py_any(py)
128            })
129        })
130    }
131}