Commit Graph
100 Commits
Author SHA1 Message Date
kingchencandGitHub 4d602df8a3 release: bump 0.5.0 -> 0.5.1 (#162)
Version bump **0.5.0 → 0.5.1** for the Seasonality & Session family release (12 indicators, PR #161).

Bumped: `Cargo.toml` (workspace version + `wickra-core` dep), `Cargo.lock` (via `cargo build`), `bindings/python/pyproject.toml`, `bindings/node/package.json` (+ 6 `optionalDependencies`), the 6 `bindings/node/npm/<platform>/package.json`, both `package-lock.json` files, and `CHANGELOG.md` (`[Unreleased]` → `[0.5.1]` + compare URLs).

No code changes — version strings only.
2026-06-03 20:55:13 +02:00
kingchencandGitHub 3ab2d6ec2d feat(seasonality): add the Seasonality & Session family (12 indicators) (#161)
## Summary

Adds the **Seasonality & Session** family — the first family that reads the wall-clock fields of `Candle::timestamp`. A new private `calendar` module decomposes an epoch-millisecond instant (shifted by a per-indicator `utc_offset_minutes`) into civil fields via Howard Hinnant's branch-light `civil_from_days` algorithm. Session / day / month rollovers are detected automatically, so callers never have to invoke `reset()` at a boundary.

Indicator counter **339 → 351**; family count **20 → 21**.

## Indicators

| Shape | Indicators |
|-------|-----------|
| Scalar (`f64`) | `SessionVwap`, `AverageDailyRange`, `OvernightGap`, `TurnOfMonth`, `SeasonalZScore` |
| Struct | `SessionHighLow`, `SessionRange` (Asia/EU/US), `OvernightIntradayReturn` |
| Profile (`Vec<f64>`) | `TimeOfDayReturnProfile`, `DayOfWeekProfile`, `IntradayVolatilityProfile`, `VolumeByTimeProfile` |

## Bindings

The input is the **full** candle (`open, high, low, close, volume, timestamp`), not the `high/low/close` slice the value-indicator helper assumes, so the Python / Node / WASM bindings are custom full-candle implementations:

- **Python** — `update((o,h,l,c,v,ts))`; `batch(open, high, low, close, volume, timestamp)` → `PyArray1` (scalar) / `PyArray2` (struct & profile), warmup rows `NaN`.
- **Node** — `update(open, high, low, close, volume, timestamp)`; `batch(...)` → flat `Vec<f64>`; struct outputs as `#[napi(object)]` values.
- **WASM** — `update` only (multi-input precedent); profiles as `Float64Array`, structs as camelCase objects, `timestamp` as `BigInt`.

## Verification

- `wickra-core`: full per-branch unit tests, **100%** coverage target; 2852 lib tests + 334 doctests green.
- `cargo clippy --workspace --all-targets --all-features -- -D warnings`: clean.
- Node: 428 tests (dedicated `seasonality.test.js` streaming-vs-batch).
- Python: full suite + dedicated `test_seasonality.py` streaming-vs-batch.
- Counter check: mod-count == counted lib block == 351.
2026-06-03 20:31:32 +02:00
kingchencandGitHub 5e96d41916 chore: add REUSE-style LICENSES directory for license auto-detection (#160)
Adds a `LICENSES/` directory with SPDX-named copies of the existing license texts (`MIT.txt`, `Apache-2.0.txt`) per the [REUSE Specification](https://reuse.software/spec/).

## Why
Automated license scanners (the OpenSSF Best Practices BadgeApp, GitHub's license API, REUSE tooling) look for a top-level `LICENSE`/`COPYING` file or a `LICENSES/` directory with SPDX-named files. Our files are named `LICENSE-MIT` / `LICENSE-APACHE` (Rust convention), which these scanners do not recognize — so the BadgeApp's `license_location` check keeps auto-flipping to "Unmet".

## What
- New `LICENSES/MIT.txt` — byte-identical copy of `LICENSE-MIT`
- New `LICENSES/Apache-2.0.txt` — byte-identical copy of `LICENSE-APACHE`
- Existing `LICENSE-MIT` and `LICENSE-APACHE` are **unchanged**

The project remains dual-licensed under **MIT OR Apache-2.0**. This change is additive only.
2026-06-03 20:00:40 +02:00
kingchencandGitHub 099ae66b57 release: bump 0.4.7 -> 0.5.0 (#159)
Version bump for the 0.5.0 release, which ships the relicense to MIT OR Apache-2.0.

Stacked on #158 (base branch `chore/relicense-mit-apache`) so this PR's diff is the bump only. After #158 merges to main, GitHub retargets this PR to main; merge it, then tag `v0.5.0` to publish.

## Changes
- Bump 0.4.7 -> 0.5.0 across the Cargo workspace, Python `pyproject.toml`, Node `package.json` + 6 platform manifests + 2 lockfiles, and `Cargo.lock`.
- CHANGELOG: cut the [0.5.0] section (the relicense) and add compare URLs.
- SECURITY.md: supported versions 0.4.x -> 0.5.x.

Minor (not patch) bump: a relicense is a significant change. No code changes.

NOTE: do not tag/release until you give the go (irreversible publish to crates.io/PyPI/npm). Suggested merge order: #158 -> this -> tag `v0.5.0` -> then the downstream PRs.
2026-06-03 18:53:23 +02:00
kingchencandGitHub 11dd659b5f Relicense from PolyForm Noncommercial to MIT OR Apache-2.0 (#158)
Relicenses Wickra from PolyForm Noncommercial 1.0.0 to the dual, OSI-approved **MIT OR Apache-2.0** (the de-facto Rust convention). Wickra becomes permissive, commercial-use-permitted open source; users may choose either license.

## Changes
- Replace `LICENSE` (PolyForm) with `LICENSE-MIT` + `LICENSE-APACHE` (full texts).
- Cargo: workspace `license = "MIT OR Apache-2.0"` (SPDX) + all 7 sub-crates switched from `license-file.workspace` to `license.workspace`.
- `deny.toml`: drop PolyForm from the allowlist.
- Python: `pyproject.toml` PEP 639 SPDX expression; remove the non-commercial classifier (verified: sdist metadata emits `License-Expression: MIT OR Apache-2.0`).
- Node: `package.json`, the 6 platform manifests and both lockfiles.
- README + Python/Node/WASM binding READMEs, CONTRIBUTING, CITATION.cff, PR template, and the WASM `pkg.license` step in `release.yml`.
- SECURITY.md: refresh supported versions 0.1.x -> 0.4.x.
- CHANGELOG: note the relicense under [Unreleased].

## Notes
- No code changes; metadata/text only. `cargo build` and `cargo deny check licenses` pass locally.
- GitHub will auto-detect "MIT, Apache-2.0" once this lands (currently NOASSERTION).
- Matching downstream changes (org `.github` profile, webpage, docs) are in separate PRs; merge those together with the relicense release so the live sites and org profile do not claim MIT before the packages do.
2026-06-03 18:49:39 +02:00
kingchencandGitHub c096943bdf feat(breadth): complete the Market Breadth family (14 indicators) (#157)
Completes expansion-roadmap block **A2 — Market Breadth**: the 14 indicators that remained after the `AdvanceDecline` bootstrap, all built on the existing `CrossSection` input.

## Indicators (all scalar `Indicator<Input = CrossSection, Output = f64>`)

| Indicator | Reading |
|-----------|---------|
| `AdvanceDeclineRatio` | advancers / decliners |
| `AdVolumeLine` | cumulative net advancing volume |
| `McClellanOscillator` | 19/39 EMAs of ratio-adjusted net advances |
| `McClellanSummationIndex` | running total of the oscillator |
| `Trin` (Arms Index) | A/D ratio over up/down volume ratio |
| `BreadthThrust` (Zweig) | SMA of the advancing-issues share |
| `NewHighsNewLows` | new highs − new lows |
| `HighLowIndex` | SMA of the record-high percent |
| `PercentAboveMa` | % of the universe above its MA |
| `UpDownVolumeRatio` | advancing / declining volume |
| `BullishPercentIndex` | % on a point-and-figure buy signal |
| `CumulativeVolumeIndex` | volume-normalised cumulative net advancing volume |
| `AbsoluteBreadthIndex` | \|advancers − decliners\| |
| `TickIndex` | instantaneous net advancers − decliners |

## Input model

`AdVolumeLine` and `CumulativeVolumeIndex` are kept distinct (the latter normalises each tick's net advancing volume by total volume, so it stays comparable across volume regimes). `PercentAboveMa` and `BullishPercentIndex` need a per-symbol state signal that `Member` did not carry, so `Member` gains two additive flags (`above_ma`, `on_buy_signal`) via a new `Member::with_signals` constructor; the 4-arg `Member::new` leaves both cleared, so every existing caller and binding is unchanged. `CrossSection` gains volume / new-extreme / state aggregation helpers.

## Wiring

Fully wired across the Rust core, the python/node/wasm bindings, the cross-section fuzz target, the README + docs indicator counters (325 → 339), and dedicated python/node streaming-vs-batch tests. `fmt` / `test --workspace --all-features` / `clippy --workspace -D warnings` / node build+test / pytest all green locally.
2026-06-03 17:24:33 +02:00
kingchencandGitHub c44f625e69 release: bump 0.4.6 -> 0.4.7 (#156)
Routine patch release. Ships the 10 pairwise stat-arb indicators added to Price Statistics in #154 (Rolling Correlation, Rolling Covariance, OU Half-Life, Kalman Hedge Ratio, Variance Ratio, Spread Bollinger Bands, Spread Hurst, Distance SSD, Granger Causality, Beta-Neutral Spread) together with the new Market Breadth family and its `CrossSection` input type (AdvanceDecline).

Version strings bumped `0.4.6 -> 0.4.7` across:
- `Cargo.toml` (workspace + `wickra-core` dep), `Cargo.lock`
- `bindings/python/pyproject.toml`
- `bindings/node/package.json` (+ 6 optional platform deps) and the 6 `npm/<platform>/package.json`
- `bindings/node/package-lock.json`, `examples/node/package-lock.json`
- `CHANGELOG.md` — `[Unreleased]` rolled into `[0.4.7] - 2026-06-03` with refreshed compare links

No code changes. `fmt` / `test --workspace` / `clippy --workspace -D warnings` green locally.
2026-06-03 16:02:30 +02:00
kingchencandGitHub 46dc8f5a00 ci(sync-about): read-only PR counter check, no bot fix-up push (#155)
## Problem
On every indicator PR the `sync-about` workflow found `docs/README.md` lagging `lib.rs` (the wiring only bumped `README.md`) and pushed a `wickra-bot` *"sync indicator count"* commit onto the PR head. That push uses `GITHUB_TOKEN`, which **triggers no workflows**, so it moved the PR head onto a commit with no CI run — and the **Codecov patch status** (keyed to the PR head sha) stopped surfacing on the PR.

## Fix
- The indicator wiring (`ScriptHelpers/_common.py` `wire_readme_counter`) now bumps **both** `README.md` and `docs/README.md` in the author's code commit, so the counter is already correct when CI runs.
- This workflow's PR flow is reduced to a **read-only check** that fails loud (fork and same-repo PRs alike) if either counter is stale, and **never pushes**.
- The `GITHUB_TOKEN` job permission drops from `contents: write` back to `read` (OpenSSF Scorecard: Token-Permissions). The removed `ctx` step + push steps are gone.
- The `main`/tag outward syncs (About description, docs/webpage/wiki/org) are **unchanged** — they use the `ABOUT_SYNC_TOKEN` PAT, not `GITHUB_TOKEN`.

## Effect
Indicator PRs keep their head on the code commit → the Codecov patch status surfaces again. No functional change to merged-main state (the counts still land, now inside the squash-merged code commit).
2026-06-03 15:40:24 +02:00
kingchencandGitHub a3a1ae4dba Add 10 pairwise stat-arb indicators to Price Statistics (#154)
Adds ten pairwise `(f64, f64)` indicators to the **Price Statistics** family, completing the A1 stat-arb expansion block.

## Indicators

**Scalar output:**
- **RollingCorrelation** — rolling Pearson correlation of period-over-period *returns* (distinct from level-based `PearsonCorrelation`).
- **RollingCovariance** — rolling covariance of returns.
- **OuHalfLife** — Ornstein–Uhlenbeck half-life of mean reversion of the spread `a − b`.
- **SpreadHurst** — Hurst exponent of the spread (variance-of-lagged-differences fit) for regime detection.
- **DistanceSsd** — Gatev sum-of-squared-deviations between two start-normalised series.
- **BetaNeutralSpread** — rolling OLS regression residual `a − (α + β·b)`.
- **VarianceRatio** — Lo–MacKinlay variance-ratio test on the spread (two params: `period`, `q`).
- **GrangerCausality** — F-statistic for whether `b` predicts `a` (two params: `period`, `lag`).

**Struct output (custom bindings):**
- **KalmanHedgeRatio** — dynamic hedge ratio via a Kalman filter → `{ hedgeRatio, intercept, spread }`.
- **SpreadBollingerBands** — Bollinger bands on the spread → `{ middle, upper, lower, percentB }`.

## Notes
- No new traits or input families: all use the native `Indicator<Input = (f64, f64)>` (precedent `Beta`, `Cointegration`).
- Adds `Error::InvalidParameter` for floating-point constructor parameters (Kalman `delta`/`observation_var`, `num_std`).
- Full Python/Node/WASM bindings; the two struct-output indicators are hand-written, the rest use the pair macros.
- Indicator count 315 → 325; README, family rows, `__init__`, fuzz target, and CHANGELOG updated.

## Verification
- `cargo test --workspace --all-features` — green (2676 core lib + 308 doc).
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` — clean.
- Node: `npm run build && npm test` — 410 passing (`index.d.ts`/`index.js` regenerated).
- Python: `pytest` — 684 passing.
2026-06-03 15:39:55 +02:00
kingchencandGitHub 53941b7b07 feat: add Market Breadth family with CrossSection input (#153)
## What

Adds a new indicator input type and family for **market-breadth** analysis — indicators that aggregate the state of an entire universe of symbols at each tick, rather than a single instrument's price. This is the last open input-type on the expansion roadmap (S10) and unblocks the remaining breadth indicators (McClellan, TRIN, High-Low Index, ...).

## Core

- **`CrossSection` input type** (`crates/wickra-core/src/cross_section.rs`) — one tick carrying the per-symbol state of the whole universe as a `Vec<Member>` + `timestamp`. Each `Member` precomputes a signed `change` (sign classifies advancing / declining / unchanged), a `volume`, and `new_high` / `new_low` extreme flags, so the breadth indicators stay stateless per tick. Both `Member` and `CrossSection` are `#[non_exhaustive]` for additive field growth. `CrossSection::new` validates the universe (non-empty, finite changes, finite non-negative volumes); `new_unchecked` skips validation for hot paths. `advancers()` / `decliners()` count by sign.
- **`Error::InvalidCrossSection`** variant for the validation failures.
- **`AdvanceDecline`** (`advance_decline.rs`) — the Advance/Decline Line: the running cumulative sum of net advancing-minus-declining issues. `Input = CrossSection`, `Output = f64`, ready after the first tick.
- New **"Market Breadth"** `FAMILIES` group; indicator count **314 → 315**, family count nineteen → twenty.

## Bindings

All custom (CrossSection is non-scalar, so no macros apply). The universe crosses each boundary as parallel arrays (`change`, `volume`, `new_high`, `new_low`):
- **Python / Node** expose `update` + `batch` (one array group per tick). Node satisfies the completeness contract (`update`/`batch`/`reset`/`isReady`/`warmupPeriod`).
- **WASM** exposes only `update` (the universe is ragged across ticks, matching the other multi-input wasm indicators) with numeric high/low flags.
- Python `map_err` gains the new error arm; `__init__.py` gets a `# Market Breadth` section in both the import and `__all__` blocks. `index.d.ts` / `index.js` regenerated.

## Tests / Fuzz

- Dedicated **streaming-vs-batch + reference-value + ragged-rejection** tests in Python (`test_new_indicators.py`) and Node (`indicators.test.js`) — kept out of the scalar/candle parametrize lists.
- Rust unit tests cover every reject branch (empty / non-finite change / negative & non-finite volume) and every indicator branch.
- New fuzz target `indicator_update_crosssection` drives `AdvanceDecline` over bounded ragged universes built with `new_unchecked`.

## Verify

- `cargo fmt --all` clean
- `cargo test -p wickra-core --lib` → 2593 passed; `--doc` → 298 passed
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` clean
- `cd bindings/node && npm run build && npm test` → 398 passed
- `maturin develop --release` + `pytest bindings/python/tests` → all passed
- counter check: mod-count 315 == lib-block 315
2026-06-03 04:11:10 +02:00
kingchencandGitHub 72ec65bbde fix: classify pairwise indicators into Price Statistics family (#152)
## What

The `FAMILIES` table in `crates/wickra-core/src/indicators/mod.rs` had drifted from the indicator count: `mod`-count was **314** but the FAMILIES total asserted **309**.

The five pairwise indicators `Cointegration`, `LeadLagCrossCorrelation`, `PairSpreadZScore`, `PairwiseBeta` and `RelativeStrengthAB` were exported via `pub use` but never assigned to a `FAMILIES` group — even though the README and docs already list them under **Price Statistics**. The "−5 offset" was therefore unclassified drift, not an intentional cross-asset offset.

## Change

- Add the five indicators to the `Price Statistics` group, next to the existing pairwise cluster (`PearsonCorrelation` / `Beta` / `SpearmanCorrelation`).
- Bump the drift assert `309 → 314` so the FAMILIES total now equals the `mod`-count exactly (offset 0).

No new indicators, no binding or doc changes — purely re-classification. The `mod`-count stays 314, so no counter bump.

## Verify

- `cargo fmt --all` clean
- `cargo test -p wickra-core --lib` → 2578 passed (incl. the FAMILIES drift test)
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` clean
2026-06-03 03:38:14 +02:00
kingchencandGitHub 82d1a4fe77 release: bump 0.4.5 -> 0.4.6 (#151)
Routine patch release. Ships the **19 TA-Lib parity indicators** (DM components, price transforms, ROC ratio forms, LinReg intercept / TSF, MACDFIX / MACDEXT / SAREXT, Hilbert phasor / DC-phase / trend-mode) added in #148, with the cold-path coverage fix from #150 — indicator count **314**, repo back at 100%.

Version strings bumped `0.4.5 -> 0.4.6` across:
- `Cargo.toml` (workspace + `wickra-core` dep), `Cargo.lock`
- `bindings/python/pyproject.toml`
- `bindings/node/package.json` (+ 6 optional platform deps) and the 6 `npm/<platform>/package.json`
- `bindings/node/package-lock.json`, `examples/node/package-lock.json`
- `CHANGELOG.md` — `[Unreleased]` rolled into `[0.4.6] - 2026-06-03` with refreshed compare links

No code changes. `fmt` / `clippy --workspace -D warnings` green locally.
2026-06-03 02:56:00 +02:00
kingchencandGitHub f71b3b6b49 test: cover the cold paths in the TA-Lib parity batch (100% patch) (#150)
PR #148 merged at **99.67%** patch coverage — `codecov/patch` flagged seven by-construction-rare lines in three of the new indicators that no test exercised. This brings the batch back to 100%.

**`ht_dcphase` / `ht_trendmode`** (6 lines) — the dominant-cycle phase recovery guards against a near-zero imaginary part (where `atan(real/imag)` is undefined) by collapsing to ±90° on the sign of the real part. That branch is unreachable with realistic price data. Extracted the phase-unwrap arithmetic into a private `compute_dc_phase(real, imag, smooth_period)` helper — a pure refactor with byte-identical output — and unit-tested it directly with crafted `(real, imag)` pairs, covering both the ±90 collapse and the normal `atan` path.

**`sar_ext`** (1 line) — `Accel::validate`'s non-finite guard was only ever hit for non-positive terms, never non-finite ones, despite the test comment claiming both. Added `NaN` / `infinity` cases on the long and short acceleration schedules.

No behaviour or public-API change. Locally: `cargo test -p wickra-core` (2578 + 297 doctests) and `clippy --workspace -D warnings` all green.
2026-06-03 02:48:11 +02:00
kingchencandGitHub d081cb9581 docs: list the 19 TA-Lib parity indicators in the README family rows (#149)
The TA-Lib parity batch (#148) bumped the indicator counter to 314, but `sync-about` only syncs the *number* — the family-table prose in the README still listed the pre-batch set. This fills the 19 new names into their existing family rows so the catalogue matches the count.

- **Momentum Oscillators**: ROC Percentage (ROCP), ROC Ratio (ROCR), ROC Ratio 100 (ROCR100)
- **Trend & Directional**: MACD Fixed (MACDFIX), MACD Extended (MACDEXT), Plus DM, Minus DM, Plus DI, Minus DI, DX
- **Trailing Stops**: Parabolic SAR Extended (SAREXT)
- **Price Statistics**: Mid Price, Mid Point, Average Price, Linear Regression Intercept, Time Series Forecast
- **Ehlers / Cycle (DSP)**: Hilbert Phasor, Hilbert DC Phase, Hilbert Trend Mode

No new family — the "nineteen families" wording and the 314 counter are untouched. Docs-only, no code changes.
2026-06-03 02:36:09 +02:00
kingchencandGitHub 9eb46f144a feat: TA-Lib parity — 19 standalone indicators (DM components, price transforms, ROC/LinReg/MACD/SAR variants, Hilbert outputs) (#148)
Closes the remaining TA-Lib function-name gap by shipping each missing or
bundled-only function as a real, standalone, fully-covered indicator. 19 new
indicators across 5 families; mod-count 295 -> 314.

### Trend & Directional — Directional Movement components
- `PlusDm` (`PLUS_DM`), `MinusDm` (`MINUS_DM`) — Wilder-smoothed ±DM.
- `PlusDi` (`PLUS_DI`), `MinusDi` (`MINUS_DI`) — `100·smoothed(±DM)/ATR`.
- `Dx` (`DX`) — `100·|+DI−−DI|/(+DI+−DI)`.

### Price Statistics
- `AvgPrice` (`AVGPRICE`) — `(O+H+L+C)/4`.
- `MidPoint` (`MIDPOINT`) — `(max+min)/2` of a scalar series over N.
- `MidPrice` (`MIDPRICE`) — `(highestHigh+lowestLow)/2` over N.
- `LinRegIntercept` (`LINEARREG_INTERCEPT`) — OLS intercept.
- `Tsf` (`TSF`) — time series forecast `a + b·period`.

### Momentum Oscillators
- `Rocp` (`ROCP`), `Rocr` (`ROCR`), `Rocr100` (`ROCR100`) — ROC ratio forms.

### Trailing Stops
- `SarExt` (`SAREXT`) — Parabolic SAR with start value, reversal offset,
  separate long/short acceleration, signed output.

### Trend & Directional — MACD variants
- `MacdFix` (`MACDFIX`) — MACD fixed 12/26.
- `MacdExt` (`MACDEXT`) — MACD with a selectable moving-average type per line
  (new public `MaType` enum: SMA/EMA/WMA/DEMA/TEMA/TRIMA).

### Ehlers / Cycle (DSP) — Hilbert transform outputs
- `HtPhasor` (`HT_PHASOR`) — in-phase / quadrature components.
- `HtDcPhase` (`HT_DCPHASE`) — dominant-cycle phase (degrees).
- `HtTrendMode` (`HT_TRENDMODE`) — trend (1) vs cycle (0) classification.

Each indicator ships the full chain: core + every-branch unit tests, Python /
Node / WASM bindings, fuzz coverage, README counter + family rows, CHANGELOG.
`cargo test`, doctests, `clippy -D warnings`, `npm test` and pytest all green
locally; mod-count == lib-block == README counter (314), FAMILIES total 309.
2026-06-03 02:26:38 +02:00
kingchencandGitHub 9a98e9bf55 release: bump 0.4.4 -> 0.4.5 (#147)
Version bump for 0.4.5: ships Anchored RSI, Volume Profile, TPO Profile and the Alt-Chart Bars family (Renko/Kagi/Point & Figure). Indicator count 289 -> 295.
2026-06-02 22:14:47 +02:00
kingchencandGitHub d4b3f9dbd1 feat: add Alt-Chart Bars (Renko, Kagi, Point & Figure) via a BarBuilder trait (#146)
Introduces a BarBuilder trait for price-driven chart constructors that emit a variable number of bars per candle (deliberately not Indicator). Adds Renko (box-size bricks, 2-box reversal), Kagi (reversal-amount segments) and Point & Figure (box-size X/O columns, N-box reversal) in a new Alt-Chart Bars family, with custom Python/Node/WASM bindings, a dedicated fuzz target, tests and docs. Indicator count 292 -> 295.
2026-06-02 21:56:00 +02:00
kingchencandGitHub f37eedd44e feat: add Volume Profile and TPO Profile to the market profile family (#145)
Volume Profile exposes the full per-bin volume histogram (price bounds plus raw distribution) that Value Area reduces to POC/VAH/VAL. TPO Profile is the volume-agnostic Time-Price-Opportunity letter count over a rolling window. Both candle-input, Vec-output, Market Profile family, with custom Python/Node/WASM bindings, fuzz, benches, tests and docs. Indicator count 290 -> 292.
2026-06-02 21:16:30 +02:00
kingchencandGitHub 93097db482 feat: add Anchored RSI to the momentum oscillators family (#144)
Cumulative Relative Strength Index whose averaging begins at a runtime-chosen anchor bar (set_anchor), the momentum counterpart to Anchored VWAP. Scalar f64 input, 0..=100 output; wired through core, Python, Node and WASM bindings, fuzz, benches, tests and docs. Indicator count 289 -> 290.
2026-06-02 20:50:56 +02:00
kingchencandGitHub 2f3a0b9149 release: bump 0.4.3 -> 0.4.4 (#143)
Release 0.4.4.

Version bump only — no code changes. Ships the 40 TA-Lib candlestick patterns
(parts 2–9, #132–#141, 249 → 289 indicators) plus the candlestick rejection-
guard coverage tests (#142) that landed on `main` since 0.4.3.

Bumped: workspace `Cargo.toml` (+ `wickra-core` dep) and `Cargo.lock`,
`bindings/python/pyproject.toml`, `bindings/node/package.json` (+ 6 platform
`optionalDependencies`), the 6 `bindings/node/npm/*/package.json`, both
`package-lock.json` files, and `CHANGELOG.md` ([Unreleased] → [0.4.4]).
2026-06-02 18:10:24 +02:00
kingchencandGitHub 49c0fd7dd5 ci: Dependabot cooldown + accept residual zizmor notes (#136)
Clears the remaining zizmor code-scanning findings on this repo.

**Fixed**
- `dependabot-cooldown` (5): a 7-day cooldown on every update ecosystem
  (cargo, npm, pip, ci-pip, github-actions) so Dependabot waits a week after a
  release before opening the bump PR.

**Accepted via `.github/zizmor.yml`** (no workflow code changed)
- `template-injection` (sync-about.yml): false positive — every expansion is the
  internal `grep -c` indicator count, not attacker-controllable.
- `use-trusted-publishing` (release.yml): OIDC migration tracked separately.
- `superfluous-actions` (release.yml): `softprops/action-gh-release` kept deliberately.

Verified with zizmor 1.25.2: 0 findings.
2026-06-02 17:59:46 +02:00
kingchencandGitHub 3d98592461 test: cover candlestick pattern rejection guards (#142)
Closes the coverage gaps in the candlestick-pattern family that landed across
PRs #132–#141. Codecov flagged 25 uncovered lines on `main` (99.93%) — all in
the new candlestick files, and all early-return rejection guards or unused
`Default` impls that the accept-path unit tests never exercised.

This PR adds focused white-box unit tests (one or two per affected file) that
drive each rejection branch through the public `update()` API:

- **Zero-range guards** — a flat bar (`high == low`) at the relevant window
  position: `DojiStar`, `InNeck`, `OnNeck`, `Thrusting`, `SeparatingLines`,
  `EveningDojiStar`, `MorningDojiStar`, `GapSideBySideWhite`,
  `FallingThreeMethods`, `RisingThreeMethods`, `MatHold`.
- **Too-short trigger body** — a wide-range bar with a tiny body that fails the
  "long body" check: the three-bar stars, the three-methods pair, `MatHold`,
  `SeparatingLines`.
- **Shape-specific guards** — `GapSideBySideWhite` body-size mismatch, `MatHold`
  bar-2 fails to gap up.
- **`Default` impls** — `LongLine` / `ShortLine` (`default()` was never called).

No production code changes; tests only. `cargo test -p wickra-core` and
`cargo clippy -p wickra-core --all-targets -- -D warnings` pass locally.
2026-06-02 17:52:04 +02:00
kingchencandGitHub 124efb4432 feat: TA-Lib candlestick patterns — tasuki-gap/unique-three-river/marubozu-pair/concealing-baby-swallow (part 9 of 9) (#141)
The final batch of the TA-Lib candlestick roadmap. Adds five patterns, each a streaming `Indicator<Input = Candle, Output = f64>` emitting the family's uniform `±1.0 / 0.0` sign convention, fully wired across the Rust core, Python / Node / WASM bindings, fuzz target and reference tests.

- **Tasuki Gap** (`CDLTASUKIGAP`) — a 3-bar continuation: two same-coloured candles gap in the trend direction, then an opposite candle opens within the second body and closes back into the gap without filling it; upside +1, downside -1.
- **Unique Three River** (`CDLUNIQUE3RIVER`) — a 3-bar bullish reversal: a long black candle, a black candle probing a new low with its body inside the first, then a small white candle held below it; bullish +1.
- **Closing Marubozu** (`CDLCLOSINGMARUBOZU`) — a single long-bodied candle with no shadow on the close end; +1 (white, closes at the high) or -1 (black, closes at the low).
- **Opening Marubozu** — a single long-bodied candle with no shadow on the open end; +1 (white, opens at the low) or -1 (black, opens at the high). No direct TA-Lib equivalent — completes the pair with the closing marubozu.
- **Concealing Baby Swallow** (`CDLCONCEALBABYSWALL`) — a rare 4-bar bullish capitulation: two black marubozu, a black candle gapping down with an upper shadow into the second, then a large black candle engulfing it entirely; bullish +1.

Body and shadow thresholds follow the geometric house style (fixed fractions of the bar range) rather than TA-Lib's rolling averages.

Counter 284 → 289 (mod-count == lib counted block; FAMILIES total 279 → 284).

Stacked on #140 (`feat/cdl-gap-methods`); base retargets to `main` as the stack merges down.
2026-06-02 17:27:39 +02:00
kingchencandGitHub 4d0bc08efd feat: TA-Lib candlestick patterns — gap-three-methods/stalled/stick-sandwich/takuri (part 8 of 9) (#140)
Adds five TA-Lib candlestick patterns, each a streaming `Indicator<Input = Candle, Output = f64>` emitting the family's uniform `±1.0 / 0.0` sign convention, fully wired across the Rust core, Python / Node / WASM bindings, fuzz target and reference tests.

- **Upside Gap Three Methods** (`CDLXSIDEGAP3METHODS`) — a 3-bar bullish continuation: two white candles gap up, then a black candle opens within the second body and closes within the first; bullish +1.
- **Downside Gap Three Methods** (`CDLXSIDEGAP3METHODS`) — the bearish mirror: two black candles gap down, then a white candle opens within the second body and closes within the first; bearish -1.
- **Stalled Pattern** (`CDLSTALLEDPATTERN`) — a 3-bar bearish reversal warning: two long white candles then a small white candle riding the shoulder, signalling the rally is stalling; bearish -1.
- **Stick Sandwich** (`CDLSTICKSANDWICH`) — a 3-bar bullish reversal: two black candles closing at the same level sandwich a white candle, marking a support floor; bullish +1.
- **Takuri** (`CDLTAKURI`) — a single-bar bullish reversal, a strict Dragonfly Doji with a negligible upper shadow and very long lower shadow; bullish +1.

Body and shadow thresholds follow the geometric house style (fixed fractions of the bar range) rather than TA-Lib's rolling averages. Upside / Downside Gap Three Methods share the `CDLXSIDEGAP3METHODS` code, so the second carries a manual CHANGELOG entry (as with Rising / Falling Three Methods).

Counter 279 → 284 (mod-count == lib counted block; FAMILIES total 274 → 279).

Stacked on #139 (`feat/cdl-lines`); base retargets to `main` once the predecessor merges.
2026-06-02 17:24:42 +02:00
kingchencandGitHub c2c85c7ecf feat: TA-Lib candlestick patterns — matching-low/lines/three-methods (part 7 of 9) (#139)
Adds five TA-Lib candlestick patterns, all `Input = Candle`, `Output = f64`
(`+1.0` bullish / `-1.0` bearish / `0.0` no pattern), wired across core,
Python/Node/WASM bindings, fuzz, and tests.

- **Matching Low** (`CDLMATCHINGLOW`) — 2-bar bullish reversal: two black candles in a decline share the same close, signalling selling pressure is exhausting; bullish +1.
- **Long Line** (`CDLLONGLINE`) — a candle whose range beats a rolling average of recent ranges with a body-dominated range; bullish +1 (white) / bearish -1 (black).
- **Short Line** (`CDLSHORTLINE`) — a compact candle whose range falls below the rolling average with a body-dominated range; bullish +1 (white) / bearish -1 (black).
- **Rising Three Methods** (`CDLRISEFALL3METHODS`) — 5-bar bullish continuation: a long white candle, three small bars holding within its range, then a white breakout to new highs; bullish +1.
- **Falling Three Methods** (`CDLRISEFALL3METHODS`) — the bearish mirror: a long black candle, three small bars within its range, then a black breakdown to new lows; bearish -1.

Counter 274 → 279 (mod-count == lib counted block; FAMILIES total 269 → 274).

Stacked on #138 (part 6 of 9); base retargets to `main` as the chain merges.
2026-06-02 17:16:15 +02:00
kingchencandGitHub 04ae145126 feat: TA-Lib candlestick patterns — separating/kicking/ladder/mat-hold (part 6 of 9) (#138) 2026-06-02 17:06:40 +02:00
e4ca9c3f8f feat: TA-Lib candlestick patterns — hikkake-mod/pigeon/neck-lines (part 5 of 9) (#137)
* feat: add hikkake-modified, homing-pigeon and neck-line candlestick patterns

Five patterns, all `Input = Candle`, `Output = f64`:

- Modified Hikkake (CDLHIKKAKEMOD) — a close-confirmed Hikkake: an inside bar
  then a breakout that closes back inside the inside-bar range; bullish +1,
  bearish -1.
- Homing Pigeon (CDLHOMINGPIGEON) — two black candles, the second a small body
  inside the first, a bullish reversal; +1.
- On-Neck (CDLONNECK) — long black bar then a white bar closing at its low (the
  neckline), a bearish continuation; -1.
- In-Neck (CDLINNECK) — long black bar then a white bar closing just into its
  body, a bearish continuation; -1.
- Thrusting (CDLTHRUSTING) — long black bar then a white bar closing well into
  but below the midpoint of its body, a bearish continuation; -1.

Counter 264 -> 269 (mod-count == lib counted block; FAMILIES total 259 -> 264).

* chore: sync indicator count to 269

---------

Co-authored-by: wickra-bot <wickra-bot@users.noreply.github.com>
2026-06-02 17:03:49 +02:00
d43bc9ddf3 feat: TA-Lib candlestick patterns — doji-star/gap/high-wave/hikkake (part 4 of 9) (#135)
* feat: add doji-star, gap, high-wave and hikkake candlestick patterns

Five patterns, all `Input = Candle`, `Output = f64`:

- Evening Doji Star (CDLEVENINGDOJISTAR) — bearish top reversal: long white bar,
  a doji gapping up, then a black bar closing deep into the first body; -1
  (penetration configurable, default 0.3).
- Morning Doji Star (CDLMORNINGDOJISTAR) — bullish bottom reversal mirror; +1.
- Gap Side-by-Side White (CDLGAPSIDESIDEWHITE) — two similar white candles
  opening side by side after a gap, a continuation; gap up +1, gap down -1.
- High-Wave (CDLHIGHWAVE) — a small body with very long shadows on both sides,
  an extreme indecision flag; +1 on detection.
- Hikkake (CDLHIKKAKE) — an inside bar followed by a failed breakout (a trap);
  bullish +1, bearish -1.

Counter 259 -> 264 (mod-count == lib counted block; FAMILIES total 254 -> 259).

* chore: sync indicator count to 264

---------

Co-authored-by: wickra-bot <wickra-bot@users.noreply.github.com>
2026-06-02 16:54:47 +02:00
244d754707 feat: TA-Lib candlestick patterns — Doji family (part 3 of 9) (#134)
* feat: add Doji-family candlestick patterns

Five single-/two-bar Doji patterns, all `Input = Candle`, `Output = f64`:

- Doji Star (CDLDOJISTAR) — a long body followed by a doji gapping away in the
  trend direction; bullish +1 (after a black bar), bearish -1 (after a white bar).
- Dragonfly Doji (CDLDRAGONFLYDOJI) — a doji opening and closing at the high with
  a long lower shadow; bullish +1.
- Gravestone Doji (CDLGRAVESTONEDOJI) — a doji opening and closing at the low with
  a long upper shadow; bearish -1.
- Long-Legged Doji (CDLLONGLEGGEDDOJI) — a doji with long shadows on both sides; a
  non-directional indecision flag, +1 on detection.
- Rickshaw Man (CDLRICKSHAWMAN) — a long-legged doji with the body centred in the
  range; a non-directional indecision flag, +1 on detection.

Counter 254 -> 259 (mod-count == lib counted block; FAMILIES total 249 -> 254).

* chore: sync indicator count to 259

---------

Co-authored-by: wickra-bot <wickra-bot@users.noreply.github.com>
2026-06-02 16:45:08 +02:00
03ceac1f3b feat: TA-Lib candlestick patterns — abandoned/advance/belt/break/counter (part 2 of 9) (#132)
* feat: add Abandoned Baby candlestick pattern (CDLABANDONEDBABY)

* feat: add Advance Block candlestick pattern (CDLADVANCEBLOCK)

* feat: add Belt Hold candlestick pattern (CDLBELTHOLD)

* feat: add Breakaway and Counterattack candlestick patterns (CDLBREAKAWAY, CDLCOUNTERATTACK)

Breakaway is a 5-bar reversal: a trend gaps away on the second bar, drifts
two more bars, then the fifth bar snaps back and closes inside the bar1/bar2
body gap (bullish +1, bearish -1). Counterattack is a 2-bar reversal where an
opposite-coloured long second bar closes level with the first (the counterattack
line; bullish +1, bearish -1).

Also suppress libtest's spanless `large_stack_arrays` false positive in
wickra-core test builds: the `#[test]` harness collects every test into a
compiler-generated array of references that crosses clippy's 16 KB threshold
once the suite passes ~2048 unit tests. The allow is scoped to `cfg(test)`, so
library code is still linted for genuinely large stack arrays.

* chore: sync indicator count to 254

---------

Co-authored-by: wickra-bot <wickra-bot@users.noreply.github.com>
2026-06-02 16:34:15 +02:00
kingchencandGitHub 73415cd2dc ci: zizmor security hardening (#133)
* ci: pass ref context through env in release tag step

zizmor flagged the "Resolve target tag" step in release.yml for
template-injection: github.event_name / github.ref / github.ref_name
were interpolated directly into the shell script. On a tag push the tag
name is attacker-influenceable, so a crafted tag could inject commands.

Move all three context values into the step env and reference them as
shell variables instead. Verified with zizmor 1.16.3: template-injection
findings on release.yml drop from 2 to 0.

* ci: accept release.yml build caches via zizmor config

The release pipeline restores Swatinem/rust-cache and actions/setup-node
caches as a deliberate optimisation. zizmor flags all eight under
cache-poisoning because release.yml publishes to crates.io / PyPI / npm.
The caches are maintainer-controlled and the restore speedup is kept on
purpose, so accept the finding via a zizmor config ignore for release.yml
rather than running cache-free release builds. (Six of the eight are
actions/setup-node, reported at Low confidence.)

Adds .github/zizmor.yml; release.yml now reports 0 high findings.

* ci: drop persisted checkout credentials on read-only jobs

zizmor's artipacked audit flags every actions/checkout that keeps the
default persisted credential: the token is written to the runner's
.git/config, where it can leak if a later step packs .git into an
uploaded artifact, or be read by another step in the same job.

Set persist-credentials: false on the 20 checkouts whose jobs never push
or authenticate to git (build/test/clippy/msrv/coverage/supply-chain/
fuzz/python/wasm/node in ci.yml, plus bench.yml, codeql.yml, the seven
release.yml build/publish jobs, and sync-metadata.yml). The publish and
release jobs authenticate to crates.io / npm / PyPI / the GitHub API with
their own tokens, not persisted git credentials, so this is safe.

sync-about.yml genuinely pushes the indicator-count fix-up to the PR
branch, so it keeps its credential and is accepted via .github/zizmor.yml.
zizmor artipacked for the repo drops to 0 (0 high, 0 medium remaining).
2026-06-02 02:08:40 +02:00
kingchencandGitHub ad51dbc1a3 fix: keep docs/README indicator count in sync-about (#131)
* fix: keep docs/README indicator count in sync-about

The docs/README.md pointer prose names the indicator count
("**N indicators**") but was never part of the sync-about counter
pipeline, so it drifted to 214 while the real count (lib.rs) is 249.

Add docs/README.md to the PR-flow gate check, the patch sed, and the
fix-up commit so future count changes keep it in sync, and correct the
current stale value to 249.

* docs: fix stale Wiki reference in CONTRIBUTING layout table

The project-layout table still described docs/ as a "Pointer to the
project Wiki" even though the wiki was retired and the docs moved to
docs.wickra.org (wickra-lib/wickra-docs). Align the row with the
already-correct doc-site section further down the same file.
2026-06-01 23:35:03 +02:00
kingchencandGitHub f09057aaf1 feat: TA-Lib candlestick patterns — crows & three-line (part 1 of 9) (#130)
* feat: add Two Crows candlestick pattern (CDL2CROWS)

* feat: add Upside Gap Two Crows candlestick pattern (CDLUPSIDEGAP2CROWS)

* feat: add Identical Three Crows candlestick pattern (CDLIDENTICAL3CROWS)

* feat: add Three Line Strike candlestick pattern (CDL3LINESTRIKE)

* feat: add Three Stars in the South candlestick pattern (CDL3STARSINSOUTH)
2026-06-01 23:20:10 +02:00
kingchencandGitHub 458ef2385e ci: add zizmor GitHub Actions security scanning (#129) 2026-06-01 22:34:03 +02:00
kingchencandGitHub 2d140419bb feat: derivatives basis & calendar-spread indicators (part 3 of 3) (#128)
* feat(derivatives): TermStructureBasis indicator (core)

* feat(derivatives): CalendarSpread indicator (core)

* feat(derivatives): Python, Node and WASM bindings for basis & calendar-spread indicators

* test(derivatives): Python and Node tests for basis & calendar-spread indicators

* docs(derivatives): README row + counter 242->244, CHANGELOG part 3; fuzz basis indicators
2026-06-01 22:07:35 +02:00
kingchencandGitHub 8e5bfd07ce feat: derivatives open-interest, flow & liquidation indicators (part 2 of 3) (#127)
* feat(derivatives): OIPriceDivergence indicator (core)

* feat(derivatives): OIWeighted indicator (core)

* feat(derivatives): LongShortRatio indicator (core)

* feat(derivatives): TakerBuySellRatio indicator (core)

* feat(derivatives): LiquidationFeatures multi-output indicator (core)

* feat(derivatives): Python, Node and WASM bindings for OI, flow & liquidation indicators

* test(derivatives): Python and Node tests for OI, flow & liquidation indicators

* fuzz(derivatives): drive OI, flow & liquidation indicators in derivatives target

* docs(derivatives): README row + counter 237->242, CHANGELOG part 2
2026-06-01 21:50:35 +02:00
kingchencandGitHub 5eb820a9c7 feat: derivatives funding & open-interest indicators (part 1 of 3) (#126)
* feat(derivatives): DerivativesTick input type + InvalidDerivatives error

* feat(derivatives): FundingRate indicator (core)

* feat(derivatives): FundingRateMean indicator (core)

* feat(derivatives): FundingRateZScore indicator (core)

* feat(derivatives): FundingBasis indicator (core)

* feat(derivatives): OpenInterestDelta indicator (core)

* feat(derivatives): Python, Node and WASM bindings for funding & OI-delta indicators

* test(derivatives): Python and Node tests for funding & OI-delta indicators

* bench(derivatives): synthetic-tick bench + derivatives fuzz target

* docs(derivatives): README family row + counter 232->237, CHANGELOG entry
2026-06-01 21:26:37 +02:00
kingchencandGitHub fae60e0d54 release: bump 0.4.2 -> 0.4.3 (#125) 2026-06-01 20:49:11 +02:00
kingchencandGitHub 433b06367f ci: bump actions/checkout v4.3.1 -> v6.0.2 in codeql & scorecard (#124) 2026-06-01 20:24:20 +02:00
kingchencandGitHub 3dd7010129 feat: footprint microstructure indicator (part 4 of 4) (#123) 2026-06-01 20:00:58 +02:00
kingchencandGitHub 4f11df0e33 feat: microstructure price-impact & depth indicators (part 3 of 4) (#122)
* feat: effective spread microstructure indicator (part 3 of 4)

* feat: realized spread microstructure indicator (part 3 of 4)

* feat: kyle's lambda microstructure indicator (part 3 of 4)

* feat: depth slope microstructure indicator (part 3 of 4)
2026-06-01 19:45:38 +02:00
kingchencandGitHub b5d9e47a2e release: bump 0.4.1 -> 0.4.2 (#121) 2026-06-01 18:29:32 +02:00
kingchencandGitHub 511d3a27f7 ci: self-updating README banner + fix webpage count sync (#119)
* ci: fix webpage count sync crashing on removed public/hero.svg

The webpage indicator-count sync sed'd index.md, .vitepress/config.ts and
public/hero.svg, but hero.svg was removed from wickra-lib/webpage (the count is
now baked into og-banner.webp at build time from index.md). Under 'bash -e' the
missing file made sed exit 2 before the commit/push, so the count never reached
the webpage repo and its OG banner stayed stale — masked green by the step's
continue-on-error. Drops public/hero.svg from the sed and git add; index.md and
.vitepress/config.ts (both still carry the count) remain.

* docs: point README banner at the self-updating org profile image

Switches the top README banner from https://wickra.org/og-banner.webp (baked at
webpage deploy time) to the org profile banner that wickra-lib/.github's
banner.yml regenerates from the indicator count on every sync
(raw.githubusercontent.com/wickra-lib/.github/main/profile/wickra-banner.webp).
The ?v=227 query busts GitHub's Camo image cache; the next commit teaches
sync-about to bump it with the count.

* ci: bump README banner cache-buster alongside the indicator count

Extends the PR-head README counter patch to also rewrite wickra-banner.webp?v=N
to the current count. The banner now points at the org profile image, whose
content changes when .github/banner.yml regenerates it; bumping the ?v query
busts GitHub's Camo cache so the README shows the new banner immediately. Rides
in the existing count-sync commit, so no extra commit lands on main.

* docs: changelog entry for self-updating README banner and sync fix
2026-06-01 18:19:23 +02:00
kingchencandGitHub 5b23b36261 ci: pin CI dependency installs by hash (Scorecard PinnedDependencies) (#114)
* ci: use npm ci instead of npm install for reproducible installs

Pins the node binding dependency install to the committed package-lock.json
integrity hashes (OpenSSF Scorecard PinnedDependencies). npm ci installs
strictly from the lockfile; npm install could resolve newer patch versions.
Covers ci.yml and both release.yml node steps.

* ci: hash-pin Python dev tooling in ci.yml (Scorecard #19)

Replaces the unpinned 'pip install maturin pytest numpy hypothesis' with a
hash-locked '--require-hashes -r' install (OpenSSF Scorecard PinnedDependencies).

Two lock files are needed because numpy publishes no single release with wheels
for both cp39 and cp313 (<=2.0.2 has cp39 only, >=2.1 drops cp39):
  ci-dev-py39.txt  numpy 2.0.2  (Python 3.9, + tomli/exceptiongroup)
  ci-dev-py3.txt   numpy 2.4.6  (Python 3.10+)

The step selects the file by matrix.python-version under shell: bash. Both are
generated from ci-dev.in via uv (scripts/update-lockfiles.sh, added next).

* ci: hash-pin Python deps in bench.yml (Scorecard #16)

Replaces the unpinned 'pip install maturin numpy pandas talipp finta' with a
hash-locked '--require-hashes -r .github/requirements/bench.txt' install.
bench.yml runs on a single Python version (3.11), so one lock file (generated
from bench.in via uv) is sufficient.

* build: add scripts/update-lockfiles.sh to regenerate all lockfiles

One command refreshes every committed lockfile across languages: Cargo.lock and
fuzz/Cargo.lock (cargo update), the Node binding package-lock.json, and the
hash-pinned Python requirements under .github/requirements/ (uv pip compile
--generate-hashes). Uses uv for the Python locks so a target Python version's
hashed transitive closure can be resolved without that interpreter installed
(needed for the numpy cp39/cp313 split); bootstraps uv if absent.

.gitattributes pins *.sh to LF so the script stays runnable on Linux/macOS.

* ci: split ci-dev requirements per Python version + Dependabot rehash

Splits the single ci-dev.in into ci-dev-py39.in (numpy <2.1, the last series
with cp39 wheels) and ci-dev-py3.in (3.10+), giving a 1:1 .in->.txt layout.
The cap keeps Python 3.9 permanently installable and stops Dependabot from
proposing 3.9-breaking numpy bumps.

Adds a Dependabot pip entry on /.github/requirements so the hash-locked tooling
is kept current automatically; the canonical manual refresh stays
scripts/update-lockfiles.sh. Only the '# via -r' provenance lines in the .txt
change; no package versions or hashes move.

* ci: cache pip and npm downloads in the PR-loop jobs

Adds setup-python cache: pip (ci.yml python matrix + bench.yml, keyed on the
hash-locked requirements) and setup-node cache: npm (ci.yml node job, keyed on
bindings/node/package-lock.json), on both the primary and retry setup steps.

Scoped to jobs that actually install dependencies; the examples-smoke and
clippy-bindings jobs install nothing and are left uncached. release.yml is
intentionally left out: it runs only on tag push (not the PR loop) and is the
publish-critical path, so no caching is added there.

* docs: document hash-pinned requirements and update-lockfiles.sh

Updates the lockfile-policy table: the bindings/python row no longer claims CI
installs tooling unpinned, and a new .github/requirements row documents the
hash-locked CI/bench tooling and the per-Python-version ci-dev split. Adds a
paragraph pointing contributors at scripts/update-lockfiles.sh (uv-based,
self-bootstrapping) as the canonical lockfile refresh.

* docs: changelog entry for hash-pinned CI dependency installs
2026-06-01 17:58:31 +02:00
kingchencandGitHub 5867f71450 feat: trade-flow microstructure indicators (part 2 of 4) (#113)
* feat(core): add 3 trade-flow microstructure indicators

SignedVolume (per-trade size signed by aggressor), CumulativeVolumeDelta
(running signed-volume total), and TradeImbalance (rolling buy/sell volume
imbalance over a trade window). All consume the Trade type, with full unit
coverage. Extends the Microstructure family.

* feat(bindings): expose trade-flow microstructure indicators

Python, Node and WASM bindings for SignedVolume, CumulativeVolumeDelta and
TradeImbalance. Each takes a trade via update(price, size, is_buy); Python and
Node expose a batch over three parallel arrays, WASM exposes per-trade update.
Regenerates node index.d.ts/.js.

* test(bindings,fuzz,bench): cover trade-flow microstructure indicators

Python and Node: reference values, streaming-vs-batch, lifecycle/repr and input
validation (zero window, negative size, non-positive price, mismatched batch
lengths). New indicator_update_trade fuzz target. Synthetic trade-tape benches
(signed_volume cheapest, trade_imbalance windowed/expensive).

* docs: add trade-flow indicators + bump counter to 227

README Microstructure family row gains signed volume / CVD / trade imbalance and
the counter goes 224 -> 227; CHANGELOG records the trade-flow indicators.
2026-06-01 16:38:48 +02:00
kingchencandGitHub 2be21df803 feat: order-book microstructure indicators (part 1 of 4) (#112)
* feat(core): add microstructure input types (OrderBook, Trade, TradeQuote)

New non-OHLCV value types for the order-book / trade-flow indicator family:
Level, OrderBook (sorted, uncrossed depth snapshot), Side, Trade (with
aggressor side), and TradeQuote (trade paired with prevailing mid). Each has a
validating constructor plus a new_unchecked hot-path constructor, with full
unit coverage. Adds InvalidOrderBook / InvalidTrade error variants.

* feat(core): add 5 order-book microstructure indicators

OrderBookImbalanceTop1/TopN/Full (signed depth imbalance), Microprice
(size-weighted fair value), and QuotedSpread (top-of-book spread in bps). All
consume the OrderBook snapshot type, emit f64, are stateless and ready after
the first snapshot, with full unit coverage. Registers a new Microstructure
family in the taxonomy.

* feat(bindings): expose order-book microstructure indicators

Python, Node, and WASM bindings for OrderBookImbalanceTop1/TopN/Full,
Microprice and QuotedSpread. Each takes a depth snapshot via four equal-length
(bid_px, bid_sz, ask_px, ask_sz) arrays. Python and Node expose a batch over a
list of snapshots; WASM exposes per-snapshot update (the streaming model that
fits a browser book feed). Regenerates node index.d.ts/.js and registers the
new InvalidOrderBook/InvalidTrade arms in the Python error mapping.

* test(bindings,fuzz): cover order-book microstructure indicators

Python: smoke, reference values, streaming-vs-batch, lifecycle/repr and input
validation (mismatched lengths, crossed book, misordered levels, zero levels)
for all five order-book indicators. Node: reference values, streaming-vs-batch,
and rejection cases. Adds an indicator_update_orderbook fuzz target driving
every order-book indicator over arbitrary (incl. degenerate) snapshots.

* bench(microstructure): synthetic order-book benchmarks

Add a bench_orderbook_input harness and synthesise a five-level book around
each candle close (no order-book dataset ships with the repo). Benches the
cheapest (top-of-book imbalance) and most-expensive (full-depth imbalance) plus
microprice, matching the curated cheapest/expensive-per-family approach.

* docs: add Microstructure family + bump indicator counter to 224

README gains the Microstructure family row (order-book imbalance, microprice,
quoted spread) and the indicator counter goes 219 -> 224 across seventeen
families; CHANGELOG records the new order-book indicators and value types.
2026-06-01 16:06:22 +02:00
kingchenc 498b74a5ae chore(license): point package metadata at the modified license file
The LICENSE now carries an Additional Permissions section on top of
PolyForm Noncommercial 1.0.0, so the bare SPDX id no longer describes
it exactly. Update the package manifests to reference the actual file
instead of claiming the unmodified standard:

- Cargo (workspace + all crates): license -> license-file = "LICENSE"
- npm (main + 6 platform packages): LicenseRef-Wickra-Noncommercial-1.0.0
- PyPI: license text notes the additional personal-account permissions
2026-06-01 15:28:18 +02:00
kingchenc 921a250715 docs(license): grant personal-account trading use to match README
Append an Additional Permissions section to the PolyForm Noncommercial
License 1.0.0. The base PolyForm text is unmodified; the section only
broadens the grant. It explicitly permits a natural person to use the
software for their own personal account, including running a trading
bot on their own capital for profit, matching the README's plain-English
summary (hobby trading bots: all fine). Commercial sale of the software
or of services built around it still requires a commercial license.
2026-06-01 15:05:03 +02:00
kingchencandGitHub 0479191b66 feat: signed candlestick directional ±1 encoding (Doji signed mode) (#111)
* feat(core): add signed dragonfly/gravestone encoding to Doji

Doji gains an opt-in `.signed()` mode that classifies a detected Doji by the
position of its body within the bar range: dragonfly (long lower shadow) emits
+1.0 (bullish), gravestone (long upper shadow) emits -1.0 (bearish), and a
long-legged/standard Doji emits 0.0. The default detection-flag behaviour
(+1.0/0.0) is unchanged, so existing callers are unaffected.

The other 14 candlestick patterns already emit the uniform +1 bull / -1 bear /
0 none convention; document that explicitly with a "Signed +-1 encoding"
section on each so the whole family is a consistent drop-in ML feature.

* feat(bindings): expose Doji signed mode in python, node, wasm

Hand-write the Doji binding in all three language bindings (instead of the
shared candle-pattern macro) so it accepts an opt-in `signed` flag and exposes
an `is_signed`/`isSigned` accessor:

- Python: `Doji(signed=False)` keyword argument
- Node: `new Doji(signed?)` optional constructor argument (index.d.ts/.js
  regenerated via napi build)
- WASM: `new Doji(signed?)` optional constructor argument

The default construction is unchanged, so existing callers keep the
direction-less +1/0 detection flag.

* test(bindings,fuzz): cover Doji signed dragonfly/gravestone encoding

- python: dragonfly(+1)/gravestone(-1)/neutral(0) and default-flag cases in
  test_known_values
- node: equivalent signed/default assertions in indicators.test.js
- fuzz: drive a signed Doji alongside the default in indicator_update_candle

* docs: document signed candlestick convention and Doji signed mode

README gains a candlestick sign-convention note; CHANGELOG records the new
opt-in Doji signed dragonfly/gravestone encoding under [Unreleased].
2026-06-01 14:37:20 +02:00
kingchencandGitHub 4631519885 release: bump 0.4.0 -> 0.4.1 (#110)
Releases the cross-asset / pairwise indicator family (PR #109):
PairwiseBeta, PairSpreadZScore, LeadLagCrossCorrelation, Cointegration,
RelativeStrengthAB. Indicator count 214 -> 219.

Bumps workspace + binding versions and the CHANGELOG ([Unreleased] ->
[0.4.1]) with the new compare URL.
2026-06-01 13:58:50 +02:00
kingchencandGitHub 0b85142ad1 feat: cross-asset / pairwise indicators (5 new) (#109)
* feat(core): add PairwiseBeta cross-asset indicator

Rolling OLS slope of one asset's log-returns on another's. Unlike Beta,
which regresses the raw inputs it is fed, PairwiseBeta differences
consecutive prices into log-returns internally -- the conventional way to
measure cross-asset beta, where a beta on price levels would be dominated
by the shared trend.

Two-series Indicator<Input = (f64, f64)>, exposed in Rust, Python, Node
and WASM, with unit/known-value/streaming tests and a pair fuzz target.

* feat(core): add PairSpreadZScore cross-asset indicator

Standardised log-spread ln(a) - beta*ln(b) of a pair, where beta is a
rolling-OLS hedge ratio and the spread is z-scored over its own look-back.
The canonical mean-reversion / statistical-arbitrage entry signal, with
independent beta_period and z_period windows.

Two-series Indicator<Input = (f64, f64)>, exposed in Rust, Python, Node
and WASM, with sign/known-value/streaming tests and a pair fuzz target.

* feat(core): add LeadLagCrossCorrelation cross-asset indicator

Reports the integer offset k in [-max_lag, max_lag] that maximises
|corr(a[t], b[t+k])|, answering which of two assets leads the other and by
how many bars. A positive lag means a leads b. Fully causal: a's window is
held centred while b's window slides across the buffered history, so every
lag is evaluated only against data already seen.

Struct output { lag, correlation }, exposed in Rust, Python, Node and WASM
with lead-detection/streaming tests and a pair fuzz driver.

* feat(core): add Cointegration (Engle-Granger + ADF) indicator

Rolling pairs-trading screen: an OLS hedge ratio of a on b, the spread
(residual) a - (alpha + beta*b), and an augmented Dickey-Fuller t-statistic
on the spread with configurable lags. A strongly negative statistic flags a
mean-reverting, tradeable spread. Includes a small Gaussian-elimination
solver for the augmented regression.

Struct output { hedge_ratio, spread, adf_stat }, exposed in Rust, Python,
Node and WASM with stationarity/hedge-ratio/streaming tests and a pair fuzz
driver.

* feat(core): add RelativeStrengthAB cross-asset indicator

Comparative relative strength of two assets: the ratio line a/b together
with its moving average and its RSI, the classic asset-vs-asset /
asset-vs-index rotation screen. Composes the existing Sma and Rsi over the
ratio; a zero denominator or non-finite price is skipped.

Struct output { ratio, ratio_ma, ratio_rsi }, exposed in Rust, Python, Node
and WASM with flat/rising-ratio/streaming tests and a pair fuzz driver.

* test(cointegration): cover ADF guard branches

The ADF helper's short-series and degrees-of-freedom guards and the
zero-dispersion (perfect AR) path are unreachable through the public
Cointegration API (period >= 2*adf_lags + 4), so exercise them with direct
unit tests on adf_no_constant. The second linear solve cannot be singular
once the coefficient solve on the same matrix has succeeded, so it now uses
expect() instead of a dead error branch.
2026-06-01 13:45:21 +02:00
kingchencandGitHub 1ab9bc70d1 ci(release): make the release immutability-ready (draft then publish) (#108)
GitHub release immutability locks a release's assets at publish time. The
current flow publishes the release in github-release and only afterwards uploads
the Sigstore provenance bundle (P21.1e) via 'gh release upload', which
immutability would reject (actions/attest-build-provenance#734).

Reorder to draft -> attach everything -> publish:
- github-release now creates the release as a draft (draft: true) with all build
  artefacts.
- attestations attaches the provenance bundle to the draft (gh release upload
  works on drafts), unchanged otherwise.
- a new publish-release job flips the draft to published + latest, gated on
  'always() && needs.github-release.result == success' so a Sigstore hiccup in
  attestations costs only the provenance asset, never the release — the same
  isolation as before. A skipped github-release (failed publish) skips this too.

Correct with immutability off (today: release ends published with every asset)
and on (later, user toggle: all assets present before the lock). No behaviour
removed; nothing deleted.
2026-06-01 12:06:20 +02:00
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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).
2026-06-01 01:28:53 +02:00
kingchencandGitHub f7f947e048 docs(changelog): record provenance attestations + CI security tooling (Unreleased) (#102) 2026-06-01 01:08:14 +02:00
kingchencandGitHub 01dd08714b ci: harden workflows against network/CDN flakes (resilient cache + setup retries) (#101) 2026-06-01 01:07:56 +02:00
kingchencandGitHub 0fa70c9882 ci: pin remaining GitHub Actions to commit SHAs (P21.1c) (#100) 2026-06-01 01:07:34 +02:00
kingchencandGitHub debe4523d5 ci: pass untrusted workflow contexts via env to prevent shell injection (P21.1a) (#99) 2026-06-01 00:32:24 +02:00
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub c8e5d8a658 ci: add OpenSSF Scorecard workflow + badge (P13.1) (#90) 2026-05-31 22:05:00 +02:00
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 86a595d505 ci(sync-about): auto-sync the published version to wickra-docs on release (#87) 2026-05-31 21:48:53 +02:00
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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).
2026-05-30 19:50:45 +02:00
kingchencandGitHub 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
kingchencandGitHub 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.
2026-05-30 19:06:48 +02:00
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 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
kingchencandGitHub 4f9ed34884 feat(family-11): add DeMark suite (TD Setup, Sequential, DeMarker, REI, Pressure) (#48)
* feat(family-11): add DeMark suite (TD Setup, Sequential, DeMarker, REI, Pressure)

Family 11 (DeMark) was previously empty; this PR adds five
streaming-first DeMark indicators in one batch.

- **TD Setup** (`TdSetup`): parameterised buy/sell setup counter.
  Counts consecutive bars whose close is less-than (buy) or
  greater-than (sell) the close `lookback` bars earlier, saturating
  at `target`. Emits a signed `f64` so callers read direction from
  the sign and run length from the magnitude. Classic config:
  `lookback = 4`, `target = 9`.

- **TD Sequential** (`TdSequential`): the canonical Setup + Countdown
  exhaustion pattern. Output struct `{ setup, countdown, direction }`
  exposes both phase counts as signed numbers plus the active
  countdown direction (+1 buy / -1 sell / 0 none). Countdown
  activates when a setup completes and tracks the close-vs-high/low
  comparison `countdown_lookback` bars back, capped at
  `countdown_target`. Classic: 4/9/2/13.

- **TD DeMarker** (`TdDeMarker`): bounded [0, 1] oscillator from the
  rolling average of upward high expansion (DeMax) and downward low
  expansion (DeMin). Falls back to the neutral 0.5 on a flat market
  (denominator zero).

- **TD REI** (`TdRei`): Range Expansion Index, bounded [-100, 100].
  Per-bar numerator gated on a range-overlap condition vs the bars
  5 and 6 back, normalised by a `period`-bar sum of absolute moves.
  Classic period = 5. Saturates at +100 in a slow steady uptrend
  and at -100 in the mirror downtrend; emits 0 on a flat market.

- **TD Pressure** (`TdPressure`): volume-weighted buying / selling
  pressure normalised to [-100, 100]. Per-bar pressure is the
  intra-bar close-vs-open ratio scaled by volume; the output is the
  rolling mean divided by the rolling mean volume. Zero-range bars
  contribute zero (avoid the undefined ratio) and a flat zero-volume
  window falls back to 0.

Bindings: all five exposed in Python (`ta.TDSetup`, `ta.TDSequential`,
`ta.TDDeMarker`, `ta.TDREI`, `ta.TDPressure`), Node (`wickra.TDSetup`
etc.), and WASM. Multi-output classes (`TDSequential`) return either
a struct `{ setup, countdown, direction }` per bar (streaming) or a
flat interleaved Float64Array of length `3 * n` (batch).

Tests: 47 unit tests across the five new core files (pure-trend
saturation, flat-market neutral fallback, batch-equals-streaming,
zero-parameter rejection, reset semantics, accessors). Python
test_new_indicators.py picks up all five plus a multi-output TD
Sequential block. Node indicators.test.js picks up all five.
Reference values added to test_known_values.py.

Fuzz: candle fuzz target sweeps all five DeMark indicators with the
existing `Vec<f64>` -> `Vec<Candle>` driver.

Benches: BTCUSDT 1-minute dataset benches for each DeMark indicator
in `crates/wickra/benches/indicators.rs`.

Docs: README family table gains a "DeMark" row; indicator counter
bumped 71 -> 76. CHANGELOG entry added under [Unreleased]. Wiki
drafts (deep-dive pages + Sidebar / Overview / Warmup-Periods / Home
deltas) live under `indicator-ideas/families/wiki/family-11-demark/`
for manual merge into the wiki repo.

* feat(family-11): add 7 missing DeMark indicators

Complete the DeMark suite (family 11) with the seven indicators not
covered by the first commit: TD Combo, TD Countdown, TD Lines (TDST),
TD Range Projection, TD Differential, TD Open, and TD Risk Level.

- TdCombo: aggressive countdown variant with three strictness rules
  on top of the classic close-vs-low/high lookback rule (monotone
  low/high, monotone close vs prior bar).
- TdCountdown: standalone 13-bar countdown packaging only the signed
  countdown count (the setup machine runs internally).
- TdLines: TDST horizontal support/resistance levels from the
  highest-high / lowest-low bars of the most-recently-completed
  setup, exposed as a multi-output struct.
- TdRangeProjection: DeMark X-projection of the next bar's high and
  low from the current bar's OHLC via an open-vs-close-weighted
  pivot (three branches: close<open, close>open, close==open).
- TdDifferential: two-bar buying-pressure vs selling-pressure
  reversal pattern emitting +1/-1/0.
- TdOpen: gap-and-fade reversal pattern (open outside prior range
  with subsequent recovery into it) emitting +1/-1/0.
- TdRiskLevel: protective stop levels derived from the setup
  extreme bar +/- its true range.

All seven are wired through Rust core, Python, Node and WASM
bindings, registered in the candle-stream fuzz target, given
benchmark entries on the BTCUSDT 1-minute dataset, and covered by
streaming-vs-batch equivalence, reference-value, lifecycle and
input-validation tests on the Python and Node sides. README counter
moves 76 -> 83 and the CHANGELOG "family 11" entry is extended to
list all twelve indicators.

* fix(td_risk_level tests): check first emission at idx 12, not last bar

TdRiskLevel re-ratchets the sell-risk level on each subsequent setup
completion, so a strictly rising series produces 22.0 at idx 19 (latest
setup) rather than 15.0 (first setup). The test comment already named
idx 12 as the reference; switch the assertion from out[-1] to out[12]
to match the reference computation.

* test(family-11): cover buy-direction branches in TD indicators

Add downtrend tests to TdSequential, TdCombo and TdCountdown so the
buy-side countdown/combo increment branches are exercised; remove an
empty `if buy_countdown == target {}` block in TdSequential whose
behavior is already enforced by the outer strict `<` guard.

Closes codecov/patch gaps reported on PR #48 (10 missed lines across
the three files).
2026-05-25 20:36:36 +02:00