indexes_rs/v1/rsi/
main.rs

1//! A Relative Strength Index (RSI) calculator module.
2//!
3//! This module provides an implementation for calculating the Relative Strength Index
4//! (RSI) over a configurable period with customizable overbought and oversold thresholds.
5//!
6//! The RSI is a momentum oscillator that measures the speed and change of price movements.
7//! It is used to indicate overbought or oversold market conditions.
8//!
9//! # Examples
10//!
11//! Using the default thresholds (70 for overbought, 30 for oversold):
12//!
13//! ```rust
14//! use indexes_rs::v1::rsi::main::RSI;
15//! use indexes_rs::v1::sma::main::SMAError;
16//! use indexes_rs::v1::rsi::types::RSIResult;
17//! use indexes_rs::v1::types::TrendDirection;
18//!
19//! let mut rsi = RSI::new(14, None, None);
20//! let prices = vec![44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.42];
21//!
22//! for price in prices {
23//!     if let Some(result) = rsi.calculate(price) {
24//!         println!("RSI: {:.2}, Condition: {:?}", result.value, result.condition);
25//!     }
26//! }
27//! ```
28//!
29//! Using custom thresholds (e.g., 80 for overbought and 20 for oversold):
30//!
31//! ```rust
32//! use indexes_rs::v1::rsi::main::RSI;
33//! let mut rsi = RSI::new(14, Some(80.0), Some(20.0));
34//! ```
35
36use super::types::{MarketCondition, RSIResult};
37use std::collections::VecDeque;
38
39/// A struct for calculating the Relative Strength Index (RSI) with customizable thresholds.
40///
41/// The RSI calculator maintains a sliding window of gains and losses based on incoming
42/// price data. Once sufficient data is collected (i.e. equal to the specified period),
43/// it returns the current RSI value along with an indication of market condition.
44/// Users can customize the overbought and oversold thresholds via the constructor.
45pub struct RSI {
46    period: usize,
47    gains: VecDeque<f64>,
48    losses: VecDeque<f64>,
49    sum_gains: f64,
50    sum_losses: f64,
51    prev_price: Option<f64>,
52    /// The overbought threshold (default is 70.0).
53    overbought: f64,
54    /// The oversold threshold (default is 30.0).
55    oversold: f64,
56}
57
58impl RSI {
59    /// Creates a new RSI calculator with the specified period and optional thresholds.
60    ///
61    /// # Arguments
62    ///
63    /// * `period` - The number of periods over which to calculate the RSI.
64    /// * `overbought` - Optional overbought threshold. If `None`, defaults to 70.0.
65    /// * `oversold` - Optional oversold threshold. If `None`, defaults to 30.0.
66    pub fn new(period: usize, overbought: Option<f64>, oversold: Option<f64>) -> Self {
67        RSI {
68            period,
69            gains: VecDeque::with_capacity(period),
70            losses: VecDeque::with_capacity(period),
71            sum_gains: 0.0,
72            sum_losses: 0.0,
73            prev_price: None,
74            overbought: overbought.unwrap_or(70.0),
75            oversold: oversold.unwrap_or(30.0),
76        }
77    }
78
79    /// Updates the RSI calculation with a new price and returns the current RSI result if available.
80    ///
81    /// This method processes the new `price`, updates the internal sliding window of gains
82    /// and losses, and calculates the RSI once enough data has been gathered.
83    ///
84    /// # Arguments
85    ///
86    /// * `price` - The latest price data.
87    ///
88    /// # Returns
89    ///
90    /// An `Option<RSIResult>` containing:
91    /// - `value`: The calculated RSI.
92    /// - `condition`: The market condition determined from the RSI value.
93    ///
94    /// Returns `None` if insufficient data has been provided.
95    ///
96    /// # Examples
97    ///
98    /// ```rust
99    /// use indexes_rs::v1::rsi::main::RSI;
100    /// use indexes_rs::v1::sma::main::SMAError;
101    /// use indexes_rs::v1::rsi::types::RSIResult;
102    /// use indexes_rs::v1::types::TrendDirection;
103    ///
104    /// let mut rsi = RSI::new(14, None, None);
105    /// let price = 44.33;
106    /// if let Some(result) = rsi.calculate(price) {
107    ///     println!("RSI: {:.2}", result.value);
108    /// }
109    /// ```
110    pub fn calculate(&mut self, price: f64) -> Option<RSIResult> {
111        // If a previous price exists, compute the change and update gains/losses.
112        if let Some(prev) = self.prev_price {
113            let change = price - prev;
114            let (gain, loss) = if change >= 0.0 { (change, 0.0) } else { (0.0, change.abs()) };
115
116            self.gains.push_back(gain);
117            self.losses.push_back(loss);
118            self.sum_gains += gain;
119            self.sum_losses += loss;
120
121            // Maintain the sliding window size.
122            if self.gains.len() > self.period {
123                if let Some(old_gain) = self.gains.pop_front() {
124                    self.sum_gains -= old_gain;
125                }
126                if let Some(old_loss) = self.losses.pop_front() {
127                    self.sum_losses -= old_loss;
128                }
129            }
130        }
131
132        self.prev_price = Some(price);
133
134        // Return None if not enough data is available.
135        if self.gains.len() < self.period {
136            return None;
137        }
138
139        let avg_gain = self.sum_gains / self.period as f64;
140        let avg_loss = self.sum_losses / self.period as f64;
141
142        // Calculate RS and then RSI.
143        let rs = if avg_loss == 0.0 { 100.0 } else { avg_gain / avg_loss };
144        let rsi = 100.0 - (100.0 / (1.0 + rs));
145
146        Some(RSIResult {
147            value: rsi,
148            condition: self.determine_condition(rsi),
149        })
150    }
151
152    /// Determines the market condition based on the given RSI value and the configured thresholds.
153    ///
154    /// - Returns `Overbought` if RSI is greater than or equal to the overbought threshold.
155    /// - Returns `Oversold` if RSI is less than or equal to the oversold threshold.
156    /// - Returns `Neutral` otherwise.
157    ///
158    /// # Arguments
159    ///
160    /// * `rsi` - The RSI value.
161    ///
162    /// # Examples
163    ///
164    /// ```rust
165    /// use indexes_rs::v1::rsi::main::RSI;
166    /// use indexes_rs::v1::rsi::types::MarketCondition;
167    /// let rsi = RSI::new(14, Some(80.0), Some(20.0));
168    /// assert_eq!(rsi.determine_condition(85.0), MarketCondition::Overbought);
169    /// ```
170    pub fn determine_condition(&self, rsi: f64) -> MarketCondition {
171        if rsi >= self.overbought {
172            MarketCondition::Overbought
173        } else if rsi <= self.oversold {
174            MarketCondition::Oversold
175        } else {
176            MarketCondition::Neutral
177        }
178    }
179}