indexes_rs/v2/support_resistance/main.rs
1use super::types::{HighLow, SupportResistanceLevels};
2
3/// A stateless struct to encapsulate the support and resistance calculation logic.
4pub struct SupportResistance;
5
6impl SupportResistance {
7 /// Calculates support and resistance levels from a slice of HighLow price data.
8 ///
9 /// This method identifies levels using a fractal-based approach, looking for a candle
10 /// whose high is higher (for resistance) or low is lower (for support) than the
11 /// `period` candles on both sides.
12 ///
13 /// # Arguments
14 ///
15 /// * `price_data` - A slice of `HighLow` structs representing the price history.
16 /// * `period` - The number of candles to check on each side of a potential fractal.
17 ///
18 /// # Returns
19 ///
20 /// A `SupportResistanceLevels` struct containing the identified levels.
21 pub fn calculate(price_data: &[HighLow], period: usize) -> SupportResistanceLevels {
22 // The total window size is `period` candles left, the current candle, and `period` candles right.
23 let window_size = 2 * period + 1;
24
25 // Not enough data to form a single window, so no levels can be found.
26 if period == 0 || price_data.len() < window_size {
27 return SupportResistanceLevels {
28 support: vec![],
29 resistance: vec![],
30 };
31 }
32
33 let mut supports = Vec::new();
34 let mut resistances = Vec::new();
35
36 // Iterate through the data points where a full window can be formed.
37 for i in period..(price_data.len() - period) {
38 let current_high = price_data[i].high;
39 let current_low = price_data[i].low;
40
41 // Check for a resistance fractal (a peak).
42 // The current high must be strictly greater than all other highs in the window.
43 let is_resistance = (i - period..=i + period)
44 .filter(|&j| j != i) // Exclude the current candle from comparison
45 .all(|j| price_data[j].high < current_high);
46
47 // Check for a support fractal (a trough).
48 // The current low must be strictly lower than all other lows in the window.
49 let is_support = (i - period..=i + period)
50 .filter(|&j| j != i) // Exclude the current candle from comparison
51 .all(|j| price_data[j].low > current_low);
52
53 if is_resistance {
54 resistances.push(current_high);
55 }
56 if is_support {
57 supports.push(current_low);
58 }
59 }
60
61 // Sort and remove duplicates to get clean lists of levels.
62 supports.sort_by(|a, b| b.partial_cmp(a).unwrap()); // Descending order
63 supports.dedup();
64
65 resistances.sort_by(|a, b| a.partial_cmp(b).unwrap()); // Ascending order
66 resistances.dedup();
67
68 SupportResistanceLevels {
69 support: supports,
70 resistance: resistances,
71 }
72 }
73}