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());