Skip to main content

nautilus_common/python/
indicators.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::{any::Any, fmt::Debug, rc::Rc};
17
18use nautilus_core::python::to_pytype_err;
19use nautilus_model::data::{Bar, QuoteTick, TradeTick};
20use pyo3::{
21    IntoPyObjectExt, Py, PyAny, PyResult, Python,
22    types::{PyAnyMethods, PyList, PyListMethods},
23};
24
25use crate::actor::indicators::{ActorIndicator, SharedActorIndicator};
26
27struct PyActorIndicator {
28    indicator: Py<PyAny>,
29    key: usize,
30}
31
32impl Debug for PyActorIndicator {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        f.debug_struct(stringify!(PyActorIndicator))
35            .field("key", &self.key)
36            .finish_non_exhaustive()
37    }
38}
39
40impl PyActorIndicator {
41    fn new(py: Python<'_>, indicator: Py<PyAny>) -> Self {
42        Self {
43            key: indicator.bind(py).as_ptr() as usize,
44            indicator,
45        }
46    }
47
48    fn clone_ref(&self, py: Python<'_>) -> Py<PyAny> {
49        self.indicator.clone_ref(py)
50    }
51}
52
53impl ActorIndicator for PyActorIndicator {
54    fn key(&self) -> usize {
55        self.key
56    }
57
58    fn as_any(&self) -> &dyn Any {
59        self
60    }
61
62    fn initialized(&self) -> anyhow::Result<bool> {
63        Python::attach(|py| {
64            let initialized = self.indicator.bind(py).getattr("initialized")?;
65            let initialized = if initialized.is_callable() {
66                initialized.call0()?
67            } else {
68                initialized
69            };
70
71            initialized.extract::<bool>()
72        })
73        .map_err(|e| anyhow::anyhow!("{e}"))
74    }
75
76    fn handle_quote(&self, quote: &QuoteTick) -> anyhow::Result<()> {
77        Python::attach(|py| {
78            let quote = (*quote).into_py_any(py)?;
79            self.indicator
80                .call_method1(py, "handle_quote_tick", (quote,))
81        })
82        .map(|_| ())
83        .map_err(|e| anyhow::anyhow!("{e}"))
84    }
85
86    fn handle_trade(&self, trade: &TradeTick) -> anyhow::Result<()> {
87        Python::attach(|py| {
88            let trade = (*trade).into_py_any(py)?;
89            self.indicator
90                .call_method1(py, "handle_trade_tick", (trade,))
91        })
92        .map(|_| ())
93        .map_err(|e| anyhow::anyhow!("{e}"))
94    }
95
96    fn handle_bar(&self, bar: &Bar) -> anyhow::Result<()> {
97        Python::attach(|py| {
98            let bar = (*bar).into_py_any(py)?;
99            self.indicator.call_method1(py, "handle_bar", (bar,))
100        })
101        .map(|_| ())
102        .map_err(|e| anyhow::anyhow!("{e}"))
103    }
104}
105
106/// Wraps a Python indicator object for registration with the Rust actor core.
107pub fn wrap_python_indicator(py: Python<'_>, indicator: Py<PyAny>) -> SharedActorIndicator {
108    Rc::new(PyActorIndicator::new(py, indicator))
109}
110
111/// Returns registered Python indicators as a Python list.
112///
113/// # Errors
114///
115/// Returns an error if a registered indicator is not backed by a Python object.
116pub fn registered_python_indicators(
117    py: Python<'_>,
118    indicators: Vec<SharedActorIndicator>,
119) -> PyResult<Py<PyList>> {
120    let py_indicators = PyList::empty(py);
121
122    for indicator in indicators {
123        let Some(indicator) = indicator.as_any().downcast_ref::<PyActorIndicator>() else {
124            return Err(to_pytype_err(
125                "registered indicator is not a Python indicator",
126            ));
127        };
128
129        py_indicators.append(indicator.clone_ref(py))?;
130    }
131
132    Ok(py_indicators.unbind())
133}