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:
@@ -2,6 +2,7 @@ package wickra
|
||||
|
||||
import (
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
@@ -51,6 +52,35 @@ func TestResamplerGolden(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCandleReaderGolden(t *testing.T) {
|
||||
csv, err := os.ReadFile("../../testdata/golden/data_csv.csv")
|
||||
if err != nil {
|
||||
t.Fatalf("read data_csv.csv: %v", err)
|
||||
}
|
||||
r, err := NewCandleReader(string(csv))
|
||||
if err != nil {
|
||||
t.Fatalf("new: %v", err)
|
||||
}
|
||||
defer r.Close()
|
||||
candles := r.Read()
|
||||
got := make([][6]float64, len(candles))
|
||||
for i, c := range candles {
|
||||
got[i] = [6]float64{c.Open, c.High, c.Low, c.Close, c.Volume, float64(c.Timestamp)}
|
||||
}
|
||||
want := readGolden(t, "data_csv_candles")
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("candle reader: %d candles vs %d", len(got), len(want))
|
||||
}
|
||||
for i := range got {
|
||||
for j := 0; j < 6; j++ {
|
||||
w := dataParseF(t, want[i][j])
|
||||
if math.Abs(got[i][j]-w) > 1e-9*math.Max(1, math.Abs(w)) {
|
||||
t.Errorf("candle reader row %d col %d: %v vs %v", i, j, got[i][j], w)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTickAggregatorGolden(t *testing.T) {
|
||||
ticks := readGolden(t, "data_ticks")
|
||||
cases := []struct {
|
||||
|
||||
@@ -128,6 +128,8 @@ typedef struct CalmarRatio CalmarRatio;
|
||||
|
||||
typedef struct Camarilla Camarilla;
|
||||
|
||||
typedef struct CandleReader CandleReader;
|
||||
|
||||
typedef struct CandleVolume CandleVolume;
|
||||
|
||||
typedef struct Cci Cci;
|
||||
@@ -13736,6 +13738,16 @@ bool wickra_resampler_flush(struct Resampler *handle, struct WickraCandle *out);
|
||||
|
||||
void wickra_resampler_free(struct Resampler *handle);
|
||||
|
||||
struct CandleReader *wickra_candle_reader_new(const uint8_t *data, uintptr_t len);
|
||||
|
||||
uintptr_t wickra_candle_reader_count(const struct CandleReader *handle);
|
||||
|
||||
uintptr_t wickra_candle_reader_read(struct CandleReader *handle,
|
||||
struct WickraCandle *out,
|
||||
uintptr_t cap);
|
||||
|
||||
void wickra_candle_reader_free(struct CandleReader *handle);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif // __cplusplus
|
||||
|
||||
@@ -5746,6 +5746,57 @@ func (ind *Camarilla) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
// CandleReader parses an OHLCV CSV buffer into candles over the Wickra C ABI.
|
||||
type CandleReader struct {
|
||||
handle *C.struct_CandleReader
|
||||
}
|
||||
|
||||
// NewCandleReader parses a timestamp,open,high,low,close,volume CSV string (a
|
||||
// leading UTF-8 BOM and field whitespace are tolerated). It returns
|
||||
// ErrInvalidParams when the header or a row is malformed.
|
||||
func NewCandleReader(csv string) (*CandleReader, error) {
|
||||
data := []byte(csv)
|
||||
var ptr *C.struct_CandleReader
|
||||
if len(data) == 0 {
|
||||
ptr = C.wickra_candle_reader_new(nil, 0)
|
||||
} else {
|
||||
ptr = C.wickra_candle_reader_new((*C.uint8_t)(unsafe.Pointer(&data[0])), C.uintptr_t(len(data)))
|
||||
}
|
||||
if ptr == nil {
|
||||
return nil, ErrInvalidParams
|
||||
}
|
||||
obj := &CandleReader{handle: ptr}
|
||||
runtime.SetFinalizer(obj, (*CandleReader).Close)
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// Read returns every candle parsed from the CSV, in file order.
|
||||
func (ind *CandleReader) Read() []Candle {
|
||||
n := int(C.wickra_candle_reader_count(ind.handle))
|
||||
runtime.KeepAlive(ind)
|
||||
if n <= 0 {
|
||||
return nil
|
||||
}
|
||||
buf := make([]C.struct_WickraCandle, n)
|
||||
C.wickra_candle_reader_read(ind.handle, &buf[0], C.uintptr_t(n))
|
||||
runtime.KeepAlive(ind)
|
||||
out := make([]Candle, n)
|
||||
for i := 0; i < n; i++ {
|
||||
out[i] = Candle{float64(buf[i].open), float64(buf[i].high), float64(buf[i].low), float64(buf[i].close), float64(buf[i].volume), int64(buf[i].timestamp)}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Close frees the native handle. It is idempotent and safe to call
|
||||
// alongside the finalizer.
|
||||
func (ind *CandleReader) Close() {
|
||||
if ind.handle != nil {
|
||||
C.wickra_candle_reader_free(ind.handle)
|
||||
ind.handle = nil
|
||||
runtime.SetFinalizer(ind, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// CandleVolume wraps the CandleVolume indicator over the Wickra C ABI.
|
||||
type CandleVolume struct {
|
||||
handle *C.struct_CandleVolume
|
||||
|
||||
Reference in New Issue
Block a user