Skip to main content

wickra_backtest_core/
registry.rs

1//! Indicator registry: constructs `wickra-core` indicators by name and wraps
2//! them behind a uniform, object-safe [`EvalIndicator`] the engine can drive
3//! from a [`Candle`].
4//!
5//! GENERATED FILE — do not edit by hand. Regenerate with:
6//!
7//! ```text
8//! python tools/gen_registry.py --wickra ../wickra --out crates/wickra-backtest-core/src/registry.rs
9//! cargo fmt --all
10//! ```
11//!
12//! Source of truth: the wickra-core indicator sources (the `Indicator` impls,
13//! `new` signatures and Output structs). Every single-instrument indicator
14//! (`Input = f64` fed the close, or `Input = Candle`) with a scalar `f64` or
15//! all-`f64`-field struct output is registered, plus pairwise
16//! (`Input = (f64, f64)`) indicators fed `(close, reference_close)` from the
17//! reference series. Multi-output indicators expose named fields, referenced in
18//! the spec as `"name.field"`.
19
20use wickra_core::{
21    self as wc, Candle as CoreCandle, CrossSection as CoreCrossSection,
22    DerivativesTick as CoreDerivativesTick, Indicator, OrderBook as CoreOrderBook,
23    Trade as CoreTrade, TradeQuote as CoreTradeQuote,
24};
25
26use crate::data::Candle;
27use crate::error::{BacktestError, Result};
28use crate::spec::Feed;
29
30/// Everything an indicator may consume on one bar. Single-instrument indicators
31/// use `candle`; pairwise indicators also use `reference`; derivatives,
32/// order-book and trade indicators use `deriv` / `orderbook` / `trades`. Feeds
33/// that are absent are `None` / empty.
34#[derive(Debug)]
35pub struct BarInput<'a> {
36    /// The current bar.
37    pub candle: &'a Candle,
38    /// The reference series' close (for pairwise indicators).
39    pub reference: Option<f64>,
40    /// The derivatives tick for this bar (for derivatives indicators).
41    pub deriv: Option<CoreDerivativesTick>,
42    /// The order-book snapshot for this bar (for order-book indicators).
43    pub orderbook: Option<&'a CoreOrderBook>,
44    /// The trades that printed within this bar (for trade-flow indicators),
45    /// replayed in order; empty when there is no trade feed.
46    pub trades: &'a [CoreTrade],
47    /// The market cross-section for this bar (for breadth indicators).
48    pub cross_section: Option<&'a CoreCrossSection>,
49}
50
51/// A uniform, object-safe indicator the engine drives one bar at a time.
52pub trait EvalIndicator: Send + Sync {
53    /// Feed one bar's [`BarInput`]; returns the primary value, or `None` while
54    /// warming up or when the required feed is absent.
55    fn update(&mut self, input: &BarInput) -> Option<f64>;
56    /// Named output fields of the most recent update (empty for single-output).
57    fn fields(&self) -> Vec<(&'static str, f64)>;
58    /// Number of bars required before the first value.
59    fn warmup(&self) -> usize;
60}
61
62/// Wraps a scalar (`Input = f64`) single-output indicator, fed the bar close.
63struct ScalarClose<I>(I);
64
65impl<I> EvalIndicator for ScalarClose<I>
66where
67    I: Indicator<Input = f64, Output = f64> + Send + Sync,
68{
69    fn update(&mut self, input: &BarInput) -> Option<f64> {
70        self.0.update(input.candle.close)
71    }
72    fn fields(&self) -> Vec<(&'static str, f64)> {
73        Vec::new()
74    }
75    fn warmup(&self) -> usize {
76        self.0.warmup_period()
77    }
78}
79
80/// Wraps a candle (`Input = Candle`) single-output indicator.
81struct CandleIn<I>(I);
82
83impl<I> EvalIndicator for CandleIn<I>
84where
85    I: Indicator<Input = CoreCandle, Output = f64> + Send + Sync,
86{
87    fn update(&mut self, input: &BarInput) -> Option<f64> {
88        input.candle.to_core().ok().and_then(|c| self.0.update(c))
89    }
90    fn fields(&self) -> Vec<(&'static str, f64)> {
91        Vec::new()
92    }
93    fn warmup(&self) -> usize {
94        self.0.warmup_period()
95    }
96}
97
98/// Wraps a pairwise (`Input = (f64, f64)`) single-output indicator, fed
99/// `(close, reference_close)`. Without a reference series it yields `None`.
100struct PairClose<I>(I);
101
102impl<I> EvalIndicator for PairClose<I>
103where
104    I: Indicator<Input = (f64, f64), Output = f64> + Send + Sync,
105{
106    fn update(&mut self, input: &BarInput) -> Option<f64> {
107        input
108            .reference
109            .and_then(|r| self.0.update((input.candle.close, r)))
110    }
111    fn fields(&self) -> Vec<(&'static str, f64)> {
112        Vec::new()
113    }
114    fn warmup(&self) -> usize {
115        self.0.warmup_period()
116    }
117}
118
119/// Wraps a derivatives (`Input = DerivativesTick`) single-output indicator.
120/// Without a derivatives feed it yields `None`.
121struct DerivativesIn<I>(I);
122
123impl<I> EvalIndicator for DerivativesIn<I>
124where
125    I: Indicator<Input = CoreDerivativesTick, Output = f64> + Send + Sync,
126{
127    fn update(&mut self, input: &BarInput) -> Option<f64> {
128        input.deriv.and_then(|d| self.0.update(d))
129    }
130    fn fields(&self) -> Vec<(&'static str, f64)> {
131        Vec::new()
132    }
133    fn warmup(&self) -> usize {
134        self.0.warmup_period()
135    }
136}
137
138/// Wraps an order-book (`Input = OrderBook`) single-output indicator. Without an
139/// order-book feed it yields `None`.
140struct OrderBookIn<I>(I);
141
142impl<I> EvalIndicator for OrderBookIn<I>
143where
144    I: Indicator<Input = CoreOrderBook, Output = f64> + Send + Sync,
145{
146    fn update(&mut self, input: &BarInput) -> Option<f64> {
147        input.orderbook.and_then(|ob| self.0.update(ob.clone()))
148    }
149    fn fields(&self) -> Vec<(&'static str, f64)> {
150        Vec::new()
151    }
152    fn warmup(&self) -> usize {
153        self.0.warmup_period()
154    }
155}
156
157/// Wraps a trade (`Input = Trade`) single-output indicator: replays the bar's
158/// trades in order, returning the value after the last. With no trades it yields
159/// `None`.
160struct TradeIn<I>(I);
161
162impl<I> EvalIndicator for TradeIn<I>
163where
164    I: Indicator<Input = CoreTrade, Output = f64> + Send + Sync,
165{
166    fn update(&mut self, input: &BarInput) -> Option<f64> {
167        let mut last = None;
168        for &t in input.trades {
169            last = self.0.update(t);
170        }
171        last
172    }
173    fn fields(&self) -> Vec<(&'static str, f64)> {
174        Vec::new()
175    }
176    fn warmup(&self) -> usize {
177        self.0.warmup_period()
178    }
179}
180
181/// Wraps a trade-quote (`Input = TradeQuote`) single-output indicator: pairs each
182/// bar trade with the prevailing mid (the order book's mid if present, else the
183/// bar close) and replays them. With no trades it yields `None`.
184struct TradeQuoteIn<I>(I);
185
186impl<I> EvalIndicator for TradeQuoteIn<I>
187where
188    I: Indicator<Input = CoreTradeQuote, Output = f64> + Send + Sync,
189{
190    fn update(&mut self, input: &BarInput) -> Option<f64> {
191        let mid = input
192            .orderbook
193            .and_then(|ob| match (ob.best_bid(), ob.best_ask()) {
194                (Some(bid), Some(ask)) => Some(f64::midpoint(ask.price, bid.price)),
195                _ => None,
196            })
197            .unwrap_or(input.candle.close);
198        let mut last = None;
199        for &t in input.trades {
200            if let Ok(tq) = CoreTradeQuote::new(t, mid) {
201                last = self.0.update(tq);
202            }
203        }
204        last
205    }
206    fn fields(&self) -> Vec<(&'static str, f64)> {
207        Vec::new()
208    }
209    fn warmup(&self) -> usize {
210        self.0.warmup_period()
211    }
212}
213
214/// Wraps a cross-section (`Input = CrossSection`) single-output breadth
215/// indicator. Without a cross-section feed it yields `None`.
216struct CrossSectionIn<I>(I);
217
218impl<I> EvalIndicator for CrossSectionIn<I>
219where
220    I: Indicator<Input = CoreCrossSection, Output = f64> + Send + Sync,
221{
222    fn update(&mut self, input: &BarInput) -> Option<f64> {
223        input.cross_section.and_then(|cs| self.0.update(cs.clone()))
224    }
225    fn fields(&self) -> Vec<(&'static str, f64)> {
226        Vec::new()
227    }
228    fn warmup(&self) -> usize {
229        self.0.warmup_period()
230    }
231}
232
233/// Define a multi-output wrapper over an `Input = f64` indicator. The primary
234/// value (bare `"name"` reference) is the first field; all fields are exposed
235/// for `"name.field"` references.
236macro_rules! multi_close {
237    ($wrap:ident, $ty:ident, $first:ident, [$($f:ident),+]) => {
238        struct $wrap {
239            inner: wc::$ty,
240            last: Vec<(&'static str, f64)>,
241        }
242        impl $wrap {
243            fn wrap(inner: wc::$ty) -> Self {
244                Self { inner, last: Vec::new() }
245            }
246        }
247        impl EvalIndicator for $wrap {
248            fn update(&mut self, input: &BarInput) -> Option<f64> {
249                let out = self.inner.update(input.candle.close)?;
250                self.last = vec![$((stringify!($f), out.$f)),+];
251                Some(out.$first)
252            }
253            fn fields(&self) -> Vec<(&'static str, f64)> {
254                self.last.clone()
255            }
256            fn warmup(&self) -> usize {
257                self.inner.warmup_period()
258            }
259        }
260    };
261}
262
263/// Define a multi-output wrapper over an `Input = Candle` indicator.
264macro_rules! multi_candle {
265    ($wrap:ident, $ty:ident, $first:ident, [$($f:ident),+]) => {
266        struct $wrap {
267            inner: wc::$ty,
268            last: Vec<(&'static str, f64)>,
269        }
270        impl $wrap {
271            fn wrap(inner: wc::$ty) -> Self {
272                Self { inner, last: Vec::new() }
273            }
274        }
275        impl EvalIndicator for $wrap {
276            fn update(&mut self, input: &BarInput) -> Option<f64> {
277                let c = input.candle.to_core().ok()?;
278                let out = self.inner.update(c)?;
279                self.last = vec![$((stringify!($f), out.$f)),+];
280                Some(out.$first)
281            }
282            fn fields(&self) -> Vec<(&'static str, f64)> {
283                self.last.clone()
284            }
285            fn warmup(&self) -> usize {
286                self.inner.warmup_period()
287            }
288        }
289    };
290}
291
292/// Define a multi-output wrapper over a pairwise (`Input = (f64, f64)`)
293/// indicator, fed `(close, reference_close)`. Without a reference it yields none.
294macro_rules! multi_pair {
295    ($wrap:ident, $ty:ident, $first:ident, [$($f:ident),+]) => {
296        struct $wrap {
297            inner: wc::$ty,
298            last: Vec<(&'static str, f64)>,
299        }
300        impl $wrap {
301            fn wrap(inner: wc::$ty) -> Self {
302                Self { inner, last: Vec::new() }
303            }
304        }
305        impl EvalIndicator for $wrap {
306            fn update(&mut self, input: &BarInput) -> Option<f64> {
307                let out = self.inner.update((input.candle.close, input.reference?))?;
308                self.last = vec![$((stringify!($f), out.$f)),+];
309                Some(out.$first)
310            }
311            fn fields(&self) -> Vec<(&'static str, f64)> {
312                self.last.clone()
313            }
314            fn warmup(&self) -> usize {
315                self.inner.warmup_period()
316            }
317        }
318    };
319}
320
321/// Define a multi-output wrapper over a derivatives (`Input = DerivativesTick`)
322/// indicator. Without a derivatives feed it yields none.
323macro_rules! multi_deriv {
324    ($wrap:ident, $ty:ident, $first:ident, [$($f:ident),+]) => {
325        struct $wrap {
326            inner: wc::$ty,
327            last: Vec<(&'static str, f64)>,
328        }
329        impl $wrap {
330            fn wrap(inner: wc::$ty) -> Self {
331                Self { inner, last: Vec::new() }
332            }
333        }
334        impl EvalIndicator for $wrap {
335            fn update(&mut self, input: &BarInput) -> Option<f64> {
336                let out = self.inner.update(input.deriv?)?;
337                self.last = vec![$((stringify!($f), out.$f)),+];
338                Some(out.$first)
339            }
340            fn fields(&self) -> Vec<(&'static str, f64)> {
341                self.last.clone()
342            }
343            fn warmup(&self) -> usize {
344                self.inner.warmup_period()
345            }
346        }
347    };
348}
349
350multi_candle!(
351    AccelerationBandsWrap,
352    AccelerationBands,
353    upper,
354    [upper, middle, lower]
355);
356multi_candle!(AdxWrap, Adx, plus_di, [plus_di, minus_di, adx]);
357multi_candle!(AlligatorWrap, Alligator, jaw, [jaw, teeth, lips]);
358multi_candle!(
359    AndrewsPitchforkWrap,
360    AndrewsPitchfork,
361    median,
362    [median, upper, lower]
363);
364multi_candle!(AroonWrap, Aroon, up, [up, down]);
365multi_candle!(AtrBandsWrap, AtrBands, upper, [upper, middle, lower]);
366multi_candle!(AtrRatchetWrap, AtrRatchet, value, [value, direction]);
367multi_candle!(
368    AutoFibWrap,
369    AutoFib,
370    level_0,
371    [level_0, level_236, level_382, level_500, level_618, level_786, level_1000]
372);
373multi_close!(
374    BollingerBandsWrap,
375    BollingerBands,
376    upper,
377    [upper, middle, lower, stddev]
378);
379multi_close!(BomarBandsWrap, BomarBands, upper, [upper, middle, lower]);
380multi_candle!(
381    CamarillaWrap,
382    Camarilla,
383    pp,
384    [pp, r1, r2, r3, r4, s1, s2, s3, s4]
385);
386multi_candle!(CandleVolumeWrap, CandleVolume, body, [body, width]);
387multi_candle!(
388    CentralPivotRangeWrap,
389    CentralPivotRange,
390    pivot,
391    [pivot, tc, bc]
392);
393multi_candle!(
394    ChandeKrollStopWrap,
395    ChandeKrollStop,
396    stop_long,
397    [stop_long, stop_short]
398);
399multi_candle!(
400    ChandelierExitWrap,
401    ChandelierExit,
402    long_stop,
403    [long_stop, short_stop]
404);
405multi_candle!(
406    ClassicPivotsWrap,
407    ClassicPivots,
408    pp,
409    [pp, r1, r2, r3, s1, s2, s3]
410);
411multi_candle!(CompositeProfileWrap, CompositeProfile, poc, [poc, vah, val]);
412multi_candle!(DemarkPivotsWrap, DemarkPivots, pp, [pp, r1, s1]);
413multi_candle!(DonchianWrap, Donchian, upper, [upper, middle, lower]);
414multi_candle!(
415    DonchianStopWrap,
416    DonchianStop,
417    stop_long,
418    [stop_long, stop_short]
419);
420multi_close!(
421    DoubleBollingerWrap,
422    DoubleBollinger,
423    upper_outer,
424    [upper_outer, upper_inner, middle, lower_inner, lower_outer]
425);
426multi_candle!(ElderRayWrap, ElderRay, bull_power, [bull_power, bear_power]);
427multi_candle!(ElderSafeZoneWrap, ElderSafeZone, value, [value, direction]);
428multi_candle!(EquivolumeWrap, Equivolume, height, [height, width]);
429multi_candle!(FibArcsWrap, FibArcs, arc_382, [arc_382, arc_500, arc_618]);
430multi_candle!(
431    FibChannelWrap,
432    FibChannel,
433    base,
434    [base, level_618, level_1000, level_1618]
435);
436multi_candle!(FibConfluenceWrap, FibConfluence, price, [price, strength]);
437multi_candle!(
438    FibExtensionWrap,
439    FibExtension,
440    level_1272,
441    [level_1272, level_1414, level_1618, level_2000, level_2618]
442);
443multi_candle!(FibFanWrap, FibFan, fan_382, [fan_382, fan_500, fan_618]);
444multi_candle!(
445    FibProjectionWrap,
446    FibProjection,
447    level_618,
448    [level_618, level_1000, level_1618, level_2618]
449);
450multi_candle!(
451    FibRetracementWrap,
452    FibRetracement,
453    level_0,
454    [level_0, level_236, level_382, level_500, level_618, level_786, level_1000]
455);
456multi_candle!(
457    FibTimeZonesWrap,
458    FibTimeZones,
459    on_zone,
460    [on_zone, bars_to_next]
461);
462multi_candle!(
463    FibonacciPivotsWrap,
464    FibonacciPivots,
465    pp,
466    [pp, r1, r2, r3, s1, s2, s3]
467);
468multi_candle!(
469    FractalChaosBandsWrap,
470    FractalChaosBands,
471    upper,
472    [upper, lower]
473);
474multi_candle!(GatorOscillatorWrap, GatorOscillator, upper, [upper, lower]);
475multi_candle!(GoldenPocketWrap, GoldenPocket, low, [low, mid, high]);
476multi_candle!(HeikinAshiWrap, HeikinAshi, open, [open, high, low, close]);
477multi_candle!(HighLowVolumeNodesWrap, HighLowVolumeNodes, hvn, [hvn, lvn]);
478multi_close!(HtPhasorWrap, HtPhasor, inphase, [inphase, quadrature]);
479multi_candle!(
480    HurstChannelWrap,
481    HurstChannel,
482    upper,
483    [upper, middle, lower]
484);
485multi_candle!(InitialBalanceWrap, InitialBalance, high, [high, low]);
486multi_candle!(KaseDevStopWrap, KaseDevStop, value, [value, direction]);
487multi_candle!(
488    KasePermissionStochasticWrap,
489    KasePermissionStochastic,
490    fast,
491    [fast, slow]
492);
493multi_candle!(KeltnerWrap, Keltner, upper, [upper, middle, lower]);
494multi_close!(KstWrap, Kst, kst, [kst, signal]);
495multi_close!(
496    LinRegChannelWrap,
497    LinRegChannel,
498    upper,
499    [upper, middle, lower]
500);
501multi_close!(MaEnvelopeWrap, MaEnvelope, upper, [upper, middle, lower]);
502multi_close!(MacdFixWrap, MacdFix, macd, [macd, signal, histogram]);
503multi_close!(
504    MacdIndicatorWrap,
505    MacdIndicator,
506    macd,
507    [macd, signal, histogram]
508);
509multi_close!(MamaWrap, Mama, mama, [mama, fama]);
510multi_close!(
511    MedianChannelWrap,
512    MedianChannel,
513    upper,
514    [upper, middle, lower]
515);
516multi_candle!(
517    ModifiedMaStopWrap,
518    ModifiedMaStop,
519    value,
520    [value, direction]
521);
522multi_candle!(
523    MurreyMathLinesWrap,
524    MurreyMathLines,
525    mm8_8,
526    [mm8_8, mm7_8, mm6_8, mm5_8, mm4_8, mm3_8, mm2_8, mm1_8, mm0_8]
527);
528multi_candle!(NrtrWrap, Nrtr, value, [value, direction]);
529multi_candle!(
530    OpeningRangeWrap,
531    OpeningRange,
532    high,
533    [high, low, breakout_distance]
534);
535multi_candle!(
536    OvernightIntradayReturnWrap,
537    OvernightIntradayReturn,
538    overnight,
539    [overnight, intraday]
540);
541multi_candle!(
542    ProjectionBandsWrap,
543    ProjectionBands,
544    upper,
545    [upper, middle, lower]
546);
547multi_close!(QqeWrap, Qqe, rsi_ma, [rsi_ma, trailing_line]);
548multi_close!(
549    QuartileBandsWrap,
550    QuartileBands,
551    upper,
552    [upper, middle, lower]
553);
554multi_candle!(RwiWrap, Rwi, high, [high, low]);
555multi_candle!(SessionHighLowWrap, SessionHighLow, high, [high, low]);
556multi_candle!(SessionRangeWrap, SessionRange, asia, [asia, eu, us]);
557multi_candle!(
558    SmoothedHeikinAshiWrap,
559    SmoothedHeikinAshi,
560    open,
561    [open, high, low, close]
562);
563multi_close!(
564    StandardErrorBandsWrap,
565    StandardErrorBands,
566    upper,
567    [upper, middle, lower]
568);
569multi_candle!(StarcBandsWrap, StarcBands, upper, [upper, middle, lower]);
570multi_candle!(StochasticWrap, Stochastic, k, [k, d]);
571multi_candle!(SuperTrendWrap, SuperTrend, value, [value, direction]);
572multi_candle!(TdLinesWrap, TdLines, resistance, [resistance, support]);
573multi_candle!(TdMovingAverageWrap, TdMovingAverage, st1, [st1, st2]);
574multi_candle!(TdRangeProjectionWrap, TdRangeProjection, high, [high, low]);
575multi_candle!(
576    TdRiskLevelWrap,
577    TdRiskLevel,
578    buy_risk,
579    [buy_risk, sell_risk]
580);
581multi_candle!(
582    TdSequentialWrap,
583    TdSequential,
584    setup,
585    [setup, countdown, direction]
586);
587multi_candle!(
588    TpoProfileWrap,
589    TpoProfile,
590    price_low,
591    [price_low, price_high]
592);
593multi_candle!(TtmSqueezeWrap, TtmSqueeze, squeeze, [squeeze, momentum]);
594multi_candle!(ValueAreaWrap, ValueArea, poc, [poc, vah, val]);
595multi_candle!(
596    VolatilityConeWrap,
597    VolatilityCone,
598    current,
599    [current, min, median, max, percentile]
600);
601multi_candle!(
602    VolumeProfileWrap,
603    VolumeProfile,
604    price_low,
605    [price_low, price_high]
606);
607multi_candle!(
608    VolumeWeightedMacdWrap,
609    VolumeWeightedMacd,
610    macd,
611    [macd, signal, histogram]
612);
613multi_candle!(
614    VolumeWeightedSrWrap,
615    VolumeWeightedSr,
616    support,
617    [support, resistance]
618);
619multi_candle!(VortexWrap, Vortex, plus, [plus, minus]);
620multi_candle!(
621    VwapStdDevBandsWrap,
622    VwapStdDevBands,
623    upper,
624    [upper, middle, lower, stddev]
625);
626multi_candle!(WaveTrendWrap, WaveTrend, wt1, [wt1, wt2]);
627multi_candle!(WoodiePivotsWrap, WoodiePivots, pp, [pp, r1, r2, s1, s2]);
628multi_close!(
629    ZeroLagMacdWrap,
630    ZeroLagMacd,
631    macd,
632    [macd, signal, histogram]
633);
634multi_candle!(ZigZagWrap, ZigZag, swing, [swing, direction]);
635multi_pair!(
636    CointegrationWrap,
637    Cointegration,
638    hedge_ratio,
639    [hedge_ratio, spread, adf_stat]
640);
641multi_pair!(
642    KalmanHedgeRatioWrap,
643    KalmanHedgeRatio,
644    hedge_ratio,
645    [hedge_ratio, intercept, spread]
646);
647multi_pair!(
648    LeadLagCrossCorrelationWrap,
649    LeadLagCrossCorrelation,
650    correlation,
651    [correlation]
652);
653multi_pair!(
654    RelativeStrengthABWrap,
655    RelativeStrengthAB,
656    ratio,
657    [ratio, ratio_ma, ratio_rsi]
658);
659multi_pair!(
660    SpreadBollingerBandsWrap,
661    SpreadBollingerBands,
662    middle,
663    [middle, upper, lower, percent_b]
664);
665multi_deriv!(
666    LiquidationFeaturesWrap,
667    LiquidationFeatures,
668    long,
669    [long, short, net, total, imbalance]
670);
671
672/// Read parameter `idx` as a positive-integer period.
673fn period(params: &[f64], idx: usize, kind: &str) -> Result<usize> {
674    let v = float_param(params, idx, kind)?;
675    if v <= 0.0 || v.fract().abs() > f64::EPSILON {
676        return Err(BacktestError::InvalidParams {
677            indicator: kind.to_string(),
678            reason: format!("parameter #{idx} must be a positive integer, got {v}"),
679        });
680    }
681    Ok(v as usize)
682}
683
684/// Read parameter `idx` as a non-negative `u32`.
685fn u32_param(params: &[f64], idx: usize, kind: &str) -> Result<u32> {
686    let v = float_param(params, idx, kind)?;
687    if v < 0.0 || v.fract().abs() > f64::EPSILON || v > f64::from(u32::MAX) {
688        return Err(BacktestError::InvalidParams {
689            indicator: kind.to_string(),
690            reason: format!("parameter #{idx} must be a u32, got {v}"),
691        });
692    }
693    Ok(v as u32)
694}
695
696/// Read parameter `idx` as an `i32`.
697fn i32_param(params: &[f64], idx: usize, kind: &str) -> Result<i32> {
698    let v = float_param(params, idx, kind)?;
699    if v.fract().abs() > f64::EPSILON || v > f64::from(i32::MAX) || v < f64::from(i32::MIN) {
700        return Err(BacktestError::InvalidParams {
701            indicator: kind.to_string(),
702            reason: format!("parameter #{idx} must be an i32, got {v}"),
703        });
704    }
705    Ok(v as i32)
706}
707
708/// Read parameter `idx` as a finite `f64`.
709fn float_param(params: &[f64], idx: usize, kind: &str) -> Result<f64> {
710    let v = params
711        .get(idx)
712        .copied()
713        .ok_or_else(|| BacktestError::InvalidParams {
714            indicator: kind.to_string(),
715            reason: format!("missing parameter #{idx}"),
716        })?;
717    if !v.is_finite() {
718        return Err(BacktestError::InvalidParams {
719            indicator: kind.to_string(),
720            reason: format!("parameter #{idx} must be finite"),
721        });
722    }
723    Ok(v)
724}
725
726/// Map a `wickra-core` constructor error into a [`BacktestError`].
727fn map_new<T>(kind: &str, r: wc::Result<T>) -> Result<T> {
728    r.map_err(|e| BacktestError::InvalidParams {
729        indicator: kind.to_string(),
730        reason: e.to_string(),
731    })
732}
733
734/// Construct an indicator by its `wickra-core` type name.
735#[allow(clippy::too_many_lines)]
736pub fn build(kind: &str, params: &[f64]) -> Result<Box<dyn EvalIndicator>> {
737    let p = |i| period(params, i, kind);
738    match kind {
739        // --- scalar single-output (Input = f64), fed the close ---
740        "AdaptiveCycle" => Ok(Box::new(ScalarClose(wc::AdaptiveCycle::new()))),
741        "AdaptiveLaguerreFilter" => Ok(Box::new(ScalarClose(map_new(
742            kind,
743            wc::AdaptiveLaguerreFilter::new(p(0)?),
744        )?))),
745        "AdaptiveRsi" => Ok(Box::new(ScalarClose(map_new(
746            kind,
747            wc::AdaptiveRsi::new(p(0)?),
748        )?))),
749        "Alma" => Ok(Box::new(ScalarClose(map_new(
750            kind,
751            wc::Alma::new(
752                p(0)?,
753                float_param(params, 1, kind)?,
754                float_param(params, 2, kind)?,
755            ),
756        )?))),
757        "AnchoredRsi" => Ok(Box::new(ScalarClose(wc::AnchoredRsi::new()))),
758        "Apo" => Ok(Box::new(ScalarClose(map_new(
759            kind,
760            wc::Apo::new(p(0)?, p(1)?),
761        )?))),
762        "Autocorrelation" => Ok(Box::new(ScalarClose(map_new(
763            kind,
764            wc::Autocorrelation::new(p(0)?, p(1)?),
765        )?))),
766        "AutocorrelationPeriodogram" => Ok(Box::new(ScalarClose(map_new(
767            kind,
768            wc::AutocorrelationPeriodogram::new(p(0)?, p(1)?),
769        )?))),
770        "AverageDrawdown" => Ok(Box::new(ScalarClose(map_new(
771            kind,
772            wc::AverageDrawdown::new(p(0)?),
773        )?))),
774        "BandpassFilter" => Ok(Box::new(ScalarClose(map_new(
775            kind,
776            wc::BandpassFilter::new(p(0)?, float_param(params, 1, kind)?),
777        )?))),
778        "BipowerVariation" => Ok(Box::new(ScalarClose(map_new(
779            kind,
780            wc::BipowerVariation::new(p(0)?),
781        )?))),
782        "BollingerBandwidth" => Ok(Box::new(ScalarClose(map_new(
783            kind,
784            wc::BollingerBandwidth::new(p(0)?, float_param(params, 1, kind)?),
785        )?))),
786        "BurkeRatio" => Ok(Box::new(ScalarClose(map_new(
787            kind,
788            wc::BurkeRatio::new(p(0)?),
789        )?))),
790        "CalmarRatio" => Ok(Box::new(ScalarClose(map_new(
791            kind,
792            wc::CalmarRatio::new(p(0)?),
793        )?))),
794        "CenterOfGravity" => Ok(Box::new(ScalarClose(map_new(
795            kind,
796            wc::CenterOfGravity::new(p(0)?),
797        )?))),
798        "Cfo" => Ok(Box::new(ScalarClose(map_new(kind, wc::Cfo::new(p(0)?))?))),
799        "Cmo" => Ok(Box::new(ScalarClose(map_new(kind, wc::Cmo::new(p(0)?))?))),
800        "CoefficientOfVariation" => Ok(Box::new(ScalarClose(map_new(
801            kind,
802            wc::CoefficientOfVariation::new(p(0)?),
803        )?))),
804        "CommonSenseRatio" => Ok(Box::new(ScalarClose(map_new(
805            kind,
806            wc::CommonSenseRatio::new(p(0)?),
807        )?))),
808        "ConditionalValueAtRisk" => Ok(Box::new(ScalarClose(map_new(
809            kind,
810            wc::ConditionalValueAtRisk::new(p(0)?, float_param(params, 1, kind)?),
811        )?))),
812        "ConnorsRsi" => Ok(Box::new(ScalarClose(map_new(
813            kind,
814            wc::ConnorsRsi::new(p(0)?, p(1)?, p(2)?),
815        )?))),
816        "Coppock" => Ok(Box::new(ScalarClose(map_new(
817            kind,
818            wc::Coppock::new(p(0)?, p(1)?, p(2)?),
819        )?))),
820        "CorrelationTrendIndicator" => Ok(Box::new(ScalarClose(map_new(
821            kind,
822            wc::CorrelationTrendIndicator::new(p(0)?),
823        )?))),
824        "CyberneticCycle" => Ok(Box::new(ScalarClose(map_new(
825            kind,
826            wc::CyberneticCycle::new(p(0)?),
827        )?))),
828        "Decycler" => Ok(Box::new(ScalarClose(map_new(
829            kind,
830            wc::Decycler::new(p(0)?),
831        )?))),
832        "DecyclerOscillator" => Ok(Box::new(ScalarClose(map_new(
833            kind,
834            wc::DecyclerOscillator::new(p(0)?, p(1)?),
835        )?))),
836        "Dema" => Ok(Box::new(ScalarClose(map_new(kind, wc::Dema::new(p(0)?))?))),
837        "DerivativeOscillator" => Ok(Box::new(ScalarClose(map_new(
838            kind,
839            wc::DerivativeOscillator::new(p(0)?, p(1)?, p(2)?, p(3)?),
840        )?))),
841        "DetrendedStdDev" => Ok(Box::new(ScalarClose(map_new(
842            kind,
843            wc::DetrendedStdDev::new(p(0)?),
844        )?))),
845        "DisparityIndex" => Ok(Box::new(ScalarClose(map_new(
846            kind,
847            wc::DisparityIndex::new(p(0)?),
848        )?))),
849        "Dpo" => Ok(Box::new(ScalarClose(map_new(kind, wc::Dpo::new(p(0)?))?))),
850        "DynamicMomentumIndex" => Ok(Box::new(ScalarClose(map_new(
851            kind,
852            wc::DynamicMomentumIndex::new(p(0)?),
853        )?))),
854        "EhlersStochastic" => Ok(Box::new(ScalarClose(map_new(
855            kind,
856            wc::EhlersStochastic::new(p(0)?),
857        )?))),
858        "Ehma" => Ok(Box::new(ScalarClose(map_new(kind, wc::Ehma::new(p(0)?))?))),
859        "ElderImpulse" => Ok(Box::new(ScalarClose(map_new(
860            kind,
861            wc::ElderImpulse::new(p(0)?, p(1)?, p(2)?, p(3)?),
862        )?))),
863        "Ema" => Ok(Box::new(ScalarClose(map_new(kind, wc::Ema::new(p(0)?))?))),
864        "EmpiricalModeDecomposition" => Ok(Box::new(ScalarClose(map_new(
865            kind,
866            wc::EmpiricalModeDecomposition::new(p(0)?, float_param(params, 1, kind)?),
867        )?))),
868        "EvenBetterSinewave" => Ok(Box::new(ScalarClose(map_new(
869            kind,
870            wc::EvenBetterSinewave::new(p(0)?, p(1)?),
871        )?))),
872        "EwmaVolatility" => Ok(Box::new(ScalarClose(map_new(
873            kind,
874            wc::EwmaVolatility::new(float_param(params, 0, kind)?),
875        )?))),
876        "Expectancy" => Ok(Box::new(ScalarClose(map_new(
877            kind,
878            wc::Expectancy::new(p(0)?),
879        )?))),
880        "Fama" => Ok(Box::new(ScalarClose(map_new(
881            kind,
882            wc::Fama::new(float_param(params, 0, kind)?, float_param(params, 1, kind)?),
883        )?))),
884        "FisherRsi" => Ok(Box::new(ScalarClose(map_new(
885            kind,
886            wc::FisherRsi::new(p(0)?),
887        )?))),
888        "FisherTransform" => Ok(Box::new(ScalarClose(map_new(
889            kind,
890            wc::FisherTransform::new(p(0)?),
891        )?))),
892        "Frama" => Ok(Box::new(ScalarClose(map_new(kind, wc::Frama::new(p(0)?))?))),
893        "GainLossRatio" => Ok(Box::new(ScalarClose(map_new(
894            kind,
895            wc::GainLossRatio::new(p(0)?),
896        )?))),
897        "GainToPainRatio" => Ok(Box::new(ScalarClose(map_new(
898            kind,
899            wc::GainToPainRatio::new(p(0)?),
900        )?))),
901        "Garch11" => Ok(Box::new(ScalarClose(map_new(
902            kind,
903            wc::Garch11::new(
904                float_param(params, 0, kind)?,
905                float_param(params, 1, kind)?,
906                float_param(params, 2, kind)?,
907            ),
908        )?))),
909        "GeneralizedDema" => Ok(Box::new(ScalarClose(map_new(
910            kind,
911            wc::GeneralizedDema::new(p(0)?, float_param(params, 1, kind)?),
912        )?))),
913        "GeometricMa" => Ok(Box::new(ScalarClose(map_new(
914            kind,
915            wc::GeometricMa::new(p(0)?),
916        )?))),
917        "HighpassFilter" => Ok(Box::new(ScalarClose(map_new(
918            kind,
919            wc::HighpassFilter::new(p(0)?),
920        )?))),
921        "HilbertDominantCycle" => Ok(Box::new(ScalarClose(wc::HilbertDominantCycle::new()))),
922        "HistoricalVolatility" => Ok(Box::new(ScalarClose(map_new(
923            kind,
924            wc::HistoricalVolatility::new(p(0)?, p(1)?),
925        )?))),
926        "Hma" => Ok(Box::new(ScalarClose(map_new(kind, wc::Hma::new(p(0)?))?))),
927        "HoltWinters" => Ok(Box::new(ScalarClose(map_new(
928            kind,
929            wc::HoltWinters::new(float_param(params, 0, kind)?, float_param(params, 1, kind)?),
930        )?))),
931        "HtDcPhase" => Ok(Box::new(ScalarClose(wc::HtDcPhase::new()))),
932        "HtTrendMode" => Ok(Box::new(ScalarClose(wc::HtTrendMode::new()))),
933        "HurstExponent" => Ok(Box::new(ScalarClose(map_new(
934            kind,
935            wc::HurstExponent::new(p(0)?, p(1)?),
936        )?))),
937        "InstantaneousTrendline" => Ok(Box::new(ScalarClose(map_new(
938            kind,
939            wc::InstantaneousTrendline::new(p(0)?),
940        )?))),
941        "InverseFisherTransform" => Ok(Box::new(ScalarClose(map_new(
942            kind,
943            wc::InverseFisherTransform::new(float_param(params, 0, kind)?),
944        )?))),
945        "JarqueBera" => Ok(Box::new(ScalarClose(map_new(
946            kind,
947            wc::JarqueBera::new(p(0)?),
948        )?))),
949        "Jma" => Ok(Box::new(ScalarClose(map_new(
950            kind,
951            wc::Jma::new(
952                p(0)?,
953                float_param(params, 1, kind)?,
954                u32_param(params, 2, kind)?,
955            ),
956        )?))),
957        "JumpIndicator" => Ok(Box::new(ScalarClose(map_new(
958            kind,
959            wc::JumpIndicator::new(p(0)?, float_param(params, 1, kind)?),
960        )?))),
961        "KRatio" => Ok(Box::new(ScalarClose(map_new(
962            kind,
963            wc::KRatio::new(p(0)?),
964        )?))),
965        "Kama" => Ok(Box::new(ScalarClose(map_new(
966            kind,
967            wc::Kama::new(p(0)?, p(1)?, p(2)?),
968        )?))),
969        "KellyCriterion" => Ok(Box::new(ScalarClose(map_new(
970            kind,
971            wc::KellyCriterion::new(p(0)?),
972        )?))),
973        "Kurtosis" => Ok(Box::new(ScalarClose(map_new(
974            kind,
975            wc::Kurtosis::new(p(0)?),
976        )?))),
977        "LaguerreRsi" => Ok(Box::new(ScalarClose(map_new(
978            kind,
979            wc::LaguerreRsi::new(float_param(params, 0, kind)?),
980        )?))),
981        "LinRegAngle" => Ok(Box::new(ScalarClose(map_new(
982            kind,
983            wc::LinRegAngle::new(p(0)?),
984        )?))),
985        "LinRegIntercept" => Ok(Box::new(ScalarClose(map_new(
986            kind,
987            wc::LinRegIntercept::new(p(0)?),
988        )?))),
989        "LinRegSlope" => Ok(Box::new(ScalarClose(map_new(
990            kind,
991            wc::LinRegSlope::new(p(0)?),
992        )?))),
993        "LinearRegression" => Ok(Box::new(ScalarClose(map_new(
994            kind,
995            wc::LinearRegression::new(p(0)?),
996        )?))),
997        "LogReturn" => Ok(Box::new(ScalarClose(map_new(
998            kind,
999            wc::LogReturn::new(p(0)?),
1000        )?))),
1001        "M2Measure" => Ok(Box::new(ScalarClose(map_new(
1002            kind,
1003            wc::M2Measure::new(
1004                p(0)?,
1005                float_param(params, 1, kind)?,
1006                float_param(params, 2, kind)?,
1007            ),
1008        )?))),
1009        "MacdHistogram" => Ok(Box::new(ScalarClose(map_new(
1010            kind,
1011            wc::MacdHistogram::new(p(0)?, p(1)?, p(2)?),
1012        )?))),
1013        "MartinRatio" => Ok(Box::new(ScalarClose(map_new(
1014            kind,
1015            wc::MartinRatio::new(p(0)?),
1016        )?))),
1017        "MaxDrawdown" => Ok(Box::new(ScalarClose(map_new(
1018            kind,
1019            wc::MaxDrawdown::new(p(0)?),
1020        )?))),
1021        "McGinleyDynamic" => Ok(Box::new(ScalarClose(map_new(
1022            kind,
1023            wc::McGinleyDynamic::new(p(0)?),
1024        )?))),
1025        "MedianAbsoluteDeviation" => Ok(Box::new(ScalarClose(map_new(
1026            kind,
1027            wc::MedianAbsoluteDeviation::new(p(0)?),
1028        )?))),
1029        "MedianMa" => Ok(Box::new(ScalarClose(map_new(
1030            kind,
1031            wc::MedianMa::new(p(0)?),
1032        )?))),
1033        "MidPoint" => Ok(Box::new(ScalarClose(map_new(
1034            kind,
1035            wc::MidPoint::new(p(0)?),
1036        )?))),
1037        "Mom" => Ok(Box::new(ScalarClose(map_new(kind, wc::Mom::new(p(0)?))?))),
1038        "OmegaRatio" => Ok(Box::new(ScalarClose(map_new(
1039            kind,
1040            wc::OmegaRatio::new(p(0)?, float_param(params, 1, kind)?),
1041        )?))),
1042        "PainIndex" => Ok(Box::new(ScalarClose(map_new(
1043            kind,
1044            wc::PainIndex::new(p(0)?),
1045        )?))),
1046        "PercentB" => Ok(Box::new(ScalarClose(map_new(
1047            kind,
1048            wc::PercentB::new(p(0)?, float_param(params, 1, kind)?),
1049        )?))),
1050        "PercentageTrailingStop" => Ok(Box::new(ScalarClose(map_new(
1051            kind,
1052            wc::PercentageTrailingStop::new(float_param(params, 0, kind)?),
1053        )?))),
1054        "Pmo" => Ok(Box::new(ScalarClose(map_new(
1055            kind,
1056            wc::Pmo::new(p(0)?, p(1)?),
1057        )?))),
1058        "PolarizedFractalEfficiency" => Ok(Box::new(ScalarClose(map_new(
1059            kind,
1060            wc::PolarizedFractalEfficiency::new(p(0)?, p(1)?),
1061        )?))),
1062        "Ppo" => Ok(Box::new(ScalarClose(map_new(
1063            kind,
1064            wc::Ppo::new(p(0)?, p(1)?),
1065        )?))),
1066        "PpoHistogram" => Ok(Box::new(ScalarClose(map_new(
1067            kind,
1068            wc::PpoHistogram::new(p(0)?, p(1)?, p(2)?),
1069        )?))),
1070        "ProfitFactor" => Ok(Box::new(ScalarClose(map_new(
1071            kind,
1072            wc::ProfitFactor::new(p(0)?),
1073        )?))),
1074        "RSquared" => Ok(Box::new(ScalarClose(map_new(
1075            kind,
1076            wc::RSquared::new(p(0)?),
1077        )?))),
1078        "RealizedVolatility" => Ok(Box::new(ScalarClose(map_new(
1079            kind,
1080            wc::RealizedVolatility::new(p(0)?),
1081        )?))),
1082        "RecoveryFactor" => Ok(Box::new(ScalarClose(wc::RecoveryFactor::new()))),
1083        "Reflex" => Ok(Box::new(ScalarClose(map_new(
1084            kind,
1085            wc::Reflex::new(p(0)?),
1086        )?))),
1087        "RegimeLabel" => Ok(Box::new(ScalarClose(map_new(
1088            kind,
1089            wc::RegimeLabel::new(p(0)?, p(1)?),
1090        )?))),
1091        "RenkoTrailingStop" => Ok(Box::new(ScalarClose(map_new(
1092            kind,
1093            wc::RenkoTrailingStop::new(float_param(params, 0, kind)?),
1094        )?))),
1095        "Rmi" => Ok(Box::new(ScalarClose(map_new(
1096            kind,
1097            wc::Rmi::new(p(0)?, p(1)?),
1098        )?))),
1099        "Roc" => Ok(Box::new(ScalarClose(map_new(kind, wc::Roc::new(p(0)?))?))),
1100        "Rocp" => Ok(Box::new(ScalarClose(map_new(kind, wc::Rocp::new(p(0)?))?))),
1101        "Rocr" => Ok(Box::new(ScalarClose(map_new(kind, wc::Rocr::new(p(0)?))?))),
1102        "Rocr100" => Ok(Box::new(ScalarClose(map_new(
1103            kind,
1104            wc::Rocr100::new(p(0)?),
1105        )?))),
1106        "RollingIqr" => Ok(Box::new(ScalarClose(map_new(
1107            kind,
1108            wc::RollingIqr::new(p(0)?),
1109        )?))),
1110        "RollingMinMaxScaler" => Ok(Box::new(ScalarClose(map_new(
1111            kind,
1112            wc::RollingMinMaxScaler::new(p(0)?),
1113        )?))),
1114        "RollingPercentileRank" => Ok(Box::new(ScalarClose(map_new(
1115            kind,
1116            wc::RollingPercentileRank::new(p(0)?),
1117        )?))),
1118        "RollingQuantile" => Ok(Box::new(ScalarClose(map_new(
1119            kind,
1120            wc::RollingQuantile::new(p(0)?, float_param(params, 1, kind)?),
1121        )?))),
1122        "RoofingFilter" => Ok(Box::new(ScalarClose(map_new(
1123            kind,
1124            wc::RoofingFilter::new(p(0)?, p(1)?),
1125        )?))),
1126        "Rsi" => Ok(Box::new(ScalarClose(map_new(kind, wc::Rsi::new(p(0)?))?))),
1127        "Rsx" => Ok(Box::new(ScalarClose(map_new(kind, wc::Rsx::new(p(0)?))?))),
1128        "RviVolatility" => Ok(Box::new(ScalarClose(map_new(
1129            kind,
1130            wc::RviVolatility::new(p(0)?),
1131        )?))),
1132        "SampleEntropy" => Ok(Box::new(ScalarClose(map_new(
1133            kind,
1134            wc::SampleEntropy::new(p(0)?, p(1)?, float_param(params, 2, kind)?),
1135        )?))),
1136        "ShannonEntropy" => Ok(Box::new(ScalarClose(map_new(
1137            kind,
1138            wc::ShannonEntropy::new(p(0)?, p(1)?),
1139        )?))),
1140        "SharpeRatio" => Ok(Box::new(ScalarClose(map_new(
1141            kind,
1142            wc::SharpeRatio::new(p(0)?, float_param(params, 1, kind)?),
1143        )?))),
1144        "SineWave" => Ok(Box::new(ScalarClose(wc::SineWave::new()))),
1145        "SineWeightedMa" => Ok(Box::new(ScalarClose(map_new(
1146            kind,
1147            wc::SineWeightedMa::new(p(0)?),
1148        )?))),
1149        "Skewness" => Ok(Box::new(ScalarClose(map_new(
1150            kind,
1151            wc::Skewness::new(p(0)?),
1152        )?))),
1153        "Sma" => Ok(Box::new(ScalarClose(map_new(kind, wc::Sma::new(p(0)?))?))),
1154        "Smma" => Ok(Box::new(ScalarClose(map_new(kind, wc::Smma::new(p(0)?))?))),
1155        "SortinoRatio" => Ok(Box::new(ScalarClose(map_new(
1156            kind,
1157            wc::SortinoRatio::new(p(0)?, float_param(params, 1, kind)?),
1158        )?))),
1159        "StandardError" => Ok(Box::new(ScalarClose(map_new(
1160            kind,
1161            wc::StandardError::new(p(0)?),
1162        )?))),
1163        "Stc" => Ok(Box::new(ScalarClose(map_new(
1164            kind,
1165            wc::Stc::new(p(0)?, p(1)?, p(2)?, float_param(params, 3, kind)?),
1166        )?))),
1167        "StdDev" => Ok(Box::new(ScalarClose(map_new(
1168            kind,
1169            wc::StdDev::new(p(0)?),
1170        )?))),
1171        "StepTrailingStop" => Ok(Box::new(ScalarClose(map_new(
1172            kind,
1173            wc::StepTrailingStop::new(float_param(params, 0, kind)?),
1174        )?))),
1175        "SterlingRatio" => Ok(Box::new(ScalarClose(map_new(
1176            kind,
1177            wc::SterlingRatio::new(p(0)?),
1178        )?))),
1179        "StochRsi" => Ok(Box::new(ScalarClose(map_new(
1180            kind,
1181            wc::StochRsi::new(p(0)?, p(1)?),
1182        )?))),
1183        "SuperSmoother" => Ok(Box::new(ScalarClose(map_new(
1184            kind,
1185            wc::SuperSmoother::new(p(0)?),
1186        )?))),
1187        "T3" => Ok(Box::new(ScalarClose(map_new(
1188            kind,
1189            wc::T3::new(p(0)?, float_param(params, 1, kind)?),
1190        )?))),
1191        "TailRatio" => Ok(Box::new(ScalarClose(map_new(
1192            kind,
1193            wc::TailRatio::new(p(0)?),
1194        )?))),
1195        "Tema" => Ok(Box::new(ScalarClose(map_new(kind, wc::Tema::new(p(0)?))?))),
1196        "Tii" => Ok(Box::new(ScalarClose(map_new(
1197            kind,
1198            wc::Tii::new(p(0)?, p(1)?),
1199        )?))),
1200        "TrendLabel" => Ok(Box::new(ScalarClose(map_new(
1201            kind,
1202            wc::TrendLabel::new(p(0)?),
1203        )?))),
1204        "TrendStrengthIndex" => Ok(Box::new(ScalarClose(map_new(
1205            kind,
1206            wc::TrendStrengthIndex::new(p(0)?),
1207        )?))),
1208        "Trendflex" => Ok(Box::new(ScalarClose(map_new(
1209            kind,
1210            wc::Trendflex::new(p(0)?),
1211        )?))),
1212        "Trima" => Ok(Box::new(ScalarClose(map_new(kind, wc::Trima::new(p(0)?))?))),
1213        "Trix" => Ok(Box::new(ScalarClose(map_new(kind, wc::Trix::new(p(0)?))?))),
1214        "Tsf" => Ok(Box::new(ScalarClose(map_new(kind, wc::Tsf::new(p(0)?))?))),
1215        "TsfOscillator" => Ok(Box::new(ScalarClose(map_new(
1216            kind,
1217            wc::TsfOscillator::new(p(0)?),
1218        )?))),
1219        "Tsi" => Ok(Box::new(ScalarClose(map_new(
1220            kind,
1221            wc::Tsi::new(p(0)?, p(1)?),
1222        )?))),
1223        "UlcerIndex" => Ok(Box::new(ScalarClose(map_new(
1224            kind,
1225            wc::UlcerIndex::new(p(0)?),
1226        )?))),
1227        "UniversalOscillator" => Ok(Box::new(ScalarClose(map_new(
1228            kind,
1229            wc::UniversalOscillator::new(p(0)?),
1230        )?))),
1231        "UpsidePotentialRatio" => Ok(Box::new(ScalarClose(map_new(
1232            kind,
1233            wc::UpsidePotentialRatio::new(p(0)?, float_param(params, 1, kind)?),
1234        )?))),
1235        "ValueAtRisk" => Ok(Box::new(ScalarClose(map_new(
1236            kind,
1237            wc::ValueAtRisk::new(p(0)?, float_param(params, 1, kind)?),
1238        )?))),
1239        "Variance" => Ok(Box::new(ScalarClose(map_new(
1240            kind,
1241            wc::Variance::new(p(0)?),
1242        )?))),
1243        "VerticalHorizontalFilter" => Ok(Box::new(ScalarClose(map_new(
1244            kind,
1245            wc::VerticalHorizontalFilter::new(p(0)?),
1246        )?))),
1247        "Vidya" => Ok(Box::new(ScalarClose(map_new(
1248            kind,
1249            wc::Vidya::new(p(0)?, p(1)?),
1250        )?))),
1251        "VolatilityOfVolatility" => Ok(Box::new(ScalarClose(map_new(
1252            kind,
1253            wc::VolatilityOfVolatility::new(p(0)?, p(1)?),
1254        )?))),
1255        "WavePm" => Ok(Box::new(ScalarClose(map_new(
1256            kind,
1257            wc::WavePm::new(p(0)?, p(1)?),
1258        )?))),
1259        "WinRate" => Ok(Box::new(ScalarClose(map_new(
1260            kind,
1261            wc::WinRate::new(p(0)?),
1262        )?))),
1263        "Wma" => Ok(Box::new(ScalarClose(map_new(kind, wc::Wma::new(p(0)?))?))),
1264        "ZScore" => Ok(Box::new(ScalarClose(map_new(
1265            kind,
1266            wc::ZScore::new(p(0)?),
1267        )?))),
1268        "Zlema" => Ok(Box::new(ScalarClose(map_new(kind, wc::Zlema::new(p(0)?))?))),
1269        // --- scalar single-output (Input = Candle) ---
1270        "AbandonedBaby" => Ok(Box::new(CandleIn(wc::AbandonedBaby::new()))),
1271        "Abcd" => Ok(Box::new(CandleIn(wc::Abcd::new()))),
1272        "AcceleratorOscillator" => Ok(Box::new(CandleIn(map_new(
1273            kind,
1274            wc::AcceleratorOscillator::new(p(0)?, p(1)?, p(2)?),
1275        )?))),
1276        "AdOscillator" => Ok(Box::new(CandleIn(wc::AdOscillator::new()))),
1277        "AdaptiveCci" => Ok(Box::new(CandleIn(map_new(
1278            kind,
1279            wc::AdaptiveCci::new(p(0)?),
1280        )?))),
1281        "Adl" => Ok(Box::new(CandleIn(wc::Adl::new()))),
1282        "AdvanceBlock" => Ok(Box::new(CandleIn(wc::AdvanceBlock::new()))),
1283        "Adxr" => Ok(Box::new(CandleIn(map_new(kind, wc::Adxr::new(p(0)?))?))),
1284        "AnchoredVwap" => Ok(Box::new(CandleIn(wc::AnchoredVwap::new()))),
1285        "AroonOscillator" => Ok(Box::new(CandleIn(map_new(
1286            kind,
1287            wc::AroonOscillator::new(p(0)?),
1288        )?))),
1289        "Atr" => Ok(Box::new(CandleIn(map_new(kind, wc::Atr::new(p(0)?))?))),
1290        "AtrTrailingStop" => Ok(Box::new(CandleIn(map_new(
1291            kind,
1292            wc::AtrTrailingStop::new(p(0)?, float_param(params, 1, kind)?),
1293        )?))),
1294        "AverageDailyRange" => Ok(Box::new(CandleIn(map_new(
1295            kind,
1296            wc::AverageDailyRange::new(p(0)?, i32_param(params, 1, kind)?),
1297        )?))),
1298        "AvgPrice" => Ok(Box::new(CandleIn(wc::AvgPrice::new()))),
1299        "AwesomeOscillator" => Ok(Box::new(CandleIn(map_new(
1300            kind,
1301            wc::AwesomeOscillator::new(p(0)?, p(1)?),
1302        )?))),
1303        "AwesomeOscillatorHistogram" => Ok(Box::new(CandleIn(map_new(
1304            kind,
1305            wc::AwesomeOscillatorHistogram::new(p(0)?, p(1)?, p(2)?),
1306        )?))),
1307        "BalanceOfPower" => Ok(Box::new(CandleIn(wc::BalanceOfPower::new()))),
1308        "Bat" => Ok(Box::new(CandleIn(wc::Bat::new()))),
1309        "BeltHold" => Ok(Box::new(CandleIn(wc::BeltHold::new()))),
1310        "BetterVolume" => Ok(Box::new(CandleIn(map_new(
1311            kind,
1312            wc::BetterVolume::new(p(0)?),
1313        )?))),
1314        "BodySizePct" => Ok(Box::new(CandleIn(wc::BodySizePct::new()))),
1315        "Breakaway" => Ok(Box::new(CandleIn(wc::Breakaway::new()))),
1316        "Butterfly" => Ok(Box::new(CandleIn(wc::Butterfly::new()))),
1317        "Cci" => Ok(Box::new(CandleIn(map_new(kind, wc::Cci::new(p(0)?))?))),
1318        "ChaikinMoneyFlow" => Ok(Box::new(CandleIn(map_new(
1319            kind,
1320            wc::ChaikinMoneyFlow::new(p(0)?),
1321        )?))),
1322        "ChaikinOscillator" => Ok(Box::new(CandleIn(map_new(
1323            kind,
1324            wc::ChaikinOscillator::new(p(0)?, p(1)?),
1325        )?))),
1326        "ChaikinVolatility" => Ok(Box::new(CandleIn(map_new(
1327            kind,
1328            wc::ChaikinVolatility::new(p(0)?, p(1)?),
1329        )?))),
1330        "ChoppinessIndex" => Ok(Box::new(CandleIn(map_new(
1331            kind,
1332            wc::ChoppinessIndex::new(p(0)?),
1333        )?))),
1334        "CloseVsOpen" => Ok(Box::new(CandleIn(wc::CloseVsOpen::new()))),
1335        "ClosingMarubozu" => Ok(Box::new(CandleIn(wc::ClosingMarubozu::new()))),
1336        "ConcealingBabySwallow" => Ok(Box::new(CandleIn(wc::ConcealingBabySwallow::new()))),
1337        "Counterattack" => Ok(Box::new(CandleIn(wc::Counterattack::new()))),
1338        "Crab" => Ok(Box::new(CandleIn(wc::Crab::new()))),
1339        "CupAndHandle" => Ok(Box::new(CandleIn(wc::CupAndHandle::new()))),
1340        "Cypher" => Ok(Box::new(CandleIn(wc::Cypher::new()))),
1341        "DemandIndex" => Ok(Box::new(CandleIn(map_new(
1342            kind,
1343            wc::DemandIndex::new(p(0)?),
1344        )?))),
1345        "Doji" => Ok(Box::new(CandleIn(wc::Doji::new()))),
1346        "DojiStar" => Ok(Box::new(CandleIn(wc::DojiStar::new()))),
1347        "DoubleTopBottom" => Ok(Box::new(CandleIn(wc::DoubleTopBottom::new()))),
1348        "DownsideGapThreeMethods" => Ok(Box::new(CandleIn(wc::DownsideGapThreeMethods::new()))),
1349        "DragonflyDoji" => Ok(Box::new(CandleIn(wc::DragonflyDoji::new()))),
1350        "DumplingTop" => Ok(Box::new(CandleIn(map_new(
1351            kind,
1352            wc::DumplingTop::new(p(0)?),
1353        )?))),
1354        "Dx" => Ok(Box::new(CandleIn(map_new(kind, wc::Dx::new(p(0)?))?))),
1355        "EaseOfMovement" => Ok(Box::new(CandleIn(map_new(
1356            kind,
1357            wc::EaseOfMovement::new(p(0)?),
1358        )?))),
1359        "Engulfing" => Ok(Box::new(CandleIn(wc::Engulfing::new()))),
1360        "EveningDojiStar" => Ok(Box::new(CandleIn(wc::EveningDojiStar::new()))),
1361        "Evwma" => Ok(Box::new(CandleIn(map_new(kind, wc::Evwma::new(p(0)?))?))),
1362        "FallingThreeMethods" => Ok(Box::new(CandleIn(wc::FallingThreeMethods::new()))),
1363        "FlagPennant" => Ok(Box::new(CandleIn(wc::FlagPennant::new()))),
1364        "ForceIndex" => Ok(Box::new(CandleIn(map_new(
1365            kind,
1366            wc::ForceIndex::new(p(0)?),
1367        )?))),
1368        "FryPanBottom" => Ok(Box::new(CandleIn(map_new(
1369            kind,
1370            wc::FryPanBottom::new(p(0)?),
1371        )?))),
1372        "GapSideBySideWhite" => Ok(Box::new(CandleIn(wc::GapSideBySideWhite::new()))),
1373        "GarmanKlassVolatility" => Ok(Box::new(CandleIn(map_new(
1374            kind,
1375            wc::GarmanKlassVolatility::new(p(0)?, p(1)?),
1376        )?))),
1377        "Gartley" => Ok(Box::new(CandleIn(wc::Gartley::new()))),
1378        "GravestoneDoji" => Ok(Box::new(CandleIn(wc::GravestoneDoji::new()))),
1379        "Hammer" => Ok(Box::new(CandleIn(wc::Hammer::new()))),
1380        "HangingMan" => Ok(Box::new(CandleIn(wc::HangingMan::new()))),
1381        "Harami" => Ok(Box::new(CandleIn(wc::Harami::new()))),
1382        "HaramiCross" => Ok(Box::new(CandleIn(wc::HaramiCross::new()))),
1383        "HeadAndShoulders" => Ok(Box::new(CandleIn(wc::HeadAndShoulders::new()))),
1384        "HeikinAshiOscillator" => Ok(Box::new(CandleIn(map_new(
1385            kind,
1386            wc::HeikinAshiOscillator::new(p(0)?),
1387        )?))),
1388        "HiLoActivator" => Ok(Box::new(CandleIn(map_new(
1389            kind,
1390            wc::HiLoActivator::new(p(0)?),
1391        )?))),
1392        "HighLowRange" => Ok(Box::new(CandleIn(wc::HighLowRange::new()))),
1393        "HighWave" => Ok(Box::new(CandleIn(wc::HighWave::new()))),
1394        "Hikkake" => Ok(Box::new(CandleIn(wc::Hikkake::new()))),
1395        "HikkakeModified" => Ok(Box::new(CandleIn(wc::HikkakeModified::new()))),
1396        "HomingPigeon" => Ok(Box::new(CandleIn(wc::HomingPigeon::new()))),
1397        "IdenticalThreeCrows" => Ok(Box::new(CandleIn(wc::IdenticalThreeCrows::new()))),
1398        "InNeck" => Ok(Box::new(CandleIn(wc::InNeck::new()))),
1399        "Inertia" => Ok(Box::new(CandleIn(map_new(
1400            kind,
1401            wc::Inertia::new(p(0)?, p(1)?),
1402        )?))),
1403        "IntradayIntensity" => Ok(Box::new(CandleIn(wc::IntradayIntensity::new()))),
1404        "IntradayMomentumIndex" => Ok(Box::new(CandleIn(map_new(
1405            kind,
1406            wc::IntradayMomentumIndex::new(p(0)?),
1407        )?))),
1408        "InvertedHammer" => Ok(Box::new(CandleIn(wc::InvertedHammer::new()))),
1409        "Kicking" => Ok(Box::new(CandleIn(wc::Kicking::new()))),
1410        "KickingByLength" => Ok(Box::new(CandleIn(wc::KickingByLength::new()))),
1411        "Kvo" => Ok(Box::new(CandleIn(map_new(
1412            kind,
1413            wc::Kvo::new(p(0)?, p(1)?),
1414        )?))),
1415        "LadderBottom" => Ok(Box::new(CandleIn(wc::LadderBottom::new()))),
1416        "LongLeggedDoji" => Ok(Box::new(CandleIn(wc::LongLeggedDoji::new()))),
1417        "LongLine" => Ok(Box::new(CandleIn(wc::LongLine::new()))),
1418        "MarketFacilitationIndex" => Ok(Box::new(CandleIn(wc::MarketFacilitationIndex::new()))),
1419        "Marubozu" => Ok(Box::new(CandleIn(wc::Marubozu::new()))),
1420        "MassIndex" => Ok(Box::new(CandleIn(map_new(
1421            kind,
1422            wc::MassIndex::new(p(0)?, p(1)?),
1423        )?))),
1424        "MatHold" => Ok(Box::new(CandleIn(wc::MatHold::new()))),
1425        "MatchingLow" => Ok(Box::new(CandleIn(wc::MatchingLow::new()))),
1426        "MedianPrice" => Ok(Box::new(CandleIn(wc::MedianPrice::new()))),
1427        "Mfi" => Ok(Box::new(CandleIn(map_new(kind, wc::Mfi::new(p(0)?))?))),
1428        "MidPrice" => Ok(Box::new(CandleIn(map_new(kind, wc::MidPrice::new(p(0)?))?))),
1429        "MinusDi" => Ok(Box::new(CandleIn(map_new(kind, wc::MinusDi::new(p(0)?))?))),
1430        "MinusDm" => Ok(Box::new(CandleIn(map_new(kind, wc::MinusDm::new(p(0)?))?))),
1431        "MorningDojiStar" => Ok(Box::new(CandleIn(wc::MorningDojiStar::new()))),
1432        "MorningEveningStar" => Ok(Box::new(CandleIn(wc::MorningEveningStar::new()))),
1433        "NakedPoc" => Ok(Box::new(CandleIn(map_new(
1434            kind,
1435            wc::NakedPoc::new(p(0)?, p(1)?),
1436        )?))),
1437        "Natr" => Ok(Box::new(CandleIn(map_new(kind, wc::Natr::new(p(0)?))?))),
1438        "NewPriceLines" => Ok(Box::new(CandleIn(map_new(
1439            kind,
1440            wc::NewPriceLines::new(p(0)?),
1441        )?))),
1442        "Nvi" => Ok(Box::new(CandleIn(wc::Nvi::new()))),
1443        "Obv" => Ok(Box::new(CandleIn(wc::Obv::new()))),
1444        "OnNeck" => Ok(Box::new(CandleIn(wc::OnNeck::new()))),
1445        "OpeningMarubozu" => Ok(Box::new(CandleIn(wc::OpeningMarubozu::new()))),
1446        "OvernightGap" => Ok(Box::new(CandleIn(wc::OvernightGap::new(i32_param(
1447            params, 0, kind,
1448        )?)))),
1449        "ParkinsonVolatility" => Ok(Box::new(CandleIn(map_new(
1450            kind,
1451            wc::ParkinsonVolatility::new(p(0)?, p(1)?),
1452        )?))),
1453        "Pgo" => Ok(Box::new(CandleIn(map_new(kind, wc::Pgo::new(p(0)?))?))),
1454        "PiercingDarkCloud" => Ok(Box::new(CandleIn(wc::PiercingDarkCloud::new()))),
1455        "PivotReversal" => Ok(Box::new(CandleIn(map_new(
1456            kind,
1457            wc::PivotReversal::new(p(0)?, p(1)?),
1458        )?))),
1459        "PlusDi" => Ok(Box::new(CandleIn(map_new(kind, wc::PlusDi::new(p(0)?))?))),
1460        "PlusDm" => Ok(Box::new(CandleIn(map_new(kind, wc::PlusDm::new(p(0)?))?))),
1461        "ProfileShape" => Ok(Box::new(CandleIn(map_new(
1462            kind,
1463            wc::ProfileShape::new(p(0)?, p(1)?),
1464        )?))),
1465        "ProjectionOscillator" => Ok(Box::new(CandleIn(map_new(
1466            kind,
1467            wc::ProjectionOscillator::new(p(0)?),
1468        )?))),
1469        "Psar" => Ok(Box::new(CandleIn(map_new(
1470            kind,
1471            wc::Psar::new(
1472                float_param(params, 0, kind)?,
1473                float_param(params, 1, kind)?,
1474                float_param(params, 2, kind)?,
1475            ),
1476        )?))),
1477        "Pvi" => Ok(Box::new(CandleIn(wc::Pvi::new()))),
1478        "Qstick" => Ok(Box::new(CandleIn(map_new(kind, wc::Qstick::new(p(0)?))?))),
1479        "RectangleRange" => Ok(Box::new(CandleIn(wc::RectangleRange::new()))),
1480        "RickshawMan" => Ok(Box::new(CandleIn(wc::RickshawMan::new()))),
1481        "RisingThreeMethods" => Ok(Box::new(CandleIn(wc::RisingThreeMethods::new()))),
1482        "RogersSatchellVolatility" => Ok(Box::new(CandleIn(map_new(
1483            kind,
1484            wc::RogersSatchellVolatility::new(p(0)?, p(1)?),
1485        )?))),
1486        "RollingVwap" => Ok(Box::new(CandleIn(map_new(
1487            kind,
1488            wc::RollingVwap::new(p(0)?),
1489        )?))),
1490        "Rvi" => Ok(Box::new(CandleIn(map_new(kind, wc::Rvi::new(p(0)?))?))),
1491        "SarExt" => Ok(Box::new(CandleIn(map_new(
1492            kind,
1493            wc::SarExt::new(
1494                float_param(params, 0, kind)?,
1495                float_param(params, 1, kind)?,
1496                float_param(params, 2, kind)?,
1497                float_param(params, 3, kind)?,
1498                float_param(params, 4, kind)?,
1499                float_param(params, 5, kind)?,
1500                float_param(params, 6, kind)?,
1501                float_param(params, 7, kind)?,
1502            ),
1503        )?))),
1504        "SeasonalZScore" => Ok(Box::new(CandleIn(wc::SeasonalZScore::new(i32_param(
1505            params, 0, kind,
1506        )?)))),
1507        "SeparatingLines" => Ok(Box::new(CandleIn(wc::SeparatingLines::new()))),
1508        "SessionVwap" => Ok(Box::new(CandleIn(wc::SessionVwap::new(i32_param(
1509            params, 0, kind,
1510        )?)))),
1511        "Shark" => Ok(Box::new(CandleIn(wc::Shark::new()))),
1512        "ShootingStar" => Ok(Box::new(CandleIn(wc::ShootingStar::new()))),
1513        "ShortLine" => Ok(Box::new(CandleIn(wc::ShortLine::new()))),
1514        "SinglePrints" => Ok(Box::new(CandleIn(map_new(
1515            kind,
1516            wc::SinglePrints::new(p(0)?, p(1)?),
1517        )?))),
1518        "Smi" => Ok(Box::new(CandleIn(map_new(
1519            kind,
1520            wc::Smi::new(p(0)?, p(1)?, p(2)?),
1521        )?))),
1522        "SpinningTop" => Ok(Box::new(CandleIn(wc::SpinningTop::new()))),
1523        "StalledPattern" => Ok(Box::new(CandleIn(wc::StalledPattern::new()))),
1524        "StickSandwich" => Ok(Box::new(CandleIn(wc::StickSandwich::new()))),
1525        "StochasticCci" => Ok(Box::new(CandleIn(map_new(
1526            kind,
1527            wc::StochasticCci::new(p(0)?),
1528        )?))),
1529        "Takuri" => Ok(Box::new(CandleIn(wc::Takuri::new()))),
1530        "TasukiGap" => Ok(Box::new(CandleIn(wc::TasukiGap::new()))),
1531        "TdCamouflage" => Ok(Box::new(CandleIn(wc::TdCamouflage::new()))),
1532        "TdClop" => Ok(Box::new(CandleIn(wc::TdClop::new()))),
1533        "TdClopwin" => Ok(Box::new(CandleIn(wc::TdClopwin::new()))),
1534        "TdCombo" => Ok(Box::new(CandleIn(map_new(
1535            kind,
1536            wc::TdCombo::new(p(0)?, p(1)?, p(2)?, p(3)?),
1537        )?))),
1538        "TdCountdown" => Ok(Box::new(CandleIn(map_new(
1539            kind,
1540            wc::TdCountdown::new(p(0)?, p(1)?, p(2)?, p(3)?),
1541        )?))),
1542        "TdDWave" => Ok(Box::new(CandleIn(map_new(kind, wc::TdDWave::new(p(0)?))?))),
1543        "TdDeMarker" => Ok(Box::new(CandleIn(map_new(
1544            kind,
1545            wc::TdDeMarker::new(p(0)?),
1546        )?))),
1547        "TdDifferential" => Ok(Box::new(CandleIn(wc::TdDifferential::new()))),
1548        "TdOpen" => Ok(Box::new(CandleIn(wc::TdOpen::new()))),
1549        "TdPressure" => Ok(Box::new(CandleIn(map_new(
1550            kind,
1551            wc::TdPressure::new(p(0)?),
1552        )?))),
1553        "TdPropulsion" => Ok(Box::new(CandleIn(wc::TdPropulsion::new()))),
1554        "TdRei" => Ok(Box::new(CandleIn(map_new(kind, wc::TdRei::new(p(0)?))?))),
1555        "TdSetup" => Ok(Box::new(CandleIn(map_new(
1556            kind,
1557            wc::TdSetup::new(p(0)?, p(1)?),
1558        )?))),
1559        "TdTrap" => Ok(Box::new(CandleIn(wc::TdTrap::new()))),
1560        "ThreeDrives" => Ok(Box::new(CandleIn(wc::ThreeDrives::new()))),
1561        "ThreeInside" => Ok(Box::new(CandleIn(wc::ThreeInside::new()))),
1562        "ThreeLineBreak" => Ok(Box::new(CandleIn(map_new(
1563            kind,
1564            wc::ThreeLineBreak::new(p(0)?),
1565        )?))),
1566        "ThreeLineStrike" => Ok(Box::new(CandleIn(wc::ThreeLineStrike::new()))),
1567        "ThreeOutside" => Ok(Box::new(CandleIn(wc::ThreeOutside::new()))),
1568        "ThreeSoldiersOrCrows" => Ok(Box::new(CandleIn(wc::ThreeSoldiersOrCrows::new()))),
1569        "ThreeStarsInSouth" => Ok(Box::new(CandleIn(wc::ThreeStarsInSouth::new()))),
1570        "Thrusting" => Ok(Box::new(CandleIn(wc::Thrusting::new()))),
1571        "TimeBasedStop" => Ok(Box::new(CandleIn(map_new(
1572            kind,
1573            wc::TimeBasedStop::new(p(0)?),
1574        )?))),
1575        "TowerTopBottom" => Ok(Box::new(CandleIn(wc::TowerTopBottom::new()))),
1576        "TradeVolumeIndex" => Ok(Box::new(CandleIn(map_new(
1577            kind,
1578            wc::TradeVolumeIndex::new(float_param(params, 0, kind)?),
1579        )?))),
1580        "Triangle" => Ok(Box::new(CandleIn(wc::Triangle::new()))),
1581        "TripleTopBottom" => Ok(Box::new(CandleIn(wc::TripleTopBottom::new()))),
1582        "Tristar" => Ok(Box::new(CandleIn(wc::Tristar::new()))),
1583        "TrueRange" => Ok(Box::new(CandleIn(wc::TrueRange::new()))),
1584        "Tsv" => Ok(Box::new(CandleIn(map_new(kind, wc::Tsv::new(p(0)?))?))),
1585        "TtmTrend" => Ok(Box::new(CandleIn(map_new(kind, wc::TtmTrend::new(p(0)?))?))),
1586        "TurnOfMonth" => Ok(Box::new(CandleIn(map_new(
1587            kind,
1588            wc::TurnOfMonth::new(
1589                u32_param(params, 0, kind)?,
1590                u32_param(params, 1, kind)?,
1591                i32_param(params, 2, kind)?,
1592            ),
1593        )?))),
1594        "Tweezer" => Ok(Box::new(CandleIn(wc::Tweezer::new()))),
1595        "TwiggsMoneyFlow" => Ok(Box::new(CandleIn(map_new(
1596            kind,
1597            wc::TwiggsMoneyFlow::new(p(0)?),
1598        )?))),
1599        "TwoCrows" => Ok(Box::new(CandleIn(wc::TwoCrows::new()))),
1600        "TypicalPrice" => Ok(Box::new(CandleIn(wc::TypicalPrice::new()))),
1601        "UltimateOscillator" => Ok(Box::new(CandleIn(map_new(
1602            kind,
1603            wc::UltimateOscillator::new(p(0)?, p(1)?, p(2)?),
1604        )?))),
1605        "UniqueThreeRiver" => Ok(Box::new(CandleIn(wc::UniqueThreeRiver::new()))),
1606        "UpsideGapThreeMethods" => Ok(Box::new(CandleIn(wc::UpsideGapThreeMethods::new()))),
1607        "UpsideGapTwoCrows" => Ok(Box::new(CandleIn(wc::UpsideGapTwoCrows::new()))),
1608        "VolatilityRatio" => Ok(Box::new(CandleIn(map_new(
1609            kind,
1610            wc::VolatilityRatio::new(p(0)?),
1611        )?))),
1612        "VoltyStop" => Ok(Box::new(CandleIn(map_new(
1613            kind,
1614            wc::VoltyStop::new(p(0)?, float_param(params, 1, kind)?),
1615        )?))),
1616        "VolumeOscillator" => Ok(Box::new(CandleIn(map_new(
1617            kind,
1618            wc::VolumeOscillator::new(p(0)?, p(1)?),
1619        )?))),
1620        "VolumePriceTrend" => Ok(Box::new(CandleIn(wc::VolumePriceTrend::new()))),
1621        "VolumeRsi" => Ok(Box::new(CandleIn(map_new(
1622            kind,
1623            wc::VolumeRsi::new(p(0)?),
1624        )?))),
1625        "Vwap" => Ok(Box::new(CandleIn(wc::Vwap::new()))),
1626        "Vwma" => Ok(Box::new(CandleIn(map_new(kind, wc::Vwma::new(p(0)?))?))),
1627        "Vzo" => Ok(Box::new(CandleIn(map_new(kind, wc::Vzo::new(p(0)?))?))),
1628        "Wad" => Ok(Box::new(CandleIn(wc::Wad::new()))),
1629        "Wedge" => Ok(Box::new(CandleIn(wc::Wedge::new()))),
1630        "WeightedClose" => Ok(Box::new(CandleIn(wc::WeightedClose::new()))),
1631        "WickRatio" => Ok(Box::new(CandleIn(wc::WickRatio::new()))),
1632        "WilliamsR" => Ok(Box::new(CandleIn(map_new(
1633            kind,
1634            wc::WilliamsR::new(p(0)?),
1635        )?))),
1636        "YangZhangVolatility" => Ok(Box::new(CandleIn(map_new(
1637            kind,
1638            wc::YangZhangVolatility::new(p(0)?, p(1)?),
1639        )?))),
1640        "YoyoExit" => Ok(Box::new(CandleIn(map_new(
1641            kind,
1642            wc::YoyoExit::new(p(0)?, float_param(params, 1, kind)?),
1643        )?))),
1644        // --- multi-output indicators (named fields) ---
1645        "AccelerationBands" => Ok(Box::new(AccelerationBandsWrap::wrap(map_new(
1646            kind,
1647            wc::AccelerationBands::new(p(0)?, float_param(params, 1, kind)?),
1648        )?))),
1649        "Adx" => Ok(Box::new(AdxWrap::wrap(map_new(kind, wc::Adx::new(p(0)?))?))),
1650        "Alligator" => Ok(Box::new(AlligatorWrap::wrap(map_new(
1651            kind,
1652            wc::Alligator::new(p(0)?, p(1)?, p(2)?),
1653        )?))),
1654        "AndrewsPitchfork" => Ok(Box::new(AndrewsPitchforkWrap::wrap(map_new(
1655            kind,
1656            wc::AndrewsPitchfork::new(p(0)?),
1657        )?))),
1658        "Aroon" => Ok(Box::new(AroonWrap::wrap(map_new(
1659            kind,
1660            wc::Aroon::new(p(0)?),
1661        )?))),
1662        "AtrBands" => Ok(Box::new(AtrBandsWrap::wrap(map_new(
1663            kind,
1664            wc::AtrBands::new(p(0)?, float_param(params, 1, kind)?),
1665        )?))),
1666        "AtrRatchet" => Ok(Box::new(AtrRatchetWrap::wrap(map_new(
1667            kind,
1668            wc::AtrRatchet::new(
1669                p(0)?,
1670                float_param(params, 1, kind)?,
1671                float_param(params, 2, kind)?,
1672            ),
1673        )?))),
1674        "AutoFib" => Ok(Box::new(AutoFibWrap::wrap(wc::AutoFib::new()))),
1675        "BollingerBands" => Ok(Box::new(BollingerBandsWrap::wrap(map_new(
1676            kind,
1677            wc::BollingerBands::new(p(0)?, float_param(params, 1, kind)?),
1678        )?))),
1679        "BomarBands" => Ok(Box::new(BomarBandsWrap::wrap(map_new(
1680            kind,
1681            wc::BomarBands::new(p(0)?, float_param(params, 1, kind)?),
1682        )?))),
1683        "Camarilla" => Ok(Box::new(CamarillaWrap::wrap(wc::Camarilla::new()))),
1684        "CandleVolume" => Ok(Box::new(CandleVolumeWrap::wrap(map_new(
1685            kind,
1686            wc::CandleVolume::new(p(0)?),
1687        )?))),
1688        "CentralPivotRange" => Ok(Box::new(CentralPivotRangeWrap::wrap(
1689            wc::CentralPivotRange::new(),
1690        ))),
1691        "ChandeKrollStop" => Ok(Box::new(ChandeKrollStopWrap::wrap(map_new(
1692            kind,
1693            wc::ChandeKrollStop::new(p(0)?, float_param(params, 1, kind)?, p(2)?),
1694        )?))),
1695        "ChandelierExit" => Ok(Box::new(ChandelierExitWrap::wrap(map_new(
1696            kind,
1697            wc::ChandelierExit::new(p(0)?, float_param(params, 1, kind)?),
1698        )?))),
1699        "ClassicPivots" => Ok(Box::new(ClassicPivotsWrap::wrap(wc::ClassicPivots::new()))),
1700        "CompositeProfile" => Ok(Box::new(CompositeProfileWrap::wrap(map_new(
1701            kind,
1702            wc::CompositeProfile::new(p(0)?, p(1)?, float_param(params, 2, kind)?),
1703        )?))),
1704        "DemarkPivots" => Ok(Box::new(DemarkPivotsWrap::wrap(wc::DemarkPivots::new()))),
1705        "Donchian" => Ok(Box::new(DonchianWrap::wrap(map_new(
1706            kind,
1707            wc::Donchian::new(p(0)?),
1708        )?))),
1709        "DonchianStop" => Ok(Box::new(DonchianStopWrap::wrap(map_new(
1710            kind,
1711            wc::DonchianStop::new(p(0)?),
1712        )?))),
1713        "DoubleBollinger" => Ok(Box::new(DoubleBollingerWrap::wrap(map_new(
1714            kind,
1715            wc::DoubleBollinger::new(
1716                p(0)?,
1717                float_param(params, 1, kind)?,
1718                float_param(params, 2, kind)?,
1719            ),
1720        )?))),
1721        "ElderRay" => Ok(Box::new(ElderRayWrap::wrap(map_new(
1722            kind,
1723            wc::ElderRay::new(p(0)?),
1724        )?))),
1725        "ElderSafeZone" => Ok(Box::new(ElderSafeZoneWrap::wrap(map_new(
1726            kind,
1727            wc::ElderSafeZone::new(p(0)?, float_param(params, 1, kind)?),
1728        )?))),
1729        "Equivolume" => Ok(Box::new(EquivolumeWrap::wrap(map_new(
1730            kind,
1731            wc::Equivolume::new(p(0)?),
1732        )?))),
1733        "FibArcs" => Ok(Box::new(FibArcsWrap::wrap(wc::FibArcs::new()))),
1734        "FibChannel" => Ok(Box::new(FibChannelWrap::wrap(wc::FibChannel::new()))),
1735        "FibConfluence" => Ok(Box::new(FibConfluenceWrap::wrap(wc::FibConfluence::new()))),
1736        "FibExtension" => Ok(Box::new(FibExtensionWrap::wrap(wc::FibExtension::new()))),
1737        "FibFan" => Ok(Box::new(FibFanWrap::wrap(wc::FibFan::new()))),
1738        "FibProjection" => Ok(Box::new(FibProjectionWrap::wrap(wc::FibProjection::new()))),
1739        "FibRetracement" => Ok(Box::new(
1740            FibRetracementWrap::wrap(wc::FibRetracement::new()),
1741        )),
1742        "FibTimeZones" => Ok(Box::new(FibTimeZonesWrap::wrap(wc::FibTimeZones::new()))),
1743        "FibonacciPivots" => Ok(Box::new(FibonacciPivotsWrap::wrap(
1744            wc::FibonacciPivots::new(),
1745        ))),
1746        "FractalChaosBands" => Ok(Box::new(FractalChaosBandsWrap::wrap(map_new(
1747            kind,
1748            wc::FractalChaosBands::new(p(0)?),
1749        )?))),
1750        "GatorOscillator" => Ok(Box::new(GatorOscillatorWrap::wrap(map_new(
1751            kind,
1752            wc::GatorOscillator::new(p(0)?, p(1)?, p(2)?),
1753        )?))),
1754        "GoldenPocket" => Ok(Box::new(GoldenPocketWrap::wrap(wc::GoldenPocket::new()))),
1755        "HeikinAshi" => Ok(Box::new(HeikinAshiWrap::wrap(wc::HeikinAshi::new()))),
1756        "HighLowVolumeNodes" => Ok(Box::new(HighLowVolumeNodesWrap::wrap(map_new(
1757            kind,
1758            wc::HighLowVolumeNodes::new(p(0)?, p(1)?),
1759        )?))),
1760        "HtPhasor" => Ok(Box::new(HtPhasorWrap::wrap(wc::HtPhasor::new()))),
1761        "HurstChannel" => Ok(Box::new(HurstChannelWrap::wrap(map_new(
1762            kind,
1763            wc::HurstChannel::new(p(0)?, float_param(params, 1, kind)?),
1764        )?))),
1765        "InitialBalance" => Ok(Box::new(InitialBalanceWrap::wrap(map_new(
1766            kind,
1767            wc::InitialBalance::new(p(0)?),
1768        )?))),
1769        "KaseDevStop" => Ok(Box::new(KaseDevStopWrap::wrap(map_new(
1770            kind,
1771            wc::KaseDevStop::new(p(0)?, float_param(params, 1, kind)?),
1772        )?))),
1773        "KasePermissionStochastic" => Ok(Box::new(KasePermissionStochasticWrap::wrap(map_new(
1774            kind,
1775            wc::KasePermissionStochastic::new(p(0)?, p(1)?),
1776        )?))),
1777        "Keltner" => Ok(Box::new(KeltnerWrap::wrap(map_new(
1778            kind,
1779            wc::Keltner::new(p(0)?, p(1)?, float_param(params, 2, kind)?),
1780        )?))),
1781        "Kst" => Ok(Box::new(KstWrap::wrap(map_new(
1782            kind,
1783            wc::Kst::new(
1784                p(0)?,
1785                p(1)?,
1786                p(2)?,
1787                p(3)?,
1788                p(4)?,
1789                p(5)?,
1790                p(6)?,
1791                p(7)?,
1792                p(8)?,
1793            ),
1794        )?))),
1795        "LinRegChannel" => Ok(Box::new(LinRegChannelWrap::wrap(map_new(
1796            kind,
1797            wc::LinRegChannel::new(p(0)?, float_param(params, 1, kind)?),
1798        )?))),
1799        "MaEnvelope" => Ok(Box::new(MaEnvelopeWrap::wrap(map_new(
1800            kind,
1801            wc::MaEnvelope::new(p(0)?, float_param(params, 1, kind)?),
1802        )?))),
1803        "MacdFix" => Ok(Box::new(MacdFixWrap::wrap(map_new(
1804            kind,
1805            wc::MacdFix::new(p(0)?),
1806        )?))),
1807        "MacdIndicator" => Ok(Box::new(MacdIndicatorWrap::wrap(map_new(
1808            kind,
1809            wc::MacdIndicator::new(p(0)?, p(1)?, p(2)?),
1810        )?))),
1811        "Mama" => Ok(Box::new(MamaWrap::wrap(map_new(
1812            kind,
1813            wc::Mama::new(float_param(params, 0, kind)?, float_param(params, 1, kind)?),
1814        )?))),
1815        "MedianChannel" => Ok(Box::new(MedianChannelWrap::wrap(map_new(
1816            kind,
1817            wc::MedianChannel::new(p(0)?, float_param(params, 1, kind)?),
1818        )?))),
1819        "ModifiedMaStop" => Ok(Box::new(ModifiedMaStopWrap::wrap(map_new(
1820            kind,
1821            wc::ModifiedMaStop::new(p(0)?),
1822        )?))),
1823        "MurreyMathLines" => Ok(Box::new(MurreyMathLinesWrap::wrap(map_new(
1824            kind,
1825            wc::MurreyMathLines::new(p(0)?),
1826        )?))),
1827        "Nrtr" => Ok(Box::new(NrtrWrap::wrap(map_new(
1828            kind,
1829            wc::Nrtr::new(float_param(params, 0, kind)?),
1830        )?))),
1831        "OpeningRange" => Ok(Box::new(OpeningRangeWrap::wrap(map_new(
1832            kind,
1833            wc::OpeningRange::new(p(0)?),
1834        )?))),
1835        "OvernightIntradayReturn" => Ok(Box::new(OvernightIntradayReturnWrap::wrap(
1836            wc::OvernightIntradayReturn::new(i32_param(params, 0, kind)?),
1837        ))),
1838        "ProjectionBands" => Ok(Box::new(ProjectionBandsWrap::wrap(map_new(
1839            kind,
1840            wc::ProjectionBands::new(p(0)?),
1841        )?))),
1842        "Qqe" => Ok(Box::new(QqeWrap::wrap(map_new(
1843            kind,
1844            wc::Qqe::new(p(0)?, p(1)?, float_param(params, 2, kind)?),
1845        )?))),
1846        "QuartileBands" => Ok(Box::new(QuartileBandsWrap::wrap(map_new(
1847            kind,
1848            wc::QuartileBands::new(p(0)?),
1849        )?))),
1850        "Rwi" => Ok(Box::new(RwiWrap::wrap(map_new(kind, wc::Rwi::new(p(0)?))?))),
1851        "SessionHighLow" => Ok(Box::new(SessionHighLowWrap::wrap(wc::SessionHighLow::new(
1852            i32_param(params, 0, kind)?,
1853        )))),
1854        "SessionRange" => Ok(Box::new(SessionRangeWrap::wrap(wc::SessionRange::new(
1855            i32_param(params, 0, kind)?,
1856        )))),
1857        "SmoothedHeikinAshi" => Ok(Box::new(SmoothedHeikinAshiWrap::wrap(map_new(
1858            kind,
1859            wc::SmoothedHeikinAshi::new(p(0)?),
1860        )?))),
1861        "StandardErrorBands" => Ok(Box::new(StandardErrorBandsWrap::wrap(map_new(
1862            kind,
1863            wc::StandardErrorBands::new(p(0)?, float_param(params, 1, kind)?),
1864        )?))),
1865        "StarcBands" => Ok(Box::new(StarcBandsWrap::wrap(map_new(
1866            kind,
1867            wc::StarcBands::new(p(0)?, p(1)?, float_param(params, 2, kind)?),
1868        )?))),
1869        "Stochastic" => Ok(Box::new(StochasticWrap::wrap(map_new(
1870            kind,
1871            wc::Stochastic::new(p(0)?, p(1)?),
1872        )?))),
1873        "SuperTrend" => Ok(Box::new(SuperTrendWrap::wrap(map_new(
1874            kind,
1875            wc::SuperTrend::new(p(0)?, float_param(params, 1, kind)?),
1876        )?))),
1877        "TdLines" => Ok(Box::new(TdLinesWrap::wrap(map_new(
1878            kind,
1879            wc::TdLines::new(p(0)?, p(1)?),
1880        )?))),
1881        "TdMovingAverage" => Ok(Box::new(TdMovingAverageWrap::wrap(map_new(
1882            kind,
1883            wc::TdMovingAverage::new(p(0)?, p(1)?),
1884        )?))),
1885        "TdRangeProjection" => Ok(Box::new(TdRangeProjectionWrap::wrap(
1886            wc::TdRangeProjection::new(),
1887        ))),
1888        "TdRiskLevel" => Ok(Box::new(TdRiskLevelWrap::wrap(map_new(
1889            kind,
1890            wc::TdRiskLevel::new(p(0)?, p(1)?),
1891        )?))),
1892        "TdSequential" => Ok(Box::new(TdSequentialWrap::wrap(map_new(
1893            kind,
1894            wc::TdSequential::new(p(0)?, p(1)?, p(2)?, p(3)?),
1895        )?))),
1896        "TpoProfile" => Ok(Box::new(TpoProfileWrap::wrap(map_new(
1897            kind,
1898            wc::TpoProfile::new(p(0)?, p(1)?),
1899        )?))),
1900        "TtmSqueeze" => Ok(Box::new(TtmSqueezeWrap::wrap(map_new(
1901            kind,
1902            wc::TtmSqueeze::new(
1903                p(0)?,
1904                float_param(params, 1, kind)?,
1905                float_param(params, 2, kind)?,
1906            ),
1907        )?))),
1908        "ValueArea" => Ok(Box::new(ValueAreaWrap::wrap(map_new(
1909            kind,
1910            wc::ValueArea::new(p(0)?, p(1)?, float_param(params, 2, kind)?),
1911        )?))),
1912        "VolatilityCone" => Ok(Box::new(VolatilityConeWrap::wrap(map_new(
1913            kind,
1914            wc::VolatilityCone::new(p(0)?, p(1)?),
1915        )?))),
1916        "VolumeProfile" => Ok(Box::new(VolumeProfileWrap::wrap(map_new(
1917            kind,
1918            wc::VolumeProfile::new(p(0)?, p(1)?),
1919        )?))),
1920        "VolumeWeightedMacd" => Ok(Box::new(VolumeWeightedMacdWrap::wrap(map_new(
1921            kind,
1922            wc::VolumeWeightedMacd::new(p(0)?, p(1)?, p(2)?),
1923        )?))),
1924        "VolumeWeightedSr" => Ok(Box::new(VolumeWeightedSrWrap::wrap(map_new(
1925            kind,
1926            wc::VolumeWeightedSr::new(p(0)?),
1927        )?))),
1928        "Vortex" => Ok(Box::new(VortexWrap::wrap(map_new(
1929            kind,
1930            wc::Vortex::new(p(0)?),
1931        )?))),
1932        "VwapStdDevBands" => Ok(Box::new(VwapStdDevBandsWrap::wrap(map_new(
1933            kind,
1934            wc::VwapStdDevBands::new(float_param(params, 0, kind)?),
1935        )?))),
1936        "WaveTrend" => Ok(Box::new(WaveTrendWrap::wrap(map_new(
1937            kind,
1938            wc::WaveTrend::new(p(0)?, p(1)?, p(2)?),
1939        )?))),
1940        "WoodiePivots" => Ok(Box::new(WoodiePivotsWrap::wrap(wc::WoodiePivots::new()))),
1941        "ZeroLagMacd" => Ok(Box::new(ZeroLagMacdWrap::wrap(map_new(
1942            kind,
1943            wc::ZeroLagMacd::new(p(0)?, p(1)?, p(2)?),
1944        )?))),
1945        "ZigZag" => Ok(Box::new(ZigZagWrap::wrap(map_new(
1946            kind,
1947            wc::ZigZag::new(float_param(params, 0, kind)?),
1948        )?))),
1949        // --- pairwise indicators, fed (close, reference_close) ---
1950        "Alpha" => Ok(Box::new(PairClose(map_new(
1951            kind,
1952            wc::Alpha::new(p(0)?, float_param(params, 1, kind)?),
1953        )?))),
1954        "Beta" => Ok(Box::new(PairClose(map_new(kind, wc::Beta::new(p(0)?))?))),
1955        "BetaNeutralSpread" => Ok(Box::new(PairClose(map_new(
1956            kind,
1957            wc::BetaNeutralSpread::new(p(0)?),
1958        )?))),
1959        "DistanceSsd" => Ok(Box::new(PairClose(map_new(
1960            kind,
1961            wc::DistanceSsd::new(p(0)?),
1962        )?))),
1963        "GrangerCausality" => Ok(Box::new(PairClose(map_new(
1964            kind,
1965            wc::GrangerCausality::new(p(0)?, p(1)?),
1966        )?))),
1967        "HasbrouckInformationShare" => Ok(Box::new(PairClose(map_new(
1968            kind,
1969            wc::HasbrouckInformationShare::new(p(0)?),
1970        )?))),
1971        "InformationRatio" => Ok(Box::new(PairClose(map_new(
1972            kind,
1973            wc::InformationRatio::new(p(0)?),
1974        )?))),
1975        "KendallTau" => Ok(Box::new(PairClose(map_new(
1976            kind,
1977            wc::KendallTau::new(p(0)?),
1978        )?))),
1979        "OuHalfLife" => Ok(Box::new(PairClose(map_new(
1980            kind,
1981            wc::OuHalfLife::new(p(0)?),
1982        )?))),
1983        "PairSpreadZScore" => Ok(Box::new(PairClose(map_new(
1984            kind,
1985            wc::PairSpreadZScore::new(p(0)?, p(1)?),
1986        )?))),
1987        "PairwiseBeta" => Ok(Box::new(PairClose(map_new(
1988            kind,
1989            wc::PairwiseBeta::new(p(0)?),
1990        )?))),
1991        "PearsonCorrelation" => Ok(Box::new(PairClose(map_new(
1992            kind,
1993            wc::PearsonCorrelation::new(p(0)?),
1994        )?))),
1995        "RollingCorrelation" => Ok(Box::new(PairClose(map_new(
1996            kind,
1997            wc::RollingCorrelation::new(p(0)?),
1998        )?))),
1999        "RollingCovariance" => Ok(Box::new(PairClose(map_new(
2000            kind,
2001            wc::RollingCovariance::new(p(0)?),
2002        )?))),
2003        "SpearmanCorrelation" => Ok(Box::new(PairClose(map_new(
2004            kind,
2005            wc::SpearmanCorrelation::new(p(0)?),
2006        )?))),
2007        "SpreadAr1Coefficient" => Ok(Box::new(PairClose(map_new(
2008            kind,
2009            wc::SpreadAr1Coefficient::new(p(0)?),
2010        )?))),
2011        "SpreadHurst" => Ok(Box::new(PairClose(map_new(
2012            kind,
2013            wc::SpreadHurst::new(p(0)?),
2014        )?))),
2015        "TreynorRatio" => Ok(Box::new(PairClose(map_new(
2016            kind,
2017            wc::TreynorRatio::new(p(0)?, float_param(params, 1, kind)?),
2018        )?))),
2019        "VarianceRatio" => Ok(Box::new(PairClose(map_new(
2020            kind,
2021            wc::VarianceRatio::new(p(0)?, p(1)?),
2022        )?))),
2023        // --- pairwise multi-output indicators ---
2024        "Cointegration" => Ok(Box::new(CointegrationWrap::wrap(map_new(
2025            kind,
2026            wc::Cointegration::new(p(0)?, p(1)?),
2027        )?))),
2028        "KalmanHedgeRatio" => Ok(Box::new(KalmanHedgeRatioWrap::wrap(map_new(
2029            kind,
2030            wc::KalmanHedgeRatio::new(float_param(params, 0, kind)?, float_param(params, 1, kind)?),
2031        )?))),
2032        "LeadLagCrossCorrelation" => Ok(Box::new(LeadLagCrossCorrelationWrap::wrap(map_new(
2033            kind,
2034            wc::LeadLagCrossCorrelation::new(p(0)?, p(1)?),
2035        )?))),
2036        "RelativeStrengthAB" => Ok(Box::new(RelativeStrengthABWrap::wrap(map_new(
2037            kind,
2038            wc::RelativeStrengthAB::new(p(0)?, p(1)?),
2039        )?))),
2040        "SpreadBollingerBands" => Ok(Box::new(SpreadBollingerBandsWrap::wrap(map_new(
2041            kind,
2042            wc::SpreadBollingerBands::new(p(0)?, float_param(params, 1, kind)?),
2043        )?))),
2044        // --- derivatives indicators, fed the bar's DerivativesTick ---
2045        "CalendarSpread" => Ok(Box::new(DerivativesIn(wc::CalendarSpread::new()))),
2046        "EstimatedLeverageRatio" => Ok(Box::new(DerivativesIn(wc::EstimatedLeverageRatio::new()))),
2047        "FundingBasis" => Ok(Box::new(DerivativesIn(wc::FundingBasis::new()))),
2048        "FundingImpliedApr" => Ok(Box::new(DerivativesIn(map_new(
2049            kind,
2050            wc::FundingImpliedApr::new(float_param(params, 0, kind)?),
2051        )?))),
2052        "FundingRate" => Ok(Box::new(DerivativesIn(wc::FundingRate::new()))),
2053        "FundingRateMean" => Ok(Box::new(DerivativesIn(map_new(
2054            kind,
2055            wc::FundingRateMean::new(p(0)?),
2056        )?))),
2057        "FundingRateZScore" => Ok(Box::new(DerivativesIn(map_new(
2058            kind,
2059            wc::FundingRateZScore::new(p(0)?),
2060        )?))),
2061        "LongShortRatio" => Ok(Box::new(DerivativesIn(wc::LongShortRatio::new()))),
2062        "OIPriceDivergence" => Ok(Box::new(DerivativesIn(map_new(
2063            kind,
2064            wc::OIPriceDivergence::new(p(0)?),
2065        )?))),
2066        "OIWeighted" => Ok(Box::new(DerivativesIn(wc::OIWeighted::new()))),
2067        "OiToVolumeRatio" => Ok(Box::new(DerivativesIn(wc::OiToVolumeRatio::new()))),
2068        "OpenInterestDelta" => Ok(Box::new(DerivativesIn(wc::OpenInterestDelta::new()))),
2069        "OpenInterestMomentum" => Ok(Box::new(DerivativesIn(map_new(
2070            kind,
2071            wc::OpenInterestMomentum::new(p(0)?),
2072        )?))),
2073        "PerpetualPremiumIndex" => Ok(Box::new(DerivativesIn(wc::PerpetualPremiumIndex::new()))),
2074        "TakerBuySellRatio" => Ok(Box::new(DerivativesIn(wc::TakerBuySellRatio::new()))),
2075        "TermStructureBasis" => Ok(Box::new(DerivativesIn(wc::TermStructureBasis::new()))),
2076        "LiquidationFeatures" => Ok(Box::new(LiquidationFeaturesWrap::wrap(
2077            wc::LiquidationFeatures::new(),
2078        ))),
2079        // --- order-book indicators, fed the bar's OrderBook ---
2080        "DepthSlope" => Ok(Box::new(OrderBookIn(wc::DepthSlope::new()))),
2081        "Microprice" => Ok(Box::new(OrderBookIn(wc::Microprice::new()))),
2082        "OrderBookImbalanceFull" => Ok(Box::new(OrderBookIn(wc::OrderBookImbalanceFull::new()))),
2083        "OrderBookImbalanceTop1" => Ok(Box::new(OrderBookIn(wc::OrderBookImbalanceTop1::new()))),
2084        "OrderBookImbalanceTopN" => Ok(Box::new(OrderBookIn(map_new(
2085            kind,
2086            wc::OrderBookImbalanceTopN::new(p(0)?),
2087        )?))),
2088        "OrderFlowImbalance" => Ok(Box::new(OrderBookIn(map_new(
2089            kind,
2090            wc::OrderFlowImbalance::new(p(0)?),
2091        )?))),
2092        "QuotedSpread" => Ok(Box::new(OrderBookIn(wc::QuotedSpread::new()))),
2093        // --- trade-flow indicators, fed the bar's trades ---
2094        "AmihudIlliquidity" => Ok(Box::new(TradeIn(map_new(
2095            kind,
2096            wc::AmihudIlliquidity::new(p(0)?),
2097        )?))),
2098        "CumulativeVolumeDelta" => Ok(Box::new(TradeIn(wc::CumulativeVolumeDelta::new()))),
2099        "Pin" => Ok(Box::new(TradeIn(map_new(kind, wc::Pin::new(p(0)?))?))),
2100        "RollMeasure" => Ok(Box::new(TradeIn(map_new(
2101            kind,
2102            wc::RollMeasure::new(p(0)?),
2103        )?))),
2104        "SignedVolume" => Ok(Box::new(TradeIn(wc::SignedVolume::new()))),
2105        "TradeImbalance" => Ok(Box::new(TradeIn(map_new(
2106            kind,
2107            wc::TradeImbalance::new(p(0)?),
2108        )?))),
2109        "TradeSignAutocorrelation" => Ok(Box::new(TradeIn(map_new(
2110            kind,
2111            wc::TradeSignAutocorrelation::new(p(0)?),
2112        )?))),
2113        "Vpin" => Ok(Box::new(TradeIn(map_new(
2114            kind,
2115            wc::Vpin::new(float_param(params, 0, kind)?, p(1)?),
2116        )?))),
2117        // --- trade-quote indicators, fed trades + the mid ---
2118        "EffectiveSpread" => Ok(Box::new(TradeQuoteIn(wc::EffectiveSpread::new()))),
2119        "KylesLambda" => Ok(Box::new(TradeQuoteIn(map_new(
2120            kind,
2121            wc::KylesLambda::new(p(0)?),
2122        )?))),
2123        "RealizedSpread" => Ok(Box::new(TradeQuoteIn(map_new(
2124            kind,
2125            wc::RealizedSpread::new(p(0)?),
2126        )?))),
2127        // --- market-breadth indicators, fed the cross-section ---
2128        "AbsoluteBreadthIndex" => Ok(Box::new(CrossSectionIn(wc::AbsoluteBreadthIndex::new()))),
2129        "AdVolumeLine" => Ok(Box::new(CrossSectionIn(wc::AdVolumeLine::new()))),
2130        "AdvanceDecline" => Ok(Box::new(CrossSectionIn(wc::AdvanceDecline::new()))),
2131        "AdvanceDeclineRatio" => Ok(Box::new(CrossSectionIn(wc::AdvanceDeclineRatio::new()))),
2132        "BreadthThrust" => Ok(Box::new(CrossSectionIn(map_new(
2133            kind,
2134            wc::BreadthThrust::new(p(0)?),
2135        )?))),
2136        "BullishPercentIndex" => Ok(Box::new(CrossSectionIn(wc::BullishPercentIndex::new()))),
2137        "CumulativeVolumeIndex" => Ok(Box::new(CrossSectionIn(wc::CumulativeVolumeIndex::new()))),
2138        "HighLowIndex" => Ok(Box::new(CrossSectionIn(map_new(
2139            kind,
2140            wc::HighLowIndex::new(p(0)?),
2141        )?))),
2142        "McClellanOscillator" => Ok(Box::new(CrossSectionIn(wc::McClellanOscillator::new()))),
2143        "McClellanSummationIndex" => {
2144            Ok(Box::new(CrossSectionIn(wc::McClellanSummationIndex::new())))
2145        }
2146        "NewHighsNewLows" => Ok(Box::new(CrossSectionIn(wc::NewHighsNewLows::new()))),
2147        "PercentAboveMa" => Ok(Box::new(CrossSectionIn(wc::PercentAboveMa::new()))),
2148        "TickIndex" => Ok(Box::new(CrossSectionIn(wc::TickIndex::new()))),
2149        "Trin" => Ok(Box::new(CrossSectionIn(wc::Trin::new()))),
2150        "UpDownVolumeRatio" => Ok(Box::new(CrossSectionIn(wc::UpDownVolumeRatio::new()))),
2151        // --- friendly aliases ---
2152        "Macd" => build("MacdIndicator", params),
2153        "Bollinger" => build("BollingerBands", params),
2154        other => Err(BacktestError::UnknownIndicator(other.to_string())),
2155    }
2156}
2157
2158/// The feed family an indicator consumes, or `None` for an unknown kind.
2159///
2160/// `StrategySpec::validate` uses this to reject a spec whose declared
2161/// `feed` contradicts the indicator it names.
2162///
2163///
2164/// A lookup table with one arm per indicator: long by construction, and
2165/// with an identical body for every member of a family.
2166#[allow(clippy::too_many_lines, clippy::match_same_arms)]
2167#[must_use]
2168pub fn feed_of(kind: &str) -> Option<Feed> {
2169    match kind {
2170        "AbandonedBaby" => Some(Feed::Kline),
2171        "Abcd" => Some(Feed::Kline),
2172        "AbsoluteBreadthIndex" => Some(Feed::CrossSection),
2173        "AccelerationBands" => Some(Feed::Kline),
2174        "AcceleratorOscillator" => Some(Feed::Kline),
2175        "AdOscillator" => Some(Feed::Kline),
2176        "AdVolumeLine" => Some(Feed::CrossSection),
2177        "AdaptiveCci" => Some(Feed::Kline),
2178        "AdaptiveCycle" => Some(Feed::Kline),
2179        "AdaptiveLaguerreFilter" => Some(Feed::Kline),
2180        "AdaptiveRsi" => Some(Feed::Kline),
2181        "Adl" => Some(Feed::Kline),
2182        "AdvanceBlock" => Some(Feed::Kline),
2183        "AdvanceDecline" => Some(Feed::CrossSection),
2184        "AdvanceDeclineRatio" => Some(Feed::CrossSection),
2185        "Adx" => Some(Feed::Kline),
2186        "Adxr" => Some(Feed::Kline),
2187        "Alligator" => Some(Feed::Kline),
2188        "Alma" => Some(Feed::Kline),
2189        "Alpha" => Some(Feed::Kline),
2190        "AmihudIlliquidity" => Some(Feed::Trade),
2191        "AnchoredRsi" => Some(Feed::Kline),
2192        "AnchoredVwap" => Some(Feed::Kline),
2193        "AndrewsPitchfork" => Some(Feed::Kline),
2194        "Apo" => Some(Feed::Kline),
2195        "Aroon" => Some(Feed::Kline),
2196        "AroonOscillator" => Some(Feed::Kline),
2197        "Atr" => Some(Feed::Kline),
2198        "AtrBands" => Some(Feed::Kline),
2199        "AtrRatchet" => Some(Feed::Kline),
2200        "AtrTrailingStop" => Some(Feed::Kline),
2201        "AutoFib" => Some(Feed::Kline),
2202        "Autocorrelation" => Some(Feed::Kline),
2203        "AutocorrelationPeriodogram" => Some(Feed::Kline),
2204        "AverageDailyRange" => Some(Feed::Kline),
2205        "AverageDrawdown" => Some(Feed::Kline),
2206        "AvgPrice" => Some(Feed::Kline),
2207        "AwesomeOscillator" => Some(Feed::Kline),
2208        "AwesomeOscillatorHistogram" => Some(Feed::Kline),
2209        "BalanceOfPower" => Some(Feed::Kline),
2210        "BandpassFilter" => Some(Feed::Kline),
2211        "Bat" => Some(Feed::Kline),
2212        "BeltHold" => Some(Feed::Kline),
2213        "Beta" => Some(Feed::Kline),
2214        "BetaNeutralSpread" => Some(Feed::Kline),
2215        "BetterVolume" => Some(Feed::Kline),
2216        "BipowerVariation" => Some(Feed::Kline),
2217        "BodySizePct" => Some(Feed::Kline),
2218        "BollingerBands" => Some(Feed::Kline),
2219        "BollingerBandwidth" => Some(Feed::Kline),
2220        "BomarBands" => Some(Feed::Kline),
2221        "BreadthThrust" => Some(Feed::CrossSection),
2222        "Breakaway" => Some(Feed::Kline),
2223        "BullishPercentIndex" => Some(Feed::CrossSection),
2224        "BurkeRatio" => Some(Feed::Kline),
2225        "Butterfly" => Some(Feed::Kline),
2226        "CalendarSpread" => Some(Feed::Derivatives),
2227        "CalmarRatio" => Some(Feed::Kline),
2228        "Camarilla" => Some(Feed::Kline),
2229        "CandleVolume" => Some(Feed::Kline),
2230        "Cci" => Some(Feed::Kline),
2231        "CenterOfGravity" => Some(Feed::Kline),
2232        "CentralPivotRange" => Some(Feed::Kline),
2233        "Cfo" => Some(Feed::Kline),
2234        "ChaikinMoneyFlow" => Some(Feed::Kline),
2235        "ChaikinOscillator" => Some(Feed::Kline),
2236        "ChaikinVolatility" => Some(Feed::Kline),
2237        "ChandeKrollStop" => Some(Feed::Kline),
2238        "ChandelierExit" => Some(Feed::Kline),
2239        "ChoppinessIndex" => Some(Feed::Kline),
2240        "ClassicPivots" => Some(Feed::Kline),
2241        "CloseVsOpen" => Some(Feed::Kline),
2242        "ClosingMarubozu" => Some(Feed::Kline),
2243        "Cmo" => Some(Feed::Kline),
2244        "CoefficientOfVariation" => Some(Feed::Kline),
2245        "Cointegration" => Some(Feed::Kline),
2246        "CommonSenseRatio" => Some(Feed::Kline),
2247        "CompositeProfile" => Some(Feed::Kline),
2248        "ConcealingBabySwallow" => Some(Feed::Kline),
2249        "ConditionalValueAtRisk" => Some(Feed::Kline),
2250        "ConnorsRsi" => Some(Feed::Kline),
2251        "Coppock" => Some(Feed::Kline),
2252        "CorrelationTrendIndicator" => Some(Feed::Kline),
2253        "Counterattack" => Some(Feed::Kline),
2254        "Crab" => Some(Feed::Kline),
2255        "CumulativeVolumeDelta" => Some(Feed::Trade),
2256        "CumulativeVolumeIndex" => Some(Feed::CrossSection),
2257        "CupAndHandle" => Some(Feed::Kline),
2258        "CyberneticCycle" => Some(Feed::Kline),
2259        "Cypher" => Some(Feed::Kline),
2260        "Decycler" => Some(Feed::Kline),
2261        "DecyclerOscillator" => Some(Feed::Kline),
2262        "Dema" => Some(Feed::Kline),
2263        "DemandIndex" => Some(Feed::Kline),
2264        "DemarkPivots" => Some(Feed::Kline),
2265        "DepthSlope" => Some(Feed::Orderbook),
2266        "DerivativeOscillator" => Some(Feed::Kline),
2267        "DetrendedStdDev" => Some(Feed::Kline),
2268        "DisparityIndex" => Some(Feed::Kline),
2269        "DistanceSsd" => Some(Feed::Kline),
2270        "Doji" => Some(Feed::Kline),
2271        "DojiStar" => Some(Feed::Kline),
2272        "Donchian" => Some(Feed::Kline),
2273        "DonchianStop" => Some(Feed::Kline),
2274        "DoubleBollinger" => Some(Feed::Kline),
2275        "DoubleTopBottom" => Some(Feed::Kline),
2276        "DownsideGapThreeMethods" => Some(Feed::Kline),
2277        "Dpo" => Some(Feed::Kline),
2278        "DragonflyDoji" => Some(Feed::Kline),
2279        "DumplingTop" => Some(Feed::Kline),
2280        "Dx" => Some(Feed::Kline),
2281        "DynamicMomentumIndex" => Some(Feed::Kline),
2282        "EaseOfMovement" => Some(Feed::Kline),
2283        "EffectiveSpread" => Some(Feed::TradeQuote),
2284        "EhlersStochastic" => Some(Feed::Kline),
2285        "Ehma" => Some(Feed::Kline),
2286        "ElderImpulse" => Some(Feed::Kline),
2287        "ElderRay" => Some(Feed::Kline),
2288        "ElderSafeZone" => Some(Feed::Kline),
2289        "Ema" => Some(Feed::Kline),
2290        "EmpiricalModeDecomposition" => Some(Feed::Kline),
2291        "Engulfing" => Some(Feed::Kline),
2292        "Equivolume" => Some(Feed::Kline),
2293        "EstimatedLeverageRatio" => Some(Feed::Derivatives),
2294        "EvenBetterSinewave" => Some(Feed::Kline),
2295        "EveningDojiStar" => Some(Feed::Kline),
2296        "Evwma" => Some(Feed::Kline),
2297        "EwmaVolatility" => Some(Feed::Kline),
2298        "Expectancy" => Some(Feed::Kline),
2299        "FallingThreeMethods" => Some(Feed::Kline),
2300        "Fama" => Some(Feed::Kline),
2301        "FibArcs" => Some(Feed::Kline),
2302        "FibChannel" => Some(Feed::Kline),
2303        "FibConfluence" => Some(Feed::Kline),
2304        "FibExtension" => Some(Feed::Kline),
2305        "FibFan" => Some(Feed::Kline),
2306        "FibProjection" => Some(Feed::Kline),
2307        "FibRetracement" => Some(Feed::Kline),
2308        "FibTimeZones" => Some(Feed::Kline),
2309        "FibonacciPivots" => Some(Feed::Kline),
2310        "FisherRsi" => Some(Feed::Kline),
2311        "FisherTransform" => Some(Feed::Kline),
2312        "FlagPennant" => Some(Feed::Kline),
2313        "ForceIndex" => Some(Feed::Kline),
2314        "FractalChaosBands" => Some(Feed::Kline),
2315        "Frama" => Some(Feed::Kline),
2316        "FryPanBottom" => Some(Feed::Kline),
2317        "FundingBasis" => Some(Feed::Derivatives),
2318        "FundingImpliedApr" => Some(Feed::Derivatives),
2319        "FundingRate" => Some(Feed::Derivatives),
2320        "FundingRateMean" => Some(Feed::Derivatives),
2321        "FundingRateZScore" => Some(Feed::Derivatives),
2322        "GainLossRatio" => Some(Feed::Kline),
2323        "GainToPainRatio" => Some(Feed::Kline),
2324        "GapSideBySideWhite" => Some(Feed::Kline),
2325        "Garch11" => Some(Feed::Kline),
2326        "GarmanKlassVolatility" => Some(Feed::Kline),
2327        "Gartley" => Some(Feed::Kline),
2328        "GatorOscillator" => Some(Feed::Kline),
2329        "GeneralizedDema" => Some(Feed::Kline),
2330        "GeometricMa" => Some(Feed::Kline),
2331        "GoldenPocket" => Some(Feed::Kline),
2332        "GrangerCausality" => Some(Feed::Kline),
2333        "GravestoneDoji" => Some(Feed::Kline),
2334        "Hammer" => Some(Feed::Kline),
2335        "HangingMan" => Some(Feed::Kline),
2336        "Harami" => Some(Feed::Kline),
2337        "HaramiCross" => Some(Feed::Kline),
2338        "HasbrouckInformationShare" => Some(Feed::Kline),
2339        "HeadAndShoulders" => Some(Feed::Kline),
2340        "HeikinAshi" => Some(Feed::Kline),
2341        "HeikinAshiOscillator" => Some(Feed::Kline),
2342        "HiLoActivator" => Some(Feed::Kline),
2343        "HighLowIndex" => Some(Feed::CrossSection),
2344        "HighLowRange" => Some(Feed::Kline),
2345        "HighLowVolumeNodes" => Some(Feed::Kline),
2346        "HighWave" => Some(Feed::Kline),
2347        "HighpassFilter" => Some(Feed::Kline),
2348        "Hikkake" => Some(Feed::Kline),
2349        "HikkakeModified" => Some(Feed::Kline),
2350        "HilbertDominantCycle" => Some(Feed::Kline),
2351        "HistoricalVolatility" => Some(Feed::Kline),
2352        "Hma" => Some(Feed::Kline),
2353        "HoltWinters" => Some(Feed::Kline),
2354        "HomingPigeon" => Some(Feed::Kline),
2355        "HtDcPhase" => Some(Feed::Kline),
2356        "HtPhasor" => Some(Feed::Kline),
2357        "HtTrendMode" => Some(Feed::Kline),
2358        "HurstChannel" => Some(Feed::Kline),
2359        "HurstExponent" => Some(Feed::Kline),
2360        "IdenticalThreeCrows" => Some(Feed::Kline),
2361        "InNeck" => Some(Feed::Kline),
2362        "Inertia" => Some(Feed::Kline),
2363        "InformationRatio" => Some(Feed::Kline),
2364        "InitialBalance" => Some(Feed::Kline),
2365        "InstantaneousTrendline" => Some(Feed::Kline),
2366        "IntradayIntensity" => Some(Feed::Kline),
2367        "IntradayMomentumIndex" => Some(Feed::Kline),
2368        "InverseFisherTransform" => Some(Feed::Kline),
2369        "InvertedHammer" => Some(Feed::Kline),
2370        "JarqueBera" => Some(Feed::Kline),
2371        "Jma" => Some(Feed::Kline),
2372        "JumpIndicator" => Some(Feed::Kline),
2373        "KRatio" => Some(Feed::Kline),
2374        "KalmanHedgeRatio" => Some(Feed::Kline),
2375        "Kama" => Some(Feed::Kline),
2376        "KaseDevStop" => Some(Feed::Kline),
2377        "KasePermissionStochastic" => Some(Feed::Kline),
2378        "KellyCriterion" => Some(Feed::Kline),
2379        "Keltner" => Some(Feed::Kline),
2380        "KendallTau" => Some(Feed::Kline),
2381        "Kicking" => Some(Feed::Kline),
2382        "KickingByLength" => Some(Feed::Kline),
2383        "Kst" => Some(Feed::Kline),
2384        "Kurtosis" => Some(Feed::Kline),
2385        "Kvo" => Some(Feed::Kline),
2386        "KylesLambda" => Some(Feed::TradeQuote),
2387        "LadderBottom" => Some(Feed::Kline),
2388        "LaguerreRsi" => Some(Feed::Kline),
2389        "LeadLagCrossCorrelation" => Some(Feed::Kline),
2390        "LinRegAngle" => Some(Feed::Kline),
2391        "LinRegChannel" => Some(Feed::Kline),
2392        "LinRegIntercept" => Some(Feed::Kline),
2393        "LinRegSlope" => Some(Feed::Kline),
2394        "LinearRegression" => Some(Feed::Kline),
2395        "LiquidationFeatures" => Some(Feed::Derivatives),
2396        "LogReturn" => Some(Feed::Kline),
2397        "LongLeggedDoji" => Some(Feed::Kline),
2398        "LongLine" => Some(Feed::Kline),
2399        "LongShortRatio" => Some(Feed::Derivatives),
2400        "M2Measure" => Some(Feed::Kline),
2401        "MaEnvelope" => Some(Feed::Kline),
2402        "MacdFix" => Some(Feed::Kline),
2403        "MacdHistogram" => Some(Feed::Kline),
2404        "MacdIndicator" => Some(Feed::Kline),
2405        "Mama" => Some(Feed::Kline),
2406        "MarketFacilitationIndex" => Some(Feed::Kline),
2407        "MartinRatio" => Some(Feed::Kline),
2408        "Marubozu" => Some(Feed::Kline),
2409        "MassIndex" => Some(Feed::Kline),
2410        "MatHold" => Some(Feed::Kline),
2411        "MatchingLow" => Some(Feed::Kline),
2412        "MaxDrawdown" => Some(Feed::Kline),
2413        "McClellanOscillator" => Some(Feed::CrossSection),
2414        "McClellanSummationIndex" => Some(Feed::CrossSection),
2415        "McGinleyDynamic" => Some(Feed::Kline),
2416        "MedianAbsoluteDeviation" => Some(Feed::Kline),
2417        "MedianChannel" => Some(Feed::Kline),
2418        "MedianMa" => Some(Feed::Kline),
2419        "MedianPrice" => Some(Feed::Kline),
2420        "Mfi" => Some(Feed::Kline),
2421        "Microprice" => Some(Feed::Orderbook),
2422        "MidPoint" => Some(Feed::Kline),
2423        "MidPrice" => Some(Feed::Kline),
2424        "MinusDi" => Some(Feed::Kline),
2425        "MinusDm" => Some(Feed::Kline),
2426        "ModifiedMaStop" => Some(Feed::Kline),
2427        "Mom" => Some(Feed::Kline),
2428        "MorningDojiStar" => Some(Feed::Kline),
2429        "MorningEveningStar" => Some(Feed::Kline),
2430        "MurreyMathLines" => Some(Feed::Kline),
2431        "NakedPoc" => Some(Feed::Kline),
2432        "Natr" => Some(Feed::Kline),
2433        "NewHighsNewLows" => Some(Feed::CrossSection),
2434        "NewPriceLines" => Some(Feed::Kline),
2435        "Nrtr" => Some(Feed::Kline),
2436        "Nvi" => Some(Feed::Kline),
2437        "OIPriceDivergence" => Some(Feed::Derivatives),
2438        "OIWeighted" => Some(Feed::Derivatives),
2439        "Obv" => Some(Feed::Kline),
2440        "OiToVolumeRatio" => Some(Feed::Derivatives),
2441        "OmegaRatio" => Some(Feed::Kline),
2442        "OnNeck" => Some(Feed::Kline),
2443        "OpenInterestDelta" => Some(Feed::Derivatives),
2444        "OpenInterestMomentum" => Some(Feed::Derivatives),
2445        "OpeningMarubozu" => Some(Feed::Kline),
2446        "OpeningRange" => Some(Feed::Kline),
2447        "OrderBookImbalanceFull" => Some(Feed::Orderbook),
2448        "OrderBookImbalanceTop1" => Some(Feed::Orderbook),
2449        "OrderBookImbalanceTopN" => Some(Feed::Orderbook),
2450        "OrderFlowImbalance" => Some(Feed::Orderbook),
2451        "OuHalfLife" => Some(Feed::Kline),
2452        "OvernightGap" => Some(Feed::Kline),
2453        "OvernightIntradayReturn" => Some(Feed::Kline),
2454        "PainIndex" => Some(Feed::Kline),
2455        "PairSpreadZScore" => Some(Feed::Kline),
2456        "PairwiseBeta" => Some(Feed::Kline),
2457        "ParkinsonVolatility" => Some(Feed::Kline),
2458        "PearsonCorrelation" => Some(Feed::Kline),
2459        "PercentAboveMa" => Some(Feed::CrossSection),
2460        "PercentB" => Some(Feed::Kline),
2461        "PercentageTrailingStop" => Some(Feed::Kline),
2462        "PerpetualPremiumIndex" => Some(Feed::Derivatives),
2463        "Pgo" => Some(Feed::Kline),
2464        "PiercingDarkCloud" => Some(Feed::Kline),
2465        "Pin" => Some(Feed::Trade),
2466        "PivotReversal" => Some(Feed::Kline),
2467        "PlusDi" => Some(Feed::Kline),
2468        "PlusDm" => Some(Feed::Kline),
2469        "Pmo" => Some(Feed::Kline),
2470        "PolarizedFractalEfficiency" => Some(Feed::Kline),
2471        "Ppo" => Some(Feed::Kline),
2472        "PpoHistogram" => Some(Feed::Kline),
2473        "ProfileShape" => Some(Feed::Kline),
2474        "ProfitFactor" => Some(Feed::Kline),
2475        "ProjectionBands" => Some(Feed::Kline),
2476        "ProjectionOscillator" => Some(Feed::Kline),
2477        "Psar" => Some(Feed::Kline),
2478        "Pvi" => Some(Feed::Kline),
2479        "Qqe" => Some(Feed::Kline),
2480        "Qstick" => Some(Feed::Kline),
2481        "QuartileBands" => Some(Feed::Kline),
2482        "QuotedSpread" => Some(Feed::Orderbook),
2483        "RSquared" => Some(Feed::Kline),
2484        "RealizedSpread" => Some(Feed::TradeQuote),
2485        "RealizedVolatility" => Some(Feed::Kline),
2486        "RecoveryFactor" => Some(Feed::Kline),
2487        "RectangleRange" => Some(Feed::Kline),
2488        "Reflex" => Some(Feed::Kline),
2489        "RegimeLabel" => Some(Feed::Kline),
2490        "RelativeStrengthAB" => Some(Feed::Kline),
2491        "RenkoTrailingStop" => Some(Feed::Kline),
2492        "RickshawMan" => Some(Feed::Kline),
2493        "RisingThreeMethods" => Some(Feed::Kline),
2494        "Rmi" => Some(Feed::Kline),
2495        "Roc" => Some(Feed::Kline),
2496        "Rocp" => Some(Feed::Kline),
2497        "Rocr" => Some(Feed::Kline),
2498        "Rocr100" => Some(Feed::Kline),
2499        "RogersSatchellVolatility" => Some(Feed::Kline),
2500        "RollMeasure" => Some(Feed::Trade),
2501        "RollingCorrelation" => Some(Feed::Kline),
2502        "RollingCovariance" => Some(Feed::Kline),
2503        "RollingIqr" => Some(Feed::Kline),
2504        "RollingMinMaxScaler" => Some(Feed::Kline),
2505        "RollingPercentileRank" => Some(Feed::Kline),
2506        "RollingQuantile" => Some(Feed::Kline),
2507        "RollingVwap" => Some(Feed::Kline),
2508        "RoofingFilter" => Some(Feed::Kline),
2509        "Rsi" => Some(Feed::Kline),
2510        "Rsx" => Some(Feed::Kline),
2511        "Rvi" => Some(Feed::Kline),
2512        "RviVolatility" => Some(Feed::Kline),
2513        "Rwi" => Some(Feed::Kline),
2514        "SampleEntropy" => Some(Feed::Kline),
2515        "SarExt" => Some(Feed::Kline),
2516        "SeasonalZScore" => Some(Feed::Kline),
2517        "SeparatingLines" => Some(Feed::Kline),
2518        "SessionHighLow" => Some(Feed::Kline),
2519        "SessionRange" => Some(Feed::Kline),
2520        "SessionVwap" => Some(Feed::Kline),
2521        "ShannonEntropy" => Some(Feed::Kline),
2522        "Shark" => Some(Feed::Kline),
2523        "SharpeRatio" => Some(Feed::Kline),
2524        "ShootingStar" => Some(Feed::Kline),
2525        "ShortLine" => Some(Feed::Kline),
2526        "SignedVolume" => Some(Feed::Trade),
2527        "SineWave" => Some(Feed::Kline),
2528        "SineWeightedMa" => Some(Feed::Kline),
2529        "SinglePrints" => Some(Feed::Kline),
2530        "Skewness" => Some(Feed::Kline),
2531        "Sma" => Some(Feed::Kline),
2532        "Smi" => Some(Feed::Kline),
2533        "Smma" => Some(Feed::Kline),
2534        "SmoothedHeikinAshi" => Some(Feed::Kline),
2535        "SortinoRatio" => Some(Feed::Kline),
2536        "SpearmanCorrelation" => Some(Feed::Kline),
2537        "SpinningTop" => Some(Feed::Kline),
2538        "SpreadAr1Coefficient" => Some(Feed::Kline),
2539        "SpreadBollingerBands" => Some(Feed::Kline),
2540        "SpreadHurst" => Some(Feed::Kline),
2541        "StalledPattern" => Some(Feed::Kline),
2542        "StandardError" => Some(Feed::Kline),
2543        "StandardErrorBands" => Some(Feed::Kline),
2544        "StarcBands" => Some(Feed::Kline),
2545        "Stc" => Some(Feed::Kline),
2546        "StdDev" => Some(Feed::Kline),
2547        "StepTrailingStop" => Some(Feed::Kline),
2548        "SterlingRatio" => Some(Feed::Kline),
2549        "StickSandwich" => Some(Feed::Kline),
2550        "StochRsi" => Some(Feed::Kline),
2551        "Stochastic" => Some(Feed::Kline),
2552        "StochasticCci" => Some(Feed::Kline),
2553        "SuperSmoother" => Some(Feed::Kline),
2554        "SuperTrend" => Some(Feed::Kline),
2555        "T3" => Some(Feed::Kline),
2556        "TailRatio" => Some(Feed::Kline),
2557        "TakerBuySellRatio" => Some(Feed::Derivatives),
2558        "Takuri" => Some(Feed::Kline),
2559        "TasukiGap" => Some(Feed::Kline),
2560        "TdCamouflage" => Some(Feed::Kline),
2561        "TdClop" => Some(Feed::Kline),
2562        "TdClopwin" => Some(Feed::Kline),
2563        "TdCombo" => Some(Feed::Kline),
2564        "TdCountdown" => Some(Feed::Kline),
2565        "TdDWave" => Some(Feed::Kline),
2566        "TdDeMarker" => Some(Feed::Kline),
2567        "TdDifferential" => Some(Feed::Kline),
2568        "TdLines" => Some(Feed::Kline),
2569        "TdMovingAverage" => Some(Feed::Kline),
2570        "TdOpen" => Some(Feed::Kline),
2571        "TdPressure" => Some(Feed::Kline),
2572        "TdPropulsion" => Some(Feed::Kline),
2573        "TdRangeProjection" => Some(Feed::Kline),
2574        "TdRei" => Some(Feed::Kline),
2575        "TdRiskLevel" => Some(Feed::Kline),
2576        "TdSequential" => Some(Feed::Kline),
2577        "TdSetup" => Some(Feed::Kline),
2578        "TdTrap" => Some(Feed::Kline),
2579        "Tema" => Some(Feed::Kline),
2580        "TermStructureBasis" => Some(Feed::Derivatives),
2581        "ThreeDrives" => Some(Feed::Kline),
2582        "ThreeInside" => Some(Feed::Kline),
2583        "ThreeLineBreak" => Some(Feed::Kline),
2584        "ThreeLineStrike" => Some(Feed::Kline),
2585        "ThreeOutside" => Some(Feed::Kline),
2586        "ThreeSoldiersOrCrows" => Some(Feed::Kline),
2587        "ThreeStarsInSouth" => Some(Feed::Kline),
2588        "Thrusting" => Some(Feed::Kline),
2589        "TickIndex" => Some(Feed::CrossSection),
2590        "Tii" => Some(Feed::Kline),
2591        "TimeBasedStop" => Some(Feed::Kline),
2592        "TowerTopBottom" => Some(Feed::Kline),
2593        "TpoProfile" => Some(Feed::Kline),
2594        "TradeImbalance" => Some(Feed::Trade),
2595        "TradeSignAutocorrelation" => Some(Feed::Trade),
2596        "TradeVolumeIndex" => Some(Feed::Kline),
2597        "TrendLabel" => Some(Feed::Kline),
2598        "TrendStrengthIndex" => Some(Feed::Kline),
2599        "Trendflex" => Some(Feed::Kline),
2600        "TreynorRatio" => Some(Feed::Kline),
2601        "Triangle" => Some(Feed::Kline),
2602        "Trima" => Some(Feed::Kline),
2603        "Trin" => Some(Feed::CrossSection),
2604        "TripleTopBottom" => Some(Feed::Kline),
2605        "Tristar" => Some(Feed::Kline),
2606        "Trix" => Some(Feed::Kline),
2607        "TrueRange" => Some(Feed::Kline),
2608        "Tsf" => Some(Feed::Kline),
2609        "TsfOscillator" => Some(Feed::Kline),
2610        "Tsi" => Some(Feed::Kline),
2611        "Tsv" => Some(Feed::Kline),
2612        "TtmSqueeze" => Some(Feed::Kline),
2613        "TtmTrend" => Some(Feed::Kline),
2614        "TurnOfMonth" => Some(Feed::Kline),
2615        "Tweezer" => Some(Feed::Kline),
2616        "TwiggsMoneyFlow" => Some(Feed::Kline),
2617        "TwoCrows" => Some(Feed::Kline),
2618        "TypicalPrice" => Some(Feed::Kline),
2619        "UlcerIndex" => Some(Feed::Kline),
2620        "UltimateOscillator" => Some(Feed::Kline),
2621        "UniqueThreeRiver" => Some(Feed::Kline),
2622        "UniversalOscillator" => Some(Feed::Kline),
2623        "UpDownVolumeRatio" => Some(Feed::CrossSection),
2624        "UpsideGapThreeMethods" => Some(Feed::Kline),
2625        "UpsideGapTwoCrows" => Some(Feed::Kline),
2626        "UpsidePotentialRatio" => Some(Feed::Kline),
2627        "ValueArea" => Some(Feed::Kline),
2628        "ValueAtRisk" => Some(Feed::Kline),
2629        "Variance" => Some(Feed::Kline),
2630        "VarianceRatio" => Some(Feed::Kline),
2631        "VerticalHorizontalFilter" => Some(Feed::Kline),
2632        "Vidya" => Some(Feed::Kline),
2633        "VolatilityCone" => Some(Feed::Kline),
2634        "VolatilityOfVolatility" => Some(Feed::Kline),
2635        "VolatilityRatio" => Some(Feed::Kline),
2636        "VoltyStop" => Some(Feed::Kline),
2637        "VolumeOscillator" => Some(Feed::Kline),
2638        "VolumePriceTrend" => Some(Feed::Kline),
2639        "VolumeProfile" => Some(Feed::Kline),
2640        "VolumeRsi" => Some(Feed::Kline),
2641        "VolumeWeightedMacd" => Some(Feed::Kline),
2642        "VolumeWeightedSr" => Some(Feed::Kline),
2643        "Vortex" => Some(Feed::Kline),
2644        "Vpin" => Some(Feed::Trade),
2645        "Vwap" => Some(Feed::Kline),
2646        "VwapStdDevBands" => Some(Feed::Kline),
2647        "Vwma" => Some(Feed::Kline),
2648        "Vzo" => Some(Feed::Kline),
2649        "Wad" => Some(Feed::Kline),
2650        "WavePm" => Some(Feed::Kline),
2651        "WaveTrend" => Some(Feed::Kline),
2652        "Wedge" => Some(Feed::Kline),
2653        "WeightedClose" => Some(Feed::Kline),
2654        "WickRatio" => Some(Feed::Kline),
2655        "WilliamsR" => Some(Feed::Kline),
2656        "WinRate" => Some(Feed::Kline),
2657        "Wma" => Some(Feed::Kline),
2658        "WoodiePivots" => Some(Feed::Kline),
2659        "YangZhangVolatility" => Some(Feed::Kline),
2660        "YoyoExit" => Some(Feed::Kline),
2661        "ZScore" => Some(Feed::Kline),
2662        "ZeroLagMacd" => Some(Feed::Kline),
2663        "ZigZag" => Some(Feed::Kline),
2664        "Zlema" => Some(Feed::Kline),
2665        _ => None,
2666    }
2667}
2668
2669/// Every registered indicator with valid default parameters (495 indicators).
2670#[cfg(test)]
2671const ALL_SPECS: &[(&str, &[f64])] = &[
2672    ("AdaptiveCycle", &[]),
2673    ("AdaptiveLaguerreFilter", &[20.0]),
2674    ("AdaptiveRsi", &[14.0]),
2675    ("Alma", &[9.0, 0.85, 6.0]),
2676    ("AnchoredRsi", &[]),
2677    ("Apo", &[3.0, 7.0]),
2678    ("Autocorrelation", &[10.0, 1.0]),
2679    ("AutocorrelationPeriodogram", &[10.0, 48.0]),
2680    ("AverageDrawdown", &[14.0]),
2681    ("BandpassFilter", &[20.0, 0.3]),
2682    ("BipowerVariation", &[14.0]),
2683    ("BollingerBandwidth", &[14.0, 2.0]),
2684    ("BurkeRatio", &[14.0]),
2685    ("CalmarRatio", &[14.0]),
2686    ("CenterOfGravity", &[14.0]),
2687    ("Cfo", &[14.0]),
2688    ("Cmo", &[14.0]),
2689    ("CoefficientOfVariation", &[14.0]),
2690    ("CommonSenseRatio", &[14.0]),
2691    ("ConditionalValueAtRisk", &[20.0, 0.95]),
2692    ("ConnorsRsi", &[3.0, 7.0, 14.0]),
2693    ("Coppock", &[3.0, 7.0, 14.0]),
2694    ("CorrelationTrendIndicator", &[14.0]),
2695    ("CyberneticCycle", &[14.0]),
2696    ("Decycler", &[14.0]),
2697    ("DecyclerOscillator", &[3.0, 7.0]),
2698    ("Dema", &[14.0]),
2699    ("DerivativeOscillator", &[3.0, 7.0, 14.0, 28.0]),
2700    ("DetrendedStdDev", &[14.0]),
2701    ("DisparityIndex", &[14.0]),
2702    ("Dpo", &[14.0]),
2703    ("DynamicMomentumIndex", &[14.0]),
2704    ("EhlersStochastic", &[14.0]),
2705    ("Ehma", &[14.0]),
2706    ("ElderImpulse", &[3.0, 7.0, 14.0, 28.0]),
2707    ("Ema", &[14.0]),
2708    ("EmpiricalModeDecomposition", &[20.0, 0.1]),
2709    ("EvenBetterSinewave", &[40.0, 10.0]),
2710    ("EwmaVolatility", &[0.94]),
2711    ("Expectancy", &[14.0]),
2712    ("Fama", &[0.5, 0.05]),
2713    ("FisherRsi", &[14.0]),
2714    ("FisherTransform", &[14.0]),
2715    ("Frama", &[14.0]),
2716    ("GainLossRatio", &[14.0]),
2717    ("GainToPainRatio", &[14.0]),
2718    ("Garch11", &[2e-06, 0.1, 0.88]),
2719    ("GeneralizedDema", &[5.0, 0.7]),
2720    ("GeometricMa", &[14.0]),
2721    ("HighpassFilter", &[14.0]),
2722    ("HilbertDominantCycle", &[]),
2723    ("HistoricalVolatility", &[3.0, 7.0]),
2724    ("Hma", &[14.0]),
2725    ("HoltWinters", &[0.5, 0.1]),
2726    ("HtDcPhase", &[]),
2727    ("HtTrendMode", &[]),
2728    ("HurstExponent", &[100.0, 4.0]),
2729    ("InstantaneousTrendline", &[14.0]),
2730    ("InverseFisherTransform", &[2.0]),
2731    ("JarqueBera", &[14.0]),
2732    ("Jma", &[7.0, 0.0, 2.0]),
2733    ("JumpIndicator", &[14.0, 2.0]),
2734    ("KRatio", &[14.0]),
2735    ("Kama", &[3.0, 7.0, 14.0]),
2736    ("KellyCriterion", &[14.0]),
2737    ("Kurtosis", &[14.0]),
2738    ("LaguerreRsi", &[0.5]),
2739    ("LinRegAngle", &[14.0]),
2740    ("LinRegIntercept", &[14.0]),
2741    ("LinRegSlope", &[14.0]),
2742    ("LinearRegression", &[14.0]),
2743    ("LogReturn", &[14.0]),
2744    ("M2Measure", &[14.0, 2.0, 0.5]),
2745    ("MacdHistogram", &[3.0, 7.0, 14.0]),
2746    ("MartinRatio", &[14.0]),
2747    ("MaxDrawdown", &[14.0]),
2748    ("McGinleyDynamic", &[14.0]),
2749    ("MedianAbsoluteDeviation", &[14.0]),
2750    ("MedianMa", &[14.0]),
2751    ("MidPoint", &[14.0]),
2752    ("Mom", &[14.0]),
2753    ("OmegaRatio", &[14.0, 2.0]),
2754    ("PainIndex", &[14.0]),
2755    ("PercentB", &[14.0, 2.0]),
2756    ("PercentageTrailingStop", &[2.0]),
2757    ("Pmo", &[3.0, 7.0]),
2758    ("PolarizedFractalEfficiency", &[10.0, 5.0]),
2759    ("Ppo", &[3.0, 7.0]),
2760    ("PpoHistogram", &[3.0, 7.0, 14.0]),
2761    ("ProfitFactor", &[14.0]),
2762    ("RSquared", &[14.0]),
2763    ("RealizedVolatility", &[14.0]),
2764    ("RecoveryFactor", &[]),
2765    ("Reflex", &[14.0]),
2766    ("RegimeLabel", &[3.0, 7.0]),
2767    ("RenkoTrailingStop", &[2.0]),
2768    ("Rmi", &[3.0, 7.0]),
2769    ("Roc", &[14.0]),
2770    ("Rocp", &[14.0]),
2771    ("Rocr", &[14.0]),
2772    ("Rocr100", &[14.0]),
2773    ("RollingIqr", &[14.0]),
2774    ("RollingMinMaxScaler", &[14.0]),
2775    ("RollingPercentileRank", &[14.0]),
2776    ("RollingQuantile", &[20.0, 0.5]),
2777    ("RoofingFilter", &[3.0, 7.0]),
2778    ("Rsi", &[14.0]),
2779    ("Rsx", &[14.0]),
2780    ("RviVolatility", &[14.0]),
2781    ("SampleEntropy", &[20.0, 2.0, 0.2]),
2782    ("ShannonEntropy", &[3.0, 7.0]),
2783    ("SharpeRatio", &[14.0, 2.0]),
2784    ("SineWave", &[]),
2785    ("SineWeightedMa", &[14.0]),
2786    ("Skewness", &[14.0]),
2787    ("Sma", &[14.0]),
2788    ("Smma", &[14.0]),
2789    ("SortinoRatio", &[14.0, 2.0]),
2790    ("StandardError", &[14.0]),
2791    ("Stc", &[10.0, 23.0, 10.0, 0.5]),
2792    ("StdDev", &[14.0]),
2793    ("StepTrailingStop", &[2.0]),
2794    ("SterlingRatio", &[14.0]),
2795    ("StochRsi", &[3.0, 7.0]),
2796    ("SuperSmoother", &[14.0]),
2797    ("T3", &[5.0, 0.7]),
2798    ("TailRatio", &[14.0]),
2799    ("Tema", &[14.0]),
2800    ("Tii", &[3.0, 7.0]),
2801    ("TrendLabel", &[14.0]),
2802    ("TrendStrengthIndex", &[14.0]),
2803    ("Trendflex", &[14.0]),
2804    ("Trima", &[14.0]),
2805    ("Trix", &[14.0]),
2806    ("Tsf", &[14.0]),
2807    ("TsfOscillator", &[14.0]),
2808    ("Tsi", &[3.0, 7.0]),
2809    ("UlcerIndex", &[14.0]),
2810    ("UniversalOscillator", &[14.0]),
2811    ("UpsidePotentialRatio", &[14.0, 2.0]),
2812    ("ValueAtRisk", &[20.0, 0.95]),
2813    ("Variance", &[14.0]),
2814    ("VerticalHorizontalFilter", &[14.0]),
2815    ("Vidya", &[3.0, 7.0]),
2816    ("VolatilityOfVolatility", &[3.0, 7.0]),
2817    ("WavePm", &[3.0, 7.0]),
2818    ("WinRate", &[14.0]),
2819    ("Wma", &[14.0]),
2820    ("ZScore", &[14.0]),
2821    ("Zlema", &[14.0]),
2822    ("AbandonedBaby", &[]),
2823    ("Abcd", &[]),
2824    ("AcceleratorOscillator", &[3.0, 7.0, 14.0]),
2825    ("AdOscillator", &[]),
2826    ("AdaptiveCci", &[14.0]),
2827    ("Adl", &[]),
2828    ("AdvanceBlock", &[]),
2829    ("Adxr", &[14.0]),
2830    ("AnchoredVwap", &[]),
2831    ("AroonOscillator", &[14.0]),
2832    ("Atr", &[14.0]),
2833    ("AtrTrailingStop", &[14.0, 2.0]),
2834    ("AverageDailyRange", &[14.0, 0.0]),
2835    ("AvgPrice", &[]),
2836    ("AwesomeOscillator", &[3.0, 7.0]),
2837    ("AwesomeOscillatorHistogram", &[3.0, 7.0, 14.0]),
2838    ("BalanceOfPower", &[]),
2839    ("Bat", &[]),
2840    ("BeltHold", &[]),
2841    ("BetterVolume", &[14.0]),
2842    ("BodySizePct", &[]),
2843    ("Breakaway", &[]),
2844    ("Butterfly", &[]),
2845    ("Cci", &[14.0]),
2846    ("ChaikinMoneyFlow", &[20.0]),
2847    ("ChaikinOscillator", &[3.0, 7.0]),
2848    ("ChaikinVolatility", &[3.0, 7.0]),
2849    ("ChoppinessIndex", &[14.0]),
2850    ("CloseVsOpen", &[]),
2851    ("ClosingMarubozu", &[]),
2852    ("ConcealingBabySwallow", &[]),
2853    ("Counterattack", &[]),
2854    ("Crab", &[]),
2855    ("CupAndHandle", &[]),
2856    ("Cypher", &[]),
2857    ("DemandIndex", &[14.0]),
2858    ("Doji", &[]),
2859    ("DojiStar", &[]),
2860    ("DoubleTopBottom", &[]),
2861    ("DownsideGapThreeMethods", &[]),
2862    ("DragonflyDoji", &[]),
2863    ("DumplingTop", &[14.0]),
2864    ("Dx", &[14.0]),
2865    ("EaseOfMovement", &[14.0]),
2866    ("Engulfing", &[]),
2867    ("EveningDojiStar", &[]),
2868    ("Evwma", &[14.0]),
2869    ("FallingThreeMethods", &[]),
2870    ("FlagPennant", &[]),
2871    ("ForceIndex", &[14.0]),
2872    ("FryPanBottom", &[14.0]),
2873    ("GapSideBySideWhite", &[]),
2874    ("GarmanKlassVolatility", &[20.0, 252.0]),
2875    ("Gartley", &[]),
2876    ("GravestoneDoji", &[]),
2877    ("Hammer", &[]),
2878    ("HangingMan", &[]),
2879    ("Harami", &[]),
2880    ("HaramiCross", &[]),
2881    ("HeadAndShoulders", &[]),
2882    ("HeikinAshiOscillator", &[14.0]),
2883    ("HiLoActivator", &[14.0]),
2884    ("HighLowRange", &[]),
2885    ("HighWave", &[]),
2886    ("Hikkake", &[]),
2887    ("HikkakeModified", &[]),
2888    ("HomingPigeon", &[]),
2889    ("IdenticalThreeCrows", &[]),
2890    ("InNeck", &[]),
2891    ("Inertia", &[3.0, 7.0]),
2892    ("IntradayIntensity", &[]),
2893    ("IntradayMomentumIndex", &[14.0]),
2894    ("InvertedHammer", &[]),
2895    ("Kicking", &[]),
2896    ("KickingByLength", &[]),
2897    ("Kvo", &[3.0, 7.0]),
2898    ("LadderBottom", &[]),
2899    ("LongLeggedDoji", &[]),
2900    ("LongLine", &[]),
2901    ("MarketFacilitationIndex", &[]),
2902    ("Marubozu", &[]),
2903    ("MassIndex", &[3.0, 7.0]),
2904    ("MatHold", &[]),
2905    ("MatchingLow", &[]),
2906    ("MedianPrice", &[]),
2907    ("Mfi", &[14.0]),
2908    ("MidPrice", &[14.0]),
2909    ("MinusDi", &[14.0]),
2910    ("MinusDm", &[14.0]),
2911    ("MorningDojiStar", &[]),
2912    ("MorningEveningStar", &[]),
2913    ("NakedPoc", &[3.0, 7.0]),
2914    ("Natr", &[14.0]),
2915    ("NewPriceLines", &[14.0]),
2916    ("Nvi", &[]),
2917    ("Obv", &[]),
2918    ("OnNeck", &[]),
2919    ("OpeningMarubozu", &[]),
2920    ("OvernightGap", &[0.0]),
2921    ("ParkinsonVolatility", &[20.0, 252.0]),
2922    ("Pgo", &[14.0]),
2923    ("PiercingDarkCloud", &[]),
2924    ("PivotReversal", &[3.0, 7.0]),
2925    ("PlusDi", &[14.0]),
2926    ("PlusDm", &[14.0]),
2927    ("ProfileShape", &[3.0, 7.0]),
2928    ("ProjectionOscillator", &[14.0]),
2929    ("Psar", &[0.02, 0.02, 0.2]),
2930    ("Pvi", &[]),
2931    ("Qstick", &[14.0]),
2932    ("RectangleRange", &[]),
2933    ("RickshawMan", &[]),
2934    ("RisingThreeMethods", &[]),
2935    ("RogersSatchellVolatility", &[20.0, 252.0]),
2936    ("RollingVwap", &[14.0]),
2937    ("Rvi", &[14.0]),
2938    ("SarExt", &[2.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]),
2939    ("SeasonalZScore", &[14.0]),
2940    ("SeparatingLines", &[]),
2941    ("SessionVwap", &[14.0]),
2942    ("Shark", &[]),
2943    ("ShootingStar", &[]),
2944    ("ShortLine", &[]),
2945    ("SinglePrints", &[3.0, 7.0]),
2946    ("Smi", &[3.0, 7.0, 14.0]),
2947    ("SpinningTop", &[]),
2948    ("StalledPattern", &[]),
2949    ("StickSandwich", &[]),
2950    ("StochasticCci", &[14.0]),
2951    ("Takuri", &[]),
2952    ("TasukiGap", &[]),
2953    ("TdCamouflage", &[]),
2954    ("TdClop", &[]),
2955    ("TdClopwin", &[]),
2956    ("TdCombo", &[3.0, 7.0, 14.0, 28.0]),
2957    ("TdCountdown", &[3.0, 7.0, 14.0, 28.0]),
2958    ("TdDWave", &[2.0]),
2959    ("TdDeMarker", &[14.0]),
2960    ("TdDifferential", &[]),
2961    ("TdOpen", &[]),
2962    ("TdPressure", &[14.0]),
2963    ("TdPropulsion", &[]),
2964    ("TdRei", &[14.0]),
2965    ("TdSetup", &[3.0, 7.0]),
2966    ("TdTrap", &[]),
2967    ("ThreeDrives", &[]),
2968    ("ThreeInside", &[]),
2969    ("ThreeLineBreak", &[14.0]),
2970    ("ThreeLineStrike", &[]),
2971    ("ThreeOutside", &[]),
2972    ("ThreeSoldiersOrCrows", &[]),
2973    ("ThreeStarsInSouth", &[]),
2974    ("Thrusting", &[]),
2975    ("TimeBasedStop", &[14.0]),
2976    ("TowerTopBottom", &[]),
2977    ("TradeVolumeIndex", &[2.0]),
2978    ("Triangle", &[]),
2979    ("TripleTopBottom", &[]),
2980    ("Tristar", &[]),
2981    ("TrueRange", &[]),
2982    ("Tsv", &[14.0]),
2983    ("TtmTrend", &[14.0]),
2984    ("TurnOfMonth", &[3.0, 3.0, 0.0]),
2985    ("Tweezer", &[]),
2986    ("TwiggsMoneyFlow", &[14.0]),
2987    ("TwoCrows", &[]),
2988    ("TypicalPrice", &[]),
2989    ("UltimateOscillator", &[3.0, 7.0, 14.0]),
2990    ("UniqueThreeRiver", &[]),
2991    ("UpsideGapThreeMethods", &[]),
2992    ("UpsideGapTwoCrows", &[]),
2993    ("VolatilityRatio", &[14.0]),
2994    ("VoltyStop", &[14.0, 2.0]),
2995    ("VolumeOscillator", &[3.0, 7.0]),
2996    ("VolumePriceTrend", &[]),
2997    ("VolumeRsi", &[14.0]),
2998    ("Vwap", &[]),
2999    ("Vwma", &[14.0]),
3000    ("Vzo", &[14.0]),
3001    ("Wad", &[]),
3002    ("Wedge", &[]),
3003    ("WeightedClose", &[]),
3004    ("WickRatio", &[]),
3005    ("WilliamsR", &[14.0]),
3006    ("YangZhangVolatility", &[20.0, 252.0]),
3007    ("YoyoExit", &[14.0, 2.0]),
3008    ("AccelerationBands", &[14.0, 2.0]),
3009    ("Adx", &[14.0]),
3010    ("Alligator", &[3.0, 7.0, 14.0]),
3011    ("AndrewsPitchfork", &[14.0]),
3012    ("Aroon", &[14.0]),
3013    ("AtrBands", &[14.0, 2.0]),
3014    ("AtrRatchet", &[14.0, 2.0, 0.5]),
3015    ("AutoFib", &[]),
3016    ("BollingerBands", &[20.0, 2.0]),
3017    ("BomarBands", &[4.0, 0.85]),
3018    ("Camarilla", &[]),
3019    ("CandleVolume", &[14.0]),
3020    ("CentralPivotRange", &[]),
3021    ("ChandeKrollStop", &[3.0, 2.0, 7.0]),
3022    ("ChandelierExit", &[14.0, 2.0]),
3023    ("ClassicPivots", &[]),
3024    ("CompositeProfile", &[20.0, 24.0, 0.7]),
3025    ("DemarkPivots", &[]),
3026    ("Donchian", &[14.0]),
3027    ("DonchianStop", &[14.0]),
3028    ("DoubleBollinger", &[20.0, 1.0, 2.0]),
3029    ("ElderRay", &[14.0]),
3030    ("ElderSafeZone", &[10.0, 2.0]),
3031    ("Equivolume", &[14.0]),
3032    ("FibArcs", &[]),
3033    ("FibChannel", &[]),
3034    ("FibConfluence", &[]),
3035    ("FibExtension", &[]),
3036    ("FibFan", &[]),
3037    ("FibProjection", &[]),
3038    ("FibRetracement", &[]),
3039    ("FibTimeZones", &[]),
3040    ("FibonacciPivots", &[]),
3041    ("FractalChaosBands", &[14.0]),
3042    ("GatorOscillator", &[3.0, 7.0, 14.0]),
3043    ("GoldenPocket", &[]),
3044    ("HeikinAshi", &[]),
3045    ("HighLowVolumeNodes", &[3.0, 7.0]),
3046    ("HtPhasor", &[]),
3047    ("HurstChannel", &[14.0, 2.0]),
3048    ("InitialBalance", &[14.0]),
3049    ("KaseDevStop", &[14.0, 2.0]),
3050    ("KasePermissionStochastic", &[3.0, 7.0]),
3051    ("Keltner", &[3.0, 7.0, 2.0]),
3052    ("Kst", &[3.0, 7.0, 14.0, 28.0, 35.0, 42.0, 56.0, 63.0, 70.0]),
3053    ("LinRegChannel", &[14.0, 2.0]),
3054    ("MaEnvelope", &[14.0, 2.0]),
3055    ("MacdFix", &[9.0]),
3056    ("MacdIndicator", &[12.0, 26.0, 9.0]),
3057    ("Mama", &[0.5, 0.05]),
3058    ("MedianChannel", &[14.0, 2.0]),
3059    ("ModifiedMaStop", &[14.0]),
3060    ("MurreyMathLines", &[14.0]),
3061    ("Nrtr", &[2.0]),
3062    ("OpeningRange", &[14.0]),
3063    ("OvernightIntradayReturn", &[14.0]),
3064    ("ProjectionBands", &[14.0]),
3065    ("Qqe", &[3.0, 7.0, 2.0]),
3066    ("QuartileBands", &[14.0]),
3067    ("Rwi", &[14.0]),
3068    ("SessionHighLow", &[14.0]),
3069    ("SessionRange", &[14.0]),
3070    ("SmoothedHeikinAshi", &[14.0]),
3071    ("StandardErrorBands", &[14.0, 2.0]),
3072    ("StarcBands", &[3.0, 7.0, 2.0]),
3073    ("Stochastic", &[3.0, 7.0]),
3074    ("SuperTrend", &[14.0, 2.0]),
3075    ("TdLines", &[3.0, 7.0]),
3076    ("TdMovingAverage", &[3.0, 7.0]),
3077    ("TdRangeProjection", &[]),
3078    ("TdRiskLevel", &[3.0, 7.0]),
3079    ("TdSequential", &[3.0, 7.0, 14.0, 28.0]),
3080    ("TpoProfile", &[14.0, 14.0]),
3081    ("TtmSqueeze", &[14.0, 2.0, 0.5]),
3082    ("ValueArea", &[20.0, 50.0, 0.7]),
3083    ("VolatilityCone", &[3.0, 7.0]),
3084    ("VolumeProfile", &[14.0, 14.0]),
3085    ("VolumeWeightedMacd", &[3.0, 7.0, 14.0]),
3086    ("VolumeWeightedSr", &[14.0]),
3087    ("Vortex", &[14.0]),
3088    ("VwapStdDevBands", &[2.0]),
3089    ("WaveTrend", &[3.0, 7.0, 14.0]),
3090    ("WoodiePivots", &[]),
3091    ("ZeroLagMacd", &[3.0, 7.0, 14.0]),
3092    ("ZigZag", &[0.02]),
3093    ("Alpha", &[14.0, 2.0]),
3094    ("Beta", &[14.0]),
3095    ("BetaNeutralSpread", &[14.0]),
3096    ("DistanceSsd", &[14.0]),
3097    ("GrangerCausality", &[60.0, 1.0]),
3098    ("HasbrouckInformationShare", &[14.0]),
3099    ("InformationRatio", &[14.0]),
3100    ("KendallTau", &[14.0]),
3101    ("OuHalfLife", &[14.0]),
3102    ("PairSpreadZScore", &[20.0, 20.0]),
3103    ("PairwiseBeta", &[14.0]),
3104    ("PearsonCorrelation", &[14.0]),
3105    ("RollingCorrelation", &[14.0]),
3106    ("RollingCovariance", &[14.0]),
3107    ("SpearmanCorrelation", &[14.0]),
3108    ("SpreadAr1Coefficient", &[14.0]),
3109    ("SpreadHurst", &[14.0]),
3110    ("TreynorRatio", &[14.0, 2.0]),
3111    ("VarianceRatio", &[60.0, 2.0]),
3112    ("Cointegration", &[40.0, 1.0]),
3113    ("KalmanHedgeRatio", &[0.01, 0.001]),
3114    ("LeadLagCrossCorrelation", &[20.0, 10.0]),
3115    ("RelativeStrengthAB", &[14.0, 14.0]),
3116    ("SpreadBollingerBands", &[14.0, 2.0]),
3117    ("CalendarSpread", &[]),
3118    ("EstimatedLeverageRatio", &[]),
3119    ("FundingBasis", &[]),
3120    ("FundingImpliedApr", &[2.0]),
3121    ("FundingRate", &[]),
3122    ("FundingRateMean", &[14.0]),
3123    ("FundingRateZScore", &[14.0]),
3124    ("LongShortRatio", &[]),
3125    ("OIPriceDivergence", &[14.0]),
3126    ("OIWeighted", &[]),
3127    ("OiToVolumeRatio", &[]),
3128    ("OpenInterestDelta", &[]),
3129    ("OpenInterestMomentum", &[14.0]),
3130    ("PerpetualPremiumIndex", &[]),
3131    ("TakerBuySellRatio", &[]),
3132    ("TermStructureBasis", &[]),
3133    ("LiquidationFeatures", &[]),
3134    ("DepthSlope", &[]),
3135    ("Microprice", &[]),
3136    ("OrderBookImbalanceFull", &[]),
3137    ("OrderBookImbalanceTop1", &[]),
3138    ("OrderBookImbalanceTopN", &[14.0]),
3139    ("OrderFlowImbalance", &[14.0]),
3140    ("QuotedSpread", &[]),
3141    ("AmihudIlliquidity", &[14.0]),
3142    ("CumulativeVolumeDelta", &[]),
3143    ("Pin", &[14.0]),
3144    ("RollMeasure", &[14.0]),
3145    ("SignedVolume", &[]),
3146    ("TradeImbalance", &[14.0]),
3147    ("TradeSignAutocorrelation", &[14.0]),
3148    ("Vpin", &[2.0, 14.0]),
3149    ("EffectiveSpread", &[]),
3150    ("KylesLambda", &[14.0]),
3151    ("RealizedSpread", &[14.0]),
3152    ("AbsoluteBreadthIndex", &[]),
3153    ("AdVolumeLine", &[]),
3154    ("AdvanceDecline", &[]),
3155    ("AdvanceDeclineRatio", &[]),
3156    ("BreadthThrust", &[14.0]),
3157    ("BullishPercentIndex", &[]),
3158    ("CumulativeVolumeIndex", &[]),
3159    ("HighLowIndex", &[14.0]),
3160    ("McClellanOscillator", &[]),
3161    ("McClellanSummationIndex", &[]),
3162    ("NewHighsNewLows", &[]),
3163    ("PercentAboveMa", &[]),
3164    ("TickIndex", &[]),
3165    ("Trin", &[]),
3166    ("UpDownVolumeRatio", &[]),
3167];
3168
3169#[cfg(test)]
3170mod tests {
3171    use super::*;
3172
3173    fn candle(high: f64, low: f64, close: f64) -> Candle {
3174        Candle {
3175            time: 0,
3176            open: close,
3177            high,
3178            low,
3179            close,
3180            volume: 1.0,
3181        }
3182    }
3183
3184    fn input(c: &Candle) -> BarInput<'_> {
3185        BarInput {
3186            candle: c,
3187            reference: None,
3188            deriv: None,
3189            orderbook: None,
3190            trades: &[],
3191            cross_section: None,
3192        }
3193    }
3194
3195    #[test]
3196    fn builds_all_known_indicators() {
3197        for (kind, params) in ALL_SPECS {
3198            assert!(build(kind, params).is_ok(), "{kind} should build");
3199        }
3200    }
3201
3202    #[test]
3203    fn every_indicator_updates_without_panicking() {
3204        use crate::data::{
3205            CrossSection as DCrossSection, CrossSectionMember, DerivativesTick as DDeriv, Level,
3206            OrderBook as DOrderBook, TradePrint, TradeSide,
3207        };
3208        // One populated snapshot of every feed, converted to core types, so each
3209        // input family's update arm has real data to consume.
3210        let deriv = DDeriv {
3211            funding_rate: 0.01,
3212            mark_price: 100.0,
3213            index_price: 100.0,
3214            futures_price: 100.0,
3215            open_interest: 1000.0,
3216            long_size: 600.0,
3217            short_size: 400.0,
3218            taker_buy_volume: 50.0,
3219            taker_sell_volume: 40.0,
3220            long_liquidation: 0.0,
3221            short_liquidation: 0.0,
3222            timestamp: 0,
3223        }
3224        .to_core()
3225        .unwrap();
3226        let book = DOrderBook {
3227            bids: vec![Level {
3228                price: 99.0,
3229                size: 5.0,
3230            }],
3231            asks: vec![Level {
3232                price: 101.0,
3233                size: 5.0,
3234            }],
3235        }
3236        .to_core()
3237        .unwrap();
3238        let trades: Vec<_> = [
3239            TradePrint {
3240                price: 100.0,
3241                size: 2.0,
3242                side: TradeSide::Buy,
3243                timestamp: 0,
3244            },
3245            TradePrint {
3246                price: 100.5,
3247                size: 1.0,
3248                side: TradeSide::Sell,
3249                timestamp: 0,
3250            },
3251        ]
3252        .iter()
3253        .map(|t| t.to_core().unwrap())
3254        .collect();
3255        let section = DCrossSection {
3256            members: vec![
3257                CrossSectionMember {
3258                    change: 1.0,
3259                    volume: 100.0,
3260                    new_high: true,
3261                    new_low: false,
3262                },
3263                CrossSectionMember {
3264                    change: -1.0,
3265                    volume: 100.0,
3266                    new_high: false,
3267                    new_low: true,
3268                },
3269            ],
3270            timestamp: 0,
3271        }
3272        .to_core()
3273        .unwrap();
3274
3275        // Drive every indicator through a varied bar stream with all feeds
3276        // present, so each wrapper's update arm executes without panicking.
3277        for (kind, params) in ALL_SPECS {
3278            let mut ind = build(kind, params).expect("build");
3279            for i in 0..40i64 {
3280                let px = 100.0 + ((i as f64) * 0.3).sin() * 5.0;
3281                let c = candle(px + 0.5, px - 0.5, px);
3282                let bi = BarInput {
3283                    candle: &c,
3284                    reference: Some(px * 0.5),
3285                    deriv: Some(deriv),
3286                    orderbook: Some(&book),
3287                    trades: &trades,
3288                    cross_section: Some(&section),
3289                };
3290                let _ = ind.update(&bi);
3291            }
3292        }
3293    }
3294
3295    #[test]
3296    fn registry_has_full_catalog() {
3297        // Exact, not a floor. The previous bound was `>= 400` against a catalogue
3298        // of 495, so ninety-five indicators could disappear and the test would
3299        // still pass. The generator writes this number from the sources it read,
3300        // so a mismatch means the catalogue moved and this file needs
3301        // regenerating -- a decision, rather than a drift nobody notices.
3302        assert_eq!(
3303            ALL_SPECS.len(),
3304            495,
3305            "catalogue size changed; regenerate registry.rs from the wickra-core sources"
3306        );
3307    }
3308
3309    #[test]
3310    fn unknown_indicator_errors() {
3311        assert!(matches!(
3312            build("Nope", &[1.0]),
3313            Err(BacktestError::UnknownIndicator(_))
3314        ));
3315    }
3316
3317    #[test]
3318    fn rejects_bad_period() {
3319        assert!(build("Sma", &[]).is_err());
3320        assert!(build("Sma", &[0.0]).is_err());
3321        assert!(build("Sma", &[2.5]).is_err());
3322        assert!(build("MacdIndicator", &[12.0, 26.0]).is_err()); // missing signal
3323        assert!(build("BollingerBands", &[20.0]).is_err()); // missing multiplier
3324    }
3325
3326    #[test]
3327    fn aliases_resolve() {
3328        assert!(build("Macd", &[12.0, 26.0, 9.0]).is_ok());
3329        assert!(build("Bollinger", &[20.0, 2.0]).is_ok());
3330    }
3331
3332    #[test]
3333    fn macd_exposes_fields() {
3334        let mut macd = build("MacdIndicator", &[2.0, 3.0, 2.0]).unwrap();
3335        let mut last_fields = Vec::new();
3336        for px in [10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0] {
3337            if macd.update(&input(&candle(px, px, px))).is_some() {
3338                last_fields = macd.fields();
3339            }
3340        }
3341        let names: Vec<&str> = last_fields.iter().map(|(n, _)| *n).collect();
3342        assert!(
3343            names.contains(&"macd") && names.contains(&"signal") && names.contains(&"histogram")
3344        );
3345    }
3346
3347    #[test]
3348    fn single_output_has_no_fields() {
3349        let mut sma = build("Sma", &[2.0]).unwrap();
3350        sma.update(&input(&candle(10.0, 10.0, 10.0)));
3351        sma.update(&input(&candle(20.0, 20.0, 20.0)));
3352        assert!(sma.fields().is_empty());
3353    }
3354}