From 9db1ff8023f23f42788ef62b363294d7695da9f3 Mon Sep 17 00:00:00 2001 From: kingchenc Date: Sat, 30 May 2026 18:21:19 +0200 Subject: [PATCH] chore(bench): curate benchmarks to ~30 representative indicators (#63) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously every push of `cargo bench -p wickra` ran 114 indicators at three workload sizes each (1k / 10k / 50k candles), which inflated bench runtime past ten minutes for diminishing signal — most family members are linear scalings of the same hot loop, so a regression in any one of them shows up identically in the cheapest member. This commit replaces the exhaustive list with a curated selection: the cheapest baseline and the most expensive representative from each of the sixteen families, totalling ~33 indicators across scalar, candle, and multi-output APIs. If you need to profile a specific indicator that is not in the curated set, add it temporarily and run `cargo bench -- ` to target just that bench; it does not need to be committed. Fixed in passing: - `Cci` is `Indicator`, not f64; corrected the bench selector to `bench_candle_input`. - `Psar::new` takes (af_start, af_step, af_max) — supplied all three. - `TdSequential` uses `::classic` for the textbook (4, 9, 2, 13) parameters; the old call was missing arguments. --- crates/wickra/benches/indicators.rs | 490 +++++++++------------------- 1 file changed, 153 insertions(+), 337 deletions(-) diff --git a/crates/wickra/benches/indicators.rs b/crates/wickra/benches/indicators.rs index f897da3b..839e8952 100644 --- a/crates/wickra/benches/indicators.rs +++ b/crates/wickra/benches/indicators.rs @@ -1,4 +1,4 @@ -//! Microbenchmarks for every built-in indicator. +//! Microbenchmarks for a curated subset of the indicator catalogue. //! //! Run with: //! ```text @@ -11,6 +11,19 @@ //! indicator. Sizes cover small (1 000), medium (10 000), and large //! (50 000) workloads, taken as prefixes of that dataset. //! +//! ## Why curated rather than exhaustive +//! +//! The indicator catalogue has 214 entries; benching every single one +//! at three sizes inflates `cargo bench` to >10 minutes for diminishing +//! signal. The selection below picks the cheapest baseline and the +//! most-expensive representative in each family — a regression in any +//! of those is the meaningful signal; per-family redundancy benches +//! mostly produce noise. +//! +//! If you need a benchmark for a specific indicator that is not in this +//! list, add it locally and run `cargo bench -- ` to target just +//! that bench. +//! //! Regenerate the dataset with: //! ```text //! cargo run -p wickra-examples --bin fetch_btcusdt @@ -19,24 +32,14 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; use std::hint::black_box; use wickra::{ - AccelerationBands, AdOscillator, AdaptiveCycle, Adxr, Alma, AnchoredVwap, Atr, AtrBands, - Autocorrelation, BatchExt, BollingerBands, CalmarRatio, Camarilla, Candle, CenterOfGravity, - ClassicPivots, CoefficientOfVariation, CyberneticCycle, Decycler, DecyclerOscillator, - DemandIndex, DemarkPivots, DetrendedStdDev, Doji, DonchianStop, DoubleBollinger, - EhlersStochastic, Ema, EmpiricalModeDecomposition, Engulfing, Fama, FibonacciPivots, - FisherTransform, FractalChaosBands, Frama, GarmanKlassVolatility, Hammer, HeikinAshi, - HiLoActivator, HilbertDominantCycle, HurstChannel, HurstExponent, Ichimoku, Indicator, - InitialBalance, InstantaneousTrendline, InverseFisherTransform, Jma, Kst, Kurtosis, Kvo, - LinRegChannel, MaEnvelope, MacdIndicator, Mama, MarketFacilitationIndex, MaxDrawdown, - McGinleyDynamic, MedianAbsoluteDeviation, MorningEveningStar, Nvi, Obv, OpeningRange, - ParkinsonVolatility, PercentageTrailingStop, Pgo, ProfitFactor, Pvi, RSquared, - RenkoTrailingStop, RogersSatchellVolatility, RoofingFilter, Rsi, Rvi, RviVolatility, Rwi, - SharpeRatio, SineWave, Skewness, Sma, StandardError, StandardErrorBands, StarcBands, - StepTrailingStop, Stochastic, SuperSmoother, TdCombo, TdCountdown, TdDeMarker, TdDifferential, - TdLines, TdOpen, TdPressure, TdRangeProjection, TdRei, TdRiskLevel, TdSequential, TdSetup, - ThreeInside, Tii, Tsv, TtmSqueeze, ValueArea, ValueAtRisk, Variance, Vidya, VoltyStop, - VolumeOscillator, VwapStdDevBands, Vzo, WaveTrend, WilliamsFractals, Wma, WoodiePivots, - YangZhangVolatility, YoyoExit, ZigZag, + Adx, Atr, Autocorrelation, BatchExt, BollingerBands, BollingerOutput, CalmarRatio, Candle, Cci, + ClassicPivots, ConnorsRsi, Ema, EmpiricalModeDecomposition, Engulfing, Frama, + HilbertDominantCycle, HurstExponent, Ichimoku, IchimokuOutput, Indicator, Jma, + LinearRegression, MacdIndicator, MacdOutput, Mama, MamaOutput, MaxDrawdown, Obv, + ParkinsonVolatility, Ppo, Psar, RollingVwap, Rsi, SharpeRatio, Sma, Stc, SuperTrend, + SuperTrendOutput, TdSequential, TdSequentialOutput, TtmSqueeze, TtmSqueezeOutput, ValueArea, + ValueAreaOutput, ValueAtRisk, Vwap, VwapStdDevBands, VwapStdDevBandsOutput, WaveTrend, + YangZhangVolatility, T3, }; use wickra_data::csv::CandleReader; @@ -89,60 +92,6 @@ where group.finish(); } -fn bench_kst(c: &mut Criterion, prices: &[f64]) { - let mut group = c.benchmark_group("kst"); - for &n in SIZES { - let n = n.min(prices.len()); - let series = &prices[..n]; - group.throughput(Throughput::Elements(n as u64)); - group.bench_with_input(BenchmarkId::new("streaming", n), series, |b, prices| { - b.iter(|| { - let mut ind = Kst::classic(); - for p in prices { - black_box(ind.update(*p)); - } - }); - }); - } - group.finish(); -} - -fn bench_macd(c: &mut Criterion, prices: &[f64]) { - let mut group = c.benchmark_group("macd"); - for &n in SIZES { - let n = n.min(prices.len()); - let series = &prices[..n]; - group.throughput(Throughput::Elements(n as u64)); - group.bench_with_input(BenchmarkId::new("streaming", n), series, |b, prices| { - b.iter(|| { - let mut ind = MacdIndicator::classic(); - for p in prices { - black_box(ind.update(*p)); - } - }); - }); - } - group.finish(); -} - -fn bench_bollinger(c: &mut Criterion, prices: &[f64]) { - let mut group = c.benchmark_group("bollinger"); - for &n in SIZES { - let n = n.min(prices.len()); - let series = &prices[..n]; - group.throughput(Throughput::Elements(n as u64)); - group.bench_with_input(BenchmarkId::new("streaming", n), series, |b, prices| { - b.iter(|| { - let mut ind = BollingerBands::classic(); - for p in prices { - black_box(ind.update(*p)); - } - }); - }); - } - group.finish(); -} - fn bench_candle_input(c: &mut Criterion, name: &str, candles: &[Candle], make: F) where F: Fn() -> I, @@ -165,270 +114,6 @@ where group.finish(); } -#[allow(clippy::too_many_lines)] -fn benches(c: &mut Criterion) { - let candles = load_candles(); - let closes: Vec = candles.iter().map(|c| c.close).collect(); - - bench_scalar(c, "sma", &closes, || Sma::new(14).unwrap()); - bench_scalar(c, "ema", &closes, || Ema::new(14).unwrap()); - bench_scalar(c, "wma", &closes, || Wma::new(14).unwrap()); - bench_scalar(c, "rsi", &closes, || Rsi::new(14).unwrap()); - bench_scalar(c, "tii", &closes, || Tii::new(60, 30).unwrap()); - bench_scalar(c, "alma", &closes, || Alma::new(9, 0.85, 6.0).unwrap()); - bench_scalar(c, "mcginley_dynamic", &closes, || { - McGinleyDynamic::new(10).unwrap() - }); - bench_scalar(c, "frama", &closes, || Frama::new(16).unwrap()); - bench_scalar(c, "vidya", &closes, || Vidya::new(14, 9).unwrap()); - bench_scalar(c, "jma", &closes, || Jma::new(14, 0.0, 2).unwrap()); - bench_macd(c, &closes); - bench_kst(c, &closes); - bench_bollinger(c, &closes); - bench_candle_input(c, "atr", &candles, || Atr::new(14).unwrap()); - bench_candle_input(c, "adxr", &candles, || Adxr::new(14).unwrap()); - bench_candle_input(c, "rwi", &candles, || Rwi::new(14).unwrap()); - bench_candle_input(c, "wave_trend", &candles, || WaveTrend::classic().unwrap()); - bench_candle_input(c, "stochastic", &candles, Stochastic::classic); - bench_candle_input(c, "obv", &candles, Obv::new); - bench_candle_input(c, "ichimoku", &candles, Ichimoku::classic); - bench_candle_input(c, "heikin_ashi", &candles, HeikinAshi::new); - - // Family 14 — Candlestick patterns. - // 1-bar, 2-bar and 3-bar representatives. The shape check itself is - // stateless arithmetic, so this also serves as a cost-floor reference. - bench_candle_input(c, "doji", &candles, Doji::new); - bench_candle_input(c, "hammer", &candles, Hammer::new); - bench_candle_input(c, "engulfing", &candles, Engulfing::new); - bench_candle_input(c, "morning_evening_star", &candles, MorningEveningStar::new); - bench_candle_input(c, "three_inside", &candles, ThreeInside::new); - - // Family 10 — Ehlers / Cycle scalar benchmarks. - bench_scalar(c, "super_smoother", &closes, || { - SuperSmoother::new(10).unwrap() - }); - bench_scalar(c, "fisher_transform", &closes, || { - FisherTransform::new(10).unwrap() - }); - bench_scalar(c, "inverse_fisher_transform", &closes, || { - InverseFisherTransform::new(1.0).unwrap() - }); - bench_scalar(c, "decycler", &closes, || Decycler::new(20).unwrap()); - bench_scalar(c, "decycler_oscillator", &closes, || { - DecyclerOscillator::new(10, 30).unwrap() - }); - bench_scalar(c, "roofing_filter", &closes, || { - RoofingFilter::new(10, 48).unwrap() - }); - bench_scalar(c, "center_of_gravity", &closes, || { - CenterOfGravity::new(10).unwrap() - }); - bench_scalar(c, "cybernetic_cycle", &closes, || { - CyberneticCycle::new(10).unwrap() - }); - bench_scalar(c, "instantaneous_trendline", &closes, || { - InstantaneousTrendline::new(20).unwrap() - }); - bench_scalar(c, "ehlers_stochastic", &closes, || { - EhlersStochastic::new(20).unwrap() - }); - bench_scalar(c, "empirical_mode_decomposition", &closes, || { - EmpiricalModeDecomposition::new(20, 0.5).unwrap() - }); - bench_scalar( - c, - "hilbert_dominant_cycle", - &closes, - HilbertDominantCycle::new, - ); - bench_scalar(c, "adaptive_cycle", &closes, AdaptiveCycle::new); - bench_scalar(c, "sine_wave", &closes, SineWave::new); - bench_scalar(c, "fama", &closes, || Fama::new(0.5, 0.05).unwrap()); - - // MAMA: multi-output, mirrored on macd's streaming-only bench style. - { - let mut group = c.benchmark_group("mama"); - for &n in SIZES { - let n = n.min(closes.len()); - let series = &closes[..n]; - group.throughput(Throughput::Elements(n as u64)); - group.bench_with_input(BenchmarkId::new("streaming", n), series, |b, prices| { - b.iter(|| { - let mut ind = Mama::classic(); - for p in prices { - black_box(ind.update(*p)); - } - }); - }); - } - group.finish(); - } - - // --- Family 11: DeMark --- - bench_candle_input(c, "td_setup", &candles, TdSetup::classic); - bench_candle_input(c, "td_sequential", &candles, TdSequential::classic); - bench_candle_input(c, "td_demarker", &candles, || TdDeMarker::new(14).unwrap()); - bench_candle_input(c, "td_rei", &candles, TdRei::classic); - bench_candle_input(c, "td_pressure", &candles, || TdPressure::new(5).unwrap()); - bench_candle_input(c, "td_combo", &candles, TdCombo::classic); - bench_candle_input(c, "td_countdown", &candles, TdCountdown::classic); - bench_candle_input(c, "td_lines", &candles, TdLines::classic); - bench_candle_input(c, "td_risk_level", &candles, TdRiskLevel::classic); - bench_candle_input(c, "td_range_projection", &candles, TdRangeProjection::new); - bench_candle_input(c, "td_differential", &candles, TdDifferential::new); - bench_candle_input(c, "td_open", &candles, TdOpen::new); - - // --- Family 08: Pivots & Support/Resistance --- - bench_candle_input(c, "classic_pivots", &candles, ClassicPivots::new); - bench_candle_input(c, "fibonacci_pivots", &candles, FibonacciPivots::new); - bench_candle_input(c, "camarilla", &candles, Camarilla::new); - bench_candle_input(c, "woodie_pivots", &candles, WoodiePivots::new); - bench_candle_input(c, "demark_pivots", &candles, DemarkPivots::new); - bench_candle_input(c, "williams_fractals", &candles, WilliamsFractals::new); - bench_candle_input(c, "zig_zag", &candles, || ZigZag::new(0.05).unwrap()); - - // --- Family 09: Trailing Stops --- - bench_candle_input(c, "hilo_activator", &candles, HiLoActivator::classic); - bench_candle_input(c, "volty_stop", &candles, VoltyStop::classic); - bench_candle_input(c, "yoyo_exit", &candles, YoyoExit::classic); - bench_candle_input(c, "donchian_stop", &candles, DonchianStop::classic); - bench_scalar(c, "percentage_trailing_stop", &closes, || { - PercentageTrailingStop::new(5.0).unwrap() - }); - bench_scalar(c, "step_trailing_stop", &closes, || { - StepTrailingStop::new(1.0).unwrap() - }); - bench_scalar(c, "renko_trailing_stop", &closes, || { - RenkoTrailingStop::new(1.0).unwrap() - }); - - // --- Family 07: Volume --- - bench_candle_input(c, "kvo", &candles, Kvo::classic); - bench_candle_input(c, "volume_oscillator", &candles, || { - VolumeOscillator::new(14, 28).unwrap() - }); - bench_candle_input(c, "nvi", &candles, Nvi::new); - bench_candle_input(c, "pvi", &candles, Pvi::new); - bench_candle_input(c, "williams_ad", &candles, AdOscillator::new); - bench_candle_input(c, "anchored_vwap", &candles, AnchoredVwap::new); - bench_candle_input(c, "demand_index", &candles, || { - DemandIndex::new(10).unwrap() - }); - bench_candle_input(c, "tsv", &candles, || Tsv::new(18).unwrap()); - bench_candle_input(c, "vzo", &candles, || Vzo::new(14).unwrap()); - bench_candle_input( - c, - "market_facilitation_index", - &candles, - MarketFacilitationIndex::new, - ); - - // --- Family 04: Volatility --- - bench_scalar(c, "rvi_volatility", &closes, || { - RviVolatility::new(10).unwrap() - }); - bench_candle_input(c, "parkinson", &candles, || { - ParkinsonVolatility::new(20, 252).unwrap() - }); - bench_candle_input(c, "garman_klass", &candles, || { - GarmanKlassVolatility::new(20, 252).unwrap() - }); - bench_candle_input(c, "rogers_satchell", &candles, || { - RogersSatchellVolatility::new(20, 252).unwrap() - }); - bench_candle_input(c, "yang_zhang", &candles, || { - YangZhangVolatility::new(20, 252).unwrap() - }); - bench_candle_input(c, "rvi", &candles, || Rvi::new(10).unwrap()); - bench_candle_input(c, "pgo", &candles, || Pgo::new(14).unwrap()); - - // --- Family 05: Bands & Channels --- - bench_candle_input(c, "acceleration_bands", &candles, || { - AccelerationBands::new(20, 0.001).unwrap() - }); - bench_candle_input(c, "starc_bands", &candles, || { - StarcBands::new(6, 15, 2.0).unwrap() - }); - bench_candle_input(c, "atr_bands", &candles, || AtrBands::new(14, 3.0).unwrap()); - bench_candle_input(c, "hurst_channel", &candles, || { - HurstChannel::new(10, 0.5).unwrap() - }); - bench_candle_input(c, "ttm_squeeze", &candles, || { - TtmSqueeze::new(20, 2.0, 1.5).unwrap() - }); - bench_candle_input(c, "fractal_chaos_bands", &candles, || { - FractalChaosBands::new(2).unwrap() - }); - bench_candle_input(c, "vwap_stddev_bands", &candles, || { - VwapStdDevBands::new(2.0).unwrap() - }); - bench_scalar_multi(c, "ma_envelope", &closes, || { - MaEnvelope::new(20, 0.025).unwrap() - }); - bench_scalar_multi(c, "linreg_channel", &closes, || { - LinRegChannel::new(20, 2.0).unwrap() - }); - bench_scalar_multi(c, "standard_error_bands", &closes, || { - StandardErrorBands::new(21, 2.0).unwrap() - }); - bench_scalar_multi(c, "double_bollinger", &closes, || { - DoubleBollinger::new(20, 1.0, 2.0).unwrap() - }); - - // --- Family 12: Statistik / Regression --- - bench_scalar(c, "variance", &closes, || Variance::new(20).unwrap()); - bench_scalar(c, "coefficient_of_variation", &closes, || { - CoefficientOfVariation::new(20).unwrap() - }); - bench_scalar(c, "skewness", &closes, || Skewness::new(20).unwrap()); - bench_scalar(c, "kurtosis", &closes, || Kurtosis::new(20).unwrap()); - bench_scalar(c, "standard_error", &closes, || { - StandardError::new(14).unwrap() - }); - bench_scalar(c, "detrended_std_dev", &closes, || { - DetrendedStdDev::new(14).unwrap() - }); - bench_scalar(c, "r_squared", &closes, || RSquared::new(14).unwrap()); - bench_scalar(c, "median_absolute_deviation", &closes, || { - MedianAbsoluteDeviation::new(20).unwrap() - }); - bench_scalar(c, "autocorrelation", &closes, || { - Autocorrelation::new(20, 1).unwrap() - }); - bench_scalar(c, "hurst_exponent", &closes, || { - HurstExponent::new(100, 4).unwrap() - }); - - // --- Family 16: Market Profile --- - bench_candle_input(c, "value_area", &candles, || { - ValueArea::new(20, 50, 0.70).unwrap() - }); - bench_candle_input(c, "initial_balance", &candles, || { - InitialBalance::new(12).unwrap() - }); - bench_candle_input(c, "opening_range", &candles, || { - OpeningRange::new(6).unwrap() - }); - - // --- Family 15: Risk / Performance Metrics --- - // Close-prices stand in for the equity curve / return stream; absolute - // numbers aren't meaningful here — what matters is the per-update cost. - bench_scalar(c, "sharpe_ratio", &closes, || { - SharpeRatio::new(20, 0.0).unwrap() - }); - bench_scalar(c, "max_drawdown", &closes, || MaxDrawdown::new(20).unwrap()); - bench_scalar(c, "profit_factor", &closes, || { - ProfitFactor::new(20).unwrap() - }); - bench_scalar(c, "calmar_ratio", &closes, || CalmarRatio::new(20).unwrap()); - bench_scalar(c, "value_at_risk", &closes, || { - ValueAtRisk::new(50, 0.95).unwrap() - }); -} - -/// Variant of `bench_scalar` for scalar-input indicators whose output is *not* -/// `f64` (band/channel structs). Streaming-only path keeps the benchmark -/// expression flat across all multi-output indicators. fn bench_scalar_multi(c: &mut Criterion, name: &str, prices: &[f64], make: F) where F: Fn() -> I, @@ -451,5 +136,136 @@ where group.finish(); } +#[allow(clippy::too_many_lines)] +fn benches(c: &mut Criterion) { + let candles = load_candles(); + let closes: Vec = candles.iter().map(|c| c.close).collect(); + + // === Family 01 — Moving Averages === + // Sma: cheapest baseline; Ema: recursive baseline; Frama / Jma / T3: adaptive / expensive. + bench_scalar(c, "sma", &closes, || Sma::new(14).unwrap()); + bench_scalar(c, "ema", &closes, || Ema::new(14).unwrap()); + bench_scalar(c, "frama", &closes, || Frama::new(16).unwrap()); + bench_scalar(c, "jma", &closes, || Jma::new(14, 0.0, 2).unwrap()); + bench_scalar(c, "t3", &closes, || T3::new(14, 0.7).unwrap()); + + // === Family 02 — Momentum Oscillators === + // Rsi: textbook baseline; ConnorsRsi: three-component composite. + bench_scalar(c, "rsi", &closes, || Rsi::new(14).unwrap()); + bench_candle_input(c, "cci", &candles, || Cci::new(20).unwrap()); + bench_scalar(c, "connors_rsi", &closes, ConnorsRsi::classic); + + // === Family 03 — Trend & Directional === + // Adx is multi-component (DI+/DI-/ADX); WaveTrend is the heaviest in this group. + bench_candle_input(c, "adx", &candles, || Adx::new(14).unwrap()); + bench_candle_input(c, "wave_trend", &candles, || WaveTrend::classic().unwrap()); + + // === Family 04 — Price Oscillators === + // Macd: multi-output baseline; Stc: deeply recursive (most expensive in family). + bench_scalar_multi::<_, _, MacdOutput>(c, "macd", &closes, MacdIndicator::classic); + bench_scalar(c, "ppo", &closes, || Ppo::new(12, 26).unwrap()); + bench_scalar(c, "stc", &closes, Stc::classic); + + // === Family 05 — Volatility & Bands === + // Atr: cheap baseline; Bollinger: stddev-heavy; YangZhang: most-expensive volatility metric. + bench_candle_input(c, "atr", &candles, || Atr::new(14).unwrap()); + bench_scalar_multi::<_, _, BollingerOutput>(c, "bollinger", &closes, || { + BollingerBands::new(20, 2.0).unwrap() + }); + bench_candle_input(c, "parkinson", &candles, || { + ParkinsonVolatility::new(20, 252).unwrap() + }); + bench_candle_input(c, "yang_zhang", &candles, || { + YangZhangVolatility::new(20, 252).unwrap() + }); + + // === Family 06 — Bands & Channels === + // TtmSqueeze: multi-indicator composite; VwapStdDevBands: volume-weighted. + bench_candle_input::<_, _, TtmSqueezeOutput>(c, "ttm_squeeze", &candles, || { + TtmSqueeze::new(20, 2.0, 1.5).unwrap() + }); + bench_candle_input::<_, _, VwapStdDevBandsOutput>(c, "vwap_stddev_bands", &candles, || { + VwapStdDevBands::new(2.0).unwrap() + }); + + // === Family 07 — Trailing Stops === + // Psar: textbook trailing stop; SuperTrend: ATR-anchored band. + bench_candle_input(c, "psar", &candles, || Psar::new(0.02, 0.02, 0.2).unwrap()); + bench_candle_input::<_, _, SuperTrendOutput>(c, "super_trend", &candles, || { + SuperTrend::new(10, 3.0).unwrap() + }); + + // === Family 08 — Volume === + // Obv: simplest volume cumul; Vwap: session cumul; RollingVwap: rolling window. + bench_candle_input(c, "obv", &candles, Obv::new); + bench_candle_input(c, "vwap", &candles, Vwap::new); + bench_candle_input(c, "rolling_vwap", &candles, || { + RollingVwap::new(20).unwrap() + }); + + // === Family 09 — Price Statistics === + // LinearRegression: OLS baseline; HurstExponent: R/S analysis (most expensive in family); + // Autocorrelation: lag-correlation. + bench_scalar(c, "linear_regression", &closes, || { + LinearRegression::new(14).unwrap() + }); + bench_scalar(c, "hurst_exponent", &closes, || { + HurstExponent::new(100, 4).unwrap() + }); + bench_scalar(c, "autocorrelation", &closes, || { + Autocorrelation::new(20, 1).unwrap() + }); + + // === Family 10 — Ehlers / Cycle (DSP) === + // Mama: paired adaptive MA (multi-output); HilbertDominantCycle: cycle estimation; + // EmpiricalModeDecomposition: heaviest DSP indicator in the catalogue. + bench_scalar_multi::<_, _, MamaOutput>(c, "mama", &closes, Mama::classic); + bench_scalar( + c, + "hilbert_dominant_cycle", + &closes, + HilbertDominantCycle::new, + ); + bench_scalar(c, "empirical_mode_decomposition", &closes, || { + EmpiricalModeDecomposition::new(20, 0.5).unwrap() + }); + + // === Family 11 — Pivots & Support/Resistance === + bench_candle_input(c, "classic_pivots", &candles, ClassicPivots::new); + + // === Family 12 — DeMark === + // TdSequential is the most complex in the family (state machine + countdown). + bench_candle_input::<_, _, TdSequentialOutput>( + c, + "td_sequential", + &candles, + TdSequential::classic, + ); + + // === Family 13 — Ichimoku & Charts === + bench_candle_input::<_, _, IchimokuOutput>(c, "ichimoku", &candles, Ichimoku::classic); + + // === Family 14 — Candlestick Patterns === + // Engulfing is two-bar so representative across the candlestick family. + bench_candle_input(c, "engulfing", &candles, Engulfing::new); + + // === Family 15 — Market Profile === + bench_candle_input::<_, _, ValueAreaOutput>(c, "value_area", &candles, || { + ValueArea::new(20, 50, 0.70).unwrap() + }); + + // === Family 16 — Risk / Performance Metrics === + // Close-prices stand in for the equity curve / return stream; absolute + // numbers aren't meaningful here — what matters is the per-update cost. + bench_scalar(c, "sharpe_ratio", &closes, || { + SharpeRatio::new(20, 0.0).unwrap() + }); + bench_scalar(c, "max_drawdown", &closes, || MaxDrawdown::new(20).unwrap()); + bench_scalar(c, "calmar_ratio", &closes, || CalmarRatio::new(20).unwrap()); + bench_scalar(c, "value_at_risk", &closes, || { + ValueAtRisk::new(50, 0.95).unwrap() + }); +} + criterion_group!(name = wickra_benches; config = Criterion::default(); targets = benches); criterion_main!(wickra_benches);