feat(data): expose CandleReader (CSV) natively in all 10 languages (#311)

Add the data-layer CSV candle reader to every binding so loading OHLCV
candles from a CSV no longer needs a per-language CSV/dataframe dependency.

- C ABI: wickra_candle_reader_new(bytes, len) / _count / _read / _free over
  an opaque CandleReader handle (parse the whole buffer up front, then drain).
- Native: Node/WASM CandleReader.read() -> Candle[], Python read() -> list[tuple].
- C-ABI languages: Go Read() []Candle, C# Candle[] Read(), Java Candle[] read(),
  R read() S3 generic (n x 6 matrix); C / C++ call the C ABI directly.
- Cross-language golden testdata/golden/data_csv*.csv pins the parsed candles
  bit-for-bit across every binding.

Verified locally across Rust (test+clippy+fmt), Node, WASM, Python, C#, Go,
Java, R, and the C/C++ cmake parity suite.
This commit is contained in:
kingchenc
2026-06-16 00:10:58 +02:00
committed by GitHub
parent cb6da4d737
commit d362ae26a3
31 changed files with 867 additions and 6 deletions
+30
View File
@@ -28288,6 +28288,7 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
// Data layer.
m.add_class::<PyTickAggregator>()?;
m.add_class::<PyResampler>()?;
m.add_class::<PyCandleReader>()?;
// Candlestick patterns.
m.add_class::<PyDoji>()?;
m.add_class::<PyHammer>()?;
@@ -28666,3 +28667,32 @@ impl PyResampler {
.map(|c| (c.open, c.high, c.low, c.close, c.volume, c.timestamp)))
}
}
// ===== Data layer: CSV candle reader =====
/// Parse OHLCV candles from a CSV string (header `timestamp,open,high,low,close,
/// volume`; a leading UTF-8 BOM is stripped).
#[pyclass(name = "CandleReader", module = "wickra._wickra", skip_from_py_object)]
#[derive(Clone)]
struct PyCandleReader {
candles: Vec<wc::Candle>,
}
#[pymethods]
impl PyCandleReader {
#[new]
fn new(csv: &str) -> PyResult<Self> {
let mut reader =
wickra_data::csv::CandleReader::from_reader(csv.as_bytes()).map_err(map_data_err)?;
let candles = reader.read_all().map_err(map_data_err)?;
Ok(Self { candles })
}
/// Return every parsed candle as `(open, high, low, close, volume, timestamp)`.
fn read(&self) -> Vec<CandleTuple> {
self.candles
.iter()
.map(|c| (c.open, c.high, c.low, c.close, c.volume, c.timestamp))
.collect()
}
}