From 32e18cb3a3630f83d8375ef7b75ed547240f8a25 Mon Sep 17 00:00:00 2001 From: kingchenc Date: Thu, 11 Jun 2026 21:44:56 +0200 Subject: [PATCH] feat(r): getting-started vignette + sample_ohlcv dataset (#262) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fills the two empty r-universe tabs (**Articles**, **Datasets**) for the R package and gives R users a runnable onboarding path. ## What - **`vignettes/getting-started.Rmd`** — Articles tab. Walks through batch vs streaming (and that they're equivalent), multi-output MACD, candle ATR, and `reset()`, all over the bundled sample series. Built strictly from the already-proven README quick-start + golden-test API (`Sma`/`Ema`/`Rsi`/`Atr`/`MacdIndicator`, `batch`/`update`/`reset`) — no new indicator logic. - **`data/sample_ohlcv.rda`** (+ `data-raw/sample_ohlcv.R` generator) — Datasets tab. A deterministic, seeded synthetic daily OHLCV series (250 rows × `date/open/high/low/close/volume`); documented via `R/data.R` + `man/sample_ohlcv.Rd`. `LazyData: true` → available right after `library(wickra)`. - **`DESCRIPTION`** — `Suggests: knitr, rmarkdown`, `VignetteBuilder: knitr`, `LazyData: true`. - **`ci.yml`** — the R job now knits the vignette (executes its R chunks, no pandoc needed) so a broken example is caught **in CI** before r-universe / CRAN `R CMD check`. The main job otherwise only `R CMD INSTALL`s. ## Verified locally (R 4.6.0 + Rtools45) - Package installs with the dev C-ABI override; dataset moves to the lazyload DB. - Vignette **knits cleanly** — every chunk runs, `batch == streaming` holds, MACD/ATR/RSI produce sensible values. ## Notes - No version bump — metadata/docs only; rides the next release. Merging triggers an r-universe rebuild → Articles + Datasets populate **and** (now that `support@wickra.org` is verified) the maintainer avatar resolves. - `data-raw/` is `.Rbuildignore`d (generator, not shipped). The `.rda` is XZ-compressed (~3 KB). Not merging — for review. --- .github/workflows/ci.yml | 10 ++ CHANGELOG.md | 5 + bindings/r/.Rbuildignore | 1 + bindings/r/DESCRIPTION | 4 +- bindings/r/R/data.R | 17 ++++ bindings/r/data-raw/sample_ohlcv.R | 31 +++++++ bindings/r/data/sample_ohlcv.rda | Bin 0 -> 3288 bytes bindings/r/man/sample_ohlcv.Rd | 27 ++++++ bindings/r/vignettes/getting-started.Rmd | 113 +++++++++++++++++++++++ 9 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 bindings/r/R/data.R create mode 100644 bindings/r/data-raw/sample_ohlcv.R create mode 100644 bindings/r/data/sample_ohlcv.rda create mode 100644 bindings/r/man/sample_ohlcv.Rd create mode 100644 bindings/r/vignettes/getting-started.Rmd diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 357aabbc..78475958 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -926,6 +926,16 @@ jobs: R CMD INSTALL bindings/r Rscript -e 'library(testthat); library(wickra); test_dir("bindings/r/tests/testthat", stop_on_failure = TRUE)' + - name: Build the vignette code + shell: bash + # The getting-started vignette runs at R CMD check time on r-universe / + # CRAN (with pandoc); this job only INSTALLs, so execute the vignette's R + # chunks here (knit, no pandoc needed) to catch a broken example before it + # reaches the published build. + run: | + Rscript -e 'install.packages("knitr", repos = Sys.getenv("RSPM", unset = "https://cloud.r-project.org"))' + Rscript -e 'knitr::knit("bindings/r/vignettes/getting-started.Rmd", output = tempfile(fileext = ".md"), quiet = TRUE); cat("vignette code OK\n")' + - name: Run the offline R examples shell: bash # No loader-path exports: the installed package is self-contained (bundled diff --git a/CHANGELOG.md b/CHANGELOG.md index edc08875..5117cdeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- R binding: a *Getting started* vignette and a synthetic `sample_ohlcv` example + dataset, giving new users a runnable, self-contained walkthrough and populating + the R-universe Articles and Datasets tabs. The vignette's code is exercised in + CI so a broken example is caught before the published build. ## [0.8.6] - 2026-06-11 ### Changed diff --git a/bindings/r/.Rbuildignore b/bindings/r/.Rbuildignore index 02c0d698..27946707 100644 --- a/bindings/r/.Rbuildignore +++ b/bindings/r/.Rbuildignore @@ -11,3 +11,4 @@ ^src/wickra-c$ ^src/Makevars$ ^\.gitignore$ +^data-raw$ diff --git a/bindings/r/DESCRIPTION b/bindings/r/DESCRIPTION index 3227e72b..92188f40 100644 --- a/bindings/r/DESCRIPTION +++ b/bindings/r/DESCRIPTION @@ -21,6 +21,8 @@ SystemRequirements: the Wickra C ABI shared library, downloaded automatically at Set WICKRA_INCLUDE_DIR and WICKRA_LIB_DIR to build against a locally built C ABI instead (e.g. after `cargo build -p wickra-c --release`). Roxygen: list(markdown = TRUE) -Suggests: testthat (>= 3.0.0) +Suggests: testthat (>= 3.0.0), knitr, rmarkdown +VignetteBuilder: knitr +LazyData: true Config/testthat/edition: 3 Config/roxygen2/version: 8.0.0 diff --git a/bindings/r/R/data.R b/bindings/r/R/data.R new file mode 100644 index 00000000..da438e3e --- /dev/null +++ b/bindings/r/R/data.R @@ -0,0 +1,17 @@ +#' Synthetic daily OHLCV sample series +#' +#' A deterministic, synthetic daily OHLCV (open / high / low / close / volume) +#' price series for use in the examples, the *Getting started* vignette, and +#' tests. It is a seeded random walk, **not** real market data. Regenerate with +#' `data-raw/sample_ohlcv.R`. +#' +#' @format A data frame with 250 rows and 6 columns: +#' \describe{ +#' \item{date}{Trading date (`Date`).} +#' \item{open}{Opening price.} +#' \item{high}{Session high.} +#' \item{low}{Session low.} +#' \item{close}{Closing price.} +#' \item{volume}{Traded volume.} +#' } +"sample_ohlcv" diff --git a/bindings/r/data-raw/sample_ohlcv.R b/bindings/r/data-raw/sample_ohlcv.R new file mode 100644 index 00000000..684f521f --- /dev/null +++ b/bindings/r/data-raw/sample_ohlcv.R @@ -0,0 +1,31 @@ +# Generates data/sample_ohlcv.rda — a deterministic, synthetic daily OHLCV +# series used by the examples, the getting-started vignette, and tests. It is a +# seeded random walk, NOT real market data. +# +# Regenerate (run from the R package root, bindings/r): +# Rscript data-raw/sample_ohlcv.R + +set.seed(42) +n <- 250L +dates <- seq(as.Date("2023-01-02"), by = "day", length.out = n) + +# Random-walk close with a mild upward drift; derive OHLC around it. +returns <- rnorm(n, mean = 0.0004, sd = 0.012) +close <- round(100 * cumprod(1 + returns), 2) +open <- round(c(100, head(close, -1)) * (1 + rnorm(n, 0, 0.003)), 2) +high <- round(pmax(open, close) * (1 + abs(rnorm(n, 0, 0.004))), 2) +low <- round(pmin(open, close) * (1 - abs(rnorm(n, 0, 0.004))), 2) +volume <- round(1e6 * exp(rnorm(n, 0, 0.3))) + +sample_ohlcv <- data.frame( + date = dates, + open = open, + high = high, + low = low, + close = close, + volume = volume +) + +save(sample_ohlcv, file = "data/sample_ohlcv.rda", compress = "xz", version = 2) +cat(sprintf("wrote data/sample_ohlcv.rda: %d rows x %d cols\n", + nrow(sample_ohlcv), ncol(sample_ohlcv))) diff --git a/bindings/r/data/sample_ohlcv.rda b/bindings/r/data/sample_ohlcv.rda new file mode 100644 index 0000000000000000000000000000000000000000..42f2e08b5dc266bf3c32c065224678fd78f77e00 GIT binary patch literal 3288 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 WSO5Sgj&cJ&Fb#_W000000a;qx7iK2_ literal 0 HcmV?d00001 diff --git a/bindings/r/man/sample_ohlcv.Rd b/bindings/r/man/sample_ohlcv.Rd new file mode 100644 index 00000000..9f52c135 --- /dev/null +++ b/bindings/r/man/sample_ohlcv.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data.R +\docType{data} +\name{sample_ohlcv} +\alias{sample_ohlcv} +\title{Synthetic daily OHLCV sample series} +\format{ +A data frame with 250 rows and 6 columns: +\describe{ + \item{date}{Trading date (\code{Date}).} + \item{open}{Opening price.} + \item{high}{Session high.} + \item{low}{Session low.} + \item{close}{Closing price.} + \item{volume}{Traded volume.} +} +} +\usage{ +sample_ohlcv +} +\description{ +A deterministic, synthetic daily OHLCV (open / high / low / close / volume) +price series for use in the examples, the \emph{Getting started} vignette, and +tests. It is a seeded random walk, \strong{not} real market data. Regenerate with +\code{data-raw/sample_ohlcv.R}. +} +\keyword{datasets} diff --git a/bindings/r/vignettes/getting-started.Rmd b/bindings/r/vignettes/getting-started.Rmd new file mode 100644 index 00000000..3de15190 --- /dev/null +++ b/bindings/r/vignettes/getting-started.Rmd @@ -0,0 +1,113 @@ +--- +title: "Getting started with wickra" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Getting started with wickra} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r setup, include = FALSE} +knitr::opts_chunk$set(collapse = TRUE, comment = "#>") +``` + +`wickra` exposes the Wickra technical-analysis library in R over its C ABI hub. +Every indicator is a constructor returning a `wickra_indicator`; you feed it data +one observation at a time with `update()` (an O(1) streaming step) or run a whole +series at once with `batch()`. Both paths share the exact same Rust core, so a +live feed and a historical backtest compute identical values. + +```{r} +library(wickra) +``` + +## A sample series + +The package ships a small synthetic OHLCV series, `sample_ohlcv`, for examples +(a seeded random walk — not real market data). + +```{r} +head(sample_ohlcv) +``` + +## Batch: a whole series at once + +Scalar indicators run over a vector with `batch()`. Warmup positions are `NA`. + +```{r} +sma <- Sma(20) +sma_values <- batch(sma, sample_ohlcv$close) +tail(sma_values) +``` + +## Streaming: one observation at a time + +The same indicator fed tick-by-tick with `update()` returns the identical +values — an equivalence the test suite enforces for every indicator. + +```{r} +sma_stream <- Sma(20) +streamed <- vapply(sample_ohlcv$close, function(p) update(sma_stream, p), numeric(1)) + +same_warmup <- all(is.na(streamed) == is.na(sma_values)) +same_values <- all(streamed == sma_values, na.rm = TRUE) +c(batch_equals_streaming = same_warmup && same_values) +``` + +A typical streaming loop reacts to each value as it arrives: + +```{r} +rsi <- Rsi(14) +overbought_days <- 0L +for (price in sample_ohlcv$close) { + v <- update(rsi, price) # NA during warmup + if (!is.na(v) && v > 70) overbought_days <- overbought_days + 1L +} +overbought_days +``` + +## Multi-output indicators + +Indicators with several outputs return a *named* numeric vector (`NA` while +warming up). MACD is the classic example — line, signal, and histogram: + +```{r} +macd <- MacdIndicator(12, 26, 9) +last_macd <- c(macd = NA, signal = NA, histogram = NA) +for (price in sample_ohlcv$close) last_macd <- update(macd, price) +last_macd +``` + +## Candle indicators + +Indicators that need the whole bar take the OHLCV fields plus a timestamp: + +```{r} +atr <- Atr(14) +last_atr <- NA_real_ +for (i in seq_len(nrow(sample_ohlcv))) { + last_atr <- update( + atr, + sample_ohlcv$open[i], sample_ohlcv$high[i], sample_ohlcv$low[i], + sample_ohlcv$close[i], sample_ohlcv$volume[i], i - 1 + ) +} +last_atr +``` + +## Resetting state + +`reset()` returns an indicator to its warmup state so the same object can be +reused on a fresh series: + +```{r} +reset(sma) +update(sma, 100) # NaN — warming up again +``` + +## Next steps + +- Full indicator catalogue, guides, and per-indicator reference: + . +- Every constructor (`Sma()`, `Rsi()`, `MacdIndicator()`, `Atr()`, …) is listed + in this package's help index.