Commit Graph

268 Commits

Author SHA1 Message Date
kingchenc 2ab578bee8 docs: surface docs.wickra.org + keep the wiki pointer count in sync (#107)
* docs(readme): surface the documentation site (docs.wickra.org)

The README never linked the canonical docs at docs.wickra.org — visitors had
no path from the repo to the per-indicator deep dives, quickstarts, and
guides. Add a docs badge, a Documentation section mirroring the binding
READMEs and docs/README.md, and an Indicators-Overview link in the indicator
section.

* ci(sync-about): keep the wiki pointer page's indicator count in sync

The GitHub wiki was collapsed to a single Home.md that points at
docs.wickra.org but still names the indicator count ('… for all N indicators').
Add a count-sync step mirroring the docs/webpage steps — clone wickra.wiki into
its own dir, sed Home.md, commit as wickra-bot, push — with the same
continue-on-error soft-skip so a missing PAT scope never fails the run.
2026-06-01 04:10:00 +02:00
kingchenc 2be39b8b98 ci(release): attach Sigstore provenance bundle as a release asset (P21.1e) (#106)
OpenSSF Scorecard's Signed-Releases check scans the GitHub Release *assets* for
signed/provenance files (`*.intoto.jsonl`, `*.sig`, ...). It does not look at
GitHub's separate attestations store, so although the attestations job has signed
the published bytes since v0.4.0, the v0.4.0 release assets carried no provenance
file and the check stayed at 0.

Attach the Sigstore provenance bundle (already produced by
actions/attest-build-provenance) to the release as `wickra-<tag>.provenance.intoto.jsonl`:

- github-release now exposes its resolved tag as a job output.
- attestations `needs: github-release` (so the Release already exists), gains
  `contents: write`, gives the attest step an id, and uploads the bundle with
  `gh release upload --clobber` (idempotent on re-runs).

Publishes stay fully isolated — cargo/PyPI/npm all run upstream of github-release,
so a Sigstore hiccup here can never block or corrupt a publish; at worst the
release just lacks the provenance asset. Signed-Releases climbs over the next
releases as each tag carries the bundle.
2026-06-01 04:09:09 +02:00
kingchenc 99af5f8ee1 ci: retry transient registry/DNS flakes at the cargo/npm/pip tool level (#105)
The v0.4.0-era CI failure was a runner network blip — `napi build` invokes cargo,
whose fetch of index.crates.io hit "Could not resolve host: index.crates.io" and
failed the Node-on-macOS job, forcing a manual re-run. The earlier flake-hardening
(setup-node/setup-python + rust-cache retries) only covered toolchain download and
cache restore, not the registry fetches inside the actual build/publish steps.

Set tool-level network retries as workflow env so every cargo/napi/maturin/
wasm-pack/npm/pip invocation in every job inherits them — including the nested
cargo calls inside napi/maturin/wasm-pack:

- CARGO_NET_RETRY=10 (default 3): cargo classes DNS-resolve / connect / timeout
  errors as spurious and retries with backoff; 10 attempts ride out a transient
  blip instead of failing the job.
- CARGO_NET_GIT_FETCH_WITH_CLI=true: more robust git-dep fetches.
- npm_config_fetch_retries=5 / maxtimeout=120s: npm ci/install registry retries.
- PIP_RETRIES=5 / PIP_DEFAULT_TIMEOUT=120: pip install resilience.

Applied to ci.yml, release.yml and bench.yml (the workflows that build). No more
manual re-runs for transient registry flakes.
2026-06-01 04:08:18 +02:00
kingchenc bff1148d20 ci(sync-about): fix docs version-sync clone collision + webpage npm race (#104)
* ci(sync-about): fix docs version-sync clone collision + webpage npm race

Two real release-time bugs surfaced by the v0.4.0 release, where the docs
"Published versions" table never updated and the marketing-site Cloudflare
build failed:

1. docs version sync never ran. The "Sync docs version (wickra-docs)" step
   cloned into a directory literally named `docs`, but on a tag push the job
   checks out the wickra repo at the workspace root, which already contains a
   top-level `docs/` directory. `git clone … docs` therefore failed with
   "destination path 'docs' already exists", silenced by `2>/dev/null` and
   misreported as a missing-token warning, so the docs version table stayed at
   the previous release. Clone into `docs-ver` instead (mirrors the `docs-count`
   dir the count step already uses); it collides with nothing in the repo.

2. webpage build broke on a version race. The "Sync webpage version" step bumps
   package.json's `wickra-wasm` pin to the released version and pushes
   immediately, but release.yml publishes wickra-wasm to npm in parallel on the
   same tag and finishes minutes later. Cloudflare's `npm clean-install` then
   hit `ETARGET: No matching version found for wickra-wasm@^0.4.0`. Poll npm for
   wickra-wasm@<version> (up to ~15 min) before committing; if it never appears
   the step skips with a warning rather than pushing a build-breaking commit.

Both steps were designed to mirror each other across docs/webpage; these fixes
restore that symmetry so every release self-heals both sites.

* ci(sync-about): regenerate webpage package-lock on version bump

Third v0.4.0 release-sync defect: the webpage version step seds package.json's
wickra-wasm pin but never touched package-lock.json, so even after wickra-wasm
went live on npm the Cloudflare build still failed with
`npm ci` EUSAGE: "lock file's wickra-wasm@0.3.1 does not satisfy
wickra-wasm@0.4.0".

After the package.json sed, run `npm install --package-lock-only` so the lockfile
(version + resolved + integrity) matches the new pin; commit package-lock.json
alongside package.json. The earlier npm-wait already guarantees the version is
resolvable. Guarded: if the regen fails the step skips the whole commit rather
than push a package.json/lock mismatch.

The live site was unblocked out-of-band by a matching lockfile commit on the
webpage repo; this makes it self-heal on every future release.
2026-06-01 02:02:19 +02:00
kingchenc ebddc5e376 ci: set least-privilege top-level token permissions (P21.1b) (#103)
The auto-injected GITHUB_TOKEN defaulted to write-all in ci.yml, bench.yml
and release.yml (no top-level permissions block), and codeql.yml declared
its scopes only at job level. Add a top-level `permissions: contents: read`
to all four so the token starts read-only and only the jobs that genuinely
write through it raise the scope:

- release.yml: github-release keeps contents: write; node-/wasm-publish and
  attestations keep their id-token / attestations: write blocks. The
  cargo/python/node publish jobs push to crates.io/PyPI/npm via their own
  registry secrets, not the GITHUB_TOKEN, so read-only is correct for them.
- codeql.yml: analyze keeps security-events: write (job level).
- ci.yml / bench.yml: no job writes back to the repo (coverage uploads via
  CODECOV_TOKEN; bench only uploads an artifact), so no job override is needed.

sync-about.yml already had a top-level block but at contents: write; demote
the top level to read and move contents: write down to the single `sync` job
(the PR-head counter push is the only GITHUB_TOKEN write). The cross-repo
About/docs/webpage/org writes are unaffected — they run through the
fine-grained ABOUT_SYNC_TOKEN, which the permissions key does not govern.

Raises OpenSSF Scorecard Token-Permissions from 0 toward 10.
2026-06-01 02:01:31 +02:00
kingchenc eab2649f1c release: bump 0.3.1 -> 0.4.0 (#89)
Minor release. The headline user-facing change is the Node binding now
rejecting invalid indicator periods instead of silently clamping period 0
to 1 (matches Python/WASM/core); plus per-ecosystem binding READMEs and a
corrected MSRV statement in CONTRIBUTING. See CHANGELOG [0.4.0].

- Cargo.toml (workspace.package + wickra-core dep) + Cargo.lock (6 members)
- bindings/python/pyproject.toml
- bindings/node/package.json (version + 6 optionalDependencies) + package-lock.json
- bindings/node/npm/*/package.json (6 platform subpackages)
- examples/node/package-lock.json (wickra-* platform pins)
- CHANGELOG: [Unreleased] -> [0.4.0] - 2026-05-31 + compare URL

No tag pushed (release publish is a separate, user-confirmed step).
v0.4.0
2026-06-01 01:28:53 +02:00
kingchenc f7f947e048 docs(changelog): record provenance attestations + CI security tooling (Unreleased) (#102) 2026-06-01 01:08:14 +02:00
kingchenc 01dd08714b ci: harden workflows against network/CDN flakes (resilient cache + setup retries) (#101) 2026-06-01 01:07:56 +02:00
kingchenc 0fa70c9882 ci: pin remaining GitHub Actions to commit SHAs (P21.1c) (#100) 2026-06-01 01:07:34 +02:00
kingchenc debe4523d5 ci: pass untrusted workflow contexts via env to prevent shell injection (P21.1a) (#99) 2026-06-01 00:32:24 +02:00
kingchenc a046c441e5 docs(readme): show the Wickra banner; drop the redundant heading (#98)
Embed the brand banner at the top of the README (linked to wickra.org) and
drop the now-redundant "# Wickra" heading — the banner shows the wordmark.

The image is served from https://wickra.org/og-banner.webp, which the webpage
build regenerates on every deploy from the current indicator count (4K WebP),
so the README banner stays current with no committed binary in this repo and
renders on GitHub, crates.io and docs.rs alike.
2026-06-01 00:07:36 +02:00
kingchenc f7b91f6fa5 chore: use support@wickra.org for contact/author email; drop dead sponsor link (#97)
Now that the wickra.org catch-all mailbox exists, move the project contact +
package-author email off the personal gmail to support@wickra.org across all
surfaces: CODE_OF_CONDUCT, SECURITY, CITATION.cff, Cargo.toml, the npm + PyPI
author fields, the release.yml npm author, and repo-metadata.toml. (The
package-author changes take effect on the next published release.)

repo-metadata.toml's [audit].forbidden still pins kingchencp@gmail.com (the
private commit email) as a banned substring — unchanged.

Also remove the FUNDING.yml custom "https://wickra.org/sponsor" entry: that
page 404s, so the Sponsor button linked to a dead URL. The GitHub Sponsors
entry (github: [kingchenc]) stays.
2026-05-31 23:22:57 +02:00
kingchenc 5030360a0c ci: CodeQL SAST workflow + badge (P13.6) (#96)
* ci: add OpenSSF Scorecard workflow + badge (P13.1)

* ci(release): attest build provenance for crates + Python artifacts (P13.2)

* docs(readme): add GitHub release badge (P13.4)

* ci: add CodeQL SAST workflow + badge (P13.6)
2026-05-31 22:34:00 +02:00
kingchenc 1dd487fabc docs(readme): GitHub release badge (P13.4) (#94)
* ci: add OpenSSF Scorecard workflow + badge (P13.1)

* ci(release): attest build provenance for crates + Python artifacts (P13.2)

* docs(readme): add GitHub release badge (P13.4)
2026-05-31 22:21:05 +02:00
kingchenc cee174c0de ci(release): build-provenance attestations for crates + Python (P13.2) (#91)
* ci: add OpenSSF Scorecard workflow + badge (P13.1)

* ci(release): attest build provenance for crates + Python artifacts (P13.2)
2026-05-31 22:13:06 +02:00
kingchenc c8e5d8a658 ci: add OpenSSF Scorecard workflow + badge (P13.1) (#90) 2026-05-31 22:05:00 +02:00
kingchenc dc2e19e762 ci(sync-about): self-update the marketing site count + version (P12.1) (#95)
* ci(sync-about): auto-sync the published version to wickra-docs on release

* ci(sync-about): retarget indicator-count sync from wiki to wickra-docs + point About homepage at docs.wickra.org (P8.3)

* ci(sync-about): self-update the marketing site (webpage) count + version (P12.1)
2026-05-31 22:04:32 +02:00
kingchenc d8212beff6 ci(sync-about): retarget count sync to wickra-docs + point About at docs.wickra.org (P8.3) (#88)
* ci(sync-about): auto-sync the published version to wickra-docs on release

* ci(sync-about): retarget indicator-count sync from wiki to wickra-docs + point About homepage at docs.wickra.org (P8.3)
2026-05-31 21:57:42 +02:00
kingchenc 86a595d505 ci(sync-about): auto-sync the published version to wickra-docs on release (#87) 2026-05-31 21:48:53 +02:00
kingchenc 9309bf9d60 docs(P6.4): repoint all doc links from the GitHub wiki to docs.wickra.org (#86)
The documentation now lives in the wickra-lib/wickra-docs VitePress repo and
will deploy to docs.wickra.org. Rewrite every tracked, user-facing wiki link
in the main repo to the new canonical site:

- docs/README.md, CONTRIBUTING.md, PULL_REQUEST_TEMPLATE.md
- bindings/{node,python,wasm}/README.md, examples/wasm/README.md
- repo-metadata.toml: wiki_url/wiki_git -> docs_url/docs_git

Page paths map 1:1 to VitePress clean URLs (e.g. /wiki/Quickstart-Rust.md ->
docs.wickra.org/Quickstart-Rust). The sync-about.yml wiki-sync step is left
untouched on purpose: it stays until the docs site is live (tracked as P8.3).
The GitHub wiki itself is not deleted yet for the same reason.
2026-05-31 21:48:24 +02:00
kingchenc 3f05342f72 docs(P7): per-ecosystem binding READMEs + correct MSRV documentation (#85)
* docs(contributing): correct MSRV to 1.86/1.88 and document the dep-forced floor (P7.1)

* docs(bindings): trim binding READMEs to per-ecosystem install + links (P7.2)

* docs(changelog): note per-ecosystem binding READMEs + MSRV doc fix (P7.1/P7.2)
2026-05-31 05:30:34 +02:00
kingchenc eb4454ab27 P5: track index.d.ts + reject invalid periods in the Node binding (#83)
* build(node): track the generated index.d.ts (P5.1)

index.js was committed but index.d.ts was gitignored, an inconsistency that
also contradicts CONTRIBUTING ('regenerate both .d.ts/.js when a binding API
changes'). Track index.d.ts too so the repo carries the TypeScript types as a
matched pair with index.js. Generated by napi build; ~214 indicator classes.

* fix(node): reject invalid periods instead of clamping them (P5.4)

The Node scalar-indicator macro clamped period 0 to 1 (via clamp_period + must)
and the multi-parameter constructors did the same, silently swallowing the
core's PeriodZero validation. The core rejects period 0 (Error::PeriodZero),
and the Python and WASM bindings already propagate it — Node was the outlier,
masking caller mistakes. Make the macro constructor fallible and let every
constructor propagate the core error via map_err, removing clamp_period/must.
Update the smoke test (period 0 now throws, matching core/Python/WASM).

* docs(changelog): note the Node period-validation change (P5.4)

Behavior change per CONTRIBUTING: Node constructors now reject invalid periods
instead of clamping. Add an [Unreleased] Changed entry.

* chore: drop accidentally committed scratch log
2026-05-31 05:22:56 +02:00
kingchenc 3093f194a2 docs: document the repo-wide lockfile policy (P4) (#82)
The .gitignore comment claimed package-lock.json is committed only under
bindings/node/, but examples/node/package-lock.json has also been tracked
since #80. Correct the comment and add a CONTRIBUTING 'Lockfile policy'
section spelling out every component: Cargo.lock + the two Node package-locks
are tracked; fuzz/Cargo.lock is ignored (cargo-fuzz default); the Python
package has no lockfile by PyO3 convention (pinned via Cargo.lock); the
ghost-ignored site keeps its lockfile local.
2026-05-31 05:11:08 +02:00
kingchenc 21c86f348f examples + bindings: Node/WASM strategy parity + test/benchmark parity (P2 + P3) (#81)
* examples(node): add RSI mean-reversion strategy

Node counterpart of strategy_rsi_mean_reversion.{py,rs}: RSI(14) < 30 long,
> 70 exit, 0.1% fees, hourly BTCUSDT. Output verified byte-identical to the
Rust reference (37 trades W24/L13, -17.84% return, 46.89% max drawdown).

* examples(node): add MACD + ADX trend-filter strategy

Node counterpart of strategy_macd_adx.{py,rs}: MACD(12,26,9) histogram
crossover entries gated by ADX(14) > 20, hourly BTCUSDT, 0.1% fees. Output
verified byte-identical to the Rust reference (246 trades W90/L156, -47.19%
return, 53.75% max drawdown).

* examples(node): add Bollinger-squeeze breakout strategy

Node counterpart of strategy_bollinger_squeeze.{py,rs}: enter on a fresh
180-bar Bollinger-bandwidth low + close above the upper band, exit on a
2*ATR(14) stop or upper-band collapse, daily BTCUSDT, 0.1% fees. Output
verified byte-identical to the Rust reference (1 trade, -7.82% return,
13.01% max drawdown).

* examples(wasm): add RSI mean-reversion strategy demo

Browser counterpart of strategy_rsi_mean_reversion.{py,js,rs}: RSI(14) < 30
long, > 70 exit, 0.1% fees, summary table. Same signal/fill/PnL/equity loop as
the runtime-verified Node example; loads via the established wickra_wasm.js
init + fetch-CSV pattern. (wasm32 build runs in CI.)

* examples(wasm): add MACD + ADX trend-filter strategy demo

Browser counterpart of strategy_macd_adx.{py,js,rs}: MACD(12,26,9) histogram
crossover gated by ADX(14) > 20, hourly BTCUSDT, 0.1% fees. Logic identical to
the runtime-verified Node example; standard wickra_wasm.js init + fetch-CSV
loader. (wasm32 build runs in CI.)

* examples(wasm): add Bollinger-squeeze breakout strategy demo

Browser counterpart of strategy_bollinger_squeeze.{py,js,rs}: fresh 180-bar
Bollinger-bandwidth low + upper-band breakout, 2*ATR(14) stop, daily BTCUSDT,
0.1% fees. Logic identical to the runtime-verified Node example; standard
wickra_wasm.js init + fetch-CSV loader. (wasm32 build runs in CI.)

* ci: add examples syntax-smoke job (P2.3)

The Rust examples are built by 'cargo build -p wickra-examples --bins'; the
Node, browser-WASM and Python examples had no build gate. New job parse-checks
every examples/{node,wasm}/*.js, extracts and node --checks each WASM .html
module script, and python -m py_compile's every examples/python/*.py — so a
broken example edit fails CI instead of landing silently.

* docs(examples): list the new Node + WASM strategy examples

Add the three Node strategy scripts and three WASM strategy demos to the
examples README tables, bringing Node and WASM to parity with the existing
Rust and Python strategy rows.

* chore(examples): refresh examples/node lockfile for the linked wickra binding

npm install rewrote the file: dependency snapshot of the local wickra binding
that the examples link against (version 0.1.4 -> 0.3.1, license + engines
fields), which had gone stale in the committed lockfile.

* test(node): add input-validation suite

Node counterpart of bindings/python/tests/test_input_validation.py: invalid
constructor parameters (ATR zero period, MACD non-increasing fast/slow,
BollingerBands negative multiplier, PSAR step > max, ValueArea period/pct,
InitialBalance/OpeningRange zero period, Ichimoku non-increasing periods,
Ehlers-family ordering) and unequal-length candle/ValueArea batch inputs all
throw a JS Error. Validated against the built binding.

* test(node): add indicator completeness contract

Introspects every exported indicator class and asserts the full interface
(update / batch / reset / isReady / warmupPeriod) plus the pre-warmup contract
for zero-arg indicators, and guards that the full catalogue (>= 200 classes)
is exported. Catches a new indicator wired without the standard methods, or a
stale/partial native build dropping exports, with no per-indicator boilerplate.

* test(wasm): broaden scalar streaming-vs-batch coverage

Extend the inline wasm-bindgen-test suite with a streaming==batch check across
~70 scalar indicators spanning moving averages, momentum, volatility,
statistics/regression, Ehlers/cycle and risk/performance families (previously
only EMA + the candle-input group were covered per-indicator), plus four more
invalid-constructor assertions. Constructor args mirror the CI-passing Node
factories. Host-compiles (cargo test -p wickra-wasm --no-run); executed in CI
via wasm-pack test --node.

* bench(node): add indicator throughput benchmark

Node counterpart of the Rust criterion benches / Python compare_libraries:
measures streaming (per-tick update) and batch throughput in Mupd/s across a
representative indicator set over a synthetic OHLCV series (--bars, default
200k). Dependency-free; wired as 'npm run bench'.

* docs(wasm): list strategy demos + document the benchmark story

Add the three new strategy demos to the WASM examples table and a Performance
section: parallel_assets.html is the in-browser benchmark, with raw throughput
covered by the Rust criterion / Python / Node benchmarks (the WASM engine is the
same core compiled to wasm32).
2026-05-31 05:09:26 +02:00
kingchenc a2ff35f5f9 ci(sync-about): auto-sync repo homepage + org profile from the indicator count (#84)
Extend the count-sync workflow to drive three more surfaces from the same
single count, so the org page never drifts again:

- Repo About **homepage** URL — enforced unconditionally via
  `gh repo edit --homepage` in the existing About step (fixes the stale
  kingchenc URL and self-heals). Uses the same Administration-write
  permission as --description, so NO extra token scope.
- Org **profile README** count (wickra-lib/.github, profile/README.md) —
  clone + sed + bot commit/push, same pattern as the Sync Wiki step.
- Org **description** field ('… N indicators, install-free.') — gh api PATCH.

The two org steps need ABOUT_SYNC_TOKEN scope the main-repo syncs do not
(write on wickra-lib/.github; admin:org for the description PATCH). Both are
written to soft-skip with a ::warning:: and continue-on-error, so the run
stays green until the scope is granted — then they sync with no further
change. Homepage swap-point to a docs domain is a one-line change.

Refs findings P9 / P10.
2026-05-31 03:02:32 +02:00
kingchenc 01aeb965d1 release: bump 0.3.0 -> 0.3.1 (#80)
* chore: remove ROADMAP.md from the public repo

ROADMAP is kept as a local-only draft (ghost-ignored via
.git/info/exclude); it is not part of the published package surface.

* release: bump 0.3.0 -> 0.3.1

CI-only patch: fixes the release.yml CycloneDX SBOM step (cargo-cyclonedx
has no -p flag, see #79) that skipped the GitHub Release attach-assets job
on 0.3.0. No library changes — republishes the same code with a working
release pipeline.

- Cargo.toml (workspace.package + wickra-core dep) + Cargo.lock
- bindings/python/pyproject.toml
- bindings/node/package.json (version + 6 optionalDependencies) + package-lock.json
- bindings/node/npm/*/package.json (6 platform subpackages)
- CHANGELOG: finalize [0.3.0] (was still under [Unreleased]), add [0.3.1]

* chore: track examples/node/package-lock.json

Since the global package-lock ignore rule was dropped (#68) this file was
left untracked. Commit it for reproducible example installs, consistent
with bindings/node (findings P4.1).
v0.3.1
2026-05-30 19:50:45 +02:00
kingchenc f1fed6cdd5 ci(release): fix CycloneDX SBOM generation (cargo-cyclonedx has no -p flag) (#79)
cargo-cyclonedx 0.5.9 walks the whole workspace in a single pass and
writes a <package>.cdx.json next to each member's Cargo.toml; it has no
-p/--package selector. The previous three 'cargo cyclonedx ... -p <crate>'
invocations aborted with 'error: unexpected argument -p found', failing
the cargo-publish job after the crates were already published and, in
turn, skipping the github-release attach-assets job (it needs all four
publish jobs). v0.3.0 published to every registry but got no GitHub
Release page or SBOM assets as a result.

Replace the three invalid calls with a single workspace pass and copy
the three crates.io crate SBOMs into the upload dir.
2026-05-30 19:26:30 +02:00
kingchenc 70e9cbb397 release: bump 0.2.7 -> 0.3.0 (supersedes PR #61) (#69)
Minor bump (not patch) because the [Unreleased] section since 0.2.7 has
accumulated a sweep of additive changes that justify a new minor:

- Family 9-16 indicator catalogue expansion (Bands & Channels, Trailing
  Stops, Volume, Statistics, Ehlers/Cycle DSP, Pivots, DeMark, Ichimoku,
  Candlestick Patterns, Market Profile, Risk/Performance) — roughly
  100+ new indicators since 0.2.7 across all four bindings.
- New `wickra_core::FAMILIES` const + family-taxonomy guard tests.
- GitHub org migration (kingchenc -> wickra-lib) and new maintainer
  email (wickra.lib@gmail.com).
- New `repo-metadata.toml` + `sync-metadata.yml` audit workflow.
- WASM CI tests now run on every PR (existing tests had been
  manually-only).
- CycloneDX SBOMs + npm provenance attestations attached to releases.
- Three end-to-end strategy examples.
- Governance polish: ARCHITECTURE / ROADMAP / CITATION / FUNDING /
  .editorconfig.
- Curated benchmark suite (~33 representative indicators).
- Three cold-path coverage fixes (mama, rsi, sine_wave).
- bindings/node/package-lock.json now committed.

Workspace + bindings (Rust crate, Python wheel, Node main + 6 platform
sub-packages, WASM) all step to 0.3.0. CHANGELOG opens the [0.3.0]
section dated 2026-05-28 with the full Changed / Added inventory.
Compare-URL block adds the v0.2.7...v0.3.0 line under [Unreleased] and
points [Unreleased] at v0.3.0...HEAD using the new wickra-lib org.

**Supersedes PR #61** (0.2.7 -> 0.2.8 patch bump). Close #61 when this
one merges. Merge ordering remains: #59 (org migration) + #60
(family-api) + the polish PRs first, rebase this PR on top of the new
main, then merge.

Tag-push `v0.3.0` is a SEPARATE, manual step after merge — it triggers
release.yml's irreversible publish to crates.io / PyPI / npm.
v0.3.0
2026-05-30 19:06:48 +02:00
dependabot[bot] 37e5e19b57 deps(actions): bump taiki-e/install-action from 2.79.5 to 2.79.15 (#76)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.79.5 to 2.79.15.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/6c1f7cf125e42770ff087ea443901b487cc5471a...0fd46367812ee04360509b4169d9f659d6892bb2)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.79.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-30 18:54:31 +02:00
dependabot[bot] c189075491 deps(actions): bump EmbarkStudios/cargo-deny-action (#74)
Bumps [EmbarkStudios/cargo-deny-action](https://github.com/embarkstudios/cargo-deny-action) from 2.0.19 to 2.0.20.
- [Release notes](https://github.com/embarkstudios/cargo-deny-action/releases)
- [Commits](https://github.com/embarkstudios/cargo-deny-action/compare/a531616d8ce3b9177443e48a1159bc945a099823...bb137d7af7e4fb67e5f82a49c4fce4fad40782fe)

---
updated-dependencies:
- dependency-name: EmbarkStudios/cargo-deny-action
  dependency-version: 2.0.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-30 18:52:23 +02:00
dependabot[bot] 74dd82a867 deps(actions): bump actions/checkout from 4 to 6 (#75)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-30 18:52:15 +02:00
kingchenc b654db312e docs: fix unresolved and private intra-doc links (#78)
A full `cargo doc --workspace` (and docs.rs, which builds with
`-D rustdoc::all`) emitted five broken intra-doc links. docs.rs treats
these as hard errors, so any 0.2.x doc build was at risk of aborting.

- mama.rs: `[`Fama`]` -> `[`crate::Fama`]` (Fama lives in another module)
- standard_error.rs: `[`crate::Bollinger`]` -> `[`crate::BollingerBands`]`
  (the public type is `BollingerBands`, not `Bollinger`)
- aggregator.rs: drop the link to the private `OpenBar::into_candle`,
  keep it as plain code text
- resample.rs: drop the link to the private `RolledBar::into_candle`
- csv.rs: `CandleReader::with_timestamp_parser` never existed; reword to
  state plainly that ISO/RFC timestamps must be converted to integers

Verified with `RUSTDOCFLAGS="-D rustdoc::broken_intra_doc_links \
-D rustdoc::private_intra_doc_links" cargo doc --workspace --no-deps`:
clean, zero warnings.
2026-05-30 18:46:49 +02:00
kingchenc 88f119109d chore(node): commit bindings/node/package-lock.json for reproducible installs (#68)
Until now `package-lock.json` was globally ignored. Two practical
consequences for the Node binding:

- A fresh `git clone && cd bindings/node && npm install` resolved
  `@napi-rs/cli` and any transitive deps to whatever the npm registry
  currently considered the latest matching the package.json semver
  ranges. Contributors could get different dep graphs on different days.
- No protection against transitive-dep tampering at install time
  (lockfile records resolved versions + integrity hashes, npm verifies
  on subsequent installs).

Drop the global `package-lock.json` ignore and commit the freshly
generated `bindings/node/package-lock.json` (140 lines, only a couple
of direct deps because the binding is small). The `.gitignore` comment
notes that we still don't expect lockfiles at the workspace root.

No CI workflow changes — the existing `npm install` in the Node-test job
will now consume the committed lockfile, which is exactly what we want.
2026-05-30 18:24:46 +02:00
kingchenc 2945b47e1a feat(release): add CycloneDX SBOMs and npm provenance attestations (#66)
Two modern supply-chain-trust additions to the release pipeline,
neither of which changes what gets published — only adds verifiable
signals attached to existing releases.

1. **CycloneDX SBOMs.** `cargo-cyclonedx` is installed in the
   cargo-publish job after the .crate files are built, and runs once
   per published crate (`wickra-core`, `wickra-data`, `wickra`). The
   resulting `*.cdx.json` files are uploaded as the `sboms` artifact,
   then attached to the GitHub Release alongside the existing wheels,
   tarballs, .node binaries and .crate files. Future security advisories
   can answer "is my version of crate X transitive in wickra Y.Z?" by
   reading the SBOM directly instead of resolving the lockfile.

2. **npm `--provenance` flag** on every npm publish call:
   - main `wickra` package (node-publish, first + retry)
   - per-platform `wickra-<triple>` subpackages (node-publish loop)
   - `wickra-wasm` (wasm-publish)

   Provenance attestations are generated server-side by npm from the
   GitHub Actions OIDC token. The publishing jobs gain
   `permissions: id-token: write` so the runner can exchange that
   token. The npm page for each published version will then carry the
   "Verified provenance" badge, which proves the tarball was built by
   *this* workflow run and not by an arbitrary local laptop with the
   NPM_TOKEN.

Skipped deliberately (to keep this PR focused, possibly follow-ups):
- Sigstore cosign signing of artefacts (different audit story; can be
  layered on after npm-provenance lands).
- SLSA build-provenance attestations via `actions/attest-build-provenance`
  (would target every artefact uniformly; the npm-provenance flag is
  the more pragmatic first cut).

YAML structure validated (8 jobs intact). No production code touched.
This PR conflicts with PR #59 only in line-by-line URL substitutions
on release.yml — rebase after #59 should be clean.
2026-05-30 18:23:47 +02:00
kingchenc c212f91256 docs(examples): add 3 end-to-end strategy examples (Rust + Python) (#65)
Wires real indicators into complete signal -> fill -> PnL -> equity
loops over the checked-in BTCUSDT datasets, with per-trade Sharpe and
max-drawdown reported on stdout. Closes the gap where existing
examples showed only the mechanics of calling `update`/`batch` but not
how Wickra plugs into a trading-system shape.

Three strategies, each in Rust + Python (six files total):

- strategy_rsi_mean_reversion — RSI(14) thresholds (30/70) on 1h
  BTCUSDT. Binary position, 0.1% per-trade fee.
- strategy_macd_adx — MACD crossover entries gated by ADX(14) > 20 on
  1h BTCUSDT. Trend-follower demo of multi-indicator gating.
- strategy_bollinger_squeeze — Bollinger-bandwidth 180-day-low squeeze
  + upper-band breakout entry, ATR(14) * 2 stop. On 1d BTCUSDT for
  interpretable lookback.

Each file is self-contained — print_summary is inlined per script so
the example stays a single-file read. Every script prints a
NOT-financial-advice notice next to its results.

examples/README.md updated to list the new bins/scripts.
2026-05-30 18:23:03 +02:00
kingchenc 6c2ddf319f feat(wasm): wire wasm-bindgen-test into CI and expand coverage (#64)
* feat(wasm): wire wasm-bindgen-test into CI and expand binding coverage

The WASM binding already had 8 wasm_bindgen_test cases checked in but
they ran only when someone manually invoked `wasm-pack test` locally —
CI never executed them, so a breakage between the Rust core API and the
JS surface would only surface in user reports.

Two changes:

1. **New CI job `wasm-test`** in `.github/workflows/ci.yml` runs
   `wasm-pack test --node bindings/wasm` on every push and pull-request.
   Uses Node-runtime mode (no headless browser needed), pins
   `taiki-e/install-action` for `wasm-pack`, installs the
   `wasm32-unknown-unknown` target.

2. **10 new `#[wasm_bindgen_test]` cases** in `bindings/wasm/src/lib.rs`
   covering families that the existing 8 tests did not touch:
   - Family 4 — Macd multi-output batch shape (3-component packing)
   - Family 5 — Bollinger {upper, middle, lower} ordering invariant
   - Family 10 — FisherTransform streaming roundtrip (recursive DSP)
   - Family 16 — SharpeRatio reset semantics, MaxDrawdown monotone-
     uptrend invariant, ValueAtRisk constructor validation
   - Cross-family — reset returns indicator to warmup (3 shapes),
     warmup_period parity with wickra-core, NaN input rejection
     does not advance warmup counter

No production code changed; additive tests only. Total wasm test count
goes from 8 to 18.

* fix(wasm-tests): WasmAtr's hand-coded wrapper has no is_ready accessor

The new `reset_returns_indicator_to_warmup` test in this branch called
`atr.is_ready()` but `WasmAtr` is hand-coded (not generated by the
`wasm_scalar_indicator!` macro) and does not expose `is_ready` on the
JS surface — that pattern is reserved for macro-generated scalar
wrappers.

Replace the second leg of the test with `WasmEma` so the test exercises
two macro-generated scalars whose `is_ready`/`reset` semantics are
guaranteed by the same code path. The candle-input lifecycle is
already covered by `candle_input_streaming_matches_batch_and_lifecycle`.

Workspace clippy locally green:
- cargo clippy -p wickra-wasm --all-targets -- -D warnings
- cargo check -p wickra-wasm --target wasm32-unknown-unknown --tests

* fix(wasm-tests): Bollinger batch layout is 4 floats per bar, not 3

The new `bollinger_batch_orders_upper_mid_lower` test indexed the batch
output as `[u0, m0, l0, u1, m1, l1, ...]` (3 floats per bar) but the
actual WasmBb::batch layout is `[u0, m0, l0, sd0, u1, m1, l1, sd1, ...]`
— four floats per bar including stddev. The assertion read an upper
band against a middle from the previous bar, which can violate the
expected upper >= middle ordering and made the test fail intermittently.

Switch the stride from `* 3` to `* 4` so we read upper / middle / lower
from the same bar. Also add an `is_finite()` precondition so the test
fails with a clear "warmup positions unexpectedly NaN" message rather
than a confusing NaN-comparison if the dataset ever shrinks.

* fix(wasm-tests): drop redundant wasm-test job; existing WASM build covers it

The CI workflow already had a `WASM build` job that runs
`wasm-pack test --node bindings/wasm` after building. The
`wasm-test` job I added in cc961b3 duplicated that step and was
the only one whose `taiki-e/install-action` SHA was wrong, so it
also kept failing on a non-resolvable action reference.

Removing the duplicate keeps the test coverage (it lives in the
existing WASM build job) and gets rid of the unresolvable SHA.
2026-05-30 18:21:36 +02:00
kingchenc 9db1ff8023 chore(bench): curate benchmarks to ~30 representative indicators (#63)
Previously every push of `cargo bench -p wickra` ran 114 indicators at
three workload sizes each (1k / 10k / 50k candles), which inflated bench
runtime past ten minutes for diminishing signal — most family members
are linear scalings of the same hot loop, so a regression in any one of
them shows up identically in the cheapest member.

This commit replaces the exhaustive list with a curated selection: the
cheapest baseline and the most expensive representative from each of
the sixteen families, totalling ~33 indicators across scalar, candle,
and multi-output APIs.

If you need to profile a specific indicator that is not in the curated
set, add it temporarily and run `cargo bench -- <name>` to target just
that bench; it does not need to be committed.

Fixed in passing:
- `Cci` is `Indicator<Input = Candle>`, not f64; corrected the bench
  selector to `bench_candle_input`.
- `Psar::new` takes (af_start, af_step, af_max) — supplied all three.
- `TdSequential` uses `::classic` for the textbook (4, 9, 2, 13)
  parameters; the old call was missing arguments.
2026-05-30 18:21:19 +02:00
kingchenc fb6eae7fe2 docs: add ARCHITECTURE, ROADMAP, CITATION, FUNDING, editorconfig (#62)
Five governance + onboarding files that collectively close the
"no high-level documentation outside of README" gap.

- ARCHITECTURE.md: workspace layout, Indicator trait contract,
  per-indicator file conventions, numerical-stability notes,
  cross-crate flow diagram, navigation cheatsheet, deliberate
  non-goals, performance characteristics, stability commitments.
- ROADMAP.md: north star, 0.3 / 0.4 / 0.5 release windows,
  indicator-wishlist intake rules, explicit non-goals, versioning policy.
- CITATION.cff: GitHub-renders as "Cite this repository" button;
  enables academic adoption.
- .github/FUNDING.yml: surfaces Sponsor button on the repo page.
- .editorconfig: normalises indent/EOL across IDEs (Rust/Python 4 spaces,
  JS/TS/JSON/YAML/MD 2 spaces, Makefile tabs).

Touches no Rust source, no CI workflows, no behaviour. Additive only.
URLs in ROADMAP/CITATION already reference the post-transfer
`wickra-lib/wickra` org so the files stay valid once the org migration
in PR #59 lands; merge this PR only after #59 to keep main consistent.
2026-05-30 18:20:52 +02:00
kingchenc 0edb9f4857 fix(bindings): clear clippy pedantic lints and lint bindings in CI (#77)
* fix(bindings): clear clippy pedantic lints and lint bindings in CI

Resolve the pedantic lints that only surfaced under a full
`cargo clippy --workspace` (the CI clippy job covered only the core
crates, so the Python/Node bindings drifted):

- manual_midpoint: `(a + b) / 2.0` -> `f64::midpoint(a, b)` (node + python)
- new_without_default: add `Default` impls for the six no-arg Node nodes
- type_complexity: factor the pivot/Ichimoku return tuples into
  `PivotLevels` / `WoodieLevels` / `IchimokuLines` aliases (python)
- many_single_char_names: allow at crate level — OHLCV batch helpers bind
  the conventional o/h/l/c/v column names

Add a dedicated `clippy-bindings` CI job (ubuntu-only, with Python + Node
toolchains) so future binding lints fail CI instead of slipping through.

* ci: lint Python and Node bindings in a dedicated clippy job

The main `rust` job's clippy step only covers wickra-core/wickra/
wickra-data/wickra-wasm, so pedantic lints in the PyO3/napi bindings
slipped through. Add an ubuntu-only `clippy-bindings` job that
provisions Python + Node (needed by the build scripts) and runs
`cargo clippy -p wickra-node -p wickra-python --all-targets -- -D warnings`.
2026-05-30 18:16:21 +02:00
kingchenc ea684e0d48 feat(family-api): add FAMILIES const and family-taxonomy tests (#60)
* feat(family-api): add FAMILIES const and family-taxonomy tests

Introduce `wickra_core::FAMILIES`, a `&'static [(&str, &[&str])]` mapping
every built-in indicator to one of 16 families (Moving Averages, Momentum
Oscillators, Trend & Directional, Price Oscillators, Volatility & Bands,
Bands & Channels, Trailing Stops, Volume, Price Statistics, Ehlers /
Cycle (DSP), Pivots & S/R, DeMark, Ichimoku & Charts, Candlestick
Patterns, Market Profile, Risk / Performance).

Two compile-time-anchored guards make sure the const stays trustworthy:
- `no_duplicates_across_families`: no indicator is listed in two families
- `total_count_matches_expected`: hard-coded total bumps in lockstep with
  the indicator catalogue (214 today), so any new indicator added without
  being filed under a family trips the test.

Also corrects the module doc comment which falsely claimed
indicators were grouped by category internally; the `mod` block is
alphabetical, and the canonical taxonomy now lives in `FAMILIES`.

* chore: sync indicator count to 214

---------

Co-authored-by: wickra-bot <wickra-bot@users.noreply.github.com>
2026-05-30 14:58:16 +02:00
kingchenc 62ab84c472 chore(migration): switch org to wickra-lib and maintainer email to wickra.lib@gmail.com (#59)
Introduce repo-metadata.toml as single source of truth for repo identity
(org slug, maintainer email, canonical URLs) and add sync-metadata.yml
workflow with a Python audit script that fails CI if any tracked file
drifts back to pre-migration values.

Bulk-replace across 24 tracked files:
- kingchenc/wickra -> wickra-lib/wickra (URL segment)
- kingchencp@gmail.com -> wickra.lib@gmail.com (maintainer email)
- @kingchenc -> @wickra-lib (CODEOWNERS mention only)

Person-name credits are preserved: LICENSE copyright holder, Cargo.toml
authors handle, and CHANGELOG historical @kingchenc reference all remain
unchanged. Crate / PyPI / npm package names also untouched.

Merge this PR only after the kingchenc/wickra -> wickra-lib/wickra org
transfer has happened on the GitHub side, otherwise all badges and
repository links 404 until the transfer is performed.
2026-05-30 12:18:10 +02:00
kingchenc 0f2ff9c3c7 chore(sync-about): count public indicator types, not module files (#70)
* chore(sync-about): count public indicator types, not module files

The sync-about workflow counted `mod xxx;` lines in
crates/wickra-core/src/indicators/mod.rs to derive the indicator count
that gets propagated to README, the GitHub About description, and the
wiki. That under-reported by one because `vwap.rs` exports two public
types — `Vwap` (cumulative) and `RollingVwap` (finite window) — from
the same module. The bindings reach both, so users see 214 indicators
even though there are only 213 source files.

Fix the count by parsing the canonical `pub use indicators::{ ... }`
block in lib.rs, dropping the `FAMILIES` constant and any `*Output`
companion structs, and counting the remaining public types. Pure-shell
implementation so the workflow doesn't grow a python dependency.

Sync README to the corrected count (213 -> 214) in the same commit so
the PR validates cleanly through the workflow's PR-flow.

* docs(bindings): sync per-binding READMEs and docs/ pointer to 214 / 16

The bindings/{node,python,wasm}/README.md files (which ship to
npm / PyPI / npm again) and docs/README.md (the pointer to the
wiki) all carried the stale '71 streaming-first indicators across
eight families' header from before the family expansion. Pull the
canonical 16-family table out of the main README into all three
binding READMEs and update docs/README.md to list every family by
name, so a user landing on PyPI / npm sees the current catalogue
shape.
2026-05-30 00:40:13 +02:00
kingchenc e85334a2e9 test(cold-paths): cover 3 lines in mama/rsi/sine_wave (#58)
* test(cold-paths): cover phase fallback in mama/sine_wave and rsi naive helper saturation

* fix(rsi): drop redundant closure in test helper
2026-05-26 21:08:50 +02:00
kingchenc 4e3c41ea80 feat(family-15): add 17 risk/performance metrics (#54)
* feat(family-15): add 17 risk/performance metrics

Implements Family 15 pragmatically as standard `Indicator`s instead of a
separate `wickra-metrics` crate. Input is scalar `f64` per bar — period
return, equity sample, or per-trade P&L depending on the metric.

Scalar `Indicator<f64>` (14):
- SharpeRatio(period, risk_free)
- SortinoRatio(period, mar)
- CalmarRatio(period)
- OmegaRatio(period, threshold)
- MaxDrawdown(period)          — rolling, peak-to-trough
- AverageDrawdown(period)
- DrawdownDuration             — cumulative, bars under water (u32 output)
- PainIndex(period)
- ValueAtRisk(period, confidence)
- ConditionalValueAtRisk(period, confidence)
- ProfitFactor(period)
- GainLossRatio(period)
- RecoveryFactor               — cumulative, net return / max drawdown
- KellyCriterion(period)

Two-series `Indicator<(f64, f64)>` for (asset, benchmark) returns (3):
- TreynorRatio(period, risk_free)
- InformationRatio(period)
- Alpha(period, risk_free)     — Jensen / CAPM

Touchpoints:
- 17 new files under `crates/wickra-core/src/indicators/`.
- `mod.rs` + `lib.rs` re-exports.
- Python bindings (`bindings/python/src/lib.rs`, `__init__.py`).
- Node bindings (`bindings/node/src/lib.rs`, `index.js`).
- WASM bindings (`bindings/wasm/src/lib.rs`).
- Fuzz: scalar metrics appended to `indicator_update.rs`; new
  `indicator_update_pair.rs` fuzz target for `(f64, f64)` indicators.
- Python tests: SCALAR + new PAIR parameter lists in `test_new_indicators.py`,
  reference-value cases in `test_known_values.py`.
- Node tests: scalar factories + new pair-factory block in
  `bindings/node/__tests__/indicators.test.js`.
- Benches: 5 Family-15 benches added in `crates/wickra/benches/indicators.rs`.
- Docs: README family-table row + counter (71 -> 88), CHANGELOG entry under
  [Unreleased].

Note: Family 12 (statistik-regression, PR #51) introduces
`node_pair_indicator!` and `wasm_pair_indicator!` macros for Pearson /
Beta / Spearman. Family 15 needs the same pair-input pattern but Family 12
is not yet in main, so the three pair wrappers below are written by hand
in this PR. When PR #51 lands, the trivial merge-conflict is resolved by
keeping the macros from Family 12 and re-using them for Treynor / IR /
Alpha (drop the three handwritten wrappers).

cargo check --workspace --all-features: green.

* fix(family-15): satisfy clippy doc_markdown / if_not_else / digit_grouping

* fix(family-15): unused TreynorRatio import, duplicate pairFactories, _eq_nan inf handling

* fix(family-15): node eq() handles matching infinities for ratio indicators

* test(family-15): cover cold paths flagged by codecov patch
2026-05-26 20:44:21 +02:00
kingchenc 55284a3042 feat(family-14): add 15 candlestick patterns (#53)
* feat(family-14): add 15 candlestick patterns

Introduces the Candlestick Patterns family (block A of the family-14 spec)
as scalar f64 indicators on Candle inputs. Each detector emits +1.0 for a
bullish reading, -1.0 for a bearish reading, and 0.0 when no pattern is
present. Doji is direction-less and emits +1.0 / 0.0 only.

New indicators (15):

- Doji
- Hammer
- InvertedHammer
- HangingMan
- ShootingStar
- Engulfing
- Harami
- MorningEveningStar (signed: +1.0 morning star, -1.0 evening star)
- ThreeSoldiersOrCrows (signed: +1.0 soldiers, -1.0 crows)
- PiercingDarkCloud (signed: +1.0 piercing, -1.0 dark cloud)
- Marubozu (signed: +1.0 bullish, -1.0 bearish, 5 percent shadow tolerance default)
- Tweezer (signed: +1.0 bottom, -1.0 top, 10 bps relative tolerance default)
- SpinningTop (direction-signed indecision)
- ThreeInside (confirmed Harami)
- ThreeOutside (confirmed Engulfing)

MVP scope notes:

- Pattern-shape check only, no trend filter applied. Caller combines with a
  trend indicator for actionable signals. Documented in every doc comment.
- Block B (Harmonic patterns) and block C (Chart patterns) remain
  out-of-scope and will follow when the pattern-detection framework (pivot
  detector, multi-bar state machines) lands.

Touched across all bindings: Python, Node, WASM. Fuzz target, Python tests
(streaming-vs-batch + reference values), Node tests (streaming-vs-batch +
reference values), and a representative bench subset (1-, 2- and 3-bar
patterns) added. README family table + indicator counter (71 -> 86, eight
-> nine families) and CHANGELOG [Unreleased] updated.

* fix(family-14): unpack MULTI values with *_ to handle 3-element tuples

* cov(family-14): cover Default impl cold paths and MorningEveningStar guard branches
2026-05-26 00:54:11 +02:00
kingchenc 9b8e1346ed feat(family-16): add ValueArea + InitialBalance + OpeningRange (#52)
* feat(family-16): add ValueArea + InitialBalance + OpeningRange

Opens family #16 (Market Profile) with the three OHLCV-compatible scalar /
multi-output indicators:

- ValueArea(period, bin_count, value_area_pct) -> {poc, vah, val}.
  Rolling bin-approximation volume profile over the last `period`
  candles. Each candle's volume is spread uniformly across [low, high];
  POC is the bin with highest cumulative volume; the value area expands
  symmetrically from POC and always absorbs the higher-volume neighbour
  next, until `value_area_pct` (default 0.70) of total volume is
  enclosed. Defaults (20, 50, 0.70).

- InitialBalance(period) -> {high, low}. Tracks session-opening high
  and low over the first `period` bars, then locks. Default period = 12
  (one-hour IB on 5-minute bars for US equities). Callers MUST invoke
  reset() at every session boundary, otherwise IB stays fixed for the
  lifetime of the instance.

- OpeningRange(period) -> {high, low, breakout_distance}. Same
  lock-after-N-bars semantics as IB with a shorter default period
  (6 = 30 min on 5-minute bars) and a third output that tracks
  close - or_mid (positive above the range mid, negative below).

Histogram-output Market Profile variants (Volume Profile, VPVR,
Composite Profile) are deferred because they need a new histogram
output API layer rather than fixed-arity scalars. Tick-data-only
variants (TPO Profile, Single Print, Order Flow Delta, Cumulative
Delta, Volume-Weighted Open) are out of scope because `wickra-data`
does not currently expose tick / L2 data.

All four bindings (Rust core, Python, Node, WASM) ship the new
indicators with parity tests; benches added; fuzz target extended.
Counter 71 -> 74 across 8 -> 9 families. cargo check --workspace
--all-features green.

* fix(family-16): cover cold paths in InitialBalance + ValueArea

InitialBalance::value() public getter had no test covering the post-update
Some(...) branch — extended accessors_and_metadata to call value() after one
update. ValueArea single-print bar path (c.high == c.low) was unreachable in
existing tests since the only single-print test used a uniform 100-price
window which exits early via the span == 0 guard; added a mixed-window test
that triggers the c.high <= c.low branch directly. The (None, None) arm of
the expansion match was by-construction unreachable (the loop condition
already requires at least one neighbour) and has been folded into an
if/else.
2026-05-26 00:14:30 +02:00
kingchenc 05fcdd9a5e feat(family-12): add 13 Statistik/Regression indicators (#51)
* feat(family-12): add 13 Statistik/Regression indicators

Brings the Price Statistics family to 20 indicators (7 → 20) and the
total catalogue to 84 (71 → 84). Every indicator ships in the Rust
core plus Python, Node, and WASM bindings with full streaming ↔ batch
parity, fuzz coverage, and benches.

Scalar (f64 → f64):
- Variance, CoefficientOfVariation: rolling population variance and
  its dimensionless ratio with the mean. O(1) updates.
- Skewness, Kurtosis: rolling Pearson skewness and excess kurtosis,
  derived from running sums of x, x², x³, x⁴ via the binomial
  identities — also O(1) per bar.
- StandardError, DetrendedStdDev: standard error of estimate (n − 2)
  and population StdDev (n) of OLS residuals, sharing the LinReg
  O(1) sliding sums.
- RSquared: coefficient of determination of the rolling OLS fit; the
  trend-quality filter, clamped to [0, 1].
- MedianAbsoluteDeviation: robust dispersion estimator; O(period log
  period) per emission via two in-place sorts of a reusable scratch
  buffer.
- Autocorrelation(period, lag): rolling lag-k Pearson autocorrelation.
- HurstExponent(period, chunks): R/S-analysis trend-persistence
  estimator clamped to [0, 1].

Pair indicators (Input = (f64, f64)):
- PearsonCorrelation: rolling cross-series Pearson, O(1).
- Beta: rolling OLS slope of asset vs. benchmark (CAPM).
- SpearmanCorrelation: rolling rank correlation with mid-rank tie
  handling; O(period log period).

Touchpoints:
- crates/wickra-core: 13 new indicator modules + mod.rs / lib.rs
  re-exports.
- bindings/python: pyclasses + add_class registration + __init__.py
  import & __all__ updates. The pair indicators expose
  update(x, y) and batch(x, y) over two equally-sized numpy arrays.
- bindings/node: scalar indicators via node_scalar_indicator! macro;
  pair indicators via new node_pair_indicator! macro; explicit
  structs for Autocorrelation and HurstExponent (two-arg ctors).
  index.js extended with the new exports.
- bindings/wasm: scalar wrappers via wasm_scalar_indicator!; pair
  wrappers via new wasm_pair_indicator! macro.
- fuzz: every scalar drove through the generic helper; pair
  indicators stress-tested by pairing adjacent samples of the fuzz
  input.
- Python tests (test_new_indicators.py): added to SCALAR
  parametrisation, plus algebraic reference values
  (variance of [2,4,6] = 8/3, MAD ignoring outlier = 0, monotone
  non-linear Spearman = 1, two-to-one Beta = 2, etc.) and a
  streaming-vs-batch test for the pair indicators.
- Node tests (indicators.test.js): extended the scalar factories
  map and added a pair-indicator section with the same algebraic
  reference values.
- crates/wickra/benches: bench_scalar entries for all 10 single-
  input new indicators.
- README: counter 71 → 84; Price Statistics family-table row
  expanded with the 13 new indicators.
- CHANGELOG: Unreleased section documents the family addition.

Wiki drafts (ghost-ignored, manual sync to wickra.wiki at release
time): indicator-ideas/families/wiki/family-12-statistik-regression/
contains 13 deep-dive pages plus _Sidebar / Indicators-Overview /
Warmup-Periods / Home fragments for the curator merge.

cargo check --workspace --all-features: clean.

* fix(family-12): remove unreachable defensive guards in hurst_exponent

The three guards (m < 2 continue, end > buf.len() break, denom == 0.0
return) are by-construction unreachable given the constructor invariant
period >= 2 * chunks: m = period / k for k in 1..=chunks always
satisfies m >= 2 and end = (c+1) * m <= k * m <= period = buf.len(),
and m_1 = period and m_2 = period / 2 are always distinct so the slope
denominator is strictly positive. Removing them brings codecov/patch
back to 100%.
2026-05-25 23:42:05 +02:00
kingchenc 5aa0949bce feat(family-13): add Ichimoku + Heikin-Ashi (#50)
Two new indicators in a brand-new "Ichimoku & alternative charts"
family:

- `Ichimoku` (Ichimoku Kinko Hyo): the full five-line cloud system
  (Tenkan-sen, Kijun-sen, Senkou Span A/B, Chikou Span). Classic
  (9, 26, 52, 26) defaults; configurable. Forward displacement is
  handled in an O(1) ring buffer so the visible Senkou A/B at bar n
  are the values computed at bar n-displacement.
- `HeikinAshi`: recursive candle smoothing transform emitting a
  four-field synthetic candle. Seeds ha_open from (open+close)/2 on
  the first bar.

Touchpoints: core + unit tests, mod.rs/lib.rs re-exports, Python +
Node + WASM bindings (multi-output via PyArray2 / interleaved Vec<f64>
/ Object+Float64Array), Python tests across smoke/new-indicators/
input-validation, Node parity tests, fuzz target (Candle), benches,
README family table + counter (71 -> 73, 8 -> 9 families), CHANGELOG.

Note: Renko, Kagi, and Point & Figure from the family-13 ideas list
are intentionally skipped. They are bar generators (the bar boundary
is defined by price moves, not by a fixed time interval) rather than
indicators that consume a candle stream, and belong in wickra-data
as candle/tick transforms alongside the existing tick-to-candle
aggregator and resampler.
2026-05-25 23:02:29 +02:00
kingchenc b971e671b4 test(family-10): cover cold paths flagged by codecov (#57)
Add tests that exercise the protective fallbacks reachable via flat or
zero-valued input:

- `CenterOfGravity::zero_window_uses_zero_fallback` — den == 0 branch.
- `EhlersStochastic::flat_window_emits_zero` — range == 0 branch.
- `Mama::flat_input_uses_phase_fallback` and the matching
  `SineWave` variant — `i1` collapses to zero on a constant series.
- `Fama::new_with_valid_limits_constructs_via_mama` exercises the
  `Ok(Self { inner: Mama::new(..)? })` arm that no other test reaches.

Three branches were genuinely unreachable by construction, so the dead
code is removed rather than masked with an attribute:

- `Mama` clamped `alpha > fast_limit` after the lower-bound clamp; the
  upper bound is implied by `delta_phase >= 1` and `alpha = fast / delta_phase`.
- `CyberneticCycle` had a `0.0` fallback after the warmup gate that the
  3-slot ring buffers preclude (`count >= 7` => all five `Some`s).
- `DecyclerOscillator` used a `let-else { return None }` over a pair of
  `Decycler::update` calls that always emit `Some` from the first bar.
2026-05-25 22:32:00 +02:00
kingchenc 7a18a26daf feat(family-10): add 16 Ehlers / Cycle (DSP) indicators (#49)
Implements Family 10 (Ehlers / Cycle) end-to-end across Rust core,
Python / Node / WASM bindings, fuzz, tests, benches and docs. This
is an entirely new family covering John Ehlers' digital-signal-
processing school of cycle analytics — a strong differentiator
versus TA-Lib and pandas-ta, which ship only fragments.

Indicators:
- MAMA (Mesa Adaptive MA) — multi-output { mama, fama }
- FAMA (Following Adaptive MA) — scalar wrapper around MAMA's slow line
- Fisher Transform — Gaussian-normalising price transform
- Inverse Fisher Transform — bounded oscillator (tanh-based)
- SuperSmoother — 2-pole Butterworth lowpass
- Roofing Filter — high-pass + SuperSmoother bandpass
- Decycler — price minus 2-pole high-pass (lag-free trend)
- Decycler Oscillator — fast / slow Decycler difference (MACD-like)
- Hilbert Dominant Cycle — phase-derived period estimator [6, 50]
- Sine Wave Indicator — sin(phase) with 45° lead companion
- Adaptive Cycle Indicator — half-period driver for adaptive oscillators
- Center of Gravity Oscillator — weighted-mass momentum
- Cybernetic Cycle Component — EasyLanguage classic
- Empirical Mode Decomposition — bandpass + envelope mean
- Ehlers Stochastic — Stochastic on Roofing Filter input, [-1, +1]
- Instantaneous Trendline — Ehlers 2-pole lag-free trend

Indicator count rises 71 -> 87 across nine families (was eight).

All sixteen pass batch == streaming equivalence, expose the standard
Indicator surface (update / batch / reset / is_ready / warmup_period
/ name), are fuzz-tested, benchmarked against the checked-in BTCUSDT
1-minute dataset and reach across all four bindings.

Wiki deep-dive drafts for every indicator + Sidebar / Overview /
Home / Warmup updates are staged under indicator-ideas/families/
wiki/family-10-ehlers-cycle/ in the main repo (ghost-ignored) for
the maintainer to publish to the wiki repo manually.
2026-05-25 22:14:27 +02:00