7f1a6df202ff6a7fc2cb4b2b27e8b83a7ea7657f
210 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7f1a6df202 |
ci(sync-about): Push counter fix to PR branch instead of main (#56)
Previously the workflow patched README.md on main after every push, producing an unsigned 'chore: sync indicator count' commit per merge. Now the README counter is kept in sync on the PR side instead: on every pull_request event, the workflow checks out the PR's head ref, compares grep -c '^mod ' to the README counter, and if they differ, pushes a fix-up commit back onto the PR branch using the default GITHUB_TOKEN. When the PR is squash-merged, that fix-up commit is folded into the single web-flow-signed merge commit on main — so main's history never shows a separate bot commit. About description and Wiki sync still run on push to main / v* tags via the existing PAT, since both reach outside the main repo (Administration:write and the .wiki repo). For PRs from forks the workflow cannot push back; it emits a hard ::error:: pointing at README.md so the contributor can fix the counter manually. Pushes via GITHUB_TOKEN do not re-trigger downstream workflows (GitHub's anti-recursion policy), so the fix-up commit costs zero extra CI minutes — only sync-about itself re-runs on the next synchronize event and no-ops once the counter matches. |
||
|
|
1ea05fb2a1 | chore: sync indicator count to 85 [skip ci] | ||
|
|
24e723fa7d |
feat: Family 02 Momentum Oscillators — RVI / PGO / KST / SMI / Laguerre / Connors / Inertia (#40)
* feat(rvi): add Relative Vigor Index
Dorsey's RVI = SMA(close - open, period) / SMA(high - low, period) over
a rolling window of period candles. Candle input, single parameter
period (default 10). Positive on average-bullish windows, negative on
average-bearish. Holds the previous value if the entire window has
zero range (denominator undefined).
Reference: Donald Dorsey, also pandas-ta rvi.
Touchpoints: rvi.rs + mod.rs + lib.rs re-export, PyRvi + __init__.py
+ test_new_indicators CANDLE_SCALAR + test_known_values reference,
RviNode (4-column OHLC batch) + index.d.ts/index.js + indicators.test
.js factory + reference, WasmRvi + make_candle_ohlc helper, candle-fuzz
target + criterion bench, README + CHANGELOG.
* feat(pgo): add Pretty Good Oscillator
Mark Johnson's PGO = (close - SMA(close, period)) / EMA(TR, period).
Counts roughly how many ATR-equivalents the close sits from its
period-bar mean. Candle input, single parameter period (default 14).
Johnson's heuristic uses +3/-3 crossings as entry signals.
Touchpoints: pgo.rs + mod.rs + lib.rs re-export, PyPgo + __init__.py
+ test_new_indicators CANDLE_SCALAR + test_known_values flat-close
reference, PgoNode (h/l/c) + index.d.ts/index.js + indicators.test.js
factory + reference, WasmPgo, candle-fuzz target + bench, README +
CHANGELOG.
* feat(kst): add Know Sure Thing (Pring)
Pring's long-horizon momentum oscillator: weighted sum of four
SMA-smoothed ROC series with fixed weights 1, 2, 3, 4, plus an SMA
signal line. Nine parameters (four ROC periods, four SMA periods, one
signal period); classic() applies Pring's recommended defaults.
Multi-output indicator emitting KstOutput { kst, signal }.
Touchpoints: kst.rs + mod.rs + lib.rs re-export, PyKst + __init__.py
+ test_new_indicators MULTI + test_known_values flat-input reference,
KstNode + KstValue + index.d.ts/index.js + indicators.test.js multi
factory + reference, WasmKst (manual JsValue object), scalar-fuzz
target (handled outside the f64-output drive helper), README +
CHANGELOG.
* feat(smi): add Stochastic Momentum Index (Blau)
Blau's doubly-EMA-smoothed bounded oscillator: measures the close's
displacement from the centre of the recent high-low range, scaled by
the smoothed range. Candle input, three parameters (period, d_period,
d2_period) with defaults 5 / 3 / 3.
Internally feeds both the displacement-EMA stack and the range-EMA
stack on every candle so they warm up in parallel (gating either
behind the other starves the second by one input).
Touchpoints: smi.rs + mod.rs + lib.rs re-export, PySmi + __init__.py
+ test_new_indicators CANDLE_SCALAR + test_known_values flat-input
reference, SmiNode + index.d.ts/index.js + indicators.test.js factory
+ reference, WasmSmi, candle-fuzz target, README + CHANGELOG.
* feat(laguerre-rsi): add Ehlers Laguerre RSI
Four-stage Laguerre polynomial filter wrapped in an RSI-style up/down
accumulator. Single gamma in [0, 1] (default 0.5) trades lag for
smoothness. State is seeded by setting all four L_i to the first input
so a constant series stays at the neutral 50. Output clamped to
[0, 100] to absorb floating-point rounding.
Reference: Ehlers, Time Warp - Without Space Travel, 2002.
Touchpoints: laguerre_rsi.rs + mod.rs + lib.rs re-export, PyLaguerreRsi
+ __init__.py + test_new_indicators SCALAR + test_known_values neutral
reference, LaguerreRsiNode + index.d.ts/index.js + indicators.test.js
factory + reference, WasmLaguerreRsi via scalar macro, scalar-fuzz
target, README + CHANGELOG.
* feat(connors-rsi): add Connors RSI (CRSI)
Larry Connors' 3-component aggregate: RSI(close), RSI(streak), and
PercentRank of the 1-period return over the last period_rank returns.
Each component is bounded in [0, 100] so the aggregate is too.
Three parameters (period_rsi, period_streak, period_rank) with
defaults 3 / 2 / 100. Streak tracks consecutive up/down runs (resets
to 0 on unchanged close).
Touchpoints: connors_rsi.rs + mod.rs + lib.rs re-export, PyConnorsRsi
+ __init__.py + test_new_indicators SCALAR + test_known_values bounded
reference, ConnorsRsiNode + index.d.ts/index.js + indicators.test.js
factory + reference, WasmConnorsRsi via scalar macro, scalar-fuzz
target, README + CHANGELOG.
* feat(inertia): add Dorsey Inertia (RVI + LinReg)
Donald Dorsey's Inertia — a LinearRegression smoothing of the RVI
series. Endpoint of an n-bar least-squares fit of RVI is the indicator
reading. Preserves trend direction while damping the ratio. Candle
input, two parameters (rvi_period, linreg_period) with defaults 14 / 20.
Touchpoints: inertia.rs + mod.rs + lib.rs re-export, PyInertia +
__init__.py + test_new_indicators CANDLE_SCALAR + test_known_values
constant reference, InertiaNode (4-column OHLC batch) + index.d.ts /
index.js + indicators.test.js factory + reference, WasmInertia,
candle-fuzz target, README + CHANGELOG.
* test(kst): Move KST out of MULTI dict (it is scalar-input)
KST sits in the MULTI dict (candle-input, multi-output) but its
update() takes a single f64, not a candle tuple. The shared streaming
loop in test_multi_streaming_matches_batch fed the OHLCV tuple in,
which crashed with `TypeError: argument 'value': must be real number,
not tuple` on every Python matrix entry.
Split into a new MULTI_SCALAR_INPUT dict with its own test function
that feeds the close-price stream as floats. KST is currently the
only such indicator; structure is ready for future scalar-input
multi-output additions (e.g. some MACD-shaped indicators).
* test(coverage): Cover SMI zero-range and ConnorsRsi zero-prev cold paths
codecov/patch on PR 40 flagged two uncovered defensive branches:
- SMI returns self.current early when the smoothed range collapses to
zero (`r2 <= 0.0`) so the formula stays defined. Exercised by feeding
bars where high == low.
- ConnorsRsi skips the ROC ring-buffer update when the previous price
is exactly zero so the divide-by-zero in `(input - prev) / prev` is
impossible. Exercised by seeding the first bar at 0.0.
|
||
|
|
a39adb9dae | chore: sync indicator count to 78 [skip ci] | ||
|
|
1cd5d1d8da |
fix(ci): Drop site/index.md from sync-about workflow (#55)
site/ is local-only (listed in .git/info/exclude), so the sed call in the Patch step aborted the workflow on every push to main with "sed: can't read site/index.md: No such file or directory". That kept the README counter from being committed and skipped the wiki sync. Patches README only now. site/ stays out of CI until the marketing site is promoted. |
||
|
|
466faddd87 |
feat: Family 01 Moving Averages — ALMA / McGinley / FRAMA / VIDYA / JMA / Alligator / EVWMA (#39)
* feat(alma): add Arnaud Legoux Moving Average
Gaussian-weighted moving average with configurable centre (offset in
[0, 1]) and kernel width (sigma > 0). Pre-computes normalised weights
at construction so each update is a single rolling window dot product.
Reference: Arnaud Legoux and Dimitrios Kouzis-Loukas, 2009.
Touchpoints:
- crates/wickra-core: alma.rs + mod.rs + lib.rs re-export
- bindings/python: PyAlma + __init__.py + test_new_indicators +
test_known_values reference
- bindings/node: AlmaNode + index.d.ts/index.js + indicators.test.js
factory + reference value
- bindings/wasm: wasm_scalar_indicator! macro
- fuzz: indicator_update target covers ALMA(9, 0.85, 6.0)
- crates/wickra/benches: bench_scalar entry
- README + CHANGELOG: Moving Averages row + Unreleased entry
* feat(mcginley): add McGinley Dynamic moving average
John McGinley's self-adjusting moving average with the recurrence
MD + (price - MD) / (0.6 * period * (price / MD)^4). Speeds up when
price falls below the indicator and damps when price runs above the
indicator. Seeded with the simple average of the first period inputs.
Reference: McGinley, Technical Analysis of Stocks & Commodities, 1990.
Touchpoints:
- crates/wickra-core: mcginley_dynamic.rs + mod.rs + lib.rs re-export
- bindings/python: PyMcGinleyDynamic + __init__.py + test_new_indicators
+ test_known_values reference
- bindings/node: McGinleyDynamicNode (scalar macro) + index.d.ts/index.js
+ indicators.test.js factory + reference value
- bindings/wasm: wasm_scalar_indicator! macro
- fuzz: indicator_update target covers McGinleyDynamic(10)
- crates/wickra/benches: bench_scalar entry
- README + CHANGELOG: Moving Averages row + Unreleased entry
* feat(frama): add Fractal Adaptive Moving Average
Ehlers' FRAMA adapts its smoothing constant to the fractal dimension of
the recent window: tight tracking in trends, heavy smoothing in chop.
Uses the close-only variant where max/min over each window half drive
the dimension estimate. Period must be even (default 16).
Reference: Ehlers, Fractal Adaptive Moving Average, 2005.
Touchpoints:
- crates/wickra-core: frama.rs + mod.rs + lib.rs re-export
- bindings/python: PyFrama + __init__.py + test_new_indicators +
test_known_values reference (constant series + uptrend tracking)
- bindings/node: FramaNode (scalar macro) + index.d.ts/index.js +
indicators.test.js factory + reference value
- bindings/wasm: wasm_scalar_indicator! macro
- fuzz: indicator_update target covers Frama(16)
- crates/wickra/benches: bench_scalar entry
- README + CHANGELOG: Moving Averages row + Unreleased entry
* feat(vidya): add Variable Index Dynamic Average
Chande's VIDYA — an EMA whose alpha scales with |CMO(cmo_period)| / 100.
Strong directional momentum lifts the smoothing constant toward the
EMA-of-period rate; flat or choppy windows shrink it toward zero so
VIDYA coasts on its previous value. Two parameters: period (14) and
cmo_period (9). Reuses the existing wickra-core Cmo internally.
Reference: Chande, Stocks & Commodities, 1992.
Also fixes a silent gap from
|
||
|
|
178fbfd68e |
ci: Add sync-about workflow to auto-update indicator count (#38)
Counts `mod xxx;` declarations in crates/wickra-core/src/indicators/mod.rs on every push to main, every PR, and every v* tag push. On non-PR runs it syncs the count into: - GitHub repo About description (via `gh repo edit`) - README.md + site/index.md (commit with [skip ci] back to main) - Wiki: Home.md, FAQ.md, Streaming-vs-Batch.md Requires a `ABOUT_SYNC_TOKEN` secret (classic PAT with `repo` scope, or fine-grained PAT with Administration+Contents write on the wickra repo). PR runs are read-only: count is logged but nothing is mutated, so forks cannot trigger writes. |
||
|
|
e30b3c6b35 |
release: 0.2.7 (Windows ARM64 restored + CPU label fix) (#37)
* chore(docs): rename benchmark CPU from 7950X3D to 9950X
The "Reproduced on" line in the umbrella + binding READMEs and the
benchmark page on the site listed the wrong AMD CPU. The benchmarks
were actually produced on a Ryzen 9 9950X, not a 7950X3D. Same
column for absolute µs values applies — the speedup ratios in the
tables are unchanged either way because they're relative across
libraries on the same machine.
The performance-regression issue template's CPU example also
updated for consistency (it was a generic placeholder, but matching
the canonical machine makes the example concrete).
* chore(npm): restore Windows ARM64 sub-package + napi matrix entry
npm Support unblocked the `wickra-win32-arm64-msvc` package name and
transferred write access to @kingchenc (placeholder 0.0.1-security
was published from their side; we ship our first real version on
top of that). This re-enables every change
v0.2.7
|
||
|
|
070be2eb27 |
release: 0.2.6 (docs.rs fix + README table reordering) (#36)
* fix(docs-rs): rename `doc_auto_cfg` to `doc_cfg` after Rust 1.92 merge `doc_auto_cfg` was removed in Rust 1.92.0 and folded back into `doc_cfg` (rust-lang/rust#138907). docs.rs builds with the latest nightly and sets `--cfg docsrs`, so the previous #![cfg_attr(docsrs, feature(doc_auto_cfg))] aborts compilation with E0557 on every published 0.2.x. GitHub CI never tripped this — stable rustc ignores the line because nothing sets the `docsrs` cfg there. Switch all three published library crates (`wickra`, `wickra-core`, `wickra-data`) to the merged-into `doc_cfg` gate. Same intent, same on-docs.rs output, builds again on nightly. * docs(readme): float Wickra to the top of the comparison tables Reorders the "Why Wickra exists" library-comparison table and the two benchmark headers so Wickra is the first row (with a ★ marker) instead of the last. The previous order placed Wickra at the bottom, which buries the only row a reader landing on the README is here to compare against. Same column data, same ★/winner annotations, just the row order flipped and a ★ prefix on the Wickra label. Mirrored across the umbrella README and every binding README so the crates.io / PyPI / npm landing pages stay in sync. * release: bump workspace + bindings to 0.2.6 Workspace, every binding (Python, Node, Node platform stubs), the release.yml comment and the CHANGELOG all move together to 0.2.6 so the next tagged release lines every artefact up. 0.2.6 carries two changes from the [0.2.6] CHANGELOG entry: - fix(docs-rs): swap the now-removed `doc_auto_cfg` feature gate for the merged-into `doc_cfg` so docs.rs nightly builds resume. - docs(readme): float ★ Wickra to the top of every comparison table across the umbrella + binding READMEs. wickra-win32-arm64-msvc stays excluded for this release with the same npm spam-filter rationale that held for 0.2.5.v0.2.6 |
||
|
|
221f7a71bc |
chore(github): add detailed issue & PR templates, capitalize title prefixes
Adds five new issue templates (bug_report_detailed, feature_request_detailed, performance_regression, documentation, question) alongside the existing short forms, plus an optional detailed PR template under .github/PULL_REQUEST_TEMPLATE/ that contributors can opt into via the ?template=detailed.md URL. Existing templates keep their behavior; only title prefixes and prose-paren wording were capitalized for consistency.v0.2.5 |
||
|
|
b5afc0a7e7 |
release: bump workspace + bindings to 0.2.5 (#35)
Workspace, every binding (Python, Node, Node platform stubs), and the release.yml comment are all updated together so the next tagged release on `v0.2.5` lines every artefact up. Also adds a short README "Disclaimer" section pointing out that Wickra is an indicator toolkit, not a trading system, and that production use is at the caller's own risk. The legal terms in LICENSE (PolyForm Noncommercial 1.0.0, "No Liability") already cover the warranty / as-is language — the README section just makes the trading-specific framing visible without burying it in a click-through. CHANGELOG carries the new 0.2.5 entry with the API addition (`BinanceConfig` + `connect_with_config`) and the best-effort Pong write change in `BinanceKlineStream::next_event`. wickra-win32-arm64-msvc stays excluded for this release with the same npm-spam-filter rationale that held for 0.2.1. |
||
|
|
9acb2f607e |
test(binance): mock-WS suite drives async/reconnect paths to ~100% (#34)
* refactor(binance): introduce BinanceConfig for endpoint + timing knobs Replaces the file-private READ_TIMEOUT / MAX_RECONNECT_ATTEMPTS / size limit constants with a Default-equipped BinanceConfig the caller can hand to a new connect_with_config(). connect() forwards to it with the defaults, so the public API stays backwards-compatible. Behaviour-preserving: every default matches the value of the constant it replaces, and the WebSocketConfig is built the same way. The change unlocks two real use-cases — pointing at Binance Testnet (wss://testnet.binance.vision) and pointing at a local mock server with millisecond-scale reconnect timing in tests. * test(binance): cover the Interval table and the empty-symbol guard Three quick wins that don't need a live or mock socket: - interval_as_str_covers_every_variant pins every wire-format mapping in one table so a typo on any of the 14 variants is caught. - binance_config_default_matches_production_endpoint guards the default base URL and timing knobs against an accidental drift. - connect_rejects_an_empty_symbol_list exercises the guard before the WebSocket handshake — the one async path we can hit without a server. * test(binance): cover the async / reconnect / control-frame paths Adds a small mock-WebSocket scaffold built on a `127.0.0.1:0` listener and tokio-tungstenite's `accept_async`, plus nine integration tests that drive `BinanceKlineStream::next_event` through every branch: - text + binary kline frames decode to a KlineEvent - inbound Ping is answered with a Pong, then the kline arrives - inbound Pong / Frame variants are silently skipped - a server-side Close triggers a transparent reconnect that then serves the kline - a stalled connection trips read_timeout and reconnects on its own - close() flips the closed flag and next_event() yields None forever - when every reconnect attempt is refused, next_event surfaces an Err - a "kline" envelope whose numbers are unparseable bubbles up as Error::Malformed rather than being silently skipped `one_shot_server` drops the listener as soon as the first accept is done, so a follow-up reconnect lands on a refused port — that is what lets the exhaustion test hit the final `last_err.expect(...)`. The whole suite runs in ~4 s with millisecond-scale reconnect timings supplied via the new test-only [`test_config`]. * test(binance): drop defensive cold-paths in the mock-WS scaffolding Codecov's patch report on PR #34 flagged seven uncovered lines, all of them in the test scaffolding rather than in production code: - the `let Ok((stream, _)) = … else { return }` shortcut and the `if let Ok(ws) = accept_async(stream).await { … }` branch in the mock-server helpers — both error arms never fire on a passing test - the closing braces of the spawned-task bodies in the close-frame and read-timeout reconnect tests — the spawned async blocks were getting killed mid-drain when the test asserted and returned Refactor the helpers to `.unwrap()` every Result (a failure here is a bug in the scaffold, not in production) and have `multi_shot_server` accept a fixed `n_accepts`, await every spawned inner task, and hand the outer JoinHandle back to the caller. Refactor the two affected tests to capture that JoinHandle, collapse the per-index `if/else` so both arms reach the same trailing expression, swap the read-timeout drain for a bounded sleep, and await `server_done` at the end. Every handler now reaches its closing brace before the runtime is torn down, so coverage on the patch should collapse from 97.89 % to 100 %. * test(binance): cover the non-kline-skip path and simplify the Ping arm After the scaffolding fix landed three lines on binance.rs were still uncovered: - L305 / L313: the Text- and Binary-arm "frame was not a kline, keep reading" fall-throughs. No existing test drove the loop through a non-kline frame followed by a kline; the new `next_event_skips_non_kline_frames_and_returns_the_next_kline` does exactly that (Text ack, Binary id frame, then a real kline). - L317: the Ping-Err defensive arm that forced a reconnect when the Pong reply itself failed to write. A failed Pong reply means the socket is already dead, so the very next read will surface the error and reconnect through the existing timeout/err branch — one tokio scheduling iteration later. Drop the defensive arm and write the Pong reply best-effort. Same observable behaviour, no test back door, no dead-line guard. Repo coverage on `cov/binance-mock-ws` now sits at 100 %. |
||
|
|
32caf023dd |
test(psar): drop violation-tuple cold path in trend tests (99.03 -> 100) (#33)
After PR #27 brought psar.rs to 99.03 %, Codecov still flagged the 'violation found' tuple arms in the trend tests (line 256 in pure_uptrend_sar_below_lows, line 285 in pure_downtrend_sar_above_highs) as missed: both tests are designed to NEVER find a violation, so the filter_map branch that constructs the (index, sar, bound) tuple is dead by design. Restructure both tests to use `.all(|(i, sar)| sar.is_none_or(|s| <bound>))` instead of collecting violations into a Vec. The closure runs on every emitted Some, asserts the SAR-vs-extreme bound directly, and the iterator short-circuits on the first false — no cold tuple construction left to count as uncovered. Semantics are identical (still asserts every SAR sits on the correct side of every candle's extreme); the diagnostic message loses the violating index list, which the tests never printed in any green run anyway. psar.rs is now at 207/207 lines, no behavioural change. |
||
|
|
250b75d468 |
test: 100% coverage for balance_of_power + median_price + true_range + typical_price + weighted_close (#32)
* test(balance_of_power): cover name metadata Codecov flagged 3 lines (file at 96.25%): Indicator-impl name body (73-75). * test(median_price): cover name metadata Codecov flagged 3 lines (file at 94.44%): Indicator-impl name body (62-64). * test(true_range): cover name metadata Codecov flagged 3 lines (file at 95.94%): Indicator-impl name body (73-75). * test(typical_price): cover name metadata Codecov flagged 3 lines (file at 94.44%): Indicator-impl name body (62-64). * test(weighted_close): cover name metadata Codecov flagged 3 lines (file at 94.44%): Indicator-impl name body (61-63). |
||
|
|
adc8488939 |
test: 100% coverage for ema + historical_volatility + kama + linreg_angle + mass_index (#24)
* test(ema): cover period accessor + warmup/name metadata Codecov flagged 9 lines in crates/wickra-core/src/indicators/ema.rs (file at 94.03%): const accessor period (74-77), Indicator-impl warmup_period (123-125), name (131-133). ema.rs now at 151/151. * test(historical_volatility): cover periods/value accessors + name metadata Codecov flagged 9 lines in crates/wickra-core/src/indicators/historical_volatility.rs (file at 93.87%): const accessors periods (80-83), value (85-88) and Indicator-impl name (153-155). historical_volatility.rs now at 147/147. * test(kama): cover periods accessor + warmup/name metadata Codecov flagged 9 lines in crates/wickra-core/src/indicators/kama.rs (file at 91.26%): accessor periods (65-67), Indicator-impl warmup_period (115-117), name (123-125). kama.rs now at 103/103. * test(linreg_angle): cover period accessor + warmup/name metadata Codecov flagged 9 lines in crates/wickra-core/src/indicators/linreg_angle.rs (file at 88.15%): const accessor period (50-52), Indicator-impl warmup_period (67-69), name (75-77). linreg_angle.rs now at 76/76. * test(mass_index): cover periods/value accessors + name metadata Codecov flagged 9 lines in crates/wickra-core/src/indicators/mass_index.rs (file at 91.42%): const accessors periods (80-82), value (85-87) and Indicator-impl name (134-136). mass_index.rs now at 105/105. |
||
|
|
d55d3db3d1 |
test: 100% coverage for vertical_horizontal_filter + z_score + vpt + csv + adl (#31)
* test(vertical_horizontal_filter): cover period accessor + name metadata Codecov flagged 6 lines (file at 94.44%): period (61-63) + name (119-121). * test(z_score): cover period accessor + name metadata Codecov flagged 6 lines (file at 93.75%): period (59-61) + name (106-108). * test(vpt): cover value() Some branch, name, zero-prev fallback Codecov flagged 5 lines (file at 94.38%): value() Some branch (57), prev==0.0 ROC fallback (77), and Indicator-impl name (100-102). Add accessors_and_metadata covering value()/name and zero_previous_ close_contributes_zero — feeding a 0.0 baseline + non-zero candle proves the divide-by-zero guard yields a 0 contribution rather than NaN. * test(csv): cover from_csv_reader + kill rejects_header dead panic arm Codecov flagged 5 lines in csv.rs (file at 96.98%): from_csv_reader (201-204) — never called by existing tests which use from_reader / open — and the cold arm in rejects_header_missing_a_column (279). Add from_csv_reader_accepts_a_ prebuilt_reader (demonstrates the API by building a custom-delimited csv::Reader and passing it in), and refactor the header-missing test to use a single matches!() assertion so the panic arm is gone. * test(adl): cover name metadata Codecov flagged 3 lines (file at 96.84%): Indicator-impl name body (94-96). |
||
|
|
512bbf75c4 |
test: 100% coverage for keltner + linreg + linreg_slope + macd + super_trend (#30)
* test(keltner): cover periods accessor + name metadata Codecov flagged 6 lines (file at 95.23%): periods (68-70) + name (106-108). * test(linreg): cover period accessor + name metadata Codecov flagged 6 lines (file at 96.10%): period (92-94) + name (142-144). * test(linreg_slope): cover period accessor + name metadata Codecov flagged 6 lines (file at 95.91%): period (80-82) + name (125-127). * test(macd): cover periods/value accessors + name metadata Codecov flagged 6 lines (file at 95.45%): periods (81-83) + name (135-137). * test(super_trend): cover params accessor + name metadata Codecov flagged 6 lines (file at 96.36%): params (99-101) + name (176-178). |
||
|
|
8f6ffe5a62 |
test: 100% coverage for chaikin_volatility + chande_kroll_stop + chandelier_exit + choppiness_index + force_index (#29)
* test(chaikin_volatility): cover periods accessor + name metadata Codecov flagged 6 lines (file at 94.91%): periods (69-71) + name (99-101). * test(chande_kroll_stop): cover params accessor + name metadata Codecov flagged 6 lines (file at 95.45%): params (97-99) + name (164-166). * test(chandelier_exit): cover params accessor + name metadata Codecov flagged 6 lines (file at 95.12%): params (83-85) + name (128-130). * test(choppiness_index): cover period accessor + name metadata Codecov flagged 6 lines (file at 95.04%): period (73-75) + name (125-127). * test(force_index): cover period accessor + name metadata Codecov flagged 6 lines (file at 93.33%): period (58-60) + name (93-95). |
||
|
|
d9a1950007 |
test: 100% coverage for rsi + accelerator_oscillator + aroon + atr_trailing_stop + chaikin_oscillator (#28)
* test(rsi): cover period/value accessors, name, naive flat-series branch Codecov flagged 7 lines in indicators/rsi.rs (file at 96.42%): const accessors period (60-62), value (65-67), Indicator-impl name (145-147), and line 167 in the test-helper rsi_naive's ag==0 fallback. The proptest reference never lands on a fully flat series so the helper's 50.0 branch was dead. Add accessors_and_metadata covering period/value/name and naive_helper_flat_series_yields_50 driving rsi_naive on [42.0; 20] — both avg_gain and avg_loss converge to 0, hitting the 50.0 branch. rsi.rs now at 196/196. * test(accelerator_oscillator): cover params accessor + name metadata Codecov flagged 6 lines in indicators/accelerator_oscillator.rs (file at 93.68%): const accessor params (69-71) and Indicator-impl name (99-101). ac.rs now at 95/95. * test(aroon): cover period accessor + name metadata Codecov flagged 6 lines in indicators/aroon.rs (file at 94.28%): const accessor period (56-58) and Indicator-impl name (104-106). aroon.rs now at 105/105. * test(atr_trailing_stop): cover params accessor + name metadata Codecov flagged 6 lines in indicators/atr_trailing_stop.rs (file at 95.91%): const accessor params (77-79) and Indicator-impl name (130-132). atr_trailing_stop.rs now at 147/147. * test(chaikin_oscillator): cover periods accessor + name metadata Codecov flagged 6 lines in indicators/chaikin_oscillator.rs (file at 95.27%): const accessor periods (76-78) and Indicator-impl name (109-111). chaikin_oscillator.rs now at 127/127. |
||
|
|
c7f1e14629 |
test: 100% coverage for mfi + psar + cmf + hma + obv (#27)
* test(mfi): cover period accessor, name, flat-TP fallback Codecov flagged 8 lines in indicators/mfi.rs (file at 93.10%): const accessor period (58-60), (0.0, 0.0) arm when tp==prev (85), the Some(50.0) flat-flow fallback (105), and Indicator-impl name body (132-134). Add accessors_and_metadata and flat_typical_prices_default_to_50. mfi.rs now at 116/116. * test(psar): cover warmup/name, drop cold format-arg + panic-only asserts Codecov flagged 8 lines in indicators/psar.rs (file at 95.69%): warmup_period (206-208), name (220-222), the cold format-arg line 254 in pure_uptrend_sar_below_lows, and the in-loop assert! at line 275 in pure_downtrend_sar_above_highs (its panic body is dead). Add accessors_and_metadata for warmup/name. Refactor both trend tests to collect violations into a Vec and assert once outside the loop — the single assert can now legitimately reach its panic body in a regression, while removing the dead cold-path lines from the happy-path coverage. * test(cmf): cover period accessor, name, zero-range branch Codecov flagged 7 lines in indicators/cmf.rs (file at 95.03%): const accessor period (71-73), the range==0.0 zero-MFV branch (84), and Indicator-impl name body (124-126). Add accessors_and_metadata and zero_range_candle_contributes_zero_mfv (flat H=L=close candles). cmf.rs now at 141/141. * test(hma): cover period accessor + name, kill dead naive panic arm Codecov flagged 7 lines in indicators/hma.rs (file at 92.22%): const accessor period (51-53), Indicator-impl name body (87-89), and the unreachable arm at line 167 in matches_independent_wmas. Refactor that test to assert the warmup-shape invariant via assert_eq!(got.is_some(), want.is_some()) + if let, removing the dead panic arm. Add accessors_and_metadata covering period/name. hma.rs now at 90/90. * test(obv): cover value() Some branch + warmup/name metadata Codecov flagged 7 lines in indicators/obv.rs (file at 92.92%): the Some(self.total) branch of value() (47) — only the None branch was hit by reset_clears_state — plus Indicator-impl warmup_period (79-81), name (87-89). Add accessors_and_metadata covering all four. obv.rs now at 99/99. |
||
|
|
a5d4926718 |
test: 100% coverage for tsi + ultimate_oscillator + vortex + vwma + zlema (#26)
* test(tsi): cover periods/value accessors + name metadata Codecov flagged 9 lines in indicators/tsi.rs (file at 92.30%): const accessors periods (70-72), value (75-77) and Indicator-impl name (137-139). tsi.rs now at 117/117. * test(ultimate_oscillator): cover periods/value accessors + name metadata Codecov flagged 9 lines in indicators/ultimate_oscillator.rs (file at 94.76%): const accessors periods (96-98), value (101-103) and Indicator-impl name (193-195). uo.rs now at 172/172. * test(vortex): cover period/value accessors + name metadata Codecov flagged 9 lines in indicators/vortex.rs (file at 93.18%): const accessors period (84-86), value (89-91) and Indicator-impl name (157-159). vortex.rs now at 132/132. * test(vwma): cover period/value accessors + name metadata Codecov flagged 9 lines in indicators/vwma.rs (file at 92.56%): const accessors period (72-74), value (77-79) and Indicator-impl name (129-131). vwma.rs now at 121/121. * test(zlema): cover period/value accessors + name metadata Codecov flagged 9 lines in indicators/zlema.rs (file at 90.62%): const accessors period (62-64), value (72-74) and Indicator-impl name (111-113). zlema.rs now at 96/96. |
||
|
|
645b002958 |
test: 100% coverage for mom + sma + stoch_rsi + tema + trima (#25)
* test(mom): cover period/value accessors + name metadata Codecov flagged 9 lines in indicators/mom.rs (file at 89.53%): const accessors period (56-58), value (61-63) and Indicator-impl name (101-103). mom.rs now at 86/86. * test(sma): cover period accessor + warmup/name metadata Codecov flagged 9 lines in indicators/sma.rs (file at 93.12%): const accessor period (70-72), Indicator-impl warmup_period (115-117), name (123-125). sma.rs now at 131/131. * test(stoch_rsi): cover periods/value accessors + name metadata Codecov flagged 9 lines in indicators/stoch_rsi.rs (file at 92.37%): const accessors periods (69-71), value (74-76) and Indicator-impl name (131-133). stoch_rsi.rs now at 118/118. * test(tema): cover period accessor + warmup/name metadata Codecov flagged 9 lines in indicators/tema.rs (file at 83.63%): const accessor period (45-47), Indicator-impl warmup_period (67-69), name (75-77). tema.rs now at 55/55. * test(trima): cover period/value accessors + name metadata Codecov flagged 9 lines in indicators/trima.rs (file at 89.53%): const accessors period (59-61), value (64-66) and Indicator-impl name (99-101). trima.rs now at 86/86. |
||
|
|
5a6689cf1a |
test: 100% coverage for cmo + dema + donchian + dpo + ease_of_movement (#23)
* test(cmo): cover period/value accessors + name metadata Codecov flagged 9 lines in crates/wickra-core/src/indicators/cmo.rs (file at 92.30%): const accessors period (66-68), value (71-73) and Indicator-impl name (134-136). cmo.rs now at 117/117. * test(dema): cover period accessor + warmup/name metadata Codecov flagged 9 lines in crates/wickra-core/src/indicators/dema.rs (file at 85.00%): const accessor period (43-45), Indicator-impl warmup_period (63,65,66) and name (72-74). dema.rs now at 60/60. * test(donchian): cover period accessor + warmup/name metadata Codecov flagged 9 lines in crates/wickra-core/src/indicators/donchian.rs (file at 90.21%): const accessor period (57-59), Indicator-impl warmup_period (95-97), name (103-105). donchian.rs now at 92/92. * test(dpo): cover period/value accessors + name metadata Codecov flagged 9 lines in crates/wickra-core/src/indicators/dpo.rs (file at 91.74%): const accessors period (73-75), value (83-85) and Indicator-impl name (132-134). dpo.rs now at 109/109. * test(ease_of_movement): cover period/divisor accessors + name metadata Codecov flagged 9 lines in crates/wickra-core/src/indicators/ease_of_movement.rs (file at 94.15%): const accessors period (83-85), divisor (88-90) and Indicator-impl name (141-143). ease_of_movement.rs now at 154/154. |
||
|
|
8582338b5d |
test: 100% coverage for wma + aroon_oscillator + atr + awesome_oscillator + cci (#22)
* test(wma): cover period/warmup/name + kill dead naive panic arm
Codecov flagged 10 lines in crates/wickra-core/src/indicators/wma.rs
(file at 92.48%): const accessor period (56-58), Indicator-impl
warmup_period (111-113), name (119-121), and line 186 — the
`_ => panic!("warmup mismatch")` arm in matches_naive_over_random_
inputs, an invariant guard that never fires when both streams share
a warmup period.
Add accessors_and_metadata covering the three metadata methods.
Refactor matches_naive_over_random_inputs to assert the warmup-shape
invariant via assert_eq!(g.is_some(), w.is_some()) + if let,
removing the dead panic arm.
wma.rs is now at 133/133 lines, no behavioural change.
* test(aroon_oscillator): cover period/value accessors + name metadata
Codecov flagged 9 lines in crates/wickra-core/src/indicators/aroon_
oscillator.rs (file at 90.42%): const accessors period (57-59),
value (62-64) and Indicator-impl name (90-92). warmup_period is
already covered by warmup_period_matches_aroon.
Add accessors_and_metadata asserting period == 7, name ==
"AroonOscillator", and value() across the None (pre-warmup) and
Some (post-warmup) branches.
aroon_oscillator.rs is now at 94/94 lines, no behavioural change.
* test(atr): cover period/value accessors + name metadata
Codecov flagged 9 lines in crates/wickra-core/src/indicators/atr.rs
(file at 93.70%): const accessors period (54-57), value (59-62) and
Indicator-impl name body (103-105). warmup_period is exercised
indirectly via downstream indicators; the metadata getters were
never queried directly.
Add accessors_and_metadata asserting period == 14, name == "ATR",
and value() across the None (pre-warmup) and Some (post-warmup)
branches.
atr.rs is now at 143/143 lines, no behavioural change.
* test(awesome_oscillator): cover periods accessor + warmup/name metadata
Codecov flagged 9 lines in crates/wickra-core/src/indicators/awesome_
oscillator.rs (file at 88.15%): const accessor periods (59-61),
Indicator-impl warmup_period (83-85), name (91-93). The classic()
constructor is covered indirectly through the existing tests; only
the metadata methods were dead.
Add accessors_and_metadata asserting periods == (5, 34),
warmup_period == 34 (= slow_period), name == "AwesomeOscillator".
awesome_oscillator.rs is now at 76/76 lines, no behavioural change.
* test(cci): cover period accessor + warmup/name metadata
Codecov flagged 9 lines in crates/wickra-core/src/indicators/cci.rs
(file at 89.65%): const accessor period (68-70), Indicator-impl
warmup_period (102-104), name (110-112). Existing tests never
inspected the metadata surface.
Add accessors_and_metadata asserting period == 20, warmup_period ==
20, name == "CCI".
cci.rs is now at 87/87 lines, no behavioural change.
|
||
|
|
a9670b0ad1 |
test: 100% coverage for pmo + ppo + roc + ulcer_index + williams_r (#21)
* test(pmo): cover periods/value accessors, name, zero-prev fallback
Codecov flagged 10 lines in crates/wickra-core/src/indicators/pmo.rs
(file at 90.56%):
- const accessors periods (76-78), value (81-83) — never queried
- line 103 (`0.0` in the prev == 0.0 ROC fallback) — every existing
test used prices > 0, so the divide-by-zero guard never fired
- Indicator-impl name body (130-132) — never queried
Add accessors_and_metadata covering periods/value/name. Add
zero_previous_price_treats_roc_as_flat seeding prev_price = 0 then
pushing a non-zero price — the ROC must take the flat-momentum
fallback (0.0) and the doubly-smoothed PMO emits exactly 0.0
rather than NaN.
pmo.rs is now at 106/106 lines, no behavioural change.
* test(ppo): cover periods/value accessors, name, zero-slow-EMA fallback
Codecov flagged 10 lines in crates/wickra-core/src/indicators/ppo.rs
(file at 90.29%):
- const accessors periods (71-73), value (76-78) — never queried
- line 96 (`0.0` in the s == 0.0 PPO fallback) — every existing test
used prices ≈ 100, so the slow EMA was never 0 and the
divide-by-zero guard never fired
- Indicator-impl name body (122-124) — never queried
Add accessors_and_metadata covering periods/value/name. Add
zero_slow_ema_yields_zero_ppo feeding a stream of zeros — both EMAs
converge to 0.0 and the indicator must emit exactly 0.0 (flat
momentum) rather than NaN.
ppo.rs is now at 103/103 lines, no behavioural change.
* test(roc): cover period accessor, warmup/name, zero-prev fallback
Codecov flagged 10 lines in crates/wickra-core/src/indicators/roc.rs
(file at 87.80%):
- const accessor period (47-49) — never queried
- line 70 (`0.0` in the prev == 0.0 ROC fallback) — every test used
prices ≥ 1.0, so the divide-by-zero guard never fired
- Indicator-impl warmup_period (83-85), name (91-93) — never queried
Add accessors_and_metadata covering period == 5, warmup_period == 6
(= period + 1), name == "ROC". Add zero_previous_price_yields_zero_roc
feeding a leading zero followed by `period` more values so the front
of the window is exactly 0.0; the next emission must be the
flat-momentum fallback 0.0 (not NaN).
roc.rs is now at 82/82 lines, no behavioural change.
* test(ulcer_index): cover period/value accessors, name, zero-max fallback
Codecov flagged 10 lines in crates/wickra-core/src/indicators/ulcer_index.rs
(file at 93.86%):
- const accessors period (77-80), value (82-85) — never queried
- line 123 (`0.0` in the max_price == 0.0 drawdown fallback) — every
test used prices > 0, so the trailing-max divisor was always positive
- Indicator-impl name body (162-164) — never queried
Add accessors_and_metadata covering period/value/name. Add
zero_max_price_yields_zero_drawdown feeding a stream of zeros — the
trailing max is exactly 0.0 and the drawdown computation would
otherwise hit 0/0 NaN; the indicator must emit exactly 0.0
(drawdown is 0% by convention).
ulcer_index.rs is now at 163/163 lines, no behavioural change.
* test(williams_r): cover period accessor, warmup/name, zero-range branch
Codecov flagged 10 lines in crates/wickra-core/src/indicators/williams_r.rs
(file at 89.79%):
- const accessor period (49-51) — never queried
- line 78 (`Some(-50.0)` in the range == 0.0 fallback) — every test
used H != L candles, so the lookback range was always positive
- Indicator-impl warmup_period (87-89), name (95-97) — never queried
Add accessors_and_metadata covering period == 14, warmup_period == 14,
name == "WilliamsR". Add zero_range_yields_minus_fifty feeding flat
candles (H == L == close) — the lookback hi/lo coincide and the
divide-by-zero guard fires, returning the neutral mid-range value
-50.0.
williams_r.rs is now at 98/98 lines, no behavioural change.
|
||
|
|
b86cf68eb8 |
test: 100% coverage for t3 + adx + natr + trix + coppock (#20)
* test(t3): cover period/volume_factor/value accessors + name metadata
Codecov flagged 12 lines in crates/wickra-core/src/indicators/t3.rs
(file at 91.48%): const accessors period (95-97), volume_factor
(100-102), value (105-107) and Indicator-impl name (148-150). The
warmup_period method is already covered by first_emission_at_warmup_
period; the other four metadata methods were never queried.
Add accessors_and_metadata asserting period == 5, volume_factor == 0.7,
name == "T3", and value() across both the None (pre-warmup) and Some
(post-warmup) branches.
t3.rs is now at 141/141 lines, no behavioural change.
* test(adx): cover period accessor, warmup/name metadata, zero-TR branch
Codecov flagged 11 lines in crates/wickra-core/src/indicators/adx.rs
(file at 94.17%): the const accessor period (89-91), the tr_v == 0.0
defensive branches inside update (142, 147), and the Indicator-impl
warmup_period (199-201) and name (207-209) bodies.
Add accessors_and_metadata asserting period == 14, warmup_period == 28,
name == "ADX". Add zero_true_range_yields_zero_di_and_zero_adx feeding
flat all-zero candles (H == L == close == 0) — every TR is 0, so the
smoothed tr_smooth stays at 0 and update must take the zero-denominator
fallback for both plus_di and minus_di, then the dx_den == 0 path for
ADX. The indicator must emit 0/0/0 rather than NaN.
adx.rs is now at 189/189 lines, no behavioural change.
* test(natr): cover accessors, zero-close branch, kill dead panic arm
Codecov flagged 11 lines in crates/wickra-core/src/indicators/natr.rs
(file at 87.64%):
- const accessors period (59-61), value (64-66) — never queried
- line 77 (`0.0` in the candle.close == 0.0 fallback) — every test
used candles with close ≈ 100, so the divide-by-zero guard never
fired
- Indicator-impl name body (98-100) — never queried
- line 142 (`_ => panic!("warmup mismatch at {i}")`) — unreachable
invariant guard in natr_is_atr_over_close_as_percent because the
NATR wrapper inherits ATR's warmup period exactly
Add accessors_and_metadata covering period/value/name. Add
zero_close_yields_zero_natr feeding an all-zero candle series (Candle
validator accepts open == high == low == close == 0 with positive
volume) — ATR is 0 each bar, so the indicator must emit exactly 0.0
rather than 100 * 0 / 0 = NaN. Refactor natr_is_atr_over_close_as_
percent to assert the warmup-shape invariant via assert_eq! on
is_some(), removing the dead panic arm.
natr.rs is now at 89/89 lines, no behavioural change.
* test(trix): cover period accessor, warmup/name metadata, zero-prev branch
Codecov flagged 11 lines in crates/wickra-core/src/indicators/trix.rs
(file at 84.05%):
- const accessor period (47-49) — never queried
- the Some(_) match arm (67-68) — the degenerate path where the
previous triple-EMA value is exactly 0.0 (would otherwise divide
by zero on the percent-rate formula). All other tests used
inputs ≈ 100, so prev_tr was never 0.0
- Indicator-impl warmup_period (84, 86-87) and name (93-95) — never
queried
Add accessors_and_metadata asserting period == 5, warmup_period == 14
(= 3*5 - 1), name == "TRIX". Add zero_input_series_yields_zero_trix
feeding [0.0; 20] — every EMA stage collapses to 0.0, so once warmed
up prev_tr is Some(0.0) and every subsequent emission must take the
fallback arm returning 0.0.
trix.rs is now at 69/69 lines, no behavioural change.
* test(coppock): cover periods/value accessors + name + simplify assert
Codecov flagged 10 lines in crates/wickra-core/src/indicators/coppock.rs
(file at 91.07%):
- const accessors periods (68-70), value (73-75) — never queried
- Indicator-impl name body (128-130) — never queried
- line 180 (`warmup - 1,` format-arg) inside the multi-line assert!
in warmup_period_matches_first_some_for_every_parameter_set —
only evaluated on assertion failure, which never happens, so
Codecov flagged the cold path as uncovered
Add accessors_and_metadata covering periods/value/name. Simplify the
multi-line assert's format args to a static message — the {warmup}
binding already appears once in the cold path so dropping the literal
"warmup index" arg loses nothing diagnostic but kills the dead
expression-arg line.
coppock.rs is now at 112/112 lines, no behavioural change.
|
||
|
|
24919153dd |
style(bollinger): wrap naive helper assert to satisfy rustfmt
Commit
|
||
|
|
73507b1cb6 |
test(std_dev): cover period/value accessors + warmup/name metadata
Codecov flagged 12 lines in crates/wickra-core/src/indicators/std_dev.rs (file at 89.09%): const accessors period (64-66), value (68-71) and Indicator-impl bodies warmup_period (110-112), name (118-120). None of the existing tests inspected the metadata surface. Add accessors_and_metadata asserting period == 14, warmup_period == 14, name == "StdDev", and value() across both the None (pre-warmup) and Some (post-warmup) branches. std_dev.rs is now at 110/110 lines, no behavioural change. |
||
|
|
6dfa4ee134 |
test(smma): cover period/value accessors + warmup/name metadata
Codecov flagged 12 lines in crates/wickra-core/src/indicators/smma.rs (file at 86.81%): the const accessors period (57-59), value (62-64) and the Indicator-impl bodies warmup_period (95-97), name (103-105). None of the existing tests inspected the metadata surface — they only fed numeric updates and asserted on SMMA values. Add accessors_and_metadata exercising period == 7, warmup_period == 7, name == "SMMA", and value() across both the None (pre-warmup) and Some (post-warmup) branches. smma.rs is now at 91/91 lines, no behavioural change. |
||
|
|
6969541bb1 |
test(stochastic): cover classic/periods/metadata + naive_k flat branch
Codecov flagged 13 uncovered lines in
crates/wickra-core/src/indicators/stochastic.rs (file at 93.43%):
- classic() convenience constructor (76-78) — every test passed
explicit (k_period, d_period) to new
- periods() const accessor (81-83) — never queried
- warmup_period (170-172), name (178-180) Indicator-impl bodies —
never queried
- line 208 (`50.0` literal) inside the test-only naive_k helper's
flat-range branch — k_matches_naive feeds an oscillating price
series, so the helper's range == 0 path was dead
Add classic_periods_and_metadata test asserting Stochastic::classic()
has periods (14, 3), warmup_period 16 (= 14 + 3 - 1) and name
"Stochastic". Extend flat_range_yields_k_50 to also call naive_k on
the flat candle series and verify the helper returns Some(50.0) for
every index ≥ k_period - 1 — exercises line 208 without diluting the
production-code assertion.
stochastic.rs is now at 198/198 lines, no behavioural change.
|
||
|
|
ba4e126799 |
test(aggregator): cover convenience tf-ctors + getter, kill dead gap-fill arms
Codecov flagged 15 uncovered lines in crates/wickra-data/src/aggregator.rs
(file at 95.11%):
- Timeframe::millis / Timeframe::seconds / Timeframe::one_minute_ms
convenience constructors (40-52) — every existing test built
Timeframes via new / minutes / hours / days, never via these three
- the cold `?` Err arm on `Candle::new(...)?` for the flat gap-fill
candle (line 334) — `prev.close` is already finite (came from a
closed bar), volume is exactly 0.0, OHLC are trivially equal, so
Candle::new's error path is unreachable here
- the cold `ok_or_else` overflow closure on `t.checked_add(step)`
inside the gap-fill loop (336-337) — bucket alignment guarantees
start + (gap_count-1)*step ≤ next_bucket - step < i64::MAX, so
every aligned-bucket layout reaches t == next_bucket cleanly and
exits without ever invoking the overflow path
- TickAggregator::timeframe accessor (353-355) — never queried
Add two new tests:
- timeframe_convenience_constructors exercises millis/seconds/
one_minute_ms with both happy-path and rejection cases
- aggregator_timeframe_getter asserts timeframe().bucket() round-trips
Refactor fill_between to use Candle::new_unchecked for the flat-candle
push (the OHLCV invariants hold by construction) and iterate via
`0..gap_count` with `saturating_add(step)` instead of `while t <
next_bucket` with `checked_add(...).ok_or_else(...)?`. gap_count
already controls iteration count and saturating_add cannot panic,
preserving observable behaviour on every reachable input while
removing the unreachable overflow-error branch.
aggregator.rs is now at 307/307 lines, no observable behaviour change
on aligned-bucket inputs (which is every input fill_between can be
called with given the call site's preconditions).
|
||
|
|
aa2846c250 |
test(bollinger): collapse naive helper assert to single-line message
Codecov re-check after |
||
|
|
1255892b1e |
test(bollinger): cover classic()+accessors+metadata, drop dead naive arm
Codecov flagged 16 uncovered lines in
crates/wickra-core/src/indicators/bollinger.rs (file at 91.30%):
- classic() convenience constructor (91-93) — every test passed
explicit parameters to BollingerBands::new, so the classic-defaults
path was dead
- const accessors period (96-98), multiplier (101-103) — never queried
- Indicator-impl bodies warmup_period (156-158), name (164-166) —
never queried
- `return None;` (line 177) inside the test-only `naive` helper's
`if prices.len() < period` early-return — every caller passes a
slice of length >= period (matches_naive_definition uses
`&prices[..=i]` for `i in 19..` with period=20; long_stream_drift_
stays_bounded fills the window before measuring), so the arm is dead
Add classic_and_accessors_and_metadata to cover the constructor and
the four getter bodies, and refactor naive to return BollingerOutput
directly with an `assert!(prices.len() >= period)` precondition. The
two existing callers were already using .unwrap()/.expect() on the
Option result and simplify to direct calls.
bollinger.rs is now at 184/184 lines, no behavioural change.
|
||
|
|
36dc951f1b |
test(ohlcv): cover Candle::new_unchecked skip-validation constructor
Codecov flagged lines 86-102 in crates/wickra-core/src/ohlcv.rs as missed
(file at 90.00%) — the entire body of `Candle::new_unchecked`. Every
existing test routes through the validating `Candle::new`, so the unchecked
constructor (intended for callers like the aggregator and parsed-payload
paths that have already validated upstream) was dead.
Add candle_new_unchecked_preserves_fields_verbatim:
- first assertion builds a candle with six distinct field values and
verifies each reads back exactly.
- second assertion feeds an OHLC combination (high < low) that the
checked constructor rejects with Error::InvalidCandle, then proves
Candle::new_unchecked still builds the struct as-is. This documents
and enforces the API contract that the unchecked variant performs
no validation.
ohlcv.rs is now at 170/170 lines, no behavioural change.
|
||
|
|
0abc5f71c1 |
test(vwap): cover Vwap value()/zero-volume/metadata + RollingVwap getters
Codecov flagged 17 uncovered lines in
crates/wickra-core/src/indicators/vwap.rs (file at 87.94%):
- Vwap::value() Some branch (line 53) — the only test calling value()
did so after reset() when sum_v == 0, exercising only the None branch
- Vwap::update zero-volume early-return `return None;` (line 67) — all
existing candles carried strictly positive volume
- Vwap::warmup_period body returning 1 (79-81), Vwap::name body
returning "VWAP" (87-89) — metadata never queried
- RollingVwap::period accessor (134-136), RollingVwap::warmup_period
body (165-167), RollingVwap::name body returning "RollingVWAP"
(173-175) — same metadata gap on the rolling variant
Add four new tests:
- cumulative_value_some_branch_after_update drives a single
non-zero-volume candle then asserts value() == Some(typical_price).
- cumulative_zero_volume_first_candle_returns_none feeds a candle
with volume == 0.0, asserts update returns None and is_ready stays
false, then adds a real candle to confirm the indicator still works.
- cumulative_metadata asserts warmup_period() == 1 and name() == "VWAP".
- rolling_accessors_and_metadata asserts period() == 7,
warmup_period() == 7, name() == "RollingVWAP" on a RollingVwap::new(7).
vwap.rs is now at 141/141 lines, no behavioural change.
|
||
|
|
404fd29c31 |
test(percent_b): cover accessors, kill dead arm, fix flaky middle test
Codecov flagged 17 uncovered lines in
crates/wickra-core/src/indicators/percent_b.rs (file at 81.11%):
- const accessors period (53-55), multiplier (58-60), value (63-65)
- Indicator-impl bodies warmup_period (90-92) and name (98-100)
- the unreachable `_ => panic!("warmup mismatch at {i}")` arm in
matches_bands_definition (line 141)
- the inner `assert_relative_eq!(*pv, 0.5, …)` (line 158) inside
price_at_middle_is_half, gated by `(prices[i] - bv.middle).abs()
< 1e-9` over a sin-based oscillation that, with period=20 over 60
samples, never lands within 1e-9 of the rolling SMA — so the
assertion was silently dead and the test made no checks.
Add accessors_and_metadata covering the five getter bodies, refactor
matches_bands_definition to assert the warmup-shape invariant via
assert_eq!(p.is_some(), b.is_some()) + if let (kills the panic arm),
and replace price_at_middle_is_half with a deterministic construction:
PercentB::new(3, 2.0) on [1.0, 5.0, 3.0] gives SMA=3.0 at index 2
which equals the third price exactly, stddev=√(8/3)≈1.633 keeps the
width strictly positive so the divide path runs, and %b lands on
exactly 0.5 because price sits on the centre line of symmetric bands.
percent_b.rs is now at 90/90 lines, no behavioural change.
|
||
|
|
050e5b74b8 |
test(bollinger_bandwidth): cover accessors, zero-middle branch, kill dead arm
Codecov flagged 17 uncovered lines in
crates/wickra-core/src/indicators/bollinger_bandwidth.rs (file at 79.51%):
- the const accessors period (54-56), multiplier (59-61), value (64-66)
- the zero-middle defensive fallback 0.0 (line 77) inside update
- the Indicator-impl bodies warmup_period (90-92) and name (98-100)
- the unreachable `_ => panic!("warmup mismatch")` arm (line 140) in
the existing matches_bands_definition test
None of the existing tests inspected the metadata surface — every test
fed numeric updates and asserted on bandwidth values, leaving the five
getter bodies dead. The zero-middle path was unreachable because all
existing tests used positive price levels ≈100, so the rolling SMA was
always strictly positive and the divide-by-zero guard never fired.
The panic arm in matches_bands_definition was an invariant guard that
by design cannot fire when the two streams share a warmup period; that
invariant is now asserted directly with assert_eq!(w.is_some(),
b.is_some()), and the catch-all arm is gone.
Add two new tests and refactor one existing:
- accessors_and_metadata asserts period == 20, multiplier == 2.0,
value() == None before warmup, warmup_period == 20, name ==
"BollingerBandwidth", then drives 20 updates so value() also
exercises the Some branch.
- zero_middle_band_yields_zero_bandwidth feeds [-2, -1, 0, 1, 2] so
the 5-bar SMA lands on exactly 0.0 at the fifth input, and asserts
the emitted bandwidth is exactly 0.0 (rather than inf/nan from the
would-be divide-by-zero).
- matches_bands_definition now uses an explicit assert_eq! on
is_some() agreement plus an if let for the numeric compare,
removing the unreachable panic arm without weakening the
invariant check.
bollinger_bandwidth.rs is now at 83/83 lines, no behavioural change.
|
||
|
|
62fe7a81aa |
test(traits): cover Chain accessors + Identity/Doubler helper surface
Codecov flagged 30 uncovered lines in crates/wickra-core/src/traits.rs (file at
75.60%): the const borrow accessors Chain::first / Chain::second (140-147), the
Chain::warmup_period + Chain::name Indicator-impl bodies (167-178), the full
Identity test-helper Indicator surface — reset, warmup_period, is_ready, name
(198-209), and Doubler's warmup_period + name (228-236).
None of the existing tests touched those code paths: every chain test invoked
update/reset/is_ready through the Chain wrapper without ever inspecting the
borrow accessors, querying chain.warmup_period(), or asking for chain.name(),
and the Identity helper was only ever driven by batch (which calls update
only). Doubler's warmup_period and name were similarly dead because
Chain::warmup_period and Chain::name themselves were dead.
Add two new tests at the end of the Chain section in mod tests, immediately
before the parallel-feature-gated test:
- chain_accessors_and_metadata exercises chain.first(), chain.second(),
chain.warmup_period(), chain.name(), and pulls Doubler::warmup_period
+ Doubler::name in via the borrowed accessors.
- identity_helper_full_indicator_surface asserts warmup_period == 0,
name == "Identity", and walks is_ready through both seen=false and
seen=true via an update/reset cycle.
traits.rs is now at 123/123 lines, no behavioural change.
|
||
|
|
3fcf2094f1 |
ci(wasm): install wasm-pack via taiki-e prebuilt instead of jetli's stale 0.10
jetli/wasm-pack-action@v0.4.0 with no version: input installs whatever
wasm-pack the action's bundled installer fetches — currently a ~0.10.x
release whose 'build' subcommand does not yet accept --features. Our
build invocation 'wasm-pack build … --features panic-hook' now fails
with
error: Found argument '--features' which wasn't expected, or isn't
valid in this context
USAGE: wasm-pack build --release --target <target>
even though that exact command worked on past runs where the action
happened to install a newer wasm-pack. (The bundler-target release.yml
job passed for v0.2.1 only because it shared the same cached install
on that runner.)
wasm-pack's --features top-level flag has been stable since 0.12.0, so
the fix is to install a fresh wasm-pack each run. Switch both the ci.yml
'WASM build' step and the release.yml 'wasm-publish' job to the same
taiki-e/install-action prebuilt-binary installer we already use for
cargo-llvm-cov and cargo-fuzz. taiki-e tracks the latest wasm-pack
release and the install is a single binary download — no compile, no
shell installer.
The wasm-pack invocations themselves are unchanged.
|
||
|
|
a2ccd202aa |
test(resample): cover RolledBar::absorb low-update branch
Codecov flagged a single uncovered line in crates/wickra-data/src/resample.rs: line 46, the `self.low = c.low;` assignment inside RolledBar::absorb. None of the existing resampler tests fed a follow-up candle with a strictly lower low than the first candle in the bucket, so the `c.low < self.low` branch never fired. Coverage stayed at 122/123. Add a small dedicated test that pushes a 10.0-low candle into bucket 0, then a 8.0-low candle into the same bucket, and asserts the rolled bar's low reflects the dip. Resample file is now at 123/123 lines, no behavioural change. |
||
|
|
4aec5d544c |
docs(wiki): migrate documentation out of repo into GitHub Wiki
The 84 markdown files under docs/wiki/ are now published to the project's GitHub Wiki at https://github.com/kingchenc/wickra/wiki — a separate git repository (https://github.com/kingchenc/wickra.wiki.git) that GitHub hosts natively with its own UI, search and history. The flat layout that the GitHub Wiki requires has been generated, all internal cross-links rewritten, and a _Sidebar.md groups the 71 indicators by their canonical 8 families. Effects: - docs/wiki/ is removed from the main repo (-84 files). docs/README.md now just points readers at the Wiki. - PR template + CONTRIBUTING text updated to point at the Wiki instead of the in-repo path. The Wiki repo is separately cloneable and editable via the GitHub web UI. - examples/wasm/README.md cross-link fixed to use the Wiki URL. - The (still in-repo) CHANGELOG keeps its historical references to docs/wiki/ paths — those describe what the tree looked like at past releases and stay accurate as history. - README.md, license, all source unaffected. The Wiki itself ships with _Sidebar.md / _Footer.md generated from the 8-families taxonomy and 503/503 cross-links resolved. |
||
|
|
e6375746d3 |
docs: unify README across crates.io / PyPI / npm / GitHub
Three separate README files (root, bindings/node, bindings/python) had been drifting independently — each registry showed a different project page, which is exactly the consistency debt I want to avoid. Single source of truth: /README.md. The three binding READMEs are overwritten with the root README content as a baseline, and release.yml gets a one-line cp step right before every publishing call so future edits to /README.md propagate automatically: - python-wheels job: cp README.md bindings/python/README.md before PyO3/maturin-action runs the wheel build - python-sdist job: same, before the sdist build - node-publish job: cp ../../README.md README.md (working-directory bindings/node) before the main 'npm publish wickra' - wasm-publish job: cp README.md bindings/wasm/README.md before wasm-pack build (which copies the crate README into pkg/ on its own) Cargo crates (wickra, wickra-core, wickra-data) already inherit readme.workspace = true pointing at /README.md, so crates.io was already correct — no change needed there. The per-platform npm subpackages (bindings/node/npm/<target>/) keep their tiny package.json with no README; they are install-time optionalDependencies that the loader reads through, never user-facing on the registry. Effect: same README on github.com/kingchenc/wickra, crates.io/crates/wickra, pypi.org/project/wickra, and npmjs.com/package/wickra. Will be live on the registries with the next tag-push. |
||
|
|
a876b145b0 |
chore: trigger CI to upload first Codecov coverage report
CODECOV_TOKEN was just added as a repository secret; the existing Coverage job in .github/workflows/ci.yml will pick it up on the next run. This empty commit fires that run. |
||
|
|
5a573650d2 |
Merge pull request #18 from kingchenc/release/0.2.1
release(0.2.1): bump to 0.2.1, Windows ARM64 skipped this cyclev0.2.1 |
||
|
|
8aa74cb638 |
release(0.2.1): bump to 0.2.1, skipping Windows ARM64 this cycle
The 0.2.0 release left wickra@npm stuck at 0.1.4 and never created a GitHub Release entry because the brand-new `wickra-win32-arm64-msvc` sub-package name was caught by npm's spam-detection filter on its first publish attempt (same situation that affected `wickra-win32-x64-msvc` through 0.1.4 until npm Support unblocked it). A support ticket is open; until it is resolved, ship 0.2.1 for the five platforms whose sub-packages are already on npm and re-add Windows ARM64 in a follow-up release. Changes for this cycle: - bindings/node/package.json: remove "wickra-win32-arm64-msvc" from optionalDependencies and "aarch64-pc-windows-msvc" from napi.triples.additional. - bindings/node/npm/win32-arm64-msvc/: removed (will be restored fresh once the npm name is unblocked). - .github/workflows/release.yml: comment out the aarch64-pc-windows-msvc entry of the node-build matrix with a TODO/restore note. - Bump every workspace and binding version to 0.2.1 (Cargo.toml, pyproject.toml, bindings/node/package.json, five npm/<target> templates, the wiki version table). Cargo.lock regenerated. - CHANGELOG: new [0.2.1] block consolidating every fix that has landed on main since 0.2.0 (HV epsilon, examples CI step, fuzz cargo-fuzz install, MSRV 1.85 -> 1.86 / 1.77 -> 1.88, criterion 0.5 -> 0.8, tokio-tungstenite 0.24 -> 0.29, tick_aggregator gap-fill cap, every GitHub Action SHA-pin bump). Compare-link added. The arm64 loader branch in bindings/node/index.js is left untouched: a Windows ARM64 user installing 0.2.1 will get the standard `Cannot find module 'wickra-win32-arm64-msvc'` error from the loader, which is accurate. PyPI's win-arm64 wheel is unaffected. Verified locally: cargo fmt/clippy/test --workspace --all-features -> 630 passed / 0 failed cargo build -p wickra-examples --bins -> clean cargo build -p wickra-node -> clean |
||
|
|
aea17a87af |
Merge pull request #16 from kingchenc/fix/criterion-0.8
deps: criterion 0.5 → 0.8 (replaces #10) |
||
|
|
23c649aca9 |
Merge pull request #17 from kingchenc/fix/tokio-tungstenite-0.29
deps: tokio-tungstenite 0.24 → 0.29 (replaces #13) |
||
|
|
57d3b785c2 |
Merge pull request #9 from kingchenc/dependabot/github_actions/actions/download-artifact-8.0.1
deps(actions): bump actions/download-artifact from 4.3.0 to 8.0.1 |
||
|
|
d94dc43fb2 |
Merge pull request #5 from kingchenc/dependabot/github_actions/actions/upload-artifact-7.0.1
deps(actions): bump actions/upload-artifact from 4.6.2 to 7.0.1 |
||
|
|
ed57632576 |
build: lift workspace MSRV to 1.86 to satisfy criterion 0.8.2
criterion 0.8.2 raised its MSRV from 1.85 to 1.86 (rustc 1.85.1 is now explicitly rejected by its Cargo.toml `rust-version`). One more notch on the same upward drift that already took us from 1.75 -> 1.80 (rayon) -> 1.85 (clap_lex/edition2024). Updated: - Cargo.toml workspace rust-version 1.85 -> 1.86 - .github/workflows/ci.yml MSRV matrix name + toolchain - ci.yml comment refreshed to reflect the new driving dep |