summaryrefslogtreecommitdiff
path: root/src/data.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/data.rs')
-rw-r--r--src/data.rs52
1 files changed, 52 insertions, 0 deletions
diff --git a/src/data.rs b/src/data.rs
new file mode 100644
index 0000000..c5fc18c
--- /dev/null
+++ b/src/data.rs
@@ -0,0 +1,52 @@
+use serde::{Deserialize, Serialize};
+use std::path::Path;
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Candle {
+ #[serde(rename = "Date")]
+ pub date: String,
+ #[serde(rename = "Open")]
+ pub open: f64,
+ #[serde(rename = "High")]
+ pub high: f64,
+ #[serde(rename = "Low")]
+ pub low: f64,
+ #[serde(rename = "Close")]
+ pub close: f64,
+ #[serde(rename = "Volume")]
+ pub volume: f64,
+}
+
+/// Read `Date,Open,High,Low,Close,Volume` rows; dates must be `YYYY-MM-DD`.
+/// Rows are sorted ascending by date; duplicate dates are an error.
+pub fn load_csv(path: &Path) -> Result<Vec<Candle>, String> {
+ let mut reader = csv::Reader::from_path(path).map_err(|e| format!("{}: {e}", path.display()))?;
+ let mut candles = Vec::new();
+ for (i, row) in reader.deserialize::<Candle>().enumerate() {
+ let line = i + 2; // header is line 1
+ let c = row.map_err(|e| format!("{}: line {line}: {e}", path.display()))?;
+ if !valid_date(&c.date) {
+ return Err(format!("{}: line {line}: bad date '{}', expected YYYY-MM-DD", path.display(), c.date));
+ }
+ if c.high < c.low || !(c.low..=c.high).contains(&c.close) {
+ return Err(format!("{}: line {line}: inconsistent High/Low/Close", path.display()));
+ }
+ candles.push(c);
+ }
+ candles.sort_by(|a, b| a.date.cmp(&b.date));
+ if let Some(w) = candles.windows(2).find(|w| w[0].date == w[1].date) {
+ return Err(format!("{}: duplicate date {}", path.display(), w[0].date));
+ }
+ if candles.is_empty() {
+ return Err(format!("{}: no data rows", path.display()));
+ }
+ Ok(candles)
+}
+
+fn valid_date(s: &str) -> bool {
+ let b = s.as_bytes();
+ b.len() == 10
+ && b[4] == b'-'
+ && b[7] == b'-'
+ && b.iter().enumerate().all(|(i, c)| i == 4 || i == 7 || c.is_ascii_digit())
+}