Skip to content

Commit b361565

Browse files
authored
Merge pull request #1 from Flicker-Finance/0.1.1.r.c
feat(improvements): changing logic, fixing bugs and adding tests and docs
2 parents 663c3b3 + 33344e1 commit b361565

32 files changed

+1708
-200
lines changed

.github/workflows/rust.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Rust
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
9+
env:
10+
CARGO_TERM_COLOR: always
11+
12+
jobs:
13+
build:
14+
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- uses: actions/checkout@v4
19+
- name: Build
20+
run: cargo build --verbose
21+
- name: Run tests
22+
run: cargo test --verbose

src/lib.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,54 @@
11
pub mod v1 {
22
pub mod atr {
3+
mod __tests__;
34
pub mod main;
45
}
56
pub mod bollinger {
7+
mod __tests__;
68
pub mod main;
79
pub mod types;
810
}
911
pub mod ema {
10-
pub mod __tests__;
12+
mod __tests__;
1113
pub mod main;
1214
}
1315
pub mod ma {
16+
mod __tests__;
1417
pub mod main;
1518
}
1619
pub mod macd {
17-
pub mod __tests__;
20+
mod __tests__;
1821
pub mod main;
1922
pub mod types;
2023
}
2124

2225
pub mod rsi {
23-
pub mod __tests__;
26+
mod __tests__;
2427
pub mod main;
2528
pub mod types;
2629
}
2730
pub mod sma {
28-
pub mod __tests__;
31+
mod __tests__;
2932
pub mod main;
3033
pub mod types;
3134
}
3235
pub mod roc {
36+
mod __tests__;
3337
pub mod main;
3438
pub mod types;
3539
}
3640
pub mod momentum {
41+
mod __tests__;
3742
pub mod main;
3843
pub mod types;
3944
}
4045
pub mod stochastic {
46+
mod __tests__;
4147
pub mod main;
4248
pub mod types;
4349
}
4450
pub mod support_resistance {
51+
mod _tests__;
4552
pub mod main;
4653
pub mod types;
4754
}

src/v1/atr/__tests__.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#[cfg(test)]
2+
mod tests {
3+
use crate::v1::atr::main::ATR;
4+
5+
#[test]
6+
fn test_insufficient_data() {
7+
let mut atr = ATR::new(5);
8+
// Feed fewer than 5 values.
9+
for price in [100.0, 101.0, 102.0, 103.0] {
10+
assert!(atr.calculate(price).is_none());
11+
}
12+
}
13+
14+
#[test]
15+
fn test_atr_calculation() {
16+
let mut atr = ATR::new(3);
17+
// For a period of 3, we need exactly 3 true range values.
18+
// Let’s feed: first price: 100.0 (no true range), then 102.0, then 101.0, then 103.0.
19+
// True ranges will be:
20+
// - When 102.0 is fed: |102.0 - 100.0| = 2.0
21+
// - When 101.0 is fed: |101.0 - 102.0| = 1.0
22+
// - When 103.0 is fed: |103.0 - 101.0| = 2.0
23+
// The sliding window will then be [2.0, 1.0, 2.0] and ATR = (2+1+2)/3 = 1.67 approximately.
24+
let prices = vec![100.0, 102.0, 101.0, 103.0];
25+
let mut result = None;
26+
for price in prices {
27+
result = atr.calculate(price);
28+
}
29+
let atr_value = result.unwrap();
30+
assert!((atr_value - 1.67).abs() < 0.05);
31+
}
32+
33+
#[test]
34+
fn test_sliding_window_update() {
35+
let mut atr = ATR::new(3);
36+
// Feed more than 3 values to ensure the window slides correctly.
37+
let prices = vec![100.0, 102.0, 101.0, 103.0, 104.0];
38+
let mut last_atr = None;
39+
for price in prices {
40+
last_atr = atr.calculate(price);
41+
}
42+
// When the window has slid, the ATR should be calculated using the latest 3 true ranges.
43+
// Let's compute them:
44+
// For prices: [101.0, 103.0, 104.0]
45+
// True ranges: |103.0 - 101.0| = 2.0, |104.0 - 103.0| = 1.0, and the first true range (for price 101.0) would be from the previous sliding window.
46+
// However, for this test we simply check that an ATR is produced.
47+
assert!(last_atr.is_some());
48+
}
49+
}

src/v1/atr/main.rs

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,89 @@
1+
//! # Average True Range (ATR) Module
2+
//!
3+
//! This module implements a simplified Average True Range (ATR) indicator.
4+
//! In this version, the true range is calculated as the absolute difference between the
5+
//! current closing price and the previous closing price. (Note that the full ATR calculation
6+
//! typically uses the high, low, and previous close values, but this simplified version
7+
//! uses only the close values.)
8+
//!
9+
//! The ATR is computed as the average of the true range over a specified period.
10+
//!
11+
//! # Examples
12+
//!
13+
//! ```rust
14+
//! use indexes_rs::v1::atr::main::ATR;
15+
//!
16+
//! // Create an ATR indicator with a period of 14
17+
//! let mut atr = ATR::new(14);
18+
//!
19+
//! // Feed a series of closing prices
20+
//! let prices = vec![100.0, 101.0, 100.5, 102.0, 101.5, 102.5, 103.0, 102.0, 101.0, 100.0,
21+
//! 100.5, 101.0, 102.0, 101.5, 102.5];
22+
//!
23+
//! for price in prices {
24+
//! if let Some(atr_value) = atr.calculate(price) {
25+
//! println!("ATR: {:.2}", atr_value);
26+
//! }
27+
//! }
28+
//! ```
29+
30+
use std::collections::VecDeque;
31+
32+
/// A simplified Average True Range (ATR) indicator.
33+
///
34+
/// This ATR calculates the true range as the absolute difference between the current closing price and the previous closing price.
35+
/// It then computes the ATR as the average of these true ranges over a specified period.
136
pub struct ATR {
37+
/// The period over which to calculate the ATR.
238
period: usize,
39+
/// A sliding window of true range values.
40+
values: VecDeque<f64>,
41+
/// The previous closing price.
342
prev_close: Option<f64>,
4-
values: Vec<f64>,
543
}
644

745
impl ATR {
46+
/// Creates a new ATR indicator with the specified period.
47+
///
48+
/// # Arguments
49+
///
50+
/// * `period` - The number of periods over which to calculate the ATR.
51+
///
52+
/// # Example
53+
///
54+
/// ```rust
55+
/// use indexes_rs::v1::atr::main::ATR;
56+
///
57+
/// let atr = ATR::new(14);
58+
/// ```
859
pub fn new(period: usize) -> Self {
960
ATR {
1061
period,
62+
values: VecDeque::with_capacity(period),
1163
prev_close: None,
12-
values: Vec::new(),
1364
}
1465
}
1566

67+
/// Calculates the current ATR value using the latest closing price.
68+
///
69+
/// The true range is computed as the absolute difference between the current closing price and the previous closing price.
70+
/// This true range is stored in a sliding window; once the window contains `period` values, the ATR is returned as their average.
71+
///
72+
/// # Arguments
73+
///
74+
/// * `close` - The latest closing price.
75+
///
76+
/// # Returns
77+
///
78+
/// * `Some(f64)` containing the ATR value if enough data is available.
79+
/// * `None` if there aren't enough values yet.
1680
pub fn calculate(&mut self, close: f64) -> Option<f64> {
17-
let true_range = self.prev_close.map_or(0.0, |prev| {
18-
(close - prev).abs() // Using price change as true range
19-
});
81+
// Compute the true range based on the previous close, or 0.0 if none exists.
82+
let true_range = self.prev_close.map_or(0.0, |prev| (close - prev).abs());
2083

21-
self.values.push(true_range);
84+
self.values.push_back(true_range);
2285
if self.values.len() > self.period {
23-
self.values.remove(0);
86+
self.values.pop_front();
2487
}
2588

2689
self.prev_close = Some(close);

src/v1/bollinger/__tests__.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#[cfg(test)]
2+
mod tests {
3+
use crate::v1::bollinger::{main::BollingerBands, types::BBResult};
4+
5+
#[test]
6+
fn test_insufficient_data() {
7+
let mut bb = BollingerBands::new(5, 2.0).unwrap();
8+
// Feed fewer than 5 prices.
9+
for price in [100.0, 101.0, 102.0, 103.0] {
10+
assert!(bb.calculate(price).is_none());
11+
}
12+
}
13+
14+
#[test]
15+
fn test_bollinger_bands_calculation() {
16+
let mut bb = BollingerBands::new(3, 2.0).unwrap();
17+
// Provide exactly 3 prices.
18+
let prices = vec![100.0, 102.0, 104.0];
19+
let mut result = None;
20+
for price in prices {
21+
result = bb.calculate(price);
22+
}
23+
let res: BBResult = result.unwrap();
24+
// The middle band should be the SMA of [100, 102, 104] which is 102.0.
25+
// Standard deviation = sqrt(((100-102)² + (102-102)² + (104-102)²)/3)
26+
// = sqrt((4 + 0 + 4) / 3) = sqrt(8/3) ≈ 1.633
27+
// Band width = 1.633 * 2.0 ≈ 3.266
28+
// Upper band ≈ 102 + 3.266 = 105.266, Lower band ≈ 102 - 3.266 = 98.734.
29+
assert!((res.middle - 102.0).abs() < 1e-6);
30+
assert!((res.upper - 105.266).abs() < 0.01);
31+
assert!((res.lower - 98.734).abs() < 0.01);
32+
}
33+
}

0 commit comments

Comments
 (0)