Skip to content

Commit

Permalink
Add Rust indicator tests
Browse files Browse the repository at this point in the history
  • Loading branch information
cjdsellers committed Mar 29, 2024
1 parent b60b383 commit d796277
Show file tree
Hide file tree
Showing 14 changed files with 325 additions and 1,206 deletions.
1 change: 1 addition & 0 deletions nautilus_core/indicators/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub mod book;
pub mod indicator;
pub mod momentum;
pub mod ratio;
pub mod testing;
pub mod volatility;

#[cfg(test)]
Expand Down
93 changes: 93 additions & 0 deletions nautilus_core/indicators/src/momentum/aroon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,96 @@ impl AroonOscillator {
}
}
}

////////////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////////////
#[cfg(test)]
mod tests {
use rstest::rstest;

use super::*;
use crate::indicator::Indicator;

#[rstest]
fn test_name_returns_expected_string() {
let aroon = AroonOscillator::new(10).unwrap();
assert_eq!(aroon.name(), "AroonOscillator");
}

#[rstest]
fn test_period() {
let aroon = AroonOscillator::new(10).unwrap();
assert_eq!(aroon.period, 10);
}

#[rstest]
fn test_initialized_without_inputs_returns_false() {
let aroon = AroonOscillator::new(10).unwrap();
assert!(!aroon.initialized());
}

#[rstest]
fn test_initialized_with_required_inputs_returns_true() {
let mut aroon = AroonOscillator::new(10).unwrap();
for _ in 0..20 {
aroon.update_raw(110.08, 109.61);
}
assert!(aroon.initialized());
}

#[rstest]
fn test_value_with_one_input() {
let mut aroon = AroonOscillator::new(1).unwrap();
aroon.update_raw(110.08, 109.61);
assert_eq!(aroon.aroon_up, 100.0);
assert_eq!(aroon.aroon_down, 100.0);
assert_eq!(aroon.value, 0.0);
}

#[rstest]
fn test_value_with_twenty_inputs() {
let mut aroon = AroonOscillator::new(20).unwrap();
let inputs = [
(110.08, 109.61),
(110.15, 109.91),
(110.1, 109.73),
(110.06, 109.77),
(110.29, 109.88),
(110.53, 110.29),
(110.61, 110.26),
(110.28, 110.17),
(110.3, 110.0),
(110.25, 110.01),
(110.25, 109.81),
(109.92, 109.71),
(110.21, 109.84),
(110.08, 109.95),
(110.2, 109.96),
(110.16, 109.95),
(109.99, 109.75),
(110.2, 109.73),
(110.1, 109.81),
(110.04, 109.96),
];
for &(high, low) in &inputs {
aroon.update_raw(high, low);
}
assert_eq!(aroon.aroon_up, 35.0);
assert_eq!(aroon.aroon_down, 5.0);
assert_eq!(aroon.value, 30.0);
}

#[rstest]
fn test_reset_successfully_returns_indicator_to_fresh_state() {
let mut aroon = AroonOscillator::new(10).unwrap();
for _ in 0..1000 {
aroon.update_raw(110.08, 109.61);
}
aroon.reset();
assert!(!aroon.initialized());
assert_eq!(aroon.aroon_up, 0.0);
assert_eq!(aroon.aroon_down, 0.0);
assert_eq!(aroon.value, 0.0);
}
}
74 changes: 74 additions & 0 deletions nautilus_core/indicators/src/momentum/bias.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,77 @@ impl Bias {
}
}
}

////////////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////////////
#[cfg(test)]
mod tests {
use rstest::{fixture, rstest};

use super::*;
use crate::testing::approx_equal;

#[fixture]
fn bias() -> Bias {
Bias::new(10, None).unwrap()
}

#[rstest]
fn test_name_returns_expected_string(bias: Bias) {
assert_eq!(bias.name(), "Bias");
}

#[rstest]
fn test_str_repr_returns_expected_string(bias: Bias) {
assert_eq!(format!("{bias}"), "Bias(10,SIMPLE)");
}

#[rstest]
fn test_period_returns_expected_value(bias: Bias) {
assert_eq!(bias.period, 10);
}

#[rstest]
fn test_initialized_without_inputs_returns_false(bias: Bias) {
assert!(!bias.initialized());
}

#[rstest]
fn test_initialized_with_required_inputs_returns_true(mut bias: Bias) {
for i in 1..=10 {
bias.update_raw(f64::from(i));
}
assert!(bias.initialized());
}

#[rstest]
fn test_value_with_one_input_returns_expected_value(mut bias: Bias) {
bias.update_raw(1.0);
assert_eq!(bias.value, 0.0);
}

#[rstest]
fn test_value_with_all_higher_inputs_returns_expected_value(mut bias: Bias) {
let inputs = [
109.93, 110.0, 109.77, 109.96, 110.29, 110.53, 110.27, 110.21, 110.06, 110.19, 109.83,
109.9, 110.0, 110.03, 110.13, 109.95, 109.75, 110.15, 109.9, 110.04,
];
for input in &inputs {
bias.update_raw(*input);
}
assert!(approx_equal(bias.value, 0.000_654_735_923_177_662_8));
}

#[rstest]
fn test_reset_successfully_returns_indicator_to_fresh_state(mut bias: Bias) {
bias.update_raw(1.00020);
bias.update_raw(1.00030);
bias.update_raw(1.00050);

bias.reset();

assert!(!bias.initialized());
assert_eq!(bias.value, 0.0);
}
}
3 changes: 2 additions & 1 deletion nautilus_core/indicators/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
/// let b = 0.3;
/// assert!(approx_equal(a, b));
/// ```
#[must_use] pub fn approx_equal(a: f64, b: f64) -> bool {
#[must_use]
pub fn approx_equal(a: f64, b: f64) -> bool {
(a - b).abs() < f64::EPSILON
}
155 changes: 155 additions & 0 deletions nautilus_core/indicators/src/volatility/atr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,158 @@ impl AverageTrueRange {
}
}
}

