termichart_data/
csv_adapter.rs1use std::fs;
2
3use termichart_core::{Candle, DataAdapter, Point, Result, TermichartError};
4
5pub struct CsvAdapter {
7 data: String,
8}
9
10impl CsvAdapter {
11 pub fn from_str(s: &str) -> Self {
13 Self {
14 data: s.to_owned(),
15 }
16 }
17
18 pub fn from_file(path: &str) -> Result<Self> {
20 let data = fs::read_to_string(path)?;
21 Ok(Self { data })
22 }
23}
24
25impl DataAdapter for CsvAdapter {
26 fn candles(&self) -> Result<Vec<Candle>> {
27 let mut reader = csv::Reader::from_reader(self.data.as_bytes());
28 let mut candles = Vec::new();
29
30 for result in reader.records() {
31 let record = result.map_err(|e| TermichartError::ParseError(e.to_string()))?;
32
33 let time: f64 = record
34 .get(0)
35 .ok_or_else(|| TermichartError::ParseError("missing time field".into()))?
36 .trim()
37 .parse()
38 .map_err(|e: std::num::ParseFloatError| TermichartError::ParseError(e.to_string()))?;
39
40 let open: f64 = record
41 .get(1)
42 .ok_or_else(|| TermichartError::ParseError("missing open field".into()))?
43 .trim()
44 .parse()
45 .map_err(|e: std::num::ParseFloatError| TermichartError::ParseError(e.to_string()))?;
46
47 let high: f64 = record
48 .get(2)
49 .ok_or_else(|| TermichartError::ParseError("missing high field".into()))?
50 .trim()
51 .parse()
52 .map_err(|e: std::num::ParseFloatError| TermichartError::ParseError(e.to_string()))?;
53
54 let low: f64 = record
55 .get(3)
56 .ok_or_else(|| TermichartError::ParseError("missing low field".into()))?
57 .trim()
58 .parse()
59 .map_err(|e: std::num::ParseFloatError| TermichartError::ParseError(e.to_string()))?;
60
61 let close: f64 = record
62 .get(4)
63 .ok_or_else(|| TermichartError::ParseError("missing close field".into()))?
64 .trim()
65 .parse()
66 .map_err(|e: std::num::ParseFloatError| TermichartError::ParseError(e.to_string()))?;
67
68 let volume: f64 = record
69 .get(5)
70 .ok_or_else(|| TermichartError::ParseError("missing volume field".into()))?
71 .trim()
72 .parse()
73 .map_err(|e: std::num::ParseFloatError| TermichartError::ParseError(e.to_string()))?;
74
75 candles.push(Candle {
76 time,
77 open,
78 high,
79 low,
80 close,
81 volume,
82 });
83 }
84
85 Ok(candles)
86 }
87
88 fn points(&self) -> Result<Vec<Point>> {
89 let mut reader = csv::Reader::from_reader(self.data.as_bytes());
90 let mut points = Vec::new();
91
92 for result in reader.records() {
93 let record = result.map_err(|e| TermichartError::ParseError(e.to_string()))?;
94
95 let x: f64 = record
96 .get(0)
97 .ok_or_else(|| TermichartError::ParseError("missing x field".into()))?
98 .trim()
99 .parse()
100 .map_err(|e: std::num::ParseFloatError| TermichartError::ParseError(e.to_string()))?;
101
102 let y: f64 = record
103 .get(1)
104 .ok_or_else(|| TermichartError::ParseError("missing y field".into()))?
105 .trim()
106 .parse()
107 .map_err(|e: std::num::ParseFloatError| TermichartError::ParseError(e.to_string()))?;
108
109 points.push(Point { x, y });
110 }
111
112 Ok(points)
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn parse_candles_from_csv() {
122 let csv = "time,open,high,low,close,volume\n1.0,10.0,15.0,9.0,14.0,100.0\n2.0,14.0,16.0,13.0,15.0,200.0\n";
123 let adapter = CsvAdapter::from_str(csv);
124 let candles = adapter.candles().unwrap();
125 assert_eq!(candles.len(), 2);
126 assert_eq!(candles[0].open, 10.0);
127 assert_eq!(candles[1].close, 15.0);
128 }
129
130 #[test]
131 fn parse_points_from_csv() {
132 let csv = "x,y\n1.0,2.0\n3.0,4.0\n";
133 let adapter = CsvAdapter::from_str(csv);
134 let points = adapter.points().unwrap();
135 assert_eq!(points.len(), 2);
136 assert_eq!(points[0].x, 1.0);
137 assert_eq!(points[1].y, 4.0);
138 }
139
140 #[test]
141 fn missing_field_returns_parse_error() {
142 let csv = "time,open,high,low,close\n1.0,10.0,15.0,9.0,14.0\n";
143 let adapter = CsvAdapter::from_str(csv);
144 assert!(adapter.candles().is_err());
145 }
146}