1use super::{
2 IndicatorBatchOutput, IndicatorBatchRequest, IndicatorDataRef, IndicatorDispatchError,
3 IndicatorParamSet, ParamKV, ParamValue,
4};
5use crate::indicators::absolute_strength_index_oscillator::{
6 absolute_strength_index_oscillator_with_kernel, AbsoluteStrengthIndexOscillatorInput,
7 AbsoluteStrengthIndexOscillatorParams,
8};
9use crate::indicators::accumulation_swing_index::{
10 accumulation_swing_index_with_kernel, AccumulationSwingIndexInput,
11 AccumulationSwingIndexParams,
12};
13use crate::indicators::acosc::{acosc_with_kernel, AcoscInput, AcoscParams};
14use crate::indicators::ad::{ad_with_kernel, AdInput, AdParams};
15use crate::indicators::adaptive_macd::{
16 adaptive_macd_with_kernel, AdaptiveMacdInput, AdaptiveMacdParams,
17};
18use crate::indicators::adaptive_schaff_trend_cycle::{
19 adaptive_schaff_trend_cycle_with_kernel, AdaptiveSchaffTrendCycleInput,
20 AdaptiveSchaffTrendCycleParams,
21};
22use crate::indicators::adaptive_momentum_oscillator::{
23 adaptive_momentum_oscillator_with_kernel, AdaptiveMomentumOscillatorInput,
24 AdaptiveMomentumOscillatorParams,
25};
26use crate::indicators::adjustable_ma_alternating_extremities::{
27 adjustable_ma_alternating_extremities_with_kernel, AdjustableMaAlternatingExtremitiesInput,
28 AdjustableMaAlternatingExtremitiesParams,
29};
30use crate::indicators::adaptive_bandpass_trigger_oscillator::{
31 adaptive_bandpass_trigger_oscillator_with_kernel, AdaptiveBandpassTriggerOscillatorInput,
32 AdaptiveBandpassTriggerOscillatorParams,
33};
34use crate::indicators::adosc::{adosc_with_kernel, AdoscInput, AdoscParams};
35use crate::indicators::advance_decline_line::{
36 advance_decline_line_with_kernel, AdvanceDeclineLineInput, AdvanceDeclineLineParams,
37};
38use crate::indicators::adx::{adx_with_kernel, AdxInput, AdxParams};
39use crate::indicators::adxr::{adxr_with_kernel, AdxrInput, AdxrParams};
40use crate::indicators::alligator::{alligator_with_kernel, AlligatorInput, AlligatorParams};
41use crate::indicators::alphatrend::{alphatrend_with_kernel, AlphaTrendInput, AlphaTrendParams};
42use crate::indicators::andean_oscillator::{
43 andean_oscillator_with_kernel, AndeanOscillatorInput, AndeanOscillatorParams,
44};
45use crate::indicators::ao::{ao_into_slice, AoInput, AoParams};
46use crate::indicators::apo::{apo_with_kernel, ApoInput, ApoParams};
47use crate::indicators::aroon::{aroon_with_kernel, AroonInput, AroonParams};
48use crate::indicators::aroonosc::{aroon_osc_with_kernel, AroonOscInput, AroonOscParams};
49use crate::indicators::aso::{aso_with_kernel, AsoInput, AsoParams};
50use crate::indicators::atr::{atr_with_kernel, AtrInput, AtrParams};
51use crate::indicators::atr_percentile::{
52 atr_percentile_with_kernel, AtrPercentileInput, AtrPercentileParams,
53};
54use crate::indicators::autocorrelation_indicator::{
55 autocorrelation_indicator_with_kernel, AutocorrelationIndicatorInput,
56 AutocorrelationIndicatorParams,
57};
58use crate::indicators::avsl::{avsl_with_kernel, AvslInput, AvslParams};
59use crate::indicators::bandpass::{bandpass_with_kernel, BandPassInput, BandPassParams};
60use crate::indicators::bollinger_bands::{
61 bollinger_bands_with_kernel, BollingerBandsInput, BollingerBandsParams,
62};
63use crate::indicators::bollinger_bands_width::{
64 bollinger_bands_width_with_kernel, BollingerBandsWidthInput, BollingerBandsWidthParams,
65};
66use crate::indicators::bop::{bop_with_kernel, BopInput, BopParams};
67use crate::indicators::bulls_v_bears::{
68 bulls_v_bears_with_kernel, BullsVBearsCalculationMethod, BullsVBearsInput, BullsVBearsMaType,
69 BullsVBearsParams,
70};
71use crate::indicators::bull_power_vs_bear_power::{
72 bull_power_vs_bear_power_with_kernel, BullPowerVsBearPowerInput, BullPowerVsBearPowerParams,
73};
74use crate::indicators::candle_strength_oscillator::{
75 candle_strength_oscillator_with_kernel, CandleStrengthOscillatorInput,
76 CandleStrengthOscillatorParams,
77};
78use crate::indicators::cci::{cci_with_kernel, CciInput, CciParams};
79use crate::indicators::cci_cycle::{cci_cycle_with_kernel, CciCycleInput, CciCycleParams};
80use crate::indicators::cfo::{cfo_with_kernel, CfoInput, CfoParams};
81use crate::indicators::chande::{chande_with_kernel, ChandeInput, ChandeParams};
82use crate::indicators::chandelier_exit::{
83 chandelier_exit_with_kernel, ChandelierExitInput, ChandelierExitParams,
84};
85use crate::indicators::chop::{chop_with_kernel, ChopInput, ChopParams};
86use crate::indicators::cksp::{cksp_with_kernel, CkspInput, CkspParams};
87use crate::indicators::cmo::{cmo_with_kernel, CmoInput, CmoParams};
88use crate::indicators::coppock::{coppock_with_kernel, CoppockInput, CoppockParams};
89use crate::indicators::correl_hl::{correl_hl_with_kernel, CorrelHlInput, CorrelHlParams};
90use crate::indicators::correlation_cycle::{
91 correlation_cycle_with_kernel, CorrelationCycleInput, CorrelationCycleParams,
92};
93use crate::indicators::cycle_channel_oscillator::{
94 cycle_channel_oscillator_with_kernel, CycleChannelOscillatorInput,
95 CycleChannelOscillatorParams,
96};
97use crate::indicators::cyberpunk_value_trend_analyzer::{
98 cyberpunk_value_trend_analyzer_with_kernel, CyberpunkValueTrendAnalyzerInput,
99 CyberpunkValueTrendAnalyzerParams,
100};
101use crate::indicators::daily_factor::{
102 daily_factor_with_kernel, DailyFactorInput, DailyFactorParams,
103};
104use crate::indicators::damiani_volatmeter::{
105 damiani_volatmeter_with_kernel, DamianiVolatmeterInput, DamianiVolatmeterParams,
106};
107use crate::indicators::decisionpoint_breadth_swenlin_trading_oscillator::{
108 decisionpoint_breadth_swenlin_trading_oscillator_with_kernel,
109 DecisionPointBreadthSwenlinTradingOscillatorInput,
110 DecisionPointBreadthSwenlinTradingOscillatorParams,
111};
112use crate::indicators::demand_index::{
113 demand_index_with_kernel, DemandIndexInput, DemandIndexParams,
114};
115use crate::indicators::deviation::{deviation_with_kernel, DeviationInput, DeviationParams};
116use crate::indicators::devstop::{devstop_with_kernel, DevStopInput, DevStopParams};
117use crate::indicators::di::{di_with_kernel, DiInput, DiParams};
118use crate::indicators::directional_imbalance_index::{
119 directional_imbalance_index_with_kernel, DirectionalImbalanceIndexInput,
120 DirectionalImbalanceIndexParams,
121};
122use crate::indicators::disparity_index::{
123 disparity_index_into_slice, DisparityIndexInput, DisparityIndexParams,
124};
125use crate::indicators::didi_index::{didi_index_with_kernel, DidiIndexInput, DidiIndexParams};
126use crate::indicators::dm::{dm_with_kernel, DmInput, DmParams};
127use crate::indicators::donchian::{donchian_with_kernel, DonchianInput, DonchianParams};
128use crate::indicators::donchian_channel_width::{
129 donchian_channel_width_into_slice, DonchianChannelWidthInput, DonchianChannelWidthParams,
130};
131use crate::indicators::dpo::{dpo_with_kernel, DpoInput, DpoParams};
132use crate::indicators::dti::{dti_into_slice, DtiInput, DtiParams};
133use crate::indicators::dual_ulcer_index::{
134 dual_ulcer_index_with_kernel, DualUlcerIndexInput, DualUlcerIndexParams,
135};
136use crate::indicators::dvdiqqe::{dvdiqqe_with_kernel, DvdiqqeInput, DvdiqqeParams};
137use crate::indicators::dx::{dx_batch_with_kernel, dx_into_slice, DxBatchRange, DxInput, DxParams};
138use crate::indicators::dynamic_momentum_index::{
139 dynamic_momentum_index_into_slice, dynamic_momentum_index_with_kernel,
140 DynamicMomentumIndexInput, DynamicMomentumIndexParams,
141};
142use crate::indicators::efi::{efi_with_kernel, EfiInput, EfiParams};
143use crate::indicators::ehlers_autocorrelation_periodogram::{
144 ehlers_autocorrelation_periodogram_with_kernel, EhlersAutocorrelationPeriodogramInput,
145 EhlersAutocorrelationPeriodogramParams,
146};
147use crate::indicators::ehlers_adaptive_cg::{
148 ehlers_adaptive_cg_with_kernel, EhlersAdaptiveCgInput, EhlersAdaptiveCgParams,
149};
150use crate::indicators::ehlers_adaptive_cyber_cycle::{
151 ehlers_adaptive_cyber_cycle_with_kernel, EhlersAdaptiveCyberCycleInput,
152 EhlersAdaptiveCyberCycleParams,
153};
154use crate::indicators::ehlers_data_sampling_relative_strength_indicator::{
155 ehlers_data_sampling_relative_strength_indicator_with_kernel,
156 EhlersDataSamplingRelativeStrengthIndicatorInput,
157 EhlersDataSamplingRelativeStrengthIndicatorParams,
158};
159use crate::indicators::ehlers_fm_demodulator::{
160 ehlers_fm_demodulator_with_kernel, EhlersFmDemodulatorInput, EhlersFmDemodulatorParams,
161};
162use crate::indicators::ehlers_detrending_filter::{
163 ehlers_detrending_filter_with_kernel, EhlersDetrendingFilterInput,
164 EhlersDetrendingFilterParams,
165};
166use crate::indicators::ehlers_linear_extrapolation_predictor::{
167 ehlers_linear_extrapolation_predictor_with_kernel, EhlersLinearExtrapolationPredictorInput,
168 EhlersLinearExtrapolationPredictorParams,
169};
170use crate::indicators::ehlers_simple_cycle_indicator::{
171 ehlers_simple_cycle_indicator_with_kernel, EhlersSimpleCycleIndicatorInput,
172 EhlersSimpleCycleIndicatorParams,
173};
174use crate::indicators::ehlers_smoothed_adaptive_momentum::{
175 ehlers_smoothed_adaptive_momentum_with_kernel, EhlersSmoothedAdaptiveMomentumInput,
176 EhlersSmoothedAdaptiveMomentumParams,
177};
178use crate::indicators::emd::{emd_with_kernel, EmdInput, EmdParams};
179use crate::indicators::emd_trend::{emd_trend_with_kernel, EmdTrendInput, EmdTrendParams};
180use crate::indicators::emv::{emv_with_kernel, EmvInput};
181use crate::indicators::er::{er_with_kernel, ErInput, ErParams};
182use crate::indicators::eri::{eri_with_kernel, EriInput, EriParams};
183use crate::indicators::ewma_volatility::{
184 ewma_volatility_with_kernel, EwmaVolatilityInput, EwmaVolatilityParams,
185};
186use crate::indicators::evasive_supertrend::{
187 evasive_supertrend_with_kernel, EvasiveSuperTrendInput, EvasiveSuperTrendParams,
188};
189use crate::indicators::exponential_trend::{
190 exponential_trend_with_kernel, ExponentialTrendInput, ExponentialTrendParams,
191};
192use crate::indicators::fibonacci_entry_bands::{
193 fibonacci_entry_bands_with_kernel, FibonacciEntryBandsInput, FibonacciEntryBandsParams,
194};
195use crate::indicators::fibonacci_trailing_stop::{
196 fibonacci_trailing_stop_with_kernel, FibonacciTrailingStopInput, FibonacciTrailingStopParams,
197};
198use crate::indicators::fisher::{fisher_with_kernel, FisherInput, FisherParams};
199use crate::indicators::forward_backward_exponential_oscillator::{
200 forward_backward_exponential_oscillator_with_kernel, ForwardBackwardExponentialOscillatorInput,
201 ForwardBackwardExponentialOscillatorParams,
202};
203use crate::indicators::fosc::{fosc_with_kernel, FoscInput, FoscParams};
204use crate::indicators::fractal_dimension_index::{
205 fractal_dimension_index_with_kernel, FractalDimensionIndexInput, FractalDimensionIndexParams,
206};
207use crate::indicators::fvg_positioning_average::{
208 fvg_positioning_average_with_kernel, FvgPositioningAverageInput, FvgPositioningAverageParams,
209};
210use crate::indicators::fvg_trailing_stop::{
211 fvg_trailing_stop_with_kernel, FvgTrailingStopInput, FvgTrailingStopParams,
212};
213use crate::indicators::garman_klass_volatility::{
214 garman_klass_volatility_with_kernel, GarmanKlassVolatilityInput, GarmanKlassVolatilityParams,
215};
216use crate::indicators::gatorosc::{gatorosc_with_kernel, GatorOscInput, GatorOscParams};
217use crate::indicators::geometric_bias_oscillator::{
218 geometric_bias_oscillator_with_kernel, GeometricBiasOscillatorInput,
219 GeometricBiasOscillatorParams,
220};
221use crate::indicators::gmma_oscillator::{
222 gmma_oscillator_with_kernel, GmmaOscillatorInput, GmmaOscillatorParams,
223};
224use crate::indicators::goertzel_cycle_composite_wave::{
225 goertzel_cycle_composite_wave_into_slice, GoertzelCycleCompositeWaveInput,
226 GoertzelCycleCompositeWaveParams, GoertzelDetrendMode,
227};
228use crate::indicators::gopalakrishnan_range_index::{
229 gopalakrishnan_range_index_with_kernel, GopalakrishnanRangeIndexInput,
230 GopalakrishnanRangeIndexParams,
231};
232use crate::indicators::grover_llorens_cycle_oscillator::{
233 grover_llorens_cycle_oscillator_with_kernel, GroverLlorensCycleOscillatorInput,
234 GroverLlorensCycleOscillatorParams,
235};
236use crate::indicators::half_causal_estimator::{
237 half_causal_estimator_with_kernel, HalfCausalEstimatorConfidenceAdjust,
238 HalfCausalEstimatorInput, HalfCausalEstimatorKernelType, HalfCausalEstimatorParams,
239};
240use crate::indicators::halftrend::{halftrend_with_kernel, HalfTrendInput, HalfTrendParams};
241use crate::indicators::hema_trend_levels::{
242 hema_trend_levels_with_kernel, HemaTrendLevelsInput, HemaTrendLevelsParams,
243};
244use crate::indicators::historical_volatility::{
245 historical_volatility_with_kernel, HistoricalVolatilityInput, HistoricalVolatilityParams,
246};
247use crate::indicators::historical_volatility_percentile::{
248 historical_volatility_percentile_with_kernel, HistoricalVolatilityPercentileInput,
249 HistoricalVolatilityPercentileParams,
250};
251use crate::indicators::historical_volatility_rank::{
252 historical_volatility_rank_with_kernel, HistoricalVolatilityRankInput,
253 HistoricalVolatilityRankParams,
254};
255use crate::indicators::hull_butterfly_oscillator::{
256 hull_butterfly_oscillator_with_kernel, HullButterflyOscillatorInput,
257 HullButterflyOscillatorParams,
258};
259use crate::indicators::hypertrend::{hypertrend_with_kernel, HyperTrendInput, HyperTrendParams};
260use crate::indicators::ift_rsi::{ift_rsi_with_kernel, IftRsiInput, IftRsiParams};
261use crate::indicators::ichimoku_oscillator::{
262 ichimoku_oscillator_with_kernel, IchimokuOscillatorInput, IchimokuOscillatorNormalizeMode,
263 IchimokuOscillatorParams,
264};
265use crate::indicators::ict_propulsion_block::{
266 ict_propulsion_block_with_kernel, IctPropulsionBlockInput,
267 IctPropulsionBlockMitigationPrice, IctPropulsionBlockParams,
268};
269use crate::indicators::impulse_macd::{
270 impulse_macd_with_kernel, ImpulseMacdInput, ImpulseMacdParams,
271};
272use crate::indicators::intraday_momentum_index::{
273 intraday_momentum_index_with_kernel, IntradayMomentumIndexInput, IntradayMomentumIndexParams,
274};
275use crate::indicators::kase_peak_oscillator_with_divergences::{
276 kase_peak_oscillator_with_divergences_with_kernel, KasePeakOscillatorWithDivergencesInput,
277 KasePeakOscillatorWithDivergencesParams,
278};
279use crate::indicators::kairi_relative_index::{
280 kairi_relative_index_into_slice, KairiRelativeIndexInput, KairiRelativeIndexParams,
281};
282use crate::indicators::kaufmanstop::{
283 kaufmanstop_with_kernel, KaufmanstopInput, KaufmanstopParams,
284};
285use crate::indicators::kdj::{kdj_with_kernel, KdjInput, KdjParams};
286use crate::indicators::keltner::{keltner_with_kernel, KeltnerInput, KeltnerParams};
287use crate::indicators::keltner_channel_width_oscillator::{
288 keltner_channel_width_oscillator_with_kernel, KeltnerChannelWidthOscillatorInput,
289 KeltnerChannelWidthOscillatorParams,
290};
291use crate::indicators::kst::{kst_with_kernel, KstInput, KstParams};
292use crate::indicators::kurtosis::{kurtosis_with_kernel, KurtosisInput, KurtosisParams};
293use crate::indicators::kvo::{kvo_with_kernel, KvoInput, KvoParams};
294use crate::indicators::leavitt_convolution_acceleration::{
295 leavitt_convolution_acceleration_with_kernel, LeavittConvolutionAccelerationInput,
296 LeavittConvolutionAccelerationParams,
297};
298use crate::indicators::linear_regression_intensity::{
299 linear_regression_intensity_with_kernel, LinearRegressionIntensityInput,
300 LinearRegressionIntensityParams,
301};
302use crate::indicators::l1_ehlers_phasor::{
303 l1_ehlers_phasor_with_kernel, L1EhlersPhasorInput, L1EhlersPhasorParams,
304};
305use crate::indicators::l2_ehlers_signal_to_noise::{
306 l2_ehlers_signal_to_noise_with_kernel, L2EhlersSignalToNoiseInput, L2EhlersSignalToNoiseParams,
307};
308use crate::indicators::linear_correlation_oscillator::{
309 linear_correlation_oscillator_with_kernel, LinearCorrelationOscillatorInput,
310 LinearCorrelationOscillatorParams,
311};
312use crate::indicators::linearreg_angle::{
313 linearreg_angle_with_kernel, Linearreg_angleInput, Linearreg_angleParams,
314};
315use crate::indicators::linearreg_intercept::{
316 linearreg_intercept_with_kernel, LinearRegInterceptInput, LinearRegInterceptParams,
317};
318use crate::indicators::linearreg_slope::{
319 linearreg_slope_with_kernel, LinearRegSlopeInput, LinearRegSlopeParams,
320};
321use crate::indicators::lpc::{lpc_with_kernel, LpcInput, LpcParams};
322use crate::indicators::lrsi::{lrsi_with_kernel, LrsiInput, LrsiParams};
323use crate::indicators::mab::{mab_with_kernel, MabInput, MabParams};
324use crate::indicators::macd::{macd_with_kernel, MacdInput, MacdParams};
325use crate::indicators::macd_wave_signal_pro::{
326 macd_wave_signal_pro_with_kernel, MacdWaveSignalProInput,
327};
328use crate::indicators::macz::{macz_with_kernel, MaczInput, MaczParams};
329use crate::indicators::market_meanness_index::{
330 market_meanness_index_with_kernel, MarketMeannessIndexInput, MarketMeannessIndexParams,
331};
332use crate::indicators::market_structure_trailing_stop::{
333 market_structure_trailing_stop_with_kernel, MarketStructureTrailingStopInput,
334 MarketStructureTrailingStopParams,
335};
336use crate::indicators::mass::{mass_with_kernel, MassInput, MassParams};
337use crate::indicators::mean_ad::{mean_ad_with_kernel, MeanAdInput, MeanAdParams};
338use crate::indicators::medium_ad::{medium_ad_with_kernel, MediumAdInput, MediumAdParams};
339use crate::indicators::medprice::{medprice_with_kernel, MedpriceInput, MedpriceParams};
340use crate::indicators::mesa_stochastic_multi_length::{
341 mesa_stochastic_multi_length_with_kernel, MesaStochasticMultiLengthInput,
342 MesaStochasticMultiLengthParams,
343};
344use crate::indicators::mfi::{
345 mfi_batch_with_kernel, mfi_into_slice, MfiBatchRange, MfiInput, MfiParams,
346};
347use crate::indicators::midpoint::{midpoint_with_kernel, MidpointInput, MidpointParams};
348use crate::indicators::midprice::{midprice_with_kernel, MidpriceInput, MidpriceParams};
349use crate::indicators::minmax::{minmax_with_kernel, MinmaxInput, MinmaxParams};
350use crate::indicators::mod_god_mode::{
351 mod_god_mode, ModGodModeData, ModGodModeInput, ModGodModeMode, ModGodModeParams,
352};
353use crate::indicators::mom::{mom_with_kernel, MomInput, MomParams};
354use crate::indicators::monotonicity_index::{
355 monotonicity_index_with_kernel, MonotonicityIndexInput, MonotonicityIndexMode,
356 MonotonicityIndexParams,
357};
358use crate::indicators::momentum_ratio_oscillator::{
359 momentum_ratio_oscillator_with_kernel, MomentumRatioOscillatorInput,
360 MomentumRatioOscillatorParams,
361};
362use crate::indicators::moving_average_cross_probability::{
363 moving_average_cross_probability_with_kernel, MovingAverageCrossProbabilityInput,
364 MovingAverageCrossProbabilityMaType, MovingAverageCrossProbabilityParams,
365};
366use crate::indicators::moving_averages::ma::MaData;
367use crate::indicators::moving_averages::ma_batch::{
368 ma_batch_with_kernel_and_typed_params, MaBatchParamKV, MaBatchParamValue,
369};
370use crate::indicators::moving_averages::logarithmic_moving_average::{
371 logarithmic_moving_average_with_kernel, LogarithmicMovingAverageInput,
372 LogarithmicMovingAverageParams,
373};
374use crate::indicators::moving_averages::registry::list_moving_averages;
375use crate::indicators::msw::{msw_with_kernel, MswInput, MswParams};
376use crate::indicators::multi_length_stochastic_average::{
377 multi_length_stochastic_average_with_kernel, MultiLengthStochasticAverageInput,
378 MultiLengthStochasticAverageParams,
379};
380use crate::indicators::nadaraya_watson_envelope::{
381 nadaraya_watson_envelope_with_kernel, NweInput, NweParams,
382};
383use crate::indicators::natr::{natr_with_kernel, NatrInput, NatrParams};
384use crate::indicators::neighboring_trailing_stop::{
385 neighboring_trailing_stop_with_kernel, NeighboringTrailingStopInput,
386 NeighboringTrailingStopParams,
387};
388use crate::indicators::net_myrsi::{net_myrsi_with_kernel, NetMyrsiInput, NetMyrsiParams};
389use crate::indicators::nonlinear_regression_zero_lag_moving_average::{
390 nonlinear_regression_zero_lag_moving_average_with_kernel,
391 NonlinearRegressionZeroLagMovingAverageInput,
392 NonlinearRegressionZeroLagMovingAverageParams,
393};
394use crate::indicators::normalized_volume_true_range::{
395 normalized_volume_true_range_with_kernel, NormalizedVolumeTrueRangeInput,
396 NormalizedVolumeTrueRangeParams, NormalizedVolumeTrueRangeStyle,
397};
398use crate::indicators::normalized_resonator::{
399 normalized_resonator_with_kernel, NormalizedResonatorInput, NormalizedResonatorParams,
400};
401use crate::indicators::nvi::{nvi_with_kernel, NviInput, NviParams};
402use crate::indicators::obv::{obv_with_kernel, ObvInput, ObvParams};
403use crate::indicators::on_balance_volume_oscillator::{
404 on_balance_volume_oscillator_with_kernel, OnBalanceVolumeOscillatorInput,
405 OnBalanceVolumeOscillatorParams,
406};
407use crate::indicators::otto::{otto_with_kernel, OttoInput, OttoParams};
408use crate::indicators::parkinson_volatility::{
409 parkinson_volatility_with_kernel, ParkinsonVolatilityInput, ParkinsonVolatilityParams,
410};
411use crate::indicators::price_moving_average_ratio_percentile::{
412 price_moving_average_ratio_percentile_with_kernel, PriceMovingAverageRatioPercentileInput,
413 PriceMovingAverageRatioPercentileLineMode, PriceMovingAverageRatioPercentileMaType,
414 PriceMovingAverageRatioPercentileParams,
415};
416use crate::indicators::percentile_nearest_rank::{
417 percentile_nearest_rank_with_kernel, PercentileNearestRankInput, PercentileNearestRankParams,
418};
419use crate::indicators::pfe::{pfe_with_kernel, PfeInput, PfeParams};
420use crate::indicators::pivot::{pivot_with_kernel, PivotInput, PivotParams};
421use crate::indicators::pma::{pma_with_kernel, PmaInput, PmaParams};
422use crate::indicators::possible_rsi::{
423 possible_rsi_with_kernel, PossibleRsiInput, PossibleRsiParams,
424};
425use crate::indicators::polynomial_regression_extrapolation::{
426 polynomial_regression_extrapolation_with_kernel, PolynomialRegressionExtrapolationInput,
427 PolynomialRegressionExtrapolationParams,
428};
429use crate::indicators::ppo::{ppo_with_kernel, PpoInput, PpoParams};
430use crate::indicators::prb::{prb_with_kernel, PrbInput, PrbParams};
431use crate::indicators::premier_rsi_oscillator::{
432 premier_rsi_oscillator_with_kernel, PremierRsiOscillatorInput, PremierRsiOscillatorParams,
433};
434use crate::indicators::pvi::{pvi_with_kernel, PviInput, PviParams};
435use crate::indicators::pretty_good_oscillator::{
436 pretty_good_oscillator_with_kernel, PrettyGoodOscillatorInput, PrettyGoodOscillatorParams,
437};
438use crate::indicators::price_density_market_noise::{
439 price_density_market_noise_with_kernel, PriceDensityMarketNoiseInput,
440 PriceDensityMarketNoiseParams,
441};
442use crate::indicators::projection_oscillator::{
443 projection_oscillator_with_kernel, ProjectionOscillatorInput, ProjectionOscillatorParams,
444};
445use crate::indicators::psychological_line::{
446 psychological_line_with_kernel, PsychologicalLineInput, PsychologicalLineParams,
447};
448use crate::indicators::qqe::{qqe_with_kernel, QqeInput, QqeParams};
449use crate::indicators::qqe_weighted_oscillator::{
450 qqe_weighted_oscillator_with_kernel, QqeWeightedOscillatorInput, QqeWeightedOscillatorParams,
451};
452use crate::indicators::qstick::{qstick_with_kernel, QstickInput, QstickParams};
453use crate::indicators::random_walk_index::{
454 random_walk_index_with_kernel, RandomWalkIndexInput, RandomWalkIndexParams,
455};
456use crate::indicators::range_breakout_signals::{
457 range_breakout_signals_with_kernel, RangeBreakoutSignalsInput, RangeBreakoutSignalsParams,
458};
459use crate::indicators::range_filter::{
460 range_filter_with_kernel, RangeFilterInput, RangeFilterParams,
461};
462use crate::indicators::rank_correlation_index::{
463 rank_correlation_index_with_kernel, RankCorrelationIndexInput, RankCorrelationIndexParams,
464};
465use crate::indicators::market_structure_confluence::{
466 market_structure_confluence_with_kernel, MarketStructureConfluenceInput,
467 MarketStructureConfluenceParams,
468};
469use crate::indicators::range_filtered_trend_signals::{
470 range_filtered_trend_signals_with_kernel, RangeFilteredTrendSignalsInput,
471 RangeFilteredTrendSignalsParams,
472};
473use crate::indicators::range_oscillator::{
474 range_oscillator_with_kernel, RangeOscillatorInput, RangeOscillatorParams,
475};
476use crate::indicators::registry::{
477 get_indicator, IndicatorInfo, IndicatorInputKind, ParamValueStatic,
478};
479use crate::indicators::regression_slope_oscillator::{
480 regression_slope_oscillator_with_kernel, RegressionSlopeOscillatorInput,
481 RegressionSlopeOscillatorParams,
482};
483use crate::indicators::relative_strength_index_wave_indicator::{
484 relative_strength_index_wave_indicator_with_kernel, RelativeStrengthIndexWaveIndicatorInput,
485 RelativeStrengthIndexWaveIndicatorParams,
486};
487use crate::indicators::reversal_signals::{
488 reversal_signals_with_kernel, ReversalSignalsInput, ReversalSignalsParams,
489};
490use crate::indicators::reverse_rsi::{reverse_rsi_with_kernel, ReverseRsiInput, ReverseRsiParams};
491use crate::indicators::roc::{roc_with_kernel, RocInput, RocParams};
492use crate::indicators::rocp::{rocp_with_kernel, RocpInput, RocpParams};
493use crate::indicators::rocr::{rocr_with_kernel, RocrInput, RocrParams};
494use crate::indicators::rogers_satchell_volatility::{
495 rogers_satchell_volatility_with_kernel, RogersSatchellVolatilityInput,
496 RogersSatchellVolatilityParams,
497};
498use crate::indicators::rolling_skewness_kurtosis::{
499 rolling_skewness_kurtosis_with_kernel, RollingSkewnessKurtosisInput,
500 RollingSkewnessKurtosisParams,
501};
502use crate::indicators::rolling_z_score_trend::{
503 rolling_z_score_trend_with_kernel, RollingZScoreTrendInput, RollingZScoreTrendParams,
504};
505use crate::indicators::rsi::{rsi_with_kernel, RsiInput, RsiParams};
506use crate::indicators::rsmk::{rsmk_with_kernel, RsmkInput, RsmkParams};
507use crate::indicators::rvi::{rvi_with_kernel, RviInput, RviParams};
508use crate::indicators::safezonestop::{
509 safezonestop_with_kernel, SafeZoneStopInput, SafeZoneStopParams,
510};
511use crate::indicators::smooth_theil_sen::{
512 smooth_theil_sen_with_kernel, SmoothTheilSenDeviationType, SmoothTheilSenInput,
513 SmoothTheilSenParams, SmoothTheilSenStatStyle,
514};
515use crate::indicators::spearman_correlation::{
516 spearman_correlation_with_kernel, SpearmanCorrelationInput, SpearmanCorrelationParams,
517};
518use crate::indicators::squeeze_index::{
519 squeeze_index_with_kernel, SqueezeIndexInput, SqueezeIndexParams,
520};
521use crate::indicators::squeeze_momentum::{
522 squeeze_momentum_with_kernel, SqueezeMomentumInput, SqueezeMomentumParams,
523};
524use crate::indicators::smoothed_gaussian_trend_filter::{
525 smoothed_gaussian_trend_filter_with_kernel, SmoothedGaussianTrendFilterInput,
526 SmoothedGaussianTrendFilterParams,
527};
528use crate::indicators::srsi::{srsi_with_kernel, SrsiInput, SrsiParams};
529use crate::indicators::standardized_psar_oscillator::{
530 standardized_psar_oscillator_with_kernel, StandardizedPsarOscillatorInput,
531 StandardizedPsarOscillatorParams,
532};
533use crate::indicators::statistical_trailing_stop::{
534 statistical_trailing_stop_with_kernel, StatisticalTrailingStopInput,
535 StatisticalTrailingStopParams,
536};
537use crate::indicators::stc::{stc_with_kernel, StcInput, StcParams};
538use crate::indicators::stddev::{stddev_with_kernel, StdDevInput, StdDevParams};
539use crate::indicators::stoch::{stoch_with_kernel, StochInput, StochParams};
540use crate::indicators::stochastic_distance::{
541 stochastic_distance_with_kernel, StochasticDistanceInput, StochasticDistanceParams,
542};
543use crate::indicators::stochastic_money_flow_index::{
544 stochastic_money_flow_index_with_kernel, StochasticMoneyFlowIndexInput,
545 StochasticMoneyFlowIndexParams,
546};
547use crate::indicators::stochastic_adaptive_d::{
548 stochastic_adaptive_d_with_kernel, StochasticAdaptiveDInput, StochasticAdaptiveDParams,
549};
550use crate::indicators::stochastic_connors_rsi::{
551 stochastic_connors_rsi_with_kernel, StochasticConnorsRsiInput, StochasticConnorsRsiParams,
552};
553use crate::indicators::stochf::{stochf_with_kernel, StochfInput, StochfParams};
554use crate::indicators::supertrend::{supertrend_with_kernel, SuperTrendInput, SuperTrendParams};
555use crate::indicators::supertrend_oscillator::{
556 supertrend_oscillator_with_kernel, SuperTrendOscillatorInput, SuperTrendOscillatorParams,
557};
558use crate::indicators::supertrend_recovery::{
559 supertrend_recovery_with_kernel, SuperTrendRecoveryInput, SuperTrendRecoveryParams,
560};
561use crate::indicators::trend_trigger_factor::{
562 trend_trigger_factor_with_kernel, TrendTriggerFactorInput, TrendTriggerFactorParams,
563};
564use crate::indicators::trend_flow_trail::{
565 trend_flow_trail_with_kernel, TrendFlowTrailInput, TrendFlowTrailParams,
566};
567use crate::indicators::trend_direction_force_index::{
568 trend_direction_force_index_into_slice, TrendDirectionForceIndexInput,
569 TrendDirectionForceIndexParams,
570};
571use crate::indicators::trix::{
572 trix_batch_with_kernel, trix_into_slice, trix_with_kernel, TrixBatchRange, TrixInput,
573 TrixParams,
574};
575use crate::indicators::tsf::{tsf_with_kernel, TsfInput, TsfParams};
576use crate::indicators::tsi::{tsi_with_kernel, TsiInput, TsiParams};
577use crate::indicators::ttm_squeeze::{ttm_squeeze_with_kernel, TtmSqueezeInput, TtmSqueezeParams};
578use crate::indicators::ttm_trend::{ttm_trend_with_kernel, TtmTrendInput, TtmTrendParams};
579use crate::indicators::trend_continuation_factor::{
580 trend_continuation_factor_with_kernel, TrendContinuationFactorInput,
581 TrendContinuationFactorParams,
582};
583use crate::indicators::twiggs_money_flow::{
584 twiggs_money_flow_with_kernel, TwiggsMoneyFlowInput, TwiggsMoneyFlowParams,
585};
586use crate::indicators::ui::{ui_with_kernel, UiInput, UiParams};
587use crate::indicators::ultosc::{ultosc_with_kernel, UltOscInput, UltOscParams};
588use crate::indicators::var::{var_with_kernel, VarInput, VarParams};
589use crate::indicators::vdubus_divergence_wave_pattern_generator::{
590 vdubus_divergence_wave_pattern_generator_with_kernel,
591 VdubusDivergenceWavePatternGeneratorInput,
592 VdubusDivergenceWavePatternGeneratorParams,
593};
594use crate::indicators::velocity::{velocity_with_kernel, VelocityInput, VelocityParams};
595use crate::indicators::velocity_acceleration_convergence_divergence_indicator::{
596 velocity_acceleration_convergence_divergence_indicator_with_kernel,
597 VelocityAccelerationConvergenceDivergenceIndicatorInput,
598 VelocityAccelerationConvergenceDivergenceIndicatorParams,
599};
600use crate::indicators::velocity_acceleration_indicator::{
601 velocity_acceleration_indicator_with_kernel, VelocityAccelerationIndicatorInput,
602 VelocityAccelerationIndicatorParams,
603};
604use crate::indicators::vertical_horizontal_filter::{
605 vertical_horizontal_filter_with_kernel, VerticalHorizontalFilterInput,
606 VerticalHorizontalFilterParams,
607};
608use crate::indicators::vi::{vi_with_kernel, ViInput, ViParams};
609use crate::indicators::vidya::{vidya_with_kernel, VidyaInput, VidyaParams};
610use crate::indicators::vlma::{vlma_with_kernel, VlmaInput, VlmaParams};
611use crate::indicators::volatility_quality_index::{
612 volatility_quality_index_with_kernel, VolatilityQualityIndexInput, VolatilityQualityIndexParams,
613};
614use crate::indicators::volatility_ratio_adaptive_rsx::{
615 volatility_ratio_adaptive_rsx_with_kernel, VolatilityRatioAdaptiveRsxInput,
616 VolatilityRatioAdaptiveRsxParams,
617};
618use crate::indicators::volume_energy_reservoirs::{
619 volume_energy_reservoirs_with_kernel, VolumeEnergyReservoirsInput, VolumeEnergyReservoirsParams,
620};
621use crate::indicators::volume_weighted_rsi::{
622 volume_weighted_rsi_batch_with_kernel, volume_weighted_rsi_into_slice,
623 VolumeWeightedRsiBatchRange, VolumeWeightedRsiInput, VolumeWeightedRsiParams,
624};
625use crate::indicators::volume_weighted_relative_strength_index::{
626 volume_weighted_relative_strength_index_with_kernel, VolumeWeightedRelativeStrengthIndexInput,
627 VolumeWeightedRelativeStrengthIndexParams,
628};
629use crate::indicators::volume_weighted_stochastic_rsi::{
630 volume_weighted_stochastic_rsi_with_kernel, VolumeWeightedStochasticRsiInput,
631 VolumeWeightedStochasticRsiParams,
632};
633use crate::indicators::volume_zone_oscillator::{
634 volume_zone_oscillator_with_kernel, VolumeZoneOscillatorInput, VolumeZoneOscillatorParams,
635};
636use crate::indicators::vosc::{vosc_with_kernel, VoscInput, VoscParams};
637use crate::indicators::voss::{voss_with_kernel, VossInput, VossParams};
638use crate::indicators::vpci::{vpci_with_kernel, VpciInput, VpciParams};
639use crate::indicators::vpt::{vpt_with_kernel, VptInput};
640use crate::indicators::vwap_deviation_oscillator::{
641 vwap_deviation_oscillator_with_kernel, VwapDeviationMode, VwapDeviationOscillatorInput,
642 VwapDeviationOscillatorParams, VwapDeviationSessionMode,
643};
644use crate::indicators::vwap_zscore_with_signals::{
645 vwap_zscore_with_signals_with_kernel, VwapZscoreWithSignalsInput, VwapZscoreWithSignalsParams,
646};
647use crate::indicators::vwmacd::{vwmacd_with_kernel, VwmacdInput, VwmacdParams};
648use crate::indicators::wad::{wad_with_kernel, WadInput};
649use crate::indicators::wavetrend::{wavetrend_with_kernel, WavetrendInput, WavetrendParams};
650use crate::indicators::wclprice::{wclprice_with_kernel, WclpriceInput};
651use crate::indicators::willr::{willr_with_kernel, WillrInput, WillrParams};
652use crate::indicators::wto::{wto_with_kernel, WtoInput, WtoParams};
653use crate::indicators::yang_zhang_volatility::{
654 yang_zhang_volatility_with_kernel, YangZhangVolatilityInput, YangZhangVolatilityParams,
655};
656use crate::indicators::zig_zag_channels::{
657 zig_zag_channels_with_kernel, ZigZagChannelsInput, ZigZagChannelsParams,
658};
659use crate::indicators::zscore::{zscore_with_kernel, ZscoreInput, ZscoreParams};
660use crate::indicators::{cg::cg_with_kernel, cg::CgInput, cg::CgParams};
661use crate::utilities::data_loader::source_type;
662use crate::utilities::enums::Kernel;
663use std::collections::HashMap;
664use std::str::FromStr;
665
666pub fn compute_cpu_batch(
667 req: IndicatorBatchRequest<'_>,
668) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
669 compute_cpu_batch_internal(req, false)
670}
671
672pub fn compute_cpu_batch_strict(
673 req: IndicatorBatchRequest<'_>,
674) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
675 compute_cpu_batch_internal(req, true)
676}
677
678fn compute_cpu_batch_internal(
679 req: IndicatorBatchRequest<'_>,
680 strict_inputs: bool,
681) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
682 if !strict_inputs {
683 if let Some(out) = try_fast_dispatch_non_strict(req) {
684 return out;
685 }
686 }
687
688 let info = get_indicator(req.indicator_id);
689
690 if let Some(info) = info {
691 if strict_inputs {
692 validate_input_kind_strict(info.id, info.input_kind, req.data)?;
693 }
694
695 let output_id = resolve_output_id(info, req.output_id)?;
696
697 if info.id.eq_ignore_ascii_case("logarithmic_moving_average") {
698 return compute_logarithmic_moving_average_batch(req, output_id);
699 }
700
701 if is_moving_average(info.id) {
702 return compute_ma_batch(req, info, output_id);
703 }
704
705 return dispatch_cpu_batch_by_indicator(req, info.id, output_id);
706 }
707
708 let output_id = req.output_id.unwrap_or("value");
709 match dispatch_cpu_batch_by_indicator(req, req.indicator_id, output_id) {
710 Err(IndicatorDispatchError::UnsupportedCapability { .. }) => {
711 Err(IndicatorDispatchError::UnknownIndicator {
712 id: req.indicator_id.to_string(),
713 })
714 }
715 other => other,
716 }
717}
718
719fn try_fast_dispatch_non_strict(
720 req: IndicatorBatchRequest<'_>,
721) -> Option<Result<IndicatorBatchOutput, IndicatorDispatchError>> {
722 let id = req.indicator_id;
723 let output_id = req.output_id;
724
725 if !id.as_bytes().iter().any(|b| b.is_ascii_uppercase()) {
726 return match id {
727 "bop" => Some(compute_bop_batch(req, output_id.unwrap_or("value"))),
728 "dpo" => Some(compute_dpo_batch(req, output_id.unwrap_or("value"))),
729 "cmo" => Some(compute_cmo_batch(req, output_id.unwrap_or("value"))),
730 "fosc" => Some(compute_fosc_batch(req, output_id.unwrap_or("value"))),
731 "emv" => Some(compute_emv_batch(req, output_id.unwrap_or("value"))),
732 "cci_cycle" => Some(compute_cci_cycle_batch(req, output_id.unwrap_or("value"))),
733 "cfo" => Some(compute_cfo_batch(req, output_id.unwrap_or("value"))),
734 "ehlers_adaptive_cg" => Some(compute_ehlers_adaptive_cg_batch(
735 req,
736 output_id.unwrap_or("cg"),
737 )),
738 "adaptive_momentum_oscillator" => Some(compute_adaptive_momentum_oscillator_batch(
739 req,
740 output_id.unwrap_or("amo"),
741 )),
742 "lrsi" => Some(compute_lrsi_batch(req, output_id.unwrap_or("value"))),
743 "nvi" => Some(compute_nvi_batch(req, output_id.unwrap_or("value"))),
744 "mom" => Some(compute_mom_batch(req, output_id.unwrap_or("value"))),
745 "velocity" => Some(compute_velocity_batch(req, output_id.unwrap_or("value"))),
746 "normalized_volume_true_range" => Some(compute_normalized_volume_true_range_batch(
747 req,
748 output_id.unwrap_or("normalized_volume"),
749 )),
750 "exponential_trend" => Some(compute_exponential_trend_batch(
751 req,
752 output_id.unwrap_or("uptrend_base"),
753 )),
754 "trend_flow_trail" => Some(compute_trend_flow_trail_batch(
755 req,
756 output_id.unwrap_or("alpha_trail"),
757 )),
758 "range_breakout_signals" => Some(compute_range_breakout_signals_batch(
759 req,
760 output_id.unwrap_or("range_top"),
761 )),
762 "vi" => {
763 if let Some(out) = output_id {
764 Some(compute_vi_batch(req, out))
765 } else {
766 None
767 }
768 }
769 "wto" => {
770 if let Some(out) = output_id {
771 Some(compute_wto_batch(req, out))
772 } else {
773 None
774 }
775 }
776 "rogers_satchell_volatility" => {
777 if let Some(out) = output_id {
778 Some(compute_rogers_satchell_volatility_batch(req, out))
779 } else {
780 None
781 }
782 }
783 "historical_volatility_rank" => {
784 if let Some(out) = output_id {
785 Some(compute_historical_volatility_rank_batch(req, out))
786 } else {
787 None
788 }
789 }
790 "dual_ulcer_index" => {
791 if let Some(out) = output_id {
792 Some(compute_dual_ulcer_index_batch(req, out))
793 } else {
794 None
795 }
796 }
797 "fractal_dimension_index" => {
798 if let Some(out) = output_id {
799 Some(compute_fractal_dimension_index_batch(req, out))
800 } else {
801 None
802 }
803 }
804 "volume_weighted_rsi" => {
805 if let Some(out) = output_id {
806 Some(compute_volume_weighted_rsi_batch(req, out))
807 } else {
808 None
809 }
810 }
811 "dynamic_momentum_index" => {
812 if let Some(out) = output_id {
813 Some(compute_dynamic_momentum_index_batch(req, out))
814 } else {
815 None
816 }
817 }
818 "disparity_index" => {
819 if let Some(out) = output_id {
820 Some(compute_disparity_index_batch(req, out))
821 } else {
822 None
823 }
824 }
825 "donchian_channel_width" => {
826 if let Some(out) = output_id {
827 Some(compute_donchian_channel_width_batch(req, out))
828 } else {
829 None
830 }
831 }
832 "kairi_relative_index" => {
833 if let Some(out) = output_id {
834 Some(compute_kairi_relative_index_batch(req, out))
835 } else {
836 None
837 }
838 }
839 "projection_oscillator" => {
840 if let Some(out) = output_id {
841 Some(compute_projection_oscillator_batch(req, out))
842 } else {
843 None
844 }
845 }
846 "market_structure_trailing_stop" => {
847 if let Some(out) = output_id {
848 Some(compute_market_structure_trailing_stop_batch(req, out))
849 } else {
850 None
851 }
852 }
853 "emd_trend" => {
854 if let Some(out) = output_id {
855 Some(compute_emd_trend_batch(req, out))
856 } else {
857 None
858 }
859 }
860 "cyberpunk_value_trend_analyzer" => {
861 if let Some(out) = output_id {
862 Some(compute_cyberpunk_value_trend_analyzer_batch(req, out))
863 } else {
864 None
865 }
866 }
867 "evasive_supertrend" => {
868 if let Some(out) = output_id {
869 Some(compute_evasive_supertrend_batch(req, out))
870 } else {
871 None
872 }
873 }
874 "reversal_signals" => {
875 if let Some(out) = output_id {
876 Some(compute_reversal_signals_batch(req, out))
877 } else {
878 None
879 }
880 }
881 "zig_zag_channels" => {
882 if let Some(out) = output_id {
883 Some(compute_zig_zag_channels_batch(req, out))
884 } else {
885 None
886 }
887 }
888 "directional_imbalance_index" => {
889 if let Some(out) = output_id {
890 Some(compute_directional_imbalance_index_batch(req, out))
891 } else {
892 None
893 }
894 }
895 "candle_strength_oscillator" => {
896 if let Some(out) = output_id {
897 Some(compute_candle_strength_oscillator_batch(req, out))
898 } else {
899 None
900 }
901 }
902 "gmma_oscillator" => {
903 if let Some(out) = output_id {
904 Some(compute_gmma_oscillator_batch(req, out))
905 } else {
906 None
907 }
908 }
909 "nonlinear_regression_zero_lag_moving_average" => {
910 if let Some(out) = output_id {
911 Some(compute_nonlinear_regression_zero_lag_moving_average_batch(
912 req, out,
913 ))
914 } else {
915 None
916 }
917 }
918 "possible_rsi" => {
919 if let Some(out) = output_id {
920 Some(compute_possible_rsi_batch(req, out))
921 } else {
922 None
923 }
924 }
925 "autocorrelation_indicator" => {
926 if let Some(out) = output_id {
927 Some(compute_autocorrelation_indicator_batch(req, out))
928 } else {
929 None
930 }
931 }
932 "goertzel_cycle_composite_wave" => {
933 if let Some(out) = output_id {
934 Some(compute_goertzel_cycle_composite_wave_batch(req, out))
935 } else {
936 None
937 }
938 }
939 "rolling_skewness_kurtosis" => {
940 if let Some(out) = output_id {
941 Some(compute_rolling_skewness_kurtosis_batch(req, out))
942 } else {
943 None
944 }
945 }
946 "rolling_z_score_trend" => {
947 if let Some(out) = output_id {
948 Some(compute_rolling_z_score_trend_batch(req, out))
949 } else {
950 None
951 }
952 }
953 "ehlers_data_sampling_relative_strength_indicator" => {
954 if let Some(out) = output_id {
955 Some(compute_ehlers_data_sampling_relative_strength_indicator_batch(req, out))
956 } else {
957 None
958 }
959 }
960 "velocity_acceleration_convergence_divergence_indicator" => {
961 if let Some(out) = output_id {
962 Some(
963 compute_velocity_acceleration_convergence_divergence_indicator_batch(
964 req, out,
965 ),
966 )
967 } else {
968 None
969 }
970 }
971 "trend_direction_force_index" => {
972 if let Some(out) = output_id {
973 Some(compute_trend_direction_force_index_batch(req, out))
974 } else {
975 None
976 }
977 }
978 "yang_zhang_volatility" => {
979 if let Some(out) = output_id {
980 Some(compute_yang_zhang_volatility_batch(req, out))
981 } else {
982 None
983 }
984 }
985 "garman_klass_volatility" => Some(compute_garman_klass_volatility_batch(
986 req,
987 output_id.unwrap_or("value"),
988 )),
989 "advance_decline_line" => Some(compute_advance_decline_line_batch(
990 req,
991 output_id.unwrap_or("value"),
992 )),
993 "decisionpoint_breadth_swenlin_trading_oscillator" => Some(
994 compute_decisionpoint_breadth_swenlin_trading_oscillator_batch(
995 req,
996 output_id.unwrap_or("value"),
997 ),
998 ),
999 "velocity_acceleration_indicator" => Some(
1000 compute_velocity_acceleration_indicator_batch(req, output_id.unwrap_or("value")),
1001 ),
1002 "normalized_resonator" => Some(compute_normalized_resonator_batch(
1003 req,
1004 output_id.unwrap_or("oscillator"),
1005 )),
1006 "monotonicity_index" => Some(compute_monotonicity_index_batch(
1007 req,
1008 output_id.unwrap_or("index"),
1009 )),
1010 "half_causal_estimator" => Some(compute_half_causal_estimator_batch(
1011 req,
1012 output_id.unwrap_or("estimate"),
1013 )),
1014 "atr_percentile" => Some(compute_atr_percentile_batch(
1015 req,
1016 output_id.unwrap_or("value"),
1017 )),
1018 "bull_power_vs_bear_power" => Some(compute_bull_power_vs_bear_power_batch(
1019 req,
1020 output_id.unwrap_or("value"),
1021 )),
1022 "didi_index" => Some(compute_didi_index_batch(req, output_id.unwrap_or("short"))),
1023 "ehlers_autocorrelation_periodogram" => {
1024 Some(compute_ehlers_autocorrelation_periodogram_batch(
1025 req,
1026 output_id.unwrap_or("dominant_cycle"),
1027 ))
1028 }
1029 "ehlers_linear_extrapolation_predictor" => {
1030 Some(compute_ehlers_linear_extrapolation_predictor_batch(
1031 req,
1032 output_id.unwrap_or("prediction"),
1033 ))
1034 }
1035 "kase_peak_oscillator_with_divergences" => {
1036 Some(compute_kase_peak_oscillator_with_divergences_batch(
1037 req,
1038 output_id.unwrap_or("oscillator"),
1039 ))
1040 }
1041 "absolute_strength_index_oscillator" => {
1042 Some(compute_absolute_strength_index_oscillator_batch(
1043 req,
1044 output_id.unwrap_or("oscillator"),
1045 ))
1046 }
1047 "adaptive_bandpass_trigger_oscillator" => {
1048 Some(compute_adaptive_bandpass_trigger_oscillator_batch(
1049 req,
1050 output_id.unwrap_or("in_phase"),
1051 ))
1052 }
1053 "premier_rsi_oscillator" => Some(compute_premier_rsi_oscillator_batch(
1054 req,
1055 output_id.unwrap_or("value"),
1056 )),
1057 "multi_length_stochastic_average" => Some(
1058 compute_multi_length_stochastic_average_batch(req, output_id.unwrap_or("value")),
1059 ),
1060 "hull_butterfly_oscillator" => Some(compute_hull_butterfly_oscillator_batch(
1061 req,
1062 output_id.unwrap_or("oscillator"),
1063 )),
1064 "fibonacci_trailing_stop" => Some(compute_fibonacci_trailing_stop_batch(
1065 req,
1066 output_id.unwrap_or("trailing_stop"),
1067 )),
1068 "fibonacci_entry_bands" => Some(compute_fibonacci_entry_bands_batch(
1069 req,
1070 output_id.unwrap_or("middle"),
1071 )),
1072 "volume_energy_reservoirs" => Some(compute_volume_energy_reservoirs_batch(
1073 req,
1074 output_id.unwrap_or("momentum"),
1075 )),
1076 "neighboring_trailing_stop" => Some(compute_neighboring_trailing_stop_batch(
1077 req,
1078 output_id.unwrap_or("trailing_stop"),
1079 )),
1080 "grover_llorens_cycle_oscillator" => Some(
1081 compute_grover_llorens_cycle_oscillator_batch(req, output_id.unwrap_or("value")),
1082 ),
1083 "historical_volatility" => Some(compute_historical_volatility_batch(
1084 req,
1085 output_id.unwrap_or("value"),
1086 )),
1087 "squeeze_index" => Some(compute_squeeze_index_batch(
1088 req,
1089 output_id.unwrap_or("value"),
1090 )),
1091 "stochastic_distance" => Some(compute_stochastic_distance_batch(
1092 req,
1093 output_id.unwrap_or("oscillator"),
1094 )),
1095 "vertical_horizontal_filter" => Some(compute_vertical_horizontal_filter_batch(
1096 req,
1097 output_id.unwrap_or("value"),
1098 )),
1099 "intraday_momentum_index" => {
1100 if let Some(out) = output_id {
1101 Some(compute_intraday_momentum_index_batch(req, out))
1102 } else {
1103 None
1104 }
1105 }
1106 "vwap_zscore_with_signals" => {
1107 if let Some(out) = output_id {
1108 Some(compute_vwap_zscore_with_signals_batch(req, out))
1109 } else {
1110 None
1111 }
1112 }
1113 "macd_wave_signal_pro" => {
1114 if let Some(out) = output_id {
1115 Some(compute_macd_wave_signal_pro_batch(req, out))
1116 } else {
1117 None
1118 }
1119 }
1120 "hema_trend_levels" => {
1121 if let Some(out) = output_id {
1122 Some(compute_hema_trend_levels_batch(req, out))
1123 } else {
1124 None
1125 }
1126 }
1127 "demand_index" => {
1128 if let Some(out) = output_id {
1129 Some(compute_demand_index_batch(req, out))
1130 } else {
1131 None
1132 }
1133 }
1134 "gopalakrishnan_range_index" => Some(compute_gopalakrishnan_range_index_batch(
1135 req,
1136 output_id.unwrap_or("value"),
1137 )),
1138 "voss" => {
1139 if let Some(out) = output_id {
1140 Some(compute_voss_batch(req, out))
1141 } else {
1142 None
1143 }
1144 }
1145 "acosc" => {
1146 if let Some(out) = output_id {
1147 Some(compute_acosc_batch(req, out))
1148 } else {
1149 None
1150 }
1151 }
1152 _ => None,
1153 };
1154 }
1155
1156 if id.eq_ignore_ascii_case("bop") {
1157 return Some(compute_bop_batch(req, output_id.unwrap_or("value")));
1158 }
1159 if id.eq_ignore_ascii_case("dpo") {
1160 return Some(compute_dpo_batch(req, output_id.unwrap_or("value")));
1161 }
1162 if id.eq_ignore_ascii_case("cmo") {
1163 return Some(compute_cmo_batch(req, output_id.unwrap_or("value")));
1164 }
1165 if id.eq_ignore_ascii_case("fosc") {
1166 return Some(compute_fosc_batch(req, output_id.unwrap_or("value")));
1167 }
1168 if id.eq_ignore_ascii_case("emv") {
1169 return Some(compute_emv_batch(req, output_id.unwrap_or("value")));
1170 }
1171 if id.eq_ignore_ascii_case("cfo") {
1172 return Some(compute_cfo_batch(req, output_id.unwrap_or("value")));
1173 }
1174 if id.eq_ignore_ascii_case("ehlers_adaptive_cg") {
1175 return Some(compute_ehlers_adaptive_cg_batch(
1176 req,
1177 output_id.unwrap_or("cg"),
1178 ));
1179 }
1180 if id.eq_ignore_ascii_case("adaptive_momentum_oscillator") {
1181 return Some(compute_adaptive_momentum_oscillator_batch(
1182 req,
1183 output_id.unwrap_or("amo"),
1184 ));
1185 }
1186 if id.eq_ignore_ascii_case("adaptive_macd") {
1187 return Some(compute_adaptive_macd_batch(
1188 req,
1189 output_id.unwrap_or("macd"),
1190 ));
1191 }
1192 if id.eq_ignore_ascii_case("linear_correlation_oscillator") {
1193 return Some(compute_linear_correlation_oscillator_batch(
1194 req,
1195 output_id.unwrap_or("value"),
1196 ));
1197 }
1198 if id.eq_ignore_ascii_case("polynomial_regression_extrapolation") {
1199 return Some(compute_polynomial_regression_extrapolation_batch(
1200 req,
1201 output_id.unwrap_or("value"),
1202 ));
1203 }
1204 if id.eq_ignore_ascii_case("statistical_trailing_stop") {
1205 return Some(compute_statistical_trailing_stop_batch(
1206 req,
1207 output_id.unwrap_or("level"),
1208 ));
1209 }
1210 if id.eq_ignore_ascii_case("supertrend_recovery") {
1211 return Some(compute_supertrend_recovery_batch(
1212 req,
1213 output_id.unwrap_or("band"),
1214 ));
1215 }
1216 if id.eq_ignore_ascii_case("standardized_psar_oscillator") {
1217 return Some(compute_standardized_psar_oscillator_batch(
1218 req,
1219 output_id.unwrap_or("oscillator"),
1220 ));
1221 }
1222 if id.eq_ignore_ascii_case("geometric_bias_oscillator") {
1223 return Some(compute_geometric_bias_oscillator_batch(
1224 req,
1225 output_id.unwrap_or("value"),
1226 ));
1227 }
1228 if id.eq_ignore_ascii_case("lrsi") {
1229 return Some(compute_lrsi_batch(req, output_id.unwrap_or("value")));
1230 }
1231 if id.eq_ignore_ascii_case("nvi") {
1232 return Some(compute_nvi_batch(req, output_id.unwrap_or("value")));
1233 }
1234 if id.eq_ignore_ascii_case("mom") {
1235 return Some(compute_mom_batch(req, output_id.unwrap_or("value")));
1236 }
1237 if id.eq_ignore_ascii_case("velocity") {
1238 return Some(compute_velocity_batch(req, output_id.unwrap_or("value")));
1239 }
1240 if id.eq_ignore_ascii_case("normalized_volume_true_range") {
1241 return Some(compute_normalized_volume_true_range_batch(
1242 req,
1243 output_id.unwrap_or("normalized_volume"),
1244 ));
1245 }
1246 if id.eq_ignore_ascii_case("exponential_trend") {
1247 return Some(compute_exponential_trend_batch(
1248 req,
1249 output_id.unwrap_or("uptrend_base"),
1250 ));
1251 }
1252 if id.eq_ignore_ascii_case("trend_flow_trail") {
1253 return Some(compute_trend_flow_trail_batch(
1254 req,
1255 output_id.unwrap_or("alpha_trail"),
1256 ));
1257 }
1258 if id.eq_ignore_ascii_case("range_breakout_signals") {
1259 return Some(compute_range_breakout_signals_batch(
1260 req,
1261 output_id.unwrap_or("range_top"),
1262 ));
1263 }
1264 if id.eq_ignore_ascii_case("vi") {
1265 if let Some(out) = output_id {
1266 return Some(compute_vi_batch(req, out));
1267 }
1268 return None;
1269 }
1270 if id.eq_ignore_ascii_case("wto") {
1271 if let Some(out) = output_id {
1272 return Some(compute_wto_batch(req, out));
1273 }
1274 return None;
1275 }
1276 if id.eq_ignore_ascii_case("rogers_satchell_volatility") {
1277 if let Some(out) = output_id {
1278 return Some(compute_rogers_satchell_volatility_batch(req, out));
1279 }
1280 return None;
1281 }
1282 if id.eq_ignore_ascii_case("historical_volatility_rank") {
1283 if let Some(out) = output_id {
1284 return Some(compute_historical_volatility_rank_batch(req, out));
1285 }
1286 return None;
1287 }
1288 if id.eq_ignore_ascii_case("dual_ulcer_index") {
1289 if let Some(out) = output_id {
1290 return Some(compute_dual_ulcer_index_batch(req, out));
1291 }
1292 return None;
1293 }
1294 if id.eq_ignore_ascii_case("fractal_dimension_index") {
1295 if let Some(out) = output_id {
1296 return Some(compute_fractal_dimension_index_batch(req, out));
1297 }
1298 return None;
1299 }
1300 if id.eq_ignore_ascii_case("volume_weighted_rsi") {
1301 if let Some(out) = output_id {
1302 return Some(compute_volume_weighted_rsi_batch(req, out));
1303 }
1304 return None;
1305 }
1306 if id.eq_ignore_ascii_case("dynamic_momentum_index") {
1307 if let Some(out) = output_id {
1308 return Some(compute_dynamic_momentum_index_batch(req, out));
1309 }
1310 return None;
1311 }
1312 if id.eq_ignore_ascii_case("disparity_index") {
1313 if let Some(out) = output_id {
1314 return Some(compute_disparity_index_batch(req, out));
1315 }
1316 return None;
1317 }
1318 if id.eq_ignore_ascii_case("donchian_channel_width") {
1319 if let Some(out) = output_id {
1320 return Some(compute_donchian_channel_width_batch(req, out));
1321 }
1322 return None;
1323 }
1324 if id.eq_ignore_ascii_case("kairi_relative_index") {
1325 if let Some(out) = output_id {
1326 return Some(compute_kairi_relative_index_batch(req, out));
1327 }
1328 return None;
1329 }
1330 if id.eq_ignore_ascii_case("projection_oscillator") {
1331 if let Some(out) = output_id {
1332 return Some(compute_projection_oscillator_batch(req, out));
1333 }
1334 return None;
1335 }
1336 if id.eq_ignore_ascii_case("market_structure_trailing_stop") {
1337 if let Some(out) = output_id {
1338 return Some(compute_market_structure_trailing_stop_batch(req, out));
1339 }
1340 return None;
1341 }
1342 if id.eq_ignore_ascii_case("emd_trend") {
1343 if let Some(out) = output_id {
1344 return Some(compute_emd_trend_batch(req, out));
1345 }
1346 return None;
1347 }
1348 if id.eq_ignore_ascii_case("cyberpunk_value_trend_analyzer") {
1349 if let Some(out) = output_id {
1350 return Some(compute_cyberpunk_value_trend_analyzer_batch(req, out));
1351 }
1352 return None;
1353 }
1354 if id.eq_ignore_ascii_case("evasive_supertrend") {
1355 if let Some(out) = output_id {
1356 return Some(compute_evasive_supertrend_batch(req, out));
1357 }
1358 return None;
1359 }
1360 if id.eq_ignore_ascii_case("reversal_signals") {
1361 if let Some(out) = output_id {
1362 return Some(compute_reversal_signals_batch(req, out));
1363 }
1364 return None;
1365 }
1366 if id.eq_ignore_ascii_case("zig_zag_channels") {
1367 if let Some(out) = output_id {
1368 return Some(compute_zig_zag_channels_batch(req, out));
1369 }
1370 return None;
1371 }
1372 if id.eq_ignore_ascii_case("directional_imbalance_index") {
1373 if let Some(out) = output_id {
1374 return Some(compute_directional_imbalance_index_batch(req, out));
1375 }
1376 return None;
1377 }
1378 if id.eq_ignore_ascii_case("candle_strength_oscillator") {
1379 if let Some(out) = output_id {
1380 return Some(compute_candle_strength_oscillator_batch(req, out));
1381 }
1382 return None;
1383 }
1384 if id.eq_ignore_ascii_case("gmma_oscillator") {
1385 if let Some(out) = output_id {
1386 return Some(compute_gmma_oscillator_batch(req, out));
1387 }
1388 return None;
1389 }
1390 if id.eq_ignore_ascii_case("nonlinear_regression_zero_lag_moving_average") {
1391 if let Some(out) = output_id {
1392 return Some(compute_nonlinear_regression_zero_lag_moving_average_batch(
1393 req, out,
1394 ));
1395 }
1396 return None;
1397 }
1398 if id.eq_ignore_ascii_case("autocorrelation_indicator") {
1399 if let Some(out) = output_id {
1400 return Some(compute_autocorrelation_indicator_batch(req, out));
1401 }
1402 return None;
1403 }
1404 if id.eq_ignore_ascii_case("goertzel_cycle_composite_wave") {
1405 if let Some(out) = output_id {
1406 return Some(compute_goertzel_cycle_composite_wave_batch(req, out));
1407 }
1408 return None;
1409 }
1410 if id.eq_ignore_ascii_case("rolling_skewness_kurtosis") {
1411 if let Some(out) = output_id {
1412 return Some(compute_rolling_skewness_kurtosis_batch(req, out));
1413 }
1414 return None;
1415 }
1416 if id.eq_ignore_ascii_case("rolling_z_score_trend") {
1417 if let Some(out) = output_id {
1418 return Some(compute_rolling_z_score_trend_batch(req, out));
1419 }
1420 return None;
1421 }
1422 if id.eq_ignore_ascii_case("ehlers_data_sampling_relative_strength_indicator") {
1423 if let Some(out) = output_id {
1424 return Some(compute_ehlers_data_sampling_relative_strength_indicator_batch(req, out));
1425 }
1426 return None;
1427 }
1428 if id.eq_ignore_ascii_case("velocity_acceleration_convergence_divergence_indicator") {
1429 if let Some(out) = output_id {
1430 return Some(
1431 compute_velocity_acceleration_convergence_divergence_indicator_batch(req, out),
1432 );
1433 }
1434 return None;
1435 }
1436 if id.eq_ignore_ascii_case("trend_direction_force_index") {
1437 if let Some(out) = output_id {
1438 return Some(compute_trend_direction_force_index_batch(req, out));
1439 }
1440 return None;
1441 }
1442 if id.eq_ignore_ascii_case("yang_zhang_volatility") {
1443 if let Some(out) = output_id {
1444 return Some(compute_yang_zhang_volatility_batch(req, out));
1445 }
1446 return None;
1447 }
1448 if id.eq_ignore_ascii_case("garman_klass_volatility") {
1449 return Some(compute_garman_klass_volatility_batch(
1450 req,
1451 output_id.unwrap_or("value"),
1452 ));
1453 }
1454 if id.eq_ignore_ascii_case("advance_decline_line") {
1455 return Some(compute_advance_decline_line_batch(
1456 req,
1457 output_id.unwrap_or("value"),
1458 ));
1459 }
1460 if id.eq_ignore_ascii_case("decisionpoint_breadth_swenlin_trading_oscillator") {
1461 return Some(
1462 compute_decisionpoint_breadth_swenlin_trading_oscillator_batch(
1463 req,
1464 output_id.unwrap_or("value"),
1465 ),
1466 );
1467 }
1468 if id.eq_ignore_ascii_case("velocity_acceleration_indicator") {
1469 return Some(compute_velocity_acceleration_indicator_batch(
1470 req,
1471 output_id.unwrap_or("value"),
1472 ));
1473 }
1474 if id.eq_ignore_ascii_case("normalized_resonator") {
1475 return Some(compute_normalized_resonator_batch(
1476 req,
1477 output_id.unwrap_or("oscillator"),
1478 ));
1479 }
1480 if id.eq_ignore_ascii_case("monotonicity_index") {
1481 return Some(compute_monotonicity_index_batch(
1482 req,
1483 output_id.unwrap_or("index"),
1484 ));
1485 }
1486 if id.eq_ignore_ascii_case("half_causal_estimator") {
1487 return Some(compute_half_causal_estimator_batch(
1488 req,
1489 output_id.unwrap_or("estimate"),
1490 ));
1491 }
1492 if id.eq_ignore_ascii_case("atr_percentile") {
1493 return Some(compute_atr_percentile_batch(
1494 req,
1495 output_id.unwrap_or("value"),
1496 ));
1497 }
1498 if id.eq_ignore_ascii_case("bull_power_vs_bear_power") {
1499 return Some(compute_bull_power_vs_bear_power_batch(
1500 req,
1501 output_id.unwrap_or("value"),
1502 ));
1503 }
1504 if id.eq_ignore_ascii_case("didi_index") {
1505 return Some(compute_didi_index_batch(req, output_id.unwrap_or("short")));
1506 }
1507 if id.eq_ignore_ascii_case("ehlers_autocorrelation_periodogram") {
1508 return Some(compute_ehlers_autocorrelation_periodogram_batch(
1509 req,
1510 output_id.unwrap_or("dominant_cycle"),
1511 ));
1512 }
1513 if id.eq_ignore_ascii_case("ehlers_linear_extrapolation_predictor") {
1514 return Some(compute_ehlers_linear_extrapolation_predictor_batch(
1515 req,
1516 output_id.unwrap_or("prediction"),
1517 ));
1518 }
1519 if id.eq_ignore_ascii_case("kase_peak_oscillator_with_divergences") {
1520 return Some(compute_kase_peak_oscillator_with_divergences_batch(
1521 req,
1522 output_id.unwrap_or("oscillator"),
1523 ));
1524 }
1525 if id.eq_ignore_ascii_case("absolute_strength_index_oscillator") {
1526 return Some(compute_absolute_strength_index_oscillator_batch(
1527 req,
1528 output_id.unwrap_or("oscillator"),
1529 ));
1530 }
1531 if id.eq_ignore_ascii_case("adaptive_bandpass_trigger_oscillator") {
1532 return Some(compute_adaptive_bandpass_trigger_oscillator_batch(
1533 req,
1534 output_id.unwrap_or("in_phase"),
1535 ));
1536 }
1537 if id.eq_ignore_ascii_case("premier_rsi_oscillator") {
1538 return Some(compute_premier_rsi_oscillator_batch(
1539 req,
1540 output_id.unwrap_or("value"),
1541 ));
1542 }
1543 if id.eq_ignore_ascii_case("multi_length_stochastic_average") {
1544 return Some(compute_multi_length_stochastic_average_batch(
1545 req,
1546 output_id.unwrap_or("value"),
1547 ));
1548 }
1549 if id.eq_ignore_ascii_case("hull_butterfly_oscillator") {
1550 return Some(compute_hull_butterfly_oscillator_batch(
1551 req,
1552 output_id.unwrap_or("oscillator"),
1553 ));
1554 }
1555 if id.eq_ignore_ascii_case("fibonacci_trailing_stop") {
1556 return Some(compute_fibonacci_trailing_stop_batch(
1557 req,
1558 output_id.unwrap_or("trailing_stop"),
1559 ));
1560 }
1561 if id.eq_ignore_ascii_case("fibonacci_entry_bands") {
1562 return Some(compute_fibonacci_entry_bands_batch(
1563 req,
1564 output_id.unwrap_or("middle"),
1565 ));
1566 }
1567 if id.eq_ignore_ascii_case("volume_energy_reservoirs") {
1568 return Some(compute_volume_energy_reservoirs_batch(
1569 req,
1570 output_id.unwrap_or("momentum"),
1571 ));
1572 }
1573 if id.eq_ignore_ascii_case("neighboring_trailing_stop") {
1574 return Some(compute_neighboring_trailing_stop_batch(
1575 req,
1576 output_id.unwrap_or("trailing_stop"),
1577 ));
1578 }
1579 if id.eq_ignore_ascii_case("grover_llorens_cycle_oscillator") {
1580 return Some(compute_grover_llorens_cycle_oscillator_batch(
1581 req,
1582 output_id.unwrap_or("value"),
1583 ));
1584 }
1585 if id.eq_ignore_ascii_case("historical_volatility") {
1586 return Some(compute_historical_volatility_batch(
1587 req,
1588 output_id.unwrap_or("value"),
1589 ));
1590 }
1591 if id.eq_ignore_ascii_case("squeeze_index") {
1592 return Some(compute_squeeze_index_batch(
1593 req,
1594 output_id.unwrap_or("value"),
1595 ));
1596 }
1597 if id.eq_ignore_ascii_case("stochastic_distance") {
1598 return Some(compute_stochastic_distance_batch(
1599 req,
1600 output_id.unwrap_or("oscillator"),
1601 ));
1602 }
1603 if id.eq_ignore_ascii_case("vertical_horizontal_filter") {
1604 return Some(compute_vertical_horizontal_filter_batch(
1605 req,
1606 output_id.unwrap_or("value"),
1607 ));
1608 }
1609 if id.eq_ignore_ascii_case("intraday_momentum_index") {
1610 if let Some(out) = output_id {
1611 return Some(compute_intraday_momentum_index_batch(req, out));
1612 }
1613 }
1614 if id.eq_ignore_ascii_case("vwap_zscore_with_signals") {
1615 if let Some(out) = output_id {
1616 return Some(compute_vwap_zscore_with_signals_batch(req, out));
1617 }
1618 }
1619 if id.eq_ignore_ascii_case("macd_wave_signal_pro") {
1620 if let Some(out) = output_id {
1621 return Some(compute_macd_wave_signal_pro_batch(req, out));
1622 }
1623 }
1624 if id.eq_ignore_ascii_case("hema_trend_levels") {
1625 if let Some(out) = output_id {
1626 return Some(compute_hema_trend_levels_batch(req, out));
1627 }
1628 }
1629 if id.eq_ignore_ascii_case("demand_index") {
1630 if let Some(out) = output_id {
1631 return Some(compute_demand_index_batch(req, out));
1632 }
1633 }
1634 if id.eq_ignore_ascii_case("gopalakrishnan_range_index") {
1635 return Some(compute_gopalakrishnan_range_index_batch(
1636 req,
1637 output_id.unwrap_or("value"),
1638 ));
1639 }
1640 if id.eq_ignore_ascii_case("voss") {
1641 if let Some(out) = output_id {
1642 return Some(compute_voss_batch(req, out));
1643 }
1644 return None;
1645 }
1646 if id.eq_ignore_ascii_case("acosc") {
1647 if let Some(out) = output_id {
1648 return Some(compute_acosc_batch(req, out));
1649 }
1650 return None;
1651 }
1652
1653 None
1654}
1655
1656fn dispatch_cpu_batch_by_indicator(
1657 req: IndicatorBatchRequest<'_>,
1658 indicator_id: &str,
1659 output_id: &str,
1660) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
1661 if indicator_id.eq_ignore_ascii_case("logarithmic_moving_average") {
1662 return compute_logarithmic_moving_average_batch(req, output_id);
1663 }
1664 if is_moving_average(indicator_id) {
1665 if let Some(info) = get_indicator(indicator_id) {
1666 return compute_ma_batch(req, info, output_id);
1667 }
1668 }
1669 match indicator_id {
1670 "accumulation_swing_index" => compute_accumulation_swing_index_batch(req, output_id),
1671 "ad" => compute_ad_batch(req, output_id),
1672 "adosc" => compute_adosc_batch(req, output_id),
1673 "ao" => compute_ao_batch(req, output_id),
1674 "emv" => compute_emv_batch(req, output_id),
1675 "efi" => compute_efi_batch(req, output_id),
1676 "mfi" => compute_mfi_batch(req, output_id),
1677 "mass" => compute_mass_batch(req, output_id),
1678 "kvo" => compute_kvo_batch(req, output_id),
1679 "vosc" => compute_vosc_batch(req, output_id),
1680 "wad" => compute_wad_batch(req, output_id),
1681 "dx" => compute_dx_batch(req, output_id),
1682 "fosc" => compute_fosc_batch(req, output_id),
1683 "ift_rsi" => compute_ift_rsi_batch(req, output_id),
1684 "linearreg_angle" => compute_linearreg_angle_batch(req, output_id),
1685 "linearreg_intercept" => compute_linearreg_intercept_batch(req, output_id),
1686 "linearreg_slope" => compute_linearreg_slope_batch(req, output_id),
1687 "cg" => compute_cg_batch(req, output_id),
1688 "rsi" => compute_rsi_batch(req, output_id),
1689 "roc" => compute_roc_batch(req, output_id),
1690 "apo" => compute_apo_batch(req, output_id),
1691 "bop" => compute_bop_batch(req, output_id),
1692 "bulls_v_bears" => compute_bulls_v_bears_batch(req, output_id),
1693 "cci" => compute_cci_batch(req, output_id),
1694 "cci_cycle" => compute_cci_cycle_batch(req, output_id),
1695 "cfo" => compute_cfo_batch(req, output_id),
1696 "cycle_channel_oscillator" => compute_cycle_channel_oscillator_batch(req, output_id),
1697 "daily_factor" => compute_daily_factor_batch(req, output_id),
1698 "ehlers_adaptive_cg" => compute_ehlers_adaptive_cg_batch(req, output_id),
1699 "ehlers_adaptive_cyber_cycle" => compute_ehlers_adaptive_cyber_cycle_batch(req, output_id),
1700 "adaptive_schaff_trend_cycle" => compute_adaptive_schaff_trend_cycle_batch(req, output_id),
1701 "adaptive_momentum_oscillator" => {
1702 compute_adaptive_momentum_oscillator_batch(req, output_id)
1703 }
1704 "adaptive_macd" => compute_adaptive_macd_batch(req, output_id),
1705 "linear_correlation_oscillator" => {
1706 compute_linear_correlation_oscillator_batch(req, output_id)
1707 }
1708 "polynomial_regression_extrapolation" => {
1709 compute_polynomial_regression_extrapolation_batch(req, output_id)
1710 }
1711 "statistical_trailing_stop" => compute_statistical_trailing_stop_batch(req, output_id),
1712 "supertrend_recovery" => compute_supertrend_recovery_batch(req, output_id),
1713 "standardized_psar_oscillator" => {
1714 compute_standardized_psar_oscillator_batch(req, output_id)
1715 }
1716 "geometric_bias_oscillator" => compute_geometric_bias_oscillator_batch(req, output_id),
1717 "vdubus_divergence_wave_pattern_generator" => {
1718 compute_vdubus_divergence_wave_pattern_generator_batch(req, output_id)
1719 }
1720 "lrsi" => compute_lrsi_batch(req, output_id),
1721 "er" => compute_er_batch(req, output_id),
1722 "kurtosis" => compute_kurtosis_batch(req, output_id),
1723 "natr" => compute_natr_batch(req, output_id),
1724 "net_myrsi" => compute_net_myrsi_batch(req, output_id),
1725 "mean_ad" => compute_mean_ad_batch(req, output_id),
1726 "medium_ad" => compute_medium_ad_batch(req, output_id),
1727 "deviation" => compute_deviation_batch(req, output_id),
1728 "dpo" => compute_dpo_batch(req, output_id),
1729 "pfe" => compute_pfe_batch(req, output_id),
1730 "ehlers_detrending_filter" => compute_ehlers_detrending_filter_batch(req, output_id),
1731 "ehlers_fm_demodulator" => compute_ehlers_fm_demodulator_batch(req, output_id),
1732 "ehlers_simple_cycle_indicator" => compute_ehlers_simple_cycle_indicator_batch(req, output_id),
1733 "ehlers_smoothed_adaptive_momentum" => {
1734 compute_ehlers_smoothed_adaptive_momentum_batch(req, output_id)
1735 }
1736 "ewma_volatility" => compute_ewma_volatility_batch(req, output_id),
1737 "qstick" => compute_qstick_batch(req, output_id),
1738 "reverse_rsi" => compute_reverse_rsi_batch(req, output_id),
1739 "percentile_nearest_rank" => compute_percentile_nearest_rank_batch(req, output_id),
1740 "obv" => compute_obv_batch(req, output_id),
1741 "on_balance_volume_oscillator" => compute_on_balance_volume_oscillator_batch(req, output_id),
1742 "vpt" => compute_vpt_batch(req, output_id),
1743 "nvi" => compute_nvi_batch(req, output_id),
1744 "pvi" => compute_pvi_batch(req, output_id),
1745 "wclprice" => compute_wclprice_batch(req, output_id),
1746 "ui" => compute_ui_batch(req, output_id),
1747 "zscore" => compute_zscore_batch(req, output_id),
1748 "medprice" => compute_medprice_batch(req, output_id),
1749 "midpoint" => compute_midpoint_batch(req, output_id),
1750 "midprice" => compute_midprice_batch(req, output_id),
1751 "mom" => compute_mom_batch(req, output_id),
1752 "velocity" => compute_velocity_batch(req, output_id),
1753 "normalized_volume_true_range" => {
1754 compute_normalized_volume_true_range_batch(req, output_id)
1755 }
1756 "exponential_trend" => compute_exponential_trend_batch(req, output_id),
1757 "trend_flow_trail" => compute_trend_flow_trail_batch(req, output_id),
1758 "range_breakout_signals" => compute_range_breakout_signals_batch(req, output_id),
1759 "cmo" => compute_cmo_batch(req, output_id),
1760 "rocp" => compute_rocp_batch(req, output_id),
1761 "rocr" => compute_rocr_batch(req, output_id),
1762 "ppo" => compute_ppo_batch(req, output_id),
1763 "tsf" => compute_tsf_batch(req, output_id),
1764 "trix" => compute_trix_batch(req, output_id),
1765 "tsi" => compute_tsi_batch(req, output_id),
1766 "var" => compute_var_batch(req, output_id),
1767 "stddev" => compute_stddev_batch(req, output_id),
1768 "willr" => compute_willr_batch(req, output_id),
1769 "ultosc" => compute_ultosc_batch(req, output_id),
1770 "adx" => compute_adx_batch(req, output_id),
1771 "adxr" => compute_adxr_batch(req, output_id),
1772 "atr" => compute_atr_batch(req, output_id),
1773 "macd" => compute_macd_batch(req, output_id),
1774 "bollinger_bands" => compute_bollinger_batch(req, output_id),
1775 "bollinger_bands_width" => compute_bbw_batch(req, output_id),
1776 "stoch" => compute_stoch_batch(req, output_id),
1777 "stochf" => compute_stochf_batch(req, output_id),
1778 "stochastic_money_flow_index" => compute_stochastic_money_flow_index_batch(req, output_id),
1779 "vwmacd" => compute_vwmacd_batch(req, output_id),
1780 "vpci" => compute_vpci_batch(req, output_id),
1781 "ttm_trend" => compute_ttm_trend_batch(req, output_id),
1782 "ttm_squeeze" => compute_ttm_squeeze_batch(req, output_id),
1783 "aroon" => compute_aroon_batch(req, output_id),
1784 "aroonosc" => compute_aroonosc_batch(req, output_id),
1785 "di" => compute_di_batch(req, output_id),
1786 "dm" => compute_dm_batch(req, output_id),
1787 "dti" => compute_dti_batch(req, output_id),
1788 "donchian" => compute_donchian_batch(req, output_id),
1789 "kdj" => compute_kdj_batch(req, output_id),
1790 "keltner" => compute_keltner_batch(req, output_id),
1791 "squeeze_momentum" => compute_squeeze_momentum_batch(req, output_id),
1792 "srsi" => compute_srsi_batch(req, output_id),
1793 "supertrend" => compute_supertrend_batch(req, output_id),
1794 "adjustable_ma_alternating_extremities" => {
1795 compute_adjustable_ma_alternating_extremities_batch(req, output_id)
1796 }
1797 "vi" => compute_vi_batch(req, output_id),
1798 "wavetrend" => compute_wavetrend_batch(req, output_id),
1799 "wto" => compute_wto_batch(req, output_id),
1800 "rogers_satchell_volatility" => compute_rogers_satchell_volatility_batch(req, output_id),
1801 "historical_volatility_percentile" => {
1802 compute_historical_volatility_percentile_batch(req, output_id)
1803 }
1804 "historical_volatility_rank" => compute_historical_volatility_rank_batch(req, output_id),
1805 "dual_ulcer_index" => compute_dual_ulcer_index_batch(req, output_id),
1806 "fractal_dimension_index" => compute_fractal_dimension_index_batch(req, output_id),
1807 "ichimoku_oscillator" => compute_ichimoku_oscillator_batch(req, output_id),
1808 "volume_weighted_rsi" => compute_volume_weighted_rsi_batch(req, output_id),
1809 "dynamic_momentum_index" => compute_dynamic_momentum_index_batch(req, output_id),
1810 "disparity_index" => compute_disparity_index_batch(req, output_id),
1811 "donchian_channel_width" => compute_donchian_channel_width_batch(req, output_id),
1812 "kairi_relative_index" => compute_kairi_relative_index_batch(req, output_id),
1813 "projection_oscillator" => compute_projection_oscillator_batch(req, output_id),
1814 "market_structure_trailing_stop" => {
1815 compute_market_structure_trailing_stop_batch(req, output_id)
1816 }
1817 "emd_trend" => compute_emd_trend_batch(req, output_id),
1818 "cyberpunk_value_trend_analyzer" => {
1819 compute_cyberpunk_value_trend_analyzer_batch(req, output_id)
1820 }
1821 "evasive_supertrend" => compute_evasive_supertrend_batch(req, output_id),
1822 "reversal_signals" => compute_reversal_signals_batch(req, output_id),
1823 "zig_zag_channels" => compute_zig_zag_channels_batch(req, output_id),
1824 "directional_imbalance_index" => compute_directional_imbalance_index_batch(req, output_id),
1825 "candle_strength_oscillator" => compute_candle_strength_oscillator_batch(req, output_id),
1826 "gmma_oscillator" => compute_gmma_oscillator_batch(req, output_id),
1827 "nonlinear_regression_zero_lag_moving_average" => {
1828 compute_nonlinear_regression_zero_lag_moving_average_batch(req, output_id)
1829 }
1830 "possible_rsi" => compute_possible_rsi_batch(req, output_id),
1831 "autocorrelation_indicator" => compute_autocorrelation_indicator_batch(req, output_id),
1832 "goertzel_cycle_composite_wave" => {
1833 compute_goertzel_cycle_composite_wave_batch(req, output_id)
1834 }
1835 "rolling_skewness_kurtosis" => compute_rolling_skewness_kurtosis_batch(req, output_id),
1836 "rolling_z_score_trend" => compute_rolling_z_score_trend_batch(req, output_id),
1837 "ehlers_data_sampling_relative_strength_indicator" => {
1838 compute_ehlers_data_sampling_relative_strength_indicator_batch(req, output_id)
1839 }
1840 "velocity_acceleration_convergence_divergence_indicator" => {
1841 compute_velocity_acceleration_convergence_divergence_indicator_batch(req, output_id)
1842 }
1843 "trend_direction_force_index" => compute_trend_direction_force_index_batch(req, output_id),
1844 "yang_zhang_volatility" => compute_yang_zhang_volatility_batch(req, output_id),
1845 "garman_klass_volatility" => compute_garman_klass_volatility_batch(req, output_id),
1846 "advance_decline_line" => compute_advance_decline_line_batch(req, output_id),
1847 "decisionpoint_breadth_swenlin_trading_oscillator" => {
1848 compute_decisionpoint_breadth_swenlin_trading_oscillator_batch(req, output_id)
1849 }
1850 "velocity_acceleration_indicator" => {
1851 compute_velocity_acceleration_indicator_batch(req, output_id)
1852 }
1853 "normalized_resonator" => compute_normalized_resonator_batch(req, output_id),
1854 "monotonicity_index" => compute_monotonicity_index_batch(req, output_id),
1855 "half_causal_estimator" => compute_half_causal_estimator_batch(req, output_id),
1856 "atr_percentile" => compute_atr_percentile_batch(req, output_id),
1857 "andean_oscillator" => compute_andean_oscillator_batch(req, output_id),
1858 "bull_power_vs_bear_power" => compute_bull_power_vs_bear_power_batch(req, output_id),
1859 "didi_index" => compute_didi_index_batch(req, output_id),
1860 "ehlers_autocorrelation_periodogram" => {
1861 compute_ehlers_autocorrelation_periodogram_batch(req, output_id)
1862 }
1863 "ehlers_linear_extrapolation_predictor" => {
1864 compute_ehlers_linear_extrapolation_predictor_batch(req, output_id)
1865 }
1866 "absolute_strength_index_oscillator" => {
1867 compute_absolute_strength_index_oscillator_batch(req, output_id)
1868 }
1869 "adaptive_bandpass_trigger_oscillator" => {
1870 compute_adaptive_bandpass_trigger_oscillator_batch(req, output_id)
1871 }
1872 "premier_rsi_oscillator" => compute_premier_rsi_oscillator_batch(req, output_id),
1873 "multi_length_stochastic_average" => {
1874 compute_multi_length_stochastic_average_batch(req, output_id)
1875 }
1876 "hull_butterfly_oscillator" => compute_hull_butterfly_oscillator_batch(req, output_id),
1877 "fibonacci_trailing_stop" => compute_fibonacci_trailing_stop_batch(req, output_id),
1878 "fibonacci_entry_bands" => compute_fibonacci_entry_bands_batch(req, output_id),
1879 "volume_energy_reservoirs" => compute_volume_energy_reservoirs_batch(req, output_id),
1880 "neighboring_trailing_stop" => compute_neighboring_trailing_stop_batch(req, output_id),
1881 "grover_llorens_cycle_oscillator" => {
1882 compute_grover_llorens_cycle_oscillator_batch(req, output_id)
1883 }
1884 "historical_volatility" => compute_historical_volatility_batch(req, output_id),
1885 "hypertrend" => compute_hypertrend_batch(req, output_id),
1886 "ict_propulsion_block" => compute_ict_propulsion_block_batch(req, output_id),
1887 "impulse_macd" => compute_impulse_macd_batch(req, output_id),
1888 "l1_ehlers_phasor" => compute_l1_ehlers_phasor_batch(req, output_id),
1889 "l2_ehlers_signal_to_noise" => compute_l2_ehlers_signal_to_noise_batch(req, output_id),
1890 "keltner_channel_width_oscillator" => {
1891 compute_keltner_channel_width_oscillator_batch(req, output_id)
1892 }
1893 "leavitt_convolution_acceleration" => {
1894 compute_leavitt_convolution_acceleration_batch(req, output_id)
1895 }
1896 "linear_regression_intensity" => compute_linear_regression_intensity_batch(req, output_id),
1897 "market_meanness_index" => compute_market_meanness_index_batch(req, output_id),
1898 "mesa_stochastic_multi_length" => compute_mesa_stochastic_multi_length_batch(req, output_id),
1899 "moving_average_cross_probability" => {
1900 compute_moving_average_cross_probability_batch(req, output_id)
1901 }
1902 "momentum_ratio_oscillator" => compute_momentum_ratio_oscillator_batch(req, output_id),
1903 "parkinson_volatility" => compute_parkinson_volatility_batch(req, output_id),
1904 "price_moving_average_ratio_percentile" => {
1905 compute_price_moving_average_ratio_percentile_batch(req, output_id)
1906 }
1907 "pretty_good_oscillator" => compute_pretty_good_oscillator_batch(req, output_id),
1908 "price_density_market_noise" => compute_price_density_market_noise_batch(req, output_id),
1909 "psychological_line" => compute_psychological_line_batch(req, output_id),
1910 "random_walk_index" => compute_random_walk_index_batch(req, output_id),
1911 "rank_correlation_index" => compute_rank_correlation_index_batch(req, output_id),
1912 "relative_strength_index_wave_indicator" => {
1913 compute_relative_strength_index_wave_indicator_batch(req, output_id)
1914 }
1915 "regression_slope_oscillator" => compute_regression_slope_oscillator_batch(req, output_id),
1916 "squeeze_index" => compute_squeeze_index_batch(req, output_id),
1917 "smoothed_gaussian_trend_filter" => {
1918 compute_smoothed_gaussian_trend_filter_batch(req, output_id)
1919 }
1920 "smooth_theil_sen" => compute_smooth_theil_sen_batch(req, output_id),
1921 "spearman_correlation" => compute_spearman_correlation_batch(req, output_id),
1922 "stochastic_adaptive_d" => compute_stochastic_adaptive_d_batch(req, output_id),
1923 "stochastic_connors_rsi" => compute_stochastic_connors_rsi_batch(req, output_id),
1924 "stochastic_distance" => compute_stochastic_distance_batch(req, output_id),
1925 "supertrend_oscillator" => compute_supertrend_oscillator_batch(req, output_id),
1926 "trend_trigger_factor" => compute_trend_trigger_factor_batch(req, output_id),
1927 "trend_continuation_factor" => compute_trend_continuation_factor_batch(req, output_id),
1928 "twiggs_money_flow" => compute_twiggs_money_flow_batch(req, output_id),
1929 "vertical_horizontal_filter" => compute_vertical_horizontal_filter_batch(req, output_id),
1930 "intraday_momentum_index" => compute_intraday_momentum_index_batch(req, output_id),
1931 "volatility_quality_index" => compute_volatility_quality_index_batch(req, output_id),
1932 "volatility_ratio_adaptive_rsx" => {
1933 compute_volatility_ratio_adaptive_rsx_batch(req, output_id)
1934 }
1935 "volume_weighted_stochastic_rsi" => {
1936 compute_volume_weighted_stochastic_rsi_batch(req, output_id)
1937 }
1938 "volume_zone_oscillator" => compute_volume_zone_oscillator_batch(req, output_id),
1939 "vwap_deviation_oscillator" => compute_vwap_deviation_oscillator_batch(req, output_id),
1940 "vwap_zscore_with_signals" => compute_vwap_zscore_with_signals_batch(req, output_id),
1941 "macd_wave_signal_pro" => compute_macd_wave_signal_pro_batch(req, output_id),
1942 "hema_trend_levels" => compute_hema_trend_levels_batch(req, output_id),
1943 "demand_index" => compute_demand_index_batch(req, output_id),
1944 "kase_peak_oscillator_with_divergences" => {
1945 compute_kase_peak_oscillator_with_divergences_batch(req, output_id)
1946 }
1947 "gopalakrishnan_range_index" => compute_gopalakrishnan_range_index_batch(req, output_id),
1948 "acosc" => compute_acosc_batch(req, output_id),
1949 "alligator" => compute_alligator_batch(req, output_id),
1950 "alphatrend" => compute_alphatrend_batch(req, output_id),
1951 "aso" => compute_aso_batch(req, output_id),
1952 "avsl" => compute_avsl_batch(req, output_id),
1953 "bandpass" => compute_bandpass_batch(req, output_id),
1954 "chande" => compute_chande_batch(req, output_id),
1955 "chandelier_exit" => compute_chandelier_exit_batch(req, output_id),
1956 "cksp" => compute_cksp_batch(req, output_id),
1957 "coppock" => compute_coppock_batch(req, output_id),
1958 "correl_hl" => compute_correl_hl_batch(req, output_id),
1959 "correlation_cycle" => compute_correlation_cycle_batch(req, output_id),
1960 "damiani_volatmeter" => compute_damiani_volatmeter_batch(req, output_id),
1961 "dvdiqqe" => compute_dvdiqqe_batch(req, output_id),
1962 "emd" => compute_emd_batch(req, output_id),
1963 "eri" => compute_eri_batch(req, output_id),
1964 "fisher" => compute_fisher_batch(req, output_id),
1965 "fvg_positioning_average" => compute_fvg_positioning_average_batch(req, output_id),
1966 "fvg_trailing_stop" => compute_fvg_trailing_stop_batch(req, output_id),
1967 "gatorosc" => compute_gatorosc_batch(req, output_id),
1968 "halftrend" => compute_halftrend_batch(req, output_id),
1969 "kaufmanstop" => compute_kaufmanstop_batch(req, output_id),
1970 "kst" => compute_kst_batch(req, output_id),
1971 "lpc" => compute_lpc_batch(req, output_id),
1972 "mab" => compute_mab_batch(req, output_id),
1973 "macz" => compute_macz_batch(req, output_id),
1974 "minmax" => compute_minmax_batch(req, output_id),
1975 "mod_god_mode" => compute_mod_god_mode_batch(req, output_id),
1976 "msw" => compute_msw_batch(req, output_id),
1977 "nadaraya_watson_envelope" => compute_nadaraya_watson_envelope_batch(req, output_id),
1978 "otto" => compute_otto_batch(req, output_id),
1979 "vidya" => compute_vidya_batch(req, output_id),
1980 "vlma" => compute_vlma_batch(req, output_id),
1981 "pma" => compute_pma_batch(req, output_id),
1982 "prb" => compute_prb_batch(req, output_id),
1983 "qqe" => compute_qqe_batch(req, output_id),
1984 "forward_backward_exponential_oscillator" => {
1985 compute_forward_backward_exponential_oscillator_batch(req, output_id)
1986 }
1987 "qqe_weighted_oscillator" => compute_qqe_weighted_oscillator_batch(req, output_id),
1988 "market_structure_confluence" => compute_market_structure_confluence_batch(req, output_id),
1989 "range_filtered_trend_signals" => {
1990 compute_range_filtered_trend_signals_batch(req, output_id)
1991 }
1992 "range_oscillator" => compute_range_oscillator_batch(req, output_id),
1993 "volume_weighted_relative_strength_index" => {
1994 compute_volume_weighted_relative_strength_index_batch(req, output_id)
1995 }
1996 "range_filter" => compute_range_filter_batch(req, output_id),
1997 "rsmk" => compute_rsmk_batch(req, output_id),
1998 "voss" => compute_voss_batch(req, output_id),
1999 "stc" => compute_stc_batch(req, output_id),
2000 "rvi" => compute_rvi_batch(req, output_id),
2001 "safezonestop" => compute_safezonestop_batch(req, output_id),
2002 "devstop" => compute_devstop_batch(req, output_id),
2003 "chop" => compute_chop_batch(req, output_id),
2004 "pivot" => compute_pivot_batch(req, output_id),
2005 _ => Err(IndicatorDispatchError::UnsupportedCapability {
2006 indicator: indicator_id.to_string(),
2007 capability: "cpu_batch",
2008 }),
2009 }
2010}
2011
2012fn validate_input_kind_strict(
2013 indicator: &str,
2014 expected: IndicatorInputKind,
2015 data: IndicatorDataRef<'_>,
2016) -> Result<(), IndicatorDispatchError> {
2017 let expected = strict_expected_input_kind(indicator, expected);
2018 if indicator.eq_ignore_ascii_case("mod_god_mode") {
2019 let matches = matches!(
2020 data,
2021 IndicatorDataRef::Candles { .. }
2022 | IndicatorDataRef::Ohlc { .. }
2023 | IndicatorDataRef::Ohlcv { .. }
2024 );
2025 if matches {
2026 return Ok(());
2027 }
2028 }
2029 let matches = matches!(
2030 (expected, data),
2031 (IndicatorInputKind::Slice, IndicatorDataRef::Slice { .. })
2032 | (
2033 IndicatorInputKind::Candles,
2034 IndicatorDataRef::Candles { .. }
2035 )
2036 | (IndicatorInputKind::Ohlc, IndicatorDataRef::Ohlc { .. })
2037 | (IndicatorInputKind::Ohlcv, IndicatorDataRef::Ohlcv { .. })
2038 | (
2039 IndicatorInputKind::HighLow,
2040 IndicatorDataRef::HighLow { .. }
2041 )
2042 | (
2043 IndicatorInputKind::CloseVolume,
2044 IndicatorDataRef::CloseVolume { .. }
2045 )
2046 );
2047
2048 if matches {
2049 Ok(())
2050 } else {
2051 Err(IndicatorDispatchError::MissingRequiredInput {
2052 indicator: indicator.to_string(),
2053 input: expected,
2054 })
2055 }
2056}
2057
2058fn strict_expected_input_kind(indicator: &str, fallback: IndicatorInputKind) -> IndicatorInputKind {
2059 if indicator.eq_ignore_ascii_case("ao") {
2060 return IndicatorInputKind::Slice;
2061 }
2062 if indicator.eq_ignore_ascii_case("ttm_trend") {
2063 return IndicatorInputKind::Candles;
2064 }
2065 fallback
2066}
2067
2068fn resolve_output_id<'a>(
2069 info: &'a IndicatorInfo,
2070 requested: Option<&str>,
2071) -> Result<&'a str, IndicatorDispatchError> {
2072 if info.outputs.is_empty() {
2073 return Err(IndicatorDispatchError::ComputeFailed {
2074 indicator: info.id.to_string(),
2075 details: "indicator has no registered outputs".to_string(),
2076 });
2077 }
2078
2079 if info.outputs.len() == 1 {
2080 let only = info.outputs[0].id;
2081 if let Some(req) = requested {
2082 if req == only {
2083 return Ok(only);
2084 }
2085 if !req.eq_ignore_ascii_case(only) {
2086 return Err(IndicatorDispatchError::UnknownOutput {
2087 indicator: info.id.to_string(),
2088 output: req.to_string(),
2089 });
2090 }
2091 }
2092 return Ok(only);
2093 }
2094
2095 let req = requested.ok_or_else(|| IndicatorDispatchError::InvalidParam {
2096 indicator: info.id.to_string(),
2097 key: "output_id".to_string(),
2098 reason: "output_id is required for multi-output indicators".to_string(),
2099 })?;
2100
2101 if let Some(out) = info.outputs.iter().find(|o| o.id == req) {
2102 return Ok(out.id);
2103 }
2104 info.outputs
2105 .iter()
2106 .find(|o| o.id.eq_ignore_ascii_case(req))
2107 .map(|o| o.id)
2108 .ok_or_else(|| IndicatorDispatchError::UnknownOutput {
2109 indicator: info.id.to_string(),
2110 output: req.to_string(),
2111 })
2112}
2113
2114fn is_moving_average(id: &str) -> bool {
2115 list_moving_averages()
2116 .iter()
2117 .any(|ma| ma.id.eq_ignore_ascii_case(id))
2118}
2119
2120fn ma_is_period_based(info: &IndicatorInfo) -> bool {
2121 info.params
2122 .iter()
2123 .any(|p| p.key.eq_ignore_ascii_case("period"))
2124}
2125
2126fn compute_ma_batch(
2127 req: IndicatorBatchRequest<'_>,
2128 info: &IndicatorInfo,
2129 output_id: &str,
2130) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2131 let data = ma_data_from_req(info.id, req.data)?;
2132 let cols = ma_len_from_req(info.id, req.data)?;
2133 let period_based = ma_is_period_based(info);
2134 if period_based {
2135 if let Some(out) = try_compute_ma_batch_fast(req, info, output_id, data.clone(), cols)? {
2136 return Ok(out);
2137 }
2138 }
2139 let rows = req.combos.len();
2140 let mut matrix = Vec::with_capacity(rows.saturating_mul(cols));
2141
2142 for combo in req.combos {
2143 let period = ma_period_for_combo(info, combo.params)?;
2144 let mut params = convert_ma_params(combo.params, info.id, output_id)?;
2145 if info.outputs.len() > 1 && !has_key(combo.params, "output") {
2146 params.push(MaBatchParamKV {
2147 key: "output",
2148 value: MaBatchParamValue::EnumString(output_id),
2149 });
2150 }
2151 let out = ma_batch_with_kernel_and_typed_params(
2152 info.id,
2153 data.clone(),
2154 (period, period, 0),
2155 req.kernel,
2156 ¶ms,
2157 )
2158 .map_err(|e| IndicatorDispatchError::ComputeFailed {
2159 indicator: info.id.to_string(),
2160 details: e.to_string(),
2161 })?;
2162 ensure_len(info.id, cols, out.cols)?;
2163 let row_values = if out.rows == 1 {
2164 out.values
2165 } else {
2166 reorder_or_take_f64_matrix_by_period(
2167 info.id,
2168 &[period],
2169 &out.periods,
2170 out.cols,
2171 out.values,
2172 )?
2173 };
2174 ensure_len(info.id, cols, row_values.len())?;
2175 matrix.extend_from_slice(&row_values);
2176 }
2177
2178 Ok(f64_output(output_id, rows, cols, matrix))
2179}
2180
2181fn try_compute_ma_batch_fast(
2182 req: IndicatorBatchRequest<'_>,
2183 info: &IndicatorInfo,
2184 output_id: &str,
2185 data: MaData<'_>,
2186 cols: usize,
2187) -> Result<Option<IndicatorBatchOutput>, IndicatorDispatchError> {
2188 if req.combos.is_empty() {
2189 return Ok(Some(f64_output(output_id, 0, cols, Vec::new())));
2190 }
2191 if !ma_is_period_based(info) {
2192 return Ok(None);
2193 }
2194
2195 let mut periods = Vec::with_capacity(req.combos.len());
2196 let mut shared_params: Option<Vec<MaBatchParamKV<'_>>> = None;
2197
2198 for combo in req.combos {
2199 periods.push(ma_period_for_combo(info, combo.params)?);
2200 let mut params = convert_ma_params(combo.params, info.id, output_id)?;
2201 if info.outputs.len() > 1 && !has_key(combo.params, "output") {
2202 params.push(MaBatchParamKV {
2203 key: "output",
2204 value: MaBatchParamValue::EnumString(output_id),
2205 });
2206 }
2207 match &shared_params {
2208 None => shared_params = Some(params),
2209 Some(existing) => {
2210 if !ma_params_equal(existing, ¶ms) {
2211 return Ok(None);
2212 }
2213 }
2214 }
2215 }
2216
2217 let Some((start, end, step)) = derive_period_sweep(&periods) else {
2218 return Ok(None);
2219 };
2220
2221 let out = ma_batch_with_kernel_and_typed_params(
2222 info.id,
2223 data,
2224 (start, end, step),
2225 req.kernel,
2226 shared_params.as_deref().unwrap_or(&[]),
2227 )
2228 .map_err(|e| IndicatorDispatchError::ComputeFailed {
2229 indicator: info.id.to_string(),
2230 details: e.to_string(),
2231 })?;
2232 ensure_len(info.id, cols, out.cols)?;
2233
2234 let values = reorder_or_take_f64_matrix_by_period(
2235 info.id,
2236 &periods,
2237 &out.periods,
2238 out.cols,
2239 out.values,
2240 )?;
2241 Ok(Some(f64_output(output_id, periods.len(), cols, values)))
2242}
2243
2244fn ma_params_equal(a: &[MaBatchParamKV<'_>], b: &[MaBatchParamKV<'_>]) -> bool {
2245 if a.len() != b.len() {
2246 return false;
2247 }
2248
2249 for (lhs, rhs) in a.iter().zip(b.iter()) {
2250 if !lhs.key.eq_ignore_ascii_case(rhs.key) {
2251 return false;
2252 }
2253 let same = match (&lhs.value, &rhs.value) {
2254 (MaBatchParamValue::Int(x), MaBatchParamValue::Int(y)) => x == y,
2255 (MaBatchParamValue::Float(x), MaBatchParamValue::Float(y)) => x == y,
2256 (MaBatchParamValue::Bool(x), MaBatchParamValue::Bool(y)) => x == y,
2257 (MaBatchParamValue::EnumString(x), MaBatchParamValue::EnumString(y)) => {
2258 x.eq_ignore_ascii_case(y)
2259 }
2260 _ => false,
2261 };
2262 if !same {
2263 return false;
2264 }
2265 }
2266 true
2267}
2268
2269fn collect_f64(
2270 indicator: &str,
2271 output_id: &str,
2272 combos: &[IndicatorParamSet<'_>],
2273 cols: usize,
2274 mut eval: impl FnMut(&[ParamKV<'_>]) -> Result<Vec<f64>, IndicatorDispatchError>,
2275) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2276 let rows = combos.len();
2277 let mut matrix = Vec::with_capacity(rows.saturating_mul(cols));
2278 for combo in combos {
2279 let series = eval(combo.params)?;
2280 ensure_len(indicator, cols, series.len())?;
2281 matrix.extend_from_slice(&series);
2282 }
2283 Ok(f64_output(output_id, rows, cols, matrix))
2284}
2285
2286fn collect_bool(
2287 indicator: &str,
2288 output_id: &str,
2289 combos: &[IndicatorParamSet<'_>],
2290 cols: usize,
2291 mut eval: impl FnMut(&[ParamKV<'_>]) -> Result<Vec<bool>, IndicatorDispatchError>,
2292) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2293 let rows = combos.len();
2294 let mut matrix = Vec::with_capacity(rows.saturating_mul(cols));
2295 for combo in combos {
2296 let series = eval(combo.params)?;
2297 ensure_len(indicator, cols, series.len())?;
2298 matrix.extend_from_slice(&series);
2299 }
2300 Ok(bool_output(output_id, rows, cols, matrix))
2301}
2302
2303fn collect_f64_into_rows(
2304 indicator: &str,
2305 output_id: &str,
2306 combos: &[IndicatorParamSet<'_>],
2307 cols: usize,
2308 mut eval_into: impl FnMut(&[ParamKV<'_>], &mut [f64]) -> Result<(), IndicatorDispatchError>,
2309) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2310 let rows = combos.len();
2311 let total = rows
2312 .checked_mul(cols)
2313 .ok_or_else(|| IndicatorDispatchError::ComputeFailed {
2314 indicator: indicator.to_string(),
2315 details: "rows*cols overflow".to_string(),
2316 })?;
2317 let mut matrix = vec![f64::NAN; total];
2318 for (row, combo) in combos.iter().enumerate() {
2319 let start = row * cols;
2320 let end = start + cols;
2321 eval_into(combo.params, &mut matrix[start..end])?;
2322 }
2323 Ok(f64_output(output_id, rows, cols, matrix))
2324}
2325
2326fn to_batch_kernel(kernel: Kernel) -> Kernel {
2327 match kernel {
2328 Kernel::Auto => Kernel::Auto,
2329 Kernel::Scalar => Kernel::ScalarBatch,
2330 Kernel::Avx2 => Kernel::Avx2Batch,
2331 Kernel::Avx512 => Kernel::Avx512Batch,
2332 other => other,
2333 }
2334}
2335
2336fn combo_periods(
2337 indicator: &str,
2338 combos: &[IndicatorParamSet<'_>],
2339 key: &str,
2340 default: usize,
2341) -> Result<Vec<usize>, IndicatorDispatchError> {
2342 let mut out = Vec::with_capacity(combos.len());
2343 for combo in combos {
2344 out.push(get_usize_param(indicator, combo.params, key, default)?);
2345 }
2346 Ok(out)
2347}
2348
2349fn derive_period_sweep(periods: &[usize]) -> Option<(usize, usize, usize)> {
2350 if periods.is_empty() {
2351 return None;
2352 }
2353 if periods.len() == 1 {
2354 return Some((periods[0], periods[0], 0));
2355 }
2356 if periods.windows(2).all(|w| w[0] == w[1]) {
2357 return Some((periods[0], periods[0], 0));
2358 }
2359
2360 let diff = periods[1] as isize - periods[0] as isize;
2361 if diff == 0 {
2362 return None;
2363 }
2364 if !periods
2365 .windows(2)
2366 .all(|w| (w[1] as isize - w[0] as isize) == diff)
2367 {
2368 return None;
2369 }
2370
2371 Some((
2372 periods[0],
2373 *periods.last().unwrap_or(&periods[0]),
2374 diff.unsigned_abs(),
2375 ))
2376}
2377
2378fn reorder_or_take_f64_matrix_by_period(
2379 indicator: &str,
2380 requested_periods: &[usize],
2381 produced_periods: &[usize],
2382 cols: usize,
2383 values: Vec<f64>,
2384) -> Result<Vec<f64>, IndicatorDispatchError> {
2385 ensure_len(
2386 indicator,
2387 produced_periods.len().saturating_mul(cols),
2388 values.len(),
2389 )?;
2390
2391 if requested_periods.len() == produced_periods.len() && requested_periods == produced_periods {
2392 return Ok(values);
2393 }
2394
2395 let period_to_row: HashMap<usize, usize> = produced_periods
2396 .iter()
2397 .copied()
2398 .enumerate()
2399 .map(|(row, period)| (period, row))
2400 .collect();
2401
2402 let mut out = Vec::with_capacity(requested_periods.len().saturating_mul(cols));
2403 for period in requested_periods {
2404 let row = period_to_row.get(period).copied().ok_or_else(|| {
2405 IndicatorDispatchError::ComputeFailed {
2406 indicator: indicator.to_string(),
2407 details: format!("batch output did not contain requested period {period}"),
2408 }
2409 })?;
2410 let start = row * cols;
2411 let end = start + cols;
2412 out.extend_from_slice(&values[start..end]);
2413 }
2414 Ok(out)
2415}
2416
2417fn compute_ad_batch(
2418 req: IndicatorBatchRequest<'_>,
2419 output_id: &str,
2420) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2421 expect_value_output("ad", output_id)?;
2422 let (high, low, close, volume) = extract_hlcv_input("ad", req.data)?;
2423 let kernel = req.kernel.to_non_batch();
2424 collect_f64("ad", output_id, req.combos, close.len(), |_params| {
2425 let input = AdInput::from_slices(high, low, close, volume, AdParams::default());
2426 let out =
2427 ad_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
2428 indicator: "ad".to_string(),
2429 details: e.to_string(),
2430 })?;
2431 Ok(out.values)
2432 })
2433}
2434
2435fn compute_adosc_batch(
2436 req: IndicatorBatchRequest<'_>,
2437 output_id: &str,
2438) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2439 expect_value_output("adosc", output_id)?;
2440 let (high, low, close, volume) = extract_hlcv_input("adosc", req.data)?;
2441 let kernel = req.kernel.to_non_batch();
2442 collect_f64("adosc", output_id, req.combos, close.len(), |params| {
2443 let short_period = get_usize_param("adosc", params, "short_period", 3)?;
2444 let long_period = get_usize_param("adosc", params, "long_period", 10)?;
2445 let input = AdoscInput::from_slices(
2446 high,
2447 low,
2448 close,
2449 volume,
2450 AdoscParams {
2451 short_period: Some(short_period),
2452 long_period: Some(long_period),
2453 },
2454 );
2455 let out = adosc_with_kernel(&input, kernel).map_err(|e| {
2456 IndicatorDispatchError::ComputeFailed {
2457 indicator: "adosc".to_string(),
2458 details: e.to_string(),
2459 }
2460 })?;
2461 Ok(out.values)
2462 })
2463}
2464
2465fn compute_ao_batch(
2466 req: IndicatorBatchRequest<'_>,
2467 output_id: &str,
2468) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2469 expect_value_output("ao", output_id)?;
2470 let mut derived_source: Option<Vec<f64>> = None;
2471 let source: &[f64] = match req.data {
2472 IndicatorDataRef::Slice { values } => values,
2473 IndicatorDataRef::Candles { candles, source } => {
2474 source_type(candles, source.unwrap_or("hl2"))
2475 }
2476 IndicatorDataRef::HighLow { high, low } => {
2477 ensure_same_len_2("ao", high.len(), low.len())?;
2478 derived_source = Some(high.iter().zip(low).map(|(h, l)| 0.5 * (h + l)).collect());
2479 derived_source.as_deref().unwrap_or(high)
2480 }
2481 IndicatorDataRef::Ohlc {
2482 open,
2483 high,
2484 low,
2485 close,
2486 } => {
2487 ensure_same_len_4("ao", open.len(), high.len(), low.len(), close.len())?;
2488 derived_source = Some(high.iter().zip(low).map(|(h, l)| 0.5 * (h + l)).collect());
2489 derived_source.as_deref().unwrap_or(close)
2490 }
2491 IndicatorDataRef::Ohlcv {
2492 open,
2493 high,
2494 low,
2495 close,
2496 volume,
2497 } => {
2498 ensure_same_len_5(
2499 "ao",
2500 open.len(),
2501 high.len(),
2502 low.len(),
2503 close.len(),
2504 volume.len(),
2505 )?;
2506 derived_source = Some(high.iter().zip(low).map(|(h, l)| 0.5 * (h + l)).collect());
2507 derived_source.as_deref().unwrap_or(close)
2508 }
2509 IndicatorDataRef::CloseVolume { .. } => {
2510 return Err(IndicatorDispatchError::MissingRequiredInput {
2511 indicator: "ao".to_string(),
2512 input: IndicatorInputKind::HighLow,
2513 })
2514 }
2515 };
2516 let kernel = req.kernel.to_non_batch();
2517 collect_f64_into_rows("ao", output_id, req.combos, source.len(), |params, row| {
2518 let short_period = get_usize_param("ao", params, "short_period", 5)?;
2519 let long_period = get_usize_param("ao", params, "long_period", 34)?;
2520 let input = AoInput::from_slice(
2521 source,
2522 AoParams {
2523 short_period: Some(short_period),
2524 long_period: Some(long_period),
2525 },
2526 );
2527 ao_into_slice(row, &input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
2528 indicator: "ao".to_string(),
2529 details: e.to_string(),
2530 })
2531 })
2532}
2533
2534fn compute_bop_batch(
2535 req: IndicatorBatchRequest<'_>,
2536 output_id: &str,
2537) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2538 expect_value_output("bop", output_id)?;
2539 let (open, high, low, close): (&[f64], &[f64], &[f64], &[f64]) = match req.data {
2540 IndicatorDataRef::Candles { candles, .. } => (
2541 candles.open.as_slice(),
2542 candles.high.as_slice(),
2543 candles.low.as_slice(),
2544 candles.close.as_slice(),
2545 ),
2546 IndicatorDataRef::Ohlc {
2547 open,
2548 high,
2549 low,
2550 close,
2551 } => {
2552 ensure_same_len_4("bop", open.len(), high.len(), low.len(), close.len())?;
2553 (open, high, low, close)
2554 }
2555 IndicatorDataRef::Ohlcv {
2556 open,
2557 high,
2558 low,
2559 close,
2560 volume,
2561 } => {
2562 ensure_same_len_5(
2563 "bop",
2564 open.len(),
2565 high.len(),
2566 low.len(),
2567 close.len(),
2568 volume.len(),
2569 )?;
2570 (open, high, low, close)
2571 }
2572 _ => {
2573 return Err(IndicatorDispatchError::MissingRequiredInput {
2574 indicator: "bop".to_string(),
2575 input: IndicatorInputKind::Ohlc,
2576 })
2577 }
2578 };
2579 let kernel = req.kernel.to_non_batch();
2580 collect_f64("bop", output_id, req.combos, close.len(), |_params| {
2581 let input = BopInput::from_slices(open, high, low, close, BopParams::default());
2582 let out =
2583 bop_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
2584 indicator: "bop".to_string(),
2585 details: e.to_string(),
2586 })?;
2587 Ok(out.values)
2588 })
2589}
2590
2591fn compute_emv_batch(
2592 req: IndicatorBatchRequest<'_>,
2593 output_id: &str,
2594) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2595 expect_value_output("emv", output_id)?;
2596 let (high, low, close, volume) = extract_hlcv_input("emv", req.data)?;
2597 let kernel = req.kernel.to_non_batch();
2598 collect_f64("emv", output_id, req.combos, close.len(), |_params| {
2599 let input = EmvInput::from_slices(high, low, close, volume);
2600 let out =
2601 emv_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
2602 indicator: "emv".to_string(),
2603 details: e.to_string(),
2604 })?;
2605 Ok(out.values)
2606 })
2607}
2608
2609fn compute_efi_batch(
2610 req: IndicatorBatchRequest<'_>,
2611 output_id: &str,
2612) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2613 expect_value_output("efi", output_id)?;
2614 let (price, volume) = extract_close_volume_input("efi", req.data, "close")?;
2615 let kernel = req.kernel.to_non_batch();
2616 collect_f64("efi", output_id, req.combos, price.len(), |params| {
2617 let period = get_usize_param("efi", params, "period", 13)?;
2618 let input = EfiInput::from_slices(
2619 price,
2620 volume,
2621 EfiParams {
2622 period: Some(period),
2623 },
2624 );
2625 let out =
2626 efi_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
2627 indicator: "efi".to_string(),
2628 details: e.to_string(),
2629 })?;
2630 Ok(out.values)
2631 })
2632}
2633
2634fn compute_mfi_batch(
2635 req: IndicatorBatchRequest<'_>,
2636 output_id: &str,
2637) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2638 expect_value_output("mfi", output_id)?;
2639 let mut derived_typical_price: Option<Vec<f64>> = None;
2640 let (typical_price, volume): (&[f64], &[f64]) = match req.data {
2641 IndicatorDataRef::Candles { candles, source } => (
2642 source_type(candles, source.unwrap_or("hlc3")),
2643 candles.volume.as_slice(),
2644 ),
2645 IndicatorDataRef::Ohlcv {
2646 open,
2647 high,
2648 low,
2649 close,
2650 volume,
2651 } => {
2652 ensure_same_len_5(
2653 "mfi",
2654 open.len(),
2655 high.len(),
2656 low.len(),
2657 close.len(),
2658 volume.len(),
2659 )?;
2660 derived_typical_price = Some(
2661 high.iter()
2662 .zip(low)
2663 .zip(close)
2664 .map(|((h, l), c)| (h + l + c) / 3.0)
2665 .collect(),
2666 );
2667 (derived_typical_price.as_deref().unwrap_or(close), volume)
2668 }
2669 IndicatorDataRef::CloseVolume { close, volume } => {
2670 ensure_same_len_2("mfi", close.len(), volume.len())?;
2671 (close, volume)
2672 }
2673 _ => {
2674 return Err(IndicatorDispatchError::MissingRequiredInput {
2675 indicator: "mfi".to_string(),
2676 input: IndicatorInputKind::CloseVolume,
2677 })
2678 }
2679 };
2680
2681 let periods = combo_periods("mfi", req.combos, "period", 14)?;
2682 if let Some((start, end, step)) = derive_period_sweep(&periods) {
2683 let out = mfi_batch_with_kernel(
2684 typical_price,
2685 volume,
2686 &MfiBatchRange {
2687 period: (start, end, step),
2688 },
2689 to_batch_kernel(req.kernel),
2690 )
2691 .map_err(|e| IndicatorDispatchError::ComputeFailed {
2692 indicator: "mfi".to_string(),
2693 details: e.to_string(),
2694 })?;
2695 ensure_len("mfi", typical_price.len(), out.cols)?;
2696 let produced_periods: Vec<usize> = out
2697 .combos
2698 .iter()
2699 .map(|combo| combo.period.unwrap_or(14))
2700 .collect();
2701 let values = reorder_or_take_f64_matrix_by_period(
2702 "mfi",
2703 &periods,
2704 &produced_periods,
2705 out.cols,
2706 out.values,
2707 )?;
2708 return Ok(f64_output(output_id, periods.len(), out.cols, values));
2709 }
2710
2711 let kernel = req.kernel.to_non_batch();
2712 collect_f64_into_rows(
2713 "mfi",
2714 output_id,
2715 req.combos,
2716 typical_price.len(),
2717 |params, row| {
2718 let period = get_usize_param("mfi", params, "period", 14)?;
2719 let input = MfiInput::from_slices(
2720 typical_price,
2721 volume,
2722 MfiParams {
2723 period: Some(period),
2724 },
2725 );
2726 mfi_into_slice(row, &input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
2727 indicator: "mfi".to_string(),
2728 details: e.to_string(),
2729 })
2730 },
2731 )
2732}
2733
2734fn compute_mass_batch(
2735 req: IndicatorBatchRequest<'_>,
2736 output_id: &str,
2737) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2738 expect_value_output("mass", output_id)?;
2739 let (high, low) = extract_high_low_input("mass", req.data)?;
2740 let kernel = req.kernel.to_non_batch();
2741 collect_f64("mass", output_id, req.combos, high.len(), |params| {
2742 let period = get_usize_param("mass", params, "period", 5)?;
2743 let input = MassInput::from_slices(
2744 high,
2745 low,
2746 MassParams {
2747 period: Some(period),
2748 },
2749 );
2750 let out = mass_with_kernel(&input, kernel).map_err(|e| {
2751 IndicatorDispatchError::ComputeFailed {
2752 indicator: "mass".to_string(),
2753 details: e.to_string(),
2754 }
2755 })?;
2756 Ok(out.values)
2757 })
2758}
2759
2760fn compute_kvo_batch(
2761 req: IndicatorBatchRequest<'_>,
2762 output_id: &str,
2763) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2764 expect_value_output("kvo", output_id)?;
2765 let (high, low, close, volume) = extract_hlcv_input("kvo", req.data)?;
2766 let kernel = req.kernel.to_non_batch();
2767 collect_f64("kvo", output_id, req.combos, close.len(), |params| {
2768 let short_period = get_usize_param("kvo", params, "short_period", 2)?;
2769 let long_period = get_usize_param("kvo", params, "long_period", 5)?;
2770 let input = KvoInput::from_slices(
2771 high,
2772 low,
2773 close,
2774 volume,
2775 KvoParams {
2776 short_period: Some(short_period),
2777 long_period: Some(long_period),
2778 },
2779 );
2780 let out =
2781 kvo_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
2782 indicator: "kvo".to_string(),
2783 details: e.to_string(),
2784 })?;
2785 Ok(out.values)
2786 })
2787}
2788
2789fn compute_vosc_batch(
2790 req: IndicatorBatchRequest<'_>,
2791 output_id: &str,
2792) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2793 expect_value_output("vosc", output_id)?;
2794 let volume = extract_volume_input("vosc", req.data)?;
2795 let kernel = req.kernel.to_non_batch();
2796 collect_f64("vosc", output_id, req.combos, volume.len(), |params| {
2797 let short_period = get_usize_param("vosc", params, "short_period", 2)?;
2798 let long_period = get_usize_param("vosc", params, "long_period", 5)?;
2799 let input = VoscInput::from_slice(
2800 volume,
2801 VoscParams {
2802 short_period: Some(short_period),
2803 long_period: Some(long_period),
2804 },
2805 );
2806 let out = vosc_with_kernel(&input, kernel).map_err(|e| {
2807 IndicatorDispatchError::ComputeFailed {
2808 indicator: "vosc".to_string(),
2809 details: e.to_string(),
2810 }
2811 })?;
2812 Ok(out.values)
2813 })
2814}
2815
2816fn compute_dx_batch(
2817 req: IndicatorBatchRequest<'_>,
2818 output_id: &str,
2819) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2820 expect_value_output("dx", output_id)?;
2821 let (high, low, close) = extract_ohlc_input("dx", req.data)?;
2822
2823 let periods = combo_periods("dx", req.combos, "period", 14)?;
2824 if let Some((start, end, step)) = derive_period_sweep(&periods) {
2825 let out = dx_batch_with_kernel(
2826 high,
2827 low,
2828 close,
2829 &DxBatchRange {
2830 period: (start, end, step),
2831 },
2832 to_batch_kernel(req.kernel),
2833 )
2834 .map_err(|e| IndicatorDispatchError::ComputeFailed {
2835 indicator: "dx".to_string(),
2836 details: e.to_string(),
2837 })?;
2838 ensure_len("dx", close.len(), out.cols)?;
2839 let produced_periods: Vec<usize> = out
2840 .combos
2841 .iter()
2842 .map(|combo| combo.period.unwrap_or(14))
2843 .collect();
2844 let values = reorder_or_take_f64_matrix_by_period(
2845 "dx",
2846 &periods,
2847 &produced_periods,
2848 out.cols,
2849 out.values,
2850 )?;
2851 return Ok(f64_output(output_id, periods.len(), out.cols, values));
2852 }
2853
2854 let kernel = req.kernel.to_non_batch();
2855 collect_f64_into_rows("dx", output_id, req.combos, close.len(), |params, row| {
2856 let period = get_usize_param("dx", params, "period", 14)?;
2857 let input = DxInput::from_hlc_slices(
2858 high,
2859 low,
2860 close,
2861 DxParams {
2862 period: Some(period),
2863 },
2864 );
2865 dx_into_slice(row, &input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
2866 indicator: "dx".to_string(),
2867 details: e.to_string(),
2868 })
2869 })
2870}
2871
2872fn compute_fosc_batch(
2873 req: IndicatorBatchRequest<'_>,
2874 output_id: &str,
2875) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2876 expect_value_output("fosc", output_id)?;
2877 let data = extract_slice_input("fosc", req.data, "close")?;
2878 let kernel = req.kernel.to_non_batch();
2879 collect_f64("fosc", output_id, req.combos, data.len(), |params| {
2880 let period = get_usize_param("fosc", params, "period", 5)?;
2881 let input = FoscInput::from_slice(
2882 data,
2883 FoscParams {
2884 period: Some(period),
2885 },
2886 );
2887 let out = fosc_with_kernel(&input, kernel).map_err(|e| {
2888 IndicatorDispatchError::ComputeFailed {
2889 indicator: "fosc".to_string(),
2890 details: e.to_string(),
2891 }
2892 })?;
2893 Ok(out.values)
2894 })
2895}
2896
2897fn compute_ift_rsi_batch(
2898 req: IndicatorBatchRequest<'_>,
2899 output_id: &str,
2900) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2901 expect_value_output("ift_rsi", output_id)?;
2902 let data = extract_slice_input("ift_rsi", req.data, "close")?;
2903 let kernel = req.kernel.to_non_batch();
2904 collect_f64("ift_rsi", output_id, req.combos, data.len(), |params| {
2905 let rsi_period = get_usize_param("ift_rsi", params, "rsi_period", 5)?;
2906 let wma_period = get_usize_param("ift_rsi", params, "wma_period", 9)?;
2907 let input = IftRsiInput::from_slice(
2908 data,
2909 IftRsiParams {
2910 rsi_period: Some(rsi_period),
2911 wma_period: Some(wma_period),
2912 },
2913 );
2914 let out = ift_rsi_with_kernel(&input, kernel).map_err(|e| {
2915 IndicatorDispatchError::ComputeFailed {
2916 indicator: "ift_rsi".to_string(),
2917 details: e.to_string(),
2918 }
2919 })?;
2920 Ok(out.values)
2921 })
2922}
2923
2924fn compute_linearreg_angle_batch(
2925 req: IndicatorBatchRequest<'_>,
2926 output_id: &str,
2927) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2928 expect_value_output("linearreg_angle", output_id)?;
2929 let data = extract_slice_input("linearreg_angle", req.data, "close")?;
2930 let kernel = req.kernel.to_non_batch();
2931 collect_f64(
2932 "linearreg_angle",
2933 output_id,
2934 req.combos,
2935 data.len(),
2936 |params| {
2937 let period = get_usize_param("linearreg_angle", params, "period", 14)?;
2938 let input = Linearreg_angleInput::from_slice(
2939 data,
2940 Linearreg_angleParams {
2941 period: Some(period),
2942 },
2943 );
2944 let out = linearreg_angle_with_kernel(&input, kernel).map_err(|e| {
2945 IndicatorDispatchError::ComputeFailed {
2946 indicator: "linearreg_angle".to_string(),
2947 details: e.to_string(),
2948 }
2949 })?;
2950 Ok(out.values)
2951 },
2952 )
2953}
2954
2955fn compute_linearreg_intercept_batch(
2956 req: IndicatorBatchRequest<'_>,
2957 output_id: &str,
2958) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2959 expect_value_output("linearreg_intercept", output_id)?;
2960 let data = extract_slice_input("linearreg_intercept", req.data, "close")?;
2961 let kernel = req.kernel.to_non_batch();
2962 collect_f64(
2963 "linearreg_intercept",
2964 output_id,
2965 req.combos,
2966 data.len(),
2967 |params| {
2968 let period = get_usize_param("linearreg_intercept", params, "period", 14)?;
2969 let input = LinearRegInterceptInput::from_slice(
2970 data,
2971 LinearRegInterceptParams {
2972 period: Some(period),
2973 },
2974 );
2975 let out = linearreg_intercept_with_kernel(&input, kernel).map_err(|e| {
2976 IndicatorDispatchError::ComputeFailed {
2977 indicator: "linearreg_intercept".to_string(),
2978 details: e.to_string(),
2979 }
2980 })?;
2981 Ok(out.values)
2982 },
2983 )
2984}
2985
2986fn compute_linearreg_slope_batch(
2987 req: IndicatorBatchRequest<'_>,
2988 output_id: &str,
2989) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2990 expect_value_output("linearreg_slope", output_id)?;
2991 let data = extract_slice_input("linearreg_slope", req.data, "close")?;
2992 let kernel = req.kernel.to_non_batch();
2993 collect_f64(
2994 "linearreg_slope",
2995 output_id,
2996 req.combos,
2997 data.len(),
2998 |params| {
2999 let period = get_usize_param("linearreg_slope", params, "period", 14)?;
3000 let input = LinearRegSlopeInput::from_slice(
3001 data,
3002 LinearRegSlopeParams {
3003 period: Some(period),
3004 },
3005 );
3006 let out = linearreg_slope_with_kernel(&input, kernel).map_err(|e| {
3007 IndicatorDispatchError::ComputeFailed {
3008 indicator: "linearreg_slope".to_string(),
3009 details: e.to_string(),
3010 }
3011 })?;
3012 Ok(out.values)
3013 },
3014 )
3015}
3016
3017fn compute_cg_batch(
3018 req: IndicatorBatchRequest<'_>,
3019 output_id: &str,
3020) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3021 expect_value_output("cg", output_id)?;
3022 let data = extract_slice_input("cg", req.data, "close")?;
3023 let kernel = req.kernel.to_non_batch();
3024 collect_f64("cg", output_id, req.combos, data.len(), |params| {
3025 let period = get_usize_param("cg", params, "period", 10)?;
3026 let input = CgInput::from_slice(
3027 data,
3028 CgParams {
3029 period: Some(period),
3030 },
3031 );
3032 let out =
3033 cg_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3034 indicator: "cg".to_string(),
3035 details: e.to_string(),
3036 })?;
3037 Ok(out.values)
3038 })
3039}
3040
3041fn compute_rsi_batch(
3042 req: IndicatorBatchRequest<'_>,
3043 output_id: &str,
3044) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3045 expect_value_output("rsi", output_id)?;
3046 let data = extract_slice_input("rsi", req.data, "close")?;
3047 let kernel = req.kernel.to_non_batch();
3048 collect_f64("rsi", output_id, req.combos, data.len(), |params| {
3049 let period = get_usize_param("rsi", params, "period", 14)?;
3050 let input = RsiInput::from_slice(
3051 data,
3052 RsiParams {
3053 period: Some(period),
3054 },
3055 );
3056 let out =
3057 rsi_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3058 indicator: "rsi".to_string(),
3059 details: e.to_string(),
3060 })?;
3061 Ok(out.values)
3062 })
3063}
3064
3065fn compute_roc_batch(
3066 req: IndicatorBatchRequest<'_>,
3067 output_id: &str,
3068) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3069 expect_value_output("roc", output_id)?;
3070 let data = extract_slice_input("roc", req.data, "close")?;
3071 let kernel = req.kernel.to_non_batch();
3072 collect_f64("roc", output_id, req.combos, data.len(), |params| {
3073 let period = get_usize_param("roc", params, "period", 9)?;
3074 let input = RocInput::from_slice(
3075 data,
3076 RocParams {
3077 period: Some(period),
3078 },
3079 );
3080 let out =
3081 roc_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3082 indicator: "roc".to_string(),
3083 details: e.to_string(),
3084 })?;
3085 Ok(out.values)
3086 })
3087}
3088
3089fn compute_linear_correlation_oscillator_batch(
3090 req: IndicatorBatchRequest<'_>,
3091 output_id: &str,
3092) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3093 expect_value_output("linear_correlation_oscillator", output_id)?;
3094 let data = extract_slice_input("linear_correlation_oscillator", req.data, "close")?;
3095 let kernel = req.kernel.to_non_batch();
3096 collect_f64(
3097 "linear_correlation_oscillator",
3098 output_id,
3099 req.combos,
3100 data.len(),
3101 |params| {
3102 let period = get_usize_param("linear_correlation_oscillator", params, "period", 14)?;
3103 let input = LinearCorrelationOscillatorInput::from_slice(
3104 data,
3105 LinearCorrelationOscillatorParams {
3106 period: Some(period),
3107 },
3108 );
3109 let out = linear_correlation_oscillator_with_kernel(&input, kernel).map_err(|e| {
3110 IndicatorDispatchError::ComputeFailed {
3111 indicator: "linear_correlation_oscillator".to_string(),
3112 details: e.to_string(),
3113 }
3114 })?;
3115 Ok(out.values)
3116 },
3117 )
3118}
3119
3120fn compute_apo_batch(
3121 req: IndicatorBatchRequest<'_>,
3122 output_id: &str,
3123) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3124 expect_value_output("apo", output_id)?;
3125 let data = extract_slice_input("apo", req.data, "close")?;
3126 let kernel = req.kernel.to_non_batch();
3127 collect_f64("apo", output_id, req.combos, data.len(), |params| {
3128 let short_period = get_usize_param("apo", params, "short_period", 10)?;
3129 let long_period = get_usize_param("apo", params, "long_period", 20)?;
3130 let input = ApoInput::from_slice(
3131 data,
3132 ApoParams {
3133 short_period: Some(short_period),
3134 long_period: Some(long_period),
3135 },
3136 );
3137 let out =
3138 apo_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3139 indicator: "apo".to_string(),
3140 details: e.to_string(),
3141 })?;
3142 Ok(out.values)
3143 })
3144}
3145
3146fn compute_cci_batch(
3147 req: IndicatorBatchRequest<'_>,
3148 output_id: &str,
3149) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3150 expect_value_output("cci", output_id)?;
3151 let data = extract_slice_input("cci", req.data, "hlc3")?;
3152 let kernel = req.kernel.to_non_batch();
3153 collect_f64("cci", output_id, req.combos, data.len(), |params| {
3154 let period = get_usize_param("cci", params, "period", 14)?;
3155 let input = CciInput::from_slice(
3156 data,
3157 CciParams {
3158 period: Some(period),
3159 },
3160 );
3161 let out =
3162 cci_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3163 indicator: "cci".to_string(),
3164 details: e.to_string(),
3165 })?;
3166 Ok(out.values)
3167 })
3168}
3169
3170fn compute_cfo_batch(
3171 req: IndicatorBatchRequest<'_>,
3172 output_id: &str,
3173) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3174 expect_value_output("cfo", output_id)?;
3175 let data = extract_slice_input("cfo", req.data, "close")?;
3176 let kernel = req.kernel.to_non_batch();
3177 collect_f64("cfo", output_id, req.combos, data.len(), |params| {
3178 let period = get_usize_param("cfo", params, "period", 14)?;
3179 let scalar = get_f64_param("cfo", params, "scalar", 100.0)?;
3180 let input = CfoInput::from_slice(
3181 data,
3182 CfoParams {
3183 period: Some(period),
3184 scalar: Some(scalar),
3185 },
3186 );
3187 let out =
3188 cfo_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3189 indicator: "cfo".to_string(),
3190 details: e.to_string(),
3191 })?;
3192 Ok(out.values)
3193 })
3194}
3195
3196fn compute_cci_cycle_batch(
3197 req: IndicatorBatchRequest<'_>,
3198 output_id: &str,
3199) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3200 expect_value_output("cci_cycle", output_id)?;
3201 let data = extract_slice_input("cci_cycle", req.data, "close")?;
3202 let kernel = req.kernel.to_non_batch();
3203 collect_f64("cci_cycle", output_id, req.combos, data.len(), |params| {
3204 let length = get_usize_param("cci_cycle", params, "length", 10)?;
3205 let factor = get_f64_param("cci_cycle", params, "factor", 0.5)?;
3206 let input = CciCycleInput::from_slice(
3207 data,
3208 CciCycleParams {
3209 length: Some(length),
3210 factor: Some(factor),
3211 },
3212 );
3213 let out = cci_cycle_with_kernel(&input, kernel).map_err(|e| {
3214 IndicatorDispatchError::ComputeFailed {
3215 indicator: "cci_cycle".to_string(),
3216 details: e.to_string(),
3217 }
3218 })?;
3219 Ok(out.values)
3220 })
3221}
3222
3223fn compute_lrsi_batch(
3224 req: IndicatorBatchRequest<'_>,
3225 output_id: &str,
3226) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3227 expect_value_output("lrsi", output_id)?;
3228 let (high, low) = extract_high_low_input("lrsi", req.data)?;
3229 let kernel = req.kernel.to_non_batch();
3230 collect_f64("lrsi", output_id, req.combos, high.len(), |params| {
3231 let alpha = get_f64_param("lrsi", params, "alpha", 0.2)?;
3232 let input = LrsiInput::from_slices(high, low, LrsiParams { alpha: Some(alpha) });
3233 let out = lrsi_with_kernel(&input, kernel).map_err(|e| {
3234 IndicatorDispatchError::ComputeFailed {
3235 indicator: "lrsi".to_string(),
3236 details: e.to_string(),
3237 }
3238 })?;
3239 Ok(out.values)
3240 })
3241}
3242
3243fn compute_er_batch(
3244 req: IndicatorBatchRequest<'_>,
3245 output_id: &str,
3246) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3247 expect_value_output("er", output_id)?;
3248 let data = extract_slice_input("er", req.data, "close")?;
3249 let kernel = req.kernel.to_non_batch();
3250 collect_f64("er", output_id, req.combos, data.len(), |params| {
3251 let period = get_usize_param("er", params, "period", 5)?;
3252 let input = ErInput::from_slice(
3253 data,
3254 ErParams {
3255 period: Some(period),
3256 },
3257 );
3258 let out =
3259 er_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3260 indicator: "er".to_string(),
3261 details: e.to_string(),
3262 })?;
3263 Ok(out.values)
3264 })
3265}
3266
3267fn compute_kurtosis_batch(
3268 req: IndicatorBatchRequest<'_>,
3269 output_id: &str,
3270) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3271 expect_value_output("kurtosis", output_id)?;
3272 let data = extract_slice_input("kurtosis", req.data, "hl2")?;
3273 let kernel = req.kernel.to_non_batch();
3274 collect_f64("kurtosis", output_id, req.combos, data.len(), |params| {
3275 let period = get_usize_param("kurtosis", params, "period", 5)?;
3276 let input = KurtosisInput::from_slice(
3277 data,
3278 KurtosisParams {
3279 period: Some(period),
3280 },
3281 );
3282 let out = kurtosis_with_kernel(&input, kernel).map_err(|e| {
3283 IndicatorDispatchError::ComputeFailed {
3284 indicator: "kurtosis".to_string(),
3285 details: e.to_string(),
3286 }
3287 })?;
3288 Ok(out.values)
3289 })
3290}
3291
3292fn compute_natr_batch(
3293 req: IndicatorBatchRequest<'_>,
3294 output_id: &str,
3295) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3296 expect_value_output("natr", output_id)?;
3297 let (high, low, close) = extract_ohlc_input("natr", req.data)?;
3298 let kernel = req.kernel.to_non_batch();
3299 collect_f64("natr", output_id, req.combos, close.len(), |params| {
3300 let period = get_usize_param("natr", params, "period", 14)?;
3301 let input = NatrInput::from_slices(
3302 high,
3303 low,
3304 close,
3305 NatrParams {
3306 period: Some(period),
3307 },
3308 );
3309 let out = natr_with_kernel(&input, kernel).map_err(|e| {
3310 IndicatorDispatchError::ComputeFailed {
3311 indicator: "natr".to_string(),
3312 details: e.to_string(),
3313 }
3314 })?;
3315 Ok(out.values)
3316 })
3317}
3318
3319fn compute_mean_ad_batch(
3320 req: IndicatorBatchRequest<'_>,
3321 output_id: &str,
3322) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3323 expect_value_output("mean_ad", output_id)?;
3324 let data = extract_slice_input("mean_ad", req.data, "close")?;
3325 let kernel = req.kernel.to_non_batch();
3326 collect_f64("mean_ad", output_id, req.combos, data.len(), |params| {
3327 let period = get_usize_param("mean_ad", params, "period", 5)?;
3328 let input = MeanAdInput::from_slice(
3329 data,
3330 MeanAdParams {
3331 period: Some(period),
3332 },
3333 );
3334 let out = mean_ad_with_kernel(&input, kernel).map_err(|e| {
3335 IndicatorDispatchError::ComputeFailed {
3336 indicator: "mean_ad".to_string(),
3337 details: e.to_string(),
3338 }
3339 })?;
3340 Ok(out.values)
3341 })
3342}
3343
3344fn compute_medium_ad_batch(
3345 req: IndicatorBatchRequest<'_>,
3346 output_id: &str,
3347) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3348 expect_value_output("medium_ad", output_id)?;
3349 let data = extract_slice_input("medium_ad", req.data, "close")?;
3350 let kernel = req.kernel.to_non_batch();
3351 collect_f64("medium_ad", output_id, req.combos, data.len(), |params| {
3352 let period = get_usize_param("medium_ad", params, "period", 5)?;
3353 let input = MediumAdInput::from_slice(
3354 data,
3355 MediumAdParams {
3356 period: Some(period),
3357 },
3358 );
3359 let out = medium_ad_with_kernel(&input, kernel).map_err(|e| {
3360 IndicatorDispatchError::ComputeFailed {
3361 indicator: "medium_ad".to_string(),
3362 details: e.to_string(),
3363 }
3364 })?;
3365 Ok(out.values)
3366 })
3367}
3368
3369fn compute_deviation_batch(
3370 req: IndicatorBatchRequest<'_>,
3371 output_id: &str,
3372) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3373 expect_value_output("deviation", output_id)?;
3374 let data = extract_slice_input("deviation", req.data, "close")?;
3375 let kernel = req.kernel.to_non_batch();
3376 collect_f64("deviation", output_id, req.combos, data.len(), |params| {
3377 let period = get_usize_param("deviation", params, "period", 9)?;
3378 let devtype = get_usize_param("deviation", params, "devtype", 0)?;
3379 let input = DeviationInput::from_slice(
3380 data,
3381 DeviationParams {
3382 period: Some(period),
3383 devtype: Some(devtype),
3384 },
3385 );
3386 let out = deviation_with_kernel(&input, kernel).map_err(|e| {
3387 IndicatorDispatchError::ComputeFailed {
3388 indicator: "deviation".to_string(),
3389 details: e.to_string(),
3390 }
3391 })?;
3392 Ok(out.values)
3393 })
3394}
3395
3396fn compute_dpo_batch(
3397 req: IndicatorBatchRequest<'_>,
3398 output_id: &str,
3399) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3400 expect_value_output("dpo", output_id)?;
3401 let data = extract_slice_input("dpo", req.data, "close")?;
3402 let kernel = req.kernel.to_non_batch();
3403 collect_f64("dpo", output_id, req.combos, data.len(), |params| {
3404 let period = get_usize_param("dpo", params, "period", 5)?;
3405 let input = DpoInput::from_slice(
3406 data,
3407 DpoParams {
3408 period: Some(period),
3409 },
3410 );
3411 let out =
3412 dpo_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3413 indicator: "dpo".to_string(),
3414 details: e.to_string(),
3415 })?;
3416 Ok(out.values)
3417 })
3418}
3419
3420fn compute_pfe_batch(
3421 req: IndicatorBatchRequest<'_>,
3422 output_id: &str,
3423) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3424 expect_value_output("pfe", output_id)?;
3425 let data = extract_slice_input("pfe", req.data, "close")?;
3426 let kernel = req.kernel.to_non_batch();
3427 collect_f64("pfe", output_id, req.combos, data.len(), |params| {
3428 let period = get_usize_param("pfe", params, "period", 10)?;
3429 let smoothing = get_usize_param("pfe", params, "smoothing", 5)?;
3430 let input = PfeInput::from_slice(
3431 data,
3432 PfeParams {
3433 period: Some(period),
3434 smoothing: Some(smoothing),
3435 },
3436 );
3437 let out =
3438 pfe_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3439 indicator: "pfe".to_string(),
3440 details: e.to_string(),
3441 })?;
3442 Ok(out.values)
3443 })
3444}
3445
3446fn compute_qstick_batch(
3447 req: IndicatorBatchRequest<'_>,
3448 output_id: &str,
3449) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3450 expect_value_output("qstick", output_id)?;
3451 let (open, close) = match req.data {
3452 IndicatorDataRef::Candles { candles, .. } => {
3453 (candles.open.as_slice(), candles.close.as_slice())
3454 }
3455 IndicatorDataRef::Ohlc {
3456 open,
3457 high,
3458 low,
3459 close,
3460 } => {
3461 ensure_same_len_4("qstick", open.len(), high.len(), low.len(), close.len())?;
3462 (open, close)
3463 }
3464 IndicatorDataRef::Ohlcv {
3465 open,
3466 high,
3467 low,
3468 close,
3469 volume,
3470 } => {
3471 ensure_same_len_5(
3472 "qstick",
3473 open.len(),
3474 high.len(),
3475 low.len(),
3476 close.len(),
3477 volume.len(),
3478 )?;
3479 (open, close)
3480 }
3481 _ => {
3482 return Err(IndicatorDispatchError::MissingRequiredInput {
3483 indicator: "qstick".to_string(),
3484 input: IndicatorInputKind::Ohlc,
3485 })
3486 }
3487 };
3488 let kernel = req.kernel.to_non_batch();
3489 collect_f64("qstick", output_id, req.combos, close.len(), |params| {
3490 let period = get_usize_param("qstick", params, "period", 5)?;
3491 let input = QstickInput::from_slices(
3492 open,
3493 close,
3494 QstickParams {
3495 period: Some(period),
3496 },
3497 );
3498 let out = qstick_with_kernel(&input, kernel).map_err(|e| {
3499 IndicatorDispatchError::ComputeFailed {
3500 indicator: "qstick".to_string(),
3501 details: e.to_string(),
3502 }
3503 })?;
3504 Ok(out.values)
3505 })
3506}
3507
3508fn compute_ehlers_fm_demodulator_batch(
3509 req: IndicatorBatchRequest<'_>,
3510 output_id: &str,
3511) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3512 expect_value_output("ehlers_fm_demodulator", output_id)?;
3513 let (open, close) = match req.data {
3514 IndicatorDataRef::Candles { candles, .. } => {
3515 (candles.open.as_slice(), candles.close.as_slice())
3516 }
3517 IndicatorDataRef::Ohlc {
3518 open,
3519 high,
3520 low,
3521 close,
3522 } => {
3523 ensure_same_len_4(
3524 "ehlers_fm_demodulator",
3525 open.len(),
3526 high.len(),
3527 low.len(),
3528 close.len(),
3529 )?;
3530 (open, close)
3531 }
3532 IndicatorDataRef::Ohlcv {
3533 open,
3534 high,
3535 low,
3536 close,
3537 volume,
3538 } => {
3539 ensure_same_len_5(
3540 "ehlers_fm_demodulator",
3541 open.len(),
3542 high.len(),
3543 low.len(),
3544 close.len(),
3545 volume.len(),
3546 )?;
3547 (open, close)
3548 }
3549 _ => {
3550 return Err(IndicatorDispatchError::MissingRequiredInput {
3551 indicator: "ehlers_fm_demodulator".to_string(),
3552 input: IndicatorInputKind::Ohlc,
3553 })
3554 }
3555 };
3556 let kernel = req.kernel.to_non_batch();
3557 collect_f64(
3558 "ehlers_fm_demodulator",
3559 output_id,
3560 req.combos,
3561 close.len(),
3562 |params| {
3563 let period = get_usize_param("ehlers_fm_demodulator", params, "period", 30)?;
3564 let input = EhlersFmDemodulatorInput::from_slices(
3565 open,
3566 close,
3567 EhlersFmDemodulatorParams {
3568 period: Some(period),
3569 },
3570 );
3571 let out = ehlers_fm_demodulator_with_kernel(&input, kernel).map_err(|e| {
3572 IndicatorDispatchError::ComputeFailed {
3573 indicator: "ehlers_fm_demodulator".to_string(),
3574 details: e.to_string(),
3575 }
3576 })?;
3577 Ok(out.values)
3578 },
3579 )
3580}
3581
3582fn compute_reverse_rsi_batch(
3583 req: IndicatorBatchRequest<'_>,
3584 output_id: &str,
3585) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3586 expect_value_output("reverse_rsi", output_id)?;
3587 let data = extract_slice_input("reverse_rsi", req.data, "close")?;
3588 let kernel = req.kernel.to_non_batch();
3589 collect_f64("reverse_rsi", output_id, req.combos, data.len(), |params| {
3590 let rsi_length = get_usize_param("reverse_rsi", params, "rsi_length", 14)?;
3591 let rsi_level = get_f64_param("reverse_rsi", params, "rsi_level", 50.0)?;
3592 let input = ReverseRsiInput::from_slice(
3593 data,
3594 ReverseRsiParams {
3595 rsi_length: Some(rsi_length),
3596 rsi_level: Some(rsi_level),
3597 },
3598 );
3599 let out = reverse_rsi_with_kernel(&input, kernel).map_err(|e| {
3600 IndicatorDispatchError::ComputeFailed {
3601 indicator: "reverse_rsi".to_string(),
3602 details: e.to_string(),
3603 }
3604 })?;
3605 Ok(out.values)
3606 })
3607}
3608
3609fn compute_percentile_nearest_rank_batch(
3610 req: IndicatorBatchRequest<'_>,
3611 output_id: &str,
3612) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3613 expect_value_output("percentile_nearest_rank", output_id)?;
3614 let data = extract_slice_input("percentile_nearest_rank", req.data, "close")?;
3615 let kernel = req.kernel.to_non_batch();
3616 collect_f64(
3617 "percentile_nearest_rank",
3618 output_id,
3619 req.combos,
3620 data.len(),
3621 |params| {
3622 let length = get_usize_param("percentile_nearest_rank", params, "length", 15)?;
3623 let percentage = get_f64_param("percentile_nearest_rank", params, "percentage", 50.0)?;
3624 let input = PercentileNearestRankInput::from_slice(
3625 data,
3626 PercentileNearestRankParams {
3627 length: Some(length),
3628 percentage: Some(percentage),
3629 },
3630 );
3631 let out = percentile_nearest_rank_with_kernel(&input, kernel).map_err(|e| {
3632 IndicatorDispatchError::ComputeFailed {
3633 indicator: "percentile_nearest_rank".to_string(),
3634 details: e.to_string(),
3635 }
3636 })?;
3637 Ok(out.values)
3638 },
3639 )
3640}
3641
3642fn compute_obv_batch(
3643 req: IndicatorBatchRequest<'_>,
3644 output_id: &str,
3645) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3646 expect_value_output("obv", output_id)?;
3647 let (close, volume) = extract_close_volume_input("obv", req.data, "close")?;
3648 let kernel = req.kernel.to_non_batch();
3649 collect_f64("obv", output_id, req.combos, close.len(), |_params| {
3650 let input = ObvInput::from_slices(close, volume, ObvParams::default());
3651 let out =
3652 obv_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3653 indicator: "obv".to_string(),
3654 details: e.to_string(),
3655 })?;
3656 Ok(out.values)
3657 })
3658}
3659
3660fn compute_vpt_batch(
3661 req: IndicatorBatchRequest<'_>,
3662 output_id: &str,
3663) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3664 expect_value_output("vpt", output_id)?;
3665 let (close, volume) = extract_close_volume_input("vpt", req.data, "close")?;
3666 let kernel = req.kernel.to_non_batch();
3667 collect_f64("vpt", output_id, req.combos, close.len(), |_params| {
3668 let input = VptInput::from_slices(close, volume);
3669 let out =
3670 vpt_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3671 indicator: "vpt".to_string(),
3672 details: e.to_string(),
3673 })?;
3674 Ok(out.values)
3675 })
3676}
3677
3678fn compute_nvi_batch(
3679 req: IndicatorBatchRequest<'_>,
3680 output_id: &str,
3681) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3682 expect_value_output("nvi", output_id)?;
3683 let (close, volume) = extract_close_volume_input("nvi", req.data, "close")?;
3684 let kernel = req.kernel.to_non_batch();
3685 collect_f64("nvi", output_id, req.combos, close.len(), |_params| {
3686 let input = NviInput::from_slices(close, volume, NviParams::default());
3687 let out =
3688 nvi_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3689 indicator: "nvi".to_string(),
3690 details: e.to_string(),
3691 })?;
3692 Ok(out.values)
3693 })
3694}
3695
3696fn compute_pvi_batch(
3697 req: IndicatorBatchRequest<'_>,
3698 output_id: &str,
3699) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3700 expect_value_output("pvi", output_id)?;
3701 let (close, volume) = extract_close_volume_input("pvi", req.data, "close")?;
3702 let kernel = req.kernel.to_non_batch();
3703 collect_f64("pvi", output_id, req.combos, close.len(), |params| {
3704 let initial_value = get_f64_param("pvi", params, "initial_value", 1000.0)?;
3705 let input = PviInput::from_slices(
3706 close,
3707 volume,
3708 PviParams {
3709 initial_value: Some(initial_value),
3710 },
3711 );
3712 let out =
3713 pvi_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3714 indicator: "pvi".to_string(),
3715 details: e.to_string(),
3716 })?;
3717 Ok(out.values)
3718 })
3719}
3720
3721fn compute_wclprice_batch(
3722 req: IndicatorBatchRequest<'_>,
3723 output_id: &str,
3724) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3725 expect_value_output("wclprice", output_id)?;
3726 let (high, low, close) = extract_ohlc_input("wclprice", req.data)?;
3727 let kernel = req.kernel.to_non_batch();
3728 collect_f64("wclprice", output_id, req.combos, close.len(), |_params| {
3729 let input = WclpriceInput::from_slices(high, low, close);
3730 let out = wclprice_with_kernel(&input, kernel).map_err(|e| {
3731 IndicatorDispatchError::ComputeFailed {
3732 indicator: "wclprice".to_string(),
3733 details: e.to_string(),
3734 }
3735 })?;
3736 Ok(out.values)
3737 })
3738}
3739
3740fn compute_ui_batch(
3741 req: IndicatorBatchRequest<'_>,
3742 output_id: &str,
3743) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3744 expect_value_output("ui", output_id)?;
3745 let data = extract_slice_input("ui", req.data, "close")?;
3746 let kernel = req.kernel.to_non_batch();
3747 collect_f64("ui", output_id, req.combos, data.len(), |params| {
3748 let period = get_usize_param("ui", params, "period", 14)?;
3749 let scalar = get_f64_param("ui", params, "scalar", 100.0)?;
3750 let input = UiInput::from_slice(
3751 data,
3752 UiParams {
3753 period: Some(period),
3754 scalar: Some(scalar),
3755 },
3756 );
3757 let out =
3758 ui_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3759 indicator: "ui".to_string(),
3760 details: e.to_string(),
3761 })?;
3762 Ok(out.values)
3763 })
3764}
3765
3766fn compute_zscore_batch(
3767 req: IndicatorBatchRequest<'_>,
3768 output_id: &str,
3769) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3770 expect_value_output("zscore", output_id)?;
3771 let data = extract_slice_input("zscore", req.data, "close")?;
3772 let kernel = req.kernel.to_non_batch();
3773 collect_f64("zscore", output_id, req.combos, data.len(), |params| {
3774 let period = get_usize_param("zscore", params, "period", 14)?;
3775 let ma_type = get_enum_param("zscore", params, "ma_type", "sma")?;
3776 let nbdev = get_f64_param("zscore", params, "nbdev", 1.0)?;
3777 let devtype = get_usize_param("zscore", params, "devtype", 0)?;
3778 let input = ZscoreInput::from_slice(
3779 data,
3780 ZscoreParams {
3781 period: Some(period),
3782 ma_type: Some(ma_type),
3783 nbdev: Some(nbdev),
3784 devtype: Some(devtype),
3785 },
3786 );
3787 let out = zscore_with_kernel(&input, kernel).map_err(|e| {
3788 IndicatorDispatchError::ComputeFailed {
3789 indicator: "zscore".to_string(),
3790 details: e.to_string(),
3791 }
3792 })?;
3793 Ok(out.values)
3794 })
3795}
3796
3797fn compute_medprice_batch(
3798 req: IndicatorBatchRequest<'_>,
3799 output_id: &str,
3800) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3801 expect_value_output("medprice", output_id)?;
3802 let (high, low) = extract_high_low_input("medprice", req.data)?;
3803 let kernel = req.kernel.to_non_batch();
3804 collect_f64("medprice", output_id, req.combos, high.len(), |_params| {
3805 let input = MedpriceInput::from_slices(high, low, MedpriceParams::default());
3806 let out = medprice_with_kernel(&input, kernel).map_err(|e| {
3807 IndicatorDispatchError::ComputeFailed {
3808 indicator: "medprice".to_string(),
3809 details: e.to_string(),
3810 }
3811 })?;
3812 Ok(out.values)
3813 })
3814}
3815
3816fn compute_midpoint_batch(
3817 req: IndicatorBatchRequest<'_>,
3818 output_id: &str,
3819) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3820 expect_value_output("midpoint", output_id)?;
3821 let data = extract_slice_input("midpoint", req.data, "close")?;
3822 let kernel = req.kernel.to_non_batch();
3823 collect_f64("midpoint", output_id, req.combos, data.len(), |params| {
3824 let period = get_usize_param("midpoint", params, "period", 14)?;
3825 let input = MidpointInput::from_slice(
3826 data,
3827 MidpointParams {
3828 period: Some(period),
3829 },
3830 );
3831 let out = midpoint_with_kernel(&input, kernel).map_err(|e| {
3832 IndicatorDispatchError::ComputeFailed {
3833 indicator: "midpoint".to_string(),
3834 details: e.to_string(),
3835 }
3836 })?;
3837 Ok(out.values)
3838 })
3839}
3840
3841fn compute_midprice_batch(
3842 req: IndicatorBatchRequest<'_>,
3843 output_id: &str,
3844) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3845 expect_value_output("midprice", output_id)?;
3846 let (high, low) = extract_high_low_input("midprice", req.data)?;
3847 let kernel = req.kernel.to_non_batch();
3848 collect_f64("midprice", output_id, req.combos, high.len(), |params| {
3849 let period = get_usize_param("midprice", params, "period", 14)?;
3850 let input = MidpriceInput::from_slices(
3851 high,
3852 low,
3853 MidpriceParams {
3854 period: Some(period),
3855 },
3856 );
3857 let out = midprice_with_kernel(&input, kernel).map_err(|e| {
3858 IndicatorDispatchError::ComputeFailed {
3859 indicator: "midprice".to_string(),
3860 details: e.to_string(),
3861 }
3862 })?;
3863 Ok(out.values)
3864 })
3865}
3866
3867fn compute_mom_batch(
3868 req: IndicatorBatchRequest<'_>,
3869 output_id: &str,
3870) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3871 expect_value_output("mom", output_id)?;
3872 let data = extract_slice_input("mom", req.data, "close")?;
3873 let kernel = req.kernel.to_non_batch();
3874 collect_f64("mom", output_id, req.combos, data.len(), |params| {
3875 let period = get_usize_param("mom", params, "period", 10)?;
3876 let input = MomInput::from_slice(
3877 data,
3878 MomParams {
3879 period: Some(period),
3880 },
3881 );
3882 let out =
3883 mom_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3884 indicator: "mom".to_string(),
3885 details: e.to_string(),
3886 })?;
3887 Ok(out.values)
3888 })
3889}
3890
3891fn compute_velocity_batch(
3892 req: IndicatorBatchRequest<'_>,
3893 output_id: &str,
3894) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3895 expect_value_output("velocity", output_id)?;
3896 let data = extract_slice_input("velocity", req.data, "hlcc4")?;
3897 let kernel = req.kernel.to_non_batch();
3898 collect_f64("velocity", output_id, req.combos, data.len(), |params| {
3899 let length = get_usize_param("velocity", params, "length", 21)?;
3900 let smooth_length = get_usize_param("velocity", params, "smooth_length", 5)?;
3901 let input = VelocityInput::from_slice(
3902 data,
3903 VelocityParams {
3904 length: Some(length),
3905 smooth_length: Some(smooth_length),
3906 },
3907 );
3908 let out = velocity_with_kernel(&input, kernel).map_err(|e| {
3909 IndicatorDispatchError::ComputeFailed {
3910 indicator: "velocity".to_string(),
3911 details: e.to_string(),
3912 }
3913 })?;
3914 Ok(out.values)
3915 })
3916}
3917
3918fn compute_adaptive_momentum_oscillator_batch(
3919 req: IndicatorBatchRequest<'_>,
3920 output_id: &str,
3921) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3922 let data = extract_slice_input("adaptive_momentum_oscillator", req.data, "close")?;
3923 let kernel = req.kernel.to_non_batch();
3924 collect_f64(
3925 "adaptive_momentum_oscillator",
3926 output_id,
3927 req.combos,
3928 data.len(),
3929 |params| {
3930 let length = get_usize_param("adaptive_momentum_oscillator", params, "length", 14)?;
3931 let smoothing_length = get_usize_param(
3932 "adaptive_momentum_oscillator",
3933 params,
3934 "smoothing_length",
3935 9,
3936 )?;
3937 let input = AdaptiveMomentumOscillatorInput::from_slice(
3938 data,
3939 AdaptiveMomentumOscillatorParams {
3940 length: Some(length),
3941 smoothing_length: Some(smoothing_length),
3942 },
3943 );
3944 let out = adaptive_momentum_oscillator_with_kernel(&input, kernel).map_err(|e| {
3945 IndicatorDispatchError::ComputeFailed {
3946 indicator: "adaptive_momentum_oscillator".to_string(),
3947 details: e.to_string(),
3948 }
3949 })?;
3950 match output_id {
3951 "amo" | "value" => Ok(out.amo),
3952 "ama" => Ok(out.ama),
3953 other => Err(IndicatorDispatchError::UnknownOutput {
3954 indicator: "adaptive_momentum_oscillator".to_string(),
3955 output: other.to_string(),
3956 }),
3957 }
3958 },
3959 )
3960}
3961
3962fn compute_normalized_volume_true_range_batch(
3963 req: IndicatorBatchRequest<'_>,
3964 output_id: &str,
3965) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3966 let (open, high, low, close, volume) =
3967 extract_ohlcv_full_input("normalized_volume_true_range", req.data)?;
3968 let kernel = req.kernel.to_non_batch();
3969 collect_f64(
3970 "normalized_volume_true_range",
3971 output_id,
3972 req.combos,
3973 close.len(),
3974 |params| {
3975 let true_range_style = match find_param(params, "true_range_style") {
3976 Some(ParamValue::EnumString(value)) => Some(
3977 value
3978 .parse::<NormalizedVolumeTrueRangeStyle>()
3979 .map_err(|e| IndicatorDispatchError::InvalidParam {
3980 indicator: "normalized_volume_true_range".to_string(),
3981 key: "true_range_style".to_string(),
3982 reason: e,
3983 })?,
3984 ),
3985 Some(_) => {
3986 return Err(IndicatorDispatchError::InvalidParam {
3987 indicator: "normalized_volume_true_range".to_string(),
3988 key: "true_range_style".to_string(),
3989 reason: "expected enum string".to_string(),
3990 });
3991 }
3992 None => Some(NormalizedVolumeTrueRangeStyle::Body),
3993 };
3994 let outlier_range =
3995 get_f64_param("normalized_volume_true_range", params, "outlier_range", 5.0)?;
3996 let atr_length =
3997 get_usize_param("normalized_volume_true_range", params, "atr_length", 14)?;
3998 let volume_length =
3999 get_usize_param("normalized_volume_true_range", params, "volume_length", 14)?;
4000
4001 let input = NormalizedVolumeTrueRangeInput::from_slices(
4002 open,
4003 high,
4004 low,
4005 close,
4006 volume,
4007 NormalizedVolumeTrueRangeParams {
4008 true_range_style,
4009 outlier_range: Some(outlier_range),
4010 atr_length: Some(atr_length),
4011 volume_length: Some(volume_length),
4012 },
4013 );
4014 let out = normalized_volume_true_range_with_kernel(&input, kernel).map_err(|e| {
4015 IndicatorDispatchError::ComputeFailed {
4016 indicator: "normalized_volume_true_range".to_string(),
4017 details: e.to_string(),
4018 }
4019 })?;
4020 if output_id.eq_ignore_ascii_case("normalized_volume")
4021 || output_id.eq_ignore_ascii_case("value")
4022 {
4023 return Ok(out.normalized_volume);
4024 }
4025 if output_id.eq_ignore_ascii_case("normalized_true_range") {
4026 return Ok(out.normalized_true_range);
4027 }
4028 if output_id.eq_ignore_ascii_case("baseline") {
4029 return Ok(out.baseline);
4030 }
4031 if output_id.eq_ignore_ascii_case("atr") {
4032 return Ok(out.atr);
4033 }
4034 if output_id.eq_ignore_ascii_case("average_volume") {
4035 return Ok(out.average_volume);
4036 }
4037 Err(IndicatorDispatchError::UnknownOutput {
4038 indicator: "normalized_volume_true_range".to_string(),
4039 output: output_id.to_string(),
4040 })
4041 },
4042 )
4043}
4044
4045fn compute_range_breakout_signals_batch(
4046 req: IndicatorBatchRequest<'_>,
4047 output_id: &str,
4048) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4049 let (open, high, low, close, volume) =
4050 extract_ohlcv_full_input("range_breakout_signals", req.data)?;
4051 let kernel = req.kernel.to_non_batch();
4052 collect_f64(
4053 "range_breakout_signals",
4054 output_id,
4055 req.combos,
4056 close.len(),
4057 |params| {
4058 let range_length =
4059 get_usize_param("range_breakout_signals", params, "range_length", 20)?;
4060 let confirmation_length =
4061 get_usize_param("range_breakout_signals", params, "confirmation_length", 5)?;
4062 let input = RangeBreakoutSignalsInput::from_slices(
4063 open,
4064 high,
4065 low,
4066 close,
4067 volume,
4068 RangeBreakoutSignalsParams {
4069 range_length: Some(range_length),
4070 confirmation_length: Some(confirmation_length),
4071 },
4072 );
4073 let out = range_breakout_signals_with_kernel(&input, kernel).map_err(|e| {
4074 IndicatorDispatchError::ComputeFailed {
4075 indicator: "range_breakout_signals".to_string(),
4076 details: e.to_string(),
4077 }
4078 })?;
4079 if output_id.eq_ignore_ascii_case("range_top")
4080 || output_id.eq_ignore_ascii_case("value")
4081 {
4082 return Ok(out.range_top);
4083 }
4084 if output_id.eq_ignore_ascii_case("range_bottom") {
4085 return Ok(out.range_bottom);
4086 }
4087 if output_id.eq_ignore_ascii_case("bullish") {
4088 return Ok(out.bullish);
4089 }
4090 if output_id.eq_ignore_ascii_case("extra_bullish") {
4091 return Ok(out.extra_bullish);
4092 }
4093 if output_id.eq_ignore_ascii_case("bearish") {
4094 return Ok(out.bearish);
4095 }
4096 if output_id.eq_ignore_ascii_case("extra_bearish") {
4097 return Ok(out.extra_bearish);
4098 }
4099 Err(IndicatorDispatchError::UnknownOutput {
4100 indicator: "range_breakout_signals".to_string(),
4101 output: output_id.to_string(),
4102 })
4103 },
4104 )
4105}
4106
4107fn compute_exponential_trend_batch(
4108 req: IndicatorBatchRequest<'_>,
4109 output_id: &str,
4110) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4111 let (high, low, close) = extract_ohlc_input("exponential_trend", req.data)?;
4112 let kernel = req.kernel.to_non_batch();
4113 collect_f64(
4114 "exponential_trend",
4115 output_id,
4116 req.combos,
4117 close.len(),
4118 |params| {
4119 let exp_rate = get_f64_param("exponential_trend", params, "exp_rate", 0.00003)?;
4120 let initial_distance =
4121 get_f64_param("exponential_trend", params, "initial_distance", 4.0)?;
4122 let width_multiplier =
4123 get_f64_param("exponential_trend", params, "width_multiplier", 1.0)?;
4124 let input = ExponentialTrendInput::from_slices(
4125 high,
4126 low,
4127 close,
4128 ExponentialTrendParams {
4129 exp_rate: Some(exp_rate),
4130 initial_distance: Some(initial_distance),
4131 width_multiplier: Some(width_multiplier),
4132 },
4133 );
4134 let out = exponential_trend_with_kernel(&input, kernel).map_err(|e| {
4135 IndicatorDispatchError::ComputeFailed {
4136 indicator: "exponential_trend".to_string(),
4137 details: e.to_string(),
4138 }
4139 })?;
4140 if output_id.eq_ignore_ascii_case("uptrend_base")
4141 || output_id.eq_ignore_ascii_case("value")
4142 {
4143 return Ok(out.uptrend_base);
4144 }
4145 if output_id.eq_ignore_ascii_case("downtrend_base") {
4146 return Ok(out.downtrend_base);
4147 }
4148 if output_id.eq_ignore_ascii_case("uptrend_extension") {
4149 return Ok(out.uptrend_extension);
4150 }
4151 if output_id.eq_ignore_ascii_case("downtrend_extension") {
4152 return Ok(out.downtrend_extension);
4153 }
4154 if output_id.eq_ignore_ascii_case("bullish_change") {
4155 return Ok(out.bullish_change);
4156 }
4157 if output_id.eq_ignore_ascii_case("bearish_change") {
4158 return Ok(out.bearish_change);
4159 }
4160 Err(IndicatorDispatchError::UnknownOutput {
4161 indicator: "exponential_trend".to_string(),
4162 output: output_id.to_string(),
4163 })
4164 },
4165 )
4166}
4167
4168fn compute_trend_flow_trail_batch(
4169 req: IndicatorBatchRequest<'_>,
4170 output_id: &str,
4171) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4172 let (open, high, low, close, volume) = extract_ohlcv_full_input("trend_flow_trail", req.data)?;
4173 let kernel = req.kernel.to_non_batch();
4174 collect_f64(
4175 "trend_flow_trail",
4176 output_id,
4177 req.combos,
4178 close.len(),
4179 |params| {
4180 let alpha_length = get_usize_param("trend_flow_trail", params, "alpha_length", 33)?;
4181 let alpha_multiplier =
4182 get_f64_param("trend_flow_trail", params, "alpha_multiplier", 3.3)?;
4183 let mfi_length = get_usize_param("trend_flow_trail", params, "mfi_length", 14)?;
4184 let input = TrendFlowTrailInput::from_slices(
4185 open,
4186 high,
4187 low,
4188 close,
4189 volume,
4190 TrendFlowTrailParams {
4191 alpha_length: Some(alpha_length),
4192 alpha_multiplier: Some(alpha_multiplier),
4193 mfi_length: Some(mfi_length),
4194 },
4195 );
4196 let out = trend_flow_trail_with_kernel(&input, kernel).map_err(|e| {
4197 IndicatorDispatchError::ComputeFailed {
4198 indicator: "trend_flow_trail".to_string(),
4199 details: e.to_string(),
4200 }
4201 })?;
4202 if output_id.eq_ignore_ascii_case("alpha_trail")
4203 || output_id.eq_ignore_ascii_case("value")
4204 {
4205 return Ok(out.alpha_trail);
4206 }
4207 if output_id.eq_ignore_ascii_case("alpha_trail_bullish") {
4208 return Ok(out.alpha_trail_bullish);
4209 }
4210 if output_id.eq_ignore_ascii_case("alpha_trail_bearish") {
4211 return Ok(out.alpha_trail_bearish);
4212 }
4213 if output_id.eq_ignore_ascii_case("alpha_dir") {
4214 return Ok(out.alpha_dir);
4215 }
4216 if output_id.eq_ignore_ascii_case("mfi") {
4217 return Ok(out.mfi);
4218 }
4219 if output_id.eq_ignore_ascii_case("tp_upper") {
4220 return Ok(out.tp_upper);
4221 }
4222 if output_id.eq_ignore_ascii_case("tp_lower") {
4223 return Ok(out.tp_lower);
4224 }
4225 if output_id.eq_ignore_ascii_case("alpha_trail_bullish_switch") {
4226 return Ok(out.alpha_trail_bullish_switch);
4227 }
4228 if output_id.eq_ignore_ascii_case("alpha_trail_bearish_switch") {
4229 return Ok(out.alpha_trail_bearish_switch);
4230 }
4231 if output_id.eq_ignore_ascii_case("mfi_overbought") {
4232 return Ok(out.mfi_overbought);
4233 }
4234 if output_id.eq_ignore_ascii_case("mfi_oversold") {
4235 return Ok(out.mfi_oversold);
4236 }
4237 if output_id.eq_ignore_ascii_case("mfi_cross_up_mid") {
4238 return Ok(out.mfi_cross_up_mid);
4239 }
4240 if output_id.eq_ignore_ascii_case("mfi_cross_down_mid") {
4241 return Ok(out.mfi_cross_down_mid);
4242 }
4243 if output_id.eq_ignore_ascii_case("price_cross_alpha_trail_up") {
4244 return Ok(out.price_cross_alpha_trail_up);
4245 }
4246 if output_id.eq_ignore_ascii_case("price_cross_alpha_trail_down") {
4247 return Ok(out.price_cross_alpha_trail_down);
4248 }
4249 if output_id.eq_ignore_ascii_case("mfi_above_90") {
4250 return Ok(out.mfi_above_90);
4251 }
4252 if output_id.eq_ignore_ascii_case("mfi_below_10") {
4253 return Ok(out.mfi_below_10);
4254 }
4255 Err(IndicatorDispatchError::UnknownOutput {
4256 indicator: "trend_flow_trail".to_string(),
4257 output: output_id.to_string(),
4258 })
4259 },
4260 )
4261}
4262
4263fn compute_cmo_batch(
4264 req: IndicatorBatchRequest<'_>,
4265 output_id: &str,
4266) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4267 expect_value_output("cmo", output_id)?;
4268 let data = extract_slice_input("cmo", req.data, "close")?;
4269 let kernel = req.kernel.to_non_batch();
4270 collect_f64("cmo", output_id, req.combos, data.len(), |params| {
4271 let period = get_usize_param("cmo", params, "period", 14)?;
4272 let input = CmoInput::from_slice(
4273 data,
4274 CmoParams {
4275 period: Some(period),
4276 },
4277 );
4278 let out =
4279 cmo_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
4280 indicator: "cmo".to_string(),
4281 details: e.to_string(),
4282 })?;
4283 Ok(out.values)
4284 })
4285}
4286
4287fn compute_rocp_batch(
4288 req: IndicatorBatchRequest<'_>,
4289 output_id: &str,
4290) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4291 expect_value_output("rocp", output_id)?;
4292 let data = extract_slice_input("rocp", req.data, "close")?;
4293 let kernel = req.kernel.to_non_batch();
4294 collect_f64("rocp", output_id, req.combos, data.len(), |params| {
4295 let period = get_usize_param("rocp", params, "period", 10)?;
4296 let input = RocpInput::from_slice(
4297 data,
4298 RocpParams {
4299 period: Some(period),
4300 },
4301 );
4302 let out = rocp_with_kernel(&input, kernel).map_err(|e| {
4303 IndicatorDispatchError::ComputeFailed {
4304 indicator: "rocp".to_string(),
4305 details: e.to_string(),
4306 }
4307 })?;
4308 Ok(out.values)
4309 })
4310}
4311
4312fn compute_rocr_batch(
4313 req: IndicatorBatchRequest<'_>,
4314 output_id: &str,
4315) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4316 expect_value_output("rocr", output_id)?;
4317 let data = extract_slice_input("rocr", req.data, "close")?;
4318 let kernel = req.kernel.to_non_batch();
4319 collect_f64("rocr", output_id, req.combos, data.len(), |params| {
4320 let period = get_usize_param("rocr", params, "period", 10)?;
4321 let input = RocrInput::from_slice(
4322 data,
4323 RocrParams {
4324 period: Some(period),
4325 },
4326 );
4327 let out = rocr_with_kernel(&input, kernel).map_err(|e| {
4328 IndicatorDispatchError::ComputeFailed {
4329 indicator: "rocr".to_string(),
4330 details: e.to_string(),
4331 }
4332 })?;
4333 Ok(out.values)
4334 })
4335}
4336
4337fn compute_ppo_batch(
4338 req: IndicatorBatchRequest<'_>,
4339 output_id: &str,
4340) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4341 expect_value_output("ppo", output_id)?;
4342 let data = extract_slice_input("ppo", req.data, "close")?;
4343 let kernel = req.kernel.to_non_batch();
4344 collect_f64("ppo", output_id, req.combos, data.len(), |params| {
4345 let fast_period = get_usize_param("ppo", params, "fast_period", 12)?;
4346 let slow_period = get_usize_param("ppo", params, "slow_period", 26)?;
4347 let ma_type = get_enum_param("ppo", params, "ma_type", "sma")?;
4348 let input = PpoInput::from_slice(
4349 data,
4350 PpoParams {
4351 fast_period: Some(fast_period),
4352 slow_period: Some(slow_period),
4353 ma_type: Some(ma_type),
4354 },
4355 );
4356 let out =
4357 ppo_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
4358 indicator: "ppo".to_string(),
4359 details: e.to_string(),
4360 })?;
4361 Ok(out.values)
4362 })
4363}
4364
4365fn compute_trix_batch(
4366 req: IndicatorBatchRequest<'_>,
4367 output_id: &str,
4368) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4369 expect_value_output("trix", output_id)?;
4370 let data = extract_slice_input("trix", req.data, "close")?;
4371 let periods = combo_periods("trix", req.combos, "period", 18)?;
4372 if let Some((start, end, step)) = derive_period_sweep(&periods) {
4373 let out = trix_batch_with_kernel(
4374 data,
4375 &TrixBatchRange {
4376 period: (start, end, step),
4377 },
4378 to_batch_kernel(req.kernel),
4379 )
4380 .map_err(|e| IndicatorDispatchError::ComputeFailed {
4381 indicator: "trix".to_string(),
4382 details: e.to_string(),
4383 })?;
4384 ensure_len("trix", data.len(), out.cols)?;
4385 let produced_periods: Vec<usize> = out
4386 .combos
4387 .iter()
4388 .map(|combo| combo.period.unwrap_or(18))
4389 .collect();
4390 let values = reorder_or_take_f64_matrix_by_period(
4391 "trix",
4392 &periods,
4393 &produced_periods,
4394 out.cols,
4395 out.values,
4396 )?;
4397 return Ok(f64_output(output_id, periods.len(), out.cols, values));
4398 }
4399
4400 let kernel = req.kernel.to_non_batch();
4401 collect_f64_into_rows("trix", output_id, req.combos, data.len(), |params, row| {
4402 let period = get_usize_param("trix", params, "period", 18)?;
4403 let input = TrixInput::from_slice(
4404 data,
4405 TrixParams {
4406 period: Some(period),
4407 },
4408 );
4409 trix_into_slice(row, &input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
4410 indicator: "trix".to_string(),
4411 details: e.to_string(),
4412 })
4413 })
4414}
4415
4416fn compute_tsi_batch(
4417 req: IndicatorBatchRequest<'_>,
4418 output_id: &str,
4419) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4420 expect_value_output("tsi", output_id)?;
4421 let data = extract_slice_input("tsi", req.data, "close")?;
4422 let kernel = req.kernel.to_non_batch();
4423 collect_f64("tsi", output_id, req.combos, data.len(), |params| {
4424 let long_period = get_usize_param("tsi", params, "long_period", 25)?;
4425 let short_period = get_usize_param("tsi", params, "short_period", 13)?;
4426 let input = TsiInput::from_slice(
4427 data,
4428 TsiParams {
4429 long_period: Some(long_period),
4430 short_period: Some(short_period),
4431 },
4432 );
4433 let out =
4434 tsi_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
4435 indicator: "tsi".to_string(),
4436 details: e.to_string(),
4437 })?;
4438 Ok(out.values)
4439 })
4440}
4441
4442fn compute_tsf_batch(
4443 req: IndicatorBatchRequest<'_>,
4444 output_id: &str,
4445) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4446 expect_value_output("tsf", output_id)?;
4447 let data = extract_slice_input("tsf", req.data, "close")?;
4448 let kernel = req.kernel.to_non_batch();
4449 collect_f64("tsf", output_id, req.combos, data.len(), |params| {
4450 let period = get_usize_param("tsf", params, "period", 14)?;
4451 let input = TsfInput::from_slice(
4452 data,
4453 TsfParams {
4454 period: Some(period),
4455 },
4456 );
4457 let out =
4458 tsf_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
4459 indicator: "tsf".to_string(),
4460 details: e.to_string(),
4461 })?;
4462 Ok(out.values)
4463 })
4464}
4465
4466fn compute_polynomial_regression_extrapolation_batch(
4467 req: IndicatorBatchRequest<'_>,
4468 output_id: &str,
4469) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4470 expect_value_output("polynomial_regression_extrapolation", output_id)?;
4471 let data = extract_slice_input("polynomial_regression_extrapolation", req.data, "close")?;
4472 let kernel = req.kernel.to_non_batch();
4473 collect_f64(
4474 "polynomial_regression_extrapolation",
4475 output_id,
4476 req.combos,
4477 data.len(),
4478 |params| {
4479 let length =
4480 get_usize_param("polynomial_regression_extrapolation", params, "length", 100)?;
4481 let extrapolate = get_usize_param(
4482 "polynomial_regression_extrapolation",
4483 params,
4484 "extrapolate",
4485 10,
4486 )?;
4487 let degree =
4488 get_usize_param("polynomial_regression_extrapolation", params, "degree", 3)?;
4489 let input = PolynomialRegressionExtrapolationInput::from_slice(
4490 data,
4491 PolynomialRegressionExtrapolationParams {
4492 length: Some(length),
4493 extrapolate: Some(extrapolate),
4494 degree: Some(degree),
4495 },
4496 );
4497 let out =
4498 polynomial_regression_extrapolation_with_kernel(&input, kernel).map_err(|e| {
4499 IndicatorDispatchError::ComputeFailed {
4500 indicator: "polynomial_regression_extrapolation".to_string(),
4501 details: e.to_string(),
4502 }
4503 })?;
4504 Ok(out.values)
4505 },
4506 )
4507}
4508
4509fn compute_adaptive_macd_batch(
4510 req: IndicatorBatchRequest<'_>,
4511 output_id: &str,
4512) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4513 let data = extract_slice_input("adaptive_macd", req.data, "close")?;
4514 let kernel = req.kernel.to_non_batch();
4515 collect_f64(
4516 "adaptive_macd",
4517 output_id,
4518 req.combos,
4519 data.len(),
4520 |params| {
4521 let length = get_usize_param("adaptive_macd", params, "length", 20)?;
4522 let fast_period = get_usize_param("adaptive_macd", params, "fast_period", 10)?;
4523 let slow_period = get_usize_param("adaptive_macd", params, "slow_period", 20)?;
4524 let signal_period = get_usize_param("adaptive_macd", params, "signal_period", 9)?;
4525 let input = AdaptiveMacdInput::from_slice(
4526 data,
4527 AdaptiveMacdParams {
4528 length: Some(length),
4529 fast_period: Some(fast_period),
4530 slow_period: Some(slow_period),
4531 signal_period: Some(signal_period),
4532 },
4533 );
4534 let out = adaptive_macd_with_kernel(&input, kernel).map_err(|e| {
4535 IndicatorDispatchError::ComputeFailed {
4536 indicator: "adaptive_macd".to_string(),
4537 details: e.to_string(),
4538 }
4539 })?;
4540 if output_id.eq_ignore_ascii_case("macd") || output_id.eq_ignore_ascii_case("value") {
4541 return Ok(out.macd);
4542 }
4543 if output_id.eq_ignore_ascii_case("signal") {
4544 return Ok(out.signal);
4545 }
4546 if output_id.eq_ignore_ascii_case("hist") {
4547 return Ok(out.hist);
4548 }
4549 Err(IndicatorDispatchError::UnknownOutput {
4550 indicator: "adaptive_macd".to_string(),
4551 output: output_id.to_string(),
4552 })
4553 },
4554 )
4555}
4556
4557fn compute_statistical_trailing_stop_batch(
4558 req: IndicatorBatchRequest<'_>,
4559 output_id: &str,
4560) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4561 let (high, low, close) = extract_ohlc_input("statistical_trailing_stop", req.data)?;
4562 let kernel = req.kernel.to_non_batch();
4563 collect_f64(
4564 "statistical_trailing_stop",
4565 output_id,
4566 req.combos,
4567 close.len(),
4568 |params| {
4569 let data_length =
4570 get_usize_param("statistical_trailing_stop", params, "data_length", 10)?;
4571 let normalization_length = get_usize_param(
4572 "statistical_trailing_stop",
4573 params,
4574 "normalization_length",
4575 100,
4576 )?;
4577 let base_level =
4578 get_enum_param("statistical_trailing_stop", params, "base_level", "level2")?;
4579 let input = StatisticalTrailingStopInput::from_slices(
4580 high,
4581 low,
4582 close,
4583 StatisticalTrailingStopParams {
4584 data_length: Some(data_length),
4585 normalization_length: Some(normalization_length),
4586 base_level: Some(base_level),
4587 },
4588 );
4589 let out = statistical_trailing_stop_with_kernel(&input, kernel).map_err(|e| {
4590 IndicatorDispatchError::ComputeFailed {
4591 indicator: "statistical_trailing_stop".to_string(),
4592 details: e.to_string(),
4593 }
4594 })?;
4595 if output_id.eq_ignore_ascii_case("level") || output_id.eq_ignore_ascii_case("value") {
4596 return Ok(out.level);
4597 }
4598 if output_id.eq_ignore_ascii_case("anchor") {
4599 return Ok(out.anchor);
4600 }
4601 if output_id.eq_ignore_ascii_case("bias") {
4602 return Ok(out.bias);
4603 }
4604 if output_id.eq_ignore_ascii_case("changed") {
4605 return Ok(out.changed);
4606 }
4607 Err(IndicatorDispatchError::UnknownOutput {
4608 indicator: "statistical_trailing_stop".to_string(),
4609 output: output_id.to_string(),
4610 })
4611 },
4612 )
4613}
4614
4615fn compute_supertrend_recovery_batch(
4616 req: IndicatorBatchRequest<'_>,
4617 output_id: &str,
4618) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4619 let (high, low, close) = extract_ohlc_input("supertrend_recovery", req.data)?;
4620 let kernel = req.kernel.to_non_batch();
4621 collect_f64(
4622 "supertrend_recovery",
4623 output_id,
4624 req.combos,
4625 close.len(),
4626 |params| {
4627 let atr_length = get_usize_param("supertrend_recovery", params, "atr_length", 10)?;
4628 let multiplier = get_f64_param("supertrend_recovery", params, "multiplier", 3.0)?;
4629 let alpha_percent = get_f64_param("supertrend_recovery", params, "alpha_percent", 5.0)?;
4630 let threshold_atr = get_f64_param("supertrend_recovery", params, "threshold_atr", 1.0)?;
4631 let input = SuperTrendRecoveryInput::from_slices(
4632 high,
4633 low,
4634 close,
4635 SuperTrendRecoveryParams {
4636 atr_length: Some(atr_length),
4637 multiplier: Some(multiplier),
4638 alpha_percent: Some(alpha_percent),
4639 threshold_atr: Some(threshold_atr),
4640 },
4641 );
4642 let out = supertrend_recovery_with_kernel(&input, kernel).map_err(|e| {
4643 IndicatorDispatchError::ComputeFailed {
4644 indicator: "supertrend_recovery".to_string(),
4645 details: e.to_string(),
4646 }
4647 })?;
4648 if output_id.eq_ignore_ascii_case("band") || output_id.eq_ignore_ascii_case("value") {
4649 return Ok(out.band);
4650 }
4651 if output_id.eq_ignore_ascii_case("switch_price") {
4652 return Ok(out.switch_price);
4653 }
4654 if output_id.eq_ignore_ascii_case("trend") {
4655 return Ok(out.trend);
4656 }
4657 if output_id.eq_ignore_ascii_case("changed") {
4658 return Ok(out.changed);
4659 }
4660 Err(IndicatorDispatchError::UnknownOutput {
4661 indicator: "supertrend_recovery".to_string(),
4662 output: output_id.to_string(),
4663 })
4664 },
4665 )
4666}
4667
4668fn compute_standardized_psar_oscillator_batch(
4669 req: IndicatorBatchRequest<'_>,
4670 output_id: &str,
4671) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4672 let (high, low, close) = extract_ohlc_input("standardized_psar_oscillator", req.data)?;
4673 let kernel = req.kernel.to_non_batch();
4674 collect_f64(
4675 "standardized_psar_oscillator",
4676 output_id,
4677 req.combos,
4678 close.len(),
4679 |params| {
4680 let start = get_f64_param("standardized_psar_oscillator", params, "start", 0.02)?;
4681 let increment =
4682 get_f64_param("standardized_psar_oscillator", params, "increment", 0.0005)?;
4683 let maximum = get_f64_param("standardized_psar_oscillator", params, "maximum", 0.2)?;
4684 let standardization_length = get_usize_param(
4685 "standardized_psar_oscillator",
4686 params,
4687 "standardization_length",
4688 21,
4689 )?;
4690 let wma_length =
4691 get_usize_param("standardized_psar_oscillator", params, "wma_length", 40)?;
4692 let wma_lag = get_usize_param("standardized_psar_oscillator", params, "wma_lag", 3)?;
4693 let pivot_left =
4694 get_usize_param("standardized_psar_oscillator", params, "pivot_left", 15)?;
4695 let pivot_right =
4696 get_usize_param("standardized_psar_oscillator", params, "pivot_right", 1)?;
4697 let plot_bullish =
4698 get_bool_param("standardized_psar_oscillator", params, "plot_bullish", true)?;
4699 let plot_bearish =
4700 get_bool_param("standardized_psar_oscillator", params, "plot_bearish", true)?;
4701 let input = StandardizedPsarOscillatorInput::from_slices(
4702 high,
4703 low,
4704 close,
4705 StandardizedPsarOscillatorParams {
4706 start: Some(start),
4707 increment: Some(increment),
4708 maximum: Some(maximum),
4709 standardization_length: Some(standardization_length),
4710 wma_length: Some(wma_length),
4711 wma_lag: Some(wma_lag),
4712 pivot_left: Some(pivot_left),
4713 pivot_right: Some(pivot_right),
4714 plot_bullish: Some(plot_bullish),
4715 plot_bearish: Some(plot_bearish),
4716 },
4717 );
4718 let out = standardized_psar_oscillator_with_kernel(&input, kernel).map_err(|e| {
4719 IndicatorDispatchError::ComputeFailed {
4720 indicator: "standardized_psar_oscillator".to_string(),
4721 details: e.to_string(),
4722 }
4723 })?;
4724 match output_id {
4725 "oscillator" | "value" => Ok(out.oscillator),
4726 "ma" => Ok(out.ma),
4727 "bullish_reversal" => Ok(out.bullish_reversal),
4728 "bearish_reversal" => Ok(out.bearish_reversal),
4729 "regular_bullish" => Ok(out.regular_bullish),
4730 "regular_bearish" => Ok(out.regular_bearish),
4731 "bullish_weakening" => Ok(out.bullish_weakening),
4732 "bearish_weakening" => Ok(out.bearish_weakening),
4733 _ => Err(IndicatorDispatchError::UnknownOutput {
4734 indicator: "standardized_psar_oscillator".to_string(),
4735 output: output_id.to_string(),
4736 }),
4737 }
4738 },
4739 )
4740}
4741
4742fn compute_geometric_bias_oscillator_batch(
4743 req: IndicatorBatchRequest<'_>,
4744 output_id: &str,
4745) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4746 expect_value_output("geometric_bias_oscillator", output_id)?;
4747 let (high, low, close) = extract_ohlc_input("geometric_bias_oscillator", req.data)?;
4748 let kernel = req.kernel.to_non_batch();
4749 collect_f64(
4750 "geometric_bias_oscillator",
4751 output_id,
4752 req.combos,
4753 close.len(),
4754 |params| {
4755 let length = get_usize_param("geometric_bias_oscillator", params, "length", 100)?;
4756 let multiplier = get_f64_param("geometric_bias_oscillator", params, "multiplier", 2.0)?;
4757 let atr_length =
4758 get_usize_param("geometric_bias_oscillator", params, "atr_length", 14)?;
4759 let smooth = get_usize_param("geometric_bias_oscillator", params, "smooth", 1)?;
4760 let input = GeometricBiasOscillatorInput::from_slices(
4761 high,
4762 low,
4763 close,
4764 GeometricBiasOscillatorParams {
4765 length: Some(length),
4766 multiplier: Some(multiplier),
4767 atr_length: Some(atr_length),
4768 smooth: Some(smooth),
4769 },
4770 );
4771 let out = geometric_bias_oscillator_with_kernel(&input, kernel).map_err(|e| {
4772 IndicatorDispatchError::ComputeFailed {
4773 indicator: "geometric_bias_oscillator".to_string(),
4774 details: e.to_string(),
4775 }
4776 })?;
4777 Ok(out.values)
4778 },
4779 )
4780}
4781
4782fn compute_stddev_batch(
4783 req: IndicatorBatchRequest<'_>,
4784 output_id: &str,
4785) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4786 expect_value_output("stddev", output_id)?;
4787 let data = extract_slice_input("stddev", req.data, "close")?;
4788 let kernel = req.kernel.to_non_batch();
4789 collect_f64("stddev", output_id, req.combos, data.len(), |params| {
4790 let period = get_usize_param("stddev", params, "period", 5)?;
4791 let nbdev = get_f64_param("stddev", params, "nbdev", 1.0)?;
4792 let input = StdDevInput::from_slice(
4793 data,
4794 StdDevParams {
4795 period: Some(period),
4796 nbdev: Some(nbdev),
4797 },
4798 );
4799 let out = stddev_with_kernel(&input, kernel).map_err(|e| {
4800 IndicatorDispatchError::ComputeFailed {
4801 indicator: "stddev".to_string(),
4802 details: e.to_string(),
4803 }
4804 })?;
4805 Ok(out.values)
4806 })
4807}
4808
4809fn compute_vdubus_divergence_wave_pattern_generator_batch(
4810 req: IndicatorBatchRequest<'_>,
4811 output_id: &str,
4812) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4813 expect_value_output("vdubus_divergence_wave_pattern_generator", output_id)?;
4814 let (high, low, close) =
4815 extract_ohlc_input("vdubus_divergence_wave_pattern_generator", req.data)?;
4816 let kernel = req.kernel.to_non_batch();
4817 collect_f64(
4818 "vdubus_divergence_wave_pattern_generator",
4819 output_id,
4820 req.combos,
4821 close.len(),
4822 |params| {
4823 let fast_depth = get_usize_param(
4824 "vdubus_divergence_wave_pattern_generator",
4825 params,
4826 "fast_depth",
4827 9,
4828 )?;
4829 let slow_depth = get_usize_param(
4830 "vdubus_divergence_wave_pattern_generator",
4831 params,
4832 "slow_depth",
4833 24,
4834 )?;
4835 let fast_length = get_usize_param(
4836 "vdubus_divergence_wave_pattern_generator",
4837 params,
4838 "fast_length",
4839 21,
4840 )?;
4841 let slow_length = get_usize_param(
4842 "vdubus_divergence_wave_pattern_generator",
4843 params,
4844 "slow_length",
4845 34,
4846 )?;
4847 let signal_length = get_usize_param(
4848 "vdubus_divergence_wave_pattern_generator",
4849 params,
4850 "signal_length",
4851 5,
4852 )?;
4853 let lookback = get_usize_param(
4854 "vdubus_divergence_wave_pattern_generator",
4855 params,
4856 "lookback",
4857 3,
4858 )?;
4859 let err_tol = get_f64_param(
4860 "vdubus_divergence_wave_pattern_generator",
4861 params,
4862 "err_tol",
4863 0.15,
4864 )?;
4865 let show_standard = get_bool_param(
4866 "vdubus_divergence_wave_pattern_generator",
4867 params,
4868 "show_standard",
4869 true,
4870 )?;
4871 let show_climax = get_bool_param(
4872 "vdubus_divergence_wave_pattern_generator",
4873 params,
4874 "show_climax",
4875 true,
4876 )?;
4877 let show_rounded = get_bool_param(
4878 "vdubus_divergence_wave_pattern_generator",
4879 params,
4880 "show_rounded",
4881 true,
4882 )?;
4883 let show_predator = get_bool_param(
4884 "vdubus_divergence_wave_pattern_generator",
4885 params,
4886 "show_predator",
4887 true,
4888 )?;
4889 let show_gartley = get_bool_param(
4890 "vdubus_divergence_wave_pattern_generator",
4891 params,
4892 "show_gartley",
4893 false,
4894 )?;
4895 let show_bat = get_bool_param(
4896 "vdubus_divergence_wave_pattern_generator",
4897 params,
4898 "show_bat",
4899 false,
4900 )?;
4901 let show_butterfly = get_bool_param(
4902 "vdubus_divergence_wave_pattern_generator",
4903 params,
4904 "show_butterfly",
4905 false,
4906 )?;
4907 let show_crab = get_bool_param(
4908 "vdubus_divergence_wave_pattern_generator",
4909 params,
4910 "show_crab",
4911 false,
4912 )?;
4913 let show_deep = get_bool_param(
4914 "vdubus_divergence_wave_pattern_generator",
4915 params,
4916 "show_deep",
4917 false,
4918 )?;
4919 let show_hs = get_bool_param(
4920 "vdubus_divergence_wave_pattern_generator",
4921 params,
4922 "show_hs",
4923 true,
4924 )?;
4925 let input = VdubusDivergenceWavePatternGeneratorInput::from_slices(
4926 high,
4927 low,
4928 close,
4929 VdubusDivergenceWavePatternGeneratorParams {
4930 fast_depth: Some(fast_depth),
4931 slow_depth: Some(slow_depth),
4932 fast_length: Some(fast_length),
4933 slow_length: Some(slow_length),
4934 signal_length: Some(signal_length),
4935 lookback: Some(lookback),
4936 err_tol: Some(err_tol),
4937 show_standard: Some(show_standard),
4938 show_climax: Some(show_climax),
4939 show_rounded: Some(show_rounded),
4940 show_predator: Some(show_predator),
4941 show_gartley: Some(show_gartley),
4942 show_bat: Some(show_bat),
4943 show_butterfly: Some(show_butterfly),
4944 show_crab: Some(show_crab),
4945 show_deep: Some(show_deep),
4946 show_hs: Some(show_hs),
4947 },
4948 );
4949 let out = vdubus_divergence_wave_pattern_generator_with_kernel(&input, kernel)
4950 .map_err(|e| IndicatorDispatchError::ComputeFailed {
4951 indicator: "vdubus_divergence_wave_pattern_generator".to_string(),
4952 details: e.to_string(),
4953 })?;
4954 match output_id {
4955 "fast_standard" => Ok(out.fast_standard),
4956 "fast_climax" => Ok(out.fast_climax),
4957 "fast_rounded" => Ok(out.fast_rounded),
4958 "fast_predator" => Ok(out.fast_predator),
4959 "slow_standard" => Ok(out.slow_standard),
4960 "slow_climax" => Ok(out.slow_climax),
4961 "slow_rounded" => Ok(out.slow_rounded),
4962 "slow_predator" => Ok(out.slow_predator),
4963 "opposing_force" => Ok(out.opposing_force),
4964 "macd" => Ok(out.macd),
4965 "signal" => Ok(out.signal),
4966 "hist" => Ok(out.hist),
4967 _ => Err(IndicatorDispatchError::UnknownOutput {
4968 indicator: "vdubus_divergence_wave_pattern_generator".to_string(),
4969 output: output_id.to_string(),
4970 }),
4971 }
4972 },
4973 )
4974}
4975
4976fn compute_var_batch(
4977 req: IndicatorBatchRequest<'_>,
4978 output_id: &str,
4979) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4980 expect_value_output("var", output_id)?;
4981 let data = extract_slice_input("var", req.data, "close")?;
4982 let kernel = req.kernel.to_non_batch();
4983 collect_f64("var", output_id, req.combos, data.len(), |params| {
4984 let period = get_usize_param("var", params, "period", 14)?;
4985 let nbdev = get_f64_param("var", params, "nbdev", 1.0)?;
4986 let input = VarInput::from_slice(
4987 data,
4988 VarParams {
4989 period: Some(period),
4990 nbdev: Some(nbdev),
4991 },
4992 );
4993 let out =
4994 var_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
4995 indicator: "var".to_string(),
4996 details: e.to_string(),
4997 })?;
4998 Ok(out.values)
4999 })
5000}
5001
5002fn compute_willr_batch(
5003 req: IndicatorBatchRequest<'_>,
5004 output_id: &str,
5005) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5006 expect_value_output("willr", output_id)?;
5007 let (high, low, close) = extract_ohlc_input("willr", req.data)?;
5008 let kernel = req.kernel.to_non_batch();
5009 collect_f64("willr", output_id, req.combos, close.len(), |params| {
5010 let period = get_usize_param("willr", params, "period", 14)?;
5011 let input = WillrInput::from_slices(
5012 high,
5013 low,
5014 close,
5015 WillrParams {
5016 period: Some(period),
5017 },
5018 );
5019 let out = willr_with_kernel(&input, kernel).map_err(|e| {
5020 IndicatorDispatchError::ComputeFailed {
5021 indicator: "willr".to_string(),
5022 details: e.to_string(),
5023 }
5024 })?;
5025 Ok(out.values)
5026 })
5027}
5028
5029fn compute_ultosc_batch(
5030 req: IndicatorBatchRequest<'_>,
5031 output_id: &str,
5032) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5033 expect_value_output("ultosc", output_id)?;
5034 let (high, low, close) = extract_ohlc_input("ultosc", req.data)?;
5035 let kernel = req.kernel.to_non_batch();
5036 collect_f64("ultosc", output_id, req.combos, close.len(), |params| {
5037 let timeperiod1 = get_usize_param("ultosc", params, "timeperiod1", 7)?;
5038 let timeperiod2 = get_usize_param("ultosc", params, "timeperiod2", 14)?;
5039 let timeperiod3 = get_usize_param("ultosc", params, "timeperiod3", 28)?;
5040 let input = UltOscInput::from_slices(
5041 high,
5042 low,
5043 close,
5044 UltOscParams {
5045 timeperiod1: Some(timeperiod1),
5046 timeperiod2: Some(timeperiod2),
5047 timeperiod3: Some(timeperiod3),
5048 },
5049 );
5050 let out = ultosc_with_kernel(&input, kernel).map_err(|e| {
5051 IndicatorDispatchError::ComputeFailed {
5052 indicator: "ultosc".to_string(),
5053 details: e.to_string(),
5054 }
5055 })?;
5056 Ok(out.values)
5057 })
5058}
5059
5060fn compute_adx_batch(
5061 req: IndicatorBatchRequest<'_>,
5062 output_id: &str,
5063) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5064 expect_value_output("adx", output_id)?;
5065 let (high, low, close) = extract_ohlc_input("adx", req.data)?;
5066 let kernel = req.kernel.to_non_batch();
5067 collect_f64("adx", output_id, req.combos, close.len(), |params| {
5068 let period = get_usize_param("adx", params, "period", 14)?;
5069 let input = AdxInput::from_slices(
5070 high,
5071 low,
5072 close,
5073 AdxParams {
5074 period: Some(period),
5075 },
5076 );
5077 let out =
5078 adx_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
5079 indicator: "adx".to_string(),
5080 details: e.to_string(),
5081 })?;
5082 Ok(out.values)
5083 })
5084}
5085
5086fn compute_adxr_batch(
5087 req: IndicatorBatchRequest<'_>,
5088 output_id: &str,
5089) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5090 expect_value_output("adxr", output_id)?;
5091 let (high, low, close) = extract_ohlc_input("adxr", req.data)?;
5092 let kernel = req.kernel.to_non_batch();
5093 collect_f64("adxr", output_id, req.combos, close.len(), |params| {
5094 let period = get_usize_param("adxr", params, "period", 14)?;
5095 let input = AdxrInput::from_slices(
5096 high,
5097 low,
5098 close,
5099 AdxrParams {
5100 period: Some(period),
5101 },
5102 );
5103 let out = adxr_with_kernel(&input, kernel).map_err(|e| {
5104 IndicatorDispatchError::ComputeFailed {
5105 indicator: "adxr".to_string(),
5106 details: e.to_string(),
5107 }
5108 })?;
5109 Ok(out.values)
5110 })
5111}
5112
5113fn compute_atr_batch(
5114 req: IndicatorBatchRequest<'_>,
5115 output_id: &str,
5116) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5117 expect_value_output("atr", output_id)?;
5118 let (high, low, close) = extract_ohlc_input("atr", req.data)?;
5119 let kernel = req.kernel.to_non_batch();
5120 collect_f64("atr", output_id, req.combos, close.len(), |params| {
5121 let length = get_usize_param("atr", params, "length", 14)?;
5122 let input = AtrInput::from_slices(
5123 high,
5124 low,
5125 close,
5126 AtrParams {
5127 length: Some(length),
5128 },
5129 );
5130 let out =
5131 atr_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
5132 indicator: "atr".to_string(),
5133 details: e.to_string(),
5134 })?;
5135 Ok(out.values)
5136 })
5137}
5138
5139fn compute_macd_batch(
5140 req: IndicatorBatchRequest<'_>,
5141 output_id: &str,
5142) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5143 let data = extract_slice_input("macd", req.data, "close")?;
5144 let kernel = req.kernel.to_non_batch();
5145 collect_f64("macd", output_id, req.combos, data.len(), |params| {
5146 let fast_period = get_usize_param("macd", params, "fast_period", 12)?;
5147 let slow_period = get_usize_param("macd", params, "slow_period", 26)?;
5148 let signal_period = get_usize_param("macd", params, "signal_period", 9)?;
5149 let ma_type = get_enum_param("macd", params, "ma_type", "ema")?;
5150 let input = MacdInput::from_slice(
5151 data,
5152 MacdParams {
5153 fast_period: Some(fast_period),
5154 slow_period: Some(slow_period),
5155 signal_period: Some(signal_period),
5156 ma_type: Some(ma_type),
5157 },
5158 );
5159 let out = macd_with_kernel(&input, kernel).map_err(|e| {
5160 IndicatorDispatchError::ComputeFailed {
5161 indicator: "macd".to_string(),
5162 details: e.to_string(),
5163 }
5164 })?;
5165 if output_id.eq_ignore_ascii_case("macd") || output_id.eq_ignore_ascii_case("value") {
5166 return Ok(out.macd);
5167 }
5168 if output_id.eq_ignore_ascii_case("signal") {
5169 return Ok(out.signal);
5170 }
5171 if output_id.eq_ignore_ascii_case("hist") {
5172 return Ok(out.hist);
5173 }
5174 Err(IndicatorDispatchError::UnknownOutput {
5175 indicator: "macd".to_string(),
5176 output: output_id.to_string(),
5177 })
5178 })
5179}
5180
5181fn compute_bollinger_batch(
5182 req: IndicatorBatchRequest<'_>,
5183 output_id: &str,
5184) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5185 let data = extract_slice_input("bollinger_bands", req.data, "close")?;
5186 let kernel = req.kernel.to_non_batch();
5187 collect_f64(
5188 "bollinger_bands",
5189 output_id,
5190 req.combos,
5191 data.len(),
5192 |params| {
5193 let period = get_usize_param("bollinger_bands", params, "period", 20)?;
5194 let devup = get_f64_param("bollinger_bands", params, "devup", 2.0)?;
5195 let devdn = get_f64_param("bollinger_bands", params, "devdn", 2.0)?;
5196 let matype = get_enum_param("bollinger_bands", params, "matype", "sma")?;
5197 let devtype = get_usize_param("bollinger_bands", params, "devtype", 0)?;
5198 let input = BollingerBandsInput::from_slice(
5199 data,
5200 BollingerBandsParams {
5201 period: Some(period),
5202 devup: Some(devup),
5203 devdn: Some(devdn),
5204 matype: Some(matype),
5205 devtype: Some(devtype),
5206 },
5207 );
5208 let out = bollinger_bands_with_kernel(&input, kernel).map_err(|e| {
5209 IndicatorDispatchError::ComputeFailed {
5210 indicator: "bollinger_bands".to_string(),
5211 details: e.to_string(),
5212 }
5213 })?;
5214 if output_id.eq_ignore_ascii_case("upper") || output_id.eq_ignore_ascii_case("value") {
5215 return Ok(out.upper_band);
5216 }
5217 if output_id.eq_ignore_ascii_case("middle") {
5218 return Ok(out.middle_band);
5219 }
5220 if output_id.eq_ignore_ascii_case("lower") {
5221 return Ok(out.lower_band);
5222 }
5223 Err(IndicatorDispatchError::UnknownOutput {
5224 indicator: "bollinger_bands".to_string(),
5225 output: output_id.to_string(),
5226 })
5227 },
5228 )
5229}
5230
5231fn compute_bbw_batch(
5232 req: IndicatorBatchRequest<'_>,
5233 output_id: &str,
5234) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5235 let data = extract_slice_input("bollinger_bands_width", req.data, "close")?;
5236 let kernel = req.kernel.to_non_batch();
5237 collect_f64(
5238 "bollinger_bands_width",
5239 output_id,
5240 req.combos,
5241 data.len(),
5242 |params| {
5243 let period = get_usize_param("bollinger_bands_width", params, "period", 20)?;
5244 let devup = get_f64_param("bollinger_bands_width", params, "devup", 2.0)?;
5245 let devdn = get_f64_param("bollinger_bands_width", params, "devdn", 2.0)?;
5246 let matype = get_enum_param("bollinger_bands_width", params, "matype", "sma")?;
5247 let devtype = get_usize_param("bollinger_bands_width", params, "devtype", 0)?;
5248 let input = BollingerBandsWidthInput::from_slice(
5249 data,
5250 BollingerBandsWidthParams {
5251 period: Some(period),
5252 devup: Some(devup),
5253 devdn: Some(devdn),
5254 matype: Some(matype),
5255 devtype: Some(devtype),
5256 },
5257 );
5258 let out = bollinger_bands_width_with_kernel(&input, kernel).map_err(|e| {
5259 IndicatorDispatchError::ComputeFailed {
5260 indicator: "bollinger_bands_width".to_string(),
5261 details: e.to_string(),
5262 }
5263 })?;
5264 if output_id.eq_ignore_ascii_case("value") || output_id.eq_ignore_ascii_case("values") {
5265 return Ok(out.values);
5266 }
5267 Err(IndicatorDispatchError::UnknownOutput {
5268 indicator: "bollinger_bands_width".to_string(),
5269 output: output_id.to_string(),
5270 })
5271 },
5272 )
5273}
5274
5275fn compute_stoch_batch(
5276 req: IndicatorBatchRequest<'_>,
5277 output_id: &str,
5278) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5279 let (high, low, close) = extract_ohlc_input("stoch", req.data)?;
5280 let kernel = req.kernel.to_non_batch();
5281 collect_f64("stoch", output_id, req.combos, close.len(), |params| {
5282 let fastk_period = get_usize_param("stoch", params, "fastk_period", 14)?;
5283 let slowk_period = get_usize_param("stoch", params, "slowk_period", 3)?;
5284 let slowd_period = get_usize_param("stoch", params, "slowd_period", 3)?;
5285 let slowk_ma_type = get_enum_param("stoch", params, "slowk_ma_type", "sma")?;
5286 let slowd_ma_type = get_enum_param("stoch", params, "slowd_ma_type", "sma")?;
5287 let input = StochInput::from_slices(
5288 high,
5289 low,
5290 close,
5291 StochParams {
5292 fastk_period: Some(fastk_period),
5293 slowk_period: Some(slowk_period),
5294 slowk_ma_type: Some(slowk_ma_type),
5295 slowd_period: Some(slowd_period),
5296 slowd_ma_type: Some(slowd_ma_type),
5297 },
5298 );
5299 let out = stoch_with_kernel(&input, kernel).map_err(|e| {
5300 IndicatorDispatchError::ComputeFailed {
5301 indicator: "stoch".to_string(),
5302 details: e.to_string(),
5303 }
5304 })?;
5305 if output_id.eq_ignore_ascii_case("k") || output_id.eq_ignore_ascii_case("value") {
5306 return Ok(out.k);
5307 }
5308 if output_id.eq_ignore_ascii_case("d") {
5309 return Ok(out.d);
5310 }
5311 Err(IndicatorDispatchError::UnknownOutput {
5312 indicator: "stoch".to_string(),
5313 output: output_id.to_string(),
5314 })
5315 })
5316}
5317
5318fn compute_stochf_batch(
5319 req: IndicatorBatchRequest<'_>,
5320 output_id: &str,
5321) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5322 let (high, low, close) = extract_ohlc_input("stochf", req.data)?;
5323 let kernel = req.kernel.to_non_batch();
5324 collect_f64("stochf", output_id, req.combos, close.len(), |params| {
5325 let fastk_period = get_usize_param("stochf", params, "fastk_period", 5)?;
5326 let fastd_period = get_usize_param("stochf", params, "fastd_period", 3)?;
5327 let fastd_matype = get_usize_param("stochf", params, "fastd_matype", 0)?;
5328 let input = StochfInput::from_slices(
5329 high,
5330 low,
5331 close,
5332 StochfParams {
5333 fastk_period: Some(fastk_period),
5334 fastd_period: Some(fastd_period),
5335 fastd_matype: Some(fastd_matype),
5336 },
5337 );
5338 let out = stochf_with_kernel(&input, kernel).map_err(|e| {
5339 IndicatorDispatchError::ComputeFailed {
5340 indicator: "stochf".to_string(),
5341 details: e.to_string(),
5342 }
5343 })?;
5344 if output_id.eq_ignore_ascii_case("k") || output_id.eq_ignore_ascii_case("value") {
5345 return Ok(out.k);
5346 }
5347 if output_id.eq_ignore_ascii_case("d") {
5348 return Ok(out.d);
5349 }
5350 Err(IndicatorDispatchError::UnknownOutput {
5351 indicator: "stochf".to_string(),
5352 output: output_id.to_string(),
5353 })
5354 })
5355}
5356
5357fn compute_stochastic_money_flow_index_batch(
5358 req: IndicatorBatchRequest<'_>,
5359 output_id: &str,
5360) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5361 let (source, volume) =
5362 extract_close_volume_input("stochastic_money_flow_index", req.data, "close")?;
5363 let kernel = req.kernel.to_non_batch();
5364 collect_f64(
5365 "stochastic_money_flow_index",
5366 output_id,
5367 req.combos,
5368 source.len(),
5369 |params| {
5370 let stoch_k_length =
5371 get_usize_param("stochastic_money_flow_index", params, "stoch_k_length", 14)?;
5372 let stoch_k_smooth =
5373 get_usize_param("stochastic_money_flow_index", params, "stoch_k_smooth", 3)?;
5374 let stoch_d_smooth =
5375 get_usize_param("stochastic_money_flow_index", params, "stoch_d_smooth", 3)?;
5376 let mfi_length =
5377 get_usize_param("stochastic_money_flow_index", params, "mfi_length", 14)?;
5378 let input = StochasticMoneyFlowIndexInput::from_slices(
5379 source,
5380 volume,
5381 StochasticMoneyFlowIndexParams {
5382 stoch_k_length: Some(stoch_k_length),
5383 stoch_k_smooth: Some(stoch_k_smooth),
5384 stoch_d_smooth: Some(stoch_d_smooth),
5385 mfi_length: Some(mfi_length),
5386 },
5387 );
5388 let out = stochastic_money_flow_index_with_kernel(&input, kernel).map_err(|e| {
5389 IndicatorDispatchError::ComputeFailed {
5390 indicator: "stochastic_money_flow_index".to_string(),
5391 details: e.to_string(),
5392 }
5393 })?;
5394 if output_id.eq_ignore_ascii_case("k") || output_id.eq_ignore_ascii_case("value") {
5395 return Ok(out.k);
5396 }
5397 if output_id.eq_ignore_ascii_case("d") {
5398 return Ok(out.d);
5399 }
5400 Err(IndicatorDispatchError::UnknownOutput {
5401 indicator: "stochastic_money_flow_index".to_string(),
5402 output: output_id.to_string(),
5403 })
5404 },
5405 )
5406}
5407
5408fn compute_vwmacd_batch(
5409 req: IndicatorBatchRequest<'_>,
5410 output_id: &str,
5411) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5412 let (close, volume) = extract_close_volume_input("vwmacd", req.data, "close")?;
5413 let kernel = req.kernel.to_non_batch();
5414 collect_f64("vwmacd", output_id, req.combos, close.len(), |params| {
5415 let fast_period =
5416 get_usize_param_with_aliases("vwmacd", params, &["fast", "fast_period"], 12)?;
5417 let slow_period =
5418 get_usize_param_with_aliases("vwmacd", params, &["slow", "slow_period"], 26)?;
5419 let signal_period =
5420 get_usize_param_with_aliases("vwmacd", params, &["signal", "signal_period"], 9)?;
5421 let fast_ma_type = get_enum_param("vwmacd", params, "fast_ma_type", "sma")?;
5422 let slow_ma_type = get_enum_param("vwmacd", params, "slow_ma_type", "sma")?;
5423 let signal_ma_type = get_enum_param("vwmacd", params, "signal_ma_type", "ema")?;
5424 let input = VwmacdInput::from_slices(
5425 close,
5426 volume,
5427 VwmacdParams {
5428 fast_period: Some(fast_period),
5429 slow_period: Some(slow_period),
5430 signal_period: Some(signal_period),
5431 fast_ma_type: Some(fast_ma_type),
5432 slow_ma_type: Some(slow_ma_type),
5433 signal_ma_type: Some(signal_ma_type),
5434 },
5435 );
5436 let out = vwmacd_with_kernel(&input, kernel).map_err(|e| {
5437 IndicatorDispatchError::ComputeFailed {
5438 indicator: "vwmacd".to_string(),
5439 details: e.to_string(),
5440 }
5441 })?;
5442 if output_id.eq_ignore_ascii_case("macd") || output_id.eq_ignore_ascii_case("value") {
5443 return Ok(out.macd);
5444 }
5445 if output_id.eq_ignore_ascii_case("signal") {
5446 return Ok(out.signal);
5447 }
5448 if output_id.eq_ignore_ascii_case("hist") {
5449 return Ok(out.hist);
5450 }
5451 Err(IndicatorDispatchError::UnknownOutput {
5452 indicator: "vwmacd".to_string(),
5453 output: output_id.to_string(),
5454 })
5455 })
5456}
5457
5458fn compute_vpci_batch(
5459 req: IndicatorBatchRequest<'_>,
5460 output_id: &str,
5461) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5462 let (close, volume) = extract_close_volume_input("vpci", req.data, "close")?;
5463 let kernel = req.kernel.to_non_batch();
5464 collect_f64("vpci", output_id, req.combos, close.len(), |params| {
5465 let short_range = get_usize_param("vpci", params, "short_range", 5)?;
5466 let long_range = get_usize_param("vpci", params, "long_range", 25)?;
5467 let input = VpciInput::from_slices(
5468 close,
5469 volume,
5470 VpciParams {
5471 short_range: Some(short_range),
5472 long_range: Some(long_range),
5473 },
5474 );
5475 let out = vpci_with_kernel(&input, kernel).map_err(|e| {
5476 IndicatorDispatchError::ComputeFailed {
5477 indicator: "vpci".to_string(),
5478 details: e.to_string(),
5479 }
5480 })?;
5481 if output_id.eq_ignore_ascii_case("vpci") || output_id.eq_ignore_ascii_case("value") {
5482 return Ok(out.vpci);
5483 }
5484 if output_id.eq_ignore_ascii_case("vpcis") {
5485 return Ok(out.vpcis);
5486 }
5487 Err(IndicatorDispatchError::UnknownOutput {
5488 indicator: "vpci".to_string(),
5489 output: output_id.to_string(),
5490 })
5491 })
5492}
5493
5494fn compute_ttm_trend_batch(
5495 req: IndicatorBatchRequest<'_>,
5496 output_id: &str,
5497) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5498 expect_value_output("ttm_trend", output_id)?;
5499 let mut derived_source: Option<Vec<f64>> = None;
5500 let (source, close): (&[f64], &[f64]) = match req.data {
5501 IndicatorDataRef::Candles { candles, source } => (
5502 source_type(candles, source.unwrap_or("hl2")),
5503 candles.close.as_slice(),
5504 ),
5505 IndicatorDataRef::Ohlc {
5506 high, low, close, ..
5507 } => {
5508 ensure_same_len_3("ttm_trend", high.len(), low.len(), close.len())?;
5509 derived_source = Some(high.iter().zip(low).map(|(h, l)| 0.5 * (h + l)).collect());
5510 (derived_source.as_deref().unwrap_or(close), close)
5511 }
5512 IndicatorDataRef::Ohlcv {
5513 high, low, close, ..
5514 } => {
5515 ensure_same_len_3("ttm_trend", high.len(), low.len(), close.len())?;
5516 derived_source = Some(high.iter().zip(low).map(|(h, l)| 0.5 * (h + l)).collect());
5517 (derived_source.as_deref().unwrap_or(close), close)
5518 }
5519 _ => {
5520 return Err(IndicatorDispatchError::MissingRequiredInput {
5521 indicator: "ttm_trend".to_string(),
5522 input: IndicatorInputKind::Ohlc,
5523 })
5524 }
5525 };
5526 let kernel = req.kernel.to_non_batch();
5527 collect_bool("ttm_trend", output_id, req.combos, close.len(), |params| {
5528 let period = get_usize_param("ttm_trend", params, "period", 5)?;
5529 let input = TtmTrendInput::from_slices(
5530 source,
5531 close,
5532 TtmTrendParams {
5533 period: Some(period),
5534 },
5535 );
5536 let out = ttm_trend_with_kernel(&input, kernel).map_err(|e| {
5537 IndicatorDispatchError::ComputeFailed {
5538 indicator: "ttm_trend".to_string(),
5539 details: e.to_string(),
5540 }
5541 })?;
5542 Ok(out.values)
5543 })
5544}
5545
5546fn compute_ttm_squeeze_batch(
5547 req: IndicatorBatchRequest<'_>,
5548 output_id: &str,
5549) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5550 let (high, low, close) = extract_ohlc_input("ttm_squeeze", req.data)?;
5551 let kernel = req.kernel.to_non_batch();
5552 collect_f64(
5553 "ttm_squeeze",
5554 output_id,
5555 req.combos,
5556 close.len(),
5557 |params| {
5558 let length = get_usize_param("ttm_squeeze", params, "length", 20)?;
5559 let bb_mult = get_f64_param("ttm_squeeze", params, "bb_mult", 2.0)?;
5560 let kc_mult_high = get_f64_param_with_aliases(
5561 "ttm_squeeze",
5562 params,
5563 &["kc_high", "kc_mult_high"],
5564 1.0,
5565 )?;
5566 let kc_mult_mid =
5567 get_f64_param_with_aliases("ttm_squeeze", params, &["kc_mid", "kc_mult_mid"], 1.5)?;
5568 let kc_mult_low =
5569 get_f64_param_with_aliases("ttm_squeeze", params, &["kc_low", "kc_mult_low"], 2.0)?;
5570 let input = TtmSqueezeInput::from_slices(
5571 high,
5572 low,
5573 close,
5574 TtmSqueezeParams {
5575 length: Some(length),
5576 bb_mult: Some(bb_mult),
5577 kc_mult_high: Some(kc_mult_high),
5578 kc_mult_mid: Some(kc_mult_mid),
5579 kc_mult_low: Some(kc_mult_low),
5580 },
5581 );
5582 let out = ttm_squeeze_with_kernel(&input, kernel).map_err(|e| {
5583 IndicatorDispatchError::ComputeFailed {
5584 indicator: "ttm_squeeze".to_string(),
5585 details: e.to_string(),
5586 }
5587 })?;
5588 if output_id.eq_ignore_ascii_case("momentum") || output_id.eq_ignore_ascii_case("value")
5589 {
5590 return Ok(out.momentum);
5591 }
5592 if output_id.eq_ignore_ascii_case("squeeze") {
5593 return Ok(out.squeeze);
5594 }
5595 Err(IndicatorDispatchError::UnknownOutput {
5596 indicator: "ttm_squeeze".to_string(),
5597 output: output_id.to_string(),
5598 })
5599 },
5600 )
5601}
5602
5603fn compute_aroon_batch(
5604 req: IndicatorBatchRequest<'_>,
5605 output_id: &str,
5606) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5607 let (high, low) = extract_high_low_input("aroon", req.data)?;
5608 let kernel = req.kernel.to_non_batch();
5609 collect_f64("aroon", output_id, req.combos, high.len(), |params| {
5610 let length = get_usize_param("aroon", params, "length", 14)?;
5611 let input = AroonInput::from_slices_hl(
5612 high,
5613 low,
5614 AroonParams {
5615 length: Some(length),
5616 },
5617 );
5618 let out = aroon_with_kernel(&input, kernel).map_err(|e| {
5619 IndicatorDispatchError::ComputeFailed {
5620 indicator: "aroon".to_string(),
5621 details: e.to_string(),
5622 }
5623 })?;
5624 if output_id.eq_ignore_ascii_case("up")
5625 || output_id.eq_ignore_ascii_case("aroon_up")
5626 || output_id.eq_ignore_ascii_case("value")
5627 {
5628 return Ok(out.aroon_up);
5629 }
5630 if output_id.eq_ignore_ascii_case("down") || output_id.eq_ignore_ascii_case("aroon_down") {
5631 return Ok(out.aroon_down);
5632 }
5633 Err(IndicatorDispatchError::UnknownOutput {
5634 indicator: "aroon".to_string(),
5635 output: output_id.to_string(),
5636 })
5637 })
5638}
5639
5640fn compute_aroonosc_batch(
5641 req: IndicatorBatchRequest<'_>,
5642 output_id: &str,
5643) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5644 let (high, low) = extract_high_low_input("aroonosc", req.data)?;
5645 let kernel = req.kernel.to_non_batch();
5646 collect_f64("aroonosc", output_id, req.combos, high.len(), |params| {
5647 let length = get_usize_param("aroonosc", params, "length", 14)?;
5648 let input = AroonOscInput::from_slices_hl(
5649 high,
5650 low,
5651 AroonOscParams {
5652 length: Some(length),
5653 },
5654 );
5655 let out = aroon_osc_with_kernel(&input, kernel).map_err(|e| {
5656 IndicatorDispatchError::ComputeFailed {
5657 indicator: "aroonosc".to_string(),
5658 details: e.to_string(),
5659 }
5660 })?;
5661 if output_id.eq_ignore_ascii_case("value") {
5662 return Ok(out.values);
5663 }
5664 Err(IndicatorDispatchError::UnknownOutput {
5665 indicator: "aroonosc".to_string(),
5666 output: output_id.to_string(),
5667 })
5668 })
5669}
5670
5671fn compute_di_batch(
5672 req: IndicatorBatchRequest<'_>,
5673 output_id: &str,
5674) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5675 let (high, low, close) = extract_ohlc_input("di", req.data)?;
5676 let kernel = req.kernel.to_non_batch();
5677 collect_f64("di", output_id, req.combos, close.len(), |params| {
5678 let period = get_usize_param("di", params, "period", 14)?;
5679 let input = DiInput::from_slices(
5680 high,
5681 low,
5682 close,
5683 DiParams {
5684 period: Some(period),
5685 },
5686 );
5687 let out =
5688 di_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
5689 indicator: "di".to_string(),
5690 details: e.to_string(),
5691 })?;
5692 if output_id.eq_ignore_ascii_case("plus") || output_id.eq_ignore_ascii_case("value") {
5693 return Ok(out.plus);
5694 }
5695 if output_id.eq_ignore_ascii_case("minus") {
5696 return Ok(out.minus);
5697 }
5698 Err(IndicatorDispatchError::UnknownOutput {
5699 indicator: "di".to_string(),
5700 output: output_id.to_string(),
5701 })
5702 })
5703}
5704
5705fn compute_dm_batch(
5706 req: IndicatorBatchRequest<'_>,
5707 output_id: &str,
5708) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5709 let (high, low) = extract_high_low_input("dm", req.data)?;
5710 let kernel = req.kernel.to_non_batch();
5711 collect_f64("dm", output_id, req.combos, high.len(), |params| {
5712 let period = get_usize_param("dm", params, "period", 14)?;
5713 let input = DmInput::from_slices(
5714 high,
5715 low,
5716 DmParams {
5717 period: Some(period),
5718 },
5719 );
5720 let out =
5721 dm_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
5722 indicator: "dm".to_string(),
5723 details: e.to_string(),
5724 })?;
5725 if output_id.eq_ignore_ascii_case("plus") || output_id.eq_ignore_ascii_case("value") {
5726 return Ok(out.plus);
5727 }
5728 if output_id.eq_ignore_ascii_case("minus") {
5729 return Ok(out.minus);
5730 }
5731 Err(IndicatorDispatchError::UnknownOutput {
5732 indicator: "dm".to_string(),
5733 output: output_id.to_string(),
5734 })
5735 })
5736}
5737
5738fn compute_dti_batch(
5739 req: IndicatorBatchRequest<'_>,
5740 output_id: &str,
5741) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5742 expect_value_output("dti", output_id)?;
5743 let (high, low) = extract_high_low_input("dti", req.data)?;
5744 let kernel = req.kernel.to_non_batch();
5745 collect_f64_into_rows("dti", output_id, req.combos, high.len(), |params, row| {
5746 let r = get_usize_param("dti", params, "r", 14)?;
5747 let s = get_usize_param("dti", params, "s", 10)?;
5748 let u = get_usize_param("dti", params, "u", 5)?;
5749 let input = DtiInput::from_slices(
5750 high,
5751 low,
5752 DtiParams {
5753 r: Some(r),
5754 s: Some(s),
5755 u: Some(u),
5756 },
5757 );
5758 dti_into_slice(row, &input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
5759 indicator: "dti".to_string(),
5760 details: e.to_string(),
5761 })
5762 })
5763}
5764
5765fn compute_donchian_batch(
5766 req: IndicatorBatchRequest<'_>,
5767 output_id: &str,
5768) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5769 let (high, low) = extract_high_low_input("donchian", req.data)?;
5770 let kernel = req.kernel.to_non_batch();
5771 collect_f64("donchian", output_id, req.combos, high.len(), |params| {
5772 let period = get_usize_param("donchian", params, "period", 20)?;
5773 let input = DonchianInput::from_slices(
5774 high,
5775 low,
5776 DonchianParams {
5777 period: Some(period),
5778 },
5779 );
5780 let out = donchian_with_kernel(&input, kernel).map_err(|e| {
5781 IndicatorDispatchError::ComputeFailed {
5782 indicator: "donchian".to_string(),
5783 details: e.to_string(),
5784 }
5785 })?;
5786 if output_id.eq_ignore_ascii_case("upper") || output_id.eq_ignore_ascii_case("value") {
5787 return Ok(out.upperband);
5788 }
5789 if output_id.eq_ignore_ascii_case("middle") {
5790 return Ok(out.middleband);
5791 }
5792 if output_id.eq_ignore_ascii_case("lower") {
5793 return Ok(out.lowerband);
5794 }
5795 Err(IndicatorDispatchError::UnknownOutput {
5796 indicator: "donchian".to_string(),
5797 output: output_id.to_string(),
5798 })
5799 })
5800}
5801
5802fn compute_kdj_batch(
5803 req: IndicatorBatchRequest<'_>,
5804 output_id: &str,
5805) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5806 let (high, low, close) = extract_ohlc_input("kdj", req.data)?;
5807 let kernel = req.kernel.to_non_batch();
5808 collect_f64("kdj", output_id, req.combos, close.len(), |params| {
5809 let fast_k_period = get_usize_param("kdj", params, "fast_k_period", 9)?;
5810 let slow_k_period = get_usize_param("kdj", params, "slow_k_period", 3)?;
5811 let slow_k_ma_type = get_enum_param("kdj", params, "slow_k_ma_type", "sma")?;
5812 let slow_d_period = get_usize_param("kdj", params, "slow_d_period", 3)?;
5813 let slow_d_ma_type = get_enum_param("kdj", params, "slow_d_ma_type", "sma")?;
5814 let input = KdjInput::from_slices(
5815 high,
5816 low,
5817 close,
5818 KdjParams {
5819 fast_k_period: Some(fast_k_period),
5820 slow_k_period: Some(slow_k_period),
5821 slow_k_ma_type: Some(slow_k_ma_type),
5822 slow_d_period: Some(slow_d_period),
5823 slow_d_ma_type: Some(slow_d_ma_type),
5824 },
5825 );
5826 let out =
5827 kdj_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
5828 indicator: "kdj".to_string(),
5829 details: e.to_string(),
5830 })?;
5831 if output_id.eq_ignore_ascii_case("k") || output_id.eq_ignore_ascii_case("value") {
5832 return Ok(out.k);
5833 }
5834 if output_id.eq_ignore_ascii_case("d") {
5835 return Ok(out.d);
5836 }
5837 if output_id.eq_ignore_ascii_case("j") {
5838 return Ok(out.j);
5839 }
5840 Err(IndicatorDispatchError::UnknownOutput {
5841 indicator: "kdj".to_string(),
5842 output: output_id.to_string(),
5843 })
5844 })
5845}
5846
5847fn compute_keltner_batch(
5848 req: IndicatorBatchRequest<'_>,
5849 output_id: &str,
5850) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5851 let (high, low, close) = extract_ohlc_input("keltner", req.data)?;
5852 let kernel = req.kernel.to_non_batch();
5853 collect_f64("keltner", output_id, req.combos, close.len(), |params| {
5854 let period = get_usize_param("keltner", params, "period", 20)?;
5855 let multiplier = get_f64_param("keltner", params, "multiplier", 2.0)?;
5856 let ma_type = get_enum_param("keltner", params, "ma_type", "ema")?;
5857 let input = KeltnerInput::from_slice(
5858 high,
5859 low,
5860 close,
5861 close,
5862 KeltnerParams {
5863 period: Some(period),
5864 multiplier: Some(multiplier),
5865 ma_type: Some(ma_type),
5866 },
5867 );
5868 let out = keltner_with_kernel(&input, kernel).map_err(|e| {
5869 IndicatorDispatchError::ComputeFailed {
5870 indicator: "keltner".to_string(),
5871 details: e.to_string(),
5872 }
5873 })?;
5874 if output_id.eq_ignore_ascii_case("upper") || output_id.eq_ignore_ascii_case("value") {
5875 return Ok(out.upper_band);
5876 }
5877 if output_id.eq_ignore_ascii_case("middle") {
5878 return Ok(out.middle_band);
5879 }
5880 if output_id.eq_ignore_ascii_case("lower") {
5881 return Ok(out.lower_band);
5882 }
5883 Err(IndicatorDispatchError::UnknownOutput {
5884 indicator: "keltner".to_string(),
5885 output: output_id.to_string(),
5886 })
5887 })
5888}
5889
5890fn compute_squeeze_momentum_batch(
5891 req: IndicatorBatchRequest<'_>,
5892 output_id: &str,
5893) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5894 let (high, low, close) = extract_ohlc_input("squeeze_momentum", req.data)?;
5895 let kernel = req.kernel.to_non_batch();
5896 collect_f64(
5897 "squeeze_momentum",
5898 output_id,
5899 req.combos,
5900 close.len(),
5901 |params| {
5902 let length_bb = get_usize_param("squeeze_momentum", params, "length_bb", 20)?;
5903 let mult_bb = get_f64_param("squeeze_momentum", params, "mult_bb", 2.0)?;
5904 let length_kc = get_usize_param("squeeze_momentum", params, "length_kc", 20)?;
5905 let mult_kc = get_f64_param("squeeze_momentum", params, "mult_kc", 1.5)?;
5906 let input = SqueezeMomentumInput::from_slices(
5907 high,
5908 low,
5909 close,
5910 SqueezeMomentumParams {
5911 length_bb: Some(length_bb),
5912 mult_bb: Some(mult_bb),
5913 length_kc: Some(length_kc),
5914 mult_kc: Some(mult_kc),
5915 },
5916 );
5917 let out = squeeze_momentum_with_kernel(&input, kernel).map_err(|e| {
5918 IndicatorDispatchError::ComputeFailed {
5919 indicator: "squeeze_momentum".to_string(),
5920 details: e.to_string(),
5921 }
5922 })?;
5923 if output_id.eq_ignore_ascii_case("momentum") || output_id.eq_ignore_ascii_case("value")
5924 {
5925 return Ok(out.momentum);
5926 }
5927 if output_id.eq_ignore_ascii_case("squeeze") {
5928 return Ok(out.squeeze);
5929 }
5930 if output_id.eq_ignore_ascii_case("signal")
5931 || output_id.eq_ignore_ascii_case("momentum_signal")
5932 {
5933 return Ok(out.momentum_signal);
5934 }
5935 Err(IndicatorDispatchError::UnknownOutput {
5936 indicator: "squeeze_momentum".to_string(),
5937 output: output_id.to_string(),
5938 })
5939 },
5940 )
5941}
5942
5943fn compute_srsi_batch(
5944 req: IndicatorBatchRequest<'_>,
5945 output_id: &str,
5946) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5947 let data = extract_slice_input("srsi", req.data, "close")?;
5948 let kernel = req.kernel.to_non_batch();
5949 collect_f64("srsi", output_id, req.combos, data.len(), |params| {
5950 let rsi_period = get_usize_param("srsi", params, "rsi_period", 14)?;
5951 let stoch_period = get_usize_param("srsi", params, "stoch_period", 14)?;
5952 let k = get_usize_param("srsi", params, "k", 3)?;
5953 let d = get_usize_param("srsi", params, "d", 3)?;
5954 let source = get_enum_param("srsi", params, "source", "close")?;
5955 let input = SrsiInput::from_slice(
5956 data,
5957 SrsiParams {
5958 rsi_period: Some(rsi_period),
5959 stoch_period: Some(stoch_period),
5960 k: Some(k),
5961 d: Some(d),
5962 source: Some(source),
5963 },
5964 );
5965 let out = srsi_with_kernel(&input, kernel).map_err(|e| {
5966 IndicatorDispatchError::ComputeFailed {
5967 indicator: "srsi".to_string(),
5968 details: e.to_string(),
5969 }
5970 })?;
5971 if output_id.eq_ignore_ascii_case("k") || output_id.eq_ignore_ascii_case("value") {
5972 return Ok(out.k);
5973 }
5974 if output_id.eq_ignore_ascii_case("d") {
5975 return Ok(out.d);
5976 }
5977 Err(IndicatorDispatchError::UnknownOutput {
5978 indicator: "srsi".to_string(),
5979 output: output_id.to_string(),
5980 })
5981 })
5982}
5983
5984fn compute_supertrend_batch(
5985 req: IndicatorBatchRequest<'_>,
5986 output_id: &str,
5987) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5988 let (high, low, close) = extract_ohlc_input("supertrend", req.data)?;
5989 let kernel = req.kernel.to_non_batch();
5990 collect_f64("supertrend", output_id, req.combos, close.len(), |params| {
5991 let period = get_usize_param("supertrend", params, "period", 10)?;
5992 let factor = get_f64_param("supertrend", params, "factor", 3.0)?;
5993 let input = SuperTrendInput::from_slices(
5994 high,
5995 low,
5996 close,
5997 SuperTrendParams {
5998 period: Some(period),
5999 factor: Some(factor),
6000 },
6001 );
6002 let out = supertrend_with_kernel(&input, kernel).map_err(|e| {
6003 IndicatorDispatchError::ComputeFailed {
6004 indicator: "supertrend".to_string(),
6005 details: e.to_string(),
6006 }
6007 })?;
6008 if output_id.eq_ignore_ascii_case("trend") || output_id.eq_ignore_ascii_case("value") {
6009 return Ok(out.trend);
6010 }
6011 if output_id.eq_ignore_ascii_case("changed") {
6012 return Ok(out.changed);
6013 }
6014 Err(IndicatorDispatchError::UnknownOutput {
6015 indicator: "supertrend".to_string(),
6016 output: output_id.to_string(),
6017 })
6018 })
6019}
6020
6021fn compute_adjustable_ma_alternating_extremities_batch(
6022 req: IndicatorBatchRequest<'_>,
6023 output_id: &str,
6024) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6025 let (high, low, close) = extract_ohlc_input("adjustable_ma_alternating_extremities", req.data)?;
6026 let kernel = req.kernel.to_non_batch();
6027 collect_f64(
6028 "adjustable_ma_alternating_extremities",
6029 output_id,
6030 req.combos,
6031 close.len(),
6032 |params| {
6033 let length = get_usize_param(
6034 "adjustable_ma_alternating_extremities",
6035 params,
6036 "length",
6037 50,
6038 )?;
6039 let mult = get_f64_param("adjustable_ma_alternating_extremities", params, "mult", 2.0)?;
6040 let alpha = get_f64_param(
6041 "adjustable_ma_alternating_extremities",
6042 params,
6043 "alpha",
6044 1.0,
6045 )?;
6046 let beta = get_f64_param("adjustable_ma_alternating_extremities", params, "beta", 0.5)?;
6047 let input = AdjustableMaAlternatingExtremitiesInput::from_slices(
6048 high,
6049 low,
6050 close,
6051 AdjustableMaAlternatingExtremitiesParams {
6052 length: Some(length),
6053 mult: Some(mult),
6054 alpha: Some(alpha),
6055 beta: Some(beta),
6056 },
6057 );
6058 let out =
6059 adjustable_ma_alternating_extremities_with_kernel(&input, kernel).map_err(|e| {
6060 IndicatorDispatchError::ComputeFailed {
6061 indicator: "adjustable_ma_alternating_extremities".to_string(),
6062 details: e.to_string(),
6063 }
6064 })?;
6065 if output_id.eq_ignore_ascii_case("ma") || output_id.eq_ignore_ascii_case("value") {
6066 return Ok(out.ma);
6067 }
6068 if output_id.eq_ignore_ascii_case("upper") {
6069 return Ok(out.upper);
6070 }
6071 if output_id.eq_ignore_ascii_case("lower") {
6072 return Ok(out.lower);
6073 }
6074 if output_id.eq_ignore_ascii_case("extremity") {
6075 return Ok(out.extremity);
6076 }
6077 if output_id.eq_ignore_ascii_case("state") {
6078 return Ok(out.state);
6079 }
6080 if output_id.eq_ignore_ascii_case("changed") {
6081 return Ok(out.changed);
6082 }
6083 if output_id.eq_ignore_ascii_case("smoothed_open") {
6084 return Ok(out.smoothed_open);
6085 }
6086 if output_id.eq_ignore_ascii_case("smoothed_high") {
6087 return Ok(out.smoothed_high);
6088 }
6089 if output_id.eq_ignore_ascii_case("smoothed_low") {
6090 return Ok(out.smoothed_low);
6091 }
6092 if output_id.eq_ignore_ascii_case("smoothed_close") {
6093 return Ok(out.smoothed_close);
6094 }
6095 Err(IndicatorDispatchError::UnknownOutput {
6096 indicator: "adjustable_ma_alternating_extremities".to_string(),
6097 output: output_id.to_string(),
6098 })
6099 },
6100 )
6101}
6102
6103fn compute_vi_batch(
6104 req: IndicatorBatchRequest<'_>,
6105 output_id: &str,
6106) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6107 let (high, low, close) = extract_ohlc_input("vi", req.data)?;
6108 let kernel = req.kernel.to_non_batch();
6109 collect_f64("vi", output_id, req.combos, close.len(), |params| {
6110 let period = get_usize_param("vi", params, "period", 14)?;
6111 let input = ViInput::from_slices(
6112 high,
6113 low,
6114 close,
6115 ViParams {
6116 period: Some(period),
6117 },
6118 );
6119 let out =
6120 vi_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
6121 indicator: "vi".to_string(),
6122 details: e.to_string(),
6123 })?;
6124 if output_id.eq_ignore_ascii_case("plus") || output_id.eq_ignore_ascii_case("value") {
6125 return Ok(out.plus);
6126 }
6127 if output_id.eq_ignore_ascii_case("minus") {
6128 return Ok(out.minus);
6129 }
6130 Err(IndicatorDispatchError::UnknownOutput {
6131 indicator: "vi".to_string(),
6132 output: output_id.to_string(),
6133 })
6134 })
6135}
6136
6137fn compute_wavetrend_batch(
6138 req: IndicatorBatchRequest<'_>,
6139 output_id: &str,
6140) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6141 let data = extract_slice_input("wavetrend", req.data, "hlc3")?;
6142 let kernel = req.kernel.to_non_batch();
6143 collect_f64("wavetrend", output_id, req.combos, data.len(), |params| {
6144 let channel_length = get_usize_param("wavetrend", params, "channel_length", 9)?;
6145 let average_length = get_usize_param("wavetrend", params, "average_length", 12)?;
6146 let ma_length = get_usize_param("wavetrend", params, "ma_length", 3)?;
6147 let factor = get_f64_param("wavetrend", params, "factor", 0.015)?;
6148 let input = WavetrendInput::from_slice(
6149 data,
6150 WavetrendParams {
6151 channel_length: Some(channel_length),
6152 average_length: Some(average_length),
6153 ma_length: Some(ma_length),
6154 factor: Some(factor),
6155 },
6156 );
6157 let out = wavetrend_with_kernel(&input, kernel).map_err(|e| {
6158 IndicatorDispatchError::ComputeFailed {
6159 indicator: "wavetrend".to_string(),
6160 details: e.to_string(),
6161 }
6162 })?;
6163 if output_id.eq_ignore_ascii_case("wt1") || output_id.eq_ignore_ascii_case("value") {
6164 return Ok(out.wt1);
6165 }
6166 if output_id.eq_ignore_ascii_case("wt2") {
6167 return Ok(out.wt2);
6168 }
6169 if output_id.eq_ignore_ascii_case("wt_diff") {
6170 return Ok(out.wt_diff);
6171 }
6172 Err(IndicatorDispatchError::UnknownOutput {
6173 indicator: "wavetrend".to_string(),
6174 output: output_id.to_string(),
6175 })
6176 })
6177}
6178
6179fn compute_wto_batch(
6180 req: IndicatorBatchRequest<'_>,
6181 output_id: &str,
6182) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6183 let data = extract_slice_input("wto", req.data, "close")?;
6184 let kernel = req.kernel.to_non_batch();
6185 collect_f64("wto", output_id, req.combos, data.len(), |params| {
6186 let channel_length = get_usize_param("wto", params, "channel_length", 10)?;
6187 let average_length = get_usize_param("wto", params, "average_length", 21)?;
6188 let input = WtoInput::from_slice(
6189 data,
6190 WtoParams {
6191 channel_length: Some(channel_length),
6192 average_length: Some(average_length),
6193 },
6194 );
6195 let out =
6196 wto_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
6197 indicator: "wto".to_string(),
6198 details: e.to_string(),
6199 })?;
6200 if output_id.eq_ignore_ascii_case("wavetrend1")
6201 || output_id.eq_ignore_ascii_case("wt1")
6202 || output_id.eq_ignore_ascii_case("value")
6203 {
6204 return Ok(out.wavetrend1);
6205 }
6206 if output_id.eq_ignore_ascii_case("wavetrend2") || output_id.eq_ignore_ascii_case("wt2") {
6207 return Ok(out.wavetrend2);
6208 }
6209 if output_id.eq_ignore_ascii_case("histogram") || output_id.eq_ignore_ascii_case("hist") {
6210 return Ok(out.histogram);
6211 }
6212 Err(IndicatorDispatchError::UnknownOutput {
6213 indicator: "wto".to_string(),
6214 output: output_id.to_string(),
6215 })
6216 })
6217}
6218
6219fn compute_rogers_satchell_volatility_batch(
6220 req: IndicatorBatchRequest<'_>,
6221 output_id: &str,
6222) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6223 let (open, high, low, close) = extract_ohlc_full_input("rogers_satchell_volatility", req.data)?;
6224 let kernel = req.kernel.to_non_batch();
6225 collect_f64(
6226 "rogers_satchell_volatility",
6227 output_id,
6228 req.combos,
6229 close.len(),
6230 |params| {
6231 let lookback = get_usize_param("rogers_satchell_volatility", params, "lookback", 8)?;
6232 let signal_length =
6233 get_usize_param("rogers_satchell_volatility", params, "signal_length", 8)?;
6234 let input = RogersSatchellVolatilityInput::from_slices(
6235 open,
6236 high,
6237 low,
6238 close,
6239 RogersSatchellVolatilityParams {
6240 lookback: Some(lookback),
6241 signal_length: Some(signal_length),
6242 },
6243 );
6244 let out = rogers_satchell_volatility_with_kernel(&input, kernel).map_err(|e| {
6245 IndicatorDispatchError::ComputeFailed {
6246 indicator: "rogers_satchell_volatility".to_string(),
6247 details: e.to_string(),
6248 }
6249 })?;
6250 if output_id.eq_ignore_ascii_case("rs") || output_id.eq_ignore_ascii_case("value") {
6251 return Ok(out.rs);
6252 }
6253 if output_id.eq_ignore_ascii_case("signal") {
6254 return Ok(out.signal);
6255 }
6256 Err(IndicatorDispatchError::UnknownOutput {
6257 indicator: "rogers_satchell_volatility".to_string(),
6258 output: output_id.to_string(),
6259 })
6260 },
6261 )
6262}
6263
6264fn compute_historical_volatility_rank_batch(
6265 req: IndicatorBatchRequest<'_>,
6266 output_id: &str,
6267) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6268 let data = extract_slice_input("historical_volatility_rank", req.data, "close")?;
6269 let kernel = req.kernel.to_non_batch();
6270 collect_f64(
6271 "historical_volatility_rank",
6272 output_id,
6273 req.combos,
6274 data.len(),
6275 |params| {
6276 let hv_length = get_usize_param("historical_volatility_rank", params, "hv_length", 10)?;
6277 let rank_length =
6278 get_usize_param("historical_volatility_rank", params, "rank_length", 52 * 7)?;
6279 let annualization_days = get_f64_param(
6280 "historical_volatility_rank",
6281 params,
6282 "annualization_days",
6283 365.0,
6284 )?;
6285 let bar_days = get_f64_param("historical_volatility_rank", params, "bar_days", 1.0)?;
6286 let input = HistoricalVolatilityRankInput::from_slice(
6287 data,
6288 HistoricalVolatilityRankParams {
6289 hv_length: Some(hv_length),
6290 rank_length: Some(rank_length),
6291 annualization_days: Some(annualization_days),
6292 bar_days: Some(bar_days),
6293 },
6294 );
6295 let out = historical_volatility_rank_with_kernel(&input, kernel).map_err(|e| {
6296 IndicatorDispatchError::ComputeFailed {
6297 indicator: "historical_volatility_rank".to_string(),
6298 details: e.to_string(),
6299 }
6300 })?;
6301 if output_id.eq_ignore_ascii_case("hvr") || output_id.eq_ignore_ascii_case("value") {
6302 return Ok(out.hvr);
6303 }
6304 if output_id.eq_ignore_ascii_case("hv") {
6305 return Ok(out.hv);
6306 }
6307 Err(IndicatorDispatchError::UnknownOutput {
6308 indicator: "historical_volatility_rank".to_string(),
6309 output: output_id.to_string(),
6310 })
6311 },
6312 )
6313}
6314
6315fn compute_dual_ulcer_index_batch(
6316 req: IndicatorBatchRequest<'_>,
6317 output_id: &str,
6318) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6319 let data = extract_slice_input("dual_ulcer_index", req.data, "close")?;
6320 let kernel = req.kernel.to_non_batch();
6321 collect_f64(
6322 "dual_ulcer_index",
6323 output_id,
6324 req.combos,
6325 data.len(),
6326 |params| {
6327 let period = get_usize_param("dual_ulcer_index", params, "period", 5)?;
6328 let auto_threshold =
6329 get_bool_param("dual_ulcer_index", params, "auto_threshold", true)?;
6330 let threshold = get_f64_param("dual_ulcer_index", params, "threshold", 0.1)?;
6331 let input = DualUlcerIndexInput::from_slice(
6332 data,
6333 DualUlcerIndexParams {
6334 period: Some(period),
6335 auto_threshold: Some(auto_threshold),
6336 threshold: Some(threshold),
6337 },
6338 );
6339 let out = dual_ulcer_index_with_kernel(&input, kernel).map_err(|e| {
6340 IndicatorDispatchError::ComputeFailed {
6341 indicator: "dual_ulcer_index".to_string(),
6342 details: e.to_string(),
6343 }
6344 })?;
6345 if output_id.eq_ignore_ascii_case("long_ulcer")
6346 || output_id.eq_ignore_ascii_case("uulcer")
6347 || output_id.eq_ignore_ascii_case("value")
6348 {
6349 return Ok(out.long_ulcer);
6350 }
6351 if output_id.eq_ignore_ascii_case("short_ulcer")
6352 || output_id.eq_ignore_ascii_case("dulcer")
6353 {
6354 return Ok(out.short_ulcer);
6355 }
6356 if output_id.eq_ignore_ascii_case("threshold") {
6357 return Ok(out.threshold);
6358 }
6359 Err(IndicatorDispatchError::UnknownOutput {
6360 indicator: "dual_ulcer_index".to_string(),
6361 output: output_id.to_string(),
6362 })
6363 },
6364 )
6365}
6366
6367fn compute_fractal_dimension_index_batch(
6368 req: IndicatorBatchRequest<'_>,
6369 output_id: &str,
6370) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6371 let data = extract_slice_input("fractal_dimension_index", req.data, "close")?;
6372 let kernel = req.kernel.to_non_batch();
6373 collect_f64(
6374 "fractal_dimension_index",
6375 output_id,
6376 req.combos,
6377 data.len(),
6378 |params| {
6379 let length = get_usize_param("fractal_dimension_index", params, "length", 30)?;
6380 let input = FractalDimensionIndexInput::from_slice(
6381 data,
6382 FractalDimensionIndexParams {
6383 length: Some(length),
6384 },
6385 );
6386 let out = fractal_dimension_index_with_kernel(&input, kernel).map_err(|e| {
6387 IndicatorDispatchError::ComputeFailed {
6388 indicator: "fractal_dimension_index".to_string(),
6389 details: e.to_string(),
6390 }
6391 })?;
6392 if output_id.eq_ignore_ascii_case("value") {
6393 return Ok(out.values);
6394 }
6395 Err(IndicatorDispatchError::UnknownOutput {
6396 indicator: "fractal_dimension_index".to_string(),
6397 output: output_id.to_string(),
6398 })
6399 },
6400 )
6401}
6402
6403fn compute_volume_weighted_rsi_batch(
6404 req: IndicatorBatchRequest<'_>,
6405 output_id: &str,
6406) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6407 expect_value_output("volume_weighted_rsi", output_id)?;
6408 let (close, volume) = extract_close_volume_input("volume_weighted_rsi", req.data, "close")?;
6409 let periods = combo_periods("volume_weighted_rsi", req.combos, "period", 14)?;
6410 if let Some((start, end, step)) = derive_period_sweep(&periods) {
6411 let out = volume_weighted_rsi_batch_with_kernel(
6412 close,
6413 volume,
6414 &VolumeWeightedRsiBatchRange {
6415 period: (start, end, step),
6416 },
6417 to_batch_kernel(req.kernel),
6418 )
6419 .map_err(|e| IndicatorDispatchError::ComputeFailed {
6420 indicator: "volume_weighted_rsi".to_string(),
6421 details: e.to_string(),
6422 })?;
6423 ensure_len("volume_weighted_rsi", close.len(), out.cols)?;
6424 let produced_periods: Vec<usize> = out
6425 .combos
6426 .iter()
6427 .map(|combo| combo.period.unwrap_or(14))
6428 .collect();
6429 let values = reorder_or_take_f64_matrix_by_period(
6430 "volume_weighted_rsi",
6431 &periods,
6432 &produced_periods,
6433 out.cols,
6434 out.values,
6435 )?;
6436 return Ok(f64_output(output_id, periods.len(), out.cols, values));
6437 }
6438
6439 let kernel = req.kernel.to_non_batch();
6440 collect_f64_into_rows(
6441 "volume_weighted_rsi",
6442 output_id,
6443 req.combos,
6444 close.len(),
6445 |params, row| {
6446 let period = get_usize_param("volume_weighted_rsi", params, "period", 14)?;
6447 let input = VolumeWeightedRsiInput::from_slices(
6448 close,
6449 volume,
6450 VolumeWeightedRsiParams {
6451 period: Some(period),
6452 },
6453 );
6454 volume_weighted_rsi_into_slice(row, &input, kernel).map_err(|e| {
6455 IndicatorDispatchError::ComputeFailed {
6456 indicator: "volume_weighted_rsi".to_string(),
6457 details: e.to_string(),
6458 }
6459 })
6460 },
6461 )
6462}
6463
6464fn compute_dynamic_momentum_index_batch(
6465 req: IndicatorBatchRequest<'_>,
6466 output_id: &str,
6467) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6468 expect_value_output("dynamic_momentum_index", output_id)?;
6469 let data = extract_slice_input("dynamic_momentum_index", req.data, "close")?;
6470 let kernel = req.kernel.to_non_batch();
6471 collect_f64_into_rows(
6472 "dynamic_momentum_index",
6473 output_id,
6474 req.combos,
6475 data.len(),
6476 |params, row| {
6477 let rsi_period = get_usize_param("dynamic_momentum_index", params, "rsi_period", 14)?;
6478 let volatility_period =
6479 get_usize_param("dynamic_momentum_index", params, "volatility_period", 5)?;
6480 let volatility_sma_period = get_usize_param(
6481 "dynamic_momentum_index",
6482 params,
6483 "volatility_sma_period",
6484 10,
6485 )?;
6486 let upper_limit = get_usize_param("dynamic_momentum_index", params, "upper_limit", 30)?;
6487 let lower_limit = get_usize_param("dynamic_momentum_index", params, "lower_limit", 5)?;
6488 let input = DynamicMomentumIndexInput::from_slice(
6489 data,
6490 DynamicMomentumIndexParams {
6491 rsi_period: Some(rsi_period),
6492 volatility_period: Some(volatility_period),
6493 volatility_sma_period: Some(volatility_sma_period),
6494 upper_limit: Some(upper_limit),
6495 lower_limit: Some(lower_limit),
6496 },
6497 );
6498 dynamic_momentum_index_into_slice(row, &input, kernel).map_err(|e| {
6499 IndicatorDispatchError::ComputeFailed {
6500 indicator: "dynamic_momentum_index".to_string(),
6501 details: e.to_string(),
6502 }
6503 })
6504 },
6505 )
6506}
6507
6508fn compute_disparity_index_batch(
6509 req: IndicatorBatchRequest<'_>,
6510 output_id: &str,
6511) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6512 expect_value_output("disparity_index", output_id)?;
6513 let data = extract_slice_input("disparity_index", req.data, "close")?;
6514 let kernel = req.kernel.to_non_batch();
6515 collect_f64_into_rows(
6516 "disparity_index",
6517 output_id,
6518 req.combos,
6519 data.len(),
6520 |params, row| {
6521 let ema_period = get_usize_param("disparity_index", params, "ema_period", 14)?;
6522 let lookback_period =
6523 get_usize_param("disparity_index", params, "lookback_period", 14)?;
6524 let smoothing_period =
6525 get_usize_param("disparity_index", params, "smoothing_period", 9)?;
6526 let smoothing_type =
6527 get_enum_param("disparity_index", params, "smoothing_type", "ema")?;
6528 let input = DisparityIndexInput::from_slice(
6529 data,
6530 DisparityIndexParams {
6531 ema_period: Some(ema_period),
6532 lookback_period: Some(lookback_period),
6533 smoothing_period: Some(smoothing_period),
6534 smoothing_type: Some(smoothing_type),
6535 },
6536 );
6537 disparity_index_into_slice(row, &input, kernel).map_err(|e| {
6538 IndicatorDispatchError::ComputeFailed {
6539 indicator: "disparity_index".to_string(),
6540 details: e.to_string(),
6541 }
6542 })
6543 },
6544 )
6545}
6546
6547fn compute_donchian_channel_width_batch(
6548 req: IndicatorBatchRequest<'_>,
6549 output_id: &str,
6550) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6551 expect_value_output("donchian_channel_width", output_id)?;
6552 let (high, low) = extract_high_low_input("donchian_channel_width", req.data)?;
6553
6554 collect_f64_into_rows(
6555 "donchian_channel_width",
6556 output_id,
6557 req.combos,
6558 high.len(),
6559 |params, row| {
6560 let period = get_usize_param("donchian_channel_width", params, "period", 20)?;
6561 let kernel = req.kernel;
6562 let input = DonchianChannelWidthInput::from_slices(
6563 high,
6564 low,
6565 DonchianChannelWidthParams {
6566 period: Some(period),
6567 },
6568 );
6569 donchian_channel_width_into_slice(row, &input, kernel).map_err(|e| {
6570 IndicatorDispatchError::ComputeFailed {
6571 indicator: "donchian_channel_width".to_string(),
6572 details: e.to_string(),
6573 }
6574 })
6575 },
6576 )
6577}
6578
6579fn compute_kairi_relative_index_batch(
6580 req: IndicatorBatchRequest<'_>,
6581 output_id: &str,
6582) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6583 expect_value_output("kairi_relative_index", output_id)?;
6584 let kernel = req.kernel.to_non_batch();
6585 let len = match req.data {
6586 IndicatorDataRef::Slice { values } => values.len(),
6587 IndicatorDataRef::Candles { candles, source } => {
6588 source_type(candles, source.unwrap_or("close")).len()
6589 }
6590 IndicatorDataRef::CloseVolume { close, volume } => {
6591 ensure_same_len_2("kairi_relative_index", close.len(), volume.len())?;
6592 close.len()
6593 }
6594 IndicatorDataRef::Ohlc {
6595 open,
6596 high,
6597 low,
6598 close,
6599 } => {
6600 ensure_same_len_4(
6601 "kairi_relative_index",
6602 open.len(),
6603 high.len(),
6604 low.len(),
6605 close.len(),
6606 )?;
6607 close.len()
6608 }
6609 IndicatorDataRef::Ohlcv {
6610 open,
6611 high,
6612 low,
6613 close,
6614 volume,
6615 } => {
6616 ensure_same_len_5(
6617 "kairi_relative_index",
6618 open.len(),
6619 high.len(),
6620 low.len(),
6621 close.len(),
6622 volume.len(),
6623 )?;
6624 close.len()
6625 }
6626 IndicatorDataRef::HighLow { .. } => {
6627 return Err(IndicatorDispatchError::MissingRequiredInput {
6628 indicator: "kairi_relative_index".to_string(),
6629 input: IndicatorInputKind::Candles,
6630 });
6631 }
6632 };
6633
6634 collect_f64_into_rows(
6635 "kairi_relative_index",
6636 output_id,
6637 req.combos,
6638 len,
6639 |params, row| {
6640 let length = get_usize_param("kairi_relative_index", params, "length", 50)?;
6641 let ma_type = get_enum_param("kairi_relative_index", params, "ma_type", "SMA")?;
6642 if ma_type.eq_ignore_ascii_case("VWMA") {
6643 match req.data {
6644 IndicatorDataRef::Slice { .. } | IndicatorDataRef::Ohlc { .. } => {
6645 return Err(IndicatorDispatchError::MissingRequiredInput {
6646 indicator: "kairi_relative_index".to_string(),
6647 input: IndicatorInputKind::CloseVolume,
6648 });
6649 }
6650 _ => {}
6651 }
6652 }
6653
6654 let input = match req.data {
6655 IndicatorDataRef::Slice { values } => KairiRelativeIndexInput::from_slices(
6656 values,
6657 values,
6658 KairiRelativeIndexParams {
6659 length: Some(length),
6660 ma_type: Some(ma_type.to_string()),
6661 },
6662 ),
6663 IndicatorDataRef::Candles { candles, source } => {
6664 KairiRelativeIndexInput::from_candles(
6665 candles,
6666 source.unwrap_or("close"),
6667 KairiRelativeIndexParams {
6668 length: Some(length),
6669 ma_type: Some(ma_type.to_string()),
6670 },
6671 )
6672 }
6673 IndicatorDataRef::CloseVolume { close, volume } => {
6674 KairiRelativeIndexInput::from_slices(
6675 close,
6676 volume,
6677 KairiRelativeIndexParams {
6678 length: Some(length),
6679 ma_type: Some(ma_type.to_string()),
6680 },
6681 )
6682 }
6683 IndicatorDataRef::Ohlc { close, .. } => KairiRelativeIndexInput::from_slices(
6684 close,
6685 close,
6686 KairiRelativeIndexParams {
6687 length: Some(length),
6688 ma_type: Some(ma_type.to_string()),
6689 },
6690 ),
6691 IndicatorDataRef::Ohlcv { close, volume, .. } => {
6692 KairiRelativeIndexInput::from_slices(
6693 close,
6694 volume,
6695 KairiRelativeIndexParams {
6696 length: Some(length),
6697 ma_type: Some(ma_type.to_string()),
6698 },
6699 )
6700 }
6701 IndicatorDataRef::HighLow { .. } => unreachable!(),
6702 };
6703
6704 kairi_relative_index_into_slice(row, &input, kernel).map_err(|e| {
6705 IndicatorDispatchError::ComputeFailed {
6706 indicator: "kairi_relative_index".to_string(),
6707 details: e.to_string(),
6708 }
6709 })
6710 },
6711 )
6712}
6713
6714fn compute_projection_oscillator_batch(
6715 req: IndicatorBatchRequest<'_>,
6716 output_id: &str,
6717) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6718 let (high, low, close) = extract_ohlc_input("projection_oscillator", req.data)?;
6719 let kernel = req.kernel.to_non_batch();
6720 collect_f64(
6721 "projection_oscillator",
6722 output_id,
6723 req.combos,
6724 close.len(),
6725 |params| {
6726 let length = get_usize_param("projection_oscillator", params, "length", 14)?;
6727 let smooth_length =
6728 get_usize_param("projection_oscillator", params, "smooth_length", 4)?;
6729 let input = ProjectionOscillatorInput::from_slices(
6730 high,
6731 low,
6732 close,
6733 ProjectionOscillatorParams {
6734 length: Some(length),
6735 smooth_length: Some(smooth_length),
6736 },
6737 );
6738 let out = projection_oscillator_with_kernel(&input, kernel).map_err(|e| {
6739 IndicatorDispatchError::ComputeFailed {
6740 indicator: "projection_oscillator".to_string(),
6741 details: e.to_string(),
6742 }
6743 })?;
6744 if output_id.eq_ignore_ascii_case("pbo") || output_id.eq_ignore_ascii_case("value") {
6745 return Ok(out.pbo);
6746 }
6747 if output_id.eq_ignore_ascii_case("signal") {
6748 return Ok(out.signal);
6749 }
6750 Err(IndicatorDispatchError::UnknownOutput {
6751 indicator: "projection_oscillator".to_string(),
6752 output: output_id.to_string(),
6753 })
6754 },
6755 )
6756}
6757
6758fn compute_market_structure_trailing_stop_batch(
6759 req: IndicatorBatchRequest<'_>,
6760 output_id: &str,
6761) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6762 let (open, high, low, close) =
6763 extract_ohlc_full_input("market_structure_trailing_stop", req.data)?;
6764 let kernel = req.kernel.to_non_batch();
6765 collect_f64(
6766 "market_structure_trailing_stop",
6767 output_id,
6768 req.combos,
6769 close.len(),
6770 |params| {
6771 let length = get_usize_param("market_structure_trailing_stop", params, "length", 14)?;
6772 let increment_factor = get_f64_param(
6773 "market_structure_trailing_stop",
6774 params,
6775 "increment_factor",
6776 100.0,
6777 )?;
6778 let reset_on = get_enum_param(
6779 "market_structure_trailing_stop",
6780 params,
6781 "reset_on",
6782 "CHoCH",
6783 )?;
6784 let input = MarketStructureTrailingStopInput::from_slices(
6785 open,
6786 high,
6787 low,
6788 close,
6789 MarketStructureTrailingStopParams {
6790 length: Some(length),
6791 increment_factor: Some(increment_factor),
6792 reset_on: Some(reset_on),
6793 },
6794 );
6795 let out = market_structure_trailing_stop_with_kernel(&input, kernel).map_err(|e| {
6796 IndicatorDispatchError::ComputeFailed {
6797 indicator: "market_structure_trailing_stop".to_string(),
6798 details: e.to_string(),
6799 }
6800 })?;
6801 if output_id.eq_ignore_ascii_case("trailing_stop")
6802 || output_id.eq_ignore_ascii_case("value")
6803 {
6804 return Ok(out.trailing_stop);
6805 }
6806 if output_id.eq_ignore_ascii_case("state") {
6807 return Ok(out.state);
6808 }
6809 if output_id.eq_ignore_ascii_case("structure") {
6810 return Ok(out.structure);
6811 }
6812 Err(IndicatorDispatchError::UnknownOutput {
6813 indicator: "market_structure_trailing_stop".to_string(),
6814 output: output_id.to_string(),
6815 })
6816 },
6817 )
6818}
6819
6820fn compute_evasive_supertrend_batch(
6821 req: IndicatorBatchRequest<'_>,
6822 output_id: &str,
6823) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6824 let (open, high, low, close) = extract_ohlc_full_input("evasive_supertrend", req.data)?;
6825 let kernel = req.kernel.to_non_batch();
6826 collect_f64(
6827 "evasive_supertrend",
6828 output_id,
6829 req.combos,
6830 close.len(),
6831 |params| {
6832 let atr_length = get_usize_param("evasive_supertrend", params, "atr_length", 10)?;
6833 let base_multiplier =
6834 get_f64_param("evasive_supertrend", params, "base_multiplier", 3.0)?;
6835 let noise_threshold =
6836 get_f64_param("evasive_supertrend", params, "noise_threshold", 1.0)?;
6837 let expansion_alpha =
6838 get_f64_param("evasive_supertrend", params, "expansion_alpha", 0.5)?;
6839 let input = EvasiveSuperTrendInput::from_slices(
6840 open,
6841 high,
6842 low,
6843 close,
6844 EvasiveSuperTrendParams {
6845 atr_length: Some(atr_length),
6846 base_multiplier: Some(base_multiplier),
6847 noise_threshold: Some(noise_threshold),
6848 expansion_alpha: Some(expansion_alpha),
6849 },
6850 );
6851 let out = evasive_supertrend_with_kernel(&input, kernel).map_err(|e| {
6852 IndicatorDispatchError::ComputeFailed {
6853 indicator: "evasive_supertrend".to_string(),
6854 details: e.to_string(),
6855 }
6856 })?;
6857 if output_id.eq_ignore_ascii_case("band") || output_id.eq_ignore_ascii_case("value") {
6858 return Ok(out.band);
6859 }
6860 if output_id.eq_ignore_ascii_case("state") {
6861 return Ok(out.state);
6862 }
6863 if output_id.eq_ignore_ascii_case("noisy") {
6864 return Ok(out.noisy);
6865 }
6866 if output_id.eq_ignore_ascii_case("changed") {
6867 return Ok(out.changed);
6868 }
6869 Err(IndicatorDispatchError::UnknownOutput {
6870 indicator: "evasive_supertrend".to_string(),
6871 output: output_id.to_string(),
6872 })
6873 },
6874 )
6875}
6876
6877fn compute_reversal_signals_batch(
6878 req: IndicatorBatchRequest<'_>,
6879 output_id: &str,
6880) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6881 let (open, high, low, close, volume) =
6882 extract_ohlcv_full_input("reversal_signals", req.data)?;
6883 let kernel = req.kernel.to_non_batch();
6884 collect_f64("reversal_signals", output_id, req.combos, close.len(), |params| {
6885 let lookback_period =
6886 get_usize_param("reversal_signals", params, "lookback_period", 12)?;
6887 let confirmation_period =
6888 get_usize_param("reversal_signals", params, "confirmation_period", 3)?;
6889 let use_volume_confirmation = get_bool_param(
6890 "reversal_signals",
6891 params,
6892 "use_volume_confirmation",
6893 true,
6894 )?;
6895 let trend_ma_period =
6896 get_usize_param("reversal_signals", params, "trend_ma_period", 50)?;
6897 let trend_ma_type =
6898 get_enum_param("reversal_signals", params, "trend_ma_type", "EMA")?;
6899 let ma_step_period =
6900 get_usize_param("reversal_signals", params, "ma_step_period", 33)?;
6901 let input = ReversalSignalsInput::from_slices(
6902 open,
6903 high,
6904 low,
6905 close,
6906 volume,
6907 ReversalSignalsParams {
6908 lookback_period: Some(lookback_period),
6909 confirmation_period: Some(confirmation_period),
6910 use_volume_confirmation: Some(use_volume_confirmation),
6911 trend_ma_period: Some(trend_ma_period),
6912 trend_ma_type: Some(trend_ma_type.to_string()),
6913 ma_step_period: Some(ma_step_period),
6914 },
6915 );
6916 let out = reversal_signals_with_kernel(&input, kernel).map_err(|e| {
6917 IndicatorDispatchError::ComputeFailed {
6918 indicator: "reversal_signals".to_string(),
6919 details: e.to_string(),
6920 }
6921 })?;
6922 if output_id.eq_ignore_ascii_case("buy_signal") {
6923 return Ok(out.buy_signal);
6924 }
6925 if output_id.eq_ignore_ascii_case("sell_signal") {
6926 return Ok(out.sell_signal);
6927 }
6928 if output_id.eq_ignore_ascii_case("stepped_ma") || output_id.eq_ignore_ascii_case("value")
6929 {
6930 return Ok(out.stepped_ma);
6931 }
6932 if output_id.eq_ignore_ascii_case("state") {
6933 return Ok(out.state);
6934 }
6935 Err(IndicatorDispatchError::UnknownOutput {
6936 indicator: "reversal_signals".to_string(),
6937 output: output_id.to_string(),
6938 })
6939 })
6940}
6941
6942fn compute_zig_zag_channels_batch(
6943 req: IndicatorBatchRequest<'_>,
6944 output_id: &str,
6945) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6946 let (open, high, low, close) = extract_ohlc_full_input("zig_zag_channels", req.data)?;
6947 let kernel = req.kernel.to_non_batch();
6948 collect_f64(
6949 "zig_zag_channels",
6950 output_id,
6951 req.combos,
6952 close.len(),
6953 |params| {
6954 let length = get_usize_param("zig_zag_channels", params, "length", 100)?;
6955 let extend = get_bool_param("zig_zag_channels", params, "extend", true)?;
6956 let input = ZigZagChannelsInput::from_slices(
6957 open,
6958 high,
6959 low,
6960 close,
6961 ZigZagChannelsParams {
6962 length: Some(length),
6963 extend: Some(extend),
6964 },
6965 );
6966 let out = zig_zag_channels_with_kernel(&input, kernel).map_err(|e| {
6967 IndicatorDispatchError::ComputeFailed {
6968 indicator: "zig_zag_channels".to_string(),
6969 details: e.to_string(),
6970 }
6971 })?;
6972 if output_id.eq_ignore_ascii_case("middle") || output_id.eq_ignore_ascii_case("value") {
6973 return Ok(out.middle);
6974 }
6975 if output_id.eq_ignore_ascii_case("upper") {
6976 return Ok(out.upper);
6977 }
6978 if output_id.eq_ignore_ascii_case("lower") {
6979 return Ok(out.lower);
6980 }
6981 Err(IndicatorDispatchError::UnknownOutput {
6982 indicator: "zig_zag_channels".to_string(),
6983 output: output_id.to_string(),
6984 })
6985 },
6986 )
6987}
6988
6989fn compute_directional_imbalance_index_batch(
6990 req: IndicatorBatchRequest<'_>,
6991 output_id: &str,
6992) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6993 let (high, low) = match req.data {
6994 IndicatorDataRef::Candles { candles, .. } => {
6995 (candles.high.as_slice(), candles.low.as_slice())
6996 }
6997 IndicatorDataRef::HighLow { high, low } => (high, low),
6998 IndicatorDataRef::Ohlc { high, low, .. } => (high, low),
6999 IndicatorDataRef::Ohlcv { high, low, .. } => (high, low),
7000 _ => {
7001 return Err(IndicatorDispatchError::MissingRequiredInput {
7002 indicator: "directional_imbalance_index".to_string(),
7003 input: IndicatorInputKind::HighLow,
7004 });
7005 }
7006 };
7007 let kernel = req.kernel.to_non_batch();
7008 collect_f64(
7009 "directional_imbalance_index",
7010 output_id,
7011 req.combos,
7012 high.len(),
7013 |params| {
7014 let length = get_usize_param("directional_imbalance_index", params, "length", 10)?;
7015 let period = get_usize_param("directional_imbalance_index", params, "period", 70)?;
7016 let input = DirectionalImbalanceIndexInput::from_slices(
7017 high,
7018 low,
7019 DirectionalImbalanceIndexParams {
7020 length: Some(length),
7021 period: Some(period),
7022 },
7023 );
7024 let out = directional_imbalance_index_with_kernel(&input, kernel).map_err(|e| {
7025 IndicatorDispatchError::ComputeFailed {
7026 indicator: "directional_imbalance_index".to_string(),
7027 details: e.to_string(),
7028 }
7029 })?;
7030 if output_id.eq_ignore_ascii_case("up") || output_id.eq_ignore_ascii_case("value") {
7031 return Ok(out.up);
7032 }
7033 if output_id.eq_ignore_ascii_case("down") {
7034 return Ok(out.down);
7035 }
7036 if output_id.eq_ignore_ascii_case("bulls") {
7037 return Ok(out.bulls);
7038 }
7039 if output_id.eq_ignore_ascii_case("bears") {
7040 return Ok(out.bears);
7041 }
7042 if output_id.eq_ignore_ascii_case("upper") {
7043 return Ok(out.upper);
7044 }
7045 if output_id.eq_ignore_ascii_case("lower") {
7046 return Ok(out.lower);
7047 }
7048 Err(IndicatorDispatchError::UnknownOutput {
7049 indicator: "directional_imbalance_index".to_string(),
7050 output: output_id.to_string(),
7051 })
7052 },
7053 )
7054}
7055
7056fn compute_candle_strength_oscillator_batch(
7057 req: IndicatorBatchRequest<'_>,
7058 output_id: &str,
7059) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7060 let (open, high, low, close) = match req.data {
7061 IndicatorDataRef::Candles { candles, .. } => (
7062 candles.open.as_slice(),
7063 candles.high.as_slice(),
7064 candles.low.as_slice(),
7065 candles.close.as_slice(),
7066 ),
7067 IndicatorDataRef::Ohlc {
7068 open,
7069 high,
7070 low,
7071 close,
7072 } => (open, high, low, close),
7073 IndicatorDataRef::Ohlcv {
7074 open,
7075 high,
7076 low,
7077 close,
7078 ..
7079 } => (open, high, low, close),
7080 _ => {
7081 return Err(IndicatorDispatchError::MissingRequiredInput {
7082 indicator: "candle_strength_oscillator".to_string(),
7083 input: IndicatorInputKind::Ohlc,
7084 });
7085 }
7086 };
7087 let kernel = req.kernel.to_non_batch();
7088 collect_f64(
7089 "candle_strength_oscillator",
7090 output_id,
7091 req.combos,
7092 close.len(),
7093 |params| {
7094 let period = get_usize_param("candle_strength_oscillator", params, "period", 50)?;
7095 let atr_enabled =
7096 get_bool_param("candle_strength_oscillator", params, "atr_enabled", false)?;
7097 let atr_length =
7098 get_usize_param("candle_strength_oscillator", params, "atr_length", 50)?;
7099 let mode = get_enum_param("candle_strength_oscillator", params, "mode", "bollinger")?;
7100 let input = CandleStrengthOscillatorInput::from_slices(
7101 open,
7102 high,
7103 low,
7104 close,
7105 CandleStrengthOscillatorParams {
7106 period: Some(period),
7107 atr_enabled: Some(atr_enabled),
7108 atr_length: Some(atr_length),
7109 mode: Some(mode.to_string()),
7110 },
7111 );
7112 let out = candle_strength_oscillator_with_kernel(&input, kernel).map_err(|e| {
7113 IndicatorDispatchError::ComputeFailed {
7114 indicator: "candle_strength_oscillator".to_string(),
7115 details: e.to_string(),
7116 }
7117 })?;
7118 if output_id.eq_ignore_ascii_case("strength") || output_id.eq_ignore_ascii_case("value")
7119 {
7120 return Ok(out.strength);
7121 }
7122 if output_id.eq_ignore_ascii_case("highs") {
7123 return Ok(out.highs);
7124 }
7125 if output_id.eq_ignore_ascii_case("lows") {
7126 return Ok(out.lows);
7127 }
7128 if output_id.eq_ignore_ascii_case("mid") {
7129 return Ok(out.mid);
7130 }
7131 if output_id.eq_ignore_ascii_case("long_signal") {
7132 return Ok(out.long_signal);
7133 }
7134 if output_id.eq_ignore_ascii_case("short_signal") {
7135 return Ok(out.short_signal);
7136 }
7137 Err(IndicatorDispatchError::UnknownOutput {
7138 indicator: "candle_strength_oscillator".to_string(),
7139 output: output_id.to_string(),
7140 })
7141 },
7142 )
7143}
7144
7145fn compute_gmma_oscillator_batch(
7146 req: IndicatorBatchRequest<'_>,
7147 output_id: &str,
7148) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7149 let kernel = req.kernel.to_non_batch();
7150 let owned_source;
7151 let data = match req.data {
7152 IndicatorDataRef::Slice { values } => values,
7153 IndicatorDataRef::Candles { candles, source } => {
7154 source_type(candles, source.unwrap_or("close"))
7155 }
7156 IndicatorDataRef::Ohlc { close, .. } => close,
7157 IndicatorDataRef::Ohlcv { close, .. } => close,
7158 IndicatorDataRef::CloseVolume { close, volume } => {
7159 ensure_same_len_2("gmma_oscillator", close.len(), volume.len())?;
7160 close
7161 }
7162 IndicatorDataRef::HighLow { high, low } => {
7163 ensure_same_len_2("gmma_oscillator", high.len(), low.len())?;
7164 owned_source = high
7165 .iter()
7166 .zip(low.iter())
7167 .map(|(&h, &l)| (h + l) * 0.5)
7168 .collect::<Vec<_>>();
7169 owned_source.as_slice()
7170 }
7171 };
7172
7173 collect_f64(
7174 "gmma_oscillator",
7175 output_id,
7176 req.combos,
7177 data.len(),
7178 |params| {
7179 let gmma_type = get_enum_param("gmma_oscillator", params, "gmma_type", "guppy")?;
7180 let smooth_length = get_usize_param("gmma_oscillator", params, "smooth_length", 1)?;
7181 let signal_length = get_usize_param("gmma_oscillator", params, "signal_length", 13)?;
7182 let anchor_minutes = get_usize_param("gmma_oscillator", params, "anchor_minutes", 0)?;
7183 let interval_minutes = if params
7184 .iter()
7185 .any(|param| param.key.eq_ignore_ascii_case("interval_minutes"))
7186 {
7187 Some(get_usize_param(
7188 "gmma_oscillator",
7189 params,
7190 "interval_minutes",
7191 1,
7192 )?)
7193 } else {
7194 None
7195 };
7196 let input = GmmaOscillatorInput::from_slice(
7197 data,
7198 GmmaOscillatorParams {
7199 gmma_type: Some(gmma_type.to_string()),
7200 smooth_length: Some(smooth_length),
7201 signal_length: Some(signal_length),
7202 anchor_minutes: Some(anchor_minutes),
7203 interval_minutes,
7204 },
7205 );
7206 let out = gmma_oscillator_with_kernel(&input, kernel).map_err(|e| {
7207 IndicatorDispatchError::ComputeFailed {
7208 indicator: "gmma_oscillator".to_string(),
7209 details: e.to_string(),
7210 }
7211 })?;
7212 if output_id.eq_ignore_ascii_case("oscillator")
7213 || output_id.eq_ignore_ascii_case("value")
7214 {
7215 return Ok(out.oscillator);
7216 }
7217 if output_id.eq_ignore_ascii_case("signal") {
7218 return Ok(out.signal);
7219 }
7220 Err(IndicatorDispatchError::UnknownOutput {
7221 indicator: "gmma_oscillator".to_string(),
7222 output: output_id.to_string(),
7223 })
7224 },
7225 )
7226}
7227
7228fn compute_nonlinear_regression_zero_lag_moving_average_batch(
7229 req: IndicatorBatchRequest<'_>,
7230 output_id: &str,
7231) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7232 let data = extract_slice_input(
7233 "nonlinear_regression_zero_lag_moving_average",
7234 req.data,
7235 "close",
7236 )?;
7237 let kernel = req.kernel.to_non_batch();
7238 collect_f64(
7239 "nonlinear_regression_zero_lag_moving_average",
7240 output_id,
7241 req.combos,
7242 data.len(),
7243 |params| {
7244 let zlma_period = get_usize_param(
7245 "nonlinear_regression_zero_lag_moving_average",
7246 params,
7247 "zlma_period",
7248 15,
7249 )?;
7250 let regression_period = get_usize_param(
7251 "nonlinear_regression_zero_lag_moving_average",
7252 params,
7253 "regression_period",
7254 15,
7255 )?;
7256 let input = NonlinearRegressionZeroLagMovingAverageInput::from_slice(
7257 data,
7258 NonlinearRegressionZeroLagMovingAverageParams {
7259 zlma_period: Some(zlma_period),
7260 regression_period: Some(regression_period),
7261 },
7262 );
7263 let out = nonlinear_regression_zero_lag_moving_average_with_kernel(&input, kernel)
7264 .map_err(|e| IndicatorDispatchError::ComputeFailed {
7265 indicator: "nonlinear_regression_zero_lag_moving_average".to_string(),
7266 details: e.to_string(),
7267 })?;
7268 if output_id.eq_ignore_ascii_case("value") {
7269 return Ok(out.value);
7270 }
7271 if output_id.eq_ignore_ascii_case("signal") {
7272 return Ok(out.signal);
7273 }
7274 if output_id.eq_ignore_ascii_case("long_signal") {
7275 return Ok(out.long_signal);
7276 }
7277 if output_id.eq_ignore_ascii_case("short_signal") {
7278 return Ok(out.short_signal);
7279 }
7280 Err(IndicatorDispatchError::UnknownOutput {
7281 indicator: "nonlinear_regression_zero_lag_moving_average".to_string(),
7282 output: output_id.to_string(),
7283 })
7284 },
7285 )
7286}
7287
7288fn compute_possible_rsi_batch(
7289 req: IndicatorBatchRequest<'_>,
7290 output_id: &str,
7291) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7292 let data = extract_slice_input("possible_rsi", req.data, "close")?;
7293 let kernel = req.kernel.to_non_batch();
7294 collect_f64(
7295 "possible_rsi",
7296 output_id,
7297 req.combos,
7298 data.len(),
7299 |params| {
7300 let period = get_usize_param("possible_rsi", params, "period", 32)?;
7301 let rsi_mode = get_enum_param("possible_rsi", params, "rsi_mode", "regular")?;
7302 let norm_period = get_usize_param("possible_rsi", params, "norm_period", 100)?;
7303 let normalization_mode = get_enum_param(
7304 "possible_rsi",
7305 params,
7306 "normalization_mode",
7307 "gaussian_fisher",
7308 )?;
7309 let normalization_length =
7310 get_usize_param("possible_rsi", params, "normalization_length", 15)?;
7311 let nonlag_period = get_usize_param("possible_rsi", params, "nonlag_period", 15)?;
7312 let dynamic_zone_period =
7313 get_usize_param("possible_rsi", params, "dynamic_zone_period", 20)?;
7314 let buy_probability = get_f64_param("possible_rsi", params, "buy_probability", 0.2)?;
7315 let sell_probability = get_f64_param("possible_rsi", params, "sell_probability", 0.2)?;
7316 let signal_type =
7317 get_enum_param("possible_rsi", params, "signal_type", "zeroline_crossover")?;
7318 let run_highpass = get_bool_param("possible_rsi", params, "run_highpass", false)?;
7319 let highpass_period = get_usize_param("possible_rsi", params, "highpass_period", 15)?;
7320 let input = PossibleRsiInput::from_slice(
7321 data,
7322 PossibleRsiParams {
7323 period: Some(period),
7324 rsi_mode: Some(rsi_mode.to_string()),
7325 norm_period: Some(norm_period),
7326 normalization_mode: Some(normalization_mode.to_string()),
7327 normalization_length: Some(normalization_length),
7328 nonlag_period: Some(nonlag_period),
7329 dynamic_zone_period: Some(dynamic_zone_period),
7330 buy_probability: Some(buy_probability),
7331 sell_probability: Some(sell_probability),
7332 signal_type: Some(signal_type.to_string()),
7333 run_highpass: Some(run_highpass),
7334 highpass_period: Some(highpass_period),
7335 },
7336 );
7337 let out = possible_rsi_with_kernel(&input, kernel).map_err(|e| {
7338 IndicatorDispatchError::ComputeFailed {
7339 indicator: "possible_rsi".to_string(),
7340 details: e.to_string(),
7341 }
7342 })?;
7343 if output_id.eq_ignore_ascii_case("value") {
7344 return Ok(out.value);
7345 }
7346 if output_id.eq_ignore_ascii_case("buy_level") {
7347 return Ok(out.buy_level);
7348 }
7349 if output_id.eq_ignore_ascii_case("sell_level") {
7350 return Ok(out.sell_level);
7351 }
7352 if output_id.eq_ignore_ascii_case("middle")
7353 || output_id.eq_ignore_ascii_case("middle_level")
7354 {
7355 return Ok(out.middle_level);
7356 }
7357 if output_id.eq_ignore_ascii_case("trend") || output_id.eq_ignore_ascii_case("state") {
7358 return Ok(out.state);
7359 }
7360 if output_id.eq_ignore_ascii_case("long_signal") {
7361 return Ok(out.long_signal);
7362 }
7363 if output_id.eq_ignore_ascii_case("short_signal") {
7364 return Ok(out.short_signal);
7365 }
7366 Err(IndicatorDispatchError::UnknownOutput {
7367 indicator: "possible_rsi".to_string(),
7368 output: output_id.to_string(),
7369 })
7370 },
7371 )
7372}
7373
7374fn compute_autocorrelation_indicator_batch(
7375 req: IndicatorBatchRequest<'_>,
7376 output_id: &str,
7377) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7378 let data = extract_slice_input("autocorrelation_indicator", req.data, "close")?;
7379 let kernel = req.kernel.to_non_batch();
7380 collect_f64(
7381 "autocorrelation_indicator",
7382 output_id,
7383 req.combos,
7384 data.len(),
7385 |params| {
7386 let length = get_usize_param("autocorrelation_indicator", params, "length", 20)?;
7387 let lag = get_usize_param("autocorrelation_indicator", params, "lag", 1)?;
7388 let use_test_signal = get_bool_param(
7389 "autocorrelation_indicator",
7390 params,
7391 "use_test_signal",
7392 false,
7393 )?;
7394 let max_lag = if output_id.eq_ignore_ascii_case("correlation") {
7395 lag
7396 } else {
7397 1
7398 };
7399 let input = AutocorrelationIndicatorInput::from_slice(
7400 data,
7401 AutocorrelationIndicatorParams {
7402 length: Some(length),
7403 max_lag: Some(max_lag),
7404 use_test_signal: Some(use_test_signal),
7405 },
7406 );
7407 let out = autocorrelation_indicator_with_kernel(&input, kernel).map_err(|e| {
7408 IndicatorDispatchError::ComputeFailed {
7409 indicator: "autocorrelation_indicator".to_string(),
7410 details: e.to_string(),
7411 }
7412 })?;
7413 if output_id.eq_ignore_ascii_case("filtered") || output_id.eq_ignore_ascii_case("value")
7414 {
7415 return Ok(out.filtered);
7416 }
7417 if output_id.eq_ignore_ascii_case("correlation") {
7418 let start = (lag - 1).checked_mul(data.len()).ok_or_else(|| {
7419 IndicatorDispatchError::ComputeFailed {
7420 indicator: "autocorrelation_indicator".to_string(),
7421 details: "lag * cols overflow".to_string(),
7422 }
7423 })?;
7424 let end = start + data.len();
7425 return Ok(out.correlations[start..end].to_vec());
7426 }
7427 Err(IndicatorDispatchError::UnknownOutput {
7428 indicator: "autocorrelation_indicator".to_string(),
7429 output: output_id.to_string(),
7430 })
7431 },
7432 )
7433}
7434
7435fn compute_goertzel_cycle_composite_wave_batch(
7436 req: IndicatorBatchRequest<'_>,
7437 output_id: &str,
7438) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7439 if !output_id.eq_ignore_ascii_case("value") && !output_id.eq_ignore_ascii_case("wave") {
7440 return Err(IndicatorDispatchError::UnknownOutput {
7441 indicator: "goertzel_cycle_composite_wave".to_string(),
7442 output: output_id.to_string(),
7443 });
7444 }
7445 let data = extract_slice_input("goertzel_cycle_composite_wave", req.data, "close")?;
7446 let kernel = req.kernel.to_non_batch();
7447 collect_f64_into_rows(
7448 "goertzel_cycle_composite_wave",
7449 output_id,
7450 req.combos,
7451 data.len(),
7452 |params, row| {
7453 let max_period =
7454 get_usize_param("goertzel_cycle_composite_wave", params, "max_period", 120)?;
7455 let start_at_cycle =
7456 get_usize_param("goertzel_cycle_composite_wave", params, "start_at_cycle", 1)?;
7457 let use_top_cycles =
7458 get_usize_param("goertzel_cycle_composite_wave", params, "use_top_cycles", 2)?;
7459 let bar_to_calculate = get_usize_param(
7460 "goertzel_cycle_composite_wave",
7461 params,
7462 "bar_to_calculate",
7463 1,
7464 )?;
7465 let detrend_mode = get_enum_string_param(
7466 "goertzel_cycle_composite_wave",
7467 params,
7468 "detrend_mode",
7469 "hodrick_prescott_detrending",
7470 )?;
7471 let detrend_mode = GoertzelDetrendMode::parse(detrend_mode).ok_or_else(|| {
7472 IndicatorDispatchError::InvalidParam {
7473 indicator: "goertzel_cycle_composite_wave".to_string(),
7474 key: "detrend_mode".to_string(),
7475 reason: format!("unknown mode: {detrend_mode}"),
7476 }
7477 })?;
7478 let dt_zl_per1 =
7479 get_usize_param("goertzel_cycle_composite_wave", params, "dt_zl_per1", 10)?;
7480 let dt_zl_per2 =
7481 get_usize_param("goertzel_cycle_composite_wave", params, "dt_zl_per2", 40)?;
7482 let dt_hp_per1 =
7483 get_usize_param("goertzel_cycle_composite_wave", params, "dt_hp_per1", 20)?;
7484 let dt_hp_per2 =
7485 get_usize_param("goertzel_cycle_composite_wave", params, "dt_hp_per2", 80)?;
7486 let dt_reg_zl_smooth_per = get_usize_param(
7487 "goertzel_cycle_composite_wave",
7488 params,
7489 "dt_reg_zl_smooth_per",
7490 5,
7491 )?;
7492 let hp_smooth_per =
7493 get_usize_param("goertzel_cycle_composite_wave", params, "hp_smooth_per", 20)?;
7494 let zlma_smooth_per = get_usize_param(
7495 "goertzel_cycle_composite_wave",
7496 params,
7497 "zlma_smooth_per",
7498 10,
7499 )?;
7500 let filter_bartels = get_bool_param(
7501 "goertzel_cycle_composite_wave",
7502 params,
7503 "filter_bartels",
7504 false,
7505 )?;
7506 let bart_no_cycles =
7507 get_usize_param("goertzel_cycle_composite_wave", params, "bart_no_cycles", 5)?;
7508 let bart_smooth_per = get_usize_param(
7509 "goertzel_cycle_composite_wave",
7510 params,
7511 "bart_smooth_per",
7512 2,
7513 )?;
7514 let bart_sig_limit = get_usize_param(
7515 "goertzel_cycle_composite_wave",
7516 params,
7517 "bart_sig_limit",
7518 50,
7519 )?;
7520 let sort_bartels = get_bool_param(
7521 "goertzel_cycle_composite_wave",
7522 params,
7523 "sort_bartels",
7524 false,
7525 )?;
7526 let squared_amp =
7527 get_bool_param("goertzel_cycle_composite_wave", params, "squared_amp", true)?;
7528 let use_cosine =
7529 get_bool_param("goertzel_cycle_composite_wave", params, "use_cosine", true)?;
7530 let subtract_noise = get_bool_param(
7531 "goertzel_cycle_composite_wave",
7532 params,
7533 "subtract_noise",
7534 false,
7535 )?;
7536 let use_cycle_strength = get_bool_param(
7537 "goertzel_cycle_composite_wave",
7538 params,
7539 "use_cycle_strength",
7540 true,
7541 )?;
7542
7543 let input = GoertzelCycleCompositeWaveInput::from_slice(
7544 data,
7545 GoertzelCycleCompositeWaveParams {
7546 max_period: Some(max_period),
7547 start_at_cycle: Some(start_at_cycle),
7548 use_top_cycles: Some(use_top_cycles),
7549 bar_to_calculate: Some(bar_to_calculate),
7550 detrend_mode: Some(detrend_mode),
7551 dt_zl_per1: Some(dt_zl_per1),
7552 dt_zl_per2: Some(dt_zl_per2),
7553 dt_hp_per1: Some(dt_hp_per1),
7554 dt_hp_per2: Some(dt_hp_per2),
7555 dt_reg_zl_smooth_per: Some(dt_reg_zl_smooth_per),
7556 hp_smooth_per: Some(hp_smooth_per),
7557 zlma_smooth_per: Some(zlma_smooth_per),
7558 filter_bartels: Some(filter_bartels),
7559 bart_no_cycles: Some(bart_no_cycles),
7560 bart_smooth_per: Some(bart_smooth_per),
7561 bart_sig_limit: Some(bart_sig_limit),
7562 sort_bartels: Some(sort_bartels),
7563 squared_amp: Some(squared_amp),
7564 use_cosine: Some(use_cosine),
7565 subtract_noise: Some(subtract_noise),
7566 use_cycle_strength: Some(use_cycle_strength),
7567 },
7568 );
7569 goertzel_cycle_composite_wave_into_slice(row, &input, kernel).map_err(|e| {
7570 IndicatorDispatchError::ComputeFailed {
7571 indicator: "goertzel_cycle_composite_wave".to_string(),
7572 details: e.to_string(),
7573 }
7574 })
7575 },
7576 )
7577}
7578
7579fn compute_rolling_skewness_kurtosis_batch(
7580 req: IndicatorBatchRequest<'_>,
7581 output_id: &str,
7582) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7583 let data = extract_slice_input("rolling_skewness_kurtosis", req.data, "close")?;
7584 let kernel = req.kernel.to_non_batch();
7585 collect_f64(
7586 "rolling_skewness_kurtosis",
7587 output_id,
7588 req.combos,
7589 data.len(),
7590 |params| {
7591 let length = get_usize_param("rolling_skewness_kurtosis", params, "length", 50)?;
7592 let smooth_length =
7593 get_usize_param("rolling_skewness_kurtosis", params, "smooth_length", 3)?;
7594 let input = RollingSkewnessKurtosisInput::from_slice(
7595 data,
7596 RollingSkewnessKurtosisParams {
7597 length: Some(length),
7598 smooth_length: Some(smooth_length),
7599 },
7600 );
7601 let out = rolling_skewness_kurtosis_with_kernel(&input, kernel).map_err(|e| {
7602 IndicatorDispatchError::ComputeFailed {
7603 indicator: "rolling_skewness_kurtosis".to_string(),
7604 details: e.to_string(),
7605 }
7606 })?;
7607 if output_id.eq_ignore_ascii_case("skewness") {
7608 return Ok(out.skewness);
7609 }
7610 if output_id.eq_ignore_ascii_case("kurtosis") {
7611 return Ok(out.kurtosis);
7612 }
7613 Err(IndicatorDispatchError::UnknownOutput {
7614 indicator: "rolling_skewness_kurtosis".to_string(),
7615 output: output_id.to_string(),
7616 })
7617 },
7618 )
7619}
7620
7621fn compute_rolling_z_score_trend_batch(
7622 req: IndicatorBatchRequest<'_>,
7623 output_id: &str,
7624) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7625 let data = extract_slice_input("rolling_z_score_trend", req.data, "close")?;
7626 let kernel = req.kernel.to_non_batch();
7627 collect_f64(
7628 "rolling_z_score_trend",
7629 output_id,
7630 req.combos,
7631 data.len(),
7632 |params| {
7633 let lookback_period =
7634 get_usize_param("rolling_z_score_trend", params, "lookback_period", 20)?;
7635 let input = RollingZScoreTrendInput::from_slice(
7636 data,
7637 RollingZScoreTrendParams {
7638 lookback_period: Some(lookback_period),
7639 },
7640 );
7641 let out = rolling_z_score_trend_with_kernel(&input, kernel).map_err(|e| {
7642 IndicatorDispatchError::ComputeFailed {
7643 indicator: "rolling_z_score_trend".to_string(),
7644 details: e.to_string(),
7645 }
7646 })?;
7647 if output_id.eq_ignore_ascii_case("zscore") {
7648 return Ok(out.zscore);
7649 }
7650 if output_id.eq_ignore_ascii_case("momentum") {
7651 return Ok(out.momentum);
7652 }
7653 Err(IndicatorDispatchError::UnknownOutput {
7654 indicator: "rolling_z_score_trend".to_string(),
7655 output: output_id.to_string(),
7656 })
7657 },
7658 )
7659}
7660
7661fn compute_ehlers_data_sampling_relative_strength_indicator_batch(
7662 req: IndicatorBatchRequest<'_>,
7663 output_id: &str,
7664) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7665 let (open, close) = match req.data {
7666 IndicatorDataRef::Candles { candles, .. } => {
7667 (candles.open.as_slice(), candles.close.as_slice())
7668 }
7669 IndicatorDataRef::Ohlc {
7670 open,
7671 high,
7672 low,
7673 close,
7674 } => {
7675 ensure_same_len_4(
7676 "ehlers_data_sampling_relative_strength_indicator",
7677 open.len(),
7678 high.len(),
7679 low.len(),
7680 close.len(),
7681 )?;
7682 (open, close)
7683 }
7684 IndicatorDataRef::Ohlcv {
7685 open,
7686 high,
7687 low,
7688 close,
7689 volume,
7690 } => {
7691 ensure_same_len_5(
7692 "ehlers_data_sampling_relative_strength_indicator",
7693 open.len(),
7694 high.len(),
7695 low.len(),
7696 close.len(),
7697 volume.len(),
7698 )?;
7699 (open, close)
7700 }
7701 _ => {
7702 return Err(IndicatorDispatchError::MissingRequiredInput {
7703 indicator: "ehlers_data_sampling_relative_strength_indicator".to_string(),
7704 input: IndicatorInputKind::Ohlc,
7705 })
7706 }
7707 };
7708 let kernel = req.kernel.to_non_batch();
7709 collect_f64(
7710 "ehlers_data_sampling_relative_strength_indicator",
7711 output_id,
7712 req.combos,
7713 close.len(),
7714 |params| {
7715 let length = get_usize_param(
7716 "ehlers_data_sampling_relative_strength_indicator",
7717 params,
7718 "length",
7719 14,
7720 )?;
7721 let input = EhlersDataSamplingRelativeStrengthIndicatorInput::from_slices(
7722 open,
7723 close,
7724 EhlersDataSamplingRelativeStrengthIndicatorParams {
7725 length: Some(length),
7726 },
7727 );
7728 let out = ehlers_data_sampling_relative_strength_indicator_with_kernel(&input, kernel)
7729 .map_err(|e| IndicatorDispatchError::ComputeFailed {
7730 indicator: "ehlers_data_sampling_relative_strength_indicator".to_string(),
7731 details: e.to_string(),
7732 })?;
7733 if output_id.eq_ignore_ascii_case("ds_rsi")
7734 || output_id.eq_ignore_ascii_case("data_sampling_rsi")
7735 {
7736 return Ok(out.ds_rsi);
7737 }
7738 if output_id.eq_ignore_ascii_case("original_rsi")
7739 || output_id.eq_ignore_ascii_case("orig_rsi")
7740 {
7741 return Ok(out.original_rsi);
7742 }
7743 if output_id.eq_ignore_ascii_case("signal") {
7744 return Ok(out.signal);
7745 }
7746 Err(IndicatorDispatchError::UnknownOutput {
7747 indicator: "ehlers_data_sampling_relative_strength_indicator".to_string(),
7748 output: output_id.to_string(),
7749 })
7750 },
7751 )
7752}
7753
7754fn compute_velocity_acceleration_convergence_divergence_indicator_batch(
7755 req: IndicatorBatchRequest<'_>,
7756 output_id: &str,
7757) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7758 let owned_source;
7759 let data = match req.data {
7760 IndicatorDataRef::Slice { values } => values,
7761 IndicatorDataRef::Candles { candles, source } => {
7762 source_type(candles, source.unwrap_or("hlcc4"))
7763 }
7764 IndicatorDataRef::Ohlc {
7765 open,
7766 high,
7767 low,
7768 close,
7769 } => {
7770 ensure_same_len_4(
7771 "velocity_acceleration_convergence_divergence_indicator",
7772 open.len(),
7773 high.len(),
7774 low.len(),
7775 close.len(),
7776 )?;
7777 owned_source = high
7778 .iter()
7779 .zip(low.iter())
7780 .zip(close.iter())
7781 .map(|((&h, &l), &c)| (h + l + 2.0 * c) * 0.25)
7782 .collect::<Vec<_>>();
7783 owned_source.as_slice()
7784 }
7785 IndicatorDataRef::Ohlcv {
7786 open,
7787 high,
7788 low,
7789 close,
7790 volume,
7791 } => {
7792 ensure_same_len_5(
7793 "velocity_acceleration_convergence_divergence_indicator",
7794 open.len(),
7795 high.len(),
7796 low.len(),
7797 close.len(),
7798 volume.len(),
7799 )?;
7800 owned_source = high
7801 .iter()
7802 .zip(low.iter())
7803 .zip(close.iter())
7804 .map(|((&h, &l), &c)| (h + l + 2.0 * c) * 0.25)
7805 .collect::<Vec<_>>();
7806 owned_source.as_slice()
7807 }
7808 IndicatorDataRef::CloseVolume { close, volume } => {
7809 ensure_same_len_2(
7810 "velocity_acceleration_convergence_divergence_indicator",
7811 close.len(),
7812 volume.len(),
7813 )?;
7814 close
7815 }
7816 IndicatorDataRef::HighLow { .. } => {
7817 return Err(IndicatorDispatchError::MissingRequiredInput {
7818 indicator: "velocity_acceleration_convergence_divergence_indicator".to_string(),
7819 input: IndicatorInputKind::Candles,
7820 });
7821 }
7822 };
7823 let kernel = req.kernel.to_non_batch();
7824 collect_f64(
7825 "velocity_acceleration_convergence_divergence_indicator",
7826 output_id,
7827 req.combos,
7828 data.len(),
7829 |params| {
7830 let length = get_usize_param(
7831 "velocity_acceleration_convergence_divergence_indicator",
7832 params,
7833 "length",
7834 21,
7835 )?;
7836 let smooth_length = get_usize_param(
7837 "velocity_acceleration_convergence_divergence_indicator",
7838 params,
7839 "smooth_length",
7840 5,
7841 )?;
7842 let input = VelocityAccelerationConvergenceDivergenceIndicatorInput::from_slice(
7843 data,
7844 VelocityAccelerationConvergenceDivergenceIndicatorParams {
7845 length: Some(length),
7846 smooth_length: Some(smooth_length),
7847 },
7848 );
7849 let out =
7850 velocity_acceleration_convergence_divergence_indicator_with_kernel(&input, kernel)
7851 .map_err(|e| IndicatorDispatchError::ComputeFailed {
7852 indicator: "velocity_acceleration_convergence_divergence_indicator"
7853 .to_string(),
7854 details: e.to_string(),
7855 })?;
7856 if output_id.eq_ignore_ascii_case("vacd") || output_id.eq_ignore_ascii_case("value") {
7857 return Ok(out.vacd);
7858 }
7859 if output_id.eq_ignore_ascii_case("signal") {
7860 return Ok(out.signal);
7861 }
7862 Err(IndicatorDispatchError::UnknownOutput {
7863 indicator: "velocity_acceleration_convergence_divergence_indicator".to_string(),
7864 output: output_id.to_string(),
7865 })
7866 },
7867 )
7868}
7869
7870fn compute_trend_direction_force_index_batch(
7871 req: IndicatorBatchRequest<'_>,
7872 output_id: &str,
7873) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7874 expect_value_output("trend_direction_force_index", output_id)?;
7875 let data = extract_slice_input("trend_direction_force_index", req.data, "close")?;
7876 let kernel = req.kernel.to_non_batch();
7877 collect_f64_into_rows(
7878 "trend_direction_force_index",
7879 output_id,
7880 req.combos,
7881 data.len(),
7882 |params, row| {
7883 let length = get_usize_param("trend_direction_force_index", params, "length", 10)?;
7884 let input = TrendDirectionForceIndexInput::from_slice(
7885 data,
7886 TrendDirectionForceIndexParams {
7887 length: Some(length),
7888 },
7889 );
7890 trend_direction_force_index_into_slice(row, &input, kernel).map_err(|e| {
7891 IndicatorDispatchError::ComputeFailed {
7892 indicator: "trend_direction_force_index".to_string(),
7893 details: e.to_string(),
7894 }
7895 })
7896 },
7897 )
7898}
7899
7900fn compute_yang_zhang_volatility_batch(
7901 req: IndicatorBatchRequest<'_>,
7902 output_id: &str,
7903) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7904 let (open, high, low, close) = extract_ohlc_full_input("yang_zhang_volatility", req.data)?;
7905 let kernel = req.kernel.to_non_batch();
7906 collect_f64(
7907 "yang_zhang_volatility",
7908 output_id,
7909 req.combos,
7910 close.len(),
7911 |params| {
7912 let lookback = get_usize_param("yang_zhang_volatility", params, "lookback", 14)?;
7913 let k_override = get_bool_param("yang_zhang_volatility", params, "k_override", false)?;
7914 let k = get_f64_param("yang_zhang_volatility", params, "k", 0.34)?;
7915 let input = YangZhangVolatilityInput::from_slices(
7916 open,
7917 high,
7918 low,
7919 close,
7920 YangZhangVolatilityParams {
7921 lookback: Some(lookback),
7922 k_override: Some(k_override),
7923 k: Some(k),
7924 },
7925 );
7926 let out = yang_zhang_volatility_with_kernel(&input, kernel).map_err(|e| {
7927 IndicatorDispatchError::ComputeFailed {
7928 indicator: "yang_zhang_volatility".to_string(),
7929 details: e.to_string(),
7930 }
7931 })?;
7932 if output_id.eq_ignore_ascii_case("yz") || output_id.eq_ignore_ascii_case("value") {
7933 return Ok(out.yz);
7934 }
7935 if output_id.eq_ignore_ascii_case("rs") {
7936 return Ok(out.rs);
7937 }
7938 Err(IndicatorDispatchError::UnknownOutput {
7939 indicator: "yang_zhang_volatility".to_string(),
7940 output: output_id.to_string(),
7941 })
7942 },
7943 )
7944}
7945
7946fn compute_garman_klass_volatility_batch(
7947 req: IndicatorBatchRequest<'_>,
7948 output_id: &str,
7949) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7950 let (open, high, low, close) = extract_ohlc_full_input("garman_klass_volatility", req.data)?;
7951 let kernel = req.kernel.to_non_batch();
7952 collect_f64(
7953 "garman_klass_volatility",
7954 output_id,
7955 req.combos,
7956 close.len(),
7957 |params| {
7958 let lookback = get_usize_param("garman_klass_volatility", params, "lookback", 14)?;
7959 let input = GarmanKlassVolatilityInput::from_slices(
7960 open,
7961 high,
7962 low,
7963 close,
7964 GarmanKlassVolatilityParams {
7965 lookback: Some(lookback),
7966 },
7967 );
7968 let out = garman_klass_volatility_with_kernel(&input, kernel).map_err(|e| {
7969 IndicatorDispatchError::ComputeFailed {
7970 indicator: "garman_klass_volatility".to_string(),
7971 details: e.to_string(),
7972 }
7973 })?;
7974 if output_id.eq_ignore_ascii_case("value") {
7975 return Ok(out.values);
7976 }
7977 Err(IndicatorDispatchError::UnknownOutput {
7978 indicator: "garman_klass_volatility".to_string(),
7979 output: output_id.to_string(),
7980 })
7981 },
7982 )
7983}
7984
7985fn compute_atr_percentile_batch(
7986 req: IndicatorBatchRequest<'_>,
7987 output_id: &str,
7988) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7989 expect_value_output("atr_percentile", output_id)?;
7990 let (high, low, close) = extract_ohlc_input("atr_percentile", req.data)?;
7991 let kernel = req.kernel.to_non_batch();
7992 collect_f64(
7993 "atr_percentile",
7994 output_id,
7995 req.combos,
7996 close.len(),
7997 |params| {
7998 let atr_length = get_usize_param("atr_percentile", params, "atr_length", 10)?;
7999 let percentile_length =
8000 get_usize_param("atr_percentile", params, "percentile_length", 50)?;
8001 let input = AtrPercentileInput::from_slices(
8002 high,
8003 low,
8004 close,
8005 AtrPercentileParams {
8006 atr_length: Some(atr_length),
8007 percentile_length: Some(percentile_length),
8008 },
8009 );
8010 let out = atr_percentile_with_kernel(&input, kernel).map_err(|e| {
8011 IndicatorDispatchError::ComputeFailed {
8012 indicator: "atr_percentile".to_string(),
8013 details: e.to_string(),
8014 }
8015 })?;
8016 Ok(out.values)
8017 },
8018 )
8019}
8020
8021fn compute_bull_power_vs_bear_power_batch(
8022 req: IndicatorBatchRequest<'_>,
8023 output_id: &str,
8024) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8025 expect_value_output("bull_power_vs_bear_power", output_id)?;
8026 let (open, high, low, close) = extract_ohlc_full_input("bull_power_vs_bear_power", req.data)?;
8027 let kernel = req.kernel.to_non_batch();
8028 collect_f64(
8029 "bull_power_vs_bear_power",
8030 output_id,
8031 req.combos,
8032 close.len(),
8033 |params| {
8034 let period = get_usize_param("bull_power_vs_bear_power", params, "period", 5)?;
8035 let input = BullPowerVsBearPowerInput::from_slices(
8036 open,
8037 high,
8038 low,
8039 close,
8040 BullPowerVsBearPowerParams {
8041 period: Some(period),
8042 },
8043 );
8044 let out = bull_power_vs_bear_power_with_kernel(&input, kernel).map_err(|e| {
8045 IndicatorDispatchError::ComputeFailed {
8046 indicator: "bull_power_vs_bear_power".to_string(),
8047 details: e.to_string(),
8048 }
8049 })?;
8050 Ok(out.values)
8051 },
8052 )
8053}
8054
8055fn compute_advance_decline_line_batch(
8056 req: IndicatorBatchRequest<'_>,
8057 output_id: &str,
8058) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8059 expect_value_output("advance_decline_line", output_id)?;
8060 let data = extract_slice_input("advance_decline_line", req.data, "close")?;
8061 let kernel = req.kernel.to_non_batch();
8062 collect_f64(
8063 "advance_decline_line",
8064 output_id,
8065 req.combos,
8066 data.len(),
8067 |_params| {
8068 let input = AdvanceDeclineLineInput::from_slice(data, AdvanceDeclineLineParams);
8069 let out = advance_decline_line_with_kernel(&input, kernel).map_err(|e| {
8070 IndicatorDispatchError::ComputeFailed {
8071 indicator: "advance_decline_line".to_string(),
8072 details: e.to_string(),
8073 }
8074 })?;
8075 Ok(out.values)
8076 },
8077 )
8078}
8079
8080fn compute_didi_index_batch(
8081 req: IndicatorBatchRequest<'_>,
8082 output_id: &str,
8083) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8084 let data = extract_slice_input("didi_index", req.data, "close")?;
8085 let kernel = req.kernel.to_non_batch();
8086 collect_f64("didi_index", output_id, req.combos, data.len(), |params| {
8087 let short_length = get_usize_param("didi_index", params, "short_length", 3)?;
8088 let medium_length = get_usize_param("didi_index", params, "medium_length", 8)?;
8089 let long_length = get_usize_param("didi_index", params, "long_length", 20)?;
8090 let input = DidiIndexInput::from_slice(
8091 data,
8092 DidiIndexParams {
8093 short_length: Some(short_length),
8094 medium_length: Some(medium_length),
8095 long_length: Some(long_length),
8096 },
8097 );
8098 let out = didi_index_with_kernel(&input, kernel).map_err(|e| {
8099 IndicatorDispatchError::ComputeFailed {
8100 indicator: "didi_index".to_string(),
8101 details: e.to_string(),
8102 }
8103 })?;
8104 if output_id.eq_ignore_ascii_case("short") || output_id.eq_ignore_ascii_case("value") {
8105 return Ok(out.short);
8106 }
8107 if output_id.eq_ignore_ascii_case("long") {
8108 return Ok(out.long);
8109 }
8110 if output_id.eq_ignore_ascii_case("crossover") {
8111 return Ok(out.crossover);
8112 }
8113 if output_id.eq_ignore_ascii_case("crossunder") {
8114 return Ok(out.crossunder);
8115 }
8116 Err(IndicatorDispatchError::UnknownOutput {
8117 indicator: "didi_index".to_string(),
8118 output: output_id.to_string(),
8119 })
8120 })
8121}
8122
8123fn compute_absolute_strength_index_oscillator_batch(
8124 req: IndicatorBatchRequest<'_>,
8125 output_id: &str,
8126) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8127 let data = extract_slice_input("absolute_strength_index_oscillator", req.data, "close")?;
8128 let kernel = req.kernel.to_non_batch();
8129 collect_f64(
8130 "absolute_strength_index_oscillator",
8131 output_id,
8132 req.combos,
8133 data.len(),
8134 |params| {
8135 let ema_length = get_usize_param(
8136 "absolute_strength_index_oscillator",
8137 params,
8138 "ema_length",
8139 21,
8140 )?;
8141 let signal_length = get_usize_param(
8142 "absolute_strength_index_oscillator",
8143 params,
8144 "signal_length",
8145 34,
8146 )?;
8147 let input = AbsoluteStrengthIndexOscillatorInput::from_slice(
8148 data,
8149 AbsoluteStrengthIndexOscillatorParams {
8150 ema_length: Some(ema_length),
8151 signal_length: Some(signal_length),
8152 },
8153 );
8154 let out =
8155 absolute_strength_index_oscillator_with_kernel(&input, kernel).map_err(|e| {
8156 IndicatorDispatchError::ComputeFailed {
8157 indicator: "absolute_strength_index_oscillator".to_string(),
8158 details: e.to_string(),
8159 }
8160 })?;
8161 if output_id.eq_ignore_ascii_case("oscillator")
8162 || output_id.eq_ignore_ascii_case("indicator")
8163 || output_id.eq_ignore_ascii_case("value")
8164 {
8165 return Ok(out.oscillator);
8166 }
8167 if output_id.eq_ignore_ascii_case("signal") {
8168 return Ok(out.signal);
8169 }
8170 if output_id.eq_ignore_ascii_case("histogram") || output_id.eq_ignore_ascii_case("hist")
8171 {
8172 return Ok(out.histogram);
8173 }
8174 Err(IndicatorDispatchError::UnknownOutput {
8175 indicator: "absolute_strength_index_oscillator".to_string(),
8176 output: output_id.to_string(),
8177 })
8178 },
8179 )
8180}
8181
8182fn compute_adaptive_bandpass_trigger_oscillator_batch(
8183 req: IndicatorBatchRequest<'_>,
8184 output_id: &str,
8185) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8186 let data = extract_slice_input("adaptive_bandpass_trigger_oscillator", req.data, "close")?;
8187 let kernel = req.kernel.to_non_batch();
8188 collect_f64(
8189 "adaptive_bandpass_trigger_oscillator",
8190 output_id,
8191 req.combos,
8192 data.len(),
8193 |params| {
8194 let delta =
8195 get_f64_param("adaptive_bandpass_trigger_oscillator", params, "delta", 0.1)?;
8196 let alpha = get_f64_param(
8197 "adaptive_bandpass_trigger_oscillator",
8198 params,
8199 "alpha",
8200 0.07,
8201 )?;
8202 let input = AdaptiveBandpassTriggerOscillatorInput::from_slice(
8203 data,
8204 AdaptiveBandpassTriggerOscillatorParams {
8205 delta: Some(delta),
8206 alpha: Some(alpha),
8207 },
8208 );
8209 let out =
8210 adaptive_bandpass_trigger_oscillator_with_kernel(&input, kernel).map_err(|e| {
8211 IndicatorDispatchError::ComputeFailed {
8212 indicator: "adaptive_bandpass_trigger_oscillator".to_string(),
8213 details: e.to_string(),
8214 }
8215 })?;
8216 match output_id {
8217 "in_phase" => Ok(out.in_phase),
8218 "lead" => Ok(out.lead),
8219 _ => Err(IndicatorDispatchError::UnknownOutput {
8220 indicator: "adaptive_bandpass_trigger_oscillator".to_string(),
8221 output: output_id.to_string(),
8222 }),
8223 }
8224 },
8225 )
8226}
8227
8228fn compute_premier_rsi_oscillator_batch(
8229 req: IndicatorBatchRequest<'_>,
8230 output_id: &str,
8231) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8232 expect_value_output("premier_rsi_oscillator", output_id)?;
8233 let data = extract_slice_input("premier_rsi_oscillator", req.data, "close")?;
8234 let kernel = req.kernel.to_non_batch();
8235 collect_f64(
8236 "premier_rsi_oscillator",
8237 output_id,
8238 req.combos,
8239 data.len(),
8240 |params| {
8241 let rsi_length = get_usize_param("premier_rsi_oscillator", params, "rsi_length", 14)?;
8242 let stoch_length =
8243 get_usize_param("premier_rsi_oscillator", params, "stoch_length", 8)?;
8244 let smooth_length =
8245 get_usize_param("premier_rsi_oscillator", params, "smooth_length", 25)?;
8246 let input = PremierRsiOscillatorInput::from_slice(
8247 data,
8248 PremierRsiOscillatorParams {
8249 rsi_length: Some(rsi_length),
8250 stoch_length: Some(stoch_length),
8251 smooth_length: Some(smooth_length),
8252 },
8253 );
8254 let out = premier_rsi_oscillator_with_kernel(&input, kernel).map_err(|e| {
8255 IndicatorDispatchError::ComputeFailed {
8256 indicator: "premier_rsi_oscillator".to_string(),
8257 details: e.to_string(),
8258 }
8259 })?;
8260 Ok(out.values)
8261 },
8262 )
8263}
8264
8265fn compute_multi_length_stochastic_average_batch(
8266 req: IndicatorBatchRequest<'_>,
8267 output_id: &str,
8268) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8269 expect_value_output("multi_length_stochastic_average", output_id)?;
8270 let data_len = match req.data {
8271 IndicatorDataRef::Slice { values } => values.len(),
8272 IndicatorDataRef::Candles { candles, source } => {
8273 source_type(candles, source.unwrap_or("close")).len()
8274 }
8275 _ => {
8276 return Err(IndicatorDispatchError::MissingRequiredInput {
8277 indicator: "multi_length_stochastic_average".to_string(),
8278 input: IndicatorInputKind::Candles,
8279 });
8280 }
8281 };
8282 let kernel = req.kernel.to_non_batch();
8283 collect_f64(
8284 "multi_length_stochastic_average",
8285 output_id,
8286 req.combos,
8287 data_len,
8288 |params| {
8289 let source =
8290 get_enum_param("multi_length_stochastic_average", params, "source", "close")?;
8291 let length = get_usize_param("multi_length_stochastic_average", params, "length", 14)?;
8292 let presmooth =
8293 get_usize_param("multi_length_stochastic_average", params, "presmooth", 10)?;
8294 let premethod = get_enum_param(
8295 "multi_length_stochastic_average",
8296 params,
8297 "premethod",
8298 "sma",
8299 )?;
8300 let postsmooth =
8301 get_usize_param("multi_length_stochastic_average", params, "postsmooth", 10)?;
8302 let postmethod = get_enum_param(
8303 "multi_length_stochastic_average",
8304 params,
8305 "postmethod",
8306 "sma",
8307 )?;
8308 let data = match req.data {
8309 IndicatorDataRef::Slice { values } => values,
8310 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
8311 _ => unreachable!(),
8312 };
8313 let input = MultiLengthStochasticAverageInput::from_slice(
8314 data,
8315 MultiLengthStochasticAverageParams {
8316 length: Some(length),
8317 presmooth: Some(presmooth),
8318 premethod: Some(premethod),
8319 postsmooth: Some(postsmooth),
8320 postmethod: Some(postmethod),
8321 },
8322 );
8323 let out = multi_length_stochastic_average_with_kernel(&input, kernel).map_err(|e| {
8324 IndicatorDispatchError::ComputeFailed {
8325 indicator: "multi_length_stochastic_average".to_string(),
8326 details: e.to_string(),
8327 }
8328 })?;
8329 Ok(out.values)
8330 },
8331 )
8332}
8333
8334fn compute_hull_butterfly_oscillator_batch(
8335 req: IndicatorBatchRequest<'_>,
8336 output_id: &str,
8337) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8338 let data_len = match req.data {
8339 IndicatorDataRef::Slice { values } => values.len(),
8340 IndicatorDataRef::Candles { candles, source } => {
8341 source_type(candles, source.unwrap_or("close")).len()
8342 }
8343 _ => {
8344 return Err(IndicatorDispatchError::MissingRequiredInput {
8345 indicator: "hull_butterfly_oscillator".to_string(),
8346 input: IndicatorInputKind::Candles,
8347 });
8348 }
8349 };
8350 let kernel = req.kernel.to_non_batch();
8351 collect_f64(
8352 "hull_butterfly_oscillator",
8353 output_id,
8354 req.combos,
8355 data_len,
8356 |params| {
8357 let source = get_enum_param("hull_butterfly_oscillator", params, "source", "close")?;
8358 let length = get_usize_param("hull_butterfly_oscillator", params, "length", 14)?;
8359 let mult = get_f64_param("hull_butterfly_oscillator", params, "mult", 2.0)?;
8360 let data = match req.data {
8361 IndicatorDataRef::Slice { values } => values,
8362 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
8363 _ => unreachable!(),
8364 };
8365 let input = HullButterflyOscillatorInput::from_slice(
8366 data,
8367 HullButterflyOscillatorParams {
8368 length: Some(length),
8369 mult: Some(mult),
8370 },
8371 );
8372 let out = hull_butterfly_oscillator_with_kernel(&input, kernel).map_err(|e| {
8373 IndicatorDispatchError::ComputeFailed {
8374 indicator: "hull_butterfly_oscillator".to_string(),
8375 details: e.to_string(),
8376 }
8377 })?;
8378 match output_id {
8379 "oscillator" => Ok(out.oscillator),
8380 "cumulative_mean" => Ok(out.cumulative_mean),
8381 "signal" => Ok(out.signal),
8382 _ => Err(IndicatorDispatchError::UnknownOutput {
8383 indicator: "hull_butterfly_oscillator".to_string(),
8384 output: output_id.to_string(),
8385 }),
8386 }
8387 },
8388 )
8389}
8390
8391fn compute_fibonacci_trailing_stop_batch(
8392 req: IndicatorBatchRequest<'_>,
8393 output_id: &str,
8394) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8395 let (high, low, close) = extract_ohlc_input("fibonacci_trailing_stop", req.data)?;
8396 let kernel = req.kernel.to_non_batch();
8397 collect_f64(
8398 "fibonacci_trailing_stop",
8399 output_id,
8400 req.combos,
8401 close.len(),
8402 |params| {
8403 let left_bars = get_usize_param("fibonacci_trailing_stop", params, "left_bars", 20)?;
8404 let right_bars = get_usize_param("fibonacci_trailing_stop", params, "right_bars", 1)?;
8405 let level = get_f64_param("fibonacci_trailing_stop", params, "level", -0.382)?;
8406 let trigger = get_enum_param("fibonacci_trailing_stop", params, "trigger", "close")?;
8407 let input = FibonacciTrailingStopInput::from_slices(
8408 high,
8409 low,
8410 close,
8411 FibonacciTrailingStopParams {
8412 left_bars: Some(left_bars),
8413 right_bars: Some(right_bars),
8414 level: Some(level),
8415 trigger: Some(trigger),
8416 },
8417 );
8418 let out = fibonacci_trailing_stop_with_kernel(&input, kernel).map_err(|e| {
8419 IndicatorDispatchError::ComputeFailed {
8420 indicator: "fibonacci_trailing_stop".to_string(),
8421 details: e.to_string(),
8422 }
8423 })?;
8424 if output_id.eq_ignore_ascii_case("trailing_stop")
8425 || output_id.eq_ignore_ascii_case("value")
8426 {
8427 return Ok(out.trailing_stop);
8428 }
8429 if output_id.eq_ignore_ascii_case("long_stop") {
8430 return Ok(out.long_stop);
8431 }
8432 if output_id.eq_ignore_ascii_case("short_stop") {
8433 return Ok(out.short_stop);
8434 }
8435 if output_id.eq_ignore_ascii_case("direction") {
8436 return Ok(out.direction);
8437 }
8438 Err(IndicatorDispatchError::UnknownOutput {
8439 indicator: "fibonacci_trailing_stop".to_string(),
8440 output: output_id.to_string(),
8441 })
8442 },
8443 )
8444}
8445
8446fn compute_fibonacci_entry_bands_batch(
8447 req: IndicatorBatchRequest<'_>,
8448 output_id: &str,
8449) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8450 let (open, high, low, close) = extract_ohlc_full_input("fibonacci_entry_bands", req.data)?;
8451 let kernel = req.kernel.to_non_batch();
8452 collect_f64(
8453 "fibonacci_entry_bands",
8454 output_id,
8455 req.combos,
8456 close.len(),
8457 |params| {
8458 let source = get_enum_param("fibonacci_entry_bands", params, "source", "hlc3")?;
8459 let length = get_usize_param("fibonacci_entry_bands", params, "length", 21)?;
8460 let atr_length = get_usize_param("fibonacci_entry_bands", params, "atr_length", 14)?;
8461 let use_atr = get_bool_param("fibonacci_entry_bands", params, "use_atr", true)?;
8462 let tp_aggressiveness =
8463 get_enum_param("fibonacci_entry_bands", params, "tp_aggressiveness", "low")?;
8464 let input = FibonacciEntryBandsInput::from_slices(
8465 open,
8466 high,
8467 low,
8468 close,
8469 FibonacciEntryBandsParams {
8470 source: Some(source),
8471 length: Some(length),
8472 atr_length: Some(atr_length),
8473 use_atr: Some(use_atr),
8474 tp_aggressiveness: Some(tp_aggressiveness),
8475 },
8476 );
8477 let out = fibonacci_entry_bands_with_kernel(&input, kernel).map_err(|e| {
8478 IndicatorDispatchError::ComputeFailed {
8479 indicator: "fibonacci_entry_bands".to_string(),
8480 details: e.to_string(),
8481 }
8482 })?;
8483 if output_id.eq_ignore_ascii_case("middle") || output_id.eq_ignore_ascii_case("basis") {
8484 return Ok(out.basis);
8485 }
8486 if output_id.eq_ignore_ascii_case("trend") {
8487 return Ok(out.trend);
8488 }
8489 if output_id.eq_ignore_ascii_case("upper_0618") {
8490 return Ok(out.upper_0618);
8491 }
8492 if output_id.eq_ignore_ascii_case("upper_1000") {
8493 return Ok(out.upper_1000);
8494 }
8495 if output_id.eq_ignore_ascii_case("upper_1618") {
8496 return Ok(out.upper_1618);
8497 }
8498 if output_id.eq_ignore_ascii_case("upper_2618") {
8499 return Ok(out.upper_2618);
8500 }
8501 if output_id.eq_ignore_ascii_case("lower_0618") {
8502 return Ok(out.lower_0618);
8503 }
8504 if output_id.eq_ignore_ascii_case("lower_1000") {
8505 return Ok(out.lower_1000);
8506 }
8507 if output_id.eq_ignore_ascii_case("lower_1618") {
8508 return Ok(out.lower_1618);
8509 }
8510 if output_id.eq_ignore_ascii_case("lower_2618") {
8511 return Ok(out.lower_2618);
8512 }
8513 if output_id.eq_ignore_ascii_case("tp_long_band") {
8514 return Ok(out.tp_long_band);
8515 }
8516 if output_id.eq_ignore_ascii_case("tp_short_band") {
8517 return Ok(out.tp_short_band);
8518 }
8519 if output_id.eq_ignore_ascii_case("go_long")
8520 || output_id.eq_ignore_ascii_case("long_entry")
8521 {
8522 return Ok(out.long_entry);
8523 }
8524 if output_id.eq_ignore_ascii_case("go_short")
8525 || output_id.eq_ignore_ascii_case("short_entry")
8526 {
8527 return Ok(out.short_entry);
8528 }
8529 if output_id.eq_ignore_ascii_case("rejection_long") {
8530 return Ok(out.rejection_long);
8531 }
8532 if output_id.eq_ignore_ascii_case("rejection_short") {
8533 return Ok(out.rejection_short);
8534 }
8535 if output_id.eq_ignore_ascii_case("long_bounce") {
8536 return Ok(out.long_bounce);
8537 }
8538 if output_id.eq_ignore_ascii_case("short_bounce") {
8539 return Ok(out.short_bounce);
8540 }
8541 Err(IndicatorDispatchError::UnknownOutput {
8542 indicator: "fibonacci_entry_bands".to_string(),
8543 output: output_id.to_string(),
8544 })
8545 },
8546 )
8547}
8548
8549fn compute_volume_energy_reservoirs_batch(
8550 req: IndicatorBatchRequest<'_>,
8551 output_id: &str,
8552) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8553 let (_, high, low, close, volume) =
8554 extract_ohlcv_full_input("volume_energy_reservoirs", req.data)?;
8555 let kernel = req.kernel.to_non_batch();
8556 collect_f64(
8557 "volume_energy_reservoirs",
8558 output_id,
8559 req.combos,
8560 close.len(),
8561 |params| {
8562 let length = get_usize_param("volume_energy_reservoirs", params, "length", 20)?;
8563 let sensitivity =
8564 get_f64_param("volume_energy_reservoirs", params, "sensitivity", 1.5)?;
8565 let input = VolumeEnergyReservoirsInput::from_slices(
8566 high,
8567 low,
8568 close,
8569 volume,
8570 VolumeEnergyReservoirsParams {
8571 length: Some(length),
8572 sensitivity: Some(sensitivity),
8573 },
8574 );
8575 let out = volume_energy_reservoirs_with_kernel(&input, kernel).map_err(|e| {
8576 IndicatorDispatchError::ComputeFailed {
8577 indicator: "volume_energy_reservoirs".to_string(),
8578 details: e.to_string(),
8579 }
8580 })?;
8581 match output_id {
8582 "momentum" | "value" => Ok(out.momentum),
8583 "reservoir" => Ok(out.reservoir),
8584 "squeeze_active" => Ok(out.squeeze_active),
8585 "squeeze_start" => Ok(out.squeeze_start),
8586 "range_high" => Ok(out.range_high),
8587 "range_low" => Ok(out.range_low),
8588 _ => Err(IndicatorDispatchError::UnknownOutput {
8589 indicator: "volume_energy_reservoirs".to_string(),
8590 output: output_id.to_string(),
8591 }),
8592 }
8593 },
8594 )
8595}
8596
8597fn compute_neighboring_trailing_stop_batch(
8598 req: IndicatorBatchRequest<'_>,
8599 output_id: &str,
8600) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8601 let (high, low, close) = extract_ohlc_input("neighboring_trailing_stop", req.data)?;
8602 let kernel = req.kernel.to_non_batch();
8603 collect_f64(
8604 "neighboring_trailing_stop",
8605 output_id,
8606 req.combos,
8607 close.len(),
8608 |params| {
8609 let buffer_size =
8610 get_usize_param("neighboring_trailing_stop", params, "buffer_size", 200)?;
8611 let k = get_usize_param("neighboring_trailing_stop", params, "k", 50)?;
8612 let percentile =
8613 get_f64_param("neighboring_trailing_stop", params, "percentile", 90.0)?;
8614 let smooth = get_usize_param("neighboring_trailing_stop", params, "smooth", 5)?;
8615 let input = NeighboringTrailingStopInput::from_slices(
8616 high,
8617 low,
8618 close,
8619 NeighboringTrailingStopParams {
8620 buffer_size: Some(buffer_size),
8621 k: Some(k),
8622 percentile: Some(percentile),
8623 smooth: Some(smooth),
8624 },
8625 );
8626 let out = neighboring_trailing_stop_with_kernel(&input, kernel).map_err(|e| {
8627 IndicatorDispatchError::ComputeFailed {
8628 indicator: "neighboring_trailing_stop".to_string(),
8629 details: e.to_string(),
8630 }
8631 })?;
8632 match output_id {
8633 "trailing_stop" | "value" => Ok(out.trailing_stop),
8634 "bullish_band" => Ok(out.bullish_band),
8635 "bearish_band" => Ok(out.bearish_band),
8636 "direction" => Ok(out.direction),
8637 "discovery_bull" => Ok(out.discovery_bull),
8638 "discovery_bear" => Ok(out.discovery_bear),
8639 _ => Err(IndicatorDispatchError::UnknownOutput {
8640 indicator: "neighboring_trailing_stop".to_string(),
8641 output: output_id.to_string(),
8642 }),
8643 }
8644 },
8645 )
8646}
8647
8648fn compute_grover_llorens_cycle_oscillator_batch(
8649 req: IndicatorBatchRequest<'_>,
8650 output_id: &str,
8651) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8652 expect_value_output("grover_llorens_cycle_oscillator", output_id)?;
8653 let (open, high, low, close) =
8654 extract_ohlc_full_input("grover_llorens_cycle_oscillator", req.data)?;
8655 let kernel = req.kernel.to_non_batch();
8656 collect_f64(
8657 "grover_llorens_cycle_oscillator",
8658 output_id,
8659 req.combos,
8660 close.len(),
8661 |params| {
8662 let length = get_usize_param("grover_llorens_cycle_oscillator", params, "length", 100)?;
8663 let mult = get_f64_param("grover_llorens_cycle_oscillator", params, "mult", 10.0)?;
8664 let source = match find_param(params, "source") {
8665 Some(ParamValue::EnumString(v)) => (*v).to_string(),
8666 Some(_) => {
8667 return Err(IndicatorDispatchError::InvalidParam {
8668 indicator: "grover_llorens_cycle_oscillator".to_string(),
8669 key: "source".to_string(),
8670 reason: "expected string".to_string(),
8671 });
8672 }
8673 None => "close".to_string(),
8674 };
8675 let smooth = get_bool_param("grover_llorens_cycle_oscillator", params, "smooth", true)?;
8676 let rsi_period =
8677 get_usize_param("grover_llorens_cycle_oscillator", params, "rsi_period", 20)?;
8678 let input = GroverLlorensCycleOscillatorInput::from_slices(
8679 open,
8680 high,
8681 low,
8682 close,
8683 GroverLlorensCycleOscillatorParams {
8684 length: Some(length),
8685 mult: Some(mult),
8686 source: Some(source),
8687 smooth: Some(smooth),
8688 rsi_period: Some(rsi_period),
8689 },
8690 );
8691 let out = grover_llorens_cycle_oscillator_with_kernel(&input, kernel).map_err(|e| {
8692 IndicatorDispatchError::ComputeFailed {
8693 indicator: "grover_llorens_cycle_oscillator".to_string(),
8694 details: e.to_string(),
8695 }
8696 })?;
8697 Ok(out.values)
8698 },
8699 )
8700}
8701
8702fn compute_ehlers_autocorrelation_periodogram_batch(
8703 req: IndicatorBatchRequest<'_>,
8704 output_id: &str,
8705) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8706 let data = extract_slice_input("ehlers_autocorrelation_periodogram", req.data, "close")?;
8707 let kernel = req.kernel.to_non_batch();
8708 collect_f64(
8709 "ehlers_autocorrelation_periodogram",
8710 output_id,
8711 req.combos,
8712 data.len(),
8713 |params| {
8714 let min_period = get_usize_param(
8715 "ehlers_autocorrelation_periodogram",
8716 params,
8717 "min_period",
8718 8,
8719 )?;
8720 let max_period = get_usize_param(
8721 "ehlers_autocorrelation_periodogram",
8722 params,
8723 "max_period",
8724 48,
8725 )?;
8726 let avg_length = get_usize_param(
8727 "ehlers_autocorrelation_periodogram",
8728 params,
8729 "avg_length",
8730 3,
8731 )?;
8732 let enhance = get_bool_param(
8733 "ehlers_autocorrelation_periodogram",
8734 params,
8735 "enhance",
8736 true,
8737 )?;
8738 let input = EhlersAutocorrelationPeriodogramInput::from_slice(
8739 data,
8740 EhlersAutocorrelationPeriodogramParams {
8741 min_period: Some(min_period),
8742 max_period: Some(max_period),
8743 avg_length: Some(avg_length),
8744 enhance: Some(enhance),
8745 },
8746 );
8747 let out =
8748 ehlers_autocorrelation_periodogram_with_kernel(&input, kernel).map_err(|e| {
8749 IndicatorDispatchError::ComputeFailed {
8750 indicator: "ehlers_autocorrelation_periodogram".to_string(),
8751 details: e.to_string(),
8752 }
8753 })?;
8754 if output_id.eq_ignore_ascii_case("dominant_cycle")
8755 || output_id.eq_ignore_ascii_case("value")
8756 {
8757 return Ok(out.dominant_cycle);
8758 }
8759 if output_id.eq_ignore_ascii_case("normalized_power") {
8760 return Ok(out.normalized_power);
8761 }
8762 Err(IndicatorDispatchError::UnknownOutput {
8763 indicator: "ehlers_autocorrelation_periodogram".to_string(),
8764 output: output_id.to_string(),
8765 })
8766 },
8767 )
8768}
8769
8770fn compute_ehlers_linear_extrapolation_predictor_batch(
8771 req: IndicatorBatchRequest<'_>,
8772 output_id: &str,
8773) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8774 let data = extract_slice_input("ehlers_linear_extrapolation_predictor", req.data, "close")?;
8775 let kernel = req.kernel.to_non_batch();
8776 collect_f64(
8777 "ehlers_linear_extrapolation_predictor",
8778 output_id,
8779 req.combos,
8780 data.len(),
8781 |params| {
8782 let high_pass_length = get_usize_param(
8783 "ehlers_linear_extrapolation_predictor",
8784 params,
8785 "high_pass_length",
8786 125,
8787 )?;
8788 let low_pass_length = get_usize_param(
8789 "ehlers_linear_extrapolation_predictor",
8790 params,
8791 "low_pass_length",
8792 12,
8793 )?;
8794 let gain = get_f64_param("ehlers_linear_extrapolation_predictor", params, "gain", 0.7)?;
8795 let bars_forward = get_usize_param(
8796 "ehlers_linear_extrapolation_predictor",
8797 params,
8798 "bars_forward",
8799 5,
8800 )?;
8801 let signal_mode = get_enum_param(
8802 "ehlers_linear_extrapolation_predictor",
8803 params,
8804 "signal_mode",
8805 "predict_filter_crosses",
8806 )?;
8807 let input = EhlersLinearExtrapolationPredictorInput::from_slice(
8808 data,
8809 EhlersLinearExtrapolationPredictorParams {
8810 high_pass_length: Some(high_pass_length),
8811 low_pass_length: Some(low_pass_length),
8812 gain: Some(gain),
8813 bars_forward: Some(bars_forward),
8814 signal_mode: Some(signal_mode),
8815 },
8816 );
8817 let out =
8818 ehlers_linear_extrapolation_predictor_with_kernel(&input, kernel).map_err(|e| {
8819 IndicatorDispatchError::ComputeFailed {
8820 indicator: "ehlers_linear_extrapolation_predictor".to_string(),
8821 details: e.to_string(),
8822 }
8823 })?;
8824 if output_id.eq_ignore_ascii_case("prediction")
8825 || output_id.eq_ignore_ascii_case("value")
8826 {
8827 return Ok(out.prediction);
8828 }
8829 if output_id.eq_ignore_ascii_case("filter") {
8830 return Ok(out.filter);
8831 }
8832 if output_id.eq_ignore_ascii_case("state") {
8833 return Ok(out.state);
8834 }
8835 if output_id.eq_ignore_ascii_case("go_long") {
8836 return Ok(out.go_long);
8837 }
8838 if output_id.eq_ignore_ascii_case("go_short") {
8839 return Ok(out.go_short);
8840 }
8841 Err(IndicatorDispatchError::UnknownOutput {
8842 indicator: "ehlers_linear_extrapolation_predictor".to_string(),
8843 output: output_id.to_string(),
8844 })
8845 },
8846 )
8847}
8848
8849fn compute_decisionpoint_breadth_swenlin_trading_oscillator_batch(
8850 req: IndicatorBatchRequest<'_>,
8851 output_id: &str,
8852) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8853 expect_value_output(
8854 "decisionpoint_breadth_swenlin_trading_oscillator",
8855 output_id,
8856 )?;
8857 let (advancing, declining) =
8858 extract_high_low_input("decisionpoint_breadth_swenlin_trading_oscillator", req.data)?;
8859 let kernel = req.kernel.to_non_batch();
8860 collect_f64(
8861 "decisionpoint_breadth_swenlin_trading_oscillator",
8862 output_id,
8863 req.combos,
8864 advancing.len(),
8865 |_params| {
8866 let input = DecisionPointBreadthSwenlinTradingOscillatorInput::from_slices(
8867 advancing,
8868 declining,
8869 DecisionPointBreadthSwenlinTradingOscillatorParams,
8870 );
8871 let out = decisionpoint_breadth_swenlin_trading_oscillator_with_kernel(&input, kernel)
8872 .map_err(|e| IndicatorDispatchError::ComputeFailed {
8873 indicator: "decisionpoint_breadth_swenlin_trading_oscillator".to_string(),
8874 details: e.to_string(),
8875 })?;
8876 Ok(out.values)
8877 },
8878 )
8879}
8880
8881fn compute_velocity_acceleration_indicator_batch(
8882 req: IndicatorBatchRequest<'_>,
8883 output_id: &str,
8884) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8885 expect_value_output("velocity_acceleration_indicator", output_id)?;
8886 let data_len = match req.data {
8887 IndicatorDataRef::Slice { values } => values.len(),
8888 IndicatorDataRef::Candles { candles, source } => {
8889 source_type(candles, source.unwrap_or("hlcc4")).len()
8890 }
8891 _ => {
8892 return Err(IndicatorDispatchError::MissingRequiredInput {
8893 indicator: "velocity_acceleration_indicator".to_string(),
8894 input: IndicatorInputKind::Candles,
8895 });
8896 }
8897 };
8898 let kernel = req.kernel.to_non_batch();
8899 collect_f64(
8900 "velocity_acceleration_indicator",
8901 output_id,
8902 req.combos,
8903 data_len,
8904 |params| {
8905 let source =
8906 get_enum_param("velocity_acceleration_indicator", params, "source", "hlcc4")?;
8907 let length = get_usize_param("velocity_acceleration_indicator", params, "length", 21)?;
8908 let smooth_length = get_usize_param(
8909 "velocity_acceleration_indicator",
8910 params,
8911 "smooth_length",
8912 5,
8913 )?;
8914 let data = match req.data {
8915 IndicatorDataRef::Slice { values } => values,
8916 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
8917 _ => unreachable!(),
8918 };
8919 let input = VelocityAccelerationIndicatorInput::from_slice(
8920 data,
8921 VelocityAccelerationIndicatorParams {
8922 length: Some(length),
8923 smooth_length: Some(smooth_length),
8924 },
8925 );
8926 let out = velocity_acceleration_indicator_with_kernel(&input, kernel).map_err(|e| {
8927 IndicatorDispatchError::ComputeFailed {
8928 indicator: "velocity_acceleration_indicator".to_string(),
8929 details: e.to_string(),
8930 }
8931 })?;
8932 Ok(out.values)
8933 },
8934 )
8935}
8936
8937fn compute_normalized_resonator_batch(
8938 req: IndicatorBatchRequest<'_>,
8939 output_id: &str,
8940) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8941 let data_len = match req.data {
8942 IndicatorDataRef::Slice { values } => values.len(),
8943 IndicatorDataRef::Candles { candles, source } => {
8944 source_type(candles, source.unwrap_or("hl2")).len()
8945 }
8946 _ => {
8947 return Err(IndicatorDispatchError::MissingRequiredInput {
8948 indicator: "normalized_resonator".to_string(),
8949 input: IndicatorInputKind::Candles,
8950 });
8951 }
8952 };
8953 let kernel = req.kernel.to_non_batch();
8954 collect_f64(
8955 "normalized_resonator",
8956 output_id,
8957 req.combos,
8958 data_len,
8959 |params| {
8960 let source = get_enum_param("normalized_resonator", params, "source", "hl2")?;
8961 let period = get_usize_param("normalized_resonator", params, "period", 100)?;
8962 let delta = get_f64_param("normalized_resonator", params, "delta", 0.5)?;
8963 let lookback_mult =
8964 get_f64_param("normalized_resonator", params, "lookback_mult", 1.0)?;
8965 let signal_length =
8966 get_usize_param("normalized_resonator", params, "signal_length", 9)?;
8967 let data = match req.data {
8968 IndicatorDataRef::Slice { values } => values,
8969 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
8970 _ => unreachable!(),
8971 };
8972 let input = NormalizedResonatorInput::from_slice(
8973 data,
8974 NormalizedResonatorParams {
8975 period: Some(period),
8976 delta: Some(delta),
8977 lookback_mult: Some(lookback_mult),
8978 signal_length: Some(signal_length),
8979 },
8980 );
8981 let out = normalized_resonator_with_kernel(&input, kernel).map_err(|e| {
8982 IndicatorDispatchError::ComputeFailed {
8983 indicator: "normalized_resonator".to_string(),
8984 details: e.to_string(),
8985 }
8986 })?;
8987 match output_id {
8988 "oscillator" => Ok(out.oscillator),
8989 "signal" => Ok(out.signal),
8990 _ => Err(IndicatorDispatchError::UnknownOutput {
8991 indicator: "normalized_resonator".to_string(),
8992 output: output_id.to_string(),
8993 }),
8994 }
8995 },
8996 )
8997}
8998
8999fn compute_monotonicity_index_batch(
9000 req: IndicatorBatchRequest<'_>,
9001 output_id: &str,
9002) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9003 let data_len = match req.data {
9004 IndicatorDataRef::Slice { values } => values.len(),
9005 IndicatorDataRef::Candles { candles, source } => {
9006 source_type(candles, source.unwrap_or("close")).len()
9007 }
9008 _ => {
9009 return Err(IndicatorDispatchError::MissingRequiredInput {
9010 indicator: "monotonicity_index".to_string(),
9011 input: IndicatorInputKind::Candles,
9012 });
9013 }
9014 };
9015 let kernel = req.kernel.to_non_batch();
9016 collect_f64(
9017 "monotonicity_index",
9018 output_id,
9019 req.combos,
9020 data_len,
9021 |params| {
9022 let source = get_enum_param("monotonicity_index", params, "source", "close")?;
9023 let length = get_usize_param("monotonicity_index", params, "length", 20)?;
9024 let mode = get_enum_param("monotonicity_index", params, "mode", "efficiency")?;
9025 let index_smooth = get_usize_param("monotonicity_index", params, "index_smooth", 5)?;
9026 let mode = MonotonicityIndexMode::parse(&mode).ok_or_else(|| {
9027 IndicatorDispatchError::InvalidParam {
9028 indicator: "monotonicity_index".to_string(),
9029 key: "mode".to_string(),
9030 reason: format!("invalid mode: {mode}"),
9031 }
9032 })?;
9033 let data = match req.data {
9034 IndicatorDataRef::Slice { values } => values,
9035 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
9036 _ => unreachable!(),
9037 };
9038 let input = MonotonicityIndexInput::from_slice(
9039 data,
9040 MonotonicityIndexParams {
9041 length: Some(length),
9042 mode: Some(mode),
9043 index_smooth: Some(index_smooth),
9044 },
9045 );
9046 let out = monotonicity_index_with_kernel(&input, kernel).map_err(|e| {
9047 IndicatorDispatchError::ComputeFailed {
9048 indicator: "monotonicity_index".to_string(),
9049 details: e.to_string(),
9050 }
9051 })?;
9052 match output_id {
9053 "index" => Ok(out.index),
9054 "cumulative_mean" => Ok(out.cumulative_mean),
9055 "upper_bound" => Ok(out.upper_bound),
9056 _ => Err(IndicatorDispatchError::UnknownOutput {
9057 indicator: "monotonicity_index".to_string(),
9058 output: output_id.to_string(),
9059 }),
9060 }
9061 },
9062 )
9063}
9064
9065fn compute_half_causal_estimator_batch(
9066 req: IndicatorBatchRequest<'_>,
9067 output_id: &str,
9068) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9069 if output_id != "estimate" && output_id != "expected_value" {
9070 return Err(IndicatorDispatchError::UnknownOutput {
9071 indicator: "half_causal_estimator".to_string(),
9072 output: output_id.to_string(),
9073 });
9074 }
9075
9076 let data_len = match req.data {
9077 IndicatorDataRef::Slice { values } => values.len(),
9078 IndicatorDataRef::Candles { candles, .. } => candles.close.len(),
9079 _ => {
9080 return Err(IndicatorDispatchError::MissingRequiredInput {
9081 indicator: "half_causal_estimator".to_string(),
9082 input: IndicatorInputKind::Candles,
9083 });
9084 }
9085 };
9086 let kernel = req.kernel.to_non_batch();
9087 collect_f64(
9088 "half_causal_estimator",
9089 output_id,
9090 req.combos,
9091 data_len,
9092 |params| {
9093 let slots_per_day = get_usize_param_with_aliases(
9094 "half_causal_estimator",
9095 params,
9096 &["slots_per_day"],
9097 0,
9098 )?;
9099 let data_period = get_usize_param("half_causal_estimator", params, "data_period", 5)?;
9100 let filter_length =
9101 get_usize_param("half_causal_estimator", params, "filter_length", 20)?;
9102 let kernel_width =
9103 get_f64_param("half_causal_estimator", params, "kernel_width", 20.0)?;
9104 let maximum_confidence_adjust = get_f64_param(
9105 "half_causal_estimator",
9106 params,
9107 "maximum_confidence_adjust",
9108 100.0,
9109 )?;
9110 let extra_smoothing =
9111 get_usize_param("half_causal_estimator", params, "extra_smoothing", 0)?;
9112 let enable_expected_value = get_bool_param(
9113 "half_causal_estimator",
9114 params,
9115 "enable_expected_value",
9116 false,
9117 )?;
9118 let source = get_enum_param("half_causal_estimator", params, "source", "volume")?;
9119 let kernel_type = match get_enum_param(
9120 "half_causal_estimator",
9121 params,
9122 "kernel_type",
9123 "epanechnikov",
9124 )?
9125 .to_ascii_lowercase()
9126 .as_str()
9127 {
9128 "gaussian" => HalfCausalEstimatorKernelType::Gaussian,
9129 "epanechnikov" => HalfCausalEstimatorKernelType::Epanechnikov,
9130 "triangular" => HalfCausalEstimatorKernelType::Triangular,
9131 "sinc" => HalfCausalEstimatorKernelType::Sinc,
9132 other => {
9133 return Err(IndicatorDispatchError::InvalidParam {
9134 indicator: "half_causal_estimator".to_string(),
9135 key: "kernel_type".to_string(),
9136 reason: format!("unsupported value '{other}'"),
9137 })
9138 }
9139 };
9140 let confidence_adjust = match get_enum_param(
9141 "half_causal_estimator",
9142 params,
9143 "confidence_adjust",
9144 "symmetric",
9145 )?
9146 .to_ascii_lowercase()
9147 .as_str()
9148 {
9149 "symmetric" => HalfCausalEstimatorConfidenceAdjust::Symmetric,
9150 "linear" => HalfCausalEstimatorConfidenceAdjust::Linear,
9151 "none" => HalfCausalEstimatorConfidenceAdjust::None,
9152 other => {
9153 return Err(IndicatorDispatchError::InvalidParam {
9154 indicator: "half_causal_estimator".to_string(),
9155 key: "confidence_adjust".to_string(),
9156 reason: format!("unsupported value '{other}'"),
9157 })
9158 }
9159 };
9160
9161 let indicator_params = HalfCausalEstimatorParams {
9162 slots_per_day: if slots_per_day == 0 {
9163 None
9164 } else {
9165 Some(slots_per_day)
9166 },
9167 data_period: Some(data_period),
9168 filter_length: Some(filter_length),
9169 kernel_width: Some(kernel_width),
9170 kernel_type: Some(kernel_type),
9171 confidence_adjust: Some(confidence_adjust),
9172 maximum_confidence_adjust: Some(maximum_confidence_adjust),
9173 enable_expected_value: Some(enable_expected_value),
9174 extra_smoothing: Some(extra_smoothing),
9175 };
9176
9177 let out = match req.data {
9178 IndicatorDataRef::Slice { values } => {
9179 let input = HalfCausalEstimatorInput::from_slice(values, indicator_params);
9180 half_causal_estimator_with_kernel(&input, kernel)
9181 }
9182 IndicatorDataRef::Candles { candles, .. } => {
9183 let input =
9184 HalfCausalEstimatorInput::from_candles(candles, &source, indicator_params);
9185 half_causal_estimator_with_kernel(&input, kernel)
9186 }
9187 _ => unreachable!(),
9188 }
9189 .map_err(|e| IndicatorDispatchError::ComputeFailed {
9190 indicator: "half_causal_estimator".to_string(),
9191 details: e.to_string(),
9192 })?;
9193
9194 Ok(match output_id {
9195 "estimate" => out.estimate,
9196 "expected_value" => out.expected_value,
9197 _ => unreachable!(),
9198 })
9199 },
9200 )
9201}
9202
9203fn compute_historical_volatility_batch(
9204 req: IndicatorBatchRequest<'_>,
9205 output_id: &str,
9206) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9207 expect_value_output("historical_volatility", output_id)?;
9208 let data = extract_slice_input("historical_volatility", req.data, "close")?;
9209 let kernel = req.kernel.to_non_batch();
9210 collect_f64(
9211 "historical_volatility",
9212 output_id,
9213 req.combos,
9214 data.len(),
9215 |params| {
9216 let lookback = get_usize_param("historical_volatility", params, "lookback", 20)?;
9217 let annualization_days =
9218 get_f64_param("historical_volatility", params, "annualization_days", 250.0)?;
9219 let input = HistoricalVolatilityInput::from_slice(
9220 data,
9221 HistoricalVolatilityParams {
9222 lookback: Some(lookback),
9223 annualization_days: Some(annualization_days),
9224 },
9225 );
9226 let out = historical_volatility_with_kernel(&input, kernel).map_err(|e| {
9227 IndicatorDispatchError::ComputeFailed {
9228 indicator: "historical_volatility".to_string(),
9229 details: e.to_string(),
9230 }
9231 })?;
9232 Ok(out.values)
9233 },
9234 )
9235}
9236
9237fn compute_historical_volatility_percentile_batch(
9238 req: IndicatorBatchRequest<'_>,
9239 output_id: &str,
9240) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9241 let data = extract_slice_input("historical_volatility_percentile", req.data, "close")?;
9242 let kernel = req.kernel.to_non_batch();
9243 collect_f64(
9244 "historical_volatility_percentile",
9245 output_id,
9246 req.combos,
9247 data.len(),
9248 |params| {
9249 let length =
9250 get_usize_param("historical_volatility_percentile", params, "length", 20)?;
9251 let annual_length = get_usize_param(
9252 "historical_volatility_percentile",
9253 params,
9254 "annual_length",
9255 252,
9256 )?;
9257 let input = HistoricalVolatilityPercentileInput::from_slice(
9258 data,
9259 HistoricalVolatilityPercentileParams {
9260 length: Some(length),
9261 annual_length: Some(annual_length),
9262 },
9263 );
9264 let out = historical_volatility_percentile_with_kernel(&input, kernel).map_err(|e| {
9265 IndicatorDispatchError::ComputeFailed {
9266 indicator: "historical_volatility_percentile".to_string(),
9267 details: e.to_string(),
9268 }
9269 })?;
9270 if output_id.eq_ignore_ascii_case("hvp") {
9271 return Ok(out.hvp);
9272 }
9273 if output_id.eq_ignore_ascii_case("hvp_sma") {
9274 return Ok(out.hvp_sma);
9275 }
9276 Err(IndicatorDispatchError::UnknownOutput {
9277 indicator: "historical_volatility_percentile".to_string(),
9278 output: output_id.to_string(),
9279 })
9280 },
9281 )
9282}
9283
9284fn compute_volatility_ratio_adaptive_rsx_batch(
9285 req: IndicatorBatchRequest<'_>,
9286 output_id: &str,
9287) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9288 let data = extract_slice_input("volatility_ratio_adaptive_rsx", req.data, "close")?;
9289 let kernel = req.kernel.to_non_batch();
9290 collect_f64(
9291 "volatility_ratio_adaptive_rsx",
9292 output_id,
9293 req.combos,
9294 data.len(),
9295 |params| {
9296 let period =
9297 get_usize_param("volatility_ratio_adaptive_rsx", params, "period", 14)?;
9298 let speed = get_f64_param("volatility_ratio_adaptive_rsx", params, "speed", 0.5)?;
9299 let input = VolatilityRatioAdaptiveRsxInput::from_slice(
9300 data,
9301 VolatilityRatioAdaptiveRsxParams {
9302 period: Some(period),
9303 speed: Some(speed),
9304 },
9305 );
9306 let out = volatility_ratio_adaptive_rsx_with_kernel(&input, kernel).map_err(|e| {
9307 IndicatorDispatchError::ComputeFailed {
9308 indicator: "volatility_ratio_adaptive_rsx".to_string(),
9309 details: e.to_string(),
9310 }
9311 })?;
9312 if output_id.eq_ignore_ascii_case("line") || output_id.eq_ignore_ascii_case("value") {
9313 return Ok(out.line);
9314 }
9315 if output_id.eq_ignore_ascii_case("signal") {
9316 return Ok(out.signal);
9317 }
9318 Err(IndicatorDispatchError::UnknownOutput {
9319 indicator: "volatility_ratio_adaptive_rsx".to_string(),
9320 output: output_id.to_string(),
9321 })
9322 },
9323 )
9324}
9325
9326fn compute_on_balance_volume_oscillator_batch(
9327 req: IndicatorBatchRequest<'_>,
9328 output_id: &str,
9329) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9330 let (close, volume) =
9331 extract_close_volume_input("on_balance_volume_oscillator", req.data, "close")?;
9332 let kernel = req.kernel.to_non_batch();
9333 collect_f64(
9334 "on_balance_volume_oscillator",
9335 output_id,
9336 req.combos,
9337 close.len(),
9338 |params| {
9339 let obv_length =
9340 get_usize_param("on_balance_volume_oscillator", params, "obv_length", 20)?;
9341 let ema_length =
9342 get_usize_param("on_balance_volume_oscillator", params, "ema_length", 9)?;
9343 let input = OnBalanceVolumeOscillatorInput::from_slices(
9344 close,
9345 volume,
9346 OnBalanceVolumeOscillatorParams {
9347 obv_length: Some(obv_length),
9348 ema_length: Some(ema_length),
9349 },
9350 );
9351 let out = on_balance_volume_oscillator_with_kernel(&input, kernel).map_err(|e| {
9352 IndicatorDispatchError::ComputeFailed {
9353 indicator: "on_balance_volume_oscillator".to_string(),
9354 details: e.to_string(),
9355 }
9356 })?;
9357 if output_id.eq_ignore_ascii_case("line") || output_id.eq_ignore_ascii_case("value") {
9358 return Ok(out.line);
9359 }
9360 if output_id.eq_ignore_ascii_case("signal") {
9361 return Ok(out.signal);
9362 }
9363 Err(IndicatorDispatchError::UnknownOutput {
9364 indicator: "on_balance_volume_oscillator".to_string(),
9365 output: output_id.to_string(),
9366 })
9367 },
9368 )
9369}
9370
9371fn compute_twiggs_money_flow_batch(
9372 req: IndicatorBatchRequest<'_>,
9373 output_id: &str,
9374) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9375 let (high, low, close, volume) = extract_hlcv_input("twiggs_money_flow", req.data)?;
9376 let kernel = req.kernel.to_non_batch();
9377 collect_f64("twiggs_money_flow", output_id, req.combos, close.len(), |params| {
9378 let length = get_usize_param("twiggs_money_flow", params, "length", 21)?;
9379 let smoothing_length =
9380 get_usize_param("twiggs_money_flow", params, "smoothing_length", 4)?;
9381 let ma_type = get_enum_param("twiggs_money_flow", params, "ma_type", "ema")?;
9382 let input = TwiggsMoneyFlowInput::from_slices(
9383 high,
9384 low,
9385 close,
9386 volume,
9387 TwiggsMoneyFlowParams {
9388 length: Some(length),
9389 smoothing_length: Some(smoothing_length),
9390 ma_type: Some(ma_type),
9391 },
9392 );
9393 let out = twiggs_money_flow_with_kernel(&input, kernel).map_err(|e| {
9394 IndicatorDispatchError::ComputeFailed {
9395 indicator: "twiggs_money_flow".to_string(),
9396 details: e.to_string(),
9397 }
9398 })?;
9399 if output_id.eq_ignore_ascii_case("tmf") || output_id.eq_ignore_ascii_case("value") {
9400 return Ok(out.tmf);
9401 }
9402 if output_id.eq_ignore_ascii_case("smoothed") {
9403 return Ok(out.smoothed);
9404 }
9405 Err(IndicatorDispatchError::UnknownOutput {
9406 indicator: "twiggs_money_flow".to_string(),
9407 output: output_id.to_string(),
9408 })
9409 })
9410}
9411
9412fn compute_parkinson_volatility_batch(
9413 req: IndicatorBatchRequest<'_>,
9414 output_id: &str,
9415) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9416 let (high, low) = extract_high_low_input("parkinson_volatility", req.data)?;
9417 let kernel = req.kernel.to_non_batch();
9418 collect_f64(
9419 "parkinson_volatility",
9420 output_id,
9421 req.combos,
9422 high.len(),
9423 |params| {
9424 let period = get_usize_param("parkinson_volatility", params, "period", 10)?;
9425 let input = ParkinsonVolatilityInput::from_slices(
9426 high,
9427 low,
9428 ParkinsonVolatilityParams {
9429 period: Some(period),
9430 },
9431 );
9432 let out = parkinson_volatility_with_kernel(&input, kernel).map_err(|e| {
9433 IndicatorDispatchError::ComputeFailed {
9434 indicator: "parkinson_volatility".to_string(),
9435 details: e.to_string(),
9436 }
9437 })?;
9438 if output_id.eq_ignore_ascii_case("volatility") || output_id.eq_ignore_ascii_case("value")
9439 {
9440 return Ok(out.volatility);
9441 }
9442 if output_id.eq_ignore_ascii_case("variance") {
9443 return Ok(out.variance);
9444 }
9445 Err(IndicatorDispatchError::UnknownOutput {
9446 indicator: "parkinson_volatility".to_string(),
9447 output: output_id.to_string(),
9448 })
9449 },
9450 )
9451}
9452
9453fn compute_l2_ehlers_signal_to_noise_batch(
9454 req: IndicatorBatchRequest<'_>,
9455 output_id: &str,
9456) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9457 expect_value_output("l2_ehlers_signal_to_noise", output_id)?;
9458 let data_len = match req.data {
9459 IndicatorDataRef::Slice { values } => values.len(),
9460 IndicatorDataRef::Candles { candles, source } => {
9461 source_type(candles, source.unwrap_or("hl2")).len()
9462 }
9463 _ => {
9464 return Err(IndicatorDispatchError::MissingRequiredInput {
9465 indicator: "l2_ehlers_signal_to_noise".to_string(),
9466 input: IndicatorInputKind::Candles,
9467 })
9468 }
9469 };
9470 let kernel = req.kernel.to_non_batch();
9471 collect_f64(
9472 "l2_ehlers_signal_to_noise",
9473 output_id,
9474 req.combos,
9475 data_len,
9476 |params| {
9477 let source = get_enum_param("l2_ehlers_signal_to_noise", params, "source", "hl2")?;
9478 let smooth_period =
9479 get_usize_param("l2_ehlers_signal_to_noise", params, "smooth_period", 10)?;
9480 let src = match req.data {
9481 IndicatorDataRef::Slice { values } => values,
9482 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
9483 _ => unreachable!(),
9484 };
9485 let (high, low) = match req.data {
9486 IndicatorDataRef::Candles { candles, .. } => {
9487 (candles.high.as_slice(), candles.low.as_slice())
9488 }
9489 IndicatorDataRef::Ohlc { high, low, .. } => (high, low),
9490 IndicatorDataRef::Ohlcv { high, low, .. } => (high, low),
9491 _ => {
9492 return Err(IndicatorDispatchError::MissingRequiredInput {
9493 indicator: "l2_ehlers_signal_to_noise".to_string(),
9494 input: IndicatorInputKind::Candles,
9495 })
9496 }
9497 };
9498 let input = L2EhlersSignalToNoiseInput::from_slices(
9499 src,
9500 high,
9501 low,
9502 L2EhlersSignalToNoiseParams {
9503 smooth_period: Some(smooth_period),
9504 },
9505 );
9506 let out = l2_ehlers_signal_to_noise_with_kernel(&input, kernel).map_err(|e| {
9507 IndicatorDispatchError::ComputeFailed {
9508 indicator: "l2_ehlers_signal_to_noise".to_string(),
9509 details: e.to_string(),
9510 }
9511 })?;
9512 Ok(out.values)
9513 },
9514 )
9515}
9516
9517fn compute_cycle_channel_oscillator_batch(
9518 req: IndicatorBatchRequest<'_>,
9519 output_id: &str,
9520) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9521 let data_len = match req.data {
9522 IndicatorDataRef::Candles { candles, source } => {
9523 source_type(candles, source.unwrap_or("close")).len()
9524 }
9525 _ => {
9526 return Err(IndicatorDispatchError::MissingRequiredInput {
9527 indicator: "cycle_channel_oscillator".to_string(),
9528 input: IndicatorInputKind::Candles,
9529 })
9530 }
9531 };
9532 let kernel = req.kernel.to_non_batch();
9533 collect_f64(
9534 "cycle_channel_oscillator",
9535 output_id,
9536 req.combos,
9537 data_len,
9538 |params| {
9539 let source = get_enum_param("cycle_channel_oscillator", params, "source", "close")?;
9540 let short_cycle_length =
9541 get_usize_param("cycle_channel_oscillator", params, "short_cycle_length", 10)?;
9542 let medium_cycle_length =
9543 get_usize_param("cycle_channel_oscillator", params, "medium_cycle_length", 30)?;
9544 let short_multiplier =
9545 get_f64_param("cycle_channel_oscillator", params, "short_multiplier", 1.0)?;
9546 let medium_multiplier =
9547 get_f64_param("cycle_channel_oscillator", params, "medium_multiplier", 3.0)?;
9548 let (src, high, low, close) = match req.data {
9549 IndicatorDataRef::Candles { candles, .. } => (
9550 source_type(candles, &source),
9551 candles.high.as_slice(),
9552 candles.low.as_slice(),
9553 candles.close.as_slice(),
9554 ),
9555 _ => unreachable!(),
9556 };
9557 let input = CycleChannelOscillatorInput::from_slices(
9558 src,
9559 high,
9560 low,
9561 close,
9562 CycleChannelOscillatorParams {
9563 short_cycle_length: Some(short_cycle_length),
9564 medium_cycle_length: Some(medium_cycle_length),
9565 short_multiplier: Some(short_multiplier),
9566 medium_multiplier: Some(medium_multiplier),
9567 },
9568 );
9569 let out = cycle_channel_oscillator_with_kernel(&input, kernel).map_err(|e| {
9570 IndicatorDispatchError::ComputeFailed {
9571 indicator: "cycle_channel_oscillator".to_string(),
9572 details: e.to_string(),
9573 }
9574 })?;
9575 if output_id.eq_ignore_ascii_case("fast") || output_id.eq_ignore_ascii_case("value") {
9576 return Ok(out.fast);
9577 }
9578 if output_id.eq_ignore_ascii_case("slow") {
9579 return Ok(out.slow);
9580 }
9581 Err(IndicatorDispatchError::UnknownOutput {
9582 indicator: "cycle_channel_oscillator".to_string(),
9583 output: output_id.to_string(),
9584 })
9585 },
9586 )
9587}
9588
9589fn compute_andean_oscillator_batch(
9590 req: IndicatorBatchRequest<'_>,
9591 output_id: &str,
9592) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9593 let (open, _high, _low, close) = extract_ohlc_full_input("andean_oscillator", req.data)?;
9594 let kernel = req.kernel.to_non_batch();
9595 collect_f64("andean_oscillator", output_id, req.combos, close.len(), |params| {
9596 let length = get_usize_param("andean_oscillator", params, "length", 50)?;
9597 let signal_length =
9598 get_usize_param("andean_oscillator", params, "signal_length", 9)?;
9599 let input = AndeanOscillatorInput::from_slices(
9600 open,
9601 close,
9602 AndeanOscillatorParams {
9603 length: Some(length),
9604 signal_length: Some(signal_length),
9605 },
9606 );
9607 let out = andean_oscillator_with_kernel(&input, kernel).map_err(|e| {
9608 IndicatorDispatchError::ComputeFailed {
9609 indicator: "andean_oscillator".to_string(),
9610 details: e.to_string(),
9611 }
9612 })?;
9613 if output_id.eq_ignore_ascii_case("bull") {
9614 return Ok(out.bull);
9615 }
9616 if output_id.eq_ignore_ascii_case("bear") {
9617 return Ok(out.bear);
9618 }
9619 if output_id.eq_ignore_ascii_case("signal") {
9620 return Ok(out.signal);
9621 }
9622 Err(IndicatorDispatchError::UnknownOutput {
9623 indicator: "andean_oscillator".to_string(),
9624 output: output_id.to_string(),
9625 })
9626 })
9627}
9628
9629fn compute_daily_factor_batch(
9630 req: IndicatorBatchRequest<'_>,
9631 output_id: &str,
9632) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9633 let (open, high, low, close) = extract_ohlc_full_input("daily_factor", req.data)?;
9634 let kernel = req.kernel.to_non_batch();
9635 collect_f64("daily_factor", output_id, req.combos, close.len(), |params| {
9636 let threshold_level =
9637 get_f64_param("daily_factor", params, "threshold_level", 0.35)?;
9638 let input = DailyFactorInput::from_slices(
9639 open,
9640 high,
9641 low,
9642 close,
9643 DailyFactorParams {
9644 threshold_level: Some(threshold_level),
9645 },
9646 );
9647 let out = daily_factor_with_kernel(&input, kernel).map_err(|e| {
9648 IndicatorDispatchError::ComputeFailed {
9649 indicator: "daily_factor".to_string(),
9650 details: e.to_string(),
9651 }
9652 })?;
9653 if output_id.eq_ignore_ascii_case("value") {
9654 return Ok(out.value);
9655 }
9656 if output_id.eq_ignore_ascii_case("ema") {
9657 return Ok(out.ema);
9658 }
9659 if output_id.eq_ignore_ascii_case("signal") {
9660 return Ok(out.signal);
9661 }
9662 Err(IndicatorDispatchError::UnknownOutput {
9663 indicator: "daily_factor".to_string(),
9664 output: output_id.to_string(),
9665 })
9666 })
9667}
9668
9669fn compute_ehlers_adaptive_cyber_cycle_batch(
9670 req: IndicatorBatchRequest<'_>,
9671 output_id: &str,
9672) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9673 let data_len = match req.data {
9674 IndicatorDataRef::Slice { values } => values.len(),
9675 IndicatorDataRef::Candles { candles, source } => {
9676 source_type(candles, source.unwrap_or("hl2")).len()
9677 }
9678 _ => {
9679 return Err(IndicatorDispatchError::MissingRequiredInput {
9680 indicator: "ehlers_adaptive_cyber_cycle".to_string(),
9681 input: IndicatorInputKind::Candles,
9682 })
9683 }
9684 };
9685 let kernel = req.kernel.to_non_batch();
9686 collect_f64(
9687 "ehlers_adaptive_cyber_cycle",
9688 output_id,
9689 req.combos,
9690 data_len,
9691 |params| {
9692 let source = get_enum_param("ehlers_adaptive_cyber_cycle", params, "source", "hl2")?;
9693 let alpha = get_f64_param("ehlers_adaptive_cyber_cycle", params, "alpha", 0.07)?;
9694 let data = match req.data {
9695 IndicatorDataRef::Slice { values } => values,
9696 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
9697 _ => unreachable!(),
9698 };
9699 let input = EhlersAdaptiveCyberCycleInput::from_slice(
9700 data,
9701 EhlersAdaptiveCyberCycleParams { alpha: Some(alpha) },
9702 );
9703 let out = ehlers_adaptive_cyber_cycle_with_kernel(&input, kernel).map_err(|e| {
9704 IndicatorDispatchError::ComputeFailed {
9705 indicator: "ehlers_adaptive_cyber_cycle".to_string(),
9706 details: e.to_string(),
9707 }
9708 })?;
9709 if output_id.eq_ignore_ascii_case("cycle") || output_id.eq_ignore_ascii_case("value") {
9710 return Ok(out.cycle);
9711 }
9712 if output_id.eq_ignore_ascii_case("trigger") {
9713 return Ok(out.trigger);
9714 }
9715 Err(IndicatorDispatchError::UnknownOutput {
9716 indicator: "ehlers_adaptive_cyber_cycle".to_string(),
9717 output: output_id.to_string(),
9718 })
9719 },
9720 )
9721}
9722
9723fn compute_ehlers_simple_cycle_indicator_batch(
9724 req: IndicatorBatchRequest<'_>,
9725 output_id: &str,
9726) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9727 let data_len = match req.data {
9728 IndicatorDataRef::Slice { values } => values.len(),
9729 IndicatorDataRef::Candles { candles, source } => {
9730 source_type(candles, source.unwrap_or("hl2")).len()
9731 }
9732 _ => {
9733 return Err(IndicatorDispatchError::MissingRequiredInput {
9734 indicator: "ehlers_simple_cycle_indicator".to_string(),
9735 input: IndicatorInputKind::Candles,
9736 })
9737 }
9738 };
9739 let kernel = req.kernel.to_non_batch();
9740 collect_f64(
9741 "ehlers_simple_cycle_indicator",
9742 output_id,
9743 req.combos,
9744 data_len,
9745 |params| {
9746 let source = get_enum_param("ehlers_simple_cycle_indicator", params, "source", "hl2")?;
9747 let alpha = get_f64_param("ehlers_simple_cycle_indicator", params, "alpha", 0.07)?;
9748 let data = match req.data {
9749 IndicatorDataRef::Slice { values } => values,
9750 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
9751 _ => unreachable!(),
9752 };
9753 let input = EhlersSimpleCycleIndicatorInput::from_slice(
9754 data,
9755 EhlersSimpleCycleIndicatorParams { alpha: Some(alpha) },
9756 );
9757 let out = ehlers_simple_cycle_indicator_with_kernel(&input, kernel).map_err(|e| {
9758 IndicatorDispatchError::ComputeFailed {
9759 indicator: "ehlers_simple_cycle_indicator".to_string(),
9760 details: e.to_string(),
9761 }
9762 })?;
9763 if output_id.eq_ignore_ascii_case("cycle") || output_id.eq_ignore_ascii_case("value") {
9764 return Ok(out.cycle);
9765 }
9766 if output_id.eq_ignore_ascii_case("trigger") {
9767 return Ok(out.trigger);
9768 }
9769 Err(IndicatorDispatchError::UnknownOutput {
9770 indicator: "ehlers_simple_cycle_indicator".to_string(),
9771 output: output_id.to_string(),
9772 })
9773 },
9774 )
9775}
9776
9777fn compute_l1_ehlers_phasor_batch(
9778 req: IndicatorBatchRequest<'_>,
9779 output_id: &str,
9780) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9781 expect_value_output("l1_ehlers_phasor", output_id)?;
9782 let data = extract_slice_input("l1_ehlers_phasor", req.data, "close")?;
9783 let kernel = req.kernel.to_non_batch();
9784 collect_f64("l1_ehlers_phasor", output_id, req.combos, data.len(), |params| {
9785 let domestic_cycle_length =
9786 get_usize_param("l1_ehlers_phasor", params, "domestic_cycle_length", 15)?;
9787 let input = L1EhlersPhasorInput::from_slice(
9788 data,
9789 L1EhlersPhasorParams {
9790 domestic_cycle_length: Some(domestic_cycle_length),
9791 },
9792 );
9793 let out = l1_ehlers_phasor_with_kernel(&input, kernel).map_err(|e| {
9794 IndicatorDispatchError::ComputeFailed {
9795 indicator: "l1_ehlers_phasor".to_string(),
9796 details: e.to_string(),
9797 }
9798 })?;
9799 Ok(out.values)
9800 })
9801}
9802
9803fn compute_ehlers_smoothed_adaptive_momentum_batch(
9804 req: IndicatorBatchRequest<'_>,
9805 output_id: &str,
9806) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9807 expect_value_output("ehlers_smoothed_adaptive_momentum", output_id)?;
9808 let data_len = match req.data {
9809 IndicatorDataRef::Slice { values } => values.len(),
9810 IndicatorDataRef::Candles { candles, source } => {
9811 source_type(candles, source.unwrap_or("hl2")).len()
9812 }
9813 _ => {
9814 return Err(IndicatorDispatchError::MissingRequiredInput {
9815 indicator: "ehlers_smoothed_adaptive_momentum".to_string(),
9816 input: IndicatorInputKind::Candles,
9817 })
9818 }
9819 };
9820 let kernel = req.kernel.to_non_batch();
9821 collect_f64(
9822 "ehlers_smoothed_adaptive_momentum",
9823 output_id,
9824 req.combos,
9825 data_len,
9826 |params| {
9827 let source =
9828 get_enum_param("ehlers_smoothed_adaptive_momentum", params, "source", "hl2")?;
9829 let alpha =
9830 get_f64_param("ehlers_smoothed_adaptive_momentum", params, "alpha", 0.07)?;
9831 let cutoff =
9832 get_f64_param("ehlers_smoothed_adaptive_momentum", params, "cutoff", 8.0)?;
9833 let data = match req.data {
9834 IndicatorDataRef::Slice { values } => values,
9835 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
9836 _ => unreachable!(),
9837 };
9838 let input = EhlersSmoothedAdaptiveMomentumInput::from_slice(
9839 data,
9840 EhlersSmoothedAdaptiveMomentumParams {
9841 alpha: Some(alpha),
9842 cutoff: Some(cutoff),
9843 },
9844 );
9845 let out =
9846 ehlers_smoothed_adaptive_momentum_with_kernel(&input, kernel).map_err(|e| {
9847 IndicatorDispatchError::ComputeFailed {
9848 indicator: "ehlers_smoothed_adaptive_momentum".to_string(),
9849 details: e.to_string(),
9850 }
9851 })?;
9852 Ok(out.values)
9853 },
9854 )
9855}
9856
9857fn compute_ewma_volatility_batch(
9858 req: IndicatorBatchRequest<'_>,
9859 output_id: &str,
9860) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9861 expect_value_output("ewma_volatility", output_id)?;
9862 let data = extract_slice_input("ewma_volatility", req.data, "close")?;
9863 let kernel = req.kernel.to_non_batch();
9864 collect_f64("ewma_volatility", output_id, req.combos, data.len(), |params| {
9865 let lambda = get_f64_param("ewma_volatility", params, "lambda", 0.94)?;
9866 let input = EwmaVolatilityInput::from_slice(
9867 data,
9868 EwmaVolatilityParams {
9869 lambda: Some(lambda),
9870 },
9871 );
9872 let out = ewma_volatility_with_kernel(&input, kernel).map_err(|e| {
9873 IndicatorDispatchError::ComputeFailed {
9874 indicator: "ewma_volatility".to_string(),
9875 details: e.to_string(),
9876 }
9877 })?;
9878 Ok(out.values)
9879 })
9880}
9881
9882fn compute_random_walk_index_batch(
9883 req: IndicatorBatchRequest<'_>,
9884 output_id: &str,
9885) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9886 let (high, low, close) = extract_ohlc_input("random_walk_index", req.data)?;
9887 let kernel = req.kernel.to_non_batch();
9888 collect_f64("random_walk_index", output_id, req.combos, close.len(), |params| {
9889 let length = get_usize_param("random_walk_index", params, "length", 14)?;
9890 let input = RandomWalkIndexInput::from_slices(
9891 high,
9892 low,
9893 close,
9894 RandomWalkIndexParams {
9895 length: Some(length),
9896 },
9897 );
9898 let out = random_walk_index_with_kernel(&input, kernel).map_err(|e| {
9899 IndicatorDispatchError::ComputeFailed {
9900 indicator: "random_walk_index".to_string(),
9901 details: e.to_string(),
9902 }
9903 })?;
9904 if output_id.eq_ignore_ascii_case("high") {
9905 return Ok(out.high);
9906 }
9907 if output_id.eq_ignore_ascii_case("low") {
9908 return Ok(out.low);
9909 }
9910 Err(IndicatorDispatchError::UnknownOutput {
9911 indicator: "random_walk_index".to_string(),
9912 output: output_id.to_string(),
9913 })
9914 })
9915}
9916
9917fn compute_price_moving_average_ratio_percentile_batch(
9918 req: IndicatorBatchRequest<'_>,
9919 output_id: &str,
9920) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9921 let data_len = match req.data {
9922 IndicatorDataRef::Candles { candles, source } => {
9923 source_type(candles, source.unwrap_or("close")).len()
9924 }
9925 IndicatorDataRef::CloseVolume { close, .. } => close.len(),
9926 IndicatorDataRef::Ohlcv { close, .. } => close.len(),
9927 _ => {
9928 return Err(IndicatorDispatchError::MissingRequiredInput {
9929 indicator: "price_moving_average_ratio_percentile".to_string(),
9930 input: IndicatorInputKind::CloseVolume,
9931 })
9932 }
9933 };
9934 let kernel = req.kernel.to_non_batch();
9935 collect_f64(
9936 "price_moving_average_ratio_percentile",
9937 output_id,
9938 req.combos,
9939 data_len,
9940 |params| {
9941 let source =
9942 get_enum_param("price_moving_average_ratio_percentile", params, "source", "close")?;
9943 let ma_length =
9944 get_usize_param("price_moving_average_ratio_percentile", params, "ma_length", 20)?;
9945 let ma_type = get_enum_param(
9946 "price_moving_average_ratio_percentile",
9947 params,
9948 "ma_type",
9949 "sma",
9950 )?
9951 .parse::<PriceMovingAverageRatioPercentileMaType>()
9952 .map_err(|e| IndicatorDispatchError::InvalidParam {
9953 indicator: "price_moving_average_ratio_percentile".to_string(),
9954 key: "ma_type".to_string(),
9955 reason: e,
9956 })?;
9957 let pmarp_lookback = get_usize_param(
9958 "price_moving_average_ratio_percentile",
9959 params,
9960 "pmarp_lookback",
9961 350,
9962 )?;
9963 let signal_ma_length = get_usize_param(
9964 "price_moving_average_ratio_percentile",
9965 params,
9966 "signal_ma_length",
9967 20,
9968 )?;
9969 let signal_ma_type = get_enum_param(
9970 "price_moving_average_ratio_percentile",
9971 params,
9972 "signal_ma_type",
9973 "sma",
9974 )?
9975 .parse::<PriceMovingAverageRatioPercentileMaType>()
9976 .map_err(|e| IndicatorDispatchError::InvalidParam {
9977 indicator: "price_moving_average_ratio_percentile".to_string(),
9978 key: "signal_ma_type".to_string(),
9979 reason: e,
9980 })?;
9981 let line_mode = get_enum_param(
9982 "price_moving_average_ratio_percentile",
9983 params,
9984 "line_mode",
9985 "pmar",
9986 )?
9987 .parse::<PriceMovingAverageRatioPercentileLineMode>()
9988 .map_err(|e| IndicatorDispatchError::InvalidParam {
9989 indicator: "price_moving_average_ratio_percentile".to_string(),
9990 key: "line_mode".to_string(),
9991 reason: e,
9992 })?;
9993 let (price, volume) = match req.data {
9994 IndicatorDataRef::Candles { candles, .. } => {
9995 (source_type(candles, &source), candles.volume.as_slice())
9996 }
9997 IndicatorDataRef::CloseVolume { close, volume } => (close, volume),
9998 IndicatorDataRef::Ohlcv {
9999 open,
10000 high,
10001 low,
10002 close,
10003 volume,
10004 } => {
10005 let price = match source.to_ascii_lowercase().as_str() {
10006 "open" => open,
10007 "high" => high,
10008 "low" => low,
10009 _ => close,
10010 };
10011 (price, volume)
10012 }
10013 _ => unreachable!(),
10014 };
10015 let input = PriceMovingAverageRatioPercentileInput::from_slices(
10016 price,
10017 volume,
10018 PriceMovingAverageRatioPercentileParams {
10019 ma_length: Some(ma_length),
10020 ma_type: Some(ma_type),
10021 pmarp_lookback: Some(pmarp_lookback),
10022 signal_ma_length: Some(signal_ma_length),
10023 signal_ma_type: Some(signal_ma_type),
10024 line_mode: Some(line_mode),
10025 },
10026 );
10027 let out = price_moving_average_ratio_percentile_with_kernel(&input, kernel).map_err(
10028 |e| IndicatorDispatchError::ComputeFailed {
10029 indicator: "price_moving_average_ratio_percentile".to_string(),
10030 details: e.to_string(),
10031 },
10032 )?;
10033 if output_id.eq_ignore_ascii_case("pmar") {
10034 return Ok(out.pmar);
10035 }
10036 if output_id.eq_ignore_ascii_case("pmarp") {
10037 return Ok(out.pmarp);
10038 }
10039 if output_id.eq_ignore_ascii_case("plotline") || output_id.eq_ignore_ascii_case("value")
10040 {
10041 return Ok(out.plotline);
10042 }
10043 if output_id.eq_ignore_ascii_case("signal") {
10044 return Ok(out.signal);
10045 }
10046 if output_id.eq_ignore_ascii_case("pmar_high") {
10047 return Ok(out.pmar_high);
10048 }
10049 if output_id.eq_ignore_ascii_case("pmar_low") {
10050 return Ok(out.pmar_low);
10051 }
10052 if output_id.eq_ignore_ascii_case("scaled_pmar") {
10053 return Ok(out.scaled_pmar);
10054 }
10055 Err(IndicatorDispatchError::UnknownOutput {
10056 indicator: "price_moving_average_ratio_percentile".to_string(),
10057 output: output_id.to_string(),
10058 })
10059 },
10060 )
10061}
10062
10063fn compute_trend_trigger_factor_batch(
10064 req: IndicatorBatchRequest<'_>,
10065 output_id: &str,
10066) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10067 expect_value_output("trend_trigger_factor", output_id)?;
10068 let (high, low) = extract_high_low_input("trend_trigger_factor", req.data)?;
10069 let kernel = req.kernel.to_non_batch();
10070 collect_f64("trend_trigger_factor", output_id, req.combos, high.len(), |params| {
10071 let length = get_usize_param("trend_trigger_factor", params, "length", 15)?;
10072 let input = TrendTriggerFactorInput::from_slices(
10073 high,
10074 low,
10075 TrendTriggerFactorParams {
10076 length: Some(length),
10077 },
10078 );
10079 let out = trend_trigger_factor_with_kernel(&input, kernel).map_err(|e| {
10080 IndicatorDispatchError::ComputeFailed {
10081 indicator: "trend_trigger_factor".to_string(),
10082 details: e.to_string(),
10083 }
10084 })?;
10085 Ok(out.values)
10086 })
10087}
10088
10089fn compute_mesa_stochastic_multi_length_batch(
10090 req: IndicatorBatchRequest<'_>,
10091 output_id: &str,
10092) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10093 let data_len = match req.data {
10094 IndicatorDataRef::Slice { values } => values.len(),
10095 IndicatorDataRef::Candles { candles, source } => {
10096 source_type(candles, source.unwrap_or("close")).len()
10097 }
10098 _ => {
10099 return Err(IndicatorDispatchError::MissingRequiredInput {
10100 indicator: "mesa_stochastic_multi_length".to_string(),
10101 input: IndicatorInputKind::Candles,
10102 })
10103 }
10104 };
10105 let kernel = req.kernel.to_non_batch();
10106 collect_f64(
10107 "mesa_stochastic_multi_length",
10108 output_id,
10109 req.combos,
10110 data_len,
10111 |params| {
10112 let source =
10113 get_enum_param("mesa_stochastic_multi_length", params, "source", "close")?;
10114 let length_1 =
10115 get_usize_param("mesa_stochastic_multi_length", params, "length_1", 48)?;
10116 let length_2 =
10117 get_usize_param("mesa_stochastic_multi_length", params, "length_2", 21)?;
10118 let length_3 =
10119 get_usize_param("mesa_stochastic_multi_length", params, "length_3", 9)?;
10120 let length_4 =
10121 get_usize_param("mesa_stochastic_multi_length", params, "length_4", 6)?;
10122 let trigger_length =
10123 get_usize_param("mesa_stochastic_multi_length", params, "trigger_length", 2)?;
10124 let data = match req.data {
10125 IndicatorDataRef::Slice { values } => values,
10126 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
10127 _ => unreachable!(),
10128 };
10129 let input = MesaStochasticMultiLengthInput::from_slices(
10130 data,
10131 MesaStochasticMultiLengthParams {
10132 length_1: Some(length_1),
10133 length_2: Some(length_2),
10134 length_3: Some(length_3),
10135 length_4: Some(length_4),
10136 trigger_length: Some(trigger_length),
10137 },
10138 );
10139 let out = mesa_stochastic_multi_length_with_kernel(&input, kernel).map_err(|e| {
10140 IndicatorDispatchError::ComputeFailed {
10141 indicator: "mesa_stochastic_multi_length".to_string(),
10142 details: e.to_string(),
10143 }
10144 })?;
10145 if output_id.eq_ignore_ascii_case("mesa_1") {
10146 return Ok(out.mesa_1);
10147 }
10148 if output_id.eq_ignore_ascii_case("mesa_2") {
10149 return Ok(out.mesa_2);
10150 }
10151 if output_id.eq_ignore_ascii_case("mesa_3") {
10152 return Ok(out.mesa_3);
10153 }
10154 if output_id.eq_ignore_ascii_case("mesa_4") {
10155 return Ok(out.mesa_4);
10156 }
10157 if output_id.eq_ignore_ascii_case("trigger_1") {
10158 return Ok(out.trigger_1);
10159 }
10160 if output_id.eq_ignore_ascii_case("trigger_2") {
10161 return Ok(out.trigger_2);
10162 }
10163 if output_id.eq_ignore_ascii_case("trigger_3") {
10164 return Ok(out.trigger_3);
10165 }
10166 if output_id.eq_ignore_ascii_case("trigger_4") {
10167 return Ok(out.trigger_4);
10168 }
10169 Err(IndicatorDispatchError::UnknownOutput {
10170 indicator: "mesa_stochastic_multi_length".to_string(),
10171 output: output_id.to_string(),
10172 })
10173 },
10174 )
10175}
10176
10177fn compute_spearman_correlation_batch(
10178 req: IndicatorBatchRequest<'_>,
10179 output_id: &str,
10180) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10181 let data_len = match req.data {
10182 IndicatorDataRef::Candles { candles, source } => {
10183 source_type(candles, source.unwrap_or("close")).len()
10184 }
10185 _ => {
10186 return Err(IndicatorDispatchError::MissingRequiredInput {
10187 indicator: "spearman_correlation".to_string(),
10188 input: IndicatorInputKind::Candles,
10189 })
10190 }
10191 };
10192 let kernel = req.kernel.to_non_batch();
10193 collect_f64("spearman_correlation", output_id, req.combos, data_len, |params| {
10194 let source = get_enum_param("spearman_correlation", params, "source", "close")?;
10195 let comparison_source =
10196 get_enum_param("spearman_correlation", params, "comparison_source", "open")?;
10197 let lookback = get_usize_param("spearman_correlation", params, "lookback", 30)?;
10198 let smoothing_length =
10199 get_usize_param("spearman_correlation", params, "smoothing_length", 3)?;
10200 let (main, compare) = match req.data {
10201 IndicatorDataRef::Candles { candles, .. } => (
10202 source_type(candles, &source),
10203 source_type(candles, &comparison_source),
10204 ),
10205 _ => unreachable!(),
10206 };
10207 let input = SpearmanCorrelationInput::from_slices(
10208 main,
10209 compare,
10210 SpearmanCorrelationParams {
10211 lookback: Some(lookback),
10212 smoothing_length: Some(smoothing_length),
10213 },
10214 );
10215 let out = spearman_correlation_with_kernel(&input, kernel).map_err(|e| {
10216 IndicatorDispatchError::ComputeFailed {
10217 indicator: "spearman_correlation".to_string(),
10218 details: e.to_string(),
10219 }
10220 })?;
10221 if output_id.eq_ignore_ascii_case("raw") || output_id.eq_ignore_ascii_case("value") {
10222 return Ok(out.raw);
10223 }
10224 if output_id.eq_ignore_ascii_case("smoothed") {
10225 return Ok(out.smoothed);
10226 }
10227 Err(IndicatorDispatchError::UnknownOutput {
10228 indicator: "spearman_correlation".to_string(),
10229 output: output_id.to_string(),
10230 })
10231 })
10232}
10233
10234fn compute_relative_strength_index_wave_indicator_batch(
10235 req: IndicatorBatchRequest<'_>,
10236 output_id: &str,
10237) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10238 let data_len = match req.data {
10239 IndicatorDataRef::Candles { candles, source } => {
10240 source_type(candles, source.unwrap_or("close")).len()
10241 }
10242 _ => {
10243 return Err(IndicatorDispatchError::MissingRequiredInput {
10244 indicator: "relative_strength_index_wave_indicator".to_string(),
10245 input: IndicatorInputKind::Candles,
10246 })
10247 }
10248 };
10249 let kernel = req.kernel.to_non_batch();
10250 collect_f64(
10251 "relative_strength_index_wave_indicator",
10252 output_id,
10253 req.combos,
10254 data_len,
10255 |params| {
10256 let source = get_enum_param(
10257 "relative_strength_index_wave_indicator",
10258 params,
10259 "source",
10260 "close",
10261 )?;
10262 let rsi_length = get_usize_param(
10263 "relative_strength_index_wave_indicator",
10264 params,
10265 "rsi_length",
10266 14,
10267 )?;
10268 let length1 = get_usize_param(
10269 "relative_strength_index_wave_indicator",
10270 params,
10271 "length1",
10272 2,
10273 )?;
10274 let length2 = get_usize_param(
10275 "relative_strength_index_wave_indicator",
10276 params,
10277 "length2",
10278 5,
10279 )?;
10280 let length3 = get_usize_param(
10281 "relative_strength_index_wave_indicator",
10282 params,
10283 "length3",
10284 9,
10285 )?;
10286 let length4 = get_usize_param(
10287 "relative_strength_index_wave_indicator",
10288 params,
10289 "length4",
10290 13,
10291 )?;
10292 let (src, high, low) = match req.data {
10293 IndicatorDataRef::Candles { candles, .. } => (
10294 source_type(candles, &source),
10295 candles.high.as_slice(),
10296 candles.low.as_slice(),
10297 ),
10298 _ => unreachable!(),
10299 };
10300 let input = RelativeStrengthIndexWaveIndicatorInput::from_slices(
10301 src,
10302 high,
10303 low,
10304 RelativeStrengthIndexWaveIndicatorParams {
10305 rsi_length: Some(rsi_length),
10306 length1: Some(length1),
10307 length2: Some(length2),
10308 length3: Some(length3),
10309 length4: Some(length4),
10310 },
10311 );
10312 let out =
10313 relative_strength_index_wave_indicator_with_kernel(&input, kernel).map_err(|e| {
10314 IndicatorDispatchError::ComputeFailed {
10315 indicator: "relative_strength_index_wave_indicator".to_string(),
10316 details: e.to_string(),
10317 }
10318 })?;
10319 if output_id.eq_ignore_ascii_case("rsi_ma1") || output_id.eq_ignore_ascii_case("value")
10320 {
10321 return Ok(out.rsi_ma1);
10322 }
10323 if output_id.eq_ignore_ascii_case("rsi_ma2") {
10324 return Ok(out.rsi_ma2);
10325 }
10326 if output_id.eq_ignore_ascii_case("rsi_ma3") {
10327 return Ok(out.rsi_ma3);
10328 }
10329 if output_id.eq_ignore_ascii_case("rsi_ma4") {
10330 return Ok(out.rsi_ma4);
10331 }
10332 if output_id.eq_ignore_ascii_case("state") {
10333 return Ok(out.state);
10334 }
10335 Err(IndicatorDispatchError::UnknownOutput {
10336 indicator: "relative_strength_index_wave_indicator".to_string(),
10337 output: output_id.to_string(),
10338 })
10339 },
10340 )
10341}
10342
10343fn compute_accumulation_swing_index_batch(
10344 req: IndicatorBatchRequest<'_>,
10345 output_id: &str,
10346) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10347 expect_value_output("accumulation_swing_index", output_id)?;
10348 let (open, high, low, close) = extract_ohlc_full_input("accumulation_swing_index", req.data)?;
10349 let kernel = req.kernel.to_non_batch();
10350 collect_f64(
10351 "accumulation_swing_index",
10352 output_id,
10353 req.combos,
10354 close.len(),
10355 |params| {
10356 let daily_limit =
10357 get_f64_param("accumulation_swing_index", params, "daily_limit", 10_000.0)?;
10358 let input = AccumulationSwingIndexInput::from_slices(
10359 open,
10360 high,
10361 low,
10362 close,
10363 AccumulationSwingIndexParams {
10364 daily_limit: Some(daily_limit),
10365 },
10366 );
10367 let out = accumulation_swing_index_with_kernel(&input, kernel).map_err(|e| {
10368 IndicatorDispatchError::ComputeFailed {
10369 indicator: "accumulation_swing_index".to_string(),
10370 details: e.to_string(),
10371 }
10372 })?;
10373 Ok(out.values)
10374 },
10375 )
10376}
10377
10378fn compute_ichimoku_oscillator_batch(
10379 req: IndicatorBatchRequest<'_>,
10380 output_id: &str,
10381) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10382 let (high, low, close) = extract_ohlc_input("ichimoku_oscillator", req.data)?;
10383 let kernel = req.kernel.to_non_batch();
10384 collect_f64("ichimoku_oscillator", output_id, req.combos, close.len(), |params| {
10385 let source_name = get_enum_param("ichimoku_oscillator", params, "source", "close")?;
10386 let conversion_periods =
10387 get_usize_param("ichimoku_oscillator", params, "conversion_periods", 9)?;
10388 let base_periods = get_usize_param("ichimoku_oscillator", params, "base_periods", 26)?;
10389 let lagging_span_periods =
10390 get_usize_param("ichimoku_oscillator", params, "lagging_span_periods", 52)?;
10391 let displacement = get_usize_param("ichimoku_oscillator", params, "displacement", 26)?;
10392 let ma_length = get_usize_param("ichimoku_oscillator", params, "ma_length", 12)?;
10393 let smoothing_length =
10394 get_usize_param("ichimoku_oscillator", params, "smoothing_length", 3)?;
10395 let extra_smoothing =
10396 get_bool_param("ichimoku_oscillator", params, "extra_smoothing", true)?;
10397 let normalize = get_enum_param("ichimoku_oscillator", params, "normalize", "window")?
10398 .parse::<IchimokuOscillatorNormalizeMode>()
10399 .map_err(|e| IndicatorDispatchError::InvalidParam {
10400 indicator: "ichimoku_oscillator".to_string(),
10401 key: "normalize".to_string(),
10402 reason: e,
10403 })?;
10404 let window_size = get_usize_param("ichimoku_oscillator", params, "window_size", 20)?;
10405 let clamp = get_bool_param("ichimoku_oscillator", params, "clamp", true)?;
10406 let top_band = get_f64_param("ichimoku_oscillator", params, "top_band", 2.0)?;
10407 let mid_band = get_f64_param("ichimoku_oscillator", params, "mid_band", 1.5)?;
10408 let source = match req.data {
10409 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source_name),
10410 _ => close,
10411 };
10412 let input = IchimokuOscillatorInput::from_slices(
10413 high,
10414 low,
10415 close,
10416 source,
10417 IchimokuOscillatorParams {
10418 conversion_periods: Some(conversion_periods),
10419 base_periods: Some(base_periods),
10420 lagging_span_periods: Some(lagging_span_periods),
10421 displacement: Some(displacement),
10422 ma_length: Some(ma_length),
10423 smoothing_length: Some(smoothing_length),
10424 extra_smoothing: Some(extra_smoothing),
10425 normalize: Some(normalize),
10426 window_size: Some(window_size),
10427 clamp: Some(clamp),
10428 top_band: Some(top_band),
10429 mid_band: Some(mid_band),
10430 },
10431 );
10432 let out = ichimoku_oscillator_with_kernel(&input, kernel).map_err(|e| {
10433 IndicatorDispatchError::ComputeFailed {
10434 indicator: "ichimoku_oscillator".to_string(),
10435 details: e.to_string(),
10436 }
10437 })?;
10438 if output_id.eq_ignore_ascii_case("signal") || output_id.eq_ignore_ascii_case("value") {
10439 return Ok(out.signal);
10440 }
10441 if output_id.eq_ignore_ascii_case("ma") {
10442 return Ok(out.ma);
10443 }
10444 if output_id.eq_ignore_ascii_case("conversion") {
10445 return Ok(out.conversion);
10446 }
10447 if output_id.eq_ignore_ascii_case("base") {
10448 return Ok(out.base);
10449 }
10450 if output_id.eq_ignore_ascii_case("chikou") {
10451 return Ok(out.chikou);
10452 }
10453 if output_id.eq_ignore_ascii_case("current_kumo_a") {
10454 return Ok(out.current_kumo_a);
10455 }
10456 if output_id.eq_ignore_ascii_case("current_kumo_b") {
10457 return Ok(out.current_kumo_b);
10458 }
10459 if output_id.eq_ignore_ascii_case("future_kumo_a") {
10460 return Ok(out.future_kumo_a);
10461 }
10462 if output_id.eq_ignore_ascii_case("future_kumo_b") {
10463 return Ok(out.future_kumo_b);
10464 }
10465 if output_id.eq_ignore_ascii_case("max_level") {
10466 return Ok(out.max_level);
10467 }
10468 if output_id.eq_ignore_ascii_case("high_level") {
10469 return Ok(out.high_level);
10470 }
10471 if output_id.eq_ignore_ascii_case("low_level") {
10472 return Ok(out.low_level);
10473 }
10474 if output_id.eq_ignore_ascii_case("min_level") {
10475 return Ok(out.min_level);
10476 }
10477 Err(IndicatorDispatchError::UnknownOutput {
10478 indicator: "ichimoku_oscillator".to_string(),
10479 output: output_id.to_string(),
10480 })
10481 })
10482}
10483
10484fn compute_volatility_quality_index_batch(
10485 req: IndicatorBatchRequest<'_>,
10486 output_id: &str,
10487) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10488 let (open, high, low, close) = extract_ohlc_full_input("volatility_quality_index", req.data)?;
10489 let kernel = req.kernel.to_non_batch();
10490 collect_f64("volatility_quality_index", output_id, req.combos, close.len(), |params| {
10491 let fast_length = get_usize_param("volatility_quality_index", params, "fast_length", 9)?;
10492 let slow_length =
10493 get_usize_param("volatility_quality_index", params, "slow_length", 200)?;
10494 let input = VolatilityQualityIndexInput::from_slices(
10495 open,
10496 high,
10497 low,
10498 close,
10499 VolatilityQualityIndexParams {
10500 fast_length: Some(fast_length),
10501 slow_length: Some(slow_length),
10502 },
10503 );
10504 let out = volatility_quality_index_with_kernel(&input, kernel).map_err(|e| {
10505 IndicatorDispatchError::ComputeFailed {
10506 indicator: "volatility_quality_index".to_string(),
10507 details: e.to_string(),
10508 }
10509 })?;
10510 if output_id.eq_ignore_ascii_case("vqi_sum") || output_id.eq_ignore_ascii_case("value") {
10511 return Ok(out.vqi_sum);
10512 }
10513 if output_id.eq_ignore_ascii_case("fast_sma") {
10514 return Ok(out.fast_sma);
10515 }
10516 if output_id.eq_ignore_ascii_case("slow_sma") {
10517 return Ok(out.slow_sma);
10518 }
10519 Err(IndicatorDispatchError::UnknownOutput {
10520 indicator: "volatility_quality_index".to_string(),
10521 output: output_id.to_string(),
10522 })
10523 })
10524}
10525
10526fn compute_vwap_deviation_oscillator_batch(
10527 req: IndicatorBatchRequest<'_>,
10528 output_id: &str,
10529) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10530 let (timestamps, high, low, close, volume): (&[i64], &[f64], &[f64], &[f64], &[f64]) =
10531 match req.data {
10532 IndicatorDataRef::Candles { candles, .. } => (
10533 candles.timestamp.as_slice(),
10534 candles.high.as_slice(),
10535 candles.low.as_slice(),
10536 candles.close.as_slice(),
10537 candles.volume.as_slice(),
10538 ),
10539 _ => {
10540 return Err(IndicatorDispatchError::MissingRequiredInput {
10541 indicator: "vwap_deviation_oscillator".to_string(),
10542 input: IndicatorInputKind::Candles,
10543 })
10544 }
10545 };
10546 let kernel = req.kernel.to_non_batch();
10547 collect_f64(
10548 "vwap_deviation_oscillator",
10549 output_id,
10550 req.combos,
10551 close.len(),
10552 |params| {
10553 let session_mode =
10554 get_enum_param("vwap_deviation_oscillator", params, "session_mode", "rolling_bars")?
10555 .parse::<VwapDeviationSessionMode>()
10556 .map_err(|e| IndicatorDispatchError::InvalidParam {
10557 indicator: "vwap_deviation_oscillator".to_string(),
10558 key: "session_mode".to_string(),
10559 reason: e,
10560 })?;
10561 let rolling_period =
10562 get_usize_param("vwap_deviation_oscillator", params, "rolling_period", 20)?;
10563 let rolling_days =
10564 get_usize_param("vwap_deviation_oscillator", params, "rolling_days", 30)?;
10565 let use_close =
10566 get_bool_param("vwap_deviation_oscillator", params, "use_close", false)?;
10567 let deviation_mode =
10568 get_enum_param("vwap_deviation_oscillator", params, "deviation_mode", "absolute")?
10569 .parse::<VwapDeviationMode>()
10570 .map_err(|e| IndicatorDispatchError::InvalidParam {
10571 indicator: "vwap_deviation_oscillator".to_string(),
10572 key: "deviation_mode".to_string(),
10573 reason: e,
10574 })?;
10575 let z_window = get_usize_param("vwap_deviation_oscillator", params, "z_window", 50)?;
10576 let pct_vol_lookback =
10577 get_usize_param("vwap_deviation_oscillator", params, "pct_vol_lookback", 100)?;
10578 let pct_min_sigma =
10579 get_f64_param("vwap_deviation_oscillator", params, "pct_min_sigma", 0.1)?;
10580 let abs_vol_lookback =
10581 get_usize_param("vwap_deviation_oscillator", params, "abs_vol_lookback", 100)?;
10582 let input = VwapDeviationOscillatorInput::from_slices(
10583 timestamps,
10584 high,
10585 low,
10586 close,
10587 volume,
10588 VwapDeviationOscillatorParams {
10589 session_mode: Some(session_mode),
10590 rolling_period: Some(rolling_period),
10591 rolling_days: Some(rolling_days),
10592 use_close: Some(use_close),
10593 deviation_mode: Some(deviation_mode),
10594 z_window: Some(z_window),
10595 pct_vol_lookback: Some(pct_vol_lookback),
10596 pct_min_sigma: Some(pct_min_sigma),
10597 abs_vol_lookback: Some(abs_vol_lookback),
10598 },
10599 );
10600 let out = vwap_deviation_oscillator_with_kernel(&input, kernel).map_err(|e| {
10601 IndicatorDispatchError::ComputeFailed {
10602 indicator: "vwap_deviation_oscillator".to_string(),
10603 details: e.to_string(),
10604 }
10605 })?;
10606 if output_id.eq_ignore_ascii_case("osc") || output_id.eq_ignore_ascii_case("value") {
10607 return Ok(out.osc);
10608 }
10609 if output_id.eq_ignore_ascii_case("std1") {
10610 return Ok(out.std1);
10611 }
10612 if output_id.eq_ignore_ascii_case("std2") {
10613 return Ok(out.std2);
10614 }
10615 if output_id.eq_ignore_ascii_case("std3") {
10616 return Ok(out.std3);
10617 }
10618 Err(IndicatorDispatchError::UnknownOutput {
10619 indicator: "vwap_deviation_oscillator".to_string(),
10620 output: output_id.to_string(),
10621 })
10622 },
10623 )
10624}
10625
10626fn compute_bulls_v_bears_batch(
10627 req: IndicatorBatchRequest<'_>,
10628 output_id: &str,
10629) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10630 let (high, low, close) = extract_ohlc_input("bulls_v_bears", req.data)?;
10631 let kernel = req.kernel.to_non_batch();
10632 collect_f64("bulls_v_bears", output_id, req.combos, close.len(), |params| {
10633 let period = get_usize_param("bulls_v_bears", params, "period", 14)?;
10634 let ma_type = get_enum_param("bulls_v_bears", params, "ma_type", "ema")?
10635 .parse::<BullsVBearsMaType>()
10636 .map_err(|e| IndicatorDispatchError::InvalidParam {
10637 indicator: "bulls_v_bears".to_string(),
10638 key: "ma_type".to_string(),
10639 reason: e,
10640 })?;
10641 let calculation_method =
10642 get_enum_param("bulls_v_bears", params, "calculation_method", "normalized")?
10643 .parse::<BullsVBearsCalculationMethod>()
10644 .map_err(|e| IndicatorDispatchError::InvalidParam {
10645 indicator: "bulls_v_bears".to_string(),
10646 key: "calculation_method".to_string(),
10647 reason: e,
10648 })?;
10649 let normalized_bars_back =
10650 get_usize_param("bulls_v_bears", params, "normalized_bars_back", 120)?;
10651 let raw_rolling_period =
10652 get_usize_param("bulls_v_bears", params, "raw_rolling_period", 50)?;
10653 let raw_threshold_percentile =
10654 get_f64_param("bulls_v_bears", params, "raw_threshold_percentile", 95.0)?;
10655 let threshold_level =
10656 get_f64_param("bulls_v_bears", params, "threshold_level", 80.0)?;
10657 let input = BullsVBearsInput::from_slices(
10658 high,
10659 low,
10660 close,
10661 BullsVBearsParams {
10662 period: Some(period),
10663 ma_type: Some(ma_type),
10664 calculation_method: Some(calculation_method),
10665 normalized_bars_back: Some(normalized_bars_back),
10666 raw_rolling_period: Some(raw_rolling_period),
10667 raw_threshold_percentile: Some(raw_threshold_percentile),
10668 threshold_level: Some(threshold_level),
10669 },
10670 );
10671 let out = bulls_v_bears_with_kernel(&input, kernel).map_err(|e| {
10672 IndicatorDispatchError::ComputeFailed {
10673 indicator: "bulls_v_bears".to_string(),
10674 details: e.to_string(),
10675 }
10676 })?;
10677 if output_id.eq_ignore_ascii_case("value") {
10678 return Ok(out.value);
10679 }
10680 if output_id.eq_ignore_ascii_case("bull") {
10681 return Ok(out.bull);
10682 }
10683 if output_id.eq_ignore_ascii_case("bear") {
10684 return Ok(out.bear);
10685 }
10686 if output_id.eq_ignore_ascii_case("ma") {
10687 return Ok(out.ma);
10688 }
10689 if output_id.eq_ignore_ascii_case("upper") {
10690 return Ok(out.upper);
10691 }
10692 if output_id.eq_ignore_ascii_case("lower") {
10693 return Ok(out.lower);
10694 }
10695 if output_id.eq_ignore_ascii_case("bullish_signal") {
10696 return Ok(out.bullish_signal);
10697 }
10698 if output_id.eq_ignore_ascii_case("bearish_signal") {
10699 return Ok(out.bearish_signal);
10700 }
10701 if output_id.eq_ignore_ascii_case("zero_cross_up") {
10702 return Ok(out.zero_cross_up);
10703 }
10704 if output_id.eq_ignore_ascii_case("zero_cross_down") {
10705 return Ok(out.zero_cross_down);
10706 }
10707 Err(IndicatorDispatchError::UnknownOutput {
10708 indicator: "bulls_v_bears".to_string(),
10709 output: output_id.to_string(),
10710 })
10711 })
10712}
10713
10714fn compute_smooth_theil_sen_batch(
10715 req: IndicatorBatchRequest<'_>,
10716 output_id: &str,
10717) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10718 let data = extract_slice_input("smooth_theil_sen", req.data, "close")?;
10719 let kernel = req.kernel.to_non_batch();
10720 collect_f64("smooth_theil_sen", output_id, req.combos, data.len(), |params| {
10721 let length = get_usize_param("smooth_theil_sen", params, "length", 25)?;
10722 let offset = get_usize_param("smooth_theil_sen", params, "offset", 0)?;
10723 let multiplier = get_f64_param("smooth_theil_sen", params, "multiplier", 2.0)?;
10724 let slope_style = get_enum_param("smooth_theil_sen", params, "slope_style", "smooth_median")?
10725 .parse::<SmoothTheilSenStatStyle>()
10726 .map_err(|e| IndicatorDispatchError::InvalidParam {
10727 indicator: "smooth_theil_sen".to_string(),
10728 key: "slope_style".to_string(),
10729 reason: e,
10730 })?;
10731 let residual_style =
10732 get_enum_param("smooth_theil_sen", params, "residual_style", "smooth_median")?
10733 .parse::<SmoothTheilSenStatStyle>()
10734 .map_err(|e| IndicatorDispatchError::InvalidParam {
10735 indicator: "smooth_theil_sen".to_string(),
10736 key: "residual_style".to_string(),
10737 reason: e,
10738 })?;
10739 let deviation_style =
10740 get_enum_param("smooth_theil_sen", params, "deviation_style", "mad")?
10741 .parse::<SmoothTheilSenDeviationType>()
10742 .map_err(|e| IndicatorDispatchError::InvalidParam {
10743 indicator: "smooth_theil_sen".to_string(),
10744 key: "deviation_style".to_string(),
10745 reason: e,
10746 })?;
10747 let mad_style = get_enum_param("smooth_theil_sen", params, "mad_style", "smooth_median")?
10748 .parse::<SmoothTheilSenStatStyle>()
10749 .map_err(|e| IndicatorDispatchError::InvalidParam {
10750 indicator: "smooth_theil_sen".to_string(),
10751 key: "mad_style".to_string(),
10752 reason: e,
10753 })?;
10754 let include_prediction_in_deviation = get_bool_param(
10755 "smooth_theil_sen",
10756 params,
10757 "include_prediction_in_deviation",
10758 false,
10759 )?;
10760 let input = SmoothTheilSenInput::from_slice(
10761 data,
10762 SmoothTheilSenParams {
10763 length: Some(length),
10764 offset: Some(offset),
10765 multiplier: Some(multiplier),
10766 slope_style: Some(slope_style),
10767 residual_style: Some(residual_style),
10768 deviation_style: Some(deviation_style),
10769 mad_style: Some(mad_style),
10770 include_prediction_in_deviation: Some(include_prediction_in_deviation),
10771 },
10772 );
10773 let out = smooth_theil_sen_with_kernel(&input, kernel).map_err(|e| {
10774 IndicatorDispatchError::ComputeFailed {
10775 indicator: "smooth_theil_sen".to_string(),
10776 details: e.to_string(),
10777 }
10778 })?;
10779 if output_id.eq_ignore_ascii_case("value") {
10780 return Ok(out.value);
10781 }
10782 if output_id.eq_ignore_ascii_case("upper") {
10783 return Ok(out.upper);
10784 }
10785 if output_id.eq_ignore_ascii_case("lower") {
10786 return Ok(out.lower);
10787 }
10788 if output_id.eq_ignore_ascii_case("slope") {
10789 return Ok(out.slope);
10790 }
10791 if output_id.eq_ignore_ascii_case("intercept") {
10792 return Ok(out.intercept);
10793 }
10794 if output_id.eq_ignore_ascii_case("deviation") {
10795 return Ok(out.deviation);
10796 }
10797 Err(IndicatorDispatchError::UnknownOutput {
10798 indicator: "smooth_theil_sen".to_string(),
10799 output: output_id.to_string(),
10800 })
10801 })
10802}
10803
10804fn compute_regression_slope_oscillator_batch(
10805 req: IndicatorBatchRequest<'_>,
10806 output_id: &str,
10807) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10808 let data_len = match req.data {
10809 IndicatorDataRef::Candles { candles, .. } => candles.close.len(),
10810 IndicatorDataRef::Slice { values } => values.len(),
10811 _ => {
10812 return Err(IndicatorDispatchError::MissingRequiredInput {
10813 indicator: "regression_slope_oscillator".to_string(),
10814 input: IndicatorInputKind::Slice,
10815 })
10816 }
10817 };
10818 let kernel = req.kernel.to_non_batch();
10819 collect_f64(
10820 "regression_slope_oscillator",
10821 output_id,
10822 req.combos,
10823 data_len,
10824 |params| {
10825 let min_range =
10826 get_usize_param("regression_slope_oscillator", params, "min_range", 10)?;
10827 let max_range =
10828 get_usize_param("regression_slope_oscillator", params, "max_range", 100)?;
10829 let step = get_usize_param("regression_slope_oscillator", params, "step", 5)?;
10830 let signal_line =
10831 get_usize_param("regression_slope_oscillator", params, "signal_line", 7)?;
10832 let input = match req.data {
10833 IndicatorDataRef::Candles { candles, .. } => {
10834 RegressionSlopeOscillatorInput::from_candles(
10835 candles,
10836 RegressionSlopeOscillatorParams {
10837 min_range: Some(min_range),
10838 max_range: Some(max_range),
10839 step: Some(step),
10840 signal_line: Some(signal_line),
10841 },
10842 )
10843 }
10844 IndicatorDataRef::Slice { values } => RegressionSlopeOscillatorInput::from_slice(
10845 values,
10846 RegressionSlopeOscillatorParams {
10847 min_range: Some(min_range),
10848 max_range: Some(max_range),
10849 step: Some(step),
10850 signal_line: Some(signal_line),
10851 },
10852 ),
10853 _ => unreachable!(),
10854 };
10855 let out = regression_slope_oscillator_with_kernel(&input, kernel).map_err(|e| {
10856 IndicatorDispatchError::ComputeFailed {
10857 indicator: "regression_slope_oscillator".to_string(),
10858 details: e.to_string(),
10859 }
10860 })?;
10861 if output_id.eq_ignore_ascii_case("value") {
10862 return Ok(out.value);
10863 }
10864 if output_id.eq_ignore_ascii_case("signal") {
10865 return Ok(out.signal);
10866 }
10867 if output_id.eq_ignore_ascii_case("bullish_reversal") {
10868 return Ok(out.bullish_reversal);
10869 }
10870 if output_id.eq_ignore_ascii_case("bearish_reversal") {
10871 return Ok(out.bearish_reversal);
10872 }
10873 Err(IndicatorDispatchError::UnknownOutput {
10874 indicator: "regression_slope_oscillator".to_string(),
10875 output: output_id.to_string(),
10876 })
10877 },
10878 )
10879}
10880
10881fn compute_linear_regression_intensity_batch(
10882 req: IndicatorBatchRequest<'_>,
10883 output_id: &str,
10884) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10885 expect_value_output("linear_regression_intensity", output_id)?;
10886 let data_len = match req.data {
10887 IndicatorDataRef::Candles { candles, source } => {
10888 source_type(candles, source.unwrap_or("close")).len()
10889 }
10890 IndicatorDataRef::Slice { values } => values.len(),
10891 _ => {
10892 return Err(IndicatorDispatchError::MissingRequiredInput {
10893 indicator: "linear_regression_intensity".to_string(),
10894 input: IndicatorInputKind::Slice,
10895 })
10896 }
10897 };
10898 let kernel = req.kernel.to_non_batch();
10899 collect_f64(
10900 "linear_regression_intensity",
10901 output_id,
10902 req.combos,
10903 data_len,
10904 |params| {
10905 let source =
10906 get_enum_param("linear_regression_intensity", params, "source", "close")?;
10907 let lookback_period =
10908 get_usize_param("linear_regression_intensity", params, "lookback_period", 12)?;
10909 let range_tolerance = get_f64_param(
10910 "linear_regression_intensity",
10911 params,
10912 "range_tolerance",
10913 90.0,
10914 )?;
10915 let linreg_length =
10916 get_usize_param("linear_regression_intensity", params, "linreg_length", 90)?;
10917 let input = match req.data {
10918 IndicatorDataRef::Candles { candles, .. } => {
10919 LinearRegressionIntensityInput::from_candles(
10920 candles,
10921 &source,
10922 LinearRegressionIntensityParams {
10923 lookback_period: Some(lookback_period),
10924 range_tolerance: Some(range_tolerance),
10925 linreg_length: Some(linreg_length),
10926 },
10927 )
10928 }
10929 IndicatorDataRef::Slice { values } => LinearRegressionIntensityInput::from_slice(
10930 values,
10931 LinearRegressionIntensityParams {
10932 lookback_period: Some(lookback_period),
10933 range_tolerance: Some(range_tolerance),
10934 linreg_length: Some(linreg_length),
10935 },
10936 ),
10937 _ => unreachable!(),
10938 };
10939 let out = linear_regression_intensity_with_kernel(&input, kernel).map_err(|e| {
10940 IndicatorDispatchError::ComputeFailed {
10941 indicator: "linear_regression_intensity".to_string(),
10942 details: e.to_string(),
10943 }
10944 })?;
10945 Ok(out.values)
10946 },
10947 )
10948}
10949
10950fn compute_moving_average_cross_probability_batch(
10951 req: IndicatorBatchRequest<'_>,
10952 output_id: &str,
10953) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10954 let data_len = match req.data {
10955 IndicatorDataRef::Candles { candles, .. } => candles.close.len(),
10956 IndicatorDataRef::Slice { values } => values.len(),
10957 _ => {
10958 return Err(IndicatorDispatchError::MissingRequiredInput {
10959 indicator: "moving_average_cross_probability".to_string(),
10960 input: IndicatorInputKind::Slice,
10961 })
10962 }
10963 };
10964 let kernel = req.kernel.to_non_batch();
10965 collect_f64(
10966 "moving_average_cross_probability",
10967 output_id,
10968 req.combos,
10969 data_len,
10970 |params| {
10971 let ma_type = get_enum_param(
10972 "moving_average_cross_probability",
10973 params,
10974 "ma_type",
10975 "ema",
10976 )?
10977 .parse::<MovingAverageCrossProbabilityMaType>()
10978 .map_err(|e| IndicatorDispatchError::InvalidParam {
10979 indicator: "moving_average_cross_probability".to_string(),
10980 key: "ma_type".to_string(),
10981 reason: e,
10982 })?;
10983 let smoothing_window = get_usize_param(
10984 "moving_average_cross_probability",
10985 params,
10986 "smoothing_window",
10987 7,
10988 )?;
10989 let slow_length =
10990 get_usize_param("moving_average_cross_probability", params, "slow_length", 30)?;
10991 let fast_length =
10992 get_usize_param("moving_average_cross_probability", params, "fast_length", 14)?;
10993 let resolution =
10994 get_usize_param("moving_average_cross_probability", params, "resolution", 50)?;
10995 let params = MovingAverageCrossProbabilityParams {
10996 ma_type: Some(ma_type),
10997 smoothing_window: Some(smoothing_window),
10998 slow_length: Some(slow_length),
10999 fast_length: Some(fast_length),
11000 resolution: Some(resolution),
11001 };
11002 let input = match req.data {
11003 IndicatorDataRef::Candles { candles, .. } => {
11004 MovingAverageCrossProbabilityInput::from_candles(candles, params)
11005 }
11006 IndicatorDataRef::Slice { values } => {
11007 MovingAverageCrossProbabilityInput::from_slice(values, params)
11008 }
11009 _ => unreachable!(),
11010 };
11011 let out = moving_average_cross_probability_with_kernel(&input, kernel).map_err(
11012 |e| IndicatorDispatchError::ComputeFailed {
11013 indicator: "moving_average_cross_probability".to_string(),
11014 details: e.to_string(),
11015 },
11016 )?;
11017 if output_id.eq_ignore_ascii_case("value") {
11018 return Ok(out.value);
11019 }
11020 if output_id.eq_ignore_ascii_case("slow_ma") {
11021 return Ok(out.slow_ma);
11022 }
11023 if output_id.eq_ignore_ascii_case("fast_ma") {
11024 return Ok(out.fast_ma);
11025 }
11026 if output_id.eq_ignore_ascii_case("forecast") {
11027 return Ok(out.forecast);
11028 }
11029 if output_id.eq_ignore_ascii_case("upper") {
11030 return Ok(out.upper);
11031 }
11032 if output_id.eq_ignore_ascii_case("lower") {
11033 return Ok(out.lower);
11034 }
11035 if output_id.eq_ignore_ascii_case("direction") {
11036 return Ok(out.direction);
11037 }
11038 Err(IndicatorDispatchError::UnknownOutput {
11039 indicator: "moving_average_cross_probability".to_string(),
11040 output: output_id.to_string(),
11041 })
11042 },
11043 )
11044}
11045
11046fn compute_volume_zone_oscillator_batch(
11047 req: IndicatorBatchRequest<'_>,
11048 output_id: &str,
11049) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11050 expect_value_output("volume_zone_oscillator", output_id)?;
11051 let (close, volume) = extract_close_volume_input("volume_zone_oscillator", req.data, "close")?;
11052 let kernel = req.kernel.to_non_batch();
11053 collect_f64(
11054 "volume_zone_oscillator",
11055 output_id,
11056 req.combos,
11057 close.len(),
11058 |params| {
11059 let length = get_usize_param("volume_zone_oscillator", params, "length", 14)?;
11060 let intraday_smoothing = get_bool_param(
11061 "volume_zone_oscillator",
11062 params,
11063 "intraday_smoothing",
11064 true,
11065 )?;
11066 let noise_filter =
11067 get_usize_param("volume_zone_oscillator", params, "noise_filter", 4)?;
11068 let input = VolumeZoneOscillatorInput::from_slices(
11069 close,
11070 volume,
11071 VolumeZoneOscillatorParams {
11072 length: Some(length),
11073 intraday_smoothing: Some(intraday_smoothing),
11074 noise_filter: Some(noise_filter),
11075 },
11076 );
11077 let out = volume_zone_oscillator_with_kernel(&input, kernel).map_err(|e| {
11078 IndicatorDispatchError::ComputeFailed {
11079 indicator: "volume_zone_oscillator".to_string(),
11080 details: e.to_string(),
11081 }
11082 })?;
11083 Ok(out.values)
11084 },
11085 )
11086}
11087
11088fn compute_market_meanness_index_batch(
11089 req: IndicatorBatchRequest<'_>,
11090 output_id: &str,
11091) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11092 let data_len = match req.data {
11093 IndicatorDataRef::Candles { candles, .. } => candles.close.len(),
11094 IndicatorDataRef::Ohlc { close, .. } => close.len(),
11095 IndicatorDataRef::Ohlcv { close, .. } => close.len(),
11096 _ => {
11097 return Err(IndicatorDispatchError::MissingRequiredInput {
11098 indicator: "market_meanness_index".to_string(),
11099 input: IndicatorInputKind::Ohlc,
11100 })
11101 }
11102 };
11103 let kernel = req.kernel.to_non_batch();
11104 collect_f64("market_meanness_index", output_id, req.combos, data_len, |params| {
11105 let length = get_usize_param("market_meanness_index", params, "length", 300)?;
11106 let source_mode =
11107 get_enum_param("market_meanness_index", params, "source_mode", "Price")?;
11108 let input = match req.data {
11109 IndicatorDataRef::Candles { candles, .. } => MarketMeannessIndexInput::from_candles(
11110 candles,
11111 MarketMeannessIndexParams {
11112 length: Some(length),
11113 source_mode: Some(source_mode),
11114 },
11115 ),
11116 IndicatorDataRef::Ohlc { open, close, .. } => MarketMeannessIndexInput::from_slices(
11117 open,
11118 close,
11119 MarketMeannessIndexParams {
11120 length: Some(length),
11121 source_mode: Some(source_mode),
11122 },
11123 ),
11124 IndicatorDataRef::Ohlcv { open, close, .. } => MarketMeannessIndexInput::from_slices(
11125 open,
11126 close,
11127 MarketMeannessIndexParams {
11128 length: Some(length),
11129 source_mode: Some(source_mode),
11130 },
11131 ),
11132 _ => unreachable!(),
11133 };
11134 let out = market_meanness_index_with_kernel(&input, kernel).map_err(|e| {
11135 IndicatorDispatchError::ComputeFailed {
11136 indicator: "market_meanness_index".to_string(),
11137 details: e.to_string(),
11138 }
11139 })?;
11140 if output_id.eq_ignore_ascii_case("mmi") || output_id.eq_ignore_ascii_case("value") {
11141 return Ok(out.mmi);
11142 }
11143 if output_id.eq_ignore_ascii_case("mmi_smoothed") {
11144 return Ok(out.mmi_smoothed);
11145 }
11146 Err(IndicatorDispatchError::UnknownOutput {
11147 indicator: "market_meanness_index".to_string(),
11148 output: output_id.to_string(),
11149 })
11150 })
11151}
11152
11153fn compute_momentum_ratio_oscillator_batch(
11154 req: IndicatorBatchRequest<'_>,
11155 output_id: &str,
11156) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11157 let data_len = match req.data {
11158 IndicatorDataRef::Candles { candles, source } => {
11159 source_type(candles, source.unwrap_or("close")).len()
11160 }
11161 IndicatorDataRef::Slice { values } => values.len(),
11162 _ => {
11163 return Err(IndicatorDispatchError::MissingRequiredInput {
11164 indicator: "momentum_ratio_oscillator".to_string(),
11165 input: IndicatorInputKind::Slice,
11166 })
11167 }
11168 };
11169 let kernel = req.kernel.to_non_batch();
11170 collect_f64(
11171 "momentum_ratio_oscillator",
11172 output_id,
11173 req.combos,
11174 data_len,
11175 |params| {
11176 let source = get_enum_param("momentum_ratio_oscillator", params, "source", "close")?;
11177 let period = get_usize_param("momentum_ratio_oscillator", params, "period", 50)?;
11178 let input = match req.data {
11179 IndicatorDataRef::Candles { candles, .. } => {
11180 MomentumRatioOscillatorInput::from_candles(
11181 candles,
11182 &source,
11183 MomentumRatioOscillatorParams {
11184 period: Some(period),
11185 },
11186 )
11187 }
11188 IndicatorDataRef::Slice { values } => MomentumRatioOscillatorInput::from_slice(
11189 values,
11190 MomentumRatioOscillatorParams {
11191 period: Some(period),
11192 },
11193 ),
11194 _ => unreachable!(),
11195 };
11196 let out = momentum_ratio_oscillator_with_kernel(&input, kernel).map_err(|e| {
11197 IndicatorDispatchError::ComputeFailed {
11198 indicator: "momentum_ratio_oscillator".to_string(),
11199 details: e.to_string(),
11200 }
11201 })?;
11202 if output_id.eq_ignore_ascii_case("line") || output_id.eq_ignore_ascii_case("value") {
11203 return Ok(out.line);
11204 }
11205 if output_id.eq_ignore_ascii_case("signal") {
11206 return Ok(out.signal);
11207 }
11208 Err(IndicatorDispatchError::UnknownOutput {
11209 indicator: "momentum_ratio_oscillator".to_string(),
11210 output: output_id.to_string(),
11211 })
11212 },
11213 )
11214}
11215
11216fn compute_pretty_good_oscillator_batch(
11217 req: IndicatorBatchRequest<'_>,
11218 output_id: &str,
11219) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11220 expect_value_output("pretty_good_oscillator", output_id)?;
11221 let data_len = match req.data {
11222 IndicatorDataRef::Candles { candles, source } => {
11223 source_type(candles, source.unwrap_or("close")).len()
11224 }
11225 IndicatorDataRef::Ohlc { close, .. } => close.len(),
11226 IndicatorDataRef::Ohlcv { close, .. } => close.len(),
11227 _ => {
11228 return Err(IndicatorDispatchError::MissingRequiredInput {
11229 indicator: "pretty_good_oscillator".to_string(),
11230 input: IndicatorInputKind::Ohlc,
11231 })
11232 }
11233 };
11234 let kernel = req.kernel.to_non_batch();
11235 collect_f64("pretty_good_oscillator", output_id, req.combos, data_len, |params| {
11236 let source = get_enum_param("pretty_good_oscillator", params, "source", "close")?;
11237 let length = get_usize_param("pretty_good_oscillator", params, "length", 14)?;
11238 let input = match req.data {
11239 IndicatorDataRef::Candles { candles, .. } => PrettyGoodOscillatorInput::from_candles(
11240 candles,
11241 &source,
11242 PrettyGoodOscillatorParams {
11243 length: Some(length),
11244 },
11245 ),
11246 IndicatorDataRef::Ohlc { high, low, close, open } => {
11247 ensure_same_len_4("pretty_good_oscillator", open.len(), high.len(), low.len(), close.len())?;
11248 let src = match source.to_ascii_lowercase().as_str() {
11249 "open" => open,
11250 "high" => high,
11251 "low" => low,
11252 _ => close,
11253 };
11254 PrettyGoodOscillatorInput::from_slices(
11255 high,
11256 low,
11257 close,
11258 src,
11259 PrettyGoodOscillatorParams {
11260 length: Some(length),
11261 },
11262 )
11263 }
11264 IndicatorDataRef::Ohlcv { high, low, close, open, volume } => {
11265 ensure_same_len_5(
11266 "pretty_good_oscillator",
11267 open.len(),
11268 high.len(),
11269 low.len(),
11270 close.len(),
11271 volume.len(),
11272 )?;
11273 let src = match source.to_ascii_lowercase().as_str() {
11274 "open" => open,
11275 "high" => high,
11276 "low" => low,
11277 _ => close,
11278 };
11279 PrettyGoodOscillatorInput::from_slices(
11280 high,
11281 low,
11282 close,
11283 src,
11284 PrettyGoodOscillatorParams {
11285 length: Some(length),
11286 },
11287 )
11288 }
11289 _ => unreachable!(),
11290 };
11291 let out = pretty_good_oscillator_with_kernel(&input, kernel).map_err(|e| {
11292 IndicatorDispatchError::ComputeFailed {
11293 indicator: "pretty_good_oscillator".to_string(),
11294 details: e.to_string(),
11295 }
11296 })?;
11297 Ok(out.values)
11298 })
11299}
11300
11301fn compute_price_density_market_noise_batch(
11302 req: IndicatorBatchRequest<'_>,
11303 output_id: &str,
11304) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11305 let (high, low, close) = extract_ohlc_input("price_density_market_noise", req.data)?;
11306 let kernel = req.kernel.to_non_batch();
11307 collect_f64(
11308 "price_density_market_noise",
11309 output_id,
11310 req.combos,
11311 close.len(),
11312 |params| {
11313 let length = get_usize_param("price_density_market_noise", params, "length", 14)?;
11314 let eval_period =
11315 get_usize_param("price_density_market_noise", params, "eval_period", 200)?;
11316 let input = PriceDensityMarketNoiseInput::from_slices(
11317 high,
11318 low,
11319 close,
11320 PriceDensityMarketNoiseParams {
11321 length: Some(length),
11322 eval_period: Some(eval_period),
11323 },
11324 );
11325 let out = price_density_market_noise_with_kernel(&input, kernel).map_err(|e| {
11326 IndicatorDispatchError::ComputeFailed {
11327 indicator: "price_density_market_noise".to_string(),
11328 details: e.to_string(),
11329 }
11330 })?;
11331 if output_id.eq_ignore_ascii_case("price_density") || output_id.eq_ignore_ascii_case("value")
11332 {
11333 return Ok(out.price_density);
11334 }
11335 if output_id.eq_ignore_ascii_case("price_density_percent") {
11336 return Ok(out.price_density_percent);
11337 }
11338 Err(IndicatorDispatchError::UnknownOutput {
11339 indicator: "price_density_market_noise".to_string(),
11340 output: output_id.to_string(),
11341 })
11342 },
11343 )
11344}
11345
11346fn compute_psychological_line_batch(
11347 req: IndicatorBatchRequest<'_>,
11348 output_id: &str,
11349) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11350 expect_value_output("psychological_line", output_id)?;
11351 let data_len = match req.data {
11352 IndicatorDataRef::Candles { candles, source } => {
11353 source_type(candles, source.unwrap_or("close")).len()
11354 }
11355 IndicatorDataRef::Slice { values } => values.len(),
11356 _ => {
11357 return Err(IndicatorDispatchError::MissingRequiredInput {
11358 indicator: "psychological_line".to_string(),
11359 input: IndicatorInputKind::Slice,
11360 })
11361 }
11362 };
11363 let kernel = req.kernel.to_non_batch();
11364 collect_f64("psychological_line", output_id, req.combos, data_len, |params| {
11365 let source = get_enum_param("psychological_line", params, "source", "close")?;
11366 let length = get_usize_param("psychological_line", params, "length", 20)?;
11367 let input = match req.data {
11368 IndicatorDataRef::Candles { candles, .. } => PsychologicalLineInput::from_candles(
11369 candles,
11370 &source,
11371 PsychologicalLineParams {
11372 length: Some(length),
11373 },
11374 ),
11375 IndicatorDataRef::Slice { values } => PsychologicalLineInput::from_slice(
11376 values,
11377 PsychologicalLineParams {
11378 length: Some(length),
11379 },
11380 ),
11381 _ => unreachable!(),
11382 };
11383 let out = psychological_line_with_kernel(&input, kernel).map_err(|e| {
11384 IndicatorDispatchError::ComputeFailed {
11385 indicator: "psychological_line".to_string(),
11386 details: e.to_string(),
11387 }
11388 })?;
11389 Ok(out.values)
11390 })
11391}
11392
11393fn compute_rank_correlation_index_batch(
11394 req: IndicatorBatchRequest<'_>,
11395 output_id: &str,
11396) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11397 expect_value_output("rank_correlation_index", output_id)?;
11398 let data_len = match req.data {
11399 IndicatorDataRef::Candles { candles, source } => {
11400 source_type(candles, source.unwrap_or("close")).len()
11401 }
11402 IndicatorDataRef::Slice { values } => values.len(),
11403 _ => {
11404 return Err(IndicatorDispatchError::MissingRequiredInput {
11405 indicator: "rank_correlation_index".to_string(),
11406 input: IndicatorInputKind::Slice,
11407 })
11408 }
11409 };
11410 let kernel = req.kernel.to_non_batch();
11411 collect_f64("rank_correlation_index", output_id, req.combos, data_len, |params| {
11412 let source = get_enum_param("rank_correlation_index", params, "source", "close")?;
11413 let length = get_usize_param("rank_correlation_index", params, "length", 12)?;
11414 let input = match req.data {
11415 IndicatorDataRef::Candles { candles, .. } => RankCorrelationIndexInput::from_candles(
11416 candles,
11417 &source,
11418 RankCorrelationIndexParams {
11419 length: Some(length),
11420 },
11421 ),
11422 IndicatorDataRef::Slice { values } => RankCorrelationIndexInput::from_slice(
11423 values,
11424 RankCorrelationIndexParams {
11425 length: Some(length),
11426 },
11427 ),
11428 _ => unreachable!(),
11429 };
11430 let out = rank_correlation_index_with_kernel(&input, kernel).map_err(|e| {
11431 IndicatorDispatchError::ComputeFailed {
11432 indicator: "rank_correlation_index".to_string(),
11433 details: e.to_string(),
11434 }
11435 })?;
11436 Ok(out.values)
11437 })
11438}
11439
11440fn compute_smoothed_gaussian_trend_filter_batch(
11441 req: IndicatorBatchRequest<'_>,
11442 output_id: &str,
11443) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11444 let (high, low, close) = extract_ohlc_input("smoothed_gaussian_trend_filter", req.data)?;
11445 let kernel = req.kernel.to_non_batch();
11446 collect_f64(
11447 "smoothed_gaussian_trend_filter",
11448 output_id,
11449 req.combos,
11450 close.len(),
11451 |params| {
11452 let gaussian_length = get_usize_param(
11453 "smoothed_gaussian_trend_filter",
11454 params,
11455 "gaussian_length",
11456 15,
11457 )?;
11458 let poles =
11459 get_usize_param("smoothed_gaussian_trend_filter", params, "poles", 3)?;
11460 let smoothing_length = get_usize_param(
11461 "smoothed_gaussian_trend_filter",
11462 params,
11463 "smoothing_length",
11464 22,
11465 )?;
11466 let linreg_offset = get_usize_param(
11467 "smoothed_gaussian_trend_filter",
11468 params,
11469 "linreg_offset",
11470 7,
11471 )?;
11472 let input = SmoothedGaussianTrendFilterInput::from_slices(
11473 high,
11474 low,
11475 close,
11476 SmoothedGaussianTrendFilterParams {
11477 gaussian_length: Some(gaussian_length),
11478 poles: Some(poles),
11479 smoothing_length: Some(smoothing_length),
11480 linreg_offset: Some(linreg_offset),
11481 },
11482 );
11483 let out = smoothed_gaussian_trend_filter_with_kernel(&input, kernel).map_err(|e| {
11484 IndicatorDispatchError::ComputeFailed {
11485 indicator: "smoothed_gaussian_trend_filter".to_string(),
11486 details: e.to_string(),
11487 }
11488 })?;
11489 if output_id.eq_ignore_ascii_case("filter") || output_id.eq_ignore_ascii_case("value") {
11490 return Ok(out.filter);
11491 }
11492 if output_id.eq_ignore_ascii_case("supertrend") {
11493 return Ok(out.supertrend);
11494 }
11495 if output_id.eq_ignore_ascii_case("trend") {
11496 return Ok(out.trend);
11497 }
11498 if output_id.eq_ignore_ascii_case("ranging") {
11499 return Ok(out.ranging);
11500 }
11501 Err(IndicatorDispatchError::UnknownOutput {
11502 indicator: "smoothed_gaussian_trend_filter".to_string(),
11503 output: output_id.to_string(),
11504 })
11505 },
11506 )
11507}
11508
11509fn compute_stochastic_adaptive_d_batch(
11510 req: IndicatorBatchRequest<'_>,
11511 output_id: &str,
11512) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11513 let (high, low, close) = extract_ohlc_input("stochastic_adaptive_d", req.data)?;
11514 let kernel = req.kernel.to_non_batch();
11515 collect_f64("stochastic_adaptive_d", output_id, req.combos, close.len(), |params| {
11516 let k_length = get_usize_param("stochastic_adaptive_d", params, "k_length", 20)?;
11517 let d_smoothing =
11518 get_usize_param("stochastic_adaptive_d", params, "d_smoothing", 9)?;
11519 let pre_smooth =
11520 get_usize_param("stochastic_adaptive_d", params, "pre_smooth", 20)?;
11521 let attenuation =
11522 get_f64_param("stochastic_adaptive_d", params, "attenuation", 2.0)?;
11523 let input = StochasticAdaptiveDInput::from_slices(
11524 high,
11525 low,
11526 close,
11527 StochasticAdaptiveDParams {
11528 k_length: Some(k_length),
11529 d_smoothing: Some(d_smoothing),
11530 pre_smooth: Some(pre_smooth),
11531 attenuation: Some(attenuation),
11532 },
11533 );
11534 let out = stochastic_adaptive_d_with_kernel(&input, kernel).map_err(|e| {
11535 IndicatorDispatchError::ComputeFailed {
11536 indicator: "stochastic_adaptive_d".to_string(),
11537 details: e.to_string(),
11538 }
11539 })?;
11540 if output_id.eq_ignore_ascii_case("standard_d") || output_id.eq_ignore_ascii_case("value") {
11541 return Ok(out.standard_d);
11542 }
11543 if output_id.eq_ignore_ascii_case("adaptive_d") {
11544 return Ok(out.adaptive_d);
11545 }
11546 if output_id.eq_ignore_ascii_case("difference") {
11547 return Ok(out.difference);
11548 }
11549 Err(IndicatorDispatchError::UnknownOutput {
11550 indicator: "stochastic_adaptive_d".to_string(),
11551 output: output_id.to_string(),
11552 })
11553 })
11554}
11555
11556fn compute_stochastic_connors_rsi_batch(
11557 req: IndicatorBatchRequest<'_>,
11558 output_id: &str,
11559) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11560 let data_len = match req.data {
11561 IndicatorDataRef::Candles { candles, source } => {
11562 source_type(candles, source.unwrap_or("close")).len()
11563 }
11564 IndicatorDataRef::Slice { values } => values.len(),
11565 _ => {
11566 return Err(IndicatorDispatchError::MissingRequiredInput {
11567 indicator: "stochastic_connors_rsi".to_string(),
11568 input: IndicatorInputKind::Slice,
11569 })
11570 }
11571 };
11572 let kernel = req.kernel.to_non_batch();
11573 collect_f64("stochastic_connors_rsi", output_id, req.combos, data_len, |params| {
11574 let source = get_enum_param("stochastic_connors_rsi", params, "source", "close")?;
11575 let stoch_length =
11576 get_usize_param("stochastic_connors_rsi", params, "stoch_length", 3)?;
11577 let smooth_k = get_usize_param("stochastic_connors_rsi", params, "smooth_k", 3)?;
11578 let smooth_d = get_usize_param("stochastic_connors_rsi", params, "smooth_d", 3)?;
11579 let rsi_length = get_usize_param("stochastic_connors_rsi", params, "rsi_length", 3)?;
11580 let updown_length =
11581 get_usize_param("stochastic_connors_rsi", params, "updown_length", 2)?;
11582 let roc_length = get_usize_param("stochastic_connors_rsi", params, "roc_length", 100)?;
11583 let input = match req.data {
11584 IndicatorDataRef::Candles { candles, .. } => StochasticConnorsRsiInput::from_candles(
11585 candles,
11586 &source,
11587 StochasticConnorsRsiParams {
11588 stoch_length: Some(stoch_length),
11589 smooth_k: Some(smooth_k),
11590 smooth_d: Some(smooth_d),
11591 rsi_length: Some(rsi_length),
11592 updown_length: Some(updown_length),
11593 roc_length: Some(roc_length),
11594 },
11595 ),
11596 IndicatorDataRef::Slice { values } => StochasticConnorsRsiInput::from_slice(
11597 values,
11598 StochasticConnorsRsiParams {
11599 stoch_length: Some(stoch_length),
11600 smooth_k: Some(smooth_k),
11601 smooth_d: Some(smooth_d),
11602 rsi_length: Some(rsi_length),
11603 updown_length: Some(updown_length),
11604 roc_length: Some(roc_length),
11605 },
11606 ),
11607 _ => unreachable!(),
11608 };
11609 let out = stochastic_connors_rsi_with_kernel(&input, kernel).map_err(|e| {
11610 IndicatorDispatchError::ComputeFailed {
11611 indicator: "stochastic_connors_rsi".to_string(),
11612 details: e.to_string(),
11613 }
11614 })?;
11615 if output_id.eq_ignore_ascii_case("k") || output_id.eq_ignore_ascii_case("value") {
11616 return Ok(out.k);
11617 }
11618 if output_id.eq_ignore_ascii_case("d") {
11619 return Ok(out.d);
11620 }
11621 Err(IndicatorDispatchError::UnknownOutput {
11622 indicator: "stochastic_connors_rsi".to_string(),
11623 output: output_id.to_string(),
11624 })
11625 })
11626}
11627
11628fn compute_supertrend_oscillator_batch(
11629 req: IndicatorBatchRequest<'_>,
11630 output_id: &str,
11631) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11632 let data_len = match req.data {
11633 IndicatorDataRef::Candles { candles, source } => {
11634 source_type(candles, source.unwrap_or("close")).len()
11635 }
11636 IndicatorDataRef::Ohlc { close, .. } => close.len(),
11637 IndicatorDataRef::Ohlcv { close, .. } => close.len(),
11638 _ => {
11639 return Err(IndicatorDispatchError::MissingRequiredInput {
11640 indicator: "supertrend_oscillator".to_string(),
11641 input: IndicatorInputKind::Ohlc,
11642 })
11643 }
11644 };
11645 let kernel = req.kernel.to_non_batch();
11646 collect_f64("supertrend_oscillator", output_id, req.combos, data_len, |params| {
11647 let source = get_enum_param("supertrend_oscillator", params, "source", "close")?;
11648 let length = get_usize_param("supertrend_oscillator", params, "length", 10)?;
11649 let mult = get_f64_param("supertrend_oscillator", params, "mult", 2.0)?;
11650 let smooth = get_usize_param("supertrend_oscillator", params, "smooth", 72)?;
11651 let input = match req.data {
11652 IndicatorDataRef::Candles { candles, .. } => SuperTrendOscillatorInput::from_candles(
11653 candles,
11654 &source,
11655 SuperTrendOscillatorParams {
11656 length: Some(length),
11657 mult: Some(mult),
11658 smooth: Some(smooth),
11659 },
11660 ),
11661 IndicatorDataRef::Ohlc { high, low, close, open } => {
11662 ensure_same_len_4("supertrend_oscillator", open.len(), high.len(), low.len(), close.len())?;
11663 let src = match source.to_ascii_lowercase().as_str() {
11664 "open" => open,
11665 "high" => high,
11666 "low" => low,
11667 _ => close,
11668 };
11669 SuperTrendOscillatorInput::from_slices(
11670 high,
11671 low,
11672 src,
11673 SuperTrendOscillatorParams {
11674 length: Some(length),
11675 mult: Some(mult),
11676 smooth: Some(smooth),
11677 },
11678 )
11679 }
11680 IndicatorDataRef::Ohlcv { high, low, close, open, volume } => {
11681 ensure_same_len_5(
11682 "supertrend_oscillator",
11683 open.len(),
11684 high.len(),
11685 low.len(),
11686 close.len(),
11687 volume.len(),
11688 )?;
11689 let src = match source.to_ascii_lowercase().as_str() {
11690 "open" => open,
11691 "high" => high,
11692 "low" => low,
11693 _ => close,
11694 };
11695 SuperTrendOscillatorInput::from_slices(
11696 high,
11697 low,
11698 src,
11699 SuperTrendOscillatorParams {
11700 length: Some(length),
11701 mult: Some(mult),
11702 smooth: Some(smooth),
11703 },
11704 )
11705 }
11706 _ => unreachable!(),
11707 };
11708 let out = supertrend_oscillator_with_kernel(&input, kernel).map_err(|e| {
11709 IndicatorDispatchError::ComputeFailed {
11710 indicator: "supertrend_oscillator".to_string(),
11711 details: e.to_string(),
11712 }
11713 })?;
11714 if output_id.eq_ignore_ascii_case("oscillator") || output_id.eq_ignore_ascii_case("value") {
11715 return Ok(out.oscillator);
11716 }
11717 if output_id.eq_ignore_ascii_case("signal") {
11718 return Ok(out.signal);
11719 }
11720 if output_id.eq_ignore_ascii_case("histogram") || output_id.eq_ignore_ascii_case("hist") {
11721 return Ok(out.histogram);
11722 }
11723 Err(IndicatorDispatchError::UnknownOutput {
11724 indicator: "supertrend_oscillator".to_string(),
11725 output: output_id.to_string(),
11726 })
11727 })
11728}
11729
11730fn compute_trend_continuation_factor_batch(
11731 req: IndicatorBatchRequest<'_>,
11732 output_id: &str,
11733) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11734 let data_len = match req.data {
11735 IndicatorDataRef::Candles { candles, source } => {
11736 source_type(candles, source.unwrap_or("close")).len()
11737 }
11738 IndicatorDataRef::Slice { values } => values.len(),
11739 _ => {
11740 return Err(IndicatorDispatchError::MissingRequiredInput {
11741 indicator: "trend_continuation_factor".to_string(),
11742 input: IndicatorInputKind::Slice,
11743 })
11744 }
11745 };
11746 let kernel = req.kernel.to_non_batch();
11747 collect_f64(
11748 "trend_continuation_factor",
11749 output_id,
11750 req.combos,
11751 data_len,
11752 |params| {
11753 let source =
11754 get_enum_param("trend_continuation_factor", params, "source", "close")?;
11755 let length = get_usize_param("trend_continuation_factor", params, "length", 35)?;
11756 let input = match req.data {
11757 IndicatorDataRef::Candles { candles, .. } => {
11758 TrendContinuationFactorInput::from_candles(
11759 candles,
11760 &source,
11761 TrendContinuationFactorParams {
11762 length: Some(length),
11763 },
11764 )
11765 }
11766 IndicatorDataRef::Slice { values } => TrendContinuationFactorInput::from_slice(
11767 values,
11768 TrendContinuationFactorParams {
11769 length: Some(length),
11770 },
11771 ),
11772 _ => unreachable!(),
11773 };
11774 let out = trend_continuation_factor_with_kernel(&input, kernel).map_err(|e| {
11775 IndicatorDispatchError::ComputeFailed {
11776 indicator: "trend_continuation_factor".to_string(),
11777 details: e.to_string(),
11778 }
11779 })?;
11780 if output_id.eq_ignore_ascii_case("plus_tcf") || output_id.eq_ignore_ascii_case("value") {
11781 return Ok(out.plus_tcf);
11782 }
11783 if output_id.eq_ignore_ascii_case("minus_tcf") {
11784 return Ok(out.minus_tcf);
11785 }
11786 Err(IndicatorDispatchError::UnknownOutput {
11787 indicator: "trend_continuation_factor".to_string(),
11788 output: output_id.to_string(),
11789 })
11790 },
11791 )
11792}
11793
11794fn compute_volume_weighted_stochastic_rsi_batch(
11795 req: IndicatorBatchRequest<'_>,
11796 output_id: &str,
11797) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11798 let (source, volume) =
11799 extract_close_volume_input("volume_weighted_stochastic_rsi", req.data, "close")?;
11800 let kernel = req.kernel.to_non_batch();
11801 collect_f64(
11802 "volume_weighted_stochastic_rsi",
11803 output_id,
11804 req.combos,
11805 source.len(),
11806 |params| {
11807 let rsi_length =
11808 get_usize_param("volume_weighted_stochastic_rsi", params, "rsi_length", 14)?;
11809 let stoch_length =
11810 get_usize_param("volume_weighted_stochastic_rsi", params, "stoch_length", 14)?;
11811 let k_length =
11812 get_usize_param("volume_weighted_stochastic_rsi", params, "k_length", 3)?;
11813 let d_length =
11814 get_usize_param("volume_weighted_stochastic_rsi", params, "d_length", 3)?;
11815 let ma_type =
11816 get_enum_param("volume_weighted_stochastic_rsi", params, "ma_type", "WSMA")?;
11817 let input = VolumeWeightedStochasticRsiInput::from_slices(
11818 source,
11819 volume,
11820 VolumeWeightedStochasticRsiParams {
11821 rsi_length: Some(rsi_length),
11822 stoch_length: Some(stoch_length),
11823 k_length: Some(k_length),
11824 d_length: Some(d_length),
11825 ma_type: Some(ma_type),
11826 },
11827 );
11828 let out = volume_weighted_stochastic_rsi_with_kernel(&input, kernel).map_err(|e| {
11829 IndicatorDispatchError::ComputeFailed {
11830 indicator: "volume_weighted_stochastic_rsi".to_string(),
11831 details: e.to_string(),
11832 }
11833 })?;
11834 if output_id.eq_ignore_ascii_case("k") || output_id.eq_ignore_ascii_case("value") {
11835 return Ok(out.k);
11836 }
11837 if output_id.eq_ignore_ascii_case("d") {
11838 return Ok(out.d);
11839 }
11840 Err(IndicatorDispatchError::UnknownOutput {
11841 indicator: "volume_weighted_stochastic_rsi".to_string(),
11842 output: output_id.to_string(),
11843 })
11844 },
11845 )
11846}
11847
11848fn compute_logarithmic_moving_average_batch(
11849 req: IndicatorBatchRequest<'_>,
11850 output_id: &str,
11851) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11852 let data_len = match req.data {
11853 IndicatorDataRef::Candles { candles, source } => {
11854 source_type(candles, source.unwrap_or("close")).len()
11855 }
11856 IndicatorDataRef::Slice { values } => values.len(),
11857 IndicatorDataRef::CloseVolume { close, .. } => close.len(),
11858 IndicatorDataRef::Ohlcv { close, .. } => close.len(),
11859 _ => {
11860 return Err(IndicatorDispatchError::MissingRequiredInput {
11861 indicator: "logarithmic_moving_average".to_string(),
11862 input: IndicatorInputKind::CloseVolume,
11863 })
11864 }
11865 };
11866 let kernel = req.kernel.to_non_batch();
11867 collect_f64(
11868 "logarithmic_moving_average",
11869 output_id,
11870 req.combos,
11871 data_len,
11872 |params| {
11873 let source =
11874 get_enum_param("logarithmic_moving_average", params, "source", "close")?;
11875 let period = get_usize_param("logarithmic_moving_average", params, "period", 100)?;
11876 let steepness =
11877 get_f64_param("logarithmic_moving_average", params, "steepness", 2.5)?;
11878 let ma_type =
11879 get_enum_param("logarithmic_moving_average", params, "ma_type", "ema")?;
11880 let smooth = get_usize_param("logarithmic_moving_average", params, "smooth", 10)?;
11881 let momentum_weight = get_f64_param(
11882 "logarithmic_moving_average",
11883 params,
11884 "momentum_weight",
11885 1.2,
11886 )?;
11887 let long_threshold = get_f64_param(
11888 "logarithmic_moving_average",
11889 params,
11890 "long_threshold",
11891 0.5,
11892 )?;
11893 let short_threshold = get_f64_param(
11894 "logarithmic_moving_average",
11895 params,
11896 "short_threshold",
11897 -0.5,
11898 )?;
11899 let params = LogarithmicMovingAverageParams {
11900 period: Some(period),
11901 steepness: Some(steepness),
11902 ma_type: Some(ma_type),
11903 smooth: Some(smooth),
11904 momentum_weight: Some(momentum_weight),
11905 long_threshold: Some(long_threshold),
11906 short_threshold: Some(short_threshold),
11907 };
11908 let input = match req.data {
11909 IndicatorDataRef::Candles { candles, .. } => {
11910 LogarithmicMovingAverageInput::from_candles(candles, &source, params)
11911 }
11912 IndicatorDataRef::Slice { values } => {
11913 LogarithmicMovingAverageInput::from_slice(values, params)
11914 }
11915 IndicatorDataRef::CloseVolume { close, volume } => {
11916 LogarithmicMovingAverageInput::from_slice_with_volume(close, volume, params)
11917 }
11918 IndicatorDataRef::Ohlcv {
11919 open,
11920 high,
11921 low,
11922 close,
11923 volume,
11924 } => {
11925 let price = match source.to_ascii_lowercase().as_str() {
11926 "open" => open,
11927 "high" => high,
11928 "low" => low,
11929 _ => close,
11930 };
11931 LogarithmicMovingAverageInput::from_slice_with_volume(price, volume, params)
11932 }
11933 _ => unreachable!(),
11934 };
11935 let out = logarithmic_moving_average_with_kernel(&input, kernel).map_err(|e| {
11936 IndicatorDispatchError::ComputeFailed {
11937 indicator: "logarithmic_moving_average".to_string(),
11938 details: e.to_string(),
11939 }
11940 })?;
11941 if output_id.eq_ignore_ascii_case("lma") || output_id.eq_ignore_ascii_case("value") {
11942 return Ok(out.lma);
11943 }
11944 if output_id.eq_ignore_ascii_case("signal") {
11945 return Ok(out.signal);
11946 }
11947 if output_id.eq_ignore_ascii_case("position") {
11948 return Ok(out.position);
11949 }
11950 if output_id.eq_ignore_ascii_case("momentum_confirmed") {
11951 return Ok(out.momentum_confirmed);
11952 }
11953 Err(IndicatorDispatchError::UnknownOutput {
11954 indicator: "logarithmic_moving_average".to_string(),
11955 output: output_id.to_string(),
11956 })
11957 },
11958 )
11959}
11960
11961fn compute_adaptive_schaff_trend_cycle_batch(
11962 req: IndicatorBatchRequest<'_>,
11963 output_id: &str,
11964) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11965 let (high, low, close) = extract_ohlc_input("adaptive_schaff_trend_cycle", req.data)?;
11966 let kernel = req.kernel.to_non_batch();
11967 collect_f64(
11968 "adaptive_schaff_trend_cycle",
11969 output_id,
11970 req.combos,
11971 close.len(),
11972 |params| {
11973 let adaptive_length =
11974 get_usize_param("adaptive_schaff_trend_cycle", params, "adaptive_length", 55)?;
11975 let stc_length =
11976 get_usize_param("adaptive_schaff_trend_cycle", params, "stc_length", 12)?;
11977 let smoothing_factor =
11978 get_f64_param("adaptive_schaff_trend_cycle", params, "smoothing_factor", 0.45)?;
11979 let fast_length =
11980 get_usize_param("adaptive_schaff_trend_cycle", params, "fast_length", 26)?;
11981 let slow_length =
11982 get_usize_param("adaptive_schaff_trend_cycle", params, "slow_length", 50)?;
11983 let input = AdaptiveSchaffTrendCycleInput::from_slices(
11984 high,
11985 low,
11986 close,
11987 AdaptiveSchaffTrendCycleParams {
11988 adaptive_length: Some(adaptive_length),
11989 stc_length: Some(stc_length),
11990 smoothing_factor: Some(smoothing_factor),
11991 fast_length: Some(fast_length),
11992 slow_length: Some(slow_length),
11993 },
11994 );
11995 let out = adaptive_schaff_trend_cycle_with_kernel(&input, kernel).map_err(|e| {
11996 IndicatorDispatchError::ComputeFailed {
11997 indicator: "adaptive_schaff_trend_cycle".to_string(),
11998 details: e.to_string(),
11999 }
12000 })?;
12001 if output_id.eq_ignore_ascii_case("stc") || output_id.eq_ignore_ascii_case("value") {
12002 return Ok(out.stc);
12003 }
12004 if output_id.eq_ignore_ascii_case("histogram") || output_id.eq_ignore_ascii_case("hist")
12005 {
12006 return Ok(out.histogram);
12007 }
12008 Err(IndicatorDispatchError::UnknownOutput {
12009 indicator: "adaptive_schaff_trend_cycle".to_string(),
12010 output: output_id.to_string(),
12011 })
12012 },
12013 )
12014}
12015
12016fn compute_ehlers_detrending_filter_batch(
12017 req: IndicatorBatchRequest<'_>,
12018 output_id: &str,
12019) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12020 let data_len = match req.data {
12021 IndicatorDataRef::Candles { candles, source } => {
12022 source_type(candles, source.unwrap_or("hlcc4")).len()
12023 }
12024 IndicatorDataRef::Slice { values } => values.len(),
12025 _ => {
12026 return Err(IndicatorDispatchError::MissingRequiredInput {
12027 indicator: "ehlers_detrending_filter".to_string(),
12028 input: IndicatorInputKind::Slice,
12029 })
12030 }
12031 };
12032 let kernel = req.kernel.to_non_batch();
12033 collect_f64(
12034 "ehlers_detrending_filter",
12035 output_id,
12036 req.combos,
12037 data_len,
12038 |params| {
12039 let source = get_enum_param("ehlers_detrending_filter", params, "source", "hlcc4")?;
12040 let length = get_usize_param("ehlers_detrending_filter", params, "length", 10)?;
12041 let input = match req.data {
12042 IndicatorDataRef::Candles { candles, .. } => EhlersDetrendingFilterInput::from_candles(
12043 candles,
12044 &source,
12045 EhlersDetrendingFilterParams {
12046 length: Some(length),
12047 },
12048 ),
12049 IndicatorDataRef::Slice { values } => EhlersDetrendingFilterInput::from_slice(
12050 values,
12051 EhlersDetrendingFilterParams {
12052 length: Some(length),
12053 },
12054 ),
12055 _ => unreachable!(),
12056 };
12057 let out = ehlers_detrending_filter_with_kernel(&input, kernel).map_err(|e| {
12058 IndicatorDispatchError::ComputeFailed {
12059 indicator: "ehlers_detrending_filter".to_string(),
12060 details: e.to_string(),
12061 }
12062 })?;
12063 if output_id.eq_ignore_ascii_case("edf") || output_id.eq_ignore_ascii_case("value") {
12064 return Ok(out.edf);
12065 }
12066 if output_id.eq_ignore_ascii_case("signal") {
12067 return Ok(out.signal);
12068 }
12069 Err(IndicatorDispatchError::UnknownOutput {
12070 indicator: "ehlers_detrending_filter".to_string(),
12071 output: output_id.to_string(),
12072 })
12073 },
12074 )
12075}
12076
12077fn compute_hypertrend_batch(
12078 req: IndicatorBatchRequest<'_>,
12079 output_id: &str,
12080) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12081 let data_len = match req.data {
12082 IndicatorDataRef::Candles { candles, source } => {
12083 source_type(candles, source.unwrap_or("close")).len()
12084 }
12085 IndicatorDataRef::Ohlc { close, .. } => close.len(),
12086 IndicatorDataRef::Ohlcv { close, .. } => close.len(),
12087 _ => {
12088 return Err(IndicatorDispatchError::MissingRequiredInput {
12089 indicator: "hypertrend".to_string(),
12090 input: IndicatorInputKind::Ohlc,
12091 })
12092 }
12093 };
12094 let kernel = req.kernel.to_non_batch();
12095 collect_f64("hypertrend", output_id, req.combos, data_len, |params| {
12096 let source = get_enum_param("hypertrend", params, "source", "close")?;
12097 let factor = get_f64_param("hypertrend", params, "factor", 5.0)?;
12098 let slope = get_f64_param("hypertrend", params, "slope", 14.0)?;
12099 let width_percent = get_f64_param("hypertrend", params, "width_percent", 80.0)?;
12100 let input = match req.data {
12101 IndicatorDataRef::Candles { candles, .. } => HyperTrendInput::from_candles(
12102 candles,
12103 &source,
12104 HyperTrendParams {
12105 factor: Some(factor),
12106 slope: Some(slope),
12107 width_percent: Some(width_percent),
12108 },
12109 ),
12110 IndicatorDataRef::Ohlc { high, low, close, open } => {
12111 ensure_same_len_4("hypertrend", open.len(), high.len(), low.len(), close.len())?;
12112 let src = match source.to_ascii_lowercase().as_str() {
12113 "open" => open,
12114 "high" => high,
12115 "low" => low,
12116 _ => close,
12117 };
12118 HyperTrendInput::from_slices(
12119 high,
12120 low,
12121 src,
12122 HyperTrendParams {
12123 factor: Some(factor),
12124 slope: Some(slope),
12125 width_percent: Some(width_percent),
12126 },
12127 )
12128 }
12129 IndicatorDataRef::Ohlcv { high, low, close, open, volume } => {
12130 ensure_same_len_5("hypertrend", open.len(), high.len(), low.len(), close.len(), volume.len())?;
12131 let src = match source.to_ascii_lowercase().as_str() {
12132 "open" => open,
12133 "high" => high,
12134 "low" => low,
12135 _ => close,
12136 };
12137 HyperTrendInput::from_slices(
12138 high,
12139 low,
12140 src,
12141 HyperTrendParams {
12142 factor: Some(factor),
12143 slope: Some(slope),
12144 width_percent: Some(width_percent),
12145 },
12146 )
12147 }
12148 _ => unreachable!(),
12149 };
12150 let out = hypertrend_with_kernel(&input, kernel).map_err(|e| {
12151 IndicatorDispatchError::ComputeFailed {
12152 indicator: "hypertrend".to_string(),
12153 details: e.to_string(),
12154 }
12155 })?;
12156 if output_id.eq_ignore_ascii_case("upper") {
12157 return Ok(out.upper);
12158 }
12159 if output_id.eq_ignore_ascii_case("average") || output_id.eq_ignore_ascii_case("value") {
12160 return Ok(out.average);
12161 }
12162 if output_id.eq_ignore_ascii_case("lower") {
12163 return Ok(out.lower);
12164 }
12165 if output_id.eq_ignore_ascii_case("trend") {
12166 return Ok(out.trend);
12167 }
12168 if output_id.eq_ignore_ascii_case("changed") {
12169 return Ok(out.changed);
12170 }
12171 Err(IndicatorDispatchError::UnknownOutput {
12172 indicator: "hypertrend".to_string(),
12173 output: output_id.to_string(),
12174 })
12175 })
12176}
12177
12178fn compute_ict_propulsion_block_batch(
12179 req: IndicatorBatchRequest<'_>,
12180 output_id: &str,
12181) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12182 let (open, high, low, close) = extract_ohlc_full_input("ict_propulsion_block", req.data)?;
12183 let kernel = req.kernel.to_non_batch();
12184 collect_f64("ict_propulsion_block", output_id, req.combos, close.len(), |params| {
12185 let swing_length = get_usize_param("ict_propulsion_block", params, "swing_length", 3)?;
12186 let mitigation_price = match get_enum_param(
12187 "ict_propulsion_block",
12188 params,
12189 "mitigation_price",
12190 "close",
12191 )?
12192 .to_ascii_lowercase()
12193 .as_str()
12194 {
12195 "close" => IctPropulsionBlockMitigationPrice::Close,
12196 "wick" => IctPropulsionBlockMitigationPrice::Wick,
12197 other => {
12198 return Err(IndicatorDispatchError::InvalidParam {
12199 indicator: "ict_propulsion_block".to_string(),
12200 key: "mitigation_price".to_string(),
12201 reason: format!("unsupported value '{other}'"),
12202 })
12203 }
12204 };
12205 let input = IctPropulsionBlockInput::from_slices(
12206 open,
12207 high,
12208 low,
12209 close,
12210 IctPropulsionBlockParams {
12211 swing_length: Some(swing_length),
12212 mitigation_price: Some(mitigation_price),
12213 },
12214 );
12215 let out = ict_propulsion_block_with_kernel(&input, kernel).map_err(|e| {
12216 IndicatorDispatchError::ComputeFailed {
12217 indicator: "ict_propulsion_block".to_string(),
12218 details: e.to_string(),
12219 }
12220 })?;
12221 if output_id.eq_ignore_ascii_case("bullish_high") {
12222 return Ok(out.bullish_high);
12223 }
12224 if output_id.eq_ignore_ascii_case("bullish_low") {
12225 return Ok(out.bullish_low);
12226 }
12227 if output_id.eq_ignore_ascii_case("bullish_kind") {
12228 return Ok(out.bullish_kind);
12229 }
12230 if output_id.eq_ignore_ascii_case("bullish_active") {
12231 return Ok(out.bullish_active);
12232 }
12233 if output_id.eq_ignore_ascii_case("bullish_mitigated") {
12234 return Ok(out.bullish_mitigated);
12235 }
12236 if output_id.eq_ignore_ascii_case("bullish_new") {
12237 return Ok(out.bullish_new);
12238 }
12239 if output_id.eq_ignore_ascii_case("bearish_high") {
12240 return Ok(out.bearish_high);
12241 }
12242 if output_id.eq_ignore_ascii_case("bearish_low") {
12243 return Ok(out.bearish_low);
12244 }
12245 if output_id.eq_ignore_ascii_case("bearish_kind") {
12246 return Ok(out.bearish_kind);
12247 }
12248 if output_id.eq_ignore_ascii_case("bearish_active") {
12249 return Ok(out.bearish_active);
12250 }
12251 if output_id.eq_ignore_ascii_case("bearish_mitigated") {
12252 return Ok(out.bearish_mitigated);
12253 }
12254 if output_id.eq_ignore_ascii_case("bearish_new") {
12255 return Ok(out.bearish_new);
12256 }
12257 Err(IndicatorDispatchError::UnknownOutput {
12258 indicator: "ict_propulsion_block".to_string(),
12259 output: output_id.to_string(),
12260 })
12261 })
12262}
12263
12264fn compute_impulse_macd_batch(
12265 req: IndicatorBatchRequest<'_>,
12266 output_id: &str,
12267) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12268 let (high, low, close) = extract_ohlc_input("impulse_macd", req.data)?;
12269 let kernel = req.kernel.to_non_batch();
12270 collect_f64("impulse_macd", output_id, req.combos, close.len(), |params| {
12271 let length_ma = get_usize_param("impulse_macd", params, "length_ma", 34)?;
12272 let length_signal = get_usize_param("impulse_macd", params, "length_signal", 9)?;
12273 let input = ImpulseMacdInput::from_slices(
12274 high,
12275 low,
12276 close,
12277 ImpulseMacdParams {
12278 length_ma: Some(length_ma),
12279 length_signal: Some(length_signal),
12280 },
12281 );
12282 let out = impulse_macd_with_kernel(&input, kernel).map_err(|e| {
12283 IndicatorDispatchError::ComputeFailed {
12284 indicator: "impulse_macd".to_string(),
12285 details: e.to_string(),
12286 }
12287 })?;
12288 if output_id.eq_ignore_ascii_case("impulse_macd") || output_id.eq_ignore_ascii_case("value")
12289 {
12290 return Ok(out.impulse_macd);
12291 }
12292 if output_id.eq_ignore_ascii_case("impulse_histo")
12293 || output_id.eq_ignore_ascii_case("histogram")
12294 || output_id.eq_ignore_ascii_case("hist")
12295 {
12296 return Ok(out.impulse_histo);
12297 }
12298 if output_id.eq_ignore_ascii_case("signal") {
12299 return Ok(out.signal);
12300 }
12301 Err(IndicatorDispatchError::UnknownOutput {
12302 indicator: "impulse_macd".to_string(),
12303 output: output_id.to_string(),
12304 })
12305 })
12306}
12307
12308fn compute_keltner_channel_width_oscillator_batch(
12309 req: IndicatorBatchRequest<'_>,
12310 output_id: &str,
12311) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12312 let (high, low, close) = extract_ohlc_input("keltner_channel_width_oscillator", req.data)?;
12313 let kernel = req.kernel.to_non_batch();
12314 collect_f64(
12315 "keltner_channel_width_oscillator",
12316 output_id,
12317 req.combos,
12318 close.len(),
12319 |params| {
12320 let source =
12321 get_enum_param("keltner_channel_width_oscillator", params, "source", "close")?;
12322 let length =
12323 get_usize_param("keltner_channel_width_oscillator", params, "length", 20)?;
12324 let multiplier =
12325 get_f64_param("keltner_channel_width_oscillator", params, "multiplier", 2.0)?;
12326 let use_exponential = get_bool_param(
12327 "keltner_channel_width_oscillator",
12328 params,
12329 "use_exponential",
12330 true,
12331 )?;
12332 let bands_style = get_enum_param(
12333 "keltner_channel_width_oscillator",
12334 params,
12335 "bands_style",
12336 "Average True Range",
12337 )?;
12338 let atr_length =
12339 get_usize_param("keltner_channel_width_oscillator", params, "atr_length", 10)?;
12340 let src = match req.data {
12341 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
12342 IndicatorDataRef::Ohlc { open, high, low, close } => {
12343 ensure_same_len_4(
12344 "keltner_channel_width_oscillator",
12345 open.len(),
12346 high.len(),
12347 low.len(),
12348 close.len(),
12349 )?;
12350 match source.to_ascii_lowercase().as_str() {
12351 "open" => open,
12352 "high" => high,
12353 "low" => low,
12354 _ => close,
12355 }
12356 }
12357 IndicatorDataRef::Ohlcv {
12358 open,
12359 high,
12360 low,
12361 close,
12362 volume,
12363 } => {
12364 ensure_same_len_5(
12365 "keltner_channel_width_oscillator",
12366 open.len(),
12367 high.len(),
12368 low.len(),
12369 close.len(),
12370 volume.len(),
12371 )?;
12372 match source.to_ascii_lowercase().as_str() {
12373 "open" => open,
12374 "high" => high,
12375 "low" => low,
12376 _ => close,
12377 }
12378 }
12379 _ => close,
12380 };
12381 let input = KeltnerChannelWidthOscillatorInput::from_slices(
12382 high,
12383 low,
12384 close,
12385 src,
12386 KeltnerChannelWidthOscillatorParams {
12387 length: Some(length),
12388 multiplier: Some(multiplier),
12389 use_exponential: Some(use_exponential),
12390 bands_style: Some(bands_style),
12391 atr_length: Some(atr_length),
12392 },
12393 );
12394 let out = keltner_channel_width_oscillator_with_kernel(&input, kernel).map_err(|e| {
12395 IndicatorDispatchError::ComputeFailed {
12396 indicator: "keltner_channel_width_oscillator".to_string(),
12397 details: e.to_string(),
12398 }
12399 })?;
12400 if output_id.eq_ignore_ascii_case("kbw") || output_id.eq_ignore_ascii_case("value") {
12401 return Ok(out.kbw);
12402 }
12403 if output_id.eq_ignore_ascii_case("kbw_sma") {
12404 return Ok(out.kbw_sma);
12405 }
12406 Err(IndicatorDispatchError::UnknownOutput {
12407 indicator: "keltner_channel_width_oscillator".to_string(),
12408 output: output_id.to_string(),
12409 })
12410 },
12411 )
12412}
12413
12414fn compute_leavitt_convolution_acceleration_batch(
12415 req: IndicatorBatchRequest<'_>,
12416 output_id: &str,
12417) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12418 let data_len = match req.data {
12419 IndicatorDataRef::Candles { candles, source } => {
12420 source_type(candles, source.unwrap_or("close")).len()
12421 }
12422 IndicatorDataRef::Slice { values } => values.len(),
12423 _ => {
12424 return Err(IndicatorDispatchError::MissingRequiredInput {
12425 indicator: "leavitt_convolution_acceleration".to_string(),
12426 input: IndicatorInputKind::Slice,
12427 })
12428 }
12429 };
12430 let kernel = req.kernel.to_non_batch();
12431 collect_f64(
12432 "leavitt_convolution_acceleration",
12433 output_id,
12434 req.combos,
12435 data_len,
12436 |params| {
12437 let source =
12438 get_enum_param("leavitt_convolution_acceleration", params, "source", "close")?;
12439 let length =
12440 get_usize_param("leavitt_convolution_acceleration", params, "length", 70)?;
12441 let norm_length =
12442 get_usize_param("leavitt_convolution_acceleration", params, "norm_length", 150)?;
12443 let use_norm_hyperbolic = get_bool_param(
12444 "leavitt_convolution_acceleration",
12445 params,
12446 "use_norm_hyperbolic",
12447 true,
12448 )?;
12449 let input = match req.data {
12450 IndicatorDataRef::Candles { candles, .. } => {
12451 LeavittConvolutionAccelerationInput::from_candles(
12452 candles,
12453 &source,
12454 LeavittConvolutionAccelerationParams {
12455 length: Some(length),
12456 norm_length: Some(norm_length),
12457 use_norm_hyperbolic: Some(use_norm_hyperbolic),
12458 },
12459 )
12460 }
12461 IndicatorDataRef::Slice { values } => LeavittConvolutionAccelerationInput::from_slice(
12462 values,
12463 LeavittConvolutionAccelerationParams {
12464 length: Some(length),
12465 norm_length: Some(norm_length),
12466 use_norm_hyperbolic: Some(use_norm_hyperbolic),
12467 },
12468 ),
12469 _ => unreachable!(),
12470 };
12471 let out = leavitt_convolution_acceleration_with_kernel(&input, kernel).map_err(|e| {
12472 IndicatorDispatchError::ComputeFailed {
12473 indicator: "leavitt_convolution_acceleration".to_string(),
12474 details: e.to_string(),
12475 }
12476 })?;
12477 if output_id.eq_ignore_ascii_case("conv_acceleration")
12478 || output_id.eq_ignore_ascii_case("value")
12479 {
12480 return Ok(out.conv_acceleration);
12481 }
12482 if output_id.eq_ignore_ascii_case("signal") {
12483 return Ok(out.signal);
12484 }
12485 Err(IndicatorDispatchError::UnknownOutput {
12486 indicator: "leavitt_convolution_acceleration".to_string(),
12487 output: output_id.to_string(),
12488 })
12489 },
12490 )
12491}
12492
12493fn compute_squeeze_index_batch(
12494 req: IndicatorBatchRequest<'_>,
12495 output_id: &str,
12496) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12497 expect_value_output("squeeze_index", output_id)?;
12498 let data = extract_slice_input("squeeze_index", req.data, "close")?;
12499 let kernel = req.kernel.to_non_batch();
12500 collect_f64(
12501 "squeeze_index",
12502 output_id,
12503 req.combos,
12504 data.len(),
12505 |params| {
12506 let conv = get_f64_param("squeeze_index", params, "conv", 50.0)?;
12507 let length = get_usize_param("squeeze_index", params, "length", 20)?;
12508 let input = SqueezeIndexInput::from_slice(
12509 data,
12510 SqueezeIndexParams {
12511 conv: Some(conv),
12512 length: Some(length),
12513 },
12514 );
12515 let out = squeeze_index_with_kernel(&input, kernel).map_err(|e| {
12516 IndicatorDispatchError::ComputeFailed {
12517 indicator: "squeeze_index".to_string(),
12518 details: e.to_string(),
12519 }
12520 })?;
12521 Ok(out.values)
12522 },
12523 )
12524}
12525
12526fn compute_stochastic_distance_batch(
12527 req: IndicatorBatchRequest<'_>,
12528 output_id: &str,
12529) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12530 if !output_id.eq_ignore_ascii_case("oscillator") && !output_id.eq_ignore_ascii_case("signal") {
12531 return Err(IndicatorDispatchError::UnknownOutput {
12532 indicator: "stochastic_distance".to_string(),
12533 output: output_id.to_string(),
12534 });
12535 }
12536 let data = extract_slice_input("stochastic_distance", req.data, "close")?;
12537 let kernel = req.kernel.to_non_batch();
12538 collect_f64(
12539 "stochastic_distance",
12540 output_id,
12541 req.combos,
12542 data.len(),
12543 |params| {
12544 let lookback_length =
12545 get_usize_param("stochastic_distance", params, "lookback_length", 200)?;
12546 let length1 = get_usize_param("stochastic_distance", params, "length1", 12)?;
12547 let length2 = get_usize_param("stochastic_distance", params, "length2", 3)?;
12548 let ob_level = get_i32_param("stochastic_distance", params, "ob_level", 40)?;
12549 let os_level = get_i32_param("stochastic_distance", params, "os_level", -40)?;
12550 let input = StochasticDistanceInput::from_slice(
12551 data,
12552 StochasticDistanceParams {
12553 lookback_length: Some(lookback_length),
12554 length1: Some(length1),
12555 length2: Some(length2),
12556 ob_level: Some(ob_level),
12557 os_level: Some(os_level),
12558 },
12559 );
12560 let out = stochastic_distance_with_kernel(&input, kernel).map_err(|e| {
12561 IndicatorDispatchError::ComputeFailed {
12562 indicator: "stochastic_distance".to_string(),
12563 details: e.to_string(),
12564 }
12565 })?;
12566 match output_id {
12567 "oscillator" => Ok(out.oscillator),
12568 "signal" => Ok(out.signal),
12569 _ => Err(IndicatorDispatchError::UnknownOutput {
12570 indicator: "stochastic_distance".to_string(),
12571 output: output_id.to_string(),
12572 }),
12573 }
12574 },
12575 )
12576}
12577
12578fn compute_vertical_horizontal_filter_batch(
12579 req: IndicatorBatchRequest<'_>,
12580 output_id: &str,
12581) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12582 expect_value_output("vertical_horizontal_filter", output_id)?;
12583 let data = extract_slice_input("vertical_horizontal_filter", req.data, "close")?;
12584 let kernel = req.kernel.to_non_batch();
12585 collect_f64(
12586 "vertical_horizontal_filter",
12587 output_id,
12588 req.combos,
12589 data.len(),
12590 |params| {
12591 let length = get_usize_param("vertical_horizontal_filter", params, "length", 28)?;
12592 let input = VerticalHorizontalFilterInput::from_slice(
12593 data,
12594 VerticalHorizontalFilterParams {
12595 length: Some(length),
12596 },
12597 );
12598 let out = vertical_horizontal_filter_with_kernel(&input, kernel).map_err(|e| {
12599 IndicatorDispatchError::ComputeFailed {
12600 indicator: "vertical_horizontal_filter".to_string(),
12601 details: e.to_string(),
12602 }
12603 })?;
12604 Ok(out.values)
12605 },
12606 )
12607}
12608
12609fn compute_intraday_momentum_index_batch(
12610 req: IndicatorBatchRequest<'_>,
12611 output_id: &str,
12612) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12613 let (open, _high, _low, close) = extract_ohlc_full_input("intraday_momentum_index", req.data)?;
12614 let kernel = req.kernel.to_non_batch();
12615 collect_f64(
12616 "intraday_momentum_index",
12617 output_id,
12618 req.combos,
12619 open.len(),
12620 |params| {
12621 let length = get_usize_param("intraday_momentum_index", params, "length", 14)?;
12622 let length_ma = get_usize_param("intraday_momentum_index", params, "length_ma", 6)?;
12623 let mult = get_f64_param("intraday_momentum_index", params, "mult", 2.0)?;
12624 let length_bb = get_usize_param("intraday_momentum_index", params, "length_bb", 20)?;
12625 let apply_smoothing =
12626 get_bool_param("intraday_momentum_index", params, "apply_smoothing", false)?;
12627 let low_band = get_usize_param("intraday_momentum_index", params, "low_band", 10)?;
12628 let input = IntradayMomentumIndexInput::from_slices(
12629 open,
12630 close,
12631 IntradayMomentumIndexParams {
12632 length: Some(length),
12633 length_ma: Some(length_ma),
12634 mult: Some(mult),
12635 length_bb: Some(length_bb),
12636 apply_smoothing: Some(apply_smoothing),
12637 low_band: Some(low_band),
12638 },
12639 );
12640 let out = intraday_momentum_index_with_kernel(&input, kernel).map_err(|e| {
12641 IndicatorDispatchError::ComputeFailed {
12642 indicator: "intraday_momentum_index".to_string(),
12643 details: e.to_string(),
12644 }
12645 })?;
12646 if output_id.eq_ignore_ascii_case("imi") || output_id.eq_ignore_ascii_case("value") {
12647 return Ok(out.imi);
12648 }
12649 if output_id.eq_ignore_ascii_case("upper_hit") {
12650 return Ok(out.upper_hit);
12651 }
12652 if output_id.eq_ignore_ascii_case("lower_hit") {
12653 return Ok(out.lower_hit);
12654 }
12655 if output_id.eq_ignore_ascii_case("signal") {
12656 return Ok(out.signal);
12657 }
12658 Err(IndicatorDispatchError::UnknownOutput {
12659 indicator: "intraday_momentum_index".to_string(),
12660 output: output_id.to_string(),
12661 })
12662 },
12663 )
12664}
12665
12666fn compute_vwap_zscore_with_signals_batch(
12667 req: IndicatorBatchRequest<'_>,
12668 output_id: &str,
12669) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12670 let (close, volume) =
12671 extract_close_volume_input("vwap_zscore_with_signals", req.data, "close")?;
12672 let kernel = req.kernel.to_non_batch();
12673 collect_f64(
12674 "vwap_zscore_with_signals",
12675 output_id,
12676 req.combos,
12677 close.len(),
12678 |params| {
12679 let length = get_usize_param("vwap_zscore_with_signals", params, "length", 20)?;
12680 let upper_bottom =
12681 get_f64_param("vwap_zscore_with_signals", params, "upper_bottom", 2.5)?;
12682 let lower_bottom =
12683 get_f64_param("vwap_zscore_with_signals", params, "lower_bottom", -2.5)?;
12684 let input = VwapZscoreWithSignalsInput::from_slices(
12685 close,
12686 volume,
12687 VwapZscoreWithSignalsParams {
12688 length: Some(length),
12689 upper_bottom: Some(upper_bottom),
12690 lower_bottom: Some(lower_bottom),
12691 },
12692 );
12693 let out = vwap_zscore_with_signals_with_kernel(&input, kernel).map_err(|e| {
12694 IndicatorDispatchError::ComputeFailed {
12695 indicator: "vwap_zscore_with_signals".to_string(),
12696 details: e.to_string(),
12697 }
12698 })?;
12699 if output_id.eq_ignore_ascii_case("zvwap") || output_id.eq_ignore_ascii_case("value") {
12700 return Ok(out.zvwap);
12701 }
12702 if output_id.eq_ignore_ascii_case("support_signal") {
12703 return Ok(out.support_signal);
12704 }
12705 if output_id.eq_ignore_ascii_case("resistance_signal") {
12706 return Ok(out.resistance_signal);
12707 }
12708 Err(IndicatorDispatchError::UnknownOutput {
12709 indicator: "vwap_zscore_with_signals".to_string(),
12710 output: output_id.to_string(),
12711 })
12712 },
12713 )
12714}
12715
12716fn compute_hema_trend_levels_batch(
12717 req: IndicatorBatchRequest<'_>,
12718 output_id: &str,
12719) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12720 let (open, high, low, close) = extract_ohlc_full_input("hema_trend_levels", req.data)?;
12721 let kernel = req.kernel.to_non_batch();
12722 collect_f64(
12723 "hema_trend_levels",
12724 output_id,
12725 req.combos,
12726 close.len(),
12727 |params| {
12728 let fast_length = get_usize_param("hema_trend_levels", params, "fast_length", 20)?;
12729 let slow_length = get_usize_param("hema_trend_levels", params, "slow_length", 40)?;
12730 let input = HemaTrendLevelsInput::from_slices(
12731 open,
12732 high,
12733 low,
12734 close,
12735 HemaTrendLevelsParams {
12736 fast_length: Some(fast_length),
12737 slow_length: Some(slow_length),
12738 },
12739 );
12740 let out = hema_trend_levels_with_kernel(&input, kernel).map_err(|e| {
12741 IndicatorDispatchError::ComputeFailed {
12742 indicator: "hema_trend_levels".to_string(),
12743 details: e.to_string(),
12744 }
12745 })?;
12746 if output_id.eq_ignore_ascii_case("fast_hema")
12747 || output_id.eq_ignore_ascii_case("value")
12748 {
12749 return Ok(out.fast_hema);
12750 }
12751 if output_id.eq_ignore_ascii_case("slow_hema") {
12752 return Ok(out.slow_hema);
12753 }
12754 if output_id.eq_ignore_ascii_case("trend_direction")
12755 || output_id.eq_ignore_ascii_case("trend")
12756 {
12757 return Ok(out.trend_direction);
12758 }
12759 if output_id.eq_ignore_ascii_case("bar_state") {
12760 return Ok(out.bar_state);
12761 }
12762 if output_id.eq_ignore_ascii_case("bullish_crossover")
12763 || output_id.eq_ignore_ascii_case("buy_signal")
12764 || output_id.eq_ignore_ascii_case("buy")
12765 {
12766 return Ok(out.bullish_crossover);
12767 }
12768 if output_id.eq_ignore_ascii_case("bearish_crossunder")
12769 || output_id.eq_ignore_ascii_case("sell_signal")
12770 || output_id.eq_ignore_ascii_case("sell")
12771 {
12772 return Ok(out.bearish_crossunder);
12773 }
12774 if output_id.eq_ignore_ascii_case("box_offset") {
12775 return Ok(out.box_offset);
12776 }
12777 if output_id.eq_ignore_ascii_case("bull_box_top") {
12778 return Ok(out.bull_box_top);
12779 }
12780 if output_id.eq_ignore_ascii_case("bull_box_bottom") {
12781 return Ok(out.bull_box_bottom);
12782 }
12783 if output_id.eq_ignore_ascii_case("bear_box_top") {
12784 return Ok(out.bear_box_top);
12785 }
12786 if output_id.eq_ignore_ascii_case("bear_box_bottom") {
12787 return Ok(out.bear_box_bottom);
12788 }
12789 if output_id.eq_ignore_ascii_case("bullish_test") {
12790 return Ok(out.bullish_test);
12791 }
12792 if output_id.eq_ignore_ascii_case("bearish_test") {
12793 return Ok(out.bearish_test);
12794 }
12795 if output_id.eq_ignore_ascii_case("bullish_test_level") {
12796 return Ok(out.bullish_test_level);
12797 }
12798 if output_id.eq_ignore_ascii_case("bearish_test_level") {
12799 return Ok(out.bearish_test_level);
12800 }
12801 Err(IndicatorDispatchError::UnknownOutput {
12802 indicator: "hema_trend_levels".to_string(),
12803 output: output_id.to_string(),
12804 })
12805 },
12806 )
12807}
12808
12809fn compute_macd_wave_signal_pro_batch(
12810 req: IndicatorBatchRequest<'_>,
12811 output_id: &str,
12812) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12813 let (open, high, low, close) = extract_ohlc_full_input("macd_wave_signal_pro", req.data)?;
12814 let kernel = req.kernel.to_non_batch();
12815 collect_f64(
12816 "macd_wave_signal_pro",
12817 output_id,
12818 req.combos,
12819 close.len(),
12820 |_params| {
12821 let input =
12822 MacdWaveSignalProInput::from_slices(open, high, low, close, Default::default());
12823 let out = macd_wave_signal_pro_with_kernel(&input, kernel).map_err(|e| {
12824 IndicatorDispatchError::ComputeFailed {
12825 indicator: "macd_wave_signal_pro".to_string(),
12826 details: e.to_string(),
12827 }
12828 })?;
12829 if output_id.eq_ignore_ascii_case("diff") || output_id.eq_ignore_ascii_case("value") {
12830 return Ok(out.diff);
12831 }
12832 if output_id.eq_ignore_ascii_case("dea") {
12833 return Ok(out.dea);
12834 }
12835 if output_id.eq_ignore_ascii_case("macd_histogram")
12836 || output_id.eq_ignore_ascii_case("macd")
12837 || output_id.eq_ignore_ascii_case("histogram")
12838 || output_id.eq_ignore_ascii_case("hist")
12839 {
12840 return Ok(out.macd_histogram);
12841 }
12842 if output_id.eq_ignore_ascii_case("line_convergence")
12843 || output_id.eq_ignore_ascii_case("line_conv")
12844 {
12845 return Ok(out.line_convergence);
12846 }
12847 if output_id.eq_ignore_ascii_case("buy_signal") || output_id.eq_ignore_ascii_case("buy")
12848 {
12849 return Ok(out.buy_signal);
12850 }
12851 if output_id.eq_ignore_ascii_case("sell_signal")
12852 || output_id.eq_ignore_ascii_case("sell")
12853 {
12854 return Ok(out.sell_signal);
12855 }
12856 Err(IndicatorDispatchError::UnknownOutput {
12857 indicator: "macd_wave_signal_pro".to_string(),
12858 output: output_id.to_string(),
12859 })
12860 },
12861 )
12862}
12863
12864fn compute_demand_index_batch(
12865 req: IndicatorBatchRequest<'_>,
12866 output_id: &str,
12867) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12868 let (high, low, close, volume) = extract_hlcv_input("demand_index", req.data)?;
12869 let kernel = req.kernel.to_non_batch();
12870 collect_f64(
12871 "demand_index",
12872 output_id,
12873 req.combos,
12874 high.len(),
12875 |params| {
12876 let len_bs = get_usize_param("demand_index", params, "len_bs", 19)?;
12877 let len_bs_ma = get_usize_param("demand_index", params, "len_bs_ma", 19)?;
12878 let len_di_ma = get_usize_param("demand_index", params, "len_di_ma", 19)?;
12879 let ma_type = get_enum_param("demand_index", params, "ma_type", "ema")?;
12880 let input = DemandIndexInput::from_slices(
12881 high,
12882 low,
12883 close,
12884 volume,
12885 DemandIndexParams {
12886 len_bs: Some(len_bs),
12887 len_bs_ma: Some(len_bs_ma),
12888 len_di_ma: Some(len_di_ma),
12889 ma_type: Some(ma_type),
12890 },
12891 );
12892 let out = demand_index_with_kernel(&input, kernel).map_err(|e| {
12893 IndicatorDispatchError::ComputeFailed {
12894 indicator: "demand_index".to_string(),
12895 details: e.to_string(),
12896 }
12897 })?;
12898 if output_id.eq_ignore_ascii_case("demand_index")
12899 || output_id.eq_ignore_ascii_case("value")
12900 {
12901 return Ok(out.demand_index);
12902 }
12903 if output_id.eq_ignore_ascii_case("signal") {
12904 return Ok(out.signal);
12905 }
12906 Err(IndicatorDispatchError::UnknownOutput {
12907 indicator: "demand_index".to_string(),
12908 output: output_id.to_string(),
12909 })
12910 },
12911 )
12912}
12913
12914fn compute_kase_peak_oscillator_with_divergences_batch(
12915 req: IndicatorBatchRequest<'_>,
12916 output_id: &str,
12917) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12918 let (high, low, close) = extract_ohlc_input("kase_peak_oscillator_with_divergences", req.data)?;
12919 let kernel = req.kernel.to_non_batch();
12920 collect_f64(
12921 "kase_peak_oscillator_with_divergences",
12922 output_id,
12923 req.combos,
12924 close.len(),
12925 |params| {
12926 let deviations = get_f64_param(
12927 "kase_peak_oscillator_with_divergences",
12928 params,
12929 "deviations",
12930 2.0,
12931 )?;
12932 let short_cycle = get_usize_param(
12933 "kase_peak_oscillator_with_divergences",
12934 params,
12935 "short_cycle",
12936 8,
12937 )?;
12938 let long_cycle = get_usize_param(
12939 "kase_peak_oscillator_with_divergences",
12940 params,
12941 "long_cycle",
12942 65,
12943 )?;
12944 let sensitivity = get_f64_param(
12945 "kase_peak_oscillator_with_divergences",
12946 params,
12947 "sensitivity",
12948 40.0,
12949 )?;
12950 let all_peaks_mode = get_bool_param(
12951 "kase_peak_oscillator_with_divergences",
12952 params,
12953 "all_peaks_mode",
12954 true,
12955 )?;
12956 let lb_r = get_usize_param("kase_peak_oscillator_with_divergences", params, "lb_r", 5)?;
12957 let lb_l = get_usize_param("kase_peak_oscillator_with_divergences", params, "lb_l", 5)?;
12958 let range_upper = get_usize_param(
12959 "kase_peak_oscillator_with_divergences",
12960 params,
12961 "range_upper",
12962 60,
12963 )?;
12964 let range_lower = get_usize_param(
12965 "kase_peak_oscillator_with_divergences",
12966 params,
12967 "range_lower",
12968 5,
12969 )?;
12970 let plot_bull = get_bool_param(
12971 "kase_peak_oscillator_with_divergences",
12972 params,
12973 "plot_bull",
12974 true,
12975 )?;
12976 let plot_hidden_bull = get_bool_param(
12977 "kase_peak_oscillator_with_divergences",
12978 params,
12979 "plot_hidden_bull",
12980 false,
12981 )?;
12982 let plot_bear = get_bool_param(
12983 "kase_peak_oscillator_with_divergences",
12984 params,
12985 "plot_bear",
12986 true,
12987 )?;
12988 let plot_hidden_bear = get_bool_param(
12989 "kase_peak_oscillator_with_divergences",
12990 params,
12991 "plot_hidden_bear",
12992 false,
12993 )?;
12994 let input = KasePeakOscillatorWithDivergencesInput::from_slices(
12995 high,
12996 low,
12997 close,
12998 KasePeakOscillatorWithDivergencesParams {
12999 deviations: Some(deviations),
13000 short_cycle: Some(short_cycle),
13001 long_cycle: Some(long_cycle),
13002 sensitivity: Some(sensitivity),
13003 all_peaks_mode: Some(all_peaks_mode),
13004 lb_r: Some(lb_r),
13005 lb_l: Some(lb_l),
13006 range_upper: Some(range_upper),
13007 range_lower: Some(range_lower),
13008 plot_bull: Some(plot_bull),
13009 plot_hidden_bull: Some(plot_hidden_bull),
13010 plot_bear: Some(plot_bear),
13011 plot_hidden_bear: Some(plot_hidden_bear),
13012 },
13013 );
13014 let out =
13015 kase_peak_oscillator_with_divergences_with_kernel(&input, kernel).map_err(|e| {
13016 IndicatorDispatchError::ComputeFailed {
13017 indicator: "kase_peak_oscillator_with_divergences".to_string(),
13018 details: e.to_string(),
13019 }
13020 })?;
13021 if output_id.eq_ignore_ascii_case("oscillator")
13022 || output_id.eq_ignore_ascii_case("value")
13023 {
13024 return Ok(out.oscillator);
13025 }
13026 if output_id.eq_ignore_ascii_case("hist") || output_id.eq_ignore_ascii_case("histogram")
13027 {
13028 return Ok(out.histogram);
13029 }
13030 if output_id.eq_ignore_ascii_case("max_peak_value") {
13031 return Ok(out.max_peak_value);
13032 }
13033 if output_id.eq_ignore_ascii_case("min_peak_value") {
13034 return Ok(out.min_peak_value);
13035 }
13036 if output_id.eq_ignore_ascii_case("market_extreme") {
13037 return Ok(out.market_extreme);
13038 }
13039 if output_id.eq_ignore_ascii_case("regular_bullish") {
13040 return Ok(out.regular_bullish);
13041 }
13042 if output_id.eq_ignore_ascii_case("hidden_bullish") {
13043 return Ok(out.hidden_bullish);
13044 }
13045 if output_id.eq_ignore_ascii_case("regular_bearish") {
13046 return Ok(out.regular_bearish);
13047 }
13048 if output_id.eq_ignore_ascii_case("hidden_bearish") {
13049 return Ok(out.hidden_bearish);
13050 }
13051 if output_id.eq_ignore_ascii_case("go_long") {
13052 return Ok(out.go_long);
13053 }
13054 if output_id.eq_ignore_ascii_case("go_short") {
13055 return Ok(out.go_short);
13056 }
13057 Err(IndicatorDispatchError::UnknownOutput {
13058 indicator: "kase_peak_oscillator_with_divergences".to_string(),
13059 output: output_id.to_string(),
13060 })
13061 },
13062 )
13063}
13064
13065fn compute_gopalakrishnan_range_index_batch(
13066 req: IndicatorBatchRequest<'_>,
13067 output_id: &str,
13068) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13069 expect_value_output("gopalakrishnan_range_index", output_id)?;
13070 let (high, low) = extract_high_low_input("gopalakrishnan_range_index", req.data)?;
13071 let kernel = req.kernel.to_non_batch();
13072 collect_f64(
13073 "gopalakrishnan_range_index",
13074 output_id,
13075 req.combos,
13076 high.len(),
13077 |params| {
13078 let length = get_usize_param("gopalakrishnan_range_index", params, "length", 5)?;
13079 let input = GopalakrishnanRangeIndexInput::from_slices(
13080 high,
13081 low,
13082 GopalakrishnanRangeIndexParams {
13083 length: Some(length),
13084 },
13085 );
13086 let out = gopalakrishnan_range_index_with_kernel(&input, kernel).map_err(|e| {
13087 IndicatorDispatchError::ComputeFailed {
13088 indicator: "gopalakrishnan_range_index".to_string(),
13089 details: e.to_string(),
13090 }
13091 })?;
13092 Ok(out.values)
13093 },
13094 )
13095}
13096
13097fn compute_acosc_batch(
13098 req: IndicatorBatchRequest<'_>,
13099 output_id: &str,
13100) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13101 let (high, low) = extract_high_low_input("acosc", req.data)?;
13102 let kernel = req.kernel.to_non_batch();
13103 collect_f64("acosc", output_id, req.combos, high.len(), |_params| {
13104 let input = AcoscInput::from_slices(high, low, AcoscParams::default());
13105 let out = acosc_with_kernel(&input, kernel).map_err(|e| {
13106 IndicatorDispatchError::ComputeFailed {
13107 indicator: "acosc".to_string(),
13108 details: e.to_string(),
13109 }
13110 })?;
13111 if output_id.eq_ignore_ascii_case("osc") || output_id.eq_ignore_ascii_case("value") {
13112 return Ok(out.osc);
13113 }
13114 if output_id.eq_ignore_ascii_case("change") {
13115 return Ok(out.change);
13116 }
13117 Err(IndicatorDispatchError::UnknownOutput {
13118 indicator: "acosc".to_string(),
13119 output: output_id.to_string(),
13120 })
13121 })
13122}
13123
13124fn compute_alligator_batch(
13125 req: IndicatorBatchRequest<'_>,
13126 output_id: &str,
13127) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13128 let data = extract_slice_input("alligator", req.data, "hl2")?;
13129 let kernel = req.kernel.to_non_batch();
13130 collect_f64("alligator", output_id, req.combos, data.len(), |params| {
13131 let jaw_period = get_usize_param("alligator", params, "jaw_period", 13)?;
13132 let jaw_offset = get_usize_param("alligator", params, "jaw_offset", 8)?;
13133 let teeth_period = get_usize_param("alligator", params, "teeth_period", 8)?;
13134 let teeth_offset = get_usize_param("alligator", params, "teeth_offset", 5)?;
13135 let lips_period = get_usize_param("alligator", params, "lips_period", 5)?;
13136 let lips_offset = get_usize_param("alligator", params, "lips_offset", 3)?;
13137 let input = AlligatorInput::from_slice(
13138 data,
13139 AlligatorParams {
13140 jaw_period: Some(jaw_period),
13141 jaw_offset: Some(jaw_offset),
13142 teeth_period: Some(teeth_period),
13143 teeth_offset: Some(teeth_offset),
13144 lips_period: Some(lips_period),
13145 lips_offset: Some(lips_offset),
13146 },
13147 );
13148 let out = alligator_with_kernel(&input, kernel).map_err(|e| {
13149 IndicatorDispatchError::ComputeFailed {
13150 indicator: "alligator".to_string(),
13151 details: e.to_string(),
13152 }
13153 })?;
13154 if output_id.eq_ignore_ascii_case("jaw") || output_id.eq_ignore_ascii_case("value") {
13155 return Ok(out.jaw);
13156 }
13157 if output_id.eq_ignore_ascii_case("teeth") {
13158 return Ok(out.teeth);
13159 }
13160 if output_id.eq_ignore_ascii_case("lips") {
13161 return Ok(out.lips);
13162 }
13163 Err(IndicatorDispatchError::UnknownOutput {
13164 indicator: "alligator".to_string(),
13165 output: output_id.to_string(),
13166 })
13167 })
13168}
13169
13170fn compute_alphatrend_batch(
13171 req: IndicatorBatchRequest<'_>,
13172 output_id: &str,
13173) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13174 let (open, high, low, close, volume) = extract_ohlcv_full_input("alphatrend", req.data)?;
13175 let kernel = req.kernel.to_non_batch();
13176 collect_f64("alphatrend", output_id, req.combos, close.len(), |params| {
13177 let coeff = get_f64_param("alphatrend", params, "coeff", 1.0)?;
13178 let period = get_usize_param("alphatrend", params, "period", 14)?;
13179 let no_volume = get_bool_param("alphatrend", params, "no_volume", false)?;
13180 let input = AlphaTrendInput::from_slices(
13181 open,
13182 high,
13183 low,
13184 close,
13185 volume,
13186 AlphaTrendParams {
13187 coeff: Some(coeff),
13188 period: Some(period),
13189 no_volume: Some(no_volume),
13190 },
13191 );
13192 let out = alphatrend_with_kernel(&input, kernel).map_err(|e| {
13193 IndicatorDispatchError::ComputeFailed {
13194 indicator: "alphatrend".to_string(),
13195 details: e.to_string(),
13196 }
13197 })?;
13198 if output_id.eq_ignore_ascii_case("k1") || output_id.eq_ignore_ascii_case("value") {
13199 return Ok(out.k1);
13200 }
13201 if output_id.eq_ignore_ascii_case("k2") {
13202 return Ok(out.k2);
13203 }
13204 Err(IndicatorDispatchError::UnknownOutput {
13205 indicator: "alphatrend".to_string(),
13206 output: output_id.to_string(),
13207 })
13208 })
13209}
13210
13211fn compute_aso_batch(
13212 req: IndicatorBatchRequest<'_>,
13213 output_id: &str,
13214) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13215 let (open, high, low, close) = match req.data {
13216 IndicatorDataRef::Candles { candles, source } => (
13217 candles.open.as_slice(),
13218 candles.high.as_slice(),
13219 candles.low.as_slice(),
13220 source_type(candles, source.unwrap_or("close")),
13221 ),
13222 IndicatorDataRef::Ohlc {
13223 open,
13224 high,
13225 low,
13226 close,
13227 } => {
13228 ensure_same_len_4("aso", open.len(), high.len(), low.len(), close.len())?;
13229 (open, high, low, close)
13230 }
13231 IndicatorDataRef::Ohlcv {
13232 open,
13233 high,
13234 low,
13235 close,
13236 volume,
13237 } => {
13238 ensure_same_len_5(
13239 "aso",
13240 open.len(),
13241 high.len(),
13242 low.len(),
13243 close.len(),
13244 volume.len(),
13245 )?;
13246 (open, high, low, close)
13247 }
13248 _ => {
13249 return Err(IndicatorDispatchError::MissingRequiredInput {
13250 indicator: "aso".to_string(),
13251 input: IndicatorInputKind::Ohlc,
13252 });
13253 }
13254 };
13255 let kernel = req.kernel.to_non_batch();
13256 collect_f64("aso", output_id, req.combos, close.len(), |params| {
13257 let period = get_usize_param("aso", params, "period", 10)?;
13258 let mode = get_usize_param("aso", params, "mode", 0)?;
13259 let input = AsoInput::from_slices(
13260 open,
13261 high,
13262 low,
13263 close,
13264 AsoParams {
13265 period: Some(period),
13266 mode: Some(mode),
13267 },
13268 );
13269 let out =
13270 aso_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
13271 indicator: "aso".to_string(),
13272 details: e.to_string(),
13273 })?;
13274 if output_id.eq_ignore_ascii_case("bulls") || output_id.eq_ignore_ascii_case("value") {
13275 return Ok(out.bulls);
13276 }
13277 if output_id.eq_ignore_ascii_case("bears") {
13278 return Ok(out.bears);
13279 }
13280 Err(IndicatorDispatchError::UnknownOutput {
13281 indicator: "aso".to_string(),
13282 output: output_id.to_string(),
13283 })
13284 })
13285}
13286
13287fn compute_avsl_batch(
13288 req: IndicatorBatchRequest<'_>,
13289 output_id: &str,
13290) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13291 expect_value_output("avsl", output_id)?;
13292 let (_high, low, close, volume) = extract_hlcv_input("avsl", req.data)?;
13293 let kernel = req.kernel.to_non_batch();
13294 collect_f64("avsl", output_id, req.combos, close.len(), |params| {
13295 let fast_period = get_usize_param("avsl", params, "fast_period", 12)?;
13296 let slow_period = get_usize_param("avsl", params, "slow_period", 26)?;
13297 let multiplier = get_f64_param("avsl", params, "multiplier", 2.0)?;
13298 let input = AvslInput::from_slices(
13299 close,
13300 low,
13301 volume,
13302 AvslParams {
13303 fast_period: Some(fast_period),
13304 slow_period: Some(slow_period),
13305 multiplier: Some(multiplier),
13306 },
13307 );
13308 let out = avsl_with_kernel(&input, kernel).map_err(|e| {
13309 IndicatorDispatchError::ComputeFailed {
13310 indicator: "avsl".to_string(),
13311 details: e.to_string(),
13312 }
13313 })?;
13314 Ok(out.values)
13315 })
13316}
13317
13318fn compute_bandpass_batch(
13319 req: IndicatorBatchRequest<'_>,
13320 output_id: &str,
13321) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13322 let data = extract_slice_input("bandpass", req.data, "close")?;
13323 let kernel = req.kernel.to_non_batch();
13324 collect_f64("bandpass", output_id, req.combos, data.len(), |params| {
13325 let period = get_usize_param("bandpass", params, "period", 20)?;
13326 let bandwidth = get_f64_param("bandpass", params, "bandwidth", 0.3)?;
13327 let input = BandPassInput::from_slice(
13328 data,
13329 BandPassParams {
13330 period: Some(period),
13331 bandwidth: Some(bandwidth),
13332 },
13333 );
13334 let out = bandpass_with_kernel(&input, kernel).map_err(|e| {
13335 IndicatorDispatchError::ComputeFailed {
13336 indicator: "bandpass".to_string(),
13337 details: e.to_string(),
13338 }
13339 })?;
13340 if output_id.eq_ignore_ascii_case("bp") || output_id.eq_ignore_ascii_case("value") {
13341 return Ok(out.bp);
13342 }
13343 if output_id.eq_ignore_ascii_case("bp_normalized")
13344 || output_id.eq_ignore_ascii_case("normalized")
13345 {
13346 return Ok(out.bp_normalized);
13347 }
13348 if output_id.eq_ignore_ascii_case("signal") {
13349 return Ok(out.signal);
13350 }
13351 if output_id.eq_ignore_ascii_case("trigger") {
13352 return Ok(out.trigger);
13353 }
13354 Err(IndicatorDispatchError::UnknownOutput {
13355 indicator: "bandpass".to_string(),
13356 output: output_id.to_string(),
13357 })
13358 })
13359}
13360
13361fn compute_chande_batch(
13362 req: IndicatorBatchRequest<'_>,
13363 output_id: &str,
13364) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13365 expect_value_output("chande", output_id)?;
13366 let (high, low, close) = extract_ohlc_input("chande", req.data)?;
13367 let kernel = req.kernel.to_non_batch();
13368 collect_f64("chande", output_id, req.combos, close.len(), |params| {
13369 let period = get_usize_param("chande", params, "period", 22)?;
13370 let mult = get_f64_param("chande", params, "mult", 3.0)?;
13371 let direction = get_enum_param("chande", params, "direction", "long")?;
13372 let input = ChandeInput::from_slices(
13373 high,
13374 low,
13375 close,
13376 ChandeParams {
13377 period: Some(period),
13378 mult: Some(mult),
13379 direction: Some(direction.to_string()),
13380 },
13381 );
13382 let out = chande_with_kernel(&input, kernel).map_err(|e| {
13383 IndicatorDispatchError::ComputeFailed {
13384 indicator: "chande".to_string(),
13385 details: e.to_string(),
13386 }
13387 })?;
13388 Ok(out.values)
13389 })
13390}
13391
13392fn compute_chandelier_exit_batch(
13393 req: IndicatorBatchRequest<'_>,
13394 output_id: &str,
13395) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13396 let (high, low, close) = extract_ohlc_input("chandelier_exit", req.data)?;
13397 let kernel = req.kernel.to_non_batch();
13398 collect_f64(
13399 "chandelier_exit",
13400 output_id,
13401 req.combos,
13402 close.len(),
13403 |params| {
13404 let period = get_usize_param("chandelier_exit", params, "period", 22)?;
13405 let mult = get_f64_param("chandelier_exit", params, "mult", 3.0)?;
13406 let use_close = get_bool_param("chandelier_exit", params, "use_close", true)?;
13407 let input = ChandelierExitInput::from_slices(
13408 high,
13409 low,
13410 close,
13411 ChandelierExitParams {
13412 period: Some(period),
13413 mult: Some(mult),
13414 use_close: Some(use_close),
13415 },
13416 );
13417 let out = chandelier_exit_with_kernel(&input, kernel).map_err(|e| {
13418 IndicatorDispatchError::ComputeFailed {
13419 indicator: "chandelier_exit".to_string(),
13420 details: e.to_string(),
13421 }
13422 })?;
13423 if output_id.eq_ignore_ascii_case("long_stop")
13424 || output_id.eq_ignore_ascii_case("value")
13425 {
13426 return Ok(out.long_stop);
13427 }
13428 if output_id.eq_ignore_ascii_case("short_stop") {
13429 return Ok(out.short_stop);
13430 }
13431 Err(IndicatorDispatchError::UnknownOutput {
13432 indicator: "chandelier_exit".to_string(),
13433 output: output_id.to_string(),
13434 })
13435 },
13436 )
13437}
13438
13439fn compute_cksp_batch(
13440 req: IndicatorBatchRequest<'_>,
13441 output_id: &str,
13442) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13443 let (high, low, close) = extract_ohlc_input("cksp", req.data)?;
13444 let kernel = req.kernel.to_non_batch();
13445 collect_f64("cksp", output_id, req.combos, close.len(), |params| {
13446 let p = get_usize_param("cksp", params, "p", 10)?;
13447 let x = get_f64_param("cksp", params, "x", 1.0)?;
13448 let q = get_usize_param("cksp", params, "q", 9)?;
13449 let input = CkspInput::from_slices(
13450 high,
13451 low,
13452 close,
13453 CkspParams {
13454 p: Some(p),
13455 x: Some(x),
13456 q: Some(q),
13457 },
13458 );
13459 let out = cksp_with_kernel(&input, kernel).map_err(|e| {
13460 IndicatorDispatchError::ComputeFailed {
13461 indicator: "cksp".to_string(),
13462 details: e.to_string(),
13463 }
13464 })?;
13465 if output_id.eq_ignore_ascii_case("long_values")
13466 || output_id.eq_ignore_ascii_case("long")
13467 || output_id.eq_ignore_ascii_case("value")
13468 {
13469 return Ok(out.long_values);
13470 }
13471 if output_id.eq_ignore_ascii_case("short_values") || output_id.eq_ignore_ascii_case("short")
13472 {
13473 return Ok(out.short_values);
13474 }
13475 Err(IndicatorDispatchError::UnknownOutput {
13476 indicator: "cksp".to_string(),
13477 output: output_id.to_string(),
13478 })
13479 })
13480}
13481
13482fn compute_correlation_cycle_batch(
13483 req: IndicatorBatchRequest<'_>,
13484 output_id: &str,
13485) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13486 let data = extract_slice_input("correlation_cycle", req.data, "close")?;
13487 let kernel = req.kernel.to_non_batch();
13488 collect_f64(
13489 "correlation_cycle",
13490 output_id,
13491 req.combos,
13492 data.len(),
13493 |params| {
13494 let period = get_usize_param("correlation_cycle", params, "period", 20)?;
13495 let threshold = get_f64_param("correlation_cycle", params, "threshold", 9.0)?;
13496 let input = CorrelationCycleInput::from_slice(
13497 data,
13498 CorrelationCycleParams {
13499 period: Some(period),
13500 threshold: Some(threshold),
13501 },
13502 );
13503 let out = correlation_cycle_with_kernel(&input, kernel).map_err(|e| {
13504 IndicatorDispatchError::ComputeFailed {
13505 indicator: "correlation_cycle".to_string(),
13506 details: e.to_string(),
13507 }
13508 })?;
13509 if output_id.eq_ignore_ascii_case("real") || output_id.eq_ignore_ascii_case("value") {
13510 return Ok(out.real);
13511 }
13512 if output_id.eq_ignore_ascii_case("imag") {
13513 return Ok(out.imag);
13514 }
13515 if output_id.eq_ignore_ascii_case("angle") {
13516 return Ok(out.angle);
13517 }
13518 if output_id.eq_ignore_ascii_case("state") {
13519 return Ok(out.state);
13520 }
13521 Err(IndicatorDispatchError::UnknownOutput {
13522 indicator: "correlation_cycle".to_string(),
13523 output: output_id.to_string(),
13524 })
13525 },
13526 )
13527}
13528
13529fn compute_damiani_volatmeter_batch(
13530 req: IndicatorBatchRequest<'_>,
13531 output_id: &str,
13532) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13533 let data = extract_slice_input("damiani_volatmeter", req.data, "close")?;
13534 let kernel = req.kernel.to_non_batch();
13535 collect_f64(
13536 "damiani_volatmeter",
13537 output_id,
13538 req.combos,
13539 data.len(),
13540 |params| {
13541 let vis_atr = get_usize_param("damiani_volatmeter", params, "vis_atr", 13)?;
13542 let vis_std = get_usize_param("damiani_volatmeter", params, "vis_std", 20)?;
13543 let sed_atr = get_usize_param("damiani_volatmeter", params, "sed_atr", 40)?;
13544 let sed_std = get_usize_param("damiani_volatmeter", params, "sed_std", 100)?;
13545 let threshold = get_f64_param("damiani_volatmeter", params, "threshold", 1.4)?;
13546 let input = DamianiVolatmeterInput::from_slice(
13547 data,
13548 DamianiVolatmeterParams {
13549 vis_atr: Some(vis_atr),
13550 vis_std: Some(vis_std),
13551 sed_atr: Some(sed_atr),
13552 sed_std: Some(sed_std),
13553 threshold: Some(threshold),
13554 },
13555 );
13556 let out = damiani_volatmeter_with_kernel(&input, kernel).map_err(|e| {
13557 IndicatorDispatchError::ComputeFailed {
13558 indicator: "damiani_volatmeter".to_string(),
13559 details: e.to_string(),
13560 }
13561 })?;
13562 if output_id.eq_ignore_ascii_case("vol") || output_id.eq_ignore_ascii_case("value") {
13563 return Ok(out.vol);
13564 }
13565 if output_id.eq_ignore_ascii_case("anti") {
13566 return Ok(out.anti);
13567 }
13568 Err(IndicatorDispatchError::UnknownOutput {
13569 indicator: "damiani_volatmeter".to_string(),
13570 output: output_id.to_string(),
13571 })
13572 },
13573 )
13574}
13575
13576fn compute_dvdiqqe_batch(
13577 req: IndicatorBatchRequest<'_>,
13578 output_id: &str,
13579) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13580 let (open, high, low, close, volume) = match req.data {
13581 IndicatorDataRef::Candles { candles, .. } => (
13582 candles.open.as_slice(),
13583 candles.high.as_slice(),
13584 candles.low.as_slice(),
13585 candles.close.as_slice(),
13586 Some(candles.volume.as_slice()),
13587 ),
13588 IndicatorDataRef::Ohlcv {
13589 open,
13590 high,
13591 low,
13592 close,
13593 volume,
13594 } => {
13595 ensure_same_len_5(
13596 "dvdiqqe",
13597 open.len(),
13598 high.len(),
13599 low.len(),
13600 close.len(),
13601 volume.len(),
13602 )?;
13603 (open, high, low, close, Some(volume))
13604 }
13605 IndicatorDataRef::Ohlc {
13606 open,
13607 high,
13608 low,
13609 close,
13610 } => {
13611 ensure_same_len_4("dvdiqqe", open.len(), high.len(), low.len(), close.len())?;
13612 (open, high, low, close, None)
13613 }
13614 _ => {
13615 return Err(IndicatorDispatchError::MissingRequiredInput {
13616 indicator: "dvdiqqe".to_string(),
13617 input: IndicatorInputKind::Ohlc,
13618 })
13619 }
13620 };
13621 let kernel = req.kernel.to_non_batch();
13622 collect_f64("dvdiqqe", output_id, req.combos, close.len(), |params| {
13623 let period = get_usize_param("dvdiqqe", params, "period", 13)?;
13624 let smoothing_period = get_usize_param("dvdiqqe", params, "smoothing_period", 6)?;
13625 let fast_multiplier = get_f64_param("dvdiqqe", params, "fast_multiplier", 2.618)?;
13626 let slow_multiplier = get_f64_param("dvdiqqe", params, "slow_multiplier", 4.236)?;
13627 let volume_type = get_enum_param("dvdiqqe", params, "volume_type", "default")?;
13628 let center_type = get_enum_param("dvdiqqe", params, "center_type", "dynamic")?;
13629 let tick_size = get_f64_param("dvdiqqe", params, "tick_size", 0.01)?;
13630 let input = DvdiqqeInput::from_slices(
13631 open,
13632 high,
13633 low,
13634 close,
13635 volume,
13636 DvdiqqeParams {
13637 period: Some(period),
13638 smoothing_period: Some(smoothing_period),
13639 fast_multiplier: Some(fast_multiplier),
13640 slow_multiplier: Some(slow_multiplier),
13641 volume_type: Some(volume_type),
13642 center_type: Some(center_type),
13643 tick_size: Some(tick_size),
13644 },
13645 );
13646 let out = dvdiqqe_with_kernel(&input, kernel).map_err(|e| {
13647 IndicatorDispatchError::ComputeFailed {
13648 indicator: "dvdiqqe".to_string(),
13649 details: e.to_string(),
13650 }
13651 })?;
13652 if output_id.eq_ignore_ascii_case("dvdi") || output_id.eq_ignore_ascii_case("value") {
13653 return Ok(out.dvdi);
13654 }
13655 if output_id.eq_ignore_ascii_case("fast_tl") || output_id.eq_ignore_ascii_case("fast") {
13656 return Ok(out.fast_tl);
13657 }
13658 if output_id.eq_ignore_ascii_case("slow_tl") || output_id.eq_ignore_ascii_case("slow") {
13659 return Ok(out.slow_tl);
13660 }
13661 if output_id.eq_ignore_ascii_case("center_line") || output_id.eq_ignore_ascii_case("center")
13662 {
13663 return Ok(out.center_line);
13664 }
13665 Err(IndicatorDispatchError::UnknownOutput {
13666 indicator: "dvdiqqe".to_string(),
13667 output: output_id.to_string(),
13668 })
13669 })
13670}
13671
13672fn compute_emd_batch(
13673 req: IndicatorBatchRequest<'_>,
13674 output_id: &str,
13675) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13676 let (high, low, close, volume) = extract_hlcv_input("emd", req.data)?;
13677 let kernel = req.kernel.to_non_batch();
13678 collect_f64("emd", output_id, req.combos, close.len(), |params| {
13679 let period = get_usize_param("emd", params, "period", 20)?;
13680 let delta = get_f64_param("emd", params, "delta", 0.5)?;
13681 let fraction = get_f64_param("emd", params, "fraction", 0.1)?;
13682 let input = EmdInput::from_slices(
13683 high,
13684 low,
13685 close,
13686 volume,
13687 EmdParams {
13688 period: Some(period),
13689 delta: Some(delta),
13690 fraction: Some(fraction),
13691 },
13692 );
13693 let out =
13694 emd_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
13695 indicator: "emd".to_string(),
13696 details: e.to_string(),
13697 })?;
13698 if output_id.eq_ignore_ascii_case("upperband")
13699 || output_id.eq_ignore_ascii_case("upper")
13700 || output_id.eq_ignore_ascii_case("value")
13701 {
13702 return Ok(out.upperband);
13703 }
13704 if output_id.eq_ignore_ascii_case("middleband") || output_id.eq_ignore_ascii_case("middle")
13705 {
13706 return Ok(out.middleband);
13707 }
13708 if output_id.eq_ignore_ascii_case("lowerband") || output_id.eq_ignore_ascii_case("lower") {
13709 return Ok(out.lowerband);
13710 }
13711 Err(IndicatorDispatchError::UnknownOutput {
13712 indicator: "emd".to_string(),
13713 output: output_id.to_string(),
13714 })
13715 })
13716}
13717
13718fn compute_emd_trend_batch(
13719 req: IndicatorBatchRequest<'_>,
13720 output_id: &str,
13721) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13722 let (open, high, low, close) = extract_ohlc_full_input("emd_trend", req.data)?;
13723 let kernel = req.kernel.to_non_batch();
13724 collect_f64("emd_trend", output_id, req.combos, close.len(), |params| {
13725 let source = get_enum_param("emd_trend", params, "source", "close")?;
13726 let avg_type = get_enum_param("emd_trend", params, "avg_type", "SMA")?;
13727 let length = get_usize_param("emd_trend", params, "length", 28)?;
13728 let mult = get_f64_param("emd_trend", params, "mult", 1.0)?;
13729 let input = EmdTrendInput::from_slices(
13730 open,
13731 high,
13732 low,
13733 close,
13734 EmdTrendParams {
13735 source: Some(source),
13736 avg_type: Some(avg_type),
13737 length: Some(length),
13738 mult: Some(mult),
13739 },
13740 );
13741 let out = emd_trend_with_kernel(&input, kernel).map_err(|e| {
13742 IndicatorDispatchError::ComputeFailed {
13743 indicator: "emd_trend".to_string(),
13744 details: e.to_string(),
13745 }
13746 })?;
13747 if output_id.eq_ignore_ascii_case("direction") {
13748 return Ok(out.direction);
13749 }
13750 if output_id.eq_ignore_ascii_case("average") || output_id.eq_ignore_ascii_case("value") {
13751 return Ok(out.average);
13752 }
13753 if output_id.eq_ignore_ascii_case("upper") {
13754 return Ok(out.upper);
13755 }
13756 if output_id.eq_ignore_ascii_case("lower") {
13757 return Ok(out.lower);
13758 }
13759 Err(IndicatorDispatchError::UnknownOutput {
13760 indicator: "emd_trend".to_string(),
13761 output: output_id.to_string(),
13762 })
13763 })
13764}
13765
13766fn compute_cyberpunk_value_trend_analyzer_batch(
13767 req: IndicatorBatchRequest<'_>,
13768 output_id: &str,
13769) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13770 let (open, high, low, close) =
13771 extract_ohlc_full_input("cyberpunk_value_trend_analyzer", req.data)?;
13772 let kernel = req.kernel.to_non_batch();
13773 collect_f64(
13774 "cyberpunk_value_trend_analyzer",
13775 output_id,
13776 req.combos,
13777 close.len(),
13778 |params| {
13779 let entry_level =
13780 get_usize_param("cyberpunk_value_trend_analyzer", params, "entry_level", 30)?;
13781 let exit_level =
13782 get_usize_param("cyberpunk_value_trend_analyzer", params, "exit_level", 75)?;
13783 let input = CyberpunkValueTrendAnalyzerInput::from_slices(
13784 open,
13785 high,
13786 low,
13787 close,
13788 CyberpunkValueTrendAnalyzerParams {
13789 entry_level: Some(entry_level),
13790 exit_level: Some(exit_level),
13791 },
13792 );
13793 let out = cyberpunk_value_trend_analyzer_with_kernel(&input, kernel).map_err(|e| {
13794 IndicatorDispatchError::ComputeFailed {
13795 indicator: "cyberpunk_value_trend_analyzer".to_string(),
13796 details: e.to_string(),
13797 }
13798 })?;
13799 if output_id.eq_ignore_ascii_case("value_trend")
13800 || output_id.eq_ignore_ascii_case("value")
13801 {
13802 return Ok(out.value_trend);
13803 }
13804 if output_id.eq_ignore_ascii_case("value_trend_lag")
13805 || output_id.eq_ignore_ascii_case("lag")
13806 {
13807 return Ok(out.value_trend_lag);
13808 }
13809 if output_id.eq_ignore_ascii_case("deviation_index") {
13810 return Ok(out.deviation_index);
13811 }
13812 if output_id.eq_ignore_ascii_case("overbought_signal")
13813 || output_id.eq_ignore_ascii_case("overbought")
13814 {
13815 return Ok(out.overbought_signal);
13816 }
13817 if output_id.eq_ignore_ascii_case("buy_signal") {
13818 return Ok(out.buy_signal);
13819 }
13820 if output_id.eq_ignore_ascii_case("sell_signal") {
13821 return Ok(out.sell_signal);
13822 }
13823 Err(IndicatorDispatchError::UnknownOutput {
13824 indicator: "cyberpunk_value_trend_analyzer".to_string(),
13825 output: output_id.to_string(),
13826 })
13827 },
13828 )
13829}
13830
13831fn compute_eri_batch(
13832 req: IndicatorBatchRequest<'_>,
13833 output_id: &str,
13834) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13835 let (high, low, source) = match req.data {
13836 IndicatorDataRef::Candles { candles, source } => (
13837 candles.high.as_slice(),
13838 candles.low.as_slice(),
13839 source_type(candles, source.unwrap_or("close")),
13840 ),
13841 IndicatorDataRef::Ohlc {
13842 open,
13843 high,
13844 low,
13845 close,
13846 } => {
13847 ensure_same_len_4("eri", open.len(), high.len(), low.len(), close.len())?;
13848 (high, low, close)
13849 }
13850 IndicatorDataRef::Ohlcv {
13851 open,
13852 high,
13853 low,
13854 close,
13855 volume,
13856 } => {
13857 ensure_same_len_5(
13858 "eri",
13859 open.len(),
13860 high.len(),
13861 low.len(),
13862 close.len(),
13863 volume.len(),
13864 )?;
13865 (high, low, close)
13866 }
13867 _ => {
13868 return Err(IndicatorDispatchError::MissingRequiredInput {
13869 indicator: "eri".to_string(),
13870 input: IndicatorInputKind::Ohlc,
13871 });
13872 }
13873 };
13874 let kernel = req.kernel.to_non_batch();
13875 collect_f64("eri", output_id, req.combos, source.len(), |params| {
13876 let period = get_usize_param("eri", params, "period", 13)?;
13877 let ma_type = get_enum_param("eri", params, "ma_type", "ema")?;
13878 let input = EriInput::from_slices(
13879 high,
13880 low,
13881 source,
13882 EriParams {
13883 period: Some(period),
13884 ma_type: Some(ma_type),
13885 },
13886 );
13887 let out =
13888 eri_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
13889 indicator: "eri".to_string(),
13890 details: e.to_string(),
13891 })?;
13892 if output_id.eq_ignore_ascii_case("bull") || output_id.eq_ignore_ascii_case("value") {
13893 return Ok(out.bull);
13894 }
13895 if output_id.eq_ignore_ascii_case("bear") {
13896 return Ok(out.bear);
13897 }
13898 Err(IndicatorDispatchError::UnknownOutput {
13899 indicator: "eri".to_string(),
13900 output: output_id.to_string(),
13901 })
13902 })
13903}
13904
13905fn compute_fisher_batch(
13906 req: IndicatorBatchRequest<'_>,
13907 output_id: &str,
13908) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13909 let (high, low) = extract_high_low_input("fisher", req.data)?;
13910 let kernel = req.kernel.to_non_batch();
13911 collect_f64("fisher", output_id, req.combos, high.len(), |params| {
13912 let period = get_usize_param("fisher", params, "period", 9)?;
13913 let input = FisherInput::from_slices(
13914 high,
13915 low,
13916 FisherParams {
13917 period: Some(period),
13918 },
13919 );
13920 let out = fisher_with_kernel(&input, kernel).map_err(|e| {
13921 IndicatorDispatchError::ComputeFailed {
13922 indicator: "fisher".to_string(),
13923 details: e.to_string(),
13924 }
13925 })?;
13926 if output_id.eq_ignore_ascii_case("fisher") || output_id.eq_ignore_ascii_case("value") {
13927 return Ok(out.fisher);
13928 }
13929 if output_id.eq_ignore_ascii_case("signal") {
13930 return Ok(out.signal);
13931 }
13932 Err(IndicatorDispatchError::UnknownOutput {
13933 indicator: "fisher".to_string(),
13934 output: output_id.to_string(),
13935 })
13936 })
13937}
13938
13939fn compute_fvg_positioning_average_batch(
13940 req: IndicatorBatchRequest<'_>,
13941 output_id: &str,
13942) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13943 let (open, high, low, close) = extract_ohlc_full_input("fvg_positioning_average", req.data)?;
13944 let kernel = req.kernel.to_non_batch();
13945 collect_f64(
13946 "fvg_positioning_average",
13947 output_id,
13948 req.combos,
13949 close.len(),
13950 |params| {
13951 let lookback = get_usize_param("fvg_positioning_average", params, "lookback", 30)?;
13952 let lookback_type = get_enum_param(
13953 "fvg_positioning_average",
13954 params,
13955 "lookback_type",
13956 "Bar Count",
13957 )?;
13958 let atr_multiplier =
13959 get_f64_param("fvg_positioning_average", params, "atr_multiplier", 0.25)?;
13960 let input = FvgPositioningAverageInput::from_slices(
13961 open,
13962 high,
13963 low,
13964 close,
13965 FvgPositioningAverageParams {
13966 lookback: Some(lookback),
13967 lookback_type: Some(lookback_type),
13968 atr_multiplier: Some(atr_multiplier),
13969 },
13970 );
13971 let out = fvg_positioning_average_with_kernel(&input, kernel).map_err(|e| {
13972 IndicatorDispatchError::ComputeFailed {
13973 indicator: "fvg_positioning_average".to_string(),
13974 details: e.to_string(),
13975 }
13976 })?;
13977 if output_id.eq_ignore_ascii_case("bull_average")
13978 || output_id.eq_ignore_ascii_case("value")
13979 {
13980 return Ok(out.bull_average);
13981 }
13982 if output_id.eq_ignore_ascii_case("bear_average") {
13983 return Ok(out.bear_average);
13984 }
13985 if output_id.eq_ignore_ascii_case("bull_mid") {
13986 return Ok(out.bull_mid);
13987 }
13988 if output_id.eq_ignore_ascii_case("bear_mid") {
13989 return Ok(out.bear_mid);
13990 }
13991 Err(IndicatorDispatchError::UnknownOutput {
13992 indicator: "fvg_positioning_average".to_string(),
13993 output: output_id.to_string(),
13994 })
13995 },
13996 )
13997}
13998
13999fn compute_fvg_trailing_stop_batch(
14000 req: IndicatorBatchRequest<'_>,
14001 output_id: &str,
14002) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14003 let (high, low, close) = extract_ohlc_input("fvg_trailing_stop", req.data)?;
14004 let kernel = req.kernel.to_non_batch();
14005 collect_f64(
14006 "fvg_trailing_stop",
14007 output_id,
14008 req.combos,
14009 close.len(),
14010 |params| {
14011 let lookback =
14012 get_usize_param("fvg_trailing_stop", params, "unmitigated_fvg_lookback", 5)?;
14013 let smoothing_length =
14014 get_usize_param("fvg_trailing_stop", params, "smoothing_length", 9)?;
14015 let reset_on_cross =
14016 get_bool_param("fvg_trailing_stop", params, "reset_on_cross", false)?;
14017 let input = FvgTrailingStopInput::from_slices(
14018 high,
14019 low,
14020 close,
14021 FvgTrailingStopParams {
14022 unmitigated_fvg_lookback: Some(lookback),
14023 smoothing_length: Some(smoothing_length),
14024 reset_on_cross: Some(reset_on_cross),
14025 },
14026 );
14027 let out = fvg_trailing_stop_with_kernel(&input, kernel).map_err(|e| {
14028 IndicatorDispatchError::ComputeFailed {
14029 indicator: "fvg_trailing_stop".to_string(),
14030 details: e.to_string(),
14031 }
14032 })?;
14033 if output_id.eq_ignore_ascii_case("upper") || output_id.eq_ignore_ascii_case("value") {
14034 return Ok(out.upper);
14035 }
14036 if output_id.eq_ignore_ascii_case("lower") {
14037 return Ok(out.lower);
14038 }
14039 if output_id.eq_ignore_ascii_case("upper_ts") {
14040 return Ok(out.upper_ts);
14041 }
14042 if output_id.eq_ignore_ascii_case("lower_ts") {
14043 return Ok(out.lower_ts);
14044 }
14045 Err(IndicatorDispatchError::UnknownOutput {
14046 indicator: "fvg_trailing_stop".to_string(),
14047 output: output_id.to_string(),
14048 })
14049 },
14050 )
14051}
14052
14053fn compute_gatorosc_batch(
14054 req: IndicatorBatchRequest<'_>,
14055 output_id: &str,
14056) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14057 let data = extract_slice_input("gatorosc", req.data, "close")?;
14058 let kernel = req.kernel.to_non_batch();
14059 collect_f64("gatorosc", output_id, req.combos, data.len(), |params| {
14060 let jaws_length = get_usize_param("gatorosc", params, "jaws_length", 13)?;
14061 let jaws_shift = get_usize_param("gatorosc", params, "jaws_shift", 8)?;
14062 let teeth_length = get_usize_param("gatorosc", params, "teeth_length", 8)?;
14063 let teeth_shift = get_usize_param("gatorosc", params, "teeth_shift", 5)?;
14064 let lips_length = get_usize_param("gatorosc", params, "lips_length", 5)?;
14065 let lips_shift = get_usize_param("gatorosc", params, "lips_shift", 3)?;
14066 let input = GatorOscInput::from_slice(
14067 data,
14068 GatorOscParams {
14069 jaws_length: Some(jaws_length),
14070 jaws_shift: Some(jaws_shift),
14071 teeth_length: Some(teeth_length),
14072 teeth_shift: Some(teeth_shift),
14073 lips_length: Some(lips_length),
14074 lips_shift: Some(lips_shift),
14075 },
14076 );
14077 let out = gatorosc_with_kernel(&input, kernel).map_err(|e| {
14078 IndicatorDispatchError::ComputeFailed {
14079 indicator: "gatorosc".to_string(),
14080 details: e.to_string(),
14081 }
14082 })?;
14083 if output_id.eq_ignore_ascii_case("upper") || output_id.eq_ignore_ascii_case("value") {
14084 return Ok(out.upper);
14085 }
14086 if output_id.eq_ignore_ascii_case("lower") {
14087 return Ok(out.lower);
14088 }
14089 if output_id.eq_ignore_ascii_case("upper_change") {
14090 return Ok(out.upper_change);
14091 }
14092 if output_id.eq_ignore_ascii_case("lower_change") {
14093 return Ok(out.lower_change);
14094 }
14095 Err(IndicatorDispatchError::UnknownOutput {
14096 indicator: "gatorosc".to_string(),
14097 output: output_id.to_string(),
14098 })
14099 })
14100}
14101
14102fn compute_halftrend_batch(
14103 req: IndicatorBatchRequest<'_>,
14104 output_id: &str,
14105) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14106 let (high, low, close) = extract_ohlc_input("halftrend", req.data)?;
14107 let kernel = req.kernel.to_non_batch();
14108 collect_f64("halftrend", output_id, req.combos, close.len(), |params| {
14109 let amplitude = get_usize_param("halftrend", params, "amplitude", 2)?;
14110 let channel_deviation = get_f64_param("halftrend", params, "channel_deviation", 2.0)?;
14111 let atr_period = get_usize_param("halftrend", params, "atr_period", 100)?;
14112 let input = HalfTrendInput::from_slices(
14113 high,
14114 low,
14115 close,
14116 HalfTrendParams {
14117 amplitude: Some(amplitude),
14118 channel_deviation: Some(channel_deviation),
14119 atr_period: Some(atr_period),
14120 },
14121 );
14122 let out = halftrend_with_kernel(&input, kernel).map_err(|e| {
14123 IndicatorDispatchError::ComputeFailed {
14124 indicator: "halftrend".to_string(),
14125 details: e.to_string(),
14126 }
14127 })?;
14128 if output_id.eq_ignore_ascii_case("halftrend") || output_id.eq_ignore_ascii_case("value") {
14129 return Ok(out.halftrend);
14130 }
14131 if output_id.eq_ignore_ascii_case("trend") {
14132 return Ok(out.trend);
14133 }
14134 if output_id.eq_ignore_ascii_case("atr_high") {
14135 return Ok(out.atr_high);
14136 }
14137 if output_id.eq_ignore_ascii_case("atr_low") {
14138 return Ok(out.atr_low);
14139 }
14140 if output_id.eq_ignore_ascii_case("buy_signal") || output_id.eq_ignore_ascii_case("buy") {
14141 return Ok(out.buy_signal);
14142 }
14143 if output_id.eq_ignore_ascii_case("sell_signal") || output_id.eq_ignore_ascii_case("sell") {
14144 return Ok(out.sell_signal);
14145 }
14146 Err(IndicatorDispatchError::UnknownOutput {
14147 indicator: "halftrend".to_string(),
14148 output: output_id.to_string(),
14149 })
14150 })
14151}
14152
14153fn compute_safezonestop_batch(
14154 req: IndicatorBatchRequest<'_>,
14155 output_id: &str,
14156) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14157 let (high, low) = extract_high_low_input("safezonestop", req.data)?;
14158 let kernel = req.kernel.to_non_batch();
14159 collect_f64(
14160 "safezonestop",
14161 output_id,
14162 req.combos,
14163 high.len(),
14164 |params| {
14165 let period = get_usize_param("safezonestop", params, "period", 22)?;
14166 let mult = get_f64_param("safezonestop", params, "mult", 2.5)?;
14167 let max_lookback = get_usize_param("safezonestop", params, "max_lookback", 3)?;
14168 let direction = get_enum_param("safezonestop", params, "direction", "long")?;
14169 let input = SafeZoneStopInput::from_slices(
14170 high,
14171 low,
14172 direction.as_str(),
14173 SafeZoneStopParams {
14174 period: Some(period),
14175 mult: Some(mult),
14176 max_lookback: Some(max_lookback),
14177 },
14178 );
14179 let out = safezonestop_with_kernel(&input, kernel).map_err(|e| {
14180 IndicatorDispatchError::ComputeFailed {
14181 indicator: "safezonestop".to_string(),
14182 details: e.to_string(),
14183 }
14184 })?;
14185 if output_id.eq_ignore_ascii_case("value") {
14186 return Ok(out.values);
14187 }
14188 Err(IndicatorDispatchError::UnknownOutput {
14189 indicator: "safezonestop".to_string(),
14190 output: output_id.to_string(),
14191 })
14192 },
14193 )
14194}
14195
14196fn compute_devstop_batch(
14197 req: IndicatorBatchRequest<'_>,
14198 output_id: &str,
14199) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14200 let (high, low) = extract_high_low_input("devstop", req.data)?;
14201 let kernel = req.kernel.to_non_batch();
14202 collect_f64("devstop", output_id, req.combos, high.len(), |params| {
14203 let period = get_usize_param("devstop", params, "period", 20)?;
14204 let mult = get_f64_param("devstop", params, "mult", 0.0)?;
14205 let devtype = get_usize_param("devstop", params, "devtype", 0)?;
14206 let direction = get_enum_param("devstop", params, "direction", "long")?;
14207 let ma_type = get_enum_param("devstop", params, "ma_type", "sma")?;
14208 let input = DevStopInput::from_slices(
14209 high,
14210 low,
14211 DevStopParams {
14212 period: Some(period),
14213 mult: Some(mult),
14214 devtype: Some(devtype),
14215 direction: Some(direction),
14216 ma_type: Some(ma_type),
14217 },
14218 );
14219 let out = devstop_with_kernel(&input, kernel).map_err(|e| {
14220 IndicatorDispatchError::ComputeFailed {
14221 indicator: "devstop".to_string(),
14222 details: e.to_string(),
14223 }
14224 })?;
14225 if output_id.eq_ignore_ascii_case("value") {
14226 return Ok(out.values);
14227 }
14228 Err(IndicatorDispatchError::UnknownOutput {
14229 indicator: "devstop".to_string(),
14230 output: output_id.to_string(),
14231 })
14232 })
14233}
14234
14235fn compute_chop_batch(
14236 req: IndicatorBatchRequest<'_>,
14237 output_id: &str,
14238) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14239 let (high, low, close) = extract_ohlc_input("chop", req.data)?;
14240 let kernel = req.kernel.to_non_batch();
14241 collect_f64("chop", output_id, req.combos, close.len(), |params| {
14242 let period = get_usize_param("chop", params, "period", 14)?;
14243 let scalar = get_f64_param("chop", params, "scalar", 100.0)?;
14244 let drift = get_usize_param("chop", params, "drift", 1)?;
14245 let input = ChopInput::from_slices(
14246 high,
14247 low,
14248 close,
14249 ChopParams {
14250 period: Some(period),
14251 scalar: Some(scalar),
14252 drift: Some(drift),
14253 },
14254 );
14255 let out = chop_with_kernel(&input, kernel).map_err(|e| {
14256 IndicatorDispatchError::ComputeFailed {
14257 indicator: "chop".to_string(),
14258 details: e.to_string(),
14259 }
14260 })?;
14261 if output_id.eq_ignore_ascii_case("value") {
14262 return Ok(out.values);
14263 }
14264 Err(IndicatorDispatchError::UnknownOutput {
14265 indicator: "chop".to_string(),
14266 output: output_id.to_string(),
14267 })
14268 })
14269}
14270
14271fn compute_kst_batch(
14272 req: IndicatorBatchRequest<'_>,
14273 output_id: &str,
14274) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14275 let data = extract_slice_input("kst", req.data, "close")?;
14276 let kernel = req.kernel.to_non_batch();
14277 collect_f64("kst", output_id, req.combos, data.len(), |params| {
14278 let sma_period1 = get_usize_param("kst", params, "sma_period1", 10)?;
14279 let sma_period2 = get_usize_param("kst", params, "sma_period2", 10)?;
14280 let sma_period3 = get_usize_param("kst", params, "sma_period3", 10)?;
14281 let sma_period4 = get_usize_param("kst", params, "sma_period4", 15)?;
14282 let roc_period1 = get_usize_param("kst", params, "roc_period1", 10)?;
14283 let roc_period2 = get_usize_param("kst", params, "roc_period2", 15)?;
14284 let roc_period3 = get_usize_param("kst", params, "roc_period3", 20)?;
14285 let roc_period4 = get_usize_param("kst", params, "roc_period4", 30)?;
14286 let signal_period = get_usize_param("kst", params, "signal_period", 9)?;
14287 let input = KstInput::from_slice(
14288 data,
14289 KstParams {
14290 sma_period1: Some(sma_period1),
14291 sma_period2: Some(sma_period2),
14292 sma_period3: Some(sma_period3),
14293 sma_period4: Some(sma_period4),
14294 roc_period1: Some(roc_period1),
14295 roc_period2: Some(roc_period2),
14296 roc_period3: Some(roc_period3),
14297 roc_period4: Some(roc_period4),
14298 signal_period: Some(signal_period),
14299 },
14300 );
14301 let out =
14302 kst_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
14303 indicator: "kst".to_string(),
14304 details: e.to_string(),
14305 })?;
14306 if output_id.eq_ignore_ascii_case("line") || output_id.eq_ignore_ascii_case("value") {
14307 return Ok(out.line);
14308 }
14309 if output_id.eq_ignore_ascii_case("signal") {
14310 return Ok(out.signal);
14311 }
14312 Err(IndicatorDispatchError::UnknownOutput {
14313 indicator: "kst".to_string(),
14314 output: output_id.to_string(),
14315 })
14316 })
14317}
14318
14319fn compute_kaufmanstop_batch(
14320 req: IndicatorBatchRequest<'_>,
14321 output_id: &str,
14322) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14323 expect_value_output("kaufmanstop", output_id)?;
14324 let (high, low) = extract_high_low_input("kaufmanstop", req.data)?;
14325 let kernel = req.kernel.to_non_batch();
14326 collect_f64("kaufmanstop", output_id, req.combos, high.len(), |params| {
14327 let period = get_usize_param("kaufmanstop", params, "period", 22)?;
14328 let mult = get_f64_param("kaufmanstop", params, "mult", 2.0)?;
14329 let direction = get_enum_param("kaufmanstop", params, "direction", "long")?;
14330 let ma_type = get_enum_param("kaufmanstop", params, "ma_type", "sma")?;
14331 let input = KaufmanstopInput::from_slices(
14332 high,
14333 low,
14334 KaufmanstopParams {
14335 period: Some(period),
14336 mult: Some(mult),
14337 direction: Some(direction),
14338 ma_type: Some(ma_type),
14339 },
14340 );
14341 let out = kaufmanstop_with_kernel(&input, kernel).map_err(|e| {
14342 IndicatorDispatchError::ComputeFailed {
14343 indicator: "kaufmanstop".to_string(),
14344 details: e.to_string(),
14345 }
14346 })?;
14347 Ok(out.values)
14348 })
14349}
14350
14351fn compute_lpc_batch(
14352 req: IndicatorBatchRequest<'_>,
14353 output_id: &str,
14354) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14355 let (high, low, close, src) = match req.data {
14356 IndicatorDataRef::Candles { candles, source } => (
14357 candles.high.as_slice(),
14358 candles.low.as_slice(),
14359 candles.close.as_slice(),
14360 source_type(candles, source.unwrap_or("close")),
14361 ),
14362 IndicatorDataRef::Ohlc {
14363 open,
14364 high,
14365 low,
14366 close,
14367 } => {
14368 ensure_same_len_4("lpc", open.len(), high.len(), low.len(), close.len())?;
14369 (high, low, close, close)
14370 }
14371 IndicatorDataRef::Ohlcv {
14372 open,
14373 high,
14374 low,
14375 close,
14376 volume,
14377 } => {
14378 ensure_same_len_5(
14379 "lpc",
14380 open.len(),
14381 high.len(),
14382 low.len(),
14383 close.len(),
14384 volume.len(),
14385 )?;
14386 (high, low, close, close)
14387 }
14388 _ => {
14389 return Err(IndicatorDispatchError::MissingRequiredInput {
14390 indicator: "lpc".to_string(),
14391 input: IndicatorInputKind::Ohlc,
14392 });
14393 }
14394 };
14395 let kernel = req.kernel.to_non_batch();
14396 collect_f64("lpc", output_id, req.combos, src.len(), |params| {
14397 let cutoff_type = get_enum_param("lpc", params, "cutoff_type", "adaptive")?;
14398 let fixed_period = get_usize_param("lpc", params, "fixed_period", 20)?;
14399 let max_cycle_limit = get_usize_param("lpc", params, "max_cycle_limit", 60)?;
14400 let cycle_mult = get_f64_param("lpc", params, "cycle_mult", 1.0)?;
14401 let tr_mult = get_f64_param("lpc", params, "tr_mult", 1.0)?;
14402 let input = LpcInput::from_slices(
14403 high,
14404 low,
14405 close,
14406 src,
14407 LpcParams {
14408 cutoff_type: Some(cutoff_type),
14409 fixed_period: Some(fixed_period),
14410 max_cycle_limit: Some(max_cycle_limit),
14411 cycle_mult: Some(cycle_mult),
14412 tr_mult: Some(tr_mult),
14413 },
14414 );
14415 let out =
14416 lpc_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
14417 indicator: "lpc".to_string(),
14418 details: e.to_string(),
14419 })?;
14420 if output_id.eq_ignore_ascii_case("filter") || output_id.eq_ignore_ascii_case("value") {
14421 return Ok(out.filter);
14422 }
14423 if output_id.eq_ignore_ascii_case("high_band") || output_id.eq_ignore_ascii_case("high") {
14424 return Ok(out.high_band);
14425 }
14426 if output_id.eq_ignore_ascii_case("low_band") || output_id.eq_ignore_ascii_case("low") {
14427 return Ok(out.low_band);
14428 }
14429 Err(IndicatorDispatchError::UnknownOutput {
14430 indicator: "lpc".to_string(),
14431 output: output_id.to_string(),
14432 })
14433 })
14434}
14435
14436fn compute_mab_batch(
14437 req: IndicatorBatchRequest<'_>,
14438 output_id: &str,
14439) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14440 let data = extract_slice_input("mab", req.data, "close")?;
14441 let kernel = req.kernel.to_non_batch();
14442 collect_f64("mab", output_id, req.combos, data.len(), |params| {
14443 let fast_period = get_usize_param("mab", params, "fast_period", 10)?;
14444 let slow_period = get_usize_param("mab", params, "slow_period", 50)?;
14445 let devup = get_f64_param("mab", params, "devup", 1.0)?;
14446 let devdn = get_f64_param("mab", params, "devdn", 1.0)?;
14447 let fast_ma_type = get_enum_param("mab", params, "fast_ma_type", "sma")?;
14448 let slow_ma_type = get_enum_param("mab", params, "slow_ma_type", "sma")?;
14449 let input = MabInput::from_slice(
14450 data,
14451 MabParams {
14452 fast_period: Some(fast_period),
14453 slow_period: Some(slow_period),
14454 devup: Some(devup),
14455 devdn: Some(devdn),
14456 fast_ma_type: Some(fast_ma_type),
14457 slow_ma_type: Some(slow_ma_type),
14458 },
14459 );
14460 let out =
14461 mab_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
14462 indicator: "mab".to_string(),
14463 details: e.to_string(),
14464 })?;
14465 if output_id.eq_ignore_ascii_case("upperband")
14466 || output_id.eq_ignore_ascii_case("upper")
14467 || output_id.eq_ignore_ascii_case("value")
14468 {
14469 return Ok(out.upperband);
14470 }
14471 if output_id.eq_ignore_ascii_case("middleband") || output_id.eq_ignore_ascii_case("middle")
14472 {
14473 return Ok(out.middleband);
14474 }
14475 if output_id.eq_ignore_ascii_case("lowerband") || output_id.eq_ignore_ascii_case("lower") {
14476 return Ok(out.lowerband);
14477 }
14478 Err(IndicatorDispatchError::UnknownOutput {
14479 indicator: "mab".to_string(),
14480 output: output_id.to_string(),
14481 })
14482 })
14483}
14484
14485fn compute_macz_batch(
14486 req: IndicatorBatchRequest<'_>,
14487 output_id: &str,
14488) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14489 let (data, volume) = match req.data {
14490 IndicatorDataRef::Slice { values } => (values, None),
14491 IndicatorDataRef::Candles { candles, source } => (
14492 source_type(candles, source.unwrap_or("close")),
14493 Some(candles.volume.as_slice()),
14494 ),
14495 IndicatorDataRef::CloseVolume { close, volume } => {
14496 ensure_same_len_2("macz", close.len(), volume.len())?;
14497 (close, Some(volume))
14498 }
14499 IndicatorDataRef::Ohlc {
14500 open,
14501 high,
14502 low,
14503 close,
14504 } => {
14505 ensure_same_len_4("macz", open.len(), high.len(), low.len(), close.len())?;
14506 (close, None)
14507 }
14508 IndicatorDataRef::Ohlcv {
14509 open,
14510 high,
14511 low,
14512 close,
14513 volume,
14514 } => {
14515 ensure_same_len_5(
14516 "macz",
14517 open.len(),
14518 high.len(),
14519 low.len(),
14520 close.len(),
14521 volume.len(),
14522 )?;
14523 (close, Some(volume))
14524 }
14525 IndicatorDataRef::HighLow { .. } => {
14526 return Err(IndicatorDispatchError::MissingRequiredInput {
14527 indicator: "macz".to_string(),
14528 input: IndicatorInputKind::Slice,
14529 })
14530 }
14531 };
14532 let kernel = req.kernel.to_non_batch();
14533 collect_f64("macz", output_id, req.combos, data.len(), |params| {
14534 let fast_length = get_usize_param("macz", params, "fast_length", 12)?;
14535 let slow_length = get_usize_param("macz", params, "slow_length", 25)?;
14536 let signal_length = get_usize_param("macz", params, "signal_length", 9)?;
14537 let lengthz = get_usize_param("macz", params, "lengthz", 20)?;
14538 let length_stdev = get_usize_param("macz", params, "length_stdev", 25)?;
14539 let a = get_f64_param("macz", params, "a", 1.0)?;
14540 let b = get_f64_param("macz", params, "b", 1.0)?;
14541 let use_lag = get_bool_param("macz", params, "use_lag", false)?;
14542 let gamma = get_f64_param("macz", params, "gamma", 0.02)?;
14543 let macz_params = MaczParams {
14544 fast_length: Some(fast_length),
14545 slow_length: Some(slow_length),
14546 signal_length: Some(signal_length),
14547 lengthz: Some(lengthz),
14548 length_stdev: Some(length_stdev),
14549 a: Some(a),
14550 b: Some(b),
14551 use_lag: Some(use_lag),
14552 gamma: Some(gamma),
14553 };
14554 let input = if let Some(vol) = volume {
14555 MaczInput::from_slice_with_volume(data, vol, macz_params)
14556 } else {
14557 MaczInput::from_slice(data, macz_params)
14558 };
14559 let out = macz_with_kernel(&input, kernel).map_err(|e| {
14560 IndicatorDispatchError::ComputeFailed {
14561 indicator: "macz".to_string(),
14562 details: e.to_string(),
14563 }
14564 })?;
14565 if output_id.eq_ignore_ascii_case("value") || output_id.eq_ignore_ascii_case("values") {
14566 return Ok(out.values);
14567 }
14568 Err(IndicatorDispatchError::UnknownOutput {
14569 indicator: "macz".to_string(),
14570 output: output_id.to_string(),
14571 })
14572 })
14573}
14574
14575fn compute_minmax_batch(
14576 req: IndicatorBatchRequest<'_>,
14577 output_id: &str,
14578) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14579 let (high, low) = extract_high_low_input("minmax", req.data)?;
14580 let kernel = req.kernel.to_non_batch();
14581 collect_f64("minmax", output_id, req.combos, high.len(), |params| {
14582 let order = get_usize_param("minmax", params, "order", 3)?;
14583 let input = MinmaxInput::from_slices(high, low, MinmaxParams { order: Some(order) });
14584 let out = minmax_with_kernel(&input, kernel).map_err(|e| {
14585 IndicatorDispatchError::ComputeFailed {
14586 indicator: "minmax".to_string(),
14587 details: e.to_string(),
14588 }
14589 })?;
14590 if output_id.eq_ignore_ascii_case("is_min") || output_id.eq_ignore_ascii_case("value") {
14591 return Ok(out.is_min);
14592 }
14593 if output_id.eq_ignore_ascii_case("is_max") {
14594 return Ok(out.is_max);
14595 }
14596 if output_id.eq_ignore_ascii_case("last_min") {
14597 return Ok(out.last_min);
14598 }
14599 if output_id.eq_ignore_ascii_case("last_max") {
14600 return Ok(out.last_max);
14601 }
14602 Err(IndicatorDispatchError::UnknownOutput {
14603 indicator: "minmax".to_string(),
14604 output: output_id.to_string(),
14605 })
14606 })
14607}
14608
14609fn compute_mod_god_mode_batch(
14610 req: IndicatorBatchRequest<'_>,
14611 output_id: &str,
14612) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14613 let (high, low, close, volume) = match req.data {
14614 IndicatorDataRef::Candles { candles, .. } => (
14615 candles.high.as_slice(),
14616 candles.low.as_slice(),
14617 candles.close.as_slice(),
14618 Some(candles.volume.as_slice()),
14619 ),
14620 IndicatorDataRef::Ohlc {
14621 open,
14622 high,
14623 low,
14624 close,
14625 } => {
14626 ensure_same_len_4(
14627 "mod_god_mode",
14628 open.len(),
14629 high.len(),
14630 low.len(),
14631 close.len(),
14632 )?;
14633 (high, low, close, None)
14634 }
14635 IndicatorDataRef::Ohlcv {
14636 open,
14637 high,
14638 low,
14639 close,
14640 volume,
14641 } => {
14642 ensure_same_len_5(
14643 "mod_god_mode",
14644 open.len(),
14645 high.len(),
14646 low.len(),
14647 close.len(),
14648 volume.len(),
14649 )?;
14650 (high, low, close, Some(volume))
14651 }
14652 _ => {
14653 return Err(IndicatorDispatchError::MissingRequiredInput {
14654 indicator: "mod_god_mode".to_string(),
14655 input: IndicatorInputKind::Ohlc,
14656 });
14657 }
14658 };
14659
14660 collect_f64(
14661 "mod_god_mode",
14662 output_id,
14663 req.combos,
14664 close.len(),
14665 |params| {
14666 let n1 = get_usize_param("mod_god_mode", params, "n1", 17)?;
14667 let n2 = get_usize_param("mod_god_mode", params, "n2", 6)?;
14668 let n3 = get_usize_param("mod_god_mode", params, "n3", 4)?;
14669 let mode = get_enum_param("mod_god_mode", params, "mode", "tradition_mg")?;
14670 let use_volume = get_bool_param("mod_god_mode", params, "use_volume", true)?;
14671 let mode = match mode.as_str() {
14672 "godmode" => ModGodModeMode::Godmode,
14673 "tradition" => ModGodModeMode::Tradition,
14674 "godmode_mg" => ModGodModeMode::GodmodeMg,
14675 "tradition_mg" => ModGodModeMode::TraditionMg,
14676 other => {
14677 return Err(IndicatorDispatchError::InvalidParam {
14678 indicator: "mod_god_mode".to_string(),
14679 key: "mode".to_string(),
14680 reason: format!("unknown mode: {other}"),
14681 });
14682 }
14683 };
14684 let input = ModGodModeInput {
14685 data: ModGodModeData::Slices {
14686 high,
14687 low,
14688 close,
14689 volume: if use_volume { volume } else { None },
14690 },
14691 params: ModGodModeParams {
14692 n1: Some(n1),
14693 n2: Some(n2),
14694 n3: Some(n3),
14695 mode: Some(mode),
14696 use_volume: Some(use_volume),
14697 },
14698 };
14699 let out = mod_god_mode(&input).map_err(|e| IndicatorDispatchError::ComputeFailed {
14700 indicator: "mod_god_mode".to_string(),
14701 details: e.to_string(),
14702 })?;
14703 if output_id.eq_ignore_ascii_case("wavetrend")
14704 || output_id.eq_ignore_ascii_case("wt1")
14705 || output_id.eq_ignore_ascii_case("value")
14706 {
14707 return Ok(out.wavetrend);
14708 }
14709 if output_id.eq_ignore_ascii_case("signal") || output_id.eq_ignore_ascii_case("wt2") {
14710 return Ok(out.signal);
14711 }
14712 if output_id.eq_ignore_ascii_case("histogram") || output_id.eq_ignore_ascii_case("hist")
14713 {
14714 return Ok(out.histogram);
14715 }
14716 Err(IndicatorDispatchError::UnknownOutput {
14717 indicator: "mod_god_mode".to_string(),
14718 output: output_id.to_string(),
14719 })
14720 },
14721 )
14722}
14723
14724fn compute_msw_batch(
14725 req: IndicatorBatchRequest<'_>,
14726 output_id: &str,
14727) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14728 let data = extract_slice_input("msw", req.data, "close")?;
14729 let kernel = req.kernel.to_non_batch();
14730 collect_f64("msw", output_id, req.combos, data.len(), |params| {
14731 let period = get_usize_param("msw", params, "period", 5)?;
14732 let input = MswInput::from_slice(
14733 data,
14734 MswParams {
14735 period: Some(period),
14736 },
14737 );
14738 let out =
14739 msw_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
14740 indicator: "msw".to_string(),
14741 details: e.to_string(),
14742 })?;
14743 if output_id.eq_ignore_ascii_case("sine") || output_id.eq_ignore_ascii_case("value") {
14744 return Ok(out.sine);
14745 }
14746 if output_id.eq_ignore_ascii_case("lead") {
14747 return Ok(out.lead);
14748 }
14749 Err(IndicatorDispatchError::UnknownOutput {
14750 indicator: "msw".to_string(),
14751 output: output_id.to_string(),
14752 })
14753 })
14754}
14755
14756fn compute_nadaraya_watson_envelope_batch(
14757 req: IndicatorBatchRequest<'_>,
14758 output_id: &str,
14759) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14760 let data = extract_slice_input("nadaraya_watson_envelope", req.data, "close")?;
14761 let kernel = req.kernel.to_non_batch();
14762 collect_f64(
14763 "nadaraya_watson_envelope",
14764 output_id,
14765 req.combos,
14766 data.len(),
14767 |params| {
14768 let bandwidth = get_f64_param("nadaraya_watson_envelope", params, "bandwidth", 8.0)?;
14769 let multiplier = get_f64_param("nadaraya_watson_envelope", params, "multiplier", 3.0)?;
14770 let lookback = get_usize_param("nadaraya_watson_envelope", params, "lookback", 500)?;
14771 let input = NweInput::from_slice(
14772 data,
14773 NweParams {
14774 bandwidth: Some(bandwidth),
14775 multiplier: Some(multiplier),
14776 lookback: Some(lookback),
14777 },
14778 );
14779 let out = nadaraya_watson_envelope_with_kernel(&input, kernel).map_err(|e| {
14780 IndicatorDispatchError::ComputeFailed {
14781 indicator: "nadaraya_watson_envelope".to_string(),
14782 details: e.to_string(),
14783 }
14784 })?;
14785 if output_id.eq_ignore_ascii_case("upper") || output_id.eq_ignore_ascii_case("value") {
14786 return Ok(out.upper);
14787 }
14788 if output_id.eq_ignore_ascii_case("lower") {
14789 return Ok(out.lower);
14790 }
14791 Err(IndicatorDispatchError::UnknownOutput {
14792 indicator: "nadaraya_watson_envelope".to_string(),
14793 output: output_id.to_string(),
14794 })
14795 },
14796 )
14797}
14798
14799fn compute_otto_batch(
14800 req: IndicatorBatchRequest<'_>,
14801 output_id: &str,
14802) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14803 let data = extract_slice_input("otto", req.data, "close")?;
14804 let kernel = req.kernel.to_non_batch();
14805 collect_f64("otto", output_id, req.combos, data.len(), |params| {
14806 let ott_period = get_usize_param("otto", params, "ott_period", 2)?;
14807 let ott_percent = get_f64_param("otto", params, "ott_percent", 0.6)?;
14808 let fast_vidya_length = get_usize_param("otto", params, "fast_vidya_length", 10)?;
14809 let slow_vidya_length = get_usize_param("otto", params, "slow_vidya_length", 25)?;
14810 let correcting_constant = get_f64_param("otto", params, "correcting_constant", 100000.0)?;
14811 let ma_type = get_enum_param("otto", params, "ma_type", "VAR")?;
14812 let input = OttoInput::from_slice(
14813 data,
14814 OttoParams {
14815 ott_period: Some(ott_period),
14816 ott_percent: Some(ott_percent),
14817 fast_vidya_length: Some(fast_vidya_length),
14818 slow_vidya_length: Some(slow_vidya_length),
14819 correcting_constant: Some(correcting_constant),
14820 ma_type: Some(ma_type),
14821 },
14822 );
14823 let out = otto_with_kernel(&input, kernel).map_err(|e| {
14824 IndicatorDispatchError::ComputeFailed {
14825 indicator: "otto".to_string(),
14826 details: e.to_string(),
14827 }
14828 })?;
14829 if output_id.eq_ignore_ascii_case("hott") || output_id.eq_ignore_ascii_case("value") {
14830 return Ok(out.hott);
14831 }
14832 if output_id.eq_ignore_ascii_case("lott") {
14833 return Ok(out.lott);
14834 }
14835 Err(IndicatorDispatchError::UnknownOutput {
14836 indicator: "otto".to_string(),
14837 output: output_id.to_string(),
14838 })
14839 })
14840}
14841
14842fn compute_vidya_batch(
14843 req: IndicatorBatchRequest<'_>,
14844 output_id: &str,
14845) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14846 let data = extract_slice_input("vidya", req.data, "close")?;
14847 let kernel = req.kernel.to_non_batch();
14848 collect_f64("vidya", output_id, req.combos, data.len(), |params| {
14849 let short_period = get_usize_param("vidya", params, "short_period", 2)?;
14850 let long_period = get_usize_param("vidya", params, "long_period", 5)?;
14851 let alpha = get_f64_param("vidya", params, "alpha", 0.2)?;
14852 let input = VidyaInput::from_slice(
14853 data,
14854 VidyaParams {
14855 short_period: Some(short_period),
14856 long_period: Some(long_period),
14857 alpha: Some(alpha),
14858 },
14859 );
14860 let out = vidya_with_kernel(&input, kernel).map_err(|e| {
14861 IndicatorDispatchError::ComputeFailed {
14862 indicator: "vidya".to_string(),
14863 details: e.to_string(),
14864 }
14865 })?;
14866 if output_id.eq_ignore_ascii_case("value") || output_id.eq_ignore_ascii_case("values") {
14867 return Ok(out.values);
14868 }
14869 Err(IndicatorDispatchError::UnknownOutput {
14870 indicator: "vidya".to_string(),
14871 output: output_id.to_string(),
14872 })
14873 })
14874}
14875
14876fn compute_vlma_batch(
14877 req: IndicatorBatchRequest<'_>,
14878 output_id: &str,
14879) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14880 let data = extract_slice_input("vlma", req.data, "close")?;
14881 let kernel = req.kernel.to_non_batch();
14882 collect_f64("vlma", output_id, req.combos, data.len(), |params| {
14883 let min_period = get_usize_param("vlma", params, "min_period", 5)?;
14884 let max_period = get_usize_param("vlma", params, "max_period", 50)?;
14885 let matype = get_enum_param("vlma", params, "matype", "sma")?;
14886 let devtype = get_usize_param("vlma", params, "devtype", 0)?;
14887 let input = VlmaInput::from_slice(
14888 data,
14889 VlmaParams {
14890 min_period: Some(min_period),
14891 max_period: Some(max_period),
14892 matype: Some(matype),
14893 devtype: Some(devtype),
14894 },
14895 );
14896 let out = vlma_with_kernel(&input, kernel).map_err(|e| {
14897 IndicatorDispatchError::ComputeFailed {
14898 indicator: "vlma".to_string(),
14899 details: e.to_string(),
14900 }
14901 })?;
14902 if output_id.eq_ignore_ascii_case("value") || output_id.eq_ignore_ascii_case("values") {
14903 return Ok(out.values);
14904 }
14905 Err(IndicatorDispatchError::UnknownOutput {
14906 indicator: "vlma".to_string(),
14907 output: output_id.to_string(),
14908 })
14909 })
14910}
14911
14912fn compute_pma_batch(
14913 req: IndicatorBatchRequest<'_>,
14914 output_id: &str,
14915) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14916 let data = extract_slice_input("pma", req.data, "close")?;
14917 let kernel = req.kernel.to_non_batch();
14918 collect_f64("pma", output_id, req.combos, data.len(), |_params| {
14919 let input = PmaInput::from_slice(data, PmaParams::default());
14920 let out =
14921 pma_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
14922 indicator: "pma".to_string(),
14923 details: e.to_string(),
14924 })?;
14925 if output_id.eq_ignore_ascii_case("predict") || output_id.eq_ignore_ascii_case("value") {
14926 return Ok(out.predict);
14927 }
14928 if output_id.eq_ignore_ascii_case("trigger") {
14929 return Ok(out.trigger);
14930 }
14931 Err(IndicatorDispatchError::UnknownOutput {
14932 indicator: "pma".to_string(),
14933 output: output_id.to_string(),
14934 })
14935 })
14936}
14937
14938fn compute_ehlers_adaptive_cg_batch(
14939 req: IndicatorBatchRequest<'_>,
14940 output_id: &str,
14941) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14942 let data = extract_slice_input("ehlers_adaptive_cg", req.data, "hl2")?;
14943 let kernel = req.kernel.to_non_batch();
14944 collect_f64(
14945 "ehlers_adaptive_cg",
14946 output_id,
14947 req.combos,
14948 data.len(),
14949 |params| {
14950 let alpha = get_f64_param("ehlers_adaptive_cg", params, "alpha", 0.07)?;
14951 let input = EhlersAdaptiveCgInput::from_slice(
14952 data,
14953 EhlersAdaptiveCgParams { alpha: Some(alpha) },
14954 );
14955 let out = ehlers_adaptive_cg_with_kernel(&input, kernel).map_err(|e| {
14956 IndicatorDispatchError::ComputeFailed {
14957 indicator: "ehlers_adaptive_cg".to_string(),
14958 details: e.to_string(),
14959 }
14960 })?;
14961 if output_id.eq_ignore_ascii_case("cg") || output_id.eq_ignore_ascii_case("value") {
14962 return Ok(out.cg);
14963 }
14964 if output_id.eq_ignore_ascii_case("trigger") {
14965 return Ok(out.trigger);
14966 }
14967 Err(IndicatorDispatchError::UnknownOutput {
14968 indicator: "ehlers_adaptive_cg".to_string(),
14969 output: output_id.to_string(),
14970 })
14971 },
14972 )
14973}
14974
14975fn compute_prb_batch(
14976 req: IndicatorBatchRequest<'_>,
14977 output_id: &str,
14978) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14979 let data = extract_slice_input("prb", req.data, "close")?;
14980 let kernel = req.kernel.to_non_batch();
14981 collect_f64("prb", output_id, req.combos, data.len(), |params| {
14982 let smooth_data = get_bool_param("prb", params, "smooth_data", true)?;
14983 let smooth_period = get_usize_param("prb", params, "smooth_period", 10)?;
14984 let regression_period = get_usize_param("prb", params, "regression_period", 100)?;
14985 let polynomial_order = get_usize_param("prb", params, "polynomial_order", 2)?;
14986 let regression_offset = get_i32_param("prb", params, "regression_offset", 0)?;
14987 let ndev = get_f64_param("prb", params, "ndev", 2.0)?;
14988 let equ_from = get_usize_param("prb", params, "equ_from", 0)?;
14989 let input = PrbInput::from_slice(
14990 data,
14991 PrbParams {
14992 smooth_data: Some(smooth_data),
14993 smooth_period: Some(smooth_period),
14994 regression_period: Some(regression_period),
14995 polynomial_order: Some(polynomial_order),
14996 regression_offset: Some(regression_offset),
14997 ndev: Some(ndev),
14998 equ_from: Some(equ_from),
14999 },
15000 );
15001 let out =
15002 prb_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
15003 indicator: "prb".to_string(),
15004 details: e.to_string(),
15005 })?;
15006 if output_id.eq_ignore_ascii_case("values") || output_id.eq_ignore_ascii_case("value") {
15007 return Ok(out.values);
15008 }
15009 if output_id.eq_ignore_ascii_case("upper_band") || output_id.eq_ignore_ascii_case("upper") {
15010 return Ok(out.upper_band);
15011 }
15012 if output_id.eq_ignore_ascii_case("lower_band") || output_id.eq_ignore_ascii_case("lower") {
15013 return Ok(out.lower_band);
15014 }
15015 Err(IndicatorDispatchError::UnknownOutput {
15016 indicator: "prb".to_string(),
15017 output: output_id.to_string(),
15018 })
15019 })
15020}
15021
15022fn compute_qqe_batch(
15023 req: IndicatorBatchRequest<'_>,
15024 output_id: &str,
15025) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15026 let data = extract_slice_input("qqe", req.data, "close")?;
15027 let kernel = req.kernel.to_non_batch();
15028 collect_f64("qqe", output_id, req.combos, data.len(), |params| {
15029 let rsi_period = get_usize_param("qqe", params, "rsi_period", 14)?;
15030 let smoothing_factor = get_usize_param("qqe", params, "smoothing_factor", 5)?;
15031 let fast_factor = get_f64_param("qqe", params, "fast_factor", 4.236)?;
15032 let input = QqeInput::from_slice(
15033 data,
15034 QqeParams {
15035 rsi_period: Some(rsi_period),
15036 smoothing_factor: Some(smoothing_factor),
15037 fast_factor: Some(fast_factor),
15038 },
15039 );
15040 let out =
15041 qqe_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
15042 indicator: "qqe".to_string(),
15043 details: e.to_string(),
15044 })?;
15045 if output_id.eq_ignore_ascii_case("fast") || output_id.eq_ignore_ascii_case("value") {
15046 return Ok(out.fast);
15047 }
15048 if output_id.eq_ignore_ascii_case("slow") {
15049 return Ok(out.slow);
15050 }
15051 Err(IndicatorDispatchError::UnknownOutput {
15052 indicator: "qqe".to_string(),
15053 output: output_id.to_string(),
15054 })
15055 })
15056}
15057
15058fn compute_qqe_weighted_oscillator_batch(
15059 req: IndicatorBatchRequest<'_>,
15060 output_id: &str,
15061) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15062 let data = extract_slice_input("qqe_weighted_oscillator", req.data, "close")?;
15063 let kernel = req.kernel.to_non_batch();
15064 collect_f64(
15065 "qqe_weighted_oscillator",
15066 output_id,
15067 req.combos,
15068 data.len(),
15069 |params| {
15070 let length = get_usize_param("qqe_weighted_oscillator", params, "length", 14)?;
15071 let factor = get_f64_param("qqe_weighted_oscillator", params, "factor", 4.236)?;
15072 let smooth = get_usize_param("qqe_weighted_oscillator", params, "smooth", 5)?;
15073 let weight = get_f64_param("qqe_weighted_oscillator", params, "weight", 2.0)?;
15074 let input = QqeWeightedOscillatorInput::from_slice(
15075 data,
15076 QqeWeightedOscillatorParams {
15077 length: Some(length),
15078 factor: Some(factor),
15079 smooth: Some(smooth),
15080 weight: Some(weight),
15081 },
15082 );
15083 let out = qqe_weighted_oscillator_with_kernel(&input, kernel).map_err(|e| {
15084 IndicatorDispatchError::ComputeFailed {
15085 indicator: "qqe_weighted_oscillator".to_string(),
15086 details: e.to_string(),
15087 }
15088 })?;
15089 if output_id.eq_ignore_ascii_case("rsi") || output_id.eq_ignore_ascii_case("value") {
15090 return Ok(out.rsi);
15091 }
15092 if output_id.eq_ignore_ascii_case("trailing_stop")
15093 || output_id.eq_ignore_ascii_case("ts")
15094 {
15095 return Ok(out.trailing_stop);
15096 }
15097 Err(IndicatorDispatchError::UnknownOutput {
15098 indicator: "qqe_weighted_oscillator".to_string(),
15099 output: output_id.to_string(),
15100 })
15101 },
15102 )
15103}
15104
15105fn compute_forward_backward_exponential_oscillator_batch(
15106 req: IndicatorBatchRequest<'_>,
15107 output_id: &str,
15108) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15109 let data = extract_slice_input("forward_backward_exponential_oscillator", req.data, "close")?;
15110 let kernel = req.kernel.to_non_batch();
15111 collect_f64(
15112 "forward_backward_exponential_oscillator",
15113 output_id,
15114 req.combos,
15115 data.len(),
15116 |params| {
15117 let length = get_usize_param(
15118 "forward_backward_exponential_oscillator",
15119 params,
15120 "length",
15121 20,
15122 )?;
15123 let smooth = get_usize_param(
15124 "forward_backward_exponential_oscillator",
15125 params,
15126 "smooth",
15127 10,
15128 )?;
15129 let input = ForwardBackwardExponentialOscillatorInput::from_slice(
15130 data,
15131 ForwardBackwardExponentialOscillatorParams {
15132 length: Some(length),
15133 smooth: Some(smooth),
15134 },
15135 );
15136 let out = forward_backward_exponential_oscillator_with_kernel(&input, kernel).map_err(
15137 |e| IndicatorDispatchError::ComputeFailed {
15138 indicator: "forward_backward_exponential_oscillator".to_string(),
15139 details: e.to_string(),
15140 },
15141 )?;
15142 if output_id.eq_ignore_ascii_case("forward_backward")
15143 || output_id.eq_ignore_ascii_case("value")
15144 || output_id.eq_ignore_ascii_case("fb")
15145 {
15146 return Ok(out.forward_backward);
15147 }
15148 if output_id.eq_ignore_ascii_case("backward")
15149 || output_id.eq_ignore_ascii_case("bwrd")
15150 || output_id.eq_ignore_ascii_case("bw")
15151 {
15152 return Ok(out.backward);
15153 }
15154 if output_id.eq_ignore_ascii_case("histogram") || output_id.eq_ignore_ascii_case("hist")
15155 {
15156 return Ok(out.histogram);
15157 }
15158 Err(IndicatorDispatchError::UnknownOutput {
15159 indicator: "forward_backward_exponential_oscillator".to_string(),
15160 output: output_id.to_string(),
15161 })
15162 },
15163 )
15164}
15165
15166fn compute_range_oscillator_batch(
15167 req: IndicatorBatchRequest<'_>,
15168 output_id: &str,
15169) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15170 let (high, low, close) = extract_ohlc_input("range_oscillator", req.data)?;
15171 let kernel = req.kernel.to_non_batch();
15172 collect_f64(
15173 "range_oscillator",
15174 output_id,
15175 req.combos,
15176 close.len(),
15177 |params| {
15178 let length = get_usize_param("range_oscillator", params, "length", 50)?;
15179 let mult = get_f64_param("range_oscillator", params, "mult", 2.0)?;
15180 let input = RangeOscillatorInput::from_slices(
15181 high,
15182 low,
15183 close,
15184 RangeOscillatorParams {
15185 length: Some(length),
15186 mult: Some(mult),
15187 },
15188 );
15189 let out = range_oscillator_with_kernel(&input, kernel).map_err(|e| {
15190 IndicatorDispatchError::ComputeFailed {
15191 indicator: "range_oscillator".to_string(),
15192 details: e.to_string(),
15193 }
15194 })?;
15195 if output_id.eq_ignore_ascii_case("oscillator")
15196 || output_id.eq_ignore_ascii_case("osc")
15197 || output_id.eq_ignore_ascii_case("value")
15198 {
15199 return Ok(out.oscillator);
15200 }
15201 if output_id.eq_ignore_ascii_case("ma") {
15202 return Ok(out.ma);
15203 }
15204 if output_id.eq_ignore_ascii_case("upper_band")
15205 || output_id.eq_ignore_ascii_case("upper")
15206 {
15207 return Ok(out.upper_band);
15208 }
15209 if output_id.eq_ignore_ascii_case("lower_band")
15210 || output_id.eq_ignore_ascii_case("lower")
15211 {
15212 return Ok(out.lower_band);
15213 }
15214 if output_id.eq_ignore_ascii_case("range_width")
15215 || output_id.eq_ignore_ascii_case("width")
15216 {
15217 return Ok(out.range_width);
15218 }
15219 if output_id.eq_ignore_ascii_case("in_range") {
15220 return Ok(out.in_range);
15221 }
15222 if output_id.eq_ignore_ascii_case("trend") {
15223 return Ok(out.trend);
15224 }
15225 if output_id.eq_ignore_ascii_case("break_up") {
15226 return Ok(out.break_up);
15227 }
15228 if output_id.eq_ignore_ascii_case("break_down") {
15229 return Ok(out.break_down);
15230 }
15231 Err(IndicatorDispatchError::UnknownOutput {
15232 indicator: "range_oscillator".to_string(),
15233 output: output_id.to_string(),
15234 })
15235 },
15236 )
15237}
15238
15239fn compute_market_structure_confluence_batch(
15240 req: IndicatorBatchRequest<'_>,
15241 output_id: &str,
15242) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15243 let (high, low, close) = extract_ohlc_input("market_structure_confluence", req.data)?;
15244 let kernel = req.kernel.to_non_batch();
15245 collect_f64(
15246 "market_structure_confluence",
15247 output_id,
15248 req.combos,
15249 close.len(),
15250 |params| {
15251 let swing_size =
15252 get_usize_param("market_structure_confluence", params, "swing_size", 10)?;
15253 let bos_confirmation = get_enum_param(
15254 "market_structure_confluence",
15255 params,
15256 "bos_confirmation",
15257 "Candle Close",
15258 )?;
15259 let basis_length =
15260 get_usize_param("market_structure_confluence", params, "basis_length", 100)?;
15261 let atr_length =
15262 get_usize_param("market_structure_confluence", params, "atr_length", 14)?;
15263 let atr_smooth =
15264 get_usize_param("market_structure_confluence", params, "atr_smooth", 21)?;
15265 let vol_mult =
15266 get_f64_param("market_structure_confluence", params, "vol_mult", 2.0)?;
15267 let input = MarketStructureConfluenceInput::from_slices(
15268 high,
15269 low,
15270 close,
15271 MarketStructureConfluenceParams {
15272 swing_size: Some(swing_size),
15273 bos_confirmation: Some(bos_confirmation),
15274 basis_length: Some(basis_length),
15275 atr_length: Some(atr_length),
15276 atr_smooth: Some(atr_smooth),
15277 vol_mult: Some(vol_mult),
15278 },
15279 );
15280 let out = market_structure_confluence_with_kernel(&input, kernel).map_err(|e| {
15281 IndicatorDispatchError::ComputeFailed {
15282 indicator: "market_structure_confluence".to_string(),
15283 details: e.to_string(),
15284 }
15285 })?;
15286 if output_id.eq_ignore_ascii_case("basis") {
15287 return Ok(out.basis);
15288 }
15289 if output_id.eq_ignore_ascii_case("upper_band")
15290 || output_id.eq_ignore_ascii_case("upper")
15291 {
15292 return Ok(out.upper_band);
15293 }
15294 if output_id.eq_ignore_ascii_case("lower_band")
15295 || output_id.eq_ignore_ascii_case("lower")
15296 {
15297 return Ok(out.lower_band);
15298 }
15299 if output_id.eq_ignore_ascii_case("structure_direction")
15300 || output_id.eq_ignore_ascii_case("direction")
15301 || output_id.eq_ignore_ascii_case("trend")
15302 {
15303 return Ok(out.structure_direction);
15304 }
15305 if output_id.eq_ignore_ascii_case("bullish_arrow") {
15306 return Ok(out.bullish_arrow);
15307 }
15308 if output_id.eq_ignore_ascii_case("bearish_arrow") {
15309 return Ok(out.bearish_arrow);
15310 }
15311 if output_id.eq_ignore_ascii_case("bullish_change") {
15312 return Ok(out.bullish_change);
15313 }
15314 if output_id.eq_ignore_ascii_case("bearish_change") {
15315 return Ok(out.bearish_change);
15316 }
15317 if output_id.eq_ignore_ascii_case("hh") {
15318 return Ok(out.hh);
15319 }
15320 if output_id.eq_ignore_ascii_case("lh") {
15321 return Ok(out.lh);
15322 }
15323 if output_id.eq_ignore_ascii_case("hl") {
15324 return Ok(out.hl);
15325 }
15326 if output_id.eq_ignore_ascii_case("ll") {
15327 return Ok(out.ll);
15328 }
15329 if output_id.eq_ignore_ascii_case("bullish_bos") {
15330 return Ok(out.bullish_bos);
15331 }
15332 if output_id.eq_ignore_ascii_case("bullish_choch") {
15333 return Ok(out.bullish_choch);
15334 }
15335 if output_id.eq_ignore_ascii_case("bearish_bos") {
15336 return Ok(out.bearish_bos);
15337 }
15338 if output_id.eq_ignore_ascii_case("bearish_choch") {
15339 return Ok(out.bearish_choch);
15340 }
15341 Err(IndicatorDispatchError::UnknownOutput {
15342 indicator: "market_structure_confluence".to_string(),
15343 output: output_id.to_string(),
15344 })
15345 },
15346 )
15347}
15348
15349fn compute_range_filtered_trend_signals_batch(
15350 req: IndicatorBatchRequest<'_>,
15351 output_id: &str,
15352) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15353 let (high, low, close) = extract_ohlc_input("range_filtered_trend_signals", req.data)?;
15354 let kernel = req.kernel.to_non_batch();
15355 collect_f64(
15356 "range_filtered_trend_signals",
15357 output_id,
15358 req.combos,
15359 close.len(),
15360 |params| {
15361 let kalman_alpha =
15362 get_f64_param("range_filtered_trend_signals", params, "kalman_alpha", 0.01)?;
15363 let kalman_beta =
15364 get_f64_param("range_filtered_trend_signals", params, "kalman_beta", 0.1)?;
15365 let kalman_period =
15366 get_usize_param("range_filtered_trend_signals", params, "kalman_period", 77)?;
15367 let dev = get_f64_param("range_filtered_trend_signals", params, "dev", 1.2)?;
15368 let supertrend_factor = get_f64_param(
15369 "range_filtered_trend_signals",
15370 params,
15371 "supertrend_factor",
15372 0.7,
15373 )?;
15374 let supertrend_atr_period = get_usize_param(
15375 "range_filtered_trend_signals",
15376 params,
15377 "supertrend_atr_period",
15378 7,
15379 )?;
15380 let input = RangeFilteredTrendSignalsInput::from_slices(
15381 high,
15382 low,
15383 close,
15384 RangeFilteredTrendSignalsParams {
15385 kalman_alpha: Some(kalman_alpha),
15386 kalman_beta: Some(kalman_beta),
15387 kalman_period: Some(kalman_period),
15388 dev: Some(dev),
15389 supertrend_factor: Some(supertrend_factor),
15390 supertrend_atr_period: Some(supertrend_atr_period),
15391 },
15392 );
15393 let out = range_filtered_trend_signals_with_kernel(&input, kernel).map_err(|e| {
15394 IndicatorDispatchError::ComputeFailed {
15395 indicator: "range_filtered_trend_signals".to_string(),
15396 details: e.to_string(),
15397 }
15398 })?;
15399 if output_id.eq_ignore_ascii_case("kalman") {
15400 return Ok(out.kalman);
15401 }
15402 if output_id.eq_ignore_ascii_case("supertrend") {
15403 return Ok(out.supertrend);
15404 }
15405 if output_id.eq_ignore_ascii_case("upper_band")
15406 || output_id.eq_ignore_ascii_case("upper")
15407 {
15408 return Ok(out.upper_band);
15409 }
15410 if output_id.eq_ignore_ascii_case("lower_band")
15411 || output_id.eq_ignore_ascii_case("lower")
15412 {
15413 return Ok(out.lower_band);
15414 }
15415 if output_id.eq_ignore_ascii_case("trend") {
15416 return Ok(out.trend);
15417 }
15418 if output_id.eq_ignore_ascii_case("kalman_trend")
15419 || output_id.eq_ignore_ascii_case("long_trend")
15420 {
15421 return Ok(out.kalman_trend);
15422 }
15423 if output_id.eq_ignore_ascii_case("state") {
15424 return Ok(out.state);
15425 }
15426 if output_id.eq_ignore_ascii_case("market_trending") {
15427 return Ok(out.market_trending);
15428 }
15429 if output_id.eq_ignore_ascii_case("market_ranging") {
15430 return Ok(out.market_ranging);
15431 }
15432 if output_id.eq_ignore_ascii_case("short_term_bullish") {
15433 return Ok(out.short_term_bullish);
15434 }
15435 if output_id.eq_ignore_ascii_case("short_term_bearish") {
15436 return Ok(out.short_term_bearish);
15437 }
15438 if output_id.eq_ignore_ascii_case("long_term_bullish") {
15439 return Ok(out.long_term_bullish);
15440 }
15441 if output_id.eq_ignore_ascii_case("long_term_bearish") {
15442 return Ok(out.long_term_bearish);
15443 }
15444 Err(IndicatorDispatchError::UnknownOutput {
15445 indicator: "range_filtered_trend_signals".to_string(),
15446 output: output_id.to_string(),
15447 })
15448 },
15449 )
15450}
15451
15452fn compute_volume_weighted_relative_strength_index_batch(
15453 req: IndicatorBatchRequest<'_>,
15454 output_id: &str,
15455) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15456 let (source, volume) =
15457 extract_close_volume_input("volume_weighted_relative_strength_index", req.data, "close")?;
15458 let kernel = req.kernel.to_non_batch();
15459 collect_f64(
15460 "volume_weighted_relative_strength_index",
15461 output_id,
15462 req.combos,
15463 source.len(),
15464 |params| {
15465 let rsi_length = get_usize_param(
15466 "volume_weighted_relative_strength_index",
15467 params,
15468 "rsi_length",
15469 14,
15470 )?;
15471 let range_length = get_usize_param(
15472 "volume_weighted_relative_strength_index",
15473 params,
15474 "range_length",
15475 10,
15476 )?;
15477 let ma_length = get_usize_param(
15478 "volume_weighted_relative_strength_index",
15479 params,
15480 "ma_length",
15481 14,
15482 )?;
15483 let ma_type = get_enum_param(
15484 "volume_weighted_relative_strength_index",
15485 params,
15486 "ma_type",
15487 "EMA",
15488 )?;
15489 let input = VolumeWeightedRelativeStrengthIndexInput::from_slices(
15490 source,
15491 volume,
15492 VolumeWeightedRelativeStrengthIndexParams {
15493 rsi_length: Some(rsi_length),
15494 range_length: Some(range_length),
15495 ma_length: Some(ma_length),
15496 ma_type: Some(ma_type),
15497 },
15498 );
15499 let out = volume_weighted_relative_strength_index_with_kernel(&input, kernel).map_err(
15500 |e| IndicatorDispatchError::ComputeFailed {
15501 indicator: "volume_weighted_relative_strength_index".to_string(),
15502 details: e.to_string(),
15503 },
15504 )?;
15505 if output_id.eq_ignore_ascii_case("rsi") || output_id.eq_ignore_ascii_case("value") {
15506 return Ok(out.rsi);
15507 }
15508 if output_id.eq_ignore_ascii_case("consolidation_strength")
15509 || output_id.eq_ignore_ascii_case("consolidation")
15510 {
15511 return Ok(out.consolidation_strength);
15512 }
15513 if output_id.eq_ignore_ascii_case("rsi_ma") || output_id.eq_ignore_ascii_case("ma") {
15514 return Ok(out.rsi_ma);
15515 }
15516 if output_id.eq_ignore_ascii_case("bearish_tp") {
15517 return Ok(out.bearish_tp);
15518 }
15519 if output_id.eq_ignore_ascii_case("bullish_tp") {
15520 return Ok(out.bullish_tp);
15521 }
15522 Err(IndicatorDispatchError::UnknownOutput {
15523 indicator: "volume_weighted_relative_strength_index".to_string(),
15524 output: output_id.to_string(),
15525 })
15526 },
15527 )
15528}
15529
15530fn compute_range_filter_batch(
15531 req: IndicatorBatchRequest<'_>,
15532 output_id: &str,
15533) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15534 let data = extract_slice_input("range_filter", req.data, "close")?;
15535 let kernel = req.kernel.to_non_batch();
15536 collect_f64(
15537 "range_filter",
15538 output_id,
15539 req.combos,
15540 data.len(),
15541 |params| {
15542 let range_size = get_f64_param("range_filter", params, "range_size", 2.618)?;
15543 let range_period = get_usize_param("range_filter", params, "range_period", 14)?;
15544 let smooth_range = get_bool_param("range_filter", params, "smooth_range", true)?;
15545 let smooth_period = get_usize_param("range_filter", params, "smooth_period", 27)?;
15546 let input = RangeFilterInput::from_slice(
15547 data,
15548 RangeFilterParams {
15549 range_size: Some(range_size),
15550 range_period: Some(range_period),
15551 smooth_range: Some(smooth_range),
15552 smooth_period: Some(smooth_period),
15553 },
15554 );
15555 let out = range_filter_with_kernel(&input, kernel).map_err(|e| {
15556 IndicatorDispatchError::ComputeFailed {
15557 indicator: "range_filter".to_string(),
15558 details: e.to_string(),
15559 }
15560 })?;
15561 if output_id.eq_ignore_ascii_case("filter") || output_id.eq_ignore_ascii_case("value") {
15562 return Ok(out.filter);
15563 }
15564 if output_id.eq_ignore_ascii_case("high_band") || output_id.eq_ignore_ascii_case("high")
15565 {
15566 return Ok(out.high_band);
15567 }
15568 if output_id.eq_ignore_ascii_case("low_band") || output_id.eq_ignore_ascii_case("low") {
15569 return Ok(out.low_band);
15570 }
15571 Err(IndicatorDispatchError::UnknownOutput {
15572 indicator: "range_filter".to_string(),
15573 output: output_id.to_string(),
15574 })
15575 },
15576 )
15577}
15578
15579fn compute_rsmk_batch(
15580 req: IndicatorBatchRequest<'_>,
15581 output_id: &str,
15582) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15583 let (main, compare) = match req.data {
15584 IndicatorDataRef::CloseVolume { close, volume } => {
15585 ensure_same_len_2("rsmk", close.len(), volume.len())?;
15586 (close, volume)
15587 }
15588 IndicatorDataRef::Ohlcv {
15589 open,
15590 high,
15591 low,
15592 close,
15593 volume,
15594 } => {
15595 ensure_same_len_5(
15596 "rsmk",
15597 open.len(),
15598 high.len(),
15599 low.len(),
15600 close.len(),
15601 volume.len(),
15602 )?;
15603 (close, volume)
15604 }
15605 IndicatorDataRef::Candles { candles, source } => (
15606 source_type(candles, source.unwrap_or("close")),
15607 candles.volume.as_slice(),
15608 ),
15609 _ => {
15610 return Err(IndicatorDispatchError::MissingRequiredInput {
15611 indicator: "rsmk".to_string(),
15612 input: IndicatorInputKind::CloseVolume,
15613 });
15614 }
15615 };
15616 let kernel = req.kernel.to_non_batch();
15617 collect_f64("rsmk", output_id, req.combos, main.len(), |params| {
15618 let lookback = get_usize_param("rsmk", params, "lookback", 90)?;
15619 let period = get_usize_param("rsmk", params, "period", 3)?;
15620 let signal_period = get_usize_param("rsmk", params, "signal_period", 20)?;
15621 let matype = get_enum_param("rsmk", params, "matype", "ema")?;
15622 let signal_matype = get_enum_param("rsmk", params, "signal_matype", "ema")?;
15623 let input = RsmkInput::from_slices(
15624 main,
15625 compare,
15626 RsmkParams {
15627 lookback: Some(lookback),
15628 period: Some(period),
15629 signal_period: Some(signal_period),
15630 matype: Some(matype),
15631 signal_matype: Some(signal_matype),
15632 },
15633 );
15634 let out = rsmk_with_kernel(&input, kernel).map_err(|e| {
15635 IndicatorDispatchError::ComputeFailed {
15636 indicator: "rsmk".to_string(),
15637 details: e.to_string(),
15638 }
15639 })?;
15640 if output_id.eq_ignore_ascii_case("indicator") || output_id.eq_ignore_ascii_case("value") {
15641 return Ok(out.indicator);
15642 }
15643 if output_id.eq_ignore_ascii_case("signal") {
15644 return Ok(out.signal);
15645 }
15646 Err(IndicatorDispatchError::UnknownOutput {
15647 indicator: "rsmk".to_string(),
15648 output: output_id.to_string(),
15649 })
15650 })
15651}
15652
15653fn compute_voss_batch(
15654 req: IndicatorBatchRequest<'_>,
15655 output_id: &str,
15656) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15657 let data = extract_slice_input("voss", req.data, "close")?;
15658 let kernel = req.kernel.to_non_batch();
15659 collect_f64("voss", output_id, req.combos, data.len(), |params| {
15660 let period = get_usize_param("voss", params, "period", 20)?;
15661 let predict = get_usize_param("voss", params, "predict", 3)?;
15662 let bandwidth = get_f64_param("voss", params, "bandwidth", 0.25)?;
15663 let input = VossInput::from_slice(
15664 data,
15665 VossParams {
15666 period: Some(period),
15667 predict: Some(predict),
15668 bandwidth: Some(bandwidth),
15669 },
15670 );
15671 let out = voss_with_kernel(&input, kernel).map_err(|e| {
15672 IndicatorDispatchError::ComputeFailed {
15673 indicator: "voss".to_string(),
15674 details: e.to_string(),
15675 }
15676 })?;
15677 if output_id.eq_ignore_ascii_case("voss") || output_id.eq_ignore_ascii_case("value") {
15678 return Ok(out.voss);
15679 }
15680 if output_id.eq_ignore_ascii_case("filt") || output_id.eq_ignore_ascii_case("filter") {
15681 return Ok(out.filt);
15682 }
15683 Err(IndicatorDispatchError::UnknownOutput {
15684 indicator: "voss".to_string(),
15685 output: output_id.to_string(),
15686 })
15687 })
15688}
15689
15690fn compute_stc_batch(
15691 req: IndicatorBatchRequest<'_>,
15692 output_id: &str,
15693) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15694 let data = extract_slice_input("stc", req.data, "close")?;
15695 let kernel = req.kernel.to_non_batch();
15696 collect_f64("stc", output_id, req.combos, data.len(), |params| {
15697 let fast_period = get_usize_param("stc", params, "fast_period", 23)?;
15698 let slow_period = get_usize_param("stc", params, "slow_period", 50)?;
15699 let k_period = get_usize_param("stc", params, "k_period", 10)?;
15700 let d_period = get_usize_param("stc", params, "d_period", 3)?;
15701 let input = StcInput::from_slice(
15702 data,
15703 StcParams {
15704 fast_period: Some(fast_period),
15705 slow_period: Some(slow_period),
15706 k_period: Some(k_period),
15707 d_period: Some(d_period),
15708 fast_ma_type: Some("ema".to_string()),
15709 slow_ma_type: Some("ema".to_string()),
15710 },
15711 );
15712 let out =
15713 stc_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
15714 indicator: "stc".to_string(),
15715 details: e.to_string(),
15716 })?;
15717 if output_id.eq_ignore_ascii_case("value") || output_id.eq_ignore_ascii_case("values") {
15718 return Ok(out.values);
15719 }
15720 Err(IndicatorDispatchError::UnknownOutput {
15721 indicator: "stc".to_string(),
15722 output: output_id.to_string(),
15723 })
15724 })
15725}
15726
15727fn compute_rvi_batch(
15728 req: IndicatorBatchRequest<'_>,
15729 output_id: &str,
15730) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15731 let data = extract_slice_input("rvi", req.data, "close")?;
15732 let kernel = req.kernel.to_non_batch();
15733 collect_f64("rvi", output_id, req.combos, data.len(), |params| {
15734 let period = get_usize_param("rvi", params, "period", 10)?;
15735 let ma_len = get_usize_param("rvi", params, "ma_len", 14)?;
15736 let matype = get_usize_param("rvi", params, "matype", 1)?;
15737 let devtype = get_usize_param("rvi", params, "devtype", 0)?;
15738 let input = RviInput::from_slice(
15739 data,
15740 RviParams {
15741 period: Some(period),
15742 ma_len: Some(ma_len),
15743 matype: Some(matype),
15744 devtype: Some(devtype),
15745 },
15746 );
15747 let out =
15748 rvi_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
15749 indicator: "rvi".to_string(),
15750 details: e.to_string(),
15751 })?;
15752 if output_id.eq_ignore_ascii_case("value") || output_id.eq_ignore_ascii_case("values") {
15753 return Ok(out.values);
15754 }
15755 Err(IndicatorDispatchError::UnknownOutput {
15756 indicator: "rvi".to_string(),
15757 output: output_id.to_string(),
15758 })
15759 })
15760}
15761
15762fn compute_coppock_batch(
15763 req: IndicatorBatchRequest<'_>,
15764 output_id: &str,
15765) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15766 let data = extract_slice_input("coppock", req.data, "close")?;
15767 let kernel = req.kernel.to_non_batch();
15768 collect_f64("coppock", output_id, req.combos, data.len(), |params| {
15769 let short_roc_period = get_usize_param("coppock", params, "short_roc_period", 11)?;
15770 let long_roc_period = get_usize_param("coppock", params, "long_roc_period", 14)?;
15771 let ma_period = get_usize_param("coppock", params, "ma_period", 10)?;
15772 let input = CoppockInput::from_slice(
15773 data,
15774 CoppockParams {
15775 short_roc_period: Some(short_roc_period),
15776 long_roc_period: Some(long_roc_period),
15777 ma_period: Some(ma_period),
15778 ma_type: Some("wma".to_string()),
15779 },
15780 );
15781 let out = coppock_with_kernel(&input, kernel).map_err(|e| {
15782 IndicatorDispatchError::ComputeFailed {
15783 indicator: "coppock".to_string(),
15784 details: e.to_string(),
15785 }
15786 })?;
15787 if output_id.eq_ignore_ascii_case("value") || output_id.eq_ignore_ascii_case("values") {
15788 return Ok(out.values);
15789 }
15790 Err(IndicatorDispatchError::UnknownOutput {
15791 indicator: "coppock".to_string(),
15792 output: output_id.to_string(),
15793 })
15794 })
15795}
15796
15797fn compute_correl_hl_batch(
15798 req: IndicatorBatchRequest<'_>,
15799 output_id: &str,
15800) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15801 expect_value_output("correl_hl", output_id)?;
15802 let (high, low) = extract_high_low_input("correl_hl", req.data)?;
15803 let kernel = req.kernel.to_non_batch();
15804 collect_f64("correl_hl", output_id, req.combos, high.len(), |params| {
15805 let period = get_usize_param("correl_hl", params, "period", 9)?;
15806 let input = CorrelHlInput::from_slices(
15807 high,
15808 low,
15809 CorrelHlParams {
15810 period: Some(period),
15811 },
15812 );
15813 let out = correl_hl_with_kernel(&input, kernel).map_err(|e| {
15814 IndicatorDispatchError::ComputeFailed {
15815 indicator: "correl_hl".to_string(),
15816 details: e.to_string(),
15817 }
15818 })?;
15819 Ok(out.values)
15820 })
15821}
15822
15823fn compute_net_myrsi_batch(
15824 req: IndicatorBatchRequest<'_>,
15825 output_id: &str,
15826) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15827 let data = extract_slice_input("net_myrsi", req.data, "close")?;
15828 let kernel = req.kernel.to_non_batch();
15829 collect_f64("net_myrsi", output_id, req.combos, data.len(), |params| {
15830 let period = get_usize_param("net_myrsi", params, "period", 14)?;
15831 let input = NetMyrsiInput::from_slice(
15832 data,
15833 NetMyrsiParams {
15834 period: Some(period),
15835 },
15836 );
15837 let out = net_myrsi_with_kernel(&input, kernel).map_err(|e| {
15838 IndicatorDispatchError::ComputeFailed {
15839 indicator: "net_myrsi".to_string(),
15840 details: e.to_string(),
15841 }
15842 })?;
15843 if output_id.eq_ignore_ascii_case("value") || output_id.eq_ignore_ascii_case("values") {
15844 return Ok(out.values);
15845 }
15846 Err(IndicatorDispatchError::UnknownOutput {
15847 indicator: "net_myrsi".to_string(),
15848 output: output_id.to_string(),
15849 })
15850 })
15851}
15852
15853fn compute_pivot_batch(
15854 req: IndicatorBatchRequest<'_>,
15855 output_id: &str,
15856) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15857 let (open, high, low, close) = extract_ohlc_full_input("pivot", req.data)?;
15858 let kernel = req.kernel.to_non_batch();
15859 collect_f64("pivot", output_id, req.combos, close.len(), |params| {
15860 let mode = get_usize_param("pivot", params, "mode", 3)?;
15861 let input =
15862 PivotInput::from_slices(high, low, close, open, PivotParams { mode: Some(mode) });
15863 let out = pivot_with_kernel(&input, kernel).map_err(|e| {
15864 IndicatorDispatchError::ComputeFailed {
15865 indicator: "pivot".to_string(),
15866 details: e.to_string(),
15867 }
15868 })?;
15869 if output_id.eq_ignore_ascii_case("pp") || output_id.eq_ignore_ascii_case("value") {
15870 return Ok(out.pp);
15871 }
15872 if output_id.eq_ignore_ascii_case("r1") {
15873 return Ok(out.r1);
15874 }
15875 if output_id.eq_ignore_ascii_case("r2") {
15876 return Ok(out.r2);
15877 }
15878 if output_id.eq_ignore_ascii_case("r3") {
15879 return Ok(out.r3);
15880 }
15881 if output_id.eq_ignore_ascii_case("r4") {
15882 return Ok(out.r4);
15883 }
15884 if output_id.eq_ignore_ascii_case("s1") {
15885 return Ok(out.s1);
15886 }
15887 if output_id.eq_ignore_ascii_case("s2") {
15888 return Ok(out.s2);
15889 }
15890 if output_id.eq_ignore_ascii_case("s3") {
15891 return Ok(out.s3);
15892 }
15893 if output_id.eq_ignore_ascii_case("s4") {
15894 return Ok(out.s4);
15895 }
15896 Err(IndicatorDispatchError::UnknownOutput {
15897 indicator: "pivot".to_string(),
15898 output: output_id.to_string(),
15899 })
15900 })
15901}
15902
15903fn compute_wad_batch(
15904 req: IndicatorBatchRequest<'_>,
15905 output_id: &str,
15906) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15907 expect_value_output("wad", output_id)?;
15908 let (_open, high, low, close) = extract_ohlc_full_input("wad", req.data)?;
15909 let kernel = req.kernel.to_non_batch();
15910 collect_f64("wad", output_id, req.combos, close.len(), |_params| {
15911 let input = WadInput::from_slices(high, low, close);
15912 let out =
15913 wad_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
15914 indicator: "wad".to_string(),
15915 details: e.to_string(),
15916 })?;
15917 Ok(out.values)
15918 })
15919}
15920
15921fn ma_data_from_req<'a>(
15922 indicator: &str,
15923 data: IndicatorDataRef<'a>,
15924) -> Result<MaData<'a>, IndicatorDispatchError> {
15925 match data {
15926 IndicatorDataRef::Slice { values } => Ok(MaData::Slice(values)),
15927 IndicatorDataRef::Candles { candles, source } => Ok(MaData::Candles {
15928 candles,
15929 source: source.unwrap_or("close"),
15930 }),
15931 IndicatorDataRef::Ohlc { close, .. } => Ok(MaData::Slice(close)),
15932 IndicatorDataRef::Ohlcv { close, .. } => Ok(MaData::Slice(close)),
15933 IndicatorDataRef::CloseVolume { close, .. } => Ok(MaData::Slice(close)),
15934 IndicatorDataRef::HighLow { .. } => Err(IndicatorDispatchError::MissingRequiredInput {
15935 indicator: indicator.to_string(),
15936 input: IndicatorInputKind::Slice,
15937 }),
15938 }
15939}
15940
15941fn ma_len_from_req(
15942 indicator: &str,
15943 data: IndicatorDataRef<'_>,
15944) -> Result<usize, IndicatorDispatchError> {
15945 match data {
15946 IndicatorDataRef::Slice { values } => Ok(values.len()),
15947 IndicatorDataRef::Candles { candles, source } => {
15948 Ok(source_type(candles, source.unwrap_or("close")).len())
15949 }
15950 IndicatorDataRef::Ohlc { close, .. } => Ok(close.len()),
15951 IndicatorDataRef::Ohlcv { close, .. } => Ok(close.len()),
15952 IndicatorDataRef::CloseVolume { close, .. } => Ok(close.len()),
15953 IndicatorDataRef::HighLow { .. } => Err(IndicatorDispatchError::MissingRequiredInput {
15954 indicator: indicator.to_string(),
15955 input: IndicatorInputKind::Slice,
15956 }),
15957 }
15958}
15959
15960fn ma_period_for_combo(
15961 info: &IndicatorInfo,
15962 params: &[ParamKV<'_>],
15963) -> Result<usize, IndicatorDispatchError> {
15964 if let Some(v) = find_param(params, "period") {
15965 return parse_usize_param_value(info.id, "period", v);
15966 }
15967 if let Some(default) = info
15968 .params
15969 .iter()
15970 .find(|p| p.key.eq_ignore_ascii_case("period"))
15971 .and_then(|p| p.default.as_ref())
15972 {
15973 if let ParamValueStatic::Int(v) = default {
15974 if *v >= 0 {
15975 return Ok(*v as usize);
15976 }
15977 }
15978 }
15979 Ok(14)
15980}
15981
15982fn convert_ma_params<'a>(
15983 params: &'a [ParamKV<'a>],
15984 indicator: &str,
15985 output_id: &str,
15986) -> Result<Vec<MaBatchParamKV<'a>>, IndicatorDispatchError> {
15987 let mut out = Vec::with_capacity(params.len());
15988 for p in params {
15989 if p.key.eq_ignore_ascii_case("period") {
15990 continue;
15991 }
15992 if p.key.eq_ignore_ascii_case("output") {
15993 let selected = match p.value {
15994 ParamValue::EnumString(v) => v,
15995 _ => {
15996 return Err(IndicatorDispatchError::InvalidParam {
15997 indicator: indicator.to_string(),
15998 key: "output".to_string(),
15999 reason: "expected EnumString".to_string(),
16000 })
16001 }
16002 };
16003 if !selected.eq_ignore_ascii_case(output_id) {
16004 return Err(IndicatorDispatchError::InvalidParam {
16005 indicator: indicator.to_string(),
16006 key: "output".to_string(),
16007 reason: format!(
16008 "param output '{}' does not match requested output_id '{}'",
16009 selected, output_id
16010 ),
16011 });
16012 }
16013 }
16014 let value = match p.value {
16015 ParamValue::Int(v) => MaBatchParamValue::Int(v),
16016 ParamValue::Float(v) => {
16017 if !v.is_finite() {
16018 return Err(IndicatorDispatchError::InvalidParam {
16019 indicator: indicator.to_string(),
16020 key: p.key.to_string(),
16021 reason: "expected finite float".to_string(),
16022 });
16023 }
16024 MaBatchParamValue::Float(v)
16025 }
16026 ParamValue::Bool(v) => MaBatchParamValue::Bool(v),
16027 ParamValue::EnumString(v) => MaBatchParamValue::EnumString(v),
16028 };
16029 out.push(MaBatchParamKV { key: p.key, value });
16030 }
16031 Ok(out)
16032}
16033
16034fn extract_slice_input<'a>(
16035 indicator: &str,
16036 data: IndicatorDataRef<'a>,
16037 default_source: &'a str,
16038) -> Result<&'a [f64], IndicatorDispatchError> {
16039 match data {
16040 IndicatorDataRef::Slice { values } => Ok(values),
16041 IndicatorDataRef::Candles { candles, source } => {
16042 Ok(source_type(candles, source.unwrap_or(default_source)))
16043 }
16044 IndicatorDataRef::Ohlc { close, .. } => Ok(close),
16045 IndicatorDataRef::Ohlcv { close, .. } => Ok(close),
16046 IndicatorDataRef::CloseVolume { close, .. } => Ok(close),
16047 IndicatorDataRef::HighLow { .. } => Err(IndicatorDispatchError::MissingRequiredInput {
16048 indicator: indicator.to_string(),
16049 input: IndicatorInputKind::Slice,
16050 }),
16051 }
16052}
16053
16054fn extract_ohlc_input<'a>(
16055 indicator: &str,
16056 data: IndicatorDataRef<'a>,
16057) -> Result<(&'a [f64], &'a [f64], &'a [f64]), IndicatorDispatchError> {
16058 match data {
16059 IndicatorDataRef::Candles { candles, .. } => Ok((
16060 candles.high.as_slice(),
16061 candles.low.as_slice(),
16062 candles.close.as_slice(),
16063 )),
16064 IndicatorDataRef::Ohlc {
16065 high,
16066 low,
16067 close,
16068 open,
16069 } => {
16070 ensure_same_len_4(indicator, open.len(), high.len(), low.len(), close.len())?;
16071 Ok((high, low, close))
16072 }
16073 IndicatorDataRef::Ohlcv {
16074 high,
16075 low,
16076 close,
16077 open,
16078 volume,
16079 } => {
16080 ensure_same_len_5(
16081 indicator,
16082 open.len(),
16083 high.len(),
16084 low.len(),
16085 close.len(),
16086 volume.len(),
16087 )?;
16088 Ok((high, low, close))
16089 }
16090 _ => Err(IndicatorDispatchError::MissingRequiredInput {
16091 indicator: indicator.to_string(),
16092 input: IndicatorInputKind::Ohlc,
16093 }),
16094 }
16095}
16096
16097fn extract_ohlc_full_input<'a>(
16098 indicator: &str,
16099 data: IndicatorDataRef<'a>,
16100) -> Result<(&'a [f64], &'a [f64], &'a [f64], &'a [f64]), IndicatorDispatchError> {
16101 match data {
16102 IndicatorDataRef::Candles { candles, .. } => Ok((
16103 candles.open.as_slice(),
16104 candles.high.as_slice(),
16105 candles.low.as_slice(),
16106 candles.close.as_slice(),
16107 )),
16108 IndicatorDataRef::Ohlc {
16109 open,
16110 high,
16111 low,
16112 close,
16113 } => {
16114 ensure_same_len_4(indicator, open.len(), high.len(), low.len(), close.len())?;
16115 Ok((open, high, low, close))
16116 }
16117 IndicatorDataRef::Ohlcv {
16118 open,
16119 high,
16120 low,
16121 close,
16122 volume,
16123 } => {
16124 ensure_same_len_5(
16125 indicator,
16126 open.len(),
16127 high.len(),
16128 low.len(),
16129 close.len(),
16130 volume.len(),
16131 )?;
16132 Ok((open, high, low, close))
16133 }
16134 _ => Err(IndicatorDispatchError::MissingRequiredInput {
16135 indicator: indicator.to_string(),
16136 input: IndicatorInputKind::Ohlc,
16137 }),
16138 }
16139}
16140
16141fn extract_ohlcv_full_input<'a>(
16142 indicator: &str,
16143 data: IndicatorDataRef<'a>,
16144) -> Result<(&'a [f64], &'a [f64], &'a [f64], &'a [f64], &'a [f64]), IndicatorDispatchError> {
16145 match data {
16146 IndicatorDataRef::Candles { candles, .. } => Ok((
16147 candles.open.as_slice(),
16148 candles.high.as_slice(),
16149 candles.low.as_slice(),
16150 candles.close.as_slice(),
16151 candles.volume.as_slice(),
16152 )),
16153 IndicatorDataRef::Ohlcv {
16154 open,
16155 high,
16156 low,
16157 close,
16158 volume,
16159 } => {
16160 ensure_same_len_5(
16161 indicator,
16162 open.len(),
16163 high.len(),
16164 low.len(),
16165 close.len(),
16166 volume.len(),
16167 )?;
16168 Ok((open, high, low, close, volume))
16169 }
16170 _ => Err(IndicatorDispatchError::MissingRequiredInput {
16171 indicator: indicator.to_string(),
16172 input: IndicatorInputKind::Ohlcv,
16173 }),
16174 }
16175}
16176
16177fn extract_high_low_input<'a>(
16178 indicator: &str,
16179 data: IndicatorDataRef<'a>,
16180) -> Result<(&'a [f64], &'a [f64]), IndicatorDispatchError> {
16181 match data {
16182 IndicatorDataRef::Candles { candles, .. } => {
16183 Ok((candles.high.as_slice(), candles.low.as_slice()))
16184 }
16185 IndicatorDataRef::Ohlc {
16186 high,
16187 low,
16188 open,
16189 close,
16190 } => {
16191 ensure_same_len_4(indicator, open.len(), high.len(), low.len(), close.len())?;
16192 Ok((high, low))
16193 }
16194 IndicatorDataRef::Ohlcv {
16195 high,
16196 low,
16197 open,
16198 close,
16199 volume,
16200 } => {
16201 ensure_same_len_5(
16202 indicator,
16203 open.len(),
16204 high.len(),
16205 low.len(),
16206 close.len(),
16207 volume.len(),
16208 )?;
16209 Ok((high, low))
16210 }
16211 IndicatorDataRef::HighLow { high, low } => {
16212 ensure_same_len_2(indicator, high.len(), low.len())?;
16213 Ok((high, low))
16214 }
16215 _ => Err(IndicatorDispatchError::MissingRequiredInput {
16216 indicator: indicator.to_string(),
16217 input: IndicatorInputKind::HighLow,
16218 }),
16219 }
16220}
16221
16222fn extract_hlcv_input<'a>(
16223 indicator: &str,
16224 data: IndicatorDataRef<'a>,
16225) -> Result<(&'a [f64], &'a [f64], &'a [f64], &'a [f64]), IndicatorDispatchError> {
16226 match data {
16227 IndicatorDataRef::Candles { candles, .. } => Ok((
16228 candles.high.as_slice(),
16229 candles.low.as_slice(),
16230 candles.close.as_slice(),
16231 candles.volume.as_slice(),
16232 )),
16233 IndicatorDataRef::Ohlcv {
16234 open,
16235 high,
16236 low,
16237 close,
16238 volume,
16239 } => {
16240 ensure_same_len_5(
16241 indicator,
16242 open.len(),
16243 high.len(),
16244 low.len(),
16245 close.len(),
16246 volume.len(),
16247 )?;
16248 Ok((high, low, close, volume))
16249 }
16250 _ => Err(IndicatorDispatchError::MissingRequiredInput {
16251 indicator: indicator.to_string(),
16252 input: IndicatorInputKind::Ohlcv,
16253 }),
16254 }
16255}
16256
16257fn extract_volume_input<'a>(
16258 indicator: &str,
16259 data: IndicatorDataRef<'a>,
16260) -> Result<&'a [f64], IndicatorDispatchError> {
16261 match data {
16262 IndicatorDataRef::Slice { values } => Ok(values),
16263 IndicatorDataRef::Candles { candles, source } => {
16264 Ok(source_type(candles, source.unwrap_or("volume")))
16265 }
16266 IndicatorDataRef::CloseVolume { close, volume } => {
16267 ensure_same_len_2(indicator, close.len(), volume.len())?;
16268 Ok(volume)
16269 }
16270 IndicatorDataRef::Ohlcv {
16271 open,
16272 high,
16273 low,
16274 close,
16275 volume,
16276 } => {
16277 ensure_same_len_5(
16278 indicator,
16279 open.len(),
16280 high.len(),
16281 low.len(),
16282 close.len(),
16283 volume.len(),
16284 )?;
16285 Ok(volume)
16286 }
16287 _ => Err(IndicatorDispatchError::MissingRequiredInput {
16288 indicator: indicator.to_string(),
16289 input: IndicatorInputKind::Slice,
16290 }),
16291 }
16292}
16293
16294fn extract_close_volume_input<'a>(
16295 indicator: &str,
16296 data: IndicatorDataRef<'a>,
16297 default_close_source: &'a str,
16298) -> Result<(&'a [f64], &'a [f64]), IndicatorDispatchError> {
16299 match data {
16300 IndicatorDataRef::CloseVolume { close, volume } => {
16301 ensure_same_len_2(indicator, close.len(), volume.len())?;
16302 Ok((close, volume))
16303 }
16304 IndicatorDataRef::Ohlcv {
16305 close,
16306 volume,
16307 open,
16308 high,
16309 low,
16310 } => {
16311 ensure_same_len_5(
16312 indicator,
16313 open.len(),
16314 high.len(),
16315 low.len(),
16316 close.len(),
16317 volume.len(),
16318 )?;
16319 Ok((close, volume))
16320 }
16321 IndicatorDataRef::Candles { candles, source } => {
16322 let close = source_type(candles, source.unwrap_or(default_close_source));
16323 let volume = candles.volume.as_slice();
16324 ensure_same_len_2(indicator, close.len(), volume.len())?;
16325 Ok((close, volume))
16326 }
16327 _ => Err(IndicatorDispatchError::MissingRequiredInput {
16328 indicator: indicator.to_string(),
16329 input: IndicatorInputKind::CloseVolume,
16330 }),
16331 }
16332}
16333
16334fn f64_output(output_id: &str, rows: usize, cols: usize, values: Vec<f64>) -> IndicatorBatchOutput {
16335 IndicatorBatchOutput {
16336 output_id: output_id.to_string(),
16337 rows,
16338 cols,
16339 values_f64: Some(values),
16340 values_i32: None,
16341 values_bool: None,
16342 }
16343}
16344
16345fn bool_output(
16346 output_id: &str,
16347 rows: usize,
16348 cols: usize,
16349 values: Vec<bool>,
16350) -> IndicatorBatchOutput {
16351 IndicatorBatchOutput {
16352 output_id: output_id.to_string(),
16353 rows,
16354 cols,
16355 values_f64: None,
16356 values_i32: None,
16357 values_bool: Some(values),
16358 }
16359}
16360
16361fn expect_value_output(indicator: &str, output_id: &str) -> Result<(), IndicatorDispatchError> {
16362 if output_id.eq_ignore_ascii_case("value") {
16363 return Ok(());
16364 }
16365 Err(IndicatorDispatchError::UnknownOutput {
16366 indicator: indicator.to_string(),
16367 output: output_id.to_string(),
16368 })
16369}
16370
16371fn ensure_len(indicator: &str, expected: usize, got: usize) -> Result<(), IndicatorDispatchError> {
16372 if expected == got {
16373 return Ok(());
16374 }
16375 Err(IndicatorDispatchError::DataLengthMismatch {
16376 details: format!("{indicator}: expected output length {expected}, got {got}"),
16377 })
16378}
16379
16380fn ensure_same_len_2(indicator: &str, a: usize, b: usize) -> Result<(), IndicatorDispatchError> {
16381 if a == b {
16382 return Ok(());
16383 }
16384 Err(IndicatorDispatchError::DataLengthMismatch {
16385 details: format!("{indicator}: expected equal lengths, got {a} and {b}"),
16386 })
16387}
16388
16389fn ensure_same_len_3(
16390 indicator: &str,
16391 a: usize,
16392 b: usize,
16393 c: usize,
16394) -> Result<(), IndicatorDispatchError> {
16395 if a == b && b == c {
16396 return Ok(());
16397 }
16398 Err(IndicatorDispatchError::DataLengthMismatch {
16399 details: format!("{indicator}: expected equal lengths, got {a}, {b}, {c}"),
16400 })
16401}
16402
16403fn ensure_same_len_4(
16404 indicator: &str,
16405 a: usize,
16406 b: usize,
16407 c: usize,
16408 d: usize,
16409) -> Result<(), IndicatorDispatchError> {
16410 if a == b && b == c && c == d {
16411 return Ok(());
16412 }
16413 Err(IndicatorDispatchError::DataLengthMismatch {
16414 details: format!("{indicator}: expected equal lengths, got {a}, {b}, {c}, {d}"),
16415 })
16416}
16417
16418fn ensure_same_len_5(
16419 indicator: &str,
16420 a: usize,
16421 b: usize,
16422 c: usize,
16423 d: usize,
16424 e: usize,
16425) -> Result<(), IndicatorDispatchError> {
16426 if a == b && b == c && c == d && d == e {
16427 return Ok(());
16428 }
16429 Err(IndicatorDispatchError::DataLengthMismatch {
16430 details: format!("{indicator}: expected equal lengths, got {a}, {b}, {c}, {d}, {e}"),
16431 })
16432}
16433
16434fn has_key(params: &[ParamKV<'_>], key: &str) -> bool {
16435 params.iter().any(|kv| kv.key.eq_ignore_ascii_case(key))
16436}
16437
16438fn find_param<'a>(params: &'a [ParamKV<'a>], key: &str) -> Option<&'a ParamValue<'a>> {
16439 params
16440 .iter()
16441 .rev()
16442 .find(|kv| kv.key.eq_ignore_ascii_case(key))
16443 .map(|kv| &kv.value)
16444}
16445
16446fn get_usize_param(
16447 indicator: &str,
16448 params: &[ParamKV<'_>],
16449 key: &str,
16450 default: usize,
16451) -> Result<usize, IndicatorDispatchError> {
16452 match find_param(params, key) {
16453 Some(v) => parse_usize_param_value(indicator, key, v),
16454 None => Ok(default),
16455 }
16456}
16457
16458fn get_usize_param_with_aliases(
16459 indicator: &str,
16460 params: &[ParamKV<'_>],
16461 keys: &[&str],
16462 default: usize,
16463) -> Result<usize, IndicatorDispatchError> {
16464 for key in keys {
16465 if let Some(v) = find_param(params, key) {
16466 return parse_usize_param_value(indicator, key, v);
16467 }
16468 }
16469 Ok(default)
16470}
16471
16472fn get_f64_param_with_aliases(
16473 indicator: &str,
16474 params: &[ParamKV<'_>],
16475 keys: &[&str],
16476 default: f64,
16477) -> Result<f64, IndicatorDispatchError> {
16478 for key in keys {
16479 match find_param(params, key) {
16480 Some(ParamValue::Int(v)) => return Ok(*v as f64),
16481 Some(ParamValue::Float(v)) => {
16482 if v.is_finite() {
16483 return Ok(*v);
16484 }
16485 return Err(IndicatorDispatchError::InvalidParam {
16486 indicator: indicator.to_string(),
16487 key: key.to_string(),
16488 reason: "expected finite float".to_string(),
16489 });
16490 }
16491 Some(_) => {
16492 return Err(IndicatorDispatchError::InvalidParam {
16493 indicator: indicator.to_string(),
16494 key: key.to_string(),
16495 reason: "expected Int or Float".to_string(),
16496 });
16497 }
16498 None => continue,
16499 }
16500 }
16501 Ok(default)
16502}
16503
16504fn parse_usize_param_value(
16505 indicator: &str,
16506 key: &str,
16507 value: &ParamValue<'_>,
16508) -> Result<usize, IndicatorDispatchError> {
16509 match value {
16510 ParamValue::Int(v) => {
16511 if *v < 0 {
16512 return Err(IndicatorDispatchError::InvalidParam {
16513 indicator: indicator.to_string(),
16514 key: key.to_string(),
16515 reason: "expected integer >= 0".to_string(),
16516 });
16517 }
16518 Ok(*v as usize)
16519 }
16520 ParamValue::Float(v) => {
16521 if !v.is_finite() {
16522 return Err(IndicatorDispatchError::InvalidParam {
16523 indicator: indicator.to_string(),
16524 key: key.to_string(),
16525 reason: "expected finite number".to_string(),
16526 });
16527 }
16528 if *v < 0.0 {
16529 return Err(IndicatorDispatchError::InvalidParam {
16530 indicator: indicator.to_string(),
16531 key: key.to_string(),
16532 reason: "expected number >= 0".to_string(),
16533 });
16534 }
16535 let r = v.round();
16536 if (*v - r).abs() > 1e-9 {
16537 return Err(IndicatorDispatchError::InvalidParam {
16538 indicator: indicator.to_string(),
16539 key: key.to_string(),
16540 reason: "expected integer value".to_string(),
16541 });
16542 }
16543 Ok(r as usize)
16544 }
16545 _ => Err(IndicatorDispatchError::InvalidParam {
16546 indicator: indicator.to_string(),
16547 key: key.to_string(),
16548 reason: "expected Int or Float".to_string(),
16549 }),
16550 }
16551}
16552
16553fn get_f64_param(
16554 indicator: &str,
16555 params: &[ParamKV<'_>],
16556 key: &str,
16557 default: f64,
16558) -> Result<f64, IndicatorDispatchError> {
16559 match find_param(params, key) {
16560 Some(ParamValue::Int(v)) => Ok(*v as f64),
16561 Some(ParamValue::Float(v)) => {
16562 if v.is_finite() {
16563 Ok(*v)
16564 } else {
16565 Err(IndicatorDispatchError::InvalidParam {
16566 indicator: indicator.to_string(),
16567 key: key.to_string(),
16568 reason: "expected finite float".to_string(),
16569 })
16570 }
16571 }
16572 Some(_) => Err(IndicatorDispatchError::InvalidParam {
16573 indicator: indicator.to_string(),
16574 key: key.to_string(),
16575 reason: "expected Int or Float".to_string(),
16576 }),
16577 None => Ok(default),
16578 }
16579}
16580
16581fn get_bool_param(
16582 indicator: &str,
16583 params: &[ParamKV<'_>],
16584 key: &str,
16585 default: bool,
16586) -> Result<bool, IndicatorDispatchError> {
16587 match find_param(params, key) {
16588 Some(ParamValue::Bool(v)) => Ok(*v),
16589 Some(ParamValue::Int(v)) => match *v {
16590 0 => Ok(false),
16591 1 => Ok(true),
16592 _ => Err(IndicatorDispatchError::InvalidParam {
16593 indicator: indicator.to_string(),
16594 key: key.to_string(),
16595 reason: "expected Bool or Int(0/1)".to_string(),
16596 }),
16597 },
16598 Some(_) => Err(IndicatorDispatchError::InvalidParam {
16599 indicator: indicator.to_string(),
16600 key: key.to_string(),
16601 reason: "expected Bool".to_string(),
16602 }),
16603 None => Ok(default),
16604 }
16605}
16606
16607fn get_enum_string_param<'a>(
16608 indicator: &str,
16609 params: &'a [ParamKV<'a>],
16610 key: &str,
16611 default: &'a str,
16612) -> Result<&'a str, IndicatorDispatchError> {
16613 match find_param(params, key) {
16614 Some(ParamValue::EnumString(v)) => Ok(v),
16615 Some(_) => Err(IndicatorDispatchError::InvalidParam {
16616 indicator: indicator.to_string(),
16617 key: key.to_string(),
16618 reason: "expected EnumString".to_string(),
16619 }),
16620 None => Ok(default),
16621 }
16622}
16623
16624fn get_i32_param(
16625 indicator: &str,
16626 params: &[ParamKV<'_>],
16627 key: &str,
16628 default: i32,
16629) -> Result<i32, IndicatorDispatchError> {
16630 match find_param(params, key) {
16631 Some(ParamValue::Int(v)) => {
16632 if *v < i32::MIN as i64 || *v > i32::MAX as i64 {
16633 return Err(IndicatorDispatchError::InvalidParam {
16634 indicator: indicator.to_string(),
16635 key: key.to_string(),
16636 reason: "integer out of i32 range".to_string(),
16637 });
16638 }
16639 Ok(*v as i32)
16640 }
16641 Some(ParamValue::Float(v)) => {
16642 if !v.is_finite() {
16643 return Err(IndicatorDispatchError::InvalidParam {
16644 indicator: indicator.to_string(),
16645 key: key.to_string(),
16646 reason: "expected finite number".to_string(),
16647 });
16648 }
16649 let r = v.round();
16650 if (*v - r).abs() > 1e-9 || r < i32::MIN as f64 || r > i32::MAX as f64 {
16651 return Err(IndicatorDispatchError::InvalidParam {
16652 indicator: indicator.to_string(),
16653 key: key.to_string(),
16654 reason: "expected i32-compatible whole number".to_string(),
16655 });
16656 }
16657 Ok(r as i32)
16658 }
16659 Some(_) => Err(IndicatorDispatchError::InvalidParam {
16660 indicator: indicator.to_string(),
16661 key: key.to_string(),
16662 reason: "expected Int or Float".to_string(),
16663 }),
16664 None => Ok(default),
16665 }
16666}
16667
16668fn get_enum_param(
16669 indicator: &str,
16670 params: &[ParamKV<'_>],
16671 key: &str,
16672 default: &str,
16673) -> Result<String, IndicatorDispatchError> {
16674 match find_param(params, key) {
16675 Some(ParamValue::EnumString(v)) => Ok((*v).to_string()),
16676 Some(_) => Err(IndicatorDispatchError::InvalidParam {
16677 indicator: indicator.to_string(),
16678 key: key.to_string(),
16679 reason: "expected EnumString".to_string(),
16680 }),
16681 None => Ok(default.to_string()),
16682 }
16683}
16684
16685#[cfg(test)]
16686mod tests {
16687 use super::*;
16688 use crate::indicators::absolute_strength_index_oscillator::{
16689 absolute_strength_index_oscillator_with_kernel, AbsoluteStrengthIndexOscillatorInput,
16690 AbsoluteStrengthIndexOscillatorParams,
16691 };
16692 use crate::indicators::ad::{ad_with_kernel, AdInput, AdParams};
16693 use crate::indicators::adaptive_bandpass_trigger_oscillator::{
16694 adaptive_bandpass_trigger_oscillator_with_kernel, AdaptiveBandpassTriggerOscillatorInput,
16695 AdaptiveBandpassTriggerOscillatorParams,
16696 };
16697 use crate::indicators::advance_decline_line::{
16698 advance_decline_line_with_kernel, AdvanceDeclineLineInput, AdvanceDeclineLineParams,
16699 };
16700 use crate::indicators::accumulation_swing_index::{
16701 accumulation_swing_index_with_kernel, AccumulationSwingIndexInput,
16702 AccumulationSwingIndexParams,
16703 };
16704 use crate::indicators::adx::{adx_with_kernel, AdxInput, AdxParams};
16705 use crate::indicators::ao::{ao_with_kernel, AoInput, AoParams};
16706 use crate::indicators::apo::{apo_with_kernel, ApoInput, ApoParams};
16707 use crate::indicators::atr_percentile::{
16708 atr_percentile_with_kernel, AtrPercentileInput, AtrPercentileParams,
16709 };
16710 use crate::indicators::bull_power_vs_bear_power::{
16711 bull_power_vs_bear_power_with_kernel, BullPowerVsBearPowerInput, BullPowerVsBearPowerParams,
16712 };
16713 use crate::indicators::cg::{cg_with_kernel, CgInput, CgParams};
16714 use crate::indicators::cmo::{cmo_with_kernel, CmoInput, CmoParams};
16715 use crate::indicators::decisionpoint_breadth_swenlin_trading_oscillator::{
16716 decisionpoint_breadth_swenlin_trading_oscillator_with_kernel,
16717 DecisionPointBreadthSwenlinTradingOscillatorInput,
16718 DecisionPointBreadthSwenlinTradingOscillatorParams,
16719 };
16720 use crate::indicators::demand_index::{
16721 demand_index_with_kernel, DemandIndexInput, DemandIndexParams,
16722 };
16723 use crate::indicators::deviation::{deviation_with_kernel, DeviationInput, DeviationParams};
16724 use crate::indicators::dx::{
16725 dx_batch_with_kernel, dx_with_kernel, DxBatchRange, DxInput, DxParams,
16726 };
16727 use crate::indicators::efi::{efi_with_kernel, EfiInput, EfiParams};
16728 use crate::indicators::cycle_channel_oscillator::{
16729 cycle_channel_oscillator_with_kernel, CycleChannelOscillatorInput,
16730 CycleChannelOscillatorParams,
16731 };
16732 use crate::indicators::daily_factor::{
16733 daily_factor_with_kernel, DailyFactorInput, DailyFactorParams,
16734 };
16735 use crate::indicators::ehlers_adaptive_cyber_cycle::{
16736 ehlers_adaptive_cyber_cycle_with_kernel, EhlersAdaptiveCyberCycleInput,
16737 EhlersAdaptiveCyberCycleParams,
16738 };
16739 use crate::indicators::ehlers_linear_extrapolation_predictor::{
16740 ehlers_linear_extrapolation_predictor_with_kernel, EhlersLinearExtrapolationPredictorInput,
16741 EhlersLinearExtrapolationPredictorParams,
16742 };
16743 use crate::indicators::ehlers_simple_cycle_indicator::{
16744 ehlers_simple_cycle_indicator_with_kernel, EhlersSimpleCycleIndicatorInput,
16745 EhlersSimpleCycleIndicatorParams,
16746 };
16747 use crate::indicators::ehlers_smoothed_adaptive_momentum::{
16748 ehlers_smoothed_adaptive_momentum_with_kernel, EhlersSmoothedAdaptiveMomentumInput,
16749 EhlersSmoothedAdaptiveMomentumParams,
16750 };
16751 use crate::indicators::ewma_volatility::{
16752 ewma_volatility_with_kernel, EwmaVolatilityInput, EwmaVolatilityParams,
16753 };
16754 use crate::indicators::fibonacci_entry_bands::{
16755 fibonacci_entry_bands_with_kernel, FibonacciEntryBandsInput, FibonacciEntryBandsParams,
16756 };
16757 use crate::indicators::fibonacci_trailing_stop::{
16758 fibonacci_trailing_stop_with_kernel, FibonacciTrailingStopInput,
16759 FibonacciTrailingStopParams,
16760 };
16761 use crate::indicators::fosc::{fosc_with_kernel, FoscInput, FoscParams};
16762 use crate::indicators::garman_klass_volatility::{
16763 garman_klass_volatility_with_kernel, GarmanKlassVolatilityInput,
16764 GarmanKlassVolatilityParams,
16765 };
16766 use crate::indicators::gopalakrishnan_range_index::{
16767 gopalakrishnan_range_index_with_kernel, GopalakrishnanRangeIndexInput,
16768 GopalakrishnanRangeIndexParams,
16769 };
16770 use crate::indicators::grover_llorens_cycle_oscillator::{
16771 grover_llorens_cycle_oscillator_with_kernel, GroverLlorensCycleOscillatorInput,
16772 GroverLlorensCycleOscillatorParams,
16773 };
16774 use crate::indicators::hema_trend_levels::{
16775 hema_trend_levels_with_kernel, HemaTrendLevelsInput, HemaTrendLevelsParams,
16776 };
16777 use crate::indicators::historical_volatility::{
16778 historical_volatility_with_kernel, HistoricalVolatilityInput, HistoricalVolatilityParams,
16779 };
16780 use crate::indicators::historical_volatility_percentile::{
16781 historical_volatility_percentile_with_kernel, HistoricalVolatilityPercentileInput,
16782 HistoricalVolatilityPercentileParams,
16783 };
16784 use crate::indicators::hull_butterfly_oscillator::{
16785 hull_butterfly_oscillator_with_kernel, HullButterflyOscillatorInput,
16786 HullButterflyOscillatorParams,
16787 };
16788 use crate::indicators::ichimoku_oscillator::{
16789 ichimoku_oscillator_with_kernel, IchimokuOscillatorInput,
16790 IchimokuOscillatorNormalizeMode, IchimokuOscillatorParams,
16791 };
16792 use crate::indicators::ift_rsi::{ift_rsi_with_kernel, IftRsiInput, IftRsiParams};
16793 use crate::indicators::intraday_momentum_index::{
16794 intraday_momentum_index_with_kernel, IntradayMomentumIndexInput,
16795 IntradayMomentumIndexParams,
16796 };
16797 use crate::indicators::kvo::{kvo_with_kernel, KvoInput, KvoParams};
16798 use crate::indicators::l2_ehlers_signal_to_noise::{
16799 l2_ehlers_signal_to_noise_with_kernel, L2EhlersSignalToNoiseInput,
16800 L2EhlersSignalToNoiseParams,
16801 };
16802 use crate::indicators::linearreg_angle::{
16803 linearreg_angle_with_kernel, Linearreg_angleInput, Linearreg_angleParams,
16804 };
16805 use crate::indicators::linearreg_intercept::{
16806 linearreg_intercept_with_kernel, LinearRegInterceptInput, LinearRegInterceptParams,
16807 };
16808 use crate::indicators::linearreg_slope::{
16809 linearreg_slope_with_kernel, LinearRegSlopeInput, LinearRegSlopeParams,
16810 };
16811 use crate::indicators::macd::{macd_with_kernel, MacdInput, MacdParams};
16812 use crate::indicators::macd_wave_signal_pro::{
16813 macd_wave_signal_pro_with_kernel, MacdWaveSignalProInput,
16814 };
16815 use crate::indicators::mean_ad::{mean_ad_with_kernel, MeanAdInput, MeanAdParams};
16816 use crate::indicators::medprice::{medprice_with_kernel, MedpriceInput, MedpriceParams};
16817 use crate::indicators::mesa_stochastic_multi_length::{
16818 mesa_stochastic_multi_length_with_kernel, MesaStochasticMultiLengthInput,
16819 MesaStochasticMultiLengthParams,
16820 };
16821 use crate::indicators::mfi::{
16822 mfi_batch_with_kernel, mfi_with_kernel, MfiBatchRange, MfiInput, MfiParams,
16823 };
16824 use crate::indicators::monotonicity_index::{
16825 monotonicity_index_with_kernel, MonotonicityIndexInput, MonotonicityIndexMode,
16826 MonotonicityIndexParams,
16827 };
16828 use crate::indicators::moving_averages::ma::MaData;
16829 use crate::indicators::moving_averages::ma_batch::{
16830 ma_batch_with_kernel_and_typed_params, MaBatchParamKV, MaBatchParamValue,
16831 };
16832 use crate::indicators::multi_length_stochastic_average::{
16833 multi_length_stochastic_average_with_kernel, MultiLengthStochasticAverageInput,
16834 MultiLengthStochasticAverageParams,
16835 };
16836 use crate::indicators::natr::{natr_with_kernel, NatrInput, NatrParams};
16837 use crate::indicators::neighboring_trailing_stop::{
16838 neighboring_trailing_stop_with_kernel, NeighboringTrailingStopInput,
16839 NeighboringTrailingStopParams,
16840 };
16841 use crate::indicators::percentile_nearest_rank::{
16842 percentile_nearest_rank_with_kernel, PercentileNearestRankInput,
16843 PercentileNearestRankParams,
16844 };
16845 use crate::indicators::ppo::{ppo_with_kernel, PpoInput, PpoParams};
16846 use crate::indicators::price_moving_average_ratio_percentile::{
16847 price_moving_average_ratio_percentile_with_kernel, PriceMovingAverageRatioPercentileInput,
16848 PriceMovingAverageRatioPercentileLineMode, PriceMovingAverageRatioPercentileMaType,
16849 PriceMovingAverageRatioPercentileParams,
16850 };
16851 use crate::indicators::premier_rsi_oscillator::{
16852 premier_rsi_oscillator_with_kernel, PremierRsiOscillatorInput, PremierRsiOscillatorParams,
16853 };
16854 use crate::indicators::pvi::{pvi_with_kernel, PviInput, PviParams};
16855 use crate::indicators::random_walk_index::{
16856 random_walk_index_with_kernel, RandomWalkIndexInput, RandomWalkIndexParams,
16857 };
16858 use crate::indicators::registry::{list_indicators, IndicatorParamKind};
16859 use crate::indicators::spearman_correlation::{
16860 spearman_correlation_with_kernel, SpearmanCorrelationInput, SpearmanCorrelationParams,
16861 };
16862 use crate::indicators::squeeze_index::{
16863 squeeze_index_with_kernel, SqueezeIndexInput, SqueezeIndexParams,
16864 };
16865 use crate::indicators::stochastic_distance::{
16866 stochastic_distance_with_kernel, StochasticDistanceInput, StochasticDistanceParams,
16867 };
16868 use crate::indicators::trix::{
16869 trix_batch_with_kernel, trix_with_kernel, TrixBatchRange, TrixInput, TrixParams,
16870 };
16871 use crate::indicators::trend_trigger_factor::{
16872 trend_trigger_factor_with_kernel, TrendTriggerFactorInput, TrendTriggerFactorParams,
16873 };
16874 use crate::indicators::ttm_trend::{ttm_trend_with_kernel, TtmTrendInput, TtmTrendParams};
16875 use crate::indicators::velocity_acceleration_indicator::{
16876 velocity_acceleration_indicator_with_kernel, VelocityAccelerationIndicatorInput,
16877 VelocityAccelerationIndicatorParams,
16878 };
16879 use crate::indicators::velocity_acceleration_convergence_divergence_indicator::{
16880 velocity_acceleration_convergence_divergence_indicator_with_kernel,
16881 VelocityAccelerationConvergenceDivergenceIndicatorInput,
16882 VelocityAccelerationConvergenceDivergenceIndicatorParams,
16883 };
16884 use crate::indicators::volatility_quality_index::{
16885 volatility_quality_index_with_kernel, VolatilityQualityIndexInput,
16886 VolatilityQualityIndexParams,
16887 };
16888 use crate::indicators::volatility_ratio_adaptive_rsx::{
16889 volatility_ratio_adaptive_rsx_with_kernel, VolatilityRatioAdaptiveRsxInput,
16890 VolatilityRatioAdaptiveRsxParams,
16891 };
16892 use crate::indicators::volume_energy_reservoirs::{
16893 volume_energy_reservoirs_with_kernel, VolumeEnergyReservoirsInput,
16894 VolumeEnergyReservoirsParams,
16895 };
16896 use crate::indicators::volume_zone_oscillator::{
16897 volume_zone_oscillator_with_kernel, VolumeZoneOscillatorInput, VolumeZoneOscillatorParams,
16898 };
16899 use crate::indicators::vpci::{vpci_with_kernel, VpciInput, VpciParams};
16900 use crate::indicators::vwap_deviation_oscillator::{
16901 vwap_deviation_oscillator_with_kernel, VwapDeviationMode, VwapDeviationOscillatorInput,
16902 VwapDeviationOscillatorParams, VwapDeviationSessionMode,
16903 };
16904 use crate::indicators::vwap_zscore_with_signals::{
16905 vwap_zscore_with_signals_with_kernel, VwapZscoreWithSignalsInput,
16906 VwapZscoreWithSignalsParams,
16907 };
16908 use crate::indicators::yang_zhang_volatility::{
16909 yang_zhang_volatility_with_kernel, YangZhangVolatilityInput, YangZhangVolatilityParams,
16910 };
16911 use crate::indicators::zscore::{zscore_with_kernel, ZscoreInput, ZscoreParams};
16912 use crate::utilities::data_loader::Candles;
16913 use crate::utilities::enums::Kernel;
16914 use std::time::Instant;
16915
16916 fn sample_series() -> Vec<f64> {
16917 (1..=64).map(|v| v as f64).collect()
16918 }
16919
16920 fn sample_ohlc() -> (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>) {
16921 let open: Vec<f64> = (0..128).map(|i| 100.0 + (i as f64 * 0.1)).collect();
16922 let high: Vec<f64> = open.iter().map(|v| v + 1.25).collect();
16923 let low: Vec<f64> = open.iter().map(|v| v - 1.1).collect();
16924 let close: Vec<f64> = open.iter().map(|v| v + 0.3).collect();
16925 (open, high, low, close)
16926 }
16927
16928 fn sample_candles() -> crate::utilities::data_loader::Candles {
16929 let (open, high, low, close) = sample_ohlc();
16930 let volume: Vec<f64> = (0..close.len()).map(|i| 1000.0 + (i as f64)).collect();
16931 let timestamp: Vec<i64> = (0..close.len()).map(|i| i as i64).collect();
16932 crate::utilities::data_loader::Candles::new(timestamp, open, high, low, close, volume)
16933 }
16934
16935 fn assert_series_eq(actual: &[f64], expected: &[f64], tol: f64) {
16936 assert_eq!(actual.len(), expected.len());
16937 for i in 0..actual.len() {
16938 let a = actual[i];
16939 let b = expected[i];
16940 if a.is_nan() && b.is_nan() {
16941 continue;
16942 }
16943 assert!(
16944 (a - b).abs() <= tol,
16945 "mismatch at index {i}: actual={a}, expected={b}, tol={tol}"
16946 );
16947 }
16948 }
16949
16950 #[test]
16951 fn unknown_indicator_is_rejected() {
16952 let data = sample_series();
16953 let req = IndicatorBatchRequest {
16954 indicator_id: "not_real",
16955 output_id: None,
16956 data: IndicatorDataRef::Slice { values: &data },
16957 combos: &[],
16958 kernel: Kernel::Auto,
16959 };
16960 let err = compute_cpu_batch(req).unwrap_err();
16961 assert!(matches!(
16962 err,
16963 IndicatorDispatchError::UnknownIndicator { .. }
16964 ));
16965 }
16966
16967 #[test]
16968 fn bucket_b_ma_indicator_is_supported() {
16969 let data = sample_series();
16970 let combos = [IndicatorParamSet { params: &[] }];
16971 let req = IndicatorBatchRequest {
16972 indicator_id: "mama",
16973 output_id: Some("mama"),
16974 data: IndicatorDataRef::Slice { values: &data },
16975 combos: &combos,
16976 kernel: Kernel::Auto,
16977 };
16978 let out = compute_cpu_batch(req).unwrap();
16979 assert_eq!(out.rows, 1);
16980 assert_eq!(out.cols, data.len());
16981 assert!(out.values_f64.is_some());
16982 }
16983
16984 #[test]
16985 fn strict_mode_rejects_convenience_mfi_ohlcv() {
16986 let (open, high, low, close) = sample_ohlc();
16987 let volume: Vec<f64> = (0..close.len()).map(|i| 1200.0 + (i as f64)).collect();
16988 let combo = [ParamKV {
16989 key: "period",
16990 value: ParamValue::Int(14),
16991 }];
16992 let combos = [IndicatorParamSet { params: &combo }];
16993 let req = IndicatorBatchRequest {
16994 indicator_id: "mfi",
16995 output_id: Some("value"),
16996 data: IndicatorDataRef::Ohlcv {
16997 open: &open,
16998 high: &high,
16999 low: &low,
17000 close: &close,
17001 volume: &volume,
17002 },
17003 combos: &combos,
17004 kernel: Kernel::Auto,
17005 };
17006 let err = compute_cpu_batch_strict(req).unwrap_err();
17007 match err {
17008 IndicatorDispatchError::MissingRequiredInput { indicator, input } => {
17009 assert_eq!(indicator, "mfi");
17010 assert_eq!(input, IndicatorInputKind::CloseVolume);
17011 }
17012 other => panic!("expected MissingRequiredInput, got {other:?}"),
17013 }
17014 }
17015
17016 #[test]
17017 fn strict_mode_accepts_precomputed_mfi_close_volume() {
17018 let (_open, high, low, close) = sample_ohlc();
17019 let volume: Vec<f64> = (0..close.len())
17020 .map(|i| 1000.0 + (i as f64 * 2.0))
17021 .collect();
17022 let typical: Vec<f64> = high
17023 .iter()
17024 .zip(&low)
17025 .zip(&close)
17026 .map(|((h, l), c)| (h + l + c) / 3.0)
17027 .collect();
17028 let combo = [ParamKV {
17029 key: "period",
17030 value: ParamValue::Int(14),
17031 }];
17032 let combos = [IndicatorParamSet { params: &combo }];
17033 let req = IndicatorBatchRequest {
17034 indicator_id: "mfi",
17035 output_id: Some("value"),
17036 data: IndicatorDataRef::CloseVolume {
17037 close: &typical,
17038 volume: &volume,
17039 },
17040 combos: &combos,
17041 kernel: Kernel::Auto,
17042 };
17043 let strict = compute_cpu_batch_strict(req).unwrap();
17044 let input = MfiInput::from_slices(&typical, &volume, MfiParams { period: Some(14) });
17045 let direct = mfi_with_kernel(&input, Kernel::Auto.to_non_batch())
17046 .unwrap()
17047 .values;
17048 assert_series_eq(strict.values_f64.as_ref().unwrap(), &direct, 1e-12);
17049 }
17050
17051 #[test]
17052 fn strict_mode_rejects_ao_high_low_and_requires_slice() {
17053 let (_open, high, low, _close) = sample_ohlc();
17054 let combo = [
17055 ParamKV {
17056 key: "short_period",
17057 value: ParamValue::Int(5),
17058 },
17059 ParamKV {
17060 key: "long_period",
17061 value: ParamValue::Int(34),
17062 },
17063 ];
17064 let combos = [IndicatorParamSet { params: &combo }];
17065 let req = IndicatorBatchRequest {
17066 indicator_id: "ao",
17067 output_id: Some("value"),
17068 data: IndicatorDataRef::HighLow {
17069 high: &high,
17070 low: &low,
17071 },
17072 combos: &combos,
17073 kernel: Kernel::Auto,
17074 };
17075 let err = compute_cpu_batch_strict(req).unwrap_err();
17076 match err {
17077 IndicatorDispatchError::MissingRequiredInput { indicator, input } => {
17078 assert_eq!(indicator, "ao");
17079 assert_eq!(input, IndicatorInputKind::Slice);
17080 }
17081 other => panic!("expected MissingRequiredInput, got {other:?}"),
17082 }
17083 }
17084
17085 #[test]
17086 fn strict_mode_rejects_ttm_trend_ohlc_and_requires_candles() {
17087 let (open, high, low, close) = sample_ohlc();
17088 let combo = [ParamKV {
17089 key: "period",
17090 value: ParamValue::Int(5),
17091 }];
17092 let combos = [IndicatorParamSet { params: &combo }];
17093 let req = IndicatorBatchRequest {
17094 indicator_id: "ttm_trend",
17095 output_id: Some("value"),
17096 data: IndicatorDataRef::Ohlc {
17097 open: &open,
17098 high: &high,
17099 low: &low,
17100 close: &close,
17101 },
17102 combos: &combos,
17103 kernel: Kernel::Auto,
17104 };
17105 let err = compute_cpu_batch_strict(req).unwrap_err();
17106 match err {
17107 IndicatorDispatchError::MissingRequiredInput { indicator, input } => {
17108 assert_eq!(indicator, "ttm_trend");
17109 assert_eq!(input, IndicatorInputKind::Candles);
17110 }
17111 other => panic!("expected MissingRequiredInput, got {other:?}"),
17112 }
17113 }
17114
17115 #[test]
17116 fn strict_mode_accepts_ttm_trend_candles() {
17117 let candles = sample_candles();
17118 let combo = [ParamKV {
17119 key: "period",
17120 value: ParamValue::Int(5),
17121 }];
17122 let combos = [IndicatorParamSet { params: &combo }];
17123 let req = IndicatorBatchRequest {
17124 indicator_id: "ttm_trend",
17125 output_id: Some("value"),
17126 data: IndicatorDataRef::Candles {
17127 candles: &candles,
17128 source: Some("hl2"),
17129 },
17130 combos: &combos,
17131 kernel: Kernel::Auto,
17132 };
17133 let strict = compute_cpu_batch_strict(req).unwrap();
17134 let input = TtmTrendInput::from_slices(
17135 candles.hl2.as_slice(),
17136 candles.close.as_slice(),
17137 TtmTrendParams { period: Some(5) },
17138 );
17139 let direct = ttm_trend_with_kernel(&input, Kernel::Auto.to_non_batch())
17140 .unwrap()
17141 .values;
17142 let got = strict.values_bool.unwrap();
17143 assert_eq!(got, direct);
17144 }
17145
17146 #[test]
17147 fn rsi_cpu_batch_smoke() {
17148 let data = sample_series();
17149 let combo_1 = [ParamKV {
17150 key: "period",
17151 value: ParamValue::Int(7),
17152 }];
17153 let combo_2 = [ParamKV {
17154 key: "period",
17155 value: ParamValue::Int(14),
17156 }];
17157 let combos = [
17158 IndicatorParamSet { params: &combo_1 },
17159 IndicatorParamSet { params: &combo_2 },
17160 ];
17161 let req = IndicatorBatchRequest {
17162 indicator_id: "rsi",
17163 output_id: Some("value"),
17164 data: IndicatorDataRef::Slice { values: &data },
17165 combos: &combos,
17166 kernel: Kernel::Auto,
17167 };
17168 let out = compute_cpu_batch(req).unwrap();
17169 assert_eq!(out.output_id, "value");
17170 assert_eq!(out.rows, 2);
17171 assert_eq!(out.cols, data.len());
17172 assert_eq!(out.values_f64.as_ref().map(Vec::len), Some(2 * data.len()));
17173 }
17174
17175 #[test]
17176 fn ma_dispatch_regression_sma_matches_existing_ma_batch_api() {
17177 let data = sample_series();
17178 let combo = [ParamKV {
17179 key: "period",
17180 value: ParamValue::Int(14),
17181 }];
17182 let combos = [IndicatorParamSet { params: &combo }];
17183 let dispatch = compute_cpu_batch(IndicatorBatchRequest {
17184 indicator_id: "sma",
17185 output_id: Some("value"),
17186 data: IndicatorDataRef::Slice { values: &data },
17187 combos: &combos,
17188 kernel: Kernel::Auto,
17189 })
17190 .unwrap();
17191
17192 let direct = ma_batch_with_kernel_and_typed_params(
17193 "sma",
17194 MaData::Slice(&data),
17195 (14, 14, 0),
17196 Kernel::Auto,
17197 &[],
17198 )
17199 .unwrap();
17200 assert_eq!(dispatch.rows, direct.rows);
17201 assert_eq!(dispatch.cols, direct.cols);
17202 assert_series_eq(dispatch.values_f64.as_ref().unwrap(), &direct.values, 1e-12);
17203 }
17204
17205 #[test]
17206 fn ma_dispatch_sma_period_sweep_matches_direct_batch() {
17207 let data = sample_series();
17208 let combo_1 = [ParamKV {
17209 key: "period",
17210 value: ParamValue::Int(5),
17211 }];
17212 let combo_2 = [ParamKV {
17213 key: "period",
17214 value: ParamValue::Int(7),
17215 }];
17216 let combo_3 = [ParamKV {
17217 key: "period",
17218 value: ParamValue::Int(9),
17219 }];
17220 let combos = [
17221 IndicatorParamSet { params: &combo_1 },
17222 IndicatorParamSet { params: &combo_2 },
17223 IndicatorParamSet { params: &combo_3 },
17224 ];
17225 let dispatch = compute_cpu_batch(IndicatorBatchRequest {
17226 indicator_id: "sma",
17227 output_id: Some("value"),
17228 data: IndicatorDataRef::Slice { values: &data },
17229 combos: &combos,
17230 kernel: Kernel::Auto,
17231 })
17232 .unwrap();
17233
17234 let direct = ma_batch_with_kernel_and_typed_params(
17235 "sma",
17236 MaData::Slice(&data),
17237 (5, 9, 2),
17238 Kernel::Auto,
17239 &[],
17240 )
17241 .unwrap();
17242 assert_eq!(dispatch.rows, direct.rows);
17243 assert_eq!(dispatch.cols, direct.cols);
17244 assert_series_eq(dispatch.values_f64.as_ref().unwrap(), &direct.values, 1e-12);
17245 }
17246
17247 #[test]
17248 fn mfi_dispatch_period_sweep_matches_direct_batch() {
17249 let (_open, high, low, close) = sample_ohlc();
17250 let volume: Vec<f64> = (0..close.len())
17251 .map(|i| 1000.0 + (i as f64 * 2.0))
17252 .collect();
17253 let typical: Vec<f64> = high
17254 .iter()
17255 .zip(&low)
17256 .zip(&close)
17257 .map(|((h, l), c)| (h + l + c) / 3.0)
17258 .collect();
17259 let combo_1 = [ParamKV {
17260 key: "period",
17261 value: ParamValue::Int(5),
17262 }];
17263 let combo_2 = [ParamKV {
17264 key: "period",
17265 value: ParamValue::Int(7),
17266 }];
17267 let combo_3 = [ParamKV {
17268 key: "period",
17269 value: ParamValue::Int(9),
17270 }];
17271 let combos = [
17272 IndicatorParamSet { params: &combo_1 },
17273 IndicatorParamSet { params: &combo_2 },
17274 IndicatorParamSet { params: &combo_3 },
17275 ];
17276 let dispatch = compute_cpu_batch(IndicatorBatchRequest {
17277 indicator_id: "mfi",
17278 output_id: Some("value"),
17279 data: IndicatorDataRef::CloseVolume {
17280 close: &typical,
17281 volume: &volume,
17282 },
17283 combos: &combos,
17284 kernel: Kernel::Auto,
17285 })
17286 .unwrap();
17287 let direct = mfi_batch_with_kernel(
17288 &typical,
17289 &volume,
17290 &MfiBatchRange { period: (5, 9, 2) },
17291 Kernel::Auto,
17292 )
17293 .unwrap();
17294 assert_eq!(dispatch.rows, direct.rows);
17295 assert_eq!(dispatch.cols, direct.cols);
17296 assert_series_eq(dispatch.values_f64.as_ref().unwrap(), &direct.values, 1e-12);
17297 }
17298
17299 #[test]
17300 fn dx_dispatch_period_sweep_keeps_requested_row_order() {
17301 let (open, high, low, close) = sample_ohlc();
17302 let combo_1 = [ParamKV {
17303 key: "period",
17304 value: ParamValue::Int(9),
17305 }];
17306 let combo_2 = [ParamKV {
17307 key: "period",
17308 value: ParamValue::Int(7),
17309 }];
17310 let combo_3 = [ParamKV {
17311 key: "period",
17312 value: ParamValue::Int(5),
17313 }];
17314 let combos = [
17315 IndicatorParamSet { params: &combo_1 },
17316 IndicatorParamSet { params: &combo_2 },
17317 IndicatorParamSet { params: &combo_3 },
17318 ];
17319 let dispatch = compute_cpu_batch(IndicatorBatchRequest {
17320 indicator_id: "dx",
17321 output_id: Some("value"),
17322 data: IndicatorDataRef::Ohlc {
17323 open: &open,
17324 high: &high,
17325 low: &low,
17326 close: &close,
17327 },
17328 combos: &combos,
17329 kernel: Kernel::Auto,
17330 })
17331 .unwrap();
17332 let direct = dx_batch_with_kernel(
17333 &high,
17334 &low,
17335 &close,
17336 &DxBatchRange { period: (9, 5, 2) },
17337 Kernel::Auto,
17338 )
17339 .unwrap();
17340 let direct_periods: Vec<usize> = direct
17341 .combos
17342 .iter()
17343 .map(|combo| combo.period.unwrap_or(14))
17344 .collect();
17345 let period_to_row: std::collections::HashMap<usize, usize> = direct_periods
17346 .iter()
17347 .copied()
17348 .enumerate()
17349 .map(|(row, period)| (period, row))
17350 .collect();
17351 let requested = [9usize, 7usize, 5usize];
17352 let mut expected = Vec::with_capacity(requested.len() * direct.cols);
17353 for period in requested {
17354 let row = period_to_row[&period];
17355 let start = row * direct.cols;
17356 let end = start + direct.cols;
17357 expected.extend_from_slice(&direct.values[start..end]);
17358 }
17359 assert_eq!(dispatch.rows, requested.len());
17360 assert_eq!(dispatch.cols, direct.cols);
17361 assert_series_eq(dispatch.values_f64.as_ref().unwrap(), &expected, 1e-12);
17362 }
17363
17364 #[test]
17365 fn ma_dispatch_regression_alma_typed_params_match_existing_ma_batch_api() {
17366 let data = sample_series();
17367 let combo = [
17368 ParamKV {
17369 key: "period",
17370 value: ParamValue::Int(14),
17371 },
17372 ParamKV {
17373 key: "offset",
17374 value: ParamValue::Float(0.87),
17375 },
17376 ParamKV {
17377 key: "sigma",
17378 value: ParamValue::Float(5.5),
17379 },
17380 ];
17381 let combos = [IndicatorParamSet { params: &combo }];
17382 let dispatch = compute_cpu_batch(IndicatorBatchRequest {
17383 indicator_id: "alma",
17384 output_id: Some("value"),
17385 data: IndicatorDataRef::Slice { values: &data },
17386 combos: &combos,
17387 kernel: Kernel::Auto,
17388 })
17389 .unwrap();
17390
17391 let typed = [
17392 MaBatchParamKV {
17393 key: "offset",
17394 value: MaBatchParamValue::Float(0.87),
17395 },
17396 MaBatchParamKV {
17397 key: "sigma",
17398 value: MaBatchParamValue::Float(5.5),
17399 },
17400 ];
17401 let direct = ma_batch_with_kernel_and_typed_params(
17402 "alma",
17403 MaData::Slice(&data),
17404 (14, 14, 0),
17405 Kernel::Auto,
17406 &typed,
17407 )
17408 .unwrap();
17409 assert_eq!(dispatch.rows, direct.rows);
17410 assert_eq!(dispatch.cols, direct.cols);
17411 assert_series_eq(dispatch.values_f64.as_ref().unwrap(), &direct.values, 1e-12);
17412 }
17413
17414 #[test]
17415 fn macd_signal_output_matches_direct() {
17416 let data = sample_series();
17417 let combo_1 = [
17418 ParamKV {
17419 key: "fast_period",
17420 value: ParamValue::Int(8),
17421 },
17422 ParamKV {
17423 key: "slow_period",
17424 value: ParamValue::Int(21),
17425 },
17426 ParamKV {
17427 key: "signal_period",
17428 value: ParamValue::Int(5),
17429 },
17430 ];
17431 let combo_2 = [
17432 ParamKV {
17433 key: "fast_period",
17434 value: ParamValue::Int(12),
17435 },
17436 ParamKV {
17437 key: "slow_period",
17438 value: ParamValue::Int(26),
17439 },
17440 ParamKV {
17441 key: "signal_period",
17442 value: ParamValue::Int(9),
17443 },
17444 ];
17445 let combos = [
17446 IndicatorParamSet { params: &combo_1 },
17447 IndicatorParamSet { params: &combo_2 },
17448 ];
17449 let req = IndicatorBatchRequest {
17450 indicator_id: "macd",
17451 output_id: Some("signal"),
17452 data: IndicatorDataRef::Slice { values: &data },
17453 combos: &combos,
17454 kernel: Kernel::Auto,
17455 };
17456 let out = compute_cpu_batch(req).unwrap();
17457 let matrix = out.values_f64.unwrap();
17458 for (row, combo) in combos.iter().enumerate() {
17459 let fast = match combo.params[0].value {
17460 ParamValue::Int(v) => v as usize,
17461 _ => unreachable!(),
17462 };
17463 let slow = match combo.params[1].value {
17464 ParamValue::Int(v) => v as usize,
17465 _ => unreachable!(),
17466 };
17467 let signal = match combo.params[2].value {
17468 ParamValue::Int(v) => v as usize,
17469 _ => unreachable!(),
17470 };
17471 let input = MacdInput::from_slice(
17472 &data,
17473 MacdParams {
17474 fast_period: Some(fast),
17475 slow_period: Some(slow),
17476 signal_period: Some(signal),
17477 ma_type: Some("ema".to_string()),
17478 },
17479 );
17480 let direct = macd_with_kernel(&input, Kernel::Auto.to_non_batch())
17481 .unwrap()
17482 .signal;
17483 let start = row * out.cols;
17484 let end = start + out.cols;
17485 assert_series_eq(&matrix[start..end], direct.as_slice(), 1e-12);
17486 }
17487 }
17488
17489 #[test]
17490 fn adx_output_matches_direct() {
17491 let (open, high, low, close) = sample_ohlc();
17492 let combo = [ParamKV {
17493 key: "period",
17494 value: ParamValue::Int(14),
17495 }];
17496 let combos = [IndicatorParamSet { params: &combo }];
17497 let req = IndicatorBatchRequest {
17498 indicator_id: "adx",
17499 output_id: Some("value"),
17500 data: IndicatorDataRef::Ohlc {
17501 open: &open,
17502 high: &high,
17503 low: &low,
17504 close: &close,
17505 },
17506 combos: &combos,
17507 kernel: Kernel::Auto,
17508 };
17509 let out = compute_cpu_batch(req).unwrap();
17510 let matrix = out.values_f64.unwrap();
17511 let input = AdxInput::from_slices(&high, &low, &close, AdxParams { period: Some(14) });
17512 let direct = adx_with_kernel(&input, Kernel::Auto.to_non_batch())
17513 .unwrap()
17514 .values;
17515 assert_series_eq(&matrix, &direct, 1e-12);
17516 }
17517
17518 #[test]
17519 fn garman_klass_output_matches_direct() {
17520 let (open, high, low, close) = sample_ohlc();
17521 let combo = [ParamKV {
17522 key: "lookback",
17523 value: ParamValue::Int(17),
17524 }];
17525 let combos = [IndicatorParamSet { params: &combo }];
17526 let req = IndicatorBatchRequest {
17527 indicator_id: "garman_klass_volatility",
17528 output_id: Some("value"),
17529 data: IndicatorDataRef::Ohlc {
17530 open: &open,
17531 high: &high,
17532 low: &low,
17533 close: &close,
17534 },
17535 combos: &combos,
17536 kernel: Kernel::Auto,
17537 };
17538 let out = compute_cpu_batch(req).unwrap();
17539 let got = out.values_f64.unwrap();
17540 let input = GarmanKlassVolatilityInput::from_slices(
17541 &open,
17542 &high,
17543 &low,
17544 &close,
17545 GarmanKlassVolatilityParams { lookback: Some(17) },
17546 );
17547 let direct = garman_klass_volatility_with_kernel(&input, Kernel::Auto.to_non_batch())
17548 .unwrap()
17549 .values;
17550 assert_series_eq(&got, &direct, 1e-12);
17551 }
17552
17553 #[test]
17554 fn cmo_output_matches_direct() {
17555 let data = sample_series();
17556 let combo = [ParamKV {
17557 key: "period",
17558 value: ParamValue::Int(14),
17559 }];
17560 let combos = [IndicatorParamSet { params: &combo }];
17561 let req = IndicatorBatchRequest {
17562 indicator_id: "cmo",
17563 output_id: Some("value"),
17564 data: IndicatorDataRef::Slice { values: &data },
17565 combos: &combos,
17566 kernel: Kernel::Auto,
17567 };
17568 let out = compute_cpu_batch(req).unwrap();
17569 let input = CmoInput::from_slice(&data, CmoParams { period: Some(14) });
17570 let direct = cmo_with_kernel(&input, Kernel::Auto.to_non_batch())
17571 .unwrap()
17572 .values;
17573 let got = out.values_f64.unwrap();
17574 assert_series_eq(&got, &direct, 1e-12);
17575 }
17576
17577 #[test]
17578 fn ppo_output_matches_direct() {
17579 let data = sample_series();
17580 let combo = [
17581 ParamKV {
17582 key: "fast_period",
17583 value: ParamValue::Int(12),
17584 },
17585 ParamKV {
17586 key: "slow_period",
17587 value: ParamValue::Int(26),
17588 },
17589 ParamKV {
17590 key: "ma_type",
17591 value: ParamValue::EnumString("sma"),
17592 },
17593 ];
17594 let combos = [IndicatorParamSet { params: &combo }];
17595 let req = IndicatorBatchRequest {
17596 indicator_id: "ppo",
17597 output_id: Some("value"),
17598 data: IndicatorDataRef::Slice { values: &data },
17599 combos: &combos,
17600 kernel: Kernel::Auto,
17601 };
17602 let out = compute_cpu_batch(req).unwrap();
17603 let input = PpoInput::from_slice(
17604 &data,
17605 PpoParams {
17606 fast_period: Some(12),
17607 slow_period: Some(26),
17608 ma_type: Some("sma".to_string()),
17609 },
17610 );
17611 let direct = ppo_with_kernel(&input, Kernel::Auto.to_non_batch())
17612 .unwrap()
17613 .values;
17614 let got = out.values_f64.unwrap();
17615 assert_series_eq(&got, &direct, 1e-12);
17616 }
17617
17618 #[test]
17619 fn apo_output_matches_direct() {
17620 let data = sample_series();
17621 let combo = [
17622 ParamKV {
17623 key: "short_period",
17624 value: ParamValue::Int(10),
17625 },
17626 ParamKV {
17627 key: "long_period",
17628 value: ParamValue::Int(20),
17629 },
17630 ];
17631 let combos = [IndicatorParamSet { params: &combo }];
17632 let req = IndicatorBatchRequest {
17633 indicator_id: "apo",
17634 output_id: Some("value"),
17635 data: IndicatorDataRef::Slice { values: &data },
17636 combos: &combos,
17637 kernel: Kernel::Auto,
17638 };
17639 let out = compute_cpu_batch(req).unwrap();
17640 let input = ApoInput::from_slice(
17641 &data,
17642 ApoParams {
17643 short_period: Some(10),
17644 long_period: Some(20),
17645 },
17646 );
17647 let direct = apo_with_kernel(&input, Kernel::Auto.to_non_batch())
17648 .unwrap()
17649 .values;
17650 let got = out.values_f64.unwrap();
17651 assert_series_eq(&got, &direct, 1e-12);
17652 }
17653
17654 #[test]
17655 fn natr_output_matches_direct() {
17656 let (open, high, low, close) = sample_ohlc();
17657 let combo = [ParamKV {
17658 key: "period",
17659 value: ParamValue::Int(14),
17660 }];
17661 let combos = [IndicatorParamSet { params: &combo }];
17662 let req = IndicatorBatchRequest {
17663 indicator_id: "natr",
17664 output_id: Some("value"),
17665 data: IndicatorDataRef::Ohlc {
17666 open: &open,
17667 high: &high,
17668 low: &low,
17669 close: &close,
17670 },
17671 combos: &combos,
17672 kernel: Kernel::Auto,
17673 };
17674 let out = compute_cpu_batch(req).unwrap();
17675 let input = NatrInput::from_slices(&high, &low, &close, NatrParams { period: Some(14) });
17676 let direct = natr_with_kernel(&input, Kernel::Auto.to_non_batch())
17677 .unwrap()
17678 .values;
17679 let got = out.values_f64.unwrap();
17680 assert_series_eq(&got, &direct, 1e-12);
17681 }
17682
17683 #[test]
17684 fn ad_output_matches_direct() {
17685 let (open, high, low, close) = sample_ohlc();
17686 let volume: Vec<f64> = (0..close.len())
17687 .map(|i| 1000.0 + (i as f64 * 3.0))
17688 .collect();
17689 let combos = [IndicatorParamSet { params: &[] }];
17690 let req = IndicatorBatchRequest {
17691 indicator_id: "ad",
17692 output_id: Some("value"),
17693 data: IndicatorDataRef::Ohlcv {
17694 open: &open,
17695 high: &high,
17696 low: &low,
17697 close: &close,
17698 volume: &volume,
17699 },
17700 combos: &combos,
17701 kernel: Kernel::Auto,
17702 };
17703 let out = compute_cpu_batch(req).unwrap();
17704 let input = AdInput::from_slices(&high, &low, &close, &volume, AdParams::default());
17705 let direct = ad_with_kernel(&input, Kernel::Auto.to_non_batch())
17706 .unwrap()
17707 .values;
17708 let got = out.values_f64.unwrap();
17709 assert_series_eq(&got, &direct, 1e-12);
17710 }
17711
17712 #[test]
17713 fn ao_output_matches_direct() {
17714 let (open, high, low, close) = sample_ohlc();
17715 let combo = [
17716 ParamKV {
17717 key: "short_period",
17718 value: ParamValue::Int(5),
17719 },
17720 ParamKV {
17721 key: "long_period",
17722 value: ParamValue::Int(34),
17723 },
17724 ];
17725 let combos = [IndicatorParamSet { params: &combo }];
17726 let req = IndicatorBatchRequest {
17727 indicator_id: "ao",
17728 output_id: Some("value"),
17729 data: IndicatorDataRef::Ohlc {
17730 open: &open,
17731 high: &high,
17732 low: &low,
17733 close: &close,
17734 },
17735 combos: &combos,
17736 kernel: Kernel::Auto,
17737 };
17738 let out = compute_cpu_batch(req).unwrap();
17739 let source: Vec<f64> = high.iter().zip(&low).map(|(h, l)| 0.5 * (h + l)).collect();
17740 let input = AoInput::from_slice(
17741 &source,
17742 AoParams {
17743 short_period: Some(5),
17744 long_period: Some(34),
17745 },
17746 );
17747 let direct = ao_with_kernel(&input, Kernel::Auto.to_non_batch())
17748 .unwrap()
17749 .values;
17750 let got = out.values_f64.unwrap();
17751 assert_series_eq(&got, &direct, 1e-12);
17752 }
17753
17754 #[test]
17755 fn pvi_output_matches_direct() {
17756 let data = sample_series();
17757 let volume: Vec<f64> = (0..data.len()).map(|i| 900.0 + (i as f64 * 5.0)).collect();
17758 let combo = [ParamKV {
17759 key: "initial_value",
17760 value: ParamValue::Float(1000.0),
17761 }];
17762 let combos = [IndicatorParamSet { params: &combo }];
17763 let req = IndicatorBatchRequest {
17764 indicator_id: "pvi",
17765 output_id: Some("value"),
17766 data: IndicatorDataRef::CloseVolume {
17767 close: &data,
17768 volume: &volume,
17769 },
17770 combos: &combos,
17771 kernel: Kernel::Auto,
17772 };
17773 let out = compute_cpu_batch(req).unwrap();
17774 let input = PviInput::from_slices(
17775 &data,
17776 &volume,
17777 PviParams {
17778 initial_value: Some(1000.0),
17779 },
17780 );
17781 let direct = pvi_with_kernel(&input, Kernel::Auto.to_non_batch())
17782 .unwrap()
17783 .values;
17784 let got = out.values_f64.unwrap();
17785 assert_series_eq(&got, &direct, 1e-12);
17786 }
17787
17788 #[test]
17789 fn efi_output_matches_direct() {
17790 let data = sample_series();
17791 let volume: Vec<f64> = (0..data.len()).map(|i| 1000.0 + (i as f64 * 4.0)).collect();
17792 let combo = [ParamKV {
17793 key: "period",
17794 value: ParamValue::Int(13),
17795 }];
17796 let combos = [IndicatorParamSet { params: &combo }];
17797 let req = IndicatorBatchRequest {
17798 indicator_id: "efi",
17799 output_id: Some("value"),
17800 data: IndicatorDataRef::CloseVolume {
17801 close: &data,
17802 volume: &volume,
17803 },
17804 combos: &combos,
17805 kernel: Kernel::Auto,
17806 };
17807 let out = compute_cpu_batch(req).unwrap();
17808 let input = EfiInput::from_slices(&data, &volume, EfiParams { period: Some(13) });
17809 let direct = efi_with_kernel(&input, Kernel::Auto.to_non_batch())
17810 .unwrap()
17811 .values;
17812 let got = out.values_f64.unwrap();
17813 assert_series_eq(&got, &direct, 1e-12);
17814 }
17815
17816 #[test]
17817 fn mfi_output_matches_direct() {
17818 let (open, high, low, close) = sample_ohlc();
17819 let volume: Vec<f64> = (0..close.len()).map(|i| 900.0 + (i as f64 * 6.0)).collect();
17820 let combo = [ParamKV {
17821 key: "period",
17822 value: ParamValue::Int(14),
17823 }];
17824 let combos = [IndicatorParamSet { params: &combo }];
17825 let req = IndicatorBatchRequest {
17826 indicator_id: "mfi",
17827 output_id: Some("value"),
17828 data: IndicatorDataRef::Ohlcv {
17829 open: &open,
17830 high: &high,
17831 low: &low,
17832 close: &close,
17833 volume: &volume,
17834 },
17835 combos: &combos,
17836 kernel: Kernel::Auto,
17837 };
17838 let out = compute_cpu_batch(req).unwrap();
17839 let typical_price: Vec<f64> = high
17840 .iter()
17841 .zip(&low)
17842 .zip(&close)
17843 .map(|((h, l), c)| (h + l + c) / 3.0)
17844 .collect();
17845 let input = MfiInput::from_slices(&typical_price, &volume, MfiParams { period: Some(14) });
17846 let direct = mfi_with_kernel(&input, Kernel::Auto.to_non_batch())
17847 .unwrap()
17848 .values;
17849 let got = out.values_f64.unwrap();
17850 assert_series_eq(&got, &direct, 1e-12);
17851 }
17852
17853 #[test]
17854 fn mfi_non_sweep_fallback_rows_match_direct() {
17855 let (open, high, low, close) = sample_ohlc();
17856 let volume: Vec<f64> = (0..close.len()).map(|i| 950.0 + (i as f64 * 5.0)).collect();
17857 let combo_1 = [ParamKV {
17858 key: "period",
17859 value: ParamValue::Int(5),
17860 }];
17861 let combo_2 = [ParamKV {
17862 key: "period",
17863 value: ParamValue::Int(9),
17864 }];
17865 let combo_3 = [ParamKV {
17866 key: "period",
17867 value: ParamValue::Int(8),
17868 }];
17869 let combos = [
17870 IndicatorParamSet { params: &combo_1 },
17871 IndicatorParamSet { params: &combo_2 },
17872 IndicatorParamSet { params: &combo_3 },
17873 ];
17874 let req = IndicatorBatchRequest {
17875 indicator_id: "mfi",
17876 output_id: Some("value"),
17877 data: IndicatorDataRef::Ohlcv {
17878 open: &open,
17879 high: &high,
17880 low: &low,
17881 close: &close,
17882 volume: &volume,
17883 },
17884 combos: &combos,
17885 kernel: Kernel::Auto,
17886 };
17887 let out = compute_cpu_batch(req).unwrap();
17888 let matrix = out.values_f64.unwrap();
17889 let typical_price: Vec<f64> = high
17890 .iter()
17891 .zip(&low)
17892 .zip(&close)
17893 .map(|((h, l), c)| (h + l + c) / 3.0)
17894 .collect();
17895 for (row, period) in [5usize, 9usize, 8usize].iter().enumerate() {
17896 let input = MfiInput::from_slices(
17897 &typical_price,
17898 &volume,
17899 MfiParams {
17900 period: Some(*period),
17901 },
17902 );
17903 let direct = mfi_with_kernel(&input, Kernel::Auto.to_non_batch())
17904 .unwrap()
17905 .values;
17906 let start = row * close.len();
17907 let end = start + close.len();
17908 assert_series_eq(&matrix[start..end], &direct, 1e-12);
17909 }
17910 }
17911
17912 #[test]
17913 fn kvo_output_matches_direct() {
17914 let (open, high, low, close) = sample_ohlc();
17915 let volume: Vec<f64> = (0..close.len())
17916 .map(|i| 1200.0 + (i as f64 * 5.0))
17917 .collect();
17918 let combo = [
17919 ParamKV {
17920 key: "short_period",
17921 value: ParamValue::Int(2),
17922 },
17923 ParamKV {
17924 key: "long_period",
17925 value: ParamValue::Int(5),
17926 },
17927 ];
17928 let combos = [IndicatorParamSet { params: &combo }];
17929 let req = IndicatorBatchRequest {
17930 indicator_id: "kvo",
17931 output_id: Some("value"),
17932 data: IndicatorDataRef::Ohlcv {
17933 open: &open,
17934 high: &high,
17935 low: &low,
17936 close: &close,
17937 volume: &volume,
17938 },
17939 combos: &combos,
17940 kernel: Kernel::Auto,
17941 };
17942 let out = compute_cpu_batch(req).unwrap();
17943 let input = KvoInput::from_slices(
17944 &high,
17945 &low,
17946 &close,
17947 &volume,
17948 KvoParams {
17949 short_period: Some(2),
17950 long_period: Some(5),
17951 },
17952 );
17953 let direct = kvo_with_kernel(&input, Kernel::Auto.to_non_batch())
17954 .unwrap()
17955 .values;
17956 let got = out.values_f64.unwrap();
17957 assert_series_eq(&got, &direct, 1e-12);
17958 }
17959
17960 #[test]
17961 fn dx_output_matches_direct() {
17962 let (open, high, low, close) = sample_ohlc();
17963 let combo = [ParamKV {
17964 key: "period",
17965 value: ParamValue::Int(14),
17966 }];
17967 let combos = [IndicatorParamSet { params: &combo }];
17968 let req = IndicatorBatchRequest {
17969 indicator_id: "dx",
17970 output_id: Some("value"),
17971 data: IndicatorDataRef::Ohlc {
17972 open: &open,
17973 high: &high,
17974 low: &low,
17975 close: &close,
17976 },
17977 combos: &combos,
17978 kernel: Kernel::Auto,
17979 };
17980 let out = compute_cpu_batch(req).unwrap();
17981 let input = DxInput::from_hlc_slices(&high, &low, &close, DxParams { period: Some(14) });
17982 let direct = dx_with_kernel(&input, Kernel::Auto.to_non_batch())
17983 .unwrap()
17984 .values;
17985 let got = out.values_f64.unwrap();
17986 assert_series_eq(&got, &direct, 1e-12);
17987 }
17988
17989 #[test]
17990 fn dx_non_sweep_fallback_rows_match_direct() {
17991 let (open, high, low, close) = sample_ohlc();
17992 let combo_1 = [ParamKV {
17993 key: "period",
17994 value: ParamValue::Int(9),
17995 }];
17996 let combo_2 = [ParamKV {
17997 key: "period",
17998 value: ParamValue::Int(5),
17999 }];
18000 let combo_3 = [ParamKV {
18001 key: "period",
18002 value: ParamValue::Int(8),
18003 }];
18004 let combos = [
18005 IndicatorParamSet { params: &combo_1 },
18006 IndicatorParamSet { params: &combo_2 },
18007 IndicatorParamSet { params: &combo_3 },
18008 ];
18009 let req = IndicatorBatchRequest {
18010 indicator_id: "dx",
18011 output_id: Some("value"),
18012 data: IndicatorDataRef::Ohlc {
18013 open: &open,
18014 high: &high,
18015 low: &low,
18016 close: &close,
18017 },
18018 combos: &combos,
18019 kernel: Kernel::Auto,
18020 };
18021 let out = compute_cpu_batch(req).unwrap();
18022 let matrix = out.values_f64.unwrap();
18023 for (row, period) in [9usize, 5usize, 8usize].iter().enumerate() {
18024 let input = DxInput::from_hlc_slices(
18025 &high,
18026 &low,
18027 &close,
18028 DxParams {
18029 period: Some(*period),
18030 },
18031 );
18032 let direct = dx_with_kernel(&input, Kernel::Auto.to_non_batch())
18033 .unwrap()
18034 .values;
18035 let start = row * close.len();
18036 let end = start + close.len();
18037 assert_series_eq(&matrix[start..end], &direct, 1e-12);
18038 }
18039 }
18040
18041 #[test]
18042 fn trix_dispatch_period_sweep_keeps_requested_row_order() {
18043 let data = sample_series();
18044 let combo_1 = [ParamKV {
18045 key: "period",
18046 value: ParamValue::Int(9),
18047 }];
18048 let combo_2 = [ParamKV {
18049 key: "period",
18050 value: ParamValue::Int(7),
18051 }];
18052 let combo_3 = [ParamKV {
18053 key: "period",
18054 value: ParamValue::Int(5),
18055 }];
18056 let combos = [
18057 IndicatorParamSet { params: &combo_1 },
18058 IndicatorParamSet { params: &combo_2 },
18059 IndicatorParamSet { params: &combo_3 },
18060 ];
18061 let dispatch = compute_cpu_batch(IndicatorBatchRequest {
18062 indicator_id: "trix",
18063 output_id: Some("value"),
18064 data: IndicatorDataRef::Slice { values: &data },
18065 combos: &combos,
18066 kernel: Kernel::Auto,
18067 })
18068 .unwrap();
18069
18070 let direct =
18071 trix_batch_with_kernel(&data, &TrixBatchRange { period: (9, 5, 2) }, Kernel::Auto)
18072 .unwrap();
18073 let direct_periods: Vec<usize> = direct
18074 .combos
18075 .iter()
18076 .map(|combo| combo.period.unwrap_or(18))
18077 .collect();
18078 let period_to_row: std::collections::HashMap<usize, usize> = direct_periods
18079 .iter()
18080 .copied()
18081 .enumerate()
18082 .map(|(row, period)| (period, row))
18083 .collect();
18084 let requested = [9usize, 7usize, 5usize];
18085 let mut expected = Vec::with_capacity(requested.len() * direct.cols);
18086 for period in requested {
18087 let row = period_to_row[&period];
18088 let start = row * direct.cols;
18089 let end = start + direct.cols;
18090 expected.extend_from_slice(&direct.values[start..end]);
18091 }
18092 assert_eq!(dispatch.rows, requested.len());
18093 assert_eq!(dispatch.cols, direct.cols);
18094 assert_series_eq(dispatch.values_f64.as_ref().unwrap(), &expected, 1e-12);
18095 }
18096
18097 #[test]
18098 fn trix_non_sweep_fallback_rows_match_direct() {
18099 let data = sample_series();
18100 let combo_1 = [ParamKV {
18101 key: "period",
18102 value: ParamValue::Int(9),
18103 }];
18104 let combo_2 = [ParamKV {
18105 key: "period",
18106 value: ParamValue::Int(5),
18107 }];
18108 let combo_3 = [ParamKV {
18109 key: "period",
18110 value: ParamValue::Int(8),
18111 }];
18112 let combos = [
18113 IndicatorParamSet { params: &combo_1 },
18114 IndicatorParamSet { params: &combo_2 },
18115 IndicatorParamSet { params: &combo_3 },
18116 ];
18117 let out = compute_cpu_batch(IndicatorBatchRequest {
18118 indicator_id: "trix",
18119 output_id: Some("value"),
18120 data: IndicatorDataRef::Slice { values: &data },
18121 combos: &combos,
18122 kernel: Kernel::Auto,
18123 })
18124 .unwrap();
18125 let matrix = out.values_f64.unwrap();
18126 for (row, period) in [9usize, 5usize, 8usize].iter().enumerate() {
18127 let input = TrixInput::from_slice(
18128 &data,
18129 TrixParams {
18130 period: Some(*period),
18131 },
18132 );
18133 let direct = trix_with_kernel(&input, Kernel::Auto.to_non_batch())
18134 .unwrap()
18135 .values;
18136 let start = row * data.len();
18137 let end = start + data.len();
18138 assert_series_eq(&matrix[start..end], &direct, 1e-12);
18139 }
18140 }
18141
18142 #[test]
18143 fn ift_rsi_output_matches_direct() {
18144 let data = sample_series();
18145 let combo = [
18146 ParamKV {
18147 key: "rsi_period",
18148 value: ParamValue::Int(6),
18149 },
18150 ParamKV {
18151 key: "wma_period",
18152 value: ParamValue::Int(10),
18153 },
18154 ];
18155 let combos = [IndicatorParamSet { params: &combo }];
18156 let req = IndicatorBatchRequest {
18157 indicator_id: "ift_rsi",
18158 output_id: Some("value"),
18159 data: IndicatorDataRef::Slice { values: &data },
18160 combos: &combos,
18161 kernel: Kernel::Auto,
18162 };
18163 let out = compute_cpu_batch(req).unwrap();
18164 let input = IftRsiInput::from_slice(
18165 &data,
18166 IftRsiParams {
18167 rsi_period: Some(6),
18168 wma_period: Some(10),
18169 },
18170 );
18171 let direct = ift_rsi_with_kernel(&input, Kernel::Auto.to_non_batch())
18172 .unwrap()
18173 .values;
18174 let got = out.values_f64.unwrap();
18175 assert_series_eq(&got, &direct, 1e-12);
18176 }
18177
18178 #[test]
18179 fn fosc_output_matches_direct() {
18180 let data = sample_series();
18181 let combo = [ParamKV {
18182 key: "period",
18183 value: ParamValue::Int(8),
18184 }];
18185 let combos = [IndicatorParamSet { params: &combo }];
18186 let req = IndicatorBatchRequest {
18187 indicator_id: "fosc",
18188 output_id: Some("value"),
18189 data: IndicatorDataRef::Slice { values: &data },
18190 combos: &combos,
18191 kernel: Kernel::Auto,
18192 };
18193 let out = compute_cpu_batch(req).unwrap();
18194 let input = FoscInput::from_slice(&data, FoscParams { period: Some(8) });
18195 let direct = fosc_with_kernel(&input, Kernel::Auto.to_non_batch())
18196 .unwrap()
18197 .values;
18198 let got = out.values_f64.unwrap();
18199 assert_series_eq(&got, &direct, 1e-12);
18200 }
18201
18202 #[test]
18203 fn linearreg_angle_output_matches_direct() {
18204 let data = sample_series();
18205 let combo = [ParamKV {
18206 key: "period",
18207 value: ParamValue::Int(14),
18208 }];
18209 let combos = [IndicatorParamSet { params: &combo }];
18210 let req = IndicatorBatchRequest {
18211 indicator_id: "linearreg_angle",
18212 output_id: Some("value"),
18213 data: IndicatorDataRef::Slice { values: &data },
18214 combos: &combos,
18215 kernel: Kernel::Auto,
18216 };
18217 let out = compute_cpu_batch(req).unwrap();
18218 let input =
18219 Linearreg_angleInput::from_slice(&data, Linearreg_angleParams { period: Some(14) });
18220 let direct = linearreg_angle_with_kernel(&input, Kernel::Auto.to_non_batch())
18221 .unwrap()
18222 .values;
18223 let got = out.values_f64.unwrap();
18224 assert_series_eq(&got, &direct, 1e-12);
18225 }
18226
18227 #[test]
18228 fn linearreg_intercept_output_matches_direct() {
18229 let data = sample_series();
18230 let combo = [ParamKV {
18231 key: "period",
18232 value: ParamValue::Int(14),
18233 }];
18234 let combos = [IndicatorParamSet { params: &combo }];
18235 let req = IndicatorBatchRequest {
18236 indicator_id: "linearreg_intercept",
18237 output_id: Some("value"),
18238 data: IndicatorDataRef::Slice { values: &data },
18239 combos: &combos,
18240 kernel: Kernel::Auto,
18241 };
18242 let out = compute_cpu_batch(req).unwrap();
18243 let input = LinearRegInterceptInput::from_slice(
18244 &data,
18245 LinearRegInterceptParams { period: Some(14) },
18246 );
18247 let direct = linearreg_intercept_with_kernel(&input, Kernel::Auto.to_non_batch())
18248 .unwrap()
18249 .values;
18250 let got = out.values_f64.unwrap();
18251 assert_series_eq(&got, &direct, 1e-12);
18252 }
18253
18254 #[test]
18255 fn cg_output_matches_direct() {
18256 let data = sample_series();
18257 let combo = [ParamKV {
18258 key: "period",
18259 value: ParamValue::Int(10),
18260 }];
18261 let combos = [IndicatorParamSet { params: &combo }];
18262 let req = IndicatorBatchRequest {
18263 indicator_id: "cg",
18264 output_id: Some("value"),
18265 data: IndicatorDataRef::Slice { values: &data },
18266 combos: &combos,
18267 kernel: Kernel::Auto,
18268 };
18269 let out = compute_cpu_batch(req).unwrap();
18270 let input = CgInput::from_slice(&data, CgParams { period: Some(10) });
18271 let direct = cg_with_kernel(&input, Kernel::Auto.to_non_batch())
18272 .unwrap()
18273 .values;
18274 let got = out.values_f64.unwrap();
18275 assert_series_eq(&got, &direct, 1e-12);
18276 }
18277
18278 #[test]
18279 fn linearreg_slope_output_matches_direct() {
18280 let data = sample_series();
18281 let combo = [ParamKV {
18282 key: "period",
18283 value: ParamValue::Int(14),
18284 }];
18285 let combos = [IndicatorParamSet { params: &combo }];
18286 let req = IndicatorBatchRequest {
18287 indicator_id: "linearreg_slope",
18288 output_id: Some("value"),
18289 data: IndicatorDataRef::Slice { values: &data },
18290 combos: &combos,
18291 kernel: Kernel::Auto,
18292 };
18293 let out = compute_cpu_batch(req).unwrap();
18294 let input =
18295 LinearRegSlopeInput::from_slice(&data, LinearRegSlopeParams { period: Some(14) });
18296 let direct = linearreg_slope_with_kernel(&input, Kernel::Auto.to_non_batch())
18297 .unwrap()
18298 .values;
18299 let got = out.values_f64.unwrap();
18300 assert_series_eq(&got, &direct, 1e-12);
18301 }
18302
18303 #[test]
18304 fn mean_ad_output_matches_direct() {
18305 let data = sample_series();
18306 let combo = [ParamKV {
18307 key: "period",
18308 value: ParamValue::Int(7),
18309 }];
18310 let combos = [IndicatorParamSet { params: &combo }];
18311 let req = IndicatorBatchRequest {
18312 indicator_id: "mean_ad",
18313 output_id: Some("value"),
18314 data: IndicatorDataRef::Slice { values: &data },
18315 combos: &combos,
18316 kernel: Kernel::Auto,
18317 };
18318 let out = compute_cpu_batch(req).unwrap();
18319 let input = MeanAdInput::from_slice(&data, MeanAdParams { period: Some(7) });
18320 let direct = mean_ad_with_kernel(&input, Kernel::Auto.to_non_batch())
18321 .unwrap()
18322 .values;
18323 let got = out.values_f64.unwrap();
18324 assert_series_eq(&got, &direct, 1e-12);
18325 }
18326
18327 #[test]
18328 fn deviation_output_matches_direct() {
18329 let data = sample_series();
18330 let combo = [
18331 ParamKV {
18332 key: "period",
18333 value: ParamValue::Int(9),
18334 },
18335 ParamKV {
18336 key: "devtype",
18337 value: ParamValue::Int(2),
18338 },
18339 ];
18340 let combos = [IndicatorParamSet { params: &combo }];
18341 let req = IndicatorBatchRequest {
18342 indicator_id: "deviation",
18343 output_id: Some("value"),
18344 data: IndicatorDataRef::Slice { values: &data },
18345 combos: &combos,
18346 kernel: Kernel::Auto,
18347 };
18348 let out = compute_cpu_batch(req).unwrap();
18349 let input = DeviationInput::from_slice(
18350 &data,
18351 DeviationParams {
18352 period: Some(9),
18353 devtype: Some(2),
18354 },
18355 );
18356 let direct = deviation_with_kernel(&input, Kernel::Auto.to_non_batch())
18357 .unwrap()
18358 .values;
18359 let got = out.values_f64.unwrap();
18360 assert_series_eq(&got, &direct, 1e-12);
18361 }
18362
18363 #[test]
18364 fn medprice_output_matches_direct() {
18365 let (_open, high, low, _close) = sample_ohlc();
18366 let combos = [IndicatorParamSet { params: &[] }];
18367 let req = IndicatorBatchRequest {
18368 indicator_id: "medprice",
18369 output_id: Some("value"),
18370 data: IndicatorDataRef::HighLow {
18371 high: &high,
18372 low: &low,
18373 },
18374 combos: &combos,
18375 kernel: Kernel::Auto,
18376 };
18377 let out = compute_cpu_batch(req).unwrap();
18378 let input = MedpriceInput::from_slices(&high, &low, MedpriceParams::default());
18379 let direct = medprice_with_kernel(&input, Kernel::Auto.to_non_batch())
18380 .unwrap()
18381 .values;
18382 let got = out.values_f64.unwrap();
18383 assert_series_eq(&got, &direct, 1e-12);
18384 }
18385
18386 #[test]
18387 fn percentile_nearest_rank_output_matches_direct() {
18388 let data = sample_series();
18389 let combo = [
18390 ParamKV {
18391 key: "length",
18392 value: ParamValue::Int(12),
18393 },
18394 ParamKV {
18395 key: "percentage",
18396 value: ParamValue::Float(70.0),
18397 },
18398 ];
18399 let combos = [IndicatorParamSet { params: &combo }];
18400 let req = IndicatorBatchRequest {
18401 indicator_id: "percentile_nearest_rank",
18402 output_id: Some("value"),
18403 data: IndicatorDataRef::Slice { values: &data },
18404 combos: &combos,
18405 kernel: Kernel::Auto,
18406 };
18407 let out = compute_cpu_batch(req).unwrap();
18408 let input = PercentileNearestRankInput::from_slice(
18409 &data,
18410 PercentileNearestRankParams {
18411 length: Some(12),
18412 percentage: Some(70.0),
18413 },
18414 );
18415 let direct = percentile_nearest_rank_with_kernel(&input, Kernel::Auto.to_non_batch())
18416 .unwrap()
18417 .values;
18418 let got = out.values_f64.unwrap();
18419 assert_series_eq(&got, &direct, 1e-12);
18420 }
18421
18422 #[test]
18423 fn zscore_output_matches_direct() {
18424 let data = sample_series();
18425 let combo = [
18426 ParamKV {
18427 key: "period",
18428 value: ParamValue::Int(14),
18429 },
18430 ParamKV {
18431 key: "ma_type",
18432 value: ParamValue::EnumString("ema"),
18433 },
18434 ParamKV {
18435 key: "nbdev",
18436 value: ParamValue::Float(1.25),
18437 },
18438 ParamKV {
18439 key: "devtype",
18440 value: ParamValue::Int(1),
18441 },
18442 ];
18443 let combos = [IndicatorParamSet { params: &combo }];
18444 let req = IndicatorBatchRequest {
18445 indicator_id: "zscore",
18446 output_id: Some("value"),
18447 data: IndicatorDataRef::Slice { values: &data },
18448 combos: &combos,
18449 kernel: Kernel::Auto,
18450 };
18451 let out = compute_cpu_batch(req).unwrap();
18452 let input = ZscoreInput::from_slice(
18453 &data,
18454 ZscoreParams {
18455 period: Some(14),
18456 ma_type: Some("ema".to_string()),
18457 nbdev: Some(1.25),
18458 devtype: Some(1),
18459 },
18460 );
18461 let direct = zscore_with_kernel(&input, Kernel::Auto.to_non_batch())
18462 .unwrap()
18463 .values;
18464 let got = out.values_f64.unwrap();
18465 assert_series_eq(&got, &direct, 1e-12);
18466 }
18467
18468 #[test]
18469 fn vpci_secondary_output_matches_direct() {
18470 let close = sample_series();
18471 let volume: Vec<f64> = (0..close.len())
18472 .map(|i| 1000.0 + (i as f64 * 7.0))
18473 .collect();
18474 let combo = [
18475 ParamKV {
18476 key: "short_range",
18477 value: ParamValue::Int(5),
18478 },
18479 ParamKV {
18480 key: "long_range",
18481 value: ParamValue::Int(25),
18482 },
18483 ];
18484 let combos = [IndicatorParamSet { params: &combo }];
18485 let req = IndicatorBatchRequest {
18486 indicator_id: "vpci",
18487 output_id: Some("vpcis"),
18488 data: IndicatorDataRef::CloseVolume {
18489 close: &close,
18490 volume: &volume,
18491 },
18492 combos: &combos,
18493 kernel: Kernel::Auto,
18494 };
18495 let out = compute_cpu_batch(req).unwrap();
18496 let input = VpciInput::from_slices(
18497 &close,
18498 &volume,
18499 VpciParams {
18500 short_range: Some(5),
18501 long_range: Some(25),
18502 },
18503 );
18504 let direct = vpci_with_kernel(&input, Kernel::Auto.to_non_batch())
18505 .unwrap()
18506 .vpcis;
18507 let got = out.values_f64.unwrap();
18508 assert_series_eq(&got, &direct, 1e-12);
18509 }
18510
18511 #[test]
18512 fn yang_zhang_secondary_output_matches_direct() {
18513 let (open, high, low, close) = sample_ohlc();
18514 let combo = [
18515 ParamKV {
18516 key: "lookback",
18517 value: ParamValue::Int(21),
18518 },
18519 ParamKV {
18520 key: "k_override",
18521 value: ParamValue::Bool(true),
18522 },
18523 ParamKV {
18524 key: "k",
18525 value: ParamValue::Float(0.28),
18526 },
18527 ];
18528 let combos = [IndicatorParamSet { params: &combo }];
18529 let req = IndicatorBatchRequest {
18530 indicator_id: "yang_zhang_volatility",
18531 output_id: Some("rs"),
18532 data: IndicatorDataRef::Ohlc {
18533 open: &open,
18534 high: &high,
18535 low: &low,
18536 close: &close,
18537 },
18538 combos: &combos,
18539 kernel: Kernel::Auto,
18540 };
18541 let out = compute_cpu_batch(req).unwrap();
18542 let input = YangZhangVolatilityInput::from_slices(
18543 &open,
18544 &high,
18545 &low,
18546 &close,
18547 YangZhangVolatilityParams {
18548 lookback: Some(21),
18549 k_override: Some(true),
18550 k: Some(0.28),
18551 },
18552 );
18553 let direct = yang_zhang_volatility_with_kernel(&input, Kernel::Auto.to_non_batch())
18554 .unwrap()
18555 .rs;
18556 let got = out.values_f64.unwrap();
18557 assert_series_eq(&got, &direct, 1e-12);
18558 }
18559
18560 #[test]
18561 fn historical_volatility_percentile_signal_output_matches_direct() {
18562 let data = sample_series();
18563 let combo = [
18564 ParamKV {
18565 key: "length",
18566 value: ParamValue::Int(5),
18567 },
18568 ParamKV {
18569 key: "annual_length",
18570 value: ParamValue::Int(10),
18571 },
18572 ];
18573 let combos = [IndicatorParamSet { params: &combo }];
18574 let req = IndicatorBatchRequest {
18575 indicator_id: "historical_volatility_percentile",
18576 output_id: Some("hvp_sma"),
18577 data: IndicatorDataRef::Slice { values: &data },
18578 combos: &combos,
18579 kernel: Kernel::Auto,
18580 };
18581 let out = compute_cpu_batch(req).unwrap();
18582 let input = HistoricalVolatilityPercentileInput::from_slice(
18583 &data,
18584 HistoricalVolatilityPercentileParams {
18585 length: Some(5),
18586 annual_length: Some(10),
18587 },
18588 );
18589 let direct =
18590 historical_volatility_percentile_with_kernel(&input, Kernel::Auto.to_non_batch())
18591 .unwrap()
18592 .hvp_sma;
18593 let got = out.values_f64.unwrap();
18594 assert_series_eq(&got, &direct, 1e-12);
18595 }
18596
18597 #[test]
18598 fn volatility_ratio_adaptive_rsx_signal_output_matches_direct() {
18599 let data = sample_series();
18600 let combo = [
18601 ParamKV {
18602 key: "period",
18603 value: ParamValue::Int(6),
18604 },
18605 ParamKV {
18606 key: "speed",
18607 value: ParamValue::Float(0.5),
18608 },
18609 ];
18610 let combos = [IndicatorParamSet { params: &combo }];
18611 let req = IndicatorBatchRequest {
18612 indicator_id: "volatility_ratio_adaptive_rsx",
18613 output_id: Some("signal"),
18614 data: IndicatorDataRef::Slice { values: &data },
18615 combos: &combos,
18616 kernel: Kernel::Auto,
18617 };
18618 let out = compute_cpu_batch(req).unwrap();
18619 let input = VolatilityRatioAdaptiveRsxInput::from_slice(
18620 &data,
18621 VolatilityRatioAdaptiveRsxParams {
18622 period: Some(6),
18623 speed: Some(0.5),
18624 },
18625 );
18626 let direct = volatility_ratio_adaptive_rsx_with_kernel(&input, Kernel::Auto.to_non_batch())
18627 .unwrap()
18628 .signal;
18629 let got = out.values_f64.unwrap();
18630 assert_series_eq(&got, &direct, 1e-12);
18631 }
18632
18633 #[test]
18634 fn on_balance_volume_oscillator_signal_output_matches_direct() {
18635 let close = sample_series();
18636 let volume: Vec<f64> = (0..close.len()).map(|i| 1000.0 + i as f64 * 3.0).collect();
18637 let combo = [
18638 ParamKV {
18639 key: "obv_length",
18640 value: ParamValue::Int(20),
18641 },
18642 ParamKV {
18643 key: "ema_length",
18644 value: ParamValue::Int(9),
18645 },
18646 ];
18647 let combos = [IndicatorParamSet { params: &combo }];
18648 let req = IndicatorBatchRequest {
18649 indicator_id: "on_balance_volume_oscillator",
18650 output_id: Some("signal"),
18651 data: IndicatorDataRef::CloseVolume {
18652 close: &close,
18653 volume: &volume,
18654 },
18655 combos: &combos,
18656 kernel: Kernel::Auto,
18657 };
18658 let out = compute_cpu_batch(req).unwrap();
18659 let input = OnBalanceVolumeOscillatorInput::from_slices(
18660 &close,
18661 &volume,
18662 OnBalanceVolumeOscillatorParams {
18663 obv_length: Some(20),
18664 ema_length: Some(9),
18665 },
18666 );
18667 let direct = on_balance_volume_oscillator_with_kernel(&input, Kernel::Auto.to_non_batch())
18668 .unwrap()
18669 .signal;
18670 let got = out.values_f64.unwrap();
18671 assert_series_eq(&got, &direct, 1e-12);
18672 }
18673
18674 #[test]
18675 fn twiggs_money_flow_smoothed_output_matches_direct() {
18676 let open = vec![10.0, 10.2, 10.4, 10.7, 10.9, 11.1, 11.3, 11.5, 11.7, 11.9];
18677 let high = vec![10.4, 10.7, 10.9, 11.1, 11.4, 11.6, 11.8, 12.0, 12.2, 12.4];
18678 let low = vec![9.8, 10.0, 10.2, 10.5, 10.7, 10.9, 11.1, 11.3, 11.5, 11.7];
18679 let close = vec![10.1, 10.5, 10.7, 10.9, 11.2, 11.4, 11.6, 11.8, 12.0, 12.2];
18680 let volume = vec![
18681 1000.0, 1015.0, 1030.0, 1045.0, 1060.0, 1075.0, 1090.0, 1105.0, 1120.0, 1135.0,
18682 ];
18683 let combo = [
18684 ParamKV {
18685 key: "length",
18686 value: ParamValue::Int(5),
18687 },
18688 ParamKV {
18689 key: "smoothing_length",
18690 value: ParamValue::Int(4),
18691 },
18692 ParamKV {
18693 key: "ma_type",
18694 value: ParamValue::EnumString("WMA"),
18695 },
18696 ];
18697 let combos = [IndicatorParamSet { params: &combo }];
18698 let req = IndicatorBatchRequest {
18699 indicator_id: "twiggs_money_flow",
18700 output_id: Some("smoothed"),
18701 data: IndicatorDataRef::Ohlcv {
18702 open: &open,
18703 high: &high,
18704 low: &low,
18705 close: &close,
18706 volume: &volume,
18707 },
18708 combos: &combos,
18709 kernel: Kernel::Auto,
18710 };
18711 let out = compute_cpu_batch(req).unwrap();
18712 let input = TwiggsMoneyFlowInput::from_slices(
18713 &high,
18714 &low,
18715 &close,
18716 &volume,
18717 TwiggsMoneyFlowParams {
18718 length: Some(5),
18719 smoothing_length: Some(4),
18720 ma_type: Some("WMA".to_string()),
18721 },
18722 );
18723 let direct = twiggs_money_flow_with_kernel(&input, Kernel::Auto.to_non_batch())
18724 .unwrap()
18725 .smoothed;
18726 let got = out.values_f64.unwrap();
18727 assert_series_eq(&got, &direct, 1e-12);
18728 }
18729
18730 #[test]
18731 fn parkinson_variance_output_matches_direct() {
18732 let (_open, high, low, _close) = sample_ohlc();
18733 let combo = [ParamKV {
18734 key: "period",
18735 value: ParamValue::Int(9),
18736 }];
18737 let combos = [IndicatorParamSet { params: &combo }];
18738 let req = IndicatorBatchRequest {
18739 indicator_id: "parkinson_volatility",
18740 output_id: Some("variance"),
18741 data: IndicatorDataRef::HighLow {
18742 high: &high,
18743 low: &low,
18744 },
18745 combos: &combos,
18746 kernel: Kernel::Auto,
18747 };
18748 let out = compute_cpu_batch(req).unwrap();
18749 let input = ParkinsonVolatilityInput::from_slices(
18750 &high,
18751 &low,
18752 ParkinsonVolatilityParams { period: Some(9) },
18753 );
18754 let direct = parkinson_volatility_with_kernel(&input, Kernel::Auto.to_non_batch())
18755 .unwrap()
18756 .variance;
18757 let got = out.values_f64.unwrap();
18758 assert_series_eq(&got, &direct, 1e-12);
18759 }
18760
18761 #[test]
18762 fn l2_ehlers_signal_to_noise_output_matches_direct() {
18763 let candles = sample_candles();
18764 let combo = [
18765 ParamKV {
18766 key: "source",
18767 value: ParamValue::EnumString("hl2"),
18768 },
18769 ParamKV {
18770 key: "smooth_period",
18771 value: ParamValue::Int(10),
18772 },
18773 ];
18774 let combos = [IndicatorParamSet { params: &combo }];
18775 let req = IndicatorBatchRequest {
18776 indicator_id: "l2_ehlers_signal_to_noise",
18777 output_id: Some("value"),
18778 data: IndicatorDataRef::Candles {
18779 candles: &candles,
18780 source: Some("hl2"),
18781 },
18782 combos: &combos,
18783 kernel: Kernel::Auto,
18784 };
18785 let out = compute_cpu_batch(req).unwrap();
18786 let input = L2EhlersSignalToNoiseInput::from_slices(
18787 crate::utilities::data_loader::source_type(&candles, "hl2"),
18788 candles.high.as_slice(),
18789 candles.low.as_slice(),
18790 L2EhlersSignalToNoiseParams {
18791 smooth_period: Some(10),
18792 },
18793 );
18794 let direct = l2_ehlers_signal_to_noise_with_kernel(&input, Kernel::Auto.to_non_batch())
18795 .unwrap()
18796 .values;
18797 let got = out.values_f64.unwrap();
18798 assert_series_eq(&got, &direct, 1e-12);
18799 }
18800
18801 #[test]
18802 fn cycle_channel_oscillator_output_matches_direct() {
18803 let candles = sample_candles();
18804 let combo = [
18805 ParamKV {
18806 key: "source",
18807 value: ParamValue::EnumString("close"),
18808 },
18809 ParamKV {
18810 key: "short_cycle_length",
18811 value: ParamValue::Int(10),
18812 },
18813 ParamKV {
18814 key: "medium_cycle_length",
18815 value: ParamValue::Int(30),
18816 },
18817 ParamKV {
18818 key: "short_multiplier",
18819 value: ParamValue::Float(1.0),
18820 },
18821 ParamKV {
18822 key: "medium_multiplier",
18823 value: ParamValue::Float(3.0),
18824 },
18825 ];
18826 let combos = [IndicatorParamSet { params: &combo }];
18827 let req = IndicatorBatchRequest {
18828 indicator_id: "cycle_channel_oscillator",
18829 output_id: Some("fast"),
18830 data: IndicatorDataRef::Candles {
18831 candles: &candles,
18832 source: Some("close"),
18833 },
18834 combos: &combos,
18835 kernel: Kernel::Auto,
18836 };
18837 let out = compute_cpu_batch(req).unwrap();
18838 let input = CycleChannelOscillatorInput::from_slices(
18839 crate::utilities::data_loader::source_type(&candles, "close"),
18840 candles.high.as_slice(),
18841 candles.low.as_slice(),
18842 candles.close.as_slice(),
18843 CycleChannelOscillatorParams {
18844 short_cycle_length: Some(10),
18845 medium_cycle_length: Some(30),
18846 short_multiplier: Some(1.0),
18847 medium_multiplier: Some(3.0),
18848 },
18849 );
18850 let direct = cycle_channel_oscillator_with_kernel(&input, Kernel::Auto.to_non_batch())
18851 .unwrap()
18852 .fast;
18853 let got = out.values_f64.unwrap();
18854 assert_series_eq(&got, &direct, 1e-12);
18855 }
18856
18857 #[test]
18858 fn andean_oscillator_output_matches_direct() {
18859 let candles = sample_candles();
18860 let combo = [
18861 ParamKV {
18862 key: "length",
18863 value: ParamValue::Int(50),
18864 },
18865 ParamKV {
18866 key: "signal_length",
18867 value: ParamValue::Int(9),
18868 },
18869 ];
18870 let combos = [IndicatorParamSet { params: &combo }];
18871 let req = IndicatorBatchRequest {
18872 indicator_id: "andean_oscillator",
18873 output_id: Some("bull"),
18874 data: IndicatorDataRef::Candles {
18875 candles: &candles,
18876 source: None,
18877 },
18878 combos: &combos,
18879 kernel: Kernel::Auto,
18880 };
18881 let out = compute_cpu_batch(req).unwrap();
18882 let input = AndeanOscillatorInput::from_slices(
18883 candles.open.as_slice(),
18884 candles.close.as_slice(),
18885 AndeanOscillatorParams {
18886 length: Some(50),
18887 signal_length: Some(9),
18888 },
18889 );
18890 let direct = andean_oscillator_with_kernel(&input, Kernel::Auto.to_non_batch())
18891 .unwrap()
18892 .bull;
18893 let got = out.values_f64.unwrap();
18894 assert_series_eq(&got, &direct, 1e-12);
18895 }
18896
18897 #[test]
18898 fn daily_factor_output_matches_direct() {
18899 let (open, high, low, close) = sample_ohlc();
18900 let combo = [ParamKV {
18901 key: "threshold_level",
18902 value: ParamValue::Float(0.35),
18903 }];
18904 let combos = [IndicatorParamSet { params: &combo }];
18905 let req = IndicatorBatchRequest {
18906 indicator_id: "daily_factor",
18907 output_id: Some("signal"),
18908 data: IndicatorDataRef::Ohlc {
18909 open: &open,
18910 high: &high,
18911 low: &low,
18912 close: &close,
18913 },
18914 combos: &combos,
18915 kernel: Kernel::Auto,
18916 };
18917 let out = compute_cpu_batch(req).unwrap();
18918 let input = DailyFactorInput::from_slices(
18919 &open,
18920 &high,
18921 &low,
18922 &close,
18923 DailyFactorParams {
18924 threshold_level: Some(0.35),
18925 },
18926 );
18927 let direct = daily_factor_with_kernel(&input, Kernel::Auto.to_non_batch())
18928 .unwrap()
18929 .signal;
18930 let got = out.values_f64.unwrap();
18931 assert_series_eq(&got, &direct, 1e-12);
18932 }
18933
18934 #[test]
18935 fn ehlers_adaptive_cyber_cycle_output_matches_direct() {
18936 let candles = sample_candles();
18937 let combo = [
18938 ParamKV {
18939 key: "source",
18940 value: ParamValue::EnumString("hl2"),
18941 },
18942 ParamKV {
18943 key: "alpha",
18944 value: ParamValue::Float(0.07),
18945 },
18946 ];
18947 let combos = [IndicatorParamSet { params: &combo }];
18948 let req = IndicatorBatchRequest {
18949 indicator_id: "ehlers_adaptive_cyber_cycle",
18950 output_id: Some("cycle"),
18951 data: IndicatorDataRef::Candles {
18952 candles: &candles,
18953 source: Some("hl2"),
18954 },
18955 combos: &combos,
18956 kernel: Kernel::Auto,
18957 };
18958 let out = compute_cpu_batch(req).unwrap();
18959 let input = EhlersAdaptiveCyberCycleInput::from_slice(
18960 crate::utilities::data_loader::source_type(&candles, "hl2"),
18961 EhlersAdaptiveCyberCycleParams { alpha: Some(0.07) },
18962 );
18963 let direct = ehlers_adaptive_cyber_cycle_with_kernel(&input, Kernel::Auto.to_non_batch())
18964 .unwrap()
18965 .cycle;
18966 let got = out.values_f64.unwrap();
18967 assert_series_eq(&got, &direct, 1e-12);
18968 }
18969
18970 #[test]
18971 fn ehlers_simple_cycle_indicator_output_matches_direct() {
18972 let candles = sample_candles();
18973 let combo = [
18974 ParamKV {
18975 key: "source",
18976 value: ParamValue::EnumString("hl2"),
18977 },
18978 ParamKV {
18979 key: "alpha",
18980 value: ParamValue::Float(0.07),
18981 },
18982 ];
18983 let combos = [IndicatorParamSet { params: &combo }];
18984 let req = IndicatorBatchRequest {
18985 indicator_id: "ehlers_simple_cycle_indicator",
18986 output_id: Some("cycle"),
18987 data: IndicatorDataRef::Candles {
18988 candles: &candles,
18989 source: Some("hl2"),
18990 },
18991 combos: &combos,
18992 kernel: Kernel::Auto,
18993 };
18994 let out = compute_cpu_batch(req).unwrap();
18995 let input = EhlersSimpleCycleIndicatorInput::from_slice(
18996 crate::utilities::data_loader::source_type(&candles, "hl2"),
18997 EhlersSimpleCycleIndicatorParams { alpha: Some(0.07) },
18998 );
18999 let direct = ehlers_simple_cycle_indicator_with_kernel(&input, Kernel::Auto.to_non_batch())
19000 .unwrap()
19001 .cycle;
19002 let got = out.values_f64.unwrap();
19003 assert_series_eq(&got, &direct, 1e-12);
19004 }
19005
19006 #[test]
19007 fn l1_ehlers_phasor_output_matches_direct() {
19008 let candles = sample_candles();
19009 let combo = [ParamKV {
19010 key: "domestic_cycle_length",
19011 value: ParamValue::Int(15),
19012 }];
19013 let combos = [IndicatorParamSet { params: &combo }];
19014 let req = IndicatorBatchRequest {
19015 indicator_id: "l1_ehlers_phasor",
19016 output_id: Some("value"),
19017 data: IndicatorDataRef::Candles {
19018 candles: &candles,
19019 source: Some("close"),
19020 },
19021 combos: &combos,
19022 kernel: Kernel::Auto,
19023 };
19024 let out = compute_cpu_batch(req).unwrap();
19025 let input = L1EhlersPhasorInput::from_slice(
19026 candles.close.as_slice(),
19027 L1EhlersPhasorParams {
19028 domestic_cycle_length: Some(15),
19029 },
19030 );
19031 let direct = l1_ehlers_phasor_with_kernel(&input, Kernel::Auto.to_non_batch())
19032 .unwrap()
19033 .values;
19034 let got = out.values_f64.unwrap();
19035 assert_series_eq(&got, &direct, 1e-12);
19036 }
19037
19038 #[test]
19039 fn ehlers_smoothed_adaptive_momentum_output_matches_direct() {
19040 let candles = sample_candles();
19041 let combo = [
19042 ParamKV {
19043 key: "source",
19044 value: ParamValue::EnumString("hl2"),
19045 },
19046 ParamKV {
19047 key: "alpha",
19048 value: ParamValue::Float(0.07),
19049 },
19050 ParamKV {
19051 key: "cutoff",
19052 value: ParamValue::Float(8.0),
19053 },
19054 ];
19055 let combos = [IndicatorParamSet { params: &combo }];
19056 let req = IndicatorBatchRequest {
19057 indicator_id: "ehlers_smoothed_adaptive_momentum",
19058 output_id: Some("value"),
19059 data: IndicatorDataRef::Candles {
19060 candles: &candles,
19061 source: Some("hl2"),
19062 },
19063 combos: &combos,
19064 kernel: Kernel::Auto,
19065 };
19066 let out = compute_cpu_batch(req).unwrap();
19067 let input = EhlersSmoothedAdaptiveMomentumInput::from_slice(
19068 crate::utilities::data_loader::source_type(&candles, "hl2"),
19069 EhlersSmoothedAdaptiveMomentumParams {
19070 alpha: Some(0.07),
19071 cutoff: Some(8.0),
19072 },
19073 );
19074 let direct =
19075 ehlers_smoothed_adaptive_momentum_with_kernel(&input, Kernel::Auto.to_non_batch())
19076 .unwrap()
19077 .values;
19078 let got = out.values_f64.unwrap();
19079 assert_series_eq(&got, &direct, 1e-12);
19080 }
19081
19082 #[test]
19083 fn ewma_volatility_output_matches_direct() {
19084 let close = sample_series();
19085 let combo = [ParamKV {
19086 key: "lambda",
19087 value: ParamValue::Float(0.94),
19088 }];
19089 let combos = [IndicatorParamSet { params: &combo }];
19090 let req = IndicatorBatchRequest {
19091 indicator_id: "ewma_volatility",
19092 output_id: Some("value"),
19093 data: IndicatorDataRef::Slice { values: &close },
19094 combos: &combos,
19095 kernel: Kernel::Auto,
19096 };
19097 let out = compute_cpu_batch(req).unwrap();
19098 let input =
19099 EwmaVolatilityInput::from_slice(&close, EwmaVolatilityParams { lambda: Some(0.94) });
19100 let direct = ewma_volatility_with_kernel(&input, Kernel::Auto.to_non_batch())
19101 .unwrap()
19102 .values;
19103 let got = out.values_f64.unwrap();
19104 assert_series_eq(&got, &direct, 1e-12);
19105 }
19106
19107 #[test]
19108 fn random_walk_index_output_matches_direct() {
19109 let open = sample_series();
19110 let high: Vec<f64> = open.iter().map(|v| v + 1.0).collect();
19111 let low: Vec<f64> = open.iter().map(|v| v - 1.0).collect();
19112 let close: Vec<f64> = open
19113 .iter()
19114 .enumerate()
19115 .map(|(i, v)| v + 0.1 * (i as f64 + 1.0))
19116 .collect();
19117 let combo = [ParamKV {
19118 key: "length",
19119 value: ParamValue::Int(14),
19120 }];
19121 let combos = [IndicatorParamSet { params: &combo }];
19122 let req = IndicatorBatchRequest {
19123 indicator_id: "random_walk_index",
19124 output_id: Some("high"),
19125 data: IndicatorDataRef::Ohlc {
19126 open: &open,
19127 high: &high,
19128 low: &low,
19129 close: &close,
19130 },
19131 combos: &combos,
19132 kernel: Kernel::Auto,
19133 };
19134 let out = compute_cpu_batch(req).unwrap();
19135 let input = RandomWalkIndexInput::from_slices(
19136 &high,
19137 &low,
19138 &close,
19139 RandomWalkIndexParams { length: Some(14) },
19140 );
19141 let direct = random_walk_index_with_kernel(&input, Kernel::Auto.to_non_batch())
19142 .unwrap()
19143 .high;
19144 let got = out.values_f64.unwrap();
19145 assert_series_eq(&got, &direct, 1e-12);
19146 }
19147
19148 #[test]
19149 fn price_moving_average_ratio_percentile_output_matches_direct() {
19150 let open = sample_series();
19151 let high: Vec<f64> = open
19152 .iter()
19153 .enumerate()
19154 .map(|(i, v)| v + 1.0 + (i as f64 * 0.03).sin() * 0.15)
19155 .collect();
19156 let low: Vec<f64> = open
19157 .iter()
19158 .enumerate()
19159 .map(|(i, v)| v - 1.0 - (i as f64 * 0.05).cos() * 0.12)
19160 .collect();
19161 let close: Vec<f64> = open
19162 .iter()
19163 .enumerate()
19164 .map(|(i, v)| v + 0.12 * (i as f64 + 1.0))
19165 .collect();
19166 let volume: Vec<f64> = (0..open.len())
19167 .map(|i| 1_000.0 + i as f64 * 2.0 + (i as f64 * 0.09).sin() * 40.0)
19168 .collect();
19169 let combo = [
19170 ParamKV {
19171 key: "source",
19172 value: ParamValue::EnumString("close"),
19173 },
19174 ParamKV {
19175 key: "ma_length",
19176 value: ParamValue::Int(20),
19177 },
19178 ParamKV {
19179 key: "ma_type",
19180 value: ParamValue::EnumString("vwma"),
19181 },
19182 ParamKV {
19183 key: "pmarp_lookback",
19184 value: ParamValue::Int(30),
19185 },
19186 ParamKV {
19187 key: "signal_ma_length",
19188 value: ParamValue::Int(10),
19189 },
19190 ParamKV {
19191 key: "signal_ma_type",
19192 value: ParamValue::EnumString("sma"),
19193 },
19194 ParamKV {
19195 key: "line_mode",
19196 value: ParamValue::EnumString("pmarp"),
19197 },
19198 ];
19199 let combos = [IndicatorParamSet { params: &combo }];
19200 let req = IndicatorBatchRequest {
19201 indicator_id: "price_moving_average_ratio_percentile",
19202 output_id: Some("plotline"),
19203 data: IndicatorDataRef::Candles {
19204 candles: &crate::utilities::data_loader::Candles {
19205 timestamp: vec![0; open.len()],
19206 open: open.clone(),
19207 high: high.clone(),
19208 low: low.clone(),
19209 close: close.clone(),
19210 volume: volume.clone(),
19211 fields: crate::utilities::data_loader::CandleFieldFlags {
19212 open: true,
19213 high: true,
19214 low: true,
19215 close: true,
19216 volume: true,
19217 },
19218 hl2: high
19219 .iter()
19220 .zip(low.iter())
19221 .map(|(h, l)| (h + l) * 0.5)
19222 .collect(),
19223 hlc3: high
19224 .iter()
19225 .zip(low.iter())
19226 .zip(close.iter())
19227 .map(|((h, l), c)| (h + l + c) / 3.0)
19228 .collect(),
19229 ohlc4: open
19230 .iter()
19231 .zip(high.iter())
19232 .zip(low.iter())
19233 .zip(close.iter())
19234 .map(|(((o, h), l), c)| (o + h + l + c) * 0.25)
19235 .collect(),
19236 hlcc4: high
19237 .iter()
19238 .zip(low.iter())
19239 .zip(close.iter())
19240 .map(|((h, l), c)| (h + l + c + c) * 0.25)
19241 .collect(),
19242 },
19243 source: Some("close"),
19244 },
19245 combos: &combos,
19246 kernel: Kernel::Auto,
19247 };
19248 let out = compute_cpu_batch(req).unwrap();
19249 let input = PriceMovingAverageRatioPercentileInput::from_slices(
19250 &close,
19251 &volume,
19252 PriceMovingAverageRatioPercentileParams {
19253 ma_length: Some(20),
19254 ma_type: Some(PriceMovingAverageRatioPercentileMaType::Vwma),
19255 pmarp_lookback: Some(30),
19256 signal_ma_length: Some(10),
19257 signal_ma_type: Some(PriceMovingAverageRatioPercentileMaType::Sma),
19258 line_mode: Some(PriceMovingAverageRatioPercentileLineMode::Pmarp),
19259 },
19260 );
19261 let direct =
19262 price_moving_average_ratio_percentile_with_kernel(&input, Kernel::Auto.to_non_batch())
19263 .unwrap()
19264 .plotline;
19265 let got = out.values_f64.unwrap();
19266 assert_series_eq(&got, &direct, 1e-12);
19267 }
19268
19269 #[test]
19270 fn trend_trigger_factor_output_matches_direct() {
19271 let base = sample_series();
19272 let high: Vec<f64> = base
19273 .iter()
19274 .enumerate()
19275 .map(|(i, v)| v + 1.0 + (i as f64 * 0.03).sin() * 0.15)
19276 .collect();
19277 let low: Vec<f64> = base
19278 .iter()
19279 .enumerate()
19280 .map(|(i, v)| v - 1.0 - (i as f64 * 0.05).cos() * 0.12)
19281 .collect();
19282 let combo = [ParamKV {
19283 key: "length",
19284 value: ParamValue::Int(15),
19285 }];
19286 let combos = [IndicatorParamSet { params: &combo }];
19287 let req = IndicatorBatchRequest {
19288 indicator_id: "trend_trigger_factor",
19289 output_id: Some("value"),
19290 data: IndicatorDataRef::HighLow {
19291 high: &high,
19292 low: &low,
19293 },
19294 combos: &combos,
19295 kernel: Kernel::Auto,
19296 };
19297 let out = compute_cpu_batch(req).unwrap();
19298 let input = TrendTriggerFactorInput::from_slices(
19299 &high,
19300 &low,
19301 TrendTriggerFactorParams { length: Some(15) },
19302 );
19303 let direct = trend_trigger_factor_with_kernel(&input, Kernel::Auto.to_non_batch())
19304 .unwrap()
19305 .values;
19306 let got = out.values_f64.unwrap();
19307 assert_series_eq(&got, &direct, 1e-12);
19308 }
19309
19310 #[test]
19311 fn mesa_stochastic_multi_length_output_matches_direct() {
19312 let source: Vec<f64> = (0..180)
19313 .map(|i| 100.0 + (i as f64 * 0.09).sin() * 2.0 + i as f64 * 0.015)
19314 .collect();
19315 let high: Vec<f64> = source.iter().map(|v| v + 1.0).collect();
19316 let low: Vec<f64> = source.iter().map(|v| v - 1.0).collect();
19317 let open = source.clone();
19318 let volume: Vec<f64> = (0..180).map(|i| 1000.0 + i as f64).collect();
19319 let combo = [
19320 ParamKV {
19321 key: "source",
19322 value: ParamValue::EnumString("close"),
19323 },
19324 ParamKV {
19325 key: "length_1",
19326 value: ParamValue::Int(48),
19327 },
19328 ParamKV {
19329 key: "length_2",
19330 value: ParamValue::Int(21),
19331 },
19332 ParamKV {
19333 key: "length_3",
19334 value: ParamValue::Int(9),
19335 },
19336 ParamKV {
19337 key: "length_4",
19338 value: ParamValue::Int(6),
19339 },
19340 ParamKV {
19341 key: "trigger_length",
19342 value: ParamValue::Int(2),
19343 },
19344 ];
19345 let combos = [IndicatorParamSet { params: &combo }];
19346 let req = IndicatorBatchRequest {
19347 indicator_id: "mesa_stochastic_multi_length",
19348 output_id: Some("mesa_1"),
19349 data: IndicatorDataRef::Candles {
19350 candles: &crate::utilities::data_loader::Candles {
19351 timestamp: vec![0; source.len()],
19352 open: open.clone(),
19353 high: high.clone(),
19354 low: low.clone(),
19355 close: source.clone(),
19356 volume,
19357 fields: crate::utilities::data_loader::CandleFieldFlags {
19358 open: true,
19359 high: true,
19360 low: true,
19361 close: true,
19362 volume: true,
19363 },
19364 hl2: high
19365 .iter()
19366 .zip(low.iter())
19367 .map(|(h, l)| (h + l) * 0.5)
19368 .collect(),
19369 hlc3: high
19370 .iter()
19371 .zip(low.iter())
19372 .zip(source.iter())
19373 .map(|((h, l), c)| (h + l + c) / 3.0)
19374 .collect(),
19375 ohlc4: open
19376 .iter()
19377 .zip(high.iter())
19378 .zip(low.iter())
19379 .zip(source.iter())
19380 .map(|(((o, h), l), c)| (o + h + l + c) * 0.25)
19381 .collect(),
19382 hlcc4: high
19383 .iter()
19384 .zip(low.iter())
19385 .zip(source.iter())
19386 .map(|((h, l), c)| (h + l + c + c) * 0.25)
19387 .collect(),
19388 },
19389 source: Some("close"),
19390 },
19391 combos: &combos,
19392 kernel: Kernel::Auto,
19393 };
19394 let out = compute_cpu_batch(req).unwrap();
19395 let input = MesaStochasticMultiLengthInput::from_slices(
19396 &source,
19397 MesaStochasticMultiLengthParams::default(),
19398 );
19399 let direct = mesa_stochastic_multi_length_with_kernel(&input, Kernel::Auto.to_non_batch())
19400 .unwrap()
19401 .mesa_1;
19402 let got = out.values_f64.unwrap();
19403 assert_series_eq(&got, &direct, 1e-12);
19404 }
19405
19406 #[test]
19407 fn spearman_correlation_output_matches_direct() {
19408 let close: Vec<f64> = (0..180)
19409 .map(|i| 100.0 + (i as f64 * 0.13).sin() * 2.0 + i as f64 * 0.02)
19410 .collect();
19411 let open: Vec<f64> = (0..180)
19412 .map(|i| 98.0 + (i as f64 * 0.07).cos() * 1.6 + i as f64 * 0.015)
19413 .collect();
19414 let high: Vec<f64> = close.iter().map(|v| v + 1.0).collect();
19415 let low: Vec<f64> = close.iter().map(|v| v - 1.0).collect();
19416 let volume: Vec<f64> = (0..180).map(|i| 1000.0 + i as f64).collect();
19417 let combo = [
19418 ParamKV {
19419 key: "source",
19420 value: ParamValue::EnumString("close"),
19421 },
19422 ParamKV {
19423 key: "comparison_source",
19424 value: ParamValue::EnumString("open"),
19425 },
19426 ParamKV {
19427 key: "lookback",
19428 value: ParamValue::Int(30),
19429 },
19430 ParamKV {
19431 key: "smoothing_length",
19432 value: ParamValue::Int(3),
19433 },
19434 ];
19435 let combos = [IndicatorParamSet { params: &combo }];
19436 let req = IndicatorBatchRequest {
19437 indicator_id: "spearman_correlation",
19438 output_id: Some("smoothed"),
19439 data: IndicatorDataRef::Candles {
19440 candles: &crate::utilities::data_loader::Candles {
19441 timestamp: vec![0; close.len()],
19442 open: open.clone(),
19443 high: high.clone(),
19444 low: low.clone(),
19445 close: close.clone(),
19446 volume,
19447 fields: crate::utilities::data_loader::CandleFieldFlags {
19448 open: true,
19449 high: true,
19450 low: true,
19451 close: true,
19452 volume: true,
19453 },
19454 hl2: high
19455 .iter()
19456 .zip(low.iter())
19457 .map(|(h, l)| (h + l) * 0.5)
19458 .collect(),
19459 hlc3: high
19460 .iter()
19461 .zip(low.iter())
19462 .zip(close.iter())
19463 .map(|((h, l), c)| (h + l + c) / 3.0)
19464 .collect(),
19465 ohlc4: open
19466 .iter()
19467 .zip(high.iter())
19468 .zip(low.iter())
19469 .zip(close.iter())
19470 .map(|(((o, h), l), c)| (o + h + l + c) * 0.25)
19471 .collect(),
19472 hlcc4: high
19473 .iter()
19474 .zip(low.iter())
19475 .zip(close.iter())
19476 .map(|((h, l), c)| (h + l + c + c) * 0.25)
19477 .collect(),
19478 },
19479 source: Some("close"),
19480 },
19481 combos: &combos,
19482 kernel: Kernel::Auto,
19483 };
19484 let out = compute_cpu_batch(req).unwrap();
19485 let input = SpearmanCorrelationInput::from_slices(
19486 &close,
19487 &open,
19488 SpearmanCorrelationParams {
19489 lookback: Some(30),
19490 smoothing_length: Some(3),
19491 },
19492 );
19493 let direct = spearman_correlation_with_kernel(&input, Kernel::Auto.to_non_batch())
19494 .unwrap()
19495 .smoothed;
19496 let got = out.values_f64.unwrap();
19497 assert_series_eq(&got, &direct, 1e-12);
19498 }
19499
19500 #[test]
19501 fn relative_strength_index_wave_indicator_output_matches_direct() {
19502 let open = sample_series();
19503 let close: Vec<f64> = open
19504 .iter()
19505 .enumerate()
19506 .map(|(i, v)| v + 0.2 * (i as f64 * 0.1).sin())
19507 .collect();
19508 let high: Vec<f64> = close.iter().map(|v| v + 0.9).collect();
19509 let low: Vec<f64> = close.iter().map(|v| v - 0.8).collect();
19510 let volume: Vec<f64> = (0..close.len()).map(|i| 1_000.0 + i as f64).collect();
19511 let candles = crate::utilities::data_loader::Candles {
19512 timestamp: vec![0; close.len()],
19513 open: open.clone(),
19514 high: high.clone(),
19515 low: low.clone(),
19516 close: close.clone(),
19517 volume,
19518 fields: crate::utilities::data_loader::CandleFieldFlags {
19519 open: true,
19520 high: true,
19521 low: true,
19522 close: true,
19523 volume: true,
19524 },
19525 hl2: high
19526 .iter()
19527 .zip(low.iter())
19528 .map(|(h, l)| (h + l) * 0.5)
19529 .collect(),
19530 hlc3: high
19531 .iter()
19532 .zip(low.iter())
19533 .zip(close.iter())
19534 .map(|((h, l), c)| (h + l + c) / 3.0)
19535 .collect(),
19536 ohlc4: open
19537 .iter()
19538 .zip(high.iter())
19539 .zip(low.iter())
19540 .zip(close.iter())
19541 .map(|(((o, h), l), c)| (o + h + l + c) * 0.25)
19542 .collect(),
19543 hlcc4: high
19544 .iter()
19545 .zip(low.iter())
19546 .zip(close.iter())
19547 .map(|((h, l), c)| (h + l + 2.0 * c) * 0.25)
19548 .collect(),
19549 };
19550 let combo = [
19551 ParamKV {
19552 key: "source",
19553 value: ParamValue::EnumString("hlcc4"),
19554 },
19555 ParamKV {
19556 key: "rsi_length",
19557 value: ParamValue::Int(14),
19558 },
19559 ParamKV {
19560 key: "length1",
19561 value: ParamValue::Int(2),
19562 },
19563 ParamKV {
19564 key: "length2",
19565 value: ParamValue::Int(5),
19566 },
19567 ParamKV {
19568 key: "length3",
19569 value: ParamValue::Int(9),
19570 },
19571 ParamKV {
19572 key: "length4",
19573 value: ParamValue::Int(13),
19574 },
19575 ];
19576 let combos = [IndicatorParamSet { params: &combo }];
19577 let req = IndicatorBatchRequest {
19578 indicator_id: "relative_strength_index_wave_indicator",
19579 output_id: Some("rsi_ma1"),
19580 data: IndicatorDataRef::Candles {
19581 candles: &candles,
19582 source: Some("hlcc4"),
19583 },
19584 combos: &combos,
19585 kernel: Kernel::Auto,
19586 };
19587 let out = compute_cpu_batch(req).unwrap();
19588 let input = RelativeStrengthIndexWaveIndicatorInput::from_slices(
19589 &candles.hlcc4,
19590 &high,
19591 &low,
19592 RelativeStrengthIndexWaveIndicatorParams {
19593 rsi_length: Some(14),
19594 length1: Some(2),
19595 length2: Some(5),
19596 length3: Some(9),
19597 length4: Some(13),
19598 },
19599 );
19600 let direct =
19601 relative_strength_index_wave_indicator_with_kernel(&input, Kernel::Auto.to_non_batch())
19602 .unwrap()
19603 .rsi_ma1;
19604 let got = out.values_f64.unwrap();
19605 assert_series_eq(&got, &direct, 1e-12);
19606 }
19607
19608 #[test]
19609 fn accumulation_swing_index_output_matches_direct() {
19610 let open = sample_series();
19611 let high: Vec<f64> = open.iter().map(|v| v + 1.0).collect();
19612 let low: Vec<f64> = open.iter().map(|v| v - 1.0).collect();
19613 let close: Vec<f64> = open
19614 .iter()
19615 .enumerate()
19616 .map(|(i, v)| v + 0.1 * (i as f64 + 1.0))
19617 .collect();
19618 let combo = [ParamKV {
19619 key: "daily_limit",
19620 value: ParamValue::Float(10_000.0),
19621 }];
19622 let combos = [IndicatorParamSet { params: &combo }];
19623 let req = IndicatorBatchRequest {
19624 indicator_id: "accumulation_swing_index",
19625 output_id: Some("value"),
19626 data: IndicatorDataRef::Ohlc {
19627 open: &open,
19628 high: &high,
19629 low: &low,
19630 close: &close,
19631 },
19632 combos: &combos,
19633 kernel: Kernel::Auto,
19634 };
19635 let out = compute_cpu_batch(req).unwrap();
19636 let input = AccumulationSwingIndexInput::from_slices(
19637 &open,
19638 &high,
19639 &low,
19640 &close,
19641 AccumulationSwingIndexParams {
19642 daily_limit: Some(10_000.0),
19643 },
19644 );
19645 let direct = accumulation_swing_index_with_kernel(&input, Kernel::Auto.to_non_batch())
19646 .unwrap()
19647 .values;
19648 let got = out.values_f64.unwrap();
19649 assert_series_eq(&got, &direct, 1e-12);
19650 }
19651
19652 #[test]
19653 fn ichimoku_oscillator_output_matches_direct() {
19654 let open: Vec<f64> = (0..160)
19655 .map(|i| 100.0 + (i as f64 * 0.07).sin() * 3.0 + i as f64 * 0.02)
19656 .collect();
19657 let high: Vec<f64> = open
19658 .iter()
19659 .enumerate()
19660 .map(|(i, v)| v + 1.2 + (i as f64 * 0.03).sin() * 0.25)
19661 .collect();
19662 let low: Vec<f64> = open
19663 .iter()
19664 .enumerate()
19665 .map(|(i, v)| v - 1.1 - (i as f64 * 0.05).cos() * 0.2)
19666 .collect();
19667 let close: Vec<f64> = open
19668 .iter()
19669 .enumerate()
19670 .map(|(i, v)| v + 0.12 * (i as f64 + 1.0))
19671 .collect();
19672 let combo = [
19673 ParamKV {
19674 key: "conversion_periods",
19675 value: ParamValue::Int(9),
19676 },
19677 ParamKV {
19678 key: "base_periods",
19679 value: ParamValue::Int(26),
19680 },
19681 ParamKV {
19682 key: "lagging_span_periods",
19683 value: ParamValue::Int(52),
19684 },
19685 ParamKV {
19686 key: "displacement",
19687 value: ParamValue::Int(26),
19688 },
19689 ParamKV {
19690 key: "ma_length",
19691 value: ParamValue::Int(12),
19692 },
19693 ParamKV {
19694 key: "smoothing_length",
19695 value: ParamValue::Int(3),
19696 },
19697 ParamKV {
19698 key: "extra_smoothing",
19699 value: ParamValue::Bool(true),
19700 },
19701 ParamKV {
19702 key: "normalize",
19703 value: ParamValue::EnumString("window"),
19704 },
19705 ParamKV {
19706 key: "window_size",
19707 value: ParamValue::Int(20),
19708 },
19709 ParamKV {
19710 key: "clamp",
19711 value: ParamValue::Bool(true),
19712 },
19713 ParamKV {
19714 key: "top_band",
19715 value: ParamValue::Float(2.0),
19716 },
19717 ParamKV {
19718 key: "mid_band",
19719 value: ParamValue::Float(1.5),
19720 },
19721 ];
19722 let combos = [IndicatorParamSet { params: &combo }];
19723 let req = IndicatorBatchRequest {
19724 indicator_id: "ichimoku_oscillator",
19725 output_id: Some("signal"),
19726 data: IndicatorDataRef::Ohlc {
19727 open: &open,
19728 high: &high,
19729 low: &low,
19730 close: &close,
19731 },
19732 combos: &combos,
19733 kernel: Kernel::Auto,
19734 };
19735 let out = compute_cpu_batch(req).unwrap();
19736 let input = IchimokuOscillatorInput::from_slices(
19737 &high,
19738 &low,
19739 &close,
19740 &close,
19741 IchimokuOscillatorParams {
19742 conversion_periods: Some(9),
19743 base_periods: Some(26),
19744 lagging_span_periods: Some(52),
19745 displacement: Some(26),
19746 ma_length: Some(12),
19747 smoothing_length: Some(3),
19748 extra_smoothing: Some(true),
19749 normalize: Some(IchimokuOscillatorNormalizeMode::Window),
19750 window_size: Some(20),
19751 clamp: Some(true),
19752 top_band: Some(2.0),
19753 mid_band: Some(1.5),
19754 },
19755 );
19756 let direct = ichimoku_oscillator_with_kernel(&input, Kernel::Auto.to_non_batch())
19757 .unwrap()
19758 .signal;
19759 let got = out.values_f64.unwrap();
19760 assert_series_eq(&got, &direct, 1e-12);
19761 }
19762
19763 #[test]
19764 fn volatility_quality_index_output_matches_direct() {
19765 let open = sample_series();
19766 let high: Vec<f64> = open.iter().map(|v| v + 1.0).collect();
19767 let low: Vec<f64> = open.iter().map(|v| v - 1.0).collect();
19768 let close: Vec<f64> = open
19769 .iter()
19770 .enumerate()
19771 .map(|(i, v)| v + 0.2 * (i as f64 + 1.0))
19772 .collect();
19773 let combo = [
19774 ParamKV {
19775 key: "fast_length",
19776 value: ParamValue::Int(9),
19777 },
19778 ParamKV {
19779 key: "slow_length",
19780 value: ParamValue::Int(21),
19781 },
19782 ];
19783 let combos = [IndicatorParamSet { params: &combo }];
19784 let req = IndicatorBatchRequest {
19785 indicator_id: "volatility_quality_index",
19786 output_id: Some("fast_sma"),
19787 data: IndicatorDataRef::Ohlc {
19788 open: &open,
19789 high: &high,
19790 low: &low,
19791 close: &close,
19792 },
19793 combos: &combos,
19794 kernel: Kernel::Auto,
19795 };
19796 let out = compute_cpu_batch(req).unwrap();
19797 let input = VolatilityQualityIndexInput::from_slices(
19798 &open,
19799 &high,
19800 &low,
19801 &close,
19802 VolatilityQualityIndexParams {
19803 fast_length: Some(9),
19804 slow_length: Some(21),
19805 },
19806 );
19807 let direct = volatility_quality_index_with_kernel(&input, Kernel::Auto.to_non_batch())
19808 .unwrap()
19809 .fast_sma;
19810 let got = out.values_f64.unwrap();
19811 assert_series_eq(&got, &direct, 1e-12);
19812 }
19813
19814 #[test]
19815 fn vwap_deviation_oscillator_output_matches_direct() {
19816 let open = sample_series();
19817 let high: Vec<f64> = open.iter().map(|v| v + 1.0).collect();
19818 let low: Vec<f64> = open.iter().map(|v| v - 1.0).collect();
19819 let close: Vec<f64> = open
19820 .iter()
19821 .enumerate()
19822 .map(|(i, v)| v + 0.15 * (i as f64 + 1.0))
19823 .collect();
19824 let volume: Vec<f64> = (0..close.len())
19825 .map(|i| 1000.0 + (i as f64 * 11.0))
19826 .collect();
19827 let timestamps: Vec<i64> = (0..close.len())
19828 .map(|i| 1_700_000_000_000i64 + (i as i64) * 14_400_000)
19829 .collect();
19830 let candles = Candles::new(
19831 timestamps.clone(),
19832 open.clone(),
19833 high.clone(),
19834 low.clone(),
19835 close.clone(),
19836 volume.clone(),
19837 );
19838 let combo = [
19839 ParamKV {
19840 key: "session_mode",
19841 value: ParamValue::EnumString("rolling_bars"),
19842 },
19843 ParamKV {
19844 key: "rolling_period",
19845 value: ParamValue::Int(20),
19846 },
19847 ParamKV {
19848 key: "rolling_days",
19849 value: ParamValue::Int(30),
19850 },
19851 ParamKV {
19852 key: "use_close",
19853 value: ParamValue::Bool(false),
19854 },
19855 ParamKV {
19856 key: "deviation_mode",
19857 value: ParamValue::EnumString("zscore"),
19858 },
19859 ParamKV {
19860 key: "z_window",
19861 value: ParamValue::Int(25),
19862 },
19863 ];
19864 let combos = [IndicatorParamSet { params: &combo }];
19865 let req = IndicatorBatchRequest {
19866 indicator_id: "vwap_deviation_oscillator",
19867 output_id: Some("osc"),
19868 data: IndicatorDataRef::Candles {
19869 candles: &candles,
19870 source: None,
19871 },
19872 combos: &combos,
19873 kernel: Kernel::Auto,
19874 };
19875 let out = compute_cpu_batch(req).unwrap();
19876 let input = VwapDeviationOscillatorInput::from_slices(
19877 ×tamps,
19878 &high,
19879 &low,
19880 &close,
19881 &volume,
19882 VwapDeviationOscillatorParams {
19883 session_mode: Some(VwapDeviationSessionMode::RollingBars),
19884 rolling_period: Some(20),
19885 rolling_days: Some(30),
19886 use_close: Some(false),
19887 deviation_mode: Some(VwapDeviationMode::ZScore),
19888 z_window: Some(25),
19889 pct_vol_lookback: Some(100),
19890 pct_min_sigma: Some(0.1),
19891 abs_vol_lookback: Some(100),
19892 },
19893 );
19894 let direct = vwap_deviation_oscillator_with_kernel(&input, Kernel::Auto.to_non_batch())
19895 .unwrap()
19896 .osc;
19897 let got = out.values_f64.unwrap();
19898 assert_series_eq(&got, &direct, 1e-12);
19899 }
19900
19901 #[test]
19902 fn volume_zone_oscillator_output_matches_direct() {
19903 let close = sample_series();
19904 let volume: Vec<f64> = close
19905 .iter()
19906 .enumerate()
19907 .map(|(i, _)| 1000.0 + (i as f64 * 17.0))
19908 .collect();
19909 let combo = [
19910 ParamKV {
19911 key: "length",
19912 value: ParamValue::Int(14),
19913 },
19914 ParamKV {
19915 key: "intraday_smoothing",
19916 value: ParamValue::Bool(true),
19917 },
19918 ParamKV {
19919 key: "noise_filter",
19920 value: ParamValue::Int(4),
19921 },
19922 ];
19923 let combos = [IndicatorParamSet { params: &combo }];
19924 let req = IndicatorBatchRequest {
19925 indicator_id: "volume_zone_oscillator",
19926 output_id: Some("value"),
19927 data: IndicatorDataRef::CloseVolume {
19928 close: &close,
19929 volume: &volume,
19930 },
19931 combos: &combos,
19932 kernel: Kernel::Auto,
19933 };
19934 let out = compute_cpu_batch(req).unwrap();
19935 let input = VolumeZoneOscillatorInput::from_slices(
19936 &close,
19937 &volume,
19938 VolumeZoneOscillatorParams {
19939 length: Some(14),
19940 intraday_smoothing: Some(true),
19941 noise_filter: Some(4),
19942 },
19943 );
19944 let direct = volume_zone_oscillator_with_kernel(&input, Kernel::Auto.to_non_batch())
19945 .unwrap()
19946 .values;
19947 let got = out.values_f64.unwrap();
19948 assert_series_eq(&got, &direct, 1e-12);
19949 }
19950
19951 #[test]
19952 fn ttm_trend_bool_output_matches_direct() {
19953 let (open, high, low, close) = sample_ohlc();
19954 let combo = [ParamKV {
19955 key: "period",
19956 value: ParamValue::Int(5),
19957 }];
19958 let combos = [IndicatorParamSet { params: &combo }];
19959 let req = IndicatorBatchRequest {
19960 indicator_id: "ttm_trend",
19961 output_id: Some("value"),
19962 data: IndicatorDataRef::Ohlc {
19963 open: &open,
19964 high: &high,
19965 low: &low,
19966 close: &close,
19967 },
19968 combos: &combos,
19969 kernel: Kernel::Auto,
19970 };
19971 let out = compute_cpu_batch(req).unwrap();
19972 let source: Vec<f64> = high.iter().zip(&low).map(|(h, l)| 0.5 * (h + l)).collect();
19973 let input = TtmTrendInput::from_slices(&source, &close, TtmTrendParams { period: Some(5) });
19974 let direct = ttm_trend_with_kernel(&input, Kernel::Auto.to_non_batch())
19975 .unwrap()
19976 .values;
19977 assert_eq!(out.values_bool.unwrap(), direct);
19978 }
19979
19980 fn build_default_params_for_indicator(
19981 info: &crate::indicators::registry::IndicatorInfo,
19982 ) -> Option<Vec<ParamKV<'static>>> {
19983 let mut params: Vec<ParamKV<'static>> = Vec::new();
19984 for p in &info.params {
19985 if p.key.eq_ignore_ascii_case("output") {
19986 continue;
19987 }
19988 let value = if let Some(default) = p.default {
19989 match default {
19990 crate::indicators::registry::ParamValueStatic::Int(v) => {
19991 Some(ParamValue::Int(v))
19992 }
19993 crate::indicators::registry::ParamValueStatic::Float(v) => {
19994 Some(ParamValue::Float(v))
19995 }
19996 crate::indicators::registry::ParamValueStatic::Bool(v) => {
19997 Some(ParamValue::Bool(v))
19998 }
19999 crate::indicators::registry::ParamValueStatic::EnumString(v) => {
20000 Some(ParamValue::EnumString(v))
20001 }
20002 }
20003 } else {
20004 match p.kind {
20005 IndicatorParamKind::Int => {
20006 let mut v = p.min.unwrap_or(14.0).round() as i64;
20007 if v < 0 {
20008 v = 0;
20009 }
20010 if let Some(max) = p.max {
20011 v = v.min(max.round() as i64);
20012 }
20013 Some(ParamValue::Int(v))
20014 }
20015 IndicatorParamKind::Float => {
20016 let mut v = p.min.unwrap_or(1.0);
20017 if !v.is_finite() {
20018 v = 1.0;
20019 }
20020 if let Some(max) = p.max {
20021 v = v.min(max);
20022 }
20023 Some(ParamValue::Float(v))
20024 }
20025 IndicatorParamKind::Bool => Some(ParamValue::Bool(false)),
20026 IndicatorParamKind::EnumString => {
20027 p.enum_values.first().copied().map(ParamValue::EnumString)
20028 }
20029 }
20030 };
20031
20032 match value {
20033 Some(v) => params.push(ParamKV {
20034 key: p.key,
20035 value: v,
20036 }),
20037 None => {
20038 if p.required {
20039 return None;
20040 }
20041 }
20042 }
20043 }
20044 Some(params)
20045 }
20046
20047 fn median_ns(mut samples: Vec<u128>) -> u128 {
20048 samples.sort_unstable();
20049 samples[samples.len() / 2]
20050 }
20051
20052 #[test]
20053 #[ignore]
20054 fn full_cpu_dispatch_perf_sweep_vs_direct_route() {
20055 const LEN: usize = 10_000;
20056 const REPS: usize = 5;
20057
20058 let open: Vec<f64> = (0..LEN).map(|i| 100.0 + (i as f64 * 0.01)).collect();
20059 let high: Vec<f64> = open.iter().map(|v| v + 1.0).collect();
20060 let low: Vec<f64> = open.iter().map(|v| v - 1.0).collect();
20061 let close: Vec<f64> = open.iter().map(|v| v + 0.25).collect();
20062 let volume: Vec<f64> = (0..LEN).map(|i| 1000.0 + (i as f64 * 0.5)).collect();
20063 let timestamp: Vec<i64> = (0..LEN).map(|i| i as i64).collect();
20064 let candles = crate::utilities::data_loader::Candles::new(
20065 timestamp,
20066 open.clone(),
20067 high.clone(),
20068 low.clone(),
20069 close.clone(),
20070 volume.clone(),
20071 );
20072
20073 let infos: Vec<_> = list_indicators()
20074 .iter()
20075 .filter(|i| i.capabilities.supports_cpu_batch)
20076 .collect();
20077 let mut rows: Vec<(String, f64, f64, f64)> = Vec::new();
20078 let mut failures: Vec<String> = Vec::new();
20079
20080 for info in infos {
20081 let Some(output) = info.outputs.first() else {
20082 failures.push(format!("{}: no outputs", info.id));
20083 continue;
20084 };
20085 let output_id = output.id;
20086 let Some(params_vec) = build_default_params_for_indicator(info) else {
20087 failures.push(format!("{}: missing required param defaults", info.id));
20088 continue;
20089 };
20090 let combos = [IndicatorParamSet {
20091 params: params_vec.as_slice(),
20092 }];
20093 let data = match info.input_kind {
20094 IndicatorInputKind::Slice => IndicatorDataRef::Slice {
20095 values: close.as_slice(),
20096 },
20097 IndicatorInputKind::Candles => IndicatorDataRef::Candles {
20098 candles: &candles,
20099 source: None,
20100 },
20101 IndicatorInputKind::Ohlc => IndicatorDataRef::Ohlc {
20102 open: open.as_slice(),
20103 high: high.as_slice(),
20104 low: low.as_slice(),
20105 close: close.as_slice(),
20106 },
20107 IndicatorInputKind::Ohlcv => IndicatorDataRef::Ohlcv {
20108 open: open.as_slice(),
20109 high: high.as_slice(),
20110 low: low.as_slice(),
20111 close: close.as_slice(),
20112 volume: volume.as_slice(),
20113 },
20114 IndicatorInputKind::HighLow => IndicatorDataRef::HighLow {
20115 high: high.as_slice(),
20116 low: low.as_slice(),
20117 },
20118 IndicatorInputKind::CloseVolume => IndicatorDataRef::CloseVolume {
20119 close: close.as_slice(),
20120 volume: volume.as_slice(),
20121 },
20122 };
20123
20124 let req = IndicatorBatchRequest {
20125 indicator_id: info.id,
20126 output_id: Some(output_id),
20127 data,
20128 combos: &combos,
20129 kernel: Kernel::Auto,
20130 };
20131
20132 let dispatch_once = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
20133 compute_cpu_batch(req)
20134 })) {
20135 Ok(Ok(v)) => v,
20136 Ok(Err(e)) => {
20137 failures.push(format!("{}: dispatch error: {}", info.id, e));
20138 continue;
20139 }
20140 Err(_) => {
20141 failures.push(format!("{}: dispatch panic", info.id));
20142 continue;
20143 }
20144 };
20145 let direct_once = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
20146 dispatch_cpu_batch_by_indicator(req, info.id, output_id)
20147 })) {
20148 Ok(Ok(v)) => v,
20149 Ok(Err(e)) => {
20150 failures.push(format!("{}: direct-route error: {}", info.id, e));
20151 continue;
20152 }
20153 Err(_) => {
20154 failures.push(format!("{}: direct-route panic", info.id));
20155 continue;
20156 }
20157 };
20158
20159 if dispatch_once.rows != direct_once.rows || dispatch_once.cols != direct_once.cols {
20160 failures.push(format!(
20161 "{}: shape mismatch dispatch=({},{}) direct=({},{})",
20162 info.id,
20163 dispatch_once.rows,
20164 dispatch_once.cols,
20165 direct_once.rows,
20166 direct_once.cols
20167 ));
20168 continue;
20169 }
20170
20171 let mut dispatch_samples = Vec::with_capacity(REPS);
20172 let mut direct_samples = Vec::with_capacity(REPS);
20173 let mut panicked = false;
20174 for _ in 0..REPS {
20175 let t0 = Instant::now();
20176 let dispatch_iter = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
20177 compute_cpu_batch(req)
20178 }));
20179 if !matches!(dispatch_iter, Ok(Ok(_))) {
20180 failures.push(format!("{}: dispatch panic/error during sample", info.id));
20181 panicked = true;
20182 break;
20183 }
20184 dispatch_samples.push(t0.elapsed().as_nanos());
20185
20186 let t1 = Instant::now();
20187 let direct_iter = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
20188 dispatch_cpu_batch_by_indicator(req, info.id, output_id)
20189 }));
20190 if !matches!(direct_iter, Ok(Ok(_))) {
20191 failures.push(format!(
20192 "{}: direct-route panic/error during sample",
20193 info.id
20194 ));
20195 panicked = true;
20196 break;
20197 }
20198 direct_samples.push(t1.elapsed().as_nanos());
20199 }
20200 if panicked {
20201 continue;
20202 }
20203
20204 let dispatch_median = median_ns(dispatch_samples) as f64 / 1_000_000.0;
20205 let direct_median = median_ns(direct_samples) as f64 / 1_000_000.0;
20206 let delta_pct = if direct_median > 0.0 {
20207 ((dispatch_median - direct_median) / direct_median) * 100.0
20208 } else {
20209 0.0
20210 };
20211 rows.push((
20212 info.id.to_string(),
20213 direct_median,
20214 dispatch_median,
20215 delta_pct,
20216 ));
20217 }
20218
20219 rows.sort_by(|a, b| b.3.partial_cmp(&a.3).unwrap_or(std::cmp::Ordering::Equal));
20220
20221 println!("id,direct_ms,dispatch_ms,delta_pct");
20222 for (id, direct_ms, dispatch_ms, delta_pct) in &rows {
20223 println!("{id},{direct_ms:.6},{dispatch_ms:.6},{delta_pct:.2}");
20224 }
20225 println!("total_indicators={}", rows.len());
20226
20227 assert!(
20228 failures.is_empty(),
20229 "perf sweep failures: {}",
20230 failures.join(" | ")
20231 );
20232 assert!(!rows.is_empty(), "no indicators were swept");
20233 }
20234
20235 #[test]
20236 fn multi_output_requires_output_id() {
20237 let data = sample_series();
20238 let combos: [IndicatorParamSet<'_>; 0] = [];
20239 let req = IndicatorBatchRequest {
20240 indicator_id: "macd",
20241 output_id: None,
20242 data: IndicatorDataRef::Slice { values: &data },
20243 combos: &combos,
20244 kernel: Kernel::Auto,
20245 };
20246 let err = compute_cpu_batch(req).unwrap_err();
20247 assert!(matches!(err, IndicatorDispatchError::InvalidParam { .. }));
20248 }
20249
20250 #[test]
20251 fn multi_output_unknown_output_is_rejected_globally() {
20252 let (open, high, low, close) = sample_ohlc();
20253 let volume: Vec<f64> = (0..close.len())
20254 .map(|i| 1000.0 + (i as f64 * 0.5))
20255 .collect();
20256 let timestamp: Vec<i64> = (0..close.len()).map(|i| i as i64).collect();
20257 let candles = crate::utilities::data_loader::Candles::new(
20258 timestamp,
20259 open.clone(),
20260 high.clone(),
20261 low.clone(),
20262 close.clone(),
20263 volume.clone(),
20264 );
20265
20266 for info in list_indicators()
20267 .iter()
20268 .filter(|i| i.capabilities.supports_cpu_batch && i.outputs.len() > 1)
20269 {
20270 let Some(params_vec) = build_default_params_for_indicator(info) else {
20271 continue;
20272 };
20273 let combos = [IndicatorParamSet {
20274 params: params_vec.as_slice(),
20275 }];
20276 let data = match info.input_kind {
20277 IndicatorInputKind::Slice => IndicatorDataRef::Slice {
20278 values: close.as_slice(),
20279 },
20280 IndicatorInputKind::Candles => IndicatorDataRef::Candles {
20281 candles: &candles,
20282 source: None,
20283 },
20284 IndicatorInputKind::Ohlc => IndicatorDataRef::Ohlc {
20285 open: open.as_slice(),
20286 high: high.as_slice(),
20287 low: low.as_slice(),
20288 close: close.as_slice(),
20289 },
20290 IndicatorInputKind::Ohlcv => IndicatorDataRef::Ohlcv {
20291 open: open.as_slice(),
20292 high: high.as_slice(),
20293 low: low.as_slice(),
20294 close: close.as_slice(),
20295 volume: volume.as_slice(),
20296 },
20297 IndicatorInputKind::HighLow => IndicatorDataRef::HighLow {
20298 high: high.as_slice(),
20299 low: low.as_slice(),
20300 },
20301 IndicatorInputKind::CloseVolume => IndicatorDataRef::CloseVolume {
20302 close: close.as_slice(),
20303 volume: volume.as_slice(),
20304 },
20305 };
20306 let req = IndicatorBatchRequest {
20307 indicator_id: info.id,
20308 output_id: Some("__unknown_output__"),
20309 data,
20310 combos: &combos,
20311 kernel: Kernel::Auto,
20312 };
20313 let err = compute_cpu_batch(req).unwrap_err();
20314 assert!(
20315 matches!(err, IndicatorDispatchError::UnknownOutput { .. }),
20316 "indicator {} returned unexpected error for unknown output: {:?}",
20317 info.id,
20318 err
20319 );
20320 }
20321 }
20322
20323 #[test]
20324 fn strict_mode_rejects_mismatched_input_kind_globally() {
20325 let data = sample_series();
20326 let candles = sample_candles();
20327
20328 for info in list_indicators()
20329 .iter()
20330 .filter(|i| i.capabilities.supports_cpu_batch)
20331 {
20332 let Some(output) = info.outputs.first() else {
20333 continue;
20334 };
20335 let Some(params_vec) = build_default_params_for_indicator(info) else {
20336 continue;
20337 };
20338 let combos = [IndicatorParamSet {
20339 params: params_vec.as_slice(),
20340 }];
20341 let expected = strict_expected_input_kind(info.id, info.input_kind);
20342 let mismatched = match expected {
20343 IndicatorInputKind::Slice => IndicatorDataRef::Candles {
20344 candles: &candles,
20345 source: None,
20346 },
20347 IndicatorInputKind::Candles => IndicatorDataRef::Slice { values: &data },
20348 IndicatorInputKind::Ohlc
20349 | IndicatorInputKind::Ohlcv
20350 | IndicatorInputKind::HighLow
20351 | IndicatorInputKind::CloseVolume => IndicatorDataRef::Slice { values: &data },
20352 };
20353 let req = IndicatorBatchRequest {
20354 indicator_id: info.id,
20355 output_id: Some(output.id),
20356 data: mismatched,
20357 combos: &combos,
20358 kernel: Kernel::Auto,
20359 };
20360 let err = compute_cpu_batch_strict(req).unwrap_err();
20361 assert!(
20362 matches!(err, IndicatorDispatchError::MissingRequiredInput { .. }),
20363 "indicator {} did not reject strict mismatched input: {:?}",
20364 info.id,
20365 err
20366 );
20367 }
20368 }
20369
20370 #[test]
20371 fn full_cpu_dispatch_parity_vs_direct_route_for_all_outputs() {
20372 const LEN: usize = 4096;
20373 let open: Vec<f64> = (0..LEN).map(|i| 100.0 + (i as f64 * 0.01)).collect();
20374 let high: Vec<f64> = open.iter().map(|v| v + 1.0).collect();
20375 let low: Vec<f64> = open.iter().map(|v| v - 1.0).collect();
20376 let close: Vec<f64> = open.iter().map(|v| v + 0.25).collect();
20377 let volume: Vec<f64> = (0..LEN).map(|i| 1000.0 + (i as f64 * 0.5)).collect();
20378 let timestamp: Vec<i64> = (0..LEN).map(|i| i as i64).collect();
20379 let candles = crate::utilities::data_loader::Candles::new(
20380 timestamp,
20381 open.clone(),
20382 high.clone(),
20383 low.clone(),
20384 close.clone(),
20385 volume.clone(),
20386 );
20387
20388 for info in list_indicators()
20389 .iter()
20390 .filter(|i| i.capabilities.supports_cpu_batch)
20391 {
20392 let Some(params_vec) = build_default_params_for_indicator(info) else {
20393 continue;
20394 };
20395 let combos = [IndicatorParamSet {
20396 params: params_vec.as_slice(),
20397 }];
20398 let data = match info.input_kind {
20399 IndicatorInputKind::Slice => IndicatorDataRef::Slice {
20400 values: close.as_slice(),
20401 },
20402 IndicatorInputKind::Candles => IndicatorDataRef::Candles {
20403 candles: &candles,
20404 source: None,
20405 },
20406 IndicatorInputKind::Ohlc => IndicatorDataRef::Ohlc {
20407 open: open.as_slice(),
20408 high: high.as_slice(),
20409 low: low.as_slice(),
20410 close: close.as_slice(),
20411 },
20412 IndicatorInputKind::Ohlcv => IndicatorDataRef::Ohlcv {
20413 open: open.as_slice(),
20414 high: high.as_slice(),
20415 low: low.as_slice(),
20416 close: close.as_slice(),
20417 volume: volume.as_slice(),
20418 },
20419 IndicatorInputKind::HighLow => IndicatorDataRef::HighLow {
20420 high: high.as_slice(),
20421 low: low.as_slice(),
20422 },
20423 IndicatorInputKind::CloseVolume => IndicatorDataRef::CloseVolume {
20424 close: close.as_slice(),
20425 volume: volume.as_slice(),
20426 },
20427 };
20428
20429 for output in info.outputs.iter() {
20430 let req = IndicatorBatchRequest {
20431 indicator_id: info.id,
20432 output_id: Some(output.id),
20433 data,
20434 combos: &combos,
20435 kernel: Kernel::Auto,
20436 };
20437 let generic = compute_cpu_batch(req).unwrap_or_else(|e| {
20438 panic!(
20439 "generic dispatch failed for {}:{}: {}",
20440 info.id, output.id, e
20441 )
20442 });
20443 let direct =
20444 dispatch_cpu_batch_by_indicator(req, info.id, output.id).unwrap_or_else(|e| {
20445 panic!("direct route failed for {}:{}: {}", info.id, output.id, e)
20446 });
20447
20448 assert_eq!(
20449 generic.rows, direct.rows,
20450 "rows mismatch for {}:{}",
20451 info.id, output.id
20452 );
20453 assert_eq!(
20454 generic.cols, direct.cols,
20455 "cols mismatch for {}:{}",
20456 info.id, output.id
20457 );
20458 assert_eq!(
20459 generic.output_id, direct.output_id,
20460 "output id mismatch for {}:{}",
20461 info.id, output.id
20462 );
20463
20464 match (
20465 generic.values_f64.as_ref(),
20466 direct.values_f64.as_ref(),
20467 generic.values_i32.as_ref(),
20468 direct.values_i32.as_ref(),
20469 generic.values_bool.as_ref(),
20470 direct.values_bool.as_ref(),
20471 ) {
20472 (Some(g), Some(d), None, None, None, None) => assert_series_eq(g, d, 1e-9),
20473 (None, None, Some(g), Some(d), None, None) => assert_eq!(g, d),
20474 (None, None, None, None, Some(g), Some(d)) => assert_eq!(g, d),
20475 _ => panic!("value type mismatch for {}:{}", info.id, output.id),
20476 }
20477 }
20478 }
20479 }
20480
20481 #[test]
20482 fn compute_cpu_batch_bull_power_vs_bear_power_matches_direct() {
20483 let open: Vec<f64> = (0..256)
20484 .map(|i| 100.0 + (i as f64 * 0.03).sin() + i as f64 * 0.02)
20485 .collect();
20486 let close: Vec<f64> = open
20487 .iter()
20488 .enumerate()
20489 .map(|(i, &o)| o + (i as f64 * 0.025).cos() * 0.8)
20490 .collect();
20491 let high: Vec<f64> = open
20492 .iter()
20493 .zip(close.iter())
20494 .enumerate()
20495 .map(|(i, (&o, &c))| o.max(c) + 0.5 + (i as f64 * 0.013).sin().abs() * 0.2)
20496 .collect();
20497 let low: Vec<f64> = open
20498 .iter()
20499 .zip(close.iter())
20500 .enumerate()
20501 .map(|(i, (&o, &c))| o.min(c) - 0.4 - (i as f64 * 0.017).cos().abs() * 0.15)
20502 .collect();
20503 let params = [ParamKV {
20504 key: "period",
20505 value: ParamValue::Int(5),
20506 }];
20507 let combos = [IndicatorParamSet { params: ¶ms }];
20508 let candles = crate::utilities::data_loader::Candles::new(
20509 vec![0; close.len()],
20510 open.clone(),
20511 high.clone(),
20512 low.clone(),
20513 close.clone(),
20514 vec![0.0; close.len()],
20515 );
20516
20517 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
20518 indicator_id: "bull_power_vs_bear_power",
20519 output_id: Some("value"),
20520 data: IndicatorDataRef::Candles {
20521 candles: &candles,
20522 source: None,
20523 },
20524 combos: &combos,
20525 kernel: Kernel::Auto,
20526 })
20527 .unwrap();
20528
20529 let direct = bull_power_vs_bear_power_with_kernel(
20530 &BullPowerVsBearPowerInput::from_slices(
20531 &open,
20532 &high,
20533 &low,
20534 &close,
20535 BullPowerVsBearPowerParams { period: Some(5) },
20536 ),
20537 Kernel::Auto,
20538 )
20539 .unwrap();
20540
20541 let values = dispatched.values_f64.as_ref().unwrap();
20542 assert_eq!(values.len(), close.len());
20543 assert_series_eq(values, &direct.values, 1e-9);
20544 }
20545
20546 #[test]
20547 fn compute_cpu_batch_advance_decline_line_matches_direct() {
20548 let close: Vec<f64> = (0..256)
20549 .map(|i| ((i as f64) * 0.05).sin() * 100.0 + ((i as f64) * 0.02).cos() * 25.0)
20550 .collect();
20551 let combos = [IndicatorParamSet { params: &[] }];
20552
20553 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
20554 indicator_id: "advance_decline_line",
20555 output_id: Some("value"),
20556 data: IndicatorDataRef::Slice { values: &close },
20557 combos: &combos,
20558 kernel: Kernel::Auto,
20559 })
20560 .unwrap();
20561
20562 let direct = advance_decline_line_with_kernel(
20563 &AdvanceDeclineLineInput::from_slice(&close, AdvanceDeclineLineParams),
20564 Kernel::Auto,
20565 )
20566 .unwrap();
20567
20568 let values = dispatched.values_f64.as_ref().unwrap();
20569 assert_eq!(values.len(), close.len());
20570 assert_series_eq(values, &direct.values, 1e-9);
20571 }
20572
20573 #[test]
20574 fn compute_cpu_batch_decisionpoint_breadth_swenlin_trading_oscillator_matches_direct() {
20575 let advancing: Vec<f64> = (0..256)
20576 .map(|i| 1500.0 + i as f64 * 0.8 + (i as f64 * 0.07).sin() * 120.0 + 40.0)
20577 .collect();
20578 let declining: Vec<f64> = (0..256)
20579 .map(|i| 1300.0 + i as f64 * 0.5 + (i as f64 * 0.05).cos() * 95.0 + 30.0)
20580 .collect();
20581 let combos = [IndicatorParamSet { params: &[] }];
20582
20583 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
20584 indicator_id: "decisionpoint_breadth_swenlin_trading_oscillator",
20585 output_id: Some("value"),
20586 data: IndicatorDataRef::HighLow {
20587 high: &advancing,
20588 low: &declining,
20589 },
20590 combos: &combos,
20591 kernel: Kernel::Auto,
20592 })
20593 .unwrap();
20594
20595 let direct = decisionpoint_breadth_swenlin_trading_oscillator_with_kernel(
20596 &DecisionPointBreadthSwenlinTradingOscillatorInput::from_slices(
20597 &advancing,
20598 &declining,
20599 DecisionPointBreadthSwenlinTradingOscillatorParams,
20600 ),
20601 Kernel::Auto,
20602 )
20603 .unwrap();
20604
20605 let values = dispatched.values_f64.as_ref().unwrap();
20606 assert_eq!(values.len(), advancing.len());
20607 assert_series_eq(values, &direct.values, 1e-9);
20608 }
20609
20610 #[test]
20611 fn compute_cpu_batch_velocity_acceleration_indicator_matches_direct() {
20612 let open: Vec<f64> = (0..256)
20613 .map(|i| 100.0 + i as f64 * 0.04 + (i as f64 * 0.09).sin())
20614 .collect();
20615 let close: Vec<f64> = open
20616 .iter()
20617 .enumerate()
20618 .map(|(i, &o)| o + (i as f64 * 0.11).cos() * 0.9)
20619 .collect();
20620 let high: Vec<f64> = open
20621 .iter()
20622 .zip(close.iter())
20623 .enumerate()
20624 .map(|(i, (&o, &c))| o.max(c) + 0.6 + (i as f64 * 0.03).sin().abs() * 0.2)
20625 .collect();
20626 let low: Vec<f64> = open
20627 .iter()
20628 .zip(close.iter())
20629 .enumerate()
20630 .map(|(i, (&o, &c))| o.min(c) - 0.6 - (i as f64 * 0.05).cos().abs() * 0.2)
20631 .collect();
20632 let candles = crate::utilities::data_loader::Candles::new(
20633 (0..256_i64).collect(),
20634 open,
20635 high,
20636 low,
20637 close,
20638 vec![1_000.0; 256],
20639 );
20640 let params = [
20641 ParamKV {
20642 key: "length",
20643 value: ParamValue::Int(21),
20644 },
20645 ParamKV {
20646 key: "smooth_length",
20647 value: ParamValue::Int(5),
20648 },
20649 ParamKV {
20650 key: "source",
20651 value: ParamValue::EnumString("hlcc4"),
20652 },
20653 ];
20654 let combos = [IndicatorParamSet { params: ¶ms }];
20655
20656 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
20657 indicator_id: "velocity_acceleration_indicator",
20658 output_id: Some("value"),
20659 data: IndicatorDataRef::Candles {
20660 candles: &candles,
20661 source: Some("hlcc4"),
20662 },
20663 combos: &combos,
20664 kernel: Kernel::Auto,
20665 })
20666 .unwrap();
20667
20668 let direct = velocity_acceleration_indicator_with_kernel(
20669 &VelocityAccelerationIndicatorInput::from_candles(
20670 &candles,
20671 "hlcc4",
20672 VelocityAccelerationIndicatorParams {
20673 length: Some(21),
20674 smooth_length: Some(5),
20675 },
20676 ),
20677 Kernel::Auto,
20678 )
20679 .unwrap();
20680
20681 let values = dispatched.values_f64.as_ref().unwrap();
20682 assert_eq!(values.len(), candles.close.len());
20683 assert_series_eq(values, &direct.values, 1e-9);
20684 }
20685
20686 #[test]
20687 fn compute_cpu_batch_normalized_resonator_matches_direct() {
20688 let open: Vec<f64> = (0..256)
20689 .map(|i| 100.0 + i as f64 * 0.03 + (i as f64 * 0.07).sin())
20690 .collect();
20691 let close: Vec<f64> = open
20692 .iter()
20693 .enumerate()
20694 .map(|(i, &o)| o + (i as f64 * 0.11).cos() * 0.8)
20695 .collect();
20696 let high: Vec<f64> = open
20697 .iter()
20698 .zip(close.iter())
20699 .enumerate()
20700 .map(|(i, (&o, &c))| o.max(c) + 0.6 + (i as f64 * 0.05).sin().abs() * 0.2)
20701 .collect();
20702 let low: Vec<f64> = open
20703 .iter()
20704 .zip(close.iter())
20705 .enumerate()
20706 .map(|(i, (&o, &c))| o.min(c) - 0.6 - (i as f64 * 0.03).cos().abs() * 0.2)
20707 .collect();
20708 let candles = crate::utilities::data_loader::Candles::new(
20709 (0..256_i64).collect(),
20710 open,
20711 high,
20712 low,
20713 close,
20714 vec![1_000.0; 256],
20715 );
20716 let params = [
20717 ParamKV {
20718 key: "period",
20719 value: ParamValue::Int(48),
20720 },
20721 ParamKV {
20722 key: "delta",
20723 value: ParamValue::Float(0.4),
20724 },
20725 ParamKV {
20726 key: "lookback_mult",
20727 value: ParamValue::Float(1.2),
20728 },
20729 ParamKV {
20730 key: "signal_length",
20731 value: ParamValue::Int(7),
20732 },
20733 ParamKV {
20734 key: "source",
20735 value: ParamValue::EnumString("hl2"),
20736 },
20737 ];
20738 let combos = [IndicatorParamSet { params: ¶ms }];
20739
20740 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
20741 indicator_id: "normalized_resonator",
20742 output_id: Some("oscillator"),
20743 data: IndicatorDataRef::Candles {
20744 candles: &candles,
20745 source: Some("hl2"),
20746 },
20747 combos: &combos,
20748 kernel: Kernel::Auto,
20749 })
20750 .unwrap();
20751
20752 let direct = normalized_resonator_with_kernel(
20753 &NormalizedResonatorInput::from_candles(
20754 &candles,
20755 "hl2",
20756 NormalizedResonatorParams {
20757 period: Some(48),
20758 delta: Some(0.4),
20759 lookback_mult: Some(1.2),
20760 signal_length: Some(7),
20761 },
20762 ),
20763 Kernel::Auto,
20764 )
20765 .unwrap();
20766
20767 let values = dispatched.values_f64.as_ref().unwrap();
20768 assert_eq!(values.len(), candles.close.len());
20769 assert_series_eq(values, &direct.oscillator, 1e-9);
20770 }
20771
20772 #[test]
20773 fn compute_cpu_batch_monotonicity_index_matches_direct() {
20774 let open: Vec<f64> = (0..256)
20775 .map(|i| 100.0 + i as f64 * 0.04 + (i as f64 * 0.08).sin())
20776 .collect();
20777 let close: Vec<f64> = open
20778 .iter()
20779 .enumerate()
20780 .map(|(i, &o)| o + (i as f64 * 0.13).cos() * 0.9)
20781 .collect();
20782 let high: Vec<f64> = open
20783 .iter()
20784 .zip(close.iter())
20785 .enumerate()
20786 .map(|(i, (&o, &c))| o.max(c) + 0.6 + (i as f64 * 0.05).sin().abs() * 0.2)
20787 .collect();
20788 let low: Vec<f64> = open
20789 .iter()
20790 .zip(close.iter())
20791 .enumerate()
20792 .map(|(i, (&o, &c))| o.min(c) - 0.6 - (i as f64 * 0.03).cos().abs() * 0.2)
20793 .collect();
20794 let candles = crate::utilities::data_loader::Candles::new(
20795 (0..256_i64).collect(),
20796 open,
20797 high,
20798 low,
20799 close,
20800 vec![1_000.0; 256],
20801 );
20802 let params = [
20803 ParamKV {
20804 key: "length",
20805 value: ParamValue::Int(20),
20806 },
20807 ParamKV {
20808 key: "mode",
20809 value: ParamValue::EnumString("efficiency"),
20810 },
20811 ParamKV {
20812 key: "index_smooth",
20813 value: ParamValue::Int(5),
20814 },
20815 ParamKV {
20816 key: "source",
20817 value: ParamValue::EnumString("close"),
20818 },
20819 ];
20820 let combos = [IndicatorParamSet { params: ¶ms }];
20821
20822 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
20823 indicator_id: "monotonicity_index",
20824 output_id: Some("index"),
20825 data: IndicatorDataRef::Candles {
20826 candles: &candles,
20827 source: Some("close"),
20828 },
20829 combos: &combos,
20830 kernel: Kernel::Auto,
20831 })
20832 .unwrap();
20833
20834 let direct = monotonicity_index_with_kernel(
20835 &MonotonicityIndexInput::from_candles(
20836 &candles,
20837 "close",
20838 MonotonicityIndexParams {
20839 length: Some(20),
20840 mode: Some(MonotonicityIndexMode::Efficiency),
20841 index_smooth: Some(5),
20842 },
20843 ),
20844 Kernel::Auto,
20845 )
20846 .unwrap();
20847
20848 let values = dispatched.values_f64.as_ref().unwrap();
20849 assert_eq!(values.len(), candles.close.len());
20850 assert_series_eq(values, &direct.index, 1e-9);
20851 }
20852
20853 #[test]
20854 fn compute_cpu_batch_half_causal_estimator_matches_direct() {
20855 let len = 240usize;
20856 let slots_per_day = 60usize;
20857 let close: Vec<f64> = (0..len)
20858 .map(|i| {
20859 let slot = (i % slots_per_day) as f64;
20860 let day = (i / slots_per_day) as f64;
20861 1000.0
20862 + day * 4.0
20863 + (slot * 0.13).sin() * 25.0
20864 + (slot * 0.04).cos() * 9.0
20865 + slot * 0.2
20866 })
20867 .collect();
20868 let params = [
20869 ParamKV {
20870 key: "slots_per_day",
20871 value: ParamValue::Int(slots_per_day as i64),
20872 },
20873 ParamKV {
20874 key: "data_period",
20875 value: ParamValue::Int(5),
20876 },
20877 ParamKV {
20878 key: "filter_length",
20879 value: ParamValue::Int(20),
20880 },
20881 ParamKV {
20882 key: "kernel_width",
20883 value: ParamValue::Float(20.0),
20884 },
20885 ParamKV {
20886 key: "kernel_type",
20887 value: ParamValue::EnumString("epanechnikov"),
20888 },
20889 ParamKV {
20890 key: "confidence_adjust",
20891 value: ParamValue::EnumString("symmetric"),
20892 },
20893 ParamKV {
20894 key: "maximum_confidence_adjust",
20895 value: ParamValue::Float(100.0),
20896 },
20897 ParamKV {
20898 key: "enable_expected_value",
20899 value: ParamValue::Bool(true),
20900 },
20901 ParamKV {
20902 key: "extra_smoothing",
20903 value: ParamValue::Int(0),
20904 },
20905 ];
20906 let combos = [IndicatorParamSet { params: ¶ms }];
20907
20908 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
20909 indicator_id: "half_causal_estimator",
20910 output_id: Some("estimate"),
20911 data: IndicatorDataRef::Slice { values: &close },
20912 combos: &combos,
20913 kernel: Kernel::Auto,
20914 })
20915 .unwrap();
20916
20917 let direct = crate::indicators::half_causal_estimator::half_causal_estimator_with_kernel(
20918 &crate::indicators::half_causal_estimator::HalfCausalEstimatorInput::from_slice(
20919 &close,
20920 crate::indicators::half_causal_estimator::HalfCausalEstimatorParams {
20921 slots_per_day: Some(slots_per_day),
20922 data_period: Some(5),
20923 filter_length: Some(20),
20924 kernel_width: Some(20.0),
20925 kernel_type: Some(
20926 crate::indicators::half_causal_estimator::HalfCausalEstimatorKernelType::Epanechnikov,
20927 ),
20928 confidence_adjust: Some(
20929 crate::indicators::half_causal_estimator::HalfCausalEstimatorConfidenceAdjust::Symmetric,
20930 ),
20931 maximum_confidence_adjust: Some(100.0),
20932 enable_expected_value: Some(true),
20933 extra_smoothing: Some(0),
20934 },
20935 ),
20936 Kernel::Auto,
20937 )
20938 .unwrap();
20939
20940 let values = dispatched.values_f64.as_ref().unwrap();
20941 assert_eq!(values.len(), close.len());
20942 assert_series_eq(values, &direct.estimate, 1e-9);
20943 }
20944
20945 #[test]
20946 fn compute_cpu_batch_didi_index_matches_direct() {
20947 let close: Vec<f64> = (0..256)
20948 .map(|i| 100.0 + ((i as f64) * 0.09).sin() * 7.0 + (i as f64) * 0.03)
20949 .collect();
20950 let params = [
20951 ParamKV {
20952 key: "short_length",
20953 value: ParamValue::Int(3),
20954 },
20955 ParamKV {
20956 key: "medium_length",
20957 value: ParamValue::Int(8),
20958 },
20959 ParamKV {
20960 key: "long_length",
20961 value: ParamValue::Int(20),
20962 },
20963 ];
20964 let combos = [IndicatorParamSet { params: ¶ms }];
20965
20966 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
20967 indicator_id: "didi_index",
20968 output_id: Some("short"),
20969 data: IndicatorDataRef::Slice { values: &close },
20970 combos: &combos,
20971 kernel: Kernel::Auto,
20972 })
20973 .unwrap();
20974
20975 let direct = didi_index_with_kernel(
20976 &DidiIndexInput::from_slice(
20977 &close,
20978 DidiIndexParams {
20979 short_length: Some(3),
20980 medium_length: Some(8),
20981 long_length: Some(20),
20982 },
20983 ),
20984 Kernel::Auto,
20985 )
20986 .unwrap();
20987
20988 let values = dispatched.values_f64.as_ref().unwrap();
20989 assert_eq!(values.len(), close.len());
20990 assert_series_eq(values, &direct.short, 1e-9);
20991 }
20992
20993 #[test]
20994 fn compute_cpu_batch_ehlers_autocorrelation_periodogram_matches_direct() {
20995 let close: Vec<f64> = (0..256)
20996 .map(|i| {
20997 let phase = 2.0 * std::f64::consts::PI * i as f64 / 20.0;
20998 phase.sin() + 0.15 * (phase * 0.5).cos()
20999 })
21000 .collect();
21001 let params = [
21002 ParamKV {
21003 key: "min_period",
21004 value: ParamValue::Int(8),
21005 },
21006 ParamKV {
21007 key: "max_period",
21008 value: ParamValue::Int(48),
21009 },
21010 ParamKV {
21011 key: "avg_length",
21012 value: ParamValue::Int(3),
21013 },
21014 ParamKV {
21015 key: "enhance",
21016 value: ParamValue::Bool(true),
21017 },
21018 ];
21019 let combos = [IndicatorParamSet { params: ¶ms }];
21020
21021 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21022 indicator_id: "ehlers_autocorrelation_periodogram",
21023 output_id: Some("dominant_cycle"),
21024 data: IndicatorDataRef::Slice { values: &close },
21025 combos: &combos,
21026 kernel: Kernel::Auto,
21027 })
21028 .unwrap();
21029
21030 let direct = ehlers_autocorrelation_periodogram_with_kernel(
21031 &EhlersAutocorrelationPeriodogramInput::from_slice(
21032 &close,
21033 EhlersAutocorrelationPeriodogramParams {
21034 min_period: Some(8),
21035 max_period: Some(48),
21036 avg_length: Some(3),
21037 enhance: Some(true),
21038 },
21039 ),
21040 Kernel::Auto,
21041 )
21042 .unwrap();
21043
21044 let values = dispatched.values_f64.as_ref().unwrap();
21045 assert_eq!(values.len(), close.len());
21046 assert_series_eq(values, &direct.dominant_cycle, 1e-9);
21047 }
21048
21049 #[test]
21050 fn compute_cpu_batch_ehlers_linear_extrapolation_predictor_matches_direct() {
21051 let close: Vec<f64> = (0..256)
21052 .map(|i| 100.0 + ((i as f64) * 0.09).sin() * 2.0 + (i as f64 * 0.03))
21053 .collect();
21054 let params = [
21055 ParamKV {
21056 key: "high_pass_length",
21057 value: ParamValue::Int(125),
21058 },
21059 ParamKV {
21060 key: "low_pass_length",
21061 value: ParamValue::Int(12),
21062 },
21063 ParamKV {
21064 key: "gain",
21065 value: ParamValue::Float(0.7),
21066 },
21067 ParamKV {
21068 key: "bars_forward",
21069 value: ParamValue::Int(5),
21070 },
21071 ParamKV {
21072 key: "signal_mode",
21073 value: ParamValue::EnumString("predict_filter_crosses"),
21074 },
21075 ];
21076 let combos = [IndicatorParamSet { params: ¶ms }];
21077
21078 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21079 indicator_id: "ehlers_linear_extrapolation_predictor",
21080 output_id: Some("prediction"),
21081 data: IndicatorDataRef::Slice { values: &close },
21082 combos: &combos,
21083 kernel: Kernel::Auto,
21084 })
21085 .unwrap();
21086
21087 let direct = ehlers_linear_extrapolation_predictor_with_kernel(
21088 &EhlersLinearExtrapolationPredictorInput::from_slice(
21089 &close,
21090 EhlersLinearExtrapolationPredictorParams {
21091 high_pass_length: Some(125),
21092 low_pass_length: Some(12),
21093 gain: Some(0.7),
21094 bars_forward: Some(5),
21095 signal_mode: Some("predict_filter_crosses".to_string()),
21096 },
21097 ),
21098 Kernel::Auto,
21099 )
21100 .unwrap();
21101
21102 let values = dispatched.values_f64.as_ref().unwrap();
21103 assert_eq!(values.len(), close.len());
21104 assert_series_eq(values, &direct.prediction, 1e-9);
21105 }
21106
21107 #[test]
21108 fn compute_cpu_batch_grover_llorens_cycle_oscillator_matches_direct() {
21109 let mut open = Vec::with_capacity(256);
21110 let mut high = Vec::with_capacity(256);
21111 let mut low = Vec::with_capacity(256);
21112 let mut close = Vec::with_capacity(256);
21113 let mut prev = 100.0;
21114 for i in 0..256 {
21115 let x = i as f64;
21116 let wave = (x * 0.11).sin() * 2.4 + (x * 0.037).cos() * 1.3;
21117 let o = prev + wave * 0.35;
21118 let c = o + (x * 0.19).sin() * 1.1 - (x * 0.07).cos() * 0.4;
21119 let h = o.max(c) + 0.6 + (x * 0.03).sin().abs() * 0.25;
21120 let l = o.min(c) - 0.6 - (x * 0.02).cos().abs() * 0.25;
21121 open.push(o);
21122 high.push(h);
21123 low.push(l);
21124 close.push(c);
21125 prev = c;
21126 }
21127
21128 let params = [
21129 ParamKV {
21130 key: "length",
21131 value: ParamValue::Int(60),
21132 },
21133 ParamKV {
21134 key: "mult",
21135 value: ParamValue::Float(8.0),
21136 },
21137 ParamKV {
21138 key: "source",
21139 value: ParamValue::EnumString("hlc3"),
21140 },
21141 ParamKV {
21142 key: "smooth",
21143 value: ParamValue::Bool(true),
21144 },
21145 ParamKV {
21146 key: "rsi_period",
21147 value: ParamValue::Int(14),
21148 },
21149 ];
21150 let combos = [IndicatorParamSet { params: ¶ms }];
21151
21152 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21153 indicator_id: "grover_llorens_cycle_oscillator",
21154 output_id: Some("value"),
21155 data: IndicatorDataRef::Ohlc {
21156 open: &open,
21157 high: &high,
21158 low: &low,
21159 close: &close,
21160 },
21161 combos: &combos,
21162 kernel: Kernel::Auto,
21163 })
21164 .unwrap();
21165
21166 let direct = grover_llorens_cycle_oscillator_with_kernel(
21167 &GroverLlorensCycleOscillatorInput::from_slices(
21168 &open,
21169 &high,
21170 &low,
21171 &close,
21172 GroverLlorensCycleOscillatorParams {
21173 length: Some(60),
21174 mult: Some(8.0),
21175 source: Some("hlc3".to_string()),
21176 smooth: Some(true),
21177 rsi_period: Some(14),
21178 },
21179 ),
21180 Kernel::Auto,
21181 )
21182 .unwrap();
21183
21184 let values = dispatched.values_f64.as_ref().unwrap();
21185 assert_eq!(values.len(), close.len());
21186 assert_series_eq(values, &direct.values, 1e-9);
21187 }
21188
21189 #[test]
21190 fn compute_cpu_batch_historical_volatility_matches_direct() {
21191 let close: Vec<f64> = (0..256)
21192 .map(|i| 100.0 + ((i as f64) * 0.02).sin() + (i as f64 * 0.1))
21193 .collect();
21194 let params = [
21195 ParamKV {
21196 key: "lookback",
21197 value: ParamValue::Int(20),
21198 },
21199 ParamKV {
21200 key: "annualization_days",
21201 value: ParamValue::Float(252.0),
21202 },
21203 ];
21204 let combos = [IndicatorParamSet { params: ¶ms }];
21205
21206 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21207 indicator_id: "historical_volatility",
21208 output_id: Some("value"),
21209 data: IndicatorDataRef::Slice { values: &close },
21210 combos: &combos,
21211 kernel: Kernel::Auto,
21212 })
21213 .unwrap();
21214
21215 let direct = historical_volatility_with_kernel(
21216 &HistoricalVolatilityInput::from_slice(
21217 &close,
21218 HistoricalVolatilityParams {
21219 lookback: Some(20),
21220 annualization_days: Some(252.0),
21221 },
21222 ),
21223 Kernel::Auto,
21224 )
21225 .unwrap();
21226
21227 let values = dispatched.values_f64.as_ref().unwrap();
21228 assert_eq!(values.len(), close.len());
21229 assert_series_eq(values, &direct.values, 1e-9);
21230 }
21231
21232 #[test]
21233 fn compute_cpu_batch_stochastic_distance_matches_direct() {
21234 let close: Vec<f64> = (0..256)
21235 .map(|i| 100.0 + (i as f64 * 0.07).sin() * 1.3 + i as f64 * 0.03)
21236 .collect();
21237 let params = [
21238 ParamKV {
21239 key: "lookback_length",
21240 value: ParamValue::Int(50),
21241 },
21242 ParamKV {
21243 key: "length1",
21244 value: ParamValue::Int(8),
21245 },
21246 ParamKV {
21247 key: "length2",
21248 value: ParamValue::Int(4),
21249 },
21250 ParamKV {
21251 key: "ob_level",
21252 value: ParamValue::Int(40),
21253 },
21254 ParamKV {
21255 key: "os_level",
21256 value: ParamValue::Int(-40),
21257 },
21258 ];
21259 let combos = [IndicatorParamSet { params: ¶ms }];
21260
21261 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21262 indicator_id: "stochastic_distance",
21263 output_id: Some("oscillator"),
21264 data: IndicatorDataRef::Slice { values: &close },
21265 combos: &combos,
21266 kernel: Kernel::Auto,
21267 })
21268 .unwrap();
21269
21270 let direct = stochastic_distance_with_kernel(
21271 &StochasticDistanceInput::from_slice(
21272 &close,
21273 StochasticDistanceParams {
21274 lookback_length: Some(50),
21275 length1: Some(8),
21276 length2: Some(4),
21277 ob_level: Some(40),
21278 os_level: Some(-40),
21279 },
21280 ),
21281 Kernel::Auto,
21282 )
21283 .unwrap();
21284
21285 let values = dispatched.values_f64.as_ref().unwrap();
21286 assert_eq!(values.len(), close.len());
21287 assert_series_eq(values, &direct.oscillator, 1e-9);
21288 }
21289
21290 #[test]
21291 fn compute_cpu_batch_adaptive_bandpass_trigger_oscillator_matches_direct() {
21292 let close: Vec<f64> = (0..256)
21293 .map(|i| 100.0 + (i as f64 * 0.07).sin() * 1.3 + (i as f64 * 0.03).cos() * 0.6)
21294 .collect();
21295 let params = [
21296 ParamKV {
21297 key: "delta",
21298 value: ParamValue::Float(0.1),
21299 },
21300 ParamKV {
21301 key: "alpha",
21302 value: ParamValue::Float(0.07),
21303 },
21304 ];
21305 let combos = [IndicatorParamSet { params: ¶ms }];
21306
21307 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21308 indicator_id: "adaptive_bandpass_trigger_oscillator",
21309 output_id: Some("in_phase"),
21310 data: IndicatorDataRef::Slice { values: &close },
21311 combos: &combos,
21312 kernel: Kernel::Auto,
21313 })
21314 .unwrap();
21315
21316 let direct = adaptive_bandpass_trigger_oscillator_with_kernel(
21317 &AdaptiveBandpassTriggerOscillatorInput::from_slice(
21318 &close,
21319 AdaptiveBandpassTriggerOscillatorParams {
21320 delta: Some(0.1),
21321 alpha: Some(0.07),
21322 },
21323 ),
21324 Kernel::Auto,
21325 )
21326 .unwrap();
21327
21328 let values = dispatched.values_f64.as_ref().unwrap();
21329 assert_eq!(values.len(), close.len());
21330 assert_series_eq(values, &direct.in_phase, 1e-9);
21331 }
21332
21333 #[test]
21334 fn compute_cpu_batch_squeeze_index_matches_direct() {
21335 let close: Vec<f64> = (0..256)
21336 .map(|i| 100.0 + ((i as f64) * 0.11).sin() * 1.2 + (i as f64 * 0.02))
21337 .collect();
21338 let params = [
21339 ParamKV {
21340 key: "conv",
21341 value: ParamValue::Float(50.0),
21342 },
21343 ParamKV {
21344 key: "length",
21345 value: ParamValue::Int(20),
21346 },
21347 ];
21348 let combos = [IndicatorParamSet { params: ¶ms }];
21349
21350 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21351 indicator_id: "squeeze_index",
21352 output_id: Some("value"),
21353 data: IndicatorDataRef::Slice { values: &close },
21354 combos: &combos,
21355 kernel: Kernel::Auto,
21356 })
21357 .unwrap();
21358
21359 let direct = squeeze_index_with_kernel(
21360 &SqueezeIndexInput::from_slice(
21361 &close,
21362 SqueezeIndexParams {
21363 conv: Some(50.0),
21364 length: Some(20),
21365 },
21366 ),
21367 Kernel::Auto,
21368 )
21369 .unwrap();
21370
21371 let values = dispatched.values_f64.as_ref().unwrap();
21372 assert_eq!(values.len(), close.len());
21373 assert_series_eq(values, &direct.values, 1e-9);
21374 }
21375
21376 #[test]
21377 fn compute_cpu_batch_absolute_strength_index_oscillator_matches_direct() {
21378 let close: Vec<f64> = (0..256)
21379 .map(|i| 100.0 + ((i as f64) * 0.17).sin() * 1.8 + ((i % 7) as f64 - 3.0) * 0.04)
21380 .collect();
21381 let params = [
21382 ParamKV {
21383 key: "ema_length",
21384 value: ParamValue::Int(21),
21385 },
21386 ParamKV {
21387 key: "signal_length",
21388 value: ParamValue::Int(34),
21389 },
21390 ];
21391 let combos = [IndicatorParamSet { params: ¶ms }];
21392
21393 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21394 indicator_id: "absolute_strength_index_oscillator",
21395 output_id: Some("oscillator"),
21396 data: IndicatorDataRef::Slice { values: &close },
21397 combos: &combos,
21398 kernel: Kernel::Auto,
21399 })
21400 .unwrap();
21401
21402 let direct = absolute_strength_index_oscillator_with_kernel(
21403 &AbsoluteStrengthIndexOscillatorInput::from_slice(
21404 &close,
21405 AbsoluteStrengthIndexOscillatorParams {
21406 ema_length: Some(21),
21407 signal_length: Some(34),
21408 },
21409 ),
21410 Kernel::Auto,
21411 )
21412 .unwrap();
21413
21414 let values = dispatched.values_f64.as_ref().unwrap();
21415 assert_eq!(values.len(), close.len());
21416 assert_series_eq(values, &direct.oscillator, 1e-9);
21417 }
21418
21419 #[test]
21420 fn compute_cpu_batch_premier_rsi_oscillator_matches_direct() {
21421 let close: Vec<f64> = (0..256)
21422 .map(|i| 100.0 + ((i as f64) * 0.13).sin() * 1.4 + ((i % 11) as f64 - 5.0) * 0.03)
21423 .collect();
21424 let params = [
21425 ParamKV {
21426 key: "rsi_length",
21427 value: ParamValue::Int(14),
21428 },
21429 ParamKV {
21430 key: "stoch_length",
21431 value: ParamValue::Int(8),
21432 },
21433 ParamKV {
21434 key: "smooth_length",
21435 value: ParamValue::Int(25),
21436 },
21437 ];
21438 let combos = [IndicatorParamSet { params: ¶ms }];
21439
21440 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21441 indicator_id: "premier_rsi_oscillator",
21442 output_id: Some("value"),
21443 data: IndicatorDataRef::Slice { values: &close },
21444 combos: &combos,
21445 kernel: Kernel::Auto,
21446 })
21447 .unwrap();
21448
21449 let direct = premier_rsi_oscillator_with_kernel(
21450 &PremierRsiOscillatorInput::from_slice(
21451 &close,
21452 PremierRsiOscillatorParams {
21453 rsi_length: Some(14),
21454 stoch_length: Some(8),
21455 smooth_length: Some(25),
21456 },
21457 ),
21458 Kernel::Auto,
21459 )
21460 .unwrap();
21461
21462 let values = dispatched.values_f64.as_ref().unwrap();
21463 assert_eq!(values.len(), close.len());
21464 assert_series_eq(values, &direct.values, 1e-9);
21465 }
21466
21467 #[test]
21468 fn compute_cpu_batch_multi_length_stochastic_average_matches_direct() {
21469 let open: Vec<f64> = (0..256)
21470 .map(|i| 100.0 + i as f64 * 0.03 + (i as f64 * 0.09).sin())
21471 .collect();
21472 let close: Vec<f64> = open
21473 .iter()
21474 .enumerate()
21475 .map(|(i, &o)| o + (i as f64 * 0.13).cos() * 0.8)
21476 .collect();
21477 let high: Vec<f64> = open
21478 .iter()
21479 .zip(close.iter())
21480 .enumerate()
21481 .map(|(i, (&o, &c))| o.max(c) + 0.5 + (i as f64 * 0.05).sin().abs() * 0.2)
21482 .collect();
21483 let low: Vec<f64> = open
21484 .iter()
21485 .zip(close.iter())
21486 .enumerate()
21487 .map(|(i, (&o, &c))| o.min(c) - 0.5 - (i as f64 * 0.07).cos().abs() * 0.2)
21488 .collect();
21489 let candles = crate::utilities::data_loader::Candles::new(
21490 (0..256_i64).collect(),
21491 open,
21492 high,
21493 low,
21494 close,
21495 vec![1_000.0; 256],
21496 );
21497 let params = [
21498 ParamKV {
21499 key: "length",
21500 value: ParamValue::Int(14),
21501 },
21502 ParamKV {
21503 key: "presmooth",
21504 value: ParamValue::Int(10),
21505 },
21506 ParamKV {
21507 key: "premethod",
21508 value: ParamValue::EnumString("sma"),
21509 },
21510 ParamKV {
21511 key: "postsmooth",
21512 value: ParamValue::Int(10),
21513 },
21514 ParamKV {
21515 key: "postmethod",
21516 value: ParamValue::EnumString("lsma"),
21517 },
21518 ParamKV {
21519 key: "source",
21520 value: ParamValue::EnumString("hlc3"),
21521 },
21522 ];
21523 let combos = [IndicatorParamSet { params: ¶ms }];
21524
21525 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21526 indicator_id: "multi_length_stochastic_average",
21527 output_id: Some("value"),
21528 data: IndicatorDataRef::Candles {
21529 candles: &candles,
21530 source: Some("hlc3"),
21531 },
21532 combos: &combos,
21533 kernel: Kernel::Auto,
21534 })
21535 .unwrap();
21536
21537 let direct = multi_length_stochastic_average_with_kernel(
21538 &MultiLengthStochasticAverageInput::from_candles(
21539 &candles,
21540 "hlc3",
21541 MultiLengthStochasticAverageParams {
21542 length: Some(14),
21543 presmooth: Some(10),
21544 premethod: Some("sma".to_string()),
21545 postsmooth: Some(10),
21546 postmethod: Some("lsma".to_string()),
21547 },
21548 ),
21549 Kernel::Auto,
21550 )
21551 .unwrap();
21552
21553 let values = dispatched.values_f64.as_ref().unwrap();
21554 assert_eq!(values.len(), candles.close.len());
21555 assert_series_eq(values, &direct.values, 1e-9);
21556 }
21557
21558 #[test]
21559 fn compute_cpu_batch_hull_butterfly_oscillator_matches_direct() {
21560 let open: Vec<f64> = (0..256)
21561 .map(|i| 100.0 + i as f64 * 0.03 + (i as f64 * 0.09).sin())
21562 .collect();
21563 let close: Vec<f64> = open
21564 .iter()
21565 .enumerate()
21566 .map(|(i, &o)| o + (i as f64 * 0.13).cos() * 0.8)
21567 .collect();
21568 let high: Vec<f64> = open
21569 .iter()
21570 .zip(close.iter())
21571 .enumerate()
21572 .map(|(i, (&o, &c))| o.max(c) + 0.5 + (i as f64 * 0.05).sin().abs() * 0.2)
21573 .collect();
21574 let low: Vec<f64> = open
21575 .iter()
21576 .zip(close.iter())
21577 .enumerate()
21578 .map(|(i, (&o, &c))| o.min(c) - 0.5 - (i as f64 * 0.07).cos().abs() * 0.2)
21579 .collect();
21580 let candles = crate::utilities::data_loader::Candles::new(
21581 (0..256_i64).collect(),
21582 open,
21583 high,
21584 low,
21585 close,
21586 vec![1_000.0; 256],
21587 );
21588 let params = [
21589 ParamKV {
21590 key: "length",
21591 value: ParamValue::Int(14),
21592 },
21593 ParamKV {
21594 key: "mult",
21595 value: ParamValue::Float(1.75),
21596 },
21597 ParamKV {
21598 key: "source",
21599 value: ParamValue::EnumString("hlc3"),
21600 },
21601 ];
21602 let combos = [IndicatorParamSet { params: ¶ms }];
21603
21604 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21605 indicator_id: "hull_butterfly_oscillator",
21606 output_id: Some("oscillator"),
21607 data: IndicatorDataRef::Candles {
21608 candles: &candles,
21609 source: Some("hlc3"),
21610 },
21611 combos: &combos,
21612 kernel: Kernel::Auto,
21613 })
21614 .unwrap();
21615
21616 let direct = hull_butterfly_oscillator_with_kernel(
21617 &HullButterflyOscillatorInput::from_candles(
21618 &candles,
21619 "hlc3",
21620 HullButterflyOscillatorParams {
21621 length: Some(14),
21622 mult: Some(1.75),
21623 },
21624 ),
21625 Kernel::Auto,
21626 )
21627 .unwrap();
21628
21629 let values = dispatched.values_f64.as_ref().unwrap();
21630 assert_eq!(values.len(), candles.close.len());
21631 assert_series_eq(values, &direct.oscillator, 1e-9);
21632 }
21633
21634 #[test]
21635 fn compute_cpu_batch_fibonacci_trailing_stop_matches_direct() {
21636 let open: Vec<f64> = (0..256)
21637 .map(|i| 100.0 + i as f64 * 0.03 + (i as f64 * 0.09).sin())
21638 .collect();
21639 let close: Vec<f64> = open
21640 .iter()
21641 .enumerate()
21642 .map(|(i, &o)| o + (i as f64 * 0.13).cos() * 0.8)
21643 .collect();
21644 let high: Vec<f64> = open
21645 .iter()
21646 .zip(close.iter())
21647 .enumerate()
21648 .map(|(i, (&o, &c))| o.max(c) + 0.5 + (i as f64 * 0.05).sin().abs() * 0.2)
21649 .collect();
21650 let low: Vec<f64> = open
21651 .iter()
21652 .zip(close.iter())
21653 .enumerate()
21654 .map(|(i, (&o, &c))| o.min(c) - 0.5 - (i as f64 * 0.07).cos().abs() * 0.2)
21655 .collect();
21656
21657 let params = [
21658 ParamKV {
21659 key: "left_bars",
21660 value: ParamValue::Int(12),
21661 },
21662 ParamKV {
21663 key: "right_bars",
21664 value: ParamValue::Int(2),
21665 },
21666 ParamKV {
21667 key: "level",
21668 value: ParamValue::Float(-0.236),
21669 },
21670 ParamKV {
21671 key: "trigger",
21672 value: ParamValue::EnumString("wick"),
21673 },
21674 ];
21675 let combos = [IndicatorParamSet { params: ¶ms }];
21676
21677 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21678 indicator_id: "fibonacci_trailing_stop",
21679 output_id: Some("trailing_stop"),
21680 data: IndicatorDataRef::Ohlc {
21681 open: &open,
21682 high: &high,
21683 low: &low,
21684 close: &close,
21685 },
21686 combos: &combos,
21687 kernel: Kernel::Auto,
21688 })
21689 .unwrap();
21690
21691 let direct = fibonacci_trailing_stop_with_kernel(
21692 &FibonacciTrailingStopInput::from_slices(
21693 &high,
21694 &low,
21695 &close,
21696 FibonacciTrailingStopParams {
21697 left_bars: Some(12),
21698 right_bars: Some(2),
21699 level: Some(-0.236),
21700 trigger: Some("wick".to_string()),
21701 },
21702 ),
21703 Kernel::Auto,
21704 )
21705 .unwrap();
21706
21707 let values = dispatched.values_f64.as_ref().unwrap();
21708 assert_eq!(values.len(), close.len());
21709 assert_series_eq(values, &direct.trailing_stop, 1e-9);
21710 }
21711
21712 #[test]
21713 fn compute_cpu_batch_volume_energy_reservoirs_matches_direct() {
21714 let open: Vec<f64> = (0..256)
21715 .map(|i| 100.0 + i as f64 * 0.03 + (i as f64 * 0.08).sin())
21716 .collect();
21717 let close: Vec<f64> = open
21718 .iter()
21719 .enumerate()
21720 .map(|(i, &o)| o + (i as f64 * 0.11).cos() * 0.9)
21721 .collect();
21722 let high: Vec<f64> = open
21723 .iter()
21724 .zip(close.iter())
21725 .enumerate()
21726 .map(|(i, (&o, &c))| o.max(c) + 0.6 + (i as f64 * 0.03).sin().abs() * 0.25)
21727 .collect();
21728 let low: Vec<f64> = open
21729 .iter()
21730 .zip(close.iter())
21731 .enumerate()
21732 .map(|(i, (&o, &c))| o.min(c) - 0.6 - (i as f64 * 0.05).cos().abs() * 0.2)
21733 .collect();
21734 let volume: Vec<f64> = (0..256)
21735 .map(|i| 1_000.0 + i as f64 * 4.0 + (i as f64 * 0.09).sin() * 180.0)
21736 .collect();
21737
21738 let params = [
21739 ParamKV {
21740 key: "length",
21741 value: ParamValue::Int(18),
21742 },
21743 ParamKV {
21744 key: "sensitivity",
21745 value: ParamValue::Float(1.7),
21746 },
21747 ];
21748 let combos = [IndicatorParamSet { params: ¶ms }];
21749
21750 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21751 indicator_id: "volume_energy_reservoirs",
21752 output_id: Some("momentum"),
21753 data: IndicatorDataRef::Ohlcv {
21754 open: &open,
21755 high: &high,
21756 low: &low,
21757 close: &close,
21758 volume: &volume,
21759 },
21760 combos: &combos,
21761 kernel: Kernel::Auto,
21762 })
21763 .unwrap();
21764
21765 let direct = volume_energy_reservoirs_with_kernel(
21766 &VolumeEnergyReservoirsInput::from_slices(
21767 &high,
21768 &low,
21769 &close,
21770 &volume,
21771 VolumeEnergyReservoirsParams {
21772 length: Some(18),
21773 sensitivity: Some(1.7),
21774 },
21775 ),
21776 Kernel::Auto,
21777 )
21778 .unwrap();
21779
21780 let values = dispatched.values_f64.as_ref().unwrap();
21781 assert_eq!(values.len(), close.len());
21782 assert_series_eq(values, &direct.momentum, 1e-9);
21783 }
21784
21785 #[test]
21786 fn compute_cpu_batch_neighboring_trailing_stop_matches_direct() {
21787 let open: Vec<f64> = (0..256)
21788 .map(|i| 100.0 + i as f64 * 0.04 + (i as f64 * 0.07).sin())
21789 .collect();
21790 let close: Vec<f64> = open
21791 .iter()
21792 .enumerate()
21793 .map(|(i, &o)| o + (i as f64 * 0.11).cos() * 0.85)
21794 .collect();
21795 let high: Vec<f64> = open
21796 .iter()
21797 .zip(close.iter())
21798 .enumerate()
21799 .map(|(i, (&o, &c))| o.max(c) + 0.55 + (i as f64 * 0.03).sin().abs() * 0.2)
21800 .collect();
21801 let low: Vec<f64> = open
21802 .iter()
21803 .zip(close.iter())
21804 .enumerate()
21805 .map(|(i, (&o, &c))| o.min(c) - 0.55 - (i as f64 * 0.05).cos().abs() * 0.2)
21806 .collect();
21807
21808 let params = [
21809 ParamKV {
21810 key: "buffer_size",
21811 value: ParamValue::Int(180),
21812 },
21813 ParamKV {
21814 key: "k",
21815 value: ParamValue::Int(30),
21816 },
21817 ParamKV {
21818 key: "percentile",
21819 value: ParamValue::Float(87.5),
21820 },
21821 ParamKV {
21822 key: "smooth",
21823 value: ParamValue::Int(4),
21824 },
21825 ];
21826 let combos = [IndicatorParamSet { params: ¶ms }];
21827
21828 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21829 indicator_id: "neighboring_trailing_stop",
21830 output_id: Some("trailing_stop"),
21831 data: IndicatorDataRef::Ohlc {
21832 open: &open,
21833 high: &high,
21834 low: &low,
21835 close: &close,
21836 },
21837 combos: &combos,
21838 kernel: Kernel::Auto,
21839 })
21840 .unwrap();
21841
21842 let direct = neighboring_trailing_stop_with_kernel(
21843 &NeighboringTrailingStopInput::from_slices(
21844 &high,
21845 &low,
21846 &close,
21847 NeighboringTrailingStopParams {
21848 buffer_size: Some(180),
21849 k: Some(30),
21850 percentile: Some(87.5),
21851 smooth: Some(4),
21852 },
21853 ),
21854 Kernel::Auto,
21855 )
21856 .unwrap();
21857
21858 let values = dispatched.values_f64.as_ref().unwrap();
21859 assert_eq!(values.len(), close.len());
21860 assert_series_eq(values, &direct.trailing_stop, 1e-9);
21861 }
21862
21863 #[test]
21864 fn compute_cpu_batch_macd_wave_signal_pro_matches_direct() {
21865 let open: Vec<f64> = (0..256)
21866 .map(|i| 100.0 + i as f64 * 0.08 + ((i as f64) * 0.05).sin() * 0.7)
21867 .collect();
21868 let close: Vec<f64> = open
21869 .iter()
21870 .enumerate()
21871 .map(|(i, o)| o + ((i as f64) * 0.09).cos() * 0.9)
21872 .collect();
21873 let high: Vec<f64> = open
21874 .iter()
21875 .zip(close.iter())
21876 .enumerate()
21877 .map(|(i, (&o, &c))| o.max(c) + 0.55 + (i as f64 * 0.03).sin().abs() * 0.2)
21878 .collect();
21879 let low: Vec<f64> = open
21880 .iter()
21881 .zip(close.iter())
21882 .enumerate()
21883 .map(|(i, (&o, &c))| o.min(c) - 0.55 - (i as f64 * 0.05).cos().abs() * 0.2)
21884 .collect();
21885 let combos = [IndicatorParamSet { params: &[] }];
21886
21887 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21888 indicator_id: "macd_wave_signal_pro",
21889 output_id: Some("line_convergence"),
21890 data: IndicatorDataRef::Ohlc {
21891 open: &open,
21892 high: &high,
21893 low: &low,
21894 close: &close,
21895 },
21896 combos: &combos,
21897 kernel: Kernel::Auto,
21898 })
21899 .unwrap();
21900
21901 let direct = macd_wave_signal_pro_with_kernel(
21902 &MacdWaveSignalProInput::from_slices(&open, &high, &low, &close, Default::default()),
21903 Kernel::Auto,
21904 )
21905 .unwrap();
21906
21907 let values = dispatched.values_f64.as_ref().unwrap();
21908 assert_eq!(values.len(), close.len());
21909 assert_series_eq(values, &direct.line_convergence, 1e-9);
21910 }
21911
21912 #[test]
21913 fn compute_cpu_batch_hema_trend_levels_matches_direct() {
21914 let open: Vec<f64> = (0..256)
21915 .map(|i| 100.0 + i as f64 * 0.05 + ((i as f64) * 0.09).sin() * 1.3)
21916 .collect();
21917 let close: Vec<f64> = open
21918 .iter()
21919 .enumerate()
21920 .map(|(i, o)| o + ((i as f64) * 0.07).cos() * 1.1)
21921 .collect();
21922 let high: Vec<f64> = open
21923 .iter()
21924 .zip(close.iter())
21925 .enumerate()
21926 .map(|(i, (&o, &c))| o.max(c) + 0.65 + (i as f64 * 0.03).sin().abs() * 0.25)
21927 .collect();
21928 let low: Vec<f64> = open
21929 .iter()
21930 .zip(close.iter())
21931 .enumerate()
21932 .map(|(i, (&o, &c))| o.min(c) - 0.65 - (i as f64 * 0.05).cos().abs() * 0.25)
21933 .collect();
21934 let params = [
21935 ParamKV {
21936 key: "fast_length",
21937 value: ParamValue::Int(20),
21938 },
21939 ParamKV {
21940 key: "slow_length",
21941 value: ParamValue::Int(40),
21942 },
21943 ];
21944 let combos = [IndicatorParamSet { params: ¶ms }];
21945
21946 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21947 indicator_id: "hema_trend_levels",
21948 output_id: Some("bullish_test_level"),
21949 data: IndicatorDataRef::Ohlc {
21950 open: &open,
21951 high: &high,
21952 low: &low,
21953 close: &close,
21954 },
21955 combos: &combos,
21956 kernel: Kernel::Auto,
21957 })
21958 .unwrap();
21959
21960 let direct = hema_trend_levels_with_kernel(
21961 &HemaTrendLevelsInput::from_slices(
21962 &open,
21963 &high,
21964 &low,
21965 &close,
21966 HemaTrendLevelsParams {
21967 fast_length: Some(20),
21968 slow_length: Some(40),
21969 },
21970 ),
21971 Kernel::Auto,
21972 )
21973 .unwrap();
21974
21975 let values = dispatched.values_f64.as_ref().unwrap();
21976 assert_eq!(values.len(), close.len());
21977 assert_series_eq(values, &direct.bullish_test_level, 1e-9);
21978 }
21979
21980 #[test]
21981 fn compute_cpu_batch_fibonacci_entry_bands_matches_direct() {
21982 let open: Vec<f64> = (0..256)
21983 .map(|i| 100.0 + i as f64 * 0.05 + ((i as f64) * 0.09).sin() * 1.3)
21984 .collect();
21985 let close: Vec<f64> = open
21986 .iter()
21987 .enumerate()
21988 .map(|(i, o)| o + ((i as f64) * 0.07).cos() * 1.1)
21989 .collect();
21990 let high: Vec<f64> = open
21991 .iter()
21992 .zip(close.iter())
21993 .enumerate()
21994 .map(|(i, (&o, &c))| o.max(c) + 0.65 + (i as f64 * 0.03).sin().abs() * 0.25)
21995 .collect();
21996 let low: Vec<f64> = open
21997 .iter()
21998 .zip(close.iter())
21999 .enumerate()
22000 .map(|(i, (&o, &c))| o.min(c) - 0.65 - (i as f64 * 0.05).cos().abs() * 0.25)
22001 .collect();
22002 let params = [
22003 ParamKV {
22004 key: "source",
22005 value: ParamValue::EnumString("hlc3"),
22006 },
22007 ParamKV {
22008 key: "length",
22009 value: ParamValue::Int(20),
22010 },
22011 ParamKV {
22012 key: "atr_length",
22013 value: ParamValue::Int(11),
22014 },
22015 ParamKV {
22016 key: "use_atr",
22017 value: ParamValue::Bool(true),
22018 },
22019 ParamKV {
22020 key: "tp_aggressiveness",
22021 value: ParamValue::EnumString("medium"),
22022 },
22023 ];
22024 let combos = [IndicatorParamSet { params: ¶ms }];
22025
22026 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
22027 indicator_id: "fibonacci_entry_bands",
22028 output_id: Some("tp_long_band"),
22029 data: IndicatorDataRef::Ohlc {
22030 open: &open,
22031 high: &high,
22032 low: &low,
22033 close: &close,
22034 },
22035 combos: &combos,
22036 kernel: Kernel::Auto,
22037 })
22038 .unwrap();
22039
22040 let direct = fibonacci_entry_bands_with_kernel(
22041 &FibonacciEntryBandsInput::from_slices(
22042 &open,
22043 &high,
22044 &low,
22045 &close,
22046 FibonacciEntryBandsParams {
22047 source: Some("hlc3".to_string()),
22048 length: Some(20),
22049 atr_length: Some(11),
22050 use_atr: Some(true),
22051 tp_aggressiveness: Some("medium".to_string()),
22052 },
22053 ),
22054 Kernel::Auto,
22055 )
22056 .unwrap();
22057
22058 let values = dispatched.values_f64.as_ref().unwrap();
22059 assert_eq!(values.len(), close.len());
22060 assert_series_eq(values, &direct.tp_long_band, 1e-9);
22061 }
22062
22063 #[test]
22064 fn compute_cpu_batch_vertical_horizontal_filter_matches_direct() {
22065 let close: Vec<f64> = (0..256)
22066 .map(|i| 100.0 + ((i as f64) * 0.02).sin() + (i as f64 * 0.1))
22067 .collect();
22068 let params = [ParamKV {
22069 key: "length",
22070 value: ParamValue::Int(28),
22071 }];
22072 let combos = [IndicatorParamSet { params: ¶ms }];
22073
22074 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
22075 indicator_id: "vertical_horizontal_filter",
22076 output_id: Some("value"),
22077 data: IndicatorDataRef::Slice { values: &close },
22078 combos: &combos,
22079 kernel: Kernel::Auto,
22080 })
22081 .unwrap();
22082
22083 let direct = vertical_horizontal_filter_with_kernel(
22084 &VerticalHorizontalFilterInput::from_slice(
22085 &close,
22086 VerticalHorizontalFilterParams { length: Some(28) },
22087 ),
22088 Kernel::Auto,
22089 )
22090 .unwrap();
22091
22092 let values = dispatched.values_f64.as_ref().unwrap();
22093 assert_eq!(values.len(), close.len());
22094 assert_series_eq(values, &direct.values, 1e-9);
22095 }
22096
22097 #[test]
22098 fn compute_cpu_batch_intraday_momentum_index_matches_direct() {
22099 let open: Vec<f64> = (0..256)
22100 .map(|i| 100.0 + i as f64 * 0.1 + ((i as f64) * 0.05).cos() * 0.2)
22101 .collect();
22102 let high: Vec<f64> = open.iter().map(|v| v + 0.9).collect();
22103 let low: Vec<f64> = open.iter().map(|v| v - 0.8).collect();
22104 let close: Vec<f64> = open
22105 .iter()
22106 .enumerate()
22107 .map(|(i, o)| o + ((i as f64) * 0.09).sin() * 0.6)
22108 .collect();
22109 let params = [
22110 ParamKV {
22111 key: "length",
22112 value: ParamValue::Int(14),
22113 },
22114 ParamKV {
22115 key: "length_ma",
22116 value: ParamValue::Int(6),
22117 },
22118 ParamKV {
22119 key: "mult",
22120 value: ParamValue::Float(2.0),
22121 },
22122 ParamKV {
22123 key: "length_bb",
22124 value: ParamValue::Int(20),
22125 },
22126 ParamKV {
22127 key: "apply_smoothing",
22128 value: ParamValue::Bool(true),
22129 },
22130 ParamKV {
22131 key: "low_band",
22132 value: ParamValue::Int(10),
22133 },
22134 ];
22135 let combos = [IndicatorParamSet { params: ¶ms }];
22136
22137 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
22138 indicator_id: "intraday_momentum_index",
22139 output_id: Some("imi"),
22140 data: IndicatorDataRef::Ohlc {
22141 open: &open,
22142 high: &high,
22143 low: &low,
22144 close: &close,
22145 },
22146 combos: &combos,
22147 kernel: Kernel::Auto,
22148 })
22149 .unwrap();
22150
22151 let direct = intraday_momentum_index_with_kernel(
22152 &IntradayMomentumIndexInput::from_slices(
22153 &open,
22154 &close,
22155 IntradayMomentumIndexParams {
22156 length: Some(14),
22157 length_ma: Some(6),
22158 mult: Some(2.0),
22159 length_bb: Some(20),
22160 apply_smoothing: Some(true),
22161 low_band: Some(10),
22162 },
22163 ),
22164 Kernel::Auto,
22165 )
22166 .unwrap();
22167
22168 let values = dispatched.values_f64.as_ref().unwrap();
22169 assert_eq!(values.len(), close.len());
22170 assert_series_eq(values, &direct.imi, 1e-9);
22171 }
22172
22173 #[test]
22174 fn compute_cpu_batch_atr_percentile_matches_direct() {
22175 let high: Vec<f64> = (0..256)
22176 .map(|i| 100.0 + i as f64 * 0.1 + ((i as f64) * 0.03).sin().abs())
22177 .collect();
22178 let low: Vec<f64> = high
22179 .iter()
22180 .enumerate()
22181 .map(|(i, h)| h - 0.75 - ((i as f64) * 0.02).cos().abs() * 0.2)
22182 .collect();
22183 let close: Vec<f64> = low
22184 .iter()
22185 .zip(high.iter())
22186 .enumerate()
22187 .map(|(i, (l, h))| l + (h - l) * (0.35 + 0.2 * ((i as f64) * 0.05).sin().abs()))
22188 .collect();
22189 let params = [
22190 ParamKV {
22191 key: "atr_length",
22192 value: ParamValue::Int(10),
22193 },
22194 ParamKV {
22195 key: "percentile_length",
22196 value: ParamValue::Int(20),
22197 },
22198 ];
22199 let combos = [IndicatorParamSet { params: ¶ms }];
22200
22201 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
22202 indicator_id: "atr_percentile",
22203 output_id: Some("value"),
22204 data: IndicatorDataRef::Ohlc {
22205 open: &close,
22206 high: &high,
22207 low: &low,
22208 close: &close,
22209 },
22210 combos: &combos,
22211 kernel: Kernel::Auto,
22212 })
22213 .unwrap();
22214
22215 let direct = atr_percentile_with_kernel(
22216 &AtrPercentileInput::from_slices(
22217 &high,
22218 &low,
22219 &close,
22220 AtrPercentileParams {
22221 atr_length: Some(10),
22222 percentile_length: Some(20),
22223 },
22224 ),
22225 Kernel::Auto,
22226 )
22227 .unwrap();
22228
22229 let values = dispatched.values_f64.as_ref().unwrap();
22230 assert_eq!(values.len(), close.len());
22231 assert_series_eq(values, &direct.values, 1e-9);
22232 }
22233
22234 #[test]
22235 fn compute_cpu_batch_demand_index_matches_direct() {
22236 let high: Vec<f64> = (0..256)
22237 .map(|i| 100.0 + i as f64 * 0.15 + ((i as f64) * 0.03).sin().abs())
22238 .collect();
22239 let low: Vec<f64> = high
22240 .iter()
22241 .enumerate()
22242 .map(|(i, h)| h - 0.9 - ((i as f64) * 0.04).cos().abs() * 0.3)
22243 .collect();
22244 let close: Vec<f64> = low
22245 .iter()
22246 .zip(high.iter())
22247 .enumerate()
22248 .map(|(i, (l, h))| l + (h - l) * (0.25 + 0.5 * ((i as f64) * 0.07).sin().abs()))
22249 .collect();
22250 let open: Vec<f64> = close
22251 .iter()
22252 .enumerate()
22253 .map(|(i, c)| c - 0.2 + ((i as f64) * 0.05).cos() * 0.1)
22254 .collect();
22255 let volume: Vec<f64> = (0..256)
22256 .map(|i| 1000.0 + (i as f64) * 3.0 + ((i as f64) * 0.11).sin().abs() * 40.0)
22257 .collect();
22258 let params = [
22259 ParamKV {
22260 key: "len_bs",
22261 value: ParamValue::Int(19),
22262 },
22263 ParamKV {
22264 key: "len_bs_ma",
22265 value: ParamValue::Int(19),
22266 },
22267 ParamKV {
22268 key: "len_di_ma",
22269 value: ParamValue::Int(19),
22270 },
22271 ParamKV {
22272 key: "ma_type",
22273 value: ParamValue::EnumString("ema"),
22274 },
22275 ];
22276 let combos = [IndicatorParamSet { params: ¶ms }];
22277
22278 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
22279 indicator_id: "demand_index",
22280 output_id: Some("demand_index"),
22281 data: IndicatorDataRef::Ohlcv {
22282 open: &open,
22283 high: &high,
22284 low: &low,
22285 close: &close,
22286 volume: &volume,
22287 },
22288 combos: &combos,
22289 kernel: Kernel::Auto,
22290 })
22291 .unwrap();
22292
22293 let direct = demand_index_with_kernel(
22294 &DemandIndexInput::from_slices(
22295 &high,
22296 &low,
22297 &close,
22298 &volume,
22299 DemandIndexParams {
22300 len_bs: Some(19),
22301 len_bs_ma: Some(19),
22302 len_di_ma: Some(19),
22303 ma_type: Some("ema".to_string()),
22304 },
22305 ),
22306 Kernel::Auto,
22307 )
22308 .unwrap();
22309
22310 let values = dispatched.values_f64.as_ref().unwrap();
22311 assert_eq!(values.len(), close.len());
22312 assert_series_eq(values, &direct.demand_index, 1e-9);
22313 }
22314
22315 #[test]
22316 fn compute_cpu_batch_vwap_zscore_with_signals_matches_direct() {
22317 let close: Vec<f64> = (0..192).map(|i| 100.0 + (i as f64 * 0.15)).collect();
22318 let volume: Vec<f64> = (0..192).map(|i| 1_000.0 + (i as f64 * 2.0)).collect();
22319 let req = IndicatorBatchRequest {
22320 indicator_id: "vwap_zscore_with_signals",
22321 output_id: Some("zvwap"),
22322 data: IndicatorDataRef::CloseVolume {
22323 close: &close,
22324 volume: &volume,
22325 },
22326 combos: &[IndicatorParamSet {
22327 params: &[
22328 ParamKV {
22329 key: "length",
22330 value: ParamValue::Int(20),
22331 },
22332 ParamKV {
22333 key: "upper_bottom",
22334 value: ParamValue::Float(2.5),
22335 },
22336 ParamKV {
22337 key: "lower_bottom",
22338 value: ParamValue::Float(-2.5),
22339 },
22340 ],
22341 }],
22342 kernel: Kernel::Auto,
22343 };
22344
22345 let out = compute_cpu_batch(req).unwrap();
22346 let values = out.values_f64.as_ref().unwrap();
22347 let direct = vwap_zscore_with_signals_with_kernel(
22348 &VwapZscoreWithSignalsInput::from_slices(
22349 &close,
22350 &volume,
22351 VwapZscoreWithSignalsParams {
22352 length: Some(20),
22353 upper_bottom: Some(2.5),
22354 lower_bottom: Some(-2.5),
22355 },
22356 ),
22357 Kernel::Auto,
22358 )
22359 .unwrap();
22360 assert_eq!(out.rows, 1);
22361 assert_eq!(out.cols, close.len());
22362 assert_series_eq(values, &direct.zvwap, 1e-9);
22363 }
22364
22365 #[test]
22366 fn compute_cpu_batch_gopalakrishnan_range_index_matches_direct() {
22367 let high: Vec<f64> = (0..256)
22368 .map(|i| 100.0 + i as f64 * 0.1 + ((i as f64) * 0.03).sin().abs())
22369 .collect();
22370 let low: Vec<f64> = high
22371 .iter()
22372 .enumerate()
22373 .map(|(i, h)| h - 0.75 - ((i as f64) * 0.02).cos().abs() * 0.2)
22374 .collect();
22375 let params = [ParamKV {
22376 key: "length",
22377 value: ParamValue::Int(5),
22378 }];
22379 let combos = [IndicatorParamSet { params: ¶ms }];
22380
22381 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
22382 indicator_id: "gopalakrishnan_range_index",
22383 output_id: Some("value"),
22384 data: IndicatorDataRef::HighLow {
22385 high: &high,
22386 low: &low,
22387 },
22388 combos: &combos,
22389 kernel: Kernel::Auto,
22390 })
22391 .unwrap();
22392
22393 let direct = gopalakrishnan_range_index_with_kernel(
22394 &GopalakrishnanRangeIndexInput::from_slices(
22395 &high,
22396 &low,
22397 GopalakrishnanRangeIndexParams { length: Some(5) },
22398 ),
22399 Kernel::Auto,
22400 )
22401 .unwrap();
22402
22403 let values = dispatched.values_f64.as_ref().unwrap();
22404 assert_eq!(values.len(), high.len());
22405 assert_series_eq(values, &direct.values, 1e-9);
22406 }
22407}