Skip to main content

nautilus_tardis/python/
csv.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 std::{fmt::Debug, path::PathBuf, time::Duration};
17
18use nautilus_core::python::to_pyvalue_err;
19use nautilus_model::{
20    data::{Data, FundingRateUpdate, OrderBookDelta, OrderBookDepth10, QuoteTick, TradeTick},
21    identifiers::InstrumentId,
22};
23use pyo3::prelude::*;
24
25use crate::csv::{
26    convert::{TardisOptionsChainCSVConverterConfig, convert_options_chain_csv},
27    load::{
28        load_deltas, load_depth10_from_snapshot5, load_depth10_from_snapshot25, load_funding_rates,
29        load_options_chain, load_quotes, load_trades,
30    },
31    stream::{
32        stream_batched_deltas, stream_deltas, stream_depth10_from_snapshot5,
33        stream_depth10_from_snapshot25, stream_funding_rates, stream_options_chain, stream_quotes,
34        stream_trades,
35    },
36};
37
38macro_rules! impl_tardis_stream_iterator {
39    ($struct_name:ident, $data_type:ty, $type_name:expr) => {
40        #[pyclass(unsendable)]
41        #[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.tardis")]
42        pub struct $struct_name {
43            stream: Box<dyn Iterator<Item = anyhow::Result<Vec<$data_type>>>>,
44        }
45
46        impl Debug for $struct_name {
47            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48                write!(f, "{} {{ stream: ... }}", $type_name)
49            }
50        }
51
52        #[pymethods]
53        #[pyo3_stub_gen::derive::gen_stub_pymethods]
54        impl $struct_name {
55            const fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
56                slf
57            }
58
59            fn __next__(&mut self) -> PyResult<Option<Vec<$data_type>>> {
60                match self.stream.next() {
61                    Some(Ok(chunk)) => Ok(Some(chunk)),
62                    Some(Err(e)) => Err(to_pyvalue_err(e)),
63                    None => Ok(None),
64                }
65            }
66        }
67    };
68}
69
70fn options_chain_data_to_pyobject(py: Python<'_>, data: Data) -> PyResult<Py<PyAny>> {
71    match data {
72        Data::Quote(quote) => Py::new(py, quote).map(|value| value.into_any()),
73        Data::OptionGreeks(greeks) => Py::new(py, greeks).map(|value| value.into_any()),
74        data => Err(to_pyvalue_err(format!(
75            "Unsupported options_chain data type: {data:?}"
76        ))),
77    }
78}
79
80/// # Errors
81///
82/// Returns a Python error if loading or parsing the CSV file fails.
83#[pyfunction(name = "load_tardis_deltas")]
84#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.tardis")]
85#[pyo3(signature = (filepath, price_precision=None, size_precision=None, instrument_id=None, limit=None))]
86pub fn py_load_tardis_deltas(
87    filepath: PathBuf,
88    price_precision: Option<u8>,
89    size_precision: Option<u8>,
90    instrument_id: Option<InstrumentId>,
91    limit: Option<usize>,
92) -> PyResult<Vec<OrderBookDelta>> {
93    load_deltas(
94        filepath,
95        price_precision,
96        size_precision,
97        instrument_id,
98        limit,
99    )
100    .map_err(to_pyvalue_err)
101}
102
103/// # Errors
104///
105/// Returns a Python error if loading or parsing the CSV file fails.
106#[pyfunction(name = "load_tardis_depth10_from_snapshot5")]
107#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.tardis")]
108#[pyo3(signature = (filepath, price_precision=None, size_precision=None, instrument_id=None, limit=None))]
109pub fn py_load_tardis_depth10_from_snapshot5(
110    filepath: PathBuf,
111    price_precision: Option<u8>,
112    size_precision: Option<u8>,
113    instrument_id: Option<InstrumentId>,
114    limit: Option<usize>,
115) -> PyResult<Vec<OrderBookDepth10>> {
116    load_depth10_from_snapshot5(
117        filepath,
118        price_precision,
119        size_precision,
120        instrument_id,
121        limit,
122    )
123    .map_err(to_pyvalue_err)
124}
125
126/// # Errors
127///
128/// Returns a Python error if loading or parsing the CSV file fails.
129#[pyfunction(name = "load_tardis_depth10_from_snapshot25")]
130#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.tardis")]
131#[pyo3(signature = (filepath, price_precision=None, size_precision=None, instrument_id=None, limit=None))]
132pub fn py_load_tardis_depth10_from_snapshot25(
133    filepath: PathBuf,
134    price_precision: Option<u8>,
135    size_precision: Option<u8>,
136    instrument_id: Option<InstrumentId>,
137    limit: Option<usize>,
138) -> PyResult<Vec<OrderBookDepth10>> {
139    load_depth10_from_snapshot25(
140        filepath,
141        price_precision,
142        size_precision,
143        instrument_id,
144        limit,
145    )
146    .map_err(to_pyvalue_err)
147}
148
149/// # Errors
150///
151/// Returns a Python error if loading or parsing the CSV file fails.
152#[pyfunction(name = "load_tardis_quotes")]
153#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.tardis")]
154#[pyo3(signature = (filepath, price_precision=None, size_precision=None, instrument_id=None, limit=None))]
155pub fn py_load_tardis_quotes(
156    filepath: PathBuf,
157    price_precision: Option<u8>,
158    size_precision: Option<u8>,
159    instrument_id: Option<InstrumentId>,
160    limit: Option<usize>,
161) -> PyResult<Vec<QuoteTick>> {
162    load_quotes(
163        filepath,
164        price_precision,
165        size_precision,
166        instrument_id,
167        limit,
168    )
169    .map_err(to_pyvalue_err)
170}
171
172/// # Errors
173///
174/// Returns a Python error if loading or parsing the CSV file fails.
175#[pyfunction(name = "load_tardis_trades")]
176#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.tardis")]
177#[pyo3(signature = (filepath, price_precision=None, size_precision=None, instrument_id=None, limit=None))]
178pub fn py_load_tardis_trades(
179    filepath: PathBuf,
180    price_precision: Option<u8>,
181    size_precision: Option<u8>,
182    instrument_id: Option<InstrumentId>,
183    limit: Option<usize>,
184) -> PyResult<Vec<TradeTick>> {
185    load_trades(
186        filepath,
187        price_precision,
188        size_precision,
189        instrument_id,
190        limit,
191    )
192    .map_err(to_pyvalue_err)
193}
194
195/// # Errors
196///
197/// Returns a Python error if loading or parsing the CSV file fails.
198#[pyfunction(name = "load_tardis_funding_rates")]
199#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.tardis")]
200#[pyo3(signature = (filepath, instrument_id=None, limit=None))]
201pub fn py_load_tardis_funding_rates(
202    filepath: PathBuf,
203    instrument_id: Option<InstrumentId>,
204    limit: Option<usize>,
205) -> PyResult<Vec<FundingRateUpdate>> {
206    load_funding_rates(filepath, instrument_id, limit).map_err(to_pyvalue_err)
207}
208
209/// # Errors
210///
211/// Returns a Python error if loading or parsing the CSV file fails.
212#[pyfunction(name = "load_tardis_options_chain")]
213#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.tardis")]
214#[pyo3(signature = (filepath, underlyings=None, price_precision=None, size_precision=None, limit=None))]
215pub fn py_load_tardis_options_chain(
216    py: Python<'_>,
217    filepath: PathBuf,
218    underlyings: Option<Vec<String>>,
219    price_precision: Option<u8>,
220    size_precision: Option<u8>,
221    limit: Option<usize>,
222) -> PyResult<Vec<Py<PyAny>>> {
223    load_options_chain(
224        filepath,
225        underlyings,
226        price_precision,
227        size_precision,
228        limit,
229    )
230    .map_err(to_pyvalue_err)?
231    .into_iter()
232    .map(|data| options_chain_data_to_pyobject(py, data))
233    .collect()
234}
235
236/// Converts Tardis `options_chain` CSV files into a Nautilus catalog.
237///
238/// # Errors
239///
240/// Returns a Python error if parsing, instrument derivation, or catalog writing fails.
241#[pyfunction(name = "convert_tardis_options_chain_csv")]
242#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.tardis")]
243#[pyo3(signature = (filepaths, catalog_path, underlyings=None, snapshot_interval_ms=None, extract_bbo_as_quotes=true, write_instruments=true, price_precision=None, size_precision=None))]
244#[allow(
245    clippy::too_many_arguments,
246    reason = "PyO3 exposes these keyword arguments as function parameters"
247)]
248pub fn py_convert_tardis_options_chain_csv(
249    py: Python<'_>,
250    filepaths: Vec<PathBuf>,
251    catalog_path: PathBuf,
252    underlyings: Option<Vec<String>>,
253    snapshot_interval_ms: Option<u64>,
254    extract_bbo_as_quotes: bool,
255    write_instruments: bool,
256    price_precision: Option<u8>,
257    size_precision: Option<u8>,
258) -> PyResult<()> {
259    let config = TardisOptionsChainCSVConverterConfig {
260        filepaths,
261        catalog_path,
262        underlyings,
263        snapshot_interval: snapshot_interval_ms.map(Duration::from_millis),
264        extract_bbo_as_quotes,
265        write_instruments,
266        price_precision,
267        size_precision,
268    };
269
270    py.detach(|| convert_options_chain_csv(&config))
271        .map_err(to_pyvalue_err)
272}
273
274impl_tardis_stream_iterator!(
275    TardisDeltaStreamIterator,
276    OrderBookDelta,
277    "TardisDeltasStreamIterator"
278);
279
280/// Streams order book deltas from a Tardis CSV file.
281///
282/// # Errors
283///
284/// Returns a Python error if loading or parsing the CSV file fails.
285#[pyfunction(name = "stream_tardis_deltas")]
286#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.tardis")]
287#[pyo3(signature = (filepath, chunk_size=100_000, price_precision=None, size_precision=None, instrument_id=None, limit=None))]
288pub fn py_stream_tardis_deltas(
289    filepath: PathBuf,
290    chunk_size: usize,
291    price_precision: Option<u8>,
292    size_precision: Option<u8>,
293    instrument_id: Option<InstrumentId>,
294    limit: Option<usize>,
295) -> PyResult<TardisDeltaStreamIterator> {
296    let stream = stream_deltas(
297        filepath,
298        chunk_size,
299        price_precision,
300        size_precision,
301        instrument_id,
302        limit,
303    )
304    .map_err(to_pyvalue_err)?;
305
306    Ok(TardisDeltaStreamIterator {
307        stream: Box::new(stream),
308    })
309}
310
311#[pyclass(unsendable)]
312#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.tardis")]
313pub struct TardisBatchedDeltasStreamIterator {
314    stream: Box<dyn Iterator<Item = anyhow::Result<Vec<Py<PyAny>>>>>,
315}
316
317impl Debug for TardisBatchedDeltasStreamIterator {
318    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
319        write!(f, "TardisBatchedDeltasStreamIterator {{ stream: ... }}")
320    }
321}
322
323#[pymethods]
324#[pyo3_stub_gen::derive::gen_stub_pymethods]
325impl TardisBatchedDeltasStreamIterator {
326    const fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
327        slf
328    }
329
330    fn __next__(&mut self) -> PyResult<Option<Vec<Py<PyAny>>>> {
331        match self.stream.next() {
332            Some(Ok(batch)) => Ok(Some(batch)),
333            Some(Err(e)) => Err(to_pyvalue_err(e)),
334            None => Ok(None),
335        }
336    }
337}
338
339/// Streams batched order book deltas from a Tardis CSV file.
340///
341/// # Errors
342///
343/// Returns a Python error if loading or parsing the CSV file fails.
344#[pyfunction(name = "stream_tardis_batched_deltas")]
345#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.tardis")]
346#[pyo3(signature = (filepath, chunk_size=100_000, price_precision=None, size_precision=None, instrument_id=None, limit=None))]
347pub fn py_stream_tardis_batched_deltas(
348    filepath: PathBuf,
349    chunk_size: usize,
350    price_precision: Option<u8>,
351    size_precision: Option<u8>,
352    instrument_id: Option<InstrumentId>,
353    limit: Option<usize>,
354) -> PyResult<TardisBatchedDeltasStreamIterator> {
355    let stream = stream_batched_deltas(
356        filepath,
357        chunk_size,
358        price_precision,
359        size_precision,
360        instrument_id,
361        limit,
362    )
363    .map_err(to_pyvalue_err)?;
364
365    Ok(TardisBatchedDeltasStreamIterator {
366        stream: Box::new(stream),
367    })
368}
369
370impl_tardis_stream_iterator!(
371    TardisQuoteStreamIterator,
372    QuoteTick,
373    "TardisQuoteStreamIterator"
374);
375
376/// Streams quote ticks from a Tardis CSV file.
377///
378/// # Errors
379///
380/// Returns a Python error if loading or parsing the CSV file fails.
381#[pyfunction(name = "stream_tardis_quotes")]
382#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.tardis")]
383#[pyo3(signature = (filepath, chunk_size=100_000, price_precision=None, size_precision=None, instrument_id=None, limit=None))]
384pub fn py_stream_tardis_quotes(
385    filepath: PathBuf,
386    chunk_size: usize,
387    price_precision: Option<u8>,
388    size_precision: Option<u8>,
389    instrument_id: Option<InstrumentId>,
390    limit: Option<usize>,
391) -> PyResult<TardisQuoteStreamIterator> {
392    let stream = stream_quotes(
393        filepath,
394        chunk_size,
395        price_precision,
396        size_precision,
397        instrument_id,
398        limit,
399    )
400    .map_err(to_pyvalue_err)?;
401
402    Ok(TardisQuoteStreamIterator {
403        stream: Box::new(stream),
404    })
405}
406
407#[pyclass(unsendable)]
408#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.tardis")]
409pub struct TardisOptionsChainStreamIterator {
410    stream: Box<dyn Iterator<Item = anyhow::Result<Vec<Data>>>>,
411}
412
413impl Debug for TardisOptionsChainStreamIterator {
414    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
415        write!(f, "TardisOptionsChainStreamIterator {{ stream: ... }}")
416    }
417}
418
419#[pymethods]
420#[pyo3_stub_gen::derive::gen_stub_pymethods]
421impl TardisOptionsChainStreamIterator {
422    const fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
423        slf
424    }
425
426    fn __next__(&mut self, py: Python<'_>) -> PyResult<Option<Vec<Py<PyAny>>>> {
427        match self.stream.next() {
428            Some(Ok(chunk)) => chunk
429                .into_iter()
430                .map(|data| options_chain_data_to_pyobject(py, data))
431                .collect::<PyResult<Vec<_>>>()
432                .map(Some),
433            Some(Err(e)) => Err(to_pyvalue_err(e)),
434            None => Ok(None),
435        }
436    }
437}
438
439/// Streams Tardis options chain rows from a CSV file.
440///
441/// # Errors
442///
443/// Returns a Python error if loading or parsing the CSV file fails.
444#[pyfunction(name = "stream_tardis_options_chain")]
445#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.tardis")]
446#[pyo3(signature = (filepath, chunk_size=100_000, underlyings=None, price_precision=None, size_precision=None, limit=None))]
447pub fn py_stream_tardis_options_chain(
448    filepath: PathBuf,
449    chunk_size: usize,
450    underlyings: Option<Vec<String>>,
451    price_precision: Option<u8>,
452    size_precision: Option<u8>,
453    limit: Option<usize>,
454) -> PyResult<TardisOptionsChainStreamIterator> {
455    let stream = stream_options_chain(
456        filepath,
457        chunk_size,
458        underlyings,
459        price_precision,
460        size_precision,
461        limit,
462    )
463    .map_err(to_pyvalue_err)?;
464
465    Ok(TardisOptionsChainStreamIterator {
466        stream: Box::new(stream),
467    })
468}
469
470impl_tardis_stream_iterator!(
471    TardisTradeStreamIterator,
472    TradeTick,
473    "TardisTradeStreamIterator"
474);
475
476/// Streams trade ticks from a Tardis CSV file.
477///
478/// # Errors
479///
480/// Returns a Python error if loading or parsing the CSV file fails.
481#[pyfunction(name = "stream_tardis_trades")]
482#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.tardis")]
483#[pyo3(signature = (filepath, chunk_size=100_000, price_precision=None, size_precision=None, instrument_id=None, limit=None))]
484pub fn py_stream_tardis_trades(
485    filepath: PathBuf,
486    chunk_size: usize,
487    price_precision: Option<u8>,
488    size_precision: Option<u8>,
489    instrument_id: Option<InstrumentId>,
490    limit: Option<usize>,
491) -> PyResult<TardisTradeStreamIterator> {
492    let stream = stream_trades(
493        filepath,
494        chunk_size,
495        price_precision,
496        size_precision,
497        instrument_id,
498        limit,
499    )
500    .map_err(to_pyvalue_err)?;
501
502    Ok(TardisTradeStreamIterator {
503        stream: Box::new(stream),
504    })
505}
506
507impl_tardis_stream_iterator!(
508    TardisDepth10StreamIterator,
509    OrderBookDepth10,
510    "TardisDepth10StreamIterator"
511);
512
513/// Streams order book depth10 from a Tardis snapshot5 CSV file.
514///
515/// # Errors
516///
517/// Returns a Python error if loading or parsing the CSV file fails.
518#[pyfunction(name = "stream_tardis_depth10_from_snapshot5")]
519#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.tardis")]
520#[pyo3(signature = (filepath, chunk_size=100_000, price_precision=None, size_precision=None, instrument_id=None, limit=None))]
521pub fn py_stream_tardis_depth10_from_snapshot5(
522    filepath: PathBuf,
523    chunk_size: usize,
524    price_precision: Option<u8>,
525    size_precision: Option<u8>,
526    instrument_id: Option<InstrumentId>,
527    limit: Option<usize>,
528) -> PyResult<TardisDepth10StreamIterator> {
529    let stream = stream_depth10_from_snapshot5(
530        filepath,
531        chunk_size,
532        price_precision,
533        size_precision,
534        instrument_id,
535        limit,
536    )
537    .map_err(to_pyvalue_err)?;
538
539    Ok(TardisDepth10StreamIterator {
540        stream: Box::new(stream),
541    })
542}
543
544/// Streams order book depth10 from a Tardis snapshot25 CSV file.
545///
546/// # Errors
547///
548/// Returns a Python error if loading or parsing the CSV file fails.
549#[pyfunction(name = "stream_tardis_depth10_from_snapshot25")]
550#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.tardis")]
551#[pyo3(signature = (filepath, chunk_size=100_000, price_precision=None, size_precision=None, instrument_id=None, limit=None))]
552pub fn py_stream_tardis_depth10_from_snapshot25(
553    filepath: PathBuf,
554    chunk_size: usize,
555    price_precision: Option<u8>,
556    size_precision: Option<u8>,
557    instrument_id: Option<InstrumentId>,
558    limit: Option<usize>,
559) -> PyResult<TardisDepth10StreamIterator> {
560    let stream = stream_depth10_from_snapshot25(
561        filepath,
562        chunk_size,
563        price_precision,
564        size_precision,
565        instrument_id,
566        limit,
567    )
568    .map_err(to_pyvalue_err)?;
569
570    Ok(TardisDepth10StreamIterator {
571        stream: Box::new(stream),
572    })
573}
574
575impl_tardis_stream_iterator!(
576    TardisFundingRateStreamIterator,
577    FundingRateUpdate,
578    "TardisFundingRateStreamIterator"
579);
580
581/// Streams funding rate updates from a Tardis derivative ticker CSV file.
582///
583/// # Errors
584///
585/// Returns a Python error if loading or parsing the CSV file fails.
586#[pyfunction(name = "stream_tardis_funding_rates")]
587#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.tardis")]
588#[pyo3(signature = (filepath, chunk_size=100_000, instrument_id=None, limit=None))]
589pub fn py_stream_tardis_funding_rates(
590    filepath: PathBuf,
591    chunk_size: usize,
592    instrument_id: Option<InstrumentId>,
593    limit: Option<usize>,
594) -> PyResult<TardisFundingRateStreamIterator> {
595    let stream =
596        stream_funding_rates(filepath, chunk_size, instrument_id, limit).map_err(to_pyvalue_err)?;
597
598    Ok(TardisFundingRateStreamIterator {
599        stream: Box::new(stream),
600    })
601}