Skip to main content

nautilus_model/python/data/
mod.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
16//! Data types for the trading domain model.
17
18pub mod bar;
19pub mod bet;
20pub mod close;
21pub mod delta;
22pub mod deltas;
23pub mod depth;
24pub mod funding;
25pub mod greeks;
26pub mod order;
27pub mod prices;
28pub mod quote;
29pub mod status;
30pub mod trade;
31
32use indexmap::IndexMap;
33#[cfg(feature = "ffi")]
34use nautilus_core::ffi::cvec::CVec;
35use nautilus_core::python::to_pyvalue_err;
36use pyo3::{prelude::*, types::PyCapsule};
37
38use crate::data::{
39    Bar, Data, DataType, FundingRateUpdate, IndexPriceUpdate, MarkPriceUpdate, OrderBookDelta,
40    QuoteTick, TradeTick, close::InstrumentClose, is_monotonically_increasing_by_init,
41};
42
43const ERROR_MONOTONICITY: &str = "`data` was not monotonically increasing by the `ts_init` field";
44
45#[pymethods]
46#[cfg_attr(feature = "python", pyo3_stub_gen::derive::gen_stub_pymethods)]
47impl DataType {
48    #[new]
49    #[pyo3(signature = (type_name, metadata=None))]
50    fn py_new(type_name: &str, metadata: Option<IndexMap<String, String>>) -> Self {
51        Self::new(type_name, metadata)
52    }
53
54    #[getter]
55    #[pyo3(name = "type_name")]
56    fn py_type_name(&self) -> &str {
57        self.type_name()
58    }
59
60    #[getter]
61    #[pyo3(name = "metadata")]
62    fn py_metadata(&self) -> Option<IndexMap<String, String>> {
63        self.metadata().cloned()
64    }
65
66    #[getter]
67    #[pyo3(name = "topic")]
68    fn py_topic(&self) -> &str {
69        self.topic()
70    }
71}
72
73/// Creates a Python `PyCapsule` object containing a Rust `Data` instance.
74///
75/// This function takes ownership of the `Data` instance and encapsulates it within
76/// a `PyCapsule` object, allowing the Rust data to be passed into the Python runtime.
77///
78/// # Panics
79///
80/// This function panics if the `PyCapsule` creation fails, which may occur if
81/// there are issues with memory allocation or if the `Data` instance cannot be
82/// properly encapsulated.
83#[must_use]
84pub fn data_to_pycapsule(py: Python, data: Data) -> Py<PyAny> {
85    // Register a destructor which simply drops the `Data` value once the
86    // capsule is released by Python.
87    let capsule = PyCapsule::new_with_destructor(py, data, None, |_, _| {})
88        .expect("Error creating `PyCapsule`");
89    capsule.into_any().unbind()
90}
91
92/// Drops a `PyCapsule` containing a `CVec` structure.
93///
94/// This function safely extracts and drops the `CVec` instance encapsulated within
95/// a `PyCapsule` object. It is intended for cleaning up after the `Data` instances
96/// have been transferred into Python and are no longer needed.
97///
98/// # Panics
99///
100/// Panics if the capsule cannot be downcast to a `PyCapsule`, indicating a type
101/// mismatch or improper capsule handling.
102///
103/// This function involves raw pointer dereferencing and manual memory
104/// management. The caller must ensure the `PyCapsule` contains a valid `CVec` pointer.
105#[cfg(feature = "ffi")]
106#[pyfunction]
107#[allow(unsafe_code)]
108pub fn drop_cvec_pycapsule(capsule: &Bound<'_, PyAny>) {
109    let capsule: &Bound<'_, PyCapsule> = capsule
110        .cast::<PyCapsule>()
111        .expect("Error on downcast to `&PyCapsule`");
112    let cvec: &CVec = unsafe { &*(capsule.pointer_checked(None).unwrap().as_ptr() as *const CVec) };
113    let data: Vec<Data> =
114        unsafe { Vec::from_raw_parts(cvec.ptr.cast::<Data>(), cvec.len, cvec.cap) };
115    drop(data);
116}
117
118#[cfg(not(feature = "ffi"))]
119#[pyfunction]
120/// Drops a Python `PyCapsule` containing a `CVec` when the `ffi` feature is not enabled.
121///
122/// # Panics
123///
124/// Always panics with the message "`ffi` feature is not enabled" to indicate that
125/// FFI functionality is unavailable.
126pub fn drop_cvec_pycapsule(_capsule: &Bound<'_, PyAny>) {
127    panic!("`ffi` feature is not enabled");
128}
129
130/// Transforms the given Python objects into a vector of [`OrderBookDelta`] objects.
131///
132/// # Errors
133///
134/// Returns a `PyErr` if element conversion fails or the data is not monotonically increasing.
135pub fn pyobjects_to_book_deltas(data: Vec<Bound<'_, PyAny>>) -> PyResult<Vec<OrderBookDelta>> {
136    let deltas: Vec<OrderBookDelta> = data
137        .into_iter()
138        .map(|obj| OrderBookDelta::from_pyobject(&obj))
139        .collect::<PyResult<Vec<OrderBookDelta>>>()?;
140
141    // Validate monotonically increasing
142    if !is_monotonically_increasing_by_init(&deltas) {
143        return Err(to_pyvalue_err(ERROR_MONOTONICITY));
144    }
145
146    Ok(deltas)
147}
148
149/// Transforms the given Python objects into a vector of [`QuoteTick`] objects.
150///
151/// # Errors
152///
153/// Returns a `PyErr` if element conversion fails or the data is not monotonically increasing.
154pub fn pyobjects_to_quotes(data: Vec<Bound<'_, PyAny>>) -> PyResult<Vec<QuoteTick>> {
155    let quotes: Vec<QuoteTick> = data
156        .into_iter()
157        .map(|obj| QuoteTick::from_pyobject(&obj))
158        .collect::<PyResult<Vec<QuoteTick>>>()?;
159
160    // Validate monotonically increasing
161    if !is_monotonically_increasing_by_init(&quotes) {
162        return Err(to_pyvalue_err(ERROR_MONOTONICITY));
163    }
164
165    Ok(quotes)
166}
167
168/// Transforms the given Python objects into a vector of [`TradeTick`] objects.
169///
170/// # Errors
171///
172/// Returns a `PyErr` if element conversion fails or the data is not monotonically increasing.
173pub fn pyobjects_to_trades(data: Vec<Bound<'_, PyAny>>) -> PyResult<Vec<TradeTick>> {
174    let trades: Vec<TradeTick> = data
175        .into_iter()
176        .map(|obj| TradeTick::from_pyobject(&obj))
177        .collect::<PyResult<Vec<TradeTick>>>()?;
178
179    // Validate monotonically increasing
180    if !is_monotonically_increasing_by_init(&trades) {
181        return Err(to_pyvalue_err(ERROR_MONOTONICITY));
182    }
183
184    Ok(trades)
185}
186
187/// Transforms the given Python objects into a vector of [`Bar`] objects.
188///
189/// # Errors
190///
191/// Returns a `PyErr` if element conversion fails or the data is not monotonically increasing.
192pub fn pyobjects_to_bars(data: Vec<Bound<'_, PyAny>>) -> PyResult<Vec<Bar>> {
193    let bars: Vec<Bar> = data
194        .into_iter()
195        .map(|obj| Bar::from_pyobject(&obj))
196        .collect::<PyResult<Vec<Bar>>>()?;
197
198    // Validate monotonically increasing
199    if !is_monotonically_increasing_by_init(&bars) {
200        return Err(to_pyvalue_err(ERROR_MONOTONICITY));
201    }
202
203    Ok(bars)
204}
205
206/// Transforms the given Python objects into a vector of [`MarkPriceUpdate`] objects.
207///
208/// # Errors
209///
210/// Returns a `PyErr` if element conversion fails or the data is not monotonically increasing.
211pub fn pyobjects_to_mark_prices(data: Vec<Bound<'_, PyAny>>) -> PyResult<Vec<MarkPriceUpdate>> {
212    let mark_prices: Vec<MarkPriceUpdate> = data
213        .into_iter()
214        .map(|obj| MarkPriceUpdate::from_pyobject(&obj))
215        .collect::<PyResult<Vec<MarkPriceUpdate>>>()?;
216
217    // Validate monotonically increasing
218    if !is_monotonically_increasing_by_init(&mark_prices) {
219        return Err(to_pyvalue_err(ERROR_MONOTONICITY));
220    }
221
222    Ok(mark_prices)
223}
224
225/// Transforms the given Python objects into a vector of [`IndexPriceUpdate`] objects.
226///
227/// # Errors
228///
229/// Returns a `PyErr` if element conversion fails or the data is not monotonically increasing.
230pub fn pyobjects_to_index_prices(data: Vec<Bound<'_, PyAny>>) -> PyResult<Vec<IndexPriceUpdate>> {
231    let index_prices: Vec<IndexPriceUpdate> = data
232        .into_iter()
233        .map(|obj| IndexPriceUpdate::from_pyobject(&obj))
234        .collect::<PyResult<Vec<IndexPriceUpdate>>>()?;
235
236    // Validate monotonically increasing
237    if !is_monotonically_increasing_by_init(&index_prices) {
238        return Err(to_pyvalue_err(ERROR_MONOTONICITY));
239    }
240
241    Ok(index_prices)
242}
243
244/// Transforms the given Python objects into a vector of [`InstrumentClose`] objects.
245///
246/// # Errors
247///
248/// Returns a `PyErr` if element conversion fails or the data is not monotonically increasing.
249pub fn pyobjects_to_instrument_closes(
250    data: Vec<Bound<'_, PyAny>>,
251) -> PyResult<Vec<InstrumentClose>> {
252    let closes: Vec<InstrumentClose> = data
253        .into_iter()
254        .map(|obj| InstrumentClose::from_pyobject(&obj))
255        .collect::<PyResult<Vec<InstrumentClose>>>()?;
256
257    // Validate monotonically increasing
258    if !is_monotonically_increasing_by_init(&closes) {
259        return Err(to_pyvalue_err(ERROR_MONOTONICITY));
260    }
261
262    Ok(closes)
263}
264
265/// Transforms the given Python objects into a vector of [`FundingRateUpdate`] objects.
266///
267/// # Errors
268///
269/// Returns a `PyErr` if element conversion fails or the data is not monotonically increasing.
270pub fn pyobjects_to_funding_rates(data: Vec<Bound<'_, PyAny>>) -> PyResult<Vec<FundingRateUpdate>> {
271    let funding_rates: Vec<FundingRateUpdate> = data
272        .into_iter()
273        .map(|obj| FundingRateUpdate::from_pyobject(&obj))
274        .collect::<PyResult<Vec<FundingRateUpdate>>>()?;
275
276    // Validate monotonically increasing
277    if !is_monotonically_increasing_by_init(&funding_rates) {
278        return Err(to_pyvalue_err(ERROR_MONOTONICITY));
279    }
280
281    Ok(funding_rates)
282}