feat(data-layer): Resampler (candle resampling) in all 10 languages (#310)
* feat(data-layer): Resampler (candle resampling) in all 10 languages Second data-layer feature (F3): resample candles into a higher timeframe. - Native (Node.js/WASM): new Resampler(timeframe) -> update(o,h,l,c,v,ts): Candle|null + flush(): Candle|null. Python the same -> tuple|None. - C ABI: wickra_resampler_new/update/flush/free (update has the multi-output shape so the generators auto-emit it; flush is bespoke). Go Update -> (Candle, bool) + Flush; C# Candle? Update/Flush; Java Candle update/flush; R update() generic + a flush() S3 method (extends base::flush); C/C++ direct. - Cross-language golden (testdata/golden/data_resampled.csv): the shared input candles resampled into 5-unit buckets, the final partial bucket via flush, pinned bit-for-bit across every binding. Verified locally in all 10 (3 candles for the 5-unit smoke; 16 for the golden). The WickraCandle output record is shared with the tick aggregator (deduped). * test(node): exclude data-layer types from the indicator completeness contract The Resampler exposes update(), so the completeness test flagged it as an indicator and required batch/reset/isReady/warmupPeriod, which a data-layer type does not have. Exclude TickAggregator and Resampler like the bar builders.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# Generated by roxygen2: do not edit by hand
|
||||
|
||||
S3method(batch,wickra_indicator)
|
||||
S3method(flush,wickra_indicator)
|
||||
S3method(is_ready,wickra_indicator)
|
||||
S3method(name,wickra_indicator)
|
||||
S3method(push,wickra_indicator)
|
||||
@@ -341,6 +342,7 @@ export(RegimeLabel)
|
||||
export(RelativeStrengthAB)
|
||||
export(RenkoBars)
|
||||
export(RenkoTrailingStop)
|
||||
export(Resampler)
|
||||
export(RickshawMan)
|
||||
export(RisingThreeMethods)
|
||||
export(Rmi)
|
||||
|
||||
@@ -2679,6 +2679,14 @@ RenkoTrailingStop <- function(block_size) {
|
||||
.wk_obj("renko_trailing_stop", ptr, "RenkoTrailingStop")
|
||||
}
|
||||
|
||||
#' Resampler indicator
|
||||
#' @keywords internal
|
||||
#' @export
|
||||
Resampler <- function(timeframe) {
|
||||
ptr <- .Call("wk_resampler_new", timeframe, PACKAGE = "wickra")
|
||||
.wk_obj("resampler", ptr, "Resampler")
|
||||
}
|
||||
|
||||
#' RickshawMan indicator
|
||||
#' @keywords internal
|
||||
#' @export
|
||||
|
||||
@@ -170,3 +170,22 @@ push.wickra_indicator <- function(object, price, size, timestamp) {
|
||||
colnames(out) <- c("open", "high", "low", "close", "volume", "timestamp")
|
||||
out
|
||||
}
|
||||
|
||||
#' Flush a resampler's final candle
|
||||
#'
|
||||
#' Emit the final, still-open candle a [Resampler()] is aggregating (the partial
|
||||
#' higher-timeframe bar that no later input has closed yet). Extends the base
|
||||
#' generic [base::flush()].
|
||||
#'
|
||||
#' @param con A `wickra_indicator` created by [Resampler()].
|
||||
#' @return A named numeric vector (`open`, `high`, `low`, `close`, `volume`,
|
||||
#' `timestamp`), or `NULL` if nothing is pending.
|
||||
#' @examples
|
||||
#' r <- Resampler(5)
|
||||
#' for (t in 0:6) update(r, 100 + t, 101 + t, 99 + t, 100 + t, 10, t)
|
||||
#' flush(r)
|
||||
#' @exportS3Method base::flush
|
||||
flush.wickra_indicator <- function(con) {
|
||||
out <- .Call(paste0("wk_", con$prefix, "_flush"), con$ptr, PACKAGE = "wickra")
|
||||
out
|
||||
}
|
||||
|
||||
@@ -14606,6 +14606,64 @@ SEXP wk_renko_trailing_stop_reset(SEXP e) {
|
||||
return R_NilValue;
|
||||
}
|
||||
|
||||
static void resampler_fin(SEXP e) {
|
||||
struct Resampler *h = (struct Resampler *)R_ExternalPtrAddr(e);
|
||||
if (h) wickra_resampler_free(h);
|
||||
R_ClearExternalPtr(e);
|
||||
}
|
||||
SEXP wk_resampler_new(SEXP a0) {
|
||||
struct Resampler *h = wickra_resampler_new((int64_t)Rf_asReal(a0));
|
||||
if (!h) Rf_error("invalid Resampler parameters");
|
||||
SEXP e = PROTECT(R_MakeExternalPtr(h, R_NilValue, R_NilValue));
|
||||
R_RegisterCFinalizerEx(e, resampler_fin, TRUE);
|
||||
UNPROTECT(1);
|
||||
return e;
|
||||
}
|
||||
SEXP wk_resampler_update(SEXP e, SEXP a0, SEXP a1, SEXP a2, SEXP a3, SEXP a4, SEXP a5) {
|
||||
struct Resampler *h = (struct Resampler *)R_ExternalPtrAddr(e);
|
||||
struct WickraCandle out;
|
||||
int ok = wickra_resampler_update(h, Rf_asReal(a0), Rf_asReal(a1), Rf_asReal(a2), Rf_asReal(a3), Rf_asReal(a4), (int64_t)Rf_asReal(a5), &out);
|
||||
SEXP r = PROTECT(Rf_allocVector(REALSXP, 6));
|
||||
REAL(r)[0] = ok ? (double)out.open : NA_REAL;
|
||||
REAL(r)[1] = ok ? (double)out.high : NA_REAL;
|
||||
REAL(r)[2] = ok ? (double)out.low : NA_REAL;
|
||||
REAL(r)[3] = ok ? (double)out.close : NA_REAL;
|
||||
REAL(r)[4] = ok ? (double)out.volume : NA_REAL;
|
||||
REAL(r)[5] = ok ? (double)out.timestamp : NA_REAL;
|
||||
SEXP nm = PROTECT(Rf_allocVector(STRSXP, 6));
|
||||
SET_STRING_ELT(nm, 0, Rf_mkChar("open"));
|
||||
SET_STRING_ELT(nm, 1, Rf_mkChar("high"));
|
||||
SET_STRING_ELT(nm, 2, Rf_mkChar("low"));
|
||||
SET_STRING_ELT(nm, 3, Rf_mkChar("close"));
|
||||
SET_STRING_ELT(nm, 4, Rf_mkChar("volume"));
|
||||
SET_STRING_ELT(nm, 5, Rf_mkChar("timestamp"));
|
||||
Rf_setAttrib(r, R_NamesSymbol, nm);
|
||||
UNPROTECT(2);
|
||||
return r;
|
||||
}
|
||||
SEXP wk_resampler_flush(SEXP e) {
|
||||
struct Resampler *h = (struct Resampler *)R_ExternalPtrAddr(e);
|
||||
struct WickraCandle out;
|
||||
if (!wickra_resampler_flush(h, &out)) return R_NilValue;
|
||||
SEXP r = PROTECT(Rf_allocVector(REALSXP, 6));
|
||||
REAL(r)[0] = out.open;
|
||||
REAL(r)[1] = out.high;
|
||||
REAL(r)[2] = out.low;
|
||||
REAL(r)[3] = out.close;
|
||||
REAL(r)[4] = out.volume;
|
||||
REAL(r)[5] = (double)out.timestamp;
|
||||
SEXP nm = PROTECT(Rf_allocVector(STRSXP, 6));
|
||||
SET_STRING_ELT(nm, 0, Rf_mkChar("open"));
|
||||
SET_STRING_ELT(nm, 1, Rf_mkChar("high"));
|
||||
SET_STRING_ELT(nm, 2, Rf_mkChar("low"));
|
||||
SET_STRING_ELT(nm, 3, Rf_mkChar("close"));
|
||||
SET_STRING_ELT(nm, 4, Rf_mkChar("volume"));
|
||||
SET_STRING_ELT(nm, 5, Rf_mkChar("timestamp"));
|
||||
Rf_setAttrib(r, R_NamesSymbol, nm);
|
||||
UNPROTECT(2);
|
||||
return r;
|
||||
}
|
||||
|
||||
static void rickshaw_man_fin(SEXP e) {
|
||||
struct RickshawMan *h = (struct RickshawMan *)R_ExternalPtrAddr(e);
|
||||
if (h) wickra_rickshaw_man_free(h);
|
||||
@@ -24781,6 +24839,9 @@ static const R_CallMethodDef CallEntries[] = {
|
||||
{"wk_renko_trailing_stop_is_ready", (DL_FUNC)&wk_renko_trailing_stop_is_ready, 1},
|
||||
{"wk_renko_trailing_stop_name", (DL_FUNC)&wk_renko_trailing_stop_name, 1},
|
||||
{"wk_renko_trailing_stop_reset", (DL_FUNC)&wk_renko_trailing_stop_reset, 1},
|
||||
{"wk_resampler_new", (DL_FUNC)&wk_resampler_new, 1},
|
||||
{"wk_resampler_update", (DL_FUNC)&wk_resampler_update, 7},
|
||||
{"wk_resampler_flush", (DL_FUNC)&wk_resampler_flush, 1},
|
||||
{"wk_rickshaw_man_new", (DL_FUNC)&wk_rickshaw_man_new, 0},
|
||||
{"wk_rickshaw_man_update", (DL_FUNC)&wk_rickshaw_man_update, 7},
|
||||
{"wk_rickshaw_man_batch", (DL_FUNC)&wk_rickshaw_man_batch, 7},
|
||||
|
||||
@@ -50,3 +50,31 @@ test_that("tick aggregator matches the golden candles", {
|
||||
expect_equal(got, want, tolerance = 1e-9, info = spec$fixture)
|
||||
}
|
||||
})
|
||||
|
||||
test_that("resampler 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]])))
|
||||
}
|
||||
|
||||
input <- read_mat("input") # open,high,low,close,volume (timestamp = row index)
|
||||
r <- Resampler(5)
|
||||
got <- matrix(numeric(0), 0, 6)
|
||||
for (i in seq_len(nrow(input))) {
|
||||
c <- update(r, input[i, 1], input[i, 2], input[i, 3], input[i, 4], input[i, 5], i - 1)
|
||||
if (!is.na(c[1])) {
|
||||
got <- rbind(got, unname(c))
|
||||
}
|
||||
}
|
||||
f <- flush(r)
|
||||
if (!is.null(f)) {
|
||||
got <- rbind(got, unname(f))
|
||||
}
|
||||
want <- unname(read_mat("data_resampled"))
|
||||
expect_equal(nrow(got), nrow(want))
|
||||
expect_equal(got, want, tolerance = 1e-9)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user