chore(bench): curate benchmarks to ~30 representative indicators (#63)

Previously every push of `cargo bench -p wickra` ran 114 indicators at
three workload sizes each (1k / 10k / 50k candles), which inflated bench
runtime past ten minutes for diminishing signal — most family members
are linear scalings of the same hot loop, so a regression in any one of
them shows up identically in the cheapest member.

This commit replaces the exhaustive list with a curated selection: the
cheapest baseline and the most expensive representative from each of
the sixteen families, totalling ~33 indicators across scalar, candle,
and multi-output APIs.

If you need to profile a specific indicator that is not in the curated
set, add it temporarily and run `cargo bench -- <name>` to target just
that bench; it does not need to be committed.

Fixed in passing:
- `Cci` is `Indicator<Input = Candle>`, not f64; corrected the bench
  selector to `bench_candle_input`.
- `Psar::new` takes (af_start, af_step, af_max) — supplied all three.
- `TdSequential` uses `::classic` for the textbook (4, 9, 2, 13)
  parameters; the old call was missing arguments.
This commit is contained in:
kingchenc
2026-05-30 18:21:19 +02:00
committed by GitHub
parent fb6eae7fe2
commit 9db1ff8023
+153 -337
View File
@@ -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 -- <name>` 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<I, F, O>(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<f64> = 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<I, F, O>(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<f64> = 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);