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:
@@ -5,6 +5,7 @@ S3method(flush,wickra_indicator)
|
||||
S3method(is_ready,wickra_indicator)
|
||||
S3method(name,wickra_indicator)
|
||||
S3method(push,wickra_indicator)
|
||||
S3method(read,wickra_indicator)
|
||||
S3method(reset,wickra_indicator)
|
||||
S3method(update,wickra_indicator)
|
||||
S3method(warmup_period,wickra_indicator)
|
||||
@@ -67,6 +68,7 @@ export(Butterfly)
|
||||
export(CalendarSpread)
|
||||
export(CalmarRatio)
|
||||
export(Camarilla)
|
||||
export(CandleReader)
|
||||
export(CandleVolume)
|
||||
export(Cci)
|
||||
export(CenterOfGravity)
|
||||
@@ -528,6 +530,7 @@ export(batch)
|
||||
export(is_ready)
|
||||
export(name)
|
||||
export(push)
|
||||
export(read)
|
||||
export(reset)
|
||||
export(warmup_period)
|
||||
importFrom(stats,update)
|
||||
|
||||
@@ -479,6 +479,14 @@ Camarilla <- function() {
|
||||
.wk_obj("camarilla", ptr, "Camarilla")
|
||||
}
|
||||
|
||||
#' CandleReader: parse OHLCV candles from a CSV string
|
||||
#' @keywords internal
|
||||
#' @export
|
||||
CandleReader <- function(csv) {
|
||||
ptr <- .Call("wk_candle_reader_new", csv, PACKAGE = "wickra")
|
||||
.wk_obj("candle_reader", ptr, "CandleReader")
|
||||
}
|
||||
|
||||
#' CandleVolume indicator
|
||||
#' @keywords internal
|
||||
#' @export
|
||||
|
||||
@@ -189,3 +189,26 @@ flush.wickra_indicator <- function(con) {
|
||||
out <- .Call(paste0("wk_", con$prefix, "_flush"), con$ptr, PACKAGE = "wickra")
|
||||
out
|
||||
}
|
||||
|
||||
#' Read every candle parsed by a CSV candle reader
|
||||
#'
|
||||
#' Returns all the candles a [CandleReader()] parsed from its CSV, as a numeric
|
||||
#' matrix with columns `open`, `high`, `low`, `close`, `volume`, `timestamp`.
|
||||
#'
|
||||
#' @param object A `wickra_indicator` created by [CandleReader()].
|
||||
#' @return A numeric matrix with six named columns (zero rows for an empty CSV).
|
||||
#' @examples
|
||||
#' r <- CandleReader("timestamp,open,high,low,close,volume\n0,100,101,99,100.5,10\n")
|
||||
#' read(r)
|
||||
#' @export
|
||||
read <- function(object) {
|
||||
UseMethod("read")
|
||||
}
|
||||
|
||||
#' @rdname read
|
||||
#' @export
|
||||
read.wickra_indicator <- function(object) {
|
||||
out <- .Call(paste0("wk_", object$prefix, "_read"), object$ptr, PACKAGE = "wickra")
|
||||
colnames(out) <- c("open", "high", "low", "close", "volume", "timestamp")
|
||||
out
|
||||
}
|
||||
|
||||
@@ -2603,6 +2603,40 @@ SEXP wk_camarilla_reset(SEXP e) {
|
||||
return R_NilValue;
|
||||
}
|
||||
|
||||
static void candle_reader_fin(SEXP e) {
|
||||
struct CandleReader *h = (struct CandleReader *)R_ExternalPtrAddr(e);
|
||||
if (h) wickra_candle_reader_free(h);
|
||||
R_ClearExternalPtr(e);
|
||||
}
|
||||
SEXP wk_candle_reader_new(SEXP csv) {
|
||||
SEXP s = STRING_ELT(csv, 0);
|
||||
const char *str = CHAR(s);
|
||||
struct CandleReader *h = wickra_candle_reader_new((const uint8_t *)str, (uintptr_t)LENGTH(s));
|
||||
if (!h) Rf_error("invalid CandleReader CSV");
|
||||
SEXP e = PROTECT(R_MakeExternalPtr(h, R_NilValue, R_NilValue));
|
||||
R_RegisterCFinalizerEx(e, candle_reader_fin, TRUE);
|
||||
UNPROTECT(1);
|
||||
return e;
|
||||
}
|
||||
SEXP wk_candle_reader_read(SEXP e) {
|
||||
struct CandleReader *h = (struct CandleReader *)R_ExternalPtrAddr(e);
|
||||
uintptr_t n = wickra_candle_reader_count(h);
|
||||
if (n == 0) return Rf_allocMatrix(REALSXP, 0, 6);
|
||||
struct WickraCandle *buf = (struct WickraCandle *)R_alloc(n, sizeof(struct WickraCandle));
|
||||
uintptr_t w = wickra_candle_reader_read(h, buf, n);
|
||||
SEXP r = PROTECT(Rf_allocMatrix(REALSXP, (int)w, 6));
|
||||
for (uintptr_t i = 0; i < w; i++) {
|
||||
REAL(r)[i + w * 0] = buf[i].open;
|
||||
REAL(r)[i + w * 1] = buf[i].high;
|
||||
REAL(r)[i + w * 2] = buf[i].low;
|
||||
REAL(r)[i + w * 3] = buf[i].close;
|
||||
REAL(r)[i + w * 4] = buf[i].volume;
|
||||
REAL(r)[i + w * 5] = (double)buf[i].timestamp;
|
||||
}
|
||||
UNPROTECT(1);
|
||||
return r;
|
||||
}
|
||||
|
||||
static void candle_volume_fin(SEXP e) {
|
||||
struct CandleVolume *h = (struct CandleVolume *)R_ExternalPtrAddr(e);
|
||||
if (h) wickra_candle_volume_free(h);
|
||||
@@ -23021,6 +23055,8 @@ static const R_CallMethodDef CallEntries[] = {
|
||||
{"wk_camarilla_is_ready", (DL_FUNC)&wk_camarilla_is_ready, 1},
|
||||
{"wk_camarilla_name", (DL_FUNC)&wk_camarilla_name, 1},
|
||||
{"wk_camarilla_reset", (DL_FUNC)&wk_camarilla_reset, 1},
|
||||
{"wk_candle_reader_new", (DL_FUNC)&wk_candle_reader_new, 1},
|
||||
{"wk_candle_reader_read", (DL_FUNC)&wk_candle_reader_read, 1},
|
||||
{"wk_candle_volume_new", (DL_FUNC)&wk_candle_volume_new, 1},
|
||||
{"wk_candle_volume_update", (DL_FUNC)&wk_candle_volume_update, 7},
|
||||
{"wk_candle_volume_warmup_period", (DL_FUNC)&wk_candle_volume_warmup_period, 1},
|
||||
|
||||
@@ -51,6 +51,24 @@ test_that("tick aggregator matches the golden candles", {
|
||||
}
|
||||
})
|
||||
|
||||
test_that("candle reader matches the golden candles", {
|
||||
gdir <- find_data_golden_dir()
|
||||
skip_if(is.null(gdir), "golden fixtures not bundled with the package")
|
||||
|
||||
read_mat <- function(name) {
|
||||
lines <- readLines(file.path(gdir, paste0(name, ".csv")))[-1]
|
||||
lines <- lines[nzchar(lines)]
|
||||
do.call(rbind, lapply(lines, function(l) as.numeric(strsplit(l, ",")[[1]])))
|
||||
}
|
||||
|
||||
csv <- paste(readLines(file.path(gdir, "data_csv.csv")), collapse = "\n")
|
||||
reader <- CandleReader(csv)
|
||||
got <- unname(read(reader))
|
||||
want <- unname(read_mat("data_csv_candles"))
|
||||
expect_equal(nrow(got), nrow(want))
|
||||
expect_equal(got, want, tolerance = 1e-9)
|
||||
})
|
||||
|
||||
test_that("resampler matches the golden candles", {
|
||||
gdir <- find_data_golden_dir()
|
||||
skip_if(is.null(gdir), "golden fixtures not bundled with the package")
|
||||
|
||||
Reference in New Issue
Block a user