finlib_ta/
data_item.rs

1use crate::errors::*;
2use crate::traits::{Close, High, Low, Open, Volume};
3use alloc::vec;
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8/// Data item is used as an input for indicators.
9///
10/// # Example
11///
12/// ```
13/// use finlib_ta::DataItem;
14/// use finlib_ta::{Open, High, Low, Close, Volume};
15///
16/// let item = DataItem::builder()
17///     .open(20.0)
18///     .high(25.0)
19///     .low(15.0)
20///     .close(21.0)
21///     .volume(7500.0)
22///     .build()
23///     .unwrap();
24///
25/// assert_eq!(item.open(), 20.0);
26/// assert_eq!(item.high(), 25.0);
27/// assert_eq!(item.low(), 15.0);
28/// assert_eq!(item.close(), 21.0);
29/// assert_eq!(item.volume(), 7500.0);
30/// ```
31///
32#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
33#[derive(Debug, Clone, PartialEq)]
34pub struct DataItem {
35    open: f64,
36    high: f64,
37    low: f64,
38    close: f64,
39    volume: f64,
40}
41
42impl DataItem {
43    pub fn builder() -> DataItemBuilder {
44        DataItemBuilder::new()
45    }
46}
47
48impl Open for DataItem {
49    fn open(&self) -> f64 {
50        self.open
51    }
52}
53
54impl High for DataItem {
55    fn high(&self) -> f64 {
56        self.high
57    }
58}
59
60impl Low for DataItem {
61    fn low(&self) -> f64 {
62        self.low
63    }
64}
65
66impl Close for DataItem {
67    fn close(&self) -> f64 {
68        self.close
69    }
70}
71
72impl Volume for DataItem {
73    fn volume(&self) -> f64 {
74        self.volume
75    }
76}
77
78pub struct DataItemBuilder {
79    open: Option<f64>,
80    high: Option<f64>,
81    low: Option<f64>,
82    close: Option<f64>,
83    volume: Option<f64>,
84}
85
86impl DataItemBuilder {
87    pub fn new() -> Self {
88        Self {
89            open: None,
90            high: None,
91            low: None,
92            close: None,
93            volume: None,
94        }
95    }
96
97    pub fn open(mut self, val: f64) -> Self {
98        self.open = Some(val);
99        self
100    }
101
102    pub fn high(mut self, val: f64) -> Self {
103        self.high = Some(val);
104        self
105    }
106
107    pub fn low(mut self, val: f64) -> Self {
108        self.low = Some(val);
109        self
110    }
111
112    pub fn close(mut self, val: f64) -> Self {
113        self.close = Some(val);
114        self
115    }
116
117    pub fn volume(mut self, val: f64) -> Self {
118        self.volume = Some(val);
119        self
120    }
121
122    pub fn build(self) -> Result<DataItem> {
123        if let (Some(open), Some(high), Some(low), Some(close), Some(volume)) =
124            (self.open, self.high, self.low, self.close, self.volume)
125        {
126            // validate
127            if low <= open
128                && low <= close
129                && low <= high
130                && high >= open
131                && high >= close
132                && volume >= 0.0
133            {
134                let item = DataItem {
135                    open,
136                    high,
137                    low,
138                    close,
139                    volume,
140                };
141                Ok(item)
142            } else {
143                Err(TaError::DataItemInvalid)
144            }
145        } else {
146            Err(TaError::DataItemIncomplete)
147        }
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn test_builder() {
157        fn assert_valid((open, high, low, close, volume): (f64, f64, f64, f64, f64)) {
158            let result = DataItem::builder()
159                .open(open)
160                .high(high)
161                .low(low)
162                .close(close)
163                .volume(volume)
164                .build();
165            assert!(result.is_ok());
166        }
167
168        fn assert_invalid(record: (f64, f64, f64, f64, f64)) {
169            let (open, high, low, close, volume) = record;
170            let result = DataItem::builder()
171                .open(open)
172                .high(high)
173                .low(low)
174                .close(close)
175                .volume(volume)
176                .build();
177            assert_eq!(result, Err(TaError::DataItemInvalid));
178        }
179
180        let valid_records = vec![
181            // open, high, low , close, volume
182            (20.0, 25.0, 15.0, 21.0, 7500.0),
183            (10.0, 10.0, 10.0, 10.0, 10.0),
184            (0.0, 0.0, 0.0, 0.0, 0.0),
185        ];
186        for record in valid_records {
187            assert_valid(record)
188        }
189
190        let invalid_records = vec![
191            // open, high, low , close, volume
192            (-1.0, 25.0, 15.0, 21.0, 7500.0),
193            (20.0, -1.0, 15.0, 21.0, 7500.0),
194            (20.0, 25.0, 15.0, -1.0, 7500.0),
195            (20.0, 25.0, 15.0, 21.0, -1.0),
196            (14.9, 25.0, 15.0, 21.0, 7500.0),
197            (25.1, 25.0, 15.0, 21.0, 7500.0),
198            (20.0, 25.0, 15.0, 14.9, 7500.0),
199            (20.0, 25.0, 15.0, 25.1, 7500.0),
200            (20.0, 15.0, 25.0, 21.0, 7500.0),
201        ];
202        for record in invalid_records {
203            assert_invalid(record)
204        }
205    }
206}