From e02b8c1369799eee31fed5c5d8b44a1b044e2780 Mon Sep 17 00:00:00 2001 From: JSDurand Date: Fri, 9 Oct 2026 16:12:46 +0800 Subject: first commit This is code written by claude for a static stock analysis page generator. I intend to test difference analysis indicators to see if some claims make any sense. --- src/data.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/data.rs (limited to 'src/data.rs') 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, 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::().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()) +} -- cgit v1.2.3-18-g5258