nautilus_model/python/data/
mod.rs

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