From 9973d1a6bff842b155807c6c58d9e8d6d5374925 Mon Sep 17 00:00:00 2001 From: kingchenc Date: Thu, 11 Jun 2026 14:51:46 +0200 Subject: [PATCH] test(core): catalogue-wide invariant harness + guard non-finite input in 38 indicators (#254) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `crates/wickra-core/tests/invariants.rs` — a property-based (`proptest`) harness asserting three invariants for **every** indicator and bar-builder in the catalogue (every module entry implementing `Indicator` or `BarBuilder`; the lone non-indicator helper `pattern_swing`/`SwingTracker` is excluded by design) — and fixes the non-finite bugs the harness surfaced. ## Invariants 1. **batch == streaming** — `batch()` must replay `update()` exactly. 2. **reset == fresh** — after `reset()`, re-feeding the same data matches a fresh instance. 3. **non-finite rejected without poisoning** (`f64` / `(f64, f64)` families) — a NaN/inf tick returns `None` and leaves state identical to never having seen it. ## How it works - A single generic `check_seq` covers **all** input families — `f64`, `Candle`, `(f64, f64)`, and the exotic `CrossSection`, `Trade`, `DerivativesTick`, `OrderBook`, `TradeQuote` — via per-family `proptest` generators that produce *valid* inputs (e.g. order books are strictly monotonic and uncrossed). - `check_bars` covers the bar-builder trait. - Outputs are compared by `Debug` string so bit-identical `NaN` outputs count as equal — these properties test **determinism**, not NaN-freeness. ## The 38 non-finite fixes The harness surfaced **38 more** scalar/pairwise indicators that let a NaN/inf tick poison their state — the same class as the 16 pairwise indicators fixed earlier (#251), but missed by the grep-based audit (`kalman_hedge_ratio` and `spread_bollinger_bands` carried `is_finite` on their **constructor** params, not the update input). All 38 now reject non-finite input via the established first-statement guard; signatures unchanged, so every binding inherits the fix. With these in, the non-finite invariant is enforced for every `f64`/`(f64,f64)` indicator going forward — the permanent regression net that would have caught #251. Warmup-exactness was evaluated and **left out** (not universal — many multi-component/candlestick indicators emit before `warmup_period` by design). ## Verification - `cargo test -p wickra-core`: 4225 unit + harness + 464 doc/integration, all green. - `cargo clippy --workspace --all-targets --all-features -- -D warnings`: clean. - Coverage runs integration tests, so the new guard branches are exercised by the harness's NaN feed. Also documents the harness in README's Testing section and adds the pending 0.8.4 entries to CHANGELOG `[Unreleased]`. --- CHANGELOG.md | 29 + README.md | 5 +- .../src/indicators/autocorrelation.rs | 3 + .../wickra-core/src/indicators/bomar_bands.rs | 3 + crates/wickra-core/src/indicators/cfo.rs | 3 + .../indicators/coefficient_of_variation.rs | 3 + .../src/indicators/detrended_std_dev.rs | 3 + .../wickra-core/src/indicators/expectancy.rs | 3 + .../src/indicators/hurst_exponent.rs | 3 + .../src/indicators/kalman_hedge_ratio.rs | 3 + crates/wickra-core/src/indicators/kurtosis.rs | 3 + crates/wickra-core/src/indicators/linreg.rs | 3 + .../src/indicators/linreg_angle.rs | 3 + .../src/indicators/linreg_channel.rs | 3 + .../src/indicators/linreg_intercept.rs | 3 + .../src/indicators/linreg_slope.rs | 3 + .../indicators/median_absolute_deviation.rs | 3 + .../src/indicators/median_channel.rs | 3 + .../wickra-core/src/indicators/mid_point.rs | 3 + .../indicators/percentage_trailing_stop.rs | 3 + .../polarized_fractal_efficiency.rs | 3 + .../src/indicators/quartile_bands.rs | 3 + .../wickra-core/src/indicators/r_squared.rs | 3 + .../src/indicators/renko_trailing_stop.rs | 3 + .../wickra-core/src/indicators/rolling_iqr.rs | 3 + .../src/indicators/rolling_percentile_rank.rs | 3 + .../src/indicators/rolling_quantile.rs | 3 + crates/wickra-core/src/indicators/skewness.rs | 3 + .../src/indicators/spread_bollinger_bands.rs | 3 + .../src/indicators/standard_error.rs | 3 + .../src/indicators/standard_error_bands.rs | 3 + .../src/indicators/step_trailing_stop.rs | 3 + .../wickra-core/src/indicators/trend_label.rs | 3 + .../src/indicators/trend_strength_index.rs | 3 + crates/wickra-core/src/indicators/tsf.rs | 3 + .../src/indicators/tsf_oscillator.rs | 3 + crates/wickra-core/src/indicators/variance.rs | 3 + .../indicators/vertical_horizontal_filter.rs | 3 + crates/wickra-core/src/indicators/win_rate.rs | 3 + crates/wickra-core/src/indicators/z_score.rs | 3 + crates/wickra-core/tests/invariants.rs | 1075 +++++++++++++++++ 41 files changed, 1222 insertions(+), 1 deletion(-) create mode 100644 crates/wickra-core/tests/invariants.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c291980..e16b8192 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,35 @@ All notable changes to Wickra are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] +### Fixed +- A single non-finite (NaN/inf) tick no longer poisons indicator state. + The 16 pairwise running-sum/buffer indicators fixed first (`Beta`, + `BetaNeutralSpread`, `Cointegration`, `HasbrouckInformationShare`, + `PearsonCorrelation`, `RollingCorrelation`, `RollingCovariance`, + `DistanceSsd`, `GrangerCausality`, `KendallTau`, `LeadLagCrossCorrelation`, + `OuHalfLife`, `SpearmanCorrelation`, `SpreadAr1Coefficient`, `SpreadHurst`, + `VarianceRatio`) were joined by 38 more scalar/pairwise indicators the new + property harness surfaced (the linear-regression family, rolling quantiles + and IQR, `Variance`/`StdDev`-derived stats, `Kurtosis`/`Skewness`, the + trailing stops, `KalmanHedgeRatio`, `SpreadBollingerBands`, and more). Every + `f64` / `(f64, f64)` indicator now rejects non-finite input and returns + `None`, matching the streaming-robustness guarantee — and the harness enforces + it going forward. + +### Added +- Catalogue-wide property-based invariant harness + (`crates/wickra-core/tests/invariants.rs`) asserting `batch == streaming`, + `reset == fresh`, and non-finite-input rejection for every indicator and + bar-builder. + +### Changed +- CI: every job now has a runtime cap and the historically flaky Node test step + auto-retries, so a wedged runner fails fast instead of hanging for hours. +- Documentation accuracy fixes in `SECURITY.md`, `ARCHITECTURE.md`, and + `THREAT_MODEL.md` (supported version, indicator count, WASM test coverage, + numerical-stability notes, and the C-ABI panic strategy). + ## [0.8.3] - 2026-06-10 ### Added - **Per-binding throughput benchmarks** — every target now ships a `throughput` diff --git a/README.md b/README.md index bfc1e859..8a060dcc 100644 --- a/README.md +++ b/README.md @@ -334,7 +334,10 @@ Every layer is covered; run the suites with the commands in - `wickra-core`: unit tests per indicator — textbook reference values (Wilder RSI, Bollinger Bands, MACD, ATR, Stochastic), `batch == streaming` - equivalence, `reset` semantics, NaN/Inf handling, and property tests. + equivalence, `reset` semantics, NaN/Inf handling, and property tests. A + catalogue-wide property harness (`tests/invariants.rs`) additionally asserts + `batch == streaming`, `reset == fresh`, and non-finite-input rejection for + **every** indicator and bar-builder. - `wickra-data`: unit tests for CSV decoding, the tick aggregator, the resampler, and the Binance payload parser. - `bindings/python`: pytest covering smoke checks, streaming/batch diff --git a/crates/wickra-core/src/indicators/autocorrelation.rs b/crates/wickra-core/src/indicators/autocorrelation.rs index a6763adf..cf66ad34 100644 --- a/crates/wickra-core/src/indicators/autocorrelation.rs +++ b/crates/wickra-core/src/indicators/autocorrelation.rs @@ -84,6 +84,9 @@ impl Indicator for Autocorrelation { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { self.window.pop_front(); } diff --git a/crates/wickra-core/src/indicators/bomar_bands.rs b/crates/wickra-core/src/indicators/bomar_bands.rs index 7ece0a2e..dd215a7b 100644 --- a/crates/wickra-core/src/indicators/bomar_bands.rs +++ b/crates/wickra-core/src/indicators/bomar_bands.rs @@ -106,6 +106,9 @@ impl Indicator for BomarBands { type Output = BomarBandsOutput; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { self.window.pop_front(); } diff --git a/crates/wickra-core/src/indicators/cfo.rs b/crates/wickra-core/src/indicators/cfo.rs index 6c2168f4..67b416a4 100644 --- a/crates/wickra-core/src/indicators/cfo.rs +++ b/crates/wickra-core/src/indicators/cfo.rs @@ -60,6 +60,9 @@ impl Indicator for Cfo { type Output = f64; fn update(&mut self, input: f64) -> Option { + if !input.is_finite() { + return None; + } let forecast = self.linreg.update(input)?; // Hold the previous value if the close is zero — the percentage form // is undefined and a return of inf would propagate badly. diff --git a/crates/wickra-core/src/indicators/coefficient_of_variation.rs b/crates/wickra-core/src/indicators/coefficient_of_variation.rs index c16fba87..166d4c83 100644 --- a/crates/wickra-core/src/indicators/coefficient_of_variation.rs +++ b/crates/wickra-core/src/indicators/coefficient_of_variation.rs @@ -71,6 +71,9 @@ impl Indicator for CoefficientOfVariation { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { let old = self.window.pop_front().expect("non-empty"); self.sum -= old; diff --git a/crates/wickra-core/src/indicators/detrended_std_dev.rs b/crates/wickra-core/src/indicators/detrended_std_dev.rs index 054ab23c..d475cef5 100644 --- a/crates/wickra-core/src/indicators/detrended_std_dev.rs +++ b/crates/wickra-core/src/indicators/detrended_std_dev.rs @@ -96,6 +96,9 @@ impl Indicator for DetrendedStdDev { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { let y0 = self.window.pop_front().expect("non-empty"); self.sum_xy = self.sum_xy - self.sum_y + y0; diff --git a/crates/wickra-core/src/indicators/expectancy.rs b/crates/wickra-core/src/indicators/expectancy.rs index f15ff2fd..1a8dddbe 100644 --- a/crates/wickra-core/src/indicators/expectancy.rs +++ b/crates/wickra-core/src/indicators/expectancy.rs @@ -77,6 +77,9 @@ impl Indicator for Expectancy { type Output = f64; fn update(&mut self, ret: f64) -> Option { + if !ret.is_finite() { + return None; + } if self.window.len() == self.period { let old = self.window.pop_front().expect("window is non-empty"); self.sum -= old; diff --git a/crates/wickra-core/src/indicators/hurst_exponent.rs b/crates/wickra-core/src/indicators/hurst_exponent.rs index 97bcae8e..e6675763 100644 --- a/crates/wickra-core/src/indicators/hurst_exponent.rs +++ b/crates/wickra-core/src/indicators/hurst_exponent.rs @@ -138,6 +138,9 @@ impl Indicator for HurstExponent { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { self.window.pop_front(); } diff --git a/crates/wickra-core/src/indicators/kalman_hedge_ratio.rs b/crates/wickra-core/src/indicators/kalman_hedge_ratio.rs index 82ccac9e..9733f56b 100644 --- a/crates/wickra-core/src/indicators/kalman_hedge_ratio.rs +++ b/crates/wickra-core/src/indicators/kalman_hedge_ratio.rs @@ -116,6 +116,9 @@ impl Indicator for KalmanHedgeRatio { fn update(&mut self, input: (f64, f64)) -> Option { let (a, b) = input; + if !a.is_finite() || !b.is_finite() { + return None; + } // Predicted state covariance: add the transition noise to the diagonal // (the very first observation starts from a zero prior). let mut cov_pred = self.cov; diff --git a/crates/wickra-core/src/indicators/kurtosis.rs b/crates/wickra-core/src/indicators/kurtosis.rs index b8c935aa..ea79587a 100644 --- a/crates/wickra-core/src/indicators/kurtosis.rs +++ b/crates/wickra-core/src/indicators/kurtosis.rs @@ -80,6 +80,9 @@ impl Indicator for Kurtosis { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { let old = self.window.pop_front().expect("non-empty"); let sq = old * old; diff --git a/crates/wickra-core/src/indicators/linreg.rs b/crates/wickra-core/src/indicators/linreg.rs index df174120..b425abdf 100644 --- a/crates/wickra-core/src/indicators/linreg.rs +++ b/crates/wickra-core/src/indicators/linreg.rs @@ -99,6 +99,9 @@ impl Indicator for LinearRegression { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { // Sliding phase: pop the oldest, then shift every remaining index // down by 1 in the running `sum_xy`. The identity diff --git a/crates/wickra-core/src/indicators/linreg_angle.rs b/crates/wickra-core/src/indicators/linreg_angle.rs index a4a032a7..1ebc2a29 100644 --- a/crates/wickra-core/src/indicators/linreg_angle.rs +++ b/crates/wickra-core/src/indicators/linreg_angle.rs @@ -57,6 +57,9 @@ impl Indicator for LinRegAngle { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } self.slope.update(value).map(|s| s.atan().to_degrees()) } diff --git a/crates/wickra-core/src/indicators/linreg_channel.rs b/crates/wickra-core/src/indicators/linreg_channel.rs index 1a1b769a..4408d74e 100644 --- a/crates/wickra-core/src/indicators/linreg_channel.rs +++ b/crates/wickra-core/src/indicators/linreg_channel.rs @@ -98,6 +98,9 @@ impl Indicator for LinRegChannel { type Output = LinRegChannelOutput; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { self.window.pop_front(); } diff --git a/crates/wickra-core/src/indicators/linreg_intercept.rs b/crates/wickra-core/src/indicators/linreg_intercept.rs index ca320421..91acb8d7 100644 --- a/crates/wickra-core/src/indicators/linreg_intercept.rs +++ b/crates/wickra-core/src/indicators/linreg_intercept.rs @@ -77,6 +77,9 @@ impl Indicator for LinRegIntercept { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { let y0 = self.window.pop_front().expect("non-empty"); self.sum_xy = self.sum_xy - self.sum_y + y0; diff --git a/crates/wickra-core/src/indicators/linreg_slope.rs b/crates/wickra-core/src/indicators/linreg_slope.rs index 0158da8e..05f821fb 100644 --- a/crates/wickra-core/src/indicators/linreg_slope.rs +++ b/crates/wickra-core/src/indicators/linreg_slope.rs @@ -87,6 +87,9 @@ impl Indicator for LinRegSlope { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { // Sliding-window identity: when the window slides one step forward // the indices `x` for every kept entry shift down by 1, so diff --git a/crates/wickra-core/src/indicators/median_absolute_deviation.rs b/crates/wickra-core/src/indicators/median_absolute_deviation.rs index c2f9c479..cb06e95c 100644 --- a/crates/wickra-core/src/indicators/median_absolute_deviation.rs +++ b/crates/wickra-core/src/indicators/median_absolute_deviation.rs @@ -89,6 +89,9 @@ impl Indicator for MedianAbsoluteDeviation { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { self.window.pop_front(); } diff --git a/crates/wickra-core/src/indicators/median_channel.rs b/crates/wickra-core/src/indicators/median_channel.rs index 24161e00..e08d5d8a 100644 --- a/crates/wickra-core/src/indicators/median_channel.rs +++ b/crates/wickra-core/src/indicators/median_channel.rs @@ -96,6 +96,9 @@ impl Indicator for MedianChannel { type Output = MedianChannelOutput; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { self.window.pop_front(); } diff --git a/crates/wickra-core/src/indicators/mid_point.rs b/crates/wickra-core/src/indicators/mid_point.rs index 0b5b6fd9..9504a35e 100644 --- a/crates/wickra-core/src/indicators/mid_point.rs +++ b/crates/wickra-core/src/indicators/mid_point.rs @@ -59,6 +59,9 @@ impl Indicator for MidPoint { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { self.window.pop_front(); } diff --git a/crates/wickra-core/src/indicators/percentage_trailing_stop.rs b/crates/wickra-core/src/indicators/percentage_trailing_stop.rs index a7520c9d..70a198ba 100644 --- a/crates/wickra-core/src/indicators/percentage_trailing_stop.rs +++ b/crates/wickra-core/src/indicators/percentage_trailing_stop.rs @@ -74,6 +74,9 @@ impl Indicator for PercentageTrailingStop { type Output = f64; fn update(&mut self, close: f64) -> Option { + if !close.is_finite() { + return None; + } let step = close.abs() * self.percent / 100.0; let stop = match self.prev_stop { Some(prev) => { diff --git a/crates/wickra-core/src/indicators/polarized_fractal_efficiency.rs b/crates/wickra-core/src/indicators/polarized_fractal_efficiency.rs index 470bb9bb..f865b574 100644 --- a/crates/wickra-core/src/indicators/polarized_fractal_efficiency.rs +++ b/crates/wickra-core/src/indicators/polarized_fractal_efficiency.rs @@ -82,6 +82,9 @@ impl Indicator for PolarizedFractalEfficiency { type Output = f64; fn update(&mut self, close: f64) -> Option { + if !close.is_finite() { + return None; + } if let Some(prev) = self.prev_close { let diff = close - prev; let segment = diff.mul_add(diff, 1.0).sqrt(); diff --git a/crates/wickra-core/src/indicators/quartile_bands.rs b/crates/wickra-core/src/indicators/quartile_bands.rs index d7000281..3ba28d27 100644 --- a/crates/wickra-core/src/indicators/quartile_bands.rs +++ b/crates/wickra-core/src/indicators/quartile_bands.rs @@ -80,6 +80,9 @@ impl Indicator for QuartileBands { type Output = QuartileBandsOutput; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { self.window.pop_front(); } diff --git a/crates/wickra-core/src/indicators/r_squared.rs b/crates/wickra-core/src/indicators/r_squared.rs index 6802b898..88863790 100644 --- a/crates/wickra-core/src/indicators/r_squared.rs +++ b/crates/wickra-core/src/indicators/r_squared.rs @@ -95,6 +95,9 @@ impl Indicator for RSquared { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { let y0 = self.window.pop_front().expect("non-empty"); self.sum_xy = self.sum_xy - self.sum_y + y0; diff --git a/crates/wickra-core/src/indicators/renko_trailing_stop.rs b/crates/wickra-core/src/indicators/renko_trailing_stop.rs index fab0c322..71526187 100644 --- a/crates/wickra-core/src/indicators/renko_trailing_stop.rs +++ b/crates/wickra-core/src/indicators/renko_trailing_stop.rs @@ -74,6 +74,9 @@ impl Indicator for RenkoTrailingStop { type Output = f64; fn update(&mut self, close: f64) -> Option { + if !close.is_finite() { + return None; + } let anchor = match self.anchor { Some(prev) => { if self.long { diff --git a/crates/wickra-core/src/indicators/rolling_iqr.rs b/crates/wickra-core/src/indicators/rolling_iqr.rs index b2293fda..eddcd67f 100644 --- a/crates/wickra-core/src/indicators/rolling_iqr.rs +++ b/crates/wickra-core/src/indicators/rolling_iqr.rs @@ -71,6 +71,9 @@ impl Indicator for RollingIqr { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { self.window.pop_front(); } diff --git a/crates/wickra-core/src/indicators/rolling_percentile_rank.rs b/crates/wickra-core/src/indicators/rolling_percentile_rank.rs index f599398d..512c7c93 100644 --- a/crates/wickra-core/src/indicators/rolling_percentile_rank.rs +++ b/crates/wickra-core/src/indicators/rolling_percentile_rank.rs @@ -70,6 +70,9 @@ impl Indicator for RollingPercentileRank { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { self.window.pop_front(); } diff --git a/crates/wickra-core/src/indicators/rolling_quantile.rs b/crates/wickra-core/src/indicators/rolling_quantile.rs index 47cfc223..b091efdb 100644 --- a/crates/wickra-core/src/indicators/rolling_quantile.rs +++ b/crates/wickra-core/src/indicators/rolling_quantile.rs @@ -104,6 +104,9 @@ impl Indicator for RollingQuantile { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { self.window.pop_front(); } diff --git a/crates/wickra-core/src/indicators/skewness.rs b/crates/wickra-core/src/indicators/skewness.rs index 57a62660..79f51f1e 100644 --- a/crates/wickra-core/src/indicators/skewness.rs +++ b/crates/wickra-core/src/indicators/skewness.rs @@ -79,6 +79,9 @@ impl Indicator for Skewness { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { let old = self.window.pop_front().expect("non-empty"); self.sum -= old; diff --git a/crates/wickra-core/src/indicators/spread_bollinger_bands.rs b/crates/wickra-core/src/indicators/spread_bollinger_bands.rs index 35dcffc4..3cdaea0a 100644 --- a/crates/wickra-core/src/indicators/spread_bollinger_bands.rs +++ b/crates/wickra-core/src/indicators/spread_bollinger_bands.rs @@ -116,6 +116,9 @@ impl Indicator for SpreadBollingerBands { fn update(&mut self, input: (f64, f64)) -> Option { let (a, b) = input; + if !a.is_finite() || !b.is_finite() { + return None; + } let spread = a - b; if self.window.len() == self.period { let old = self.window.pop_front().expect("non-empty"); diff --git a/crates/wickra-core/src/indicators/standard_error.rs b/crates/wickra-core/src/indicators/standard_error.rs index b0dbacd2..a3c5de9d 100644 --- a/crates/wickra-core/src/indicators/standard_error.rs +++ b/crates/wickra-core/src/indicators/standard_error.rs @@ -93,6 +93,9 @@ impl Indicator for StandardError { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { // Slide: pop oldest, shift indices, then push the new value at index n − 1. let y0 = self.window.pop_front().expect("non-empty"); diff --git a/crates/wickra-core/src/indicators/standard_error_bands.rs b/crates/wickra-core/src/indicators/standard_error_bands.rs index a1787b2c..51a66afa 100644 --- a/crates/wickra-core/src/indicators/standard_error_bands.rs +++ b/crates/wickra-core/src/indicators/standard_error_bands.rs @@ -106,6 +106,9 @@ impl Indicator for StandardErrorBands { type Output = StandardErrorBandsOutput; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { self.window.pop_front(); } diff --git a/crates/wickra-core/src/indicators/step_trailing_stop.rs b/crates/wickra-core/src/indicators/step_trailing_stop.rs index 4e5410f2..a0759e56 100644 --- a/crates/wickra-core/src/indicators/step_trailing_stop.rs +++ b/crates/wickra-core/src/indicators/step_trailing_stop.rs @@ -84,6 +84,9 @@ impl Indicator for StepTrailingStop { type Output = f64; fn update(&mut self, close: f64) -> Option { + if !close.is_finite() { + return None; + } let stop = match self.prev_stop { Some(prev) => { if self.long { diff --git a/crates/wickra-core/src/indicators/trend_label.rs b/crates/wickra-core/src/indicators/trend_label.rs index 8ee224c4..a189c8f9 100644 --- a/crates/wickra-core/src/indicators/trend_label.rs +++ b/crates/wickra-core/src/indicators/trend_label.rs @@ -72,6 +72,9 @@ impl Indicator for TrendLabel { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { self.window.pop_front(); } diff --git a/crates/wickra-core/src/indicators/trend_strength_index.rs b/crates/wickra-core/src/indicators/trend_strength_index.rs index 0e8ec63c..14088a2d 100644 --- a/crates/wickra-core/src/indicators/trend_strength_index.rs +++ b/crates/wickra-core/src/indicators/trend_strength_index.rs @@ -75,6 +75,9 @@ impl Indicator for TrendStrengthIndex { type Output = f64; fn update(&mut self, price: f64) -> Option { + if !price.is_finite() { + return None; + } self.buf.push_back(price); if self.buf.len() > self.period { self.buf.pop_front(); diff --git a/crates/wickra-core/src/indicators/tsf.rs b/crates/wickra-core/src/indicators/tsf.rs index 527c5434..4cca0605 100644 --- a/crates/wickra-core/src/indicators/tsf.rs +++ b/crates/wickra-core/src/indicators/tsf.rs @@ -80,6 +80,9 @@ impl Indicator for Tsf { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { let y0 = self.window.pop_front().expect("non-empty"); self.sum_xy = self.sum_xy - self.sum_y + y0; diff --git a/crates/wickra-core/src/indicators/tsf_oscillator.rs b/crates/wickra-core/src/indicators/tsf_oscillator.rs index 08c897e8..18ff976c 100644 --- a/crates/wickra-core/src/indicators/tsf_oscillator.rs +++ b/crates/wickra-core/src/indicators/tsf_oscillator.rs @@ -73,6 +73,9 @@ impl Indicator for TsfOscillator { type Output = f64; fn update(&mut self, input: f64) -> Option { + if !input.is_finite() { + return None; + } let forecast = self.tsf.update(input)?; // Hold the previous value if the close is zero — the percentage form // is undefined and a return of inf would propagate badly. diff --git a/crates/wickra-core/src/indicators/variance.rs b/crates/wickra-core/src/indicators/variance.rs index 99838191..90ecd60c 100644 --- a/crates/wickra-core/src/indicators/variance.rs +++ b/crates/wickra-core/src/indicators/variance.rs @@ -70,6 +70,9 @@ impl Indicator for Variance { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { let old = self.window.pop_front().expect("non-empty"); self.sum -= old; diff --git a/crates/wickra-core/src/indicators/vertical_horizontal_filter.rs b/crates/wickra-core/src/indicators/vertical_horizontal_filter.rs index 0c55e098..76c64108 100644 --- a/crates/wickra-core/src/indicators/vertical_horizontal_filter.rs +++ b/crates/wickra-core/src/indicators/vertical_horizontal_filter.rs @@ -68,6 +68,9 @@ impl Indicator for VerticalHorizontalFilter { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.closes.len() == self.period { self.closes.pop_front(); } diff --git a/crates/wickra-core/src/indicators/win_rate.rs b/crates/wickra-core/src/indicators/win_rate.rs index 7873775c..a1f998c7 100644 --- a/crates/wickra-core/src/indicators/win_rate.rs +++ b/crates/wickra-core/src/indicators/win_rate.rs @@ -66,6 +66,9 @@ impl Indicator for WinRate { type Output = f64; fn update(&mut self, ret: f64) -> Option { + if !ret.is_finite() { + return None; + } if self.window.len() == self.period { let old = self.window.pop_front().expect("window is non-empty"); if old > 0.0 { diff --git a/crates/wickra-core/src/indicators/z_score.rs b/crates/wickra-core/src/indicators/z_score.rs index eba8902d..497fba76 100644 --- a/crates/wickra-core/src/indicators/z_score.rs +++ b/crates/wickra-core/src/indicators/z_score.rs @@ -66,6 +66,9 @@ impl Indicator for ZScore { type Output = f64; fn update(&mut self, value: f64) -> Option { + if !value.is_finite() { + return None; + } if self.window.len() == self.period { let old = self.window.pop_front().expect("non-empty"); self.sum -= old; diff --git a/crates/wickra-core/tests/invariants.rs b/crates/wickra-core/tests/invariants.rs new file mode 100644 index 00000000..202168de --- /dev/null +++ b/crates/wickra-core/tests/invariants.rs @@ -0,0 +1,1075 @@ +//! Property-based invariants every indicator must uphold. +//! +//! Three properties are checked against random input sequences, rolled out over +//! the whole catalogue so a regression in any single indicator surfaces here: +//! +//! 1. **batch == streaming** — `batch()` must replay `update()` exactly. +//! 2. **reset == fresh** — after `reset()`, re-feeding the same data must match +//! a freshly constructed instance. +//! 3. **non-finite is rejected without poisoning** — a NaN/inf tick returns +//! `None` and leaves state identical to never having seen it. `Candle` and the +//! other validated input types cannot be non-finite, so this applies to the +//! `f64` and `(f64, f64)` families. This is the regression net that would have +//! caught the pairwise non-finite bug (#251). + +use proptest::prelude::*; +use wickra_core::*; + +// --- generic invariant bodies ------------------------------------------------ + +// One generic body for every `Indicator` family. Inputs are compared via Debug +// so bit-identical NaN outputs count as equal (NaN != NaN would otherwise break +// equality for indicators that emit NaN on degenerate input — these properties +// are about determinism, not NaN-freeness). +fn check_seq(make: impl Fn() -> I, xs: &[I::Input]) -> std::result::Result<(), TestCaseError> +where + I: Indicator, + I::Input: Clone, + I::Output: std::fmt::Debug, +{ + let batch = make().batch(xs); + let mut s = make(); + let stream: Vec<_> = xs.iter().map(|x| s.update(x.clone())).collect(); + prop_assert_eq!( + format!("{batch:?}"), + format!("{stream:?}"), + "batch != streaming" + ); + + let mut a = make(); + let _ = a.batch(xs); + a.reset(); + let after: Vec<_> = xs.iter().map(|x| a.update(x.clone())).collect(); + prop_assert_eq!( + format!("{after:?}"), + format!("{stream:?}"), + "reset != fresh" + ); + Ok(()) +} + +// BarBuilder has a different shape (`update(Candle) -> Vec`), so it gets its +// own body. `batch` concatenates per-candle bars; streaming flat-maps them. +fn check_bars(make: impl Fn() -> B, candles: &[Candle]) -> std::result::Result<(), TestCaseError> +where + B: BarBuilder, + B::Bar: std::fmt::Debug, +{ + let batch = make().batch(candles); + let mut s = make(); + let stream: Vec<_> = candles.iter().flat_map(|&c| s.update(c)).collect(); + prop_assert_eq!( + format!("{batch:?}"), + format!("{stream:?}"), + "batch != streaming" + ); + + let mut a = make(); + let _ = a.batch(candles); + a.reset(); + let after: Vec<_> = candles.iter().flat_map(|&c| a.update(c)).collect(); + prop_assert_eq!( + format!("{after:?}"), + format!("{stream:?}"), + "reset != fresh" + ); + Ok(()) +} + +// Non-finite rejection for the `f64` family: a NaN/inf tick returns `None` and +// does not poison state (the poisoned run must match a clean one). Only applies +// to scalar/pairwise inputs — `Candle` and the exotic types validate finiteness +// at construction. +fn check_scalar_nonfinite( + make: impl Fn() -> I, + xs: &[f64], +) -> std::result::Result<(), TestCaseError> +where + I: Indicator, + I::Output: std::fmt::Debug, +{ + let mut s = make(); + let clean: Vec<_> = xs.iter().map(|&x| s.update(x)).collect(); + let mut g = make(); + prop_assert!(g.update(f64::NAN).is_none(), "NaN not rejected"); + prop_assert!(g.update(f64::INFINITY).is_none(), "inf not rejected"); + prop_assert!(g.update(f64::NEG_INFINITY).is_none(), "-inf not rejected"); + let poisoned: Vec<_> = xs.iter().map(|&x| g.update(x)).collect(); + prop_assert_eq!( + format!("{poisoned:?}"), + format!("{clean:?}"), + "non-finite poisoned state" + ); + Ok(()) +} + +// Non-finite rejection for the `(f64, f64)` family. +fn check_pairwise_nonfinite( + make: impl Fn() -> I, + xs: &[(f64, f64)], +) -> std::result::Result<(), TestCaseError> +where + I: Indicator, + I::Output: std::fmt::Debug, +{ + let mut s = make(); + let clean: Vec<_> = xs.iter().map(|&p| s.update(p)).collect(); + let mut g = make(); + prop_assert!(g.update((f64::NAN, 1.0)).is_none(), "NaN not rejected"); + prop_assert!(g.update((1.0, f64::INFINITY)).is_none(), "inf not rejected"); + let poisoned: Vec<_> = xs.iter().map(|&p| g.update(p)).collect(); + prop_assert_eq!( + format!("{poisoned:?}"), + format!("{clean:?}"), + "non-finite poisoned state" + ); + Ok(()) +} + +// --- per-family roll-out macros ---------------------------------------------- + +macro_rules! scalar_inv { + ($name:ident, $($ctor:tt)+) => { + proptest! { + #![proptest_config(ProptestConfig::with_cases(24))] + #[test] + fn $name(xs in prop::collection::vec(-1.0e6f64..1.0e6, 0..160)) { + check_seq(|| { $($ctor)+ }, &xs)?; + check_scalar_nonfinite(|| { $($ctor)+ }, &xs)?; + } + } + }; +} + +macro_rules! candle_inv { + ($name:ident, $($ctor:tt)+) => { + proptest! { + #![proptest_config(ProptestConfig::with_cases(24))] + #[test] + fn $name( + raw in prop::collection::vec( + (1.0e1f64..1.0e4, 0.0f64..200.0, 0.0f64..1.0, 0.0f64..1.0), + 0..160, + ) + ) { + // (low, range, open_frac, close_frac) -> a valid OHLC candle. + let candles: Vec = raw + .iter() + .map(|&(low, range, of, cf)| { + Candle::new(low + range * of, low + range, low, low + range * cf, 1.0, 0) + .expect("constructed valid candle") + }) + .collect(); + check_seq(|| { $($ctor)+ }, &candles)?; + } + } + }; +} + +macro_rules! pair_inv { + ($name:ident, $($ctor:tt)+) => { + proptest! { + #![proptest_config(ProptestConfig::with_cases(24))] + #[test] + fn $name( + xs in prop::collection::vec((-1.0e6f64..1.0e6, -1.0e6f64..1.0e6), 0..160) + ) { + check_seq(|| { $($ctor)+ }, &xs)?; + check_pairwise_nonfinite(|| { $($ctor)+ }, &xs)?; + } + } + }; +} + +macro_rules! cross_section_inv { + ($name:ident, $($ctor:tt)+) => { + proptest! { + #![proptest_config(ProptestConfig::with_cases(16))] + #[test] + fn $name( + raw in prop::collection::vec( + prop::collection::vec( + (-1.0e3f64..1.0e3, 0.0f64..1.0e6, any::(), any::(), any::(), any::()), + 4..=4, + ), + 0..80, + ) + ) { + let ticks: Vec = raw + .iter() + .map(|members| { + let m = members + .iter() + .map(|&(c, v, nh, nl, a, o)| Member::with_signals(c, v, nh, nl, a, o)) + .collect(); + CrossSection::new(m, 0).expect("valid cross-section") + }) + .collect(); + check_seq(|| { $($ctor)+ }, &ticks)?; + } + } + }; +} + +macro_rules! trade_inv { + ($name:ident, $($ctor:tt)+) => { + proptest! { + #![proptest_config(ProptestConfig::with_cases(24))] + #[test] + fn $name(raw in prop::collection::vec((1.0f64..1.0e5, 0.0f64..1.0e4, any::()), 0..160)) { + let ticks: Vec = raw + .iter() + .map(|&(p, s, b)| Trade::new(p, s, if b { Side::Buy } else { Side::Sell }, 0).expect("valid trade")) + .collect(); + check_seq(|| { $($ctor)+ }, &ticks)?; + } + } + }; +} + +macro_rules! deriv_inv { + ($name:ident, $($ctor:tt)+) => { + proptest! { + #![proptest_config(ProptestConfig::with_cases(16))] + #[test] + fn $name( + raw in prop::collection::vec( + ( + (-1.0f64..1.0, 1.0f64..1.0e5, 1.0f64..1.0e5, 1.0f64..1.0e5), + (0.0f64..1.0e6, 0.0f64..1.0e6, 0.0f64..1.0e6, 0.0f64..1.0e6), + (0.0f64..1.0e6, 0.0f64..1.0e6, 0.0f64..1.0e6), + ), + 0..120, + ) + ) { + let ticks: Vec = raw + .iter() + .map(|&((fr, mk, ix, fu), (oi, ls, ss, tbv), (tsv, ll, sl))| { + DerivativesTick::new(fr, mk, ix, fu, oi, ls, ss, tbv, tsv, ll, sl, 0).expect("valid deriv tick") + }) + .collect(); + check_seq(|| { $($ctor)+ }, &ticks)?; + } + } + }; +} + +macro_rules! orderbook_inv { + ($name:ident, $($ctor:tt)+) => { + proptest! { + #![proptest_config(ProptestConfig::with_cases(16))] + #[test] + fn $name(raw in prop::collection::vec((1.0e2f64..1.0e4, 0.1f64..10.0, 0.0f64..1.0e4), 0..100)) { + // mid +/- k*gap gives strictly-monotonic, uncrossed books (best_bid < best_ask). + let books: Vec = raw + .iter() + .map(|&(mid, gap, sz)| { + let asks = (1..=3).map(|k| Level::new(mid + f64::from(k) * gap, sz).expect("ask")).collect(); + let bids = (1..=3).map(|k| Level::new(mid - f64::from(k) * gap, sz).expect("bid")).collect(); + OrderBook::new(bids, asks).expect("valid book") + }) + .collect(); + check_seq(|| { $($ctor)+ }, &books)?; + } + } + }; +} + +macro_rules! tradequote_inv { + ($name:ident, $($ctor:tt)+) => { + proptest! { + #![proptest_config(ProptestConfig::with_cases(24))] + #[test] + fn $name(raw in prop::collection::vec((1.0f64..1.0e5, 0.0f64..1.0e4, any::(), 1.0f64..1.0e5), 0..160)) { + let tqs: Vec = raw + .iter() + .map(|&(p, s, b, mid)| { + let tr = Trade::new(p, s, if b { Side::Buy } else { Side::Sell }, 0).expect("trade"); + TradeQuote::new(tr, mid).expect("valid trade-quote") + }) + .collect(); + check_seq(|| { $($ctor)+ }, &tqs)?; + } + } + }; +} + +macro_rules! bar_inv { + ($name:ident, $($ctor:tt)+) => { + proptest! { + #![proptest_config(ProptestConfig::with_cases(24))] + #[test] + fn $name( + raw in prop::collection::vec( + (1.0e1f64..1.0e4, 0.0f64..200.0, 0.0f64..1.0, 0.0f64..1.0), + 0..160, + ) + ) { + let candles: Vec = raw + .iter() + .map(|&(low, range, of, cf)| { + Candle::new(low + range * of, low + range, low, low + range * cf, 1.0, 0) + .expect("constructed valid candle") + }) + .collect(); + check_bars(|| { $($ctor)+ }, &candles)?; + } + } + }; +} + +// --- catalogue: full roll-out over the whole catalogue (513 of 514 mod entries: +// 503 Indicator + 10 BarBuilder; pattern_swing is neither, see below). --- + +// --- scalar (167) --- +scalar_inv!(inv_adaptive_cycle, AdaptiveCycle::new()); +scalar_inv!( + inv_adaptive_laguerre_filter, + AdaptiveLaguerreFilter::new(13).unwrap() +); +scalar_inv!(inv_adaptive_rsi, AdaptiveRsi::new(14).unwrap()); +scalar_inv!(inv_alma, Alma::new(9, 0.85, 6.0).unwrap()); +scalar_inv!(inv_anchored_rsi, AnchoredRsi::new()); +scalar_inv!(inv_apo, Apo::new(12, 26).unwrap()); +scalar_inv!(inv_autocorrelation, Autocorrelation::new(20, 1).unwrap()); +scalar_inv!( + inv_autocorrelation_periodogram, + AutocorrelationPeriodogram::new(10, 48).unwrap() +); +scalar_inv!(inv_average_drawdown, AverageDrawdown::new(10).unwrap()); +scalar_inv!(inv_bandpass_filter, BandpassFilter::new(20, 0.3).unwrap()); +scalar_inv!(inv_bipower_variation, BipowerVariation::new(20).unwrap()); +scalar_inv!(inv_bollinger, BollingerBands::new(5, 2.0).unwrap()); +scalar_inv!( + inv_bollinger_bandwidth, + BollingerBandwidth::new(20, 2.0).unwrap() +); +scalar_inv!(inv_bomar_bands, BomarBands::new(20, 0.85).unwrap()); +scalar_inv!(inv_burke_ratio, BurkeRatio::new(12).unwrap()); +scalar_inv!(inv_calmar_ratio, CalmarRatio::new(20).unwrap()); +scalar_inv!(inv_center_of_gravity, CenterOfGravity::new(10).unwrap()); +scalar_inv!(inv_cfo, Cfo::new(14).unwrap()); +scalar_inv!(inv_cmo, Cmo::new(14).unwrap()); +scalar_inv!( + inv_coefficient_of_variation, + CoefficientOfVariation::new(20).unwrap() +); +scalar_inv!(inv_common_sense_ratio, CommonSenseRatio::new(20).unwrap()); +scalar_inv!( + inv_conditional_value_at_risk, + ConditionalValueAtRisk::new(100, 0.95).unwrap() +); +scalar_inv!(inv_connors_rsi, ConnorsRsi::new(3, 2, 5).unwrap()); +scalar_inv!(inv_coppock, Coppock::new(14, 11, 10).unwrap()); +scalar_inv!( + inv_correlation_trend_indicator, + CorrelationTrendIndicator::new(20).unwrap() +); +scalar_inv!(inv_cybernetic_cycle, CyberneticCycle::new(10).unwrap()); +scalar_inv!(inv_decycler, Decycler::new(20).unwrap()); +scalar_inv!( + inv_decycler_oscillator, + DecyclerOscillator::new(10, 30).unwrap() +); +scalar_inv!(inv_dema, Dema::new(3).unwrap()); +scalar_inv!( + inv_derivative_oscillator, + DerivativeOscillator::new(14, 5, 3, 9).unwrap() +); +scalar_inv!(inv_detrended_std_dev, DetrendedStdDev::new(14).unwrap()); +scalar_inv!(inv_disparity_index, DisparityIndex::new(14).unwrap()); +scalar_inv!( + inv_double_bollinger, + DoubleBollinger::new(20, 1.0, 2.0).unwrap() +); +scalar_inv!(inv_dpo, Dpo::new(20).unwrap()); +scalar_inv!(inv_drawdown_duration, DrawdownDuration::new()); +scalar_inv!( + inv_dynamic_momentum_index, + DynamicMomentumIndex::new(14).unwrap() +); +scalar_inv!(inv_ehlers_stochastic, EhlersStochastic::new(20).unwrap()); +scalar_inv!(inv_ehma, Ehma::new(9).unwrap()); +scalar_inv!(inv_elder_impulse, ElderImpulse::new(3, 2, 4, 3).unwrap()); +scalar_inv!(inv_ema, Ema::new(3).unwrap()); +scalar_inv!( + inv_empirical_mode_decomposition, + EmpiricalModeDecomposition::new(20, 0.5).unwrap() +); +scalar_inv!( + inv_even_better_sinewave, + EvenBetterSinewave::new(40, 10).unwrap() +); +scalar_inv!(inv_ewma_volatility, EwmaVolatility::new(0.94).unwrap()); +scalar_inv!(inv_expectancy, Expectancy::new(4).unwrap()); +scalar_inv!(inv_fama, Fama::new(0.5, 0.05).unwrap()); +scalar_inv!(inv_fisher_rsi, FisherRsi::new(9).unwrap()); +scalar_inv!(inv_fisher_transform, FisherTransform::new(10).unwrap()); +scalar_inv!(inv_frama, Frama::new(16).unwrap()); +scalar_inv!(inv_gain_loss_ratio, GainLossRatio::new(10).unwrap()); +scalar_inv!(inv_gain_to_pain_ratio, GainToPainRatio::new(12).unwrap()); +scalar_inv!(inv_garch11, Garch11::new(0.000_002, 0.10, 0.88).unwrap()); +scalar_inv!(inv_generalized_dema, GeneralizedDema::new(5, 0.7).unwrap()); +scalar_inv!(inv_geometric_ma, GeometricMa::new(5).unwrap()); +scalar_inv!(inv_highpass_filter, HighpassFilter::new(48).unwrap()); +scalar_inv!(inv_hilbert_dominant_cycle, HilbertDominantCycle::new()); +scalar_inv!( + inv_historical_volatility, + HistoricalVolatility::new(20, 252).unwrap() +); +scalar_inv!(inv_hma, Hma::new(9).unwrap()); +scalar_inv!(inv_holt_winters, HoltWinters::new(0.2, 0.1).unwrap()); +scalar_inv!(inv_ht_dcphase, HtDcPhase::new()); +scalar_inv!(inv_ht_phasor, HtPhasor::new()); +scalar_inv!(inv_ht_trendmode, HtTrendMode::new()); +scalar_inv!(inv_hurst_exponent, HurstExponent::new(100, 4).unwrap()); +scalar_inv!( + inv_instantaneous_trendline, + InstantaneousTrendline::new(20).unwrap() +); +scalar_inv!( + inv_inverse_fisher_transform, + InverseFisherTransform::new(1.0).unwrap() +); +scalar_inv!(inv_jarque_bera, JarqueBera::new(50).unwrap()); +scalar_inv!(inv_jma, Jma::new(14, 0.0, 2).unwrap()); +scalar_inv!(inv_jump_indicator, JumpIndicator::new(20, 3.0).unwrap()); +scalar_inv!(inv_k_ratio, KRatio::new(30).unwrap()); +scalar_inv!(inv_kama, Kama::new(10, 2, 30).unwrap()); +scalar_inv!(inv_kelly_criterion, KellyCriterion::new(10).unwrap()); +scalar_inv!(inv_kst, Kst::new(2, 3, 4, 5, 2, 2, 2, 3, 2).unwrap()); +scalar_inv!(inv_kurtosis, Kurtosis::new(20).unwrap()); +scalar_inv!(inv_laguerre_rsi, LaguerreRsi::new(0.5).unwrap()); +scalar_inv!(inv_linreg, LinearRegression::new(14).unwrap()); +scalar_inv!(inv_linreg_angle, LinRegAngle::new(14).unwrap()); +scalar_inv!(inv_linreg_channel, LinRegChannel::new(20, 2.0).unwrap()); +scalar_inv!(inv_linreg_intercept, LinRegIntercept::new(14).unwrap()); +scalar_inv!(inv_linreg_slope, LinRegSlope::new(14).unwrap()); +scalar_inv!(inv_log_return, LogReturn::new(1).unwrap()); +scalar_inv!(inv_m2_measure, M2Measure::new(20, 0.0, 0.02).unwrap()); +scalar_inv!(inv_ma_envelope, MaEnvelope::new(20, 0.025).unwrap()); +scalar_inv!(inv_macd, MacdIndicator::new(3, 6, 3).unwrap()); +scalar_inv!( + inv_macd_ext, + MacdExt::new(12, MaType::Ema, 26, MaType::Ema, 9, MaType::Sma).unwrap() +); +scalar_inv!(inv_macd_fix, MacdFix::new(9).unwrap()); +scalar_inv!(inv_macd_histogram, MacdHistogram::new(12, 26, 9).unwrap()); +scalar_inv!(inv_mama, Mama::new(0.5, 0.05).unwrap()); +scalar_inv!(inv_martin_ratio, MartinRatio::new(14).unwrap()); +scalar_inv!(inv_max_drawdown, MaxDrawdown::new(10).unwrap()); +scalar_inv!(inv_mcginley_dynamic, McGinleyDynamic::new(10).unwrap()); +scalar_inv!( + inv_median_absolute_deviation, + MedianAbsoluteDeviation::new(20).unwrap() +); +scalar_inv!(inv_median_channel, MedianChannel::new(20, 2.0).unwrap()); +scalar_inv!(inv_median_ma, MedianMa::new(5).unwrap()); +scalar_inv!(inv_mid_point, MidPoint::new(5).unwrap()); +scalar_inv!(inv_mom, Mom::new(3).unwrap()); +scalar_inv!(inv_omega_ratio, OmegaRatio::new(20, 0.0).unwrap()); +scalar_inv!(inv_pain_index, PainIndex::new(10).unwrap()); +scalar_inv!(inv_percent_b, PercentB::new(20, 2.0).unwrap()); +scalar_inv!( + inv_percentage_trailing_stop, + PercentageTrailingStop::new(5.0).unwrap() +); +scalar_inv!(inv_pmo, Pmo::new(35, 20).unwrap()); +scalar_inv!( + inv_polarized_fractal_efficiency, + PolarizedFractalEfficiency::new(10, 5).unwrap() +); +scalar_inv!(inv_ppo, Ppo::new(12, 26).unwrap()); +scalar_inv!(inv_ppo_histogram, PpoHistogram::new(12, 26, 9).unwrap()); +scalar_inv!(inv_profit_factor, ProfitFactor::new(20).unwrap()); +scalar_inv!(inv_qqe, Qqe::new(14, 5, 4.236).unwrap()); +scalar_inv!(inv_quartile_bands, QuartileBands::new(20).unwrap()); +scalar_inv!(inv_r_squared, RSquared::new(14).unwrap()); +scalar_inv!( + inv_realized_volatility, + RealizedVolatility::new(20).unwrap() +); +scalar_inv!(inv_recovery_factor, RecoveryFactor::new()); +scalar_inv!(inv_reflex, Reflex::new(20).unwrap()); +scalar_inv!(inv_regime_label, RegimeLabel::new(5, 20).unwrap()); +scalar_inv!( + inv_renko_trailing_stop, + RenkoTrailingStop::new(1.0).unwrap() +); +scalar_inv!(inv_rmi, Rmi::new(14, 5).unwrap()); +scalar_inv!(inv_roc, Roc::new(3).unwrap()); +scalar_inv!(inv_rocp, Rocp::new(3).unwrap()); +scalar_inv!(inv_rocr, Rocr::new(3).unwrap()); +scalar_inv!(inv_rocr100, Rocr100::new(3).unwrap()); +scalar_inv!(inv_rolling_iqr, RollingIqr::new(20).unwrap()); +scalar_inv!( + inv_rolling_min_max_scaler, + RollingMinMaxScaler::new(14).unwrap() +); +scalar_inv!( + inv_rolling_percentile_rank, + RollingPercentileRank::new(20).unwrap() +); +scalar_inv!(inv_rolling_quantile, RollingQuantile::new(5, 0.5).unwrap()); +scalar_inv!(inv_roofing_filter, RoofingFilter::new(10, 48).unwrap()); +scalar_inv!(inv_rsi, Rsi::new(3).unwrap()); +scalar_inv!(inv_rsx, Rsx::new(14).unwrap()); +scalar_inv!(inv_rvi_volatility, RviVolatility::new(10).unwrap()); +scalar_inv!(inv_sample_entropy, SampleEntropy::new(50, 2, 0.2).unwrap()); +scalar_inv!(inv_shannon_entropy, ShannonEntropy::new(32, 8).unwrap()); +scalar_inv!(inv_sharpe_ratio, SharpeRatio::new(20, 0.0).unwrap()); +scalar_inv!(inv_sine_wave, SineWave::new()); +scalar_inv!(inv_sine_weighted_ma, SineWeightedMa::new(5).unwrap()); +scalar_inv!(inv_skewness, Skewness::new(20).unwrap()); +scalar_inv!(inv_sma, Sma::new(3).unwrap()); +scalar_inv!(inv_smma, Smma::new(3).unwrap()); +scalar_inv!(inv_sortino_ratio, SortinoRatio::new(20, 0.0).unwrap()); +scalar_inv!(inv_standard_error, StandardError::new(14).unwrap()); +scalar_inv!( + inv_standard_error_bands, + StandardErrorBands::new(21, 2.0).unwrap() +); +scalar_inv!(inv_stc, Stc::new(3, 5, 4, 0.5).unwrap()); +scalar_inv!(inv_std_dev, StdDev::new(20).unwrap()); +scalar_inv!(inv_step_trailing_stop, StepTrailingStop::new(1.0).unwrap()); +scalar_inv!(inv_sterling_ratio, SterlingRatio::new(12).unwrap()); +scalar_inv!(inv_stoch_rsi, StochRsi::new(14, 14).unwrap()); +scalar_inv!(inv_super_smoother, SuperSmoother::new(10).unwrap()); +scalar_inv!(inv_t3, T3::new(5, 0.7).unwrap()); +scalar_inv!(inv_tail_ratio, TailRatio::new(20).unwrap()); +scalar_inv!(inv_tema, Tema::new(3).unwrap()); +scalar_inv!(inv_tii, Tii::new(20, 10).unwrap()); +scalar_inv!(inv_trend_label, TrendLabel::new(10).unwrap()); +scalar_inv!( + inv_trend_strength_index, + TrendStrengthIndex::new(20).unwrap() +); +scalar_inv!(inv_trendflex, Trendflex::new(20).unwrap()); +scalar_inv!(inv_trima, Trima::new(5).unwrap()); +scalar_inv!(inv_trix, Trix::new(3).unwrap()); +scalar_inv!(inv_tsf, Tsf::new(14).unwrap()); +scalar_inv!(inv_tsf_oscillator, TsfOscillator::new(14).unwrap()); +scalar_inv!(inv_tsi, Tsi::new(25, 13).unwrap()); +scalar_inv!(inv_ulcer_index, UlcerIndex::new(14).unwrap()); +scalar_inv!( + inv_universal_oscillator, + UniversalOscillator::new(20).unwrap() +); +scalar_inv!( + inv_upside_potential_ratio, + UpsidePotentialRatio::new(20, 0.0).unwrap() +); +scalar_inv!(inv_value_at_risk, ValueAtRisk::new(100, 0.95).unwrap()); +scalar_inv!(inv_variance, Variance::new(20).unwrap()); +scalar_inv!( + inv_vertical_horizontal_filter, + VerticalHorizontalFilter::new(28).unwrap() +); +scalar_inv!(inv_vidya, Vidya::new(14, 9).unwrap()); +scalar_inv!( + inv_volatility_of_volatility, + VolatilityOfVolatility::new(20, 20).unwrap() +); +scalar_inv!(inv_wave_pm, WavePm::new(10, 3).unwrap()); +scalar_inv!(inv_win_rate, WinRate::new(4).unwrap()); +scalar_inv!(inv_wma, Wma::new(3).unwrap()); +scalar_inv!(inv_z_score, ZScore::new(20).unwrap()); +scalar_inv!(inv_zero_lag_macd, ZeroLagMacd::new(3, 5, 3).unwrap()); +scalar_inv!(inv_zlema, Zlema::new(10).unwrap()); + +// --- candle (261) --- +candle_inv!(inv_abandoned_baby, AbandonedBaby::new()); +candle_inv!(inv_abcd, Abcd::new()); +candle_inv!( + inv_acceleration_bands, + AccelerationBands::new(20, 0.001).unwrap() +); +candle_inv!( + inv_accelerator_oscillator, + AcceleratorOscillator::new(5, 34, 5).unwrap() +); +candle_inv!(inv_ad_oscillator, AdOscillator::new()); +candle_inv!(inv_adaptive_cci, AdaptiveCci::new(20).unwrap()); +candle_inv!(inv_adl, Adl::new()); +candle_inv!(inv_advance_block, AdvanceBlock::new()); +candle_inv!(inv_adx, Adx::new(5).unwrap()); +candle_inv!(inv_adxr, Adxr::new(5).unwrap()); +candle_inv!(inv_alligator, Alligator::new(5, 3, 2).unwrap()); +candle_inv!(inv_anchored_vwap, AnchoredVwap::new()); +candle_inv!(inv_andrews_pitchfork, AndrewsPitchfork::new(2).unwrap()); +candle_inv!(inv_aroon, Aroon::new(5).unwrap()); +candle_inv!(inv_aroon_oscillator, AroonOscillator::new(5).unwrap()); +candle_inv!(inv_atr, Atr::new(5).unwrap()); +candle_inv!(inv_atr_bands, AtrBands::new(14, 3.0).unwrap()); +candle_inv!(inv_atr_ratchet, AtrRatchet::new(14, 4.0, 0.1).unwrap()); +candle_inv!( + inv_atr_trailing_stop, + AtrTrailingStop::new(14, 3.0).unwrap() +); +candle_inv!(inv_auto_fib, AutoFib::new()); +candle_inv!( + inv_average_daily_range, + AverageDailyRange::new(2, 0).unwrap() +); +candle_inv!(inv_avg_price, AvgPrice::new()); +candle_inv!( + inv_awesome_oscillator, + AwesomeOscillator::new(3, 10).unwrap() +); +candle_inv!( + inv_awesome_oscillator_histogram, + AwesomeOscillatorHistogram::new(3, 5, 3).unwrap() +); +candle_inv!(inv_balance_of_power, BalanceOfPower::new()); +candle_inv!(inv_bat, Bat::new()); +candle_inv!(inv_belt_hold, BeltHold::new()); +candle_inv!(inv_better_volume, BetterVolume::new(20).unwrap()); +candle_inv!(inv_body_size_pct, BodySizePct::new()); +candle_inv!(inv_breakaway, Breakaway::new()); +candle_inv!(inv_butterfly, Butterfly::new()); +candle_inv!(inv_camarilla_pivots, Camarilla::new()); +candle_inv!(inv_candle_volume, CandleVolume::new(14).unwrap()); +candle_inv!(inv_cci, Cci::new(5).unwrap()); +candle_inv!(inv_central_pivot_range, CentralPivotRange::new()); +candle_inv!( + inv_chaikin_oscillator, + ChaikinOscillator::new(3, 10).unwrap() +); +candle_inv!( + inv_chaikin_volatility, + ChaikinVolatility::new(10, 10).unwrap() +); +candle_inv!( + inv_chande_kroll_stop, + ChandeKrollStop::new(10, 1.0, 9).unwrap() +); +candle_inv!(inv_chandelier_exit, ChandelierExit::new(22, 3.0).unwrap()); +candle_inv!(inv_choppiness_index, ChoppinessIndex::new(14).unwrap()); +candle_inv!(inv_classic_pivots, ClassicPivots::new()); +candle_inv!(inv_close_vs_open, CloseVsOpen::new()); +candle_inv!(inv_closing_marubozu, ClosingMarubozu::new()); +candle_inv!(inv_cmf, ChaikinMoneyFlow::new(20).unwrap()); +candle_inv!( + inv_composite_profile, + CompositeProfile::new(100, 50, 0.70).unwrap() +); +candle_inv!(inv_concealing_baby_swallow, ConcealingBabySwallow::new()); +candle_inv!(inv_counterattack, Counterattack::new()); +candle_inv!(inv_crab, Crab::new()); +candle_inv!(inv_cup_and_handle, CupAndHandle::new()); +candle_inv!(inv_cypher, Cypher::new()); +candle_inv!(inv_day_of_week_profile, DayOfWeekProfile::new(0)); +candle_inv!(inv_demand_index, DemandIndex::new(10).unwrap()); +candle_inv!(inv_demark_pivots, DemarkPivots::new()); +candle_inv!(inv_doji, Doji::new()); +candle_inv!(inv_doji_star, DojiStar::new()); +candle_inv!(inv_donchian, Donchian::new(5).unwrap()); +candle_inv!(inv_donchian_stop, DonchianStop::new(10).unwrap()); +candle_inv!(inv_double_top_bottom, DoubleTopBottom::new()); +candle_inv!( + inv_downside_gap_three_methods, + DownsideGapThreeMethods::new() +); +candle_inv!(inv_dragonfly_doji, DragonflyDoji::new()); +candle_inv!(inv_dumpling_top, DumplingTop::new(9).unwrap()); +candle_inv!(inv_dx, Dx::new(5).unwrap()); +candle_inv!(inv_ease_of_movement, EaseOfMovement::new(14).unwrap()); +candle_inv!(inv_elder_ray, ElderRay::new(13).unwrap()); +candle_inv!(inv_elder_safezone, ElderSafeZone::new(14, 2.0).unwrap()); +candle_inv!(inv_engulfing, Engulfing::new()); +candle_inv!(inv_equivolume, Equivolume::new(14).unwrap()); +candle_inv!(inv_evening_doji_star, EveningDojiStar::new()); +candle_inv!(inv_evwma, Evwma::new(20).unwrap()); +candle_inv!(inv_falling_three_methods, FallingThreeMethods::new()); +candle_inv!(inv_fib_arcs, FibArcs::new()); +candle_inv!(inv_fib_channel, FibChannel::new()); +candle_inv!(inv_fib_confluence, FibConfluence::new()); +candle_inv!(inv_fib_extension, FibExtension::new()); +candle_inv!(inv_fib_fan, FibFan::new()); +candle_inv!(inv_fib_projection, FibProjection::new()); +candle_inv!(inv_fib_retracement, FibRetracement::new()); +candle_inv!(inv_fib_time_zones, FibTimeZones::new()); +candle_inv!(inv_fibonacci_pivots, FibonacciPivots::new()); +candle_inv!(inv_flag_pennant, FlagPennant::new()); +candle_inv!(inv_force_index, ForceIndex::new(13).unwrap()); +candle_inv!(inv_fractal_chaos_bands, FractalChaosBands::new(2).unwrap()); +candle_inv!(inv_fry_pan_bottom, FryPanBottom::new(9).unwrap()); +candle_inv!(inv_gap_side_by_side_white, GapSideBySideWhite::new()); +candle_inv!( + inv_garman_klass, + GarmanKlassVolatility::new(20, 252).unwrap() +); +candle_inv!(inv_gartley, Gartley::new()); +candle_inv!(inv_gator_oscillator, GatorOscillator::new(5, 3, 2).unwrap()); +candle_inv!(inv_golden_pocket, GoldenPocket::new()); +candle_inv!(inv_gravestone_doji, GravestoneDoji::new()); +candle_inv!(inv_hammer, Hammer::new()); +candle_inv!(inv_hanging_man, HangingMan::new()); +candle_inv!(inv_harami, Harami::new()); +candle_inv!(inv_harami_cross, HaramiCross::new()); +candle_inv!(inv_head_and_shoulders, HeadAndShoulders::new()); +candle_inv!(inv_heikin_ashi, HeikinAshi::new()); +candle_inv!( + inv_heikin_ashi_oscillator, + HeikinAshiOscillator::new(5).unwrap() +); +candle_inv!(inv_high_low_range, HighLowRange::new()); +candle_inv!( + inv_high_low_volume_nodes, + HighLowVolumeNodes::new(20, 24).unwrap() +); +candle_inv!(inv_high_wave, HighWave::new()); +candle_inv!(inv_hikkake, Hikkake::new()); +candle_inv!(inv_hikkake_modified, HikkakeModified::new()); +candle_inv!(inv_hilo_activator, HiLoActivator::new(3).unwrap()); +candle_inv!(inv_homing_pigeon, HomingPigeon::new()); +candle_inv!(inv_hurst_channel, HurstChannel::new(10, 0.5).unwrap()); +candle_inv!(inv_ichimoku, Ichimoku::new(5, 10, 20, 10).unwrap()); +candle_inv!(inv_identical_three_crows, IdenticalThreeCrows::new()); +candle_inv!(inv_in_neck, InNeck::new()); +candle_inv!(inv_inertia, Inertia::new(14, 20).unwrap()); +candle_inv!(inv_initial_balance, InitialBalance::new(3).unwrap()); +candle_inv!(inv_intraday_intensity, IntradayIntensity::new()); +candle_inv!( + inv_intraday_momentum_index, + IntradayMomentumIndex::new(14).unwrap() +); +candle_inv!( + inv_intraday_volatility_profile, + IntradayVolatilityProfile::new(24, 0).unwrap() +); +candle_inv!(inv_inverted_hammer, InvertedHammer::new()); +candle_inv!(inv_kase_devstop, KaseDevStop::new(30, 1.0).unwrap()); +candle_inv!( + inv_kase_permission_stochastic, + KasePermissionStochastic::new(9, 3).unwrap() +); +candle_inv!(inv_keltner, Keltner::new(5, 5, 2.0).unwrap()); +candle_inv!(inv_kicking, Kicking::new()); +candle_inv!(inv_kicking_by_length, KickingByLength::new()); +candle_inv!(inv_kvo, Kvo::new(34, 55).unwrap()); +candle_inv!(inv_ladder_bottom, LadderBottom::new()); +candle_inv!(inv_long_legged_doji, LongLeggedDoji::new()); +candle_inv!(inv_long_line, LongLine::new()); +candle_inv!( + inv_market_facilitation_index, + MarketFacilitationIndex::new() +); +candle_inv!(inv_marubozu, Marubozu::new()); +candle_inv!(inv_mass_index, MassIndex::new(9, 25).unwrap()); +candle_inv!(inv_mat_hold, MatHold::new()); +candle_inv!(inv_matching_low, MatchingLow::new()); +candle_inv!(inv_median_price, MedianPrice::new()); +candle_inv!(inv_mfi, Mfi::new(5).unwrap()); +candle_inv!(inv_mid_price, MidPrice::new(5).unwrap()); +candle_inv!(inv_minus_di, MinusDi::new(5).unwrap()); +candle_inv!(inv_minus_dm, MinusDm::new(5).unwrap()); +candle_inv!(inv_modified_ma_stop, ModifiedMaStop::new(14).unwrap()); +candle_inv!(inv_morning_doji_star, MorningDojiStar::new()); +candle_inv!(inv_morning_evening_star, MorningEveningStar::new()); +candle_inv!(inv_murrey_math_lines, MurreyMathLines::new(64).unwrap()); +candle_inv!(inv_naked_poc, NakedPoc::new(20, 24).unwrap()); +candle_inv!(inv_natr, Natr::new(14).unwrap()); +candle_inv!(inv_new_price_lines, NewPriceLines::new(8).unwrap()); +candle_inv!(inv_nrtr, Nrtr::new(2.0).unwrap()); +candle_inv!(inv_nvi, Nvi::new()); +candle_inv!(inv_obv, Obv::new()); +candle_inv!(inv_on_neck, OnNeck::new()); +candle_inv!(inv_opening_marubozu, OpeningMarubozu::new()); +candle_inv!(inv_opening_range, OpeningRange::new(2).unwrap()); +candle_inv!(inv_overnight_gap, OvernightGap::new(0)); +candle_inv!( + inv_overnight_intraday_return, + OvernightIntradayReturn::new(0) +); +candle_inv!(inv_parkinson, ParkinsonVolatility::new(20, 252).unwrap()); +candle_inv!(inv_pgo, Pgo::new(14).unwrap()); +candle_inv!(inv_piercing_dark_cloud, PiercingDarkCloud::new()); +candle_inv!(inv_pivot_reversal, PivotReversal::new(2, 2).unwrap()); +candle_inv!(inv_plus_di, PlusDi::new(5).unwrap()); +candle_inv!(inv_plus_dm, PlusDm::new(5).unwrap()); +candle_inv!(inv_profile_shape, ProfileShape::new(20, 24).unwrap()); +candle_inv!(inv_projection_bands, ProjectionBands::new(14).unwrap()); +candle_inv!( + inv_projection_oscillator, + ProjectionOscillator::new(14).unwrap() +); +candle_inv!(inv_psar, Psar::new(0.02, 0.02, 0.2).unwrap()); +candle_inv!(inv_pvi, Pvi::new()); +candle_inv!(inv_qstick, Qstick::new(5).unwrap()); +candle_inv!(inv_rectangle_range, RectangleRange::new()); +candle_inv!(inv_rickshaw_man, RickshawMan::new()); +candle_inv!(inv_rising_three_methods, RisingThreeMethods::new()); +candle_inv!( + inv_rogers_satchell, + RogersSatchellVolatility::new(20, 252).unwrap() +); +candle_inv!(inv_rvi, Rvi::new(10).unwrap()); +candle_inv!(inv_rwi, Rwi::new(14).unwrap()); +candle_inv!( + inv_sar_ext, + SarExt::new(0.0, 0.0, 0.02, 0.02, 0.2, 0.02, 0.02, 0.2).unwrap() +); +candle_inv!(inv_seasonal_z_score, SeasonalZScore::new(0)); +candle_inv!(inv_separating_lines, SeparatingLines::new()); +candle_inv!(inv_session_high_low, SessionHighLow::new(0)); +candle_inv!(inv_session_range, SessionRange::new(0)); +candle_inv!(inv_session_vwap, SessionVwap::new(0)); +candle_inv!(inv_shark, Shark::new()); +candle_inv!(inv_shooting_star, ShootingStar::new()); +candle_inv!(inv_short_line, ShortLine::new()); +candle_inv!(inv_single_prints, SinglePrints::new(20, 24).unwrap()); +candle_inv!(inv_smi, Smi::new(5, 3, 3).unwrap()); +candle_inv!( + inv_smoothed_heikin_ashi, + SmoothedHeikinAshi::new(10).unwrap() +); +candle_inv!(inv_spinning_top, SpinningTop::new()); +candle_inv!(inv_stalled_pattern, StalledPattern::new()); +candle_inv!(inv_starc_bands, StarcBands::new(6, 15, 2.0).unwrap()); +candle_inv!(inv_stick_sandwich, StickSandwich::new()); +candle_inv!(inv_stochastic, Stochastic::new(5, 3).unwrap()); +candle_inv!(inv_stochastic_cci, StochasticCci::new(14).unwrap()); +candle_inv!(inv_super_trend, SuperTrend::new(10, 3.0).unwrap()); +candle_inv!(inv_takuri, Takuri::new()); +candle_inv!(inv_tasuki_gap, TasukiGap::new()); +candle_inv!(inv_td_camouflage, TdCamouflage::new()); +candle_inv!(inv_td_clop, TdClop::new()); +candle_inv!(inv_td_clopwin, TdClopwin::new()); +candle_inv!(inv_td_combo, TdCombo::new(4, 9, 2, 13).unwrap()); +candle_inv!(inv_td_countdown, TdCountdown::new(4, 9, 2, 13).unwrap()); +candle_inv!(inv_td_demarker, TdDeMarker::new(14).unwrap()); +candle_inv!(inv_td_differential, TdDifferential::new()); +candle_inv!(inv_td_dwave, TdDWave::new(2).unwrap()); +candle_inv!(inv_td_lines, TdLines::new(4, 9).unwrap()); +candle_inv!(inv_td_moving_average, TdMovingAverage::new(5, 13).unwrap()); +candle_inv!(inv_td_open, TdOpen::new()); +candle_inv!(inv_td_pressure, TdPressure::new(5).unwrap()); +candle_inv!(inv_td_propulsion, TdPropulsion::new()); +candle_inv!(inv_td_range_projection, TdRangeProjection::new()); +candle_inv!(inv_td_rei, TdRei::new(5).unwrap()); +candle_inv!(inv_td_risk_level, TdRiskLevel::new(4, 9).unwrap()); +candle_inv!(inv_td_sequential, TdSequential::new(4, 9, 2, 13).unwrap()); +candle_inv!(inv_td_setup, TdSetup::new(4, 9).unwrap()); +candle_inv!(inv_td_trap, TdTrap::new()); +candle_inv!(inv_three_drives, ThreeDrives::new()); +candle_inv!(inv_three_inside, ThreeInside::new()); +candle_inv!(inv_three_line_break, ThreeLineBreak::new(3).unwrap()); +candle_inv!(inv_three_line_strike, ThreeLineStrike::new()); +candle_inv!(inv_three_outside, ThreeOutside::new()); +candle_inv!(inv_three_soldiers_or_crows, ThreeSoldiersOrCrows::new()); +candle_inv!(inv_three_stars_in_south, ThreeStarsInSouth::new()); +candle_inv!(inv_thrusting, Thrusting::new()); +candle_inv!(inv_time_based_stop, TimeBasedStop::new(5).unwrap()); +candle_inv!( + inv_time_of_day_return_profile, + TimeOfDayReturnProfile::new(24, 0).unwrap() +); +candle_inv!(inv_tower_top_bottom, TowerTopBottom::new()); +candle_inv!(inv_tpo_profile, TpoProfile::new(5, 10).unwrap()); +candle_inv!(inv_trade_volume_index, TradeVolumeIndex::new(0.5).unwrap()); +candle_inv!(inv_triangle, Triangle::new()); +candle_inv!(inv_triple_top_bottom, TripleTopBottom::new()); +candle_inv!(inv_tristar, Tristar::new()); +candle_inv!(inv_true_range, TrueRange::new()); +candle_inv!(inv_tsv, Tsv::new(18).unwrap()); +candle_inv!(inv_ttm_squeeze, TtmSqueeze::new(20, 2.0, 1.5).unwrap()); +candle_inv!(inv_ttm_trend, TtmTrend::new(6).unwrap()); +candle_inv!(inv_turn_of_month, TurnOfMonth::new(3, 1, 0).unwrap()); +candle_inv!(inv_tweezer, Tweezer::new()); +candle_inv!(inv_twiggs_money_flow, TwiggsMoneyFlow::new(21).unwrap()); +candle_inv!(inv_two_crows, TwoCrows::new()); +candle_inv!(inv_typical_price, TypicalPrice::new()); +candle_inv!( + inv_ultimate_oscillator, + UltimateOscillator::new(7, 14, 28).unwrap() +); +candle_inv!(inv_unique_three_river, UniqueThreeRiver::new()); +candle_inv!(inv_upside_gap_three_methods, UpsideGapThreeMethods::new()); +candle_inv!(inv_upside_gap_two_crows, UpsideGapTwoCrows::new()); +candle_inv!(inv_value_area, ValueArea::new(5, 50, 0.70).unwrap()); +candle_inv!(inv_volatility_cone, VolatilityCone::new(20, 60).unwrap()); +candle_inv!(inv_volatility_ratio, VolatilityRatio::new(14).unwrap()); +candle_inv!(inv_volty_stop, VoltyStop::new(14, 2.0).unwrap()); +candle_inv!( + inv_volume_by_time_profile, + VolumeByTimeProfile::new(24, 0).unwrap() +); +candle_inv!( + inv_volume_oscillator, + VolumeOscillator::new(14, 28).unwrap() +); +candle_inv!(inv_volume_profile, VolumeProfile::new(5, 10).unwrap()); +candle_inv!(inv_volume_rsi, VolumeRsi::new(14).unwrap()); +candle_inv!( + inv_volume_weighted_macd, + VolumeWeightedMacd::new(12, 26, 9).unwrap() +); +candle_inv!(inv_volume_weighted_sr, VolumeWeightedSr::new(20).unwrap()); +candle_inv!(inv_vortex, Vortex::new(14).unwrap()); +candle_inv!(inv_vpt, VolumePriceTrend::new()); +candle_inv!(inv_vwap, Vwap::new()); +candle_inv!(inv_vwap_stddev_bands, VwapStdDevBands::new(2.0).unwrap()); +candle_inv!(inv_vwma, Vwma::new(5).unwrap()); +candle_inv!(inv_vzo, Vzo::new(14).unwrap()); +candle_inv!(inv_wad, Wad::new()); +candle_inv!(inv_wave_trend, WaveTrend::new(5, 8, 3).unwrap()); +candle_inv!(inv_wedge, Wedge::new()); +candle_inv!(inv_weighted_close, WeightedClose::new()); +candle_inv!(inv_wick_ratio, WickRatio::new()); +candle_inv!(inv_williams_fractals, WilliamsFractals::new()); +candle_inv!(inv_williams_r, WilliamsR::new(5).unwrap()); +candle_inv!(inv_woodie_pivots, WoodiePivots::new()); +candle_inv!(inv_yang_zhang, YangZhangVolatility::new(20, 252).unwrap()); +candle_inv!(inv_yoyo_exit, YoyoExit::new(14, 2.0).unwrap()); +candle_inv!(inv_zig_zag, ZigZag::new(0.10).unwrap()); + +// --- pairwise (24) --- +pair_inv!(inv_alpha, Alpha::new(20, 0.001).unwrap()); +pair_inv!(inv_beta, Beta::new(20).unwrap()); +pair_inv!(inv_beta_neutral_spread, BetaNeutralSpread::new(20).unwrap()); +pair_inv!(inv_cointegration, Cointegration::new(30, 1).unwrap()); +pair_inv!(inv_distance_ssd, DistanceSsd::new(20).unwrap()); +pair_inv!(inv_granger_causality, GrangerCausality::new(60, 1).unwrap()); +pair_inv!( + inv_hasbrouck_information_share, + HasbrouckInformationShare::new(20).unwrap() +); +pair_inv!(inv_information_ratio, InformationRatio::new(10).unwrap()); +pair_inv!( + inv_kalman_hedge_ratio, + KalmanHedgeRatio::new(1e-2, 1e-3).unwrap() +); +pair_inv!(inv_kendall_tau, KendallTau::new(20).unwrap()); +pair_inv!( + inv_lead_lag_cross_correlation, + LeadLagCrossCorrelation::new(12, 5).unwrap() +); +pair_inv!(inv_ou_half_life, OuHalfLife::new(40).unwrap()); +pair_inv!(inv_pair_spread_zscore, PairSpreadZScore::new(2, 2).unwrap()); +pair_inv!(inv_pairwise_beta, PairwiseBeta::new(10).unwrap()); +pair_inv!( + inv_pearson_correlation, + PearsonCorrelation::new(20).unwrap() +); +pair_inv!( + inv_relative_strength_ab, + RelativeStrengthAB::new(5, 5).unwrap() +); +pair_inv!( + inv_rolling_correlation, + RollingCorrelation::new(10).unwrap() +); +pair_inv!(inv_rolling_covariance, RollingCovariance::new(5).unwrap()); +pair_inv!( + inv_spearman_correlation, + SpearmanCorrelation::new(10).unwrap() +); +pair_inv!( + inv_spread_ar1_coefficient, + SpreadAr1Coefficient::new(40).unwrap() +); +pair_inv!( + inv_spread_bollinger_bands, + SpreadBollingerBands::new(20, 2.0).unwrap() +); +pair_inv!(inv_spread_hurst, SpreadHurst::new(60).unwrap()); +pair_inv!(inv_treynor_ratio, TreynorRatio::new(20, 0.001).unwrap()); +pair_inv!(inv_variance_ratio, VarianceRatio::new(60, 2).unwrap()); + +// --- cross_section_inv (15) --- +cross_section_inv!(inv_absolute_breadth_index, AbsoluteBreadthIndex::new()); +cross_section_inv!(inv_ad_volume_line, AdVolumeLine::new()); +cross_section_inv!(inv_advance_decline, AdvanceDecline::new()); +cross_section_inv!(inv_advance_decline_ratio, AdvanceDeclineRatio::new()); +cross_section_inv!(inv_breadth_thrust, BreadthThrust::new(2).unwrap()); +cross_section_inv!(inv_bullish_percent_index, BullishPercentIndex::new()); +cross_section_inv!(inv_cumulative_volume_index, CumulativeVolumeIndex::new()); +cross_section_inv!(inv_high_low_index, HighLowIndex::new(2).unwrap()); +cross_section_inv!(inv_mcclellan_oscillator, McClellanOscillator::new()); +cross_section_inv!( + inv_mcclellan_summation_index, + McClellanSummationIndex::new() +); +cross_section_inv!(inv_new_highs_new_lows, NewHighsNewLows::new()); +cross_section_inv!(inv_percent_above_ma, PercentAboveMa::new()); +cross_section_inv!(inv_tick_index, TickIndex::new()); +cross_section_inv!(inv_trin, Trin::new()); +cross_section_inv!(inv_up_down_volume_ratio, UpDownVolumeRatio::new()); + +// --- trade_inv (9) --- +trade_inv!(inv_amihud_illiquidity, AmihudIlliquidity::new(20).unwrap()); +trade_inv!(inv_cvd, CumulativeVolumeDelta::new()); +trade_inv!(inv_footprint, Footprint::new(1.0).unwrap()); +trade_inv!(inv_pin, Pin::new(20).unwrap()); +trade_inv!(inv_roll_measure, RollMeasure::new(20).unwrap()); +trade_inv!(inv_signed_volume, SignedVolume::new()); +trade_inv!(inv_trade_imbalance, TradeImbalance::new(2).unwrap()); +trade_inv!( + inv_trade_sign_autocorrelation, + TradeSignAutocorrelation::new(20).unwrap() +); +trade_inv!(inv_vpin, Vpin::new(10.0, 2).unwrap()); + +// --- deriv_inv (17) --- +deriv_inv!(inv_calendar_spread, CalendarSpread::new()); +deriv_inv!(inv_estimated_leverage_ratio, EstimatedLeverageRatio::new()); +deriv_inv!(inv_funding_basis, FundingBasis::new()); +deriv_inv!( + inv_funding_implied_apr, + FundingImpliedApr::new(1095.0).unwrap() +); +deriv_inv!(inv_funding_rate, FundingRate::new()); +deriv_inv!(inv_funding_rate_mean, FundingRateMean::new(2).unwrap()); +deriv_inv!(inv_funding_rate_zscore, FundingRateZScore::new(2).unwrap()); +deriv_inv!(inv_liquidation_features, LiquidationFeatures::new()); +deriv_inv!(inv_long_short_ratio, LongShortRatio::new()); +deriv_inv!(inv_oi_delta, OpenInterestDelta::new()); +deriv_inv!(inv_oi_price_divergence, OIPriceDivergence::new(1).unwrap()); +deriv_inv!(inv_oi_to_volume_ratio, OiToVolumeRatio::new()); +deriv_inv!(inv_oi_weighted, OIWeighted::new()); +deriv_inv!( + inv_open_interest_momentum, + OpenInterestMomentum::new(5).unwrap() +); +deriv_inv!(inv_perpetual_premium_index, PerpetualPremiumIndex::new()); +deriv_inv!(inv_taker_buy_sell_ratio, TakerBuySellRatio::new()); +deriv_inv!(inv_term_structure_basis, TermStructureBasis::new()); + +// --- orderbook_inv (7) --- +orderbook_inv!(inv_depth_slope, DepthSlope::new()); +orderbook_inv!(inv_microprice, Microprice::new()); +orderbook_inv!(inv_ob_imbalance_full, OrderBookImbalanceFull::new()); +orderbook_inv!(inv_ob_imbalance_top1, OrderBookImbalanceTop1::new()); +orderbook_inv!( + inv_ob_imbalance_topn, + OrderBookImbalanceTopN::new(2).unwrap() +); +orderbook_inv!( + inv_order_flow_imbalance, + OrderFlowImbalance::new(20).unwrap() +); +orderbook_inv!(inv_quoted_spread, QuotedSpread::new()); + +// --- tradequote_inv (3) --- +tradequote_inv!(inv_effective_spread, EffectiveSpread::new()); +tradequote_inv!(inv_kyles_lambda, KylesLambda::new(8).unwrap()); +tradequote_inv!(inv_realized_spread, RealizedSpread::new(1).unwrap()); + +// --- bar_inv (10) --- +bar_inv!(inv_dollar_bars, DollarBars::new(1000.0).unwrap()); +bar_inv!(inv_imbalance_bars, ImbalanceBars::new(3.0).unwrap()); +bar_inv!(inv_kagi_bars, KagiBars::new(2.0).unwrap()); +bar_inv!( + inv_point_and_figure_bars, + PointAndFigureBars::new(1.0, 3).unwrap() +); +bar_inv!(inv_range_bars, RangeBars::new(1.0).unwrap()); +bar_inv!(inv_renko_bars, RenkoBars::new(1.0).unwrap()); +bar_inv!(inv_run_bars, RunBars::new(3).unwrap()); +bar_inv!( + inv_three_line_break_bars, + ThreeLineBreakBars::new(3).unwrap() +); +bar_inv!(inv_tick_bars, TickBars::new(3).unwrap()); +bar_inv!(inv_volume_bars, VolumeBars::new(100.0).unwrap());