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::{IntoPyObjectNautilusExt, to_pytype_err};
19use nautilus_model::data::{Bar, QuoteTick, TradeTick};
20use pyo3::{
21    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            self.indicator
79                .call_method1(py, "handle_quote_tick", ((*quote).into_py_any_unwrap(py),))
80        })
81        .map(|_| ())
82        .map_err(|e| anyhow::anyhow!("{e}"))
83    }
84
85    fn handle_trade(&self, trade: &TradeTick) -> anyhow::Result<()> {
86        Python::attach(|py| {
87            self.indicator
88                .call_method1(py, "handle_trade_tick", ((*trade).into_py_any_unwrap(py),))
89        })
90        .map(|_| ())
91        .map_err(|e| anyhow::anyhow!("{e}"))
92    }
93
94    fn handle_bar(&self, bar: &Bar) -> anyhow::Result<()> {
95        Python::attach(|py| {
96            self.indicator
97                .call_method1(py, "handle_bar", ((*bar).into_py_any_unwrap(py),))
98        })
99        .map(|_| ())
100        .map_err(|e| anyhow::anyhow!("{e}"))
101    }
102}
103
104/// Wraps a Python indicator object for registration with the Rust actor core.
105pub fn wrap_python_indicator(py: Python<'_>, indicator: Py<PyAny>) -> SharedActorIndicator {
106    Rc::new(PyActorIndicator::new(py, indicator))
107}
108
109/// Returns registered Python indicators as a Python list.
110///
111/// # Errors
112///
113/// Returns an error if a registered indicator is not backed by a Python object.
114pub fn registered_python_indicators(
115    py: Python<'_>,
116    indicators: Vec<SharedActorIndicator>,
117) -> PyResult<Py<PyList>> {
118    let py_indicators = PyList::empty(py);
119
120    for indicator in indicators {
121        let Some(indicator) = indicator.as_any().downcast_ref::<PyActorIndicator>() else {
122            return Err(to_pytype_err(
123                "registered indicator is not a Python indicator",
124            ));
125        };
126
127        py_indicators.append(indicator.clone_ref(py))?;
128    }
129
130    Ok(py_indicators.unbind())
131}