indexes_rs/v1/roc/main.rs
1//! # Rate of Change (ROC) Module
2//!
3//! This module implements a Rate of Change (ROC) indicator.
4//! The ROC is calculated as the percentage change between the current price and the price
5//! from a specified number of periods ago. Additionally, it calculates a normalized momentum,
6//! an acceleration (change in ROC from the previous value), and generates a trading signal.
7//!
8//! Typical usage example:
9//!
10//! ```rust
11//! use indexes_rs::v1::roc::main::ROC;
12//! use indexes_rs::v1::types::TradingSignal;
13//!
14//! let mut roc = ROC::new(12);
15//! let prices = vec![100.0, 101.0, 102.0, 103.0, 104.0, 105.0, 106.0, 107.0, 108.0, 109.0, 110.0, 111.0, 112.0];
16//! let mut result = None;
17//! for price in prices {
18//! result = roc.calculate(price);
19//! }
20//! if let Some(res) = result {
21//! println!("ROC value: {:.2}%", res.value);
22//! println!("Momentum: {:.2}%", res.momentum);
23//! println!("Acceleration: {:?}", res.acceleration);
24//! }
25//! ```
26
27use super::types::ROCResult;
28use crate::v1::types::TradingSignal;
29use std::collections::VecDeque;
30
31/// A Rate of Change (ROC) indicator.
32pub struct ROC {
33 period: usize,
34 values: VecDeque<f64>,
35 prev_roc: Option<f64>,
36}
37
38impl ROC {
39 /// The default period for ROC calculation.
40 pub const DEFAULT_PERIOD: usize = 12;
41 /// The threshold used to generate trading signals.
42 pub const SIGNAL_THRESHOLD: f64 = 2.0;
43
44 /// Creates a new ROC indicator with the given period.
45 ///
46 /// # Arguments
47 ///
48 /// * `period` - The number of periods over which to calculate the ROC.
49 ///
50 /// # Example
51 ///
52 /// ```rust
53 /// use indexes_rs::v1::roc::main::ROC;
54 ///
55 /// let roc = ROC::new(12);
56 /// ```
57 pub fn new(period: usize) -> Self {
58 ROC {
59 period,
60 values: VecDeque::with_capacity(period + 1),
61 prev_roc: None,
62 }
63 }
64
65 /// Calculates the current ROC value based on the latest price.
66 ///
67 /// This method updates the sliding window of prices and computes:
68 /// - The ROC value (percentage change between the current price and the price from `period` periods ago).
69 /// - A normalized momentum value.
70 /// - The acceleration (difference between the current and previous ROC).
71 /// - A trading signal based on the ROC.
72 ///
73 /// # Arguments
74 ///
75 /// * `price` - The latest price.
76 ///
77 /// # Returns
78 ///
79 /// * `Some(ROCResult)` if there is enough data; otherwise, `None`.
80 pub fn calculate(&mut self, price: f64) -> Option<ROCResult> {
81 // Update the sliding window.
82 self.values.push_back(price);
83 if self.values.len() > self.period + 1 {
84 self.values.pop_front();
85 }
86
87 // Not enough data to calculate ROC.
88 if self.values.len() <= self.period {
89 return None;
90 }
91
92 let old_price = *self.values.front()?;
93 // Avoid division by zero: if the old price is 0, return None.
94 if old_price == 0.0 {
95 return None;
96 }
97
98 let current_roc = ((price - old_price) / old_price) * 100.0;
99
100 // Calculate acceleration if previous ROC exists.
101 let acceleration = self.prev_roc.map(|prev| current_roc - prev);
102 self.prev_roc = Some(current_roc);
103
104 Some(ROCResult {
105 value: current_roc,
106 momentum: self.normalize_momentum(current_roc),
107 acceleration,
108 signal: self.get_signal(current_roc),
109 })
110 }
111
112 /// Normalizes the ROC value to a momentum value between -100 and 100.
113 ///
114 /// This function assumes typical ROC values range approximately from -10 to +10.
115 fn normalize_momentum(&self, roc: f64) -> f64 {
116 // Normalize so that a value of 10 becomes 100%, and -10 becomes -100%.
117 let normalized = (roc / 10.0) * 100.0;
118 normalized.clamp(-100.0, 100.0)
119 }
120
121 /// Generates a trading signal based on the ROC value.
122 ///
123 /// If ROC > SIGNAL_THRESHOLD, returns `Buy`.
124 /// If ROC < -SIGNAL_THRESHOLD, returns `Sell`.
125 /// Otherwise, returns `Hold`.
126 fn get_signal(&self, roc: f64) -> TradingSignal {
127 if roc > Self::SIGNAL_THRESHOLD {
128 TradingSignal::Buy
129 } else if roc < -Self::SIGNAL_THRESHOLD {
130 TradingSignal::Sell
131 } else {
132 TradingSignal::Hold
133 }
134 }
135}