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}