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:
@@ -27,10 +27,11 @@ const BAR_BUILDERS = new Set([
|
||||
'ThreeLineBreakBars',
|
||||
]);
|
||||
|
||||
// Data-layer types (tick aggregator, resampler) are not `Indicator`s: they
|
||||
// transform raw market data into candles and have their own update/flush shape,
|
||||
// so they are excluded from the streaming-indicator completeness contract.
|
||||
const DATA_LAYER = new Set(['TickAggregator', 'Resampler']);
|
||||
// Data-layer types (tick aggregator, resampler, CSV candle reader) are not
|
||||
// `Indicator`s: they transform raw market data into candles and have their own
|
||||
// update/flush/read shape, so they are excluded from the streaming-indicator
|
||||
// completeness contract.
|
||||
const DATA_LAYER = new Set(['TickAggregator', 'Resampler', 'CandleReader']);
|
||||
|
||||
// An "indicator class" is an exported constructor whose prototype carries the
|
||||
// streaming `update` method. This excludes `version` (a plain function), the bar
|
||||
|
||||
@@ -8,7 +8,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { TickAggregator, Resampler } = require('..');
|
||||
const { TickAggregator, Resampler, CandleReader } = require('..');
|
||||
|
||||
const GOLDEN = path.resolve(__dirname, '..', '..', '..', 'testdata', 'golden');
|
||||
|
||||
@@ -52,6 +52,13 @@ test('tick aggregator gap-fill matches the golden candles', () => {
|
||||
assertCandles(run(true), readCsv('data_candles_gap'), 'gap');
|
||||
});
|
||||
|
||||
test('candle reader matches the golden candles', () => {
|
||||
const csv = fs.readFileSync(path.join(GOLDEN, 'data_csv.csv'), 'utf8');
|
||||
const reader = new CandleReader(csv);
|
||||
const got = reader.read().map((c) => [c.open, c.high, c.low, c.close, c.volume, c.timestamp]);
|
||||
assertCandles(got, readCsv('data_csv_candles'), 'candle-reader');
|
||||
});
|
||||
|
||||
const INPUT = readCsv('input'); // open,high,low,close,volume (timestamp = row index)
|
||||
|
||||
function runResample() {
|
||||
|
||||
Vendored
+11
@@ -5973,3 +5973,14 @@ export declare class Resampler {
|
||||
/** Emit the final, still-open candle (or `null` if none is pending). */
|
||||
flush(): CandleValue | null
|
||||
}
|
||||
export type CandleReaderNode = CandleReader
|
||||
/**
|
||||
* Parse OHLCV candles from a CSV string (header `timestamp,open,high,low,close,
|
||||
* volume`; a leading UTF-8 BOM is stripped).
|
||||
*/
|
||||
export declare class CandleReader {
|
||||
/** Parse the whole CSV up front; throws on a malformed header or row. */
|
||||
constructor(csv: string)
|
||||
/** Return every parsed candle as `{ open, high, low, close, volume, timestamp }`. */
|
||||
read(): Array<CandleValue>
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -22000,3 +22000,30 @@ impl ResamplerNode {
|
||||
.map(candle_to_value))
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 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).
|
||||
#[napi(js_name = "CandleReader")]
|
||||
pub struct CandleReaderNode {
|
||||
candles: Vec<wc::Candle>,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl CandleReaderNode {
|
||||
/// Parse the whole CSV up front; throws on a malformed header or row.
|
||||
#[napi(constructor)]
|
||||
pub fn new(csv: String) -> napi::Result<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 }`.
|
||||
#[napi]
|
||||
pub fn read(&self) -> Vec<CandleValue> {
|
||||
self.candles.iter().map(|&c| candle_to_value(c)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user