1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
8pub struct IndicatorValue {
9 pub timestamp: DateTime<Utc>,
11 pub value: f64,
13}
14
15impl IndicatorValue {
16 pub fn new(timestamp: DateTime<Utc>, value: f64) -> Self {
18 Self { timestamp, value }
19 }
20}
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
24pub struct MultiIndicatorValue {
25 pub timestamp: DateTime<Utc>,
27 pub values: Vec<f64>,
29}
30
31impl MultiIndicatorValue {
32 pub fn new(timestamp: DateTime<Utc>, values: Vec<f64>) -> Self {
34 Self { timestamp, values }
35 }
36
37 pub fn get(&self, index: usize) -> Option<f64> {
39 self.values.get(index).copied()
40 }
41
42 pub fn len(&self) -> usize {
44 self.values.len()
45 }
46
47 pub fn is_empty(&self) -> bool {
49 self.values.is_empty()
50 }
51}
52
53impl From<Vec<f64>> for MultiIndicatorValue {
54 fn from(values: Vec<f64>) -> Self {
55 Self {
56 timestamp: Utc::now(),
57 values,
58 }
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
64pub enum PriceType {
65 Open,
67 High,
69 Low,
71 #[default]
73 Close,
74 Typical,
76 Weighted,
78 Average,
80 Median,
82}
83
84impl PriceType {
85 pub fn extract(&self, open: f64, high: f64, low: f64, close: f64) -> f64 {
94 match self {
95 PriceType::Open => open,
96 PriceType::High => high,
97 PriceType::Low => low,
98 PriceType::Close => close,
99 PriceType::Typical => (high + low + close) / 3.0,
100 PriceType::Weighted => (high + low + close + close) / 4.0,
101 PriceType::Average | PriceType::Median => (high + low) / 2.0,
102 }
103 }
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
112pub struct OhlcBar {
113 pub open: f64,
115 pub high: f64,
117 pub low: f64,
119 pub close: f64,
121}
122
123impl OhlcBar {
124 pub fn new(open: f64, high: f64, low: f64, close: f64) -> Self {
126 Self {
127 open,
128 high,
129 low,
130 close,
131 }
132 }
133
134 pub fn price(&self, price_type: PriceType) -> f64 {
136 price_type.extract(self.open, self.high, self.low, self.close)
137 }
138
139 pub fn typical_price(&self) -> f64 {
141 (self.high + self.low + self.close) / 3.0
142 }
143
144 pub fn range(&self) -> f64 {
146 self.high - self.low
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_price_type_extraction() {
156 let open = 100.0;
157 let high = 105.0;
158 let low = 95.0;
159 let close = 102.0;
160
161 assert_eq!(PriceType::Open.extract(open, high, low, close), 100.0);
162 assert_eq!(PriceType::High.extract(open, high, low, close), 105.0);
163 assert_eq!(PriceType::Low.extract(open, high, low, close), 95.0);
164 assert_eq!(PriceType::Close.extract(open, high, low, close), 102.0);
165
166 assert!((PriceType::Typical.extract(open, high, low, close) - 100.666666).abs() < 0.001);
168
169 assert_eq!(PriceType::Weighted.extract(open, high, low, close), 101.0);
171
172 assert_eq!(PriceType::Average.extract(open, high, low, close), 100.0);
174 assert_eq!(PriceType::Median.extract(open, high, low, close), 100.0);
175 }
176
177 #[test]
178 fn test_indicator_value() {
179 let timestamp = Utc::now();
180 let value = IndicatorValue::new(timestamp, 42.0);
181
182 assert_eq!(value.timestamp, timestamp);
183 assert_eq!(value.value, 42.0);
184 }
185
186 #[test]
187 fn test_multi_indicator_value() {
188 let timestamp = Utc::now();
189 let values = vec![1.0, 2.0, 3.0];
190 let multi = MultiIndicatorValue::new(timestamp, values.clone());
191
192 assert_eq!(multi.timestamp, timestamp);
193 assert_eq!(multi.len(), 3);
194 assert!(!multi.is_empty());
195 assert_eq!(multi.get(0), Some(1.0));
196 assert_eq!(multi.get(1), Some(2.0));
197 assert_eq!(multi.get(2), Some(3.0));
198 assert_eq!(multi.get(3), None);
199 }
200}