Skip to main content

chrono_ta/
data_item.rs

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