m4rs/
rsi.rs

1//! RSI (Relative Strength Index)
2//!
3//! # Examples
4//! ```rust
5//! // Prepare candlesticks in some way
6//! let candlesticks = vec![
7//!     m4rs::Candlestick::new(1719400001, 100.0, 130.0, 90.0, 110.0, 1000.0),
8//!     m4rs::Candlestick::new(1719400002, 110.0, 140.0, 100.0, 130.0, 1000.0),
9//!     m4rs::Candlestick::new(1719400003, 130.0, 135.0, 120.0, 120.0, 1000.0),
10//!     m4rs::Candlestick::new(1719400004, 120.0, 130.0, 80.0, 95.0, 1000.0),
11//!     m4rs::Candlestick::new(1719400005, 90.0, 100.0, 70.0, 82.0, 1000.0),
12//! ];
13//!
14//! // Get RSI calculation result
15//! let result = m4rs::rsi(&candlesticks, 14);
16//! ```
17
18use crate::{Error, IndexEntry, IndexEntryLike};
19
20#[derive(Clone)]
21struct Calc {
22    result: f64,
23    prev: IndexEntry,
24    upside: f64,
25    downside: f64,
26}
27
28/// Returns RSI for given IndexEntry list
29pub fn rsi<T: IndexEntryLike>(entries: &[T], duration: usize) -> Result<Vec<IndexEntry>, Error> {
30    if duration == 0 || entries.len() < duration {
31        return Ok(vec![]);
32    }
33    IndexEntry::validate_list(entries)?;
34
35    let mut sorted = entries.to_owned();
36    sorted.sort_by_key(|x| x.get_at());
37
38    let first_rsi = calc_first_rsi(&sorted, duration);
39    if first_rsi.is_none() {
40        return Ok(vec![]);
41    }
42    let first_rsi = first_rsi.unwrap();
43    let xs: Vec<&T> = sorted.iter().skip(duration + 1).collect();
44    if xs.is_empty() {
45        return Ok(vec![IndexEntry {
46            at: first_rsi.0,
47            value: first_rsi.1.result,
48        }]);
49    }
50    Ok(xs
51        .iter()
52        .scan(first_rsi, |z, x| {
53            let upside = (z.1.upside * ((duration - 1) as f64)
54                + (x.get_value() - z.1.prev.value).max(0.0))
55                / (duration as f64);
56            let downside = (z.1.downside * ((duration - 1) as f64)
57                + (z.1.prev.value - x.get_value()).max(0.0))
58                / (duration as f64);
59            z.0 = x.get_at();
60            z.1 = Calc {
61                result: upside / (upside + downside) * 100.0,
62                prev: IndexEntry::from(*x),
63                upside,
64                downside,
65            };
66            Some(z.clone())
67        })
68        .map(|(at, calc)| IndexEntry {
69            at,
70            value: calc.result,
71        })
72        .collect())
73}
74
75fn calc_first_rsi<T: IndexEntryLike>(entries: &[T], duration: usize) -> Option<(u64, Calc)> {
76    if duration == 0 {
77        return None;
78    }
79    let xs: Vec<&T> = entries.iter().take(duration + 1).collect();
80    if xs.is_empty() || xs.len() < duration + 1 {
81        return None;
82    }
83
84    let upside = xs
85        .iter()
86        .map(|x| x.get_value())
87        .fold((-1.0, 0.0), |(z, a), b| {
88            if z < 0.0 {
89                (0.0, b)
90            } else if a < b {
91                (z + (b - a).abs(), b)
92            } else {
93                (z, b)
94            }
95        })
96        .0
97        / (duration as f64);
98
99    let downside = xs
100        .iter()
101        .map(|x| x.get_value())
102        .fold((-1.0, 0.0), |(z, a), b| {
103            if z < 0.0 {
104                (0.0, b)
105            } else if a > b {
106                (z + (b - a).abs(), b)
107            } else {
108                (z, b)
109            }
110        })
111        .0
112        / (duration as f64);
113
114    let last = xs.last().unwrap();
115    Some((
116        xs.last().unwrap().get_at(),
117        Calc {
118            result: upside / (upside + downside) * 100.0,
119            prev: IndexEntry::from(*last),
120            upside,
121            downside,
122        },
123    ))
124}