From 3f472f168f943bd794174d8f6b23f93b7316a096 Mon Sep 17 00:00:00 2001 From: Filip Macek Date: Wed, 25 Oct 2023 09:25:48 +0200 Subject: [PATCH] Add WeightedMovingAverage indicator in rust (#1305) --- nautilus_core/indicators/src/average/ama.rs | 2 +- nautilus_core/indicators/src/average/dema.rs | 77 +----- nautilus_core/indicators/src/average/ema.rs | 83 +----- nautilus_core/indicators/src/average/mod.rs | 1 + nautilus_core/indicators/src/average/sma.rs | 58 +---- nautilus_core/indicators/src/average/wma.rs | 238 ++++++++++++++++++ nautilus_core/indicators/src/lib.rs | 18 +- nautilus_core/indicators/src/momentum/rsi.rs | 67 +---- .../indicators/src/python/average/ama.rs | 104 ++++++++ .../indicators/src/python/average/dema.rs | 99 ++++++++ .../indicators/src/python/average/ema.rs | 105 ++++++++ .../indicators/src/python/average/mod.rs | 20 ++ .../indicators/src/python/average/sma.rs | 99 ++++++++ .../indicators/src/python/average/wma.rs | 97 +++++++ nautilus_core/indicators/src/python/mod.rs | 34 +++ .../indicators/src/python/momentum/mod.rs | 16 ++ .../indicators/src/python/momentum/rsi.rs | 87 +++++++ .../src/python/ratio/efficiency_ratio.rs | 66 +++++ .../indicators/src/python/ratio/mod.rs | 16 ++ .../indicators/src/ratio/efficiency_ratio.rs | 50 +--- nautilus_core/indicators/src/stubs.rs | 9 +- nautilus_core/pyo3/src/lib.rs | 2 +- 22 files changed, 1000 insertions(+), 348 deletions(-) create mode 100644 nautilus_core/indicators/src/average/wma.rs create mode 100644 nautilus_core/indicators/src/python/average/ama.rs create mode 100644 nautilus_core/indicators/src/python/average/dema.rs create mode 100644 nautilus_core/indicators/src/python/average/ema.rs create mode 100644 nautilus_core/indicators/src/python/average/mod.rs create mode 100644 nautilus_core/indicators/src/python/average/sma.rs create mode 100644 nautilus_core/indicators/src/python/average/wma.rs create mode 100644 nautilus_core/indicators/src/python/mod.rs create mode 100644 nautilus_core/indicators/src/python/momentum/mod.rs create mode 100644 nautilus_core/indicators/src/python/momentum/rsi.rs create mode 100644 nautilus_core/indicators/src/python/ratio/efficiency_ratio.rs create mode 100644 nautilus_core/indicators/src/python/ratio/mod.rs diff --git a/nautilus_core/indicators/src/average/ama.rs b/nautilus_core/indicators/src/average/ama.rs index 17997de30223..3f2d6fdf4aa3 100644 --- a/nautilus_core/indicators/src/average/ama.rs +++ b/nautilus_core/indicators/src/average/ama.rs @@ -48,12 +48,12 @@ pub struct AdaptiveMovingAverage { pub value: f64, /// The input count for the indicator. pub count: usize, + pub is_initialized: bool, _efficiency_ratio: EfficiencyRatio, _prior_value: Option, _alpha_fast: f64, _alpha_slow: f64, has_inputs: bool, - is_initialized: bool, } impl Display for AdaptiveMovingAverage { diff --git a/nautilus_core/indicators/src/average/dema.rs b/nautilus_core/indicators/src/average/dema.rs index 40bed375c7aa..c81898716a9a 100644 --- a/nautilus_core/indicators/src/average/dema.rs +++ b/nautilus_core/indicators/src/average/dema.rs @@ -16,7 +16,6 @@ use std::fmt::{Display, Formatter}; use anyhow::Result; -use nautilus_core::python::to_pyvalue_err; use nautilus_model::{ data::{bar::Bar, quote::QuoteTick, trade::TradeTick}, enums::PriceType, @@ -42,8 +41,8 @@ pub struct DoubleExponentialMovingAverage { pub value: f64, /// The input count for the indicator. pub count: usize, + pub is_initialized: bool, has_inputs: bool, - is_initialized: bool, _ema1: ExponentialMovingAverage, _ema2: ExponentialMovingAverage, } @@ -126,80 +125,6 @@ impl MovingAverage for DoubleExponentialMovingAverage { } } -#[cfg(feature = "python")] -#[pymethods] -impl DoubleExponentialMovingAverage { - #[new] - fn py_new(period: usize, price_type: Option) -> PyResult { - Self::new(period, price_type).map_err(to_pyvalue_err) - } - - #[getter] - #[pyo3(name = "name")] - fn py_name(&self) -> String { - self.name() - } - - #[getter] - #[pyo3(name = "period")] - fn py_period(&self) -> usize { - self.period - } - - #[getter] - #[pyo3(name = "count")] - fn py_count(&self) -> usize { - self.count - } - - #[getter] - #[pyo3(name = "value")] - fn py_value(&self) -> f64 { - self.value - } - - #[getter] - #[pyo3(name = "has_inputs")] - fn py_has_inputs(&self) -> bool { - self.has_inputs() - } - - #[getter] - #[pyo3(name = "initialized")] - fn py_initialized(&self) -> bool { - self.is_initialized - } - - #[pyo3(name = "handle_quote_tick")] - fn py_handle_quote_tick(&mut self, tick: &QuoteTick) { - self.py_update_raw(tick.extract_price(self.price_type).into()); - } - - #[pyo3(name = "handle_trade_tick")] - fn py_handle_trade_tick(&mut self, tick: &TradeTick) { - self.update_raw((&tick.price).into()); - } - - #[pyo3(name = "handle_bar")] - fn py_handle_bar(&mut self, bar: &Bar) { - self.update_raw((&bar.close).into()); - } - - #[pyo3(name = "reset")] - fn py_reset(&mut self) { - self.reset(); - } - - #[pyo3(name = "update_raw")] - fn py_update_raw(&mut self, value: f64) { - self.update_raw(value); - } - - fn __repr__(&self) -> String { - format!("DoubleExponentialMovingAverage({})", self.period) - } -} - //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// diff --git a/nautilus_core/indicators/src/average/ema.rs b/nautilus_core/indicators/src/average/ema.rs index 52355400414e..293cc273a913 100644 --- a/nautilus_core/indicators/src/average/ema.rs +++ b/nautilus_core/indicators/src/average/ema.rs @@ -16,7 +16,6 @@ use std::fmt::Display; use anyhow::Result; -use nautilus_core::python::to_pyvalue_err; use nautilus_model::{ data::{bar::Bar, quote::QuoteTick, trade::TradeTick}, enums::PriceType, @@ -34,8 +33,8 @@ pub struct ExponentialMovingAverage { pub alpha: f64, pub value: f64, pub count: usize, + pub is_initialized: bool, has_inputs: bool, - is_initialized: bool, } impl Display for ExponentialMovingAverage { @@ -117,86 +116,6 @@ impl MovingAverage for ExponentialMovingAverage { } } -#[cfg(feature = "python")] -#[pymethods] -impl ExponentialMovingAverage { - #[new] - fn py_new(period: usize, price_type: Option) -> PyResult { - Self::new(period, price_type).map_err(to_pyvalue_err) - } - - #[getter] - #[pyo3(name = "name")] - fn py_name(&self) -> String { - self.name() - } - - #[getter] - #[pyo3(name = "period")] - fn py_period(&self) -> usize { - self.period - } - - #[getter] - #[pyo3(name = "alpha")] - fn py_alpha(&self) -> f64 { - self.alpha - } - - #[getter] - #[pyo3(name = "count")] - fn py_count(&self) -> usize { - self.count - } - - #[getter] - #[pyo3(name = "value")] - fn py_value(&self) -> f64 { - self.value - } - - #[getter] - #[pyo3(name = "has_inputs")] - fn py_has_inputs(&self) -> bool { - self.has_inputs() - } - - #[getter] - #[pyo3(name = "initialized")] - fn py_initialized(&self) -> bool { - self.is_initialized - } - - #[pyo3(name = "handle_quote_tick")] - fn py_handle_quote_tick(&mut self, tick: &QuoteTick) { - self.py_update_raw(tick.extract_price(self.price_type).into()); - } - - #[pyo3(name = "handle_trade_tick")] - fn py_handle_trade_tick(&mut self, tick: &TradeTick) { - self.update_raw((&tick.price).into()); - } - - #[pyo3(name = "handle_bar")] - fn py_handle_bar(&mut self, bar: &Bar) { - self.update_raw((&bar.close).into()); - } - - #[pyo3(name = "reset")] - fn py_reset(&mut self) { - self.reset(); - } - - #[pyo3(name = "update_raw")] - fn py_update_raw(&mut self, value: f64) { - self.update_raw(value); - } - - fn __repr__(&self) -> String { - format!("ExponentialMovingAverage({})", self.period) - } -} - //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// diff --git a/nautilus_core/indicators/src/average/mod.rs b/nautilus_core/indicators/src/average/mod.rs index 3acfda5648d7..8623cc669d84 100644 --- a/nautilus_core/indicators/src/average/mod.rs +++ b/nautilus_core/indicators/src/average/mod.rs @@ -79,3 +79,4 @@ pub mod ama; pub mod dema; pub mod ema; pub mod sma; +pub mod wma; diff --git a/nautilus_core/indicators/src/average/sma.rs b/nautilus_core/indicators/src/average/sma.rs index b77d38fbf91d..406000ed8dad 100644 --- a/nautilus_core/indicators/src/average/sma.rs +++ b/nautilus_core/indicators/src/average/sma.rs @@ -16,7 +16,6 @@ use std::fmt::Display; use anyhow::Result; -use nautilus_core::python::to_pyvalue_err; use nautilus_model::{ data::{bar::Bar, quote::QuoteTick, trade::TradeTick}, enums::PriceType, @@ -34,7 +33,7 @@ pub struct SimpleMovingAverage { pub value: f64, pub count: usize, pub inputs: Vec, - is_initialized: bool, + pub is_initialized: bool, } impl Display for SimpleMovingAverage { @@ -85,7 +84,7 @@ impl SimpleMovingAverage { price_type: price_type.unwrap_or(PriceType::Last), value: 0.0, count: 0, - inputs: Vec::new(), + inputs: Vec::with_capacity(period), is_initialized: false, }) } @@ -115,59 +114,6 @@ impl MovingAverage for SimpleMovingAverage { } } -#[cfg(feature = "python")] -#[pymethods] -impl SimpleMovingAverage { - #[new] - fn py_new(period: usize, price_type: Option) -> PyResult { - Self::new(period, price_type).map_err(to_pyvalue_err) - } - - #[getter] - #[pyo3(name = "name")] - fn py_name(&self) -> String { - self.name() - } - - #[getter] - #[pyo3(name = "period")] - fn py_period(&self) -> usize { - self.period - } - - #[getter] - #[pyo3(name = "count")] - fn py_count(&self) -> usize { - self.count - } - - #[getter] - #[pyo3(name = "value")] - fn py_value(&self) -> f64 { - self.value - } - - #[getter] - #[pyo3(name = "initialized")] - fn py_initialized(&self) -> bool { - self.is_initialized - } - - #[pyo3(name = "has_inputs")] - fn py_has_inputs(&self) -> bool { - self.has_inputs() - } - - #[pyo3(name = "update_raw")] - fn py_update_raw(&mut self, value: f64) { - self.update_raw(value); - } - - fn __repr__(&self) -> String { - format!("SimpleMovingAverage({})", self.period) - } -} - //////////////////////////////////////////////////////////////////////////////// // Test //////////////////////////////////////////////////////////////////////////////// diff --git a/nautilus_core/indicators/src/average/wma.rs b/nautilus_core/indicators/src/average/wma.rs new file mode 100644 index 000000000000..4bff6366cad5 --- /dev/null +++ b/nautilus_core/indicators/src/average/wma.rs @@ -0,0 +1,238 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (C) 2015-2023 Nautech Systems Pty Ltd. All rights reserved. +// https://nautechsystems.io +// +// Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------------------------------- + +use std::fmt::Display; + +use anyhow::Result; +use nautilus_model::{ + data::{bar::Bar, quote::QuoteTick, trade::TradeTick}, + enums::PriceType, +}; +use pyo3::prelude::*; + +use crate::indicator::{Indicator, MovingAverage}; + +/// An indicator which calculates a weighted moving average across a rolling window. +#[repr(C)] +#[derive(Debug)] +#[pyclass(module = "nautilus_trader.core.nautilus_pyo3.indicators")] +pub struct WeightedMovingAverage { + /// The rolling window period for the indicator (> 0). + pub period: usize, + /// The weights for the moving average calculation + pub weights: Vec, + /// Price type + pub price_type: PriceType, + /// The last indicator value. + pub value: f64, + /// Whether the indicator is initialized. + pub is_initialized: bool, + /// Inputs + pub inputs: Vec, + has_inputs: bool, +} + +impl Display for WeightedMovingAverage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}({},{:?})", self.name(), self.period, self.weights) + } +} + +impl WeightedMovingAverage { + pub fn new(period: usize, weights: Vec, price_type: Option) -> Result { + if weights.len() != period { + return Err(anyhow::anyhow!("Weights length must be equal to period")); + } + Ok(Self { + period, + weights, + price_type: price_type.unwrap_or(PriceType::Last), + value: 0.0, + inputs: Vec::with_capacity(period), + is_initialized: false, + has_inputs: false, + }) + } + + fn weighted_average(&self) -> f64 { + let mut sum = 0.0; + let mut weight_sum = 0.0; + let reverse_weights: Vec = self.weights.iter().cloned().rev().collect(); + for (index, input) in self.inputs.iter().rev().enumerate() { + let weight = reverse_weights.get(index).unwrap(); + sum += input * weight; + weight_sum += weight + } + sum / weight_sum + } +} + +impl Indicator for WeightedMovingAverage { + fn name(&self) -> String { + stringify!(WeightedMovingAverage).to_string() + } + + fn has_inputs(&self) -> bool { + self.has_inputs + } + fn is_initialized(&self) -> bool { + self.is_initialized + } + + fn handle_quote_tick(&mut self, tick: &QuoteTick) { + self.update_raw(tick.extract_price(self.price_type).into()); + } + + fn handle_trade_tick(&mut self, tick: &TradeTick) { + self.update_raw((&tick.price).into()); + } + + fn handle_bar(&mut self, bar: &Bar) { + self.update_raw((&bar.close).into()); + } + + fn reset(&mut self) { + self.value = 0.0; + self.has_inputs = false; + self.is_initialized = false; + self.inputs.clear(); + } +} + +impl MovingAverage for WeightedMovingAverage { + fn value(&self) -> f64 { + self.value + } + + fn count(&self) -> usize { + self.inputs.len() + } + fn update_raw(&mut self, value: f64) { + if !self.has_inputs { + self.has_inputs = true; + self.inputs.push(value); + self.value = value; + return; + } + if self.inputs.len() == self.period { + self.inputs.remove(0); + } + self.inputs.push(value); + self.value = self.weighted_average(); + if !self.is_initialized && self.count() >= self.period { + self.is_initialized = true; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////////////// +#[cfg(test)] +mod tests { + use rstest::rstest; + + use crate::{ + average::wma::WeightedMovingAverage, + indicator::{Indicator, MovingAverage}, + stubs::*, + }; + + #[rstest] + fn test_wma_initialized(indicator_wma_10: WeightedMovingAverage) { + let display_str = format!("{}", indicator_wma_10); + assert_eq!( + display_str, + "WeightedMovingAverage(10,[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0])" + ); + assert_eq!(indicator_wma_10.name(), "WeightedMovingAverage"); + assert!(!indicator_wma_10.has_inputs()); + assert!(!indicator_wma_10.is_initialized()); + } + + #[rstest] + fn test_different_weights_len_and_period_error() { + let wma = WeightedMovingAverage::new(10, vec![0.5, 0.5, 0.5], None); + assert!(wma.is_err()); + } + + #[rstest] + fn test_value_with_one_input(mut indicator_wma_10: WeightedMovingAverage) { + indicator_wma_10.update_raw(1.0); + assert_eq!(indicator_wma_10.value, 1.0); + } + + #[rstest] + fn test_value_with_two_inputs_equal_weights() { + let mut wma = WeightedMovingAverage::new(2, vec![0.5, 0.5], None).unwrap(); + wma.update_raw(1.0); + wma.update_raw(2.0); + assert_eq!(wma.value, 1.5); + } + + #[rstest] + fn test_value_with_four_inputs_equal_weights() { + let mut wma = WeightedMovingAverage::new(4, vec![0.25, 0.25, 0.25, 0.25], None).unwrap(); + wma.update_raw(1.0); + wma.update_raw(2.0); + wma.update_raw(3.0); + wma.update_raw(4.0); + assert_eq!(wma.value, 2.5); + } + + #[rstest] + fn test_value_with_two_inputs(mut indicator_wma_10: WeightedMovingAverage) { + indicator_wma_10.update_raw(1.0); + indicator_wma_10.update_raw(2.0); + let result = (2.0 * 1.0 + 1.0 * 0.9) / 1.9; + assert_eq!(indicator_wma_10.value, result); + } + + #[rstest] + fn test_value_with_three_inputs(mut indicator_wma_10: WeightedMovingAverage) { + indicator_wma_10.update_raw(1.0); + indicator_wma_10.update_raw(2.0); + indicator_wma_10.update_raw(3.0); + let result = (3.0 * 1.0 + 2.0 * 0.9 + 1.0 * 0.8) / (1.0 + 0.9 + 0.8); + assert_eq!(indicator_wma_10.value, result); + } + + #[rstest] + fn test_value_expected_with_exact_period(mut indicator_wma_10: WeightedMovingAverage) { + for i in 1..11 { + indicator_wma_10.update_raw(i as f64); + } + assert_eq!(indicator_wma_10.value, 7.0); + } + + #[rstest] + fn test_value_expected_with_more_inputs(mut indicator_wma_10: WeightedMovingAverage) { + for i in 1..=11 { + indicator_wma_10.update_raw(i as f64); + } + assert_eq!(indicator_wma_10.value(), 8.0000000000000018); + } + + #[rstest] + fn test_reset(mut indicator_wma_10: WeightedMovingAverage) { + indicator_wma_10.update_raw(1.0); + indicator_wma_10.update_raw(2.0); + indicator_wma_10.reset(); + assert_eq!(indicator_wma_10.value, 0.0); + assert_eq!(indicator_wma_10.count(), 0); + assert!(!indicator_wma_10.has_inputs); + assert!(!indicator_wma_10.is_initialized); + } +} diff --git a/nautilus_core/indicators/src/lib.rs b/nautilus_core/indicators/src/lib.rs index 3044b4fcb80d..da03add6f2c6 100644 --- a/nautilus_core/indicators/src/lib.rs +++ b/nautilus_core/indicators/src/lib.rs @@ -13,8 +13,6 @@ // limitations under the License. // ------------------------------------------------------------------------------------------------- -use pyo3::{prelude::*, types::PyModule, Python}; - pub mod average; pub mod indicator; pub mod momentum; @@ -23,17 +21,5 @@ pub mod ratio; #[cfg(test)] mod stubs; -/// Loaded as nautilus_pyo3.indicators -#[pymodule] -pub fn indicators(_: Python<'_>, m: &PyModule) -> PyResult<()> { - // average - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - // ratio - m.add_class::()?; - // momentum - m.add_class::()?; - Ok(()) -} +#[cfg(feature = "python")] +pub mod python; diff --git a/nautilus_core/indicators/src/momentum/rsi.rs b/nautilus_core/indicators/src/momentum/rsi.rs index bcdc541aef1d..48621c0b76df 100644 --- a/nautilus_core/indicators/src/momentum/rsi.rs +++ b/nautilus_core/indicators/src/momentum/rsi.rs @@ -16,7 +16,6 @@ use std::fmt::{Debug, Display}; use anyhow::Result; -use nautilus_core::python::to_pyvalue_err; use nautilus_model::{ data::{bar::Bar, quote::QuoteTick, trade::TradeTick}, enums::PriceType, @@ -37,13 +36,12 @@ pub struct RelativeStrengthIndex { pub ma_type: MovingAverageType, pub value: f64, pub count: usize, - // pub inputs: Vec, + pub is_initialized: bool, _has_inputs: bool, _last_value: f64, _average_gain: Box, _average_loss: Box, _rsi_max: f64, - is_initialized: bool, } impl Display for RelativeStrengthIndex { @@ -143,69 +141,6 @@ impl RelativeStrengthIndex { } } -#[cfg(feature = "python")] -#[pymethods] -impl RelativeStrengthIndex { - #[new] - pub fn py_new(period: usize, ma_type: Option) -> PyResult { - Self::new(period, ma_type).map_err(to_pyvalue_err) - } - - #[getter] - #[pyo3(name = "name")] - fn py_name(&self) -> String { - self.name() - } - - #[getter] - #[pyo3(name = "period")] - fn py_period(&self) -> usize { - self.period - } - - #[getter] - #[pyo3(name = "count")] - fn py_count(&self) -> usize { - self.count - } - - #[getter] - #[pyo3(name = "value")] - fn py_value(&self) -> f64 { - self.value - } - - #[getter] - #[pyo3(name = "initialized")] - fn py_initialized(&self) -> bool { - self.is_initialized - } - - #[pyo3(name = "update_raw")] - fn py_update_raw(&mut self, value: f64) { - self.update_raw(value); - } - - #[pyo3(name = "handle_quote_tick")] - fn py_handle_quote_tick(&mut self, tick: &QuoteTick) { - self.py_update_raw(tick.extract_price(PriceType::Mid).into()); - } - - #[pyo3(name = "handle_bar")] - fn py_handle_bar(&mut self, bar: &Bar) { - self.update_raw((&bar.close).into()); - } - - #[pyo3(name = "handle_trade_tick")] - fn py_handle_trade_tick(&mut self, tick: &TradeTick) { - self.update_raw((&tick.price).into()); - } - - fn __repr__(&self) -> String { - format!("ExponentialMovingAverage({})", self.period) - } -} - //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// diff --git a/nautilus_core/indicators/src/python/average/ama.rs b/nautilus_core/indicators/src/python/average/ama.rs new file mode 100644 index 000000000000..d27568e7b50d --- /dev/null +++ b/nautilus_core/indicators/src/python/average/ama.rs @@ -0,0 +1,104 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (C) 2015-2023 Nautech Systems Pty Ltd. All rights reserved. +// https://nautechsystems.io +// +// Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------------------------------- + +use nautilus_core::python::to_pyvalue_err; +use nautilus_model::{ + data::{bar::Bar, quote::QuoteTick, trade::TradeTick}, + enums::PriceType, +}; +use pyo3::prelude::*; + +use crate::{ + average::ama::AdaptiveMovingAverage, + indicator::{Indicator, MovingAverage}, +}; + +#[pymethods] +impl AdaptiveMovingAverage { + #[new] + pub fn py_new( + period_efficiency_ratio: usize, + period_fast: usize, + period_slow: usize, + price_type: Option, + ) -> PyResult { + Self::new( + period_efficiency_ratio, + period_fast, + period_slow, + price_type, + ) + .map_err(to_pyvalue_err) + } + + #[getter] + #[pyo3(name = "name")] + fn py_name(&self) -> String { + self.name() + } + + #[getter] + #[pyo3(name = "count")] + fn py_count(&self) -> usize { + self.count + } + + #[getter] + #[pyo3(name = "has_inputs")] + fn py_has_inputs(&self) -> bool { + self.has_inputs() + } + + #[getter] + #[pyo3(name = "initialized")] + fn py_initialized(&self) -> bool { + self.is_initialized + } + + #[pyo3(name = "handle_quote_tick")] + fn py_handle_quote_tick(&mut self, tick: &QuoteTick) { + self.py_update_raw(tick.extract_price(self.price_type).into()); + } + + #[pyo3(name = "handle_trade_tick")] + fn py_handle_trade_tick(&mut self, tick: &TradeTick) { + self.update_raw((&tick.price).into()); + } + + #[pyo3(name = "handle_bar")] + fn py_handle_bar(&mut self, bar: &Bar) { + self.update_raw((&bar.close).into()); + } + + #[pyo3(name = "reset")] + fn py_reset(&mut self) { + self.reset(); + } + + #[pyo3(name = "update_raw")] + fn py_update_raw(&mut self, value: f64) { + self.update_raw(value); + } + + fn __repr__(&self) -> String { + format!( + "WeightedMovingAverage({}({},{},{})", + self.name(), + self.period_efficiency_ratio, + self.period_fast, + self.period_slow + ) + } +} diff --git a/nautilus_core/indicators/src/python/average/dema.rs b/nautilus_core/indicators/src/python/average/dema.rs new file mode 100644 index 000000000000..71f0cc6b5784 --- /dev/null +++ b/nautilus_core/indicators/src/python/average/dema.rs @@ -0,0 +1,99 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (C) 2015-2023 Nautech Systems Pty Ltd. All rights reserved. +// https://nautechsystems.io +// +// Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------------------------------- + +use nautilus_core::python::to_pyvalue_err; +use nautilus_model::{ + data::{bar::Bar, quote::QuoteTick, trade::TradeTick}, + enums::PriceType, +}; +use pyo3::prelude::*; + +use crate::{ + average::dema::DoubleExponentialMovingAverage, + indicator::{Indicator, MovingAverage}, +}; + +#[pymethods] +impl DoubleExponentialMovingAverage { + #[new] + fn py_new(period: usize, price_type: Option) -> PyResult { + Self::new(period, price_type).map_err(to_pyvalue_err) + } + + #[getter] + #[pyo3(name = "name")] + fn py_name(&self) -> String { + self.name() + } + + #[getter] + #[pyo3(name = "period")] + fn py_period(&self) -> usize { + self.period + } + + #[getter] + #[pyo3(name = "count")] + fn py_count(&self) -> usize { + self.count + } + + #[getter] + #[pyo3(name = "value")] + fn py_value(&self) -> f64 { + self.value + } + + #[getter] + #[pyo3(name = "has_inputs")] + fn py_has_inputs(&self) -> bool { + self.has_inputs() + } + + #[getter] + #[pyo3(name = "initialized")] + fn py_initialized(&self) -> bool { + self.is_initialized + } + + #[pyo3(name = "handle_quote_tick")] + fn py_handle_quote_tick(&mut self, tick: &QuoteTick) { + self.py_update_raw(tick.extract_price(self.price_type).into()); + } + + #[pyo3(name = "handle_trade_tick")] + fn py_handle_trade_tick(&mut self, tick: &TradeTick) { + self.update_raw((&tick.price).into()); + } + + #[pyo3(name = "handle_bar")] + fn py_handle_bar(&mut self, bar: &Bar) { + self.update_raw((&bar.close).into()); + } + + #[pyo3(name = "reset")] + fn py_reset(&mut self) { + self.reset(); + } + + #[pyo3(name = "update_raw")] + fn py_update_raw(&mut self, value: f64) { + self.update_raw(value); + } + + fn __repr__(&self) -> String { + format!("DoubleExponentialMovingAverage({})", self.period) + } +} diff --git a/nautilus_core/indicators/src/python/average/ema.rs b/nautilus_core/indicators/src/python/average/ema.rs new file mode 100644 index 000000000000..ff0977751d2a --- /dev/null +++ b/nautilus_core/indicators/src/python/average/ema.rs @@ -0,0 +1,105 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (C) 2015-2023 Nautech Systems Pty Ltd. All rights reserved. +// https://nautechsystems.io +// +// Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------------------------------- + +use nautilus_core::python::to_pyvalue_err; +use nautilus_model::{ + data::{bar::Bar, quote::QuoteTick, trade::TradeTick}, + enums::PriceType, +}; +use pyo3::prelude::*; + +use crate::{ + average::ema::ExponentialMovingAverage, + indicator::{Indicator, MovingAverage}, +}; + +#[pymethods] +impl ExponentialMovingAverage { + #[new] + fn py_new(period: usize, price_type: Option) -> PyResult { + Self::new(period, price_type).map_err(to_pyvalue_err) + } + + #[getter] + #[pyo3(name = "name")] + fn py_name(&self) -> String { + self.name() + } + + #[getter] + #[pyo3(name = "period")] + fn py_period(&self) -> usize { + self.period + } + + #[getter] + #[pyo3(name = "alpha")] + fn py_alpha(&self) -> f64 { + self.alpha + } + + #[getter] + #[pyo3(name = "count")] + fn py_count(&self) -> usize { + self.count + } + + #[getter] + #[pyo3(name = "value")] + fn py_value(&self) -> f64 { + self.value + } + + #[getter] + #[pyo3(name = "has_inputs")] + fn py_has_inputs(&self) -> bool { + self.has_inputs() + } + + #[getter] + #[pyo3(name = "initialized")] + fn py_initialized(&self) -> bool { + self.is_initialized + } + + #[pyo3(name = "handle_quote_tick")] + fn py_handle_quote_tick(&mut self, tick: &QuoteTick) { + self.py_update_raw(tick.extract_price(self.price_type).into()); + } + + #[pyo3(name = "handle_trade_tick")] + fn py_handle_trade_tick(&mut self, tick: &TradeTick) { + self.update_raw((&tick.price).into()); + } + + #[pyo3(name = "handle_bar")] + fn py_handle_bar(&mut self, bar: &Bar) { + self.update_raw((&bar.close).into()); + } + + #[pyo3(name = "reset")] + fn py_reset(&mut self) { + self.reset(); + } + + #[pyo3(name = "update_raw")] + fn py_update_raw(&mut self, value: f64) { + self.update_raw(value); + } + + fn __repr__(&self) -> String { + format!("ExponentialMovingAverage({})", self.period) + } +} diff --git a/nautilus_core/indicators/src/python/average/mod.rs b/nautilus_core/indicators/src/python/average/mod.rs new file mode 100644 index 000000000000..521a97e997ec --- /dev/null +++ b/nautilus_core/indicators/src/python/average/mod.rs @@ -0,0 +1,20 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (C) 2015-2023 Nautech Systems Pty Ltd. All rights reserved. +// https://nautechsystems.io +// +// Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------------------------------- + +pub mod ama; +pub mod dema; +pub mod ema; +pub mod sma; +pub mod wma; diff --git a/nautilus_core/indicators/src/python/average/sma.rs b/nautilus_core/indicators/src/python/average/sma.rs new file mode 100644 index 000000000000..1b297e95db03 --- /dev/null +++ b/nautilus_core/indicators/src/python/average/sma.rs @@ -0,0 +1,99 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (C) 2015-2023 Nautech Systems Pty Ltd. All rights reserved. +// https://nautechsystems.io +// +// Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------------------------------- + +use nautilus_core::python::to_pyvalue_err; +use nautilus_model::{ + data::{bar::Bar, quote::QuoteTick, trade::TradeTick}, + enums::PriceType, +}; +use pyo3::prelude::*; + +use crate::{ + average::sma::SimpleMovingAverage, + indicator::{Indicator, MovingAverage}, +}; + +#[pymethods] +impl SimpleMovingAverage { + #[new] + fn py_new(period: usize, price_type: Option) -> PyResult { + Self::new(period, price_type).map_err(to_pyvalue_err) + } + + #[getter] + #[pyo3(name = "name")] + fn py_name(&self) -> String { + self.name() + } + + #[getter] + #[pyo3(name = "period")] + fn py_period(&self) -> usize { + self.period + } + + #[getter] + #[pyo3(name = "count")] + fn py_count(&self) -> usize { + self.count + } + + #[getter] + #[pyo3(name = "value")] + fn py_value(&self) -> f64 { + self.value + } + + #[getter] + #[pyo3(name = "has_inputs")] + fn py_has_inputs(&self) -> bool { + self.has_inputs() + } + + #[getter] + #[pyo3(name = "initialized")] + fn py_initialized(&self) -> bool { + self.is_initialized + } + + #[pyo3(name = "handle_quote_tick")] + fn py_handle_quote_tick(&mut self, tick: &QuoteTick) { + self.py_update_raw(tick.extract_price(self.price_type).into()); + } + + #[pyo3(name = "handle_trade_tick")] + fn py_handle_trade_tick(&mut self, tick: &TradeTick) { + self.update_raw((&tick.price).into()); + } + + #[pyo3(name = "handle_bar")] + fn py_handle_bar(&mut self, bar: &Bar) { + self.update_raw((&bar.close).into()); + } + + #[pyo3(name = "reset")] + fn py_reset(&mut self) { + self.reset(); + } + + #[pyo3(name = "update_raw")] + fn py_update_raw(&mut self, value: f64) { + self.update_raw(value); + } + + fn __repr__(&self) -> String { + format!("SimpleMovingAverage({})", self.period) + } +} diff --git a/nautilus_core/indicators/src/python/average/wma.rs b/nautilus_core/indicators/src/python/average/wma.rs new file mode 100644 index 000000000000..84d212d6cc7a --- /dev/null +++ b/nautilus_core/indicators/src/python/average/wma.rs @@ -0,0 +1,97 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (C) 2015-2023 Nautech Systems Pty Ltd. All rights reserved. +// https://nautechsystems.io +// +// Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------------------------------- + +use nautilus_core::python::to_pyvalue_err; +use nautilus_model::{ + data::{bar::Bar, quote::QuoteTick, trade::TradeTick}, + enums::PriceType, +}; +use pyo3::prelude::*; + +use crate::{ + average::wma::WeightedMovingAverage, + indicator::{Indicator, MovingAverage}, +}; + +#[pymethods] +impl WeightedMovingAverage { + #[new] + pub fn py_new( + period: usize, + weights: Vec, + price_type: Option, + ) -> PyResult { + Self::new(period, weights, price_type).map_err(to_pyvalue_err) + } + + #[getter] + #[pyo3(name = "name")] + fn py_name(&self) -> String { + self.name() + } + + #[getter] + #[pyo3(name = "period")] + fn py_period(&self) -> usize { + self.period + } + + #[getter] + #[pyo3(name = "count")] + fn py_count(&self) -> usize { + self.count() + } + + #[getter] + #[pyo3(name = "has_inputs")] + fn py_has_inputs(&self) -> bool { + self.has_inputs() + } + + #[getter] + #[pyo3(name = "initialized")] + fn py_initialized(&self) -> bool { + self.is_initialized + } + + #[pyo3(name = "handle_quote_tick")] + fn py_handle_quote_tick(&mut self, tick: &QuoteTick) { + self.py_update_raw(tick.extract_price(self.price_type).into()); + } + + #[pyo3(name = "handle_trade_tick")] + fn py_handle_trade_tick(&mut self, tick: &TradeTick) { + self.update_raw((&tick.price).into()); + } + + #[pyo3(name = "handle_bar")] + fn py_handle_bar(&mut self, bar: &Bar) { + self.update_raw((&bar.close).into()); + } + + #[pyo3(name = "reset")] + fn py_reset(&mut self) { + self.reset(); + } + + #[pyo3(name = "update_raw")] + fn py_update_raw(&mut self, value: f64) { + self.update_raw(value); + } + + fn __repr__(&self) -> String { + format!("WeightedMovingAverage({},{:?})", self.period, self.weights) + } +} diff --git a/nautilus_core/indicators/src/python/mod.rs b/nautilus_core/indicators/src/python/mod.rs new file mode 100644 index 000000000000..d5e208f9c045 --- /dev/null +++ b/nautilus_core/indicators/src/python/mod.rs @@ -0,0 +1,34 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (C) 2015-2023 Nautech Systems Pty Ltd. All rights reserved. +// https://nautechsystems.io +// +// Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------------------------------- + +use pyo3::{prelude::*, pymodule}; + +pub mod average; +pub mod momentum; +pub mod ratio; + +#[pymodule] +pub fn indicators(_: Python<'_>, m: &PyModule) -> PyResult<()> { + // average + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + // ratio + m.add_class::()?; + // momentum + m.add_class::()?; + Ok(()) +} diff --git a/nautilus_core/indicators/src/python/momentum/mod.rs b/nautilus_core/indicators/src/python/momentum/mod.rs new file mode 100644 index 000000000000..fc49c3b4cf36 --- /dev/null +++ b/nautilus_core/indicators/src/python/momentum/mod.rs @@ -0,0 +1,16 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (C) 2015-2023 Nautech Systems Pty Ltd. All rights reserved. +// https://nautechsystems.io +// +// Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------------------------------- + +pub mod rsi; diff --git a/nautilus_core/indicators/src/python/momentum/rsi.rs b/nautilus_core/indicators/src/python/momentum/rsi.rs new file mode 100644 index 000000000000..ae87fd3bce52 --- /dev/null +++ b/nautilus_core/indicators/src/python/momentum/rsi.rs @@ -0,0 +1,87 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (C) 2015-2023 Nautech Systems Pty Ltd. All rights reserved. +// https://nautechsystems.io +// +// Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------------------------------- + +use nautilus_core::python::to_pyvalue_err; +use nautilus_model::{ + data::{bar::Bar, quote::QuoteTick, trade::TradeTick}, + enums::PriceType, +}; +use pyo3::prelude::*; + +use crate::{ + average::MovingAverageType, indicator::Indicator, momentum::rsi::RelativeStrengthIndex, +}; + +#[pymethods] +impl RelativeStrengthIndex { + #[new] + pub fn py_new(period: usize, ma_type: Option) -> PyResult { + Self::new(period, ma_type).map_err(to_pyvalue_err) + } + + #[getter] + #[pyo3(name = "name")] + fn py_name(&self) -> String { + self.name() + } + + #[getter] + #[pyo3(name = "period")] + fn py_period(&self) -> usize { + self.period + } + + #[getter] + #[pyo3(name = "count")] + fn py_count(&self) -> usize { + self.count + } + + #[getter] + #[pyo3(name = "value")] + fn py_value(&self) -> f64 { + self.value + } + + #[getter] + #[pyo3(name = "initialized")] + fn py_initialized(&self) -> bool { + self.is_initialized + } + + #[pyo3(name = "update_raw")] + fn py_update_raw(&mut self, value: f64) { + self.update_raw(value); + } + + #[pyo3(name = "handle_quote_tick")] + fn py_handle_quote_tick(&mut self, tick: &QuoteTick) { + self.py_update_raw(tick.extract_price(PriceType::Mid).into()); + } + + #[pyo3(name = "handle_bar")] + fn py_handle_bar(&mut self, bar: &Bar) { + self.update_raw((&bar.close).into()); + } + + #[pyo3(name = "handle_trade_tick")] + fn py_handle_trade_tick(&mut self, tick: &TradeTick) { + self.update_raw((&tick.price).into()); + } + + fn __repr__(&self) -> String { + format!("ExponentialMovingAverage({})", self.period) + } +} diff --git a/nautilus_core/indicators/src/python/ratio/efficiency_ratio.rs b/nautilus_core/indicators/src/python/ratio/efficiency_ratio.rs new file mode 100644 index 000000000000..43ee463e2721 --- /dev/null +++ b/nautilus_core/indicators/src/python/ratio/efficiency_ratio.rs @@ -0,0 +1,66 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (C) 2015-2023 Nautech Systems Pty Ltd. All rights reserved. +// https://nautechsystems.io +// +// Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------------------------------- + +use nautilus_core::python::to_pyvalue_err; +use nautilus_model::enums::PriceType; +use pyo3::prelude::*; + +use crate::{indicator::Indicator, ratio::efficiency_ratio::EfficiencyRatio}; + +#[pymethods] +impl EfficiencyRatio { + #[new] + fn py_new(period: usize, price_type: Option) -> PyResult { + Self::new(period, price_type).map_err(to_pyvalue_err) + } + + #[getter] + #[pyo3(name = "name")] + fn py_name(&self) -> String { + self.name() + } + + #[getter] + #[pyo3(name = "period")] + fn py_period(&self) -> usize { + self.period + } + + #[getter] + #[pyo3(name = "value")] + fn py_value(&self) -> f64 { + self.value + } + + #[getter] + #[pyo3(name = "initialized")] + fn py_initialized(&self) -> bool { + self.is_initialized + } + + #[pyo3(name = "has_inputs")] + fn py_has_inputs(&self) -> bool { + self.has_inputs() + } + + #[pyo3(name = "update_raw")] + fn py_update_raw(&mut self, value: f64) { + self.update_raw(value); + } + + fn __repr__(&self) -> String { + format!("EfficiencyRatio({})", self.period) + } +} diff --git a/nautilus_core/indicators/src/python/ratio/mod.rs b/nautilus_core/indicators/src/python/ratio/mod.rs new file mode 100644 index 000000000000..1d3e5ababfb9 --- /dev/null +++ b/nautilus_core/indicators/src/python/ratio/mod.rs @@ -0,0 +1,16 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (C) 2015-2023 Nautech Systems Pty Ltd. All rights reserved. +// https://nautechsystems.io +// +// Licensed under the GNU Lesser General Public License Version 3.0 (the "License"); +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------------------------------- + +pub mod efficiency_ratio; diff --git a/nautilus_core/indicators/src/ratio/efficiency_ratio.rs b/nautilus_core/indicators/src/ratio/efficiency_ratio.rs index c8345bb88b86..25fb558a60a1 100644 --- a/nautilus_core/indicators/src/ratio/efficiency_ratio.rs +++ b/nautilus_core/indicators/src/ratio/efficiency_ratio.rs @@ -16,7 +16,6 @@ use std::fmt::Display; use anyhow::Result; -use nautilus_core::python::to_pyvalue_err; use nautilus_model::{ data::{bar::Bar, quote::QuoteTick, trade::TradeTick}, enums::PriceType, @@ -37,8 +36,8 @@ pub struct EfficiencyRatio { pub price_type: PriceType, pub value: f64, pub inputs: Vec, + pub is_initialized: bool, _deltas: Vec, - is_initialized: bool, } impl Display for EfficiencyRatio { @@ -111,53 +110,6 @@ impl EfficiencyRatio { } } -#[cfg(feature = "python")] -#[pymethods] -impl EfficiencyRatio { - #[new] - fn py_new(period: usize, price_type: Option) -> PyResult { - Self::new(period, price_type).map_err(to_pyvalue_err) - } - - #[getter] - #[pyo3(name = "name")] - fn py_name(&self) -> String { - self.name() - } - - #[getter] - #[pyo3(name = "period")] - fn py_period(&self) -> usize { - self.period - } - - #[getter] - #[pyo3(name = "value")] - fn py_value(&self) -> f64 { - self.value - } - - #[getter] - #[pyo3(name = "initialized")] - fn py_initialized(&self) -> bool { - self.is_initialized - } - - #[pyo3(name = "has_inputs")] - fn py_has_inputs(&self) -> bool { - self.has_inputs() - } - - #[pyo3(name = "update_raw")] - fn py_update_raw(&mut self, value: f64) { - self.update_raw(value); - } - - fn __repr__(&self) -> String { - format!("EfficiencyRatio({})", self.period) - } -} - //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// diff --git a/nautilus_core/indicators/src/stubs.rs b/nautilus_core/indicators/src/stubs.rs index d6ae2566938e..a13295d228e6 100644 --- a/nautilus_core/indicators/src/stubs.rs +++ b/nautilus_core/indicators/src/stubs.rs @@ -27,7 +27,8 @@ use rstest::*; use crate::{ average::{ ama::AdaptiveMovingAverage, dema::DoubleExponentialMovingAverage, - ema::ExponentialMovingAverage, sma::SimpleMovingAverage, MovingAverageType, + ema::ExponentialMovingAverage, sma::SimpleMovingAverage, wma::WeightedMovingAverage, + MovingAverageType, }, momentum::rsi::RelativeStrengthIndex, ratio::efficiency_ratio::EfficiencyRatio, @@ -117,6 +118,12 @@ pub fn indicator_dema_10() -> DoubleExponentialMovingAverage { DoubleExponentialMovingAverage::new(10, Some(PriceType::Mid)).unwrap() } +#[fixture] +pub fn indicator_wma_10() -> WeightedMovingAverage { + let weights = vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]; + WeightedMovingAverage::new(10, weights, Some(PriceType::Mid)).unwrap() +} + //////////////////////////////////////////////////////////////////////////////// // Ratios //////////////////////////////////////////////////////////////////////////////// diff --git a/nautilus_core/pyo3/src/lib.rs b/nautilus_core/pyo3/src/lib.rs index e4f639e3f77e..b813d0371c49 100644 --- a/nautilus_core/pyo3/src/lib.rs +++ b/nautilus_core/pyo3/src/lib.rs @@ -126,7 +126,7 @@ fn nautilus_pyo3(py: Python<'_>, m: &PyModule) -> PyResult<()> { // Indicators let n = "indicators"; - let submodule = pyo3::wrap_pymodule!(nautilus_indicators::indicators); + let submodule = pyo3::wrap_pymodule!(nautilus_indicators::python::indicators); m.add_wrapped(submodule)?; sys_modules.set_item(format!("{module_name}.{n}"), m.getattr(n)?)?; re_export_module_attributes(m, n)?;