////////////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////////////
#[cfg(test)]
mod tests {
use rstest::rstest;

use super::*;
use crate::testing::approx_equal;

#[rstest]
fn test_name_returns_expected_string() {
let atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap();
assert_eq!(atr.name(), "AverageTrueRange");
}

#[rstest]
fn test_str_repr_returns_expected_string() {
let atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), Some(true), Some(0.0))
.unwrap();
assert_eq!(format!("{atr}"), "AverageTrueRange(10,SIMPLE,true,0)");
}

#[rstest]
fn test_period() {
let atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap();
assert_eq!(atr.period, 10);
}

#[rstest]
fn test_initialized_without_inputs_returns_false() {
let atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap();
assert!(!atr.initialized());
}

#[rstest]
fn test_initialized_with_required_inputs_returns_true() {
let mut atr =
AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap();
for _ in 0..10 {
atr.update_raw(1.0, 1.0, 1.0);
}
assert!(atr.initialized());
}

#[rstest]
fn test_value_with_no_inputs_returns_zero() {
let atr = AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap();
assert_eq!(atr.value, 0.0);
}

#[rstest]
fn test_value_with_epsilon_input() {
let mut atr =
AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap();
let epsilon = std::f64::EPSILON;
atr.update_raw(epsilon, epsilon, epsilon);
assert_eq!(atr.value, 0.0);
}

#[rstest]
fn test_value_with_one_ones_input() {
let mut atr =
AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap();
atr.update_raw(1.0, 1.0, 1.0);
assert_eq!(atr.value, 0.0);
}

#[rstest]
fn test_value_with_one_input() {
let mut atr =
AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap();
atr.update_raw(1.00020, 1.0, 1.00010);
assert!(approx_equal(atr.value, 0.0002));
}

#[rstest]
fn test_value_with_three_inputs() {
let mut atr =
AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap();
atr.update_raw(1.00020, 1.0, 1.00010);
atr.update_raw(1.00020, 1.0, 1.00010);
atr.update_raw(1.00020, 1.0, 1.00010);
assert!(approx_equal(atr.value, 0.0002));
}

#[rstest]
fn test_value_with_close_on_high() {
let mut atr =
AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap();
let mut high = 1.00010;
let mut low = 1.0;
for _ in 0..1000 {
high += 0.00010;
low += 0.00010;
let close = high;
atr.update_raw(high, low, close);
}
assert!(approx_equal(atr.value, 0.000_099_999_999_999_988_99));
}

#[rstest]
fn test_value_with_close_on_low() {
let mut atr =
AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap();
let mut high = 1.00010;
let mut low = 1.0;
for _ in 0..1000 {
high -= 0.00010;
low -= 0.00010;
let close = low;
atr.update_raw(high, low, close);
}
assert!(approx_equal(atr.value, 0.000_099_999_999_999_988_99));
}

#[rstest]
fn test_floor_with_ten_ones_inputs() {
let floor = 0.00005;
let mut floored_atr =
AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, Some(floor)).unwrap();
for _ in 0..20 {
floored_atr.update_raw(1.0, 1.0, 1.0);
}
assert_eq!(floored_atr.value, 5e-05);
}

#[rstest]
fn test_floor_with_exponentially_decreasing_high_inputs() {
let floor = 0.00005;
let mut floored_atr =
AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, Some(floor)).unwrap();
let mut high = 1.00020;
let low = 1.0;
let close = 1.0;
for _ in 0..20 {
high -= (high - low) / 2.0;
floored_atr.update_raw(high, low, close);
}
assert_eq!(floored_atr.value, floor);
}

#[rstest]
fn test_reset_successfully_returns_indicator_to_fresh_state() {
let mut atr =
AverageTrueRange::new(10, Some(MovingAverageType::Simple), None, None).unwrap();
for _ in 0..1000 {
atr.update_raw(1.00010, 1.0, 1.00005);
}
atr.reset();
assert!(!atr.initialized);
assert_eq!(atr.value, 0.0);
}
}
14 changes: 0 additions & 14 deletions tests/unit_tests/indicators/rust/__init__.py

This file was deleted.

Loading

0 comments on commit d796277

Please sign in to comment.