Skip to main content

data_preprocess/parser/
bar_csv.rs

1use std::path::Path;
2
3use chrono::FixedOffset;
4
5use crate::error::Result;
6use crate::models::{Bar, Timeframe};
7
8use super::{parse_datetime_to_utc, parse_required_f64, parse_required_i32, parse_required_i64};
9
10/// Parse a bar CSV file into a Vec<Bar>.
11/// Tab-delimited with header row. Timestamps converted from source_offset to UTC.
12pub fn parse_bar_csv(
13    path: &Path,
14    exchange: &str,
15    symbol: &str,
16    timeframe: Timeframe,
17    source_offset: &FixedOffset,
18) -> Result<(Vec<Bar>, Vec<String>)> {
19    let mut reader = csv::ReaderBuilder::new()
20        .delimiter(b'\t')
21        .has_headers(true)
22        .flexible(true)
23        .from_path(path)?;
24
25    let mut bars = Vec::new();
26    let mut warnings = Vec::new();
27
28    for (line_idx, record) in reader.records().enumerate() {
29        let line_num = line_idx + 2; // +1 for header, +1 for 1-based
30        let record = match record {
31            Ok(r) => r,
32            Err(e) => {
33                warnings.push(format!("line {}: {}", line_num, e));
34                continue;
35            }
36        };
37
38        let date_str = record.get(0).unwrap_or("").trim();
39        let time_str = record.get(1).unwrap_or("").trim();
40
41        let ts = match parse_datetime_to_utc(date_str, time_str, source_offset) {
42            Ok(t) => t,
43            Err(e) => {
44                warnings.push(format!("line {}: {}", line_num, e));
45                continue;
46            }
47        };
48
49        // Fields: DATE, TIME, OPEN, HIGH, LOW, CLOSE, TICKVOL, VOL, SPREAD
50        let open = match parse_required_f64(record.get(2), "open", line_num) {
51            Ok(v) => v,
52            Err(w) => {
53                warnings.push(w);
54                continue;
55            }
56        };
57        let high = match parse_required_f64(record.get(3), "high", line_num) {
58            Ok(v) => v,
59            Err(w) => {
60                warnings.push(w);
61                continue;
62            }
63        };
64        let low = match parse_required_f64(record.get(4), "low", line_num) {
65            Ok(v) => v,
66            Err(w) => {
67                warnings.push(w);
68                continue;
69            }
70        };
71        let close = match parse_required_f64(record.get(5), "close", line_num) {
72            Ok(v) => v,
73            Err(w) => {
74                warnings.push(w);
75                continue;
76            }
77        };
78
79        let tick_vol = parse_required_i64(record.get(6)).unwrap_or(0);
80        let volume = parse_required_i64(record.get(7)).unwrap_or(0);
81        let spread = parse_required_i32(record.get(8)).unwrap_or(0);
82
83        bars.push(Bar {
84            exchange: exchange.to_string(),
85            symbol: symbol.to_string(),
86            timeframe,
87            ts,
88            open,
89            high,
90            low,
91            close,
92            tick_vol,
93            volume,
94            spread,
95        });
96    }
97
98    Ok((bars, warnings))
99}