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:
@@ -361,6 +361,7 @@ from ._wickra import (
|
||||
# Data layer
|
||||
TickAggregator,
|
||||
Resampler,
|
||||
CandleReader,
|
||||
# Market Profile
|
||||
CompositeProfile,
|
||||
HighLowVolumeNodes,
|
||||
@@ -908,6 +909,7 @@ __all__ = [
|
||||
# Data layer
|
||||
"TickAggregator",
|
||||
"Resampler",
|
||||
"CandleReader",
|
||||
# Market Profile
|
||||
"CompositeProfile",
|
||||
"HighLowVolumeNodes",
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,18 @@ def test_tick_aggregator_matches_golden(gap_fill, fixture):
|
||||
assert abs(g[j] - w[j]) <= tol, f"row {i} col {j}: {g[j]} vs {w[j]}"
|
||||
|
||||
|
||||
def test_candle_reader_matches_golden():
|
||||
with open(os.path.join(GOLDEN, "data_csv.csv")) as f:
|
||||
text = f.read()
|
||||
got = ta.CandleReader(text).read()
|
||||
want = _read("data_csv_candles")
|
||||
assert len(got) == len(want)
|
||||
for i, (g, w) in enumerate(zip(got, want)):
|
||||
for j in range(6):
|
||||
tol = 1e-9 * max(1.0, abs(w[j]))
|
||||
assert abs(g[j] - w[j]) <= tol, f"row {i} col {j}: {g[j]} vs {w[j]}"
|
||||
|
||||
|
||||
INPUT = _read("input") # open,high,low,close,volume (timestamp = row index)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user