Skip to main content

nautilus_indicators/python/momentum/
ichimoku.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 nautilus_core::python::to_pyvalue_err;
17use nautilus_model::data::Bar;
18use pyo3::prelude::*;
19
20use crate::{indicator::Indicator, momentum::ichimoku::IchimokuCloud, python::float_precision};
21
22#[pymethods]
23#[pyo3_stub_gen::derive::gen_stub_pymethods]
24impl IchimokuCloud {
25    /// Ichimoku Kinko Hyo: the five-line cloud chart.
26    ///
27    /// ```text
28    /// tenkan_sen    = midpoint(high, low over tenkan_period)
29    /// kijun_sen     = midpoint(high, low over kijun_period)
30    /// senkou_span_a = (tenkan_sen + kijun_sen) / 2      as computed `displacement - 1` bars ago
31    /// senkou_span_b = midpoint(high, low over senkou_period) as computed `displacement - 1` bars ago
32    /// chikou_span   = close from `displacement - 1` bars ago
33    /// ```
34    ///
35    /// The two Senkou spans form the Kumo (cloud). Charts draw them `displacement`
36    /// bars ahead and the Chikou span `displacement` bars behind; streaming in
37    /// chronological order, the values visible at bar `n` are the ones buffered
38    /// `displacement` updates ago, that is bar `n - displacement + 1`.
39    ///
40    /// Each line becomes available at its own bar: `tenkan_sen` after `tenkan_period`
41    /// bars, `kijun_sen` after `kijun_period`, `chikou_span` after `displacement`,
42    /// `senkou_span_a` after `kijun_period + displacement - 1`, and `senkou_span_b`
43    /// after `senkou_period + displacement - 1` (77 bars at the classic
44    /// `(9, 26, 52, 26)`). The matching `has_*` flag reports whether each field
45    /// holds a value, and `initialized` gates on all five.
46    #[new]
47    #[pyo3(signature = (tenkan_period=9, kijun_period=26, senkou_period=52, displacement=26))]
48    fn py_new(
49        tenkan_period: usize,
50        kijun_period: usize,
51        senkou_period: usize,
52        displacement: usize,
53    ) -> PyResult<Self> {
54        Self::new_checked(tenkan_period, kijun_period, senkou_period, displacement)
55            .map_err(to_pyvalue_err)
56    }
57
58    fn __repr__(&self) -> String {
59        format!("{self}")
60    }
61
62    #[getter]
63    #[pyo3(name = "name")]
64    fn py_name(&self) -> String {
65        self.name()
66    }
67
68    #[getter]
69    #[pyo3(name = "tenkan_period")]
70    const fn py_tenkan_period(&self) -> usize {
71        self.tenkan_period
72    }
73
74    #[getter]
75    #[pyo3(name = "kijun_period")]
76    const fn py_kijun_period(&self) -> usize {
77        self.kijun_period
78    }
79
80    #[getter]
81    #[pyo3(name = "senkou_period")]
82    const fn py_senkou_period(&self) -> usize {
83        self.senkou_period
84    }
85
86    #[getter]
87    #[pyo3(name = "displacement")]
88    const fn py_displacement(&self) -> usize {
89        self.displacement
90    }
91
92    #[getter]
93    #[pyo3(name = "has_inputs")]
94    fn py_has_inputs(&self) -> bool {
95        self.has_inputs()
96    }
97
98    #[getter]
99    #[pyo3(name = "tenkan_sen")]
100    const fn py_tenkan_sen(&self) -> f64 {
101        self.tenkan_sen
102    }
103
104    #[getter]
105    #[pyo3(name = "kijun_sen")]
106    const fn py_kijun_sen(&self) -> f64 {
107        self.kijun_sen
108    }
109
110    #[getter]
111    #[pyo3(name = "senkou_span_a")]
112    const fn py_senkou_span_a(&self) -> f64 {
113        self.senkou_span_a
114    }
115
116    #[getter]
117    #[pyo3(name = "senkou_span_b")]
118    const fn py_senkou_span_b(&self) -> f64 {
119        self.senkou_span_b
120    }
121
122    #[getter]
123    #[pyo3(name = "chikou_span")]
124    const fn py_chikou_span(&self) -> f64 {
125        self.chikou_span
126    }
127
128    #[getter]
129    #[pyo3(name = "has_tenkan")]
130    const fn py_has_tenkan(&self) -> bool {
131        self.has_tenkan
132    }
133
134    #[getter]
135    #[pyo3(name = "has_kijun")]
136    const fn py_has_kijun(&self) -> bool {
137        self.has_kijun
138    }
139
140    #[getter]
141    #[pyo3(name = "has_senkou_a")]
142    const fn py_has_senkou_a(&self) -> bool {
143        self.has_senkou_a
144    }
145
146    #[getter]
147    #[pyo3(name = "has_senkou_b")]
148    const fn py_has_senkou_b(&self) -> bool {
149        self.has_senkou_b
150    }
151
152    #[getter]
153    #[pyo3(name = "has_chikou")]
154    const fn py_has_chikou(&self) -> bool {
155        self.has_chikou
156    }
157
158    #[getter]
159    #[pyo3(name = "count")]
160    const fn py_count(&self) -> usize {
161        self.count
162    }
163
164    #[getter]
165    #[pyo3(name = "initialized")]
166    const fn py_initialized(&self) -> bool {
167        self.initialized
168    }
169
170    /// Updates the indicator with the given high, low and close.
171    #[pyo3(name = "update_raw")]
172    fn py_update_raw(&mut self, high: f64, low: f64, close: f64) {
173        self.update_raw(high, low, close);
174    }
175
176    #[pyo3(name = "handle_bar")]
177    fn py_handle_bar(&mut self, bar: &Bar) -> PyResult<()> {
178        float_precision::check_bar(bar)?;
179        self.handle_bar(bar);
180        Ok(())
181    }
182
183    #[pyo3(name = "reset")]
184    fn py_reset(&mut self) {
185        self.reset();
186    }
187}