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 00000000..42f2e08b Binary files /dev/null and b/bindings/r/data/sample_ohlcv.rda differ 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.