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, AccumulationSwingIndexParams,
11};
12use crate::indicators::acosc::{acosc_with_kernel, AcoscInput, AcoscParams};
13use crate::indicators::ad::{ad_with_kernel, AdInput, AdParams};
14use crate::indicators::adaptive_bandpass_trigger_oscillator::{
15 adaptive_bandpass_trigger_oscillator_with_kernel, AdaptiveBandpassTriggerOscillatorInput,
16 AdaptiveBandpassTriggerOscillatorParams,
17};
18use crate::indicators::adaptive_macd::{
19 adaptive_macd_with_kernel, AdaptiveMacdInput, AdaptiveMacdParams,
20};
21use crate::indicators::adaptive_momentum_oscillator::{
22 adaptive_momentum_oscillator_with_kernel, AdaptiveMomentumOscillatorInput,
23 AdaptiveMomentumOscillatorParams,
24};
25use crate::indicators::adaptive_schaff_trend_cycle::{
26 adaptive_schaff_trend_cycle_with_kernel, AdaptiveSchaffTrendCycleInput,
27 AdaptiveSchaffTrendCycleParams,
28};
29use crate::indicators::adjustable_ma_alternating_extremities::{
30 adjustable_ma_alternating_extremities_with_kernel, AdjustableMaAlternatingExtremitiesInput,
31 AdjustableMaAlternatingExtremitiesParams,
32};
33use crate::indicators::adosc::{adosc_with_kernel, AdoscInput, AdoscParams};
34use crate::indicators::advance_decline_line::{
35 advance_decline_line_with_kernel, AdvanceDeclineLineInput, AdvanceDeclineLineParams,
36};
37use crate::indicators::adx::{adx_with_kernel, AdxInput, AdxParams};
38use crate::indicators::adxr::{adxr_with_kernel, AdxrInput, AdxrParams};
39use crate::indicators::alligator::{alligator_with_kernel, AlligatorInput, AlligatorParams};
40use crate::indicators::alphatrend::{alphatrend_with_kernel, AlphaTrendInput, AlphaTrendParams};
41use crate::indicators::andean_oscillator::{
42 andean_oscillator_with_kernel, AndeanOscillatorInput, AndeanOscillatorParams,
43};
44use crate::indicators::ao::{ao_into_slice, AoInput, AoParams};
45use crate::indicators::apo::{apo_with_kernel, ApoInput, ApoParams};
46use crate::indicators::aroon::{aroon_with_kernel, AroonInput, AroonParams};
47use crate::indicators::aroonosc::{aroon_osc_with_kernel, AroonOscInput, AroonOscParams};
48use crate::indicators::aso::{aso_with_kernel, AsoInput, AsoParams};
49use crate::indicators::atr::{atr_with_kernel, AtrInput, AtrParams};
50use crate::indicators::atr_percentile::{
51 atr_percentile_with_kernel, AtrPercentileInput, AtrPercentileParams,
52};
53use crate::indicators::autocorrelation_indicator::{
54 autocorrelation_indicator_with_kernel, AutocorrelationIndicatorInput,
55 AutocorrelationIndicatorParams,
56};
57use crate::indicators::avsl::{avsl_with_kernel, AvslInput, AvslParams};
58use crate::indicators::bandpass::{bandpass_with_kernel, BandPassInput, BandPassParams};
59use crate::indicators::bollinger_bands::{
60 bollinger_bands_with_kernel, BollingerBandsInput, BollingerBandsParams,
61};
62use crate::indicators::bollinger_bands_width::{
63 bollinger_bands_width_with_kernel, BollingerBandsWidthInput, BollingerBandsWidthParams,
64};
65use crate::indicators::bop::{bop_with_kernel, BopInput, BopParams};
66use crate::indicators::bull_power_vs_bear_power::{
67 bull_power_vs_bear_power_with_kernel, BullPowerVsBearPowerInput, BullPowerVsBearPowerParams,
68};
69use crate::indicators::bulls_v_bears::{
70 bulls_v_bears_with_kernel, BullsVBearsCalculationMethod, BullsVBearsInput, BullsVBearsMaType,
71 BullsVBearsParams,
72};
73use crate::indicators::candle_strength_oscillator::{
74 candle_strength_oscillator_with_kernel, CandleStrengthOscillatorInput,
75 CandleStrengthOscillatorParams,
76};
77use crate::indicators::cci::{cci_with_kernel, CciInput, CciParams};
78use crate::indicators::cci_cycle::{cci_cycle_with_kernel, CciCycleInput, CciCycleParams};
79use crate::indicators::cfo::{cfo_with_kernel, CfoInput, CfoParams};
80use crate::indicators::chande::{chande_with_kernel, ChandeInput, ChandeParams};
81use crate::indicators::chandelier_exit::{
82 chandelier_exit_with_kernel, ChandelierExitInput, ChandelierExitParams,
83};
84use crate::indicators::chop::{chop_with_kernel, ChopInput, ChopParams};
85use crate::indicators::cksp::{cksp_with_kernel, CkspInput, CkspParams};
86use crate::indicators::cmo::{cmo_with_kernel, CmoInput, CmoParams};
87use crate::indicators::coppock::{coppock_with_kernel, CoppockInput, CoppockParams};
88use crate::indicators::correl_hl::{correl_hl_with_kernel, CorrelHlInput, CorrelHlParams};
89use crate::indicators::correlation_cycle::{
90 correlation_cycle_with_kernel, CorrelationCycleInput, CorrelationCycleParams,
91};
92use crate::indicators::cyberpunk_value_trend_analyzer::{
93 cyberpunk_value_trend_analyzer_with_kernel, CyberpunkValueTrendAnalyzerInput,
94 CyberpunkValueTrendAnalyzerParams,
95};
96use crate::indicators::cycle_channel_oscillator::{
97 cycle_channel_oscillator_with_kernel, CycleChannelOscillatorInput, CycleChannelOscillatorParams,
98};
99use crate::indicators::daily_factor::{
100 daily_factor_with_kernel, DailyFactorInput, DailyFactorParams,
101};
102use crate::indicators::damiani_volatmeter::{
103 damiani_volatmeter_with_kernel, DamianiVolatmeterInput, DamianiVolatmeterParams,
104};
105use crate::indicators::decisionpoint_breadth_swenlin_trading_oscillator::{
106 decisionpoint_breadth_swenlin_trading_oscillator_with_kernel,
107 DecisionPointBreadthSwenlinTradingOscillatorInput,
108 DecisionPointBreadthSwenlinTradingOscillatorParams,
109};
110use crate::indicators::demand_index::{
111 demand_index_with_kernel, DemandIndexInput, DemandIndexParams,
112};
113use crate::indicators::deviation::{deviation_with_kernel, DeviationInput, DeviationParams};
114use crate::indicators::devstop::{devstop_with_kernel, DevStopInput, DevStopParams};
115use crate::indicators::di::{di_with_kernel, DiInput, DiParams};
116use crate::indicators::didi_index::{didi_index_with_kernel, DidiIndexInput, DidiIndexParams};
117use crate::indicators::directional_imbalance_index::{
118 directional_imbalance_index_with_kernel, DirectionalImbalanceIndexInput,
119 DirectionalImbalanceIndexParams,
120};
121use crate::indicators::disparity_index::{
122 disparity_index_into_slice, DisparityIndexInput, DisparityIndexParams,
123};
124use crate::indicators::dm::{dm_with_kernel, DmInput, DmParams};
125use crate::indicators::donchian::{donchian_with_kernel, DonchianInput, DonchianParams};
126use crate::indicators::donchian_channel_width::{
127 donchian_channel_width_into_slice, DonchianChannelWidthInput, DonchianChannelWidthParams,
128};
129use crate::indicators::dpo::{dpo_with_kernel, DpoInput, DpoParams};
130use crate::indicators::dti::{dti_into_slice, DtiInput, DtiParams};
131use crate::indicators::dual_ulcer_index::{
132 dual_ulcer_index_with_kernel, DualUlcerIndexInput, DualUlcerIndexParams,
133};
134use crate::indicators::dvdiqqe::{dvdiqqe_with_kernel, DvdiqqeInput, DvdiqqeParams};
135use crate::indicators::dx::{dx_batch_with_kernel, dx_into_slice, DxBatchRange, DxInput, DxParams};
136use crate::indicators::dynamic_momentum_index::{
137 dynamic_momentum_index_into_slice, dynamic_momentum_index_with_kernel,
138 DynamicMomentumIndexInput, DynamicMomentumIndexParams,
139};
140use crate::indicators::efi::{efi_with_kernel, EfiInput, EfiParams};
141use crate::indicators::ehlers_adaptive_cg::{
142 ehlers_adaptive_cg_with_kernel, EhlersAdaptiveCgInput, EhlersAdaptiveCgParams,
143};
144use crate::indicators::ehlers_adaptive_cyber_cycle::{
145 ehlers_adaptive_cyber_cycle_with_kernel, EhlersAdaptiveCyberCycleInput,
146 EhlersAdaptiveCyberCycleParams,
147};
148use crate::indicators::ehlers_autocorrelation_periodogram::{
149 ehlers_autocorrelation_periodogram_with_kernel, EhlersAutocorrelationPeriodogramInput,
150 EhlersAutocorrelationPeriodogramParams,
151};
152use crate::indicators::ehlers_data_sampling_relative_strength_indicator::{
153 ehlers_data_sampling_relative_strength_indicator_with_kernel,
154 EhlersDataSamplingRelativeStrengthIndicatorInput,
155 EhlersDataSamplingRelativeStrengthIndicatorParams,
156};
157use crate::indicators::ehlers_detrending_filter::{
158 ehlers_detrending_filter_with_kernel, EhlersDetrendingFilterInput, EhlersDetrendingFilterParams,
159};
160use crate::indicators::ehlers_fm_demodulator::{
161 ehlers_fm_demodulator_with_kernel, EhlersFmDemodulatorInput, EhlersFmDemodulatorParams,
162};
163use crate::indicators::ehlers_linear_extrapolation_predictor::{
164 ehlers_linear_extrapolation_predictor_with_kernel, EhlersLinearExtrapolationPredictorInput,
165 EhlersLinearExtrapolationPredictorParams,
166};
167use crate::indicators::ehlers_simple_cycle_indicator::{
168 ehlers_simple_cycle_indicator_with_kernel, EhlersSimpleCycleIndicatorInput,
169 EhlersSimpleCycleIndicatorParams,
170};
171use crate::indicators::ehlers_smoothed_adaptive_momentum::{
172 ehlers_smoothed_adaptive_momentum_with_kernel, EhlersSmoothedAdaptiveMomentumInput,
173 EhlersSmoothedAdaptiveMomentumParams,
174};
175use crate::indicators::emd::{emd_with_kernel, EmdInput, EmdParams};
176use crate::indicators::emd_trend::{emd_trend_with_kernel, EmdTrendInput, EmdTrendParams};
177use crate::indicators::emv::{emv_with_kernel, EmvInput};
178use crate::indicators::er::{er_with_kernel, ErInput, ErParams};
179use crate::indicators::eri::{eri_with_kernel, EriInput, EriParams};
180use crate::indicators::evasive_supertrend::{
181 evasive_supertrend_with_kernel, EvasiveSuperTrendInput, EvasiveSuperTrendParams,
182};
183use crate::indicators::ewma_volatility::{
184 ewma_volatility_with_kernel, EwmaVolatilityInput, EwmaVolatilityParams,
185};
186use crate::indicators::exponential_trend::{
187 exponential_trend_with_kernel, ExponentialTrendInput, ExponentialTrendParams,
188};
189use crate::indicators::fibonacci_entry_bands::{
190 fibonacci_entry_bands_with_kernel, FibonacciEntryBandsInput, FibonacciEntryBandsParams,
191};
192use crate::indicators::fibonacci_trailing_stop::{
193 fibonacci_trailing_stop_with_kernel, FibonacciTrailingStopInput, FibonacciTrailingStopParams,
194};
195use crate::indicators::fisher::{fisher_with_kernel, FisherInput, FisherParams};
196use crate::indicators::forward_backward_exponential_oscillator::{
197 forward_backward_exponential_oscillator_with_kernel, ForwardBackwardExponentialOscillatorInput,
198 ForwardBackwardExponentialOscillatorParams,
199};
200use crate::indicators::fosc::{fosc_with_kernel, FoscInput, FoscParams};
201use crate::indicators::fractal_dimension_index::{
202 fractal_dimension_index_with_kernel, FractalDimensionIndexInput, FractalDimensionIndexParams,
203};
204use crate::indicators::fvg_positioning_average::{
205 fvg_positioning_average_with_kernel, FvgPositioningAverageInput, FvgPositioningAverageParams,
206};
207use crate::indicators::fvg_trailing_stop::{
208 fvg_trailing_stop_with_kernel, FvgTrailingStopInput, FvgTrailingStopParams,
209};
210use crate::indicators::garman_klass_volatility::{
211 garman_klass_volatility_with_kernel, GarmanKlassVolatilityInput, GarmanKlassVolatilityParams,
212};
213use crate::indicators::gatorosc::{gatorosc_with_kernel, GatorOscInput, GatorOscParams};
214use crate::indicators::geometric_bias_oscillator::{
215 geometric_bias_oscillator_with_kernel, GeometricBiasOscillatorInput,
216 GeometricBiasOscillatorParams,
217};
218use crate::indicators::gmma_oscillator::{
219 gmma_oscillator_with_kernel, GmmaOscillatorInput, GmmaOscillatorParams,
220};
221use crate::indicators::goertzel_cycle_composite_wave::{
222 goertzel_cycle_composite_wave_into_slice, GoertzelCycleCompositeWaveInput,
223 GoertzelCycleCompositeWaveParams, GoertzelDetrendMode,
224};
225use crate::indicators::gopalakrishnan_range_index::{
226 gopalakrishnan_range_index_with_kernel, GopalakrishnanRangeIndexInput,
227 GopalakrishnanRangeIndexParams,
228};
229use crate::indicators::grover_llorens_cycle_oscillator::{
230 grover_llorens_cycle_oscillator_with_kernel, GroverLlorensCycleOscillatorInput,
231 GroverLlorensCycleOscillatorParams,
232};
233use crate::indicators::half_causal_estimator::{
234 half_causal_estimator_with_kernel, HalfCausalEstimatorConfidenceAdjust,
235 HalfCausalEstimatorInput, HalfCausalEstimatorKernelType, HalfCausalEstimatorParams,
236};
237use crate::indicators::halftrend::{halftrend_with_kernel, HalfTrendInput, HalfTrendParams};
238use crate::indicators::hema_trend_levels::{
239 hema_trend_levels_with_kernel, HemaTrendLevelsInput, HemaTrendLevelsParams,
240};
241use crate::indicators::historical_volatility::{
242 historical_volatility_with_kernel, HistoricalVolatilityInput, HistoricalVolatilityParams,
243};
244use crate::indicators::historical_volatility_percentile::{
245 historical_volatility_percentile_with_kernel, HistoricalVolatilityPercentileInput,
246 HistoricalVolatilityPercentileParams,
247};
248use crate::indicators::historical_volatility_rank::{
249 historical_volatility_rank_with_kernel, HistoricalVolatilityRankInput,
250 HistoricalVolatilityRankParams,
251};
252use crate::indicators::hull_butterfly_oscillator::{
253 hull_butterfly_oscillator_with_kernel, HullButterflyOscillatorInput,
254 HullButterflyOscillatorParams,
255};
256use crate::indicators::hypertrend::{hypertrend_with_kernel, HyperTrendInput, HyperTrendParams};
257use crate::indicators::ichimoku_oscillator::{
258 ichimoku_oscillator_with_kernel, IchimokuOscillatorInput, IchimokuOscillatorNormalizeMode,
259 IchimokuOscillatorParams,
260};
261use crate::indicators::ict_propulsion_block::{
262 ict_propulsion_block_with_kernel, IctPropulsionBlockInput, IctPropulsionBlockMitigationPrice,
263 IctPropulsionBlockParams,
264};
265use crate::indicators::ift_rsi::{ift_rsi_with_kernel, IftRsiInput, IftRsiParams};
266use crate::indicators::impulse_macd::{
267 impulse_macd_with_kernel, ImpulseMacdInput, ImpulseMacdParams,
268};
269use crate::indicators::intraday_momentum_index::{
270 intraday_momentum_index_with_kernel, IntradayMomentumIndexInput, IntradayMomentumIndexParams,
271};
272use crate::indicators::kairi_relative_index::{
273 kairi_relative_index_into_slice, KairiRelativeIndexInput, KairiRelativeIndexParams,
274};
275use crate::indicators::kase_peak_oscillator_with_divergences::{
276 kase_peak_oscillator_with_divergences_with_kernel, KasePeakOscillatorWithDivergencesInput,
277 KasePeakOscillatorWithDivergencesParams,
278};
279use crate::indicators::kaufmanstop::{
280 kaufmanstop_with_kernel, KaufmanstopInput, KaufmanstopParams,
281};
282use crate::indicators::kdj::{kdj_with_kernel, KdjInput, KdjParams};
283use crate::indicators::keltner::{keltner_with_kernel, KeltnerInput, KeltnerParams};
284use crate::indicators::keltner_channel_width_oscillator::{
285 keltner_channel_width_oscillator_with_kernel, KeltnerChannelWidthOscillatorInput,
286 KeltnerChannelWidthOscillatorParams,
287};
288use crate::indicators::kst::{kst_with_kernel, KstInput, KstParams};
289use crate::indicators::kurtosis::{kurtosis_with_kernel, KurtosisInput, KurtosisParams};
290use crate::indicators::kvo::{kvo_with_kernel, KvoInput, KvoParams};
291use crate::indicators::l1_ehlers_phasor::{
292 l1_ehlers_phasor_with_kernel, L1EhlersPhasorInput, L1EhlersPhasorParams,
293};
294use crate::indicators::l2_ehlers_signal_to_noise::{
295 l2_ehlers_signal_to_noise_with_kernel, L2EhlersSignalToNoiseInput, L2EhlersSignalToNoiseParams,
296};
297use crate::indicators::leavitt_convolution_acceleration::{
298 leavitt_convolution_acceleration_with_kernel, LeavittConvolutionAccelerationInput,
299 LeavittConvolutionAccelerationParams,
300};
301use crate::indicators::linear_correlation_oscillator::{
302 linear_correlation_oscillator_with_kernel, LinearCorrelationOscillatorInput,
303 LinearCorrelationOscillatorParams,
304};
305use crate::indicators::linear_regression_intensity::{
306 linear_regression_intensity_with_kernel, LinearRegressionIntensityInput,
307 LinearRegressionIntensityParams,
308};
309use crate::indicators::linearreg_angle::{
310 linearreg_angle_with_kernel, Linearreg_angleInput, Linearreg_angleParams,
311};
312use crate::indicators::linearreg_intercept::{
313 linearreg_intercept_with_kernel, LinearRegInterceptInput, LinearRegInterceptParams,
314};
315use crate::indicators::linearreg_slope::{
316 linearreg_slope_with_kernel, LinearRegSlopeInput, LinearRegSlopeParams,
317};
318use crate::indicators::lpc::{lpc_with_kernel, LpcInput, LpcParams};
319use crate::indicators::lrsi::{lrsi_with_kernel, LrsiInput, LrsiParams};
320use crate::indicators::mab::{mab_with_kernel, MabInput, MabParams};
321use crate::indicators::macd::{macd_with_kernel, MacdInput, MacdParams};
322use crate::indicators::macd_wave_signal_pro::{
323 macd_wave_signal_pro_with_kernel, MacdWaveSignalProInput,
324};
325use crate::indicators::macz::{macz_with_kernel, MaczInput, MaczParams};
326use crate::indicators::market_meanness_index::{
327 market_meanness_index_with_kernel, MarketMeannessIndexInput, MarketMeannessIndexParams,
328};
329use crate::indicators::market_structure_confluence::{
330 market_structure_confluence_with_kernel, MarketStructureConfluenceInput,
331 MarketStructureConfluenceParams,
332};
333use crate::indicators::market_structure_trailing_stop::{
334 market_structure_trailing_stop_with_kernel, MarketStructureTrailingStopInput,
335 MarketStructureTrailingStopParams,
336};
337use crate::indicators::mass::{mass_with_kernel, MassInput, MassParams};
338use crate::indicators::mean_ad::{mean_ad_with_kernel, MeanAdInput, MeanAdParams};
339use crate::indicators::medium_ad::{medium_ad_with_kernel, MediumAdInput, MediumAdParams};
340use crate::indicators::medprice::{medprice_with_kernel, MedpriceInput, MedpriceParams};
341use crate::indicators::mesa_stochastic_multi_length::{
342 mesa_stochastic_multi_length_with_kernel, MesaStochasticMultiLengthInput,
343 MesaStochasticMultiLengthParams,
344};
345use crate::indicators::mfi::{
346 mfi_batch_with_kernel, mfi_into_slice, MfiBatchRange, MfiInput, MfiParams,
347};
348use crate::indicators::midpoint::{midpoint_with_kernel, MidpointInput, MidpointParams};
349use crate::indicators::midprice::{midprice_with_kernel, MidpriceInput, MidpriceParams};
350use crate::indicators::minmax::{minmax_with_kernel, MinmaxInput, MinmaxParams};
351use crate::indicators::mod_god_mode::{
352 mod_god_mode, ModGodModeData, ModGodModeInput, ModGodModeMode, ModGodModeParams,
353};
354use crate::indicators::mom::{mom_with_kernel, MomInput, MomParams};
355use crate::indicators::momentum_ratio_oscillator::{
356 momentum_ratio_oscillator_with_kernel, MomentumRatioOscillatorInput,
357 MomentumRatioOscillatorParams,
358};
359use crate::indicators::monotonicity_index::{
360 monotonicity_index_with_kernel, MonotonicityIndexInput, MonotonicityIndexMode,
361 MonotonicityIndexParams,
362};
363use crate::indicators::moving_average_cross_probability::{
364 moving_average_cross_probability_with_kernel, MovingAverageCrossProbabilityInput,
365 MovingAverageCrossProbabilityMaType, MovingAverageCrossProbabilityParams,
366};
367use crate::indicators::moving_averages::logarithmic_moving_average::{
368 logarithmic_moving_average_with_kernel, LogarithmicMovingAverageInput,
369 LogarithmicMovingAverageParams,
370};
371use crate::indicators::moving_averages::ma::MaData;
372use crate::indicators::moving_averages::ma_batch::{
373 ma_batch_with_kernel_and_typed_params, MaBatchParamKV, MaBatchParamValue,
374};
375use crate::indicators::moving_averages::registry::list_moving_averages;
376use crate::indicators::msw::{msw_with_kernel, MswInput, MswParams};
377use crate::indicators::multi_length_stochastic_average::{
378 multi_length_stochastic_average_with_kernel, MultiLengthStochasticAverageInput,
379 MultiLengthStochasticAverageParams,
380};
381use crate::indicators::nadaraya_watson_envelope::{
382 nadaraya_watson_envelope_with_kernel, NweInput, NweParams,
383};
384use crate::indicators::natr::{natr_with_kernel, NatrInput, NatrParams};
385use crate::indicators::neighboring_trailing_stop::{
386 neighboring_trailing_stop_with_kernel, NeighboringTrailingStopInput,
387 NeighboringTrailingStopParams,
388};
389use crate::indicators::net_myrsi::{net_myrsi_with_kernel, NetMyrsiInput, NetMyrsiParams};
390use crate::indicators::nonlinear_regression_zero_lag_moving_average::{
391 nonlinear_regression_zero_lag_moving_average_with_kernel,
392 NonlinearRegressionZeroLagMovingAverageInput, NonlinearRegressionZeroLagMovingAverageParams,
393};
394use crate::indicators::normalized_resonator::{
395 normalized_resonator_with_kernel, NormalizedResonatorInput, NormalizedResonatorParams,
396};
397use crate::indicators::normalized_volume_true_range::{
398 normalized_volume_true_range_with_kernel, NormalizedVolumeTrueRangeInput,
399 NormalizedVolumeTrueRangeParams, NormalizedVolumeTrueRangeStyle,
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::percentile_nearest_rank::{
412 percentile_nearest_rank_with_kernel, PercentileNearestRankInput, PercentileNearestRankParams,
413};
414use crate::indicators::pfe::{pfe_with_kernel, PfeInput, PfeParams};
415use crate::indicators::pivot::{pivot_with_kernel, PivotInput, PivotParams};
416use crate::indicators::pma::{pma_with_kernel, PmaInput, PmaParams};
417use crate::indicators::polynomial_regression_extrapolation::{
418 polynomial_regression_extrapolation_with_kernel, PolynomialRegressionExtrapolationInput,
419 PolynomialRegressionExtrapolationParams,
420};
421use crate::indicators::possible_rsi::{
422 possible_rsi_with_kernel, PossibleRsiInput, PossibleRsiParams,
423};
424use crate::indicators::ppo::{ppo_with_kernel, PpoInput, PpoParams};
425use crate::indicators::prb::{prb_with_kernel, PrbInput, PrbParams};
426use crate::indicators::premier_rsi_oscillator::{
427 premier_rsi_oscillator_with_kernel, PremierRsiOscillatorInput, PremierRsiOscillatorParams,
428};
429use crate::indicators::pretty_good_oscillator::{
430 pretty_good_oscillator_with_kernel, PrettyGoodOscillatorInput, PrettyGoodOscillatorParams,
431};
432use crate::indicators::price_density_market_noise::{
433 price_density_market_noise_with_kernel, PriceDensityMarketNoiseInput,
434 PriceDensityMarketNoiseParams,
435};
436use crate::indicators::price_moving_average_ratio_percentile::{
437 price_moving_average_ratio_percentile_with_kernel, PriceMovingAverageRatioPercentileInput,
438 PriceMovingAverageRatioPercentileLineMode, PriceMovingAverageRatioPercentileMaType,
439 PriceMovingAverageRatioPercentileParams,
440};
441use crate::indicators::projection_oscillator::{
442 projection_oscillator_with_kernel, ProjectionOscillatorInput, ProjectionOscillatorParams,
443};
444use crate::indicators::psychological_line::{
445 psychological_line_with_kernel, PsychologicalLineInput, PsychologicalLineParams,
446};
447use crate::indicators::pvi::{pvi_with_kernel, PviInput, PviParams};
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::range_filtered_trend_signals::{
463 range_filtered_trend_signals_with_kernel, RangeFilteredTrendSignalsInput,
464 RangeFilteredTrendSignalsParams,
465};
466use crate::indicators::range_oscillator::{
467 range_oscillator_with_kernel, RangeOscillatorInput, RangeOscillatorParams,
468};
469use crate::indicators::rank_correlation_index::{
470 rank_correlation_index_with_kernel, RankCorrelationIndexInput, RankCorrelationIndexParams,
471};
472use crate::indicators::registry::{
473 get_indicator, IndicatorInfo, IndicatorInputKind, ParamValueStatic,
474};
475use crate::indicators::regression_slope_oscillator::{
476 regression_slope_oscillator_with_kernel, RegressionSlopeOscillatorInput,
477 RegressionSlopeOscillatorParams,
478};
479use crate::indicators::relative_strength_index_wave_indicator::{
480 relative_strength_index_wave_indicator_with_kernel, RelativeStrengthIndexWaveIndicatorInput,
481 RelativeStrengthIndexWaveIndicatorParams,
482};
483use crate::indicators::reversal_signals::{
484 reversal_signals_with_kernel, ReversalSignalsInput, ReversalSignalsParams,
485};
486use crate::indicators::reverse_rsi::{reverse_rsi_with_kernel, ReverseRsiInput, ReverseRsiParams};
487use crate::indicators::roc::{roc_with_kernel, RocInput, RocParams};
488use crate::indicators::rocp::{rocp_with_kernel, RocpInput, RocpParams};
489use crate::indicators::rocr::{rocr_with_kernel, RocrInput, RocrParams};
490use crate::indicators::rogers_satchell_volatility::{
491 rogers_satchell_volatility_with_kernel, RogersSatchellVolatilityInput,
492 RogersSatchellVolatilityParams,
493};
494use crate::indicators::rolling_skewness_kurtosis::{
495 rolling_skewness_kurtosis_with_kernel, RollingSkewnessKurtosisInput,
496 RollingSkewnessKurtosisParams,
497};
498use crate::indicators::rolling_z_score_trend::{
499 rolling_z_score_trend_with_kernel, RollingZScoreTrendInput, RollingZScoreTrendParams,
500};
501use crate::indicators::rsi::{rsi_with_kernel, RsiInput, RsiParams};
502use crate::indicators::rsmk::{rsmk_with_kernel, RsmkInput, RsmkParams};
503use crate::indicators::rvi::{rvi_with_kernel, RviInput, RviParams};
504use crate::indicators::safezonestop::{
505 safezonestop_with_kernel, SafeZoneStopInput, SafeZoneStopParams,
506};
507use crate::indicators::smooth_theil_sen::{
508 smooth_theil_sen_with_kernel, SmoothTheilSenDeviationType, SmoothTheilSenInput,
509 SmoothTheilSenParams, SmoothTheilSenStatStyle,
510};
511use crate::indicators::smoothed_gaussian_trend_filter::{
512 smoothed_gaussian_trend_filter_with_kernel, SmoothedGaussianTrendFilterInput,
513 SmoothedGaussianTrendFilterParams,
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::srsi::{srsi_with_kernel, SrsiInput, SrsiParams};
525use crate::indicators::standardized_psar_oscillator::{
526 standardized_psar_oscillator_with_kernel, StandardizedPsarOscillatorInput,
527 StandardizedPsarOscillatorParams,
528};
529use crate::indicators::statistical_trailing_stop::{
530 statistical_trailing_stop_with_kernel, StatisticalTrailingStopInput,
531 StatisticalTrailingStopParams,
532};
533use crate::indicators::stc::{stc_with_kernel, StcInput, StcParams};
534use crate::indicators::stddev::{stddev_with_kernel, StdDevInput, StdDevParams};
535use crate::indicators::stoch::{stoch_with_kernel, StochInput, StochParams};
536use crate::indicators::stochastic_adaptive_d::{
537 stochastic_adaptive_d_with_kernel, StochasticAdaptiveDInput, StochasticAdaptiveDParams,
538};
539use crate::indicators::stochastic_connors_rsi::{
540 stochastic_connors_rsi_with_kernel, StochasticConnorsRsiInput, StochasticConnorsRsiParams,
541};
542use crate::indicators::stochastic_distance::{
543 stochastic_distance_with_kernel, StochasticDistanceInput, StochasticDistanceParams,
544};
545use crate::indicators::stochastic_money_flow_index::{
546 stochastic_money_flow_index_with_kernel, StochasticMoneyFlowIndexInput,
547 StochasticMoneyFlowIndexParams,
548};
549use crate::indicators::stochf::{stochf_with_kernel, StochfInput, StochfParams};
550use crate::indicators::supertrend::{supertrend_with_kernel, SuperTrendInput, SuperTrendParams};
551use crate::indicators::supertrend_oscillator::{
552 supertrend_oscillator_with_kernel, SuperTrendOscillatorInput, SuperTrendOscillatorParams,
553};
554use crate::indicators::supertrend_recovery::{
555 supertrend_recovery_with_kernel, SuperTrendRecoveryInput, SuperTrendRecoveryParams,
556};
557use crate::indicators::trend_continuation_factor::{
558 trend_continuation_factor_with_kernel, TrendContinuationFactorInput,
559 TrendContinuationFactorParams,
560};
561use crate::indicators::trend_direction_force_index::{
562 trend_direction_force_index_into_slice, TrendDirectionForceIndexInput,
563 TrendDirectionForceIndexParams,
564};
565use crate::indicators::trend_flow_trail::{
566 trend_flow_trail_with_kernel, TrendFlowTrailInput, TrendFlowTrailParams,
567};
568use crate::indicators::trend_trigger_factor::{
569 trend_trigger_factor_with_kernel, TrendTriggerFactorInput, TrendTriggerFactorParams,
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::twiggs_money_flow::{
580 twiggs_money_flow_with_kernel, TwiggsMoneyFlowInput, TwiggsMoneyFlowParams,
581};
582use crate::indicators::ui::{ui_with_kernel, UiInput, UiParams};
583use crate::indicators::ultosc::{ultosc_with_kernel, UltOscInput, UltOscParams};
584use crate::indicators::var::{var_with_kernel, VarInput, VarParams};
585use crate::indicators::vdubus_divergence_wave_pattern_generator::{
586 vdubus_divergence_wave_pattern_generator_with_kernel,
587 VdubusDivergenceWavePatternGeneratorInput, VdubusDivergenceWavePatternGeneratorParams,
588};
589use crate::indicators::velocity::{velocity_with_kernel, VelocityInput, VelocityParams};
590use crate::indicators::velocity_acceleration_convergence_divergence_indicator::{
591 velocity_acceleration_convergence_divergence_indicator_with_kernel,
592 VelocityAccelerationConvergenceDivergenceIndicatorInput,
593 VelocityAccelerationConvergenceDivergenceIndicatorParams,
594};
595use crate::indicators::velocity_acceleration_indicator::{
596 velocity_acceleration_indicator_with_kernel, VelocityAccelerationIndicatorInput,
597 VelocityAccelerationIndicatorParams,
598};
599use crate::indicators::vertical_horizontal_filter::{
600 vertical_horizontal_filter_with_kernel, VerticalHorizontalFilterInput,
601 VerticalHorizontalFilterParams,
602};
603use crate::indicators::vi::{vi_with_kernel, ViInput, ViParams};
604use crate::indicators::vidya::{vidya_with_kernel, VidyaInput, VidyaParams};
605use crate::indicators::vlma::{vlma_with_kernel, VlmaInput, VlmaParams};
606use crate::indicators::volatility_quality_index::{
607 volatility_quality_index_with_kernel, VolatilityQualityIndexInput, VolatilityQualityIndexParams,
608};
609use crate::indicators::volatility_ratio_adaptive_rsx::{
610 volatility_ratio_adaptive_rsx_with_kernel, VolatilityRatioAdaptiveRsxInput,
611 VolatilityRatioAdaptiveRsxParams,
612};
613use crate::indicators::volume_energy_reservoirs::{
614 volume_energy_reservoirs_with_kernel, VolumeEnergyReservoirsInput, VolumeEnergyReservoirsParams,
615};
616use crate::indicators::volume_weighted_relative_strength_index::{
617 volume_weighted_relative_strength_index_with_kernel, VolumeWeightedRelativeStrengthIndexInput,
618 VolumeWeightedRelativeStrengthIndexParams,
619};
620use crate::indicators::volume_weighted_rsi::{
621 volume_weighted_rsi_batch_with_kernel, volume_weighted_rsi_into_slice,
622 VolumeWeightedRsiBatchRange, VolumeWeightedRsiInput, VolumeWeightedRsiParams,
623};
624use crate::indicators::volume_weighted_stochastic_rsi::{
625 volume_weighted_stochastic_rsi_with_kernel, VolumeWeightedStochasticRsiInput,
626 VolumeWeightedStochasticRsiParams,
627};
628use crate::indicators::volume_zone_oscillator::{
629 volume_zone_oscillator_with_kernel, VolumeZoneOscillatorInput, VolumeZoneOscillatorParams,
630};
631use crate::indicators::vosc::{vosc_with_kernel, VoscInput, VoscParams};
632use crate::indicators::voss::{voss_with_kernel, VossInput, VossParams};
633use crate::indicators::vpci::{vpci_with_kernel, VpciInput, VpciParams};
634use crate::indicators::vpt::{vpt_with_kernel, VptInput};
635use crate::indicators::vwap_deviation_oscillator::{
636 vwap_deviation_oscillator_with_kernel, VwapDeviationMode, VwapDeviationOscillatorInput,
637 VwapDeviationOscillatorParams, VwapDeviationSessionMode,
638};
639use crate::indicators::vwap_zscore_with_signals::{
640 vwap_zscore_with_signals_with_kernel, VwapZscoreWithSignalsInput, VwapZscoreWithSignalsParams,
641};
642use crate::indicators::vwmacd::{vwmacd_with_kernel, VwmacdInput, VwmacdParams};
643use crate::indicators::wad::{wad_with_kernel, WadInput};
644use crate::indicators::wavetrend::{wavetrend_with_kernel, WavetrendInput, WavetrendParams};
645use crate::indicators::wclprice::{wclprice_with_kernel, WclpriceInput};
646use crate::indicators::willr::{willr_with_kernel, WillrInput, WillrParams};
647use crate::indicators::wto::{wto_with_kernel, WtoInput, WtoParams};
648use crate::indicators::yang_zhang_volatility::{
649 yang_zhang_volatility_with_kernel, YangZhangVolatilityInput, YangZhangVolatilityParams,
650};
651use crate::indicators::zig_zag_channels::{
652 zig_zag_channels_with_kernel, ZigZagChannelsInput, ZigZagChannelsParams,
653};
654use crate::indicators::zscore::{zscore_with_kernel, ZscoreInput, ZscoreParams};
655use crate::indicators::{cg::cg_with_kernel, cg::CgInput, cg::CgParams};
656use crate::utilities::data_loader::source_type;
657use crate::utilities::enums::Kernel;
658use std::collections::HashMap;
659use std::str::FromStr;
660
661pub fn compute_cpu_batch(
662 req: IndicatorBatchRequest<'_>,
663) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
664 compute_cpu_batch_internal(req, false)
665}
666
667pub fn compute_cpu_batch_strict(
668 req: IndicatorBatchRequest<'_>,
669) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
670 compute_cpu_batch_internal(req, true)
671}
672
673fn compute_cpu_batch_internal(
674 req: IndicatorBatchRequest<'_>,
675 strict_inputs: bool,
676) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
677 if !strict_inputs {
678 if let Some(out) = try_fast_dispatch_non_strict(req) {
679 return out;
680 }
681 }
682
683 let info = get_indicator(req.indicator_id);
684
685 if let Some(info) = info {
686 if strict_inputs {
687 validate_input_kind_strict(info.id, info.input_kind, req.data)?;
688 }
689
690 let output_id = resolve_output_id(info, req.output_id)?;
691
692 if info.id.eq_ignore_ascii_case("logarithmic_moving_average") {
693 return compute_logarithmic_moving_average_batch(req, output_id);
694 }
695
696 if is_moving_average(info.id) {
697 return compute_ma_batch(req, info, output_id);
698 }
699
700 return dispatch_cpu_batch_by_indicator(req, info.id, output_id);
701 }
702
703 let output_id = req.output_id.unwrap_or("value");
704 match dispatch_cpu_batch_by_indicator(req, req.indicator_id, output_id) {
705 Err(IndicatorDispatchError::UnsupportedCapability { .. }) => {
706 Err(IndicatorDispatchError::UnknownIndicator {
707 id: req.indicator_id.to_string(),
708 })
709 }
710 other => other,
711 }
712}
713
714fn try_fast_dispatch_non_strict(
715 req: IndicatorBatchRequest<'_>,
716) -> Option<Result<IndicatorBatchOutput, IndicatorDispatchError>> {
717 let id = req.indicator_id;
718 let output_id = req.output_id;
719
720 if !id.as_bytes().iter().any(|b| b.is_ascii_uppercase()) {
721 return match id {
722 "bop" => Some(compute_bop_batch(req, output_id.unwrap_or("value"))),
723 "dpo" => Some(compute_dpo_batch(req, output_id.unwrap_or("value"))),
724 "cmo" => Some(compute_cmo_batch(req, output_id.unwrap_or("value"))),
725 "fosc" => Some(compute_fosc_batch(req, output_id.unwrap_or("value"))),
726 "emv" => Some(compute_emv_batch(req, output_id.unwrap_or("value"))),
727 "cci_cycle" => Some(compute_cci_cycle_batch(req, output_id.unwrap_or("value"))),
728 "cfo" => Some(compute_cfo_batch(req, output_id.unwrap_or("value"))),
729 "ehlers_adaptive_cg" => Some(compute_ehlers_adaptive_cg_batch(
730 req,
731 output_id.unwrap_or("cg"),
732 )),
733 "adaptive_momentum_oscillator" => Some(compute_adaptive_momentum_oscillator_batch(
734 req,
735 output_id.unwrap_or("amo"),
736 )),
737 "lrsi" => Some(compute_lrsi_batch(req, output_id.unwrap_or("value"))),
738 "nvi" => Some(compute_nvi_batch(req, output_id.unwrap_or("value"))),
739 "mom" => Some(compute_mom_batch(req, output_id.unwrap_or("value"))),
740 "velocity" => Some(compute_velocity_batch(req, output_id.unwrap_or("value"))),
741 "normalized_volume_true_range" => Some(compute_normalized_volume_true_range_batch(
742 req,
743 output_id.unwrap_or("normalized_volume"),
744 )),
745 "exponential_trend" => Some(compute_exponential_trend_batch(
746 req,
747 output_id.unwrap_or("uptrend_base"),
748 )),
749 "trend_flow_trail" => Some(compute_trend_flow_trail_batch(
750 req,
751 output_id.unwrap_or("alpha_trail"),
752 )),
753 "range_breakout_signals" => Some(compute_range_breakout_signals_batch(
754 req,
755 output_id.unwrap_or("range_top"),
756 )),
757 "vi" => {
758 if let Some(out) = output_id {
759 Some(compute_vi_batch(req, out))
760 } else {
761 None
762 }
763 }
764 "wto" => {
765 if let Some(out) = output_id {
766 Some(compute_wto_batch(req, out))
767 } else {
768 None
769 }
770 }
771 "rogers_satchell_volatility" => {
772 if let Some(out) = output_id {
773 Some(compute_rogers_satchell_volatility_batch(req, out))
774 } else {
775 None
776 }
777 }
778 "historical_volatility_rank" => {
779 if let Some(out) = output_id {
780 Some(compute_historical_volatility_rank_batch(req, out))
781 } else {
782 None
783 }
784 }
785 "dual_ulcer_index" => {
786 if let Some(out) = output_id {
787 Some(compute_dual_ulcer_index_batch(req, out))
788 } else {
789 None
790 }
791 }
792 "fractal_dimension_index" => {
793 if let Some(out) = output_id {
794 Some(compute_fractal_dimension_index_batch(req, out))
795 } else {
796 None
797 }
798 }
799 "volume_weighted_rsi" => {
800 if let Some(out) = output_id {
801 Some(compute_volume_weighted_rsi_batch(req, out))
802 } else {
803 None
804 }
805 }
806 "dynamic_momentum_index" => {
807 if let Some(out) = output_id {
808 Some(compute_dynamic_momentum_index_batch(req, out))
809 } else {
810 None
811 }
812 }
813 "disparity_index" => {
814 if let Some(out) = output_id {
815 Some(compute_disparity_index_batch(req, out))
816 } else {
817 None
818 }
819 }
820 "donchian_channel_width" => {
821 if let Some(out) = output_id {
822 Some(compute_donchian_channel_width_batch(req, out))
823 } else {
824 None
825 }
826 }
827 "kairi_relative_index" => {
828 if let Some(out) = output_id {
829 Some(compute_kairi_relative_index_batch(req, out))
830 } else {
831 None
832 }
833 }
834 "projection_oscillator" => {
835 if let Some(out) = output_id {
836 Some(compute_projection_oscillator_batch(req, out))
837 } else {
838 None
839 }
840 }
841 "market_structure_trailing_stop" => {
842 if let Some(out) = output_id {
843 Some(compute_market_structure_trailing_stop_batch(req, out))
844 } else {
845 None
846 }
847 }
848 "emd_trend" => {
849 if let Some(out) = output_id {
850 Some(compute_emd_trend_batch(req, out))
851 } else {
852 None
853 }
854 }
855 "cyberpunk_value_trend_analyzer" => {
856 if let Some(out) = output_id {
857 Some(compute_cyberpunk_value_trend_analyzer_batch(req, out))
858 } else {
859 None
860 }
861 }
862 "evasive_supertrend" => {
863 if let Some(out) = output_id {
864 Some(compute_evasive_supertrend_batch(req, out))
865 } else {
866 None
867 }
868 }
869 "reversal_signals" => {
870 if let Some(out) = output_id {
871 Some(compute_reversal_signals_batch(req, out))
872 } else {
873 None
874 }
875 }
876 "zig_zag_channels" => {
877 if let Some(out) = output_id {
878 Some(compute_zig_zag_channels_batch(req, out))
879 } else {
880 None
881 }
882 }
883 "directional_imbalance_index" => {
884 if let Some(out) = output_id {
885 Some(compute_directional_imbalance_index_batch(req, out))
886 } else {
887 None
888 }
889 }
890 "candle_strength_oscillator" => {
891 if let Some(out) = output_id {
892 Some(compute_candle_strength_oscillator_batch(req, out))
893 } else {
894 None
895 }
896 }
897 "gmma_oscillator" => {
898 if let Some(out) = output_id {
899 Some(compute_gmma_oscillator_batch(req, out))
900 } else {
901 None
902 }
903 }
904 "nonlinear_regression_zero_lag_moving_average" => {
905 if let Some(out) = output_id {
906 Some(compute_nonlinear_regression_zero_lag_moving_average_batch(
907 req, out,
908 ))
909 } else {
910 None
911 }
912 }
913 "possible_rsi" => {
914 if let Some(out) = output_id {
915 Some(compute_possible_rsi_batch(req, out))
916 } else {
917 None
918 }
919 }
920 "autocorrelation_indicator" => {
921 if let Some(out) = output_id {
922 Some(compute_autocorrelation_indicator_batch(req, out))
923 } else {
924 None
925 }
926 }
927 "goertzel_cycle_composite_wave" => {
928 if let Some(out) = output_id {
929 Some(compute_goertzel_cycle_composite_wave_batch(req, out))
930 } else {
931 None
932 }
933 }
934 "rolling_skewness_kurtosis" => {
935 if let Some(out) = output_id {
936 Some(compute_rolling_skewness_kurtosis_batch(req, out))
937 } else {
938 None
939 }
940 }
941 "rolling_z_score_trend" => {
942 if let Some(out) = output_id {
943 Some(compute_rolling_z_score_trend_batch(req, out))
944 } else {
945 None
946 }
947 }
948 "ehlers_data_sampling_relative_strength_indicator" => {
949 if let Some(out) = output_id {
950 Some(compute_ehlers_data_sampling_relative_strength_indicator_batch(req, out))
951 } else {
952 None
953 }
954 }
955 "velocity_acceleration_convergence_divergence_indicator" => {
956 if let Some(out) = output_id {
957 Some(
958 compute_velocity_acceleration_convergence_divergence_indicator_batch(
959 req, out,
960 ),
961 )
962 } else {
963 None
964 }
965 }
966 "trend_direction_force_index" => {
967 if let Some(out) = output_id {
968 Some(compute_trend_direction_force_index_batch(req, out))
969 } else {
970 None
971 }
972 }
973 "yang_zhang_volatility" => {
974 if let Some(out) = output_id {
975 Some(compute_yang_zhang_volatility_batch(req, out))
976 } else {
977 None
978 }
979 }
980 "garman_klass_volatility" => Some(compute_garman_klass_volatility_batch(
981 req,
982 output_id.unwrap_or("value"),
983 )),
984 "advance_decline_line" => Some(compute_advance_decline_line_batch(
985 req,
986 output_id.unwrap_or("value"),
987 )),
988 "decisionpoint_breadth_swenlin_trading_oscillator" => Some(
989 compute_decisionpoint_breadth_swenlin_trading_oscillator_batch(
990 req,
991 output_id.unwrap_or("value"),
992 ),
993 ),
994 "velocity_acceleration_indicator" => Some(
995 compute_velocity_acceleration_indicator_batch(req, output_id.unwrap_or("value")),
996 ),
997 "normalized_resonator" => Some(compute_normalized_resonator_batch(
998 req,
999 output_id.unwrap_or("oscillator"),
1000 )),
1001 "monotonicity_index" => Some(compute_monotonicity_index_batch(
1002 req,
1003 output_id.unwrap_or("index"),
1004 )),
1005 "half_causal_estimator" => Some(compute_half_causal_estimator_batch(
1006 req,
1007 output_id.unwrap_or("estimate"),
1008 )),
1009 "atr_percentile" => Some(compute_atr_percentile_batch(
1010 req,
1011 output_id.unwrap_or("value"),
1012 )),
1013 "bull_power_vs_bear_power" => Some(compute_bull_power_vs_bear_power_batch(
1014 req,
1015 output_id.unwrap_or("value"),
1016 )),
1017 "didi_index" => Some(compute_didi_index_batch(req, output_id.unwrap_or("short"))),
1018 "ehlers_autocorrelation_periodogram" => {
1019 Some(compute_ehlers_autocorrelation_periodogram_batch(
1020 req,
1021 output_id.unwrap_or("dominant_cycle"),
1022 ))
1023 }
1024 "ehlers_linear_extrapolation_predictor" => {
1025 Some(compute_ehlers_linear_extrapolation_predictor_batch(
1026 req,
1027 output_id.unwrap_or("prediction"),
1028 ))
1029 }
1030 "kase_peak_oscillator_with_divergences" => {
1031 Some(compute_kase_peak_oscillator_with_divergences_batch(
1032 req,
1033 output_id.unwrap_or("oscillator"),
1034 ))
1035 }
1036 "absolute_strength_index_oscillator" => {
1037 Some(compute_absolute_strength_index_oscillator_batch(
1038 req,
1039 output_id.unwrap_or("oscillator"),
1040 ))
1041 }
1042 "adaptive_bandpass_trigger_oscillator" => {
1043 Some(compute_adaptive_bandpass_trigger_oscillator_batch(
1044 req,
1045 output_id.unwrap_or("in_phase"),
1046 ))
1047 }
1048 "premier_rsi_oscillator" => Some(compute_premier_rsi_oscillator_batch(
1049 req,
1050 output_id.unwrap_or("value"),
1051 )),
1052 "multi_length_stochastic_average" => Some(
1053 compute_multi_length_stochastic_average_batch(req, output_id.unwrap_or("value")),
1054 ),
1055 "hull_butterfly_oscillator" => Some(compute_hull_butterfly_oscillator_batch(
1056 req,
1057 output_id.unwrap_or("oscillator"),
1058 )),
1059 "fibonacci_trailing_stop" => Some(compute_fibonacci_trailing_stop_batch(
1060 req,
1061 output_id.unwrap_or("trailing_stop"),
1062 )),
1063 "fibonacci_entry_bands" => Some(compute_fibonacci_entry_bands_batch(
1064 req,
1065 output_id.unwrap_or("middle"),
1066 )),
1067 "volume_energy_reservoirs" => Some(compute_volume_energy_reservoirs_batch(
1068 req,
1069 output_id.unwrap_or("momentum"),
1070 )),
1071 "neighboring_trailing_stop" => Some(compute_neighboring_trailing_stop_batch(
1072 req,
1073 output_id.unwrap_or("trailing_stop"),
1074 )),
1075 "grover_llorens_cycle_oscillator" => Some(
1076 compute_grover_llorens_cycle_oscillator_batch(req, output_id.unwrap_or("value")),
1077 ),
1078 "historical_volatility" => Some(compute_historical_volatility_batch(
1079 req,
1080 output_id.unwrap_or("value"),
1081 )),
1082 "squeeze_index" => Some(compute_squeeze_index_batch(
1083 req,
1084 output_id.unwrap_or("value"),
1085 )),
1086 "stochastic_distance" => Some(compute_stochastic_distance_batch(
1087 req,
1088 output_id.unwrap_or("oscillator"),
1089 )),
1090 "vertical_horizontal_filter" => Some(compute_vertical_horizontal_filter_batch(
1091 req,
1092 output_id.unwrap_or("value"),
1093 )),
1094 "intraday_momentum_index" => {
1095 if let Some(out) = output_id {
1096 Some(compute_intraday_momentum_index_batch(req, out))
1097 } else {
1098 None
1099 }
1100 }
1101 "vwap_zscore_with_signals" => {
1102 if let Some(out) = output_id {
1103 Some(compute_vwap_zscore_with_signals_batch(req, out))
1104 } else {
1105 None
1106 }
1107 }
1108 "macd_wave_signal_pro" => {
1109 if let Some(out) = output_id {
1110 Some(compute_macd_wave_signal_pro_batch(req, out))
1111 } else {
1112 None
1113 }
1114 }
1115 "hema_trend_levels" => {
1116 if let Some(out) = output_id {
1117 Some(compute_hema_trend_levels_batch(req, out))
1118 } else {
1119 None
1120 }
1121 }
1122 "demand_index" => {
1123 if let Some(out) = output_id {
1124 Some(compute_demand_index_batch(req, out))
1125 } else {
1126 None
1127 }
1128 }
1129 "gopalakrishnan_range_index" => Some(compute_gopalakrishnan_range_index_batch(
1130 req,
1131 output_id.unwrap_or("value"),
1132 )),
1133 "voss" => {
1134 if let Some(out) = output_id {
1135 Some(compute_voss_batch(req, out))
1136 } else {
1137 None
1138 }
1139 }
1140 "acosc" => {
1141 if let Some(out) = output_id {
1142 Some(compute_acosc_batch(req, out))
1143 } else {
1144 None
1145 }
1146 }
1147 _ => None,
1148 };
1149 }
1150
1151 if id.eq_ignore_ascii_case("bop") {
1152 return Some(compute_bop_batch(req, output_id.unwrap_or("value")));
1153 }
1154 if id.eq_ignore_ascii_case("dpo") {
1155 return Some(compute_dpo_batch(req, output_id.unwrap_or("value")));
1156 }
1157 if id.eq_ignore_ascii_case("cmo") {
1158 return Some(compute_cmo_batch(req, output_id.unwrap_or("value")));
1159 }
1160 if id.eq_ignore_ascii_case("fosc") {
1161 return Some(compute_fosc_batch(req, output_id.unwrap_or("value")));
1162 }
1163 if id.eq_ignore_ascii_case("emv") {
1164 return Some(compute_emv_batch(req, output_id.unwrap_or("value")));
1165 }
1166 if id.eq_ignore_ascii_case("cfo") {
1167 return Some(compute_cfo_batch(req, output_id.unwrap_or("value")));
1168 }
1169 if id.eq_ignore_ascii_case("ehlers_adaptive_cg") {
1170 return Some(compute_ehlers_adaptive_cg_batch(
1171 req,
1172 output_id.unwrap_or("cg"),
1173 ));
1174 }
1175 if id.eq_ignore_ascii_case("adaptive_momentum_oscillator") {
1176 return Some(compute_adaptive_momentum_oscillator_batch(
1177 req,
1178 output_id.unwrap_or("amo"),
1179 ));
1180 }
1181 if id.eq_ignore_ascii_case("adaptive_macd") {
1182 return Some(compute_adaptive_macd_batch(
1183 req,
1184 output_id.unwrap_or("macd"),
1185 ));
1186 }
1187 if id.eq_ignore_ascii_case("linear_correlation_oscillator") {
1188 return Some(compute_linear_correlation_oscillator_batch(
1189 req,
1190 output_id.unwrap_or("value"),
1191 ));
1192 }
1193 if id.eq_ignore_ascii_case("polynomial_regression_extrapolation") {
1194 return Some(compute_polynomial_regression_extrapolation_batch(
1195 req,
1196 output_id.unwrap_or("value"),
1197 ));
1198 }
1199 if id.eq_ignore_ascii_case("statistical_trailing_stop") {
1200 return Some(compute_statistical_trailing_stop_batch(
1201 req,
1202 output_id.unwrap_or("level"),
1203 ));
1204 }
1205 if id.eq_ignore_ascii_case("supertrend_recovery") {
1206 return Some(compute_supertrend_recovery_batch(
1207 req,
1208 output_id.unwrap_or("band"),
1209 ));
1210 }
1211 if id.eq_ignore_ascii_case("standardized_psar_oscillator") {
1212 return Some(compute_standardized_psar_oscillator_batch(
1213 req,
1214 output_id.unwrap_or("oscillator"),
1215 ));
1216 }
1217 if id.eq_ignore_ascii_case("geometric_bias_oscillator") {
1218 return Some(compute_geometric_bias_oscillator_batch(
1219 req,
1220 output_id.unwrap_or("value"),
1221 ));
1222 }
1223 if id.eq_ignore_ascii_case("lrsi") {
1224 return Some(compute_lrsi_batch(req, output_id.unwrap_or("value")));
1225 }
1226 if id.eq_ignore_ascii_case("nvi") {
1227 return Some(compute_nvi_batch(req, output_id.unwrap_or("value")));
1228 }
1229 if id.eq_ignore_ascii_case("mom") {
1230 return Some(compute_mom_batch(req, output_id.unwrap_or("value")));
1231 }
1232 if id.eq_ignore_ascii_case("velocity") {
1233 return Some(compute_velocity_batch(req, output_id.unwrap_or("value")));
1234 }
1235 if id.eq_ignore_ascii_case("normalized_volume_true_range") {
1236 return Some(compute_normalized_volume_true_range_batch(
1237 req,
1238 output_id.unwrap_or("normalized_volume"),
1239 ));
1240 }
1241 if id.eq_ignore_ascii_case("exponential_trend") {
1242 return Some(compute_exponential_trend_batch(
1243 req,
1244 output_id.unwrap_or("uptrend_base"),
1245 ));
1246 }
1247 if id.eq_ignore_ascii_case("trend_flow_trail") {
1248 return Some(compute_trend_flow_trail_batch(
1249 req,
1250 output_id.unwrap_or("alpha_trail"),
1251 ));
1252 }
1253 if id.eq_ignore_ascii_case("range_breakout_signals") {
1254 return Some(compute_range_breakout_signals_batch(
1255 req,
1256 output_id.unwrap_or("range_top"),
1257 ));
1258 }
1259 if id.eq_ignore_ascii_case("vi") {
1260 if let Some(out) = output_id {
1261 return Some(compute_vi_batch(req, out));
1262 }
1263 return None;
1264 }
1265 if id.eq_ignore_ascii_case("wto") {
1266 if let Some(out) = output_id {
1267 return Some(compute_wto_batch(req, out));
1268 }
1269 return None;
1270 }
1271 if id.eq_ignore_ascii_case("rogers_satchell_volatility") {
1272 if let Some(out) = output_id {
1273 return Some(compute_rogers_satchell_volatility_batch(req, out));
1274 }
1275 return None;
1276 }
1277 if id.eq_ignore_ascii_case("historical_volatility_rank") {
1278 if let Some(out) = output_id {
1279 return Some(compute_historical_volatility_rank_batch(req, out));
1280 }
1281 return None;
1282 }
1283 if id.eq_ignore_ascii_case("dual_ulcer_index") {
1284 if let Some(out) = output_id {
1285 return Some(compute_dual_ulcer_index_batch(req, out));
1286 }
1287 return None;
1288 }
1289 if id.eq_ignore_ascii_case("fractal_dimension_index") {
1290 if let Some(out) = output_id {
1291 return Some(compute_fractal_dimension_index_batch(req, out));
1292 }
1293 return None;
1294 }
1295 if id.eq_ignore_ascii_case("volume_weighted_rsi") {
1296 if let Some(out) = output_id {
1297 return Some(compute_volume_weighted_rsi_batch(req, out));
1298 }
1299 return None;
1300 }
1301 if id.eq_ignore_ascii_case("dynamic_momentum_index") {
1302 if let Some(out) = output_id {
1303 return Some(compute_dynamic_momentum_index_batch(req, out));
1304 }
1305 return None;
1306 }
1307 if id.eq_ignore_ascii_case("disparity_index") {
1308 if let Some(out) = output_id {
1309 return Some(compute_disparity_index_batch(req, out));
1310 }
1311 return None;
1312 }
1313 if id.eq_ignore_ascii_case("donchian_channel_width") {
1314 if let Some(out) = output_id {
1315 return Some(compute_donchian_channel_width_batch(req, out));
1316 }
1317 return None;
1318 }
1319 if id.eq_ignore_ascii_case("kairi_relative_index") {
1320 if let Some(out) = output_id {
1321 return Some(compute_kairi_relative_index_batch(req, out));
1322 }
1323 return None;
1324 }
1325 if id.eq_ignore_ascii_case("projection_oscillator") {
1326 if let Some(out) = output_id {
1327 return Some(compute_projection_oscillator_batch(req, out));
1328 }
1329 return None;
1330 }
1331 if id.eq_ignore_ascii_case("market_structure_trailing_stop") {
1332 if let Some(out) = output_id {
1333 return Some(compute_market_structure_trailing_stop_batch(req, out));
1334 }
1335 return None;
1336 }
1337 if id.eq_ignore_ascii_case("emd_trend") {
1338 if let Some(out) = output_id {
1339 return Some(compute_emd_trend_batch(req, out));
1340 }
1341 return None;
1342 }
1343 if id.eq_ignore_ascii_case("cyberpunk_value_trend_analyzer") {
1344 if let Some(out) = output_id {
1345 return Some(compute_cyberpunk_value_trend_analyzer_batch(req, out));
1346 }
1347 return None;
1348 }
1349 if id.eq_ignore_ascii_case("evasive_supertrend") {
1350 if let Some(out) = output_id {
1351 return Some(compute_evasive_supertrend_batch(req, out));
1352 }
1353 return None;
1354 }
1355 if id.eq_ignore_ascii_case("reversal_signals") {
1356 if let Some(out) = output_id {
1357 return Some(compute_reversal_signals_batch(req, out));
1358 }
1359 return None;
1360 }
1361 if id.eq_ignore_ascii_case("zig_zag_channels") {
1362 if let Some(out) = output_id {
1363 return Some(compute_zig_zag_channels_batch(req, out));
1364 }
1365 return None;
1366 }
1367 if id.eq_ignore_ascii_case("directional_imbalance_index") {
1368 if let Some(out) = output_id {
1369 return Some(compute_directional_imbalance_index_batch(req, out));
1370 }
1371 return None;
1372 }
1373 if id.eq_ignore_ascii_case("candle_strength_oscillator") {
1374 if let Some(out) = output_id {
1375 return Some(compute_candle_strength_oscillator_batch(req, out));
1376 }
1377 return None;
1378 }
1379 if id.eq_ignore_ascii_case("gmma_oscillator") {
1380 if let Some(out) = output_id {
1381 return Some(compute_gmma_oscillator_batch(req, out));
1382 }
1383 return None;
1384 }
1385 if id.eq_ignore_ascii_case("nonlinear_regression_zero_lag_moving_average") {
1386 if let Some(out) = output_id {
1387 return Some(compute_nonlinear_regression_zero_lag_moving_average_batch(
1388 req, out,
1389 ));
1390 }
1391 return None;
1392 }
1393 if id.eq_ignore_ascii_case("autocorrelation_indicator") {
1394 if let Some(out) = output_id {
1395 return Some(compute_autocorrelation_indicator_batch(req, out));
1396 }
1397 return None;
1398 }
1399 if id.eq_ignore_ascii_case("goertzel_cycle_composite_wave") {
1400 if let Some(out) = output_id {
1401 return Some(compute_goertzel_cycle_composite_wave_batch(req, out));
1402 }
1403 return None;
1404 }
1405 if id.eq_ignore_ascii_case("rolling_skewness_kurtosis") {
1406 if let Some(out) = output_id {
1407 return Some(compute_rolling_skewness_kurtosis_batch(req, out));
1408 }
1409 return None;
1410 }
1411 if id.eq_ignore_ascii_case("rolling_z_score_trend") {
1412 if let Some(out) = output_id {
1413 return Some(compute_rolling_z_score_trend_batch(req, out));
1414 }
1415 return None;
1416 }
1417 if id.eq_ignore_ascii_case("ehlers_data_sampling_relative_strength_indicator") {
1418 if let Some(out) = output_id {
1419 return Some(compute_ehlers_data_sampling_relative_strength_indicator_batch(req, out));
1420 }
1421 return None;
1422 }
1423 if id.eq_ignore_ascii_case("velocity_acceleration_convergence_divergence_indicator") {
1424 if let Some(out) = output_id {
1425 return Some(
1426 compute_velocity_acceleration_convergence_divergence_indicator_batch(req, out),
1427 );
1428 }
1429 return None;
1430 }
1431 if id.eq_ignore_ascii_case("trend_direction_force_index") {
1432 if let Some(out) = output_id {
1433 return Some(compute_trend_direction_force_index_batch(req, out));
1434 }
1435 return None;
1436 }
1437 if id.eq_ignore_ascii_case("yang_zhang_volatility") {
1438 if let Some(out) = output_id {
1439 return Some(compute_yang_zhang_volatility_batch(req, out));
1440 }
1441 return None;
1442 }
1443 if id.eq_ignore_ascii_case("garman_klass_volatility") {
1444 return Some(compute_garman_klass_volatility_batch(
1445 req,
1446 output_id.unwrap_or("value"),
1447 ));
1448 }
1449 if id.eq_ignore_ascii_case("advance_decline_line") {
1450 return Some(compute_advance_decline_line_batch(
1451 req,
1452 output_id.unwrap_or("value"),
1453 ));
1454 }
1455 if id.eq_ignore_ascii_case("decisionpoint_breadth_swenlin_trading_oscillator") {
1456 return Some(
1457 compute_decisionpoint_breadth_swenlin_trading_oscillator_batch(
1458 req,
1459 output_id.unwrap_or("value"),
1460 ),
1461 );
1462 }
1463 if id.eq_ignore_ascii_case("velocity_acceleration_indicator") {
1464 return Some(compute_velocity_acceleration_indicator_batch(
1465 req,
1466 output_id.unwrap_or("value"),
1467 ));
1468 }
1469 if id.eq_ignore_ascii_case("normalized_resonator") {
1470 return Some(compute_normalized_resonator_batch(
1471 req,
1472 output_id.unwrap_or("oscillator"),
1473 ));
1474 }
1475 if id.eq_ignore_ascii_case("monotonicity_index") {
1476 return Some(compute_monotonicity_index_batch(
1477 req,
1478 output_id.unwrap_or("index"),
1479 ));
1480 }
1481 if id.eq_ignore_ascii_case("half_causal_estimator") {
1482 return Some(compute_half_causal_estimator_batch(
1483 req,
1484 output_id.unwrap_or("estimate"),
1485 ));
1486 }
1487 if id.eq_ignore_ascii_case("atr_percentile") {
1488 return Some(compute_atr_percentile_batch(
1489 req,
1490 output_id.unwrap_or("value"),
1491 ));
1492 }
1493 if id.eq_ignore_ascii_case("bull_power_vs_bear_power") {
1494 return Some(compute_bull_power_vs_bear_power_batch(
1495 req,
1496 output_id.unwrap_or("value"),
1497 ));
1498 }
1499 if id.eq_ignore_ascii_case("didi_index") {
1500 return Some(compute_didi_index_batch(req, output_id.unwrap_or("short")));
1501 }
1502 if id.eq_ignore_ascii_case("ehlers_autocorrelation_periodogram") {
1503 return Some(compute_ehlers_autocorrelation_periodogram_batch(
1504 req,
1505 output_id.unwrap_or("dominant_cycle"),
1506 ));
1507 }
1508 if id.eq_ignore_ascii_case("ehlers_linear_extrapolation_predictor") {
1509 return Some(compute_ehlers_linear_extrapolation_predictor_batch(
1510 req,
1511 output_id.unwrap_or("prediction"),
1512 ));
1513 }
1514 if id.eq_ignore_ascii_case("kase_peak_oscillator_with_divergences") {
1515 return Some(compute_kase_peak_oscillator_with_divergences_batch(
1516 req,
1517 output_id.unwrap_or("oscillator"),
1518 ));
1519 }
1520 if id.eq_ignore_ascii_case("absolute_strength_index_oscillator") {
1521 return Some(compute_absolute_strength_index_oscillator_batch(
1522 req,
1523 output_id.unwrap_or("oscillator"),
1524 ));
1525 }
1526 if id.eq_ignore_ascii_case("adaptive_bandpass_trigger_oscillator") {
1527 return Some(compute_adaptive_bandpass_trigger_oscillator_batch(
1528 req,
1529 output_id.unwrap_or("in_phase"),
1530 ));
1531 }
1532 if id.eq_ignore_ascii_case("premier_rsi_oscillator") {
1533 return Some(compute_premier_rsi_oscillator_batch(
1534 req,
1535 output_id.unwrap_or("value"),
1536 ));
1537 }
1538 if id.eq_ignore_ascii_case("multi_length_stochastic_average") {
1539 return Some(compute_multi_length_stochastic_average_batch(
1540 req,
1541 output_id.unwrap_or("value"),
1542 ));
1543 }
1544 if id.eq_ignore_ascii_case("hull_butterfly_oscillator") {
1545 return Some(compute_hull_butterfly_oscillator_batch(
1546 req,
1547 output_id.unwrap_or("oscillator"),
1548 ));
1549 }
1550 if id.eq_ignore_ascii_case("fibonacci_trailing_stop") {
1551 return Some(compute_fibonacci_trailing_stop_batch(
1552 req,
1553 output_id.unwrap_or("trailing_stop"),
1554 ));
1555 }
1556 if id.eq_ignore_ascii_case("fibonacci_entry_bands") {
1557 return Some(compute_fibonacci_entry_bands_batch(
1558 req,
1559 output_id.unwrap_or("middle"),
1560 ));
1561 }
1562 if id.eq_ignore_ascii_case("volume_energy_reservoirs") {
1563 return Some(compute_volume_energy_reservoirs_batch(
1564 req,
1565 output_id.unwrap_or("momentum"),
1566 ));
1567 }
1568 if id.eq_ignore_ascii_case("neighboring_trailing_stop") {
1569 return Some(compute_neighboring_trailing_stop_batch(
1570 req,
1571 output_id.unwrap_or("trailing_stop"),
1572 ));
1573 }
1574 if id.eq_ignore_ascii_case("grover_llorens_cycle_oscillator") {
1575 return Some(compute_grover_llorens_cycle_oscillator_batch(
1576 req,
1577 output_id.unwrap_or("value"),
1578 ));
1579 }
1580 if id.eq_ignore_ascii_case("historical_volatility") {
1581 return Some(compute_historical_volatility_batch(
1582 req,
1583 output_id.unwrap_or("value"),
1584 ));
1585 }
1586 if id.eq_ignore_ascii_case("squeeze_index") {
1587 return Some(compute_squeeze_index_batch(
1588 req,
1589 output_id.unwrap_or("value"),
1590 ));
1591 }
1592 if id.eq_ignore_ascii_case("stochastic_distance") {
1593 return Some(compute_stochastic_distance_batch(
1594 req,
1595 output_id.unwrap_or("oscillator"),
1596 ));
1597 }
1598 if id.eq_ignore_ascii_case("vertical_horizontal_filter") {
1599 return Some(compute_vertical_horizontal_filter_batch(
1600 req,
1601 output_id.unwrap_or("value"),
1602 ));
1603 }
1604 if id.eq_ignore_ascii_case("intraday_momentum_index") {
1605 if let Some(out) = output_id {
1606 return Some(compute_intraday_momentum_index_batch(req, out));
1607 }
1608 }
1609 if id.eq_ignore_ascii_case("vwap_zscore_with_signals") {
1610 if let Some(out) = output_id {
1611 return Some(compute_vwap_zscore_with_signals_batch(req, out));
1612 }
1613 }
1614 if id.eq_ignore_ascii_case("macd_wave_signal_pro") {
1615 if let Some(out) = output_id {
1616 return Some(compute_macd_wave_signal_pro_batch(req, out));
1617 }
1618 }
1619 if id.eq_ignore_ascii_case("hema_trend_levels") {
1620 if let Some(out) = output_id {
1621 return Some(compute_hema_trend_levels_batch(req, out));
1622 }
1623 }
1624 if id.eq_ignore_ascii_case("demand_index") {
1625 if let Some(out) = output_id {
1626 return Some(compute_demand_index_batch(req, out));
1627 }
1628 }
1629 if id.eq_ignore_ascii_case("gopalakrishnan_range_index") {
1630 return Some(compute_gopalakrishnan_range_index_batch(
1631 req,
1632 output_id.unwrap_or("value"),
1633 ));
1634 }
1635 if id.eq_ignore_ascii_case("voss") {
1636 if let Some(out) = output_id {
1637 return Some(compute_voss_batch(req, out));
1638 }
1639 return None;
1640 }
1641 if id.eq_ignore_ascii_case("acosc") {
1642 if let Some(out) = output_id {
1643 return Some(compute_acosc_batch(req, out));
1644 }
1645 return None;
1646 }
1647
1648 None
1649}
1650
1651fn dispatch_cpu_batch_by_indicator(
1652 req: IndicatorBatchRequest<'_>,
1653 indicator_id: &str,
1654 output_id: &str,
1655) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
1656 if indicator_id.eq_ignore_ascii_case("logarithmic_moving_average") {
1657 return compute_logarithmic_moving_average_batch(req, output_id);
1658 }
1659 if is_moving_average(indicator_id) {
1660 if let Some(info) = get_indicator(indicator_id) {
1661 return compute_ma_batch(req, info, output_id);
1662 }
1663 }
1664 match indicator_id {
1665 "accumulation_swing_index" => compute_accumulation_swing_index_batch(req, output_id),
1666 "ad" => compute_ad_batch(req, output_id),
1667 "adosc" => compute_adosc_batch(req, output_id),
1668 "ao" => compute_ao_batch(req, output_id),
1669 "emv" => compute_emv_batch(req, output_id),
1670 "efi" => compute_efi_batch(req, output_id),
1671 "mfi" => compute_mfi_batch(req, output_id),
1672 "mass" => compute_mass_batch(req, output_id),
1673 "kvo" => compute_kvo_batch(req, output_id),
1674 "vosc" => compute_vosc_batch(req, output_id),
1675 "wad" => compute_wad_batch(req, output_id),
1676 "dx" => compute_dx_batch(req, output_id),
1677 "fosc" => compute_fosc_batch(req, output_id),
1678 "ift_rsi" => compute_ift_rsi_batch(req, output_id),
1679 "linearreg_angle" => compute_linearreg_angle_batch(req, output_id),
1680 "linearreg_intercept" => compute_linearreg_intercept_batch(req, output_id),
1681 "linearreg_slope" => compute_linearreg_slope_batch(req, output_id),
1682 "cg" => compute_cg_batch(req, output_id),
1683 "rsi" => compute_rsi_batch(req, output_id),
1684 "roc" => compute_roc_batch(req, output_id),
1685 "apo" => compute_apo_batch(req, output_id),
1686 "bop" => compute_bop_batch(req, output_id),
1687 "bulls_v_bears" => compute_bulls_v_bears_batch(req, output_id),
1688 "cci" => compute_cci_batch(req, output_id),
1689 "cci_cycle" => compute_cci_cycle_batch(req, output_id),
1690 "cfo" => compute_cfo_batch(req, output_id),
1691 "cycle_channel_oscillator" => compute_cycle_channel_oscillator_batch(req, output_id),
1692 "daily_factor" => compute_daily_factor_batch(req, output_id),
1693 "ehlers_adaptive_cg" => compute_ehlers_adaptive_cg_batch(req, output_id),
1694 "ehlers_adaptive_cyber_cycle" => compute_ehlers_adaptive_cyber_cycle_batch(req, output_id),
1695 "adaptive_schaff_trend_cycle" => compute_adaptive_schaff_trend_cycle_batch(req, output_id),
1696 "adaptive_momentum_oscillator" => {
1697 compute_adaptive_momentum_oscillator_batch(req, output_id)
1698 }
1699 "adaptive_macd" => compute_adaptive_macd_batch(req, output_id),
1700 "linear_correlation_oscillator" => {
1701 compute_linear_correlation_oscillator_batch(req, output_id)
1702 }
1703 "polynomial_regression_extrapolation" => {
1704 compute_polynomial_regression_extrapolation_batch(req, output_id)
1705 }
1706 "statistical_trailing_stop" => compute_statistical_trailing_stop_batch(req, output_id),
1707 "supertrend_recovery" => compute_supertrend_recovery_batch(req, output_id),
1708 "standardized_psar_oscillator" => {
1709 compute_standardized_psar_oscillator_batch(req, output_id)
1710 }
1711 "geometric_bias_oscillator" => compute_geometric_bias_oscillator_batch(req, output_id),
1712 "vdubus_divergence_wave_pattern_generator" => {
1713 compute_vdubus_divergence_wave_pattern_generator_batch(req, output_id)
1714 }
1715 "lrsi" => compute_lrsi_batch(req, output_id),
1716 "er" => compute_er_batch(req, output_id),
1717 "kurtosis" => compute_kurtosis_batch(req, output_id),
1718 "natr" => compute_natr_batch(req, output_id),
1719 "net_myrsi" => compute_net_myrsi_batch(req, output_id),
1720 "mean_ad" => compute_mean_ad_batch(req, output_id),
1721 "medium_ad" => compute_medium_ad_batch(req, output_id),
1722 "deviation" => compute_deviation_batch(req, output_id),
1723 "dpo" => compute_dpo_batch(req, output_id),
1724 "pfe" => compute_pfe_batch(req, output_id),
1725 "ehlers_detrending_filter" => compute_ehlers_detrending_filter_batch(req, output_id),
1726 "ehlers_fm_demodulator" => compute_ehlers_fm_demodulator_batch(req, output_id),
1727 "ehlers_simple_cycle_indicator" => {
1728 compute_ehlers_simple_cycle_indicator_batch(req, output_id)
1729 }
1730 "ehlers_smoothed_adaptive_momentum" => {
1731 compute_ehlers_smoothed_adaptive_momentum_batch(req, output_id)
1732 }
1733 "ewma_volatility" => compute_ewma_volatility_batch(req, output_id),
1734 "qstick" => compute_qstick_batch(req, output_id),
1735 "reverse_rsi" => compute_reverse_rsi_batch(req, output_id),
1736 "percentile_nearest_rank" => compute_percentile_nearest_rank_batch(req, output_id),
1737 "obv" => compute_obv_batch(req, output_id),
1738 "on_balance_volume_oscillator" => {
1739 compute_on_balance_volume_oscillator_batch(req, output_id)
1740 }
1741 "vpt" => compute_vpt_batch(req, output_id),
1742 "nvi" => compute_nvi_batch(req, output_id),
1743 "pvi" => compute_pvi_batch(req, output_id),
1744 "wclprice" => compute_wclprice_batch(req, output_id),
1745 "ui" => compute_ui_batch(req, output_id),
1746 "zscore" => compute_zscore_batch(req, output_id),
1747 "medprice" => compute_medprice_batch(req, output_id),
1748 "midpoint" => compute_midpoint_batch(req, output_id),
1749 "midprice" => compute_midprice_batch(req, output_id),
1750 "mom" => compute_mom_batch(req, output_id),
1751 "velocity" => compute_velocity_batch(req, output_id),
1752 "normalized_volume_true_range" => {
1753 compute_normalized_volume_true_range_batch(req, output_id)
1754 }
1755 "exponential_trend" => compute_exponential_trend_batch(req, output_id),
1756 "trend_flow_trail" => compute_trend_flow_trail_batch(req, output_id),
1757 "range_breakout_signals" => compute_range_breakout_signals_batch(req, output_id),
1758 "cmo" => compute_cmo_batch(req, output_id),
1759 "rocp" => compute_rocp_batch(req, output_id),
1760 "rocr" => compute_rocr_batch(req, output_id),
1761 "ppo" => compute_ppo_batch(req, output_id),
1762 "tsf" => compute_tsf_batch(req, output_id),
1763 "trix" => compute_trix_batch(req, output_id),
1764 "tsi" => compute_tsi_batch(req, output_id),
1765 "var" => compute_var_batch(req, output_id),
1766 "stddev" => compute_stddev_batch(req, output_id),
1767 "willr" => compute_willr_batch(req, output_id),
1768 "ultosc" => compute_ultosc_batch(req, output_id),
1769 "adx" => compute_adx_batch(req, output_id),
1770 "adxr" => compute_adxr_batch(req, output_id),
1771 "atr" => compute_atr_batch(req, output_id),
1772 "macd" => compute_macd_batch(req, output_id),
1773 "bollinger_bands" => compute_bollinger_batch(req, output_id),
1774 "bollinger_bands_width" => compute_bbw_batch(req, output_id),
1775 "stoch" => compute_stoch_batch(req, output_id),
1776 "stochf" => compute_stochf_batch(req, output_id),
1777 "stochastic_money_flow_index" => compute_stochastic_money_flow_index_batch(req, output_id),
1778 "vwmacd" => compute_vwmacd_batch(req, output_id),
1779 "vpci" => compute_vpci_batch(req, output_id),
1780 "ttm_trend" => compute_ttm_trend_batch(req, output_id),
1781 "ttm_squeeze" => compute_ttm_squeeze_batch(req, output_id),
1782 "aroon" => compute_aroon_batch(req, output_id),
1783 "aroonosc" => compute_aroonosc_batch(req, output_id),
1784 "di" => compute_di_batch(req, output_id),
1785 "dm" => compute_dm_batch(req, output_id),
1786 "dti" => compute_dti_batch(req, output_id),
1787 "donchian" => compute_donchian_batch(req, output_id),
1788 "kdj" => compute_kdj_batch(req, output_id),
1789 "keltner" => compute_keltner_batch(req, output_id),
1790 "squeeze_momentum" => compute_squeeze_momentum_batch(req, output_id),
1791 "srsi" => compute_srsi_batch(req, output_id),
1792 "supertrend" => compute_supertrend_batch(req, output_id),
1793 "adjustable_ma_alternating_extremities" => {
1794 compute_adjustable_ma_alternating_extremities_batch(req, output_id)
1795 }
1796 "vi" => compute_vi_batch(req, output_id),
1797 "wavetrend" => compute_wavetrend_batch(req, output_id),
1798 "wto" => compute_wto_batch(req, output_id),
1799 "rogers_satchell_volatility" => compute_rogers_satchell_volatility_batch(req, output_id),
1800 "historical_volatility_percentile" => {
1801 compute_historical_volatility_percentile_batch(req, output_id)
1802 }
1803 "historical_volatility_rank" => compute_historical_volatility_rank_batch(req, output_id),
1804 "dual_ulcer_index" => compute_dual_ulcer_index_batch(req, output_id),
1805 "fractal_dimension_index" => compute_fractal_dimension_index_batch(req, output_id),
1806 "ichimoku_oscillator" => compute_ichimoku_oscillator_batch(req, output_id),
1807 "volume_weighted_rsi" => compute_volume_weighted_rsi_batch(req, output_id),
1808 "dynamic_momentum_index" => compute_dynamic_momentum_index_batch(req, output_id),
1809 "disparity_index" => compute_disparity_index_batch(req, output_id),
1810 "donchian_channel_width" => compute_donchian_channel_width_batch(req, output_id),
1811 "kairi_relative_index" => compute_kairi_relative_index_batch(req, output_id),
1812 "projection_oscillator" => compute_projection_oscillator_batch(req, output_id),
1813 "market_structure_trailing_stop" => {
1814 compute_market_structure_trailing_stop_batch(req, output_id)
1815 }
1816 "emd_trend" => compute_emd_trend_batch(req, output_id),
1817 "cyberpunk_value_trend_analyzer" => {
1818 compute_cyberpunk_value_trend_analyzer_batch(req, output_id)
1819 }
1820 "evasive_supertrend" => compute_evasive_supertrend_batch(req, output_id),
1821 "reversal_signals" => compute_reversal_signals_batch(req, output_id),
1822 "zig_zag_channels" => compute_zig_zag_channels_batch(req, output_id),
1823 "directional_imbalance_index" => compute_directional_imbalance_index_batch(req, output_id),
1824 "candle_strength_oscillator" => compute_candle_strength_oscillator_batch(req, output_id),
1825 "gmma_oscillator" => compute_gmma_oscillator_batch(req, output_id),
1826 "nonlinear_regression_zero_lag_moving_average" => {
1827 compute_nonlinear_regression_zero_lag_moving_average_batch(req, output_id)
1828 }
1829 "possible_rsi" => compute_possible_rsi_batch(req, output_id),
1830 "autocorrelation_indicator" => compute_autocorrelation_indicator_batch(req, output_id),
1831 "goertzel_cycle_composite_wave" => {
1832 compute_goertzel_cycle_composite_wave_batch(req, output_id)
1833 }
1834 "rolling_skewness_kurtosis" => compute_rolling_skewness_kurtosis_batch(req, output_id),
1835 "rolling_z_score_trend" => compute_rolling_z_score_trend_batch(req, output_id),
1836 "ehlers_data_sampling_relative_strength_indicator" => {
1837 compute_ehlers_data_sampling_relative_strength_indicator_batch(req, output_id)
1838 }
1839 "velocity_acceleration_convergence_divergence_indicator" => {
1840 compute_velocity_acceleration_convergence_divergence_indicator_batch(req, output_id)
1841 }
1842 "trend_direction_force_index" => compute_trend_direction_force_index_batch(req, output_id),
1843 "yang_zhang_volatility" => compute_yang_zhang_volatility_batch(req, output_id),
1844 "garman_klass_volatility" => compute_garman_klass_volatility_batch(req, output_id),
1845 "advance_decline_line" => compute_advance_decline_line_batch(req, output_id),
1846 "decisionpoint_breadth_swenlin_trading_oscillator" => {
1847 compute_decisionpoint_breadth_swenlin_trading_oscillator_batch(req, output_id)
1848 }
1849 "velocity_acceleration_indicator" => {
1850 compute_velocity_acceleration_indicator_batch(req, output_id)
1851 }
1852 "normalized_resonator" => compute_normalized_resonator_batch(req, output_id),
1853 "monotonicity_index" => compute_monotonicity_index_batch(req, output_id),
1854 "half_causal_estimator" => compute_half_causal_estimator_batch(req, output_id),
1855 "atr_percentile" => compute_atr_percentile_batch(req, output_id),
1856 "andean_oscillator" => compute_andean_oscillator_batch(req, output_id),
1857 "bull_power_vs_bear_power" => compute_bull_power_vs_bear_power_batch(req, output_id),
1858 "didi_index" => compute_didi_index_batch(req, output_id),
1859 "ehlers_autocorrelation_periodogram" => {
1860 compute_ehlers_autocorrelation_periodogram_batch(req, output_id)
1861 }
1862 "ehlers_linear_extrapolation_predictor" => {
1863 compute_ehlers_linear_extrapolation_predictor_batch(req, output_id)
1864 }
1865 "absolute_strength_index_oscillator" => {
1866 compute_absolute_strength_index_oscillator_batch(req, output_id)
1867 }
1868 "adaptive_bandpass_trigger_oscillator" => {
1869 compute_adaptive_bandpass_trigger_oscillator_batch(req, output_id)
1870 }
1871 "premier_rsi_oscillator" => compute_premier_rsi_oscillator_batch(req, output_id),
1872 "multi_length_stochastic_average" => {
1873 compute_multi_length_stochastic_average_batch(req, output_id)
1874 }
1875 "hull_butterfly_oscillator" => compute_hull_butterfly_oscillator_batch(req, output_id),
1876 "fibonacci_trailing_stop" => compute_fibonacci_trailing_stop_batch(req, output_id),
1877 "fibonacci_entry_bands" => compute_fibonacci_entry_bands_batch(req, output_id),
1878 "volume_energy_reservoirs" => compute_volume_energy_reservoirs_batch(req, output_id),
1879 "neighboring_trailing_stop" => compute_neighboring_trailing_stop_batch(req, output_id),
1880 "grover_llorens_cycle_oscillator" => {
1881 compute_grover_llorens_cycle_oscillator_batch(req, output_id)
1882 }
1883 "historical_volatility" => compute_historical_volatility_batch(req, output_id),
1884 "hypertrend" => compute_hypertrend_batch(req, output_id),
1885 "ict_propulsion_block" => compute_ict_propulsion_block_batch(req, output_id),
1886 "impulse_macd" => compute_impulse_macd_batch(req, output_id),
1887 "l1_ehlers_phasor" => compute_l1_ehlers_phasor_batch(req, output_id),
1888 "l2_ehlers_signal_to_noise" => compute_l2_ehlers_signal_to_noise_batch(req, output_id),
1889 "keltner_channel_width_oscillator" => {
1890 compute_keltner_channel_width_oscillator_batch(req, output_id)
1891 }
1892 "leavitt_convolution_acceleration" => {
1893 compute_leavitt_convolution_acceleration_batch(req, output_id)
1894 }
1895 "linear_regression_intensity" => compute_linear_regression_intensity_batch(req, output_id),
1896 "market_meanness_index" => compute_market_meanness_index_batch(req, output_id),
1897 "mesa_stochastic_multi_length" => {
1898 compute_mesa_stochastic_multi_length_batch(req, output_id)
1899 }
1900 "moving_average_cross_probability" => {
1901 compute_moving_average_cross_probability_batch(req, output_id)
1902 }
1903 "momentum_ratio_oscillator" => compute_momentum_ratio_oscillator_batch(req, output_id),
1904 "parkinson_volatility" => compute_parkinson_volatility_batch(req, output_id),
1905 "price_moving_average_ratio_percentile" => {
1906 compute_price_moving_average_ratio_percentile_batch(req, output_id)
1907 }
1908 "pretty_good_oscillator" => compute_pretty_good_oscillator_batch(req, output_id),
1909 "price_density_market_noise" => compute_price_density_market_noise_batch(req, output_id),
1910 "psychological_line" => compute_psychological_line_batch(req, output_id),
1911 "random_walk_index" => compute_random_walk_index_batch(req, output_id),
1912 "rank_correlation_index" => compute_rank_correlation_index_batch(req, output_id),
1913 "relative_strength_index_wave_indicator" => {
1914 compute_relative_strength_index_wave_indicator_batch(req, output_id)
1915 }
1916 "regression_slope_oscillator" => compute_regression_slope_oscillator_batch(req, output_id),
1917 "squeeze_index" => compute_squeeze_index_batch(req, output_id),
1918 "smoothed_gaussian_trend_filter" => {
1919 compute_smoothed_gaussian_trend_filter_batch(req, output_id)
1920 }
1921 "smooth_theil_sen" => compute_smooth_theil_sen_batch(req, output_id),
1922 "spearman_correlation" => compute_spearman_correlation_batch(req, output_id),
1923 "stochastic_adaptive_d" => compute_stochastic_adaptive_d_batch(req, output_id),
1924 "stochastic_connors_rsi" => compute_stochastic_connors_rsi_batch(req, output_id),
1925 "stochastic_distance" => compute_stochastic_distance_batch(req, output_id),
1926 "supertrend_oscillator" => compute_supertrend_oscillator_batch(req, output_id),
1927 "trend_trigger_factor" => compute_trend_trigger_factor_batch(req, output_id),
1928 "trend_continuation_factor" => compute_trend_continuation_factor_batch(req, output_id),
1929 "twiggs_money_flow" => compute_twiggs_money_flow_batch(req, output_id),
1930 "vertical_horizontal_filter" => compute_vertical_horizontal_filter_batch(req, output_id),
1931 "intraday_momentum_index" => compute_intraday_momentum_index_batch(req, output_id),
1932 "volatility_quality_index" => compute_volatility_quality_index_batch(req, output_id),
1933 "volatility_ratio_adaptive_rsx" => {
1934 compute_volatility_ratio_adaptive_rsx_batch(req, output_id)
1935 }
1936 "volume_weighted_stochastic_rsi" => {
1937 compute_volume_weighted_stochastic_rsi_batch(req, output_id)
1938 }
1939 "volume_zone_oscillator" => compute_volume_zone_oscillator_batch(req, output_id),
1940 "vwap_deviation_oscillator" => compute_vwap_deviation_oscillator_batch(req, output_id),
1941 "vwap_zscore_with_signals" => compute_vwap_zscore_with_signals_batch(req, output_id),
1942 "macd_wave_signal_pro" => compute_macd_wave_signal_pro_batch(req, output_id),
1943 "hema_trend_levels" => compute_hema_trend_levels_batch(req, output_id),
1944 "demand_index" => compute_demand_index_batch(req, output_id),
1945 "kase_peak_oscillator_with_divergences" => {
1946 compute_kase_peak_oscillator_with_divergences_batch(req, output_id)
1947 }
1948 "gopalakrishnan_range_index" => compute_gopalakrishnan_range_index_batch(req, output_id),
1949 "acosc" => compute_acosc_batch(req, output_id),
1950 "alligator" => compute_alligator_batch(req, output_id),
1951 "alphatrend" => compute_alphatrend_batch(req, output_id),
1952 "aso" => compute_aso_batch(req, output_id),
1953 "avsl" => compute_avsl_batch(req, output_id),
1954 "bandpass" => compute_bandpass_batch(req, output_id),
1955 "chande" => compute_chande_batch(req, output_id),
1956 "chandelier_exit" => compute_chandelier_exit_batch(req, output_id),
1957 "cksp" => compute_cksp_batch(req, output_id),
1958 "coppock" => compute_coppock_batch(req, output_id),
1959 "correl_hl" => compute_correl_hl_batch(req, output_id),
1960 "correlation_cycle" => compute_correlation_cycle_batch(req, output_id),
1961 "damiani_volatmeter" => compute_damiani_volatmeter_batch(req, output_id),
1962 "dvdiqqe" => compute_dvdiqqe_batch(req, output_id),
1963 "emd" => compute_emd_batch(req, output_id),
1964 "eri" => compute_eri_batch(req, output_id),
1965 "fisher" => compute_fisher_batch(req, output_id),
1966 "fvg_positioning_average" => compute_fvg_positioning_average_batch(req, output_id),
1967 "fvg_trailing_stop" => compute_fvg_trailing_stop_batch(req, output_id),
1968 "gatorosc" => compute_gatorosc_batch(req, output_id),
1969 "halftrend" => compute_halftrend_batch(req, output_id),
1970 "kaufmanstop" => compute_kaufmanstop_batch(req, output_id),
1971 "kst" => compute_kst_batch(req, output_id),
1972 "lpc" => compute_lpc_batch(req, output_id),
1973 "mab" => compute_mab_batch(req, output_id),
1974 "macz" => compute_macz_batch(req, output_id),
1975 "minmax" => compute_minmax_batch(req, output_id),
1976 "mod_god_mode" => compute_mod_god_mode_batch(req, output_id),
1977 "msw" => compute_msw_batch(req, output_id),
1978 "nadaraya_watson_envelope" => compute_nadaraya_watson_envelope_batch(req, output_id),
1979 "otto" => compute_otto_batch(req, output_id),
1980 "vidya" => compute_vidya_batch(req, output_id),
1981 "vlma" => compute_vlma_batch(req, output_id),
1982 "pma" => compute_pma_batch(req, output_id),
1983 "prb" => compute_prb_batch(req, output_id),
1984 "qqe" => compute_qqe_batch(req, output_id),
1985 "forward_backward_exponential_oscillator" => {
1986 compute_forward_backward_exponential_oscillator_batch(req, output_id)
1987 }
1988 "qqe_weighted_oscillator" => compute_qqe_weighted_oscillator_batch(req, output_id),
1989 "market_structure_confluence" => compute_market_structure_confluence_batch(req, output_id),
1990 "range_filtered_trend_signals" => {
1991 compute_range_filtered_trend_signals_batch(req, output_id)
1992 }
1993 "range_oscillator" => compute_range_oscillator_batch(req, output_id),
1994 "volume_weighted_relative_strength_index" => {
1995 compute_volume_weighted_relative_strength_index_batch(req, output_id)
1996 }
1997 "range_filter" => compute_range_filter_batch(req, output_id),
1998 "rsmk" => compute_rsmk_batch(req, output_id),
1999 "voss" => compute_voss_batch(req, output_id),
2000 "stc" => compute_stc_batch(req, output_id),
2001 "rvi" => compute_rvi_batch(req, output_id),
2002 "safezonestop" => compute_safezonestop_batch(req, output_id),
2003 "devstop" => compute_devstop_batch(req, output_id),
2004 "chop" => compute_chop_batch(req, output_id),
2005 "pivot" => compute_pivot_batch(req, output_id),
2006 _ => Err(IndicatorDispatchError::UnsupportedCapability {
2007 indicator: indicator_id.to_string(),
2008 capability: "cpu_batch",
2009 }),
2010 }
2011}
2012
2013fn validate_input_kind_strict(
2014 indicator: &str,
2015 expected: IndicatorInputKind,
2016 data: IndicatorDataRef<'_>,
2017) -> Result<(), IndicatorDispatchError> {
2018 let expected = strict_expected_input_kind(indicator, expected);
2019 if indicator.eq_ignore_ascii_case("mod_god_mode") {
2020 let matches = matches!(
2021 data,
2022 IndicatorDataRef::Candles { .. }
2023 | IndicatorDataRef::Ohlc { .. }
2024 | IndicatorDataRef::Ohlcv { .. }
2025 );
2026 if matches {
2027 return Ok(());
2028 }
2029 }
2030 let matches = matches!(
2031 (expected, data),
2032 (IndicatorInputKind::Slice, IndicatorDataRef::Slice { .. })
2033 | (
2034 IndicatorInputKind::Candles,
2035 IndicatorDataRef::Candles { .. }
2036 )
2037 | (IndicatorInputKind::Ohlc, IndicatorDataRef::Ohlc { .. })
2038 | (IndicatorInputKind::Ohlcv, IndicatorDataRef::Ohlcv { .. })
2039 | (
2040 IndicatorInputKind::HighLow,
2041 IndicatorDataRef::HighLow { .. }
2042 )
2043 | (
2044 IndicatorInputKind::CloseVolume,
2045 IndicatorDataRef::CloseVolume { .. }
2046 )
2047 );
2048
2049 if matches {
2050 Ok(())
2051 } else {
2052 Err(IndicatorDispatchError::MissingRequiredInput {
2053 indicator: indicator.to_string(),
2054 input: expected,
2055 })
2056 }
2057}
2058
2059fn strict_expected_input_kind(indicator: &str, fallback: IndicatorInputKind) -> IndicatorInputKind {
2060 if indicator.eq_ignore_ascii_case("ao") {
2061 return IndicatorInputKind::Slice;
2062 }
2063 if indicator.eq_ignore_ascii_case("ttm_trend") {
2064 return IndicatorInputKind::Candles;
2065 }
2066 fallback
2067}
2068
2069fn normalize_output_token(value: &str) -> String {
2070 let mut normalized = String::with_capacity(value.len());
2071 for ch in value.chars() {
2072 if ch.is_ascii_alphanumeric() {
2073 normalized.push(ch.to_ascii_lowercase());
2074 }
2075 }
2076 if normalized == "values" {
2077 "value".to_string()
2078 } else {
2079 normalized
2080 }
2081}
2082
2083fn output_id_matches(candidate: &str, requested: &str) -> bool {
2084 candidate.eq_ignore_ascii_case(requested)
2085 || normalize_output_token(candidate) == normalize_output_token(requested)
2086}
2087
2088fn resolve_output_id<'a>(
2089 info: &'a IndicatorInfo,
2090 requested: Option<&str>,
2091) -> Result<&'a str, IndicatorDispatchError> {
2092 if info.outputs.is_empty() {
2093 return Err(IndicatorDispatchError::ComputeFailed {
2094 indicator: info.id.to_string(),
2095 details: "indicator has no registered outputs".to_string(),
2096 });
2097 }
2098
2099 if info.outputs.len() == 1 {
2100 let only = info.outputs[0].id;
2101 if let Some(req) = requested {
2102 if req == only {
2103 return Ok(only);
2104 }
2105 if !output_id_matches(only, req) {
2106 return Err(IndicatorDispatchError::UnknownOutput {
2107 indicator: info.id.to_string(),
2108 output: req.to_string(),
2109 });
2110 }
2111 }
2112 return Ok(only);
2113 }
2114
2115 let req = requested.ok_or_else(|| IndicatorDispatchError::InvalidParam {
2116 indicator: info.id.to_string(),
2117 key: "output_id".to_string(),
2118 reason: "output_id is required for multi-output indicators".to_string(),
2119 })?;
2120
2121 if let Some(out) = info.outputs.iter().find(|o| o.id == req) {
2122 return Ok(out.id);
2123 }
2124 info.outputs
2125 .iter()
2126 .find(|o| output_id_matches(o.id, req))
2127 .map(|o| o.id)
2128 .ok_or_else(|| IndicatorDispatchError::UnknownOutput {
2129 indicator: info.id.to_string(),
2130 output: req.to_string(),
2131 })
2132}
2133
2134fn is_moving_average(id: &str) -> bool {
2135 list_moving_averages()
2136 .iter()
2137 .any(|ma| ma.id.eq_ignore_ascii_case(id))
2138}
2139
2140fn ma_is_period_based(info: &IndicatorInfo) -> bool {
2141 info.params
2142 .iter()
2143 .any(|p| p.key.eq_ignore_ascii_case("period"))
2144}
2145
2146fn compute_ma_batch(
2147 req: IndicatorBatchRequest<'_>,
2148 info: &IndicatorInfo,
2149 output_id: &str,
2150) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2151 let data = ma_data_from_req(info.id, req.data)?;
2152 let cols = ma_len_from_req(info.id, req.data)?;
2153 let period_based = ma_is_period_based(info);
2154 if period_based {
2155 if let Some(out) = try_compute_ma_batch_fast(req, info, output_id, data.clone(), cols)? {
2156 return Ok(out);
2157 }
2158 }
2159 let rows = req.combos.len();
2160 let mut matrix = Vec::with_capacity(rows.saturating_mul(cols));
2161
2162 for combo in req.combos {
2163 let period = ma_period_for_combo(info, combo.params)?;
2164 let mut params = convert_ma_params(combo.params, info.id, output_id)?;
2165 if info.outputs.len() > 1 && !has_key(combo.params, "output") {
2166 params.push(MaBatchParamKV {
2167 key: "output",
2168 value: MaBatchParamValue::EnumString(output_id),
2169 });
2170 }
2171 let out = ma_batch_with_kernel_and_typed_params(
2172 info.id,
2173 data.clone(),
2174 (period, period, 0),
2175 req.kernel,
2176 ¶ms,
2177 )
2178 .map_err(|e| IndicatorDispatchError::ComputeFailed {
2179 indicator: info.id.to_string(),
2180 details: e.to_string(),
2181 })?;
2182 ensure_len(info.id, cols, out.cols)?;
2183 let row_values = if out.rows == 1 {
2184 out.values
2185 } else {
2186 reorder_or_take_f64_matrix_by_period(
2187 info.id,
2188 &[period],
2189 &out.periods,
2190 out.cols,
2191 out.values,
2192 )?
2193 };
2194 ensure_len(info.id, cols, row_values.len())?;
2195 matrix.extend_from_slice(&row_values);
2196 }
2197
2198 Ok(f64_output(output_id, rows, cols, matrix))
2199}
2200
2201fn try_compute_ma_batch_fast(
2202 req: IndicatorBatchRequest<'_>,
2203 info: &IndicatorInfo,
2204 output_id: &str,
2205 data: MaData<'_>,
2206 cols: usize,
2207) -> Result<Option<IndicatorBatchOutput>, IndicatorDispatchError> {
2208 if req.combos.is_empty() {
2209 return Ok(Some(f64_output(output_id, 0, cols, Vec::new())));
2210 }
2211 if !ma_is_period_based(info) {
2212 return Ok(None);
2213 }
2214
2215 let mut periods = Vec::with_capacity(req.combos.len());
2216 let mut shared_params: Option<Vec<MaBatchParamKV<'_>>> = None;
2217
2218 for combo in req.combos {
2219 periods.push(ma_period_for_combo(info, combo.params)?);
2220 let mut params = convert_ma_params(combo.params, info.id, output_id)?;
2221 if info.outputs.len() > 1 && !has_key(combo.params, "output") {
2222 params.push(MaBatchParamKV {
2223 key: "output",
2224 value: MaBatchParamValue::EnumString(output_id),
2225 });
2226 }
2227 match &shared_params {
2228 None => shared_params = Some(params),
2229 Some(existing) => {
2230 if !ma_params_equal(existing, ¶ms) {
2231 return Ok(None);
2232 }
2233 }
2234 }
2235 }
2236
2237 let Some((start, end, step)) = derive_period_sweep(&periods) else {
2238 return Ok(None);
2239 };
2240
2241 let out = ma_batch_with_kernel_and_typed_params(
2242 info.id,
2243 data,
2244 (start, end, step),
2245 req.kernel,
2246 shared_params.as_deref().unwrap_or(&[]),
2247 )
2248 .map_err(|e| IndicatorDispatchError::ComputeFailed {
2249 indicator: info.id.to_string(),
2250 details: e.to_string(),
2251 })?;
2252 ensure_len(info.id, cols, out.cols)?;
2253
2254 let values = reorder_or_take_f64_matrix_by_period(
2255 info.id,
2256 &periods,
2257 &out.periods,
2258 out.cols,
2259 out.values,
2260 )?;
2261 Ok(Some(f64_output(output_id, periods.len(), cols, values)))
2262}
2263
2264fn ma_params_equal(a: &[MaBatchParamKV<'_>], b: &[MaBatchParamKV<'_>]) -> bool {
2265 if a.len() != b.len() {
2266 return false;
2267 }
2268
2269 for (lhs, rhs) in a.iter().zip(b.iter()) {
2270 if !lhs.key.eq_ignore_ascii_case(rhs.key) {
2271 return false;
2272 }
2273 let same = match (&lhs.value, &rhs.value) {
2274 (MaBatchParamValue::Int(x), MaBatchParamValue::Int(y)) => x == y,
2275 (MaBatchParamValue::Float(x), MaBatchParamValue::Float(y)) => x == y,
2276 (MaBatchParamValue::Bool(x), MaBatchParamValue::Bool(y)) => x == y,
2277 (MaBatchParamValue::EnumString(x), MaBatchParamValue::EnumString(y)) => {
2278 x.eq_ignore_ascii_case(y)
2279 }
2280 _ => false,
2281 };
2282 if !same {
2283 return false;
2284 }
2285 }
2286 true
2287}
2288
2289fn collect_f64(
2290 indicator: &str,
2291 output_id: &str,
2292 combos: &[IndicatorParamSet<'_>],
2293 cols: usize,
2294 mut eval: impl FnMut(&[ParamKV<'_>]) -> Result<Vec<f64>, IndicatorDispatchError>,
2295) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2296 let rows = combos.len();
2297 let mut matrix = Vec::with_capacity(rows.saturating_mul(cols));
2298 for combo in combos {
2299 let series = eval(combo.params)?;
2300 ensure_len(indicator, cols, series.len())?;
2301 matrix.extend_from_slice(&series);
2302 }
2303 Ok(f64_output(output_id, rows, cols, matrix))
2304}
2305
2306fn collect_bool(
2307 indicator: &str,
2308 output_id: &str,
2309 combos: &[IndicatorParamSet<'_>],
2310 cols: usize,
2311 mut eval: impl FnMut(&[ParamKV<'_>]) -> Result<Vec<bool>, IndicatorDispatchError>,
2312) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2313 let rows = combos.len();
2314 let mut matrix = Vec::with_capacity(rows.saturating_mul(cols));
2315 for combo in combos {
2316 let series = eval(combo.params)?;
2317 ensure_len(indicator, cols, series.len())?;
2318 matrix.extend_from_slice(&series);
2319 }
2320 Ok(bool_output(output_id, rows, cols, matrix))
2321}
2322
2323fn collect_f64_into_rows(
2324 indicator: &str,
2325 output_id: &str,
2326 combos: &[IndicatorParamSet<'_>],
2327 cols: usize,
2328 mut eval_into: impl FnMut(&[ParamKV<'_>], &mut [f64]) -> Result<(), IndicatorDispatchError>,
2329) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2330 let rows = combos.len();
2331 let total = rows
2332 .checked_mul(cols)
2333 .ok_or_else(|| IndicatorDispatchError::ComputeFailed {
2334 indicator: indicator.to_string(),
2335 details: "rows*cols overflow".to_string(),
2336 })?;
2337 let mut matrix = vec![f64::NAN; total];
2338 for (row, combo) in combos.iter().enumerate() {
2339 let start = row * cols;
2340 let end = start + cols;
2341 eval_into(combo.params, &mut matrix[start..end])?;
2342 }
2343 Ok(f64_output(output_id, rows, cols, matrix))
2344}
2345
2346fn to_batch_kernel(kernel: Kernel) -> Kernel {
2347 match kernel {
2348 Kernel::Auto => Kernel::Auto,
2349 Kernel::Scalar => Kernel::ScalarBatch,
2350 Kernel::Avx2 => Kernel::Avx2Batch,
2351 Kernel::Avx512 => Kernel::Avx512Batch,
2352 other => other,
2353 }
2354}
2355
2356fn combo_periods(
2357 indicator: &str,
2358 combos: &[IndicatorParamSet<'_>],
2359 key: &str,
2360 default: usize,
2361) -> Result<Vec<usize>, IndicatorDispatchError> {
2362 let mut out = Vec::with_capacity(combos.len());
2363 for combo in combos {
2364 out.push(get_usize_param(indicator, combo.params, key, default)?);
2365 }
2366 Ok(out)
2367}
2368
2369fn derive_period_sweep(periods: &[usize]) -> Option<(usize, usize, usize)> {
2370 if periods.is_empty() {
2371 return None;
2372 }
2373 if periods.len() == 1 {
2374 return Some((periods[0], periods[0], 0));
2375 }
2376 if periods.windows(2).all(|w| w[0] == w[1]) {
2377 return Some((periods[0], periods[0], 0));
2378 }
2379
2380 let diff = periods[1] as isize - periods[0] as isize;
2381 if diff == 0 {
2382 return None;
2383 }
2384 if !periods
2385 .windows(2)
2386 .all(|w| (w[1] as isize - w[0] as isize) == diff)
2387 {
2388 return None;
2389 }
2390
2391 Some((
2392 periods[0],
2393 *periods.last().unwrap_or(&periods[0]),
2394 diff.unsigned_abs(),
2395 ))
2396}
2397
2398fn reorder_or_take_f64_matrix_by_period(
2399 indicator: &str,
2400 requested_periods: &[usize],
2401 produced_periods: &[usize],
2402 cols: usize,
2403 values: Vec<f64>,
2404) -> Result<Vec<f64>, IndicatorDispatchError> {
2405 ensure_len(
2406 indicator,
2407 produced_periods.len().saturating_mul(cols),
2408 values.len(),
2409 )?;
2410
2411 if requested_periods.len() == produced_periods.len() && requested_periods == produced_periods {
2412 return Ok(values);
2413 }
2414
2415 let period_to_row: HashMap<usize, usize> = produced_periods
2416 .iter()
2417 .copied()
2418 .enumerate()
2419 .map(|(row, period)| (period, row))
2420 .collect();
2421
2422 let mut out = Vec::with_capacity(requested_periods.len().saturating_mul(cols));
2423 for period in requested_periods {
2424 let row = period_to_row.get(period).copied().ok_or_else(|| {
2425 IndicatorDispatchError::ComputeFailed {
2426 indicator: indicator.to_string(),
2427 details: format!("batch output did not contain requested period {period}"),
2428 }
2429 })?;
2430 let start = row * cols;
2431 let end = start + cols;
2432 out.extend_from_slice(&values[start..end]);
2433 }
2434 Ok(out)
2435}
2436
2437fn compute_ad_batch(
2438 req: IndicatorBatchRequest<'_>,
2439 output_id: &str,
2440) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2441 expect_value_output("ad", output_id)?;
2442 let (high, low, close, volume) = extract_hlcv_input("ad", req.data)?;
2443 let kernel = req.kernel.to_non_batch();
2444 collect_f64("ad", output_id, req.combos, close.len(), |_params| {
2445 let input = AdInput::from_slices(high, low, close, volume, AdParams::default());
2446 let out =
2447 ad_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
2448 indicator: "ad".to_string(),
2449 details: e.to_string(),
2450 })?;
2451 Ok(out.values)
2452 })
2453}
2454
2455fn compute_adosc_batch(
2456 req: IndicatorBatchRequest<'_>,
2457 output_id: &str,
2458) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2459 expect_value_output("adosc", output_id)?;
2460 let (high, low, close, volume) = extract_hlcv_input("adosc", req.data)?;
2461 let kernel = req.kernel.to_non_batch();
2462 collect_f64("adosc", output_id, req.combos, close.len(), |params| {
2463 let short_period = get_usize_param("adosc", params, "short_period", 3)?;
2464 let long_period = get_usize_param("adosc", params, "long_period", 10)?;
2465 let input = AdoscInput::from_slices(
2466 high,
2467 low,
2468 close,
2469 volume,
2470 AdoscParams {
2471 short_period: Some(short_period),
2472 long_period: Some(long_period),
2473 },
2474 );
2475 let out = adosc_with_kernel(&input, kernel).map_err(|e| {
2476 IndicatorDispatchError::ComputeFailed {
2477 indicator: "adosc".to_string(),
2478 details: e.to_string(),
2479 }
2480 })?;
2481 Ok(out.values)
2482 })
2483}
2484
2485fn compute_ao_batch(
2486 req: IndicatorBatchRequest<'_>,
2487 output_id: &str,
2488) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2489 expect_value_output("ao", output_id)?;
2490 let mut derived_source: Option<Vec<f64>> = None;
2491 let source: &[f64] = match req.data {
2492 IndicatorDataRef::Slice { values } => values,
2493 IndicatorDataRef::Candles { candles, source } => {
2494 source_type(candles, source.unwrap_or("hl2"))
2495 }
2496 IndicatorDataRef::HighLow { high, low } => {
2497 ensure_same_len_2("ao", high.len(), low.len())?;
2498 derived_source = Some(high.iter().zip(low).map(|(h, l)| 0.5 * (h + l)).collect());
2499 derived_source.as_deref().unwrap_or(high)
2500 }
2501 IndicatorDataRef::Ohlc {
2502 open,
2503 high,
2504 low,
2505 close,
2506 } => {
2507 ensure_same_len_4("ao", open.len(), high.len(), low.len(), close.len())?;
2508 derived_source = Some(high.iter().zip(low).map(|(h, l)| 0.5 * (h + l)).collect());
2509 derived_source.as_deref().unwrap_or(close)
2510 }
2511 IndicatorDataRef::Ohlcv {
2512 open,
2513 high,
2514 low,
2515 close,
2516 volume,
2517 } => {
2518 ensure_same_len_5(
2519 "ao",
2520 open.len(),
2521 high.len(),
2522 low.len(),
2523 close.len(),
2524 volume.len(),
2525 )?;
2526 derived_source = Some(high.iter().zip(low).map(|(h, l)| 0.5 * (h + l)).collect());
2527 derived_source.as_deref().unwrap_or(close)
2528 }
2529 IndicatorDataRef::CloseVolume { .. } => {
2530 return Err(IndicatorDispatchError::MissingRequiredInput {
2531 indicator: "ao".to_string(),
2532 input: IndicatorInputKind::HighLow,
2533 })
2534 }
2535 };
2536 let kernel = req.kernel.to_non_batch();
2537 collect_f64_into_rows("ao", output_id, req.combos, source.len(), |params, row| {
2538 let short_period = get_usize_param("ao", params, "short_period", 5)?;
2539 let long_period = get_usize_param("ao", params, "long_period", 34)?;
2540 let input = AoInput::from_slice(
2541 source,
2542 AoParams {
2543 short_period: Some(short_period),
2544 long_period: Some(long_period),
2545 },
2546 );
2547 ao_into_slice(row, &input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
2548 indicator: "ao".to_string(),
2549 details: e.to_string(),
2550 })
2551 })
2552}
2553
2554fn compute_bop_batch(
2555 req: IndicatorBatchRequest<'_>,
2556 output_id: &str,
2557) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2558 expect_value_output("bop", output_id)?;
2559 let (open, high, low, close): (&[f64], &[f64], &[f64], &[f64]) = match req.data {
2560 IndicatorDataRef::Candles { candles, .. } => (
2561 candles.open.as_slice(),
2562 candles.high.as_slice(),
2563 candles.low.as_slice(),
2564 candles.close.as_slice(),
2565 ),
2566 IndicatorDataRef::Ohlc {
2567 open,
2568 high,
2569 low,
2570 close,
2571 } => {
2572 ensure_same_len_4("bop", open.len(), high.len(), low.len(), close.len())?;
2573 (open, high, low, close)
2574 }
2575 IndicatorDataRef::Ohlcv {
2576 open,
2577 high,
2578 low,
2579 close,
2580 volume,
2581 } => {
2582 ensure_same_len_5(
2583 "bop",
2584 open.len(),
2585 high.len(),
2586 low.len(),
2587 close.len(),
2588 volume.len(),
2589 )?;
2590 (open, high, low, close)
2591 }
2592 _ => {
2593 return Err(IndicatorDispatchError::MissingRequiredInput {
2594 indicator: "bop".to_string(),
2595 input: IndicatorInputKind::Ohlc,
2596 })
2597 }
2598 };
2599 let kernel = req.kernel.to_non_batch();
2600 collect_f64("bop", output_id, req.combos, close.len(), |_params| {
2601 let input = BopInput::from_slices(open, high, low, close, BopParams::default());
2602 let out =
2603 bop_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
2604 indicator: "bop".to_string(),
2605 details: e.to_string(),
2606 })?;
2607 Ok(out.values)
2608 })
2609}
2610
2611fn compute_emv_batch(
2612 req: IndicatorBatchRequest<'_>,
2613 output_id: &str,
2614) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2615 expect_value_output("emv", output_id)?;
2616 let (high, low, close, volume) = extract_hlcv_input("emv", req.data)?;
2617 let kernel = req.kernel.to_non_batch();
2618 collect_f64("emv", output_id, req.combos, close.len(), |_params| {
2619 let input = EmvInput::from_slices(high, low, close, volume);
2620 let out =
2621 emv_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
2622 indicator: "emv".to_string(),
2623 details: e.to_string(),
2624 })?;
2625 Ok(out.values)
2626 })
2627}
2628
2629fn compute_efi_batch(
2630 req: IndicatorBatchRequest<'_>,
2631 output_id: &str,
2632) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2633 expect_value_output("efi", output_id)?;
2634 let (price, volume) = extract_close_volume_input("efi", req.data, "close")?;
2635 let kernel = req.kernel.to_non_batch();
2636 collect_f64("efi", output_id, req.combos, price.len(), |params| {
2637 let period = get_usize_param("efi", params, "period", 13)?;
2638 let input = EfiInput::from_slices(
2639 price,
2640 volume,
2641 EfiParams {
2642 period: Some(period),
2643 },
2644 );
2645 let out =
2646 efi_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
2647 indicator: "efi".to_string(),
2648 details: e.to_string(),
2649 })?;
2650 Ok(out.values)
2651 })
2652}
2653
2654fn compute_mfi_batch(
2655 req: IndicatorBatchRequest<'_>,
2656 output_id: &str,
2657) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2658 expect_value_output("mfi", output_id)?;
2659 let mut derived_typical_price: Option<Vec<f64>> = None;
2660 let (typical_price, volume): (&[f64], &[f64]) = match req.data {
2661 IndicatorDataRef::Candles { candles, source } => (
2662 source_type(candles, source.unwrap_or("hlc3")),
2663 candles.volume.as_slice(),
2664 ),
2665 IndicatorDataRef::Ohlcv {
2666 open,
2667 high,
2668 low,
2669 close,
2670 volume,
2671 } => {
2672 ensure_same_len_5(
2673 "mfi",
2674 open.len(),
2675 high.len(),
2676 low.len(),
2677 close.len(),
2678 volume.len(),
2679 )?;
2680 derived_typical_price = Some(
2681 high.iter()
2682 .zip(low)
2683 .zip(close)
2684 .map(|((h, l), c)| (h + l + c) / 3.0)
2685 .collect(),
2686 );
2687 (derived_typical_price.as_deref().unwrap_or(close), volume)
2688 }
2689 IndicatorDataRef::CloseVolume { close, volume } => {
2690 ensure_same_len_2("mfi", close.len(), volume.len())?;
2691 (close, volume)
2692 }
2693 _ => {
2694 return Err(IndicatorDispatchError::MissingRequiredInput {
2695 indicator: "mfi".to_string(),
2696 input: IndicatorInputKind::CloseVolume,
2697 })
2698 }
2699 };
2700
2701 let periods = combo_periods("mfi", req.combos, "period", 14)?;
2702 if let Some((start, end, step)) = derive_period_sweep(&periods) {
2703 let out = mfi_batch_with_kernel(
2704 typical_price,
2705 volume,
2706 &MfiBatchRange {
2707 period: (start, end, step),
2708 },
2709 to_batch_kernel(req.kernel),
2710 )
2711 .map_err(|e| IndicatorDispatchError::ComputeFailed {
2712 indicator: "mfi".to_string(),
2713 details: e.to_string(),
2714 })?;
2715 ensure_len("mfi", typical_price.len(), out.cols)?;
2716 let produced_periods: Vec<usize> = out
2717 .combos
2718 .iter()
2719 .map(|combo| combo.period.unwrap_or(14))
2720 .collect();
2721 let values = reorder_or_take_f64_matrix_by_period(
2722 "mfi",
2723 &periods,
2724 &produced_periods,
2725 out.cols,
2726 out.values,
2727 )?;
2728 return Ok(f64_output(output_id, periods.len(), out.cols, values));
2729 }
2730
2731 let kernel = req.kernel.to_non_batch();
2732 collect_f64_into_rows(
2733 "mfi",
2734 output_id,
2735 req.combos,
2736 typical_price.len(),
2737 |params, row| {
2738 let period = get_usize_param("mfi", params, "period", 14)?;
2739 let input = MfiInput::from_slices(
2740 typical_price,
2741 volume,
2742 MfiParams {
2743 period: Some(period),
2744 },
2745 );
2746 mfi_into_slice(row, &input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
2747 indicator: "mfi".to_string(),
2748 details: e.to_string(),
2749 })
2750 },
2751 )
2752}
2753
2754fn compute_mass_batch(
2755 req: IndicatorBatchRequest<'_>,
2756 output_id: &str,
2757) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2758 expect_value_output("mass", output_id)?;
2759 let (high, low) = extract_high_low_input("mass", req.data)?;
2760 let kernel = req.kernel.to_non_batch();
2761 collect_f64("mass", output_id, req.combos, high.len(), |params| {
2762 let period = get_usize_param("mass", params, "period", 5)?;
2763 let input = MassInput::from_slices(
2764 high,
2765 low,
2766 MassParams {
2767 period: Some(period),
2768 },
2769 );
2770 let out = mass_with_kernel(&input, kernel).map_err(|e| {
2771 IndicatorDispatchError::ComputeFailed {
2772 indicator: "mass".to_string(),
2773 details: e.to_string(),
2774 }
2775 })?;
2776 Ok(out.values)
2777 })
2778}
2779
2780fn compute_kvo_batch(
2781 req: IndicatorBatchRequest<'_>,
2782 output_id: &str,
2783) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2784 expect_value_output("kvo", output_id)?;
2785 let (high, low, close, volume) = extract_hlcv_input("kvo", req.data)?;
2786 let kernel = req.kernel.to_non_batch();
2787 collect_f64("kvo", output_id, req.combos, close.len(), |params| {
2788 let short_period = get_usize_param("kvo", params, "short_period", 2)?;
2789 let long_period = get_usize_param("kvo", params, "long_period", 5)?;
2790 let input = KvoInput::from_slices(
2791 high,
2792 low,
2793 close,
2794 volume,
2795 KvoParams {
2796 short_period: Some(short_period),
2797 long_period: Some(long_period),
2798 },
2799 );
2800 let out =
2801 kvo_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
2802 indicator: "kvo".to_string(),
2803 details: e.to_string(),
2804 })?;
2805 Ok(out.values)
2806 })
2807}
2808
2809fn compute_vosc_batch(
2810 req: IndicatorBatchRequest<'_>,
2811 output_id: &str,
2812) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2813 expect_value_output("vosc", output_id)?;
2814 let volume = extract_volume_input("vosc", req.data)?;
2815 let kernel = req.kernel.to_non_batch();
2816 collect_f64("vosc", output_id, req.combos, volume.len(), |params| {
2817 let short_period = get_usize_param("vosc", params, "short_period", 2)?;
2818 let long_period = get_usize_param("vosc", params, "long_period", 5)?;
2819 let input = VoscInput::from_slice(
2820 volume,
2821 VoscParams {
2822 short_period: Some(short_period),
2823 long_period: Some(long_period),
2824 },
2825 );
2826 let out = vosc_with_kernel(&input, kernel).map_err(|e| {
2827 IndicatorDispatchError::ComputeFailed {
2828 indicator: "vosc".to_string(),
2829 details: e.to_string(),
2830 }
2831 })?;
2832 Ok(out.values)
2833 })
2834}
2835
2836fn compute_dx_batch(
2837 req: IndicatorBatchRequest<'_>,
2838 output_id: &str,
2839) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2840 expect_value_output("dx", output_id)?;
2841 let (high, low, close) = extract_ohlc_input("dx", req.data)?;
2842
2843 let periods = combo_periods("dx", req.combos, "period", 14)?;
2844 if let Some((start, end, step)) = derive_period_sweep(&periods) {
2845 let out = dx_batch_with_kernel(
2846 high,
2847 low,
2848 close,
2849 &DxBatchRange {
2850 period: (start, end, step),
2851 },
2852 to_batch_kernel(req.kernel),
2853 )
2854 .map_err(|e| IndicatorDispatchError::ComputeFailed {
2855 indicator: "dx".to_string(),
2856 details: e.to_string(),
2857 })?;
2858 ensure_len("dx", close.len(), out.cols)?;
2859 let produced_periods: Vec<usize> = out
2860 .combos
2861 .iter()
2862 .map(|combo| combo.period.unwrap_or(14))
2863 .collect();
2864 let values = reorder_or_take_f64_matrix_by_period(
2865 "dx",
2866 &periods,
2867 &produced_periods,
2868 out.cols,
2869 out.values,
2870 )?;
2871 return Ok(f64_output(output_id, periods.len(), out.cols, values));
2872 }
2873
2874 let kernel = req.kernel.to_non_batch();
2875 collect_f64_into_rows("dx", output_id, req.combos, close.len(), |params, row| {
2876 let period = get_usize_param("dx", params, "period", 14)?;
2877 let input = DxInput::from_hlc_slices(
2878 high,
2879 low,
2880 close,
2881 DxParams {
2882 period: Some(period),
2883 },
2884 );
2885 dx_into_slice(row, &input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
2886 indicator: "dx".to_string(),
2887 details: e.to_string(),
2888 })
2889 })
2890}
2891
2892fn compute_fosc_batch(
2893 req: IndicatorBatchRequest<'_>,
2894 output_id: &str,
2895) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2896 expect_value_output("fosc", output_id)?;
2897 let data = extract_slice_input("fosc", req.data, "close")?;
2898 let kernel = req.kernel.to_non_batch();
2899 collect_f64("fosc", output_id, req.combos, data.len(), |params| {
2900 let period = get_usize_param("fosc", params, "period", 5)?;
2901 let input = FoscInput::from_slice(
2902 data,
2903 FoscParams {
2904 period: Some(period),
2905 },
2906 );
2907 let out = fosc_with_kernel(&input, kernel).map_err(|e| {
2908 IndicatorDispatchError::ComputeFailed {
2909 indicator: "fosc".to_string(),
2910 details: e.to_string(),
2911 }
2912 })?;
2913 Ok(out.values)
2914 })
2915}
2916
2917fn compute_ift_rsi_batch(
2918 req: IndicatorBatchRequest<'_>,
2919 output_id: &str,
2920) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2921 expect_value_output("ift_rsi", output_id)?;
2922 let data = extract_slice_input("ift_rsi", req.data, "close")?;
2923 let kernel = req.kernel.to_non_batch();
2924 collect_f64("ift_rsi", output_id, req.combos, data.len(), |params| {
2925 let rsi_period = get_usize_param("ift_rsi", params, "rsi_period", 5)?;
2926 let wma_period = get_usize_param("ift_rsi", params, "wma_period", 9)?;
2927 let input = IftRsiInput::from_slice(
2928 data,
2929 IftRsiParams {
2930 rsi_period: Some(rsi_period),
2931 wma_period: Some(wma_period),
2932 },
2933 );
2934 let out = ift_rsi_with_kernel(&input, kernel).map_err(|e| {
2935 IndicatorDispatchError::ComputeFailed {
2936 indicator: "ift_rsi".to_string(),
2937 details: e.to_string(),
2938 }
2939 })?;
2940 Ok(out.values)
2941 })
2942}
2943
2944fn compute_linearreg_angle_batch(
2945 req: IndicatorBatchRequest<'_>,
2946 output_id: &str,
2947) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2948 expect_value_output("linearreg_angle", output_id)?;
2949 let data = extract_slice_input("linearreg_angle", req.data, "close")?;
2950 let kernel = req.kernel.to_non_batch();
2951 collect_f64(
2952 "linearreg_angle",
2953 output_id,
2954 req.combos,
2955 data.len(),
2956 |params| {
2957 let period = get_usize_param("linearreg_angle", params, "period", 14)?;
2958 let input = Linearreg_angleInput::from_slice(
2959 data,
2960 Linearreg_angleParams {
2961 period: Some(period),
2962 },
2963 );
2964 let out = linearreg_angle_with_kernel(&input, kernel).map_err(|e| {
2965 IndicatorDispatchError::ComputeFailed {
2966 indicator: "linearreg_angle".to_string(),
2967 details: e.to_string(),
2968 }
2969 })?;
2970 Ok(out.values)
2971 },
2972 )
2973}
2974
2975fn compute_linearreg_intercept_batch(
2976 req: IndicatorBatchRequest<'_>,
2977 output_id: &str,
2978) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
2979 expect_value_output("linearreg_intercept", output_id)?;
2980 let data = extract_slice_input("linearreg_intercept", req.data, "close")?;
2981 let kernel = req.kernel.to_non_batch();
2982 collect_f64(
2983 "linearreg_intercept",
2984 output_id,
2985 req.combos,
2986 data.len(),
2987 |params| {
2988 let period = get_usize_param("linearreg_intercept", params, "period", 14)?;
2989 let input = LinearRegInterceptInput::from_slice(
2990 data,
2991 LinearRegInterceptParams {
2992 period: Some(period),
2993 },
2994 );
2995 let out = linearreg_intercept_with_kernel(&input, kernel).map_err(|e| {
2996 IndicatorDispatchError::ComputeFailed {
2997 indicator: "linearreg_intercept".to_string(),
2998 details: e.to_string(),
2999 }
3000 })?;
3001 Ok(out.values)
3002 },
3003 )
3004}
3005
3006fn compute_linearreg_slope_batch(
3007 req: IndicatorBatchRequest<'_>,
3008 output_id: &str,
3009) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3010 expect_value_output("linearreg_slope", output_id)?;
3011 let data = extract_slice_input("linearreg_slope", req.data, "close")?;
3012 let kernel = req.kernel.to_non_batch();
3013 collect_f64(
3014 "linearreg_slope",
3015 output_id,
3016 req.combos,
3017 data.len(),
3018 |params| {
3019 let period = get_usize_param("linearreg_slope", params, "period", 14)?;
3020 let input = LinearRegSlopeInput::from_slice(
3021 data,
3022 LinearRegSlopeParams {
3023 period: Some(period),
3024 },
3025 );
3026 let out = linearreg_slope_with_kernel(&input, kernel).map_err(|e| {
3027 IndicatorDispatchError::ComputeFailed {
3028 indicator: "linearreg_slope".to_string(),
3029 details: e.to_string(),
3030 }
3031 })?;
3032 Ok(out.values)
3033 },
3034 )
3035}
3036
3037fn compute_cg_batch(
3038 req: IndicatorBatchRequest<'_>,
3039 output_id: &str,
3040) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3041 expect_value_output("cg", output_id)?;
3042 let data = extract_slice_input("cg", req.data, "close")?;
3043 let kernel = req.kernel.to_non_batch();
3044 collect_f64("cg", output_id, req.combos, data.len(), |params| {
3045 let period = get_usize_param("cg", params, "period", 10)?;
3046 let input = CgInput::from_slice(
3047 data,
3048 CgParams {
3049 period: Some(period),
3050 },
3051 );
3052 let out =
3053 cg_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3054 indicator: "cg".to_string(),
3055 details: e.to_string(),
3056 })?;
3057 Ok(out.values)
3058 })
3059}
3060
3061fn compute_rsi_batch(
3062 req: IndicatorBatchRequest<'_>,
3063 output_id: &str,
3064) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3065 expect_value_output("rsi", output_id)?;
3066 let data = extract_slice_input("rsi", req.data, "close")?;
3067 let kernel = req.kernel.to_non_batch();
3068 collect_f64("rsi", output_id, req.combos, data.len(), |params| {
3069 let period = get_usize_param("rsi", params, "period", 14)?;
3070 let input = RsiInput::from_slice(
3071 data,
3072 RsiParams {
3073 period: Some(period),
3074 },
3075 );
3076 let out =
3077 rsi_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3078 indicator: "rsi".to_string(),
3079 details: e.to_string(),
3080 })?;
3081 Ok(out.values)
3082 })
3083}
3084
3085fn compute_roc_batch(
3086 req: IndicatorBatchRequest<'_>,
3087 output_id: &str,
3088) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3089 expect_value_output("roc", output_id)?;
3090 let data = extract_slice_input("roc", req.data, "close")?;
3091 let kernel = req.kernel.to_non_batch();
3092 collect_f64("roc", output_id, req.combos, data.len(), |params| {
3093 let period = get_usize_param("roc", params, "period", 9)?;
3094 let input = RocInput::from_slice(
3095 data,
3096 RocParams {
3097 period: Some(period),
3098 },
3099 );
3100 let out =
3101 roc_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3102 indicator: "roc".to_string(),
3103 details: e.to_string(),
3104 })?;
3105 Ok(out.values)
3106 })
3107}
3108
3109fn compute_linear_correlation_oscillator_batch(
3110 req: IndicatorBatchRequest<'_>,
3111 output_id: &str,
3112) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3113 expect_value_output("linear_correlation_oscillator", output_id)?;
3114 let data = extract_slice_input("linear_correlation_oscillator", req.data, "close")?;
3115 let kernel = req.kernel.to_non_batch();
3116 collect_f64(
3117 "linear_correlation_oscillator",
3118 output_id,
3119 req.combos,
3120 data.len(),
3121 |params| {
3122 let period = get_usize_param("linear_correlation_oscillator", params, "period", 14)?;
3123 let input = LinearCorrelationOscillatorInput::from_slice(
3124 data,
3125 LinearCorrelationOscillatorParams {
3126 period: Some(period),
3127 },
3128 );
3129 let out = linear_correlation_oscillator_with_kernel(&input, kernel).map_err(|e| {
3130 IndicatorDispatchError::ComputeFailed {
3131 indicator: "linear_correlation_oscillator".to_string(),
3132 details: e.to_string(),
3133 }
3134 })?;
3135 Ok(out.values)
3136 },
3137 )
3138}
3139
3140fn compute_apo_batch(
3141 req: IndicatorBatchRequest<'_>,
3142 output_id: &str,
3143) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3144 expect_value_output("apo", output_id)?;
3145 let data = extract_slice_input("apo", req.data, "close")?;
3146 let kernel = req.kernel.to_non_batch();
3147 collect_f64("apo", output_id, req.combos, data.len(), |params| {
3148 let short_period = get_usize_param("apo", params, "short_period", 10)?;
3149 let long_period = get_usize_param("apo", params, "long_period", 20)?;
3150 let input = ApoInput::from_slice(
3151 data,
3152 ApoParams {
3153 short_period: Some(short_period),
3154 long_period: Some(long_period),
3155 },
3156 );
3157 let out =
3158 apo_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3159 indicator: "apo".to_string(),
3160 details: e.to_string(),
3161 })?;
3162 Ok(out.values)
3163 })
3164}
3165
3166fn compute_cci_batch(
3167 req: IndicatorBatchRequest<'_>,
3168 output_id: &str,
3169) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3170 expect_value_output("cci", output_id)?;
3171 let data = extract_slice_input("cci", req.data, "hlc3")?;
3172 let kernel = req.kernel.to_non_batch();
3173 collect_f64("cci", output_id, req.combos, data.len(), |params| {
3174 let period = get_usize_param("cci", params, "period", 14)?;
3175 let input = CciInput::from_slice(
3176 data,
3177 CciParams {
3178 period: Some(period),
3179 },
3180 );
3181 let out =
3182 cci_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3183 indicator: "cci".to_string(),
3184 details: e.to_string(),
3185 })?;
3186 Ok(out.values)
3187 })
3188}
3189
3190fn compute_cfo_batch(
3191 req: IndicatorBatchRequest<'_>,
3192 output_id: &str,
3193) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3194 expect_value_output("cfo", output_id)?;
3195 let data = extract_slice_input("cfo", req.data, "close")?;
3196 let kernel = req.kernel.to_non_batch();
3197 collect_f64("cfo", output_id, req.combos, data.len(), |params| {
3198 let period = get_usize_param("cfo", params, "period", 14)?;
3199 let scalar = get_f64_param("cfo", params, "scalar", 100.0)?;
3200 let input = CfoInput::from_slice(
3201 data,
3202 CfoParams {
3203 period: Some(period),
3204 scalar: Some(scalar),
3205 },
3206 );
3207 let out =
3208 cfo_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3209 indicator: "cfo".to_string(),
3210 details: e.to_string(),
3211 })?;
3212 Ok(out.values)
3213 })
3214}
3215
3216fn compute_cci_cycle_batch(
3217 req: IndicatorBatchRequest<'_>,
3218 output_id: &str,
3219) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3220 expect_value_output("cci_cycle", output_id)?;
3221 let data = extract_slice_input("cci_cycle", req.data, "close")?;
3222 let kernel = req.kernel.to_non_batch();
3223 collect_f64("cci_cycle", output_id, req.combos, data.len(), |params| {
3224 let length = get_usize_param("cci_cycle", params, "length", 10)?;
3225 let factor = get_f64_param("cci_cycle", params, "factor", 0.5)?;
3226 let input = CciCycleInput::from_slice(
3227 data,
3228 CciCycleParams {
3229 length: Some(length),
3230 factor: Some(factor),
3231 },
3232 );
3233 let out = cci_cycle_with_kernel(&input, kernel).map_err(|e| {
3234 IndicatorDispatchError::ComputeFailed {
3235 indicator: "cci_cycle".to_string(),
3236 details: e.to_string(),
3237 }
3238 })?;
3239 Ok(out.values)
3240 })
3241}
3242
3243fn compute_lrsi_batch(
3244 req: IndicatorBatchRequest<'_>,
3245 output_id: &str,
3246) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3247 expect_value_output("lrsi", output_id)?;
3248 let (high, low) = extract_high_low_input("lrsi", req.data)?;
3249 let kernel = req.kernel.to_non_batch();
3250 collect_f64("lrsi", output_id, req.combos, high.len(), |params| {
3251 let alpha = get_f64_param("lrsi", params, "alpha", 0.2)?;
3252 let input = LrsiInput::from_slices(high, low, LrsiParams { alpha: Some(alpha) });
3253 let out = lrsi_with_kernel(&input, kernel).map_err(|e| {
3254 IndicatorDispatchError::ComputeFailed {
3255 indicator: "lrsi".to_string(),
3256 details: e.to_string(),
3257 }
3258 })?;
3259 Ok(out.values)
3260 })
3261}
3262
3263fn compute_er_batch(
3264 req: IndicatorBatchRequest<'_>,
3265 output_id: &str,
3266) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3267 expect_value_output("er", output_id)?;
3268 let data = extract_slice_input("er", req.data, "close")?;
3269 let kernel = req.kernel.to_non_batch();
3270 collect_f64("er", output_id, req.combos, data.len(), |params| {
3271 let period = get_usize_param("er", params, "period", 5)?;
3272 let input = ErInput::from_slice(
3273 data,
3274 ErParams {
3275 period: Some(period),
3276 },
3277 );
3278 let out =
3279 er_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3280 indicator: "er".to_string(),
3281 details: e.to_string(),
3282 })?;
3283 Ok(out.values)
3284 })
3285}
3286
3287fn compute_kurtosis_batch(
3288 req: IndicatorBatchRequest<'_>,
3289 output_id: &str,
3290) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3291 expect_value_output("kurtosis", output_id)?;
3292 let data = extract_slice_input("kurtosis", req.data, "hl2")?;
3293 let kernel = req.kernel.to_non_batch();
3294 collect_f64("kurtosis", output_id, req.combos, data.len(), |params| {
3295 let period = get_usize_param("kurtosis", params, "period", 5)?;
3296 let input = KurtosisInput::from_slice(
3297 data,
3298 KurtosisParams {
3299 period: Some(period),
3300 },
3301 );
3302 let out = kurtosis_with_kernel(&input, kernel).map_err(|e| {
3303 IndicatorDispatchError::ComputeFailed {
3304 indicator: "kurtosis".to_string(),
3305 details: e.to_string(),
3306 }
3307 })?;
3308 Ok(out.values)
3309 })
3310}
3311
3312fn compute_natr_batch(
3313 req: IndicatorBatchRequest<'_>,
3314 output_id: &str,
3315) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3316 expect_value_output("natr", output_id)?;
3317 let (high, low, close) = extract_ohlc_input("natr", req.data)?;
3318 let kernel = req.kernel.to_non_batch();
3319 collect_f64("natr", output_id, req.combos, close.len(), |params| {
3320 let period = get_usize_param("natr", params, "period", 14)?;
3321 let input = NatrInput::from_slices(
3322 high,
3323 low,
3324 close,
3325 NatrParams {
3326 period: Some(period),
3327 },
3328 );
3329 let out = natr_with_kernel(&input, kernel).map_err(|e| {
3330 IndicatorDispatchError::ComputeFailed {
3331 indicator: "natr".to_string(),
3332 details: e.to_string(),
3333 }
3334 })?;
3335 Ok(out.values)
3336 })
3337}
3338
3339fn compute_mean_ad_batch(
3340 req: IndicatorBatchRequest<'_>,
3341 output_id: &str,
3342) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3343 expect_value_output("mean_ad", output_id)?;
3344 let data = extract_slice_input("mean_ad", req.data, "close")?;
3345 let kernel = req.kernel.to_non_batch();
3346 collect_f64("mean_ad", output_id, req.combos, data.len(), |params| {
3347 let period = get_usize_param("mean_ad", params, "period", 5)?;
3348 let input = MeanAdInput::from_slice(
3349 data,
3350 MeanAdParams {
3351 period: Some(period),
3352 },
3353 );
3354 let out = mean_ad_with_kernel(&input, kernel).map_err(|e| {
3355 IndicatorDispatchError::ComputeFailed {
3356 indicator: "mean_ad".to_string(),
3357 details: e.to_string(),
3358 }
3359 })?;
3360 Ok(out.values)
3361 })
3362}
3363
3364fn compute_medium_ad_batch(
3365 req: IndicatorBatchRequest<'_>,
3366 output_id: &str,
3367) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3368 expect_value_output("medium_ad", output_id)?;
3369 let data = extract_slice_input("medium_ad", req.data, "close")?;
3370 let kernel = req.kernel.to_non_batch();
3371 collect_f64("medium_ad", output_id, req.combos, data.len(), |params| {
3372 let period = get_usize_param("medium_ad", params, "period", 5)?;
3373 let input = MediumAdInput::from_slice(
3374 data,
3375 MediumAdParams {
3376 period: Some(period),
3377 },
3378 );
3379 let out = medium_ad_with_kernel(&input, kernel).map_err(|e| {
3380 IndicatorDispatchError::ComputeFailed {
3381 indicator: "medium_ad".to_string(),
3382 details: e.to_string(),
3383 }
3384 })?;
3385 Ok(out.values)
3386 })
3387}
3388
3389fn compute_deviation_batch(
3390 req: IndicatorBatchRequest<'_>,
3391 output_id: &str,
3392) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3393 expect_value_output("deviation", output_id)?;
3394 let data = extract_slice_input("deviation", req.data, "close")?;
3395 let kernel = req.kernel.to_non_batch();
3396 collect_f64("deviation", output_id, req.combos, data.len(), |params| {
3397 let period = get_usize_param("deviation", params, "period", 9)?;
3398 let devtype = get_usize_param("deviation", params, "devtype", 0)?;
3399 let input = DeviationInput::from_slice(
3400 data,
3401 DeviationParams {
3402 period: Some(period),
3403 devtype: Some(devtype),
3404 },
3405 );
3406 let out = deviation_with_kernel(&input, kernel).map_err(|e| {
3407 IndicatorDispatchError::ComputeFailed {
3408 indicator: "deviation".to_string(),
3409 details: e.to_string(),
3410 }
3411 })?;
3412 Ok(out.values)
3413 })
3414}
3415
3416fn compute_dpo_batch(
3417 req: IndicatorBatchRequest<'_>,
3418 output_id: &str,
3419) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3420 expect_value_output("dpo", output_id)?;
3421 let data = extract_slice_input("dpo", req.data, "close")?;
3422 let kernel = req.kernel.to_non_batch();
3423 collect_f64("dpo", output_id, req.combos, data.len(), |params| {
3424 let period = get_usize_param("dpo", params, "period", 5)?;
3425 let input = DpoInput::from_slice(
3426 data,
3427 DpoParams {
3428 period: Some(period),
3429 },
3430 );
3431 let out =
3432 dpo_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3433 indicator: "dpo".to_string(),
3434 details: e.to_string(),
3435 })?;
3436 Ok(out.values)
3437 })
3438}
3439
3440fn compute_pfe_batch(
3441 req: IndicatorBatchRequest<'_>,
3442 output_id: &str,
3443) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3444 expect_value_output("pfe", output_id)?;
3445 let data = extract_slice_input("pfe", req.data, "close")?;
3446 let kernel = req.kernel.to_non_batch();
3447 collect_f64("pfe", output_id, req.combos, data.len(), |params| {
3448 let period = get_usize_param("pfe", params, "period", 10)?;
3449 let smoothing = get_usize_param("pfe", params, "smoothing", 5)?;
3450 let input = PfeInput::from_slice(
3451 data,
3452 PfeParams {
3453 period: Some(period),
3454 smoothing: Some(smoothing),
3455 },
3456 );
3457 let out =
3458 pfe_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3459 indicator: "pfe".to_string(),
3460 details: e.to_string(),
3461 })?;
3462 Ok(out.values)
3463 })
3464}
3465
3466fn compute_qstick_batch(
3467 req: IndicatorBatchRequest<'_>,
3468 output_id: &str,
3469) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3470 expect_value_output("qstick", output_id)?;
3471 let (open, close) = match req.data {
3472 IndicatorDataRef::Candles { candles, .. } => {
3473 (candles.open.as_slice(), candles.close.as_slice())
3474 }
3475 IndicatorDataRef::Ohlc {
3476 open,
3477 high,
3478 low,
3479 close,
3480 } => {
3481 ensure_same_len_4("qstick", open.len(), high.len(), low.len(), close.len())?;
3482 (open, close)
3483 }
3484 IndicatorDataRef::Ohlcv {
3485 open,
3486 high,
3487 low,
3488 close,
3489 volume,
3490 } => {
3491 ensure_same_len_5(
3492 "qstick",
3493 open.len(),
3494 high.len(),
3495 low.len(),
3496 close.len(),
3497 volume.len(),
3498 )?;
3499 (open, close)
3500 }
3501 _ => {
3502 return Err(IndicatorDispatchError::MissingRequiredInput {
3503 indicator: "qstick".to_string(),
3504 input: IndicatorInputKind::Ohlc,
3505 })
3506 }
3507 };
3508 let kernel = req.kernel.to_non_batch();
3509 collect_f64("qstick", output_id, req.combos, close.len(), |params| {
3510 let period = get_usize_param("qstick", params, "period", 5)?;
3511 let input = QstickInput::from_slices(
3512 open,
3513 close,
3514 QstickParams {
3515 period: Some(period),
3516 },
3517 );
3518 let out = qstick_with_kernel(&input, kernel).map_err(|e| {
3519 IndicatorDispatchError::ComputeFailed {
3520 indicator: "qstick".to_string(),
3521 details: e.to_string(),
3522 }
3523 })?;
3524 Ok(out.values)
3525 })
3526}
3527
3528fn compute_ehlers_fm_demodulator_batch(
3529 req: IndicatorBatchRequest<'_>,
3530 output_id: &str,
3531) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3532 expect_value_output("ehlers_fm_demodulator", output_id)?;
3533 let (open, close) = match req.data {
3534 IndicatorDataRef::Candles { candles, .. } => {
3535 (candles.open.as_slice(), candles.close.as_slice())
3536 }
3537 IndicatorDataRef::Ohlc {
3538 open,
3539 high,
3540 low,
3541 close,
3542 } => {
3543 ensure_same_len_4(
3544 "ehlers_fm_demodulator",
3545 open.len(),
3546 high.len(),
3547 low.len(),
3548 close.len(),
3549 )?;
3550 (open, close)
3551 }
3552 IndicatorDataRef::Ohlcv {
3553 open,
3554 high,
3555 low,
3556 close,
3557 volume,
3558 } => {
3559 ensure_same_len_5(
3560 "ehlers_fm_demodulator",
3561 open.len(),
3562 high.len(),
3563 low.len(),
3564 close.len(),
3565 volume.len(),
3566 )?;
3567 (open, close)
3568 }
3569 _ => {
3570 return Err(IndicatorDispatchError::MissingRequiredInput {
3571 indicator: "ehlers_fm_demodulator".to_string(),
3572 input: IndicatorInputKind::Ohlc,
3573 })
3574 }
3575 };
3576 let kernel = req.kernel.to_non_batch();
3577 collect_f64(
3578 "ehlers_fm_demodulator",
3579 output_id,
3580 req.combos,
3581 close.len(),
3582 |params| {
3583 let period = get_usize_param("ehlers_fm_demodulator", params, "period", 30)?;
3584 let input = EhlersFmDemodulatorInput::from_slices(
3585 open,
3586 close,
3587 EhlersFmDemodulatorParams {
3588 period: Some(period),
3589 },
3590 );
3591 let out = ehlers_fm_demodulator_with_kernel(&input, kernel).map_err(|e| {
3592 IndicatorDispatchError::ComputeFailed {
3593 indicator: "ehlers_fm_demodulator".to_string(),
3594 details: e.to_string(),
3595 }
3596 })?;
3597 Ok(out.values)
3598 },
3599 )
3600}
3601
3602fn compute_reverse_rsi_batch(
3603 req: IndicatorBatchRequest<'_>,
3604 output_id: &str,
3605) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3606 expect_value_output("reverse_rsi", output_id)?;
3607 let data = extract_slice_input("reverse_rsi", req.data, "close")?;
3608 let kernel = req.kernel.to_non_batch();
3609 collect_f64("reverse_rsi", output_id, req.combos, data.len(), |params| {
3610 let rsi_length = get_usize_param("reverse_rsi", params, "rsi_length", 14)?;
3611 let rsi_level = get_f64_param("reverse_rsi", params, "rsi_level", 50.0)?;
3612 let input = ReverseRsiInput::from_slice(
3613 data,
3614 ReverseRsiParams {
3615 rsi_length: Some(rsi_length),
3616 rsi_level: Some(rsi_level),
3617 },
3618 );
3619 let out = reverse_rsi_with_kernel(&input, kernel).map_err(|e| {
3620 IndicatorDispatchError::ComputeFailed {
3621 indicator: "reverse_rsi".to_string(),
3622 details: e.to_string(),
3623 }
3624 })?;
3625 Ok(out.values)
3626 })
3627}
3628
3629fn compute_percentile_nearest_rank_batch(
3630 req: IndicatorBatchRequest<'_>,
3631 output_id: &str,
3632) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3633 expect_value_output("percentile_nearest_rank", output_id)?;
3634 let data = extract_slice_input("percentile_nearest_rank", req.data, "close")?;
3635 let kernel = req.kernel.to_non_batch();
3636 collect_f64(
3637 "percentile_nearest_rank",
3638 output_id,
3639 req.combos,
3640 data.len(),
3641 |params| {
3642 let length = get_usize_param("percentile_nearest_rank", params, "length", 15)?;
3643 let percentage = get_f64_param("percentile_nearest_rank", params, "percentage", 50.0)?;
3644 let input = PercentileNearestRankInput::from_slice(
3645 data,
3646 PercentileNearestRankParams {
3647 length: Some(length),
3648 percentage: Some(percentage),
3649 },
3650 );
3651 let out = percentile_nearest_rank_with_kernel(&input, kernel).map_err(|e| {
3652 IndicatorDispatchError::ComputeFailed {
3653 indicator: "percentile_nearest_rank".to_string(),
3654 details: e.to_string(),
3655 }
3656 })?;
3657 Ok(out.values)
3658 },
3659 )
3660}
3661
3662fn compute_obv_batch(
3663 req: IndicatorBatchRequest<'_>,
3664 output_id: &str,
3665) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3666 expect_value_output("obv", output_id)?;
3667 let (close, volume) = extract_close_volume_input("obv", req.data, "close")?;
3668 let kernel = req.kernel.to_non_batch();
3669 collect_f64("obv", output_id, req.combos, close.len(), |_params| {
3670 let input = ObvInput::from_slices(close, volume, ObvParams::default());
3671 let out =
3672 obv_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3673 indicator: "obv".to_string(),
3674 details: e.to_string(),
3675 })?;
3676 Ok(out.values)
3677 })
3678}
3679
3680fn compute_vpt_batch(
3681 req: IndicatorBatchRequest<'_>,
3682 output_id: &str,
3683) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3684 expect_value_output("vpt", output_id)?;
3685 let (close, volume) = extract_close_volume_input("vpt", req.data, "close")?;
3686 let kernel = req.kernel.to_non_batch();
3687 collect_f64("vpt", output_id, req.combos, close.len(), |_params| {
3688 let input = VptInput::from_slices(close, volume);
3689 let out =
3690 vpt_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3691 indicator: "vpt".to_string(),
3692 details: e.to_string(),
3693 })?;
3694 Ok(out.values)
3695 })
3696}
3697
3698fn compute_nvi_batch(
3699 req: IndicatorBatchRequest<'_>,
3700 output_id: &str,
3701) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3702 expect_value_output("nvi", output_id)?;
3703 let (close, volume) = extract_close_volume_input("nvi", req.data, "close")?;
3704 let kernel = req.kernel.to_non_batch();
3705 collect_f64("nvi", output_id, req.combos, close.len(), |_params| {
3706 let input = NviInput::from_slices(close, volume, NviParams::default());
3707 let out =
3708 nvi_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3709 indicator: "nvi".to_string(),
3710 details: e.to_string(),
3711 })?;
3712 Ok(out.values)
3713 })
3714}
3715
3716fn compute_pvi_batch(
3717 req: IndicatorBatchRequest<'_>,
3718 output_id: &str,
3719) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3720 expect_value_output("pvi", output_id)?;
3721 let (close, volume) = extract_close_volume_input("pvi", req.data, "close")?;
3722 let kernel = req.kernel.to_non_batch();
3723 collect_f64("pvi", output_id, req.combos, close.len(), |params| {
3724 let initial_value = get_f64_param("pvi", params, "initial_value", 1000.0)?;
3725 let input = PviInput::from_slices(
3726 close,
3727 volume,
3728 PviParams {
3729 initial_value: Some(initial_value),
3730 },
3731 );
3732 let out =
3733 pvi_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3734 indicator: "pvi".to_string(),
3735 details: e.to_string(),
3736 })?;
3737 Ok(out.values)
3738 })
3739}
3740
3741fn compute_wclprice_batch(
3742 req: IndicatorBatchRequest<'_>,
3743 output_id: &str,
3744) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3745 expect_value_output("wclprice", output_id)?;
3746 let (high, low, close) = extract_ohlc_input("wclprice", req.data)?;
3747 let kernel = req.kernel.to_non_batch();
3748 collect_f64("wclprice", output_id, req.combos, close.len(), |_params| {
3749 let input = WclpriceInput::from_slices(high, low, close);
3750 let out = wclprice_with_kernel(&input, kernel).map_err(|e| {
3751 IndicatorDispatchError::ComputeFailed {
3752 indicator: "wclprice".to_string(),
3753 details: e.to_string(),
3754 }
3755 })?;
3756 Ok(out.values)
3757 })
3758}
3759
3760fn compute_ui_batch(
3761 req: IndicatorBatchRequest<'_>,
3762 output_id: &str,
3763) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3764 expect_value_output("ui", output_id)?;
3765 let data = extract_slice_input("ui", req.data, "close")?;
3766 let kernel = req.kernel.to_non_batch();
3767 collect_f64("ui", output_id, req.combos, data.len(), |params| {
3768 let period = get_usize_param("ui", params, "period", 14)?;
3769 let scalar = get_f64_param("ui", params, "scalar", 100.0)?;
3770 let input = UiInput::from_slice(
3771 data,
3772 UiParams {
3773 period: Some(period),
3774 scalar: Some(scalar),
3775 },
3776 );
3777 let out =
3778 ui_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3779 indicator: "ui".to_string(),
3780 details: e.to_string(),
3781 })?;
3782 Ok(out.values)
3783 })
3784}
3785
3786fn compute_zscore_batch(
3787 req: IndicatorBatchRequest<'_>,
3788 output_id: &str,
3789) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3790 expect_value_output("zscore", output_id)?;
3791 let data = extract_slice_input("zscore", req.data, "close")?;
3792 let kernel = req.kernel.to_non_batch();
3793 collect_f64("zscore", output_id, req.combos, data.len(), |params| {
3794 let period = get_usize_param("zscore", params, "period", 14)?;
3795 let ma_type = get_enum_param("zscore", params, "ma_type", "sma")?;
3796 let nbdev = get_f64_param("zscore", params, "nbdev", 1.0)?;
3797 let devtype = get_usize_param("zscore", params, "devtype", 0)?;
3798 let input = ZscoreInput::from_slice(
3799 data,
3800 ZscoreParams {
3801 period: Some(period),
3802 ma_type: Some(ma_type),
3803 nbdev: Some(nbdev),
3804 devtype: Some(devtype),
3805 },
3806 );
3807 let out = zscore_with_kernel(&input, kernel).map_err(|e| {
3808 IndicatorDispatchError::ComputeFailed {
3809 indicator: "zscore".to_string(),
3810 details: e.to_string(),
3811 }
3812 })?;
3813 Ok(out.values)
3814 })
3815}
3816
3817fn compute_medprice_batch(
3818 req: IndicatorBatchRequest<'_>,
3819 output_id: &str,
3820) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3821 expect_value_output("medprice", output_id)?;
3822 let (high, low) = extract_high_low_input("medprice", req.data)?;
3823 let kernel = req.kernel.to_non_batch();
3824 collect_f64("medprice", output_id, req.combos, high.len(), |_params| {
3825 let input = MedpriceInput::from_slices(high, low, MedpriceParams::default());
3826 let out = medprice_with_kernel(&input, kernel).map_err(|e| {
3827 IndicatorDispatchError::ComputeFailed {
3828 indicator: "medprice".to_string(),
3829 details: e.to_string(),
3830 }
3831 })?;
3832 Ok(out.values)
3833 })
3834}
3835
3836fn compute_midpoint_batch(
3837 req: IndicatorBatchRequest<'_>,
3838 output_id: &str,
3839) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3840 expect_value_output("midpoint", output_id)?;
3841 let data = extract_slice_input("midpoint", req.data, "close")?;
3842 let kernel = req.kernel.to_non_batch();
3843 collect_f64("midpoint", output_id, req.combos, data.len(), |params| {
3844 let period = get_usize_param("midpoint", params, "period", 14)?;
3845 let input = MidpointInput::from_slice(
3846 data,
3847 MidpointParams {
3848 period: Some(period),
3849 },
3850 );
3851 let out = midpoint_with_kernel(&input, kernel).map_err(|e| {
3852 IndicatorDispatchError::ComputeFailed {
3853 indicator: "midpoint".to_string(),
3854 details: e.to_string(),
3855 }
3856 })?;
3857 Ok(out.values)
3858 })
3859}
3860
3861fn compute_midprice_batch(
3862 req: IndicatorBatchRequest<'_>,
3863 output_id: &str,
3864) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3865 expect_value_output("midprice", output_id)?;
3866 let (high, low) = extract_high_low_input("midprice", req.data)?;
3867 let kernel = req.kernel.to_non_batch();
3868 collect_f64("midprice", output_id, req.combos, high.len(), |params| {
3869 let period = get_usize_param("midprice", params, "period", 14)?;
3870 let input = MidpriceInput::from_slices(
3871 high,
3872 low,
3873 MidpriceParams {
3874 period: Some(period),
3875 },
3876 );
3877 let out = midprice_with_kernel(&input, kernel).map_err(|e| {
3878 IndicatorDispatchError::ComputeFailed {
3879 indicator: "midprice".to_string(),
3880 details: e.to_string(),
3881 }
3882 })?;
3883 Ok(out.values)
3884 })
3885}
3886
3887fn compute_mom_batch(
3888 req: IndicatorBatchRequest<'_>,
3889 output_id: &str,
3890) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3891 expect_value_output("mom", output_id)?;
3892 let data = extract_slice_input("mom", req.data, "close")?;
3893 let kernel = req.kernel.to_non_batch();
3894 collect_f64("mom", output_id, req.combos, data.len(), |params| {
3895 let period = get_usize_param("mom", params, "period", 10)?;
3896 let input = MomInput::from_slice(
3897 data,
3898 MomParams {
3899 period: Some(period),
3900 },
3901 );
3902 let out =
3903 mom_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
3904 indicator: "mom".to_string(),
3905 details: e.to_string(),
3906 })?;
3907 Ok(out.values)
3908 })
3909}
3910
3911fn compute_velocity_batch(
3912 req: IndicatorBatchRequest<'_>,
3913 output_id: &str,
3914) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3915 expect_value_output("velocity", output_id)?;
3916 let data = extract_slice_input("velocity", req.data, "hlcc4")?;
3917 let kernel = req.kernel.to_non_batch();
3918 collect_f64("velocity", output_id, req.combos, data.len(), |params| {
3919 let length = get_usize_param("velocity", params, "length", 21)?;
3920 let smooth_length = get_usize_param("velocity", params, "smooth_length", 5)?;
3921 let input = VelocityInput::from_slice(
3922 data,
3923 VelocityParams {
3924 length: Some(length),
3925 smooth_length: Some(smooth_length),
3926 },
3927 );
3928 let out = velocity_with_kernel(&input, kernel).map_err(|e| {
3929 IndicatorDispatchError::ComputeFailed {
3930 indicator: "velocity".to_string(),
3931 details: e.to_string(),
3932 }
3933 })?;
3934 Ok(out.values)
3935 })
3936}
3937
3938fn compute_adaptive_momentum_oscillator_batch(
3939 req: IndicatorBatchRequest<'_>,
3940 output_id: &str,
3941) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3942 let data = extract_slice_input("adaptive_momentum_oscillator", req.data, "close")?;
3943 let kernel = req.kernel.to_non_batch();
3944 collect_f64(
3945 "adaptive_momentum_oscillator",
3946 output_id,
3947 req.combos,
3948 data.len(),
3949 |params| {
3950 let length = get_usize_param("adaptive_momentum_oscillator", params, "length", 14)?;
3951 let smoothing_length = get_usize_param(
3952 "adaptive_momentum_oscillator",
3953 params,
3954 "smoothing_length",
3955 9,
3956 )?;
3957 let input = AdaptiveMomentumOscillatorInput::from_slice(
3958 data,
3959 AdaptiveMomentumOscillatorParams {
3960 length: Some(length),
3961 smoothing_length: Some(smoothing_length),
3962 },
3963 );
3964 let out = adaptive_momentum_oscillator_with_kernel(&input, kernel).map_err(|e| {
3965 IndicatorDispatchError::ComputeFailed {
3966 indicator: "adaptive_momentum_oscillator".to_string(),
3967 details: e.to_string(),
3968 }
3969 })?;
3970 match output_id {
3971 "amo" | "value" => Ok(out.amo),
3972 "ama" => Ok(out.ama),
3973 other => Err(IndicatorDispatchError::UnknownOutput {
3974 indicator: "adaptive_momentum_oscillator".to_string(),
3975 output: other.to_string(),
3976 }),
3977 }
3978 },
3979 )
3980}
3981
3982fn compute_normalized_volume_true_range_batch(
3983 req: IndicatorBatchRequest<'_>,
3984 output_id: &str,
3985) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
3986 let (open, high, low, close, volume) =
3987 extract_ohlcv_full_input("normalized_volume_true_range", req.data)?;
3988 let kernel = req.kernel.to_non_batch();
3989 collect_f64(
3990 "normalized_volume_true_range",
3991 output_id,
3992 req.combos,
3993 close.len(),
3994 |params| {
3995 let true_range_style = match find_param(params, "true_range_style") {
3996 Some(ParamValue::EnumString(value)) => Some(
3997 value
3998 .parse::<NormalizedVolumeTrueRangeStyle>()
3999 .map_err(|e| IndicatorDispatchError::InvalidParam {
4000 indicator: "normalized_volume_true_range".to_string(),
4001 key: "true_range_style".to_string(),
4002 reason: e,
4003 })?,
4004 ),
4005 Some(_) => {
4006 return Err(IndicatorDispatchError::InvalidParam {
4007 indicator: "normalized_volume_true_range".to_string(),
4008 key: "true_range_style".to_string(),
4009 reason: "expected enum string".to_string(),
4010 });
4011 }
4012 None => Some(NormalizedVolumeTrueRangeStyle::Body),
4013 };
4014 let outlier_range =
4015 get_f64_param("normalized_volume_true_range", params, "outlier_range", 5.0)?;
4016 let atr_length =
4017 get_usize_param("normalized_volume_true_range", params, "atr_length", 14)?;
4018 let volume_length =
4019 get_usize_param("normalized_volume_true_range", params, "volume_length", 14)?;
4020
4021 let input = NormalizedVolumeTrueRangeInput::from_slices(
4022 open,
4023 high,
4024 low,
4025 close,
4026 volume,
4027 NormalizedVolumeTrueRangeParams {
4028 true_range_style,
4029 outlier_range: Some(outlier_range),
4030 atr_length: Some(atr_length),
4031 volume_length: Some(volume_length),
4032 },
4033 );
4034 let out = normalized_volume_true_range_with_kernel(&input, kernel).map_err(|e| {
4035 IndicatorDispatchError::ComputeFailed {
4036 indicator: "normalized_volume_true_range".to_string(),
4037 details: e.to_string(),
4038 }
4039 })?;
4040 if output_id.eq_ignore_ascii_case("normalized_volume")
4041 || output_id.eq_ignore_ascii_case("value")
4042 {
4043 return Ok(out.normalized_volume);
4044 }
4045 if output_id.eq_ignore_ascii_case("normalized_true_range") {
4046 return Ok(out.normalized_true_range);
4047 }
4048 if output_id.eq_ignore_ascii_case("baseline") {
4049 return Ok(out.baseline);
4050 }
4051 if output_id.eq_ignore_ascii_case("atr") {
4052 return Ok(out.atr);
4053 }
4054 if output_id.eq_ignore_ascii_case("average_volume") {
4055 return Ok(out.average_volume);
4056 }
4057 Err(IndicatorDispatchError::UnknownOutput {
4058 indicator: "normalized_volume_true_range".to_string(),
4059 output: output_id.to_string(),
4060 })
4061 },
4062 )
4063}
4064
4065fn compute_range_breakout_signals_batch(
4066 req: IndicatorBatchRequest<'_>,
4067 output_id: &str,
4068) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4069 let (open, high, low, close, volume) =
4070 extract_ohlcv_full_input("range_breakout_signals", req.data)?;
4071 let kernel = req.kernel.to_non_batch();
4072 collect_f64(
4073 "range_breakout_signals",
4074 output_id,
4075 req.combos,
4076 close.len(),
4077 |params| {
4078 let range_length =
4079 get_usize_param("range_breakout_signals", params, "range_length", 20)?;
4080 let confirmation_length =
4081 get_usize_param("range_breakout_signals", params, "confirmation_length", 5)?;
4082 let input = RangeBreakoutSignalsInput::from_slices(
4083 open,
4084 high,
4085 low,
4086 close,
4087 volume,
4088 RangeBreakoutSignalsParams {
4089 range_length: Some(range_length),
4090 confirmation_length: Some(confirmation_length),
4091 },
4092 );
4093 let out = range_breakout_signals_with_kernel(&input, kernel).map_err(|e| {
4094 IndicatorDispatchError::ComputeFailed {
4095 indicator: "range_breakout_signals".to_string(),
4096 details: e.to_string(),
4097 }
4098 })?;
4099 if output_id.eq_ignore_ascii_case("range_top")
4100 || output_id.eq_ignore_ascii_case("value")
4101 {
4102 return Ok(out.range_top);
4103 }
4104 if output_id.eq_ignore_ascii_case("range_bottom") {
4105 return Ok(out.range_bottom);
4106 }
4107 if output_id.eq_ignore_ascii_case("bullish") {
4108 return Ok(out.bullish);
4109 }
4110 if output_id.eq_ignore_ascii_case("extra_bullish") {
4111 return Ok(out.extra_bullish);
4112 }
4113 if output_id.eq_ignore_ascii_case("bearish") {
4114 return Ok(out.bearish);
4115 }
4116 if output_id.eq_ignore_ascii_case("extra_bearish") {
4117 return Ok(out.extra_bearish);
4118 }
4119 Err(IndicatorDispatchError::UnknownOutput {
4120 indicator: "range_breakout_signals".to_string(),
4121 output: output_id.to_string(),
4122 })
4123 },
4124 )
4125}
4126
4127fn compute_exponential_trend_batch(
4128 req: IndicatorBatchRequest<'_>,
4129 output_id: &str,
4130) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4131 let (high, low, close) = extract_ohlc_input("exponential_trend", req.data)?;
4132 let kernel = req.kernel.to_non_batch();
4133 collect_f64(
4134 "exponential_trend",
4135 output_id,
4136 req.combos,
4137 close.len(),
4138 |params| {
4139 let exp_rate = get_f64_param("exponential_trend", params, "exp_rate", 0.00003)?;
4140 let initial_distance =
4141 get_f64_param("exponential_trend", params, "initial_distance", 4.0)?;
4142 let width_multiplier =
4143 get_f64_param("exponential_trend", params, "width_multiplier", 1.0)?;
4144 let input = ExponentialTrendInput::from_slices(
4145 high,
4146 low,
4147 close,
4148 ExponentialTrendParams {
4149 exp_rate: Some(exp_rate),
4150 initial_distance: Some(initial_distance),
4151 width_multiplier: Some(width_multiplier),
4152 },
4153 );
4154 let out = exponential_trend_with_kernel(&input, kernel).map_err(|e| {
4155 IndicatorDispatchError::ComputeFailed {
4156 indicator: "exponential_trend".to_string(),
4157 details: e.to_string(),
4158 }
4159 })?;
4160 if output_id.eq_ignore_ascii_case("uptrend_base")
4161 || output_id.eq_ignore_ascii_case("value")
4162 {
4163 return Ok(out.uptrend_base);
4164 }
4165 if output_id.eq_ignore_ascii_case("downtrend_base") {
4166 return Ok(out.downtrend_base);
4167 }
4168 if output_id.eq_ignore_ascii_case("uptrend_extension") {
4169 return Ok(out.uptrend_extension);
4170 }
4171 if output_id.eq_ignore_ascii_case("downtrend_extension") {
4172 return Ok(out.downtrend_extension);
4173 }
4174 if output_id.eq_ignore_ascii_case("bullish_change") {
4175 return Ok(out.bullish_change);
4176 }
4177 if output_id.eq_ignore_ascii_case("bearish_change") {
4178 return Ok(out.bearish_change);
4179 }
4180 Err(IndicatorDispatchError::UnknownOutput {
4181 indicator: "exponential_trend".to_string(),
4182 output: output_id.to_string(),
4183 })
4184 },
4185 )
4186}
4187
4188fn compute_trend_flow_trail_batch(
4189 req: IndicatorBatchRequest<'_>,
4190 output_id: &str,
4191) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4192 let (open, high, low, close, volume) = extract_ohlcv_full_input("trend_flow_trail", req.data)?;
4193 let kernel = req.kernel.to_non_batch();
4194 collect_f64(
4195 "trend_flow_trail",
4196 output_id,
4197 req.combos,
4198 close.len(),
4199 |params| {
4200 let alpha_length = get_usize_param("trend_flow_trail", params, "alpha_length", 33)?;
4201 let alpha_multiplier =
4202 get_f64_param("trend_flow_trail", params, "alpha_multiplier", 3.3)?;
4203 let mfi_length = get_usize_param("trend_flow_trail", params, "mfi_length", 14)?;
4204 let input = TrendFlowTrailInput::from_slices(
4205 open,
4206 high,
4207 low,
4208 close,
4209 volume,
4210 TrendFlowTrailParams {
4211 alpha_length: Some(alpha_length),
4212 alpha_multiplier: Some(alpha_multiplier),
4213 mfi_length: Some(mfi_length),
4214 },
4215 );
4216 let out = trend_flow_trail_with_kernel(&input, kernel).map_err(|e| {
4217 IndicatorDispatchError::ComputeFailed {
4218 indicator: "trend_flow_trail".to_string(),
4219 details: e.to_string(),
4220 }
4221 })?;
4222 if output_id.eq_ignore_ascii_case("alpha_trail")
4223 || output_id.eq_ignore_ascii_case("value")
4224 {
4225 return Ok(out.alpha_trail);
4226 }
4227 if output_id.eq_ignore_ascii_case("alpha_trail_bullish") {
4228 return Ok(out.alpha_trail_bullish);
4229 }
4230 if output_id.eq_ignore_ascii_case("alpha_trail_bearish") {
4231 return Ok(out.alpha_trail_bearish);
4232 }
4233 if output_id.eq_ignore_ascii_case("alpha_dir") {
4234 return Ok(out.alpha_dir);
4235 }
4236 if output_id.eq_ignore_ascii_case("mfi") {
4237 return Ok(out.mfi);
4238 }
4239 if output_id.eq_ignore_ascii_case("tp_upper") {
4240 return Ok(out.tp_upper);
4241 }
4242 if output_id.eq_ignore_ascii_case("tp_lower") {
4243 return Ok(out.tp_lower);
4244 }
4245 if output_id.eq_ignore_ascii_case("alpha_trail_bullish_switch") {
4246 return Ok(out.alpha_trail_bullish_switch);
4247 }
4248 if output_id.eq_ignore_ascii_case("alpha_trail_bearish_switch") {
4249 return Ok(out.alpha_trail_bearish_switch);
4250 }
4251 if output_id.eq_ignore_ascii_case("mfi_overbought") {
4252 return Ok(out.mfi_overbought);
4253 }
4254 if output_id.eq_ignore_ascii_case("mfi_oversold") {
4255 return Ok(out.mfi_oversold);
4256 }
4257 if output_id.eq_ignore_ascii_case("mfi_cross_up_mid") {
4258 return Ok(out.mfi_cross_up_mid);
4259 }
4260 if output_id.eq_ignore_ascii_case("mfi_cross_down_mid") {
4261 return Ok(out.mfi_cross_down_mid);
4262 }
4263 if output_id.eq_ignore_ascii_case("price_cross_alpha_trail_up") {
4264 return Ok(out.price_cross_alpha_trail_up);
4265 }
4266 if output_id.eq_ignore_ascii_case("price_cross_alpha_trail_down") {
4267 return Ok(out.price_cross_alpha_trail_down);
4268 }
4269 if output_id.eq_ignore_ascii_case("mfi_above_90") {
4270 return Ok(out.mfi_above_90);
4271 }
4272 if output_id.eq_ignore_ascii_case("mfi_below_10") {
4273 return Ok(out.mfi_below_10);
4274 }
4275 Err(IndicatorDispatchError::UnknownOutput {
4276 indicator: "trend_flow_trail".to_string(),
4277 output: output_id.to_string(),
4278 })
4279 },
4280 )
4281}
4282
4283fn compute_cmo_batch(
4284 req: IndicatorBatchRequest<'_>,
4285 output_id: &str,
4286) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4287 expect_value_output("cmo", output_id)?;
4288 let data = extract_slice_input("cmo", req.data, "close")?;
4289 let kernel = req.kernel.to_non_batch();
4290 collect_f64("cmo", output_id, req.combos, data.len(), |params| {
4291 let period = get_usize_param("cmo", params, "period", 14)?;
4292 let input = CmoInput::from_slice(
4293 data,
4294 CmoParams {
4295 period: Some(period),
4296 },
4297 );
4298 let out =
4299 cmo_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
4300 indicator: "cmo".to_string(),
4301 details: e.to_string(),
4302 })?;
4303 Ok(out.values)
4304 })
4305}
4306
4307fn compute_rocp_batch(
4308 req: IndicatorBatchRequest<'_>,
4309 output_id: &str,
4310) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4311 expect_value_output("rocp", output_id)?;
4312 let data = extract_slice_input("rocp", req.data, "close")?;
4313 let kernel = req.kernel.to_non_batch();
4314 collect_f64("rocp", output_id, req.combos, data.len(), |params| {
4315 let period = get_usize_param("rocp", params, "period", 10)?;
4316 let input = RocpInput::from_slice(
4317 data,
4318 RocpParams {
4319 period: Some(period),
4320 },
4321 );
4322 let out = rocp_with_kernel(&input, kernel).map_err(|e| {
4323 IndicatorDispatchError::ComputeFailed {
4324 indicator: "rocp".to_string(),
4325 details: e.to_string(),
4326 }
4327 })?;
4328 Ok(out.values)
4329 })
4330}
4331
4332fn compute_rocr_batch(
4333 req: IndicatorBatchRequest<'_>,
4334 output_id: &str,
4335) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4336 expect_value_output("rocr", output_id)?;
4337 let data = extract_slice_input("rocr", req.data, "close")?;
4338 let kernel = req.kernel.to_non_batch();
4339 collect_f64("rocr", output_id, req.combos, data.len(), |params| {
4340 let period = get_usize_param("rocr", params, "period", 10)?;
4341 let input = RocrInput::from_slice(
4342 data,
4343 RocrParams {
4344 period: Some(period),
4345 },
4346 );
4347 let out = rocr_with_kernel(&input, kernel).map_err(|e| {
4348 IndicatorDispatchError::ComputeFailed {
4349 indicator: "rocr".to_string(),
4350 details: e.to_string(),
4351 }
4352 })?;
4353 Ok(out.values)
4354 })
4355}
4356
4357fn compute_ppo_batch(
4358 req: IndicatorBatchRequest<'_>,
4359 output_id: &str,
4360) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4361 expect_value_output("ppo", output_id)?;
4362 let data = extract_slice_input("ppo", req.data, "close")?;
4363 let kernel = req.kernel.to_non_batch();
4364 collect_f64("ppo", output_id, req.combos, data.len(), |params| {
4365 let fast_period = get_usize_param("ppo", params, "fast_period", 12)?;
4366 let slow_period = get_usize_param("ppo", params, "slow_period", 26)?;
4367 let ma_type = get_enum_param("ppo", params, "ma_type", "sma")?;
4368 let input = PpoInput::from_slice(
4369 data,
4370 PpoParams {
4371 fast_period: Some(fast_period),
4372 slow_period: Some(slow_period),
4373 ma_type: Some(ma_type),
4374 },
4375 );
4376 let out =
4377 ppo_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
4378 indicator: "ppo".to_string(),
4379 details: e.to_string(),
4380 })?;
4381 Ok(out.values)
4382 })
4383}
4384
4385fn compute_trix_batch(
4386 req: IndicatorBatchRequest<'_>,
4387 output_id: &str,
4388) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4389 expect_value_output("trix", output_id)?;
4390 let data = extract_slice_input("trix", req.data, "close")?;
4391 let periods = combo_periods("trix", req.combos, "period", 18)?;
4392 if let Some((start, end, step)) = derive_period_sweep(&periods) {
4393 let out = trix_batch_with_kernel(
4394 data,
4395 &TrixBatchRange {
4396 period: (start, end, step),
4397 },
4398 to_batch_kernel(req.kernel),
4399 )
4400 .map_err(|e| IndicatorDispatchError::ComputeFailed {
4401 indicator: "trix".to_string(),
4402 details: e.to_string(),
4403 })?;
4404 ensure_len("trix", data.len(), out.cols)?;
4405 let produced_periods: Vec<usize> = out
4406 .combos
4407 .iter()
4408 .map(|combo| combo.period.unwrap_or(18))
4409 .collect();
4410 let values = reorder_or_take_f64_matrix_by_period(
4411 "trix",
4412 &periods,
4413 &produced_periods,
4414 out.cols,
4415 out.values,
4416 )?;
4417 return Ok(f64_output(output_id, periods.len(), out.cols, values));
4418 }
4419
4420 let kernel = req.kernel.to_non_batch();
4421 collect_f64_into_rows("trix", output_id, req.combos, data.len(), |params, row| {
4422 let period = get_usize_param("trix", params, "period", 18)?;
4423 let input = TrixInput::from_slice(
4424 data,
4425 TrixParams {
4426 period: Some(period),
4427 },
4428 );
4429 trix_into_slice(row, &input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
4430 indicator: "trix".to_string(),
4431 details: e.to_string(),
4432 })
4433 })
4434}
4435
4436fn compute_tsi_batch(
4437 req: IndicatorBatchRequest<'_>,
4438 output_id: &str,
4439) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4440 expect_value_output("tsi", output_id)?;
4441 let data = extract_slice_input("tsi", req.data, "close")?;
4442 let kernel = req.kernel.to_non_batch();
4443 collect_f64("tsi", output_id, req.combos, data.len(), |params| {
4444 let long_period = get_usize_param("tsi", params, "long_period", 25)?;
4445 let short_period = get_usize_param("tsi", params, "short_period", 13)?;
4446 let input = TsiInput::from_slice(
4447 data,
4448 TsiParams {
4449 long_period: Some(long_period),
4450 short_period: Some(short_period),
4451 },
4452 );
4453 let out =
4454 tsi_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
4455 indicator: "tsi".to_string(),
4456 details: e.to_string(),
4457 })?;
4458 Ok(out.values)
4459 })
4460}
4461
4462fn compute_tsf_batch(
4463 req: IndicatorBatchRequest<'_>,
4464 output_id: &str,
4465) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4466 expect_value_output("tsf", output_id)?;
4467 let data = extract_slice_input("tsf", req.data, "close")?;
4468 let kernel = req.kernel.to_non_batch();
4469 collect_f64("tsf", output_id, req.combos, data.len(), |params| {
4470 let period = get_usize_param("tsf", params, "period", 14)?;
4471 let input = TsfInput::from_slice(
4472 data,
4473 TsfParams {
4474 period: Some(period),
4475 },
4476 );
4477 let out =
4478 tsf_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
4479 indicator: "tsf".to_string(),
4480 details: e.to_string(),
4481 })?;
4482 Ok(out.values)
4483 })
4484}
4485
4486fn compute_polynomial_regression_extrapolation_batch(
4487 req: IndicatorBatchRequest<'_>,
4488 output_id: &str,
4489) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4490 expect_value_output("polynomial_regression_extrapolation", output_id)?;
4491 let data = extract_slice_input("polynomial_regression_extrapolation", req.data, "close")?;
4492 let kernel = req.kernel.to_non_batch();
4493 collect_f64(
4494 "polynomial_regression_extrapolation",
4495 output_id,
4496 req.combos,
4497 data.len(),
4498 |params| {
4499 let length =
4500 get_usize_param("polynomial_regression_extrapolation", params, "length", 100)?;
4501 let extrapolate = get_usize_param(
4502 "polynomial_regression_extrapolation",
4503 params,
4504 "extrapolate",
4505 10,
4506 )?;
4507 let degree =
4508 get_usize_param("polynomial_regression_extrapolation", params, "degree", 3)?;
4509 let input = PolynomialRegressionExtrapolationInput::from_slice(
4510 data,
4511 PolynomialRegressionExtrapolationParams {
4512 length: Some(length),
4513 extrapolate: Some(extrapolate),
4514 degree: Some(degree),
4515 },
4516 );
4517 let out =
4518 polynomial_regression_extrapolation_with_kernel(&input, kernel).map_err(|e| {
4519 IndicatorDispatchError::ComputeFailed {
4520 indicator: "polynomial_regression_extrapolation".to_string(),
4521 details: e.to_string(),
4522 }
4523 })?;
4524 Ok(out.values)
4525 },
4526 )
4527}
4528
4529fn compute_adaptive_macd_batch(
4530 req: IndicatorBatchRequest<'_>,
4531 output_id: &str,
4532) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4533 let data = extract_slice_input("adaptive_macd", req.data, "close")?;
4534 let kernel = req.kernel.to_non_batch();
4535 collect_f64(
4536 "adaptive_macd",
4537 output_id,
4538 req.combos,
4539 data.len(),
4540 |params| {
4541 let length = get_usize_param("adaptive_macd", params, "length", 20)?;
4542 let fast_period = get_usize_param("adaptive_macd", params, "fast_period", 10)?;
4543 let slow_period = get_usize_param("adaptive_macd", params, "slow_period", 20)?;
4544 let signal_period = get_usize_param("adaptive_macd", params, "signal_period", 9)?;
4545 let input = AdaptiveMacdInput::from_slice(
4546 data,
4547 AdaptiveMacdParams {
4548 length: Some(length),
4549 fast_period: Some(fast_period),
4550 slow_period: Some(slow_period),
4551 signal_period: Some(signal_period),
4552 },
4553 );
4554 let out = adaptive_macd_with_kernel(&input, kernel).map_err(|e| {
4555 IndicatorDispatchError::ComputeFailed {
4556 indicator: "adaptive_macd".to_string(),
4557 details: e.to_string(),
4558 }
4559 })?;
4560 if output_id.eq_ignore_ascii_case("macd") || output_id.eq_ignore_ascii_case("value") {
4561 return Ok(out.macd);
4562 }
4563 if output_id.eq_ignore_ascii_case("signal") {
4564 return Ok(out.signal);
4565 }
4566 if output_id.eq_ignore_ascii_case("hist") {
4567 return Ok(out.hist);
4568 }
4569 Err(IndicatorDispatchError::UnknownOutput {
4570 indicator: "adaptive_macd".to_string(),
4571 output: output_id.to_string(),
4572 })
4573 },
4574 )
4575}
4576
4577fn compute_statistical_trailing_stop_batch(
4578 req: IndicatorBatchRequest<'_>,
4579 output_id: &str,
4580) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4581 let (high, low, close) = extract_ohlc_input("statistical_trailing_stop", req.data)?;
4582 let kernel = req.kernel.to_non_batch();
4583 collect_f64(
4584 "statistical_trailing_stop",
4585 output_id,
4586 req.combos,
4587 close.len(),
4588 |params| {
4589 let data_length =
4590 get_usize_param("statistical_trailing_stop", params, "data_length", 10)?;
4591 let normalization_length = get_usize_param(
4592 "statistical_trailing_stop",
4593 params,
4594 "normalization_length",
4595 100,
4596 )?;
4597 let base_level =
4598 get_enum_param("statistical_trailing_stop", params, "base_level", "level2")?;
4599 let input = StatisticalTrailingStopInput::from_slices(
4600 high,
4601 low,
4602 close,
4603 StatisticalTrailingStopParams {
4604 data_length: Some(data_length),
4605 normalization_length: Some(normalization_length),
4606 base_level: Some(base_level),
4607 },
4608 );
4609 let out = statistical_trailing_stop_with_kernel(&input, kernel).map_err(|e| {
4610 IndicatorDispatchError::ComputeFailed {
4611 indicator: "statistical_trailing_stop".to_string(),
4612 details: e.to_string(),
4613 }
4614 })?;
4615 if output_id.eq_ignore_ascii_case("level") || output_id.eq_ignore_ascii_case("value") {
4616 return Ok(out.level);
4617 }
4618 if output_id.eq_ignore_ascii_case("anchor") {
4619 return Ok(out.anchor);
4620 }
4621 if output_id.eq_ignore_ascii_case("bias") {
4622 return Ok(out.bias);
4623 }
4624 if output_id.eq_ignore_ascii_case("changed") {
4625 return Ok(out.changed);
4626 }
4627 Err(IndicatorDispatchError::UnknownOutput {
4628 indicator: "statistical_trailing_stop".to_string(),
4629 output: output_id.to_string(),
4630 })
4631 },
4632 )
4633}
4634
4635fn compute_supertrend_recovery_batch(
4636 req: IndicatorBatchRequest<'_>,
4637 output_id: &str,
4638) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4639 let (high, low, close) = extract_ohlc_input("supertrend_recovery", req.data)?;
4640 let kernel = req.kernel.to_non_batch();
4641 collect_f64(
4642 "supertrend_recovery",
4643 output_id,
4644 req.combos,
4645 close.len(),
4646 |params| {
4647 let atr_length = get_usize_param("supertrend_recovery", params, "atr_length", 10)?;
4648 let multiplier = get_f64_param("supertrend_recovery", params, "multiplier", 3.0)?;
4649 let alpha_percent = get_f64_param("supertrend_recovery", params, "alpha_percent", 5.0)?;
4650 let threshold_atr = get_f64_param("supertrend_recovery", params, "threshold_atr", 1.0)?;
4651 let input = SuperTrendRecoveryInput::from_slices(
4652 high,
4653 low,
4654 close,
4655 SuperTrendRecoveryParams {
4656 atr_length: Some(atr_length),
4657 multiplier: Some(multiplier),
4658 alpha_percent: Some(alpha_percent),
4659 threshold_atr: Some(threshold_atr),
4660 },
4661 );
4662 let out = supertrend_recovery_with_kernel(&input, kernel).map_err(|e| {
4663 IndicatorDispatchError::ComputeFailed {
4664 indicator: "supertrend_recovery".to_string(),
4665 details: e.to_string(),
4666 }
4667 })?;
4668 if output_id.eq_ignore_ascii_case("band") || output_id.eq_ignore_ascii_case("value") {
4669 return Ok(out.band);
4670 }
4671 if output_id.eq_ignore_ascii_case("switch_price") {
4672 return Ok(out.switch_price);
4673 }
4674 if output_id.eq_ignore_ascii_case("trend") {
4675 return Ok(out.trend);
4676 }
4677 if output_id.eq_ignore_ascii_case("changed") {
4678 return Ok(out.changed);
4679 }
4680 Err(IndicatorDispatchError::UnknownOutput {
4681 indicator: "supertrend_recovery".to_string(),
4682 output: output_id.to_string(),
4683 })
4684 },
4685 )
4686}
4687
4688fn compute_standardized_psar_oscillator_batch(
4689 req: IndicatorBatchRequest<'_>,
4690 output_id: &str,
4691) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4692 let (high, low, close) = extract_ohlc_input("standardized_psar_oscillator", req.data)?;
4693 let kernel = req.kernel.to_non_batch();
4694 collect_f64(
4695 "standardized_psar_oscillator",
4696 output_id,
4697 req.combos,
4698 close.len(),
4699 |params| {
4700 let start = get_f64_param("standardized_psar_oscillator", params, "start", 0.02)?;
4701 let increment =
4702 get_f64_param("standardized_psar_oscillator", params, "increment", 0.0005)?;
4703 let maximum = get_f64_param("standardized_psar_oscillator", params, "maximum", 0.2)?;
4704 let standardization_length = get_usize_param(
4705 "standardized_psar_oscillator",
4706 params,
4707 "standardization_length",
4708 21,
4709 )?;
4710 let wma_length =
4711 get_usize_param("standardized_psar_oscillator", params, "wma_length", 40)?;
4712 let wma_lag = get_usize_param("standardized_psar_oscillator", params, "wma_lag", 3)?;
4713 let pivot_left =
4714 get_usize_param("standardized_psar_oscillator", params, "pivot_left", 15)?;
4715 let pivot_right =
4716 get_usize_param("standardized_psar_oscillator", params, "pivot_right", 1)?;
4717 let plot_bullish =
4718 get_bool_param("standardized_psar_oscillator", params, "plot_bullish", true)?;
4719 let plot_bearish =
4720 get_bool_param("standardized_psar_oscillator", params, "plot_bearish", true)?;
4721 let input = StandardizedPsarOscillatorInput::from_slices(
4722 high,
4723 low,
4724 close,
4725 StandardizedPsarOscillatorParams {
4726 start: Some(start),
4727 increment: Some(increment),
4728 maximum: Some(maximum),
4729 standardization_length: Some(standardization_length),
4730 wma_length: Some(wma_length),
4731 wma_lag: Some(wma_lag),
4732 pivot_left: Some(pivot_left),
4733 pivot_right: Some(pivot_right),
4734 plot_bullish: Some(plot_bullish),
4735 plot_bearish: Some(plot_bearish),
4736 },
4737 );
4738 let out = standardized_psar_oscillator_with_kernel(&input, kernel).map_err(|e| {
4739 IndicatorDispatchError::ComputeFailed {
4740 indicator: "standardized_psar_oscillator".to_string(),
4741 details: e.to_string(),
4742 }
4743 })?;
4744 match output_id {
4745 "oscillator" | "value" => Ok(out.oscillator),
4746 "ma" => Ok(out.ma),
4747 "bullish_reversal" => Ok(out.bullish_reversal),
4748 "bearish_reversal" => Ok(out.bearish_reversal),
4749 "regular_bullish" => Ok(out.regular_bullish),
4750 "regular_bearish" => Ok(out.regular_bearish),
4751 "bullish_weakening" => Ok(out.bullish_weakening),
4752 "bearish_weakening" => Ok(out.bearish_weakening),
4753 _ => Err(IndicatorDispatchError::UnknownOutput {
4754 indicator: "standardized_psar_oscillator".to_string(),
4755 output: output_id.to_string(),
4756 }),
4757 }
4758 },
4759 )
4760}
4761
4762fn compute_geometric_bias_oscillator_batch(
4763 req: IndicatorBatchRequest<'_>,
4764 output_id: &str,
4765) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4766 expect_value_output("geometric_bias_oscillator", output_id)?;
4767 let (high, low, close) = extract_ohlc_input("geometric_bias_oscillator", req.data)?;
4768 let kernel = req.kernel.to_non_batch();
4769 collect_f64(
4770 "geometric_bias_oscillator",
4771 output_id,
4772 req.combos,
4773 close.len(),
4774 |params| {
4775 let length = get_usize_param("geometric_bias_oscillator", params, "length", 100)?;
4776 let multiplier = get_f64_param("geometric_bias_oscillator", params, "multiplier", 2.0)?;
4777 let atr_length =
4778 get_usize_param("geometric_bias_oscillator", params, "atr_length", 14)?;
4779 let smooth = get_usize_param("geometric_bias_oscillator", params, "smooth", 1)?;
4780 let input = GeometricBiasOscillatorInput::from_slices(
4781 high,
4782 low,
4783 close,
4784 GeometricBiasOscillatorParams {
4785 length: Some(length),
4786 multiplier: Some(multiplier),
4787 atr_length: Some(atr_length),
4788 smooth: Some(smooth),
4789 },
4790 );
4791 let out = geometric_bias_oscillator_with_kernel(&input, kernel).map_err(|e| {
4792 IndicatorDispatchError::ComputeFailed {
4793 indicator: "geometric_bias_oscillator".to_string(),
4794 details: e.to_string(),
4795 }
4796 })?;
4797 Ok(out.values)
4798 },
4799 )
4800}
4801
4802fn compute_stddev_batch(
4803 req: IndicatorBatchRequest<'_>,
4804 output_id: &str,
4805) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4806 expect_value_output("stddev", output_id)?;
4807 let data = extract_slice_input("stddev", req.data, "close")?;
4808 let kernel = req.kernel.to_non_batch();
4809 collect_f64("stddev", output_id, req.combos, data.len(), |params| {
4810 let period = get_usize_param("stddev", params, "period", 5)?;
4811 let nbdev = get_f64_param("stddev", params, "nbdev", 1.0)?;
4812 let input = StdDevInput::from_slice(
4813 data,
4814 StdDevParams {
4815 period: Some(period),
4816 nbdev: Some(nbdev),
4817 },
4818 );
4819 let out = stddev_with_kernel(&input, kernel).map_err(|e| {
4820 IndicatorDispatchError::ComputeFailed {
4821 indicator: "stddev".to_string(),
4822 details: e.to_string(),
4823 }
4824 })?;
4825 Ok(out.values)
4826 })
4827}
4828
4829fn compute_vdubus_divergence_wave_pattern_generator_batch(
4830 req: IndicatorBatchRequest<'_>,
4831 output_id: &str,
4832) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
4833 expect_value_output("vdubus_divergence_wave_pattern_generator", output_id)?;
4834 let (high, low, close) =
4835 extract_ohlc_input("vdubus_divergence_wave_pattern_generator", req.data)?;
4836 let kernel = req.kernel.to_non_batch();
4837 collect_f64(
4838 "vdubus_divergence_wave_pattern_generator",
4839 output_id,
4840 req.combos,
4841 close.len(),
4842 |params| {
4843 let fast_depth = get_usize_param(
4844 "vdubus_divergence_wave_pattern_generator",
4845 params,
4846 "fast_depth",
4847 9,
4848 )?;
4849 let slow_depth = get_usize_param(
4850 "vdubus_divergence_wave_pattern_generator",
4851 params,
4852 "slow_depth",
4853 24,
4854 )?;
4855 let fast_length = get_usize_param(
4856 "vdubus_divergence_wave_pattern_generator",
4857 params,
4858 "fast_length",
4859 21,
4860 )?;
4861 let slow_length = get_usize_param(
4862 "vdubus_divergence_wave_pattern_generator",
4863 params,
4864 "slow_length",
4865 34,
4866 )?;
4867 let signal_length = get_usize_param(
4868 "vdubus_divergence_wave_pattern_generator",
4869 params,
4870 "signal_length",
4871 5,
4872 )?;
4873 let lookback = get_usize_param(
4874 "vdubus_divergence_wave_pattern_generator",
4875 params,
4876 "lookback",
4877 3,
4878 )?;
4879 let err_tol = get_f64_param(
4880 "vdubus_divergence_wave_pattern_generator",
4881 params,
4882 "err_tol",
4883 0.15,
4884 )?;
4885 let show_standard = get_bool_param(
4886 "vdubus_divergence_wave_pattern_generator",
4887 params,
4888 "show_standard",
4889 true,
4890 )?;
4891 let show_climax = get_bool_param(
4892 "vdubus_divergence_wave_pattern_generator",
4893 params,
4894 "show_climax",
4895 true,
4896 )?;
4897 let show_rounded = get_bool_param(
4898 "vdubus_divergence_wave_pattern_generator",
4899 params,
4900 "show_rounded",
4901 true,
4902 )?;
4903 let show_predator = get_bool_param(
4904 "vdubus_divergence_wave_pattern_generator",
4905 params,
4906 "show_predator",
4907 true,
4908 )?;
4909 let show_gartley = get_bool_param(
4910 "vdubus_divergence_wave_pattern_generator",
4911 params,
4912 "show_gartley",
4913 false,
4914 )?;
4915 let show_bat = get_bool_param(
4916 "vdubus_divergence_wave_pattern_generator",
4917 params,
4918 "show_bat",
4919 false,
4920 )?;
4921 let show_butterfly = get_bool_param(
4922 "vdubus_divergence_wave_pattern_generator",
4923 params,
4924 "show_butterfly",
4925 false,
4926 )?;
4927 let show_crab = get_bool_param(
4928 "vdubus_divergence_wave_pattern_generator",
4929 params,
4930 "show_crab",
4931 false,
4932 )?;
4933 let show_deep = get_bool_param(
4934 "vdubus_divergence_wave_pattern_generator",
4935 params,
4936 "show_deep",
4937 false,
4938 )?;
4939 let show_hs = get_bool_param(
4940 "vdubus_divergence_wave_pattern_generator",
4941 params,
4942 "show_hs",
4943 true,
4944 )?;
4945 let input = VdubusDivergenceWavePatternGeneratorInput::from_slices(
4946 high,
4947 low,
4948 close,
4949 VdubusDivergenceWavePatternGeneratorParams {
4950 fast_depth: Some(fast_depth),
4951 slow_depth: Some(slow_depth),
4952 fast_length: Some(fast_length),
4953 slow_length: Some(slow_length),
4954 signal_length: Some(signal_length),
4955 lookback: Some(lookback),
4956 err_tol: Some(err_tol),
4957 show_standard: Some(show_standard),
4958 show_climax: Some(show_climax),
4959 show_rounded: Some(show_rounded),
4960 show_predator: Some(show_predator),
4961 show_gartley: Some(show_gartley),
4962 show_bat: Some(show_bat),
4963 show_butterfly: Some(show_butterfly),
4964 show_crab: Some(show_crab),
4965 show_deep: Some(show_deep),
4966 show_hs: Some(show_hs),
4967 },
4968 );
4969 let out = vdubus_divergence_wave_pattern_generator_with_kernel(&input, kernel)
4970 .map_err(|e| IndicatorDispatchError::ComputeFailed {
4971 indicator: "vdubus_divergence_wave_pattern_generator".to_string(),
4972 details: e.to_string(),
4973 })?;
4974 match output_id {
4975 "fast_standard" => Ok(out.fast_standard),
4976 "fast_climax" => Ok(out.fast_climax),
4977 "fast_rounded" => Ok(out.fast_rounded),
4978 "fast_predator" => Ok(out.fast_predator),
4979 "slow_standard" => Ok(out.slow_standard),
4980 "slow_climax" => Ok(out.slow_climax),
4981 "slow_rounded" => Ok(out.slow_rounded),
4982 "slow_predator" => Ok(out.slow_predator),
4983 "opposing_force" => Ok(out.opposing_force),
4984 "macd" => Ok(out.macd),
4985 "signal" => Ok(out.signal),
4986 "hist" => Ok(out.hist),
4987 _ => Err(IndicatorDispatchError::UnknownOutput {
4988 indicator: "vdubus_divergence_wave_pattern_generator".to_string(),
4989 output: output_id.to_string(),
4990 }),
4991 }
4992 },
4993 )
4994}
4995
4996fn compute_var_batch(
4997 req: IndicatorBatchRequest<'_>,
4998 output_id: &str,
4999) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5000 expect_value_output("var", output_id)?;
5001 let data = extract_slice_input("var", req.data, "close")?;
5002 let kernel = req.kernel.to_non_batch();
5003 collect_f64("var", output_id, req.combos, data.len(), |params| {
5004 let period = get_usize_param("var", params, "period", 14)?;
5005 let nbdev = get_f64_param("var", params, "nbdev", 1.0)?;
5006 let input = VarInput::from_slice(
5007 data,
5008 VarParams {
5009 period: Some(period),
5010 nbdev: Some(nbdev),
5011 },
5012 );
5013 let out =
5014 var_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
5015 indicator: "var".to_string(),
5016 details: e.to_string(),
5017 })?;
5018 Ok(out.values)
5019 })
5020}
5021
5022fn compute_willr_batch(
5023 req: IndicatorBatchRequest<'_>,
5024 output_id: &str,
5025) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5026 expect_value_output("willr", output_id)?;
5027 let (high, low, close) = extract_ohlc_input("willr", req.data)?;
5028 let kernel = req.kernel.to_non_batch();
5029 collect_f64("willr", output_id, req.combos, close.len(), |params| {
5030 let period = get_usize_param("willr", params, "period", 14)?;
5031 let input = WillrInput::from_slices(
5032 high,
5033 low,
5034 close,
5035 WillrParams {
5036 period: Some(period),
5037 },
5038 );
5039 let out = willr_with_kernel(&input, kernel).map_err(|e| {
5040 IndicatorDispatchError::ComputeFailed {
5041 indicator: "willr".to_string(),
5042 details: e.to_string(),
5043 }
5044 })?;
5045 Ok(out.values)
5046 })
5047}
5048
5049fn compute_ultosc_batch(
5050 req: IndicatorBatchRequest<'_>,
5051 output_id: &str,
5052) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5053 expect_value_output("ultosc", output_id)?;
5054 let (high, low, close) = extract_ohlc_input("ultosc", req.data)?;
5055 let kernel = req.kernel.to_non_batch();
5056 collect_f64("ultosc", output_id, req.combos, close.len(), |params| {
5057 let timeperiod1 = get_usize_param("ultosc", params, "timeperiod1", 7)?;
5058 let timeperiod2 = get_usize_param("ultosc", params, "timeperiod2", 14)?;
5059 let timeperiod3 = get_usize_param("ultosc", params, "timeperiod3", 28)?;
5060 let input = UltOscInput::from_slices(
5061 high,
5062 low,
5063 close,
5064 UltOscParams {
5065 timeperiod1: Some(timeperiod1),
5066 timeperiod2: Some(timeperiod2),
5067 timeperiod3: Some(timeperiod3),
5068 },
5069 );
5070 let out = ultosc_with_kernel(&input, kernel).map_err(|e| {
5071 IndicatorDispatchError::ComputeFailed {
5072 indicator: "ultosc".to_string(),
5073 details: e.to_string(),
5074 }
5075 })?;
5076 Ok(out.values)
5077 })
5078}
5079
5080fn compute_adx_batch(
5081 req: IndicatorBatchRequest<'_>,
5082 output_id: &str,
5083) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5084 expect_value_output("adx", output_id)?;
5085 let (high, low, close) = extract_ohlc_input("adx", req.data)?;
5086 let kernel = req.kernel.to_non_batch();
5087 collect_f64("adx", output_id, req.combos, close.len(), |params| {
5088 let period = get_usize_param("adx", params, "period", 14)?;
5089 let input = AdxInput::from_slices(
5090 high,
5091 low,
5092 close,
5093 AdxParams {
5094 period: Some(period),
5095 },
5096 );
5097 let out =
5098 adx_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
5099 indicator: "adx".to_string(),
5100 details: e.to_string(),
5101 })?;
5102 Ok(out.values)
5103 })
5104}
5105
5106fn compute_adxr_batch(
5107 req: IndicatorBatchRequest<'_>,
5108 output_id: &str,
5109) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5110 expect_value_output("adxr", output_id)?;
5111 let (high, low, close) = extract_ohlc_input("adxr", req.data)?;
5112 let kernel = req.kernel.to_non_batch();
5113 collect_f64("adxr", output_id, req.combos, close.len(), |params| {
5114 let period = get_usize_param("adxr", params, "period", 14)?;
5115 let input = AdxrInput::from_slices(
5116 high,
5117 low,
5118 close,
5119 AdxrParams {
5120 period: Some(period),
5121 },
5122 );
5123 let out = adxr_with_kernel(&input, kernel).map_err(|e| {
5124 IndicatorDispatchError::ComputeFailed {
5125 indicator: "adxr".to_string(),
5126 details: e.to_string(),
5127 }
5128 })?;
5129 Ok(out.values)
5130 })
5131}
5132
5133fn compute_atr_batch(
5134 req: IndicatorBatchRequest<'_>,
5135 output_id: &str,
5136) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5137 expect_value_output("atr", output_id)?;
5138 let (high, low, close) = extract_ohlc_input("atr", req.data)?;
5139 let kernel = req.kernel.to_non_batch();
5140 collect_f64("atr", output_id, req.combos, close.len(), |params| {
5141 let length = get_usize_param("atr", params, "length", 14)?;
5142 let input = AtrInput::from_slices(
5143 high,
5144 low,
5145 close,
5146 AtrParams {
5147 length: Some(length),
5148 },
5149 );
5150 let out =
5151 atr_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
5152 indicator: "atr".to_string(),
5153 details: e.to_string(),
5154 })?;
5155 Ok(out.values)
5156 })
5157}
5158
5159fn compute_macd_batch(
5160 req: IndicatorBatchRequest<'_>,
5161 output_id: &str,
5162) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5163 let data = extract_slice_input("macd", req.data, "close")?;
5164 let kernel = req.kernel.to_non_batch();
5165 collect_f64("macd", output_id, req.combos, data.len(), |params| {
5166 let fast_period = get_usize_param("macd", params, "fast_period", 12)?;
5167 let slow_period = get_usize_param("macd", params, "slow_period", 26)?;
5168 let signal_period = get_usize_param("macd", params, "signal_period", 9)?;
5169 let ma_type = get_enum_param("macd", params, "ma_type", "ema")?;
5170 let input = MacdInput::from_slice(
5171 data,
5172 MacdParams {
5173 fast_period: Some(fast_period),
5174 slow_period: Some(slow_period),
5175 signal_period: Some(signal_period),
5176 ma_type: Some(ma_type),
5177 },
5178 );
5179 let out = macd_with_kernel(&input, kernel).map_err(|e| {
5180 IndicatorDispatchError::ComputeFailed {
5181 indicator: "macd".to_string(),
5182 details: e.to_string(),
5183 }
5184 })?;
5185 if output_id.eq_ignore_ascii_case("macd") || output_id.eq_ignore_ascii_case("value") {
5186 return Ok(out.macd);
5187 }
5188 if output_id.eq_ignore_ascii_case("signal") {
5189 return Ok(out.signal);
5190 }
5191 if output_id.eq_ignore_ascii_case("hist") {
5192 return Ok(out.hist);
5193 }
5194 Err(IndicatorDispatchError::UnknownOutput {
5195 indicator: "macd".to_string(),
5196 output: output_id.to_string(),
5197 })
5198 })
5199}
5200
5201fn compute_bollinger_batch(
5202 req: IndicatorBatchRequest<'_>,
5203 output_id: &str,
5204) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5205 let data = extract_slice_input("bollinger_bands", req.data, "close")?;
5206 let kernel = req.kernel.to_non_batch();
5207 collect_f64(
5208 "bollinger_bands",
5209 output_id,
5210 req.combos,
5211 data.len(),
5212 |params| {
5213 let period = get_usize_param("bollinger_bands", params, "period", 20)?;
5214 let devup = get_f64_param("bollinger_bands", params, "devup", 2.0)?;
5215 let devdn = get_f64_param("bollinger_bands", params, "devdn", 2.0)?;
5216 let matype = get_enum_param("bollinger_bands", params, "matype", "sma")?;
5217 let devtype = get_usize_param("bollinger_bands", params, "devtype", 0)?;
5218 let input = BollingerBandsInput::from_slice(
5219 data,
5220 BollingerBandsParams {
5221 period: Some(period),
5222 devup: Some(devup),
5223 devdn: Some(devdn),
5224 matype: Some(matype),
5225 devtype: Some(devtype),
5226 },
5227 );
5228 let out = bollinger_bands_with_kernel(&input, kernel).map_err(|e| {
5229 IndicatorDispatchError::ComputeFailed {
5230 indicator: "bollinger_bands".to_string(),
5231 details: e.to_string(),
5232 }
5233 })?;
5234 if output_id.eq_ignore_ascii_case("upper") || output_id.eq_ignore_ascii_case("value") {
5235 return Ok(out.upper_band);
5236 }
5237 if output_id.eq_ignore_ascii_case("middle") {
5238 return Ok(out.middle_band);
5239 }
5240 if output_id.eq_ignore_ascii_case("lower") {
5241 return Ok(out.lower_band);
5242 }
5243 Err(IndicatorDispatchError::UnknownOutput {
5244 indicator: "bollinger_bands".to_string(),
5245 output: output_id.to_string(),
5246 })
5247 },
5248 )
5249}
5250
5251fn compute_bbw_batch(
5252 req: IndicatorBatchRequest<'_>,
5253 output_id: &str,
5254) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5255 let data = extract_slice_input("bollinger_bands_width", req.data, "close")?;
5256 let kernel = req.kernel.to_non_batch();
5257 collect_f64(
5258 "bollinger_bands_width",
5259 output_id,
5260 req.combos,
5261 data.len(),
5262 |params| {
5263 let period = get_usize_param("bollinger_bands_width", params, "period", 20)?;
5264 let devup = get_f64_param("bollinger_bands_width", params, "devup", 2.0)?;
5265 let devdn = get_f64_param("bollinger_bands_width", params, "devdn", 2.0)?;
5266 let matype = get_enum_param("bollinger_bands_width", params, "matype", "sma")?;
5267 let devtype = get_usize_param("bollinger_bands_width", params, "devtype", 0)?;
5268 let input = BollingerBandsWidthInput::from_slice(
5269 data,
5270 BollingerBandsWidthParams {
5271 period: Some(period),
5272 devup: Some(devup),
5273 devdn: Some(devdn),
5274 matype: Some(matype),
5275 devtype: Some(devtype),
5276 },
5277 );
5278 let out = bollinger_bands_width_with_kernel(&input, kernel).map_err(|e| {
5279 IndicatorDispatchError::ComputeFailed {
5280 indicator: "bollinger_bands_width".to_string(),
5281 details: e.to_string(),
5282 }
5283 })?;
5284 if output_id.eq_ignore_ascii_case("value") || output_id.eq_ignore_ascii_case("values") {
5285 return Ok(out.values);
5286 }
5287 Err(IndicatorDispatchError::UnknownOutput {
5288 indicator: "bollinger_bands_width".to_string(),
5289 output: output_id.to_string(),
5290 })
5291 },
5292 )
5293}
5294
5295fn compute_stoch_batch(
5296 req: IndicatorBatchRequest<'_>,
5297 output_id: &str,
5298) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5299 let (high, low, close) = extract_ohlc_input("stoch", req.data)?;
5300 let kernel = req.kernel.to_non_batch();
5301 collect_f64("stoch", output_id, req.combos, close.len(), |params| {
5302 let fastk_period = get_usize_param("stoch", params, "fastk_period", 14)?;
5303 let slowk_period = get_usize_param("stoch", params, "slowk_period", 3)?;
5304 let slowd_period = get_usize_param("stoch", params, "slowd_period", 3)?;
5305 let slowk_ma_type = get_enum_param("stoch", params, "slowk_ma_type", "sma")?;
5306 let slowd_ma_type = get_enum_param("stoch", params, "slowd_ma_type", "sma")?;
5307 let input = StochInput::from_slices(
5308 high,
5309 low,
5310 close,
5311 StochParams {
5312 fastk_period: Some(fastk_period),
5313 slowk_period: Some(slowk_period),
5314 slowk_ma_type: Some(slowk_ma_type),
5315 slowd_period: Some(slowd_period),
5316 slowd_ma_type: Some(slowd_ma_type),
5317 },
5318 );
5319 let out = stoch_with_kernel(&input, kernel).map_err(|e| {
5320 IndicatorDispatchError::ComputeFailed {
5321 indicator: "stoch".to_string(),
5322 details: e.to_string(),
5323 }
5324 })?;
5325 if output_id.eq_ignore_ascii_case("k") || output_id.eq_ignore_ascii_case("value") {
5326 return Ok(out.k);
5327 }
5328 if output_id.eq_ignore_ascii_case("d") {
5329 return Ok(out.d);
5330 }
5331 Err(IndicatorDispatchError::UnknownOutput {
5332 indicator: "stoch".to_string(),
5333 output: output_id.to_string(),
5334 })
5335 })
5336}
5337
5338fn compute_stochf_batch(
5339 req: IndicatorBatchRequest<'_>,
5340 output_id: &str,
5341) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5342 let (high, low, close) = extract_ohlc_input("stochf", req.data)?;
5343 let kernel = req.kernel.to_non_batch();
5344 collect_f64("stochf", output_id, req.combos, close.len(), |params| {
5345 let fastk_period = get_usize_param("stochf", params, "fastk_period", 5)?;
5346 let fastd_period = get_usize_param("stochf", params, "fastd_period", 3)?;
5347 let fastd_matype = get_usize_param("stochf", params, "fastd_matype", 0)?;
5348 let input = StochfInput::from_slices(
5349 high,
5350 low,
5351 close,
5352 StochfParams {
5353 fastk_period: Some(fastk_period),
5354 fastd_period: Some(fastd_period),
5355 fastd_matype: Some(fastd_matype),
5356 },
5357 );
5358 let out = stochf_with_kernel(&input, kernel).map_err(|e| {
5359 IndicatorDispatchError::ComputeFailed {
5360 indicator: "stochf".to_string(),
5361 details: e.to_string(),
5362 }
5363 })?;
5364 if output_id.eq_ignore_ascii_case("k") || output_id.eq_ignore_ascii_case("value") {
5365 return Ok(out.k);
5366 }
5367 if output_id.eq_ignore_ascii_case("d") {
5368 return Ok(out.d);
5369 }
5370 Err(IndicatorDispatchError::UnknownOutput {
5371 indicator: "stochf".to_string(),
5372 output: output_id.to_string(),
5373 })
5374 })
5375}
5376
5377fn compute_stochastic_money_flow_index_batch(
5378 req: IndicatorBatchRequest<'_>,
5379 output_id: &str,
5380) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5381 let (source, volume) =
5382 extract_close_volume_input("stochastic_money_flow_index", req.data, "close")?;
5383 let kernel = req.kernel.to_non_batch();
5384 collect_f64(
5385 "stochastic_money_flow_index",
5386 output_id,
5387 req.combos,
5388 source.len(),
5389 |params| {
5390 let stoch_k_length =
5391 get_usize_param("stochastic_money_flow_index", params, "stoch_k_length", 14)?;
5392 let stoch_k_smooth =
5393 get_usize_param("stochastic_money_flow_index", params, "stoch_k_smooth", 3)?;
5394 let stoch_d_smooth =
5395 get_usize_param("stochastic_money_flow_index", params, "stoch_d_smooth", 3)?;
5396 let mfi_length =
5397 get_usize_param("stochastic_money_flow_index", params, "mfi_length", 14)?;
5398 let input = StochasticMoneyFlowIndexInput::from_slices(
5399 source,
5400 volume,
5401 StochasticMoneyFlowIndexParams {
5402 stoch_k_length: Some(stoch_k_length),
5403 stoch_k_smooth: Some(stoch_k_smooth),
5404 stoch_d_smooth: Some(stoch_d_smooth),
5405 mfi_length: Some(mfi_length),
5406 },
5407 );
5408 let out = stochastic_money_flow_index_with_kernel(&input, kernel).map_err(|e| {
5409 IndicatorDispatchError::ComputeFailed {
5410 indicator: "stochastic_money_flow_index".to_string(),
5411 details: e.to_string(),
5412 }
5413 })?;
5414 if output_id.eq_ignore_ascii_case("k") || output_id.eq_ignore_ascii_case("value") {
5415 return Ok(out.k);
5416 }
5417 if output_id.eq_ignore_ascii_case("d") {
5418 return Ok(out.d);
5419 }
5420 Err(IndicatorDispatchError::UnknownOutput {
5421 indicator: "stochastic_money_flow_index".to_string(),
5422 output: output_id.to_string(),
5423 })
5424 },
5425 )
5426}
5427
5428fn compute_vwmacd_batch(
5429 req: IndicatorBatchRequest<'_>,
5430 output_id: &str,
5431) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5432 let (close, volume) = extract_close_volume_input("vwmacd", req.data, "close")?;
5433 let kernel = req.kernel.to_non_batch();
5434 collect_f64("vwmacd", output_id, req.combos, close.len(), |params| {
5435 let fast_period =
5436 get_usize_param_with_aliases("vwmacd", params, &["fast", "fast_period"], 12)?;
5437 let slow_period =
5438 get_usize_param_with_aliases("vwmacd", params, &["slow", "slow_period"], 26)?;
5439 let signal_period =
5440 get_usize_param_with_aliases("vwmacd", params, &["signal", "signal_period"], 9)?;
5441 let fast_ma_type = get_enum_param("vwmacd", params, "fast_ma_type", "sma")?;
5442 let slow_ma_type = get_enum_param("vwmacd", params, "slow_ma_type", "sma")?;
5443 let signal_ma_type = get_enum_param("vwmacd", params, "signal_ma_type", "ema")?;
5444 let input = VwmacdInput::from_slices(
5445 close,
5446 volume,
5447 VwmacdParams {
5448 fast_period: Some(fast_period),
5449 slow_period: Some(slow_period),
5450 signal_period: Some(signal_period),
5451 fast_ma_type: Some(fast_ma_type),
5452 slow_ma_type: Some(slow_ma_type),
5453 signal_ma_type: Some(signal_ma_type),
5454 },
5455 );
5456 let out = vwmacd_with_kernel(&input, kernel).map_err(|e| {
5457 IndicatorDispatchError::ComputeFailed {
5458 indicator: "vwmacd".to_string(),
5459 details: e.to_string(),
5460 }
5461 })?;
5462 if output_id.eq_ignore_ascii_case("macd") || output_id.eq_ignore_ascii_case("value") {
5463 return Ok(out.macd);
5464 }
5465 if output_id.eq_ignore_ascii_case("signal") {
5466 return Ok(out.signal);
5467 }
5468 if output_id.eq_ignore_ascii_case("hist") {
5469 return Ok(out.hist);
5470 }
5471 Err(IndicatorDispatchError::UnknownOutput {
5472 indicator: "vwmacd".to_string(),
5473 output: output_id.to_string(),
5474 })
5475 })
5476}
5477
5478fn compute_vpci_batch(
5479 req: IndicatorBatchRequest<'_>,
5480 output_id: &str,
5481) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5482 let (close, volume) = extract_close_volume_input("vpci", req.data, "close")?;
5483 let kernel = req.kernel.to_non_batch();
5484 collect_f64("vpci", output_id, req.combos, close.len(), |params| {
5485 let short_range = get_usize_param("vpci", params, "short_range", 5)?;
5486 let long_range = get_usize_param("vpci", params, "long_range", 25)?;
5487 let input = VpciInput::from_slices(
5488 close,
5489 volume,
5490 VpciParams {
5491 short_range: Some(short_range),
5492 long_range: Some(long_range),
5493 },
5494 );
5495 let out = vpci_with_kernel(&input, kernel).map_err(|e| {
5496 IndicatorDispatchError::ComputeFailed {
5497 indicator: "vpci".to_string(),
5498 details: e.to_string(),
5499 }
5500 })?;
5501 if output_id.eq_ignore_ascii_case("vpci") || output_id.eq_ignore_ascii_case("value") {
5502 return Ok(out.vpci);
5503 }
5504 if output_id.eq_ignore_ascii_case("vpcis") {
5505 return Ok(out.vpcis);
5506 }
5507 Err(IndicatorDispatchError::UnknownOutput {
5508 indicator: "vpci".to_string(),
5509 output: output_id.to_string(),
5510 })
5511 })
5512}
5513
5514fn compute_ttm_trend_batch(
5515 req: IndicatorBatchRequest<'_>,
5516 output_id: &str,
5517) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5518 expect_value_output("ttm_trend", output_id)?;
5519 let mut derived_source: Option<Vec<f64>> = None;
5520 let (source, close): (&[f64], &[f64]) = match req.data {
5521 IndicatorDataRef::Candles { candles, source } => (
5522 source_type(candles, source.unwrap_or("hl2")),
5523 candles.close.as_slice(),
5524 ),
5525 IndicatorDataRef::Ohlc {
5526 high, low, close, ..
5527 } => {
5528 ensure_same_len_3("ttm_trend", high.len(), low.len(), close.len())?;
5529 derived_source = Some(high.iter().zip(low).map(|(h, l)| 0.5 * (h + l)).collect());
5530 (derived_source.as_deref().unwrap_or(close), close)
5531 }
5532 IndicatorDataRef::Ohlcv {
5533 high, low, close, ..
5534 } => {
5535 ensure_same_len_3("ttm_trend", high.len(), low.len(), close.len())?;
5536 derived_source = Some(high.iter().zip(low).map(|(h, l)| 0.5 * (h + l)).collect());
5537 (derived_source.as_deref().unwrap_or(close), close)
5538 }
5539 _ => {
5540 return Err(IndicatorDispatchError::MissingRequiredInput {
5541 indicator: "ttm_trend".to_string(),
5542 input: IndicatorInputKind::Ohlc,
5543 })
5544 }
5545 };
5546 let kernel = req.kernel.to_non_batch();
5547 collect_bool("ttm_trend", output_id, req.combos, close.len(), |params| {
5548 let period = get_usize_param("ttm_trend", params, "period", 5)?;
5549 let input = TtmTrendInput::from_slices(
5550 source,
5551 close,
5552 TtmTrendParams {
5553 period: Some(period),
5554 },
5555 );
5556 let out = ttm_trend_with_kernel(&input, kernel).map_err(|e| {
5557 IndicatorDispatchError::ComputeFailed {
5558 indicator: "ttm_trend".to_string(),
5559 details: e.to_string(),
5560 }
5561 })?;
5562 Ok(out.values)
5563 })
5564}
5565
5566fn compute_ttm_squeeze_batch(
5567 req: IndicatorBatchRequest<'_>,
5568 output_id: &str,
5569) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5570 let (high, low, close) = extract_ohlc_input("ttm_squeeze", req.data)?;
5571 let kernel = req.kernel.to_non_batch();
5572 collect_f64(
5573 "ttm_squeeze",
5574 output_id,
5575 req.combos,
5576 close.len(),
5577 |params| {
5578 let length = get_usize_param("ttm_squeeze", params, "length", 20)?;
5579 let bb_mult = get_f64_param("ttm_squeeze", params, "bb_mult", 2.0)?;
5580 let kc_mult_high = get_f64_param_with_aliases(
5581 "ttm_squeeze",
5582 params,
5583 &["kc_high", "kc_mult_high"],
5584 1.0,
5585 )?;
5586 let kc_mult_mid =
5587 get_f64_param_with_aliases("ttm_squeeze", params, &["kc_mid", "kc_mult_mid"], 1.5)?;
5588 let kc_mult_low =
5589 get_f64_param_with_aliases("ttm_squeeze", params, &["kc_low", "kc_mult_low"], 2.0)?;
5590 let input = TtmSqueezeInput::from_slices(
5591 high,
5592 low,
5593 close,
5594 TtmSqueezeParams {
5595 length: Some(length),
5596 bb_mult: Some(bb_mult),
5597 kc_mult_high: Some(kc_mult_high),
5598 kc_mult_mid: Some(kc_mult_mid),
5599 kc_mult_low: Some(kc_mult_low),
5600 },
5601 );
5602 let out = ttm_squeeze_with_kernel(&input, kernel).map_err(|e| {
5603 IndicatorDispatchError::ComputeFailed {
5604 indicator: "ttm_squeeze".to_string(),
5605 details: e.to_string(),
5606 }
5607 })?;
5608 if output_id.eq_ignore_ascii_case("momentum") || output_id.eq_ignore_ascii_case("value")
5609 {
5610 return Ok(out.momentum);
5611 }
5612 if output_id.eq_ignore_ascii_case("squeeze") {
5613 return Ok(out.squeeze);
5614 }
5615 Err(IndicatorDispatchError::UnknownOutput {
5616 indicator: "ttm_squeeze".to_string(),
5617 output: output_id.to_string(),
5618 })
5619 },
5620 )
5621}
5622
5623fn compute_aroon_batch(
5624 req: IndicatorBatchRequest<'_>,
5625 output_id: &str,
5626) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5627 let (high, low) = extract_high_low_input("aroon", req.data)?;
5628 let kernel = req.kernel.to_non_batch();
5629 collect_f64("aroon", output_id, req.combos, high.len(), |params| {
5630 let length = get_usize_param("aroon", params, "length", 14)?;
5631 let input = AroonInput::from_slices_hl(
5632 high,
5633 low,
5634 AroonParams {
5635 length: Some(length),
5636 },
5637 );
5638 let out = aroon_with_kernel(&input, kernel).map_err(|e| {
5639 IndicatorDispatchError::ComputeFailed {
5640 indicator: "aroon".to_string(),
5641 details: e.to_string(),
5642 }
5643 })?;
5644 if output_id.eq_ignore_ascii_case("up")
5645 || output_id.eq_ignore_ascii_case("aroon_up")
5646 || output_id.eq_ignore_ascii_case("value")
5647 {
5648 return Ok(out.aroon_up);
5649 }
5650 if output_id.eq_ignore_ascii_case("down") || output_id.eq_ignore_ascii_case("aroon_down") {
5651 return Ok(out.aroon_down);
5652 }
5653 Err(IndicatorDispatchError::UnknownOutput {
5654 indicator: "aroon".to_string(),
5655 output: output_id.to_string(),
5656 })
5657 })
5658}
5659
5660fn compute_aroonosc_batch(
5661 req: IndicatorBatchRequest<'_>,
5662 output_id: &str,
5663) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5664 let (high, low) = extract_high_low_input("aroonosc", req.data)?;
5665 let kernel = req.kernel.to_non_batch();
5666 collect_f64("aroonosc", output_id, req.combos, high.len(), |params| {
5667 let length = get_usize_param("aroonosc", params, "length", 14)?;
5668 let input = AroonOscInput::from_slices_hl(
5669 high,
5670 low,
5671 AroonOscParams {
5672 length: Some(length),
5673 },
5674 );
5675 let out = aroon_osc_with_kernel(&input, kernel).map_err(|e| {
5676 IndicatorDispatchError::ComputeFailed {
5677 indicator: "aroonosc".to_string(),
5678 details: e.to_string(),
5679 }
5680 })?;
5681 if output_id.eq_ignore_ascii_case("value") {
5682 return Ok(out.values);
5683 }
5684 Err(IndicatorDispatchError::UnknownOutput {
5685 indicator: "aroonosc".to_string(),
5686 output: output_id.to_string(),
5687 })
5688 })
5689}
5690
5691fn compute_di_batch(
5692 req: IndicatorBatchRequest<'_>,
5693 output_id: &str,
5694) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5695 let (high, low, close) = extract_ohlc_input("di", req.data)?;
5696 let kernel = req.kernel.to_non_batch();
5697 collect_f64("di", output_id, req.combos, close.len(), |params| {
5698 let period = get_usize_param("di", params, "period", 14)?;
5699 let input = DiInput::from_slices(
5700 high,
5701 low,
5702 close,
5703 DiParams {
5704 period: Some(period),
5705 },
5706 );
5707 let out =
5708 di_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
5709 indicator: "di".to_string(),
5710 details: e.to_string(),
5711 })?;
5712 if output_id.eq_ignore_ascii_case("plus") || output_id.eq_ignore_ascii_case("value") {
5713 return Ok(out.plus);
5714 }
5715 if output_id.eq_ignore_ascii_case("minus") {
5716 return Ok(out.minus);
5717 }
5718 Err(IndicatorDispatchError::UnknownOutput {
5719 indicator: "di".to_string(),
5720 output: output_id.to_string(),
5721 })
5722 })
5723}
5724
5725fn compute_dm_batch(
5726 req: IndicatorBatchRequest<'_>,
5727 output_id: &str,
5728) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5729 let (high, low) = extract_high_low_input("dm", req.data)?;
5730 let kernel = req.kernel.to_non_batch();
5731 collect_f64("dm", output_id, req.combos, high.len(), |params| {
5732 let period = get_usize_param("dm", params, "period", 14)?;
5733 let input = DmInput::from_slices(
5734 high,
5735 low,
5736 DmParams {
5737 period: Some(period),
5738 },
5739 );
5740 let out =
5741 dm_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
5742 indicator: "dm".to_string(),
5743 details: e.to_string(),
5744 })?;
5745 if output_id.eq_ignore_ascii_case("plus") || output_id.eq_ignore_ascii_case("value") {
5746 return Ok(out.plus);
5747 }
5748 if output_id.eq_ignore_ascii_case("minus") {
5749 return Ok(out.minus);
5750 }
5751 Err(IndicatorDispatchError::UnknownOutput {
5752 indicator: "dm".to_string(),
5753 output: output_id.to_string(),
5754 })
5755 })
5756}
5757
5758fn compute_dti_batch(
5759 req: IndicatorBatchRequest<'_>,
5760 output_id: &str,
5761) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5762 expect_value_output("dti", output_id)?;
5763 let (high, low) = extract_high_low_input("dti", req.data)?;
5764 let kernel = req.kernel.to_non_batch();
5765 collect_f64_into_rows("dti", output_id, req.combos, high.len(), |params, row| {
5766 let r = get_usize_param("dti", params, "r", 14)?;
5767 let s = get_usize_param("dti", params, "s", 10)?;
5768 let u = get_usize_param("dti", params, "u", 5)?;
5769 let input = DtiInput::from_slices(
5770 high,
5771 low,
5772 DtiParams {
5773 r: Some(r),
5774 s: Some(s),
5775 u: Some(u),
5776 },
5777 );
5778 dti_into_slice(row, &input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
5779 indicator: "dti".to_string(),
5780 details: e.to_string(),
5781 })
5782 })
5783}
5784
5785fn compute_donchian_batch(
5786 req: IndicatorBatchRequest<'_>,
5787 output_id: &str,
5788) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5789 let (high, low) = extract_high_low_input("donchian", req.data)?;
5790 let kernel = req.kernel.to_non_batch();
5791 collect_f64("donchian", output_id, req.combos, high.len(), |params| {
5792 let period = get_usize_param("donchian", params, "period", 20)?;
5793 let input = DonchianInput::from_slices(
5794 high,
5795 low,
5796 DonchianParams {
5797 period: Some(period),
5798 },
5799 );
5800 let out = donchian_with_kernel(&input, kernel).map_err(|e| {
5801 IndicatorDispatchError::ComputeFailed {
5802 indicator: "donchian".to_string(),
5803 details: e.to_string(),
5804 }
5805 })?;
5806 if output_id.eq_ignore_ascii_case("upper") || output_id.eq_ignore_ascii_case("value") {
5807 return Ok(out.upperband);
5808 }
5809 if output_id.eq_ignore_ascii_case("middle") {
5810 return Ok(out.middleband);
5811 }
5812 if output_id.eq_ignore_ascii_case("lower") {
5813 return Ok(out.lowerband);
5814 }
5815 Err(IndicatorDispatchError::UnknownOutput {
5816 indicator: "donchian".to_string(),
5817 output: output_id.to_string(),
5818 })
5819 })
5820}
5821
5822fn compute_kdj_batch(
5823 req: IndicatorBatchRequest<'_>,
5824 output_id: &str,
5825) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5826 let (high, low, close) = extract_ohlc_input("kdj", req.data)?;
5827 let kernel = req.kernel.to_non_batch();
5828 collect_f64("kdj", output_id, req.combos, close.len(), |params| {
5829 let fast_k_period = get_usize_param("kdj", params, "fast_k_period", 9)?;
5830 let slow_k_period = get_usize_param("kdj", params, "slow_k_period", 3)?;
5831 let slow_k_ma_type = get_enum_param("kdj", params, "slow_k_ma_type", "sma")?;
5832 let slow_d_period = get_usize_param("kdj", params, "slow_d_period", 3)?;
5833 let slow_d_ma_type = get_enum_param("kdj", params, "slow_d_ma_type", "sma")?;
5834 let input = KdjInput::from_slices(
5835 high,
5836 low,
5837 close,
5838 KdjParams {
5839 fast_k_period: Some(fast_k_period),
5840 slow_k_period: Some(slow_k_period),
5841 slow_k_ma_type: Some(slow_k_ma_type),
5842 slow_d_period: Some(slow_d_period),
5843 slow_d_ma_type: Some(slow_d_ma_type),
5844 },
5845 );
5846 let out =
5847 kdj_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
5848 indicator: "kdj".to_string(),
5849 details: e.to_string(),
5850 })?;
5851 if output_id.eq_ignore_ascii_case("k") || output_id.eq_ignore_ascii_case("value") {
5852 return Ok(out.k);
5853 }
5854 if output_id.eq_ignore_ascii_case("d") {
5855 return Ok(out.d);
5856 }
5857 if output_id.eq_ignore_ascii_case("j") {
5858 return Ok(out.j);
5859 }
5860 Err(IndicatorDispatchError::UnknownOutput {
5861 indicator: "kdj".to_string(),
5862 output: output_id.to_string(),
5863 })
5864 })
5865}
5866
5867fn compute_keltner_batch(
5868 req: IndicatorBatchRequest<'_>,
5869 output_id: &str,
5870) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5871 let (high, low, close) = extract_ohlc_input("keltner", req.data)?;
5872 let kernel = req.kernel.to_non_batch();
5873 collect_f64("keltner", output_id, req.combos, close.len(), |params| {
5874 let period = get_usize_param("keltner", params, "period", 20)?;
5875 let multiplier = get_f64_param("keltner", params, "multiplier", 2.0)?;
5876 let ma_type = get_enum_param("keltner", params, "ma_type", "ema")?;
5877 let input = KeltnerInput::from_slice(
5878 high,
5879 low,
5880 close,
5881 close,
5882 KeltnerParams {
5883 period: Some(period),
5884 multiplier: Some(multiplier),
5885 ma_type: Some(ma_type),
5886 },
5887 );
5888 let out = keltner_with_kernel(&input, kernel).map_err(|e| {
5889 IndicatorDispatchError::ComputeFailed {
5890 indicator: "keltner".to_string(),
5891 details: e.to_string(),
5892 }
5893 })?;
5894 if output_id.eq_ignore_ascii_case("upper") || output_id.eq_ignore_ascii_case("value") {
5895 return Ok(out.upper_band);
5896 }
5897 if output_id.eq_ignore_ascii_case("middle") {
5898 return Ok(out.middle_band);
5899 }
5900 if output_id.eq_ignore_ascii_case("lower") {
5901 return Ok(out.lower_band);
5902 }
5903 Err(IndicatorDispatchError::UnknownOutput {
5904 indicator: "keltner".to_string(),
5905 output: output_id.to_string(),
5906 })
5907 })
5908}
5909
5910fn compute_squeeze_momentum_batch(
5911 req: IndicatorBatchRequest<'_>,
5912 output_id: &str,
5913) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5914 let (high, low, close) = extract_ohlc_input("squeeze_momentum", req.data)?;
5915 let kernel = req.kernel.to_non_batch();
5916 collect_f64(
5917 "squeeze_momentum",
5918 output_id,
5919 req.combos,
5920 close.len(),
5921 |params| {
5922 let length_bb = get_usize_param("squeeze_momentum", params, "length_bb", 20)?;
5923 let mult_bb = get_f64_param("squeeze_momentum", params, "mult_bb", 2.0)?;
5924 let length_kc = get_usize_param("squeeze_momentum", params, "length_kc", 20)?;
5925 let mult_kc = get_f64_param("squeeze_momentum", params, "mult_kc", 1.5)?;
5926 let input = SqueezeMomentumInput::from_slices(
5927 high,
5928 low,
5929 close,
5930 SqueezeMomentumParams {
5931 length_bb: Some(length_bb),
5932 mult_bb: Some(mult_bb),
5933 length_kc: Some(length_kc),
5934 mult_kc: Some(mult_kc),
5935 },
5936 );
5937 let out = squeeze_momentum_with_kernel(&input, kernel).map_err(|e| {
5938 IndicatorDispatchError::ComputeFailed {
5939 indicator: "squeeze_momentum".to_string(),
5940 details: e.to_string(),
5941 }
5942 })?;
5943 if output_id.eq_ignore_ascii_case("momentum") || output_id.eq_ignore_ascii_case("value")
5944 {
5945 return Ok(out.momentum);
5946 }
5947 if output_id.eq_ignore_ascii_case("squeeze") {
5948 return Ok(out.squeeze);
5949 }
5950 if output_id.eq_ignore_ascii_case("signal")
5951 || output_id.eq_ignore_ascii_case("momentum_signal")
5952 {
5953 return Ok(out.momentum_signal);
5954 }
5955 Err(IndicatorDispatchError::UnknownOutput {
5956 indicator: "squeeze_momentum".to_string(),
5957 output: output_id.to_string(),
5958 })
5959 },
5960 )
5961}
5962
5963fn compute_srsi_batch(
5964 req: IndicatorBatchRequest<'_>,
5965 output_id: &str,
5966) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
5967 let data = extract_slice_input("srsi", req.data, "close")?;
5968 let kernel = req.kernel.to_non_batch();
5969 collect_f64("srsi", output_id, req.combos, data.len(), |params| {
5970 let rsi_period = get_usize_param("srsi", params, "rsi_period", 14)?;
5971 let stoch_period = get_usize_param("srsi", params, "stoch_period", 14)?;
5972 let k = get_usize_param("srsi", params, "k", 3)?;
5973 let d = get_usize_param("srsi", params, "d", 3)?;
5974 let source = get_enum_param("srsi", params, "source", "close")?;
5975 let input = SrsiInput::from_slice(
5976 data,
5977 SrsiParams {
5978 rsi_period: Some(rsi_period),
5979 stoch_period: Some(stoch_period),
5980 k: Some(k),
5981 d: Some(d),
5982 source: Some(source),
5983 },
5984 );
5985 let out = srsi_with_kernel(&input, kernel).map_err(|e| {
5986 IndicatorDispatchError::ComputeFailed {
5987 indicator: "srsi".to_string(),
5988 details: e.to_string(),
5989 }
5990 })?;
5991 if output_id.eq_ignore_ascii_case("k") || output_id.eq_ignore_ascii_case("value") {
5992 return Ok(out.k);
5993 }
5994 if output_id.eq_ignore_ascii_case("d") {
5995 return Ok(out.d);
5996 }
5997 Err(IndicatorDispatchError::UnknownOutput {
5998 indicator: "srsi".to_string(),
5999 output: output_id.to_string(),
6000 })
6001 })
6002}
6003
6004fn compute_supertrend_batch(
6005 req: IndicatorBatchRequest<'_>,
6006 output_id: &str,
6007) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6008 let (high, low, close) = extract_ohlc_input("supertrend", req.data)?;
6009 let kernel = req.kernel.to_non_batch();
6010 collect_f64("supertrend", output_id, req.combos, close.len(), |params| {
6011 let period = get_usize_param("supertrend", params, "period", 10)?;
6012 let factor = get_f64_param("supertrend", params, "factor", 3.0)?;
6013 let input = SuperTrendInput::from_slices(
6014 high,
6015 low,
6016 close,
6017 SuperTrendParams {
6018 period: Some(period),
6019 factor: Some(factor),
6020 },
6021 );
6022 let out = supertrend_with_kernel(&input, kernel).map_err(|e| {
6023 IndicatorDispatchError::ComputeFailed {
6024 indicator: "supertrend".to_string(),
6025 details: e.to_string(),
6026 }
6027 })?;
6028 if output_id.eq_ignore_ascii_case("trend") || output_id.eq_ignore_ascii_case("value") {
6029 return Ok(out.trend);
6030 }
6031 if output_id.eq_ignore_ascii_case("changed") {
6032 return Ok(out.changed);
6033 }
6034 Err(IndicatorDispatchError::UnknownOutput {
6035 indicator: "supertrend".to_string(),
6036 output: output_id.to_string(),
6037 })
6038 })
6039}
6040
6041fn compute_adjustable_ma_alternating_extremities_batch(
6042 req: IndicatorBatchRequest<'_>,
6043 output_id: &str,
6044) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6045 let (high, low, close) = extract_ohlc_input("adjustable_ma_alternating_extremities", req.data)?;
6046 let kernel = req.kernel.to_non_batch();
6047 collect_f64(
6048 "adjustable_ma_alternating_extremities",
6049 output_id,
6050 req.combos,
6051 close.len(),
6052 |params| {
6053 let length = get_usize_param(
6054 "adjustable_ma_alternating_extremities",
6055 params,
6056 "length",
6057 50,
6058 )?;
6059 let mult = get_f64_param("adjustable_ma_alternating_extremities", params, "mult", 2.0)?;
6060 let alpha = get_f64_param(
6061 "adjustable_ma_alternating_extremities",
6062 params,
6063 "alpha",
6064 1.0,
6065 )?;
6066 let beta = get_f64_param("adjustable_ma_alternating_extremities", params, "beta", 0.5)?;
6067 let input = AdjustableMaAlternatingExtremitiesInput::from_slices(
6068 high,
6069 low,
6070 close,
6071 AdjustableMaAlternatingExtremitiesParams {
6072 length: Some(length),
6073 mult: Some(mult),
6074 alpha: Some(alpha),
6075 beta: Some(beta),
6076 },
6077 );
6078 let out =
6079 adjustable_ma_alternating_extremities_with_kernel(&input, kernel).map_err(|e| {
6080 IndicatorDispatchError::ComputeFailed {
6081 indicator: "adjustable_ma_alternating_extremities".to_string(),
6082 details: e.to_string(),
6083 }
6084 })?;
6085 if output_id.eq_ignore_ascii_case("ma") || output_id.eq_ignore_ascii_case("value") {
6086 return Ok(out.ma);
6087 }
6088 if output_id.eq_ignore_ascii_case("upper") {
6089 return Ok(out.upper);
6090 }
6091 if output_id.eq_ignore_ascii_case("lower") {
6092 return Ok(out.lower);
6093 }
6094 if output_id.eq_ignore_ascii_case("extremity") {
6095 return Ok(out.extremity);
6096 }
6097 if output_id.eq_ignore_ascii_case("state") {
6098 return Ok(out.state);
6099 }
6100 if output_id.eq_ignore_ascii_case("changed") {
6101 return Ok(out.changed);
6102 }
6103 if output_id.eq_ignore_ascii_case("smoothed_open") {
6104 return Ok(out.smoothed_open);
6105 }
6106 if output_id.eq_ignore_ascii_case("smoothed_high") {
6107 return Ok(out.smoothed_high);
6108 }
6109 if output_id.eq_ignore_ascii_case("smoothed_low") {
6110 return Ok(out.smoothed_low);
6111 }
6112 if output_id.eq_ignore_ascii_case("smoothed_close") {
6113 return Ok(out.smoothed_close);
6114 }
6115 Err(IndicatorDispatchError::UnknownOutput {
6116 indicator: "adjustable_ma_alternating_extremities".to_string(),
6117 output: output_id.to_string(),
6118 })
6119 },
6120 )
6121}
6122
6123fn compute_vi_batch(
6124 req: IndicatorBatchRequest<'_>,
6125 output_id: &str,
6126) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6127 let (high, low, close) = extract_ohlc_input("vi", req.data)?;
6128 let kernel = req.kernel.to_non_batch();
6129 collect_f64("vi", output_id, req.combos, close.len(), |params| {
6130 let period = get_usize_param("vi", params, "period", 14)?;
6131 let input = ViInput::from_slices(
6132 high,
6133 low,
6134 close,
6135 ViParams {
6136 period: Some(period),
6137 },
6138 );
6139 let out =
6140 vi_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
6141 indicator: "vi".to_string(),
6142 details: e.to_string(),
6143 })?;
6144 if output_id.eq_ignore_ascii_case("plus") || output_id.eq_ignore_ascii_case("value") {
6145 return Ok(out.plus);
6146 }
6147 if output_id.eq_ignore_ascii_case("minus") {
6148 return Ok(out.minus);
6149 }
6150 Err(IndicatorDispatchError::UnknownOutput {
6151 indicator: "vi".to_string(),
6152 output: output_id.to_string(),
6153 })
6154 })
6155}
6156
6157fn compute_wavetrend_batch(
6158 req: IndicatorBatchRequest<'_>,
6159 output_id: &str,
6160) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6161 let data = extract_slice_input("wavetrend", req.data, "hlc3")?;
6162 let kernel = req.kernel.to_non_batch();
6163 collect_f64("wavetrend", output_id, req.combos, data.len(), |params| {
6164 let channel_length = get_usize_param("wavetrend", params, "channel_length", 9)?;
6165 let average_length = get_usize_param("wavetrend", params, "average_length", 12)?;
6166 let ma_length = get_usize_param("wavetrend", params, "ma_length", 3)?;
6167 let factor = get_f64_param("wavetrend", params, "factor", 0.015)?;
6168 let input = WavetrendInput::from_slice(
6169 data,
6170 WavetrendParams {
6171 channel_length: Some(channel_length),
6172 average_length: Some(average_length),
6173 ma_length: Some(ma_length),
6174 factor: Some(factor),
6175 },
6176 );
6177 let out = wavetrend_with_kernel(&input, kernel).map_err(|e| {
6178 IndicatorDispatchError::ComputeFailed {
6179 indicator: "wavetrend".to_string(),
6180 details: e.to_string(),
6181 }
6182 })?;
6183 if output_id.eq_ignore_ascii_case("wt1") || output_id.eq_ignore_ascii_case("value") {
6184 return Ok(out.wt1);
6185 }
6186 if output_id.eq_ignore_ascii_case("wt2") {
6187 return Ok(out.wt2);
6188 }
6189 if output_id.eq_ignore_ascii_case("wt_diff") {
6190 return Ok(out.wt_diff);
6191 }
6192 Err(IndicatorDispatchError::UnknownOutput {
6193 indicator: "wavetrend".to_string(),
6194 output: output_id.to_string(),
6195 })
6196 })
6197}
6198
6199fn compute_wto_batch(
6200 req: IndicatorBatchRequest<'_>,
6201 output_id: &str,
6202) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6203 let data = extract_slice_input("wto", req.data, "close")?;
6204 let kernel = req.kernel.to_non_batch();
6205 collect_f64("wto", output_id, req.combos, data.len(), |params| {
6206 let channel_length = get_usize_param("wto", params, "channel_length", 10)?;
6207 let average_length = get_usize_param("wto", params, "average_length", 21)?;
6208 let input = WtoInput::from_slice(
6209 data,
6210 WtoParams {
6211 channel_length: Some(channel_length),
6212 average_length: Some(average_length),
6213 },
6214 );
6215 let out =
6216 wto_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
6217 indicator: "wto".to_string(),
6218 details: e.to_string(),
6219 })?;
6220 if output_id.eq_ignore_ascii_case("wavetrend1")
6221 || output_id.eq_ignore_ascii_case("wt1")
6222 || output_id.eq_ignore_ascii_case("value")
6223 {
6224 return Ok(out.wavetrend1);
6225 }
6226 if output_id.eq_ignore_ascii_case("wavetrend2") || output_id.eq_ignore_ascii_case("wt2") {
6227 return Ok(out.wavetrend2);
6228 }
6229 if output_id.eq_ignore_ascii_case("histogram") || output_id.eq_ignore_ascii_case("hist") {
6230 return Ok(out.histogram);
6231 }
6232 Err(IndicatorDispatchError::UnknownOutput {
6233 indicator: "wto".to_string(),
6234 output: output_id.to_string(),
6235 })
6236 })
6237}
6238
6239fn compute_rogers_satchell_volatility_batch(
6240 req: IndicatorBatchRequest<'_>,
6241 output_id: &str,
6242) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6243 let (open, high, low, close) = extract_ohlc_full_input("rogers_satchell_volatility", req.data)?;
6244 let kernel = req.kernel.to_non_batch();
6245 collect_f64(
6246 "rogers_satchell_volatility",
6247 output_id,
6248 req.combos,
6249 close.len(),
6250 |params| {
6251 let lookback = get_usize_param("rogers_satchell_volatility", params, "lookback", 8)?;
6252 let signal_length =
6253 get_usize_param("rogers_satchell_volatility", params, "signal_length", 8)?;
6254 let input = RogersSatchellVolatilityInput::from_slices(
6255 open,
6256 high,
6257 low,
6258 close,
6259 RogersSatchellVolatilityParams {
6260 lookback: Some(lookback),
6261 signal_length: Some(signal_length),
6262 },
6263 );
6264 let out = rogers_satchell_volatility_with_kernel(&input, kernel).map_err(|e| {
6265 IndicatorDispatchError::ComputeFailed {
6266 indicator: "rogers_satchell_volatility".to_string(),
6267 details: e.to_string(),
6268 }
6269 })?;
6270 if output_id.eq_ignore_ascii_case("rs") || output_id.eq_ignore_ascii_case("value") {
6271 return Ok(out.rs);
6272 }
6273 if output_id.eq_ignore_ascii_case("signal") {
6274 return Ok(out.signal);
6275 }
6276 Err(IndicatorDispatchError::UnknownOutput {
6277 indicator: "rogers_satchell_volatility".to_string(),
6278 output: output_id.to_string(),
6279 })
6280 },
6281 )
6282}
6283
6284fn compute_historical_volatility_rank_batch(
6285 req: IndicatorBatchRequest<'_>,
6286 output_id: &str,
6287) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6288 let data = extract_slice_input("historical_volatility_rank", req.data, "close")?;
6289 let kernel = req.kernel.to_non_batch();
6290 collect_f64(
6291 "historical_volatility_rank",
6292 output_id,
6293 req.combos,
6294 data.len(),
6295 |params| {
6296 let hv_length = get_usize_param("historical_volatility_rank", params, "hv_length", 10)?;
6297 let rank_length =
6298 get_usize_param("historical_volatility_rank", params, "rank_length", 52 * 7)?;
6299 let annualization_days = get_f64_param(
6300 "historical_volatility_rank",
6301 params,
6302 "annualization_days",
6303 365.0,
6304 )?;
6305 let bar_days = get_f64_param("historical_volatility_rank", params, "bar_days", 1.0)?;
6306 let input = HistoricalVolatilityRankInput::from_slice(
6307 data,
6308 HistoricalVolatilityRankParams {
6309 hv_length: Some(hv_length),
6310 rank_length: Some(rank_length),
6311 annualization_days: Some(annualization_days),
6312 bar_days: Some(bar_days),
6313 },
6314 );
6315 let out = historical_volatility_rank_with_kernel(&input, kernel).map_err(|e| {
6316 IndicatorDispatchError::ComputeFailed {
6317 indicator: "historical_volatility_rank".to_string(),
6318 details: e.to_string(),
6319 }
6320 })?;
6321 if output_id.eq_ignore_ascii_case("hvr") || output_id.eq_ignore_ascii_case("value") {
6322 return Ok(out.hvr);
6323 }
6324 if output_id.eq_ignore_ascii_case("hv") {
6325 return Ok(out.hv);
6326 }
6327 Err(IndicatorDispatchError::UnknownOutput {
6328 indicator: "historical_volatility_rank".to_string(),
6329 output: output_id.to_string(),
6330 })
6331 },
6332 )
6333}
6334
6335fn compute_dual_ulcer_index_batch(
6336 req: IndicatorBatchRequest<'_>,
6337 output_id: &str,
6338) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6339 let data = extract_slice_input("dual_ulcer_index", req.data, "close")?;
6340 let kernel = req.kernel.to_non_batch();
6341 collect_f64(
6342 "dual_ulcer_index",
6343 output_id,
6344 req.combos,
6345 data.len(),
6346 |params| {
6347 let period = get_usize_param("dual_ulcer_index", params, "period", 5)?;
6348 let auto_threshold =
6349 get_bool_param("dual_ulcer_index", params, "auto_threshold", true)?;
6350 let threshold = get_f64_param("dual_ulcer_index", params, "threshold", 0.1)?;
6351 let input = DualUlcerIndexInput::from_slice(
6352 data,
6353 DualUlcerIndexParams {
6354 period: Some(period),
6355 auto_threshold: Some(auto_threshold),
6356 threshold: Some(threshold),
6357 },
6358 );
6359 let out = dual_ulcer_index_with_kernel(&input, kernel).map_err(|e| {
6360 IndicatorDispatchError::ComputeFailed {
6361 indicator: "dual_ulcer_index".to_string(),
6362 details: e.to_string(),
6363 }
6364 })?;
6365 if output_id.eq_ignore_ascii_case("long_ulcer")
6366 || output_id.eq_ignore_ascii_case("uulcer")
6367 || output_id.eq_ignore_ascii_case("value")
6368 {
6369 return Ok(out.long_ulcer);
6370 }
6371 if output_id.eq_ignore_ascii_case("short_ulcer")
6372 || output_id.eq_ignore_ascii_case("dulcer")
6373 {
6374 return Ok(out.short_ulcer);
6375 }
6376 if output_id.eq_ignore_ascii_case("threshold") {
6377 return Ok(out.threshold);
6378 }
6379 Err(IndicatorDispatchError::UnknownOutput {
6380 indicator: "dual_ulcer_index".to_string(),
6381 output: output_id.to_string(),
6382 })
6383 },
6384 )
6385}
6386
6387fn compute_fractal_dimension_index_batch(
6388 req: IndicatorBatchRequest<'_>,
6389 output_id: &str,
6390) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6391 let data = extract_slice_input("fractal_dimension_index", req.data, "close")?;
6392 let kernel = req.kernel.to_non_batch();
6393 collect_f64(
6394 "fractal_dimension_index",
6395 output_id,
6396 req.combos,
6397 data.len(),
6398 |params| {
6399 let length = get_usize_param("fractal_dimension_index", params, "length", 30)?;
6400 let input = FractalDimensionIndexInput::from_slice(
6401 data,
6402 FractalDimensionIndexParams {
6403 length: Some(length),
6404 },
6405 );
6406 let out = fractal_dimension_index_with_kernel(&input, kernel).map_err(|e| {
6407 IndicatorDispatchError::ComputeFailed {
6408 indicator: "fractal_dimension_index".to_string(),
6409 details: e.to_string(),
6410 }
6411 })?;
6412 if output_id.eq_ignore_ascii_case("value") {
6413 return Ok(out.values);
6414 }
6415 Err(IndicatorDispatchError::UnknownOutput {
6416 indicator: "fractal_dimension_index".to_string(),
6417 output: output_id.to_string(),
6418 })
6419 },
6420 )
6421}
6422
6423fn compute_volume_weighted_rsi_batch(
6424 req: IndicatorBatchRequest<'_>,
6425 output_id: &str,
6426) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6427 expect_value_output("volume_weighted_rsi", output_id)?;
6428 let (close, volume) = extract_close_volume_input("volume_weighted_rsi", req.data, "close")?;
6429 let periods = combo_periods("volume_weighted_rsi", req.combos, "period", 14)?;
6430 if let Some((start, end, step)) = derive_period_sweep(&periods) {
6431 let out = volume_weighted_rsi_batch_with_kernel(
6432 close,
6433 volume,
6434 &VolumeWeightedRsiBatchRange {
6435 period: (start, end, step),
6436 },
6437 to_batch_kernel(req.kernel),
6438 )
6439 .map_err(|e| IndicatorDispatchError::ComputeFailed {
6440 indicator: "volume_weighted_rsi".to_string(),
6441 details: e.to_string(),
6442 })?;
6443 ensure_len("volume_weighted_rsi", close.len(), out.cols)?;
6444 let produced_periods: Vec<usize> = out
6445 .combos
6446 .iter()
6447 .map(|combo| combo.period.unwrap_or(14))
6448 .collect();
6449 let values = reorder_or_take_f64_matrix_by_period(
6450 "volume_weighted_rsi",
6451 &periods,
6452 &produced_periods,
6453 out.cols,
6454 out.values,
6455 )?;
6456 return Ok(f64_output(output_id, periods.len(), out.cols, values));
6457 }
6458
6459 let kernel = req.kernel.to_non_batch();
6460 collect_f64_into_rows(
6461 "volume_weighted_rsi",
6462 output_id,
6463 req.combos,
6464 close.len(),
6465 |params, row| {
6466 let period = get_usize_param("volume_weighted_rsi", params, "period", 14)?;
6467 let input = VolumeWeightedRsiInput::from_slices(
6468 close,
6469 volume,
6470 VolumeWeightedRsiParams {
6471 period: Some(period),
6472 },
6473 );
6474 volume_weighted_rsi_into_slice(row, &input, kernel).map_err(|e| {
6475 IndicatorDispatchError::ComputeFailed {
6476 indicator: "volume_weighted_rsi".to_string(),
6477 details: e.to_string(),
6478 }
6479 })
6480 },
6481 )
6482}
6483
6484fn compute_dynamic_momentum_index_batch(
6485 req: IndicatorBatchRequest<'_>,
6486 output_id: &str,
6487) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6488 expect_value_output("dynamic_momentum_index", output_id)?;
6489 let data = extract_slice_input("dynamic_momentum_index", req.data, "close")?;
6490 let kernel = req.kernel.to_non_batch();
6491 collect_f64_into_rows(
6492 "dynamic_momentum_index",
6493 output_id,
6494 req.combos,
6495 data.len(),
6496 |params, row| {
6497 let rsi_period = get_usize_param("dynamic_momentum_index", params, "rsi_period", 14)?;
6498 let volatility_period =
6499 get_usize_param("dynamic_momentum_index", params, "volatility_period", 5)?;
6500 let volatility_sma_period = get_usize_param(
6501 "dynamic_momentum_index",
6502 params,
6503 "volatility_sma_period",
6504 10,
6505 )?;
6506 let upper_limit = get_usize_param("dynamic_momentum_index", params, "upper_limit", 30)?;
6507 let lower_limit = get_usize_param("dynamic_momentum_index", params, "lower_limit", 5)?;
6508 let input = DynamicMomentumIndexInput::from_slice(
6509 data,
6510 DynamicMomentumIndexParams {
6511 rsi_period: Some(rsi_period),
6512 volatility_period: Some(volatility_period),
6513 volatility_sma_period: Some(volatility_sma_period),
6514 upper_limit: Some(upper_limit),
6515 lower_limit: Some(lower_limit),
6516 },
6517 );
6518 dynamic_momentum_index_into_slice(row, &input, kernel).map_err(|e| {
6519 IndicatorDispatchError::ComputeFailed {
6520 indicator: "dynamic_momentum_index".to_string(),
6521 details: e.to_string(),
6522 }
6523 })
6524 },
6525 )
6526}
6527
6528fn compute_disparity_index_batch(
6529 req: IndicatorBatchRequest<'_>,
6530 output_id: &str,
6531) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6532 expect_value_output("disparity_index", output_id)?;
6533 let data = extract_slice_input("disparity_index", req.data, "close")?;
6534 let kernel = req.kernel.to_non_batch();
6535 collect_f64_into_rows(
6536 "disparity_index",
6537 output_id,
6538 req.combos,
6539 data.len(),
6540 |params, row| {
6541 let ema_period = get_usize_param("disparity_index", params, "ema_period", 14)?;
6542 let lookback_period =
6543 get_usize_param("disparity_index", params, "lookback_period", 14)?;
6544 let smoothing_period =
6545 get_usize_param("disparity_index", params, "smoothing_period", 9)?;
6546 let smoothing_type =
6547 get_enum_param("disparity_index", params, "smoothing_type", "ema")?;
6548 let input = DisparityIndexInput::from_slice(
6549 data,
6550 DisparityIndexParams {
6551 ema_period: Some(ema_period),
6552 lookback_period: Some(lookback_period),
6553 smoothing_period: Some(smoothing_period),
6554 smoothing_type: Some(smoothing_type),
6555 },
6556 );
6557 disparity_index_into_slice(row, &input, kernel).map_err(|e| {
6558 IndicatorDispatchError::ComputeFailed {
6559 indicator: "disparity_index".to_string(),
6560 details: e.to_string(),
6561 }
6562 })
6563 },
6564 )
6565}
6566
6567fn compute_donchian_channel_width_batch(
6568 req: IndicatorBatchRequest<'_>,
6569 output_id: &str,
6570) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6571 expect_value_output("donchian_channel_width", output_id)?;
6572 let (high, low) = extract_high_low_input("donchian_channel_width", req.data)?;
6573
6574 collect_f64_into_rows(
6575 "donchian_channel_width",
6576 output_id,
6577 req.combos,
6578 high.len(),
6579 |params, row| {
6580 let period = get_usize_param("donchian_channel_width", params, "period", 20)?;
6581 let kernel = req.kernel;
6582 let input = DonchianChannelWidthInput::from_slices(
6583 high,
6584 low,
6585 DonchianChannelWidthParams {
6586 period: Some(period),
6587 },
6588 );
6589 donchian_channel_width_into_slice(row, &input, kernel).map_err(|e| {
6590 IndicatorDispatchError::ComputeFailed {
6591 indicator: "donchian_channel_width".to_string(),
6592 details: e.to_string(),
6593 }
6594 })
6595 },
6596 )
6597}
6598
6599fn compute_kairi_relative_index_batch(
6600 req: IndicatorBatchRequest<'_>,
6601 output_id: &str,
6602) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6603 expect_value_output("kairi_relative_index", output_id)?;
6604 let kernel = req.kernel.to_non_batch();
6605 let len = match req.data {
6606 IndicatorDataRef::Slice { values } => values.len(),
6607 IndicatorDataRef::Candles { candles, source } => {
6608 source_type(candles, source.unwrap_or("close")).len()
6609 }
6610 IndicatorDataRef::CloseVolume { close, volume } => {
6611 ensure_same_len_2("kairi_relative_index", close.len(), volume.len())?;
6612 close.len()
6613 }
6614 IndicatorDataRef::Ohlc {
6615 open,
6616 high,
6617 low,
6618 close,
6619 } => {
6620 ensure_same_len_4(
6621 "kairi_relative_index",
6622 open.len(),
6623 high.len(),
6624 low.len(),
6625 close.len(),
6626 )?;
6627 close.len()
6628 }
6629 IndicatorDataRef::Ohlcv {
6630 open,
6631 high,
6632 low,
6633 close,
6634 volume,
6635 } => {
6636 ensure_same_len_5(
6637 "kairi_relative_index",
6638 open.len(),
6639 high.len(),
6640 low.len(),
6641 close.len(),
6642 volume.len(),
6643 )?;
6644 close.len()
6645 }
6646 IndicatorDataRef::HighLow { .. } => {
6647 return Err(IndicatorDispatchError::MissingRequiredInput {
6648 indicator: "kairi_relative_index".to_string(),
6649 input: IndicatorInputKind::Candles,
6650 });
6651 }
6652 };
6653
6654 collect_f64_into_rows(
6655 "kairi_relative_index",
6656 output_id,
6657 req.combos,
6658 len,
6659 |params, row| {
6660 let length = get_usize_param("kairi_relative_index", params, "length", 50)?;
6661 let ma_type = get_enum_param("kairi_relative_index", params, "ma_type", "SMA")?;
6662 if ma_type.eq_ignore_ascii_case("VWMA") {
6663 match req.data {
6664 IndicatorDataRef::Slice { .. } | IndicatorDataRef::Ohlc { .. } => {
6665 return Err(IndicatorDispatchError::MissingRequiredInput {
6666 indicator: "kairi_relative_index".to_string(),
6667 input: IndicatorInputKind::CloseVolume,
6668 });
6669 }
6670 _ => {}
6671 }
6672 }
6673
6674 let input = match req.data {
6675 IndicatorDataRef::Slice { values } => KairiRelativeIndexInput::from_slices(
6676 values,
6677 values,
6678 KairiRelativeIndexParams {
6679 length: Some(length),
6680 ma_type: Some(ma_type.to_string()),
6681 },
6682 ),
6683 IndicatorDataRef::Candles { candles, source } => {
6684 KairiRelativeIndexInput::from_candles(
6685 candles,
6686 source.unwrap_or("close"),
6687 KairiRelativeIndexParams {
6688 length: Some(length),
6689 ma_type: Some(ma_type.to_string()),
6690 },
6691 )
6692 }
6693 IndicatorDataRef::CloseVolume { close, volume } => {
6694 KairiRelativeIndexInput::from_slices(
6695 close,
6696 volume,
6697 KairiRelativeIndexParams {
6698 length: Some(length),
6699 ma_type: Some(ma_type.to_string()),
6700 },
6701 )
6702 }
6703 IndicatorDataRef::Ohlc { close, .. } => KairiRelativeIndexInput::from_slices(
6704 close,
6705 close,
6706 KairiRelativeIndexParams {
6707 length: Some(length),
6708 ma_type: Some(ma_type.to_string()),
6709 },
6710 ),
6711 IndicatorDataRef::Ohlcv { close, volume, .. } => {
6712 KairiRelativeIndexInput::from_slices(
6713 close,
6714 volume,
6715 KairiRelativeIndexParams {
6716 length: Some(length),
6717 ma_type: Some(ma_type.to_string()),
6718 },
6719 )
6720 }
6721 IndicatorDataRef::HighLow { .. } => unreachable!(),
6722 };
6723
6724 kairi_relative_index_into_slice(row, &input, kernel).map_err(|e| {
6725 IndicatorDispatchError::ComputeFailed {
6726 indicator: "kairi_relative_index".to_string(),
6727 details: e.to_string(),
6728 }
6729 })
6730 },
6731 )
6732}
6733
6734fn compute_projection_oscillator_batch(
6735 req: IndicatorBatchRequest<'_>,
6736 output_id: &str,
6737) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6738 let (high, low, close) = extract_ohlc_input("projection_oscillator", req.data)?;
6739 let kernel = req.kernel.to_non_batch();
6740 collect_f64(
6741 "projection_oscillator",
6742 output_id,
6743 req.combos,
6744 close.len(),
6745 |params| {
6746 let length = get_usize_param("projection_oscillator", params, "length", 14)?;
6747 let smooth_length =
6748 get_usize_param("projection_oscillator", params, "smooth_length", 4)?;
6749 let input = ProjectionOscillatorInput::from_slices(
6750 high,
6751 low,
6752 close,
6753 ProjectionOscillatorParams {
6754 length: Some(length),
6755 smooth_length: Some(smooth_length),
6756 },
6757 );
6758 let out = projection_oscillator_with_kernel(&input, kernel).map_err(|e| {
6759 IndicatorDispatchError::ComputeFailed {
6760 indicator: "projection_oscillator".to_string(),
6761 details: e.to_string(),
6762 }
6763 })?;
6764 if output_id.eq_ignore_ascii_case("pbo") || output_id.eq_ignore_ascii_case("value") {
6765 return Ok(out.pbo);
6766 }
6767 if output_id.eq_ignore_ascii_case("signal") {
6768 return Ok(out.signal);
6769 }
6770 Err(IndicatorDispatchError::UnknownOutput {
6771 indicator: "projection_oscillator".to_string(),
6772 output: output_id.to_string(),
6773 })
6774 },
6775 )
6776}
6777
6778fn compute_market_structure_trailing_stop_batch(
6779 req: IndicatorBatchRequest<'_>,
6780 output_id: &str,
6781) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6782 let (open, high, low, close) =
6783 extract_ohlc_full_input("market_structure_trailing_stop", req.data)?;
6784 let kernel = req.kernel.to_non_batch();
6785 collect_f64(
6786 "market_structure_trailing_stop",
6787 output_id,
6788 req.combos,
6789 close.len(),
6790 |params| {
6791 let length = get_usize_param("market_structure_trailing_stop", params, "length", 14)?;
6792 let increment_factor = get_f64_param(
6793 "market_structure_trailing_stop",
6794 params,
6795 "increment_factor",
6796 100.0,
6797 )?;
6798 let reset_on = get_enum_param(
6799 "market_structure_trailing_stop",
6800 params,
6801 "reset_on",
6802 "CHoCH",
6803 )?;
6804 let input = MarketStructureTrailingStopInput::from_slices(
6805 open,
6806 high,
6807 low,
6808 close,
6809 MarketStructureTrailingStopParams {
6810 length: Some(length),
6811 increment_factor: Some(increment_factor),
6812 reset_on: Some(reset_on),
6813 },
6814 );
6815 let out = market_structure_trailing_stop_with_kernel(&input, kernel).map_err(|e| {
6816 IndicatorDispatchError::ComputeFailed {
6817 indicator: "market_structure_trailing_stop".to_string(),
6818 details: e.to_string(),
6819 }
6820 })?;
6821 if output_id.eq_ignore_ascii_case("trailing_stop")
6822 || output_id.eq_ignore_ascii_case("value")
6823 {
6824 return Ok(out.trailing_stop);
6825 }
6826 if output_id.eq_ignore_ascii_case("state") {
6827 return Ok(out.state);
6828 }
6829 if output_id.eq_ignore_ascii_case("structure") {
6830 return Ok(out.structure);
6831 }
6832 Err(IndicatorDispatchError::UnknownOutput {
6833 indicator: "market_structure_trailing_stop".to_string(),
6834 output: output_id.to_string(),
6835 })
6836 },
6837 )
6838}
6839
6840fn compute_evasive_supertrend_batch(
6841 req: IndicatorBatchRequest<'_>,
6842 output_id: &str,
6843) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6844 let (open, high, low, close) = extract_ohlc_full_input("evasive_supertrend", req.data)?;
6845 let kernel = req.kernel.to_non_batch();
6846 collect_f64(
6847 "evasive_supertrend",
6848 output_id,
6849 req.combos,
6850 close.len(),
6851 |params| {
6852 let atr_length = get_usize_param("evasive_supertrend", params, "atr_length", 10)?;
6853 let base_multiplier =
6854 get_f64_param("evasive_supertrend", params, "base_multiplier", 3.0)?;
6855 let noise_threshold =
6856 get_f64_param("evasive_supertrend", params, "noise_threshold", 1.0)?;
6857 let expansion_alpha =
6858 get_f64_param("evasive_supertrend", params, "expansion_alpha", 0.5)?;
6859 let input = EvasiveSuperTrendInput::from_slices(
6860 open,
6861 high,
6862 low,
6863 close,
6864 EvasiveSuperTrendParams {
6865 atr_length: Some(atr_length),
6866 base_multiplier: Some(base_multiplier),
6867 noise_threshold: Some(noise_threshold),
6868 expansion_alpha: Some(expansion_alpha),
6869 },
6870 );
6871 let out = evasive_supertrend_with_kernel(&input, kernel).map_err(|e| {
6872 IndicatorDispatchError::ComputeFailed {
6873 indicator: "evasive_supertrend".to_string(),
6874 details: e.to_string(),
6875 }
6876 })?;
6877 if output_id.eq_ignore_ascii_case("band") || output_id.eq_ignore_ascii_case("value") {
6878 return Ok(out.band);
6879 }
6880 if output_id.eq_ignore_ascii_case("state") {
6881 return Ok(out.state);
6882 }
6883 if output_id.eq_ignore_ascii_case("noisy") {
6884 return Ok(out.noisy);
6885 }
6886 if output_id.eq_ignore_ascii_case("changed") {
6887 return Ok(out.changed);
6888 }
6889 Err(IndicatorDispatchError::UnknownOutput {
6890 indicator: "evasive_supertrend".to_string(),
6891 output: output_id.to_string(),
6892 })
6893 },
6894 )
6895}
6896
6897fn compute_reversal_signals_batch(
6898 req: IndicatorBatchRequest<'_>,
6899 output_id: &str,
6900) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6901 let (open, high, low, close, volume) = extract_ohlcv_full_input("reversal_signals", req.data)?;
6902 let kernel = req.kernel.to_non_batch();
6903 collect_f64(
6904 "reversal_signals",
6905 output_id,
6906 req.combos,
6907 close.len(),
6908 |params| {
6909 let lookback_period =
6910 get_usize_param("reversal_signals", params, "lookback_period", 12)?;
6911 let confirmation_period =
6912 get_usize_param("reversal_signals", params, "confirmation_period", 3)?;
6913 let use_volume_confirmation =
6914 get_bool_param("reversal_signals", params, "use_volume_confirmation", true)?;
6915 let trend_ma_period =
6916 get_usize_param("reversal_signals", params, "trend_ma_period", 50)?;
6917 let trend_ma_type = get_enum_param("reversal_signals", params, "trend_ma_type", "EMA")?;
6918 let ma_step_period = get_usize_param("reversal_signals", params, "ma_step_period", 33)?;
6919 let input = ReversalSignalsInput::from_slices(
6920 open,
6921 high,
6922 low,
6923 close,
6924 volume,
6925 ReversalSignalsParams {
6926 lookback_period: Some(lookback_period),
6927 confirmation_period: Some(confirmation_period),
6928 use_volume_confirmation: Some(use_volume_confirmation),
6929 trend_ma_period: Some(trend_ma_period),
6930 trend_ma_type: Some(trend_ma_type.to_string()),
6931 ma_step_period: Some(ma_step_period),
6932 },
6933 );
6934 let out = reversal_signals_with_kernel(&input, kernel).map_err(|e| {
6935 IndicatorDispatchError::ComputeFailed {
6936 indicator: "reversal_signals".to_string(),
6937 details: e.to_string(),
6938 }
6939 })?;
6940 if output_id.eq_ignore_ascii_case("buy_signal") {
6941 return Ok(out.buy_signal);
6942 }
6943 if output_id.eq_ignore_ascii_case("sell_signal") {
6944 return Ok(out.sell_signal);
6945 }
6946 if output_id.eq_ignore_ascii_case("stepped_ma")
6947 || output_id.eq_ignore_ascii_case("value")
6948 {
6949 return Ok(out.stepped_ma);
6950 }
6951 if output_id.eq_ignore_ascii_case("state") {
6952 return Ok(out.state);
6953 }
6954 Err(IndicatorDispatchError::UnknownOutput {
6955 indicator: "reversal_signals".to_string(),
6956 output: output_id.to_string(),
6957 })
6958 },
6959 )
6960}
6961
6962fn compute_zig_zag_channels_batch(
6963 req: IndicatorBatchRequest<'_>,
6964 output_id: &str,
6965) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
6966 let (open, high, low, close) = extract_ohlc_full_input("zig_zag_channels", req.data)?;
6967 let kernel = req.kernel.to_non_batch();
6968 collect_f64(
6969 "zig_zag_channels",
6970 output_id,
6971 req.combos,
6972 close.len(),
6973 |params| {
6974 let length = get_usize_param("zig_zag_channels", params, "length", 100)?;
6975 let extend = get_bool_param("zig_zag_channels", params, "extend", true)?;
6976 let input = ZigZagChannelsInput::from_slices(
6977 open,
6978 high,
6979 low,
6980 close,
6981 ZigZagChannelsParams {
6982 length: Some(length),
6983 extend: Some(extend),
6984 },
6985 );
6986 let out = zig_zag_channels_with_kernel(&input, kernel).map_err(|e| {
6987 IndicatorDispatchError::ComputeFailed {
6988 indicator: "zig_zag_channels".to_string(),
6989 details: e.to_string(),
6990 }
6991 })?;
6992 if output_id.eq_ignore_ascii_case("middle") || output_id.eq_ignore_ascii_case("value") {
6993 return Ok(out.middle);
6994 }
6995 if output_id.eq_ignore_ascii_case("upper") {
6996 return Ok(out.upper);
6997 }
6998 if output_id.eq_ignore_ascii_case("lower") {
6999 return Ok(out.lower);
7000 }
7001 Err(IndicatorDispatchError::UnknownOutput {
7002 indicator: "zig_zag_channels".to_string(),
7003 output: output_id.to_string(),
7004 })
7005 },
7006 )
7007}
7008
7009fn compute_directional_imbalance_index_batch(
7010 req: IndicatorBatchRequest<'_>,
7011 output_id: &str,
7012) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7013 let (high, low) = match req.data {
7014 IndicatorDataRef::Candles { candles, .. } => {
7015 (candles.high.as_slice(), candles.low.as_slice())
7016 }
7017 IndicatorDataRef::HighLow { high, low } => (high, low),
7018 IndicatorDataRef::Ohlc { high, low, .. } => (high, low),
7019 IndicatorDataRef::Ohlcv { high, low, .. } => (high, low),
7020 _ => {
7021 return Err(IndicatorDispatchError::MissingRequiredInput {
7022 indicator: "directional_imbalance_index".to_string(),
7023 input: IndicatorInputKind::HighLow,
7024 });
7025 }
7026 };
7027 let kernel = req.kernel.to_non_batch();
7028 collect_f64(
7029 "directional_imbalance_index",
7030 output_id,
7031 req.combos,
7032 high.len(),
7033 |params| {
7034 let length = get_usize_param("directional_imbalance_index", params, "length", 10)?;
7035 let period = get_usize_param("directional_imbalance_index", params, "period", 70)?;
7036 let input = DirectionalImbalanceIndexInput::from_slices(
7037 high,
7038 low,
7039 DirectionalImbalanceIndexParams {
7040 length: Some(length),
7041 period: Some(period),
7042 },
7043 );
7044 let out = directional_imbalance_index_with_kernel(&input, kernel).map_err(|e| {
7045 IndicatorDispatchError::ComputeFailed {
7046 indicator: "directional_imbalance_index".to_string(),
7047 details: e.to_string(),
7048 }
7049 })?;
7050 if output_id.eq_ignore_ascii_case("up") || output_id.eq_ignore_ascii_case("value") {
7051 return Ok(out.up);
7052 }
7053 if output_id.eq_ignore_ascii_case("down") {
7054 return Ok(out.down);
7055 }
7056 if output_id.eq_ignore_ascii_case("bulls") {
7057 return Ok(out.bulls);
7058 }
7059 if output_id.eq_ignore_ascii_case("bears") {
7060 return Ok(out.bears);
7061 }
7062 if output_id.eq_ignore_ascii_case("upper") {
7063 return Ok(out.upper);
7064 }
7065 if output_id.eq_ignore_ascii_case("lower") {
7066 return Ok(out.lower);
7067 }
7068 Err(IndicatorDispatchError::UnknownOutput {
7069 indicator: "directional_imbalance_index".to_string(),
7070 output: output_id.to_string(),
7071 })
7072 },
7073 )
7074}
7075
7076fn compute_candle_strength_oscillator_batch(
7077 req: IndicatorBatchRequest<'_>,
7078 output_id: &str,
7079) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7080 let (open, high, low, close) = match req.data {
7081 IndicatorDataRef::Candles { candles, .. } => (
7082 candles.open.as_slice(),
7083 candles.high.as_slice(),
7084 candles.low.as_slice(),
7085 candles.close.as_slice(),
7086 ),
7087 IndicatorDataRef::Ohlc {
7088 open,
7089 high,
7090 low,
7091 close,
7092 } => (open, high, low, close),
7093 IndicatorDataRef::Ohlcv {
7094 open,
7095 high,
7096 low,
7097 close,
7098 ..
7099 } => (open, high, low, close),
7100 _ => {
7101 return Err(IndicatorDispatchError::MissingRequiredInput {
7102 indicator: "candle_strength_oscillator".to_string(),
7103 input: IndicatorInputKind::Ohlc,
7104 });
7105 }
7106 };
7107 let kernel = req.kernel.to_non_batch();
7108 collect_f64(
7109 "candle_strength_oscillator",
7110 output_id,
7111 req.combos,
7112 close.len(),
7113 |params| {
7114 let period = get_usize_param("candle_strength_oscillator", params, "period", 50)?;
7115 let atr_enabled =
7116 get_bool_param("candle_strength_oscillator", params, "atr_enabled", false)?;
7117 let atr_length =
7118 get_usize_param("candle_strength_oscillator", params, "atr_length", 50)?;
7119 let mode = get_enum_param("candle_strength_oscillator", params, "mode", "bollinger")?;
7120 let input = CandleStrengthOscillatorInput::from_slices(
7121 open,
7122 high,
7123 low,
7124 close,
7125 CandleStrengthOscillatorParams {
7126 period: Some(period),
7127 atr_enabled: Some(atr_enabled),
7128 atr_length: Some(atr_length),
7129 mode: Some(mode.to_string()),
7130 },
7131 );
7132 let out = candle_strength_oscillator_with_kernel(&input, kernel).map_err(|e| {
7133 IndicatorDispatchError::ComputeFailed {
7134 indicator: "candle_strength_oscillator".to_string(),
7135 details: e.to_string(),
7136 }
7137 })?;
7138 if output_id.eq_ignore_ascii_case("strength") || output_id.eq_ignore_ascii_case("value")
7139 {
7140 return Ok(out.strength);
7141 }
7142 if output_id.eq_ignore_ascii_case("highs") {
7143 return Ok(out.highs);
7144 }
7145 if output_id.eq_ignore_ascii_case("lows") {
7146 return Ok(out.lows);
7147 }
7148 if output_id.eq_ignore_ascii_case("mid") {
7149 return Ok(out.mid);
7150 }
7151 if output_id.eq_ignore_ascii_case("long_signal") {
7152 return Ok(out.long_signal);
7153 }
7154 if output_id.eq_ignore_ascii_case("short_signal") {
7155 return Ok(out.short_signal);
7156 }
7157 Err(IndicatorDispatchError::UnknownOutput {
7158 indicator: "candle_strength_oscillator".to_string(),
7159 output: output_id.to_string(),
7160 })
7161 },
7162 )
7163}
7164
7165fn compute_gmma_oscillator_batch(
7166 req: IndicatorBatchRequest<'_>,
7167 output_id: &str,
7168) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7169 let kernel = req.kernel.to_non_batch();
7170 let owned_source;
7171 let data = match req.data {
7172 IndicatorDataRef::Slice { values } => values,
7173 IndicatorDataRef::Candles { candles, source } => {
7174 source_type(candles, source.unwrap_or("close"))
7175 }
7176 IndicatorDataRef::Ohlc { close, .. } => close,
7177 IndicatorDataRef::Ohlcv { close, .. } => close,
7178 IndicatorDataRef::CloseVolume { close, volume } => {
7179 ensure_same_len_2("gmma_oscillator", close.len(), volume.len())?;
7180 close
7181 }
7182 IndicatorDataRef::HighLow { high, low } => {
7183 ensure_same_len_2("gmma_oscillator", high.len(), low.len())?;
7184 owned_source = high
7185 .iter()
7186 .zip(low.iter())
7187 .map(|(&h, &l)| (h + l) * 0.5)
7188 .collect::<Vec<_>>();
7189 owned_source.as_slice()
7190 }
7191 };
7192
7193 collect_f64(
7194 "gmma_oscillator",
7195 output_id,
7196 req.combos,
7197 data.len(),
7198 |params| {
7199 let gmma_type = get_enum_param("gmma_oscillator", params, "gmma_type", "guppy")?;
7200 let smooth_length = get_usize_param("gmma_oscillator", params, "smooth_length", 1)?;
7201 let signal_length = get_usize_param("gmma_oscillator", params, "signal_length", 13)?;
7202 let anchor_minutes = get_usize_param("gmma_oscillator", params, "anchor_minutes", 0)?;
7203 let interval_minutes = if params
7204 .iter()
7205 .any(|param| param.key.eq_ignore_ascii_case("interval_minutes"))
7206 {
7207 Some(get_usize_param(
7208 "gmma_oscillator",
7209 params,
7210 "interval_minutes",
7211 1,
7212 )?)
7213 } else {
7214 None
7215 };
7216 let input = GmmaOscillatorInput::from_slice(
7217 data,
7218 GmmaOscillatorParams {
7219 gmma_type: Some(gmma_type.to_string()),
7220 smooth_length: Some(smooth_length),
7221 signal_length: Some(signal_length),
7222 anchor_minutes: Some(anchor_minutes),
7223 interval_minutes,
7224 },
7225 );
7226 let out = gmma_oscillator_with_kernel(&input, kernel).map_err(|e| {
7227 IndicatorDispatchError::ComputeFailed {
7228 indicator: "gmma_oscillator".to_string(),
7229 details: e.to_string(),
7230 }
7231 })?;
7232 if output_id.eq_ignore_ascii_case("oscillator")
7233 || output_id.eq_ignore_ascii_case("value")
7234 {
7235 return Ok(out.oscillator);
7236 }
7237 if output_id.eq_ignore_ascii_case("signal") {
7238 return Ok(out.signal);
7239 }
7240 Err(IndicatorDispatchError::UnknownOutput {
7241 indicator: "gmma_oscillator".to_string(),
7242 output: output_id.to_string(),
7243 })
7244 },
7245 )
7246}
7247
7248fn compute_nonlinear_regression_zero_lag_moving_average_batch(
7249 req: IndicatorBatchRequest<'_>,
7250 output_id: &str,
7251) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7252 let data = extract_slice_input(
7253 "nonlinear_regression_zero_lag_moving_average",
7254 req.data,
7255 "close",
7256 )?;
7257 let kernel = req.kernel.to_non_batch();
7258 collect_f64(
7259 "nonlinear_regression_zero_lag_moving_average",
7260 output_id,
7261 req.combos,
7262 data.len(),
7263 |params| {
7264 let zlma_period = get_usize_param(
7265 "nonlinear_regression_zero_lag_moving_average",
7266 params,
7267 "zlma_period",
7268 15,
7269 )?;
7270 let regression_period = get_usize_param(
7271 "nonlinear_regression_zero_lag_moving_average",
7272 params,
7273 "regression_period",
7274 15,
7275 )?;
7276 let input = NonlinearRegressionZeroLagMovingAverageInput::from_slice(
7277 data,
7278 NonlinearRegressionZeroLagMovingAverageParams {
7279 zlma_period: Some(zlma_period),
7280 regression_period: Some(regression_period),
7281 },
7282 );
7283 let out = nonlinear_regression_zero_lag_moving_average_with_kernel(&input, kernel)
7284 .map_err(|e| IndicatorDispatchError::ComputeFailed {
7285 indicator: "nonlinear_regression_zero_lag_moving_average".to_string(),
7286 details: e.to_string(),
7287 })?;
7288 if output_id.eq_ignore_ascii_case("value") {
7289 return Ok(out.value);
7290 }
7291 if output_id.eq_ignore_ascii_case("signal") {
7292 return Ok(out.signal);
7293 }
7294 if output_id.eq_ignore_ascii_case("long_signal") {
7295 return Ok(out.long_signal);
7296 }
7297 if output_id.eq_ignore_ascii_case("short_signal") {
7298 return Ok(out.short_signal);
7299 }
7300 Err(IndicatorDispatchError::UnknownOutput {
7301 indicator: "nonlinear_regression_zero_lag_moving_average".to_string(),
7302 output: output_id.to_string(),
7303 })
7304 },
7305 )
7306}
7307
7308fn compute_possible_rsi_batch(
7309 req: IndicatorBatchRequest<'_>,
7310 output_id: &str,
7311) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7312 let data = extract_slice_input("possible_rsi", req.data, "close")?;
7313 let kernel = req.kernel.to_non_batch();
7314 collect_f64(
7315 "possible_rsi",
7316 output_id,
7317 req.combos,
7318 data.len(),
7319 |params| {
7320 let period = get_usize_param("possible_rsi", params, "period", 32)?;
7321 let rsi_mode = get_enum_param("possible_rsi", params, "rsi_mode", "regular")?;
7322 let norm_period = get_usize_param("possible_rsi", params, "norm_period", 100)?;
7323 let normalization_mode = get_enum_param(
7324 "possible_rsi",
7325 params,
7326 "normalization_mode",
7327 "gaussian_fisher",
7328 )?;
7329 let normalization_length =
7330 get_usize_param("possible_rsi", params, "normalization_length", 15)?;
7331 let nonlag_period = get_usize_param("possible_rsi", params, "nonlag_period", 15)?;
7332 let dynamic_zone_period =
7333 get_usize_param("possible_rsi", params, "dynamic_zone_period", 20)?;
7334 let buy_probability = get_f64_param("possible_rsi", params, "buy_probability", 0.2)?;
7335 let sell_probability = get_f64_param("possible_rsi", params, "sell_probability", 0.2)?;
7336 let signal_type =
7337 get_enum_param("possible_rsi", params, "signal_type", "zeroline_crossover")?;
7338 let run_highpass = get_bool_param("possible_rsi", params, "run_highpass", false)?;
7339 let highpass_period = get_usize_param("possible_rsi", params, "highpass_period", 15)?;
7340 let input = PossibleRsiInput::from_slice(
7341 data,
7342 PossibleRsiParams {
7343 period: Some(period),
7344 rsi_mode: Some(rsi_mode.to_string()),
7345 norm_period: Some(norm_period),
7346 normalization_mode: Some(normalization_mode.to_string()),
7347 normalization_length: Some(normalization_length),
7348 nonlag_period: Some(nonlag_period),
7349 dynamic_zone_period: Some(dynamic_zone_period),
7350 buy_probability: Some(buy_probability),
7351 sell_probability: Some(sell_probability),
7352 signal_type: Some(signal_type.to_string()),
7353 run_highpass: Some(run_highpass),
7354 highpass_period: Some(highpass_period),
7355 },
7356 );
7357 let out = possible_rsi_with_kernel(&input, kernel).map_err(|e| {
7358 IndicatorDispatchError::ComputeFailed {
7359 indicator: "possible_rsi".to_string(),
7360 details: e.to_string(),
7361 }
7362 })?;
7363 if output_id.eq_ignore_ascii_case("value") {
7364 return Ok(out.value);
7365 }
7366 if output_id.eq_ignore_ascii_case("buy_level") {
7367 return Ok(out.buy_level);
7368 }
7369 if output_id.eq_ignore_ascii_case("sell_level") {
7370 return Ok(out.sell_level);
7371 }
7372 if output_id.eq_ignore_ascii_case("middle")
7373 || output_id.eq_ignore_ascii_case("middle_level")
7374 {
7375 return Ok(out.middle_level);
7376 }
7377 if output_id.eq_ignore_ascii_case("trend") || output_id.eq_ignore_ascii_case("state") {
7378 return Ok(out.state);
7379 }
7380 if output_id.eq_ignore_ascii_case("long_signal") {
7381 return Ok(out.long_signal);
7382 }
7383 if output_id.eq_ignore_ascii_case("short_signal") {
7384 return Ok(out.short_signal);
7385 }
7386 Err(IndicatorDispatchError::UnknownOutput {
7387 indicator: "possible_rsi".to_string(),
7388 output: output_id.to_string(),
7389 })
7390 },
7391 )
7392}
7393
7394fn compute_autocorrelation_indicator_batch(
7395 req: IndicatorBatchRequest<'_>,
7396 output_id: &str,
7397) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7398 let data = extract_slice_input("autocorrelation_indicator", req.data, "close")?;
7399 let kernel = req.kernel.to_non_batch();
7400 collect_f64(
7401 "autocorrelation_indicator",
7402 output_id,
7403 req.combos,
7404 data.len(),
7405 |params| {
7406 let length = get_usize_param("autocorrelation_indicator", params, "length", 20)?;
7407 let lag = get_usize_param("autocorrelation_indicator", params, "lag", 1)?;
7408 let use_test_signal = get_bool_param(
7409 "autocorrelation_indicator",
7410 params,
7411 "use_test_signal",
7412 false,
7413 )?;
7414 let max_lag = if output_id.eq_ignore_ascii_case("correlation") {
7415 lag
7416 } else {
7417 1
7418 };
7419 let input = AutocorrelationIndicatorInput::from_slice(
7420 data,
7421 AutocorrelationIndicatorParams {
7422 length: Some(length),
7423 max_lag: Some(max_lag),
7424 use_test_signal: Some(use_test_signal),
7425 },
7426 );
7427 let out = autocorrelation_indicator_with_kernel(&input, kernel).map_err(|e| {
7428 IndicatorDispatchError::ComputeFailed {
7429 indicator: "autocorrelation_indicator".to_string(),
7430 details: e.to_string(),
7431 }
7432 })?;
7433 if output_id.eq_ignore_ascii_case("filtered") || output_id.eq_ignore_ascii_case("value")
7434 {
7435 return Ok(out.filtered);
7436 }
7437 if output_id.eq_ignore_ascii_case("correlation") {
7438 let start = (lag - 1).checked_mul(data.len()).ok_or_else(|| {
7439 IndicatorDispatchError::ComputeFailed {
7440 indicator: "autocorrelation_indicator".to_string(),
7441 details: "lag * cols overflow".to_string(),
7442 }
7443 })?;
7444 let end = start + data.len();
7445 return Ok(out.correlations[start..end].to_vec());
7446 }
7447 Err(IndicatorDispatchError::UnknownOutput {
7448 indicator: "autocorrelation_indicator".to_string(),
7449 output: output_id.to_string(),
7450 })
7451 },
7452 )
7453}
7454
7455fn compute_goertzel_cycle_composite_wave_batch(
7456 req: IndicatorBatchRequest<'_>,
7457 output_id: &str,
7458) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7459 if !output_id.eq_ignore_ascii_case("value") && !output_id.eq_ignore_ascii_case("wave") {
7460 return Err(IndicatorDispatchError::UnknownOutput {
7461 indicator: "goertzel_cycle_composite_wave".to_string(),
7462 output: output_id.to_string(),
7463 });
7464 }
7465 let data = extract_slice_input("goertzel_cycle_composite_wave", req.data, "close")?;
7466 let kernel = req.kernel.to_non_batch();
7467 collect_f64_into_rows(
7468 "goertzel_cycle_composite_wave",
7469 output_id,
7470 req.combos,
7471 data.len(),
7472 |params, row| {
7473 let max_period =
7474 get_usize_param("goertzel_cycle_composite_wave", params, "max_period", 120)?;
7475 let start_at_cycle =
7476 get_usize_param("goertzel_cycle_composite_wave", params, "start_at_cycle", 1)?;
7477 let use_top_cycles =
7478 get_usize_param("goertzel_cycle_composite_wave", params, "use_top_cycles", 2)?;
7479 let bar_to_calculate = get_usize_param(
7480 "goertzel_cycle_composite_wave",
7481 params,
7482 "bar_to_calculate",
7483 1,
7484 )?;
7485 let detrend_mode = get_enum_string_param(
7486 "goertzel_cycle_composite_wave",
7487 params,
7488 "detrend_mode",
7489 "hodrick_prescott_detrending",
7490 )?;
7491 let detrend_mode = GoertzelDetrendMode::parse(detrend_mode).ok_or_else(|| {
7492 IndicatorDispatchError::InvalidParam {
7493 indicator: "goertzel_cycle_composite_wave".to_string(),
7494 key: "detrend_mode".to_string(),
7495 reason: format!("unknown mode: {detrend_mode}"),
7496 }
7497 })?;
7498 let dt_zl_per1 =
7499 get_usize_param("goertzel_cycle_composite_wave", params, "dt_zl_per1", 10)?;
7500 let dt_zl_per2 =
7501 get_usize_param("goertzel_cycle_composite_wave", params, "dt_zl_per2", 40)?;
7502 let dt_hp_per1 =
7503 get_usize_param("goertzel_cycle_composite_wave", params, "dt_hp_per1", 20)?;
7504 let dt_hp_per2 =
7505 get_usize_param("goertzel_cycle_composite_wave", params, "dt_hp_per2", 80)?;
7506 let dt_reg_zl_smooth_per = get_usize_param(
7507 "goertzel_cycle_composite_wave",
7508 params,
7509 "dt_reg_zl_smooth_per",
7510 5,
7511 )?;
7512 let hp_smooth_per =
7513 get_usize_param("goertzel_cycle_composite_wave", params, "hp_smooth_per", 20)?;
7514 let zlma_smooth_per = get_usize_param(
7515 "goertzel_cycle_composite_wave",
7516 params,
7517 "zlma_smooth_per",
7518 10,
7519 )?;
7520 let filter_bartels = get_bool_param(
7521 "goertzel_cycle_composite_wave",
7522 params,
7523 "filter_bartels",
7524 false,
7525 )?;
7526 let bart_no_cycles =
7527 get_usize_param("goertzel_cycle_composite_wave", params, "bart_no_cycles", 5)?;
7528 let bart_smooth_per = get_usize_param(
7529 "goertzel_cycle_composite_wave",
7530 params,
7531 "bart_smooth_per",
7532 2,
7533 )?;
7534 let bart_sig_limit = get_usize_param(
7535 "goertzel_cycle_composite_wave",
7536 params,
7537 "bart_sig_limit",
7538 50,
7539 )?;
7540 let sort_bartels = get_bool_param(
7541 "goertzel_cycle_composite_wave",
7542 params,
7543 "sort_bartels",
7544 false,
7545 )?;
7546 let squared_amp =
7547 get_bool_param("goertzel_cycle_composite_wave", params, "squared_amp", true)?;
7548 let use_cosine =
7549 get_bool_param("goertzel_cycle_composite_wave", params, "use_cosine", true)?;
7550 let subtract_noise = get_bool_param(
7551 "goertzel_cycle_composite_wave",
7552 params,
7553 "subtract_noise",
7554 false,
7555 )?;
7556 let use_cycle_strength = get_bool_param(
7557 "goertzel_cycle_composite_wave",
7558 params,
7559 "use_cycle_strength",
7560 true,
7561 )?;
7562
7563 let input = GoertzelCycleCompositeWaveInput::from_slice(
7564 data,
7565 GoertzelCycleCompositeWaveParams {
7566 max_period: Some(max_period),
7567 start_at_cycle: Some(start_at_cycle),
7568 use_top_cycles: Some(use_top_cycles),
7569 bar_to_calculate: Some(bar_to_calculate),
7570 detrend_mode: Some(detrend_mode),
7571 dt_zl_per1: Some(dt_zl_per1),
7572 dt_zl_per2: Some(dt_zl_per2),
7573 dt_hp_per1: Some(dt_hp_per1),
7574 dt_hp_per2: Some(dt_hp_per2),
7575 dt_reg_zl_smooth_per: Some(dt_reg_zl_smooth_per),
7576 hp_smooth_per: Some(hp_smooth_per),
7577 zlma_smooth_per: Some(zlma_smooth_per),
7578 filter_bartels: Some(filter_bartels),
7579 bart_no_cycles: Some(bart_no_cycles),
7580 bart_smooth_per: Some(bart_smooth_per),
7581 bart_sig_limit: Some(bart_sig_limit),
7582 sort_bartels: Some(sort_bartels),
7583 squared_amp: Some(squared_amp),
7584 use_cosine: Some(use_cosine),
7585 subtract_noise: Some(subtract_noise),
7586 use_cycle_strength: Some(use_cycle_strength),
7587 },
7588 );
7589 goertzel_cycle_composite_wave_into_slice(row, &input, kernel).map_err(|e| {
7590 IndicatorDispatchError::ComputeFailed {
7591 indicator: "goertzel_cycle_composite_wave".to_string(),
7592 details: e.to_string(),
7593 }
7594 })
7595 },
7596 )
7597}
7598
7599fn compute_rolling_skewness_kurtosis_batch(
7600 req: IndicatorBatchRequest<'_>,
7601 output_id: &str,
7602) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7603 let data = extract_slice_input("rolling_skewness_kurtosis", req.data, "close")?;
7604 let kernel = req.kernel.to_non_batch();
7605 collect_f64(
7606 "rolling_skewness_kurtosis",
7607 output_id,
7608 req.combos,
7609 data.len(),
7610 |params| {
7611 let length = get_usize_param("rolling_skewness_kurtosis", params, "length", 50)?;
7612 let smooth_length =
7613 get_usize_param("rolling_skewness_kurtosis", params, "smooth_length", 3)?;
7614 let input = RollingSkewnessKurtosisInput::from_slice(
7615 data,
7616 RollingSkewnessKurtosisParams {
7617 length: Some(length),
7618 smooth_length: Some(smooth_length),
7619 },
7620 );
7621 let out = rolling_skewness_kurtosis_with_kernel(&input, kernel).map_err(|e| {
7622 IndicatorDispatchError::ComputeFailed {
7623 indicator: "rolling_skewness_kurtosis".to_string(),
7624 details: e.to_string(),
7625 }
7626 })?;
7627 if output_id.eq_ignore_ascii_case("skewness") {
7628 return Ok(out.skewness);
7629 }
7630 if output_id.eq_ignore_ascii_case("kurtosis") {
7631 return Ok(out.kurtosis);
7632 }
7633 Err(IndicatorDispatchError::UnknownOutput {
7634 indicator: "rolling_skewness_kurtosis".to_string(),
7635 output: output_id.to_string(),
7636 })
7637 },
7638 )
7639}
7640
7641fn compute_rolling_z_score_trend_batch(
7642 req: IndicatorBatchRequest<'_>,
7643 output_id: &str,
7644) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7645 let data = extract_slice_input("rolling_z_score_trend", req.data, "close")?;
7646 let kernel = req.kernel.to_non_batch();
7647 collect_f64(
7648 "rolling_z_score_trend",
7649 output_id,
7650 req.combos,
7651 data.len(),
7652 |params| {
7653 let lookback_period =
7654 get_usize_param("rolling_z_score_trend", params, "lookback_period", 20)?;
7655 let input = RollingZScoreTrendInput::from_slice(
7656 data,
7657 RollingZScoreTrendParams {
7658 lookback_period: Some(lookback_period),
7659 },
7660 );
7661 let out = rolling_z_score_trend_with_kernel(&input, kernel).map_err(|e| {
7662 IndicatorDispatchError::ComputeFailed {
7663 indicator: "rolling_z_score_trend".to_string(),
7664 details: e.to_string(),
7665 }
7666 })?;
7667 if output_id.eq_ignore_ascii_case("zscore") {
7668 return Ok(out.zscore);
7669 }
7670 if output_id.eq_ignore_ascii_case("momentum") {
7671 return Ok(out.momentum);
7672 }
7673 Err(IndicatorDispatchError::UnknownOutput {
7674 indicator: "rolling_z_score_trend".to_string(),
7675 output: output_id.to_string(),
7676 })
7677 },
7678 )
7679}
7680
7681fn compute_ehlers_data_sampling_relative_strength_indicator_batch(
7682 req: IndicatorBatchRequest<'_>,
7683 output_id: &str,
7684) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7685 let (open, close) = match req.data {
7686 IndicatorDataRef::Candles { candles, .. } => {
7687 (candles.open.as_slice(), candles.close.as_slice())
7688 }
7689 IndicatorDataRef::Ohlc {
7690 open,
7691 high,
7692 low,
7693 close,
7694 } => {
7695 ensure_same_len_4(
7696 "ehlers_data_sampling_relative_strength_indicator",
7697 open.len(),
7698 high.len(),
7699 low.len(),
7700 close.len(),
7701 )?;
7702 (open, close)
7703 }
7704 IndicatorDataRef::Ohlcv {
7705 open,
7706 high,
7707 low,
7708 close,
7709 volume,
7710 } => {
7711 ensure_same_len_5(
7712 "ehlers_data_sampling_relative_strength_indicator",
7713 open.len(),
7714 high.len(),
7715 low.len(),
7716 close.len(),
7717 volume.len(),
7718 )?;
7719 (open, close)
7720 }
7721 _ => {
7722 return Err(IndicatorDispatchError::MissingRequiredInput {
7723 indicator: "ehlers_data_sampling_relative_strength_indicator".to_string(),
7724 input: IndicatorInputKind::Ohlc,
7725 })
7726 }
7727 };
7728 let kernel = req.kernel.to_non_batch();
7729 collect_f64(
7730 "ehlers_data_sampling_relative_strength_indicator",
7731 output_id,
7732 req.combos,
7733 close.len(),
7734 |params| {
7735 let length = get_usize_param(
7736 "ehlers_data_sampling_relative_strength_indicator",
7737 params,
7738 "length",
7739 14,
7740 )?;
7741 let input = EhlersDataSamplingRelativeStrengthIndicatorInput::from_slices(
7742 open,
7743 close,
7744 EhlersDataSamplingRelativeStrengthIndicatorParams {
7745 length: Some(length),
7746 },
7747 );
7748 let out = ehlers_data_sampling_relative_strength_indicator_with_kernel(&input, kernel)
7749 .map_err(|e| IndicatorDispatchError::ComputeFailed {
7750 indicator: "ehlers_data_sampling_relative_strength_indicator".to_string(),
7751 details: e.to_string(),
7752 })?;
7753 if output_id.eq_ignore_ascii_case("ds_rsi")
7754 || output_id.eq_ignore_ascii_case("data_sampling_rsi")
7755 {
7756 return Ok(out.ds_rsi);
7757 }
7758 if output_id.eq_ignore_ascii_case("original_rsi")
7759 || output_id.eq_ignore_ascii_case("orig_rsi")
7760 {
7761 return Ok(out.original_rsi);
7762 }
7763 if output_id.eq_ignore_ascii_case("signal") {
7764 return Ok(out.signal);
7765 }
7766 Err(IndicatorDispatchError::UnknownOutput {
7767 indicator: "ehlers_data_sampling_relative_strength_indicator".to_string(),
7768 output: output_id.to_string(),
7769 })
7770 },
7771 )
7772}
7773
7774fn compute_velocity_acceleration_convergence_divergence_indicator_batch(
7775 req: IndicatorBatchRequest<'_>,
7776 output_id: &str,
7777) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7778 let owned_source;
7779 let data = match req.data {
7780 IndicatorDataRef::Slice { values } => values,
7781 IndicatorDataRef::Candles { candles, source } => {
7782 source_type(candles, source.unwrap_or("hlcc4"))
7783 }
7784 IndicatorDataRef::Ohlc {
7785 open,
7786 high,
7787 low,
7788 close,
7789 } => {
7790 ensure_same_len_4(
7791 "velocity_acceleration_convergence_divergence_indicator",
7792 open.len(),
7793 high.len(),
7794 low.len(),
7795 close.len(),
7796 )?;
7797 owned_source = high
7798 .iter()
7799 .zip(low.iter())
7800 .zip(close.iter())
7801 .map(|((&h, &l), &c)| (h + l + 2.0 * c) * 0.25)
7802 .collect::<Vec<_>>();
7803 owned_source.as_slice()
7804 }
7805 IndicatorDataRef::Ohlcv {
7806 open,
7807 high,
7808 low,
7809 close,
7810 volume,
7811 } => {
7812 ensure_same_len_5(
7813 "velocity_acceleration_convergence_divergence_indicator",
7814 open.len(),
7815 high.len(),
7816 low.len(),
7817 close.len(),
7818 volume.len(),
7819 )?;
7820 owned_source = high
7821 .iter()
7822 .zip(low.iter())
7823 .zip(close.iter())
7824 .map(|((&h, &l), &c)| (h + l + 2.0 * c) * 0.25)
7825 .collect::<Vec<_>>();
7826 owned_source.as_slice()
7827 }
7828 IndicatorDataRef::CloseVolume { close, volume } => {
7829 ensure_same_len_2(
7830 "velocity_acceleration_convergence_divergence_indicator",
7831 close.len(),
7832 volume.len(),
7833 )?;
7834 close
7835 }
7836 IndicatorDataRef::HighLow { .. } => {
7837 return Err(IndicatorDispatchError::MissingRequiredInput {
7838 indicator: "velocity_acceleration_convergence_divergence_indicator".to_string(),
7839 input: IndicatorInputKind::Candles,
7840 });
7841 }
7842 };
7843 let kernel = req.kernel.to_non_batch();
7844 collect_f64(
7845 "velocity_acceleration_convergence_divergence_indicator",
7846 output_id,
7847 req.combos,
7848 data.len(),
7849 |params| {
7850 let length = get_usize_param(
7851 "velocity_acceleration_convergence_divergence_indicator",
7852 params,
7853 "length",
7854 21,
7855 )?;
7856 let smooth_length = get_usize_param(
7857 "velocity_acceleration_convergence_divergence_indicator",
7858 params,
7859 "smooth_length",
7860 5,
7861 )?;
7862 let input = VelocityAccelerationConvergenceDivergenceIndicatorInput::from_slice(
7863 data,
7864 VelocityAccelerationConvergenceDivergenceIndicatorParams {
7865 length: Some(length),
7866 smooth_length: Some(smooth_length),
7867 },
7868 );
7869 let out =
7870 velocity_acceleration_convergence_divergence_indicator_with_kernel(&input, kernel)
7871 .map_err(|e| IndicatorDispatchError::ComputeFailed {
7872 indicator: "velocity_acceleration_convergence_divergence_indicator"
7873 .to_string(),
7874 details: e.to_string(),
7875 })?;
7876 if output_id.eq_ignore_ascii_case("vacd") || output_id.eq_ignore_ascii_case("value") {
7877 return Ok(out.vacd);
7878 }
7879 if output_id.eq_ignore_ascii_case("signal") {
7880 return Ok(out.signal);
7881 }
7882 Err(IndicatorDispatchError::UnknownOutput {
7883 indicator: "velocity_acceleration_convergence_divergence_indicator".to_string(),
7884 output: output_id.to_string(),
7885 })
7886 },
7887 )
7888}
7889
7890fn compute_trend_direction_force_index_batch(
7891 req: IndicatorBatchRequest<'_>,
7892 output_id: &str,
7893) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7894 expect_value_output("trend_direction_force_index", output_id)?;
7895 let data = extract_slice_input("trend_direction_force_index", req.data, "close")?;
7896 let kernel = req.kernel.to_non_batch();
7897 collect_f64_into_rows(
7898 "trend_direction_force_index",
7899 output_id,
7900 req.combos,
7901 data.len(),
7902 |params, row| {
7903 let length = get_usize_param("trend_direction_force_index", params, "length", 10)?;
7904 let input = TrendDirectionForceIndexInput::from_slice(
7905 data,
7906 TrendDirectionForceIndexParams {
7907 length: Some(length),
7908 },
7909 );
7910 trend_direction_force_index_into_slice(row, &input, kernel).map_err(|e| {
7911 IndicatorDispatchError::ComputeFailed {
7912 indicator: "trend_direction_force_index".to_string(),
7913 details: e.to_string(),
7914 }
7915 })
7916 },
7917 )
7918}
7919
7920fn compute_yang_zhang_volatility_batch(
7921 req: IndicatorBatchRequest<'_>,
7922 output_id: &str,
7923) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7924 let (open, high, low, close) = extract_ohlc_full_input("yang_zhang_volatility", req.data)?;
7925 let kernel = req.kernel.to_non_batch();
7926 collect_f64(
7927 "yang_zhang_volatility",
7928 output_id,
7929 req.combos,
7930 close.len(),
7931 |params| {
7932 let lookback = get_usize_param("yang_zhang_volatility", params, "lookback", 14)?;
7933 let k_override = get_bool_param("yang_zhang_volatility", params, "k_override", false)?;
7934 let k = get_f64_param("yang_zhang_volatility", params, "k", 0.34)?;
7935 let input = YangZhangVolatilityInput::from_slices(
7936 open,
7937 high,
7938 low,
7939 close,
7940 YangZhangVolatilityParams {
7941 lookback: Some(lookback),
7942 k_override: Some(k_override),
7943 k: Some(k),
7944 },
7945 );
7946 let out = yang_zhang_volatility_with_kernel(&input, kernel).map_err(|e| {
7947 IndicatorDispatchError::ComputeFailed {
7948 indicator: "yang_zhang_volatility".to_string(),
7949 details: e.to_string(),
7950 }
7951 })?;
7952 if output_id.eq_ignore_ascii_case("yz") || output_id.eq_ignore_ascii_case("value") {
7953 return Ok(out.yz);
7954 }
7955 if output_id.eq_ignore_ascii_case("rs") {
7956 return Ok(out.rs);
7957 }
7958 Err(IndicatorDispatchError::UnknownOutput {
7959 indicator: "yang_zhang_volatility".to_string(),
7960 output: output_id.to_string(),
7961 })
7962 },
7963 )
7964}
7965
7966fn compute_garman_klass_volatility_batch(
7967 req: IndicatorBatchRequest<'_>,
7968 output_id: &str,
7969) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
7970 let (open, high, low, close) = extract_ohlc_full_input("garman_klass_volatility", req.data)?;
7971 let kernel = req.kernel.to_non_batch();
7972 collect_f64(
7973 "garman_klass_volatility",
7974 output_id,
7975 req.combos,
7976 close.len(),
7977 |params| {
7978 let lookback = get_usize_param("garman_klass_volatility", params, "lookback", 14)?;
7979 let input = GarmanKlassVolatilityInput::from_slices(
7980 open,
7981 high,
7982 low,
7983 close,
7984 GarmanKlassVolatilityParams {
7985 lookback: Some(lookback),
7986 },
7987 );
7988 let out = garman_klass_volatility_with_kernel(&input, kernel).map_err(|e| {
7989 IndicatorDispatchError::ComputeFailed {
7990 indicator: "garman_klass_volatility".to_string(),
7991 details: e.to_string(),
7992 }
7993 })?;
7994 if output_id.eq_ignore_ascii_case("value") {
7995 return Ok(out.values);
7996 }
7997 Err(IndicatorDispatchError::UnknownOutput {
7998 indicator: "garman_klass_volatility".to_string(),
7999 output: output_id.to_string(),
8000 })
8001 },
8002 )
8003}
8004
8005fn compute_atr_percentile_batch(
8006 req: IndicatorBatchRequest<'_>,
8007 output_id: &str,
8008) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8009 expect_value_output("atr_percentile", output_id)?;
8010 let (high, low, close) = extract_ohlc_input("atr_percentile", req.data)?;
8011 let kernel = req.kernel.to_non_batch();
8012 collect_f64(
8013 "atr_percentile",
8014 output_id,
8015 req.combos,
8016 close.len(),
8017 |params| {
8018 let atr_length = get_usize_param("atr_percentile", params, "atr_length", 10)?;
8019 let percentile_length =
8020 get_usize_param("atr_percentile", params, "percentile_length", 50)?;
8021 let input = AtrPercentileInput::from_slices(
8022 high,
8023 low,
8024 close,
8025 AtrPercentileParams {
8026 atr_length: Some(atr_length),
8027 percentile_length: Some(percentile_length),
8028 },
8029 );
8030 let out = atr_percentile_with_kernel(&input, kernel).map_err(|e| {
8031 IndicatorDispatchError::ComputeFailed {
8032 indicator: "atr_percentile".to_string(),
8033 details: e.to_string(),
8034 }
8035 })?;
8036 Ok(out.values)
8037 },
8038 )
8039}
8040
8041fn compute_bull_power_vs_bear_power_batch(
8042 req: IndicatorBatchRequest<'_>,
8043 output_id: &str,
8044) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8045 expect_value_output("bull_power_vs_bear_power", output_id)?;
8046 let (open, high, low, close) = extract_ohlc_full_input("bull_power_vs_bear_power", req.data)?;
8047 let kernel = req.kernel.to_non_batch();
8048 collect_f64(
8049 "bull_power_vs_bear_power",
8050 output_id,
8051 req.combos,
8052 close.len(),
8053 |params| {
8054 let period = get_usize_param("bull_power_vs_bear_power", params, "period", 5)?;
8055 let input = BullPowerVsBearPowerInput::from_slices(
8056 open,
8057 high,
8058 low,
8059 close,
8060 BullPowerVsBearPowerParams {
8061 period: Some(period),
8062 },
8063 );
8064 let out = bull_power_vs_bear_power_with_kernel(&input, kernel).map_err(|e| {
8065 IndicatorDispatchError::ComputeFailed {
8066 indicator: "bull_power_vs_bear_power".to_string(),
8067 details: e.to_string(),
8068 }
8069 })?;
8070 Ok(out.values)
8071 },
8072 )
8073}
8074
8075fn compute_advance_decline_line_batch(
8076 req: IndicatorBatchRequest<'_>,
8077 output_id: &str,
8078) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8079 expect_value_output("advance_decline_line", output_id)?;
8080 let data = extract_slice_input("advance_decline_line", req.data, "close")?;
8081 let kernel = req.kernel.to_non_batch();
8082 collect_f64(
8083 "advance_decline_line",
8084 output_id,
8085 req.combos,
8086 data.len(),
8087 |_params| {
8088 let input = AdvanceDeclineLineInput::from_slice(data, AdvanceDeclineLineParams);
8089 let out = advance_decline_line_with_kernel(&input, kernel).map_err(|e| {
8090 IndicatorDispatchError::ComputeFailed {
8091 indicator: "advance_decline_line".to_string(),
8092 details: e.to_string(),
8093 }
8094 })?;
8095 Ok(out.values)
8096 },
8097 )
8098}
8099
8100fn compute_didi_index_batch(
8101 req: IndicatorBatchRequest<'_>,
8102 output_id: &str,
8103) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8104 let data = extract_slice_input("didi_index", req.data, "close")?;
8105 let kernel = req.kernel.to_non_batch();
8106 collect_f64("didi_index", output_id, req.combos, data.len(), |params| {
8107 let short_length = get_usize_param("didi_index", params, "short_length", 3)?;
8108 let medium_length = get_usize_param("didi_index", params, "medium_length", 8)?;
8109 let long_length = get_usize_param("didi_index", params, "long_length", 20)?;
8110 let input = DidiIndexInput::from_slice(
8111 data,
8112 DidiIndexParams {
8113 short_length: Some(short_length),
8114 medium_length: Some(medium_length),
8115 long_length: Some(long_length),
8116 },
8117 );
8118 let out = didi_index_with_kernel(&input, kernel).map_err(|e| {
8119 IndicatorDispatchError::ComputeFailed {
8120 indicator: "didi_index".to_string(),
8121 details: e.to_string(),
8122 }
8123 })?;
8124 if output_id.eq_ignore_ascii_case("short") || output_id.eq_ignore_ascii_case("value") {
8125 return Ok(out.short);
8126 }
8127 if output_id.eq_ignore_ascii_case("long") {
8128 return Ok(out.long);
8129 }
8130 if output_id.eq_ignore_ascii_case("crossover") {
8131 return Ok(out.crossover);
8132 }
8133 if output_id.eq_ignore_ascii_case("crossunder") {
8134 return Ok(out.crossunder);
8135 }
8136 Err(IndicatorDispatchError::UnknownOutput {
8137 indicator: "didi_index".to_string(),
8138 output: output_id.to_string(),
8139 })
8140 })
8141}
8142
8143fn compute_absolute_strength_index_oscillator_batch(
8144 req: IndicatorBatchRequest<'_>,
8145 output_id: &str,
8146) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8147 let data = extract_slice_input("absolute_strength_index_oscillator", req.data, "close")?;
8148 let kernel = req.kernel.to_non_batch();
8149 collect_f64(
8150 "absolute_strength_index_oscillator",
8151 output_id,
8152 req.combos,
8153 data.len(),
8154 |params| {
8155 let ema_length = get_usize_param(
8156 "absolute_strength_index_oscillator",
8157 params,
8158 "ema_length",
8159 21,
8160 )?;
8161 let signal_length = get_usize_param(
8162 "absolute_strength_index_oscillator",
8163 params,
8164 "signal_length",
8165 34,
8166 )?;
8167 let input = AbsoluteStrengthIndexOscillatorInput::from_slice(
8168 data,
8169 AbsoluteStrengthIndexOscillatorParams {
8170 ema_length: Some(ema_length),
8171 signal_length: Some(signal_length),
8172 },
8173 );
8174 let out =
8175 absolute_strength_index_oscillator_with_kernel(&input, kernel).map_err(|e| {
8176 IndicatorDispatchError::ComputeFailed {
8177 indicator: "absolute_strength_index_oscillator".to_string(),
8178 details: e.to_string(),
8179 }
8180 })?;
8181 if output_id.eq_ignore_ascii_case("oscillator")
8182 || output_id.eq_ignore_ascii_case("indicator")
8183 || output_id.eq_ignore_ascii_case("value")
8184 {
8185 return Ok(out.oscillator);
8186 }
8187 if output_id.eq_ignore_ascii_case("signal") {
8188 return Ok(out.signal);
8189 }
8190 if output_id.eq_ignore_ascii_case("histogram") || output_id.eq_ignore_ascii_case("hist")
8191 {
8192 return Ok(out.histogram);
8193 }
8194 Err(IndicatorDispatchError::UnknownOutput {
8195 indicator: "absolute_strength_index_oscillator".to_string(),
8196 output: output_id.to_string(),
8197 })
8198 },
8199 )
8200}
8201
8202fn compute_adaptive_bandpass_trigger_oscillator_batch(
8203 req: IndicatorBatchRequest<'_>,
8204 output_id: &str,
8205) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8206 let data = extract_slice_input("adaptive_bandpass_trigger_oscillator", req.data, "close")?;
8207 let kernel = req.kernel.to_non_batch();
8208 collect_f64(
8209 "adaptive_bandpass_trigger_oscillator",
8210 output_id,
8211 req.combos,
8212 data.len(),
8213 |params| {
8214 let delta =
8215 get_f64_param("adaptive_bandpass_trigger_oscillator", params, "delta", 0.1)?;
8216 let alpha = get_f64_param(
8217 "adaptive_bandpass_trigger_oscillator",
8218 params,
8219 "alpha",
8220 0.07,
8221 )?;
8222 let input = AdaptiveBandpassTriggerOscillatorInput::from_slice(
8223 data,
8224 AdaptiveBandpassTriggerOscillatorParams {
8225 delta: Some(delta),
8226 alpha: Some(alpha),
8227 },
8228 );
8229 let out =
8230 adaptive_bandpass_trigger_oscillator_with_kernel(&input, kernel).map_err(|e| {
8231 IndicatorDispatchError::ComputeFailed {
8232 indicator: "adaptive_bandpass_trigger_oscillator".to_string(),
8233 details: e.to_string(),
8234 }
8235 })?;
8236 match output_id {
8237 "in_phase" => Ok(out.in_phase),
8238 "lead" => Ok(out.lead),
8239 _ => Err(IndicatorDispatchError::UnknownOutput {
8240 indicator: "adaptive_bandpass_trigger_oscillator".to_string(),
8241 output: output_id.to_string(),
8242 }),
8243 }
8244 },
8245 )
8246}
8247
8248fn compute_premier_rsi_oscillator_batch(
8249 req: IndicatorBatchRequest<'_>,
8250 output_id: &str,
8251) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8252 expect_value_output("premier_rsi_oscillator", output_id)?;
8253 let data = extract_slice_input("premier_rsi_oscillator", req.data, "close")?;
8254 let kernel = req.kernel.to_non_batch();
8255 collect_f64(
8256 "premier_rsi_oscillator",
8257 output_id,
8258 req.combos,
8259 data.len(),
8260 |params| {
8261 let rsi_length = get_usize_param("premier_rsi_oscillator", params, "rsi_length", 14)?;
8262 let stoch_length =
8263 get_usize_param("premier_rsi_oscillator", params, "stoch_length", 8)?;
8264 let smooth_length =
8265 get_usize_param("premier_rsi_oscillator", params, "smooth_length", 25)?;
8266 let input = PremierRsiOscillatorInput::from_slice(
8267 data,
8268 PremierRsiOscillatorParams {
8269 rsi_length: Some(rsi_length),
8270 stoch_length: Some(stoch_length),
8271 smooth_length: Some(smooth_length),
8272 },
8273 );
8274 let out = premier_rsi_oscillator_with_kernel(&input, kernel).map_err(|e| {
8275 IndicatorDispatchError::ComputeFailed {
8276 indicator: "premier_rsi_oscillator".to_string(),
8277 details: e.to_string(),
8278 }
8279 })?;
8280 Ok(out.values)
8281 },
8282 )
8283}
8284
8285fn compute_multi_length_stochastic_average_batch(
8286 req: IndicatorBatchRequest<'_>,
8287 output_id: &str,
8288) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8289 expect_value_output("multi_length_stochastic_average", output_id)?;
8290 let data_len = match req.data {
8291 IndicatorDataRef::Slice { values } => values.len(),
8292 IndicatorDataRef::Candles { candles, source } => {
8293 source_type(candles, source.unwrap_or("close")).len()
8294 }
8295 _ => {
8296 return Err(IndicatorDispatchError::MissingRequiredInput {
8297 indicator: "multi_length_stochastic_average".to_string(),
8298 input: IndicatorInputKind::Candles,
8299 });
8300 }
8301 };
8302 let kernel = req.kernel.to_non_batch();
8303 collect_f64(
8304 "multi_length_stochastic_average",
8305 output_id,
8306 req.combos,
8307 data_len,
8308 |params| {
8309 let source =
8310 get_enum_param("multi_length_stochastic_average", params, "source", "close")?;
8311 let length = get_usize_param("multi_length_stochastic_average", params, "length", 14)?;
8312 let presmooth =
8313 get_usize_param("multi_length_stochastic_average", params, "presmooth", 10)?;
8314 let premethod = get_enum_param(
8315 "multi_length_stochastic_average",
8316 params,
8317 "premethod",
8318 "sma",
8319 )?;
8320 let postsmooth =
8321 get_usize_param("multi_length_stochastic_average", params, "postsmooth", 10)?;
8322 let postmethod = get_enum_param(
8323 "multi_length_stochastic_average",
8324 params,
8325 "postmethod",
8326 "sma",
8327 )?;
8328 let data = match req.data {
8329 IndicatorDataRef::Slice { values } => values,
8330 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
8331 _ => unreachable!(),
8332 };
8333 let input = MultiLengthStochasticAverageInput::from_slice(
8334 data,
8335 MultiLengthStochasticAverageParams {
8336 length: Some(length),
8337 presmooth: Some(presmooth),
8338 premethod: Some(premethod),
8339 postsmooth: Some(postsmooth),
8340 postmethod: Some(postmethod),
8341 },
8342 );
8343 let out = multi_length_stochastic_average_with_kernel(&input, kernel).map_err(|e| {
8344 IndicatorDispatchError::ComputeFailed {
8345 indicator: "multi_length_stochastic_average".to_string(),
8346 details: e.to_string(),
8347 }
8348 })?;
8349 Ok(out.values)
8350 },
8351 )
8352}
8353
8354fn compute_hull_butterfly_oscillator_batch(
8355 req: IndicatorBatchRequest<'_>,
8356 output_id: &str,
8357) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8358 let data_len = match req.data {
8359 IndicatorDataRef::Slice { values } => values.len(),
8360 IndicatorDataRef::Candles { candles, source } => {
8361 source_type(candles, source.unwrap_or("close")).len()
8362 }
8363 _ => {
8364 return Err(IndicatorDispatchError::MissingRequiredInput {
8365 indicator: "hull_butterfly_oscillator".to_string(),
8366 input: IndicatorInputKind::Candles,
8367 });
8368 }
8369 };
8370 let kernel = req.kernel.to_non_batch();
8371 collect_f64(
8372 "hull_butterfly_oscillator",
8373 output_id,
8374 req.combos,
8375 data_len,
8376 |params| {
8377 let source = get_enum_param("hull_butterfly_oscillator", params, "source", "close")?;
8378 let length = get_usize_param("hull_butterfly_oscillator", params, "length", 14)?;
8379 let mult = get_f64_param("hull_butterfly_oscillator", params, "mult", 2.0)?;
8380 let data = match req.data {
8381 IndicatorDataRef::Slice { values } => values,
8382 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
8383 _ => unreachable!(),
8384 };
8385 let input = HullButterflyOscillatorInput::from_slice(
8386 data,
8387 HullButterflyOscillatorParams {
8388 length: Some(length),
8389 mult: Some(mult),
8390 },
8391 );
8392 let out = hull_butterfly_oscillator_with_kernel(&input, kernel).map_err(|e| {
8393 IndicatorDispatchError::ComputeFailed {
8394 indicator: "hull_butterfly_oscillator".to_string(),
8395 details: e.to_string(),
8396 }
8397 })?;
8398 match output_id {
8399 "oscillator" => Ok(out.oscillator),
8400 "cumulative_mean" => Ok(out.cumulative_mean),
8401 "signal" => Ok(out.signal),
8402 _ => Err(IndicatorDispatchError::UnknownOutput {
8403 indicator: "hull_butterfly_oscillator".to_string(),
8404 output: output_id.to_string(),
8405 }),
8406 }
8407 },
8408 )
8409}
8410
8411fn compute_fibonacci_trailing_stop_batch(
8412 req: IndicatorBatchRequest<'_>,
8413 output_id: &str,
8414) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8415 let (high, low, close) = extract_ohlc_input("fibonacci_trailing_stop", req.data)?;
8416 let kernel = req.kernel.to_non_batch();
8417 collect_f64(
8418 "fibonacci_trailing_stop",
8419 output_id,
8420 req.combos,
8421 close.len(),
8422 |params| {
8423 let left_bars = get_usize_param("fibonacci_trailing_stop", params, "left_bars", 20)?;
8424 let right_bars = get_usize_param("fibonacci_trailing_stop", params, "right_bars", 1)?;
8425 let level = get_f64_param("fibonacci_trailing_stop", params, "level", -0.382)?;
8426 let trigger = get_enum_param("fibonacci_trailing_stop", params, "trigger", "close")?;
8427 let input = FibonacciTrailingStopInput::from_slices(
8428 high,
8429 low,
8430 close,
8431 FibonacciTrailingStopParams {
8432 left_bars: Some(left_bars),
8433 right_bars: Some(right_bars),
8434 level: Some(level),
8435 trigger: Some(trigger),
8436 },
8437 );
8438 let out = fibonacci_trailing_stop_with_kernel(&input, kernel).map_err(|e| {
8439 IndicatorDispatchError::ComputeFailed {
8440 indicator: "fibonacci_trailing_stop".to_string(),
8441 details: e.to_string(),
8442 }
8443 })?;
8444 if output_id.eq_ignore_ascii_case("trailing_stop")
8445 || output_id.eq_ignore_ascii_case("value")
8446 {
8447 return Ok(out.trailing_stop);
8448 }
8449 if output_id.eq_ignore_ascii_case("long_stop") {
8450 return Ok(out.long_stop);
8451 }
8452 if output_id.eq_ignore_ascii_case("short_stop") {
8453 return Ok(out.short_stop);
8454 }
8455 if output_id.eq_ignore_ascii_case("direction") {
8456 return Ok(out.direction);
8457 }
8458 Err(IndicatorDispatchError::UnknownOutput {
8459 indicator: "fibonacci_trailing_stop".to_string(),
8460 output: output_id.to_string(),
8461 })
8462 },
8463 )
8464}
8465
8466fn compute_fibonacci_entry_bands_batch(
8467 req: IndicatorBatchRequest<'_>,
8468 output_id: &str,
8469) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8470 let (open, high, low, close) = extract_ohlc_full_input("fibonacci_entry_bands", req.data)?;
8471 let kernel = req.kernel.to_non_batch();
8472 collect_f64(
8473 "fibonacci_entry_bands",
8474 output_id,
8475 req.combos,
8476 close.len(),
8477 |params| {
8478 let source = get_enum_param("fibonacci_entry_bands", params, "source", "hlc3")?;
8479 let length = get_usize_param("fibonacci_entry_bands", params, "length", 21)?;
8480 let atr_length = get_usize_param("fibonacci_entry_bands", params, "atr_length", 14)?;
8481 let use_atr = get_bool_param("fibonacci_entry_bands", params, "use_atr", true)?;
8482 let tp_aggressiveness =
8483 get_enum_param("fibonacci_entry_bands", params, "tp_aggressiveness", "low")?;
8484 let input = FibonacciEntryBandsInput::from_slices(
8485 open,
8486 high,
8487 low,
8488 close,
8489 FibonacciEntryBandsParams {
8490 source: Some(source),
8491 length: Some(length),
8492 atr_length: Some(atr_length),
8493 use_atr: Some(use_atr),
8494 tp_aggressiveness: Some(tp_aggressiveness),
8495 },
8496 );
8497 let out = fibonacci_entry_bands_with_kernel(&input, kernel).map_err(|e| {
8498 IndicatorDispatchError::ComputeFailed {
8499 indicator: "fibonacci_entry_bands".to_string(),
8500 details: e.to_string(),
8501 }
8502 })?;
8503 if output_id.eq_ignore_ascii_case("middle") || output_id.eq_ignore_ascii_case("basis") {
8504 return Ok(out.basis);
8505 }
8506 if output_id.eq_ignore_ascii_case("trend") {
8507 return Ok(out.trend);
8508 }
8509 if output_id.eq_ignore_ascii_case("upper_0618") {
8510 return Ok(out.upper_0618);
8511 }
8512 if output_id.eq_ignore_ascii_case("upper_1000") {
8513 return Ok(out.upper_1000);
8514 }
8515 if output_id.eq_ignore_ascii_case("upper_1618") {
8516 return Ok(out.upper_1618);
8517 }
8518 if output_id.eq_ignore_ascii_case("upper_2618") {
8519 return Ok(out.upper_2618);
8520 }
8521 if output_id.eq_ignore_ascii_case("lower_0618") {
8522 return Ok(out.lower_0618);
8523 }
8524 if output_id.eq_ignore_ascii_case("lower_1000") {
8525 return Ok(out.lower_1000);
8526 }
8527 if output_id.eq_ignore_ascii_case("lower_1618") {
8528 return Ok(out.lower_1618);
8529 }
8530 if output_id.eq_ignore_ascii_case("lower_2618") {
8531 return Ok(out.lower_2618);
8532 }
8533 if output_id.eq_ignore_ascii_case("tp_long_band") {
8534 return Ok(out.tp_long_band);
8535 }
8536 if output_id.eq_ignore_ascii_case("tp_short_band") {
8537 return Ok(out.tp_short_band);
8538 }
8539 if output_id.eq_ignore_ascii_case("go_long")
8540 || output_id.eq_ignore_ascii_case("long_entry")
8541 {
8542 return Ok(out.long_entry);
8543 }
8544 if output_id.eq_ignore_ascii_case("go_short")
8545 || output_id.eq_ignore_ascii_case("short_entry")
8546 {
8547 return Ok(out.short_entry);
8548 }
8549 if output_id.eq_ignore_ascii_case("rejection_long") {
8550 return Ok(out.rejection_long);
8551 }
8552 if output_id.eq_ignore_ascii_case("rejection_short") {
8553 return Ok(out.rejection_short);
8554 }
8555 if output_id.eq_ignore_ascii_case("long_bounce") {
8556 return Ok(out.long_bounce);
8557 }
8558 if output_id.eq_ignore_ascii_case("short_bounce") {
8559 return Ok(out.short_bounce);
8560 }
8561 Err(IndicatorDispatchError::UnknownOutput {
8562 indicator: "fibonacci_entry_bands".to_string(),
8563 output: output_id.to_string(),
8564 })
8565 },
8566 )
8567}
8568
8569fn compute_volume_energy_reservoirs_batch(
8570 req: IndicatorBatchRequest<'_>,
8571 output_id: &str,
8572) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8573 let (_, high, low, close, volume) =
8574 extract_ohlcv_full_input("volume_energy_reservoirs", req.data)?;
8575 let kernel = req.kernel.to_non_batch();
8576 collect_f64(
8577 "volume_energy_reservoirs",
8578 output_id,
8579 req.combos,
8580 close.len(),
8581 |params| {
8582 let length = get_usize_param("volume_energy_reservoirs", params, "length", 20)?;
8583 let sensitivity =
8584 get_f64_param("volume_energy_reservoirs", params, "sensitivity", 1.5)?;
8585 let input = VolumeEnergyReservoirsInput::from_slices(
8586 high,
8587 low,
8588 close,
8589 volume,
8590 VolumeEnergyReservoirsParams {
8591 length: Some(length),
8592 sensitivity: Some(sensitivity),
8593 },
8594 );
8595 let out = volume_energy_reservoirs_with_kernel(&input, kernel).map_err(|e| {
8596 IndicatorDispatchError::ComputeFailed {
8597 indicator: "volume_energy_reservoirs".to_string(),
8598 details: e.to_string(),
8599 }
8600 })?;
8601 match output_id {
8602 "momentum" | "value" => Ok(out.momentum),
8603 "reservoir" => Ok(out.reservoir),
8604 "squeeze_active" => Ok(out.squeeze_active),
8605 "squeeze_start" => Ok(out.squeeze_start),
8606 "range_high" => Ok(out.range_high),
8607 "range_low" => Ok(out.range_low),
8608 _ => Err(IndicatorDispatchError::UnknownOutput {
8609 indicator: "volume_energy_reservoirs".to_string(),
8610 output: output_id.to_string(),
8611 }),
8612 }
8613 },
8614 )
8615}
8616
8617fn compute_neighboring_trailing_stop_batch(
8618 req: IndicatorBatchRequest<'_>,
8619 output_id: &str,
8620) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8621 let (high, low, close) = extract_ohlc_input("neighboring_trailing_stop", req.data)?;
8622 let kernel = req.kernel.to_non_batch();
8623 collect_f64(
8624 "neighboring_trailing_stop",
8625 output_id,
8626 req.combos,
8627 close.len(),
8628 |params| {
8629 let buffer_size =
8630 get_usize_param("neighboring_trailing_stop", params, "buffer_size", 200)?;
8631 let k = get_usize_param("neighboring_trailing_stop", params, "k", 50)?;
8632 let percentile =
8633 get_f64_param("neighboring_trailing_stop", params, "percentile", 90.0)?;
8634 let smooth = get_usize_param("neighboring_trailing_stop", params, "smooth", 5)?;
8635 let input = NeighboringTrailingStopInput::from_slices(
8636 high,
8637 low,
8638 close,
8639 NeighboringTrailingStopParams {
8640 buffer_size: Some(buffer_size),
8641 k: Some(k),
8642 percentile: Some(percentile),
8643 smooth: Some(smooth),
8644 },
8645 );
8646 let out = neighboring_trailing_stop_with_kernel(&input, kernel).map_err(|e| {
8647 IndicatorDispatchError::ComputeFailed {
8648 indicator: "neighboring_trailing_stop".to_string(),
8649 details: e.to_string(),
8650 }
8651 })?;
8652 match output_id {
8653 "trailing_stop" | "value" => Ok(out.trailing_stop),
8654 "bullish_band" => Ok(out.bullish_band),
8655 "bearish_band" => Ok(out.bearish_band),
8656 "direction" => Ok(out.direction),
8657 "discovery_bull" => Ok(out.discovery_bull),
8658 "discovery_bear" => Ok(out.discovery_bear),
8659 _ => Err(IndicatorDispatchError::UnknownOutput {
8660 indicator: "neighboring_trailing_stop".to_string(),
8661 output: output_id.to_string(),
8662 }),
8663 }
8664 },
8665 )
8666}
8667
8668fn compute_grover_llorens_cycle_oscillator_batch(
8669 req: IndicatorBatchRequest<'_>,
8670 output_id: &str,
8671) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8672 expect_value_output("grover_llorens_cycle_oscillator", output_id)?;
8673 let (open, high, low, close) =
8674 extract_ohlc_full_input("grover_llorens_cycle_oscillator", req.data)?;
8675 let kernel = req.kernel.to_non_batch();
8676 collect_f64(
8677 "grover_llorens_cycle_oscillator",
8678 output_id,
8679 req.combos,
8680 close.len(),
8681 |params| {
8682 let length = get_usize_param("grover_llorens_cycle_oscillator", params, "length", 100)?;
8683 let mult = get_f64_param("grover_llorens_cycle_oscillator", params, "mult", 10.0)?;
8684 let source = match find_param(params, "source") {
8685 Some(ParamValue::EnumString(v)) => (*v).to_string(),
8686 Some(_) => {
8687 return Err(IndicatorDispatchError::InvalidParam {
8688 indicator: "grover_llorens_cycle_oscillator".to_string(),
8689 key: "source".to_string(),
8690 reason: "expected string".to_string(),
8691 });
8692 }
8693 None => "close".to_string(),
8694 };
8695 let smooth = get_bool_param("grover_llorens_cycle_oscillator", params, "smooth", true)?;
8696 let rsi_period =
8697 get_usize_param("grover_llorens_cycle_oscillator", params, "rsi_period", 20)?;
8698 let input = GroverLlorensCycleOscillatorInput::from_slices(
8699 open,
8700 high,
8701 low,
8702 close,
8703 GroverLlorensCycleOscillatorParams {
8704 length: Some(length),
8705 mult: Some(mult),
8706 source: Some(source),
8707 smooth: Some(smooth),
8708 rsi_period: Some(rsi_period),
8709 },
8710 );
8711 let out = grover_llorens_cycle_oscillator_with_kernel(&input, kernel).map_err(|e| {
8712 IndicatorDispatchError::ComputeFailed {
8713 indicator: "grover_llorens_cycle_oscillator".to_string(),
8714 details: e.to_string(),
8715 }
8716 })?;
8717 Ok(out.values)
8718 },
8719 )
8720}
8721
8722fn compute_ehlers_autocorrelation_periodogram_batch(
8723 req: IndicatorBatchRequest<'_>,
8724 output_id: &str,
8725) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8726 let data = extract_slice_input("ehlers_autocorrelation_periodogram", req.data, "close")?;
8727 let kernel = req.kernel.to_non_batch();
8728 collect_f64(
8729 "ehlers_autocorrelation_periodogram",
8730 output_id,
8731 req.combos,
8732 data.len(),
8733 |params| {
8734 let min_period = get_usize_param(
8735 "ehlers_autocorrelation_periodogram",
8736 params,
8737 "min_period",
8738 8,
8739 )?;
8740 let max_period = get_usize_param(
8741 "ehlers_autocorrelation_periodogram",
8742 params,
8743 "max_period",
8744 48,
8745 )?;
8746 let avg_length = get_usize_param(
8747 "ehlers_autocorrelation_periodogram",
8748 params,
8749 "avg_length",
8750 3,
8751 )?;
8752 let enhance = get_bool_param(
8753 "ehlers_autocorrelation_periodogram",
8754 params,
8755 "enhance",
8756 true,
8757 )?;
8758 let input = EhlersAutocorrelationPeriodogramInput::from_slice(
8759 data,
8760 EhlersAutocorrelationPeriodogramParams {
8761 min_period: Some(min_period),
8762 max_period: Some(max_period),
8763 avg_length: Some(avg_length),
8764 enhance: Some(enhance),
8765 },
8766 );
8767 let out =
8768 ehlers_autocorrelation_periodogram_with_kernel(&input, kernel).map_err(|e| {
8769 IndicatorDispatchError::ComputeFailed {
8770 indicator: "ehlers_autocorrelation_periodogram".to_string(),
8771 details: e.to_string(),
8772 }
8773 })?;
8774 if output_id.eq_ignore_ascii_case("dominant_cycle")
8775 || output_id.eq_ignore_ascii_case("value")
8776 {
8777 return Ok(out.dominant_cycle);
8778 }
8779 if output_id.eq_ignore_ascii_case("normalized_power") {
8780 return Ok(out.normalized_power);
8781 }
8782 Err(IndicatorDispatchError::UnknownOutput {
8783 indicator: "ehlers_autocorrelation_periodogram".to_string(),
8784 output: output_id.to_string(),
8785 })
8786 },
8787 )
8788}
8789
8790fn compute_ehlers_linear_extrapolation_predictor_batch(
8791 req: IndicatorBatchRequest<'_>,
8792 output_id: &str,
8793) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8794 let data = extract_slice_input("ehlers_linear_extrapolation_predictor", req.data, "close")?;
8795 let kernel = req.kernel.to_non_batch();
8796 collect_f64(
8797 "ehlers_linear_extrapolation_predictor",
8798 output_id,
8799 req.combos,
8800 data.len(),
8801 |params| {
8802 let high_pass_length = get_usize_param(
8803 "ehlers_linear_extrapolation_predictor",
8804 params,
8805 "high_pass_length",
8806 125,
8807 )?;
8808 let low_pass_length = get_usize_param(
8809 "ehlers_linear_extrapolation_predictor",
8810 params,
8811 "low_pass_length",
8812 12,
8813 )?;
8814 let gain = get_f64_param("ehlers_linear_extrapolation_predictor", params, "gain", 0.7)?;
8815 let bars_forward = get_usize_param(
8816 "ehlers_linear_extrapolation_predictor",
8817 params,
8818 "bars_forward",
8819 5,
8820 )?;
8821 let signal_mode = get_enum_param(
8822 "ehlers_linear_extrapolation_predictor",
8823 params,
8824 "signal_mode",
8825 "predict_filter_crosses",
8826 )?;
8827 let input = EhlersLinearExtrapolationPredictorInput::from_slice(
8828 data,
8829 EhlersLinearExtrapolationPredictorParams {
8830 high_pass_length: Some(high_pass_length),
8831 low_pass_length: Some(low_pass_length),
8832 gain: Some(gain),
8833 bars_forward: Some(bars_forward),
8834 signal_mode: Some(signal_mode),
8835 },
8836 );
8837 let out =
8838 ehlers_linear_extrapolation_predictor_with_kernel(&input, kernel).map_err(|e| {
8839 IndicatorDispatchError::ComputeFailed {
8840 indicator: "ehlers_linear_extrapolation_predictor".to_string(),
8841 details: e.to_string(),
8842 }
8843 })?;
8844 if output_id.eq_ignore_ascii_case("prediction")
8845 || output_id.eq_ignore_ascii_case("value")
8846 {
8847 return Ok(out.prediction);
8848 }
8849 if output_id.eq_ignore_ascii_case("filter") {
8850 return Ok(out.filter);
8851 }
8852 if output_id.eq_ignore_ascii_case("state") {
8853 return Ok(out.state);
8854 }
8855 if output_id.eq_ignore_ascii_case("go_long") {
8856 return Ok(out.go_long);
8857 }
8858 if output_id.eq_ignore_ascii_case("go_short") {
8859 return Ok(out.go_short);
8860 }
8861 Err(IndicatorDispatchError::UnknownOutput {
8862 indicator: "ehlers_linear_extrapolation_predictor".to_string(),
8863 output: output_id.to_string(),
8864 })
8865 },
8866 )
8867}
8868
8869fn compute_decisionpoint_breadth_swenlin_trading_oscillator_batch(
8870 req: IndicatorBatchRequest<'_>,
8871 output_id: &str,
8872) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8873 expect_value_output(
8874 "decisionpoint_breadth_swenlin_trading_oscillator",
8875 output_id,
8876 )?;
8877 let (advancing, declining) =
8878 extract_high_low_input("decisionpoint_breadth_swenlin_trading_oscillator", req.data)?;
8879 let kernel = req.kernel.to_non_batch();
8880 collect_f64(
8881 "decisionpoint_breadth_swenlin_trading_oscillator",
8882 output_id,
8883 req.combos,
8884 advancing.len(),
8885 |_params| {
8886 let input = DecisionPointBreadthSwenlinTradingOscillatorInput::from_slices(
8887 advancing,
8888 declining,
8889 DecisionPointBreadthSwenlinTradingOscillatorParams,
8890 );
8891 let out = decisionpoint_breadth_swenlin_trading_oscillator_with_kernel(&input, kernel)
8892 .map_err(|e| IndicatorDispatchError::ComputeFailed {
8893 indicator: "decisionpoint_breadth_swenlin_trading_oscillator".to_string(),
8894 details: e.to_string(),
8895 })?;
8896 Ok(out.values)
8897 },
8898 )
8899}
8900
8901fn compute_velocity_acceleration_indicator_batch(
8902 req: IndicatorBatchRequest<'_>,
8903 output_id: &str,
8904) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8905 expect_value_output("velocity_acceleration_indicator", output_id)?;
8906 let data_len = match req.data {
8907 IndicatorDataRef::Slice { values } => values.len(),
8908 IndicatorDataRef::Candles { candles, source } => {
8909 source_type(candles, source.unwrap_or("hlcc4")).len()
8910 }
8911 _ => {
8912 return Err(IndicatorDispatchError::MissingRequiredInput {
8913 indicator: "velocity_acceleration_indicator".to_string(),
8914 input: IndicatorInputKind::Candles,
8915 });
8916 }
8917 };
8918 let kernel = req.kernel.to_non_batch();
8919 collect_f64(
8920 "velocity_acceleration_indicator",
8921 output_id,
8922 req.combos,
8923 data_len,
8924 |params| {
8925 let source =
8926 get_enum_param("velocity_acceleration_indicator", params, "source", "hlcc4")?;
8927 let length = get_usize_param("velocity_acceleration_indicator", params, "length", 21)?;
8928 let smooth_length = get_usize_param(
8929 "velocity_acceleration_indicator",
8930 params,
8931 "smooth_length",
8932 5,
8933 )?;
8934 let data = match req.data {
8935 IndicatorDataRef::Slice { values } => values,
8936 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
8937 _ => unreachable!(),
8938 };
8939 let input = VelocityAccelerationIndicatorInput::from_slice(
8940 data,
8941 VelocityAccelerationIndicatorParams {
8942 length: Some(length),
8943 smooth_length: Some(smooth_length),
8944 },
8945 );
8946 let out = velocity_acceleration_indicator_with_kernel(&input, kernel).map_err(|e| {
8947 IndicatorDispatchError::ComputeFailed {
8948 indicator: "velocity_acceleration_indicator".to_string(),
8949 details: e.to_string(),
8950 }
8951 })?;
8952 Ok(out.values)
8953 },
8954 )
8955}
8956
8957fn compute_normalized_resonator_batch(
8958 req: IndicatorBatchRequest<'_>,
8959 output_id: &str,
8960) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
8961 let data_len = match req.data {
8962 IndicatorDataRef::Slice { values } => values.len(),
8963 IndicatorDataRef::Candles { candles, source } => {
8964 source_type(candles, source.unwrap_or("hl2")).len()
8965 }
8966 _ => {
8967 return Err(IndicatorDispatchError::MissingRequiredInput {
8968 indicator: "normalized_resonator".to_string(),
8969 input: IndicatorInputKind::Candles,
8970 });
8971 }
8972 };
8973 let kernel = req.kernel.to_non_batch();
8974 collect_f64(
8975 "normalized_resonator",
8976 output_id,
8977 req.combos,
8978 data_len,
8979 |params| {
8980 let source = get_enum_param("normalized_resonator", params, "source", "hl2")?;
8981 let period = get_usize_param("normalized_resonator", params, "period", 100)?;
8982 let delta = get_f64_param("normalized_resonator", params, "delta", 0.5)?;
8983 let lookback_mult =
8984 get_f64_param("normalized_resonator", params, "lookback_mult", 1.0)?;
8985 let signal_length =
8986 get_usize_param("normalized_resonator", params, "signal_length", 9)?;
8987 let data = match req.data {
8988 IndicatorDataRef::Slice { values } => values,
8989 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
8990 _ => unreachable!(),
8991 };
8992 let input = NormalizedResonatorInput::from_slice(
8993 data,
8994 NormalizedResonatorParams {
8995 period: Some(period),
8996 delta: Some(delta),
8997 lookback_mult: Some(lookback_mult),
8998 signal_length: Some(signal_length),
8999 },
9000 );
9001 let out = normalized_resonator_with_kernel(&input, kernel).map_err(|e| {
9002 IndicatorDispatchError::ComputeFailed {
9003 indicator: "normalized_resonator".to_string(),
9004 details: e.to_string(),
9005 }
9006 })?;
9007 match output_id {
9008 "oscillator" => Ok(out.oscillator),
9009 "signal" => Ok(out.signal),
9010 _ => Err(IndicatorDispatchError::UnknownOutput {
9011 indicator: "normalized_resonator".to_string(),
9012 output: output_id.to_string(),
9013 }),
9014 }
9015 },
9016 )
9017}
9018
9019fn compute_monotonicity_index_batch(
9020 req: IndicatorBatchRequest<'_>,
9021 output_id: &str,
9022) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9023 let data_len = match req.data {
9024 IndicatorDataRef::Slice { values } => values.len(),
9025 IndicatorDataRef::Candles { candles, source } => {
9026 source_type(candles, source.unwrap_or("close")).len()
9027 }
9028 _ => {
9029 return Err(IndicatorDispatchError::MissingRequiredInput {
9030 indicator: "monotonicity_index".to_string(),
9031 input: IndicatorInputKind::Candles,
9032 });
9033 }
9034 };
9035 let kernel = req.kernel.to_non_batch();
9036 collect_f64(
9037 "monotonicity_index",
9038 output_id,
9039 req.combos,
9040 data_len,
9041 |params| {
9042 let source = get_enum_param("monotonicity_index", params, "source", "close")?;
9043 let length = get_usize_param("monotonicity_index", params, "length", 20)?;
9044 let mode = get_enum_param("monotonicity_index", params, "mode", "efficiency")?;
9045 let index_smooth = get_usize_param("monotonicity_index", params, "index_smooth", 5)?;
9046 let mode = MonotonicityIndexMode::parse(&mode).ok_or_else(|| {
9047 IndicatorDispatchError::InvalidParam {
9048 indicator: "monotonicity_index".to_string(),
9049 key: "mode".to_string(),
9050 reason: format!("invalid mode: {mode}"),
9051 }
9052 })?;
9053 let data = match req.data {
9054 IndicatorDataRef::Slice { values } => values,
9055 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
9056 _ => unreachable!(),
9057 };
9058 let input = MonotonicityIndexInput::from_slice(
9059 data,
9060 MonotonicityIndexParams {
9061 length: Some(length),
9062 mode: Some(mode),
9063 index_smooth: Some(index_smooth),
9064 },
9065 );
9066 let out = monotonicity_index_with_kernel(&input, kernel).map_err(|e| {
9067 IndicatorDispatchError::ComputeFailed {
9068 indicator: "monotonicity_index".to_string(),
9069 details: e.to_string(),
9070 }
9071 })?;
9072 match output_id {
9073 "index" => Ok(out.index),
9074 "cumulative_mean" => Ok(out.cumulative_mean),
9075 "upper_bound" => Ok(out.upper_bound),
9076 _ => Err(IndicatorDispatchError::UnknownOutput {
9077 indicator: "monotonicity_index".to_string(),
9078 output: output_id.to_string(),
9079 }),
9080 }
9081 },
9082 )
9083}
9084
9085fn compute_half_causal_estimator_batch(
9086 req: IndicatorBatchRequest<'_>,
9087 output_id: &str,
9088) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9089 if output_id != "estimate" && output_id != "expected_value" {
9090 return Err(IndicatorDispatchError::UnknownOutput {
9091 indicator: "half_causal_estimator".to_string(),
9092 output: output_id.to_string(),
9093 });
9094 }
9095
9096 let data_len = match req.data {
9097 IndicatorDataRef::Slice { values } => values.len(),
9098 IndicatorDataRef::Candles { candles, .. } => candles.close.len(),
9099 _ => {
9100 return Err(IndicatorDispatchError::MissingRequiredInput {
9101 indicator: "half_causal_estimator".to_string(),
9102 input: IndicatorInputKind::Candles,
9103 });
9104 }
9105 };
9106 let kernel = req.kernel.to_non_batch();
9107 collect_f64(
9108 "half_causal_estimator",
9109 output_id,
9110 req.combos,
9111 data_len,
9112 |params| {
9113 let slots_per_day = get_usize_param_with_aliases(
9114 "half_causal_estimator",
9115 params,
9116 &["slots_per_day"],
9117 0,
9118 )?;
9119 let data_period = get_usize_param("half_causal_estimator", params, "data_period", 5)?;
9120 let filter_length =
9121 get_usize_param("half_causal_estimator", params, "filter_length", 20)?;
9122 let kernel_width =
9123 get_f64_param("half_causal_estimator", params, "kernel_width", 20.0)?;
9124 let maximum_confidence_adjust = get_f64_param(
9125 "half_causal_estimator",
9126 params,
9127 "maximum_confidence_adjust",
9128 100.0,
9129 )?;
9130 let extra_smoothing =
9131 get_usize_param("half_causal_estimator", params, "extra_smoothing", 0)?;
9132 let enable_expected_value = get_bool_param(
9133 "half_causal_estimator",
9134 params,
9135 "enable_expected_value",
9136 false,
9137 )?;
9138 let source = get_enum_param("half_causal_estimator", params, "source", "volume")?;
9139 let kernel_type = match get_enum_param(
9140 "half_causal_estimator",
9141 params,
9142 "kernel_type",
9143 "epanechnikov",
9144 )?
9145 .to_ascii_lowercase()
9146 .as_str()
9147 {
9148 "gaussian" => HalfCausalEstimatorKernelType::Gaussian,
9149 "epanechnikov" => HalfCausalEstimatorKernelType::Epanechnikov,
9150 "triangular" => HalfCausalEstimatorKernelType::Triangular,
9151 "sinc" => HalfCausalEstimatorKernelType::Sinc,
9152 other => {
9153 return Err(IndicatorDispatchError::InvalidParam {
9154 indicator: "half_causal_estimator".to_string(),
9155 key: "kernel_type".to_string(),
9156 reason: format!("unsupported value '{other}'"),
9157 })
9158 }
9159 };
9160 let confidence_adjust = match get_enum_param(
9161 "half_causal_estimator",
9162 params,
9163 "confidence_adjust",
9164 "symmetric",
9165 )?
9166 .to_ascii_lowercase()
9167 .as_str()
9168 {
9169 "symmetric" => HalfCausalEstimatorConfidenceAdjust::Symmetric,
9170 "linear" => HalfCausalEstimatorConfidenceAdjust::Linear,
9171 "none" => HalfCausalEstimatorConfidenceAdjust::None,
9172 other => {
9173 return Err(IndicatorDispatchError::InvalidParam {
9174 indicator: "half_causal_estimator".to_string(),
9175 key: "confidence_adjust".to_string(),
9176 reason: format!("unsupported value '{other}'"),
9177 })
9178 }
9179 };
9180
9181 let indicator_params = HalfCausalEstimatorParams {
9182 slots_per_day: if slots_per_day == 0 {
9183 None
9184 } else {
9185 Some(slots_per_day)
9186 },
9187 data_period: Some(data_period),
9188 filter_length: Some(filter_length),
9189 kernel_width: Some(kernel_width),
9190 kernel_type: Some(kernel_type),
9191 confidence_adjust: Some(confidence_adjust),
9192 maximum_confidence_adjust: Some(maximum_confidence_adjust),
9193 enable_expected_value: Some(enable_expected_value),
9194 extra_smoothing: Some(extra_smoothing),
9195 };
9196
9197 let out = match req.data {
9198 IndicatorDataRef::Slice { values } => {
9199 let input = HalfCausalEstimatorInput::from_slice(values, indicator_params);
9200 half_causal_estimator_with_kernel(&input, kernel)
9201 }
9202 IndicatorDataRef::Candles { candles, .. } => {
9203 let input =
9204 HalfCausalEstimatorInput::from_candles(candles, &source, indicator_params);
9205 half_causal_estimator_with_kernel(&input, kernel)
9206 }
9207 _ => unreachable!(),
9208 }
9209 .map_err(|e| IndicatorDispatchError::ComputeFailed {
9210 indicator: "half_causal_estimator".to_string(),
9211 details: e.to_string(),
9212 })?;
9213
9214 Ok(match output_id {
9215 "estimate" => out.estimate,
9216 "expected_value" => out.expected_value,
9217 _ => unreachable!(),
9218 })
9219 },
9220 )
9221}
9222
9223fn compute_historical_volatility_batch(
9224 req: IndicatorBatchRequest<'_>,
9225 output_id: &str,
9226) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9227 expect_value_output("historical_volatility", output_id)?;
9228 let data = extract_slice_input("historical_volatility", req.data, "close")?;
9229 let kernel = req.kernel.to_non_batch();
9230 collect_f64(
9231 "historical_volatility",
9232 output_id,
9233 req.combos,
9234 data.len(),
9235 |params| {
9236 let lookback = get_usize_param("historical_volatility", params, "lookback", 20)?;
9237 let annualization_days =
9238 get_f64_param("historical_volatility", params, "annualization_days", 250.0)?;
9239 let input = HistoricalVolatilityInput::from_slice(
9240 data,
9241 HistoricalVolatilityParams {
9242 lookback: Some(lookback),
9243 annualization_days: Some(annualization_days),
9244 },
9245 );
9246 let out = historical_volatility_with_kernel(&input, kernel).map_err(|e| {
9247 IndicatorDispatchError::ComputeFailed {
9248 indicator: "historical_volatility".to_string(),
9249 details: e.to_string(),
9250 }
9251 })?;
9252 Ok(out.values)
9253 },
9254 )
9255}
9256
9257fn compute_historical_volatility_percentile_batch(
9258 req: IndicatorBatchRequest<'_>,
9259 output_id: &str,
9260) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9261 let data = extract_slice_input("historical_volatility_percentile", req.data, "close")?;
9262 let kernel = req.kernel.to_non_batch();
9263 collect_f64(
9264 "historical_volatility_percentile",
9265 output_id,
9266 req.combos,
9267 data.len(),
9268 |params| {
9269 let length = get_usize_param("historical_volatility_percentile", params, "length", 20)?;
9270 let annual_length = get_usize_param(
9271 "historical_volatility_percentile",
9272 params,
9273 "annual_length",
9274 252,
9275 )?;
9276 let input = HistoricalVolatilityPercentileInput::from_slice(
9277 data,
9278 HistoricalVolatilityPercentileParams {
9279 length: Some(length),
9280 annual_length: Some(annual_length),
9281 },
9282 );
9283 let out =
9284 historical_volatility_percentile_with_kernel(&input, kernel).map_err(|e| {
9285 IndicatorDispatchError::ComputeFailed {
9286 indicator: "historical_volatility_percentile".to_string(),
9287 details: e.to_string(),
9288 }
9289 })?;
9290 if output_id.eq_ignore_ascii_case("hvp") {
9291 return Ok(out.hvp);
9292 }
9293 if output_id.eq_ignore_ascii_case("hvp_sma") {
9294 return Ok(out.hvp_sma);
9295 }
9296 Err(IndicatorDispatchError::UnknownOutput {
9297 indicator: "historical_volatility_percentile".to_string(),
9298 output: output_id.to_string(),
9299 })
9300 },
9301 )
9302}
9303
9304fn compute_volatility_ratio_adaptive_rsx_batch(
9305 req: IndicatorBatchRequest<'_>,
9306 output_id: &str,
9307) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9308 let data = extract_slice_input("volatility_ratio_adaptive_rsx", req.data, "close")?;
9309 let kernel = req.kernel.to_non_batch();
9310 collect_f64(
9311 "volatility_ratio_adaptive_rsx",
9312 output_id,
9313 req.combos,
9314 data.len(),
9315 |params| {
9316 let period = get_usize_param("volatility_ratio_adaptive_rsx", params, "period", 14)?;
9317 let speed = get_f64_param("volatility_ratio_adaptive_rsx", params, "speed", 0.5)?;
9318 let input = VolatilityRatioAdaptiveRsxInput::from_slice(
9319 data,
9320 VolatilityRatioAdaptiveRsxParams {
9321 period: Some(period),
9322 speed: Some(speed),
9323 },
9324 );
9325 let out = volatility_ratio_adaptive_rsx_with_kernel(&input, kernel).map_err(|e| {
9326 IndicatorDispatchError::ComputeFailed {
9327 indicator: "volatility_ratio_adaptive_rsx".to_string(),
9328 details: e.to_string(),
9329 }
9330 })?;
9331 if output_id.eq_ignore_ascii_case("line") || output_id.eq_ignore_ascii_case("value") {
9332 return Ok(out.line);
9333 }
9334 if output_id.eq_ignore_ascii_case("signal") {
9335 return Ok(out.signal);
9336 }
9337 Err(IndicatorDispatchError::UnknownOutput {
9338 indicator: "volatility_ratio_adaptive_rsx".to_string(),
9339 output: output_id.to_string(),
9340 })
9341 },
9342 )
9343}
9344
9345fn compute_on_balance_volume_oscillator_batch(
9346 req: IndicatorBatchRequest<'_>,
9347 output_id: &str,
9348) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9349 let (close, volume) =
9350 extract_close_volume_input("on_balance_volume_oscillator", req.data, "close")?;
9351 let kernel = req.kernel.to_non_batch();
9352 collect_f64(
9353 "on_balance_volume_oscillator",
9354 output_id,
9355 req.combos,
9356 close.len(),
9357 |params| {
9358 let obv_length =
9359 get_usize_param("on_balance_volume_oscillator", params, "obv_length", 20)?;
9360 let ema_length =
9361 get_usize_param("on_balance_volume_oscillator", params, "ema_length", 9)?;
9362 let input = OnBalanceVolumeOscillatorInput::from_slices(
9363 close,
9364 volume,
9365 OnBalanceVolumeOscillatorParams {
9366 obv_length: Some(obv_length),
9367 ema_length: Some(ema_length),
9368 },
9369 );
9370 let out = on_balance_volume_oscillator_with_kernel(&input, kernel).map_err(|e| {
9371 IndicatorDispatchError::ComputeFailed {
9372 indicator: "on_balance_volume_oscillator".to_string(),
9373 details: e.to_string(),
9374 }
9375 })?;
9376 if output_id.eq_ignore_ascii_case("line") || output_id.eq_ignore_ascii_case("value") {
9377 return Ok(out.line);
9378 }
9379 if output_id.eq_ignore_ascii_case("signal") {
9380 return Ok(out.signal);
9381 }
9382 Err(IndicatorDispatchError::UnknownOutput {
9383 indicator: "on_balance_volume_oscillator".to_string(),
9384 output: output_id.to_string(),
9385 })
9386 },
9387 )
9388}
9389
9390fn compute_twiggs_money_flow_batch(
9391 req: IndicatorBatchRequest<'_>,
9392 output_id: &str,
9393) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9394 let (high, low, close, volume) = extract_hlcv_input("twiggs_money_flow", req.data)?;
9395 let kernel = req.kernel.to_non_batch();
9396 collect_f64(
9397 "twiggs_money_flow",
9398 output_id,
9399 req.combos,
9400 close.len(),
9401 |params| {
9402 let length = get_usize_param("twiggs_money_flow", params, "length", 21)?;
9403 let smoothing_length =
9404 get_usize_param("twiggs_money_flow", params, "smoothing_length", 4)?;
9405 let ma_type = get_enum_param("twiggs_money_flow", params, "ma_type", "ema")?;
9406 let input = TwiggsMoneyFlowInput::from_slices(
9407 high,
9408 low,
9409 close,
9410 volume,
9411 TwiggsMoneyFlowParams {
9412 length: Some(length),
9413 smoothing_length: Some(smoothing_length),
9414 ma_type: Some(ma_type),
9415 },
9416 );
9417 let out = twiggs_money_flow_with_kernel(&input, kernel).map_err(|e| {
9418 IndicatorDispatchError::ComputeFailed {
9419 indicator: "twiggs_money_flow".to_string(),
9420 details: e.to_string(),
9421 }
9422 })?;
9423 if output_id.eq_ignore_ascii_case("tmf") || output_id.eq_ignore_ascii_case("value") {
9424 return Ok(out.tmf);
9425 }
9426 if output_id.eq_ignore_ascii_case("smoothed") {
9427 return Ok(out.smoothed);
9428 }
9429 Err(IndicatorDispatchError::UnknownOutput {
9430 indicator: "twiggs_money_flow".to_string(),
9431 output: output_id.to_string(),
9432 })
9433 },
9434 )
9435}
9436
9437fn compute_parkinson_volatility_batch(
9438 req: IndicatorBatchRequest<'_>,
9439 output_id: &str,
9440) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9441 let (high, low) = extract_high_low_input("parkinson_volatility", req.data)?;
9442 let kernel = req.kernel.to_non_batch();
9443 collect_f64(
9444 "parkinson_volatility",
9445 output_id,
9446 req.combos,
9447 high.len(),
9448 |params| {
9449 let period = get_usize_param("parkinson_volatility", params, "period", 10)?;
9450 let input = ParkinsonVolatilityInput::from_slices(
9451 high,
9452 low,
9453 ParkinsonVolatilityParams {
9454 period: Some(period),
9455 },
9456 );
9457 let out = parkinson_volatility_with_kernel(&input, kernel).map_err(|e| {
9458 IndicatorDispatchError::ComputeFailed {
9459 indicator: "parkinson_volatility".to_string(),
9460 details: e.to_string(),
9461 }
9462 })?;
9463 if output_id.eq_ignore_ascii_case("volatility")
9464 || output_id.eq_ignore_ascii_case("value")
9465 {
9466 return Ok(out.volatility);
9467 }
9468 if output_id.eq_ignore_ascii_case("variance") {
9469 return Ok(out.variance);
9470 }
9471 Err(IndicatorDispatchError::UnknownOutput {
9472 indicator: "parkinson_volatility".to_string(),
9473 output: output_id.to_string(),
9474 })
9475 },
9476 )
9477}
9478
9479fn compute_l2_ehlers_signal_to_noise_batch(
9480 req: IndicatorBatchRequest<'_>,
9481 output_id: &str,
9482) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9483 expect_value_output("l2_ehlers_signal_to_noise", output_id)?;
9484 let data_len = match req.data {
9485 IndicatorDataRef::Slice { values } => values.len(),
9486 IndicatorDataRef::Candles { candles, source } => {
9487 source_type(candles, source.unwrap_or("hl2")).len()
9488 }
9489 _ => {
9490 return Err(IndicatorDispatchError::MissingRequiredInput {
9491 indicator: "l2_ehlers_signal_to_noise".to_string(),
9492 input: IndicatorInputKind::Candles,
9493 })
9494 }
9495 };
9496 let kernel = req.kernel.to_non_batch();
9497 collect_f64(
9498 "l2_ehlers_signal_to_noise",
9499 output_id,
9500 req.combos,
9501 data_len,
9502 |params| {
9503 let source = get_enum_param("l2_ehlers_signal_to_noise", params, "source", "hl2")?;
9504 let smooth_period =
9505 get_usize_param("l2_ehlers_signal_to_noise", params, "smooth_period", 10)?;
9506 let src = match req.data {
9507 IndicatorDataRef::Slice { values } => values,
9508 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
9509 _ => unreachable!(),
9510 };
9511 let (high, low) = match req.data {
9512 IndicatorDataRef::Candles { candles, .. } => {
9513 (candles.high.as_slice(), candles.low.as_slice())
9514 }
9515 IndicatorDataRef::Ohlc { high, low, .. } => (high, low),
9516 IndicatorDataRef::Ohlcv { high, low, .. } => (high, low),
9517 _ => {
9518 return Err(IndicatorDispatchError::MissingRequiredInput {
9519 indicator: "l2_ehlers_signal_to_noise".to_string(),
9520 input: IndicatorInputKind::Candles,
9521 })
9522 }
9523 };
9524 let input = L2EhlersSignalToNoiseInput::from_slices(
9525 src,
9526 high,
9527 low,
9528 L2EhlersSignalToNoiseParams {
9529 smooth_period: Some(smooth_period),
9530 },
9531 );
9532 let out = l2_ehlers_signal_to_noise_with_kernel(&input, kernel).map_err(|e| {
9533 IndicatorDispatchError::ComputeFailed {
9534 indicator: "l2_ehlers_signal_to_noise".to_string(),
9535 details: e.to_string(),
9536 }
9537 })?;
9538 Ok(out.values)
9539 },
9540 )
9541}
9542
9543fn compute_cycle_channel_oscillator_batch(
9544 req: IndicatorBatchRequest<'_>,
9545 output_id: &str,
9546) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9547 let data_len = match req.data {
9548 IndicatorDataRef::Candles { candles, source } => {
9549 source_type(candles, source.unwrap_or("close")).len()
9550 }
9551 _ => {
9552 return Err(IndicatorDispatchError::MissingRequiredInput {
9553 indicator: "cycle_channel_oscillator".to_string(),
9554 input: IndicatorInputKind::Candles,
9555 })
9556 }
9557 };
9558 let kernel = req.kernel.to_non_batch();
9559 collect_f64(
9560 "cycle_channel_oscillator",
9561 output_id,
9562 req.combos,
9563 data_len,
9564 |params| {
9565 let source = get_enum_param("cycle_channel_oscillator", params, "source", "close")?;
9566 let short_cycle_length =
9567 get_usize_param("cycle_channel_oscillator", params, "short_cycle_length", 10)?;
9568 let medium_cycle_length = get_usize_param(
9569 "cycle_channel_oscillator",
9570 params,
9571 "medium_cycle_length",
9572 30,
9573 )?;
9574 let short_multiplier =
9575 get_f64_param("cycle_channel_oscillator", params, "short_multiplier", 1.0)?;
9576 let medium_multiplier =
9577 get_f64_param("cycle_channel_oscillator", params, "medium_multiplier", 3.0)?;
9578 let (src, high, low, close) = match req.data {
9579 IndicatorDataRef::Candles { candles, .. } => (
9580 source_type(candles, &source),
9581 candles.high.as_slice(),
9582 candles.low.as_slice(),
9583 candles.close.as_slice(),
9584 ),
9585 _ => unreachable!(),
9586 };
9587 let input = CycleChannelOscillatorInput::from_slices(
9588 src,
9589 high,
9590 low,
9591 close,
9592 CycleChannelOscillatorParams {
9593 short_cycle_length: Some(short_cycle_length),
9594 medium_cycle_length: Some(medium_cycle_length),
9595 short_multiplier: Some(short_multiplier),
9596 medium_multiplier: Some(medium_multiplier),
9597 },
9598 );
9599 let out = cycle_channel_oscillator_with_kernel(&input, kernel).map_err(|e| {
9600 IndicatorDispatchError::ComputeFailed {
9601 indicator: "cycle_channel_oscillator".to_string(),
9602 details: e.to_string(),
9603 }
9604 })?;
9605 if output_id.eq_ignore_ascii_case("fast") || output_id.eq_ignore_ascii_case("value") {
9606 return Ok(out.fast);
9607 }
9608 if output_id.eq_ignore_ascii_case("slow") {
9609 return Ok(out.slow);
9610 }
9611 Err(IndicatorDispatchError::UnknownOutput {
9612 indicator: "cycle_channel_oscillator".to_string(),
9613 output: output_id.to_string(),
9614 })
9615 },
9616 )
9617}
9618
9619fn compute_andean_oscillator_batch(
9620 req: IndicatorBatchRequest<'_>,
9621 output_id: &str,
9622) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9623 let (open, _high, _low, close) = extract_ohlc_full_input("andean_oscillator", req.data)?;
9624 let kernel = req.kernel.to_non_batch();
9625 collect_f64(
9626 "andean_oscillator",
9627 output_id,
9628 req.combos,
9629 close.len(),
9630 |params| {
9631 let length = get_usize_param("andean_oscillator", params, "length", 50)?;
9632 let signal_length = get_usize_param("andean_oscillator", params, "signal_length", 9)?;
9633 let input = AndeanOscillatorInput::from_slices(
9634 open,
9635 close,
9636 AndeanOscillatorParams {
9637 length: Some(length),
9638 signal_length: Some(signal_length),
9639 },
9640 );
9641 let out = andean_oscillator_with_kernel(&input, kernel).map_err(|e| {
9642 IndicatorDispatchError::ComputeFailed {
9643 indicator: "andean_oscillator".to_string(),
9644 details: e.to_string(),
9645 }
9646 })?;
9647 if output_id.eq_ignore_ascii_case("bull") {
9648 return Ok(out.bull);
9649 }
9650 if output_id.eq_ignore_ascii_case("bear") {
9651 return Ok(out.bear);
9652 }
9653 if output_id.eq_ignore_ascii_case("signal") {
9654 return Ok(out.signal);
9655 }
9656 Err(IndicatorDispatchError::UnknownOutput {
9657 indicator: "andean_oscillator".to_string(),
9658 output: output_id.to_string(),
9659 })
9660 },
9661 )
9662}
9663
9664fn compute_daily_factor_batch(
9665 req: IndicatorBatchRequest<'_>,
9666 output_id: &str,
9667) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9668 let (open, high, low, close) = extract_ohlc_full_input("daily_factor", req.data)?;
9669 let kernel = req.kernel.to_non_batch();
9670 collect_f64(
9671 "daily_factor",
9672 output_id,
9673 req.combos,
9674 close.len(),
9675 |params| {
9676 let threshold_level = get_f64_param("daily_factor", params, "threshold_level", 0.35)?;
9677 let input = DailyFactorInput::from_slices(
9678 open,
9679 high,
9680 low,
9681 close,
9682 DailyFactorParams {
9683 threshold_level: Some(threshold_level),
9684 },
9685 );
9686 let out = daily_factor_with_kernel(&input, kernel).map_err(|e| {
9687 IndicatorDispatchError::ComputeFailed {
9688 indicator: "daily_factor".to_string(),
9689 details: e.to_string(),
9690 }
9691 })?;
9692 if output_id.eq_ignore_ascii_case("value") {
9693 return Ok(out.value);
9694 }
9695 if output_id.eq_ignore_ascii_case("ema") {
9696 return Ok(out.ema);
9697 }
9698 if output_id.eq_ignore_ascii_case("signal") {
9699 return Ok(out.signal);
9700 }
9701 Err(IndicatorDispatchError::UnknownOutput {
9702 indicator: "daily_factor".to_string(),
9703 output: output_id.to_string(),
9704 })
9705 },
9706 )
9707}
9708
9709fn compute_ehlers_adaptive_cyber_cycle_batch(
9710 req: IndicatorBatchRequest<'_>,
9711 output_id: &str,
9712) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9713 let data_len = match req.data {
9714 IndicatorDataRef::Slice { values } => values.len(),
9715 IndicatorDataRef::Candles { candles, source } => {
9716 source_type(candles, source.unwrap_or("hl2")).len()
9717 }
9718 _ => {
9719 return Err(IndicatorDispatchError::MissingRequiredInput {
9720 indicator: "ehlers_adaptive_cyber_cycle".to_string(),
9721 input: IndicatorInputKind::Candles,
9722 })
9723 }
9724 };
9725 let kernel = req.kernel.to_non_batch();
9726 collect_f64(
9727 "ehlers_adaptive_cyber_cycle",
9728 output_id,
9729 req.combos,
9730 data_len,
9731 |params| {
9732 let source = get_enum_param("ehlers_adaptive_cyber_cycle", params, "source", "hl2")?;
9733 let alpha = get_f64_param("ehlers_adaptive_cyber_cycle", params, "alpha", 0.07)?;
9734 let data = match req.data {
9735 IndicatorDataRef::Slice { values } => values,
9736 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
9737 _ => unreachable!(),
9738 };
9739 let input = EhlersAdaptiveCyberCycleInput::from_slice(
9740 data,
9741 EhlersAdaptiveCyberCycleParams { alpha: Some(alpha) },
9742 );
9743 let out = ehlers_adaptive_cyber_cycle_with_kernel(&input, kernel).map_err(|e| {
9744 IndicatorDispatchError::ComputeFailed {
9745 indicator: "ehlers_adaptive_cyber_cycle".to_string(),
9746 details: e.to_string(),
9747 }
9748 })?;
9749 if output_id.eq_ignore_ascii_case("cycle") || output_id.eq_ignore_ascii_case("value") {
9750 return Ok(out.cycle);
9751 }
9752 if output_id.eq_ignore_ascii_case("trigger") {
9753 return Ok(out.trigger);
9754 }
9755 Err(IndicatorDispatchError::UnknownOutput {
9756 indicator: "ehlers_adaptive_cyber_cycle".to_string(),
9757 output: output_id.to_string(),
9758 })
9759 },
9760 )
9761}
9762
9763fn compute_ehlers_simple_cycle_indicator_batch(
9764 req: IndicatorBatchRequest<'_>,
9765 output_id: &str,
9766) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9767 let data_len = match req.data {
9768 IndicatorDataRef::Slice { values } => values.len(),
9769 IndicatorDataRef::Candles { candles, source } => {
9770 source_type(candles, source.unwrap_or("hl2")).len()
9771 }
9772 _ => {
9773 return Err(IndicatorDispatchError::MissingRequiredInput {
9774 indicator: "ehlers_simple_cycle_indicator".to_string(),
9775 input: IndicatorInputKind::Candles,
9776 })
9777 }
9778 };
9779 let kernel = req.kernel.to_non_batch();
9780 collect_f64(
9781 "ehlers_simple_cycle_indicator",
9782 output_id,
9783 req.combos,
9784 data_len,
9785 |params| {
9786 let source = get_enum_param("ehlers_simple_cycle_indicator", params, "source", "hl2")?;
9787 let alpha = get_f64_param("ehlers_simple_cycle_indicator", params, "alpha", 0.07)?;
9788 let data = match req.data {
9789 IndicatorDataRef::Slice { values } => values,
9790 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
9791 _ => unreachable!(),
9792 };
9793 let input = EhlersSimpleCycleIndicatorInput::from_slice(
9794 data,
9795 EhlersSimpleCycleIndicatorParams { alpha: Some(alpha) },
9796 );
9797 let out = ehlers_simple_cycle_indicator_with_kernel(&input, kernel).map_err(|e| {
9798 IndicatorDispatchError::ComputeFailed {
9799 indicator: "ehlers_simple_cycle_indicator".to_string(),
9800 details: e.to_string(),
9801 }
9802 })?;
9803 if output_id.eq_ignore_ascii_case("cycle") || output_id.eq_ignore_ascii_case("value") {
9804 return Ok(out.cycle);
9805 }
9806 if output_id.eq_ignore_ascii_case("trigger") {
9807 return Ok(out.trigger);
9808 }
9809 Err(IndicatorDispatchError::UnknownOutput {
9810 indicator: "ehlers_simple_cycle_indicator".to_string(),
9811 output: output_id.to_string(),
9812 })
9813 },
9814 )
9815}
9816
9817fn compute_l1_ehlers_phasor_batch(
9818 req: IndicatorBatchRequest<'_>,
9819 output_id: &str,
9820) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9821 expect_value_output("l1_ehlers_phasor", output_id)?;
9822 let data = extract_slice_input("l1_ehlers_phasor", req.data, "close")?;
9823 let kernel = req.kernel.to_non_batch();
9824 collect_f64(
9825 "l1_ehlers_phasor",
9826 output_id,
9827 req.combos,
9828 data.len(),
9829 |params| {
9830 let domestic_cycle_length =
9831 get_usize_param("l1_ehlers_phasor", params, "domestic_cycle_length", 15)?;
9832 let input = L1EhlersPhasorInput::from_slice(
9833 data,
9834 L1EhlersPhasorParams {
9835 domestic_cycle_length: Some(domestic_cycle_length),
9836 },
9837 );
9838 let out = l1_ehlers_phasor_with_kernel(&input, kernel).map_err(|e| {
9839 IndicatorDispatchError::ComputeFailed {
9840 indicator: "l1_ehlers_phasor".to_string(),
9841 details: e.to_string(),
9842 }
9843 })?;
9844 Ok(out.values)
9845 },
9846 )
9847}
9848
9849fn compute_ehlers_smoothed_adaptive_momentum_batch(
9850 req: IndicatorBatchRequest<'_>,
9851 output_id: &str,
9852) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9853 expect_value_output("ehlers_smoothed_adaptive_momentum", output_id)?;
9854 let data_len = match req.data {
9855 IndicatorDataRef::Slice { values } => values.len(),
9856 IndicatorDataRef::Candles { candles, source } => {
9857 source_type(candles, source.unwrap_or("hl2")).len()
9858 }
9859 _ => {
9860 return Err(IndicatorDispatchError::MissingRequiredInput {
9861 indicator: "ehlers_smoothed_adaptive_momentum".to_string(),
9862 input: IndicatorInputKind::Candles,
9863 })
9864 }
9865 };
9866 let kernel = req.kernel.to_non_batch();
9867 collect_f64(
9868 "ehlers_smoothed_adaptive_momentum",
9869 output_id,
9870 req.combos,
9871 data_len,
9872 |params| {
9873 let source =
9874 get_enum_param("ehlers_smoothed_adaptive_momentum", params, "source", "hl2")?;
9875 let alpha = get_f64_param("ehlers_smoothed_adaptive_momentum", params, "alpha", 0.07)?;
9876 let cutoff = get_f64_param("ehlers_smoothed_adaptive_momentum", params, "cutoff", 8.0)?;
9877 let data = match req.data {
9878 IndicatorDataRef::Slice { values } => values,
9879 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
9880 _ => unreachable!(),
9881 };
9882 let input = EhlersSmoothedAdaptiveMomentumInput::from_slice(
9883 data,
9884 EhlersSmoothedAdaptiveMomentumParams {
9885 alpha: Some(alpha),
9886 cutoff: Some(cutoff),
9887 },
9888 );
9889 let out =
9890 ehlers_smoothed_adaptive_momentum_with_kernel(&input, kernel).map_err(|e| {
9891 IndicatorDispatchError::ComputeFailed {
9892 indicator: "ehlers_smoothed_adaptive_momentum".to_string(),
9893 details: e.to_string(),
9894 }
9895 })?;
9896 Ok(out.values)
9897 },
9898 )
9899}
9900
9901fn compute_ewma_volatility_batch(
9902 req: IndicatorBatchRequest<'_>,
9903 output_id: &str,
9904) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9905 expect_value_output("ewma_volatility", output_id)?;
9906 let data = extract_slice_input("ewma_volatility", req.data, "close")?;
9907 let kernel = req.kernel.to_non_batch();
9908 collect_f64(
9909 "ewma_volatility",
9910 output_id,
9911 req.combos,
9912 data.len(),
9913 |params| {
9914 let lambda = get_f64_param("ewma_volatility", params, "lambda", 0.94)?;
9915 let input = EwmaVolatilityInput::from_slice(
9916 data,
9917 EwmaVolatilityParams {
9918 lambda: Some(lambda),
9919 },
9920 );
9921 let out = ewma_volatility_with_kernel(&input, kernel).map_err(|e| {
9922 IndicatorDispatchError::ComputeFailed {
9923 indicator: "ewma_volatility".to_string(),
9924 details: e.to_string(),
9925 }
9926 })?;
9927 Ok(out.values)
9928 },
9929 )
9930}
9931
9932fn compute_random_walk_index_batch(
9933 req: IndicatorBatchRequest<'_>,
9934 output_id: &str,
9935) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9936 let (high, low, close) = extract_ohlc_input("random_walk_index", req.data)?;
9937 let kernel = req.kernel.to_non_batch();
9938 collect_f64(
9939 "random_walk_index",
9940 output_id,
9941 req.combos,
9942 close.len(),
9943 |params| {
9944 let length = get_usize_param("random_walk_index", params, "length", 14)?;
9945 let input = RandomWalkIndexInput::from_slices(
9946 high,
9947 low,
9948 close,
9949 RandomWalkIndexParams {
9950 length: Some(length),
9951 },
9952 );
9953 let out = random_walk_index_with_kernel(&input, kernel).map_err(|e| {
9954 IndicatorDispatchError::ComputeFailed {
9955 indicator: "random_walk_index".to_string(),
9956 details: e.to_string(),
9957 }
9958 })?;
9959 if output_id.eq_ignore_ascii_case("high") {
9960 return Ok(out.high);
9961 }
9962 if output_id.eq_ignore_ascii_case("low") {
9963 return Ok(out.low);
9964 }
9965 Err(IndicatorDispatchError::UnknownOutput {
9966 indicator: "random_walk_index".to_string(),
9967 output: output_id.to_string(),
9968 })
9969 },
9970 )
9971}
9972
9973fn compute_price_moving_average_ratio_percentile_batch(
9974 req: IndicatorBatchRequest<'_>,
9975 output_id: &str,
9976) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
9977 let data_len = match req.data {
9978 IndicatorDataRef::Candles { candles, source } => {
9979 source_type(candles, source.unwrap_or("close")).len()
9980 }
9981 IndicatorDataRef::CloseVolume { close, .. } => close.len(),
9982 IndicatorDataRef::Ohlcv { close, .. } => close.len(),
9983 _ => {
9984 return Err(IndicatorDispatchError::MissingRequiredInput {
9985 indicator: "price_moving_average_ratio_percentile".to_string(),
9986 input: IndicatorInputKind::CloseVolume,
9987 })
9988 }
9989 };
9990 let kernel = req.kernel.to_non_batch();
9991 collect_f64(
9992 "price_moving_average_ratio_percentile",
9993 output_id,
9994 req.combos,
9995 data_len,
9996 |params| {
9997 let source = get_enum_param(
9998 "price_moving_average_ratio_percentile",
9999 params,
10000 "source",
10001 "close",
10002 )?;
10003 let ma_length = get_usize_param(
10004 "price_moving_average_ratio_percentile",
10005 params,
10006 "ma_length",
10007 20,
10008 )?;
10009 let ma_type = get_enum_param(
10010 "price_moving_average_ratio_percentile",
10011 params,
10012 "ma_type",
10013 "sma",
10014 )?
10015 .parse::<PriceMovingAverageRatioPercentileMaType>()
10016 .map_err(|e| IndicatorDispatchError::InvalidParam {
10017 indicator: "price_moving_average_ratio_percentile".to_string(),
10018 key: "ma_type".to_string(),
10019 reason: e,
10020 })?;
10021 let pmarp_lookback = get_usize_param(
10022 "price_moving_average_ratio_percentile",
10023 params,
10024 "pmarp_lookback",
10025 350,
10026 )?;
10027 let signal_ma_length = get_usize_param(
10028 "price_moving_average_ratio_percentile",
10029 params,
10030 "signal_ma_length",
10031 20,
10032 )?;
10033 let signal_ma_type = get_enum_param(
10034 "price_moving_average_ratio_percentile",
10035 params,
10036 "signal_ma_type",
10037 "sma",
10038 )?
10039 .parse::<PriceMovingAverageRatioPercentileMaType>()
10040 .map_err(|e| IndicatorDispatchError::InvalidParam {
10041 indicator: "price_moving_average_ratio_percentile".to_string(),
10042 key: "signal_ma_type".to_string(),
10043 reason: e,
10044 })?;
10045 let line_mode = get_enum_param(
10046 "price_moving_average_ratio_percentile",
10047 params,
10048 "line_mode",
10049 "pmar",
10050 )?
10051 .parse::<PriceMovingAverageRatioPercentileLineMode>()
10052 .map_err(|e| IndicatorDispatchError::InvalidParam {
10053 indicator: "price_moving_average_ratio_percentile".to_string(),
10054 key: "line_mode".to_string(),
10055 reason: e,
10056 })?;
10057 let (price, volume) = match req.data {
10058 IndicatorDataRef::Candles { candles, .. } => {
10059 (source_type(candles, &source), candles.volume.as_slice())
10060 }
10061 IndicatorDataRef::CloseVolume { close, volume } => (close, volume),
10062 IndicatorDataRef::Ohlcv {
10063 open,
10064 high,
10065 low,
10066 close,
10067 volume,
10068 } => {
10069 let price = match source.to_ascii_lowercase().as_str() {
10070 "open" => open,
10071 "high" => high,
10072 "low" => low,
10073 _ => close,
10074 };
10075 (price, volume)
10076 }
10077 _ => unreachable!(),
10078 };
10079 let input = PriceMovingAverageRatioPercentileInput::from_slices(
10080 price,
10081 volume,
10082 PriceMovingAverageRatioPercentileParams {
10083 ma_length: Some(ma_length),
10084 ma_type: Some(ma_type),
10085 pmarp_lookback: Some(pmarp_lookback),
10086 signal_ma_length: Some(signal_ma_length),
10087 signal_ma_type: Some(signal_ma_type),
10088 line_mode: Some(line_mode),
10089 },
10090 );
10091 let out =
10092 price_moving_average_ratio_percentile_with_kernel(&input, kernel).map_err(|e| {
10093 IndicatorDispatchError::ComputeFailed {
10094 indicator: "price_moving_average_ratio_percentile".to_string(),
10095 details: e.to_string(),
10096 }
10097 })?;
10098 if output_id.eq_ignore_ascii_case("pmar") {
10099 return Ok(out.pmar);
10100 }
10101 if output_id.eq_ignore_ascii_case("pmarp") {
10102 return Ok(out.pmarp);
10103 }
10104 if output_id.eq_ignore_ascii_case("plotline") || output_id.eq_ignore_ascii_case("value")
10105 {
10106 return Ok(out.plotline);
10107 }
10108 if output_id.eq_ignore_ascii_case("signal") {
10109 return Ok(out.signal);
10110 }
10111 if output_id.eq_ignore_ascii_case("pmar_high") {
10112 return Ok(out.pmar_high);
10113 }
10114 if output_id.eq_ignore_ascii_case("pmar_low") {
10115 return Ok(out.pmar_low);
10116 }
10117 if output_id.eq_ignore_ascii_case("scaled_pmar") {
10118 return Ok(out.scaled_pmar);
10119 }
10120 Err(IndicatorDispatchError::UnknownOutput {
10121 indicator: "price_moving_average_ratio_percentile".to_string(),
10122 output: output_id.to_string(),
10123 })
10124 },
10125 )
10126}
10127
10128fn compute_trend_trigger_factor_batch(
10129 req: IndicatorBatchRequest<'_>,
10130 output_id: &str,
10131) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10132 expect_value_output("trend_trigger_factor", output_id)?;
10133 let (high, low) = extract_high_low_input("trend_trigger_factor", req.data)?;
10134 let kernel = req.kernel.to_non_batch();
10135 collect_f64(
10136 "trend_trigger_factor",
10137 output_id,
10138 req.combos,
10139 high.len(),
10140 |params| {
10141 let length = get_usize_param("trend_trigger_factor", params, "length", 15)?;
10142 let input = TrendTriggerFactorInput::from_slices(
10143 high,
10144 low,
10145 TrendTriggerFactorParams {
10146 length: Some(length),
10147 },
10148 );
10149 let out = trend_trigger_factor_with_kernel(&input, kernel).map_err(|e| {
10150 IndicatorDispatchError::ComputeFailed {
10151 indicator: "trend_trigger_factor".to_string(),
10152 details: e.to_string(),
10153 }
10154 })?;
10155 Ok(out.values)
10156 },
10157 )
10158}
10159
10160fn compute_mesa_stochastic_multi_length_batch(
10161 req: IndicatorBatchRequest<'_>,
10162 output_id: &str,
10163) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10164 let data_len = match req.data {
10165 IndicatorDataRef::Slice { values } => values.len(),
10166 IndicatorDataRef::Candles { candles, source } => {
10167 source_type(candles, source.unwrap_or("close")).len()
10168 }
10169 _ => {
10170 return Err(IndicatorDispatchError::MissingRequiredInput {
10171 indicator: "mesa_stochastic_multi_length".to_string(),
10172 input: IndicatorInputKind::Candles,
10173 })
10174 }
10175 };
10176 let kernel = req.kernel.to_non_batch();
10177 collect_f64(
10178 "mesa_stochastic_multi_length",
10179 output_id,
10180 req.combos,
10181 data_len,
10182 |params| {
10183 let source = get_enum_param("mesa_stochastic_multi_length", params, "source", "close")?;
10184 let length_1 = get_usize_param("mesa_stochastic_multi_length", params, "length_1", 48)?;
10185 let length_2 = get_usize_param("mesa_stochastic_multi_length", params, "length_2", 21)?;
10186 let length_3 = get_usize_param("mesa_stochastic_multi_length", params, "length_3", 9)?;
10187 let length_4 = get_usize_param("mesa_stochastic_multi_length", params, "length_4", 6)?;
10188 let trigger_length =
10189 get_usize_param("mesa_stochastic_multi_length", params, "trigger_length", 2)?;
10190 let data = match req.data {
10191 IndicatorDataRef::Slice { values } => values,
10192 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
10193 _ => unreachable!(),
10194 };
10195 let input = MesaStochasticMultiLengthInput::from_slices(
10196 data,
10197 MesaStochasticMultiLengthParams {
10198 length_1: Some(length_1),
10199 length_2: Some(length_2),
10200 length_3: Some(length_3),
10201 length_4: Some(length_4),
10202 trigger_length: Some(trigger_length),
10203 },
10204 );
10205 let out = mesa_stochastic_multi_length_with_kernel(&input, kernel).map_err(|e| {
10206 IndicatorDispatchError::ComputeFailed {
10207 indicator: "mesa_stochastic_multi_length".to_string(),
10208 details: e.to_string(),
10209 }
10210 })?;
10211 if output_id.eq_ignore_ascii_case("mesa_1") {
10212 return Ok(out.mesa_1);
10213 }
10214 if output_id.eq_ignore_ascii_case("mesa_2") {
10215 return Ok(out.mesa_2);
10216 }
10217 if output_id.eq_ignore_ascii_case("mesa_3") {
10218 return Ok(out.mesa_3);
10219 }
10220 if output_id.eq_ignore_ascii_case("mesa_4") {
10221 return Ok(out.mesa_4);
10222 }
10223 if output_id.eq_ignore_ascii_case("trigger_1") {
10224 return Ok(out.trigger_1);
10225 }
10226 if output_id.eq_ignore_ascii_case("trigger_2") {
10227 return Ok(out.trigger_2);
10228 }
10229 if output_id.eq_ignore_ascii_case("trigger_3") {
10230 return Ok(out.trigger_3);
10231 }
10232 if output_id.eq_ignore_ascii_case("trigger_4") {
10233 return Ok(out.trigger_4);
10234 }
10235 Err(IndicatorDispatchError::UnknownOutput {
10236 indicator: "mesa_stochastic_multi_length".to_string(),
10237 output: output_id.to_string(),
10238 })
10239 },
10240 )
10241}
10242
10243fn compute_spearman_correlation_batch(
10244 req: IndicatorBatchRequest<'_>,
10245 output_id: &str,
10246) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10247 let data_len = match req.data {
10248 IndicatorDataRef::Candles { candles, source } => {
10249 source_type(candles, source.unwrap_or("close")).len()
10250 }
10251 _ => {
10252 return Err(IndicatorDispatchError::MissingRequiredInput {
10253 indicator: "spearman_correlation".to_string(),
10254 input: IndicatorInputKind::Candles,
10255 })
10256 }
10257 };
10258 let kernel = req.kernel.to_non_batch();
10259 collect_f64(
10260 "spearman_correlation",
10261 output_id,
10262 req.combos,
10263 data_len,
10264 |params| {
10265 let source = get_enum_param("spearman_correlation", params, "source", "close")?;
10266 let comparison_source =
10267 get_enum_param("spearman_correlation", params, "comparison_source", "open")?;
10268 let lookback = get_usize_param("spearman_correlation", params, "lookback", 30)?;
10269 let smoothing_length =
10270 get_usize_param("spearman_correlation", params, "smoothing_length", 3)?;
10271 let (main, compare) = match req.data {
10272 IndicatorDataRef::Candles { candles, .. } => (
10273 source_type(candles, &source),
10274 source_type(candles, &comparison_source),
10275 ),
10276 _ => unreachable!(),
10277 };
10278 let input = SpearmanCorrelationInput::from_slices(
10279 main,
10280 compare,
10281 SpearmanCorrelationParams {
10282 lookback: Some(lookback),
10283 smoothing_length: Some(smoothing_length),
10284 },
10285 );
10286 let out = spearman_correlation_with_kernel(&input, kernel).map_err(|e| {
10287 IndicatorDispatchError::ComputeFailed {
10288 indicator: "spearman_correlation".to_string(),
10289 details: e.to_string(),
10290 }
10291 })?;
10292 if output_id.eq_ignore_ascii_case("raw") || output_id.eq_ignore_ascii_case("value") {
10293 return Ok(out.raw);
10294 }
10295 if output_id.eq_ignore_ascii_case("smoothed") {
10296 return Ok(out.smoothed);
10297 }
10298 Err(IndicatorDispatchError::UnknownOutput {
10299 indicator: "spearman_correlation".to_string(),
10300 output: output_id.to_string(),
10301 })
10302 },
10303 )
10304}
10305
10306fn compute_relative_strength_index_wave_indicator_batch(
10307 req: IndicatorBatchRequest<'_>,
10308 output_id: &str,
10309) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10310 let data_len = match req.data {
10311 IndicatorDataRef::Candles { candles, source } => {
10312 source_type(candles, source.unwrap_or("close")).len()
10313 }
10314 _ => {
10315 return Err(IndicatorDispatchError::MissingRequiredInput {
10316 indicator: "relative_strength_index_wave_indicator".to_string(),
10317 input: IndicatorInputKind::Candles,
10318 })
10319 }
10320 };
10321 let kernel = req.kernel.to_non_batch();
10322 collect_f64(
10323 "relative_strength_index_wave_indicator",
10324 output_id,
10325 req.combos,
10326 data_len,
10327 |params| {
10328 let source = get_enum_param(
10329 "relative_strength_index_wave_indicator",
10330 params,
10331 "source",
10332 "close",
10333 )?;
10334 let rsi_length = get_usize_param(
10335 "relative_strength_index_wave_indicator",
10336 params,
10337 "rsi_length",
10338 14,
10339 )?;
10340 let length1 = get_usize_param(
10341 "relative_strength_index_wave_indicator",
10342 params,
10343 "length1",
10344 2,
10345 )?;
10346 let length2 = get_usize_param(
10347 "relative_strength_index_wave_indicator",
10348 params,
10349 "length2",
10350 5,
10351 )?;
10352 let length3 = get_usize_param(
10353 "relative_strength_index_wave_indicator",
10354 params,
10355 "length3",
10356 9,
10357 )?;
10358 let length4 = get_usize_param(
10359 "relative_strength_index_wave_indicator",
10360 params,
10361 "length4",
10362 13,
10363 )?;
10364 let (src, high, low) = match req.data {
10365 IndicatorDataRef::Candles { candles, .. } => (
10366 source_type(candles, &source),
10367 candles.high.as_slice(),
10368 candles.low.as_slice(),
10369 ),
10370 _ => unreachable!(),
10371 };
10372 let input = RelativeStrengthIndexWaveIndicatorInput::from_slices(
10373 src,
10374 high,
10375 low,
10376 RelativeStrengthIndexWaveIndicatorParams {
10377 rsi_length: Some(rsi_length),
10378 length1: Some(length1),
10379 length2: Some(length2),
10380 length3: Some(length3),
10381 length4: Some(length4),
10382 },
10383 );
10384 let out = relative_strength_index_wave_indicator_with_kernel(&input, kernel).map_err(
10385 |e| IndicatorDispatchError::ComputeFailed {
10386 indicator: "relative_strength_index_wave_indicator".to_string(),
10387 details: e.to_string(),
10388 },
10389 )?;
10390 if output_id.eq_ignore_ascii_case("rsi_ma1") || output_id.eq_ignore_ascii_case("value")
10391 {
10392 return Ok(out.rsi_ma1);
10393 }
10394 if output_id.eq_ignore_ascii_case("rsi_ma2") {
10395 return Ok(out.rsi_ma2);
10396 }
10397 if output_id.eq_ignore_ascii_case("rsi_ma3") {
10398 return Ok(out.rsi_ma3);
10399 }
10400 if output_id.eq_ignore_ascii_case("rsi_ma4") {
10401 return Ok(out.rsi_ma4);
10402 }
10403 if output_id.eq_ignore_ascii_case("state") {
10404 return Ok(out.state);
10405 }
10406 Err(IndicatorDispatchError::UnknownOutput {
10407 indicator: "relative_strength_index_wave_indicator".to_string(),
10408 output: output_id.to_string(),
10409 })
10410 },
10411 )
10412}
10413
10414fn compute_accumulation_swing_index_batch(
10415 req: IndicatorBatchRequest<'_>,
10416 output_id: &str,
10417) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10418 expect_value_output("accumulation_swing_index", output_id)?;
10419 let (open, high, low, close) = extract_ohlc_full_input("accumulation_swing_index", req.data)?;
10420 let kernel = req.kernel.to_non_batch();
10421 collect_f64(
10422 "accumulation_swing_index",
10423 output_id,
10424 req.combos,
10425 close.len(),
10426 |params| {
10427 let daily_limit =
10428 get_f64_param("accumulation_swing_index", params, "daily_limit", 10_000.0)?;
10429 let input = AccumulationSwingIndexInput::from_slices(
10430 open,
10431 high,
10432 low,
10433 close,
10434 AccumulationSwingIndexParams {
10435 daily_limit: Some(daily_limit),
10436 },
10437 );
10438 let out = accumulation_swing_index_with_kernel(&input, kernel).map_err(|e| {
10439 IndicatorDispatchError::ComputeFailed {
10440 indicator: "accumulation_swing_index".to_string(),
10441 details: e.to_string(),
10442 }
10443 })?;
10444 Ok(out.values)
10445 },
10446 )
10447}
10448
10449fn compute_ichimoku_oscillator_batch(
10450 req: IndicatorBatchRequest<'_>,
10451 output_id: &str,
10452) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10453 let (high, low, close) = extract_ohlc_input("ichimoku_oscillator", req.data)?;
10454 let kernel = req.kernel.to_non_batch();
10455 collect_f64(
10456 "ichimoku_oscillator",
10457 output_id,
10458 req.combos,
10459 close.len(),
10460 |params| {
10461 let source_name = get_enum_param("ichimoku_oscillator", params, "source", "close")?;
10462 let conversion_periods =
10463 get_usize_param("ichimoku_oscillator", params, "conversion_periods", 9)?;
10464 let base_periods = get_usize_param("ichimoku_oscillator", params, "base_periods", 26)?;
10465 let lagging_span_periods =
10466 get_usize_param("ichimoku_oscillator", params, "lagging_span_periods", 52)?;
10467 let displacement = get_usize_param("ichimoku_oscillator", params, "displacement", 26)?;
10468 let ma_length = get_usize_param("ichimoku_oscillator", params, "ma_length", 12)?;
10469 let smoothing_length =
10470 get_usize_param("ichimoku_oscillator", params, "smoothing_length", 3)?;
10471 let extra_smoothing =
10472 get_bool_param("ichimoku_oscillator", params, "extra_smoothing", true)?;
10473 let normalize = get_enum_param("ichimoku_oscillator", params, "normalize", "window")?
10474 .parse::<IchimokuOscillatorNormalizeMode>()
10475 .map_err(|e| IndicatorDispatchError::InvalidParam {
10476 indicator: "ichimoku_oscillator".to_string(),
10477 key: "normalize".to_string(),
10478 reason: e,
10479 })?;
10480 let window_size = get_usize_param("ichimoku_oscillator", params, "window_size", 20)?;
10481 let clamp = get_bool_param("ichimoku_oscillator", params, "clamp", true)?;
10482 let top_band = get_f64_param("ichimoku_oscillator", params, "top_band", 2.0)?;
10483 let mid_band = get_f64_param("ichimoku_oscillator", params, "mid_band", 1.5)?;
10484 let source = match req.data {
10485 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source_name),
10486 _ => close,
10487 };
10488 let input = IchimokuOscillatorInput::from_slices(
10489 high,
10490 low,
10491 close,
10492 source,
10493 IchimokuOscillatorParams {
10494 conversion_periods: Some(conversion_periods),
10495 base_periods: Some(base_periods),
10496 lagging_span_periods: Some(lagging_span_periods),
10497 displacement: Some(displacement),
10498 ma_length: Some(ma_length),
10499 smoothing_length: Some(smoothing_length),
10500 extra_smoothing: Some(extra_smoothing),
10501 normalize: Some(normalize),
10502 window_size: Some(window_size),
10503 clamp: Some(clamp),
10504 top_band: Some(top_band),
10505 mid_band: Some(mid_band),
10506 },
10507 );
10508 let out = ichimoku_oscillator_with_kernel(&input, kernel).map_err(|e| {
10509 IndicatorDispatchError::ComputeFailed {
10510 indicator: "ichimoku_oscillator".to_string(),
10511 details: e.to_string(),
10512 }
10513 })?;
10514 if output_id.eq_ignore_ascii_case("signal") || output_id.eq_ignore_ascii_case("value") {
10515 return Ok(out.signal);
10516 }
10517 if output_id.eq_ignore_ascii_case("ma") {
10518 return Ok(out.ma);
10519 }
10520 if output_id.eq_ignore_ascii_case("conversion") {
10521 return Ok(out.conversion);
10522 }
10523 if output_id.eq_ignore_ascii_case("base") {
10524 return Ok(out.base);
10525 }
10526 if output_id.eq_ignore_ascii_case("chikou") {
10527 return Ok(out.chikou);
10528 }
10529 if output_id.eq_ignore_ascii_case("current_kumo_a") {
10530 return Ok(out.current_kumo_a);
10531 }
10532 if output_id.eq_ignore_ascii_case("current_kumo_b") {
10533 return Ok(out.current_kumo_b);
10534 }
10535 if output_id.eq_ignore_ascii_case("future_kumo_a") {
10536 return Ok(out.future_kumo_a);
10537 }
10538 if output_id.eq_ignore_ascii_case("future_kumo_b") {
10539 return Ok(out.future_kumo_b);
10540 }
10541 if output_id.eq_ignore_ascii_case("max_level") {
10542 return Ok(out.max_level);
10543 }
10544 if output_id.eq_ignore_ascii_case("high_level") {
10545 return Ok(out.high_level);
10546 }
10547 if output_id.eq_ignore_ascii_case("low_level") {
10548 return Ok(out.low_level);
10549 }
10550 if output_id.eq_ignore_ascii_case("min_level") {
10551 return Ok(out.min_level);
10552 }
10553 Err(IndicatorDispatchError::UnknownOutput {
10554 indicator: "ichimoku_oscillator".to_string(),
10555 output: output_id.to_string(),
10556 })
10557 },
10558 )
10559}
10560
10561fn compute_volatility_quality_index_batch(
10562 req: IndicatorBatchRequest<'_>,
10563 output_id: &str,
10564) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10565 let (open, high, low, close) = extract_ohlc_full_input("volatility_quality_index", req.data)?;
10566 let kernel = req.kernel.to_non_batch();
10567 collect_f64(
10568 "volatility_quality_index",
10569 output_id,
10570 req.combos,
10571 close.len(),
10572 |params| {
10573 let fast_length =
10574 get_usize_param("volatility_quality_index", params, "fast_length", 9)?;
10575 let slow_length =
10576 get_usize_param("volatility_quality_index", params, "slow_length", 200)?;
10577 let input = VolatilityQualityIndexInput::from_slices(
10578 open,
10579 high,
10580 low,
10581 close,
10582 VolatilityQualityIndexParams {
10583 fast_length: Some(fast_length),
10584 slow_length: Some(slow_length),
10585 },
10586 );
10587 let out = volatility_quality_index_with_kernel(&input, kernel).map_err(|e| {
10588 IndicatorDispatchError::ComputeFailed {
10589 indicator: "volatility_quality_index".to_string(),
10590 details: e.to_string(),
10591 }
10592 })?;
10593 if output_id.eq_ignore_ascii_case("vqi_sum") || output_id.eq_ignore_ascii_case("value")
10594 {
10595 return Ok(out.vqi_sum);
10596 }
10597 if output_id.eq_ignore_ascii_case("fast_sma") {
10598 return Ok(out.fast_sma);
10599 }
10600 if output_id.eq_ignore_ascii_case("slow_sma") {
10601 return Ok(out.slow_sma);
10602 }
10603 Err(IndicatorDispatchError::UnknownOutput {
10604 indicator: "volatility_quality_index".to_string(),
10605 output: output_id.to_string(),
10606 })
10607 },
10608 )
10609}
10610
10611fn compute_vwap_deviation_oscillator_batch(
10612 req: IndicatorBatchRequest<'_>,
10613 output_id: &str,
10614) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10615 let (timestamps, high, low, close, volume): (&[i64], &[f64], &[f64], &[f64], &[f64]) =
10616 match req.data {
10617 IndicatorDataRef::Candles { candles, .. } => (
10618 candles.timestamp.as_slice(),
10619 candles.high.as_slice(),
10620 candles.low.as_slice(),
10621 candles.close.as_slice(),
10622 candles.volume.as_slice(),
10623 ),
10624 _ => {
10625 return Err(IndicatorDispatchError::MissingRequiredInput {
10626 indicator: "vwap_deviation_oscillator".to_string(),
10627 input: IndicatorInputKind::Candles,
10628 })
10629 }
10630 };
10631 let kernel = req.kernel.to_non_batch();
10632 collect_f64(
10633 "vwap_deviation_oscillator",
10634 output_id,
10635 req.combos,
10636 close.len(),
10637 |params| {
10638 let session_mode = get_enum_param(
10639 "vwap_deviation_oscillator",
10640 params,
10641 "session_mode",
10642 "rolling_bars",
10643 )?
10644 .parse::<VwapDeviationSessionMode>()
10645 .map_err(|e| IndicatorDispatchError::InvalidParam {
10646 indicator: "vwap_deviation_oscillator".to_string(),
10647 key: "session_mode".to_string(),
10648 reason: e,
10649 })?;
10650 let rolling_period =
10651 get_usize_param("vwap_deviation_oscillator", params, "rolling_period", 20)?;
10652 let rolling_days =
10653 get_usize_param("vwap_deviation_oscillator", params, "rolling_days", 30)?;
10654 let use_close =
10655 get_bool_param("vwap_deviation_oscillator", params, "use_close", false)?;
10656 let deviation_mode = get_enum_param(
10657 "vwap_deviation_oscillator",
10658 params,
10659 "deviation_mode",
10660 "absolute",
10661 )?
10662 .parse::<VwapDeviationMode>()
10663 .map_err(|e| IndicatorDispatchError::InvalidParam {
10664 indicator: "vwap_deviation_oscillator".to_string(),
10665 key: "deviation_mode".to_string(),
10666 reason: e,
10667 })?;
10668 let z_window = get_usize_param("vwap_deviation_oscillator", params, "z_window", 50)?;
10669 let pct_vol_lookback =
10670 get_usize_param("vwap_deviation_oscillator", params, "pct_vol_lookback", 100)?;
10671 let pct_min_sigma =
10672 get_f64_param("vwap_deviation_oscillator", params, "pct_min_sigma", 0.1)?;
10673 let abs_vol_lookback =
10674 get_usize_param("vwap_deviation_oscillator", params, "abs_vol_lookback", 100)?;
10675 let input = VwapDeviationOscillatorInput::from_slices(
10676 timestamps,
10677 high,
10678 low,
10679 close,
10680 volume,
10681 VwapDeviationOscillatorParams {
10682 session_mode: Some(session_mode),
10683 rolling_period: Some(rolling_period),
10684 rolling_days: Some(rolling_days),
10685 use_close: Some(use_close),
10686 deviation_mode: Some(deviation_mode),
10687 z_window: Some(z_window),
10688 pct_vol_lookback: Some(pct_vol_lookback),
10689 pct_min_sigma: Some(pct_min_sigma),
10690 abs_vol_lookback: Some(abs_vol_lookback),
10691 },
10692 );
10693 let out = vwap_deviation_oscillator_with_kernel(&input, kernel).map_err(|e| {
10694 IndicatorDispatchError::ComputeFailed {
10695 indicator: "vwap_deviation_oscillator".to_string(),
10696 details: e.to_string(),
10697 }
10698 })?;
10699 if output_id.eq_ignore_ascii_case("osc") || output_id.eq_ignore_ascii_case("value") {
10700 return Ok(out.osc);
10701 }
10702 if output_id.eq_ignore_ascii_case("std1") {
10703 return Ok(out.std1);
10704 }
10705 if output_id.eq_ignore_ascii_case("std2") {
10706 return Ok(out.std2);
10707 }
10708 if output_id.eq_ignore_ascii_case("std3") {
10709 return Ok(out.std3);
10710 }
10711 Err(IndicatorDispatchError::UnknownOutput {
10712 indicator: "vwap_deviation_oscillator".to_string(),
10713 output: output_id.to_string(),
10714 })
10715 },
10716 )
10717}
10718
10719fn compute_bulls_v_bears_batch(
10720 req: IndicatorBatchRequest<'_>,
10721 output_id: &str,
10722) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10723 let (high, low, close) = extract_ohlc_input("bulls_v_bears", req.data)?;
10724 let kernel = req.kernel.to_non_batch();
10725 collect_f64(
10726 "bulls_v_bears",
10727 output_id,
10728 req.combos,
10729 close.len(),
10730 |params| {
10731 let period = get_usize_param("bulls_v_bears", params, "period", 14)?;
10732 let ma_type = get_enum_param("bulls_v_bears", params, "ma_type", "ema")?
10733 .parse::<BullsVBearsMaType>()
10734 .map_err(|e| IndicatorDispatchError::InvalidParam {
10735 indicator: "bulls_v_bears".to_string(),
10736 key: "ma_type".to_string(),
10737 reason: e,
10738 })?;
10739 let calculation_method =
10740 get_enum_param("bulls_v_bears", params, "calculation_method", "normalized")?
10741 .parse::<BullsVBearsCalculationMethod>()
10742 .map_err(|e| IndicatorDispatchError::InvalidParam {
10743 indicator: "bulls_v_bears".to_string(),
10744 key: "calculation_method".to_string(),
10745 reason: e,
10746 })?;
10747 let normalized_bars_back =
10748 get_usize_param("bulls_v_bears", params, "normalized_bars_back", 120)?;
10749 let raw_rolling_period =
10750 get_usize_param("bulls_v_bears", params, "raw_rolling_period", 50)?;
10751 let raw_threshold_percentile =
10752 get_f64_param("bulls_v_bears", params, "raw_threshold_percentile", 95.0)?;
10753 let threshold_level = get_f64_param("bulls_v_bears", params, "threshold_level", 80.0)?;
10754 let input = BullsVBearsInput::from_slices(
10755 high,
10756 low,
10757 close,
10758 BullsVBearsParams {
10759 period: Some(period),
10760 ma_type: Some(ma_type),
10761 calculation_method: Some(calculation_method),
10762 normalized_bars_back: Some(normalized_bars_back),
10763 raw_rolling_period: Some(raw_rolling_period),
10764 raw_threshold_percentile: Some(raw_threshold_percentile),
10765 threshold_level: Some(threshold_level),
10766 },
10767 );
10768 let out = bulls_v_bears_with_kernel(&input, kernel).map_err(|e| {
10769 IndicatorDispatchError::ComputeFailed {
10770 indicator: "bulls_v_bears".to_string(),
10771 details: e.to_string(),
10772 }
10773 })?;
10774 if output_id.eq_ignore_ascii_case("value") {
10775 return Ok(out.value);
10776 }
10777 if output_id.eq_ignore_ascii_case("bull") {
10778 return Ok(out.bull);
10779 }
10780 if output_id.eq_ignore_ascii_case("bear") {
10781 return Ok(out.bear);
10782 }
10783 if output_id.eq_ignore_ascii_case("ma") {
10784 return Ok(out.ma);
10785 }
10786 if output_id.eq_ignore_ascii_case("upper") {
10787 return Ok(out.upper);
10788 }
10789 if output_id.eq_ignore_ascii_case("lower") {
10790 return Ok(out.lower);
10791 }
10792 if output_id.eq_ignore_ascii_case("bullish_signal") {
10793 return Ok(out.bullish_signal);
10794 }
10795 if output_id.eq_ignore_ascii_case("bearish_signal") {
10796 return Ok(out.bearish_signal);
10797 }
10798 if output_id.eq_ignore_ascii_case("zero_cross_up") {
10799 return Ok(out.zero_cross_up);
10800 }
10801 if output_id.eq_ignore_ascii_case("zero_cross_down") {
10802 return Ok(out.zero_cross_down);
10803 }
10804 Err(IndicatorDispatchError::UnknownOutput {
10805 indicator: "bulls_v_bears".to_string(),
10806 output: output_id.to_string(),
10807 })
10808 },
10809 )
10810}
10811
10812fn compute_smooth_theil_sen_batch(
10813 req: IndicatorBatchRequest<'_>,
10814 output_id: &str,
10815) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10816 let data = extract_slice_input("smooth_theil_sen", req.data, "close")?;
10817 let kernel = req.kernel.to_non_batch();
10818 collect_f64(
10819 "smooth_theil_sen",
10820 output_id,
10821 req.combos,
10822 data.len(),
10823 |params| {
10824 let length = get_usize_param("smooth_theil_sen", params, "length", 25)?;
10825 let offset = get_usize_param("smooth_theil_sen", params, "offset", 0)?;
10826 let multiplier = get_f64_param("smooth_theil_sen", params, "multiplier", 2.0)?;
10827 let slope_style =
10828 get_enum_param("smooth_theil_sen", params, "slope_style", "smooth_median")?
10829 .parse::<SmoothTheilSenStatStyle>()
10830 .map_err(|e| IndicatorDispatchError::InvalidParam {
10831 indicator: "smooth_theil_sen".to_string(),
10832 key: "slope_style".to_string(),
10833 reason: e,
10834 })?;
10835 let residual_style = get_enum_param(
10836 "smooth_theil_sen",
10837 params,
10838 "residual_style",
10839 "smooth_median",
10840 )?
10841 .parse::<SmoothTheilSenStatStyle>()
10842 .map_err(|e| IndicatorDispatchError::InvalidParam {
10843 indicator: "smooth_theil_sen".to_string(),
10844 key: "residual_style".to_string(),
10845 reason: e,
10846 })?;
10847 let deviation_style =
10848 get_enum_param("smooth_theil_sen", params, "deviation_style", "mad")?
10849 .parse::<SmoothTheilSenDeviationType>()
10850 .map_err(|e| IndicatorDispatchError::InvalidParam {
10851 indicator: "smooth_theil_sen".to_string(),
10852 key: "deviation_style".to_string(),
10853 reason: e,
10854 })?;
10855 let mad_style =
10856 get_enum_param("smooth_theil_sen", params, "mad_style", "smooth_median")?
10857 .parse::<SmoothTheilSenStatStyle>()
10858 .map_err(|e| IndicatorDispatchError::InvalidParam {
10859 indicator: "smooth_theil_sen".to_string(),
10860 key: "mad_style".to_string(),
10861 reason: e,
10862 })?;
10863 let include_prediction_in_deviation = get_bool_param(
10864 "smooth_theil_sen",
10865 params,
10866 "include_prediction_in_deviation",
10867 false,
10868 )?;
10869 let input = SmoothTheilSenInput::from_slice(
10870 data,
10871 SmoothTheilSenParams {
10872 length: Some(length),
10873 offset: Some(offset),
10874 multiplier: Some(multiplier),
10875 slope_style: Some(slope_style),
10876 residual_style: Some(residual_style),
10877 deviation_style: Some(deviation_style),
10878 mad_style: Some(mad_style),
10879 include_prediction_in_deviation: Some(include_prediction_in_deviation),
10880 },
10881 );
10882 let out = smooth_theil_sen_with_kernel(&input, kernel).map_err(|e| {
10883 IndicatorDispatchError::ComputeFailed {
10884 indicator: "smooth_theil_sen".to_string(),
10885 details: e.to_string(),
10886 }
10887 })?;
10888 if output_id.eq_ignore_ascii_case("value") {
10889 return Ok(out.value);
10890 }
10891 if output_id.eq_ignore_ascii_case("upper") {
10892 return Ok(out.upper);
10893 }
10894 if output_id.eq_ignore_ascii_case("lower") {
10895 return Ok(out.lower);
10896 }
10897 if output_id.eq_ignore_ascii_case("slope") {
10898 return Ok(out.slope);
10899 }
10900 if output_id.eq_ignore_ascii_case("intercept") {
10901 return Ok(out.intercept);
10902 }
10903 if output_id.eq_ignore_ascii_case("deviation") {
10904 return Ok(out.deviation);
10905 }
10906 Err(IndicatorDispatchError::UnknownOutput {
10907 indicator: "smooth_theil_sen".to_string(),
10908 output: output_id.to_string(),
10909 })
10910 },
10911 )
10912}
10913
10914fn compute_regression_slope_oscillator_batch(
10915 req: IndicatorBatchRequest<'_>,
10916 output_id: &str,
10917) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10918 let data_len = match req.data {
10919 IndicatorDataRef::Candles { candles, .. } => candles.close.len(),
10920 IndicatorDataRef::Slice { values } => values.len(),
10921 _ => {
10922 return Err(IndicatorDispatchError::MissingRequiredInput {
10923 indicator: "regression_slope_oscillator".to_string(),
10924 input: IndicatorInputKind::Slice,
10925 })
10926 }
10927 };
10928 let kernel = req.kernel.to_non_batch();
10929 collect_f64(
10930 "regression_slope_oscillator",
10931 output_id,
10932 req.combos,
10933 data_len,
10934 |params| {
10935 let min_range =
10936 get_usize_param("regression_slope_oscillator", params, "min_range", 10)?;
10937 let max_range =
10938 get_usize_param("regression_slope_oscillator", params, "max_range", 100)?;
10939 let step = get_usize_param("regression_slope_oscillator", params, "step", 5)?;
10940 let signal_line =
10941 get_usize_param("regression_slope_oscillator", params, "signal_line", 7)?;
10942 let input = match req.data {
10943 IndicatorDataRef::Candles { candles, .. } => {
10944 RegressionSlopeOscillatorInput::from_candles(
10945 candles,
10946 RegressionSlopeOscillatorParams {
10947 min_range: Some(min_range),
10948 max_range: Some(max_range),
10949 step: Some(step),
10950 signal_line: Some(signal_line),
10951 },
10952 )
10953 }
10954 IndicatorDataRef::Slice { values } => RegressionSlopeOscillatorInput::from_slice(
10955 values,
10956 RegressionSlopeOscillatorParams {
10957 min_range: Some(min_range),
10958 max_range: Some(max_range),
10959 step: Some(step),
10960 signal_line: Some(signal_line),
10961 },
10962 ),
10963 _ => unreachable!(),
10964 };
10965 let out = regression_slope_oscillator_with_kernel(&input, kernel).map_err(|e| {
10966 IndicatorDispatchError::ComputeFailed {
10967 indicator: "regression_slope_oscillator".to_string(),
10968 details: e.to_string(),
10969 }
10970 })?;
10971 if output_id.eq_ignore_ascii_case("value") {
10972 return Ok(out.value);
10973 }
10974 if output_id.eq_ignore_ascii_case("signal") {
10975 return Ok(out.signal);
10976 }
10977 if output_id.eq_ignore_ascii_case("bullish_reversal") {
10978 return Ok(out.bullish_reversal);
10979 }
10980 if output_id.eq_ignore_ascii_case("bearish_reversal") {
10981 return Ok(out.bearish_reversal);
10982 }
10983 Err(IndicatorDispatchError::UnknownOutput {
10984 indicator: "regression_slope_oscillator".to_string(),
10985 output: output_id.to_string(),
10986 })
10987 },
10988 )
10989}
10990
10991fn compute_linear_regression_intensity_batch(
10992 req: IndicatorBatchRequest<'_>,
10993 output_id: &str,
10994) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
10995 expect_value_output("linear_regression_intensity", output_id)?;
10996 let data_len = match req.data {
10997 IndicatorDataRef::Candles { candles, source } => {
10998 source_type(candles, source.unwrap_or("close")).len()
10999 }
11000 IndicatorDataRef::Slice { values } => values.len(),
11001 _ => {
11002 return Err(IndicatorDispatchError::MissingRequiredInput {
11003 indicator: "linear_regression_intensity".to_string(),
11004 input: IndicatorInputKind::Slice,
11005 })
11006 }
11007 };
11008 let kernel = req.kernel.to_non_batch();
11009 collect_f64(
11010 "linear_regression_intensity",
11011 output_id,
11012 req.combos,
11013 data_len,
11014 |params| {
11015 let source = get_enum_param("linear_regression_intensity", params, "source", "close")?;
11016 let lookback_period =
11017 get_usize_param("linear_regression_intensity", params, "lookback_period", 12)?;
11018 let range_tolerance = get_f64_param(
11019 "linear_regression_intensity",
11020 params,
11021 "range_tolerance",
11022 90.0,
11023 )?;
11024 let linreg_length =
11025 get_usize_param("linear_regression_intensity", params, "linreg_length", 90)?;
11026 let input = match req.data {
11027 IndicatorDataRef::Candles { candles, .. } => {
11028 LinearRegressionIntensityInput::from_candles(
11029 candles,
11030 &source,
11031 LinearRegressionIntensityParams {
11032 lookback_period: Some(lookback_period),
11033 range_tolerance: Some(range_tolerance),
11034 linreg_length: Some(linreg_length),
11035 },
11036 )
11037 }
11038 IndicatorDataRef::Slice { values } => LinearRegressionIntensityInput::from_slice(
11039 values,
11040 LinearRegressionIntensityParams {
11041 lookback_period: Some(lookback_period),
11042 range_tolerance: Some(range_tolerance),
11043 linreg_length: Some(linreg_length),
11044 },
11045 ),
11046 _ => unreachable!(),
11047 };
11048 let out = linear_regression_intensity_with_kernel(&input, kernel).map_err(|e| {
11049 IndicatorDispatchError::ComputeFailed {
11050 indicator: "linear_regression_intensity".to_string(),
11051 details: e.to_string(),
11052 }
11053 })?;
11054 Ok(out.values)
11055 },
11056 )
11057}
11058
11059fn compute_moving_average_cross_probability_batch(
11060 req: IndicatorBatchRequest<'_>,
11061 output_id: &str,
11062) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11063 let data_len = match req.data {
11064 IndicatorDataRef::Candles { candles, .. } => candles.close.len(),
11065 IndicatorDataRef::Slice { values } => values.len(),
11066 _ => {
11067 return Err(IndicatorDispatchError::MissingRequiredInput {
11068 indicator: "moving_average_cross_probability".to_string(),
11069 input: IndicatorInputKind::Slice,
11070 })
11071 }
11072 };
11073 let kernel = req.kernel.to_non_batch();
11074 collect_f64(
11075 "moving_average_cross_probability",
11076 output_id,
11077 req.combos,
11078 data_len,
11079 |params| {
11080 let ma_type =
11081 get_enum_param("moving_average_cross_probability", params, "ma_type", "ema")?
11082 .parse::<MovingAverageCrossProbabilityMaType>()
11083 .map_err(|e| IndicatorDispatchError::InvalidParam {
11084 indicator: "moving_average_cross_probability".to_string(),
11085 key: "ma_type".to_string(),
11086 reason: e,
11087 })?;
11088 let smoothing_window = get_usize_param(
11089 "moving_average_cross_probability",
11090 params,
11091 "smoothing_window",
11092 7,
11093 )?;
11094 let slow_length = get_usize_param(
11095 "moving_average_cross_probability",
11096 params,
11097 "slow_length",
11098 30,
11099 )?;
11100 let fast_length = get_usize_param(
11101 "moving_average_cross_probability",
11102 params,
11103 "fast_length",
11104 14,
11105 )?;
11106 let resolution =
11107 get_usize_param("moving_average_cross_probability", params, "resolution", 50)?;
11108 let params = MovingAverageCrossProbabilityParams {
11109 ma_type: Some(ma_type),
11110 smoothing_window: Some(smoothing_window),
11111 slow_length: Some(slow_length),
11112 fast_length: Some(fast_length),
11113 resolution: Some(resolution),
11114 };
11115 let input = match req.data {
11116 IndicatorDataRef::Candles { candles, .. } => {
11117 MovingAverageCrossProbabilityInput::from_candles(candles, params)
11118 }
11119 IndicatorDataRef::Slice { values } => {
11120 MovingAverageCrossProbabilityInput::from_slice(values, params)
11121 }
11122 _ => unreachable!(),
11123 };
11124 let out =
11125 moving_average_cross_probability_with_kernel(&input, kernel).map_err(|e| {
11126 IndicatorDispatchError::ComputeFailed {
11127 indicator: "moving_average_cross_probability".to_string(),
11128 details: e.to_string(),
11129 }
11130 })?;
11131 if output_id.eq_ignore_ascii_case("value") {
11132 return Ok(out.value);
11133 }
11134 if output_id.eq_ignore_ascii_case("slow_ma") {
11135 return Ok(out.slow_ma);
11136 }
11137 if output_id.eq_ignore_ascii_case("fast_ma") {
11138 return Ok(out.fast_ma);
11139 }
11140 if output_id.eq_ignore_ascii_case("forecast") {
11141 return Ok(out.forecast);
11142 }
11143 if output_id.eq_ignore_ascii_case("upper") {
11144 return Ok(out.upper);
11145 }
11146 if output_id.eq_ignore_ascii_case("lower") {
11147 return Ok(out.lower);
11148 }
11149 if output_id.eq_ignore_ascii_case("direction") {
11150 return Ok(out.direction);
11151 }
11152 Err(IndicatorDispatchError::UnknownOutput {
11153 indicator: "moving_average_cross_probability".to_string(),
11154 output: output_id.to_string(),
11155 })
11156 },
11157 )
11158}
11159
11160fn compute_volume_zone_oscillator_batch(
11161 req: IndicatorBatchRequest<'_>,
11162 output_id: &str,
11163) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11164 expect_value_output("volume_zone_oscillator", output_id)?;
11165 let (close, volume) = extract_close_volume_input("volume_zone_oscillator", req.data, "close")?;
11166 let kernel = req.kernel.to_non_batch();
11167 collect_f64(
11168 "volume_zone_oscillator",
11169 output_id,
11170 req.combos,
11171 close.len(),
11172 |params| {
11173 let length = get_usize_param("volume_zone_oscillator", params, "length", 14)?;
11174 let intraday_smoothing =
11175 get_bool_param("volume_zone_oscillator", params, "intraday_smoothing", true)?;
11176 let noise_filter =
11177 get_usize_param("volume_zone_oscillator", params, "noise_filter", 4)?;
11178 let input = VolumeZoneOscillatorInput::from_slices(
11179 close,
11180 volume,
11181 VolumeZoneOscillatorParams {
11182 length: Some(length),
11183 intraday_smoothing: Some(intraday_smoothing),
11184 noise_filter: Some(noise_filter),
11185 },
11186 );
11187 let out = volume_zone_oscillator_with_kernel(&input, kernel).map_err(|e| {
11188 IndicatorDispatchError::ComputeFailed {
11189 indicator: "volume_zone_oscillator".to_string(),
11190 details: e.to_string(),
11191 }
11192 })?;
11193 Ok(out.values)
11194 },
11195 )
11196}
11197
11198fn compute_market_meanness_index_batch(
11199 req: IndicatorBatchRequest<'_>,
11200 output_id: &str,
11201) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11202 let data_len = match req.data {
11203 IndicatorDataRef::Candles { candles, .. } => candles.close.len(),
11204 IndicatorDataRef::Ohlc { close, .. } => close.len(),
11205 IndicatorDataRef::Ohlcv { close, .. } => close.len(),
11206 _ => {
11207 return Err(IndicatorDispatchError::MissingRequiredInput {
11208 indicator: "market_meanness_index".to_string(),
11209 input: IndicatorInputKind::Ohlc,
11210 })
11211 }
11212 };
11213 let kernel = req.kernel.to_non_batch();
11214 collect_f64(
11215 "market_meanness_index",
11216 output_id,
11217 req.combos,
11218 data_len,
11219 |params| {
11220 let length = get_usize_param("market_meanness_index", params, "length", 300)?;
11221 let source_mode =
11222 get_enum_param("market_meanness_index", params, "source_mode", "Price")?;
11223 let input = match req.data {
11224 IndicatorDataRef::Candles { candles, .. } => {
11225 MarketMeannessIndexInput::from_candles(
11226 candles,
11227 MarketMeannessIndexParams {
11228 length: Some(length),
11229 source_mode: Some(source_mode),
11230 },
11231 )
11232 }
11233 IndicatorDataRef::Ohlc { open, close, .. } => {
11234 MarketMeannessIndexInput::from_slices(
11235 open,
11236 close,
11237 MarketMeannessIndexParams {
11238 length: Some(length),
11239 source_mode: Some(source_mode),
11240 },
11241 )
11242 }
11243 IndicatorDataRef::Ohlcv { open, close, .. } => {
11244 MarketMeannessIndexInput::from_slices(
11245 open,
11246 close,
11247 MarketMeannessIndexParams {
11248 length: Some(length),
11249 source_mode: Some(source_mode),
11250 },
11251 )
11252 }
11253 _ => unreachable!(),
11254 };
11255 let out = market_meanness_index_with_kernel(&input, kernel).map_err(|e| {
11256 IndicatorDispatchError::ComputeFailed {
11257 indicator: "market_meanness_index".to_string(),
11258 details: e.to_string(),
11259 }
11260 })?;
11261 if output_id.eq_ignore_ascii_case("mmi") || output_id.eq_ignore_ascii_case("value") {
11262 return Ok(out.mmi);
11263 }
11264 if output_id.eq_ignore_ascii_case("mmi_smoothed") {
11265 return Ok(out.mmi_smoothed);
11266 }
11267 Err(IndicatorDispatchError::UnknownOutput {
11268 indicator: "market_meanness_index".to_string(),
11269 output: output_id.to_string(),
11270 })
11271 },
11272 )
11273}
11274
11275fn compute_momentum_ratio_oscillator_batch(
11276 req: IndicatorBatchRequest<'_>,
11277 output_id: &str,
11278) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11279 let data_len = match req.data {
11280 IndicatorDataRef::Candles { candles, source } => {
11281 source_type(candles, source.unwrap_or("close")).len()
11282 }
11283 IndicatorDataRef::Slice { values } => values.len(),
11284 _ => {
11285 return Err(IndicatorDispatchError::MissingRequiredInput {
11286 indicator: "momentum_ratio_oscillator".to_string(),
11287 input: IndicatorInputKind::Slice,
11288 })
11289 }
11290 };
11291 let kernel = req.kernel.to_non_batch();
11292 collect_f64(
11293 "momentum_ratio_oscillator",
11294 output_id,
11295 req.combos,
11296 data_len,
11297 |params| {
11298 let source = get_enum_param("momentum_ratio_oscillator", params, "source", "close")?;
11299 let period = get_usize_param("momentum_ratio_oscillator", params, "period", 50)?;
11300 let input = match req.data {
11301 IndicatorDataRef::Candles { candles, .. } => {
11302 MomentumRatioOscillatorInput::from_candles(
11303 candles,
11304 &source,
11305 MomentumRatioOscillatorParams {
11306 period: Some(period),
11307 },
11308 )
11309 }
11310 IndicatorDataRef::Slice { values } => MomentumRatioOscillatorInput::from_slice(
11311 values,
11312 MomentumRatioOscillatorParams {
11313 period: Some(period),
11314 },
11315 ),
11316 _ => unreachable!(),
11317 };
11318 let out = momentum_ratio_oscillator_with_kernel(&input, kernel).map_err(|e| {
11319 IndicatorDispatchError::ComputeFailed {
11320 indicator: "momentum_ratio_oscillator".to_string(),
11321 details: e.to_string(),
11322 }
11323 })?;
11324 if output_id.eq_ignore_ascii_case("line") || output_id.eq_ignore_ascii_case("value") {
11325 return Ok(out.line);
11326 }
11327 if output_id.eq_ignore_ascii_case("signal") {
11328 return Ok(out.signal);
11329 }
11330 Err(IndicatorDispatchError::UnknownOutput {
11331 indicator: "momentum_ratio_oscillator".to_string(),
11332 output: output_id.to_string(),
11333 })
11334 },
11335 )
11336}
11337
11338fn compute_pretty_good_oscillator_batch(
11339 req: IndicatorBatchRequest<'_>,
11340 output_id: &str,
11341) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11342 expect_value_output("pretty_good_oscillator", output_id)?;
11343 let data_len = match req.data {
11344 IndicatorDataRef::Candles { candles, source } => {
11345 source_type(candles, source.unwrap_or("close")).len()
11346 }
11347 IndicatorDataRef::Ohlc { close, .. } => close.len(),
11348 IndicatorDataRef::Ohlcv { close, .. } => close.len(),
11349 _ => {
11350 return Err(IndicatorDispatchError::MissingRequiredInput {
11351 indicator: "pretty_good_oscillator".to_string(),
11352 input: IndicatorInputKind::Ohlc,
11353 })
11354 }
11355 };
11356 let kernel = req.kernel.to_non_batch();
11357 collect_f64(
11358 "pretty_good_oscillator",
11359 output_id,
11360 req.combos,
11361 data_len,
11362 |params| {
11363 let source = get_enum_param("pretty_good_oscillator", params, "source", "close")?;
11364 let length = get_usize_param("pretty_good_oscillator", params, "length", 14)?;
11365 let input = match req.data {
11366 IndicatorDataRef::Candles { candles, .. } => {
11367 PrettyGoodOscillatorInput::from_candles(
11368 candles,
11369 &source,
11370 PrettyGoodOscillatorParams {
11371 length: Some(length),
11372 },
11373 )
11374 }
11375 IndicatorDataRef::Ohlc {
11376 high,
11377 low,
11378 close,
11379 open,
11380 } => {
11381 ensure_same_len_4(
11382 "pretty_good_oscillator",
11383 open.len(),
11384 high.len(),
11385 low.len(),
11386 close.len(),
11387 )?;
11388 let src = match source.to_ascii_lowercase().as_str() {
11389 "open" => open,
11390 "high" => high,
11391 "low" => low,
11392 _ => close,
11393 };
11394 PrettyGoodOscillatorInput::from_slices(
11395 high,
11396 low,
11397 close,
11398 src,
11399 PrettyGoodOscillatorParams {
11400 length: Some(length),
11401 },
11402 )
11403 }
11404 IndicatorDataRef::Ohlcv {
11405 high,
11406 low,
11407 close,
11408 open,
11409 volume,
11410 } => {
11411 ensure_same_len_5(
11412 "pretty_good_oscillator",
11413 open.len(),
11414 high.len(),
11415 low.len(),
11416 close.len(),
11417 volume.len(),
11418 )?;
11419 let src = match source.to_ascii_lowercase().as_str() {
11420 "open" => open,
11421 "high" => high,
11422 "low" => low,
11423 _ => close,
11424 };
11425 PrettyGoodOscillatorInput::from_slices(
11426 high,
11427 low,
11428 close,
11429 src,
11430 PrettyGoodOscillatorParams {
11431 length: Some(length),
11432 },
11433 )
11434 }
11435 _ => unreachable!(),
11436 };
11437 let out = pretty_good_oscillator_with_kernel(&input, kernel).map_err(|e| {
11438 IndicatorDispatchError::ComputeFailed {
11439 indicator: "pretty_good_oscillator".to_string(),
11440 details: e.to_string(),
11441 }
11442 })?;
11443 Ok(out.values)
11444 },
11445 )
11446}
11447
11448fn compute_price_density_market_noise_batch(
11449 req: IndicatorBatchRequest<'_>,
11450 output_id: &str,
11451) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11452 let (high, low, close) = extract_ohlc_input("price_density_market_noise", req.data)?;
11453 let kernel = req.kernel.to_non_batch();
11454 collect_f64(
11455 "price_density_market_noise",
11456 output_id,
11457 req.combos,
11458 close.len(),
11459 |params| {
11460 let length = get_usize_param("price_density_market_noise", params, "length", 14)?;
11461 let eval_period =
11462 get_usize_param("price_density_market_noise", params, "eval_period", 200)?;
11463 let input = PriceDensityMarketNoiseInput::from_slices(
11464 high,
11465 low,
11466 close,
11467 PriceDensityMarketNoiseParams {
11468 length: Some(length),
11469 eval_period: Some(eval_period),
11470 },
11471 );
11472 let out = price_density_market_noise_with_kernel(&input, kernel).map_err(|e| {
11473 IndicatorDispatchError::ComputeFailed {
11474 indicator: "price_density_market_noise".to_string(),
11475 details: e.to_string(),
11476 }
11477 })?;
11478 if output_id.eq_ignore_ascii_case("price_density")
11479 || output_id.eq_ignore_ascii_case("value")
11480 {
11481 return Ok(out.price_density);
11482 }
11483 if output_id.eq_ignore_ascii_case("price_density_percent") {
11484 return Ok(out.price_density_percent);
11485 }
11486 Err(IndicatorDispatchError::UnknownOutput {
11487 indicator: "price_density_market_noise".to_string(),
11488 output: output_id.to_string(),
11489 })
11490 },
11491 )
11492}
11493
11494fn compute_psychological_line_batch(
11495 req: IndicatorBatchRequest<'_>,
11496 output_id: &str,
11497) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11498 expect_value_output("psychological_line", output_id)?;
11499 let data_len = match req.data {
11500 IndicatorDataRef::Candles { candles, source } => {
11501 source_type(candles, source.unwrap_or("close")).len()
11502 }
11503 IndicatorDataRef::Slice { values } => values.len(),
11504 _ => {
11505 return Err(IndicatorDispatchError::MissingRequiredInput {
11506 indicator: "psychological_line".to_string(),
11507 input: IndicatorInputKind::Slice,
11508 })
11509 }
11510 };
11511 let kernel = req.kernel.to_non_batch();
11512 collect_f64(
11513 "psychological_line",
11514 output_id,
11515 req.combos,
11516 data_len,
11517 |params| {
11518 let source = get_enum_param("psychological_line", params, "source", "close")?;
11519 let length = get_usize_param("psychological_line", params, "length", 20)?;
11520 let input = match req.data {
11521 IndicatorDataRef::Candles { candles, .. } => PsychologicalLineInput::from_candles(
11522 candles,
11523 &source,
11524 PsychologicalLineParams {
11525 length: Some(length),
11526 },
11527 ),
11528 IndicatorDataRef::Slice { values } => PsychologicalLineInput::from_slice(
11529 values,
11530 PsychologicalLineParams {
11531 length: Some(length),
11532 },
11533 ),
11534 _ => unreachable!(),
11535 };
11536 let out = psychological_line_with_kernel(&input, kernel).map_err(|e| {
11537 IndicatorDispatchError::ComputeFailed {
11538 indicator: "psychological_line".to_string(),
11539 details: e.to_string(),
11540 }
11541 })?;
11542 Ok(out.values)
11543 },
11544 )
11545}
11546
11547fn compute_rank_correlation_index_batch(
11548 req: IndicatorBatchRequest<'_>,
11549 output_id: &str,
11550) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11551 expect_value_output("rank_correlation_index", output_id)?;
11552 let data_len = match req.data {
11553 IndicatorDataRef::Candles { candles, source } => {
11554 source_type(candles, source.unwrap_or("close")).len()
11555 }
11556 IndicatorDataRef::Slice { values } => values.len(),
11557 _ => {
11558 return Err(IndicatorDispatchError::MissingRequiredInput {
11559 indicator: "rank_correlation_index".to_string(),
11560 input: IndicatorInputKind::Slice,
11561 })
11562 }
11563 };
11564 let kernel = req.kernel.to_non_batch();
11565 collect_f64(
11566 "rank_correlation_index",
11567 output_id,
11568 req.combos,
11569 data_len,
11570 |params| {
11571 let source = get_enum_param("rank_correlation_index", params, "source", "close")?;
11572 let length = get_usize_param("rank_correlation_index", params, "length", 12)?;
11573 let input = match req.data {
11574 IndicatorDataRef::Candles { candles, .. } => {
11575 RankCorrelationIndexInput::from_candles(
11576 candles,
11577 &source,
11578 RankCorrelationIndexParams {
11579 length: Some(length),
11580 },
11581 )
11582 }
11583 IndicatorDataRef::Slice { values } => RankCorrelationIndexInput::from_slice(
11584 values,
11585 RankCorrelationIndexParams {
11586 length: Some(length),
11587 },
11588 ),
11589 _ => unreachable!(),
11590 };
11591 let out = rank_correlation_index_with_kernel(&input, kernel).map_err(|e| {
11592 IndicatorDispatchError::ComputeFailed {
11593 indicator: "rank_correlation_index".to_string(),
11594 details: e.to_string(),
11595 }
11596 })?;
11597 Ok(out.values)
11598 },
11599 )
11600}
11601
11602fn compute_smoothed_gaussian_trend_filter_batch(
11603 req: IndicatorBatchRequest<'_>,
11604 output_id: &str,
11605) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11606 let (high, low, close) = extract_ohlc_input("smoothed_gaussian_trend_filter", req.data)?;
11607 let kernel = req.kernel.to_non_batch();
11608 collect_f64(
11609 "smoothed_gaussian_trend_filter",
11610 output_id,
11611 req.combos,
11612 close.len(),
11613 |params| {
11614 let gaussian_length = get_usize_param(
11615 "smoothed_gaussian_trend_filter",
11616 params,
11617 "gaussian_length",
11618 15,
11619 )?;
11620 let poles = get_usize_param("smoothed_gaussian_trend_filter", params, "poles", 3)?;
11621 let smoothing_length = get_usize_param(
11622 "smoothed_gaussian_trend_filter",
11623 params,
11624 "smoothing_length",
11625 22,
11626 )?;
11627 let linreg_offset =
11628 get_usize_param("smoothed_gaussian_trend_filter", params, "linreg_offset", 7)?;
11629 let input = SmoothedGaussianTrendFilterInput::from_slices(
11630 high,
11631 low,
11632 close,
11633 SmoothedGaussianTrendFilterParams {
11634 gaussian_length: Some(gaussian_length),
11635 poles: Some(poles),
11636 smoothing_length: Some(smoothing_length),
11637 linreg_offset: Some(linreg_offset),
11638 },
11639 );
11640 let out = smoothed_gaussian_trend_filter_with_kernel(&input, kernel).map_err(|e| {
11641 IndicatorDispatchError::ComputeFailed {
11642 indicator: "smoothed_gaussian_trend_filter".to_string(),
11643 details: e.to_string(),
11644 }
11645 })?;
11646 if output_id.eq_ignore_ascii_case("filter") || output_id.eq_ignore_ascii_case("value") {
11647 return Ok(out.filter);
11648 }
11649 if output_id.eq_ignore_ascii_case("supertrend") {
11650 return Ok(out.supertrend);
11651 }
11652 if output_id.eq_ignore_ascii_case("trend") {
11653 return Ok(out.trend);
11654 }
11655 if output_id.eq_ignore_ascii_case("ranging") {
11656 return Ok(out.ranging);
11657 }
11658 Err(IndicatorDispatchError::UnknownOutput {
11659 indicator: "smoothed_gaussian_trend_filter".to_string(),
11660 output: output_id.to_string(),
11661 })
11662 },
11663 )
11664}
11665
11666fn compute_stochastic_adaptive_d_batch(
11667 req: IndicatorBatchRequest<'_>,
11668 output_id: &str,
11669) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11670 let (high, low, close) = extract_ohlc_input("stochastic_adaptive_d", req.data)?;
11671 let kernel = req.kernel.to_non_batch();
11672 collect_f64(
11673 "stochastic_adaptive_d",
11674 output_id,
11675 req.combos,
11676 close.len(),
11677 |params| {
11678 let k_length = get_usize_param("stochastic_adaptive_d", params, "k_length", 20)?;
11679 let d_smoothing = get_usize_param("stochastic_adaptive_d", params, "d_smoothing", 9)?;
11680 let pre_smooth = get_usize_param("stochastic_adaptive_d", params, "pre_smooth", 20)?;
11681 let attenuation = get_f64_param("stochastic_adaptive_d", params, "attenuation", 2.0)?;
11682 let input = StochasticAdaptiveDInput::from_slices(
11683 high,
11684 low,
11685 close,
11686 StochasticAdaptiveDParams {
11687 k_length: Some(k_length),
11688 d_smoothing: Some(d_smoothing),
11689 pre_smooth: Some(pre_smooth),
11690 attenuation: Some(attenuation),
11691 },
11692 );
11693 let out = stochastic_adaptive_d_with_kernel(&input, kernel).map_err(|e| {
11694 IndicatorDispatchError::ComputeFailed {
11695 indicator: "stochastic_adaptive_d".to_string(),
11696 details: e.to_string(),
11697 }
11698 })?;
11699 if output_id.eq_ignore_ascii_case("standard_d")
11700 || output_id.eq_ignore_ascii_case("value")
11701 {
11702 return Ok(out.standard_d);
11703 }
11704 if output_id.eq_ignore_ascii_case("adaptive_d") {
11705 return Ok(out.adaptive_d);
11706 }
11707 if output_id.eq_ignore_ascii_case("difference") {
11708 return Ok(out.difference);
11709 }
11710 Err(IndicatorDispatchError::UnknownOutput {
11711 indicator: "stochastic_adaptive_d".to_string(),
11712 output: output_id.to_string(),
11713 })
11714 },
11715 )
11716}
11717
11718fn compute_stochastic_connors_rsi_batch(
11719 req: IndicatorBatchRequest<'_>,
11720 output_id: &str,
11721) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11722 let data_len = match req.data {
11723 IndicatorDataRef::Candles { candles, source } => {
11724 source_type(candles, source.unwrap_or("close")).len()
11725 }
11726 IndicatorDataRef::Slice { values } => values.len(),
11727 _ => {
11728 return Err(IndicatorDispatchError::MissingRequiredInput {
11729 indicator: "stochastic_connors_rsi".to_string(),
11730 input: IndicatorInputKind::Slice,
11731 })
11732 }
11733 };
11734 let kernel = req.kernel.to_non_batch();
11735 collect_f64(
11736 "stochastic_connors_rsi",
11737 output_id,
11738 req.combos,
11739 data_len,
11740 |params| {
11741 let source = get_enum_param("stochastic_connors_rsi", params, "source", "close")?;
11742 let stoch_length =
11743 get_usize_param("stochastic_connors_rsi", params, "stoch_length", 3)?;
11744 let smooth_k = get_usize_param("stochastic_connors_rsi", params, "smooth_k", 3)?;
11745 let smooth_d = get_usize_param("stochastic_connors_rsi", params, "smooth_d", 3)?;
11746 let rsi_length = get_usize_param("stochastic_connors_rsi", params, "rsi_length", 3)?;
11747 let updown_length =
11748 get_usize_param("stochastic_connors_rsi", params, "updown_length", 2)?;
11749 let roc_length = get_usize_param("stochastic_connors_rsi", params, "roc_length", 100)?;
11750 let input = match req.data {
11751 IndicatorDataRef::Candles { candles, .. } => {
11752 StochasticConnorsRsiInput::from_candles(
11753 candles,
11754 &source,
11755 StochasticConnorsRsiParams {
11756 stoch_length: Some(stoch_length),
11757 smooth_k: Some(smooth_k),
11758 smooth_d: Some(smooth_d),
11759 rsi_length: Some(rsi_length),
11760 updown_length: Some(updown_length),
11761 roc_length: Some(roc_length),
11762 },
11763 )
11764 }
11765 IndicatorDataRef::Slice { values } => StochasticConnorsRsiInput::from_slice(
11766 values,
11767 StochasticConnorsRsiParams {
11768 stoch_length: Some(stoch_length),
11769 smooth_k: Some(smooth_k),
11770 smooth_d: Some(smooth_d),
11771 rsi_length: Some(rsi_length),
11772 updown_length: Some(updown_length),
11773 roc_length: Some(roc_length),
11774 },
11775 ),
11776 _ => unreachable!(),
11777 };
11778 let out = stochastic_connors_rsi_with_kernel(&input, kernel).map_err(|e| {
11779 IndicatorDispatchError::ComputeFailed {
11780 indicator: "stochastic_connors_rsi".to_string(),
11781 details: e.to_string(),
11782 }
11783 })?;
11784 if output_id.eq_ignore_ascii_case("k") || output_id.eq_ignore_ascii_case("value") {
11785 return Ok(out.k);
11786 }
11787 if output_id.eq_ignore_ascii_case("d") {
11788 return Ok(out.d);
11789 }
11790 Err(IndicatorDispatchError::UnknownOutput {
11791 indicator: "stochastic_connors_rsi".to_string(),
11792 output: output_id.to_string(),
11793 })
11794 },
11795 )
11796}
11797
11798fn compute_supertrend_oscillator_batch(
11799 req: IndicatorBatchRequest<'_>,
11800 output_id: &str,
11801) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11802 let data_len = match req.data {
11803 IndicatorDataRef::Candles { candles, source } => {
11804 source_type(candles, source.unwrap_or("close")).len()
11805 }
11806 IndicatorDataRef::Ohlc { close, .. } => close.len(),
11807 IndicatorDataRef::Ohlcv { close, .. } => close.len(),
11808 _ => {
11809 return Err(IndicatorDispatchError::MissingRequiredInput {
11810 indicator: "supertrend_oscillator".to_string(),
11811 input: IndicatorInputKind::Ohlc,
11812 })
11813 }
11814 };
11815 let kernel = req.kernel.to_non_batch();
11816 collect_f64(
11817 "supertrend_oscillator",
11818 output_id,
11819 req.combos,
11820 data_len,
11821 |params| {
11822 let source = get_enum_param("supertrend_oscillator", params, "source", "close")?;
11823 let length = get_usize_param("supertrend_oscillator", params, "length", 10)?;
11824 let mult = get_f64_param("supertrend_oscillator", params, "mult", 2.0)?;
11825 let smooth = get_usize_param("supertrend_oscillator", params, "smooth", 72)?;
11826 let input = match req.data {
11827 IndicatorDataRef::Candles { candles, .. } => {
11828 SuperTrendOscillatorInput::from_candles(
11829 candles,
11830 &source,
11831 SuperTrendOscillatorParams {
11832 length: Some(length),
11833 mult: Some(mult),
11834 smooth: Some(smooth),
11835 },
11836 )
11837 }
11838 IndicatorDataRef::Ohlc {
11839 high,
11840 low,
11841 close,
11842 open,
11843 } => {
11844 ensure_same_len_4(
11845 "supertrend_oscillator",
11846 open.len(),
11847 high.len(),
11848 low.len(),
11849 close.len(),
11850 )?;
11851 let src = match source.to_ascii_lowercase().as_str() {
11852 "open" => open,
11853 "high" => high,
11854 "low" => low,
11855 _ => close,
11856 };
11857 SuperTrendOscillatorInput::from_slices(
11858 high,
11859 low,
11860 src,
11861 SuperTrendOscillatorParams {
11862 length: Some(length),
11863 mult: Some(mult),
11864 smooth: Some(smooth),
11865 },
11866 )
11867 }
11868 IndicatorDataRef::Ohlcv {
11869 high,
11870 low,
11871 close,
11872 open,
11873 volume,
11874 } => {
11875 ensure_same_len_5(
11876 "supertrend_oscillator",
11877 open.len(),
11878 high.len(),
11879 low.len(),
11880 close.len(),
11881 volume.len(),
11882 )?;
11883 let src = match source.to_ascii_lowercase().as_str() {
11884 "open" => open,
11885 "high" => high,
11886 "low" => low,
11887 _ => close,
11888 };
11889 SuperTrendOscillatorInput::from_slices(
11890 high,
11891 low,
11892 src,
11893 SuperTrendOscillatorParams {
11894 length: Some(length),
11895 mult: Some(mult),
11896 smooth: Some(smooth),
11897 },
11898 )
11899 }
11900 _ => unreachable!(),
11901 };
11902 let out = supertrend_oscillator_with_kernel(&input, kernel).map_err(|e| {
11903 IndicatorDispatchError::ComputeFailed {
11904 indicator: "supertrend_oscillator".to_string(),
11905 details: e.to_string(),
11906 }
11907 })?;
11908 if output_id.eq_ignore_ascii_case("oscillator")
11909 || output_id.eq_ignore_ascii_case("value")
11910 {
11911 return Ok(out.oscillator);
11912 }
11913 if output_id.eq_ignore_ascii_case("signal") {
11914 return Ok(out.signal);
11915 }
11916 if output_id.eq_ignore_ascii_case("histogram") || output_id.eq_ignore_ascii_case("hist")
11917 {
11918 return Ok(out.histogram);
11919 }
11920 Err(IndicatorDispatchError::UnknownOutput {
11921 indicator: "supertrend_oscillator".to_string(),
11922 output: output_id.to_string(),
11923 })
11924 },
11925 )
11926}
11927
11928fn compute_trend_continuation_factor_batch(
11929 req: IndicatorBatchRequest<'_>,
11930 output_id: &str,
11931) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11932 let data_len = match req.data {
11933 IndicatorDataRef::Candles { candles, source } => {
11934 source_type(candles, source.unwrap_or("close")).len()
11935 }
11936 IndicatorDataRef::Slice { values } => values.len(),
11937 _ => {
11938 return Err(IndicatorDispatchError::MissingRequiredInput {
11939 indicator: "trend_continuation_factor".to_string(),
11940 input: IndicatorInputKind::Slice,
11941 })
11942 }
11943 };
11944 let kernel = req.kernel.to_non_batch();
11945 collect_f64(
11946 "trend_continuation_factor",
11947 output_id,
11948 req.combos,
11949 data_len,
11950 |params| {
11951 let source = get_enum_param("trend_continuation_factor", params, "source", "close")?;
11952 let length = get_usize_param("trend_continuation_factor", params, "length", 35)?;
11953 let input = match req.data {
11954 IndicatorDataRef::Candles { candles, .. } => {
11955 TrendContinuationFactorInput::from_candles(
11956 candles,
11957 &source,
11958 TrendContinuationFactorParams {
11959 length: Some(length),
11960 },
11961 )
11962 }
11963 IndicatorDataRef::Slice { values } => TrendContinuationFactorInput::from_slice(
11964 values,
11965 TrendContinuationFactorParams {
11966 length: Some(length),
11967 },
11968 ),
11969 _ => unreachable!(),
11970 };
11971 let out = trend_continuation_factor_with_kernel(&input, kernel).map_err(|e| {
11972 IndicatorDispatchError::ComputeFailed {
11973 indicator: "trend_continuation_factor".to_string(),
11974 details: e.to_string(),
11975 }
11976 })?;
11977 if output_id.eq_ignore_ascii_case("plus_tcf") || output_id.eq_ignore_ascii_case("value")
11978 {
11979 return Ok(out.plus_tcf);
11980 }
11981 if output_id.eq_ignore_ascii_case("minus_tcf") {
11982 return Ok(out.minus_tcf);
11983 }
11984 Err(IndicatorDispatchError::UnknownOutput {
11985 indicator: "trend_continuation_factor".to_string(),
11986 output: output_id.to_string(),
11987 })
11988 },
11989 )
11990}
11991
11992fn compute_volume_weighted_stochastic_rsi_batch(
11993 req: IndicatorBatchRequest<'_>,
11994 output_id: &str,
11995) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
11996 let (source, volume) =
11997 extract_close_volume_input("volume_weighted_stochastic_rsi", req.data, "close")?;
11998 let kernel = req.kernel.to_non_batch();
11999 collect_f64(
12000 "volume_weighted_stochastic_rsi",
12001 output_id,
12002 req.combos,
12003 source.len(),
12004 |params| {
12005 let rsi_length =
12006 get_usize_param("volume_weighted_stochastic_rsi", params, "rsi_length", 14)?;
12007 let stoch_length =
12008 get_usize_param("volume_weighted_stochastic_rsi", params, "stoch_length", 14)?;
12009 let k_length =
12010 get_usize_param("volume_weighted_stochastic_rsi", params, "k_length", 3)?;
12011 let d_length =
12012 get_usize_param("volume_weighted_stochastic_rsi", params, "d_length", 3)?;
12013 let ma_type =
12014 get_enum_param("volume_weighted_stochastic_rsi", params, "ma_type", "WSMA")?;
12015 let input = VolumeWeightedStochasticRsiInput::from_slices(
12016 source,
12017 volume,
12018 VolumeWeightedStochasticRsiParams {
12019 rsi_length: Some(rsi_length),
12020 stoch_length: Some(stoch_length),
12021 k_length: Some(k_length),
12022 d_length: Some(d_length),
12023 ma_type: Some(ma_type),
12024 },
12025 );
12026 let out = volume_weighted_stochastic_rsi_with_kernel(&input, kernel).map_err(|e| {
12027 IndicatorDispatchError::ComputeFailed {
12028 indicator: "volume_weighted_stochastic_rsi".to_string(),
12029 details: e.to_string(),
12030 }
12031 })?;
12032 if output_id.eq_ignore_ascii_case("k") || output_id.eq_ignore_ascii_case("value") {
12033 return Ok(out.k);
12034 }
12035 if output_id.eq_ignore_ascii_case("d") {
12036 return Ok(out.d);
12037 }
12038 Err(IndicatorDispatchError::UnknownOutput {
12039 indicator: "volume_weighted_stochastic_rsi".to_string(),
12040 output: output_id.to_string(),
12041 })
12042 },
12043 )
12044}
12045
12046fn compute_logarithmic_moving_average_batch(
12047 req: IndicatorBatchRequest<'_>,
12048 output_id: &str,
12049) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12050 let data_len = match req.data {
12051 IndicatorDataRef::Candles { candles, source } => {
12052 source_type(candles, source.unwrap_or("close")).len()
12053 }
12054 IndicatorDataRef::Slice { values } => values.len(),
12055 IndicatorDataRef::CloseVolume { close, .. } => close.len(),
12056 IndicatorDataRef::Ohlcv { close, .. } => close.len(),
12057 _ => {
12058 return Err(IndicatorDispatchError::MissingRequiredInput {
12059 indicator: "logarithmic_moving_average".to_string(),
12060 input: IndicatorInputKind::CloseVolume,
12061 })
12062 }
12063 };
12064 let kernel = req.kernel.to_non_batch();
12065 collect_f64(
12066 "logarithmic_moving_average",
12067 output_id,
12068 req.combos,
12069 data_len,
12070 |params| {
12071 let source = get_enum_param("logarithmic_moving_average", params, "source", "close")?;
12072 let period = get_usize_param("logarithmic_moving_average", params, "period", 100)?;
12073 let steepness = get_f64_param("logarithmic_moving_average", params, "steepness", 2.5)?;
12074 let ma_type = get_enum_param("logarithmic_moving_average", params, "ma_type", "ema")?;
12075 let smooth = get_usize_param("logarithmic_moving_average", params, "smooth", 10)?;
12076 let momentum_weight =
12077 get_f64_param("logarithmic_moving_average", params, "momentum_weight", 1.2)?;
12078 let long_threshold =
12079 get_f64_param("logarithmic_moving_average", params, "long_threshold", 0.5)?;
12080 let short_threshold = get_f64_param(
12081 "logarithmic_moving_average",
12082 params,
12083 "short_threshold",
12084 -0.5,
12085 )?;
12086 let params = LogarithmicMovingAverageParams {
12087 period: Some(period),
12088 steepness: Some(steepness),
12089 ma_type: Some(ma_type),
12090 smooth: Some(smooth),
12091 momentum_weight: Some(momentum_weight),
12092 long_threshold: Some(long_threshold),
12093 short_threshold: Some(short_threshold),
12094 };
12095 let input = match req.data {
12096 IndicatorDataRef::Candles { candles, .. } => {
12097 LogarithmicMovingAverageInput::from_candles(candles, &source, params)
12098 }
12099 IndicatorDataRef::Slice { values } => {
12100 LogarithmicMovingAverageInput::from_slice(values, params)
12101 }
12102 IndicatorDataRef::CloseVolume { close, volume } => {
12103 LogarithmicMovingAverageInput::from_slice_with_volume(close, volume, params)
12104 }
12105 IndicatorDataRef::Ohlcv {
12106 open,
12107 high,
12108 low,
12109 close,
12110 volume,
12111 } => {
12112 let price = match source.to_ascii_lowercase().as_str() {
12113 "open" => open,
12114 "high" => high,
12115 "low" => low,
12116 _ => close,
12117 };
12118 LogarithmicMovingAverageInput::from_slice_with_volume(price, volume, params)
12119 }
12120 _ => unreachable!(),
12121 };
12122 let out = logarithmic_moving_average_with_kernel(&input, kernel).map_err(|e| {
12123 IndicatorDispatchError::ComputeFailed {
12124 indicator: "logarithmic_moving_average".to_string(),
12125 details: e.to_string(),
12126 }
12127 })?;
12128 if output_id.eq_ignore_ascii_case("lma") || output_id.eq_ignore_ascii_case("value") {
12129 return Ok(out.lma);
12130 }
12131 if output_id.eq_ignore_ascii_case("signal") {
12132 return Ok(out.signal);
12133 }
12134 if output_id.eq_ignore_ascii_case("position") {
12135 return Ok(out.position);
12136 }
12137 if output_id.eq_ignore_ascii_case("momentum_confirmed") {
12138 return Ok(out.momentum_confirmed);
12139 }
12140 Err(IndicatorDispatchError::UnknownOutput {
12141 indicator: "logarithmic_moving_average".to_string(),
12142 output: output_id.to_string(),
12143 })
12144 },
12145 )
12146}
12147
12148fn compute_adaptive_schaff_trend_cycle_batch(
12149 req: IndicatorBatchRequest<'_>,
12150 output_id: &str,
12151) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12152 let (high, low, close) = extract_ohlc_input("adaptive_schaff_trend_cycle", req.data)?;
12153 let kernel = req.kernel.to_non_batch();
12154 collect_f64(
12155 "adaptive_schaff_trend_cycle",
12156 output_id,
12157 req.combos,
12158 close.len(),
12159 |params| {
12160 let adaptive_length =
12161 get_usize_param("adaptive_schaff_trend_cycle", params, "adaptive_length", 55)?;
12162 let stc_length =
12163 get_usize_param("adaptive_schaff_trend_cycle", params, "stc_length", 12)?;
12164 let smoothing_factor = get_f64_param(
12165 "adaptive_schaff_trend_cycle",
12166 params,
12167 "smoothing_factor",
12168 0.45,
12169 )?;
12170 let fast_length =
12171 get_usize_param("adaptive_schaff_trend_cycle", params, "fast_length", 26)?;
12172 let slow_length =
12173 get_usize_param("adaptive_schaff_trend_cycle", params, "slow_length", 50)?;
12174 let input = AdaptiveSchaffTrendCycleInput::from_slices(
12175 high,
12176 low,
12177 close,
12178 AdaptiveSchaffTrendCycleParams {
12179 adaptive_length: Some(adaptive_length),
12180 stc_length: Some(stc_length),
12181 smoothing_factor: Some(smoothing_factor),
12182 fast_length: Some(fast_length),
12183 slow_length: Some(slow_length),
12184 },
12185 );
12186 let out = adaptive_schaff_trend_cycle_with_kernel(&input, kernel).map_err(|e| {
12187 IndicatorDispatchError::ComputeFailed {
12188 indicator: "adaptive_schaff_trend_cycle".to_string(),
12189 details: e.to_string(),
12190 }
12191 })?;
12192 if output_id.eq_ignore_ascii_case("stc") || output_id.eq_ignore_ascii_case("value") {
12193 return Ok(out.stc);
12194 }
12195 if output_id.eq_ignore_ascii_case("histogram") || output_id.eq_ignore_ascii_case("hist")
12196 {
12197 return Ok(out.histogram);
12198 }
12199 Err(IndicatorDispatchError::UnknownOutput {
12200 indicator: "adaptive_schaff_trend_cycle".to_string(),
12201 output: output_id.to_string(),
12202 })
12203 },
12204 )
12205}
12206
12207fn compute_ehlers_detrending_filter_batch(
12208 req: IndicatorBatchRequest<'_>,
12209 output_id: &str,
12210) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12211 let data_len = match req.data {
12212 IndicatorDataRef::Candles { candles, source } => {
12213 source_type(candles, source.unwrap_or("hlcc4")).len()
12214 }
12215 IndicatorDataRef::Slice { values } => values.len(),
12216 _ => {
12217 return Err(IndicatorDispatchError::MissingRequiredInput {
12218 indicator: "ehlers_detrending_filter".to_string(),
12219 input: IndicatorInputKind::Slice,
12220 })
12221 }
12222 };
12223 let kernel = req.kernel.to_non_batch();
12224 collect_f64(
12225 "ehlers_detrending_filter",
12226 output_id,
12227 req.combos,
12228 data_len,
12229 |params| {
12230 let source = get_enum_param("ehlers_detrending_filter", params, "source", "hlcc4")?;
12231 let length = get_usize_param("ehlers_detrending_filter", params, "length", 10)?;
12232 let input = match req.data {
12233 IndicatorDataRef::Candles { candles, .. } => {
12234 EhlersDetrendingFilterInput::from_candles(
12235 candles,
12236 &source,
12237 EhlersDetrendingFilterParams {
12238 length: Some(length),
12239 },
12240 )
12241 }
12242 IndicatorDataRef::Slice { values } => EhlersDetrendingFilterInput::from_slice(
12243 values,
12244 EhlersDetrendingFilterParams {
12245 length: Some(length),
12246 },
12247 ),
12248 _ => unreachable!(),
12249 };
12250 let out = ehlers_detrending_filter_with_kernel(&input, kernel).map_err(|e| {
12251 IndicatorDispatchError::ComputeFailed {
12252 indicator: "ehlers_detrending_filter".to_string(),
12253 details: e.to_string(),
12254 }
12255 })?;
12256 if output_id.eq_ignore_ascii_case("edf") || output_id.eq_ignore_ascii_case("value") {
12257 return Ok(out.edf);
12258 }
12259 if output_id.eq_ignore_ascii_case("signal") {
12260 return Ok(out.signal);
12261 }
12262 Err(IndicatorDispatchError::UnknownOutput {
12263 indicator: "ehlers_detrending_filter".to_string(),
12264 output: output_id.to_string(),
12265 })
12266 },
12267 )
12268}
12269
12270fn compute_hypertrend_batch(
12271 req: IndicatorBatchRequest<'_>,
12272 output_id: &str,
12273) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12274 let data_len = match req.data {
12275 IndicatorDataRef::Candles { candles, source } => {
12276 source_type(candles, source.unwrap_or("close")).len()
12277 }
12278 IndicatorDataRef::Ohlc { close, .. } => close.len(),
12279 IndicatorDataRef::Ohlcv { close, .. } => close.len(),
12280 _ => {
12281 return Err(IndicatorDispatchError::MissingRequiredInput {
12282 indicator: "hypertrend".to_string(),
12283 input: IndicatorInputKind::Ohlc,
12284 })
12285 }
12286 };
12287 let kernel = req.kernel.to_non_batch();
12288 collect_f64("hypertrend", output_id, req.combos, data_len, |params| {
12289 let source = get_enum_param("hypertrend", params, "source", "close")?;
12290 let factor = get_f64_param("hypertrend", params, "factor", 5.0)?;
12291 let slope = get_f64_param("hypertrend", params, "slope", 14.0)?;
12292 let width_percent = get_f64_param("hypertrend", params, "width_percent", 80.0)?;
12293 let input = match req.data {
12294 IndicatorDataRef::Candles { candles, .. } => HyperTrendInput::from_candles(
12295 candles,
12296 &source,
12297 HyperTrendParams {
12298 factor: Some(factor),
12299 slope: Some(slope),
12300 width_percent: Some(width_percent),
12301 },
12302 ),
12303 IndicatorDataRef::Ohlc {
12304 high,
12305 low,
12306 close,
12307 open,
12308 } => {
12309 ensure_same_len_4("hypertrend", open.len(), high.len(), low.len(), close.len())?;
12310 let src = match source.to_ascii_lowercase().as_str() {
12311 "open" => open,
12312 "high" => high,
12313 "low" => low,
12314 _ => close,
12315 };
12316 HyperTrendInput::from_slices(
12317 high,
12318 low,
12319 src,
12320 HyperTrendParams {
12321 factor: Some(factor),
12322 slope: Some(slope),
12323 width_percent: Some(width_percent),
12324 },
12325 )
12326 }
12327 IndicatorDataRef::Ohlcv {
12328 high,
12329 low,
12330 close,
12331 open,
12332 volume,
12333 } => {
12334 ensure_same_len_5(
12335 "hypertrend",
12336 open.len(),
12337 high.len(),
12338 low.len(),
12339 close.len(),
12340 volume.len(),
12341 )?;
12342 let src = match source.to_ascii_lowercase().as_str() {
12343 "open" => open,
12344 "high" => high,
12345 "low" => low,
12346 _ => close,
12347 };
12348 HyperTrendInput::from_slices(
12349 high,
12350 low,
12351 src,
12352 HyperTrendParams {
12353 factor: Some(factor),
12354 slope: Some(slope),
12355 width_percent: Some(width_percent),
12356 },
12357 )
12358 }
12359 _ => unreachable!(),
12360 };
12361 let out = hypertrend_with_kernel(&input, kernel).map_err(|e| {
12362 IndicatorDispatchError::ComputeFailed {
12363 indicator: "hypertrend".to_string(),
12364 details: e.to_string(),
12365 }
12366 })?;
12367 if output_id.eq_ignore_ascii_case("upper") {
12368 return Ok(out.upper);
12369 }
12370 if output_id.eq_ignore_ascii_case("average") || output_id.eq_ignore_ascii_case("value") {
12371 return Ok(out.average);
12372 }
12373 if output_id.eq_ignore_ascii_case("lower") {
12374 return Ok(out.lower);
12375 }
12376 if output_id.eq_ignore_ascii_case("trend") {
12377 return Ok(out.trend);
12378 }
12379 if output_id.eq_ignore_ascii_case("changed") {
12380 return Ok(out.changed);
12381 }
12382 Err(IndicatorDispatchError::UnknownOutput {
12383 indicator: "hypertrend".to_string(),
12384 output: output_id.to_string(),
12385 })
12386 })
12387}
12388
12389fn compute_ict_propulsion_block_batch(
12390 req: IndicatorBatchRequest<'_>,
12391 output_id: &str,
12392) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12393 let (open, high, low, close) = extract_ohlc_full_input("ict_propulsion_block", req.data)?;
12394 let kernel = req.kernel.to_non_batch();
12395 collect_f64(
12396 "ict_propulsion_block",
12397 output_id,
12398 req.combos,
12399 close.len(),
12400 |params| {
12401 let swing_length = get_usize_param("ict_propulsion_block", params, "swing_length", 3)?;
12402 let mitigation_price =
12403 match get_enum_param("ict_propulsion_block", params, "mitigation_price", "close")?
12404 .to_ascii_lowercase()
12405 .as_str()
12406 {
12407 "close" => IctPropulsionBlockMitigationPrice::Close,
12408 "wick" => IctPropulsionBlockMitigationPrice::Wick,
12409 other => {
12410 return Err(IndicatorDispatchError::InvalidParam {
12411 indicator: "ict_propulsion_block".to_string(),
12412 key: "mitigation_price".to_string(),
12413 reason: format!("unsupported value '{other}'"),
12414 })
12415 }
12416 };
12417 let input = IctPropulsionBlockInput::from_slices(
12418 open,
12419 high,
12420 low,
12421 close,
12422 IctPropulsionBlockParams {
12423 swing_length: Some(swing_length),
12424 mitigation_price: Some(mitigation_price),
12425 },
12426 );
12427 let out = ict_propulsion_block_with_kernel(&input, kernel).map_err(|e| {
12428 IndicatorDispatchError::ComputeFailed {
12429 indicator: "ict_propulsion_block".to_string(),
12430 details: e.to_string(),
12431 }
12432 })?;
12433 if output_id.eq_ignore_ascii_case("bullish_high") {
12434 return Ok(out.bullish_high);
12435 }
12436 if output_id.eq_ignore_ascii_case("bullish_low") {
12437 return Ok(out.bullish_low);
12438 }
12439 if output_id.eq_ignore_ascii_case("bullish_kind") {
12440 return Ok(out.bullish_kind);
12441 }
12442 if output_id.eq_ignore_ascii_case("bullish_active") {
12443 return Ok(out.bullish_active);
12444 }
12445 if output_id.eq_ignore_ascii_case("bullish_mitigated") {
12446 return Ok(out.bullish_mitigated);
12447 }
12448 if output_id.eq_ignore_ascii_case("bullish_new") {
12449 return Ok(out.bullish_new);
12450 }
12451 if output_id.eq_ignore_ascii_case("bearish_high") {
12452 return Ok(out.bearish_high);
12453 }
12454 if output_id.eq_ignore_ascii_case("bearish_low") {
12455 return Ok(out.bearish_low);
12456 }
12457 if output_id.eq_ignore_ascii_case("bearish_kind") {
12458 return Ok(out.bearish_kind);
12459 }
12460 if output_id.eq_ignore_ascii_case("bearish_active") {
12461 return Ok(out.bearish_active);
12462 }
12463 if output_id.eq_ignore_ascii_case("bearish_mitigated") {
12464 return Ok(out.bearish_mitigated);
12465 }
12466 if output_id.eq_ignore_ascii_case("bearish_new") {
12467 return Ok(out.bearish_new);
12468 }
12469 Err(IndicatorDispatchError::UnknownOutput {
12470 indicator: "ict_propulsion_block".to_string(),
12471 output: output_id.to_string(),
12472 })
12473 },
12474 )
12475}
12476
12477fn compute_impulse_macd_batch(
12478 req: IndicatorBatchRequest<'_>,
12479 output_id: &str,
12480) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12481 let (high, low, close) = extract_ohlc_input("impulse_macd", req.data)?;
12482 let kernel = req.kernel.to_non_batch();
12483 collect_f64(
12484 "impulse_macd",
12485 output_id,
12486 req.combos,
12487 close.len(),
12488 |params| {
12489 let length_ma = get_usize_param("impulse_macd", params, "length_ma", 34)?;
12490 let length_signal = get_usize_param("impulse_macd", params, "length_signal", 9)?;
12491 let input = ImpulseMacdInput::from_slices(
12492 high,
12493 low,
12494 close,
12495 ImpulseMacdParams {
12496 length_ma: Some(length_ma),
12497 length_signal: Some(length_signal),
12498 },
12499 );
12500 let out = impulse_macd_with_kernel(&input, kernel).map_err(|e| {
12501 IndicatorDispatchError::ComputeFailed {
12502 indicator: "impulse_macd".to_string(),
12503 details: e.to_string(),
12504 }
12505 })?;
12506 if output_id.eq_ignore_ascii_case("impulse_macd")
12507 || output_id.eq_ignore_ascii_case("value")
12508 {
12509 return Ok(out.impulse_macd);
12510 }
12511 if output_id.eq_ignore_ascii_case("impulse_histo")
12512 || output_id.eq_ignore_ascii_case("histogram")
12513 || output_id.eq_ignore_ascii_case("hist")
12514 {
12515 return Ok(out.impulse_histo);
12516 }
12517 if output_id.eq_ignore_ascii_case("signal") {
12518 return Ok(out.signal);
12519 }
12520 Err(IndicatorDispatchError::UnknownOutput {
12521 indicator: "impulse_macd".to_string(),
12522 output: output_id.to_string(),
12523 })
12524 },
12525 )
12526}
12527
12528fn compute_keltner_channel_width_oscillator_batch(
12529 req: IndicatorBatchRequest<'_>,
12530 output_id: &str,
12531) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12532 let (high, low, close) = extract_ohlc_input("keltner_channel_width_oscillator", req.data)?;
12533 let kernel = req.kernel.to_non_batch();
12534 collect_f64(
12535 "keltner_channel_width_oscillator",
12536 output_id,
12537 req.combos,
12538 close.len(),
12539 |params| {
12540 let source = get_enum_param(
12541 "keltner_channel_width_oscillator",
12542 params,
12543 "source",
12544 "close",
12545 )?;
12546 let length = get_usize_param("keltner_channel_width_oscillator", params, "length", 20)?;
12547 let multiplier = get_f64_param(
12548 "keltner_channel_width_oscillator",
12549 params,
12550 "multiplier",
12551 2.0,
12552 )?;
12553 let use_exponential = get_bool_param(
12554 "keltner_channel_width_oscillator",
12555 params,
12556 "use_exponential",
12557 true,
12558 )?;
12559 let bands_style = get_enum_param(
12560 "keltner_channel_width_oscillator",
12561 params,
12562 "bands_style",
12563 "Average True Range",
12564 )?;
12565 let atr_length =
12566 get_usize_param("keltner_channel_width_oscillator", params, "atr_length", 10)?;
12567 let src = match req.data {
12568 IndicatorDataRef::Candles { candles, .. } => source_type(candles, &source),
12569 IndicatorDataRef::Ohlc {
12570 open,
12571 high,
12572 low,
12573 close,
12574 } => {
12575 ensure_same_len_4(
12576 "keltner_channel_width_oscillator",
12577 open.len(),
12578 high.len(),
12579 low.len(),
12580 close.len(),
12581 )?;
12582 match source.to_ascii_lowercase().as_str() {
12583 "open" => open,
12584 "high" => high,
12585 "low" => low,
12586 _ => close,
12587 }
12588 }
12589 IndicatorDataRef::Ohlcv {
12590 open,
12591 high,
12592 low,
12593 close,
12594 volume,
12595 } => {
12596 ensure_same_len_5(
12597 "keltner_channel_width_oscillator",
12598 open.len(),
12599 high.len(),
12600 low.len(),
12601 close.len(),
12602 volume.len(),
12603 )?;
12604 match source.to_ascii_lowercase().as_str() {
12605 "open" => open,
12606 "high" => high,
12607 "low" => low,
12608 _ => close,
12609 }
12610 }
12611 _ => close,
12612 };
12613 let input = KeltnerChannelWidthOscillatorInput::from_slices(
12614 high,
12615 low,
12616 close,
12617 src,
12618 KeltnerChannelWidthOscillatorParams {
12619 length: Some(length),
12620 multiplier: Some(multiplier),
12621 use_exponential: Some(use_exponential),
12622 bands_style: Some(bands_style),
12623 atr_length: Some(atr_length),
12624 },
12625 );
12626 let out =
12627 keltner_channel_width_oscillator_with_kernel(&input, kernel).map_err(|e| {
12628 IndicatorDispatchError::ComputeFailed {
12629 indicator: "keltner_channel_width_oscillator".to_string(),
12630 details: e.to_string(),
12631 }
12632 })?;
12633 if output_id.eq_ignore_ascii_case("kbw") || output_id.eq_ignore_ascii_case("value") {
12634 return Ok(out.kbw);
12635 }
12636 if output_id.eq_ignore_ascii_case("kbw_sma") {
12637 return Ok(out.kbw_sma);
12638 }
12639 Err(IndicatorDispatchError::UnknownOutput {
12640 indicator: "keltner_channel_width_oscillator".to_string(),
12641 output: output_id.to_string(),
12642 })
12643 },
12644 )
12645}
12646
12647fn compute_leavitt_convolution_acceleration_batch(
12648 req: IndicatorBatchRequest<'_>,
12649 output_id: &str,
12650) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12651 let data_len = match req.data {
12652 IndicatorDataRef::Candles { candles, source } => {
12653 source_type(candles, source.unwrap_or("close")).len()
12654 }
12655 IndicatorDataRef::Slice { values } => values.len(),
12656 _ => {
12657 return Err(IndicatorDispatchError::MissingRequiredInput {
12658 indicator: "leavitt_convolution_acceleration".to_string(),
12659 input: IndicatorInputKind::Slice,
12660 })
12661 }
12662 };
12663 let kernel = req.kernel.to_non_batch();
12664 collect_f64(
12665 "leavitt_convolution_acceleration",
12666 output_id,
12667 req.combos,
12668 data_len,
12669 |params| {
12670 let source = get_enum_param(
12671 "leavitt_convolution_acceleration",
12672 params,
12673 "source",
12674 "close",
12675 )?;
12676 let length = get_usize_param("leavitt_convolution_acceleration", params, "length", 70)?;
12677 let norm_length = get_usize_param(
12678 "leavitt_convolution_acceleration",
12679 params,
12680 "norm_length",
12681 150,
12682 )?;
12683 let use_norm_hyperbolic = get_bool_param(
12684 "leavitt_convolution_acceleration",
12685 params,
12686 "use_norm_hyperbolic",
12687 true,
12688 )?;
12689 let input = match req.data {
12690 IndicatorDataRef::Candles { candles, .. } => {
12691 LeavittConvolutionAccelerationInput::from_candles(
12692 candles,
12693 &source,
12694 LeavittConvolutionAccelerationParams {
12695 length: Some(length),
12696 norm_length: Some(norm_length),
12697 use_norm_hyperbolic: Some(use_norm_hyperbolic),
12698 },
12699 )
12700 }
12701 IndicatorDataRef::Slice { values } => {
12702 LeavittConvolutionAccelerationInput::from_slice(
12703 values,
12704 LeavittConvolutionAccelerationParams {
12705 length: Some(length),
12706 norm_length: Some(norm_length),
12707 use_norm_hyperbolic: Some(use_norm_hyperbolic),
12708 },
12709 )
12710 }
12711 _ => unreachable!(),
12712 };
12713 let out =
12714 leavitt_convolution_acceleration_with_kernel(&input, kernel).map_err(|e| {
12715 IndicatorDispatchError::ComputeFailed {
12716 indicator: "leavitt_convolution_acceleration".to_string(),
12717 details: e.to_string(),
12718 }
12719 })?;
12720 if output_id.eq_ignore_ascii_case("conv_acceleration")
12721 || output_id.eq_ignore_ascii_case("value")
12722 {
12723 return Ok(out.conv_acceleration);
12724 }
12725 if output_id.eq_ignore_ascii_case("signal") {
12726 return Ok(out.signal);
12727 }
12728 Err(IndicatorDispatchError::UnknownOutput {
12729 indicator: "leavitt_convolution_acceleration".to_string(),
12730 output: output_id.to_string(),
12731 })
12732 },
12733 )
12734}
12735
12736fn compute_squeeze_index_batch(
12737 req: IndicatorBatchRequest<'_>,
12738 output_id: &str,
12739) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12740 expect_value_output("squeeze_index", output_id)?;
12741 let data = extract_slice_input("squeeze_index", req.data, "close")?;
12742 let kernel = req.kernel.to_non_batch();
12743 collect_f64(
12744 "squeeze_index",
12745 output_id,
12746 req.combos,
12747 data.len(),
12748 |params| {
12749 let conv = get_f64_param("squeeze_index", params, "conv", 50.0)?;
12750 let length = get_usize_param("squeeze_index", params, "length", 20)?;
12751 let input = SqueezeIndexInput::from_slice(
12752 data,
12753 SqueezeIndexParams {
12754 conv: Some(conv),
12755 length: Some(length),
12756 },
12757 );
12758 let out = squeeze_index_with_kernel(&input, kernel).map_err(|e| {
12759 IndicatorDispatchError::ComputeFailed {
12760 indicator: "squeeze_index".to_string(),
12761 details: e.to_string(),
12762 }
12763 })?;
12764 Ok(out.values)
12765 },
12766 )
12767}
12768
12769fn compute_stochastic_distance_batch(
12770 req: IndicatorBatchRequest<'_>,
12771 output_id: &str,
12772) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12773 if !output_id.eq_ignore_ascii_case("oscillator") && !output_id.eq_ignore_ascii_case("signal") {
12774 return Err(IndicatorDispatchError::UnknownOutput {
12775 indicator: "stochastic_distance".to_string(),
12776 output: output_id.to_string(),
12777 });
12778 }
12779 let data = extract_slice_input("stochastic_distance", req.data, "close")?;
12780 let kernel = req.kernel.to_non_batch();
12781 collect_f64(
12782 "stochastic_distance",
12783 output_id,
12784 req.combos,
12785 data.len(),
12786 |params| {
12787 let lookback_length =
12788 get_usize_param("stochastic_distance", params, "lookback_length", 200)?;
12789 let length1 = get_usize_param("stochastic_distance", params, "length1", 12)?;
12790 let length2 = get_usize_param("stochastic_distance", params, "length2", 3)?;
12791 let ob_level = get_i32_param("stochastic_distance", params, "ob_level", 40)?;
12792 let os_level = get_i32_param("stochastic_distance", params, "os_level", -40)?;
12793 let input = StochasticDistanceInput::from_slice(
12794 data,
12795 StochasticDistanceParams {
12796 lookback_length: Some(lookback_length),
12797 length1: Some(length1),
12798 length2: Some(length2),
12799 ob_level: Some(ob_level),
12800 os_level: Some(os_level),
12801 },
12802 );
12803 let out = stochastic_distance_with_kernel(&input, kernel).map_err(|e| {
12804 IndicatorDispatchError::ComputeFailed {
12805 indicator: "stochastic_distance".to_string(),
12806 details: e.to_string(),
12807 }
12808 })?;
12809 match output_id {
12810 "oscillator" => Ok(out.oscillator),
12811 "signal" => Ok(out.signal),
12812 _ => Err(IndicatorDispatchError::UnknownOutput {
12813 indicator: "stochastic_distance".to_string(),
12814 output: output_id.to_string(),
12815 }),
12816 }
12817 },
12818 )
12819}
12820
12821fn compute_vertical_horizontal_filter_batch(
12822 req: IndicatorBatchRequest<'_>,
12823 output_id: &str,
12824) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12825 expect_value_output("vertical_horizontal_filter", output_id)?;
12826 let data = extract_slice_input("vertical_horizontal_filter", req.data, "close")?;
12827 let kernel = req.kernel.to_non_batch();
12828 collect_f64(
12829 "vertical_horizontal_filter",
12830 output_id,
12831 req.combos,
12832 data.len(),
12833 |params| {
12834 let length = get_usize_param("vertical_horizontal_filter", params, "length", 28)?;
12835 let input = VerticalHorizontalFilterInput::from_slice(
12836 data,
12837 VerticalHorizontalFilterParams {
12838 length: Some(length),
12839 },
12840 );
12841 let out = vertical_horizontal_filter_with_kernel(&input, kernel).map_err(|e| {
12842 IndicatorDispatchError::ComputeFailed {
12843 indicator: "vertical_horizontal_filter".to_string(),
12844 details: e.to_string(),
12845 }
12846 })?;
12847 Ok(out.values)
12848 },
12849 )
12850}
12851
12852fn compute_intraday_momentum_index_batch(
12853 req: IndicatorBatchRequest<'_>,
12854 output_id: &str,
12855) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12856 let (open, _high, _low, close) = extract_ohlc_full_input("intraday_momentum_index", req.data)?;
12857 let kernel = req.kernel.to_non_batch();
12858 collect_f64(
12859 "intraday_momentum_index",
12860 output_id,
12861 req.combos,
12862 open.len(),
12863 |params| {
12864 let length = get_usize_param("intraday_momentum_index", params, "length", 14)?;
12865 let length_ma = get_usize_param("intraday_momentum_index", params, "length_ma", 6)?;
12866 let mult = get_f64_param("intraday_momentum_index", params, "mult", 2.0)?;
12867 let length_bb = get_usize_param("intraday_momentum_index", params, "length_bb", 20)?;
12868 let apply_smoothing =
12869 get_bool_param("intraday_momentum_index", params, "apply_smoothing", false)?;
12870 let low_band = get_usize_param("intraday_momentum_index", params, "low_band", 10)?;
12871 let input = IntradayMomentumIndexInput::from_slices(
12872 open,
12873 close,
12874 IntradayMomentumIndexParams {
12875 length: Some(length),
12876 length_ma: Some(length_ma),
12877 mult: Some(mult),
12878 length_bb: Some(length_bb),
12879 apply_smoothing: Some(apply_smoothing),
12880 low_band: Some(low_band),
12881 },
12882 );
12883 let out = intraday_momentum_index_with_kernel(&input, kernel).map_err(|e| {
12884 IndicatorDispatchError::ComputeFailed {
12885 indicator: "intraday_momentum_index".to_string(),
12886 details: e.to_string(),
12887 }
12888 })?;
12889 if output_id.eq_ignore_ascii_case("imi") || output_id.eq_ignore_ascii_case("value") {
12890 return Ok(out.imi);
12891 }
12892 if output_id.eq_ignore_ascii_case("upper_hit") {
12893 return Ok(out.upper_hit);
12894 }
12895 if output_id.eq_ignore_ascii_case("lower_hit") {
12896 return Ok(out.lower_hit);
12897 }
12898 if output_id.eq_ignore_ascii_case("signal") {
12899 return Ok(out.signal);
12900 }
12901 Err(IndicatorDispatchError::UnknownOutput {
12902 indicator: "intraday_momentum_index".to_string(),
12903 output: output_id.to_string(),
12904 })
12905 },
12906 )
12907}
12908
12909fn compute_vwap_zscore_with_signals_batch(
12910 req: IndicatorBatchRequest<'_>,
12911 output_id: &str,
12912) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12913 let (close, volume) =
12914 extract_close_volume_input("vwap_zscore_with_signals", req.data, "close")?;
12915 let kernel = req.kernel.to_non_batch();
12916 collect_f64(
12917 "vwap_zscore_with_signals",
12918 output_id,
12919 req.combos,
12920 close.len(),
12921 |params| {
12922 let length = get_usize_param("vwap_zscore_with_signals", params, "length", 20)?;
12923 let upper_bottom =
12924 get_f64_param("vwap_zscore_with_signals", params, "upper_bottom", 2.5)?;
12925 let lower_bottom =
12926 get_f64_param("vwap_zscore_with_signals", params, "lower_bottom", -2.5)?;
12927 let input = VwapZscoreWithSignalsInput::from_slices(
12928 close,
12929 volume,
12930 VwapZscoreWithSignalsParams {
12931 length: Some(length),
12932 upper_bottom: Some(upper_bottom),
12933 lower_bottom: Some(lower_bottom),
12934 },
12935 );
12936 let out = vwap_zscore_with_signals_with_kernel(&input, kernel).map_err(|e| {
12937 IndicatorDispatchError::ComputeFailed {
12938 indicator: "vwap_zscore_with_signals".to_string(),
12939 details: e.to_string(),
12940 }
12941 })?;
12942 if output_id.eq_ignore_ascii_case("zvwap") || output_id.eq_ignore_ascii_case("value") {
12943 return Ok(out.zvwap);
12944 }
12945 if output_id.eq_ignore_ascii_case("support_signal") {
12946 return Ok(out.support_signal);
12947 }
12948 if output_id.eq_ignore_ascii_case("resistance_signal") {
12949 return Ok(out.resistance_signal);
12950 }
12951 Err(IndicatorDispatchError::UnknownOutput {
12952 indicator: "vwap_zscore_with_signals".to_string(),
12953 output: output_id.to_string(),
12954 })
12955 },
12956 )
12957}
12958
12959fn compute_hema_trend_levels_batch(
12960 req: IndicatorBatchRequest<'_>,
12961 output_id: &str,
12962) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
12963 let (open, high, low, close) = extract_ohlc_full_input("hema_trend_levels", req.data)?;
12964 let kernel = req.kernel.to_non_batch();
12965 collect_f64(
12966 "hema_trend_levels",
12967 output_id,
12968 req.combos,
12969 close.len(),
12970 |params| {
12971 let fast_length = get_usize_param("hema_trend_levels", params, "fast_length", 20)?;
12972 let slow_length = get_usize_param("hema_trend_levels", params, "slow_length", 40)?;
12973 let input = HemaTrendLevelsInput::from_slices(
12974 open,
12975 high,
12976 low,
12977 close,
12978 HemaTrendLevelsParams {
12979 fast_length: Some(fast_length),
12980 slow_length: Some(slow_length),
12981 },
12982 );
12983 let out = hema_trend_levels_with_kernel(&input, kernel).map_err(|e| {
12984 IndicatorDispatchError::ComputeFailed {
12985 indicator: "hema_trend_levels".to_string(),
12986 details: e.to_string(),
12987 }
12988 })?;
12989 if output_id.eq_ignore_ascii_case("fast_hema")
12990 || output_id.eq_ignore_ascii_case("value")
12991 {
12992 return Ok(out.fast_hema);
12993 }
12994 if output_id.eq_ignore_ascii_case("slow_hema") {
12995 return Ok(out.slow_hema);
12996 }
12997 if output_id.eq_ignore_ascii_case("trend_direction")
12998 || output_id.eq_ignore_ascii_case("trend")
12999 {
13000 return Ok(out.trend_direction);
13001 }
13002 if output_id.eq_ignore_ascii_case("bar_state") {
13003 return Ok(out.bar_state);
13004 }
13005 if output_id.eq_ignore_ascii_case("bullish_crossover")
13006 || output_id.eq_ignore_ascii_case("buy_signal")
13007 || output_id.eq_ignore_ascii_case("buy")
13008 {
13009 return Ok(out.bullish_crossover);
13010 }
13011 if output_id.eq_ignore_ascii_case("bearish_crossunder")
13012 || output_id.eq_ignore_ascii_case("sell_signal")
13013 || output_id.eq_ignore_ascii_case("sell")
13014 {
13015 return Ok(out.bearish_crossunder);
13016 }
13017 if output_id.eq_ignore_ascii_case("box_offset") {
13018 return Ok(out.box_offset);
13019 }
13020 if output_id.eq_ignore_ascii_case("bull_box_top") {
13021 return Ok(out.bull_box_top);
13022 }
13023 if output_id.eq_ignore_ascii_case("bull_box_bottom") {
13024 return Ok(out.bull_box_bottom);
13025 }
13026 if output_id.eq_ignore_ascii_case("bear_box_top") {
13027 return Ok(out.bear_box_top);
13028 }
13029 if output_id.eq_ignore_ascii_case("bear_box_bottom") {
13030 return Ok(out.bear_box_bottom);
13031 }
13032 if output_id.eq_ignore_ascii_case("bullish_test") {
13033 return Ok(out.bullish_test);
13034 }
13035 if output_id.eq_ignore_ascii_case("bearish_test") {
13036 return Ok(out.bearish_test);
13037 }
13038 if output_id.eq_ignore_ascii_case("bullish_test_level") {
13039 return Ok(out.bullish_test_level);
13040 }
13041 if output_id.eq_ignore_ascii_case("bearish_test_level") {
13042 return Ok(out.bearish_test_level);
13043 }
13044 Err(IndicatorDispatchError::UnknownOutput {
13045 indicator: "hema_trend_levels".to_string(),
13046 output: output_id.to_string(),
13047 })
13048 },
13049 )
13050}
13051
13052fn compute_macd_wave_signal_pro_batch(
13053 req: IndicatorBatchRequest<'_>,
13054 output_id: &str,
13055) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13056 let (open, high, low, close) = extract_ohlc_full_input("macd_wave_signal_pro", req.data)?;
13057 let kernel = req.kernel.to_non_batch();
13058 collect_f64(
13059 "macd_wave_signal_pro",
13060 output_id,
13061 req.combos,
13062 close.len(),
13063 |_params| {
13064 let input =
13065 MacdWaveSignalProInput::from_slices(open, high, low, close, Default::default());
13066 let out = macd_wave_signal_pro_with_kernel(&input, kernel).map_err(|e| {
13067 IndicatorDispatchError::ComputeFailed {
13068 indicator: "macd_wave_signal_pro".to_string(),
13069 details: e.to_string(),
13070 }
13071 })?;
13072 if output_id.eq_ignore_ascii_case("diff") || output_id.eq_ignore_ascii_case("value") {
13073 return Ok(out.diff);
13074 }
13075 if output_id.eq_ignore_ascii_case("dea") {
13076 return Ok(out.dea);
13077 }
13078 if output_id.eq_ignore_ascii_case("macd_histogram")
13079 || output_id.eq_ignore_ascii_case("macd")
13080 || output_id.eq_ignore_ascii_case("histogram")
13081 || output_id.eq_ignore_ascii_case("hist")
13082 {
13083 return Ok(out.macd_histogram);
13084 }
13085 if output_id.eq_ignore_ascii_case("line_convergence")
13086 || output_id.eq_ignore_ascii_case("line_conv")
13087 {
13088 return Ok(out.line_convergence);
13089 }
13090 if output_id.eq_ignore_ascii_case("buy_signal") || output_id.eq_ignore_ascii_case("buy")
13091 {
13092 return Ok(out.buy_signal);
13093 }
13094 if output_id.eq_ignore_ascii_case("sell_signal")
13095 || output_id.eq_ignore_ascii_case("sell")
13096 {
13097 return Ok(out.sell_signal);
13098 }
13099 Err(IndicatorDispatchError::UnknownOutput {
13100 indicator: "macd_wave_signal_pro".to_string(),
13101 output: output_id.to_string(),
13102 })
13103 },
13104 )
13105}
13106
13107fn compute_demand_index_batch(
13108 req: IndicatorBatchRequest<'_>,
13109 output_id: &str,
13110) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13111 let (high, low, close, volume) = extract_hlcv_input("demand_index", req.data)?;
13112 let kernel = req.kernel.to_non_batch();
13113 collect_f64(
13114 "demand_index",
13115 output_id,
13116 req.combos,
13117 high.len(),
13118 |params| {
13119 let len_bs = get_usize_param("demand_index", params, "len_bs", 19)?;
13120 let len_bs_ma = get_usize_param("demand_index", params, "len_bs_ma", 19)?;
13121 let len_di_ma = get_usize_param("demand_index", params, "len_di_ma", 19)?;
13122 let ma_type = get_enum_param("demand_index", params, "ma_type", "ema")?;
13123 let input = DemandIndexInput::from_slices(
13124 high,
13125 low,
13126 close,
13127 volume,
13128 DemandIndexParams {
13129 len_bs: Some(len_bs),
13130 len_bs_ma: Some(len_bs_ma),
13131 len_di_ma: Some(len_di_ma),
13132 ma_type: Some(ma_type),
13133 },
13134 );
13135 let out = demand_index_with_kernel(&input, kernel).map_err(|e| {
13136 IndicatorDispatchError::ComputeFailed {
13137 indicator: "demand_index".to_string(),
13138 details: e.to_string(),
13139 }
13140 })?;
13141 if output_id.eq_ignore_ascii_case("demand_index")
13142 || output_id.eq_ignore_ascii_case("value")
13143 {
13144 return Ok(out.demand_index);
13145 }
13146 if output_id.eq_ignore_ascii_case("signal") {
13147 return Ok(out.signal);
13148 }
13149 Err(IndicatorDispatchError::UnknownOutput {
13150 indicator: "demand_index".to_string(),
13151 output: output_id.to_string(),
13152 })
13153 },
13154 )
13155}
13156
13157fn compute_kase_peak_oscillator_with_divergences_batch(
13158 req: IndicatorBatchRequest<'_>,
13159 output_id: &str,
13160) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13161 let (high, low, close) = extract_ohlc_input("kase_peak_oscillator_with_divergences", req.data)?;
13162 let kernel = req.kernel.to_non_batch();
13163 collect_f64(
13164 "kase_peak_oscillator_with_divergences",
13165 output_id,
13166 req.combos,
13167 close.len(),
13168 |params| {
13169 let deviations = get_f64_param(
13170 "kase_peak_oscillator_with_divergences",
13171 params,
13172 "deviations",
13173 2.0,
13174 )?;
13175 let short_cycle = get_usize_param(
13176 "kase_peak_oscillator_with_divergences",
13177 params,
13178 "short_cycle",
13179 8,
13180 )?;
13181 let long_cycle = get_usize_param(
13182 "kase_peak_oscillator_with_divergences",
13183 params,
13184 "long_cycle",
13185 65,
13186 )?;
13187 let sensitivity = get_f64_param(
13188 "kase_peak_oscillator_with_divergences",
13189 params,
13190 "sensitivity",
13191 40.0,
13192 )?;
13193 let all_peaks_mode = get_bool_param(
13194 "kase_peak_oscillator_with_divergences",
13195 params,
13196 "all_peaks_mode",
13197 true,
13198 )?;
13199 let lb_r = get_usize_param("kase_peak_oscillator_with_divergences", params, "lb_r", 5)?;
13200 let lb_l = get_usize_param("kase_peak_oscillator_with_divergences", params, "lb_l", 5)?;
13201 let range_upper = get_usize_param(
13202 "kase_peak_oscillator_with_divergences",
13203 params,
13204 "range_upper",
13205 60,
13206 )?;
13207 let range_lower = get_usize_param(
13208 "kase_peak_oscillator_with_divergences",
13209 params,
13210 "range_lower",
13211 5,
13212 )?;
13213 let plot_bull = get_bool_param(
13214 "kase_peak_oscillator_with_divergences",
13215 params,
13216 "plot_bull",
13217 true,
13218 )?;
13219 let plot_hidden_bull = get_bool_param(
13220 "kase_peak_oscillator_with_divergences",
13221 params,
13222 "plot_hidden_bull",
13223 false,
13224 )?;
13225 let plot_bear = get_bool_param(
13226 "kase_peak_oscillator_with_divergences",
13227 params,
13228 "plot_bear",
13229 true,
13230 )?;
13231 let plot_hidden_bear = get_bool_param(
13232 "kase_peak_oscillator_with_divergences",
13233 params,
13234 "plot_hidden_bear",
13235 false,
13236 )?;
13237 let input = KasePeakOscillatorWithDivergencesInput::from_slices(
13238 high,
13239 low,
13240 close,
13241 KasePeakOscillatorWithDivergencesParams {
13242 deviations: Some(deviations),
13243 short_cycle: Some(short_cycle),
13244 long_cycle: Some(long_cycle),
13245 sensitivity: Some(sensitivity),
13246 all_peaks_mode: Some(all_peaks_mode),
13247 lb_r: Some(lb_r),
13248 lb_l: Some(lb_l),
13249 range_upper: Some(range_upper),
13250 range_lower: Some(range_lower),
13251 plot_bull: Some(plot_bull),
13252 plot_hidden_bull: Some(plot_hidden_bull),
13253 plot_bear: Some(plot_bear),
13254 plot_hidden_bear: Some(plot_hidden_bear),
13255 },
13256 );
13257 let out =
13258 kase_peak_oscillator_with_divergences_with_kernel(&input, kernel).map_err(|e| {
13259 IndicatorDispatchError::ComputeFailed {
13260 indicator: "kase_peak_oscillator_with_divergences".to_string(),
13261 details: e.to_string(),
13262 }
13263 })?;
13264 if output_id.eq_ignore_ascii_case("oscillator")
13265 || output_id.eq_ignore_ascii_case("value")
13266 {
13267 return Ok(out.oscillator);
13268 }
13269 if output_id.eq_ignore_ascii_case("hist") || output_id.eq_ignore_ascii_case("histogram")
13270 {
13271 return Ok(out.histogram);
13272 }
13273 if output_id.eq_ignore_ascii_case("max_peak_value") {
13274 return Ok(out.max_peak_value);
13275 }
13276 if output_id.eq_ignore_ascii_case("min_peak_value") {
13277 return Ok(out.min_peak_value);
13278 }
13279 if output_id.eq_ignore_ascii_case("market_extreme") {
13280 return Ok(out.market_extreme);
13281 }
13282 if output_id.eq_ignore_ascii_case("regular_bullish") {
13283 return Ok(out.regular_bullish);
13284 }
13285 if output_id.eq_ignore_ascii_case("hidden_bullish") {
13286 return Ok(out.hidden_bullish);
13287 }
13288 if output_id.eq_ignore_ascii_case("regular_bearish") {
13289 return Ok(out.regular_bearish);
13290 }
13291 if output_id.eq_ignore_ascii_case("hidden_bearish") {
13292 return Ok(out.hidden_bearish);
13293 }
13294 if output_id.eq_ignore_ascii_case("go_long") {
13295 return Ok(out.go_long);
13296 }
13297 if output_id.eq_ignore_ascii_case("go_short") {
13298 return Ok(out.go_short);
13299 }
13300 Err(IndicatorDispatchError::UnknownOutput {
13301 indicator: "kase_peak_oscillator_with_divergences".to_string(),
13302 output: output_id.to_string(),
13303 })
13304 },
13305 )
13306}
13307
13308fn compute_gopalakrishnan_range_index_batch(
13309 req: IndicatorBatchRequest<'_>,
13310 output_id: &str,
13311) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13312 expect_value_output("gopalakrishnan_range_index", output_id)?;
13313 let (high, low) = extract_high_low_input("gopalakrishnan_range_index", req.data)?;
13314 let kernel = req.kernel.to_non_batch();
13315 collect_f64(
13316 "gopalakrishnan_range_index",
13317 output_id,
13318 req.combos,
13319 high.len(),
13320 |params| {
13321 let length = get_usize_param("gopalakrishnan_range_index", params, "length", 5)?;
13322 let input = GopalakrishnanRangeIndexInput::from_slices(
13323 high,
13324 low,
13325 GopalakrishnanRangeIndexParams {
13326 length: Some(length),
13327 },
13328 );
13329 let out = gopalakrishnan_range_index_with_kernel(&input, kernel).map_err(|e| {
13330 IndicatorDispatchError::ComputeFailed {
13331 indicator: "gopalakrishnan_range_index".to_string(),
13332 details: e.to_string(),
13333 }
13334 })?;
13335 Ok(out.values)
13336 },
13337 )
13338}
13339
13340fn compute_acosc_batch(
13341 req: IndicatorBatchRequest<'_>,
13342 output_id: &str,
13343) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13344 let (high, low) = extract_high_low_input("acosc", req.data)?;
13345 let kernel = req.kernel.to_non_batch();
13346 collect_f64("acosc", output_id, req.combos, high.len(), |_params| {
13347 let input = AcoscInput::from_slices(high, low, AcoscParams::default());
13348 let out = acosc_with_kernel(&input, kernel).map_err(|e| {
13349 IndicatorDispatchError::ComputeFailed {
13350 indicator: "acosc".to_string(),
13351 details: e.to_string(),
13352 }
13353 })?;
13354 if output_id.eq_ignore_ascii_case("osc") || output_id.eq_ignore_ascii_case("value") {
13355 return Ok(out.osc);
13356 }
13357 if output_id.eq_ignore_ascii_case("change") {
13358 return Ok(out.change);
13359 }
13360 Err(IndicatorDispatchError::UnknownOutput {
13361 indicator: "acosc".to_string(),
13362 output: output_id.to_string(),
13363 })
13364 })
13365}
13366
13367fn compute_alligator_batch(
13368 req: IndicatorBatchRequest<'_>,
13369 output_id: &str,
13370) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13371 let data = extract_slice_input("alligator", req.data, "hl2")?;
13372 let kernel = req.kernel.to_non_batch();
13373 collect_f64("alligator", output_id, req.combos, data.len(), |params| {
13374 let jaw_period = get_usize_param("alligator", params, "jaw_period", 13)?;
13375 let jaw_offset = get_usize_param("alligator", params, "jaw_offset", 8)?;
13376 let teeth_period = get_usize_param("alligator", params, "teeth_period", 8)?;
13377 let teeth_offset = get_usize_param("alligator", params, "teeth_offset", 5)?;
13378 let lips_period = get_usize_param("alligator", params, "lips_period", 5)?;
13379 let lips_offset = get_usize_param("alligator", params, "lips_offset", 3)?;
13380 let input = AlligatorInput::from_slice(
13381 data,
13382 AlligatorParams {
13383 jaw_period: Some(jaw_period),
13384 jaw_offset: Some(jaw_offset),
13385 teeth_period: Some(teeth_period),
13386 teeth_offset: Some(teeth_offset),
13387 lips_period: Some(lips_period),
13388 lips_offset: Some(lips_offset),
13389 },
13390 );
13391 let out = alligator_with_kernel(&input, kernel).map_err(|e| {
13392 IndicatorDispatchError::ComputeFailed {
13393 indicator: "alligator".to_string(),
13394 details: e.to_string(),
13395 }
13396 })?;
13397 if output_id.eq_ignore_ascii_case("jaw") || output_id.eq_ignore_ascii_case("value") {
13398 return Ok(out.jaw);
13399 }
13400 if output_id.eq_ignore_ascii_case("teeth") {
13401 return Ok(out.teeth);
13402 }
13403 if output_id.eq_ignore_ascii_case("lips") {
13404 return Ok(out.lips);
13405 }
13406 Err(IndicatorDispatchError::UnknownOutput {
13407 indicator: "alligator".to_string(),
13408 output: output_id.to_string(),
13409 })
13410 })
13411}
13412
13413fn compute_alphatrend_batch(
13414 req: IndicatorBatchRequest<'_>,
13415 output_id: &str,
13416) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13417 let (open, high, low, close, volume) = extract_ohlcv_full_input("alphatrend", req.data)?;
13418 let kernel = req.kernel.to_non_batch();
13419 collect_f64("alphatrend", output_id, req.combos, close.len(), |params| {
13420 let coeff = get_f64_param("alphatrend", params, "coeff", 1.0)?;
13421 let period = get_usize_param("alphatrend", params, "period", 14)?;
13422 let no_volume = get_bool_param("alphatrend", params, "no_volume", false)?;
13423 let input = AlphaTrendInput::from_slices(
13424 open,
13425 high,
13426 low,
13427 close,
13428 volume,
13429 AlphaTrendParams {
13430 coeff: Some(coeff),
13431 period: Some(period),
13432 no_volume: Some(no_volume),
13433 },
13434 );
13435 let out = alphatrend_with_kernel(&input, kernel).map_err(|e| {
13436 IndicatorDispatchError::ComputeFailed {
13437 indicator: "alphatrend".to_string(),
13438 details: e.to_string(),
13439 }
13440 })?;
13441 if output_id.eq_ignore_ascii_case("k1") || output_id.eq_ignore_ascii_case("value") {
13442 return Ok(out.k1);
13443 }
13444 if output_id.eq_ignore_ascii_case("k2") {
13445 return Ok(out.k2);
13446 }
13447 Err(IndicatorDispatchError::UnknownOutput {
13448 indicator: "alphatrend".to_string(),
13449 output: output_id.to_string(),
13450 })
13451 })
13452}
13453
13454fn compute_aso_batch(
13455 req: IndicatorBatchRequest<'_>,
13456 output_id: &str,
13457) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13458 let (open, high, low, close) = match req.data {
13459 IndicatorDataRef::Candles { candles, source } => (
13460 candles.open.as_slice(),
13461 candles.high.as_slice(),
13462 candles.low.as_slice(),
13463 source_type(candles, source.unwrap_or("close")),
13464 ),
13465 IndicatorDataRef::Ohlc {
13466 open,
13467 high,
13468 low,
13469 close,
13470 } => {
13471 ensure_same_len_4("aso", open.len(), high.len(), low.len(), close.len())?;
13472 (open, high, low, close)
13473 }
13474 IndicatorDataRef::Ohlcv {
13475 open,
13476 high,
13477 low,
13478 close,
13479 volume,
13480 } => {
13481 ensure_same_len_5(
13482 "aso",
13483 open.len(),
13484 high.len(),
13485 low.len(),
13486 close.len(),
13487 volume.len(),
13488 )?;
13489 (open, high, low, close)
13490 }
13491 _ => {
13492 return Err(IndicatorDispatchError::MissingRequiredInput {
13493 indicator: "aso".to_string(),
13494 input: IndicatorInputKind::Ohlc,
13495 });
13496 }
13497 };
13498 let kernel = req.kernel.to_non_batch();
13499 collect_f64("aso", output_id, req.combos, close.len(), |params| {
13500 let period = get_usize_param("aso", params, "period", 10)?;
13501 let mode = get_usize_param("aso", params, "mode", 0)?;
13502 let input = AsoInput::from_slices(
13503 open,
13504 high,
13505 low,
13506 close,
13507 AsoParams {
13508 period: Some(period),
13509 mode: Some(mode),
13510 },
13511 );
13512 let out =
13513 aso_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
13514 indicator: "aso".to_string(),
13515 details: e.to_string(),
13516 })?;
13517 if output_id.eq_ignore_ascii_case("bulls") || output_id.eq_ignore_ascii_case("value") {
13518 return Ok(out.bulls);
13519 }
13520 if output_id.eq_ignore_ascii_case("bears") {
13521 return Ok(out.bears);
13522 }
13523 Err(IndicatorDispatchError::UnknownOutput {
13524 indicator: "aso".to_string(),
13525 output: output_id.to_string(),
13526 })
13527 })
13528}
13529
13530fn compute_avsl_batch(
13531 req: IndicatorBatchRequest<'_>,
13532 output_id: &str,
13533) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13534 expect_value_output("avsl", output_id)?;
13535 let (_high, low, close, volume) = extract_hlcv_input("avsl", req.data)?;
13536 let kernel = req.kernel.to_non_batch();
13537 collect_f64("avsl", output_id, req.combos, close.len(), |params| {
13538 let fast_period = get_usize_param("avsl", params, "fast_period", 12)?;
13539 let slow_period = get_usize_param("avsl", params, "slow_period", 26)?;
13540 let multiplier = get_f64_param("avsl", params, "multiplier", 2.0)?;
13541 let input = AvslInput::from_slices(
13542 close,
13543 low,
13544 volume,
13545 AvslParams {
13546 fast_period: Some(fast_period),
13547 slow_period: Some(slow_period),
13548 multiplier: Some(multiplier),
13549 },
13550 );
13551 let out = avsl_with_kernel(&input, kernel).map_err(|e| {
13552 IndicatorDispatchError::ComputeFailed {
13553 indicator: "avsl".to_string(),
13554 details: e.to_string(),
13555 }
13556 })?;
13557 Ok(out.values)
13558 })
13559}
13560
13561fn compute_bandpass_batch(
13562 req: IndicatorBatchRequest<'_>,
13563 output_id: &str,
13564) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13565 let data = extract_slice_input("bandpass", req.data, "close")?;
13566 let kernel = req.kernel.to_non_batch();
13567 collect_f64("bandpass", output_id, req.combos, data.len(), |params| {
13568 let period = get_usize_param("bandpass", params, "period", 20)?;
13569 let bandwidth = get_f64_param("bandpass", params, "bandwidth", 0.3)?;
13570 let input = BandPassInput::from_slice(
13571 data,
13572 BandPassParams {
13573 period: Some(period),
13574 bandwidth: Some(bandwidth),
13575 },
13576 );
13577 let out = bandpass_with_kernel(&input, kernel).map_err(|e| {
13578 IndicatorDispatchError::ComputeFailed {
13579 indicator: "bandpass".to_string(),
13580 details: e.to_string(),
13581 }
13582 })?;
13583 if output_id.eq_ignore_ascii_case("bp") || output_id.eq_ignore_ascii_case("value") {
13584 return Ok(out.bp);
13585 }
13586 if output_id.eq_ignore_ascii_case("bp_normalized")
13587 || output_id.eq_ignore_ascii_case("normalized")
13588 {
13589 return Ok(out.bp_normalized);
13590 }
13591 if output_id.eq_ignore_ascii_case("signal") {
13592 return Ok(out.signal);
13593 }
13594 if output_id.eq_ignore_ascii_case("trigger") {
13595 return Ok(out.trigger);
13596 }
13597 Err(IndicatorDispatchError::UnknownOutput {
13598 indicator: "bandpass".to_string(),
13599 output: output_id.to_string(),
13600 })
13601 })
13602}
13603
13604fn compute_chande_batch(
13605 req: IndicatorBatchRequest<'_>,
13606 output_id: &str,
13607) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13608 expect_value_output("chande", output_id)?;
13609 let (high, low, close) = extract_ohlc_input("chande", req.data)?;
13610 let kernel = req.kernel.to_non_batch();
13611 collect_f64("chande", output_id, req.combos, close.len(), |params| {
13612 let period = get_usize_param("chande", params, "period", 22)?;
13613 let mult = get_f64_param("chande", params, "mult", 3.0)?;
13614 let direction = get_enum_param("chande", params, "direction", "long")?;
13615 let input = ChandeInput::from_slices(
13616 high,
13617 low,
13618 close,
13619 ChandeParams {
13620 period: Some(period),
13621 mult: Some(mult),
13622 direction: Some(direction.to_string()),
13623 },
13624 );
13625 let out = chande_with_kernel(&input, kernel).map_err(|e| {
13626 IndicatorDispatchError::ComputeFailed {
13627 indicator: "chande".to_string(),
13628 details: e.to_string(),
13629 }
13630 })?;
13631 Ok(out.values)
13632 })
13633}
13634
13635fn compute_chandelier_exit_batch(
13636 req: IndicatorBatchRequest<'_>,
13637 output_id: &str,
13638) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13639 let (high, low, close) = extract_ohlc_input("chandelier_exit", req.data)?;
13640 let kernel = req.kernel.to_non_batch();
13641 collect_f64(
13642 "chandelier_exit",
13643 output_id,
13644 req.combos,
13645 close.len(),
13646 |params| {
13647 let period = get_usize_param("chandelier_exit", params, "period", 22)?;
13648 let mult = get_f64_param("chandelier_exit", params, "mult", 3.0)?;
13649 let use_close = get_bool_param("chandelier_exit", params, "use_close", true)?;
13650 let input = ChandelierExitInput::from_slices(
13651 high,
13652 low,
13653 close,
13654 ChandelierExitParams {
13655 period: Some(period),
13656 mult: Some(mult),
13657 use_close: Some(use_close),
13658 },
13659 );
13660 let out = chandelier_exit_with_kernel(&input, kernel).map_err(|e| {
13661 IndicatorDispatchError::ComputeFailed {
13662 indicator: "chandelier_exit".to_string(),
13663 details: e.to_string(),
13664 }
13665 })?;
13666 if output_id.eq_ignore_ascii_case("long_stop")
13667 || output_id.eq_ignore_ascii_case("value")
13668 {
13669 return Ok(out.long_stop);
13670 }
13671 if output_id.eq_ignore_ascii_case("short_stop") {
13672 return Ok(out.short_stop);
13673 }
13674 Err(IndicatorDispatchError::UnknownOutput {
13675 indicator: "chandelier_exit".to_string(),
13676 output: output_id.to_string(),
13677 })
13678 },
13679 )
13680}
13681
13682fn compute_cksp_batch(
13683 req: IndicatorBatchRequest<'_>,
13684 output_id: &str,
13685) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13686 let (high, low, close) = extract_ohlc_input("cksp", req.data)?;
13687 let kernel = req.kernel.to_non_batch();
13688 collect_f64("cksp", output_id, req.combos, close.len(), |params| {
13689 let p = get_usize_param("cksp", params, "p", 10)?;
13690 let x = get_f64_param("cksp", params, "x", 1.0)?;
13691 let q = get_usize_param("cksp", params, "q", 9)?;
13692 let input = CkspInput::from_slices(
13693 high,
13694 low,
13695 close,
13696 CkspParams {
13697 p: Some(p),
13698 x: Some(x),
13699 q: Some(q),
13700 },
13701 );
13702 let out = cksp_with_kernel(&input, kernel).map_err(|e| {
13703 IndicatorDispatchError::ComputeFailed {
13704 indicator: "cksp".to_string(),
13705 details: e.to_string(),
13706 }
13707 })?;
13708 if output_id.eq_ignore_ascii_case("long_values")
13709 || output_id.eq_ignore_ascii_case("long")
13710 || output_id.eq_ignore_ascii_case("value")
13711 {
13712 return Ok(out.long_values);
13713 }
13714 if output_id.eq_ignore_ascii_case("short_values") || output_id.eq_ignore_ascii_case("short")
13715 {
13716 return Ok(out.short_values);
13717 }
13718 Err(IndicatorDispatchError::UnknownOutput {
13719 indicator: "cksp".to_string(),
13720 output: output_id.to_string(),
13721 })
13722 })
13723}
13724
13725fn compute_correlation_cycle_batch(
13726 req: IndicatorBatchRequest<'_>,
13727 output_id: &str,
13728) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13729 let data = extract_slice_input("correlation_cycle", req.data, "close")?;
13730 let kernel = req.kernel.to_non_batch();
13731 collect_f64(
13732 "correlation_cycle",
13733 output_id,
13734 req.combos,
13735 data.len(),
13736 |params| {
13737 let period = get_usize_param("correlation_cycle", params, "period", 20)?;
13738 let threshold = get_f64_param("correlation_cycle", params, "threshold", 9.0)?;
13739 let input = CorrelationCycleInput::from_slice(
13740 data,
13741 CorrelationCycleParams {
13742 period: Some(period),
13743 threshold: Some(threshold),
13744 },
13745 );
13746 let out = correlation_cycle_with_kernel(&input, kernel).map_err(|e| {
13747 IndicatorDispatchError::ComputeFailed {
13748 indicator: "correlation_cycle".to_string(),
13749 details: e.to_string(),
13750 }
13751 })?;
13752 if output_id.eq_ignore_ascii_case("real") || output_id.eq_ignore_ascii_case("value") {
13753 return Ok(out.real);
13754 }
13755 if output_id.eq_ignore_ascii_case("imag") {
13756 return Ok(out.imag);
13757 }
13758 if output_id.eq_ignore_ascii_case("angle") {
13759 return Ok(out.angle);
13760 }
13761 if output_id.eq_ignore_ascii_case("state") {
13762 return Ok(out.state);
13763 }
13764 Err(IndicatorDispatchError::UnknownOutput {
13765 indicator: "correlation_cycle".to_string(),
13766 output: output_id.to_string(),
13767 })
13768 },
13769 )
13770}
13771
13772fn compute_damiani_volatmeter_batch(
13773 req: IndicatorBatchRequest<'_>,
13774 output_id: &str,
13775) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13776 let data = extract_slice_input("damiani_volatmeter", req.data, "close")?;
13777 let kernel = req.kernel.to_non_batch();
13778 collect_f64(
13779 "damiani_volatmeter",
13780 output_id,
13781 req.combos,
13782 data.len(),
13783 |params| {
13784 let vis_atr = get_usize_param("damiani_volatmeter", params, "vis_atr", 13)?;
13785 let vis_std = get_usize_param("damiani_volatmeter", params, "vis_std", 20)?;
13786 let sed_atr = get_usize_param("damiani_volatmeter", params, "sed_atr", 40)?;
13787 let sed_std = get_usize_param("damiani_volatmeter", params, "sed_std", 100)?;
13788 let threshold = get_f64_param("damiani_volatmeter", params, "threshold", 1.4)?;
13789 let input = DamianiVolatmeterInput::from_slice(
13790 data,
13791 DamianiVolatmeterParams {
13792 vis_atr: Some(vis_atr),
13793 vis_std: Some(vis_std),
13794 sed_atr: Some(sed_atr),
13795 sed_std: Some(sed_std),
13796 threshold: Some(threshold),
13797 },
13798 );
13799 let out = damiani_volatmeter_with_kernel(&input, kernel).map_err(|e| {
13800 IndicatorDispatchError::ComputeFailed {
13801 indicator: "damiani_volatmeter".to_string(),
13802 details: e.to_string(),
13803 }
13804 })?;
13805 if output_id.eq_ignore_ascii_case("vol") || output_id.eq_ignore_ascii_case("value") {
13806 return Ok(out.vol);
13807 }
13808 if output_id.eq_ignore_ascii_case("anti") {
13809 return Ok(out.anti);
13810 }
13811 Err(IndicatorDispatchError::UnknownOutput {
13812 indicator: "damiani_volatmeter".to_string(),
13813 output: output_id.to_string(),
13814 })
13815 },
13816 )
13817}
13818
13819fn compute_dvdiqqe_batch(
13820 req: IndicatorBatchRequest<'_>,
13821 output_id: &str,
13822) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13823 let (open, high, low, close, volume) = match req.data {
13824 IndicatorDataRef::Candles { candles, .. } => (
13825 candles.open.as_slice(),
13826 candles.high.as_slice(),
13827 candles.low.as_slice(),
13828 candles.close.as_slice(),
13829 Some(candles.volume.as_slice()),
13830 ),
13831 IndicatorDataRef::Ohlcv {
13832 open,
13833 high,
13834 low,
13835 close,
13836 volume,
13837 } => {
13838 ensure_same_len_5(
13839 "dvdiqqe",
13840 open.len(),
13841 high.len(),
13842 low.len(),
13843 close.len(),
13844 volume.len(),
13845 )?;
13846 (open, high, low, close, Some(volume))
13847 }
13848 IndicatorDataRef::Ohlc {
13849 open,
13850 high,
13851 low,
13852 close,
13853 } => {
13854 ensure_same_len_4("dvdiqqe", open.len(), high.len(), low.len(), close.len())?;
13855 (open, high, low, close, None)
13856 }
13857 _ => {
13858 return Err(IndicatorDispatchError::MissingRequiredInput {
13859 indicator: "dvdiqqe".to_string(),
13860 input: IndicatorInputKind::Ohlc,
13861 })
13862 }
13863 };
13864 let kernel = req.kernel.to_non_batch();
13865 collect_f64("dvdiqqe", output_id, req.combos, close.len(), |params| {
13866 let period = get_usize_param("dvdiqqe", params, "period", 13)?;
13867 let smoothing_period = get_usize_param("dvdiqqe", params, "smoothing_period", 6)?;
13868 let fast_multiplier = get_f64_param("dvdiqqe", params, "fast_multiplier", 2.618)?;
13869 let slow_multiplier = get_f64_param("dvdiqqe", params, "slow_multiplier", 4.236)?;
13870 let volume_type = get_enum_param("dvdiqqe", params, "volume_type", "default")?;
13871 let center_type = get_enum_param("dvdiqqe", params, "center_type", "dynamic")?;
13872 let tick_size = get_f64_param("dvdiqqe", params, "tick_size", 0.01)?;
13873 let input = DvdiqqeInput::from_slices(
13874 open,
13875 high,
13876 low,
13877 close,
13878 volume,
13879 DvdiqqeParams {
13880 period: Some(period),
13881 smoothing_period: Some(smoothing_period),
13882 fast_multiplier: Some(fast_multiplier),
13883 slow_multiplier: Some(slow_multiplier),
13884 volume_type: Some(volume_type),
13885 center_type: Some(center_type),
13886 tick_size: Some(tick_size),
13887 },
13888 );
13889 let out = dvdiqqe_with_kernel(&input, kernel).map_err(|e| {
13890 IndicatorDispatchError::ComputeFailed {
13891 indicator: "dvdiqqe".to_string(),
13892 details: e.to_string(),
13893 }
13894 })?;
13895 if output_id.eq_ignore_ascii_case("dvdi") || output_id.eq_ignore_ascii_case("value") {
13896 return Ok(out.dvdi);
13897 }
13898 if output_id.eq_ignore_ascii_case("fast_tl") || output_id.eq_ignore_ascii_case("fast") {
13899 return Ok(out.fast_tl);
13900 }
13901 if output_id.eq_ignore_ascii_case("slow_tl") || output_id.eq_ignore_ascii_case("slow") {
13902 return Ok(out.slow_tl);
13903 }
13904 if output_id.eq_ignore_ascii_case("center_line") || output_id.eq_ignore_ascii_case("center")
13905 {
13906 return Ok(out.center_line);
13907 }
13908 Err(IndicatorDispatchError::UnknownOutput {
13909 indicator: "dvdiqqe".to_string(),
13910 output: output_id.to_string(),
13911 })
13912 })
13913}
13914
13915fn compute_emd_batch(
13916 req: IndicatorBatchRequest<'_>,
13917 output_id: &str,
13918) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13919 let (high, low, close, volume) = extract_hlcv_input("emd", req.data)?;
13920 let kernel = req.kernel.to_non_batch();
13921 collect_f64("emd", output_id, req.combos, close.len(), |params| {
13922 let period = get_usize_param("emd", params, "period", 20)?;
13923 let delta = get_f64_param("emd", params, "delta", 0.5)?;
13924 let fraction = get_f64_param("emd", params, "fraction", 0.1)?;
13925 let input = EmdInput::from_slices(
13926 high,
13927 low,
13928 close,
13929 volume,
13930 EmdParams {
13931 period: Some(period),
13932 delta: Some(delta),
13933 fraction: Some(fraction),
13934 },
13935 );
13936 let out =
13937 emd_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
13938 indicator: "emd".to_string(),
13939 details: e.to_string(),
13940 })?;
13941 if output_id.eq_ignore_ascii_case("upperband")
13942 || output_id.eq_ignore_ascii_case("upper")
13943 || output_id.eq_ignore_ascii_case("value")
13944 {
13945 return Ok(out.upperband);
13946 }
13947 if output_id.eq_ignore_ascii_case("middleband") || output_id.eq_ignore_ascii_case("middle")
13948 {
13949 return Ok(out.middleband);
13950 }
13951 if output_id.eq_ignore_ascii_case("lowerband") || output_id.eq_ignore_ascii_case("lower") {
13952 return Ok(out.lowerband);
13953 }
13954 Err(IndicatorDispatchError::UnknownOutput {
13955 indicator: "emd".to_string(),
13956 output: output_id.to_string(),
13957 })
13958 })
13959}
13960
13961fn compute_emd_trend_batch(
13962 req: IndicatorBatchRequest<'_>,
13963 output_id: &str,
13964) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
13965 let (open, high, low, close) = extract_ohlc_full_input("emd_trend", req.data)?;
13966 let kernel = req.kernel.to_non_batch();
13967 collect_f64("emd_trend", output_id, req.combos, close.len(), |params| {
13968 let source = get_enum_param("emd_trend", params, "source", "close")?;
13969 let avg_type = get_enum_param("emd_trend", params, "avg_type", "SMA")?;
13970 let length = get_usize_param("emd_trend", params, "length", 28)?;
13971 let mult = get_f64_param("emd_trend", params, "mult", 1.0)?;
13972 let input = EmdTrendInput::from_slices(
13973 open,
13974 high,
13975 low,
13976 close,
13977 EmdTrendParams {
13978 source: Some(source),
13979 avg_type: Some(avg_type),
13980 length: Some(length),
13981 mult: Some(mult),
13982 },
13983 );
13984 let out = emd_trend_with_kernel(&input, kernel).map_err(|e| {
13985 IndicatorDispatchError::ComputeFailed {
13986 indicator: "emd_trend".to_string(),
13987 details: e.to_string(),
13988 }
13989 })?;
13990 if output_id.eq_ignore_ascii_case("direction") {
13991 return Ok(out.direction);
13992 }
13993 if output_id.eq_ignore_ascii_case("average") || output_id.eq_ignore_ascii_case("value") {
13994 return Ok(out.average);
13995 }
13996 if output_id.eq_ignore_ascii_case("upper") {
13997 return Ok(out.upper);
13998 }
13999 if output_id.eq_ignore_ascii_case("lower") {
14000 return Ok(out.lower);
14001 }
14002 Err(IndicatorDispatchError::UnknownOutput {
14003 indicator: "emd_trend".to_string(),
14004 output: output_id.to_string(),
14005 })
14006 })
14007}
14008
14009fn compute_cyberpunk_value_trend_analyzer_batch(
14010 req: IndicatorBatchRequest<'_>,
14011 output_id: &str,
14012) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14013 let (open, high, low, close) =
14014 extract_ohlc_full_input("cyberpunk_value_trend_analyzer", req.data)?;
14015 let kernel = req.kernel.to_non_batch();
14016 collect_f64(
14017 "cyberpunk_value_trend_analyzer",
14018 output_id,
14019 req.combos,
14020 close.len(),
14021 |params| {
14022 let entry_level =
14023 get_usize_param("cyberpunk_value_trend_analyzer", params, "entry_level", 30)?;
14024 let exit_level =
14025 get_usize_param("cyberpunk_value_trend_analyzer", params, "exit_level", 75)?;
14026 let input = CyberpunkValueTrendAnalyzerInput::from_slices(
14027 open,
14028 high,
14029 low,
14030 close,
14031 CyberpunkValueTrendAnalyzerParams {
14032 entry_level: Some(entry_level),
14033 exit_level: Some(exit_level),
14034 },
14035 );
14036 let out = cyberpunk_value_trend_analyzer_with_kernel(&input, kernel).map_err(|e| {
14037 IndicatorDispatchError::ComputeFailed {
14038 indicator: "cyberpunk_value_trend_analyzer".to_string(),
14039 details: e.to_string(),
14040 }
14041 })?;
14042 if output_id.eq_ignore_ascii_case("value_trend")
14043 || output_id.eq_ignore_ascii_case("value")
14044 {
14045 return Ok(out.value_trend);
14046 }
14047 if output_id.eq_ignore_ascii_case("value_trend_lag")
14048 || output_id.eq_ignore_ascii_case("lag")
14049 {
14050 return Ok(out.value_trend_lag);
14051 }
14052 if output_id.eq_ignore_ascii_case("deviation_index") {
14053 return Ok(out.deviation_index);
14054 }
14055 if output_id.eq_ignore_ascii_case("overbought_signal")
14056 || output_id.eq_ignore_ascii_case("overbought")
14057 {
14058 return Ok(out.overbought_signal);
14059 }
14060 if output_id.eq_ignore_ascii_case("buy_signal") {
14061 return Ok(out.buy_signal);
14062 }
14063 if output_id.eq_ignore_ascii_case("sell_signal") {
14064 return Ok(out.sell_signal);
14065 }
14066 Err(IndicatorDispatchError::UnknownOutput {
14067 indicator: "cyberpunk_value_trend_analyzer".to_string(),
14068 output: output_id.to_string(),
14069 })
14070 },
14071 )
14072}
14073
14074fn compute_eri_batch(
14075 req: IndicatorBatchRequest<'_>,
14076 output_id: &str,
14077) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14078 let (high, low, source) = match req.data {
14079 IndicatorDataRef::Candles { candles, source } => (
14080 candles.high.as_slice(),
14081 candles.low.as_slice(),
14082 source_type(candles, source.unwrap_or("close")),
14083 ),
14084 IndicatorDataRef::Ohlc {
14085 open,
14086 high,
14087 low,
14088 close,
14089 } => {
14090 ensure_same_len_4("eri", open.len(), high.len(), low.len(), close.len())?;
14091 (high, low, close)
14092 }
14093 IndicatorDataRef::Ohlcv {
14094 open,
14095 high,
14096 low,
14097 close,
14098 volume,
14099 } => {
14100 ensure_same_len_5(
14101 "eri",
14102 open.len(),
14103 high.len(),
14104 low.len(),
14105 close.len(),
14106 volume.len(),
14107 )?;
14108 (high, low, close)
14109 }
14110 _ => {
14111 return Err(IndicatorDispatchError::MissingRequiredInput {
14112 indicator: "eri".to_string(),
14113 input: IndicatorInputKind::Ohlc,
14114 });
14115 }
14116 };
14117 let kernel = req.kernel.to_non_batch();
14118 collect_f64("eri", output_id, req.combos, source.len(), |params| {
14119 let period = get_usize_param("eri", params, "period", 13)?;
14120 let ma_type = get_enum_param("eri", params, "ma_type", "ema")?;
14121 let input = EriInput::from_slices(
14122 high,
14123 low,
14124 source,
14125 EriParams {
14126 period: Some(period),
14127 ma_type: Some(ma_type),
14128 },
14129 );
14130 let out =
14131 eri_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
14132 indicator: "eri".to_string(),
14133 details: e.to_string(),
14134 })?;
14135 if output_id.eq_ignore_ascii_case("bull") || output_id.eq_ignore_ascii_case("value") {
14136 return Ok(out.bull);
14137 }
14138 if output_id.eq_ignore_ascii_case("bear") {
14139 return Ok(out.bear);
14140 }
14141 Err(IndicatorDispatchError::UnknownOutput {
14142 indicator: "eri".to_string(),
14143 output: output_id.to_string(),
14144 })
14145 })
14146}
14147
14148fn compute_fisher_batch(
14149 req: IndicatorBatchRequest<'_>,
14150 output_id: &str,
14151) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14152 let (high, low) = extract_high_low_input("fisher", req.data)?;
14153 let kernel = req.kernel.to_non_batch();
14154 collect_f64("fisher", output_id, req.combos, high.len(), |params| {
14155 let period = get_usize_param("fisher", params, "period", 9)?;
14156 let input = FisherInput::from_slices(
14157 high,
14158 low,
14159 FisherParams {
14160 period: Some(period),
14161 },
14162 );
14163 let out = fisher_with_kernel(&input, kernel).map_err(|e| {
14164 IndicatorDispatchError::ComputeFailed {
14165 indicator: "fisher".to_string(),
14166 details: e.to_string(),
14167 }
14168 })?;
14169 if output_id.eq_ignore_ascii_case("fisher") || output_id.eq_ignore_ascii_case("value") {
14170 return Ok(out.fisher);
14171 }
14172 if output_id.eq_ignore_ascii_case("signal") {
14173 return Ok(out.signal);
14174 }
14175 Err(IndicatorDispatchError::UnknownOutput {
14176 indicator: "fisher".to_string(),
14177 output: output_id.to_string(),
14178 })
14179 })
14180}
14181
14182fn compute_fvg_positioning_average_batch(
14183 req: IndicatorBatchRequest<'_>,
14184 output_id: &str,
14185) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14186 let (open, high, low, close) = extract_ohlc_full_input("fvg_positioning_average", req.data)?;
14187 let kernel = req.kernel.to_non_batch();
14188 collect_f64(
14189 "fvg_positioning_average",
14190 output_id,
14191 req.combos,
14192 close.len(),
14193 |params| {
14194 let lookback = get_usize_param("fvg_positioning_average", params, "lookback", 30)?;
14195 let lookback_type = get_enum_param(
14196 "fvg_positioning_average",
14197 params,
14198 "lookback_type",
14199 "Bar Count",
14200 )?;
14201 let atr_multiplier =
14202 get_f64_param("fvg_positioning_average", params, "atr_multiplier", 0.25)?;
14203 let input = FvgPositioningAverageInput::from_slices(
14204 open,
14205 high,
14206 low,
14207 close,
14208 FvgPositioningAverageParams {
14209 lookback: Some(lookback),
14210 lookback_type: Some(lookback_type),
14211 atr_multiplier: Some(atr_multiplier),
14212 },
14213 );
14214 let out = fvg_positioning_average_with_kernel(&input, kernel).map_err(|e| {
14215 IndicatorDispatchError::ComputeFailed {
14216 indicator: "fvg_positioning_average".to_string(),
14217 details: e.to_string(),
14218 }
14219 })?;
14220 if output_id.eq_ignore_ascii_case("bull_average")
14221 || output_id.eq_ignore_ascii_case("value")
14222 {
14223 return Ok(out.bull_average);
14224 }
14225 if output_id.eq_ignore_ascii_case("bear_average") {
14226 return Ok(out.bear_average);
14227 }
14228 if output_id.eq_ignore_ascii_case("bull_mid") {
14229 return Ok(out.bull_mid);
14230 }
14231 if output_id.eq_ignore_ascii_case("bear_mid") {
14232 return Ok(out.bear_mid);
14233 }
14234 Err(IndicatorDispatchError::UnknownOutput {
14235 indicator: "fvg_positioning_average".to_string(),
14236 output: output_id.to_string(),
14237 })
14238 },
14239 )
14240}
14241
14242fn compute_fvg_trailing_stop_batch(
14243 req: IndicatorBatchRequest<'_>,
14244 output_id: &str,
14245) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14246 let (high, low, close) = extract_ohlc_input("fvg_trailing_stop", req.data)?;
14247 let kernel = req.kernel.to_non_batch();
14248 collect_f64(
14249 "fvg_trailing_stop",
14250 output_id,
14251 req.combos,
14252 close.len(),
14253 |params| {
14254 let lookback =
14255 get_usize_param("fvg_trailing_stop", params, "unmitigated_fvg_lookback", 5)?;
14256 let smoothing_length =
14257 get_usize_param("fvg_trailing_stop", params, "smoothing_length", 9)?;
14258 let reset_on_cross =
14259 get_bool_param("fvg_trailing_stop", params, "reset_on_cross", false)?;
14260 let input = FvgTrailingStopInput::from_slices(
14261 high,
14262 low,
14263 close,
14264 FvgTrailingStopParams {
14265 unmitigated_fvg_lookback: Some(lookback),
14266 smoothing_length: Some(smoothing_length),
14267 reset_on_cross: Some(reset_on_cross),
14268 },
14269 );
14270 let out = fvg_trailing_stop_with_kernel(&input, kernel).map_err(|e| {
14271 IndicatorDispatchError::ComputeFailed {
14272 indicator: "fvg_trailing_stop".to_string(),
14273 details: e.to_string(),
14274 }
14275 })?;
14276 if output_id.eq_ignore_ascii_case("upper") || output_id.eq_ignore_ascii_case("value") {
14277 return Ok(out.upper);
14278 }
14279 if output_id.eq_ignore_ascii_case("lower") {
14280 return Ok(out.lower);
14281 }
14282 if output_id.eq_ignore_ascii_case("upper_ts") {
14283 return Ok(out.upper_ts);
14284 }
14285 if output_id.eq_ignore_ascii_case("lower_ts") {
14286 return Ok(out.lower_ts);
14287 }
14288 Err(IndicatorDispatchError::UnknownOutput {
14289 indicator: "fvg_trailing_stop".to_string(),
14290 output: output_id.to_string(),
14291 })
14292 },
14293 )
14294}
14295
14296fn compute_gatorosc_batch(
14297 req: IndicatorBatchRequest<'_>,
14298 output_id: &str,
14299) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14300 let data = extract_slice_input("gatorosc", req.data, "close")?;
14301 let kernel = req.kernel.to_non_batch();
14302 collect_f64("gatorosc", output_id, req.combos, data.len(), |params| {
14303 let jaws_length = get_usize_param("gatorosc", params, "jaws_length", 13)?;
14304 let jaws_shift = get_usize_param("gatorosc", params, "jaws_shift", 8)?;
14305 let teeth_length = get_usize_param("gatorosc", params, "teeth_length", 8)?;
14306 let teeth_shift = get_usize_param("gatorosc", params, "teeth_shift", 5)?;
14307 let lips_length = get_usize_param("gatorosc", params, "lips_length", 5)?;
14308 let lips_shift = get_usize_param("gatorosc", params, "lips_shift", 3)?;
14309 let input = GatorOscInput::from_slice(
14310 data,
14311 GatorOscParams {
14312 jaws_length: Some(jaws_length),
14313 jaws_shift: Some(jaws_shift),
14314 teeth_length: Some(teeth_length),
14315 teeth_shift: Some(teeth_shift),
14316 lips_length: Some(lips_length),
14317 lips_shift: Some(lips_shift),
14318 },
14319 );
14320 let out = gatorosc_with_kernel(&input, kernel).map_err(|e| {
14321 IndicatorDispatchError::ComputeFailed {
14322 indicator: "gatorosc".to_string(),
14323 details: e.to_string(),
14324 }
14325 })?;
14326 if output_id.eq_ignore_ascii_case("upper") || output_id.eq_ignore_ascii_case("value") {
14327 return Ok(out.upper);
14328 }
14329 if output_id.eq_ignore_ascii_case("lower") {
14330 return Ok(out.lower);
14331 }
14332 if output_id.eq_ignore_ascii_case("upper_change") {
14333 return Ok(out.upper_change);
14334 }
14335 if output_id.eq_ignore_ascii_case("lower_change") {
14336 return Ok(out.lower_change);
14337 }
14338 Err(IndicatorDispatchError::UnknownOutput {
14339 indicator: "gatorosc".to_string(),
14340 output: output_id.to_string(),
14341 })
14342 })
14343}
14344
14345fn compute_halftrend_batch(
14346 req: IndicatorBatchRequest<'_>,
14347 output_id: &str,
14348) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14349 let (high, low, close) = extract_ohlc_input("halftrend", req.data)?;
14350 let kernel = req.kernel.to_non_batch();
14351 collect_f64("halftrend", output_id, req.combos, close.len(), |params| {
14352 let amplitude = get_usize_param("halftrend", params, "amplitude", 2)?;
14353 let channel_deviation = get_f64_param("halftrend", params, "channel_deviation", 2.0)?;
14354 let atr_period = get_usize_param("halftrend", params, "atr_period", 100)?;
14355 let input = HalfTrendInput::from_slices(
14356 high,
14357 low,
14358 close,
14359 HalfTrendParams {
14360 amplitude: Some(amplitude),
14361 channel_deviation: Some(channel_deviation),
14362 atr_period: Some(atr_period),
14363 },
14364 );
14365 let out = halftrend_with_kernel(&input, kernel).map_err(|e| {
14366 IndicatorDispatchError::ComputeFailed {
14367 indicator: "halftrend".to_string(),
14368 details: e.to_string(),
14369 }
14370 })?;
14371 if output_id.eq_ignore_ascii_case("halftrend") || output_id.eq_ignore_ascii_case("value") {
14372 return Ok(out.halftrend);
14373 }
14374 if output_id.eq_ignore_ascii_case("trend") {
14375 return Ok(out.trend);
14376 }
14377 if output_id.eq_ignore_ascii_case("atr_high") {
14378 return Ok(out.atr_high);
14379 }
14380 if output_id.eq_ignore_ascii_case("atr_low") {
14381 return Ok(out.atr_low);
14382 }
14383 if output_id.eq_ignore_ascii_case("buy_signal") || output_id.eq_ignore_ascii_case("buy") {
14384 return Ok(out.buy_signal);
14385 }
14386 if output_id.eq_ignore_ascii_case("sell_signal") || output_id.eq_ignore_ascii_case("sell") {
14387 return Ok(out.sell_signal);
14388 }
14389 Err(IndicatorDispatchError::UnknownOutput {
14390 indicator: "halftrend".to_string(),
14391 output: output_id.to_string(),
14392 })
14393 })
14394}
14395
14396fn compute_safezonestop_batch(
14397 req: IndicatorBatchRequest<'_>,
14398 output_id: &str,
14399) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14400 let (high, low) = extract_high_low_input("safezonestop", req.data)?;
14401 let kernel = req.kernel.to_non_batch();
14402 collect_f64(
14403 "safezonestop",
14404 output_id,
14405 req.combos,
14406 high.len(),
14407 |params| {
14408 let period = get_usize_param("safezonestop", params, "period", 22)?;
14409 let mult = get_f64_param("safezonestop", params, "mult", 2.5)?;
14410 let max_lookback = get_usize_param("safezonestop", params, "max_lookback", 3)?;
14411 let direction = get_enum_param("safezonestop", params, "direction", "long")?;
14412 let input = SafeZoneStopInput::from_slices(
14413 high,
14414 low,
14415 direction.as_str(),
14416 SafeZoneStopParams {
14417 period: Some(period),
14418 mult: Some(mult),
14419 max_lookback: Some(max_lookback),
14420 },
14421 );
14422 let out = safezonestop_with_kernel(&input, kernel).map_err(|e| {
14423 IndicatorDispatchError::ComputeFailed {
14424 indicator: "safezonestop".to_string(),
14425 details: e.to_string(),
14426 }
14427 })?;
14428 if output_id.eq_ignore_ascii_case("value") {
14429 return Ok(out.values);
14430 }
14431 Err(IndicatorDispatchError::UnknownOutput {
14432 indicator: "safezonestop".to_string(),
14433 output: output_id.to_string(),
14434 })
14435 },
14436 )
14437}
14438
14439fn compute_devstop_batch(
14440 req: IndicatorBatchRequest<'_>,
14441 output_id: &str,
14442) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14443 let (high, low) = extract_high_low_input("devstop", req.data)?;
14444 let kernel = req.kernel.to_non_batch();
14445 collect_f64("devstop", output_id, req.combos, high.len(), |params| {
14446 let period = get_usize_param("devstop", params, "period", 20)?;
14447 let mult = get_f64_param("devstop", params, "mult", 0.0)?;
14448 let devtype = get_usize_param("devstop", params, "devtype", 0)?;
14449 let direction = get_enum_param("devstop", params, "direction", "long")?;
14450 let ma_type = get_enum_param("devstop", params, "ma_type", "sma")?;
14451 let input = DevStopInput::from_slices(
14452 high,
14453 low,
14454 DevStopParams {
14455 period: Some(period),
14456 mult: Some(mult),
14457 devtype: Some(devtype),
14458 direction: Some(direction),
14459 ma_type: Some(ma_type),
14460 },
14461 );
14462 let out = devstop_with_kernel(&input, kernel).map_err(|e| {
14463 IndicatorDispatchError::ComputeFailed {
14464 indicator: "devstop".to_string(),
14465 details: e.to_string(),
14466 }
14467 })?;
14468 if output_id.eq_ignore_ascii_case("value") {
14469 return Ok(out.values);
14470 }
14471 Err(IndicatorDispatchError::UnknownOutput {
14472 indicator: "devstop".to_string(),
14473 output: output_id.to_string(),
14474 })
14475 })
14476}
14477
14478fn compute_chop_batch(
14479 req: IndicatorBatchRequest<'_>,
14480 output_id: &str,
14481) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14482 let (high, low, close) = extract_ohlc_input("chop", req.data)?;
14483 let kernel = req.kernel.to_non_batch();
14484 collect_f64("chop", output_id, req.combos, close.len(), |params| {
14485 let period = get_usize_param("chop", params, "period", 14)?;
14486 let scalar = get_f64_param("chop", params, "scalar", 100.0)?;
14487 let drift = get_usize_param("chop", params, "drift", 1)?;
14488 let input = ChopInput::from_slices(
14489 high,
14490 low,
14491 close,
14492 ChopParams {
14493 period: Some(period),
14494 scalar: Some(scalar),
14495 drift: Some(drift),
14496 },
14497 );
14498 let out = chop_with_kernel(&input, kernel).map_err(|e| {
14499 IndicatorDispatchError::ComputeFailed {
14500 indicator: "chop".to_string(),
14501 details: e.to_string(),
14502 }
14503 })?;
14504 if output_id.eq_ignore_ascii_case("value") {
14505 return Ok(out.values);
14506 }
14507 Err(IndicatorDispatchError::UnknownOutput {
14508 indicator: "chop".to_string(),
14509 output: output_id.to_string(),
14510 })
14511 })
14512}
14513
14514fn compute_kst_batch(
14515 req: IndicatorBatchRequest<'_>,
14516 output_id: &str,
14517) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14518 let data = extract_slice_input("kst", req.data, "close")?;
14519 let kernel = req.kernel.to_non_batch();
14520 collect_f64("kst", output_id, req.combos, data.len(), |params| {
14521 let sma_period1 = get_usize_param("kst", params, "sma_period1", 10)?;
14522 let sma_period2 = get_usize_param("kst", params, "sma_period2", 10)?;
14523 let sma_period3 = get_usize_param("kst", params, "sma_period3", 10)?;
14524 let sma_period4 = get_usize_param("kst", params, "sma_period4", 15)?;
14525 let roc_period1 = get_usize_param("kst", params, "roc_period1", 10)?;
14526 let roc_period2 = get_usize_param("kst", params, "roc_period2", 15)?;
14527 let roc_period3 = get_usize_param("kst", params, "roc_period3", 20)?;
14528 let roc_period4 = get_usize_param("kst", params, "roc_period4", 30)?;
14529 let signal_period = get_usize_param("kst", params, "signal_period", 9)?;
14530 let input = KstInput::from_slice(
14531 data,
14532 KstParams {
14533 sma_period1: Some(sma_period1),
14534 sma_period2: Some(sma_period2),
14535 sma_period3: Some(sma_period3),
14536 sma_period4: Some(sma_period4),
14537 roc_period1: Some(roc_period1),
14538 roc_period2: Some(roc_period2),
14539 roc_period3: Some(roc_period3),
14540 roc_period4: Some(roc_period4),
14541 signal_period: Some(signal_period),
14542 },
14543 );
14544 let out =
14545 kst_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
14546 indicator: "kst".to_string(),
14547 details: e.to_string(),
14548 })?;
14549 if output_id.eq_ignore_ascii_case("line") || output_id.eq_ignore_ascii_case("value") {
14550 return Ok(out.line);
14551 }
14552 if output_id.eq_ignore_ascii_case("signal") {
14553 return Ok(out.signal);
14554 }
14555 Err(IndicatorDispatchError::UnknownOutput {
14556 indicator: "kst".to_string(),
14557 output: output_id.to_string(),
14558 })
14559 })
14560}
14561
14562fn compute_kaufmanstop_batch(
14563 req: IndicatorBatchRequest<'_>,
14564 output_id: &str,
14565) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14566 expect_value_output("kaufmanstop", output_id)?;
14567 let (high, low) = extract_high_low_input("kaufmanstop", req.data)?;
14568 let kernel = req.kernel.to_non_batch();
14569 collect_f64("kaufmanstop", output_id, req.combos, high.len(), |params| {
14570 let period = get_usize_param("kaufmanstop", params, "period", 22)?;
14571 let mult = get_f64_param("kaufmanstop", params, "mult", 2.0)?;
14572 let direction = get_enum_param("kaufmanstop", params, "direction", "long")?;
14573 let ma_type = get_enum_param("kaufmanstop", params, "ma_type", "sma")?;
14574 let input = KaufmanstopInput::from_slices(
14575 high,
14576 low,
14577 KaufmanstopParams {
14578 period: Some(period),
14579 mult: Some(mult),
14580 direction: Some(direction),
14581 ma_type: Some(ma_type),
14582 },
14583 );
14584 let out = kaufmanstop_with_kernel(&input, kernel).map_err(|e| {
14585 IndicatorDispatchError::ComputeFailed {
14586 indicator: "kaufmanstop".to_string(),
14587 details: e.to_string(),
14588 }
14589 })?;
14590 Ok(out.values)
14591 })
14592}
14593
14594fn compute_lpc_batch(
14595 req: IndicatorBatchRequest<'_>,
14596 output_id: &str,
14597) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14598 let (high, low, close, src) = match req.data {
14599 IndicatorDataRef::Candles { candles, source } => (
14600 candles.high.as_slice(),
14601 candles.low.as_slice(),
14602 candles.close.as_slice(),
14603 source_type(candles, source.unwrap_or("close")),
14604 ),
14605 IndicatorDataRef::Ohlc {
14606 open,
14607 high,
14608 low,
14609 close,
14610 } => {
14611 ensure_same_len_4("lpc", open.len(), high.len(), low.len(), close.len())?;
14612 (high, low, close, close)
14613 }
14614 IndicatorDataRef::Ohlcv {
14615 open,
14616 high,
14617 low,
14618 close,
14619 volume,
14620 } => {
14621 ensure_same_len_5(
14622 "lpc",
14623 open.len(),
14624 high.len(),
14625 low.len(),
14626 close.len(),
14627 volume.len(),
14628 )?;
14629 (high, low, close, close)
14630 }
14631 _ => {
14632 return Err(IndicatorDispatchError::MissingRequiredInput {
14633 indicator: "lpc".to_string(),
14634 input: IndicatorInputKind::Ohlc,
14635 });
14636 }
14637 };
14638 let kernel = req.kernel.to_non_batch();
14639 collect_f64("lpc", output_id, req.combos, src.len(), |params| {
14640 let cutoff_type = get_enum_param("lpc", params, "cutoff_type", "adaptive")?;
14641 let fixed_period = get_usize_param("lpc", params, "fixed_period", 20)?;
14642 let max_cycle_limit = get_usize_param("lpc", params, "max_cycle_limit", 60)?;
14643 let cycle_mult = get_f64_param("lpc", params, "cycle_mult", 1.0)?;
14644 let tr_mult = get_f64_param("lpc", params, "tr_mult", 1.0)?;
14645 let input = LpcInput::from_slices(
14646 high,
14647 low,
14648 close,
14649 src,
14650 LpcParams {
14651 cutoff_type: Some(cutoff_type),
14652 fixed_period: Some(fixed_period),
14653 max_cycle_limit: Some(max_cycle_limit),
14654 cycle_mult: Some(cycle_mult),
14655 tr_mult: Some(tr_mult),
14656 },
14657 );
14658 let out =
14659 lpc_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
14660 indicator: "lpc".to_string(),
14661 details: e.to_string(),
14662 })?;
14663 if output_id.eq_ignore_ascii_case("filter") || output_id.eq_ignore_ascii_case("value") {
14664 return Ok(out.filter);
14665 }
14666 if output_id.eq_ignore_ascii_case("high_band") || output_id.eq_ignore_ascii_case("high") {
14667 return Ok(out.high_band);
14668 }
14669 if output_id.eq_ignore_ascii_case("low_band") || output_id.eq_ignore_ascii_case("low") {
14670 return Ok(out.low_band);
14671 }
14672 Err(IndicatorDispatchError::UnknownOutput {
14673 indicator: "lpc".to_string(),
14674 output: output_id.to_string(),
14675 })
14676 })
14677}
14678
14679fn compute_mab_batch(
14680 req: IndicatorBatchRequest<'_>,
14681 output_id: &str,
14682) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14683 let data = extract_slice_input("mab", req.data, "close")?;
14684 let kernel = req.kernel.to_non_batch();
14685 collect_f64("mab", output_id, req.combos, data.len(), |params| {
14686 let fast_period = get_usize_param("mab", params, "fast_period", 10)?;
14687 let slow_period = get_usize_param("mab", params, "slow_period", 50)?;
14688 let devup = get_f64_param("mab", params, "devup", 1.0)?;
14689 let devdn = get_f64_param("mab", params, "devdn", 1.0)?;
14690 let fast_ma_type = get_enum_param("mab", params, "fast_ma_type", "sma")?;
14691 let slow_ma_type = get_enum_param("mab", params, "slow_ma_type", "sma")?;
14692 let input = MabInput::from_slice(
14693 data,
14694 MabParams {
14695 fast_period: Some(fast_period),
14696 slow_period: Some(slow_period),
14697 devup: Some(devup),
14698 devdn: Some(devdn),
14699 fast_ma_type: Some(fast_ma_type),
14700 slow_ma_type: Some(slow_ma_type),
14701 },
14702 );
14703 let out =
14704 mab_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
14705 indicator: "mab".to_string(),
14706 details: e.to_string(),
14707 })?;
14708 if output_id.eq_ignore_ascii_case("upperband")
14709 || output_id.eq_ignore_ascii_case("upper")
14710 || output_id.eq_ignore_ascii_case("value")
14711 {
14712 return Ok(out.upperband);
14713 }
14714 if output_id.eq_ignore_ascii_case("middleband") || output_id.eq_ignore_ascii_case("middle")
14715 {
14716 return Ok(out.middleband);
14717 }
14718 if output_id.eq_ignore_ascii_case("lowerband") || output_id.eq_ignore_ascii_case("lower") {
14719 return Ok(out.lowerband);
14720 }
14721 Err(IndicatorDispatchError::UnknownOutput {
14722 indicator: "mab".to_string(),
14723 output: output_id.to_string(),
14724 })
14725 })
14726}
14727
14728fn compute_macz_batch(
14729 req: IndicatorBatchRequest<'_>,
14730 output_id: &str,
14731) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14732 let (data, volume) = match req.data {
14733 IndicatorDataRef::Slice { values } => (values, None),
14734 IndicatorDataRef::Candles { candles, source } => (
14735 source_type(candles, source.unwrap_or("close")),
14736 Some(candles.volume.as_slice()),
14737 ),
14738 IndicatorDataRef::CloseVolume { close, volume } => {
14739 ensure_same_len_2("macz", close.len(), volume.len())?;
14740 (close, Some(volume))
14741 }
14742 IndicatorDataRef::Ohlc {
14743 open,
14744 high,
14745 low,
14746 close,
14747 } => {
14748 ensure_same_len_4("macz", open.len(), high.len(), low.len(), close.len())?;
14749 (close, None)
14750 }
14751 IndicatorDataRef::Ohlcv {
14752 open,
14753 high,
14754 low,
14755 close,
14756 volume,
14757 } => {
14758 ensure_same_len_5(
14759 "macz",
14760 open.len(),
14761 high.len(),
14762 low.len(),
14763 close.len(),
14764 volume.len(),
14765 )?;
14766 (close, Some(volume))
14767 }
14768 IndicatorDataRef::HighLow { .. } => {
14769 return Err(IndicatorDispatchError::MissingRequiredInput {
14770 indicator: "macz".to_string(),
14771 input: IndicatorInputKind::Slice,
14772 })
14773 }
14774 };
14775 let kernel = req.kernel.to_non_batch();
14776 collect_f64("macz", output_id, req.combos, data.len(), |params| {
14777 let fast_length = get_usize_param("macz", params, "fast_length", 12)?;
14778 let slow_length = get_usize_param("macz", params, "slow_length", 25)?;
14779 let signal_length = get_usize_param("macz", params, "signal_length", 9)?;
14780 let lengthz = get_usize_param("macz", params, "lengthz", 20)?;
14781 let length_stdev = get_usize_param("macz", params, "length_stdev", 25)?;
14782 let a = get_f64_param("macz", params, "a", 1.0)?;
14783 let b = get_f64_param("macz", params, "b", 1.0)?;
14784 let use_lag = get_bool_param("macz", params, "use_lag", false)?;
14785 let gamma = get_f64_param("macz", params, "gamma", 0.02)?;
14786 let macz_params = MaczParams {
14787 fast_length: Some(fast_length),
14788 slow_length: Some(slow_length),
14789 signal_length: Some(signal_length),
14790 lengthz: Some(lengthz),
14791 length_stdev: Some(length_stdev),
14792 a: Some(a),
14793 b: Some(b),
14794 use_lag: Some(use_lag),
14795 gamma: Some(gamma),
14796 };
14797 let input = if let Some(vol) = volume {
14798 MaczInput::from_slice_with_volume(data, vol, macz_params)
14799 } else {
14800 MaczInput::from_slice(data, macz_params)
14801 };
14802 let out = macz_with_kernel(&input, kernel).map_err(|e| {
14803 IndicatorDispatchError::ComputeFailed {
14804 indicator: "macz".to_string(),
14805 details: e.to_string(),
14806 }
14807 })?;
14808 if output_id.eq_ignore_ascii_case("value") || output_id.eq_ignore_ascii_case("values") {
14809 return Ok(out.values);
14810 }
14811 Err(IndicatorDispatchError::UnknownOutput {
14812 indicator: "macz".to_string(),
14813 output: output_id.to_string(),
14814 })
14815 })
14816}
14817
14818fn compute_minmax_batch(
14819 req: IndicatorBatchRequest<'_>,
14820 output_id: &str,
14821) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14822 let (high, low) = extract_high_low_input("minmax", req.data)?;
14823 let kernel = req.kernel.to_non_batch();
14824 collect_f64("minmax", output_id, req.combos, high.len(), |params| {
14825 let order = get_usize_param("minmax", params, "order", 3)?;
14826 let input = MinmaxInput::from_slices(high, low, MinmaxParams { order: Some(order) });
14827 let out = minmax_with_kernel(&input, kernel).map_err(|e| {
14828 IndicatorDispatchError::ComputeFailed {
14829 indicator: "minmax".to_string(),
14830 details: e.to_string(),
14831 }
14832 })?;
14833 if output_id.eq_ignore_ascii_case("is_min") || output_id.eq_ignore_ascii_case("value") {
14834 return Ok(out.is_min);
14835 }
14836 if output_id.eq_ignore_ascii_case("is_max") {
14837 return Ok(out.is_max);
14838 }
14839 if output_id.eq_ignore_ascii_case("last_min") {
14840 return Ok(out.last_min);
14841 }
14842 if output_id.eq_ignore_ascii_case("last_max") {
14843 return Ok(out.last_max);
14844 }
14845 Err(IndicatorDispatchError::UnknownOutput {
14846 indicator: "minmax".to_string(),
14847 output: output_id.to_string(),
14848 })
14849 })
14850}
14851
14852fn compute_mod_god_mode_batch(
14853 req: IndicatorBatchRequest<'_>,
14854 output_id: &str,
14855) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14856 let (high, low, close, volume) = match req.data {
14857 IndicatorDataRef::Candles { candles, .. } => (
14858 candles.high.as_slice(),
14859 candles.low.as_slice(),
14860 candles.close.as_slice(),
14861 Some(candles.volume.as_slice()),
14862 ),
14863 IndicatorDataRef::Ohlc {
14864 open,
14865 high,
14866 low,
14867 close,
14868 } => {
14869 ensure_same_len_4(
14870 "mod_god_mode",
14871 open.len(),
14872 high.len(),
14873 low.len(),
14874 close.len(),
14875 )?;
14876 (high, low, close, None)
14877 }
14878 IndicatorDataRef::Ohlcv {
14879 open,
14880 high,
14881 low,
14882 close,
14883 volume,
14884 } => {
14885 ensure_same_len_5(
14886 "mod_god_mode",
14887 open.len(),
14888 high.len(),
14889 low.len(),
14890 close.len(),
14891 volume.len(),
14892 )?;
14893 (high, low, close, Some(volume))
14894 }
14895 _ => {
14896 return Err(IndicatorDispatchError::MissingRequiredInput {
14897 indicator: "mod_god_mode".to_string(),
14898 input: IndicatorInputKind::Ohlc,
14899 });
14900 }
14901 };
14902
14903 collect_f64(
14904 "mod_god_mode",
14905 output_id,
14906 req.combos,
14907 close.len(),
14908 |params| {
14909 let n1 = get_usize_param("mod_god_mode", params, "n1", 17)?;
14910 let n2 = get_usize_param("mod_god_mode", params, "n2", 6)?;
14911 let n3 = get_usize_param("mod_god_mode", params, "n3", 4)?;
14912 let mode = get_enum_param("mod_god_mode", params, "mode", "tradition_mg")?;
14913 let use_volume = get_bool_param("mod_god_mode", params, "use_volume", true)?;
14914 let mode = match mode.as_str() {
14915 "godmode" => ModGodModeMode::Godmode,
14916 "tradition" => ModGodModeMode::Tradition,
14917 "godmode_mg" => ModGodModeMode::GodmodeMg,
14918 "tradition_mg" => ModGodModeMode::TraditionMg,
14919 other => {
14920 return Err(IndicatorDispatchError::InvalidParam {
14921 indicator: "mod_god_mode".to_string(),
14922 key: "mode".to_string(),
14923 reason: format!("unknown mode: {other}"),
14924 });
14925 }
14926 };
14927 let input = ModGodModeInput {
14928 data: ModGodModeData::Slices {
14929 high,
14930 low,
14931 close,
14932 volume: if use_volume { volume } else { None },
14933 },
14934 params: ModGodModeParams {
14935 n1: Some(n1),
14936 n2: Some(n2),
14937 n3: Some(n3),
14938 mode: Some(mode),
14939 use_volume: Some(use_volume),
14940 },
14941 };
14942 let out = mod_god_mode(&input).map_err(|e| IndicatorDispatchError::ComputeFailed {
14943 indicator: "mod_god_mode".to_string(),
14944 details: e.to_string(),
14945 })?;
14946 if output_id.eq_ignore_ascii_case("wavetrend")
14947 || output_id.eq_ignore_ascii_case("wt1")
14948 || output_id.eq_ignore_ascii_case("value")
14949 {
14950 return Ok(out.wavetrend);
14951 }
14952 if output_id.eq_ignore_ascii_case("signal") || output_id.eq_ignore_ascii_case("wt2") {
14953 return Ok(out.signal);
14954 }
14955 if output_id.eq_ignore_ascii_case("histogram") || output_id.eq_ignore_ascii_case("hist")
14956 {
14957 return Ok(out.histogram);
14958 }
14959 Err(IndicatorDispatchError::UnknownOutput {
14960 indicator: "mod_god_mode".to_string(),
14961 output: output_id.to_string(),
14962 })
14963 },
14964 )
14965}
14966
14967fn compute_msw_batch(
14968 req: IndicatorBatchRequest<'_>,
14969 output_id: &str,
14970) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
14971 let data = extract_slice_input("msw", req.data, "close")?;
14972 let kernel = req.kernel.to_non_batch();
14973 collect_f64("msw", output_id, req.combos, data.len(), |params| {
14974 let period = get_usize_param("msw", params, "period", 5)?;
14975 let input = MswInput::from_slice(
14976 data,
14977 MswParams {
14978 period: Some(period),
14979 },
14980 );
14981 let out =
14982 msw_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
14983 indicator: "msw".to_string(),
14984 details: e.to_string(),
14985 })?;
14986 if output_id.eq_ignore_ascii_case("sine") || output_id.eq_ignore_ascii_case("value") {
14987 return Ok(out.sine);
14988 }
14989 if output_id.eq_ignore_ascii_case("lead") {
14990 return Ok(out.lead);
14991 }
14992 Err(IndicatorDispatchError::UnknownOutput {
14993 indicator: "msw".to_string(),
14994 output: output_id.to_string(),
14995 })
14996 })
14997}
14998
14999fn compute_nadaraya_watson_envelope_batch(
15000 req: IndicatorBatchRequest<'_>,
15001 output_id: &str,
15002) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15003 let data = extract_slice_input("nadaraya_watson_envelope", req.data, "close")?;
15004 let kernel = req.kernel.to_non_batch();
15005 collect_f64(
15006 "nadaraya_watson_envelope",
15007 output_id,
15008 req.combos,
15009 data.len(),
15010 |params| {
15011 let bandwidth = get_f64_param("nadaraya_watson_envelope", params, "bandwidth", 8.0)?;
15012 let multiplier = get_f64_param("nadaraya_watson_envelope", params, "multiplier", 3.0)?;
15013 let lookback = get_usize_param("nadaraya_watson_envelope", params, "lookback", 500)?;
15014 let input = NweInput::from_slice(
15015 data,
15016 NweParams {
15017 bandwidth: Some(bandwidth),
15018 multiplier: Some(multiplier),
15019 lookback: Some(lookback),
15020 },
15021 );
15022 let out = nadaraya_watson_envelope_with_kernel(&input, kernel).map_err(|e| {
15023 IndicatorDispatchError::ComputeFailed {
15024 indicator: "nadaraya_watson_envelope".to_string(),
15025 details: e.to_string(),
15026 }
15027 })?;
15028 if output_id.eq_ignore_ascii_case("upper") || output_id.eq_ignore_ascii_case("value") {
15029 return Ok(out.upper);
15030 }
15031 if output_id.eq_ignore_ascii_case("lower") {
15032 return Ok(out.lower);
15033 }
15034 Err(IndicatorDispatchError::UnknownOutput {
15035 indicator: "nadaraya_watson_envelope".to_string(),
15036 output: output_id.to_string(),
15037 })
15038 },
15039 )
15040}
15041
15042fn compute_otto_batch(
15043 req: IndicatorBatchRequest<'_>,
15044 output_id: &str,
15045) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15046 let data = extract_slice_input("otto", req.data, "close")?;
15047 let kernel = req.kernel.to_non_batch();
15048 collect_f64("otto", output_id, req.combos, data.len(), |params| {
15049 let ott_period = get_usize_param("otto", params, "ott_period", 2)?;
15050 let ott_percent = get_f64_param("otto", params, "ott_percent", 0.6)?;
15051 let fast_vidya_length = get_usize_param("otto", params, "fast_vidya_length", 10)?;
15052 let slow_vidya_length = get_usize_param("otto", params, "slow_vidya_length", 25)?;
15053 let correcting_constant = get_f64_param("otto", params, "correcting_constant", 100000.0)?;
15054 let ma_type = get_enum_param("otto", params, "ma_type", "VAR")?;
15055 let input = OttoInput::from_slice(
15056 data,
15057 OttoParams {
15058 ott_period: Some(ott_period),
15059 ott_percent: Some(ott_percent),
15060 fast_vidya_length: Some(fast_vidya_length),
15061 slow_vidya_length: Some(slow_vidya_length),
15062 correcting_constant: Some(correcting_constant),
15063 ma_type: Some(ma_type),
15064 },
15065 );
15066 let out = otto_with_kernel(&input, kernel).map_err(|e| {
15067 IndicatorDispatchError::ComputeFailed {
15068 indicator: "otto".to_string(),
15069 details: e.to_string(),
15070 }
15071 })?;
15072 if output_id.eq_ignore_ascii_case("hott") || output_id.eq_ignore_ascii_case("value") {
15073 return Ok(out.hott);
15074 }
15075 if output_id.eq_ignore_ascii_case("lott") {
15076 return Ok(out.lott);
15077 }
15078 Err(IndicatorDispatchError::UnknownOutput {
15079 indicator: "otto".to_string(),
15080 output: output_id.to_string(),
15081 })
15082 })
15083}
15084
15085fn compute_vidya_batch(
15086 req: IndicatorBatchRequest<'_>,
15087 output_id: &str,
15088) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15089 let data = extract_slice_input("vidya", req.data, "close")?;
15090 let kernel = req.kernel.to_non_batch();
15091 collect_f64("vidya", output_id, req.combos, data.len(), |params| {
15092 let short_period = get_usize_param("vidya", params, "short_period", 2)?;
15093 let long_period = get_usize_param("vidya", params, "long_period", 5)?;
15094 let alpha = get_f64_param("vidya", params, "alpha", 0.2)?;
15095 let input = VidyaInput::from_slice(
15096 data,
15097 VidyaParams {
15098 short_period: Some(short_period),
15099 long_period: Some(long_period),
15100 alpha: Some(alpha),
15101 },
15102 );
15103 let out = vidya_with_kernel(&input, kernel).map_err(|e| {
15104 IndicatorDispatchError::ComputeFailed {
15105 indicator: "vidya".to_string(),
15106 details: e.to_string(),
15107 }
15108 })?;
15109 if output_id.eq_ignore_ascii_case("value") || output_id.eq_ignore_ascii_case("values") {
15110 return Ok(out.values);
15111 }
15112 Err(IndicatorDispatchError::UnknownOutput {
15113 indicator: "vidya".to_string(),
15114 output: output_id.to_string(),
15115 })
15116 })
15117}
15118
15119fn compute_vlma_batch(
15120 req: IndicatorBatchRequest<'_>,
15121 output_id: &str,
15122) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15123 let data = extract_slice_input("vlma", req.data, "close")?;
15124 let kernel = req.kernel.to_non_batch();
15125 collect_f64("vlma", output_id, req.combos, data.len(), |params| {
15126 let min_period = get_usize_param("vlma", params, "min_period", 5)?;
15127 let max_period = get_usize_param("vlma", params, "max_period", 50)?;
15128 let matype = get_enum_param("vlma", params, "matype", "sma")?;
15129 let devtype = get_usize_param("vlma", params, "devtype", 0)?;
15130 let input = VlmaInput::from_slice(
15131 data,
15132 VlmaParams {
15133 min_period: Some(min_period),
15134 max_period: Some(max_period),
15135 matype: Some(matype),
15136 devtype: Some(devtype),
15137 },
15138 );
15139 let out = vlma_with_kernel(&input, kernel).map_err(|e| {
15140 IndicatorDispatchError::ComputeFailed {
15141 indicator: "vlma".to_string(),
15142 details: e.to_string(),
15143 }
15144 })?;
15145 if output_id.eq_ignore_ascii_case("value") || output_id.eq_ignore_ascii_case("values") {
15146 return Ok(out.values);
15147 }
15148 Err(IndicatorDispatchError::UnknownOutput {
15149 indicator: "vlma".to_string(),
15150 output: output_id.to_string(),
15151 })
15152 })
15153}
15154
15155fn compute_pma_batch(
15156 req: IndicatorBatchRequest<'_>,
15157 output_id: &str,
15158) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15159 let data = extract_slice_input("pma", req.data, "close")?;
15160 let kernel = req.kernel.to_non_batch();
15161 collect_f64("pma", output_id, req.combos, data.len(), |_params| {
15162 let input = PmaInput::from_slice(data, PmaParams::default());
15163 let out =
15164 pma_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
15165 indicator: "pma".to_string(),
15166 details: e.to_string(),
15167 })?;
15168 if output_id.eq_ignore_ascii_case("predict") || output_id.eq_ignore_ascii_case("value") {
15169 return Ok(out.predict);
15170 }
15171 if output_id.eq_ignore_ascii_case("trigger") {
15172 return Ok(out.trigger);
15173 }
15174 Err(IndicatorDispatchError::UnknownOutput {
15175 indicator: "pma".to_string(),
15176 output: output_id.to_string(),
15177 })
15178 })
15179}
15180
15181fn compute_ehlers_adaptive_cg_batch(
15182 req: IndicatorBatchRequest<'_>,
15183 output_id: &str,
15184) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15185 let data = extract_slice_input("ehlers_adaptive_cg", req.data, "hl2")?;
15186 let kernel = req.kernel.to_non_batch();
15187 collect_f64(
15188 "ehlers_adaptive_cg",
15189 output_id,
15190 req.combos,
15191 data.len(),
15192 |params| {
15193 let alpha = get_f64_param("ehlers_adaptive_cg", params, "alpha", 0.07)?;
15194 let input = EhlersAdaptiveCgInput::from_slice(
15195 data,
15196 EhlersAdaptiveCgParams { alpha: Some(alpha) },
15197 );
15198 let out = ehlers_adaptive_cg_with_kernel(&input, kernel).map_err(|e| {
15199 IndicatorDispatchError::ComputeFailed {
15200 indicator: "ehlers_adaptive_cg".to_string(),
15201 details: e.to_string(),
15202 }
15203 })?;
15204 if output_id.eq_ignore_ascii_case("cg") || output_id.eq_ignore_ascii_case("value") {
15205 return Ok(out.cg);
15206 }
15207 if output_id.eq_ignore_ascii_case("trigger") {
15208 return Ok(out.trigger);
15209 }
15210 Err(IndicatorDispatchError::UnknownOutput {
15211 indicator: "ehlers_adaptive_cg".to_string(),
15212 output: output_id.to_string(),
15213 })
15214 },
15215 )
15216}
15217
15218fn compute_prb_batch(
15219 req: IndicatorBatchRequest<'_>,
15220 output_id: &str,
15221) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15222 let data = extract_slice_input("prb", req.data, "close")?;
15223 let kernel = req.kernel.to_non_batch();
15224 collect_f64("prb", output_id, req.combos, data.len(), |params| {
15225 let smooth_data = get_bool_param("prb", params, "smooth_data", true)?;
15226 let smooth_period = get_usize_param("prb", params, "smooth_period", 10)?;
15227 let regression_period = get_usize_param("prb", params, "regression_period", 100)?;
15228 let polynomial_order = get_usize_param("prb", params, "polynomial_order", 2)?;
15229 let regression_offset = get_i32_param("prb", params, "regression_offset", 0)?;
15230 let ndev = get_f64_param("prb", params, "ndev", 2.0)?;
15231 let equ_from = get_usize_param("prb", params, "equ_from", 0)?;
15232 let input = PrbInput::from_slice(
15233 data,
15234 PrbParams {
15235 smooth_data: Some(smooth_data),
15236 smooth_period: Some(smooth_period),
15237 regression_period: Some(regression_period),
15238 polynomial_order: Some(polynomial_order),
15239 regression_offset: Some(regression_offset),
15240 ndev: Some(ndev),
15241 equ_from: Some(equ_from),
15242 },
15243 );
15244 let out =
15245 prb_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
15246 indicator: "prb".to_string(),
15247 details: e.to_string(),
15248 })?;
15249 if output_id.eq_ignore_ascii_case("values") || output_id.eq_ignore_ascii_case("value") {
15250 return Ok(out.values);
15251 }
15252 if output_id.eq_ignore_ascii_case("upper_band") || output_id.eq_ignore_ascii_case("upper") {
15253 return Ok(out.upper_band);
15254 }
15255 if output_id.eq_ignore_ascii_case("lower_band") || output_id.eq_ignore_ascii_case("lower") {
15256 return Ok(out.lower_band);
15257 }
15258 Err(IndicatorDispatchError::UnknownOutput {
15259 indicator: "prb".to_string(),
15260 output: output_id.to_string(),
15261 })
15262 })
15263}
15264
15265fn compute_qqe_batch(
15266 req: IndicatorBatchRequest<'_>,
15267 output_id: &str,
15268) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15269 let data = extract_slice_input("qqe", req.data, "close")?;
15270 let kernel = req.kernel.to_non_batch();
15271 collect_f64("qqe", output_id, req.combos, data.len(), |params| {
15272 let rsi_period = get_usize_param("qqe", params, "rsi_period", 14)?;
15273 let smoothing_factor = get_usize_param("qqe", params, "smoothing_factor", 5)?;
15274 let fast_factor = get_f64_param("qqe", params, "fast_factor", 4.236)?;
15275 let input = QqeInput::from_slice(
15276 data,
15277 QqeParams {
15278 rsi_period: Some(rsi_period),
15279 smoothing_factor: Some(smoothing_factor),
15280 fast_factor: Some(fast_factor),
15281 },
15282 );
15283 let out =
15284 qqe_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
15285 indicator: "qqe".to_string(),
15286 details: e.to_string(),
15287 })?;
15288 if output_id.eq_ignore_ascii_case("fast") || output_id.eq_ignore_ascii_case("value") {
15289 return Ok(out.fast);
15290 }
15291 if output_id.eq_ignore_ascii_case("slow") {
15292 return Ok(out.slow);
15293 }
15294 Err(IndicatorDispatchError::UnknownOutput {
15295 indicator: "qqe".to_string(),
15296 output: output_id.to_string(),
15297 })
15298 })
15299}
15300
15301fn compute_qqe_weighted_oscillator_batch(
15302 req: IndicatorBatchRequest<'_>,
15303 output_id: &str,
15304) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15305 let data = extract_slice_input("qqe_weighted_oscillator", req.data, "close")?;
15306 let kernel = req.kernel.to_non_batch();
15307 collect_f64(
15308 "qqe_weighted_oscillator",
15309 output_id,
15310 req.combos,
15311 data.len(),
15312 |params| {
15313 let length = get_usize_param("qqe_weighted_oscillator", params, "length", 14)?;
15314 let factor = get_f64_param("qqe_weighted_oscillator", params, "factor", 4.236)?;
15315 let smooth = get_usize_param("qqe_weighted_oscillator", params, "smooth", 5)?;
15316 let weight = get_f64_param("qqe_weighted_oscillator", params, "weight", 2.0)?;
15317 let input = QqeWeightedOscillatorInput::from_slice(
15318 data,
15319 QqeWeightedOscillatorParams {
15320 length: Some(length),
15321 factor: Some(factor),
15322 smooth: Some(smooth),
15323 weight: Some(weight),
15324 },
15325 );
15326 let out = qqe_weighted_oscillator_with_kernel(&input, kernel).map_err(|e| {
15327 IndicatorDispatchError::ComputeFailed {
15328 indicator: "qqe_weighted_oscillator".to_string(),
15329 details: e.to_string(),
15330 }
15331 })?;
15332 if output_id.eq_ignore_ascii_case("rsi") || output_id.eq_ignore_ascii_case("value") {
15333 return Ok(out.rsi);
15334 }
15335 if output_id.eq_ignore_ascii_case("trailing_stop")
15336 || output_id.eq_ignore_ascii_case("ts")
15337 {
15338 return Ok(out.trailing_stop);
15339 }
15340 Err(IndicatorDispatchError::UnknownOutput {
15341 indicator: "qqe_weighted_oscillator".to_string(),
15342 output: output_id.to_string(),
15343 })
15344 },
15345 )
15346}
15347
15348fn compute_forward_backward_exponential_oscillator_batch(
15349 req: IndicatorBatchRequest<'_>,
15350 output_id: &str,
15351) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15352 let data = extract_slice_input("forward_backward_exponential_oscillator", req.data, "close")?;
15353 let kernel = req.kernel.to_non_batch();
15354 collect_f64(
15355 "forward_backward_exponential_oscillator",
15356 output_id,
15357 req.combos,
15358 data.len(),
15359 |params| {
15360 let length = get_usize_param(
15361 "forward_backward_exponential_oscillator",
15362 params,
15363 "length",
15364 20,
15365 )?;
15366 let smooth = get_usize_param(
15367 "forward_backward_exponential_oscillator",
15368 params,
15369 "smooth",
15370 10,
15371 )?;
15372 let input = ForwardBackwardExponentialOscillatorInput::from_slice(
15373 data,
15374 ForwardBackwardExponentialOscillatorParams {
15375 length: Some(length),
15376 smooth: Some(smooth),
15377 },
15378 );
15379 let out = forward_backward_exponential_oscillator_with_kernel(&input, kernel).map_err(
15380 |e| IndicatorDispatchError::ComputeFailed {
15381 indicator: "forward_backward_exponential_oscillator".to_string(),
15382 details: e.to_string(),
15383 },
15384 )?;
15385 if output_id.eq_ignore_ascii_case("forward_backward")
15386 || output_id.eq_ignore_ascii_case("value")
15387 || output_id.eq_ignore_ascii_case("fb")
15388 {
15389 return Ok(out.forward_backward);
15390 }
15391 if output_id.eq_ignore_ascii_case("backward")
15392 || output_id.eq_ignore_ascii_case("bwrd")
15393 || output_id.eq_ignore_ascii_case("bw")
15394 {
15395 return Ok(out.backward);
15396 }
15397 if output_id.eq_ignore_ascii_case("histogram") || output_id.eq_ignore_ascii_case("hist")
15398 {
15399 return Ok(out.histogram);
15400 }
15401 Err(IndicatorDispatchError::UnknownOutput {
15402 indicator: "forward_backward_exponential_oscillator".to_string(),
15403 output: output_id.to_string(),
15404 })
15405 },
15406 )
15407}
15408
15409fn compute_range_oscillator_batch(
15410 req: IndicatorBatchRequest<'_>,
15411 output_id: &str,
15412) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15413 let (high, low, close) = extract_ohlc_input("range_oscillator", req.data)?;
15414 let kernel = req.kernel.to_non_batch();
15415 collect_f64(
15416 "range_oscillator",
15417 output_id,
15418 req.combos,
15419 close.len(),
15420 |params| {
15421 let length = get_usize_param("range_oscillator", params, "length", 50)?;
15422 let mult = get_f64_param("range_oscillator", params, "mult", 2.0)?;
15423 let input = RangeOscillatorInput::from_slices(
15424 high,
15425 low,
15426 close,
15427 RangeOscillatorParams {
15428 length: Some(length),
15429 mult: Some(mult),
15430 },
15431 );
15432 let out = range_oscillator_with_kernel(&input, kernel).map_err(|e| {
15433 IndicatorDispatchError::ComputeFailed {
15434 indicator: "range_oscillator".to_string(),
15435 details: e.to_string(),
15436 }
15437 })?;
15438 if output_id.eq_ignore_ascii_case("oscillator")
15439 || output_id.eq_ignore_ascii_case("osc")
15440 || output_id.eq_ignore_ascii_case("value")
15441 {
15442 return Ok(out.oscillator);
15443 }
15444 if output_id.eq_ignore_ascii_case("ma") {
15445 return Ok(out.ma);
15446 }
15447 if output_id.eq_ignore_ascii_case("upper_band")
15448 || output_id.eq_ignore_ascii_case("upper")
15449 {
15450 return Ok(out.upper_band);
15451 }
15452 if output_id.eq_ignore_ascii_case("lower_band")
15453 || output_id.eq_ignore_ascii_case("lower")
15454 {
15455 return Ok(out.lower_band);
15456 }
15457 if output_id.eq_ignore_ascii_case("range_width")
15458 || output_id.eq_ignore_ascii_case("width")
15459 {
15460 return Ok(out.range_width);
15461 }
15462 if output_id.eq_ignore_ascii_case("in_range") {
15463 return Ok(out.in_range);
15464 }
15465 if output_id.eq_ignore_ascii_case("trend") {
15466 return Ok(out.trend);
15467 }
15468 if output_id.eq_ignore_ascii_case("break_up") {
15469 return Ok(out.break_up);
15470 }
15471 if output_id.eq_ignore_ascii_case("break_down") {
15472 return Ok(out.break_down);
15473 }
15474 Err(IndicatorDispatchError::UnknownOutput {
15475 indicator: "range_oscillator".to_string(),
15476 output: output_id.to_string(),
15477 })
15478 },
15479 )
15480}
15481
15482fn compute_market_structure_confluence_batch(
15483 req: IndicatorBatchRequest<'_>,
15484 output_id: &str,
15485) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15486 let (high, low, close) = extract_ohlc_input("market_structure_confluence", req.data)?;
15487 let kernel = req.kernel.to_non_batch();
15488 collect_f64(
15489 "market_structure_confluence",
15490 output_id,
15491 req.combos,
15492 close.len(),
15493 |params| {
15494 let swing_size =
15495 get_usize_param("market_structure_confluence", params, "swing_size", 10)?;
15496 let bos_confirmation = get_enum_param(
15497 "market_structure_confluence",
15498 params,
15499 "bos_confirmation",
15500 "Candle Close",
15501 )?;
15502 let basis_length =
15503 get_usize_param("market_structure_confluence", params, "basis_length", 100)?;
15504 let atr_length =
15505 get_usize_param("market_structure_confluence", params, "atr_length", 14)?;
15506 let atr_smooth =
15507 get_usize_param("market_structure_confluence", params, "atr_smooth", 21)?;
15508 let vol_mult = get_f64_param("market_structure_confluence", params, "vol_mult", 2.0)?;
15509 let input = MarketStructureConfluenceInput::from_slices(
15510 high,
15511 low,
15512 close,
15513 MarketStructureConfluenceParams {
15514 swing_size: Some(swing_size),
15515 bos_confirmation: Some(bos_confirmation),
15516 basis_length: Some(basis_length),
15517 atr_length: Some(atr_length),
15518 atr_smooth: Some(atr_smooth),
15519 vol_mult: Some(vol_mult),
15520 },
15521 );
15522 let out = market_structure_confluence_with_kernel(&input, kernel).map_err(|e| {
15523 IndicatorDispatchError::ComputeFailed {
15524 indicator: "market_structure_confluence".to_string(),
15525 details: e.to_string(),
15526 }
15527 })?;
15528 if output_id.eq_ignore_ascii_case("basis") {
15529 return Ok(out.basis);
15530 }
15531 if output_id.eq_ignore_ascii_case("upper_band")
15532 || output_id.eq_ignore_ascii_case("upper")
15533 {
15534 return Ok(out.upper_band);
15535 }
15536 if output_id.eq_ignore_ascii_case("lower_band")
15537 || output_id.eq_ignore_ascii_case("lower")
15538 {
15539 return Ok(out.lower_band);
15540 }
15541 if output_id.eq_ignore_ascii_case("structure_direction")
15542 || output_id.eq_ignore_ascii_case("direction")
15543 || output_id.eq_ignore_ascii_case("trend")
15544 {
15545 return Ok(out.structure_direction);
15546 }
15547 if output_id.eq_ignore_ascii_case("bullish_arrow") {
15548 return Ok(out.bullish_arrow);
15549 }
15550 if output_id.eq_ignore_ascii_case("bearish_arrow") {
15551 return Ok(out.bearish_arrow);
15552 }
15553 if output_id.eq_ignore_ascii_case("bullish_change") {
15554 return Ok(out.bullish_change);
15555 }
15556 if output_id.eq_ignore_ascii_case("bearish_change") {
15557 return Ok(out.bearish_change);
15558 }
15559 if output_id.eq_ignore_ascii_case("hh") {
15560 return Ok(out.hh);
15561 }
15562 if output_id.eq_ignore_ascii_case("lh") {
15563 return Ok(out.lh);
15564 }
15565 if output_id.eq_ignore_ascii_case("hl") {
15566 return Ok(out.hl);
15567 }
15568 if output_id.eq_ignore_ascii_case("ll") {
15569 return Ok(out.ll);
15570 }
15571 if output_id.eq_ignore_ascii_case("bullish_bos") {
15572 return Ok(out.bullish_bos);
15573 }
15574 if output_id.eq_ignore_ascii_case("bullish_choch") {
15575 return Ok(out.bullish_choch);
15576 }
15577 if output_id.eq_ignore_ascii_case("bearish_bos") {
15578 return Ok(out.bearish_bos);
15579 }
15580 if output_id.eq_ignore_ascii_case("bearish_choch") {
15581 return Ok(out.bearish_choch);
15582 }
15583 Err(IndicatorDispatchError::UnknownOutput {
15584 indicator: "market_structure_confluence".to_string(),
15585 output: output_id.to_string(),
15586 })
15587 },
15588 )
15589}
15590
15591fn compute_range_filtered_trend_signals_batch(
15592 req: IndicatorBatchRequest<'_>,
15593 output_id: &str,
15594) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15595 let (high, low, close) = extract_ohlc_input("range_filtered_trend_signals", req.data)?;
15596 let kernel = req.kernel.to_non_batch();
15597 collect_f64(
15598 "range_filtered_trend_signals",
15599 output_id,
15600 req.combos,
15601 close.len(),
15602 |params| {
15603 let kalman_alpha =
15604 get_f64_param("range_filtered_trend_signals", params, "kalman_alpha", 0.01)?;
15605 let kalman_beta =
15606 get_f64_param("range_filtered_trend_signals", params, "kalman_beta", 0.1)?;
15607 let kalman_period =
15608 get_usize_param("range_filtered_trend_signals", params, "kalman_period", 77)?;
15609 let dev = get_f64_param("range_filtered_trend_signals", params, "dev", 1.2)?;
15610 let supertrend_factor = get_f64_param(
15611 "range_filtered_trend_signals",
15612 params,
15613 "supertrend_factor",
15614 0.7,
15615 )?;
15616 let supertrend_atr_period = get_usize_param(
15617 "range_filtered_trend_signals",
15618 params,
15619 "supertrend_atr_period",
15620 7,
15621 )?;
15622 let input = RangeFilteredTrendSignalsInput::from_slices(
15623 high,
15624 low,
15625 close,
15626 RangeFilteredTrendSignalsParams {
15627 kalman_alpha: Some(kalman_alpha),
15628 kalman_beta: Some(kalman_beta),
15629 kalman_period: Some(kalman_period),
15630 dev: Some(dev),
15631 supertrend_factor: Some(supertrend_factor),
15632 supertrend_atr_period: Some(supertrend_atr_period),
15633 },
15634 );
15635 let out = range_filtered_trend_signals_with_kernel(&input, kernel).map_err(|e| {
15636 IndicatorDispatchError::ComputeFailed {
15637 indicator: "range_filtered_trend_signals".to_string(),
15638 details: e.to_string(),
15639 }
15640 })?;
15641 if output_id.eq_ignore_ascii_case("kalman") {
15642 return Ok(out.kalman);
15643 }
15644 if output_id.eq_ignore_ascii_case("supertrend") {
15645 return Ok(out.supertrend);
15646 }
15647 if output_id.eq_ignore_ascii_case("upper_band")
15648 || output_id.eq_ignore_ascii_case("upper")
15649 {
15650 return Ok(out.upper_band);
15651 }
15652 if output_id.eq_ignore_ascii_case("lower_band")
15653 || output_id.eq_ignore_ascii_case("lower")
15654 {
15655 return Ok(out.lower_band);
15656 }
15657 if output_id.eq_ignore_ascii_case("trend") {
15658 return Ok(out.trend);
15659 }
15660 if output_id.eq_ignore_ascii_case("kalman_trend")
15661 || output_id.eq_ignore_ascii_case("long_trend")
15662 {
15663 return Ok(out.kalman_trend);
15664 }
15665 if output_id.eq_ignore_ascii_case("state") {
15666 return Ok(out.state);
15667 }
15668 if output_id.eq_ignore_ascii_case("market_trending") {
15669 return Ok(out.market_trending);
15670 }
15671 if output_id.eq_ignore_ascii_case("market_ranging") {
15672 return Ok(out.market_ranging);
15673 }
15674 if output_id.eq_ignore_ascii_case("short_term_bullish") {
15675 return Ok(out.short_term_bullish);
15676 }
15677 if output_id.eq_ignore_ascii_case("short_term_bearish") {
15678 return Ok(out.short_term_bearish);
15679 }
15680 if output_id.eq_ignore_ascii_case("long_term_bullish") {
15681 return Ok(out.long_term_bullish);
15682 }
15683 if output_id.eq_ignore_ascii_case("long_term_bearish") {
15684 return Ok(out.long_term_bearish);
15685 }
15686 Err(IndicatorDispatchError::UnknownOutput {
15687 indicator: "range_filtered_trend_signals".to_string(),
15688 output: output_id.to_string(),
15689 })
15690 },
15691 )
15692}
15693
15694fn compute_volume_weighted_relative_strength_index_batch(
15695 req: IndicatorBatchRequest<'_>,
15696 output_id: &str,
15697) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15698 let (source, volume) =
15699 extract_close_volume_input("volume_weighted_relative_strength_index", req.data, "close")?;
15700 let kernel = req.kernel.to_non_batch();
15701 collect_f64(
15702 "volume_weighted_relative_strength_index",
15703 output_id,
15704 req.combos,
15705 source.len(),
15706 |params| {
15707 let rsi_length = get_usize_param(
15708 "volume_weighted_relative_strength_index",
15709 params,
15710 "rsi_length",
15711 14,
15712 )?;
15713 let range_length = get_usize_param(
15714 "volume_weighted_relative_strength_index",
15715 params,
15716 "range_length",
15717 10,
15718 )?;
15719 let ma_length = get_usize_param(
15720 "volume_weighted_relative_strength_index",
15721 params,
15722 "ma_length",
15723 14,
15724 )?;
15725 let ma_type = get_enum_param(
15726 "volume_weighted_relative_strength_index",
15727 params,
15728 "ma_type",
15729 "EMA",
15730 )?;
15731 let input = VolumeWeightedRelativeStrengthIndexInput::from_slices(
15732 source,
15733 volume,
15734 VolumeWeightedRelativeStrengthIndexParams {
15735 rsi_length: Some(rsi_length),
15736 range_length: Some(range_length),
15737 ma_length: Some(ma_length),
15738 ma_type: Some(ma_type),
15739 },
15740 );
15741 let out = volume_weighted_relative_strength_index_with_kernel(&input, kernel).map_err(
15742 |e| IndicatorDispatchError::ComputeFailed {
15743 indicator: "volume_weighted_relative_strength_index".to_string(),
15744 details: e.to_string(),
15745 },
15746 )?;
15747 if output_id.eq_ignore_ascii_case("rsi") || output_id.eq_ignore_ascii_case("value") {
15748 return Ok(out.rsi);
15749 }
15750 if output_id.eq_ignore_ascii_case("consolidation_strength")
15751 || output_id.eq_ignore_ascii_case("consolidation")
15752 {
15753 return Ok(out.consolidation_strength);
15754 }
15755 if output_id.eq_ignore_ascii_case("rsi_ma") || output_id.eq_ignore_ascii_case("ma") {
15756 return Ok(out.rsi_ma);
15757 }
15758 if output_id.eq_ignore_ascii_case("bearish_tp") {
15759 return Ok(out.bearish_tp);
15760 }
15761 if output_id.eq_ignore_ascii_case("bullish_tp") {
15762 return Ok(out.bullish_tp);
15763 }
15764 Err(IndicatorDispatchError::UnknownOutput {
15765 indicator: "volume_weighted_relative_strength_index".to_string(),
15766 output: output_id.to_string(),
15767 })
15768 },
15769 )
15770}
15771
15772fn compute_range_filter_batch(
15773 req: IndicatorBatchRequest<'_>,
15774 output_id: &str,
15775) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15776 let data = extract_slice_input("range_filter", req.data, "close")?;
15777 let kernel = req.kernel.to_non_batch();
15778 collect_f64(
15779 "range_filter",
15780 output_id,
15781 req.combos,
15782 data.len(),
15783 |params| {
15784 let range_size = get_f64_param("range_filter", params, "range_size", 2.618)?;
15785 let range_period = get_usize_param("range_filter", params, "range_period", 14)?;
15786 let smooth_range = get_bool_param("range_filter", params, "smooth_range", true)?;
15787 let smooth_period = get_usize_param("range_filter", params, "smooth_period", 27)?;
15788 let input = RangeFilterInput::from_slice(
15789 data,
15790 RangeFilterParams {
15791 range_size: Some(range_size),
15792 range_period: Some(range_period),
15793 smooth_range: Some(smooth_range),
15794 smooth_period: Some(smooth_period),
15795 },
15796 );
15797 let out = range_filter_with_kernel(&input, kernel).map_err(|e| {
15798 IndicatorDispatchError::ComputeFailed {
15799 indicator: "range_filter".to_string(),
15800 details: e.to_string(),
15801 }
15802 })?;
15803 if output_id.eq_ignore_ascii_case("filter") || output_id.eq_ignore_ascii_case("value") {
15804 return Ok(out.filter);
15805 }
15806 if output_id.eq_ignore_ascii_case("high_band") || output_id.eq_ignore_ascii_case("high")
15807 {
15808 return Ok(out.high_band);
15809 }
15810 if output_id.eq_ignore_ascii_case("low_band") || output_id.eq_ignore_ascii_case("low") {
15811 return Ok(out.low_band);
15812 }
15813 Err(IndicatorDispatchError::UnknownOutput {
15814 indicator: "range_filter".to_string(),
15815 output: output_id.to_string(),
15816 })
15817 },
15818 )
15819}
15820
15821fn compute_rsmk_batch(
15822 req: IndicatorBatchRequest<'_>,
15823 output_id: &str,
15824) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15825 let (main, compare) = match req.data {
15826 IndicatorDataRef::CloseVolume { close, volume } => {
15827 ensure_same_len_2("rsmk", close.len(), volume.len())?;
15828 (close, volume)
15829 }
15830 IndicatorDataRef::Ohlcv {
15831 open,
15832 high,
15833 low,
15834 close,
15835 volume,
15836 } => {
15837 ensure_same_len_5(
15838 "rsmk",
15839 open.len(),
15840 high.len(),
15841 low.len(),
15842 close.len(),
15843 volume.len(),
15844 )?;
15845 (close, volume)
15846 }
15847 IndicatorDataRef::Candles { candles, source } => (
15848 source_type(candles, source.unwrap_or("close")),
15849 candles.volume.as_slice(),
15850 ),
15851 _ => {
15852 return Err(IndicatorDispatchError::MissingRequiredInput {
15853 indicator: "rsmk".to_string(),
15854 input: IndicatorInputKind::CloseVolume,
15855 });
15856 }
15857 };
15858 let kernel = req.kernel.to_non_batch();
15859 collect_f64("rsmk", output_id, req.combos, main.len(), |params| {
15860 let lookback = get_usize_param("rsmk", params, "lookback", 90)?;
15861 let period = get_usize_param("rsmk", params, "period", 3)?;
15862 let signal_period = get_usize_param("rsmk", params, "signal_period", 20)?;
15863 let matype = get_enum_param("rsmk", params, "matype", "ema")?;
15864 let signal_matype = get_enum_param("rsmk", params, "signal_matype", "ema")?;
15865 let input = RsmkInput::from_slices(
15866 main,
15867 compare,
15868 RsmkParams {
15869 lookback: Some(lookback),
15870 period: Some(period),
15871 signal_period: Some(signal_period),
15872 matype: Some(matype),
15873 signal_matype: Some(signal_matype),
15874 },
15875 );
15876 let out = rsmk_with_kernel(&input, kernel).map_err(|e| {
15877 IndicatorDispatchError::ComputeFailed {
15878 indicator: "rsmk".to_string(),
15879 details: e.to_string(),
15880 }
15881 })?;
15882 if output_id.eq_ignore_ascii_case("indicator") || output_id.eq_ignore_ascii_case("value") {
15883 return Ok(out.indicator);
15884 }
15885 if output_id.eq_ignore_ascii_case("signal") {
15886 return Ok(out.signal);
15887 }
15888 Err(IndicatorDispatchError::UnknownOutput {
15889 indicator: "rsmk".to_string(),
15890 output: output_id.to_string(),
15891 })
15892 })
15893}
15894
15895fn compute_voss_batch(
15896 req: IndicatorBatchRequest<'_>,
15897 output_id: &str,
15898) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15899 let data = extract_slice_input("voss", req.data, "close")?;
15900 let kernel = req.kernel.to_non_batch();
15901 collect_f64("voss", output_id, req.combos, data.len(), |params| {
15902 let period = get_usize_param("voss", params, "period", 20)?;
15903 let predict = get_usize_param("voss", params, "predict", 3)?;
15904 let bandwidth = get_f64_param("voss", params, "bandwidth", 0.25)?;
15905 let input = VossInput::from_slice(
15906 data,
15907 VossParams {
15908 period: Some(period),
15909 predict: Some(predict),
15910 bandwidth: Some(bandwidth),
15911 },
15912 );
15913 let out = voss_with_kernel(&input, kernel).map_err(|e| {
15914 IndicatorDispatchError::ComputeFailed {
15915 indicator: "voss".to_string(),
15916 details: e.to_string(),
15917 }
15918 })?;
15919 if output_id.eq_ignore_ascii_case("voss") || output_id.eq_ignore_ascii_case("value") {
15920 return Ok(out.voss);
15921 }
15922 if output_id.eq_ignore_ascii_case("filt") || output_id.eq_ignore_ascii_case("filter") {
15923 return Ok(out.filt);
15924 }
15925 Err(IndicatorDispatchError::UnknownOutput {
15926 indicator: "voss".to_string(),
15927 output: output_id.to_string(),
15928 })
15929 })
15930}
15931
15932fn compute_stc_batch(
15933 req: IndicatorBatchRequest<'_>,
15934 output_id: &str,
15935) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15936 let data = extract_slice_input("stc", req.data, "close")?;
15937 let kernel = req.kernel.to_non_batch();
15938 collect_f64("stc", output_id, req.combos, data.len(), |params| {
15939 let fast_period = get_usize_param("stc", params, "fast_period", 23)?;
15940 let slow_period = get_usize_param("stc", params, "slow_period", 50)?;
15941 let k_period = get_usize_param("stc", params, "k_period", 10)?;
15942 let d_period = get_usize_param("stc", params, "d_period", 3)?;
15943 let input = StcInput::from_slice(
15944 data,
15945 StcParams {
15946 fast_period: Some(fast_period),
15947 slow_period: Some(slow_period),
15948 k_period: Some(k_period),
15949 d_period: Some(d_period),
15950 fast_ma_type: Some("ema".to_string()),
15951 slow_ma_type: Some("ema".to_string()),
15952 },
15953 );
15954 let out =
15955 stc_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
15956 indicator: "stc".to_string(),
15957 details: e.to_string(),
15958 })?;
15959 if output_id.eq_ignore_ascii_case("value") || output_id.eq_ignore_ascii_case("values") {
15960 return Ok(out.values);
15961 }
15962 Err(IndicatorDispatchError::UnknownOutput {
15963 indicator: "stc".to_string(),
15964 output: output_id.to_string(),
15965 })
15966 })
15967}
15968
15969fn compute_rvi_batch(
15970 req: IndicatorBatchRequest<'_>,
15971 output_id: &str,
15972) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
15973 let data = extract_slice_input("rvi", req.data, "close")?;
15974 let kernel = req.kernel.to_non_batch();
15975 collect_f64("rvi", output_id, req.combos, data.len(), |params| {
15976 let period = get_usize_param("rvi", params, "period", 10)?;
15977 let ma_len = get_usize_param("rvi", params, "ma_len", 14)?;
15978 let matype = get_usize_param("rvi", params, "matype", 1)?;
15979 let devtype = get_usize_param("rvi", params, "devtype", 0)?;
15980 let input = RviInput::from_slice(
15981 data,
15982 RviParams {
15983 period: Some(period),
15984 ma_len: Some(ma_len),
15985 matype: Some(matype),
15986 devtype: Some(devtype),
15987 },
15988 );
15989 let out =
15990 rvi_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
15991 indicator: "rvi".to_string(),
15992 details: e.to_string(),
15993 })?;
15994 if output_id.eq_ignore_ascii_case("value") || output_id.eq_ignore_ascii_case("values") {
15995 return Ok(out.values);
15996 }
15997 Err(IndicatorDispatchError::UnknownOutput {
15998 indicator: "rvi".to_string(),
15999 output: output_id.to_string(),
16000 })
16001 })
16002}
16003
16004fn compute_coppock_batch(
16005 req: IndicatorBatchRequest<'_>,
16006 output_id: &str,
16007) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
16008 let data = extract_slice_input("coppock", req.data, "close")?;
16009 let kernel = req.kernel.to_non_batch();
16010 collect_f64("coppock", output_id, req.combos, data.len(), |params| {
16011 let short_roc_period = get_usize_param("coppock", params, "short_roc_period", 11)?;
16012 let long_roc_period = get_usize_param("coppock", params, "long_roc_period", 14)?;
16013 let ma_period = get_usize_param("coppock", params, "ma_period", 10)?;
16014 let input = CoppockInput::from_slice(
16015 data,
16016 CoppockParams {
16017 short_roc_period: Some(short_roc_period),
16018 long_roc_period: Some(long_roc_period),
16019 ma_period: Some(ma_period),
16020 ma_type: Some("wma".to_string()),
16021 },
16022 );
16023 let out = coppock_with_kernel(&input, kernel).map_err(|e| {
16024 IndicatorDispatchError::ComputeFailed {
16025 indicator: "coppock".to_string(),
16026 details: e.to_string(),
16027 }
16028 })?;
16029 if output_id.eq_ignore_ascii_case("value") || output_id.eq_ignore_ascii_case("values") {
16030 return Ok(out.values);
16031 }
16032 Err(IndicatorDispatchError::UnknownOutput {
16033 indicator: "coppock".to_string(),
16034 output: output_id.to_string(),
16035 })
16036 })
16037}
16038
16039fn compute_correl_hl_batch(
16040 req: IndicatorBatchRequest<'_>,
16041 output_id: &str,
16042) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
16043 expect_value_output("correl_hl", output_id)?;
16044 let (high, low) = extract_high_low_input("correl_hl", req.data)?;
16045 let kernel = req.kernel.to_non_batch();
16046 collect_f64("correl_hl", output_id, req.combos, high.len(), |params| {
16047 let period = get_usize_param("correl_hl", params, "period", 9)?;
16048 let input = CorrelHlInput::from_slices(
16049 high,
16050 low,
16051 CorrelHlParams {
16052 period: Some(period),
16053 },
16054 );
16055 let out = correl_hl_with_kernel(&input, kernel).map_err(|e| {
16056 IndicatorDispatchError::ComputeFailed {
16057 indicator: "correl_hl".to_string(),
16058 details: e.to_string(),
16059 }
16060 })?;
16061 Ok(out.values)
16062 })
16063}
16064
16065fn compute_net_myrsi_batch(
16066 req: IndicatorBatchRequest<'_>,
16067 output_id: &str,
16068) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
16069 let data = extract_slice_input("net_myrsi", req.data, "close")?;
16070 let kernel = req.kernel.to_non_batch();
16071 collect_f64("net_myrsi", output_id, req.combos, data.len(), |params| {
16072 let period = get_usize_param("net_myrsi", params, "period", 14)?;
16073 let input = NetMyrsiInput::from_slice(
16074 data,
16075 NetMyrsiParams {
16076 period: Some(period),
16077 },
16078 );
16079 let out = net_myrsi_with_kernel(&input, kernel).map_err(|e| {
16080 IndicatorDispatchError::ComputeFailed {
16081 indicator: "net_myrsi".to_string(),
16082 details: e.to_string(),
16083 }
16084 })?;
16085 if output_id.eq_ignore_ascii_case("value") || output_id.eq_ignore_ascii_case("values") {
16086 return Ok(out.values);
16087 }
16088 Err(IndicatorDispatchError::UnknownOutput {
16089 indicator: "net_myrsi".to_string(),
16090 output: output_id.to_string(),
16091 })
16092 })
16093}
16094
16095fn compute_pivot_batch(
16096 req: IndicatorBatchRequest<'_>,
16097 output_id: &str,
16098) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
16099 let (open, high, low, close) = extract_ohlc_full_input("pivot", req.data)?;
16100 let kernel = req.kernel.to_non_batch();
16101 collect_f64("pivot", output_id, req.combos, close.len(), |params| {
16102 let mode = get_usize_param("pivot", params, "mode", 3)?;
16103 let input =
16104 PivotInput::from_slices(high, low, close, open, PivotParams { mode: Some(mode) });
16105 let out = pivot_with_kernel(&input, kernel).map_err(|e| {
16106 IndicatorDispatchError::ComputeFailed {
16107 indicator: "pivot".to_string(),
16108 details: e.to_string(),
16109 }
16110 })?;
16111 if output_id.eq_ignore_ascii_case("pp") || output_id.eq_ignore_ascii_case("value") {
16112 return Ok(out.pp);
16113 }
16114 if output_id.eq_ignore_ascii_case("r1") {
16115 return Ok(out.r1);
16116 }
16117 if output_id.eq_ignore_ascii_case("r2") {
16118 return Ok(out.r2);
16119 }
16120 if output_id.eq_ignore_ascii_case("r3") {
16121 return Ok(out.r3);
16122 }
16123 if output_id.eq_ignore_ascii_case("r4") {
16124 return Ok(out.r4);
16125 }
16126 if output_id.eq_ignore_ascii_case("s1") {
16127 return Ok(out.s1);
16128 }
16129 if output_id.eq_ignore_ascii_case("s2") {
16130 return Ok(out.s2);
16131 }
16132 if output_id.eq_ignore_ascii_case("s3") {
16133 return Ok(out.s3);
16134 }
16135 if output_id.eq_ignore_ascii_case("s4") {
16136 return Ok(out.s4);
16137 }
16138 Err(IndicatorDispatchError::UnknownOutput {
16139 indicator: "pivot".to_string(),
16140 output: output_id.to_string(),
16141 })
16142 })
16143}
16144
16145fn compute_wad_batch(
16146 req: IndicatorBatchRequest<'_>,
16147 output_id: &str,
16148) -> Result<IndicatorBatchOutput, IndicatorDispatchError> {
16149 expect_value_output("wad", output_id)?;
16150 let (_open, high, low, close) = extract_ohlc_full_input("wad", req.data)?;
16151 let kernel = req.kernel.to_non_batch();
16152 collect_f64("wad", output_id, req.combos, close.len(), |_params| {
16153 let input = WadInput::from_slices(high, low, close);
16154 let out =
16155 wad_with_kernel(&input, kernel).map_err(|e| IndicatorDispatchError::ComputeFailed {
16156 indicator: "wad".to_string(),
16157 details: e.to_string(),
16158 })?;
16159 Ok(out.values)
16160 })
16161}
16162
16163fn ma_data_from_req<'a>(
16164 indicator: &str,
16165 data: IndicatorDataRef<'a>,
16166) -> Result<MaData<'a>, IndicatorDispatchError> {
16167 match data {
16168 IndicatorDataRef::Slice { values } => Ok(MaData::Slice(values)),
16169 IndicatorDataRef::Candles { candles, source } => Ok(MaData::Candles {
16170 candles,
16171 source: source.unwrap_or("close"),
16172 }),
16173 IndicatorDataRef::Ohlc { close, .. } => Ok(MaData::Slice(close)),
16174 IndicatorDataRef::Ohlcv { close, .. } => Ok(MaData::Slice(close)),
16175 IndicatorDataRef::CloseVolume { close, .. } => Ok(MaData::Slice(close)),
16176 IndicatorDataRef::HighLow { .. } => Err(IndicatorDispatchError::MissingRequiredInput {
16177 indicator: indicator.to_string(),
16178 input: IndicatorInputKind::Slice,
16179 }),
16180 }
16181}
16182
16183fn ma_len_from_req(
16184 indicator: &str,
16185 data: IndicatorDataRef<'_>,
16186) -> Result<usize, IndicatorDispatchError> {
16187 match data {
16188 IndicatorDataRef::Slice { values } => Ok(values.len()),
16189 IndicatorDataRef::Candles { candles, source } => {
16190 Ok(source_type(candles, source.unwrap_or("close")).len())
16191 }
16192 IndicatorDataRef::Ohlc { close, .. } => Ok(close.len()),
16193 IndicatorDataRef::Ohlcv { close, .. } => Ok(close.len()),
16194 IndicatorDataRef::CloseVolume { close, .. } => Ok(close.len()),
16195 IndicatorDataRef::HighLow { .. } => Err(IndicatorDispatchError::MissingRequiredInput {
16196 indicator: indicator.to_string(),
16197 input: IndicatorInputKind::Slice,
16198 }),
16199 }
16200}
16201
16202fn ma_period_for_combo(
16203 info: &IndicatorInfo,
16204 params: &[ParamKV<'_>],
16205) -> Result<usize, IndicatorDispatchError> {
16206 if let Some(v) = find_param(params, "period") {
16207 return parse_usize_param_value(info.id, "period", v);
16208 }
16209 if let Some(default) = info
16210 .params
16211 .iter()
16212 .find(|p| p.key.eq_ignore_ascii_case("period"))
16213 .and_then(|p| p.default.as_ref())
16214 {
16215 if let ParamValueStatic::Int(v) = default {
16216 if *v >= 0 {
16217 return Ok(*v as usize);
16218 }
16219 }
16220 }
16221 Ok(14)
16222}
16223
16224fn convert_ma_params<'a>(
16225 params: &'a [ParamKV<'a>],
16226 indicator: &str,
16227 output_id: &str,
16228) -> Result<Vec<MaBatchParamKV<'a>>, IndicatorDispatchError> {
16229 let mut out = Vec::with_capacity(params.len());
16230 for p in params {
16231 if p.key.eq_ignore_ascii_case("period") {
16232 continue;
16233 }
16234 if p.key.eq_ignore_ascii_case("output") {
16235 let selected = match p.value {
16236 ParamValue::EnumString(v) => v,
16237 _ => {
16238 return Err(IndicatorDispatchError::InvalidParam {
16239 indicator: indicator.to_string(),
16240 key: "output".to_string(),
16241 reason: "expected EnumString".to_string(),
16242 })
16243 }
16244 };
16245 if !selected.eq_ignore_ascii_case(output_id) {
16246 return Err(IndicatorDispatchError::InvalidParam {
16247 indicator: indicator.to_string(),
16248 key: "output".to_string(),
16249 reason: format!(
16250 "param output '{}' does not match requested output_id '{}'",
16251 selected, output_id
16252 ),
16253 });
16254 }
16255 }
16256 let value = match p.value {
16257 ParamValue::Int(v) => MaBatchParamValue::Int(v),
16258 ParamValue::Float(v) => {
16259 if !v.is_finite() {
16260 return Err(IndicatorDispatchError::InvalidParam {
16261 indicator: indicator.to_string(),
16262 key: p.key.to_string(),
16263 reason: "expected finite float".to_string(),
16264 });
16265 }
16266 MaBatchParamValue::Float(v)
16267 }
16268 ParamValue::Bool(v) => MaBatchParamValue::Bool(v),
16269 ParamValue::EnumString(v) => MaBatchParamValue::EnumString(v),
16270 };
16271 out.push(MaBatchParamKV { key: p.key, value });
16272 }
16273 Ok(out)
16274}
16275
16276fn extract_slice_input<'a>(
16277 indicator: &str,
16278 data: IndicatorDataRef<'a>,
16279 default_source: &'a str,
16280) -> Result<&'a [f64], IndicatorDispatchError> {
16281 match data {
16282 IndicatorDataRef::Slice { values } => Ok(values),
16283 IndicatorDataRef::Candles { candles, source } => {
16284 Ok(source_type(candles, source.unwrap_or(default_source)))
16285 }
16286 IndicatorDataRef::Ohlc { close, .. } => Ok(close),
16287 IndicatorDataRef::Ohlcv { close, .. } => Ok(close),
16288 IndicatorDataRef::CloseVolume { close, .. } => Ok(close),
16289 IndicatorDataRef::HighLow { .. } => Err(IndicatorDispatchError::MissingRequiredInput {
16290 indicator: indicator.to_string(),
16291 input: IndicatorInputKind::Slice,
16292 }),
16293 }
16294}
16295
16296fn extract_ohlc_input<'a>(
16297 indicator: &str,
16298 data: IndicatorDataRef<'a>,
16299) -> Result<(&'a [f64], &'a [f64], &'a [f64]), IndicatorDispatchError> {
16300 match data {
16301 IndicatorDataRef::Candles { candles, .. } => Ok((
16302 candles.high.as_slice(),
16303 candles.low.as_slice(),
16304 candles.close.as_slice(),
16305 )),
16306 IndicatorDataRef::Ohlc {
16307 high,
16308 low,
16309 close,
16310 open,
16311 } => {
16312 ensure_same_len_4(indicator, open.len(), high.len(), low.len(), close.len())?;
16313 Ok((high, low, close))
16314 }
16315 IndicatorDataRef::Ohlcv {
16316 high,
16317 low,
16318 close,
16319 open,
16320 volume,
16321 } => {
16322 ensure_same_len_5(
16323 indicator,
16324 open.len(),
16325 high.len(),
16326 low.len(),
16327 close.len(),
16328 volume.len(),
16329 )?;
16330 Ok((high, low, close))
16331 }
16332 _ => Err(IndicatorDispatchError::MissingRequiredInput {
16333 indicator: indicator.to_string(),
16334 input: IndicatorInputKind::Ohlc,
16335 }),
16336 }
16337}
16338
16339fn extract_ohlc_full_input<'a>(
16340 indicator: &str,
16341 data: IndicatorDataRef<'a>,
16342) -> Result<(&'a [f64], &'a [f64], &'a [f64], &'a [f64]), IndicatorDispatchError> {
16343 match data {
16344 IndicatorDataRef::Candles { candles, .. } => Ok((
16345 candles.open.as_slice(),
16346 candles.high.as_slice(),
16347 candles.low.as_slice(),
16348 candles.close.as_slice(),
16349 )),
16350 IndicatorDataRef::Ohlc {
16351 open,
16352 high,
16353 low,
16354 close,
16355 } => {
16356 ensure_same_len_4(indicator, open.len(), high.len(), low.len(), close.len())?;
16357 Ok((open, high, low, close))
16358 }
16359 IndicatorDataRef::Ohlcv {
16360 open,
16361 high,
16362 low,
16363 close,
16364 volume,
16365 } => {
16366 ensure_same_len_5(
16367 indicator,
16368 open.len(),
16369 high.len(),
16370 low.len(),
16371 close.len(),
16372 volume.len(),
16373 )?;
16374 Ok((open, high, low, close))
16375 }
16376 _ => Err(IndicatorDispatchError::MissingRequiredInput {
16377 indicator: indicator.to_string(),
16378 input: IndicatorInputKind::Ohlc,
16379 }),
16380 }
16381}
16382
16383fn extract_ohlcv_full_input<'a>(
16384 indicator: &str,
16385 data: IndicatorDataRef<'a>,
16386) -> Result<(&'a [f64], &'a [f64], &'a [f64], &'a [f64], &'a [f64]), IndicatorDispatchError> {
16387 match data {
16388 IndicatorDataRef::Candles { candles, .. } => Ok((
16389 candles.open.as_slice(),
16390 candles.high.as_slice(),
16391 candles.low.as_slice(),
16392 candles.close.as_slice(),
16393 candles.volume.as_slice(),
16394 )),
16395 IndicatorDataRef::Ohlcv {
16396 open,
16397 high,
16398 low,
16399 close,
16400 volume,
16401 } => {
16402 ensure_same_len_5(
16403 indicator,
16404 open.len(),
16405 high.len(),
16406 low.len(),
16407 close.len(),
16408 volume.len(),
16409 )?;
16410 Ok((open, high, low, close, volume))
16411 }
16412 _ => Err(IndicatorDispatchError::MissingRequiredInput {
16413 indicator: indicator.to_string(),
16414 input: IndicatorInputKind::Ohlcv,
16415 }),
16416 }
16417}
16418
16419fn extract_high_low_input<'a>(
16420 indicator: &str,
16421 data: IndicatorDataRef<'a>,
16422) -> Result<(&'a [f64], &'a [f64]), IndicatorDispatchError> {
16423 match data {
16424 IndicatorDataRef::Candles { candles, .. } => {
16425 Ok((candles.high.as_slice(), candles.low.as_slice()))
16426 }
16427 IndicatorDataRef::Ohlc {
16428 high,
16429 low,
16430 open,
16431 close,
16432 } => {
16433 ensure_same_len_4(indicator, open.len(), high.len(), low.len(), close.len())?;
16434 Ok((high, low))
16435 }
16436 IndicatorDataRef::Ohlcv {
16437 high,
16438 low,
16439 open,
16440 close,
16441 volume,
16442 } => {
16443 ensure_same_len_5(
16444 indicator,
16445 open.len(),
16446 high.len(),
16447 low.len(),
16448 close.len(),
16449 volume.len(),
16450 )?;
16451 Ok((high, low))
16452 }
16453 IndicatorDataRef::HighLow { high, low } => {
16454 ensure_same_len_2(indicator, high.len(), low.len())?;
16455 Ok((high, low))
16456 }
16457 _ => Err(IndicatorDispatchError::MissingRequiredInput {
16458 indicator: indicator.to_string(),
16459 input: IndicatorInputKind::HighLow,
16460 }),
16461 }
16462}
16463
16464fn extract_hlcv_input<'a>(
16465 indicator: &str,
16466 data: IndicatorDataRef<'a>,
16467) -> Result<(&'a [f64], &'a [f64], &'a [f64], &'a [f64]), IndicatorDispatchError> {
16468 match data {
16469 IndicatorDataRef::Candles { candles, .. } => Ok((
16470 candles.high.as_slice(),
16471 candles.low.as_slice(),
16472 candles.close.as_slice(),
16473 candles.volume.as_slice(),
16474 )),
16475 IndicatorDataRef::Ohlcv {
16476 open,
16477 high,
16478 low,
16479 close,
16480 volume,
16481 } => {
16482 ensure_same_len_5(
16483 indicator,
16484 open.len(),
16485 high.len(),
16486 low.len(),
16487 close.len(),
16488 volume.len(),
16489 )?;
16490 Ok((high, low, close, volume))
16491 }
16492 _ => Err(IndicatorDispatchError::MissingRequiredInput {
16493 indicator: indicator.to_string(),
16494 input: IndicatorInputKind::Ohlcv,
16495 }),
16496 }
16497}
16498
16499fn extract_volume_input<'a>(
16500 indicator: &str,
16501 data: IndicatorDataRef<'a>,
16502) -> Result<&'a [f64], IndicatorDispatchError> {
16503 match data {
16504 IndicatorDataRef::Slice { values } => Ok(values),
16505 IndicatorDataRef::Candles { candles, source } => {
16506 Ok(source_type(candles, source.unwrap_or("volume")))
16507 }
16508 IndicatorDataRef::CloseVolume { close, volume } => {
16509 ensure_same_len_2(indicator, close.len(), volume.len())?;
16510 Ok(volume)
16511 }
16512 IndicatorDataRef::Ohlcv {
16513 open,
16514 high,
16515 low,
16516 close,
16517 volume,
16518 } => {
16519 ensure_same_len_5(
16520 indicator,
16521 open.len(),
16522 high.len(),
16523 low.len(),
16524 close.len(),
16525 volume.len(),
16526 )?;
16527 Ok(volume)
16528 }
16529 _ => Err(IndicatorDispatchError::MissingRequiredInput {
16530 indicator: indicator.to_string(),
16531 input: IndicatorInputKind::Slice,
16532 }),
16533 }
16534}
16535
16536fn extract_close_volume_input<'a>(
16537 indicator: &str,
16538 data: IndicatorDataRef<'a>,
16539 default_close_source: &'a str,
16540) -> Result<(&'a [f64], &'a [f64]), IndicatorDispatchError> {
16541 match data {
16542 IndicatorDataRef::CloseVolume { close, volume } => {
16543 ensure_same_len_2(indicator, close.len(), volume.len())?;
16544 Ok((close, volume))
16545 }
16546 IndicatorDataRef::Ohlcv {
16547 close,
16548 volume,
16549 open,
16550 high,
16551 low,
16552 } => {
16553 ensure_same_len_5(
16554 indicator,
16555 open.len(),
16556 high.len(),
16557 low.len(),
16558 close.len(),
16559 volume.len(),
16560 )?;
16561 Ok((close, volume))
16562 }
16563 IndicatorDataRef::Candles { candles, source } => {
16564 let close = source_type(candles, source.unwrap_or(default_close_source));
16565 let volume = candles.volume.as_slice();
16566 ensure_same_len_2(indicator, close.len(), volume.len())?;
16567 Ok((close, volume))
16568 }
16569 _ => Err(IndicatorDispatchError::MissingRequiredInput {
16570 indicator: indicator.to_string(),
16571 input: IndicatorInputKind::CloseVolume,
16572 }),
16573 }
16574}
16575
16576fn f64_output(output_id: &str, rows: usize, cols: usize, values: Vec<f64>) -> IndicatorBatchOutput {
16577 IndicatorBatchOutput {
16578 output_id: output_id.to_string(),
16579 rows,
16580 cols,
16581 values_f64: Some(values),
16582 values_i32: None,
16583 values_bool: None,
16584 }
16585}
16586
16587fn bool_output(
16588 output_id: &str,
16589 rows: usize,
16590 cols: usize,
16591 values: Vec<bool>,
16592) -> IndicatorBatchOutput {
16593 IndicatorBatchOutput {
16594 output_id: output_id.to_string(),
16595 rows,
16596 cols,
16597 values_f64: None,
16598 values_i32: None,
16599 values_bool: Some(values),
16600 }
16601}
16602
16603fn expect_value_output(indicator: &str, output_id: &str) -> Result<(), IndicatorDispatchError> {
16604 if output_id.eq_ignore_ascii_case("value") {
16605 return Ok(());
16606 }
16607 Err(IndicatorDispatchError::UnknownOutput {
16608 indicator: indicator.to_string(),
16609 output: output_id.to_string(),
16610 })
16611}
16612
16613fn ensure_len(indicator: &str, expected: usize, got: usize) -> Result<(), IndicatorDispatchError> {
16614 if expected == got {
16615 return Ok(());
16616 }
16617 Err(IndicatorDispatchError::DataLengthMismatch {
16618 details: format!("{indicator}: expected output length {expected}, got {got}"),
16619 })
16620}
16621
16622fn ensure_same_len_2(indicator: &str, a: usize, b: usize) -> Result<(), IndicatorDispatchError> {
16623 if a == b {
16624 return Ok(());
16625 }
16626 Err(IndicatorDispatchError::DataLengthMismatch {
16627 details: format!("{indicator}: expected equal lengths, got {a} and {b}"),
16628 })
16629}
16630
16631fn ensure_same_len_3(
16632 indicator: &str,
16633 a: usize,
16634 b: usize,
16635 c: usize,
16636) -> Result<(), IndicatorDispatchError> {
16637 if a == b && b == c {
16638 return Ok(());
16639 }
16640 Err(IndicatorDispatchError::DataLengthMismatch {
16641 details: format!("{indicator}: expected equal lengths, got {a}, {b}, {c}"),
16642 })
16643}
16644
16645fn ensure_same_len_4(
16646 indicator: &str,
16647 a: usize,
16648 b: usize,
16649 c: usize,
16650 d: usize,
16651) -> Result<(), IndicatorDispatchError> {
16652 if a == b && b == c && c == d {
16653 return Ok(());
16654 }
16655 Err(IndicatorDispatchError::DataLengthMismatch {
16656 details: format!("{indicator}: expected equal lengths, got {a}, {b}, {c}, {d}"),
16657 })
16658}
16659
16660fn ensure_same_len_5(
16661 indicator: &str,
16662 a: usize,
16663 b: usize,
16664 c: usize,
16665 d: usize,
16666 e: usize,
16667) -> Result<(), IndicatorDispatchError> {
16668 if a == b && b == c && c == d && d == e {
16669 return Ok(());
16670 }
16671 Err(IndicatorDispatchError::DataLengthMismatch {
16672 details: format!("{indicator}: expected equal lengths, got {a}, {b}, {c}, {d}, {e}"),
16673 })
16674}
16675
16676fn has_key(params: &[ParamKV<'_>], key: &str) -> bool {
16677 params.iter().any(|kv| kv.key.eq_ignore_ascii_case(key))
16678}
16679
16680fn find_param<'a>(params: &'a [ParamKV<'a>], key: &str) -> Option<&'a ParamValue<'a>> {
16681 params
16682 .iter()
16683 .rev()
16684 .find(|kv| kv.key.eq_ignore_ascii_case(key))
16685 .map(|kv| &kv.value)
16686}
16687
16688fn get_usize_param(
16689 indicator: &str,
16690 params: &[ParamKV<'_>],
16691 key: &str,
16692 default: usize,
16693) -> Result<usize, IndicatorDispatchError> {
16694 match find_param(params, key) {
16695 Some(v) => parse_usize_param_value(indicator, key, v),
16696 None => Ok(default),
16697 }
16698}
16699
16700fn get_usize_param_with_aliases(
16701 indicator: &str,
16702 params: &[ParamKV<'_>],
16703 keys: &[&str],
16704 default: usize,
16705) -> Result<usize, IndicatorDispatchError> {
16706 for key in keys {
16707 if let Some(v) = find_param(params, key) {
16708 return parse_usize_param_value(indicator, key, v);
16709 }
16710 }
16711 Ok(default)
16712}
16713
16714fn get_f64_param_with_aliases(
16715 indicator: &str,
16716 params: &[ParamKV<'_>],
16717 keys: &[&str],
16718 default: f64,
16719) -> Result<f64, IndicatorDispatchError> {
16720 for key in keys {
16721 match find_param(params, key) {
16722 Some(ParamValue::Int(v)) => return Ok(*v as f64),
16723 Some(ParamValue::Float(v)) => {
16724 if v.is_finite() {
16725 return Ok(*v);
16726 }
16727 return Err(IndicatorDispatchError::InvalidParam {
16728 indicator: indicator.to_string(),
16729 key: key.to_string(),
16730 reason: "expected finite float".to_string(),
16731 });
16732 }
16733 Some(_) => {
16734 return Err(IndicatorDispatchError::InvalidParam {
16735 indicator: indicator.to_string(),
16736 key: key.to_string(),
16737 reason: "expected Int or Float".to_string(),
16738 });
16739 }
16740 None => continue,
16741 }
16742 }
16743 Ok(default)
16744}
16745
16746fn parse_usize_param_value(
16747 indicator: &str,
16748 key: &str,
16749 value: &ParamValue<'_>,
16750) -> Result<usize, IndicatorDispatchError> {
16751 match value {
16752 ParamValue::Int(v) => {
16753 if *v < 0 {
16754 return Err(IndicatorDispatchError::InvalidParam {
16755 indicator: indicator.to_string(),
16756 key: key.to_string(),
16757 reason: "expected integer >= 0".to_string(),
16758 });
16759 }
16760 Ok(*v as usize)
16761 }
16762 ParamValue::Float(v) => {
16763 if !v.is_finite() {
16764 return Err(IndicatorDispatchError::InvalidParam {
16765 indicator: indicator.to_string(),
16766 key: key.to_string(),
16767 reason: "expected finite number".to_string(),
16768 });
16769 }
16770 if *v < 0.0 {
16771 return Err(IndicatorDispatchError::InvalidParam {
16772 indicator: indicator.to_string(),
16773 key: key.to_string(),
16774 reason: "expected number >= 0".to_string(),
16775 });
16776 }
16777 let r = v.round();
16778 if (*v - r).abs() > 1e-9 {
16779 return Err(IndicatorDispatchError::InvalidParam {
16780 indicator: indicator.to_string(),
16781 key: key.to_string(),
16782 reason: "expected integer value".to_string(),
16783 });
16784 }
16785 Ok(r as usize)
16786 }
16787 _ => Err(IndicatorDispatchError::InvalidParam {
16788 indicator: indicator.to_string(),
16789 key: key.to_string(),
16790 reason: "expected Int or Float".to_string(),
16791 }),
16792 }
16793}
16794
16795fn get_f64_param(
16796 indicator: &str,
16797 params: &[ParamKV<'_>],
16798 key: &str,
16799 default: f64,
16800) -> Result<f64, IndicatorDispatchError> {
16801 match find_param(params, key) {
16802 Some(ParamValue::Int(v)) => Ok(*v as f64),
16803 Some(ParamValue::Float(v)) => {
16804 if v.is_finite() {
16805 Ok(*v)
16806 } else {
16807 Err(IndicatorDispatchError::InvalidParam {
16808 indicator: indicator.to_string(),
16809 key: key.to_string(),
16810 reason: "expected finite float".to_string(),
16811 })
16812 }
16813 }
16814 Some(_) => Err(IndicatorDispatchError::InvalidParam {
16815 indicator: indicator.to_string(),
16816 key: key.to_string(),
16817 reason: "expected Int or Float".to_string(),
16818 }),
16819 None => Ok(default),
16820 }
16821}
16822
16823fn get_bool_param(
16824 indicator: &str,
16825 params: &[ParamKV<'_>],
16826 key: &str,
16827 default: bool,
16828) -> Result<bool, IndicatorDispatchError> {
16829 match find_param(params, key) {
16830 Some(ParamValue::Bool(v)) => Ok(*v),
16831 Some(ParamValue::Int(v)) => match *v {
16832 0 => Ok(false),
16833 1 => Ok(true),
16834 _ => Err(IndicatorDispatchError::InvalidParam {
16835 indicator: indicator.to_string(),
16836 key: key.to_string(),
16837 reason: "expected Bool or Int(0/1)".to_string(),
16838 }),
16839 },
16840 Some(_) => Err(IndicatorDispatchError::InvalidParam {
16841 indicator: indicator.to_string(),
16842 key: key.to_string(),
16843 reason: "expected Bool".to_string(),
16844 }),
16845 None => Ok(default),
16846 }
16847}
16848
16849fn get_enum_string_param<'a>(
16850 indicator: &str,
16851 params: &'a [ParamKV<'a>],
16852 key: &str,
16853 default: &'a str,
16854) -> Result<&'a str, IndicatorDispatchError> {
16855 match find_param(params, key) {
16856 Some(ParamValue::EnumString(v)) => Ok(v),
16857 Some(_) => Err(IndicatorDispatchError::InvalidParam {
16858 indicator: indicator.to_string(),
16859 key: key.to_string(),
16860 reason: "expected EnumString".to_string(),
16861 }),
16862 None => Ok(default),
16863 }
16864}
16865
16866fn get_i32_param(
16867 indicator: &str,
16868 params: &[ParamKV<'_>],
16869 key: &str,
16870 default: i32,
16871) -> Result<i32, IndicatorDispatchError> {
16872 match find_param(params, key) {
16873 Some(ParamValue::Int(v)) => {
16874 if *v < i32::MIN as i64 || *v > i32::MAX as i64 {
16875 return Err(IndicatorDispatchError::InvalidParam {
16876 indicator: indicator.to_string(),
16877 key: key.to_string(),
16878 reason: "integer out of i32 range".to_string(),
16879 });
16880 }
16881 Ok(*v as i32)
16882 }
16883 Some(ParamValue::Float(v)) => {
16884 if !v.is_finite() {
16885 return Err(IndicatorDispatchError::InvalidParam {
16886 indicator: indicator.to_string(),
16887 key: key.to_string(),
16888 reason: "expected finite number".to_string(),
16889 });
16890 }
16891 let r = v.round();
16892 if (*v - r).abs() > 1e-9 || r < i32::MIN as f64 || r > i32::MAX as f64 {
16893 return Err(IndicatorDispatchError::InvalidParam {
16894 indicator: indicator.to_string(),
16895 key: key.to_string(),
16896 reason: "expected i32-compatible whole number".to_string(),
16897 });
16898 }
16899 Ok(r as i32)
16900 }
16901 Some(_) => Err(IndicatorDispatchError::InvalidParam {
16902 indicator: indicator.to_string(),
16903 key: key.to_string(),
16904 reason: "expected Int or Float".to_string(),
16905 }),
16906 None => Ok(default),
16907 }
16908}
16909
16910fn get_enum_param(
16911 indicator: &str,
16912 params: &[ParamKV<'_>],
16913 key: &str,
16914 default: &str,
16915) -> Result<String, IndicatorDispatchError> {
16916 match find_param(params, key) {
16917 Some(ParamValue::EnumString(v)) => Ok((*v).to_string()),
16918 Some(_) => Err(IndicatorDispatchError::InvalidParam {
16919 indicator: indicator.to_string(),
16920 key: key.to_string(),
16921 reason: "expected EnumString".to_string(),
16922 }),
16923 None => Ok(default.to_string()),
16924 }
16925}
16926
16927#[cfg(test)]
16928mod tests {
16929 use super::*;
16930 use crate::indicators::absolute_strength_index_oscillator::{
16931 absolute_strength_index_oscillator_with_kernel, AbsoluteStrengthIndexOscillatorInput,
16932 AbsoluteStrengthIndexOscillatorParams,
16933 };
16934 use crate::indicators::accumulation_swing_index::{
16935 accumulation_swing_index_with_kernel, AccumulationSwingIndexInput,
16936 AccumulationSwingIndexParams,
16937 };
16938 use crate::indicators::ad::{ad_with_kernel, AdInput, AdParams};
16939 use crate::indicators::adaptive_bandpass_trigger_oscillator::{
16940 adaptive_bandpass_trigger_oscillator_with_kernel, AdaptiveBandpassTriggerOscillatorInput,
16941 AdaptiveBandpassTriggerOscillatorParams,
16942 };
16943 use crate::indicators::advance_decline_line::{
16944 advance_decline_line_with_kernel, AdvanceDeclineLineInput, AdvanceDeclineLineParams,
16945 };
16946 use crate::indicators::adx::{adx_with_kernel, AdxInput, AdxParams};
16947 use crate::indicators::ao::{ao_with_kernel, AoInput, AoParams};
16948 use crate::indicators::apo::{apo_with_kernel, ApoInput, ApoParams};
16949 use crate::indicators::atr_percentile::{
16950 atr_percentile_with_kernel, AtrPercentileInput, AtrPercentileParams,
16951 };
16952 use crate::indicators::bull_power_vs_bear_power::{
16953 bull_power_vs_bear_power_with_kernel, BullPowerVsBearPowerInput, BullPowerVsBearPowerParams,
16954 };
16955 use crate::indicators::cg::{cg_with_kernel, CgInput, CgParams};
16956 use crate::indicators::cmo::{cmo_with_kernel, CmoInput, CmoParams};
16957 use crate::indicators::cycle_channel_oscillator::{
16958 cycle_channel_oscillator_with_kernel, CycleChannelOscillatorInput,
16959 CycleChannelOscillatorParams,
16960 };
16961 use crate::indicators::daily_factor::{
16962 daily_factor_with_kernel, DailyFactorInput, DailyFactorParams,
16963 };
16964 use crate::indicators::decisionpoint_breadth_swenlin_trading_oscillator::{
16965 decisionpoint_breadth_swenlin_trading_oscillator_with_kernel,
16966 DecisionPointBreadthSwenlinTradingOscillatorInput,
16967 DecisionPointBreadthSwenlinTradingOscillatorParams,
16968 };
16969 use crate::indicators::demand_index::{
16970 demand_index_with_kernel, DemandIndexInput, DemandIndexParams,
16971 };
16972 use crate::indicators::deviation::{deviation_with_kernel, DeviationInput, DeviationParams};
16973 use crate::indicators::dx::{
16974 dx_batch_with_kernel, dx_with_kernel, DxBatchRange, DxInput, DxParams,
16975 };
16976 use crate::indicators::efi::{efi_with_kernel, EfiInput, EfiParams};
16977 use crate::indicators::ehlers_adaptive_cyber_cycle::{
16978 ehlers_adaptive_cyber_cycle_with_kernel, EhlersAdaptiveCyberCycleInput,
16979 EhlersAdaptiveCyberCycleParams,
16980 };
16981 use crate::indicators::ehlers_linear_extrapolation_predictor::{
16982 ehlers_linear_extrapolation_predictor_with_kernel, EhlersLinearExtrapolationPredictorInput,
16983 EhlersLinearExtrapolationPredictorParams,
16984 };
16985 use crate::indicators::ehlers_simple_cycle_indicator::{
16986 ehlers_simple_cycle_indicator_with_kernel, EhlersSimpleCycleIndicatorInput,
16987 EhlersSimpleCycleIndicatorParams,
16988 };
16989 use crate::indicators::ehlers_smoothed_adaptive_momentum::{
16990 ehlers_smoothed_adaptive_momentum_with_kernel, EhlersSmoothedAdaptiveMomentumInput,
16991 EhlersSmoothedAdaptiveMomentumParams,
16992 };
16993 use crate::indicators::ewma_volatility::{
16994 ewma_volatility_with_kernel, EwmaVolatilityInput, EwmaVolatilityParams,
16995 };
16996 use crate::indicators::fibonacci_entry_bands::{
16997 fibonacci_entry_bands_with_kernel, FibonacciEntryBandsInput, FibonacciEntryBandsParams,
16998 };
16999 use crate::indicators::fibonacci_trailing_stop::{
17000 fibonacci_trailing_stop_with_kernel, FibonacciTrailingStopInput,
17001 FibonacciTrailingStopParams,
17002 };
17003 use crate::indicators::fosc::{fosc_with_kernel, FoscInput, FoscParams};
17004 use crate::indicators::garman_klass_volatility::{
17005 garman_klass_volatility_with_kernel, GarmanKlassVolatilityInput,
17006 GarmanKlassVolatilityParams,
17007 };
17008 use crate::indicators::gopalakrishnan_range_index::{
17009 gopalakrishnan_range_index_with_kernel, GopalakrishnanRangeIndexInput,
17010 GopalakrishnanRangeIndexParams,
17011 };
17012 use crate::indicators::grover_llorens_cycle_oscillator::{
17013 grover_llorens_cycle_oscillator_with_kernel, GroverLlorensCycleOscillatorInput,
17014 GroverLlorensCycleOscillatorParams,
17015 };
17016 use crate::indicators::hema_trend_levels::{
17017 hema_trend_levels_with_kernel, HemaTrendLevelsInput, HemaTrendLevelsParams,
17018 };
17019 use crate::indicators::historical_volatility::{
17020 historical_volatility_with_kernel, HistoricalVolatilityInput, HistoricalVolatilityParams,
17021 };
17022 use crate::indicators::historical_volatility_percentile::{
17023 historical_volatility_percentile_with_kernel, HistoricalVolatilityPercentileInput,
17024 HistoricalVolatilityPercentileParams,
17025 };
17026 use crate::indicators::hull_butterfly_oscillator::{
17027 hull_butterfly_oscillator_with_kernel, HullButterflyOscillatorInput,
17028 HullButterflyOscillatorParams,
17029 };
17030 use crate::indicators::ichimoku_oscillator::{
17031 ichimoku_oscillator_with_kernel, IchimokuOscillatorInput, IchimokuOscillatorNormalizeMode,
17032 IchimokuOscillatorParams,
17033 };
17034 use crate::indicators::ift_rsi::{ift_rsi_with_kernel, IftRsiInput, IftRsiParams};
17035 use crate::indicators::intraday_momentum_index::{
17036 intraday_momentum_index_with_kernel, IntradayMomentumIndexInput,
17037 IntradayMomentumIndexParams,
17038 };
17039 use crate::indicators::kvo::{kvo_with_kernel, KvoInput, KvoParams};
17040 use crate::indicators::l2_ehlers_signal_to_noise::{
17041 l2_ehlers_signal_to_noise_with_kernel, L2EhlersSignalToNoiseInput,
17042 L2EhlersSignalToNoiseParams,
17043 };
17044 use crate::indicators::linearreg_angle::{
17045 linearreg_angle_with_kernel, Linearreg_angleInput, Linearreg_angleParams,
17046 };
17047 use crate::indicators::linearreg_intercept::{
17048 linearreg_intercept_with_kernel, LinearRegInterceptInput, LinearRegInterceptParams,
17049 };
17050 use crate::indicators::linearreg_slope::{
17051 linearreg_slope_with_kernel, LinearRegSlopeInput, LinearRegSlopeParams,
17052 };
17053 use crate::indicators::macd::{macd_with_kernel, MacdInput, MacdParams};
17054 use crate::indicators::macd_wave_signal_pro::{
17055 macd_wave_signal_pro_with_kernel, MacdWaveSignalProInput,
17056 };
17057 use crate::indicators::mean_ad::{mean_ad_with_kernel, MeanAdInput, MeanAdParams};
17058 use crate::indicators::medprice::{medprice_with_kernel, MedpriceInput, MedpriceParams};
17059 use crate::indicators::mesa_stochastic_multi_length::{
17060 mesa_stochastic_multi_length_with_kernel, MesaStochasticMultiLengthInput,
17061 MesaStochasticMultiLengthParams,
17062 };
17063 use crate::indicators::mfi::{
17064 mfi_batch_with_kernel, mfi_with_kernel, MfiBatchRange, MfiInput, MfiParams,
17065 };
17066 use crate::indicators::monotonicity_index::{
17067 monotonicity_index_with_kernel, MonotonicityIndexInput, MonotonicityIndexMode,
17068 MonotonicityIndexParams,
17069 };
17070 use crate::indicators::moving_averages::ma::MaData;
17071 use crate::indicators::moving_averages::ma_batch::{
17072 ma_batch_with_kernel_and_typed_params, MaBatchParamKV, MaBatchParamValue,
17073 };
17074 use crate::indicators::multi_length_stochastic_average::{
17075 multi_length_stochastic_average_with_kernel, MultiLengthStochasticAverageInput,
17076 MultiLengthStochasticAverageParams,
17077 };
17078 use crate::indicators::natr::{natr_with_kernel, NatrInput, NatrParams};
17079 use crate::indicators::neighboring_trailing_stop::{
17080 neighboring_trailing_stop_with_kernel, NeighboringTrailingStopInput,
17081 NeighboringTrailingStopParams,
17082 };
17083 use crate::indicators::percentile_nearest_rank::{
17084 percentile_nearest_rank_with_kernel, PercentileNearestRankInput,
17085 PercentileNearestRankParams,
17086 };
17087 use crate::indicators::ppo::{ppo_with_kernel, PpoInput, PpoParams};
17088 use crate::indicators::premier_rsi_oscillator::{
17089 premier_rsi_oscillator_with_kernel, PremierRsiOscillatorInput, PremierRsiOscillatorParams,
17090 };
17091 use crate::indicators::price_moving_average_ratio_percentile::{
17092 price_moving_average_ratio_percentile_with_kernel, PriceMovingAverageRatioPercentileInput,
17093 PriceMovingAverageRatioPercentileLineMode, PriceMovingAverageRatioPercentileMaType,
17094 PriceMovingAverageRatioPercentileParams,
17095 };
17096 use crate::indicators::pvi::{pvi_with_kernel, PviInput, PviParams};
17097 use crate::indicators::random_walk_index::{
17098 random_walk_index_with_kernel, RandomWalkIndexInput, RandomWalkIndexParams,
17099 };
17100 use crate::indicators::registry::{list_indicators, IndicatorParamKind};
17101 use crate::indicators::spearman_correlation::{
17102 spearman_correlation_with_kernel, SpearmanCorrelationInput, SpearmanCorrelationParams,
17103 };
17104 use crate::indicators::squeeze_index::{
17105 squeeze_index_with_kernel, SqueezeIndexInput, SqueezeIndexParams,
17106 };
17107 use crate::indicators::stochastic_distance::{
17108 stochastic_distance_with_kernel, StochasticDistanceInput, StochasticDistanceParams,
17109 };
17110 use crate::indicators::trend_trigger_factor::{
17111 trend_trigger_factor_with_kernel, TrendTriggerFactorInput, TrendTriggerFactorParams,
17112 };
17113 use crate::indicators::trix::{
17114 trix_batch_with_kernel, trix_with_kernel, TrixBatchRange, TrixInput, TrixParams,
17115 };
17116 use crate::indicators::ttm_trend::{ttm_trend_with_kernel, TtmTrendInput, TtmTrendParams};
17117 use crate::indicators::velocity_acceleration_convergence_divergence_indicator::{
17118 velocity_acceleration_convergence_divergence_indicator_with_kernel,
17119 VelocityAccelerationConvergenceDivergenceIndicatorInput,
17120 VelocityAccelerationConvergenceDivergenceIndicatorParams,
17121 };
17122 use crate::indicators::velocity_acceleration_indicator::{
17123 velocity_acceleration_indicator_with_kernel, VelocityAccelerationIndicatorInput,
17124 VelocityAccelerationIndicatorParams,
17125 };
17126 use crate::indicators::volatility_quality_index::{
17127 volatility_quality_index_with_kernel, VolatilityQualityIndexInput,
17128 VolatilityQualityIndexParams,
17129 };
17130 use crate::indicators::volatility_ratio_adaptive_rsx::{
17131 volatility_ratio_adaptive_rsx_with_kernel, VolatilityRatioAdaptiveRsxInput,
17132 VolatilityRatioAdaptiveRsxParams,
17133 };
17134 use crate::indicators::volume_energy_reservoirs::{
17135 volume_energy_reservoirs_with_kernel, VolumeEnergyReservoirsInput,
17136 VolumeEnergyReservoirsParams,
17137 };
17138 use crate::indicators::volume_zone_oscillator::{
17139 volume_zone_oscillator_with_kernel, VolumeZoneOscillatorInput, VolumeZoneOscillatorParams,
17140 };
17141 use crate::indicators::vpci::{vpci_with_kernel, VpciInput, VpciParams};
17142 use crate::indicators::vwap_deviation_oscillator::{
17143 vwap_deviation_oscillator_with_kernel, VwapDeviationMode, VwapDeviationOscillatorInput,
17144 VwapDeviationOscillatorParams, VwapDeviationSessionMode,
17145 };
17146 use crate::indicators::vwap_zscore_with_signals::{
17147 vwap_zscore_with_signals_with_kernel, VwapZscoreWithSignalsInput,
17148 VwapZscoreWithSignalsParams,
17149 };
17150 use crate::indicators::yang_zhang_volatility::{
17151 yang_zhang_volatility_with_kernel, YangZhangVolatilityInput, YangZhangVolatilityParams,
17152 };
17153 use crate::indicators::zscore::{zscore_with_kernel, ZscoreInput, ZscoreParams};
17154 use crate::utilities::data_loader::Candles;
17155 use crate::utilities::enums::Kernel;
17156 use std::time::Instant;
17157
17158 fn sample_series() -> Vec<f64> {
17159 (1..=64).map(|v| v as f64).collect()
17160 }
17161
17162 fn sample_ohlc() -> (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>) {
17163 let open: Vec<f64> = (0..128).map(|i| 100.0 + (i as f64 * 0.1)).collect();
17164 let high: Vec<f64> = open.iter().map(|v| v + 1.25).collect();
17165 let low: Vec<f64> = open.iter().map(|v| v - 1.1).collect();
17166 let close: Vec<f64> = open.iter().map(|v| v + 0.3).collect();
17167 (open, high, low, close)
17168 }
17169
17170 fn sample_candles() -> crate::utilities::data_loader::Candles {
17171 let (open, high, low, close) = sample_ohlc();
17172 let volume: Vec<f64> = (0..close.len()).map(|i| 1000.0 + (i as f64)).collect();
17173 let timestamp: Vec<i64> = (0..close.len()).map(|i| i as i64).collect();
17174 crate::utilities::data_loader::Candles::new(timestamp, open, high, low, close, volume)
17175 }
17176
17177 fn assert_series_eq(actual: &[f64], expected: &[f64], tol: f64) {
17178 assert_eq!(actual.len(), expected.len());
17179 for i in 0..actual.len() {
17180 let a = actual[i];
17181 let b = expected[i];
17182 if a.is_nan() && b.is_nan() {
17183 continue;
17184 }
17185 assert!(
17186 (a - b).abs() <= tol,
17187 "mismatch at index {i}: actual={a}, expected={b}, tol={tol}"
17188 );
17189 }
17190 }
17191
17192 #[test]
17193 fn unknown_indicator_is_rejected() {
17194 let data = sample_series();
17195 let req = IndicatorBatchRequest {
17196 indicator_id: "not_real",
17197 output_id: None,
17198 data: IndicatorDataRef::Slice { values: &data },
17199 combos: &[],
17200 kernel: Kernel::Auto,
17201 };
17202 let err = compute_cpu_batch(req).unwrap_err();
17203 assert!(matches!(
17204 err,
17205 IndicatorDispatchError::UnknownIndicator { .. }
17206 ));
17207 }
17208
17209 #[test]
17210 fn bucket_b_ma_indicator_is_supported() {
17211 let data = sample_series();
17212 let combos = [IndicatorParamSet { params: &[] }];
17213 let req = IndicatorBatchRequest {
17214 indicator_id: "mama",
17215 output_id: Some("mama"),
17216 data: IndicatorDataRef::Slice { values: &data },
17217 combos: &combos,
17218 kernel: Kernel::Auto,
17219 };
17220 let out = compute_cpu_batch(req).unwrap();
17221 assert_eq!(out.rows, 1);
17222 assert_eq!(out.cols, data.len());
17223 assert!(out.values_f64.is_some());
17224 }
17225
17226 #[test]
17227 fn strict_mode_rejects_convenience_mfi_ohlcv() {
17228 let (open, high, low, close) = sample_ohlc();
17229 let volume: Vec<f64> = (0..close.len()).map(|i| 1200.0 + (i as f64)).collect();
17230 let combo = [ParamKV {
17231 key: "period",
17232 value: ParamValue::Int(14),
17233 }];
17234 let combos = [IndicatorParamSet { params: &combo }];
17235 let req = IndicatorBatchRequest {
17236 indicator_id: "mfi",
17237 output_id: Some("value"),
17238 data: IndicatorDataRef::Ohlcv {
17239 open: &open,
17240 high: &high,
17241 low: &low,
17242 close: &close,
17243 volume: &volume,
17244 },
17245 combos: &combos,
17246 kernel: Kernel::Auto,
17247 };
17248 let err = compute_cpu_batch_strict(req).unwrap_err();
17249 match err {
17250 IndicatorDispatchError::MissingRequiredInput { indicator, input } => {
17251 assert_eq!(indicator, "mfi");
17252 assert_eq!(input, IndicatorInputKind::CloseVolume);
17253 }
17254 other => panic!("expected MissingRequiredInput, got {other:?}"),
17255 }
17256 }
17257
17258 #[test]
17259 fn strict_mode_accepts_precomputed_mfi_close_volume() {
17260 let (_open, high, low, close) = sample_ohlc();
17261 let volume: Vec<f64> = (0..close.len())
17262 .map(|i| 1000.0 + (i as f64 * 2.0))
17263 .collect();
17264 let typical: Vec<f64> = high
17265 .iter()
17266 .zip(&low)
17267 .zip(&close)
17268 .map(|((h, l), c)| (h + l + c) / 3.0)
17269 .collect();
17270 let combo = [ParamKV {
17271 key: "period",
17272 value: ParamValue::Int(14),
17273 }];
17274 let combos = [IndicatorParamSet { params: &combo }];
17275 let req = IndicatorBatchRequest {
17276 indicator_id: "mfi",
17277 output_id: Some("value"),
17278 data: IndicatorDataRef::CloseVolume {
17279 close: &typical,
17280 volume: &volume,
17281 },
17282 combos: &combos,
17283 kernel: Kernel::Auto,
17284 };
17285 let strict = compute_cpu_batch_strict(req).unwrap();
17286 let input = MfiInput::from_slices(&typical, &volume, MfiParams { period: Some(14) });
17287 let direct = mfi_with_kernel(&input, Kernel::Auto.to_non_batch())
17288 .unwrap()
17289 .values;
17290 assert_series_eq(strict.values_f64.as_ref().unwrap(), &direct, 1e-12);
17291 }
17292
17293 #[test]
17294 fn strict_mode_rejects_ao_high_low_and_requires_slice() {
17295 let (_open, high, low, _close) = sample_ohlc();
17296 let combo = [
17297 ParamKV {
17298 key: "short_period",
17299 value: ParamValue::Int(5),
17300 },
17301 ParamKV {
17302 key: "long_period",
17303 value: ParamValue::Int(34),
17304 },
17305 ];
17306 let combos = [IndicatorParamSet { params: &combo }];
17307 let req = IndicatorBatchRequest {
17308 indicator_id: "ao",
17309 output_id: Some("value"),
17310 data: IndicatorDataRef::HighLow {
17311 high: &high,
17312 low: &low,
17313 },
17314 combos: &combos,
17315 kernel: Kernel::Auto,
17316 };
17317 let err = compute_cpu_batch_strict(req).unwrap_err();
17318 match err {
17319 IndicatorDispatchError::MissingRequiredInput { indicator, input } => {
17320 assert_eq!(indicator, "ao");
17321 assert_eq!(input, IndicatorInputKind::Slice);
17322 }
17323 other => panic!("expected MissingRequiredInput, got {other:?}"),
17324 }
17325 }
17326
17327 #[test]
17328 fn strict_mode_rejects_ttm_trend_ohlc_and_requires_candles() {
17329 let (open, high, low, close) = sample_ohlc();
17330 let combo = [ParamKV {
17331 key: "period",
17332 value: ParamValue::Int(5),
17333 }];
17334 let combos = [IndicatorParamSet { params: &combo }];
17335 let req = IndicatorBatchRequest {
17336 indicator_id: "ttm_trend",
17337 output_id: Some("value"),
17338 data: IndicatorDataRef::Ohlc {
17339 open: &open,
17340 high: &high,
17341 low: &low,
17342 close: &close,
17343 },
17344 combos: &combos,
17345 kernel: Kernel::Auto,
17346 };
17347 let err = compute_cpu_batch_strict(req).unwrap_err();
17348 match err {
17349 IndicatorDispatchError::MissingRequiredInput { indicator, input } => {
17350 assert_eq!(indicator, "ttm_trend");
17351 assert_eq!(input, IndicatorInputKind::Candles);
17352 }
17353 other => panic!("expected MissingRequiredInput, got {other:?}"),
17354 }
17355 }
17356
17357 #[test]
17358 fn strict_mode_accepts_ttm_trend_candles() {
17359 let candles = sample_candles();
17360 let combo = [ParamKV {
17361 key: "period",
17362 value: ParamValue::Int(5),
17363 }];
17364 let combos = [IndicatorParamSet { params: &combo }];
17365 let req = IndicatorBatchRequest {
17366 indicator_id: "ttm_trend",
17367 output_id: Some("value"),
17368 data: IndicatorDataRef::Candles {
17369 candles: &candles,
17370 source: Some("hl2"),
17371 },
17372 combos: &combos,
17373 kernel: Kernel::Auto,
17374 };
17375 let strict = compute_cpu_batch_strict(req).unwrap();
17376 let input = TtmTrendInput::from_slices(
17377 candles.hl2.as_slice(),
17378 candles.close.as_slice(),
17379 TtmTrendParams { period: Some(5) },
17380 );
17381 let direct = ttm_trend_with_kernel(&input, Kernel::Auto.to_non_batch())
17382 .unwrap()
17383 .values;
17384 let got = strict.values_bool.unwrap();
17385 assert_eq!(got, direct);
17386 }
17387
17388 #[test]
17389 fn rsi_cpu_batch_smoke() {
17390 let data = sample_series();
17391 let combo_1 = [ParamKV {
17392 key: "period",
17393 value: ParamValue::Int(7),
17394 }];
17395 let combo_2 = [ParamKV {
17396 key: "period",
17397 value: ParamValue::Int(14),
17398 }];
17399 let combos = [
17400 IndicatorParamSet { params: &combo_1 },
17401 IndicatorParamSet { params: &combo_2 },
17402 ];
17403 let req = IndicatorBatchRequest {
17404 indicator_id: "rsi",
17405 output_id: Some("value"),
17406 data: IndicatorDataRef::Slice { values: &data },
17407 combos: &combos,
17408 kernel: Kernel::Auto,
17409 };
17410 let out = compute_cpu_batch(req).unwrap();
17411 assert_eq!(out.output_id, "value");
17412 assert_eq!(out.rows, 2);
17413 assert_eq!(out.cols, data.len());
17414 assert_eq!(out.values_f64.as_ref().map(Vec::len), Some(2 * data.len()));
17415 }
17416
17417 #[test]
17418 fn ma_dispatch_regression_sma_matches_existing_ma_batch_api() {
17419 let data = sample_series();
17420 let combo = [ParamKV {
17421 key: "period",
17422 value: ParamValue::Int(14),
17423 }];
17424 let combos = [IndicatorParamSet { params: &combo }];
17425 let dispatch = compute_cpu_batch(IndicatorBatchRequest {
17426 indicator_id: "sma",
17427 output_id: Some("value"),
17428 data: IndicatorDataRef::Slice { values: &data },
17429 combos: &combos,
17430 kernel: Kernel::Auto,
17431 })
17432 .unwrap();
17433
17434 let direct = ma_batch_with_kernel_and_typed_params(
17435 "sma",
17436 MaData::Slice(&data),
17437 (14, 14, 0),
17438 Kernel::Auto,
17439 &[],
17440 )
17441 .unwrap();
17442 assert_eq!(dispatch.rows, direct.rows);
17443 assert_eq!(dispatch.cols, direct.cols);
17444 assert_series_eq(dispatch.values_f64.as_ref().unwrap(), &direct.values, 1e-12);
17445 }
17446
17447 #[test]
17448 fn ma_dispatch_sma_period_sweep_matches_direct_batch() {
17449 let data = sample_series();
17450 let combo_1 = [ParamKV {
17451 key: "period",
17452 value: ParamValue::Int(5),
17453 }];
17454 let combo_2 = [ParamKV {
17455 key: "period",
17456 value: ParamValue::Int(7),
17457 }];
17458 let combo_3 = [ParamKV {
17459 key: "period",
17460 value: ParamValue::Int(9),
17461 }];
17462 let combos = [
17463 IndicatorParamSet { params: &combo_1 },
17464 IndicatorParamSet { params: &combo_2 },
17465 IndicatorParamSet { params: &combo_3 },
17466 ];
17467 let dispatch = compute_cpu_batch(IndicatorBatchRequest {
17468 indicator_id: "sma",
17469 output_id: Some("value"),
17470 data: IndicatorDataRef::Slice { values: &data },
17471 combos: &combos,
17472 kernel: Kernel::Auto,
17473 })
17474 .unwrap();
17475
17476 let direct = ma_batch_with_kernel_and_typed_params(
17477 "sma",
17478 MaData::Slice(&data),
17479 (5, 9, 2),
17480 Kernel::Auto,
17481 &[],
17482 )
17483 .unwrap();
17484 assert_eq!(dispatch.rows, direct.rows);
17485 assert_eq!(dispatch.cols, direct.cols);
17486 assert_series_eq(dispatch.values_f64.as_ref().unwrap(), &direct.values, 1e-12);
17487 }
17488
17489 #[test]
17490 fn mfi_dispatch_period_sweep_matches_direct_batch() {
17491 let (_open, high, low, close) = sample_ohlc();
17492 let volume: Vec<f64> = (0..close.len())
17493 .map(|i| 1000.0 + (i as f64 * 2.0))
17494 .collect();
17495 let typical: Vec<f64> = high
17496 .iter()
17497 .zip(&low)
17498 .zip(&close)
17499 .map(|((h, l), c)| (h + l + c) / 3.0)
17500 .collect();
17501 let combo_1 = [ParamKV {
17502 key: "period",
17503 value: ParamValue::Int(5),
17504 }];
17505 let combo_2 = [ParamKV {
17506 key: "period",
17507 value: ParamValue::Int(7),
17508 }];
17509 let combo_3 = [ParamKV {
17510 key: "period",
17511 value: ParamValue::Int(9),
17512 }];
17513 let combos = [
17514 IndicatorParamSet { params: &combo_1 },
17515 IndicatorParamSet { params: &combo_2 },
17516 IndicatorParamSet { params: &combo_3 },
17517 ];
17518 let dispatch = compute_cpu_batch(IndicatorBatchRequest {
17519 indicator_id: "mfi",
17520 output_id: Some("value"),
17521 data: IndicatorDataRef::CloseVolume {
17522 close: &typical,
17523 volume: &volume,
17524 },
17525 combos: &combos,
17526 kernel: Kernel::Auto,
17527 })
17528 .unwrap();
17529 let direct = mfi_batch_with_kernel(
17530 &typical,
17531 &volume,
17532 &MfiBatchRange { period: (5, 9, 2) },
17533 Kernel::Auto,
17534 )
17535 .unwrap();
17536 assert_eq!(dispatch.rows, direct.rows);
17537 assert_eq!(dispatch.cols, direct.cols);
17538 assert_series_eq(dispatch.values_f64.as_ref().unwrap(), &direct.values, 1e-12);
17539 }
17540
17541 #[test]
17542 fn dx_dispatch_period_sweep_keeps_requested_row_order() {
17543 let (open, high, low, close) = sample_ohlc();
17544 let combo_1 = [ParamKV {
17545 key: "period",
17546 value: ParamValue::Int(9),
17547 }];
17548 let combo_2 = [ParamKV {
17549 key: "period",
17550 value: ParamValue::Int(7),
17551 }];
17552 let combo_3 = [ParamKV {
17553 key: "period",
17554 value: ParamValue::Int(5),
17555 }];
17556 let combos = [
17557 IndicatorParamSet { params: &combo_1 },
17558 IndicatorParamSet { params: &combo_2 },
17559 IndicatorParamSet { params: &combo_3 },
17560 ];
17561 let dispatch = compute_cpu_batch(IndicatorBatchRequest {
17562 indicator_id: "dx",
17563 output_id: Some("value"),
17564 data: IndicatorDataRef::Ohlc {
17565 open: &open,
17566 high: &high,
17567 low: &low,
17568 close: &close,
17569 },
17570 combos: &combos,
17571 kernel: Kernel::Auto,
17572 })
17573 .unwrap();
17574 let direct = dx_batch_with_kernel(
17575 &high,
17576 &low,
17577 &close,
17578 &DxBatchRange { period: (9, 5, 2) },
17579 Kernel::Auto,
17580 )
17581 .unwrap();
17582 let direct_periods: Vec<usize> = direct
17583 .combos
17584 .iter()
17585 .map(|combo| combo.period.unwrap_or(14))
17586 .collect();
17587 let period_to_row: std::collections::HashMap<usize, usize> = direct_periods
17588 .iter()
17589 .copied()
17590 .enumerate()
17591 .map(|(row, period)| (period, row))
17592 .collect();
17593 let requested = [9usize, 7usize, 5usize];
17594 let mut expected = Vec::with_capacity(requested.len() * direct.cols);
17595 for period in requested {
17596 let row = period_to_row[&period];
17597 let start = row * direct.cols;
17598 let end = start + direct.cols;
17599 expected.extend_from_slice(&direct.values[start..end]);
17600 }
17601 assert_eq!(dispatch.rows, requested.len());
17602 assert_eq!(dispatch.cols, direct.cols);
17603 assert_series_eq(dispatch.values_f64.as_ref().unwrap(), &expected, 1e-12);
17604 }
17605
17606 #[test]
17607 fn ma_dispatch_regression_alma_typed_params_match_existing_ma_batch_api() {
17608 let data = sample_series();
17609 let combo = [
17610 ParamKV {
17611 key: "period",
17612 value: ParamValue::Int(14),
17613 },
17614 ParamKV {
17615 key: "offset",
17616 value: ParamValue::Float(0.87),
17617 },
17618 ParamKV {
17619 key: "sigma",
17620 value: ParamValue::Float(5.5),
17621 },
17622 ];
17623 let combos = [IndicatorParamSet { params: &combo }];
17624 let dispatch = compute_cpu_batch(IndicatorBatchRequest {
17625 indicator_id: "alma",
17626 output_id: Some("value"),
17627 data: IndicatorDataRef::Slice { values: &data },
17628 combos: &combos,
17629 kernel: Kernel::Auto,
17630 })
17631 .unwrap();
17632
17633 let typed = [
17634 MaBatchParamKV {
17635 key: "offset",
17636 value: MaBatchParamValue::Float(0.87),
17637 },
17638 MaBatchParamKV {
17639 key: "sigma",
17640 value: MaBatchParamValue::Float(5.5),
17641 },
17642 ];
17643 let direct = ma_batch_with_kernel_and_typed_params(
17644 "alma",
17645 MaData::Slice(&data),
17646 (14, 14, 0),
17647 Kernel::Auto,
17648 &typed,
17649 )
17650 .unwrap();
17651 assert_eq!(dispatch.rows, direct.rows);
17652 assert_eq!(dispatch.cols, direct.cols);
17653 assert_series_eq(dispatch.values_f64.as_ref().unwrap(), &direct.values, 1e-12);
17654 }
17655
17656 #[test]
17657 fn macd_signal_output_matches_direct() {
17658 let data = sample_series();
17659 let combo_1 = [
17660 ParamKV {
17661 key: "fast_period",
17662 value: ParamValue::Int(8),
17663 },
17664 ParamKV {
17665 key: "slow_period",
17666 value: ParamValue::Int(21),
17667 },
17668 ParamKV {
17669 key: "signal_period",
17670 value: ParamValue::Int(5),
17671 },
17672 ];
17673 let combo_2 = [
17674 ParamKV {
17675 key: "fast_period",
17676 value: ParamValue::Int(12),
17677 },
17678 ParamKV {
17679 key: "slow_period",
17680 value: ParamValue::Int(26),
17681 },
17682 ParamKV {
17683 key: "signal_period",
17684 value: ParamValue::Int(9),
17685 },
17686 ];
17687 let combos = [
17688 IndicatorParamSet { params: &combo_1 },
17689 IndicatorParamSet { params: &combo_2 },
17690 ];
17691 let req = IndicatorBatchRequest {
17692 indicator_id: "macd",
17693 output_id: Some("signal"),
17694 data: IndicatorDataRef::Slice { values: &data },
17695 combos: &combos,
17696 kernel: Kernel::Auto,
17697 };
17698 let out = compute_cpu_batch(req).unwrap();
17699 let matrix = out.values_f64.unwrap();
17700 for (row, combo) in combos.iter().enumerate() {
17701 let fast = match combo.params[0].value {
17702 ParamValue::Int(v) => v as usize,
17703 _ => unreachable!(),
17704 };
17705 let slow = match combo.params[1].value {
17706 ParamValue::Int(v) => v as usize,
17707 _ => unreachable!(),
17708 };
17709 let signal = match combo.params[2].value {
17710 ParamValue::Int(v) => v as usize,
17711 _ => unreachable!(),
17712 };
17713 let input = MacdInput::from_slice(
17714 &data,
17715 MacdParams {
17716 fast_period: Some(fast),
17717 slow_period: Some(slow),
17718 signal_period: Some(signal),
17719 ma_type: Some("ema".to_string()),
17720 },
17721 );
17722 let direct = macd_with_kernel(&input, Kernel::Auto.to_non_batch())
17723 .unwrap()
17724 .signal;
17725 let start = row * out.cols;
17726 let end = start + out.cols;
17727 assert_series_eq(&matrix[start..end], direct.as_slice(), 1e-12);
17728 }
17729 }
17730
17731 #[test]
17732 fn adx_output_matches_direct() {
17733 let (open, high, low, close) = sample_ohlc();
17734 let combo = [ParamKV {
17735 key: "period",
17736 value: ParamValue::Int(14),
17737 }];
17738 let combos = [IndicatorParamSet { params: &combo }];
17739 let req = IndicatorBatchRequest {
17740 indicator_id: "adx",
17741 output_id: Some("value"),
17742 data: IndicatorDataRef::Ohlc {
17743 open: &open,
17744 high: &high,
17745 low: &low,
17746 close: &close,
17747 },
17748 combos: &combos,
17749 kernel: Kernel::Auto,
17750 };
17751 let out = compute_cpu_batch(req).unwrap();
17752 let matrix = out.values_f64.unwrap();
17753 let input = AdxInput::from_slices(&high, &low, &close, AdxParams { period: Some(14) });
17754 let direct = adx_with_kernel(&input, Kernel::Auto.to_non_batch())
17755 .unwrap()
17756 .values;
17757 assert_series_eq(&matrix, &direct, 1e-12);
17758 }
17759
17760 #[test]
17761 fn garman_klass_output_matches_direct() {
17762 let (open, high, low, close) = sample_ohlc();
17763 let combo = [ParamKV {
17764 key: "lookback",
17765 value: ParamValue::Int(17),
17766 }];
17767 let combos = [IndicatorParamSet { params: &combo }];
17768 let req = IndicatorBatchRequest {
17769 indicator_id: "garman_klass_volatility",
17770 output_id: Some("value"),
17771 data: IndicatorDataRef::Ohlc {
17772 open: &open,
17773 high: &high,
17774 low: &low,
17775 close: &close,
17776 },
17777 combos: &combos,
17778 kernel: Kernel::Auto,
17779 };
17780 let out = compute_cpu_batch(req).unwrap();
17781 let got = out.values_f64.unwrap();
17782 let input = GarmanKlassVolatilityInput::from_slices(
17783 &open,
17784 &high,
17785 &low,
17786 &close,
17787 GarmanKlassVolatilityParams { lookback: Some(17) },
17788 );
17789 let direct = garman_klass_volatility_with_kernel(&input, Kernel::Auto.to_non_batch())
17790 .unwrap()
17791 .values;
17792 assert_series_eq(&got, &direct, 1e-12);
17793 }
17794
17795 #[test]
17796 fn cmo_output_matches_direct() {
17797 let data = sample_series();
17798 let combo = [ParamKV {
17799 key: "period",
17800 value: ParamValue::Int(14),
17801 }];
17802 let combos = [IndicatorParamSet { params: &combo }];
17803 let req = IndicatorBatchRequest {
17804 indicator_id: "cmo",
17805 output_id: Some("value"),
17806 data: IndicatorDataRef::Slice { values: &data },
17807 combos: &combos,
17808 kernel: Kernel::Auto,
17809 };
17810 let out = compute_cpu_batch(req).unwrap();
17811 let input = CmoInput::from_slice(&data, CmoParams { period: Some(14) });
17812 let direct = cmo_with_kernel(&input, Kernel::Auto.to_non_batch())
17813 .unwrap()
17814 .values;
17815 let got = out.values_f64.unwrap();
17816 assert_series_eq(&got, &direct, 1e-12);
17817 }
17818
17819 #[test]
17820 fn ppo_output_matches_direct() {
17821 let data = sample_series();
17822 let combo = [
17823 ParamKV {
17824 key: "fast_period",
17825 value: ParamValue::Int(12),
17826 },
17827 ParamKV {
17828 key: "slow_period",
17829 value: ParamValue::Int(26),
17830 },
17831 ParamKV {
17832 key: "ma_type",
17833 value: ParamValue::EnumString("sma"),
17834 },
17835 ];
17836 let combos = [IndicatorParamSet { params: &combo }];
17837 let req = IndicatorBatchRequest {
17838 indicator_id: "ppo",
17839 output_id: Some("value"),
17840 data: IndicatorDataRef::Slice { values: &data },
17841 combos: &combos,
17842 kernel: Kernel::Auto,
17843 };
17844 let out = compute_cpu_batch(req).unwrap();
17845 let input = PpoInput::from_slice(
17846 &data,
17847 PpoParams {
17848 fast_period: Some(12),
17849 slow_period: Some(26),
17850 ma_type: Some("sma".to_string()),
17851 },
17852 );
17853 let direct = ppo_with_kernel(&input, Kernel::Auto.to_non_batch())
17854 .unwrap()
17855 .values;
17856 let got = out.values_f64.unwrap();
17857 assert_series_eq(&got, &direct, 1e-12);
17858 }
17859
17860 #[test]
17861 fn apo_output_matches_direct() {
17862 let data = sample_series();
17863 let combo = [
17864 ParamKV {
17865 key: "short_period",
17866 value: ParamValue::Int(10),
17867 },
17868 ParamKV {
17869 key: "long_period",
17870 value: ParamValue::Int(20),
17871 },
17872 ];
17873 let combos = [IndicatorParamSet { params: &combo }];
17874 let req = IndicatorBatchRequest {
17875 indicator_id: "apo",
17876 output_id: Some("value"),
17877 data: IndicatorDataRef::Slice { values: &data },
17878 combos: &combos,
17879 kernel: Kernel::Auto,
17880 };
17881 let out = compute_cpu_batch(req).unwrap();
17882 let input = ApoInput::from_slice(
17883 &data,
17884 ApoParams {
17885 short_period: Some(10),
17886 long_period: Some(20),
17887 },
17888 );
17889 let direct = apo_with_kernel(&input, Kernel::Auto.to_non_batch())
17890 .unwrap()
17891 .values;
17892 let got = out.values_f64.unwrap();
17893 assert_series_eq(&got, &direct, 1e-12);
17894 }
17895
17896 #[test]
17897 fn natr_output_matches_direct() {
17898 let (open, high, low, close) = sample_ohlc();
17899 let combo = [ParamKV {
17900 key: "period",
17901 value: ParamValue::Int(14),
17902 }];
17903 let combos = [IndicatorParamSet { params: &combo }];
17904 let req = IndicatorBatchRequest {
17905 indicator_id: "natr",
17906 output_id: Some("value"),
17907 data: IndicatorDataRef::Ohlc {
17908 open: &open,
17909 high: &high,
17910 low: &low,
17911 close: &close,
17912 },
17913 combos: &combos,
17914 kernel: Kernel::Auto,
17915 };
17916 let out = compute_cpu_batch(req).unwrap();
17917 let input = NatrInput::from_slices(&high, &low, &close, NatrParams { period: Some(14) });
17918 let direct = natr_with_kernel(&input, Kernel::Auto.to_non_batch())
17919 .unwrap()
17920 .values;
17921 let got = out.values_f64.unwrap();
17922 assert_series_eq(&got, &direct, 1e-12);
17923 }
17924
17925 #[test]
17926 fn ad_output_matches_direct() {
17927 let (open, high, low, close) = sample_ohlc();
17928 let volume: Vec<f64> = (0..close.len())
17929 .map(|i| 1000.0 + (i as f64 * 3.0))
17930 .collect();
17931 let combos = [IndicatorParamSet { params: &[] }];
17932 let req = IndicatorBatchRequest {
17933 indicator_id: "ad",
17934 output_id: Some("value"),
17935 data: IndicatorDataRef::Ohlcv {
17936 open: &open,
17937 high: &high,
17938 low: &low,
17939 close: &close,
17940 volume: &volume,
17941 },
17942 combos: &combos,
17943 kernel: Kernel::Auto,
17944 };
17945 let out = compute_cpu_batch(req).unwrap();
17946 let input = AdInput::from_slices(&high, &low, &close, &volume, AdParams::default());
17947 let direct = ad_with_kernel(&input, Kernel::Auto.to_non_batch())
17948 .unwrap()
17949 .values;
17950 let got = out.values_f64.unwrap();
17951 assert_series_eq(&got, &direct, 1e-12);
17952 }
17953
17954 #[test]
17955 fn ao_output_matches_direct() {
17956 let (open, high, low, close) = sample_ohlc();
17957 let combo = [
17958 ParamKV {
17959 key: "short_period",
17960 value: ParamValue::Int(5),
17961 },
17962 ParamKV {
17963 key: "long_period",
17964 value: ParamValue::Int(34),
17965 },
17966 ];
17967 let combos = [IndicatorParamSet { params: &combo }];
17968 let req = IndicatorBatchRequest {
17969 indicator_id: "ao",
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 source: Vec<f64> = high.iter().zip(&low).map(|(h, l)| 0.5 * (h + l)).collect();
17982 let input = AoInput::from_slice(
17983 &source,
17984 AoParams {
17985 short_period: Some(5),
17986 long_period: Some(34),
17987 },
17988 );
17989 let direct = ao_with_kernel(&input, Kernel::Auto.to_non_batch())
17990 .unwrap()
17991 .values;
17992 let got = out.values_f64.unwrap();
17993 assert_series_eq(&got, &direct, 1e-12);
17994 }
17995
17996 #[test]
17997 fn pvi_output_matches_direct() {
17998 let data = sample_series();
17999 let volume: Vec<f64> = (0..data.len()).map(|i| 900.0 + (i as f64 * 5.0)).collect();
18000 let combo = [ParamKV {
18001 key: "initial_value",
18002 value: ParamValue::Float(1000.0),
18003 }];
18004 let combos = [IndicatorParamSet { params: &combo }];
18005 let req = IndicatorBatchRequest {
18006 indicator_id: "pvi",
18007 output_id: Some("value"),
18008 data: IndicatorDataRef::CloseVolume {
18009 close: &data,
18010 volume: &volume,
18011 },
18012 combos: &combos,
18013 kernel: Kernel::Auto,
18014 };
18015 let out = compute_cpu_batch(req).unwrap();
18016 let input = PviInput::from_slices(
18017 &data,
18018 &volume,
18019 PviParams {
18020 initial_value: Some(1000.0),
18021 },
18022 );
18023 let direct = pvi_with_kernel(&input, Kernel::Auto.to_non_batch())
18024 .unwrap()
18025 .values;
18026 let got = out.values_f64.unwrap();
18027 assert_series_eq(&got, &direct, 1e-12);
18028 }
18029
18030 #[test]
18031 fn efi_output_matches_direct() {
18032 let data = sample_series();
18033 let volume: Vec<f64> = (0..data.len()).map(|i| 1000.0 + (i as f64 * 4.0)).collect();
18034 let combo = [ParamKV {
18035 key: "period",
18036 value: ParamValue::Int(13),
18037 }];
18038 let combos = [IndicatorParamSet { params: &combo }];
18039 let req = IndicatorBatchRequest {
18040 indicator_id: "efi",
18041 output_id: Some("value"),
18042 data: IndicatorDataRef::CloseVolume {
18043 close: &data,
18044 volume: &volume,
18045 },
18046 combos: &combos,
18047 kernel: Kernel::Auto,
18048 };
18049 let out = compute_cpu_batch(req).unwrap();
18050 let input = EfiInput::from_slices(&data, &volume, EfiParams { period: Some(13) });
18051 let direct = efi_with_kernel(&input, Kernel::Auto.to_non_batch())
18052 .unwrap()
18053 .values;
18054 let got = out.values_f64.unwrap();
18055 assert_series_eq(&got, &direct, 1e-12);
18056 }
18057
18058 #[test]
18059 fn mfi_output_matches_direct() {
18060 let (open, high, low, close) = sample_ohlc();
18061 let volume: Vec<f64> = (0..close.len()).map(|i| 900.0 + (i as f64 * 6.0)).collect();
18062 let combo = [ParamKV {
18063 key: "period",
18064 value: ParamValue::Int(14),
18065 }];
18066 let combos = [IndicatorParamSet { params: &combo }];
18067 let req = IndicatorBatchRequest {
18068 indicator_id: "mfi",
18069 output_id: Some("value"),
18070 data: IndicatorDataRef::Ohlcv {
18071 open: &open,
18072 high: &high,
18073 low: &low,
18074 close: &close,
18075 volume: &volume,
18076 },
18077 combos: &combos,
18078 kernel: Kernel::Auto,
18079 };
18080 let out = compute_cpu_batch(req).unwrap();
18081 let typical_price: Vec<f64> = high
18082 .iter()
18083 .zip(&low)
18084 .zip(&close)
18085 .map(|((h, l), c)| (h + l + c) / 3.0)
18086 .collect();
18087 let input = MfiInput::from_slices(&typical_price, &volume, MfiParams { period: Some(14) });
18088 let direct = mfi_with_kernel(&input, Kernel::Auto.to_non_batch())
18089 .unwrap()
18090 .values;
18091 let got = out.values_f64.unwrap();
18092 assert_series_eq(&got, &direct, 1e-12);
18093 }
18094
18095 #[test]
18096 fn mfi_non_sweep_fallback_rows_match_direct() {
18097 let (open, high, low, close) = sample_ohlc();
18098 let volume: Vec<f64> = (0..close.len()).map(|i| 950.0 + (i as f64 * 5.0)).collect();
18099 let combo_1 = [ParamKV {
18100 key: "period",
18101 value: ParamValue::Int(5),
18102 }];
18103 let combo_2 = [ParamKV {
18104 key: "period",
18105 value: ParamValue::Int(9),
18106 }];
18107 let combo_3 = [ParamKV {
18108 key: "period",
18109 value: ParamValue::Int(8),
18110 }];
18111 let combos = [
18112 IndicatorParamSet { params: &combo_1 },
18113 IndicatorParamSet { params: &combo_2 },
18114 IndicatorParamSet { params: &combo_3 },
18115 ];
18116 let req = IndicatorBatchRequest {
18117 indicator_id: "mfi",
18118 output_id: Some("value"),
18119 data: IndicatorDataRef::Ohlcv {
18120 open: &open,
18121 high: &high,
18122 low: &low,
18123 close: &close,
18124 volume: &volume,
18125 },
18126 combos: &combos,
18127 kernel: Kernel::Auto,
18128 };
18129 let out = compute_cpu_batch(req).unwrap();
18130 let matrix = out.values_f64.unwrap();
18131 let typical_price: Vec<f64> = high
18132 .iter()
18133 .zip(&low)
18134 .zip(&close)
18135 .map(|((h, l), c)| (h + l + c) / 3.0)
18136 .collect();
18137 for (row, period) in [5usize, 9usize, 8usize].iter().enumerate() {
18138 let input = MfiInput::from_slices(
18139 &typical_price,
18140 &volume,
18141 MfiParams {
18142 period: Some(*period),
18143 },
18144 );
18145 let direct = mfi_with_kernel(&input, Kernel::Auto.to_non_batch())
18146 .unwrap()
18147 .values;
18148 let start = row * close.len();
18149 let end = start + close.len();
18150 assert_series_eq(&matrix[start..end], &direct, 1e-12);
18151 }
18152 }
18153
18154 #[test]
18155 fn kvo_output_matches_direct() {
18156 let (open, high, low, close) = sample_ohlc();
18157 let volume: Vec<f64> = (0..close.len())
18158 .map(|i| 1200.0 + (i as f64 * 5.0))
18159 .collect();
18160 let combo = [
18161 ParamKV {
18162 key: "short_period",
18163 value: ParamValue::Int(2),
18164 },
18165 ParamKV {
18166 key: "long_period",
18167 value: ParamValue::Int(5),
18168 },
18169 ];
18170 let combos = [IndicatorParamSet { params: &combo }];
18171 let req = IndicatorBatchRequest {
18172 indicator_id: "kvo",
18173 output_id: Some("value"),
18174 data: IndicatorDataRef::Ohlcv {
18175 open: &open,
18176 high: &high,
18177 low: &low,
18178 close: &close,
18179 volume: &volume,
18180 },
18181 combos: &combos,
18182 kernel: Kernel::Auto,
18183 };
18184 let out = compute_cpu_batch(req).unwrap();
18185 let input = KvoInput::from_slices(
18186 &high,
18187 &low,
18188 &close,
18189 &volume,
18190 KvoParams {
18191 short_period: Some(2),
18192 long_period: Some(5),
18193 },
18194 );
18195 let direct = kvo_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 dx_output_matches_direct() {
18204 let (open, high, low, close) = sample_ohlc();
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: "dx",
18212 output_id: Some("value"),
18213 data: IndicatorDataRef::Ohlc {
18214 open: &open,
18215 high: &high,
18216 low: &low,
18217 close: &close,
18218 },
18219 combos: &combos,
18220 kernel: Kernel::Auto,
18221 };
18222 let out = compute_cpu_batch(req).unwrap();
18223 let input = DxInput::from_hlc_slices(&high, &low, &close, DxParams { period: Some(14) });
18224 let direct = dx_with_kernel(&input, Kernel::Auto.to_non_batch())
18225 .unwrap()
18226 .values;
18227 let got = out.values_f64.unwrap();
18228 assert_series_eq(&got, &direct, 1e-12);
18229 }
18230
18231 #[test]
18232 fn dx_non_sweep_fallback_rows_match_direct() {
18233 let (open, high, low, close) = sample_ohlc();
18234 let combo_1 = [ParamKV {
18235 key: "period",
18236 value: ParamValue::Int(9),
18237 }];
18238 let combo_2 = [ParamKV {
18239 key: "period",
18240 value: ParamValue::Int(5),
18241 }];
18242 let combo_3 = [ParamKV {
18243 key: "period",
18244 value: ParamValue::Int(8),
18245 }];
18246 let combos = [
18247 IndicatorParamSet { params: &combo_1 },
18248 IndicatorParamSet { params: &combo_2 },
18249 IndicatorParamSet { params: &combo_3 },
18250 ];
18251 let req = IndicatorBatchRequest {
18252 indicator_id: "dx",
18253 output_id: Some("value"),
18254 data: IndicatorDataRef::Ohlc {
18255 open: &open,
18256 high: &high,
18257 low: &low,
18258 close: &close,
18259 },
18260 combos: &combos,
18261 kernel: Kernel::Auto,
18262 };
18263 let out = compute_cpu_batch(req).unwrap();
18264 let matrix = out.values_f64.unwrap();
18265 for (row, period) in [9usize, 5usize, 8usize].iter().enumerate() {
18266 let input = DxInput::from_hlc_slices(
18267 &high,
18268 &low,
18269 &close,
18270 DxParams {
18271 period: Some(*period),
18272 },
18273 );
18274 let direct = dx_with_kernel(&input, Kernel::Auto.to_non_batch())
18275 .unwrap()
18276 .values;
18277 let start = row * close.len();
18278 let end = start + close.len();
18279 assert_series_eq(&matrix[start..end], &direct, 1e-12);
18280 }
18281 }
18282
18283 #[test]
18284 fn trix_dispatch_period_sweep_keeps_requested_row_order() {
18285 let data = sample_series();
18286 let combo_1 = [ParamKV {
18287 key: "period",
18288 value: ParamValue::Int(9),
18289 }];
18290 let combo_2 = [ParamKV {
18291 key: "period",
18292 value: ParamValue::Int(7),
18293 }];
18294 let combo_3 = [ParamKV {
18295 key: "period",
18296 value: ParamValue::Int(5),
18297 }];
18298 let combos = [
18299 IndicatorParamSet { params: &combo_1 },
18300 IndicatorParamSet { params: &combo_2 },
18301 IndicatorParamSet { params: &combo_3 },
18302 ];
18303 let dispatch = compute_cpu_batch(IndicatorBatchRequest {
18304 indicator_id: "trix",
18305 output_id: Some("value"),
18306 data: IndicatorDataRef::Slice { values: &data },
18307 combos: &combos,
18308 kernel: Kernel::Auto,
18309 })
18310 .unwrap();
18311
18312 let direct =
18313 trix_batch_with_kernel(&data, &TrixBatchRange { period: (9, 5, 2) }, Kernel::Auto)
18314 .unwrap();
18315 let direct_periods: Vec<usize> = direct
18316 .combos
18317 .iter()
18318 .map(|combo| combo.period.unwrap_or(18))
18319 .collect();
18320 let period_to_row: std::collections::HashMap<usize, usize> = direct_periods
18321 .iter()
18322 .copied()
18323 .enumerate()
18324 .map(|(row, period)| (period, row))
18325 .collect();
18326 let requested = [9usize, 7usize, 5usize];
18327 let mut expected = Vec::with_capacity(requested.len() * direct.cols);
18328 for period in requested {
18329 let row = period_to_row[&period];
18330 let start = row * direct.cols;
18331 let end = start + direct.cols;
18332 expected.extend_from_slice(&direct.values[start..end]);
18333 }
18334 assert_eq!(dispatch.rows, requested.len());
18335 assert_eq!(dispatch.cols, direct.cols);
18336 assert_series_eq(dispatch.values_f64.as_ref().unwrap(), &expected, 1e-12);
18337 }
18338
18339 #[test]
18340 fn trix_non_sweep_fallback_rows_match_direct() {
18341 let data = sample_series();
18342 let combo_1 = [ParamKV {
18343 key: "period",
18344 value: ParamValue::Int(9),
18345 }];
18346 let combo_2 = [ParamKV {
18347 key: "period",
18348 value: ParamValue::Int(5),
18349 }];
18350 let combo_3 = [ParamKV {
18351 key: "period",
18352 value: ParamValue::Int(8),
18353 }];
18354 let combos = [
18355 IndicatorParamSet { params: &combo_1 },
18356 IndicatorParamSet { params: &combo_2 },
18357 IndicatorParamSet { params: &combo_3 },
18358 ];
18359 let out = compute_cpu_batch(IndicatorBatchRequest {
18360 indicator_id: "trix",
18361 output_id: Some("value"),
18362 data: IndicatorDataRef::Slice { values: &data },
18363 combos: &combos,
18364 kernel: Kernel::Auto,
18365 })
18366 .unwrap();
18367 let matrix = out.values_f64.unwrap();
18368 for (row, period) in [9usize, 5usize, 8usize].iter().enumerate() {
18369 let input = TrixInput::from_slice(
18370 &data,
18371 TrixParams {
18372 period: Some(*period),
18373 },
18374 );
18375 let direct = trix_with_kernel(&input, Kernel::Auto.to_non_batch())
18376 .unwrap()
18377 .values;
18378 let start = row * data.len();
18379 let end = start + data.len();
18380 assert_series_eq(&matrix[start..end], &direct, 1e-12);
18381 }
18382 }
18383
18384 #[test]
18385 fn ift_rsi_output_matches_direct() {
18386 let data = sample_series();
18387 let combo = [
18388 ParamKV {
18389 key: "rsi_period",
18390 value: ParamValue::Int(6),
18391 },
18392 ParamKV {
18393 key: "wma_period",
18394 value: ParamValue::Int(10),
18395 },
18396 ];
18397 let combos = [IndicatorParamSet { params: &combo }];
18398 let req = IndicatorBatchRequest {
18399 indicator_id: "ift_rsi",
18400 output_id: Some("value"),
18401 data: IndicatorDataRef::Slice { values: &data },
18402 combos: &combos,
18403 kernel: Kernel::Auto,
18404 };
18405 let out = compute_cpu_batch(req).unwrap();
18406 let input = IftRsiInput::from_slice(
18407 &data,
18408 IftRsiParams {
18409 rsi_period: Some(6),
18410 wma_period: Some(10),
18411 },
18412 );
18413 let direct = ift_rsi_with_kernel(&input, Kernel::Auto.to_non_batch())
18414 .unwrap()
18415 .values;
18416 let got = out.values_f64.unwrap();
18417 assert_series_eq(&got, &direct, 1e-12);
18418 }
18419
18420 #[test]
18421 fn fosc_output_matches_direct() {
18422 let data = sample_series();
18423 let combo = [ParamKV {
18424 key: "period",
18425 value: ParamValue::Int(8),
18426 }];
18427 let combos = [IndicatorParamSet { params: &combo }];
18428 let req = IndicatorBatchRequest {
18429 indicator_id: "fosc",
18430 output_id: Some("value"),
18431 data: IndicatorDataRef::Slice { values: &data },
18432 combos: &combos,
18433 kernel: Kernel::Auto,
18434 };
18435 let out = compute_cpu_batch(req).unwrap();
18436 let input = FoscInput::from_slice(&data, FoscParams { period: Some(8) });
18437 let direct = fosc_with_kernel(&input, Kernel::Auto.to_non_batch())
18438 .unwrap()
18439 .values;
18440 let got = out.values_f64.unwrap();
18441 assert_series_eq(&got, &direct, 1e-12);
18442 }
18443
18444 #[test]
18445 fn linearreg_angle_output_matches_direct() {
18446 let data = sample_series();
18447 let combo = [ParamKV {
18448 key: "period",
18449 value: ParamValue::Int(14),
18450 }];
18451 let combos = [IndicatorParamSet { params: &combo }];
18452 let req = IndicatorBatchRequest {
18453 indicator_id: "linearreg_angle",
18454 output_id: Some("value"),
18455 data: IndicatorDataRef::Slice { values: &data },
18456 combos: &combos,
18457 kernel: Kernel::Auto,
18458 };
18459 let out = compute_cpu_batch(req).unwrap();
18460 let input =
18461 Linearreg_angleInput::from_slice(&data, Linearreg_angleParams { period: Some(14) });
18462 let direct = linearreg_angle_with_kernel(&input, Kernel::Auto.to_non_batch())
18463 .unwrap()
18464 .values;
18465 let got = out.values_f64.unwrap();
18466 assert_series_eq(&got, &direct, 1e-12);
18467 }
18468
18469 #[test]
18470 fn linearreg_intercept_output_matches_direct() {
18471 let data = sample_series();
18472 let combo = [ParamKV {
18473 key: "period",
18474 value: ParamValue::Int(14),
18475 }];
18476 let combos = [IndicatorParamSet { params: &combo }];
18477 let req = IndicatorBatchRequest {
18478 indicator_id: "linearreg_intercept",
18479 output_id: Some("value"),
18480 data: IndicatorDataRef::Slice { values: &data },
18481 combos: &combos,
18482 kernel: Kernel::Auto,
18483 };
18484 let out = compute_cpu_batch(req).unwrap();
18485 let input = LinearRegInterceptInput::from_slice(
18486 &data,
18487 LinearRegInterceptParams { period: Some(14) },
18488 );
18489 let direct = linearreg_intercept_with_kernel(&input, Kernel::Auto.to_non_batch())
18490 .unwrap()
18491 .values;
18492 let got = out.values_f64.unwrap();
18493 assert_series_eq(&got, &direct, 1e-12);
18494 }
18495
18496 #[test]
18497 fn cg_output_matches_direct() {
18498 let data = sample_series();
18499 let combo = [ParamKV {
18500 key: "period",
18501 value: ParamValue::Int(10),
18502 }];
18503 let combos = [IndicatorParamSet { params: &combo }];
18504 let req = IndicatorBatchRequest {
18505 indicator_id: "cg",
18506 output_id: Some("value"),
18507 data: IndicatorDataRef::Slice { values: &data },
18508 combos: &combos,
18509 kernel: Kernel::Auto,
18510 };
18511 let out = compute_cpu_batch(req).unwrap();
18512 let input = CgInput::from_slice(&data, CgParams { period: Some(10) });
18513 let direct = cg_with_kernel(&input, Kernel::Auto.to_non_batch())
18514 .unwrap()
18515 .values;
18516 let got = out.values_f64.unwrap();
18517 assert_series_eq(&got, &direct, 1e-12);
18518 }
18519
18520 #[test]
18521 fn linearreg_slope_output_matches_direct() {
18522 let data = sample_series();
18523 let combo = [ParamKV {
18524 key: "period",
18525 value: ParamValue::Int(14),
18526 }];
18527 let combos = [IndicatorParamSet { params: &combo }];
18528 let req = IndicatorBatchRequest {
18529 indicator_id: "linearreg_slope",
18530 output_id: Some("value"),
18531 data: IndicatorDataRef::Slice { values: &data },
18532 combos: &combos,
18533 kernel: Kernel::Auto,
18534 };
18535 let out = compute_cpu_batch(req).unwrap();
18536 let input =
18537 LinearRegSlopeInput::from_slice(&data, LinearRegSlopeParams { period: Some(14) });
18538 let direct = linearreg_slope_with_kernel(&input, Kernel::Auto.to_non_batch())
18539 .unwrap()
18540 .values;
18541 let got = out.values_f64.unwrap();
18542 assert_series_eq(&got, &direct, 1e-12);
18543 }
18544
18545 #[test]
18546 fn mean_ad_output_matches_direct() {
18547 let data = sample_series();
18548 let combo = [ParamKV {
18549 key: "period",
18550 value: ParamValue::Int(7),
18551 }];
18552 let combos = [IndicatorParamSet { params: &combo }];
18553 let req = IndicatorBatchRequest {
18554 indicator_id: "mean_ad",
18555 output_id: Some("value"),
18556 data: IndicatorDataRef::Slice { values: &data },
18557 combos: &combos,
18558 kernel: Kernel::Auto,
18559 };
18560 let out = compute_cpu_batch(req).unwrap();
18561 let input = MeanAdInput::from_slice(&data, MeanAdParams { period: Some(7) });
18562 let direct = mean_ad_with_kernel(&input, Kernel::Auto.to_non_batch())
18563 .unwrap()
18564 .values;
18565 let got = out.values_f64.unwrap();
18566 assert_series_eq(&got, &direct, 1e-12);
18567 }
18568
18569 #[test]
18570 fn deviation_output_matches_direct() {
18571 let data = sample_series();
18572 let combo = [
18573 ParamKV {
18574 key: "period",
18575 value: ParamValue::Int(9),
18576 },
18577 ParamKV {
18578 key: "devtype",
18579 value: ParamValue::Int(2),
18580 },
18581 ];
18582 let combos = [IndicatorParamSet { params: &combo }];
18583 let req = IndicatorBatchRequest {
18584 indicator_id: "deviation",
18585 output_id: Some("value"),
18586 data: IndicatorDataRef::Slice { values: &data },
18587 combos: &combos,
18588 kernel: Kernel::Auto,
18589 };
18590 let out = compute_cpu_batch(req).unwrap();
18591 let input = DeviationInput::from_slice(
18592 &data,
18593 DeviationParams {
18594 period: Some(9),
18595 devtype: Some(2),
18596 },
18597 );
18598 let direct = deviation_with_kernel(&input, Kernel::Auto.to_non_batch())
18599 .unwrap()
18600 .values;
18601 let got = out.values_f64.unwrap();
18602 assert_series_eq(&got, &direct, 1e-12);
18603 }
18604
18605 #[test]
18606 fn medprice_output_matches_direct() {
18607 let (_open, high, low, _close) = sample_ohlc();
18608 let combos = [IndicatorParamSet { params: &[] }];
18609 let req = IndicatorBatchRequest {
18610 indicator_id: "medprice",
18611 output_id: Some("value"),
18612 data: IndicatorDataRef::HighLow {
18613 high: &high,
18614 low: &low,
18615 },
18616 combos: &combos,
18617 kernel: Kernel::Auto,
18618 };
18619 let out = compute_cpu_batch(req).unwrap();
18620 let input = MedpriceInput::from_slices(&high, &low, MedpriceParams::default());
18621 let direct = medprice_with_kernel(&input, Kernel::Auto.to_non_batch())
18622 .unwrap()
18623 .values;
18624 let got = out.values_f64.unwrap();
18625 assert_series_eq(&got, &direct, 1e-12);
18626 }
18627
18628 #[test]
18629 fn percentile_nearest_rank_output_matches_direct() {
18630 let data = sample_series();
18631 let combo = [
18632 ParamKV {
18633 key: "length",
18634 value: ParamValue::Int(12),
18635 },
18636 ParamKV {
18637 key: "percentage",
18638 value: ParamValue::Float(70.0),
18639 },
18640 ];
18641 let combos = [IndicatorParamSet { params: &combo }];
18642 let req = IndicatorBatchRequest {
18643 indicator_id: "percentile_nearest_rank",
18644 output_id: Some("value"),
18645 data: IndicatorDataRef::Slice { values: &data },
18646 combos: &combos,
18647 kernel: Kernel::Auto,
18648 };
18649 let out = compute_cpu_batch(req).unwrap();
18650 let input = PercentileNearestRankInput::from_slice(
18651 &data,
18652 PercentileNearestRankParams {
18653 length: Some(12),
18654 percentage: Some(70.0),
18655 },
18656 );
18657 let direct = percentile_nearest_rank_with_kernel(&input, Kernel::Auto.to_non_batch())
18658 .unwrap()
18659 .values;
18660 let got = out.values_f64.unwrap();
18661 assert_series_eq(&got, &direct, 1e-12);
18662 }
18663
18664 #[test]
18665 fn zscore_output_matches_direct() {
18666 let data = sample_series();
18667 let combo = [
18668 ParamKV {
18669 key: "period",
18670 value: ParamValue::Int(14),
18671 },
18672 ParamKV {
18673 key: "ma_type",
18674 value: ParamValue::EnumString("ema"),
18675 },
18676 ParamKV {
18677 key: "nbdev",
18678 value: ParamValue::Float(1.25),
18679 },
18680 ParamKV {
18681 key: "devtype",
18682 value: ParamValue::Int(1),
18683 },
18684 ];
18685 let combos = [IndicatorParamSet { params: &combo }];
18686 let req = IndicatorBatchRequest {
18687 indicator_id: "zscore",
18688 output_id: Some("value"),
18689 data: IndicatorDataRef::Slice { values: &data },
18690 combos: &combos,
18691 kernel: Kernel::Auto,
18692 };
18693 let out = compute_cpu_batch(req).unwrap();
18694 let input = ZscoreInput::from_slice(
18695 &data,
18696 ZscoreParams {
18697 period: Some(14),
18698 ma_type: Some("ema".to_string()),
18699 nbdev: Some(1.25),
18700 devtype: Some(1),
18701 },
18702 );
18703 let direct = zscore_with_kernel(&input, Kernel::Auto.to_non_batch())
18704 .unwrap()
18705 .values;
18706 let got = out.values_f64.unwrap();
18707 assert_series_eq(&got, &direct, 1e-12);
18708 }
18709
18710 #[test]
18711 fn vpci_secondary_output_matches_direct() {
18712 let close = sample_series();
18713 let volume: Vec<f64> = (0..close.len())
18714 .map(|i| 1000.0 + (i as f64 * 7.0))
18715 .collect();
18716 let combo = [
18717 ParamKV {
18718 key: "short_range",
18719 value: ParamValue::Int(5),
18720 },
18721 ParamKV {
18722 key: "long_range",
18723 value: ParamValue::Int(25),
18724 },
18725 ];
18726 let combos = [IndicatorParamSet { params: &combo }];
18727 let req = IndicatorBatchRequest {
18728 indicator_id: "vpci",
18729 output_id: Some("vpcis"),
18730 data: IndicatorDataRef::CloseVolume {
18731 close: &close,
18732 volume: &volume,
18733 },
18734 combos: &combos,
18735 kernel: Kernel::Auto,
18736 };
18737 let out = compute_cpu_batch(req).unwrap();
18738 let input = VpciInput::from_slices(
18739 &close,
18740 &volume,
18741 VpciParams {
18742 short_range: Some(5),
18743 long_range: Some(25),
18744 },
18745 );
18746 let direct = vpci_with_kernel(&input, Kernel::Auto.to_non_batch())
18747 .unwrap()
18748 .vpcis;
18749 let got = out.values_f64.unwrap();
18750 assert_series_eq(&got, &direct, 1e-12);
18751 }
18752
18753 #[test]
18754 fn yang_zhang_secondary_output_matches_direct() {
18755 let (open, high, low, close) = sample_ohlc();
18756 let combo = [
18757 ParamKV {
18758 key: "lookback",
18759 value: ParamValue::Int(21),
18760 },
18761 ParamKV {
18762 key: "k_override",
18763 value: ParamValue::Bool(true),
18764 },
18765 ParamKV {
18766 key: "k",
18767 value: ParamValue::Float(0.28),
18768 },
18769 ];
18770 let combos = [IndicatorParamSet { params: &combo }];
18771 let req = IndicatorBatchRequest {
18772 indicator_id: "yang_zhang_volatility",
18773 output_id: Some("rs"),
18774 data: IndicatorDataRef::Ohlc {
18775 open: &open,
18776 high: &high,
18777 low: &low,
18778 close: &close,
18779 },
18780 combos: &combos,
18781 kernel: Kernel::Auto,
18782 };
18783 let out = compute_cpu_batch(req).unwrap();
18784 let input = YangZhangVolatilityInput::from_slices(
18785 &open,
18786 &high,
18787 &low,
18788 &close,
18789 YangZhangVolatilityParams {
18790 lookback: Some(21),
18791 k_override: Some(true),
18792 k: Some(0.28),
18793 },
18794 );
18795 let direct = yang_zhang_volatility_with_kernel(&input, Kernel::Auto.to_non_batch())
18796 .unwrap()
18797 .rs;
18798 let got = out.values_f64.unwrap();
18799 assert_series_eq(&got, &direct, 1e-12);
18800 }
18801
18802 #[test]
18803 fn historical_volatility_percentile_signal_output_matches_direct() {
18804 let data = sample_series();
18805 let combo = [
18806 ParamKV {
18807 key: "length",
18808 value: ParamValue::Int(5),
18809 },
18810 ParamKV {
18811 key: "annual_length",
18812 value: ParamValue::Int(10),
18813 },
18814 ];
18815 let combos = [IndicatorParamSet { params: &combo }];
18816 let req = IndicatorBatchRequest {
18817 indicator_id: "historical_volatility_percentile",
18818 output_id: Some("hvp_sma"),
18819 data: IndicatorDataRef::Slice { values: &data },
18820 combos: &combos,
18821 kernel: Kernel::Auto,
18822 };
18823 let out = compute_cpu_batch(req).unwrap();
18824 let input = HistoricalVolatilityPercentileInput::from_slice(
18825 &data,
18826 HistoricalVolatilityPercentileParams {
18827 length: Some(5),
18828 annual_length: Some(10),
18829 },
18830 );
18831 let direct =
18832 historical_volatility_percentile_with_kernel(&input, Kernel::Auto.to_non_batch())
18833 .unwrap()
18834 .hvp_sma;
18835 let got = out.values_f64.unwrap();
18836 assert_series_eq(&got, &direct, 1e-12);
18837 }
18838
18839 #[test]
18840 fn volatility_ratio_adaptive_rsx_signal_output_matches_direct() {
18841 let data = sample_series();
18842 let combo = [
18843 ParamKV {
18844 key: "period",
18845 value: ParamValue::Int(6),
18846 },
18847 ParamKV {
18848 key: "speed",
18849 value: ParamValue::Float(0.5),
18850 },
18851 ];
18852 let combos = [IndicatorParamSet { params: &combo }];
18853 let req = IndicatorBatchRequest {
18854 indicator_id: "volatility_ratio_adaptive_rsx",
18855 output_id: Some("signal"),
18856 data: IndicatorDataRef::Slice { values: &data },
18857 combos: &combos,
18858 kernel: Kernel::Auto,
18859 };
18860 let out = compute_cpu_batch(req).unwrap();
18861 let input = VolatilityRatioAdaptiveRsxInput::from_slice(
18862 &data,
18863 VolatilityRatioAdaptiveRsxParams {
18864 period: Some(6),
18865 speed: Some(0.5),
18866 },
18867 );
18868 let direct = volatility_ratio_adaptive_rsx_with_kernel(&input, Kernel::Auto.to_non_batch())
18869 .unwrap()
18870 .signal;
18871 let got = out.values_f64.unwrap();
18872 assert_series_eq(&got, &direct, 1e-12);
18873 }
18874
18875 #[test]
18876 fn on_balance_volume_oscillator_signal_output_matches_direct() {
18877 let close = sample_series();
18878 let volume: Vec<f64> = (0..close.len()).map(|i| 1000.0 + i as f64 * 3.0).collect();
18879 let combo = [
18880 ParamKV {
18881 key: "obv_length",
18882 value: ParamValue::Int(20),
18883 },
18884 ParamKV {
18885 key: "ema_length",
18886 value: ParamValue::Int(9),
18887 },
18888 ];
18889 let combos = [IndicatorParamSet { params: &combo }];
18890 let req = IndicatorBatchRequest {
18891 indicator_id: "on_balance_volume_oscillator",
18892 output_id: Some("signal"),
18893 data: IndicatorDataRef::CloseVolume {
18894 close: &close,
18895 volume: &volume,
18896 },
18897 combos: &combos,
18898 kernel: Kernel::Auto,
18899 };
18900 let out = compute_cpu_batch(req).unwrap();
18901 let input = OnBalanceVolumeOscillatorInput::from_slices(
18902 &close,
18903 &volume,
18904 OnBalanceVolumeOscillatorParams {
18905 obv_length: Some(20),
18906 ema_length: Some(9),
18907 },
18908 );
18909 let direct = on_balance_volume_oscillator_with_kernel(&input, Kernel::Auto.to_non_batch())
18910 .unwrap()
18911 .signal;
18912 let got = out.values_f64.unwrap();
18913 assert_series_eq(&got, &direct, 1e-12);
18914 }
18915
18916 #[test]
18917 fn twiggs_money_flow_smoothed_output_matches_direct() {
18918 let open = vec![10.0, 10.2, 10.4, 10.7, 10.9, 11.1, 11.3, 11.5, 11.7, 11.9];
18919 let high = vec![10.4, 10.7, 10.9, 11.1, 11.4, 11.6, 11.8, 12.0, 12.2, 12.4];
18920 let low = vec![9.8, 10.0, 10.2, 10.5, 10.7, 10.9, 11.1, 11.3, 11.5, 11.7];
18921 let close = vec![10.1, 10.5, 10.7, 10.9, 11.2, 11.4, 11.6, 11.8, 12.0, 12.2];
18922 let volume = vec![
18923 1000.0, 1015.0, 1030.0, 1045.0, 1060.0, 1075.0, 1090.0, 1105.0, 1120.0, 1135.0,
18924 ];
18925 let combo = [
18926 ParamKV {
18927 key: "length",
18928 value: ParamValue::Int(5),
18929 },
18930 ParamKV {
18931 key: "smoothing_length",
18932 value: ParamValue::Int(4),
18933 },
18934 ParamKV {
18935 key: "ma_type",
18936 value: ParamValue::EnumString("WMA"),
18937 },
18938 ];
18939 let combos = [IndicatorParamSet { params: &combo }];
18940 let req = IndicatorBatchRequest {
18941 indicator_id: "twiggs_money_flow",
18942 output_id: Some("smoothed"),
18943 data: IndicatorDataRef::Ohlcv {
18944 open: &open,
18945 high: &high,
18946 low: &low,
18947 close: &close,
18948 volume: &volume,
18949 },
18950 combos: &combos,
18951 kernel: Kernel::Auto,
18952 };
18953 let out = compute_cpu_batch(req).unwrap();
18954 let input = TwiggsMoneyFlowInput::from_slices(
18955 &high,
18956 &low,
18957 &close,
18958 &volume,
18959 TwiggsMoneyFlowParams {
18960 length: Some(5),
18961 smoothing_length: Some(4),
18962 ma_type: Some("WMA".to_string()),
18963 },
18964 );
18965 let direct = twiggs_money_flow_with_kernel(&input, Kernel::Auto.to_non_batch())
18966 .unwrap()
18967 .smoothed;
18968 let got = out.values_f64.unwrap();
18969 assert_series_eq(&got, &direct, 1e-12);
18970 }
18971
18972 #[test]
18973 fn parkinson_variance_output_matches_direct() {
18974 let (_open, high, low, _close) = sample_ohlc();
18975 let combo = [ParamKV {
18976 key: "period",
18977 value: ParamValue::Int(9),
18978 }];
18979 let combos = [IndicatorParamSet { params: &combo }];
18980 let req = IndicatorBatchRequest {
18981 indicator_id: "parkinson_volatility",
18982 output_id: Some("variance"),
18983 data: IndicatorDataRef::HighLow {
18984 high: &high,
18985 low: &low,
18986 },
18987 combos: &combos,
18988 kernel: Kernel::Auto,
18989 };
18990 let out = compute_cpu_batch(req).unwrap();
18991 let input = ParkinsonVolatilityInput::from_slices(
18992 &high,
18993 &low,
18994 ParkinsonVolatilityParams { period: Some(9) },
18995 );
18996 let direct = parkinson_volatility_with_kernel(&input, Kernel::Auto.to_non_batch())
18997 .unwrap()
18998 .variance;
18999 let got = out.values_f64.unwrap();
19000 assert_series_eq(&got, &direct, 1e-12);
19001 }
19002
19003 #[test]
19004 fn l2_ehlers_signal_to_noise_output_matches_direct() {
19005 let candles = sample_candles();
19006 let combo = [
19007 ParamKV {
19008 key: "source",
19009 value: ParamValue::EnumString("hl2"),
19010 },
19011 ParamKV {
19012 key: "smooth_period",
19013 value: ParamValue::Int(10),
19014 },
19015 ];
19016 let combos = [IndicatorParamSet { params: &combo }];
19017 let req = IndicatorBatchRequest {
19018 indicator_id: "l2_ehlers_signal_to_noise",
19019 output_id: Some("value"),
19020 data: IndicatorDataRef::Candles {
19021 candles: &candles,
19022 source: Some("hl2"),
19023 },
19024 combos: &combos,
19025 kernel: Kernel::Auto,
19026 };
19027 let out = compute_cpu_batch(req).unwrap();
19028 let input = L2EhlersSignalToNoiseInput::from_slices(
19029 crate::utilities::data_loader::source_type(&candles, "hl2"),
19030 candles.high.as_slice(),
19031 candles.low.as_slice(),
19032 L2EhlersSignalToNoiseParams {
19033 smooth_period: Some(10),
19034 },
19035 );
19036 let direct = l2_ehlers_signal_to_noise_with_kernel(&input, Kernel::Auto.to_non_batch())
19037 .unwrap()
19038 .values;
19039 let got = out.values_f64.unwrap();
19040 assert_series_eq(&got, &direct, 1e-12);
19041 }
19042
19043 #[test]
19044 fn cycle_channel_oscillator_output_matches_direct() {
19045 let candles = sample_candles();
19046 let combo = [
19047 ParamKV {
19048 key: "source",
19049 value: ParamValue::EnumString("close"),
19050 },
19051 ParamKV {
19052 key: "short_cycle_length",
19053 value: ParamValue::Int(10),
19054 },
19055 ParamKV {
19056 key: "medium_cycle_length",
19057 value: ParamValue::Int(30),
19058 },
19059 ParamKV {
19060 key: "short_multiplier",
19061 value: ParamValue::Float(1.0),
19062 },
19063 ParamKV {
19064 key: "medium_multiplier",
19065 value: ParamValue::Float(3.0),
19066 },
19067 ];
19068 let combos = [IndicatorParamSet { params: &combo }];
19069 let req = IndicatorBatchRequest {
19070 indicator_id: "cycle_channel_oscillator",
19071 output_id: Some("fast"),
19072 data: IndicatorDataRef::Candles {
19073 candles: &candles,
19074 source: Some("close"),
19075 },
19076 combos: &combos,
19077 kernel: Kernel::Auto,
19078 };
19079 let out = compute_cpu_batch(req).unwrap();
19080 let input = CycleChannelOscillatorInput::from_slices(
19081 crate::utilities::data_loader::source_type(&candles, "close"),
19082 candles.high.as_slice(),
19083 candles.low.as_slice(),
19084 candles.close.as_slice(),
19085 CycleChannelOscillatorParams {
19086 short_cycle_length: Some(10),
19087 medium_cycle_length: Some(30),
19088 short_multiplier: Some(1.0),
19089 medium_multiplier: Some(3.0),
19090 },
19091 );
19092 let direct = cycle_channel_oscillator_with_kernel(&input, Kernel::Auto.to_non_batch())
19093 .unwrap()
19094 .fast;
19095 let got = out.values_f64.unwrap();
19096 assert_series_eq(&got, &direct, 1e-12);
19097 }
19098
19099 #[test]
19100 fn andean_oscillator_output_matches_direct() {
19101 let candles = sample_candles();
19102 let combo = [
19103 ParamKV {
19104 key: "length",
19105 value: ParamValue::Int(50),
19106 },
19107 ParamKV {
19108 key: "signal_length",
19109 value: ParamValue::Int(9),
19110 },
19111 ];
19112 let combos = [IndicatorParamSet { params: &combo }];
19113 let req = IndicatorBatchRequest {
19114 indicator_id: "andean_oscillator",
19115 output_id: Some("bull"),
19116 data: IndicatorDataRef::Candles {
19117 candles: &candles,
19118 source: None,
19119 },
19120 combos: &combos,
19121 kernel: Kernel::Auto,
19122 };
19123 let out = compute_cpu_batch(req).unwrap();
19124 let input = AndeanOscillatorInput::from_slices(
19125 candles.open.as_slice(),
19126 candles.close.as_slice(),
19127 AndeanOscillatorParams {
19128 length: Some(50),
19129 signal_length: Some(9),
19130 },
19131 );
19132 let direct = andean_oscillator_with_kernel(&input, Kernel::Auto.to_non_batch())
19133 .unwrap()
19134 .bull;
19135 let got = out.values_f64.unwrap();
19136 assert_series_eq(&got, &direct, 1e-12);
19137 }
19138
19139 #[test]
19140 fn daily_factor_output_matches_direct() {
19141 let (open, high, low, close) = sample_ohlc();
19142 let combo = [ParamKV {
19143 key: "threshold_level",
19144 value: ParamValue::Float(0.35),
19145 }];
19146 let combos = [IndicatorParamSet { params: &combo }];
19147 let req = IndicatorBatchRequest {
19148 indicator_id: "daily_factor",
19149 output_id: Some("signal"),
19150 data: IndicatorDataRef::Ohlc {
19151 open: &open,
19152 high: &high,
19153 low: &low,
19154 close: &close,
19155 },
19156 combos: &combos,
19157 kernel: Kernel::Auto,
19158 };
19159 let out = compute_cpu_batch(req).unwrap();
19160 let input = DailyFactorInput::from_slices(
19161 &open,
19162 &high,
19163 &low,
19164 &close,
19165 DailyFactorParams {
19166 threshold_level: Some(0.35),
19167 },
19168 );
19169 let direct = daily_factor_with_kernel(&input, Kernel::Auto.to_non_batch())
19170 .unwrap()
19171 .signal;
19172 let got = out.values_f64.unwrap();
19173 assert_series_eq(&got, &direct, 1e-12);
19174 }
19175
19176 #[test]
19177 fn ehlers_adaptive_cyber_cycle_output_matches_direct() {
19178 let candles = sample_candles();
19179 let combo = [
19180 ParamKV {
19181 key: "source",
19182 value: ParamValue::EnumString("hl2"),
19183 },
19184 ParamKV {
19185 key: "alpha",
19186 value: ParamValue::Float(0.07),
19187 },
19188 ];
19189 let combos = [IndicatorParamSet { params: &combo }];
19190 let req = IndicatorBatchRequest {
19191 indicator_id: "ehlers_adaptive_cyber_cycle",
19192 output_id: Some("cycle"),
19193 data: IndicatorDataRef::Candles {
19194 candles: &candles,
19195 source: Some("hl2"),
19196 },
19197 combos: &combos,
19198 kernel: Kernel::Auto,
19199 };
19200 let out = compute_cpu_batch(req).unwrap();
19201 let input = EhlersAdaptiveCyberCycleInput::from_slice(
19202 crate::utilities::data_loader::source_type(&candles, "hl2"),
19203 EhlersAdaptiveCyberCycleParams { alpha: Some(0.07) },
19204 );
19205 let direct = ehlers_adaptive_cyber_cycle_with_kernel(&input, Kernel::Auto.to_non_batch())
19206 .unwrap()
19207 .cycle;
19208 let got = out.values_f64.unwrap();
19209 assert_series_eq(&got, &direct, 1e-12);
19210 }
19211
19212 #[test]
19213 fn ehlers_simple_cycle_indicator_output_matches_direct() {
19214 let candles = sample_candles();
19215 let combo = [
19216 ParamKV {
19217 key: "source",
19218 value: ParamValue::EnumString("hl2"),
19219 },
19220 ParamKV {
19221 key: "alpha",
19222 value: ParamValue::Float(0.07),
19223 },
19224 ];
19225 let combos = [IndicatorParamSet { params: &combo }];
19226 let req = IndicatorBatchRequest {
19227 indicator_id: "ehlers_simple_cycle_indicator",
19228 output_id: Some("cycle"),
19229 data: IndicatorDataRef::Candles {
19230 candles: &candles,
19231 source: Some("hl2"),
19232 },
19233 combos: &combos,
19234 kernel: Kernel::Auto,
19235 };
19236 let out = compute_cpu_batch(req).unwrap();
19237 let input = EhlersSimpleCycleIndicatorInput::from_slice(
19238 crate::utilities::data_loader::source_type(&candles, "hl2"),
19239 EhlersSimpleCycleIndicatorParams { alpha: Some(0.07) },
19240 );
19241 let direct = ehlers_simple_cycle_indicator_with_kernel(&input, Kernel::Auto.to_non_batch())
19242 .unwrap()
19243 .cycle;
19244 let got = out.values_f64.unwrap();
19245 assert_series_eq(&got, &direct, 1e-12);
19246 }
19247
19248 #[test]
19249 fn l1_ehlers_phasor_output_matches_direct() {
19250 let candles = sample_candles();
19251 let combo = [ParamKV {
19252 key: "domestic_cycle_length",
19253 value: ParamValue::Int(15),
19254 }];
19255 let combos = [IndicatorParamSet { params: &combo }];
19256 let req = IndicatorBatchRequest {
19257 indicator_id: "l1_ehlers_phasor",
19258 output_id: Some("value"),
19259 data: IndicatorDataRef::Candles {
19260 candles: &candles,
19261 source: Some("close"),
19262 },
19263 combos: &combos,
19264 kernel: Kernel::Auto,
19265 };
19266 let out = compute_cpu_batch(req).unwrap();
19267 let input = L1EhlersPhasorInput::from_slice(
19268 candles.close.as_slice(),
19269 L1EhlersPhasorParams {
19270 domestic_cycle_length: Some(15),
19271 },
19272 );
19273 let direct = l1_ehlers_phasor_with_kernel(&input, Kernel::Auto.to_non_batch())
19274 .unwrap()
19275 .values;
19276 let got = out.values_f64.unwrap();
19277 assert_series_eq(&got, &direct, 1e-12);
19278 }
19279
19280 #[test]
19281 fn ehlers_smoothed_adaptive_momentum_output_matches_direct() {
19282 let candles = sample_candles();
19283 let combo = [
19284 ParamKV {
19285 key: "source",
19286 value: ParamValue::EnumString("hl2"),
19287 },
19288 ParamKV {
19289 key: "alpha",
19290 value: ParamValue::Float(0.07),
19291 },
19292 ParamKV {
19293 key: "cutoff",
19294 value: ParamValue::Float(8.0),
19295 },
19296 ];
19297 let combos = [IndicatorParamSet { params: &combo }];
19298 let req = IndicatorBatchRequest {
19299 indicator_id: "ehlers_smoothed_adaptive_momentum",
19300 output_id: Some("value"),
19301 data: IndicatorDataRef::Candles {
19302 candles: &candles,
19303 source: Some("hl2"),
19304 },
19305 combos: &combos,
19306 kernel: Kernel::Auto,
19307 };
19308 let out = compute_cpu_batch(req).unwrap();
19309 let input = EhlersSmoothedAdaptiveMomentumInput::from_slice(
19310 crate::utilities::data_loader::source_type(&candles, "hl2"),
19311 EhlersSmoothedAdaptiveMomentumParams {
19312 alpha: Some(0.07),
19313 cutoff: Some(8.0),
19314 },
19315 );
19316 let direct =
19317 ehlers_smoothed_adaptive_momentum_with_kernel(&input, Kernel::Auto.to_non_batch())
19318 .unwrap()
19319 .values;
19320 let got = out.values_f64.unwrap();
19321 assert_series_eq(&got, &direct, 1e-12);
19322 }
19323
19324 #[test]
19325 fn ewma_volatility_output_matches_direct() {
19326 let close = sample_series();
19327 let combo = [ParamKV {
19328 key: "lambda",
19329 value: ParamValue::Float(0.94),
19330 }];
19331 let combos = [IndicatorParamSet { params: &combo }];
19332 let req = IndicatorBatchRequest {
19333 indicator_id: "ewma_volatility",
19334 output_id: Some("value"),
19335 data: IndicatorDataRef::Slice { values: &close },
19336 combos: &combos,
19337 kernel: Kernel::Auto,
19338 };
19339 let out = compute_cpu_batch(req).unwrap();
19340 let input =
19341 EwmaVolatilityInput::from_slice(&close, EwmaVolatilityParams { lambda: Some(0.94) });
19342 let direct = ewma_volatility_with_kernel(&input, Kernel::Auto.to_non_batch())
19343 .unwrap()
19344 .values;
19345 let got = out.values_f64.unwrap();
19346 assert_series_eq(&got, &direct, 1e-12);
19347 }
19348
19349 #[test]
19350 fn random_walk_index_output_matches_direct() {
19351 let open = sample_series();
19352 let high: Vec<f64> = open.iter().map(|v| v + 1.0).collect();
19353 let low: Vec<f64> = open.iter().map(|v| v - 1.0).collect();
19354 let close: Vec<f64> = open
19355 .iter()
19356 .enumerate()
19357 .map(|(i, v)| v + 0.1 * (i as f64 + 1.0))
19358 .collect();
19359 let combo = [ParamKV {
19360 key: "length",
19361 value: ParamValue::Int(14),
19362 }];
19363 let combos = [IndicatorParamSet { params: &combo }];
19364 let req = IndicatorBatchRequest {
19365 indicator_id: "random_walk_index",
19366 output_id: Some("high"),
19367 data: IndicatorDataRef::Ohlc {
19368 open: &open,
19369 high: &high,
19370 low: &low,
19371 close: &close,
19372 },
19373 combos: &combos,
19374 kernel: Kernel::Auto,
19375 };
19376 let out = compute_cpu_batch(req).unwrap();
19377 let input = RandomWalkIndexInput::from_slices(
19378 &high,
19379 &low,
19380 &close,
19381 RandomWalkIndexParams { length: Some(14) },
19382 );
19383 let direct = random_walk_index_with_kernel(&input, Kernel::Auto.to_non_batch())
19384 .unwrap()
19385 .high;
19386 let got = out.values_f64.unwrap();
19387 assert_series_eq(&got, &direct, 1e-12);
19388 }
19389
19390 #[test]
19391 fn price_moving_average_ratio_percentile_output_matches_direct() {
19392 let open = sample_series();
19393 let high: Vec<f64> = open
19394 .iter()
19395 .enumerate()
19396 .map(|(i, v)| v + 1.0 + (i as f64 * 0.03).sin() * 0.15)
19397 .collect();
19398 let low: Vec<f64> = open
19399 .iter()
19400 .enumerate()
19401 .map(|(i, v)| v - 1.0 - (i as f64 * 0.05).cos() * 0.12)
19402 .collect();
19403 let close: Vec<f64> = open
19404 .iter()
19405 .enumerate()
19406 .map(|(i, v)| v + 0.12 * (i as f64 + 1.0))
19407 .collect();
19408 let volume: Vec<f64> = (0..open.len())
19409 .map(|i| 1_000.0 + i as f64 * 2.0 + (i as f64 * 0.09).sin() * 40.0)
19410 .collect();
19411 let combo = [
19412 ParamKV {
19413 key: "source",
19414 value: ParamValue::EnumString("close"),
19415 },
19416 ParamKV {
19417 key: "ma_length",
19418 value: ParamValue::Int(20),
19419 },
19420 ParamKV {
19421 key: "ma_type",
19422 value: ParamValue::EnumString("vwma"),
19423 },
19424 ParamKV {
19425 key: "pmarp_lookback",
19426 value: ParamValue::Int(30),
19427 },
19428 ParamKV {
19429 key: "signal_ma_length",
19430 value: ParamValue::Int(10),
19431 },
19432 ParamKV {
19433 key: "signal_ma_type",
19434 value: ParamValue::EnumString("sma"),
19435 },
19436 ParamKV {
19437 key: "line_mode",
19438 value: ParamValue::EnumString("pmarp"),
19439 },
19440 ];
19441 let combos = [IndicatorParamSet { params: &combo }];
19442 let req = IndicatorBatchRequest {
19443 indicator_id: "price_moving_average_ratio_percentile",
19444 output_id: Some("plotline"),
19445 data: IndicatorDataRef::Candles {
19446 candles: &crate::utilities::data_loader::Candles {
19447 timestamp: vec![0; open.len()],
19448 open: open.clone(),
19449 high: high.clone(),
19450 low: low.clone(),
19451 close: close.clone(),
19452 volume: volume.clone(),
19453 fields: crate::utilities::data_loader::CandleFieldFlags {
19454 open: true,
19455 high: true,
19456 low: true,
19457 close: true,
19458 volume: true,
19459 },
19460 hl2: high
19461 .iter()
19462 .zip(low.iter())
19463 .map(|(h, l)| (h + l) * 0.5)
19464 .collect(),
19465 hlc3: high
19466 .iter()
19467 .zip(low.iter())
19468 .zip(close.iter())
19469 .map(|((h, l), c)| (h + l + c) / 3.0)
19470 .collect(),
19471 ohlc4: open
19472 .iter()
19473 .zip(high.iter())
19474 .zip(low.iter())
19475 .zip(close.iter())
19476 .map(|(((o, h), l), c)| (o + h + l + c) * 0.25)
19477 .collect(),
19478 hlcc4: high
19479 .iter()
19480 .zip(low.iter())
19481 .zip(close.iter())
19482 .map(|((h, l), c)| (h + l + c + c) * 0.25)
19483 .collect(),
19484 },
19485 source: Some("close"),
19486 },
19487 combos: &combos,
19488 kernel: Kernel::Auto,
19489 };
19490 let out = compute_cpu_batch(req).unwrap();
19491 let input = PriceMovingAverageRatioPercentileInput::from_slices(
19492 &close,
19493 &volume,
19494 PriceMovingAverageRatioPercentileParams {
19495 ma_length: Some(20),
19496 ma_type: Some(PriceMovingAverageRatioPercentileMaType::Vwma),
19497 pmarp_lookback: Some(30),
19498 signal_ma_length: Some(10),
19499 signal_ma_type: Some(PriceMovingAverageRatioPercentileMaType::Sma),
19500 line_mode: Some(PriceMovingAverageRatioPercentileLineMode::Pmarp),
19501 },
19502 );
19503 let direct =
19504 price_moving_average_ratio_percentile_with_kernel(&input, Kernel::Auto.to_non_batch())
19505 .unwrap()
19506 .plotline;
19507 let got = out.values_f64.unwrap();
19508 assert_series_eq(&got, &direct, 1e-12);
19509 }
19510
19511 #[test]
19512 fn trend_trigger_factor_output_matches_direct() {
19513 let base = sample_series();
19514 let high: Vec<f64> = base
19515 .iter()
19516 .enumerate()
19517 .map(|(i, v)| v + 1.0 + (i as f64 * 0.03).sin() * 0.15)
19518 .collect();
19519 let low: Vec<f64> = base
19520 .iter()
19521 .enumerate()
19522 .map(|(i, v)| v - 1.0 - (i as f64 * 0.05).cos() * 0.12)
19523 .collect();
19524 let combo = [ParamKV {
19525 key: "length",
19526 value: ParamValue::Int(15),
19527 }];
19528 let combos = [IndicatorParamSet { params: &combo }];
19529 let req = IndicatorBatchRequest {
19530 indicator_id: "trend_trigger_factor",
19531 output_id: Some("value"),
19532 data: IndicatorDataRef::HighLow {
19533 high: &high,
19534 low: &low,
19535 },
19536 combos: &combos,
19537 kernel: Kernel::Auto,
19538 };
19539 let out = compute_cpu_batch(req).unwrap();
19540 let input = TrendTriggerFactorInput::from_slices(
19541 &high,
19542 &low,
19543 TrendTriggerFactorParams { length: Some(15) },
19544 );
19545 let direct = trend_trigger_factor_with_kernel(&input, Kernel::Auto.to_non_batch())
19546 .unwrap()
19547 .values;
19548 let got = out.values_f64.unwrap();
19549 assert_series_eq(&got, &direct, 1e-12);
19550 }
19551
19552 #[test]
19553 fn mesa_stochastic_multi_length_output_matches_direct() {
19554 let source: Vec<f64> = (0..180)
19555 .map(|i| 100.0 + (i as f64 * 0.09).sin() * 2.0 + i as f64 * 0.015)
19556 .collect();
19557 let high: Vec<f64> = source.iter().map(|v| v + 1.0).collect();
19558 let low: Vec<f64> = source.iter().map(|v| v - 1.0).collect();
19559 let open = source.clone();
19560 let volume: Vec<f64> = (0..180).map(|i| 1000.0 + i as f64).collect();
19561 let combo = [
19562 ParamKV {
19563 key: "source",
19564 value: ParamValue::EnumString("close"),
19565 },
19566 ParamKV {
19567 key: "length_1",
19568 value: ParamValue::Int(48),
19569 },
19570 ParamKV {
19571 key: "length_2",
19572 value: ParamValue::Int(21),
19573 },
19574 ParamKV {
19575 key: "length_3",
19576 value: ParamValue::Int(9),
19577 },
19578 ParamKV {
19579 key: "length_4",
19580 value: ParamValue::Int(6),
19581 },
19582 ParamKV {
19583 key: "trigger_length",
19584 value: ParamValue::Int(2),
19585 },
19586 ];
19587 let combos = [IndicatorParamSet { params: &combo }];
19588 let req = IndicatorBatchRequest {
19589 indicator_id: "mesa_stochastic_multi_length",
19590 output_id: Some("mesa_1"),
19591 data: IndicatorDataRef::Candles {
19592 candles: &crate::utilities::data_loader::Candles {
19593 timestamp: vec![0; source.len()],
19594 open: open.clone(),
19595 high: high.clone(),
19596 low: low.clone(),
19597 close: source.clone(),
19598 volume,
19599 fields: crate::utilities::data_loader::CandleFieldFlags {
19600 open: true,
19601 high: true,
19602 low: true,
19603 close: true,
19604 volume: true,
19605 },
19606 hl2: high
19607 .iter()
19608 .zip(low.iter())
19609 .map(|(h, l)| (h + l) * 0.5)
19610 .collect(),
19611 hlc3: high
19612 .iter()
19613 .zip(low.iter())
19614 .zip(source.iter())
19615 .map(|((h, l), c)| (h + l + c) / 3.0)
19616 .collect(),
19617 ohlc4: open
19618 .iter()
19619 .zip(high.iter())
19620 .zip(low.iter())
19621 .zip(source.iter())
19622 .map(|(((o, h), l), c)| (o + h + l + c) * 0.25)
19623 .collect(),
19624 hlcc4: high
19625 .iter()
19626 .zip(low.iter())
19627 .zip(source.iter())
19628 .map(|((h, l), c)| (h + l + c + c) * 0.25)
19629 .collect(),
19630 },
19631 source: Some("close"),
19632 },
19633 combos: &combos,
19634 kernel: Kernel::Auto,
19635 };
19636 let out = compute_cpu_batch(req).unwrap();
19637 let input = MesaStochasticMultiLengthInput::from_slices(
19638 &source,
19639 MesaStochasticMultiLengthParams::default(),
19640 );
19641 let direct = mesa_stochastic_multi_length_with_kernel(&input, Kernel::Auto.to_non_batch())
19642 .unwrap()
19643 .mesa_1;
19644 let got = out.values_f64.unwrap();
19645 assert_series_eq(&got, &direct, 1e-12);
19646 }
19647
19648 #[test]
19649 fn spearman_correlation_output_matches_direct() {
19650 let close: Vec<f64> = (0..180)
19651 .map(|i| 100.0 + (i as f64 * 0.13).sin() * 2.0 + i as f64 * 0.02)
19652 .collect();
19653 let open: Vec<f64> = (0..180)
19654 .map(|i| 98.0 + (i as f64 * 0.07).cos() * 1.6 + i as f64 * 0.015)
19655 .collect();
19656 let high: Vec<f64> = close.iter().map(|v| v + 1.0).collect();
19657 let low: Vec<f64> = close.iter().map(|v| v - 1.0).collect();
19658 let volume: Vec<f64> = (0..180).map(|i| 1000.0 + i as f64).collect();
19659 let combo = [
19660 ParamKV {
19661 key: "source",
19662 value: ParamValue::EnumString("close"),
19663 },
19664 ParamKV {
19665 key: "comparison_source",
19666 value: ParamValue::EnumString("open"),
19667 },
19668 ParamKV {
19669 key: "lookback",
19670 value: ParamValue::Int(30),
19671 },
19672 ParamKV {
19673 key: "smoothing_length",
19674 value: ParamValue::Int(3),
19675 },
19676 ];
19677 let combos = [IndicatorParamSet { params: &combo }];
19678 let req = IndicatorBatchRequest {
19679 indicator_id: "spearman_correlation",
19680 output_id: Some("smoothed"),
19681 data: IndicatorDataRef::Candles {
19682 candles: &crate::utilities::data_loader::Candles {
19683 timestamp: vec![0; close.len()],
19684 open: open.clone(),
19685 high: high.clone(),
19686 low: low.clone(),
19687 close: close.clone(),
19688 volume,
19689 fields: crate::utilities::data_loader::CandleFieldFlags {
19690 open: true,
19691 high: true,
19692 low: true,
19693 close: true,
19694 volume: true,
19695 },
19696 hl2: high
19697 .iter()
19698 .zip(low.iter())
19699 .map(|(h, l)| (h + l) * 0.5)
19700 .collect(),
19701 hlc3: high
19702 .iter()
19703 .zip(low.iter())
19704 .zip(close.iter())
19705 .map(|((h, l), c)| (h + l + c) / 3.0)
19706 .collect(),
19707 ohlc4: open
19708 .iter()
19709 .zip(high.iter())
19710 .zip(low.iter())
19711 .zip(close.iter())
19712 .map(|(((o, h), l), c)| (o + h + l + c) * 0.25)
19713 .collect(),
19714 hlcc4: high
19715 .iter()
19716 .zip(low.iter())
19717 .zip(close.iter())
19718 .map(|((h, l), c)| (h + l + c + c) * 0.25)
19719 .collect(),
19720 },
19721 source: Some("close"),
19722 },
19723 combos: &combos,
19724 kernel: Kernel::Auto,
19725 };
19726 let out = compute_cpu_batch(req).unwrap();
19727 let input = SpearmanCorrelationInput::from_slices(
19728 &close,
19729 &open,
19730 SpearmanCorrelationParams {
19731 lookback: Some(30),
19732 smoothing_length: Some(3),
19733 },
19734 );
19735 let direct = spearman_correlation_with_kernel(&input, Kernel::Auto.to_non_batch())
19736 .unwrap()
19737 .smoothed;
19738 let got = out.values_f64.unwrap();
19739 assert_series_eq(&got, &direct, 1e-12);
19740 }
19741
19742 #[test]
19743 fn relative_strength_index_wave_indicator_output_matches_direct() {
19744 let open = sample_series();
19745 let close: Vec<f64> = open
19746 .iter()
19747 .enumerate()
19748 .map(|(i, v)| v + 0.2 * (i as f64 * 0.1).sin())
19749 .collect();
19750 let high: Vec<f64> = close.iter().map(|v| v + 0.9).collect();
19751 let low: Vec<f64> = close.iter().map(|v| v - 0.8).collect();
19752 let volume: Vec<f64> = (0..close.len()).map(|i| 1_000.0 + i as f64).collect();
19753 let candles = crate::utilities::data_loader::Candles {
19754 timestamp: vec![0; close.len()],
19755 open: open.clone(),
19756 high: high.clone(),
19757 low: low.clone(),
19758 close: close.clone(),
19759 volume,
19760 fields: crate::utilities::data_loader::CandleFieldFlags {
19761 open: true,
19762 high: true,
19763 low: true,
19764 close: true,
19765 volume: true,
19766 },
19767 hl2: high
19768 .iter()
19769 .zip(low.iter())
19770 .map(|(h, l)| (h + l) * 0.5)
19771 .collect(),
19772 hlc3: high
19773 .iter()
19774 .zip(low.iter())
19775 .zip(close.iter())
19776 .map(|((h, l), c)| (h + l + c) / 3.0)
19777 .collect(),
19778 ohlc4: open
19779 .iter()
19780 .zip(high.iter())
19781 .zip(low.iter())
19782 .zip(close.iter())
19783 .map(|(((o, h), l), c)| (o + h + l + c) * 0.25)
19784 .collect(),
19785 hlcc4: high
19786 .iter()
19787 .zip(low.iter())
19788 .zip(close.iter())
19789 .map(|((h, l), c)| (h + l + 2.0 * c) * 0.25)
19790 .collect(),
19791 };
19792 let combo = [
19793 ParamKV {
19794 key: "source",
19795 value: ParamValue::EnumString("hlcc4"),
19796 },
19797 ParamKV {
19798 key: "rsi_length",
19799 value: ParamValue::Int(14),
19800 },
19801 ParamKV {
19802 key: "length1",
19803 value: ParamValue::Int(2),
19804 },
19805 ParamKV {
19806 key: "length2",
19807 value: ParamValue::Int(5),
19808 },
19809 ParamKV {
19810 key: "length3",
19811 value: ParamValue::Int(9),
19812 },
19813 ParamKV {
19814 key: "length4",
19815 value: ParamValue::Int(13),
19816 },
19817 ];
19818 let combos = [IndicatorParamSet { params: &combo }];
19819 let req = IndicatorBatchRequest {
19820 indicator_id: "relative_strength_index_wave_indicator",
19821 output_id: Some("rsi_ma1"),
19822 data: IndicatorDataRef::Candles {
19823 candles: &candles,
19824 source: Some("hlcc4"),
19825 },
19826 combos: &combos,
19827 kernel: Kernel::Auto,
19828 };
19829 let out = compute_cpu_batch(req).unwrap();
19830 let input = RelativeStrengthIndexWaveIndicatorInput::from_slices(
19831 &candles.hlcc4,
19832 &high,
19833 &low,
19834 RelativeStrengthIndexWaveIndicatorParams {
19835 rsi_length: Some(14),
19836 length1: Some(2),
19837 length2: Some(5),
19838 length3: Some(9),
19839 length4: Some(13),
19840 },
19841 );
19842 let direct =
19843 relative_strength_index_wave_indicator_with_kernel(&input, Kernel::Auto.to_non_batch())
19844 .unwrap()
19845 .rsi_ma1;
19846 let got = out.values_f64.unwrap();
19847 assert_series_eq(&got, &direct, 1e-12);
19848 }
19849
19850 #[test]
19851 fn accumulation_swing_index_output_matches_direct() {
19852 let open = sample_series();
19853 let high: Vec<f64> = open.iter().map(|v| v + 1.0).collect();
19854 let low: Vec<f64> = open.iter().map(|v| v - 1.0).collect();
19855 let close: Vec<f64> = open
19856 .iter()
19857 .enumerate()
19858 .map(|(i, v)| v + 0.1 * (i as f64 + 1.0))
19859 .collect();
19860 let combo = [ParamKV {
19861 key: "daily_limit",
19862 value: ParamValue::Float(10_000.0),
19863 }];
19864 let combos = [IndicatorParamSet { params: &combo }];
19865 let req = IndicatorBatchRequest {
19866 indicator_id: "accumulation_swing_index",
19867 output_id: Some("value"),
19868 data: IndicatorDataRef::Ohlc {
19869 open: &open,
19870 high: &high,
19871 low: &low,
19872 close: &close,
19873 },
19874 combos: &combos,
19875 kernel: Kernel::Auto,
19876 };
19877 let out = compute_cpu_batch(req).unwrap();
19878 let input = AccumulationSwingIndexInput::from_slices(
19879 &open,
19880 &high,
19881 &low,
19882 &close,
19883 AccumulationSwingIndexParams {
19884 daily_limit: Some(10_000.0),
19885 },
19886 );
19887 let direct = accumulation_swing_index_with_kernel(&input, Kernel::Auto.to_non_batch())
19888 .unwrap()
19889 .values;
19890 let got = out.values_f64.unwrap();
19891 assert_series_eq(&got, &direct, 1e-12);
19892 }
19893
19894 #[test]
19895 fn ichimoku_oscillator_output_matches_direct() {
19896 let open: Vec<f64> = (0..160)
19897 .map(|i| 100.0 + (i as f64 * 0.07).sin() * 3.0 + i as f64 * 0.02)
19898 .collect();
19899 let high: Vec<f64> = open
19900 .iter()
19901 .enumerate()
19902 .map(|(i, v)| v + 1.2 + (i as f64 * 0.03).sin() * 0.25)
19903 .collect();
19904 let low: Vec<f64> = open
19905 .iter()
19906 .enumerate()
19907 .map(|(i, v)| v - 1.1 - (i as f64 * 0.05).cos() * 0.2)
19908 .collect();
19909 let close: Vec<f64> = open
19910 .iter()
19911 .enumerate()
19912 .map(|(i, v)| v + 0.12 * (i as f64 + 1.0))
19913 .collect();
19914 let combo = [
19915 ParamKV {
19916 key: "conversion_periods",
19917 value: ParamValue::Int(9),
19918 },
19919 ParamKV {
19920 key: "base_periods",
19921 value: ParamValue::Int(26),
19922 },
19923 ParamKV {
19924 key: "lagging_span_periods",
19925 value: ParamValue::Int(52),
19926 },
19927 ParamKV {
19928 key: "displacement",
19929 value: ParamValue::Int(26),
19930 },
19931 ParamKV {
19932 key: "ma_length",
19933 value: ParamValue::Int(12),
19934 },
19935 ParamKV {
19936 key: "smoothing_length",
19937 value: ParamValue::Int(3),
19938 },
19939 ParamKV {
19940 key: "extra_smoothing",
19941 value: ParamValue::Bool(true),
19942 },
19943 ParamKV {
19944 key: "normalize",
19945 value: ParamValue::EnumString("window"),
19946 },
19947 ParamKV {
19948 key: "window_size",
19949 value: ParamValue::Int(20),
19950 },
19951 ParamKV {
19952 key: "clamp",
19953 value: ParamValue::Bool(true),
19954 },
19955 ParamKV {
19956 key: "top_band",
19957 value: ParamValue::Float(2.0),
19958 },
19959 ParamKV {
19960 key: "mid_band",
19961 value: ParamValue::Float(1.5),
19962 },
19963 ];
19964 let combos = [IndicatorParamSet { params: &combo }];
19965 let req = IndicatorBatchRequest {
19966 indicator_id: "ichimoku_oscillator",
19967 output_id: Some("signal"),
19968 data: IndicatorDataRef::Ohlc {
19969 open: &open,
19970 high: &high,
19971 low: &low,
19972 close: &close,
19973 },
19974 combos: &combos,
19975 kernel: Kernel::Auto,
19976 };
19977 let out = compute_cpu_batch(req).unwrap();
19978 let input = IchimokuOscillatorInput::from_slices(
19979 &high,
19980 &low,
19981 &close,
19982 &close,
19983 IchimokuOscillatorParams {
19984 conversion_periods: Some(9),
19985 base_periods: Some(26),
19986 lagging_span_periods: Some(52),
19987 displacement: Some(26),
19988 ma_length: Some(12),
19989 smoothing_length: Some(3),
19990 extra_smoothing: Some(true),
19991 normalize: Some(IchimokuOscillatorNormalizeMode::Window),
19992 window_size: Some(20),
19993 clamp: Some(true),
19994 top_band: Some(2.0),
19995 mid_band: Some(1.5),
19996 },
19997 );
19998 let direct = ichimoku_oscillator_with_kernel(&input, Kernel::Auto.to_non_batch())
19999 .unwrap()
20000 .signal;
20001 let got = out.values_f64.unwrap();
20002 assert_series_eq(&got, &direct, 1e-12);
20003 }
20004
20005 #[test]
20006 fn volatility_quality_index_output_matches_direct() {
20007 let open = sample_series();
20008 let high: Vec<f64> = open.iter().map(|v| v + 1.0).collect();
20009 let low: Vec<f64> = open.iter().map(|v| v - 1.0).collect();
20010 let close: Vec<f64> = open
20011 .iter()
20012 .enumerate()
20013 .map(|(i, v)| v + 0.2 * (i as f64 + 1.0))
20014 .collect();
20015 let combo = [
20016 ParamKV {
20017 key: "fast_length",
20018 value: ParamValue::Int(9),
20019 },
20020 ParamKV {
20021 key: "slow_length",
20022 value: ParamValue::Int(21),
20023 },
20024 ];
20025 let combos = [IndicatorParamSet { params: &combo }];
20026 let req = IndicatorBatchRequest {
20027 indicator_id: "volatility_quality_index",
20028 output_id: Some("fast_sma"),
20029 data: IndicatorDataRef::Ohlc {
20030 open: &open,
20031 high: &high,
20032 low: &low,
20033 close: &close,
20034 },
20035 combos: &combos,
20036 kernel: Kernel::Auto,
20037 };
20038 let out = compute_cpu_batch(req).unwrap();
20039 let input = VolatilityQualityIndexInput::from_slices(
20040 &open,
20041 &high,
20042 &low,
20043 &close,
20044 VolatilityQualityIndexParams {
20045 fast_length: Some(9),
20046 slow_length: Some(21),
20047 },
20048 );
20049 let direct = volatility_quality_index_with_kernel(&input, Kernel::Auto.to_non_batch())
20050 .unwrap()
20051 .fast_sma;
20052 let got = out.values_f64.unwrap();
20053 assert_series_eq(&got, &direct, 1e-12);
20054 }
20055
20056 #[test]
20057 fn vwap_deviation_oscillator_output_matches_direct() {
20058 let open = sample_series();
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
20062 .iter()
20063 .enumerate()
20064 .map(|(i, v)| v + 0.15 * (i as f64 + 1.0))
20065 .collect();
20066 let volume: Vec<f64> = (0..close.len())
20067 .map(|i| 1000.0 + (i as f64 * 11.0))
20068 .collect();
20069 let timestamps: Vec<i64> = (0..close.len())
20070 .map(|i| 1_700_000_000_000i64 + (i as i64) * 14_400_000)
20071 .collect();
20072 let candles = Candles::new(
20073 timestamps.clone(),
20074 open.clone(),
20075 high.clone(),
20076 low.clone(),
20077 close.clone(),
20078 volume.clone(),
20079 );
20080 let combo = [
20081 ParamKV {
20082 key: "session_mode",
20083 value: ParamValue::EnumString("rolling_bars"),
20084 },
20085 ParamKV {
20086 key: "rolling_period",
20087 value: ParamValue::Int(20),
20088 },
20089 ParamKV {
20090 key: "rolling_days",
20091 value: ParamValue::Int(30),
20092 },
20093 ParamKV {
20094 key: "use_close",
20095 value: ParamValue::Bool(false),
20096 },
20097 ParamKV {
20098 key: "deviation_mode",
20099 value: ParamValue::EnumString("zscore"),
20100 },
20101 ParamKV {
20102 key: "z_window",
20103 value: ParamValue::Int(25),
20104 },
20105 ];
20106 let combos = [IndicatorParamSet { params: &combo }];
20107 let req = IndicatorBatchRequest {
20108 indicator_id: "vwap_deviation_oscillator",
20109 output_id: Some("osc"),
20110 data: IndicatorDataRef::Candles {
20111 candles: &candles,
20112 source: None,
20113 },
20114 combos: &combos,
20115 kernel: Kernel::Auto,
20116 };
20117 let out = compute_cpu_batch(req).unwrap();
20118 let input = VwapDeviationOscillatorInput::from_slices(
20119 ×tamps,
20120 &high,
20121 &low,
20122 &close,
20123 &volume,
20124 VwapDeviationOscillatorParams {
20125 session_mode: Some(VwapDeviationSessionMode::RollingBars),
20126 rolling_period: Some(20),
20127 rolling_days: Some(30),
20128 use_close: Some(false),
20129 deviation_mode: Some(VwapDeviationMode::ZScore),
20130 z_window: Some(25),
20131 pct_vol_lookback: Some(100),
20132 pct_min_sigma: Some(0.1),
20133 abs_vol_lookback: Some(100),
20134 },
20135 );
20136 let direct = vwap_deviation_oscillator_with_kernel(&input, Kernel::Auto.to_non_batch())
20137 .unwrap()
20138 .osc;
20139 let got = out.values_f64.unwrap();
20140 assert_series_eq(&got, &direct, 1e-12);
20141 }
20142
20143 #[test]
20144 fn volume_zone_oscillator_output_matches_direct() {
20145 let close = sample_series();
20146 let volume: Vec<f64> = close
20147 .iter()
20148 .enumerate()
20149 .map(|(i, _)| 1000.0 + (i as f64 * 17.0))
20150 .collect();
20151 let combo = [
20152 ParamKV {
20153 key: "length",
20154 value: ParamValue::Int(14),
20155 },
20156 ParamKV {
20157 key: "intraday_smoothing",
20158 value: ParamValue::Bool(true),
20159 },
20160 ParamKV {
20161 key: "noise_filter",
20162 value: ParamValue::Int(4),
20163 },
20164 ];
20165 let combos = [IndicatorParamSet { params: &combo }];
20166 let req = IndicatorBatchRequest {
20167 indicator_id: "volume_zone_oscillator",
20168 output_id: Some("value"),
20169 data: IndicatorDataRef::CloseVolume {
20170 close: &close,
20171 volume: &volume,
20172 },
20173 combos: &combos,
20174 kernel: Kernel::Auto,
20175 };
20176 let out = compute_cpu_batch(req).unwrap();
20177 let input = VolumeZoneOscillatorInput::from_slices(
20178 &close,
20179 &volume,
20180 VolumeZoneOscillatorParams {
20181 length: Some(14),
20182 intraday_smoothing: Some(true),
20183 noise_filter: Some(4),
20184 },
20185 );
20186 let direct = volume_zone_oscillator_with_kernel(&input, Kernel::Auto.to_non_batch())
20187 .unwrap()
20188 .values;
20189 let got = out.values_f64.unwrap();
20190 assert_series_eq(&got, &direct, 1e-12);
20191 }
20192
20193 #[test]
20194 fn ttm_trend_bool_output_matches_direct() {
20195 let (open, high, low, close) = sample_ohlc();
20196 let combo = [ParamKV {
20197 key: "period",
20198 value: ParamValue::Int(5),
20199 }];
20200 let combos = [IndicatorParamSet { params: &combo }];
20201 let req = IndicatorBatchRequest {
20202 indicator_id: "ttm_trend",
20203 output_id: Some("value"),
20204 data: IndicatorDataRef::Ohlc {
20205 open: &open,
20206 high: &high,
20207 low: &low,
20208 close: &close,
20209 },
20210 combos: &combos,
20211 kernel: Kernel::Auto,
20212 };
20213 let out = compute_cpu_batch(req).unwrap();
20214 let source: Vec<f64> = high.iter().zip(&low).map(|(h, l)| 0.5 * (h + l)).collect();
20215 let input = TtmTrendInput::from_slices(&source, &close, TtmTrendParams { period: Some(5) });
20216 let direct = ttm_trend_with_kernel(&input, Kernel::Auto.to_non_batch())
20217 .unwrap()
20218 .values;
20219 assert_eq!(out.values_bool.unwrap(), direct);
20220 }
20221
20222 fn build_default_params_for_indicator(
20223 info: &crate::indicators::registry::IndicatorInfo,
20224 ) -> Option<Vec<ParamKV<'static>>> {
20225 let mut params: Vec<ParamKV<'static>> = Vec::new();
20226 for p in &info.params {
20227 if p.key.eq_ignore_ascii_case("output") {
20228 continue;
20229 }
20230 let value = if let Some(default) = p.default {
20231 match default {
20232 crate::indicators::registry::ParamValueStatic::Int(v) => {
20233 Some(ParamValue::Int(v))
20234 }
20235 crate::indicators::registry::ParamValueStatic::Float(v) => {
20236 Some(ParamValue::Float(v))
20237 }
20238 crate::indicators::registry::ParamValueStatic::Bool(v) => {
20239 Some(ParamValue::Bool(v))
20240 }
20241 crate::indicators::registry::ParamValueStatic::EnumString(v) => {
20242 Some(ParamValue::EnumString(v))
20243 }
20244 }
20245 } else {
20246 match p.kind {
20247 IndicatorParamKind::Int => {
20248 let mut v = p.min.unwrap_or(14.0).round() as i64;
20249 if v < 0 {
20250 v = 0;
20251 }
20252 if let Some(max) = p.max {
20253 v = v.min(max.round() as i64);
20254 }
20255 Some(ParamValue::Int(v))
20256 }
20257 IndicatorParamKind::Float => {
20258 let mut v = p.min.unwrap_or(1.0);
20259 if !v.is_finite() {
20260 v = 1.0;
20261 }
20262 if let Some(max) = p.max {
20263 v = v.min(max);
20264 }
20265 Some(ParamValue::Float(v))
20266 }
20267 IndicatorParamKind::Bool => Some(ParamValue::Bool(false)),
20268 IndicatorParamKind::EnumString => {
20269 p.enum_values.first().copied().map(ParamValue::EnumString)
20270 }
20271 }
20272 };
20273
20274 match value {
20275 Some(v) => params.push(ParamKV {
20276 key: p.key,
20277 value: v,
20278 }),
20279 None => {
20280 if p.required {
20281 return None;
20282 }
20283 }
20284 }
20285 }
20286 Some(params)
20287 }
20288
20289 fn median_ns(mut samples: Vec<u128>) -> u128 {
20290 samples.sort_unstable();
20291 samples[samples.len() / 2]
20292 }
20293
20294 #[test]
20295 #[ignore]
20296 fn full_cpu_dispatch_perf_sweep_vs_direct_route() {
20297 const LEN: usize = 10_000;
20298 const REPS: usize = 5;
20299
20300 let open: Vec<f64> = (0..LEN).map(|i| 100.0 + (i as f64 * 0.01)).collect();
20301 let high: Vec<f64> = open.iter().map(|v| v + 1.0).collect();
20302 let low: Vec<f64> = open.iter().map(|v| v - 1.0).collect();
20303 let close: Vec<f64> = open.iter().map(|v| v + 0.25).collect();
20304 let volume: Vec<f64> = (0..LEN).map(|i| 1000.0 + (i as f64 * 0.5)).collect();
20305 let timestamp: Vec<i64> = (0..LEN).map(|i| i as i64).collect();
20306 let candles = crate::utilities::data_loader::Candles::new(
20307 timestamp,
20308 open.clone(),
20309 high.clone(),
20310 low.clone(),
20311 close.clone(),
20312 volume.clone(),
20313 );
20314
20315 let infos: Vec<_> = list_indicators()
20316 .iter()
20317 .filter(|i| i.capabilities.supports_cpu_batch)
20318 .collect();
20319 let mut rows: Vec<(String, f64, f64, f64)> = Vec::new();
20320 let mut failures: Vec<String> = Vec::new();
20321
20322 for info in infos {
20323 let Some(output) = info.outputs.first() else {
20324 failures.push(format!("{}: no outputs", info.id));
20325 continue;
20326 };
20327 let output_id = output.id;
20328 let Some(params_vec) = build_default_params_for_indicator(info) else {
20329 failures.push(format!("{}: missing required param defaults", info.id));
20330 continue;
20331 };
20332 let combos = [IndicatorParamSet {
20333 params: params_vec.as_slice(),
20334 }];
20335 let data = match info.input_kind {
20336 IndicatorInputKind::Slice => IndicatorDataRef::Slice {
20337 values: close.as_slice(),
20338 },
20339 IndicatorInputKind::Candles => IndicatorDataRef::Candles {
20340 candles: &candles,
20341 source: None,
20342 },
20343 IndicatorInputKind::Ohlc => IndicatorDataRef::Ohlc {
20344 open: open.as_slice(),
20345 high: high.as_slice(),
20346 low: low.as_slice(),
20347 close: close.as_slice(),
20348 },
20349 IndicatorInputKind::Ohlcv => IndicatorDataRef::Ohlcv {
20350 open: open.as_slice(),
20351 high: high.as_slice(),
20352 low: low.as_slice(),
20353 close: close.as_slice(),
20354 volume: volume.as_slice(),
20355 },
20356 IndicatorInputKind::HighLow => IndicatorDataRef::HighLow {
20357 high: high.as_slice(),
20358 low: low.as_slice(),
20359 },
20360 IndicatorInputKind::CloseVolume => IndicatorDataRef::CloseVolume {
20361 close: close.as_slice(),
20362 volume: volume.as_slice(),
20363 },
20364 };
20365
20366 let req = IndicatorBatchRequest {
20367 indicator_id: info.id,
20368 output_id: Some(output_id),
20369 data,
20370 combos: &combos,
20371 kernel: Kernel::Auto,
20372 };
20373
20374 let dispatch_once = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
20375 compute_cpu_batch(req)
20376 })) {
20377 Ok(Ok(v)) => v,
20378 Ok(Err(e)) => {
20379 failures.push(format!("{}: dispatch error: {}", info.id, e));
20380 continue;
20381 }
20382 Err(_) => {
20383 failures.push(format!("{}: dispatch panic", info.id));
20384 continue;
20385 }
20386 };
20387 let direct_once = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
20388 dispatch_cpu_batch_by_indicator(req, info.id, output_id)
20389 })) {
20390 Ok(Ok(v)) => v,
20391 Ok(Err(e)) => {
20392 failures.push(format!("{}: direct-route error: {}", info.id, e));
20393 continue;
20394 }
20395 Err(_) => {
20396 failures.push(format!("{}: direct-route panic", info.id));
20397 continue;
20398 }
20399 };
20400
20401 if dispatch_once.rows != direct_once.rows || dispatch_once.cols != direct_once.cols {
20402 failures.push(format!(
20403 "{}: shape mismatch dispatch=({},{}) direct=({},{})",
20404 info.id,
20405 dispatch_once.rows,
20406 dispatch_once.cols,
20407 direct_once.rows,
20408 direct_once.cols
20409 ));
20410 continue;
20411 }
20412
20413 let mut dispatch_samples = Vec::with_capacity(REPS);
20414 let mut direct_samples = Vec::with_capacity(REPS);
20415 let mut panicked = false;
20416 for _ in 0..REPS {
20417 let t0 = Instant::now();
20418 let dispatch_iter = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
20419 compute_cpu_batch(req)
20420 }));
20421 if !matches!(dispatch_iter, Ok(Ok(_))) {
20422 failures.push(format!("{}: dispatch panic/error during sample", info.id));
20423 panicked = true;
20424 break;
20425 }
20426 dispatch_samples.push(t0.elapsed().as_nanos());
20427
20428 let t1 = Instant::now();
20429 let direct_iter = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
20430 dispatch_cpu_batch_by_indicator(req, info.id, output_id)
20431 }));
20432 if !matches!(direct_iter, Ok(Ok(_))) {
20433 failures.push(format!(
20434 "{}: direct-route panic/error during sample",
20435 info.id
20436 ));
20437 panicked = true;
20438 break;
20439 }
20440 direct_samples.push(t1.elapsed().as_nanos());
20441 }
20442 if panicked {
20443 continue;
20444 }
20445
20446 let dispatch_median = median_ns(dispatch_samples) as f64 / 1_000_000.0;
20447 let direct_median = median_ns(direct_samples) as f64 / 1_000_000.0;
20448 let delta_pct = if direct_median > 0.0 {
20449 ((dispatch_median - direct_median) / direct_median) * 100.0
20450 } else {
20451 0.0
20452 };
20453 rows.push((
20454 info.id.to_string(),
20455 direct_median,
20456 dispatch_median,
20457 delta_pct,
20458 ));
20459 }
20460
20461 rows.sort_by(|a, b| b.3.partial_cmp(&a.3).unwrap_or(std::cmp::Ordering::Equal));
20462
20463 println!("id,direct_ms,dispatch_ms,delta_pct");
20464 for (id, direct_ms, dispatch_ms, delta_pct) in &rows {
20465 println!("{id},{direct_ms:.6},{dispatch_ms:.6},{delta_pct:.2}");
20466 }
20467 println!("total_indicators={}", rows.len());
20468
20469 assert!(
20470 failures.is_empty(),
20471 "perf sweep failures: {}",
20472 failures.join(" | ")
20473 );
20474 assert!(!rows.is_empty(), "no indicators were swept");
20475 }
20476
20477 #[test]
20478 fn multi_output_requires_output_id() {
20479 let data = sample_series();
20480 let combos: [IndicatorParamSet<'_>; 0] = [];
20481 let req = IndicatorBatchRequest {
20482 indicator_id: "macd",
20483 output_id: None,
20484 data: IndicatorDataRef::Slice { values: &data },
20485 combos: &combos,
20486 kernel: Kernel::Auto,
20487 };
20488 let err = compute_cpu_batch(req).unwrap_err();
20489 assert!(matches!(err, IndicatorDispatchError::InvalidParam { .. }));
20490 }
20491
20492 #[test]
20493 fn multi_output_unknown_output_is_rejected_globally() {
20494 let (open, high, low, close) = sample_ohlc();
20495 let volume: Vec<f64> = (0..close.len())
20496 .map(|i| 1000.0 + (i as f64 * 0.5))
20497 .collect();
20498 let timestamp: Vec<i64> = (0..close.len()).map(|i| i as i64).collect();
20499 let candles = crate::utilities::data_loader::Candles::new(
20500 timestamp,
20501 open.clone(),
20502 high.clone(),
20503 low.clone(),
20504 close.clone(),
20505 volume.clone(),
20506 );
20507
20508 for info in list_indicators()
20509 .iter()
20510 .filter(|i| i.capabilities.supports_cpu_batch && i.outputs.len() > 1)
20511 {
20512 let Some(params_vec) = build_default_params_for_indicator(info) else {
20513 continue;
20514 };
20515 let combos = [IndicatorParamSet {
20516 params: params_vec.as_slice(),
20517 }];
20518 let data = match info.input_kind {
20519 IndicatorInputKind::Slice => IndicatorDataRef::Slice {
20520 values: close.as_slice(),
20521 },
20522 IndicatorInputKind::Candles => IndicatorDataRef::Candles {
20523 candles: &candles,
20524 source: None,
20525 },
20526 IndicatorInputKind::Ohlc => IndicatorDataRef::Ohlc {
20527 open: open.as_slice(),
20528 high: high.as_slice(),
20529 low: low.as_slice(),
20530 close: close.as_slice(),
20531 },
20532 IndicatorInputKind::Ohlcv => IndicatorDataRef::Ohlcv {
20533 open: open.as_slice(),
20534 high: high.as_slice(),
20535 low: low.as_slice(),
20536 close: close.as_slice(),
20537 volume: volume.as_slice(),
20538 },
20539 IndicatorInputKind::HighLow => IndicatorDataRef::HighLow {
20540 high: high.as_slice(),
20541 low: low.as_slice(),
20542 },
20543 IndicatorInputKind::CloseVolume => IndicatorDataRef::CloseVolume {
20544 close: close.as_slice(),
20545 volume: volume.as_slice(),
20546 },
20547 };
20548 let req = IndicatorBatchRequest {
20549 indicator_id: info.id,
20550 output_id: Some("__unknown_output__"),
20551 data,
20552 combos: &combos,
20553 kernel: Kernel::Auto,
20554 };
20555 let err = compute_cpu_batch(req).unwrap_err();
20556 assert!(
20557 matches!(err, IndicatorDispatchError::UnknownOutput { .. }),
20558 "indicator {} returned unexpected error for unknown output: {:?}",
20559 info.id,
20560 err
20561 );
20562 }
20563 }
20564
20565 #[test]
20566 fn strict_mode_rejects_mismatched_input_kind_globally() {
20567 let data = sample_series();
20568 let candles = sample_candles();
20569
20570 for info in list_indicators()
20571 .iter()
20572 .filter(|i| i.capabilities.supports_cpu_batch)
20573 {
20574 let Some(output) = info.outputs.first() else {
20575 continue;
20576 };
20577 let Some(params_vec) = build_default_params_for_indicator(info) else {
20578 continue;
20579 };
20580 let combos = [IndicatorParamSet {
20581 params: params_vec.as_slice(),
20582 }];
20583 let expected = strict_expected_input_kind(info.id, info.input_kind);
20584 let mismatched = match expected {
20585 IndicatorInputKind::Slice => IndicatorDataRef::Candles {
20586 candles: &candles,
20587 source: None,
20588 },
20589 IndicatorInputKind::Candles => IndicatorDataRef::Slice { values: &data },
20590 IndicatorInputKind::Ohlc
20591 | IndicatorInputKind::Ohlcv
20592 | IndicatorInputKind::HighLow
20593 | IndicatorInputKind::CloseVolume => IndicatorDataRef::Slice { values: &data },
20594 };
20595 let req = IndicatorBatchRequest {
20596 indicator_id: info.id,
20597 output_id: Some(output.id),
20598 data: mismatched,
20599 combos: &combos,
20600 kernel: Kernel::Auto,
20601 };
20602 let err = compute_cpu_batch_strict(req).unwrap_err();
20603 assert!(
20604 matches!(err, IndicatorDispatchError::MissingRequiredInput { .. }),
20605 "indicator {} did not reject strict mismatched input: {:?}",
20606 info.id,
20607 err
20608 );
20609 }
20610 }
20611
20612 #[test]
20613 fn full_cpu_dispatch_parity_vs_direct_route_for_all_outputs() {
20614 const LEN: usize = 4096;
20615 let open: Vec<f64> = (0..LEN).map(|i| 100.0 + (i as f64 * 0.01)).collect();
20616 let high: Vec<f64> = open.iter().map(|v| v + 1.0).collect();
20617 let low: Vec<f64> = open.iter().map(|v| v - 1.0).collect();
20618 let close: Vec<f64> = open.iter().map(|v| v + 0.25).collect();
20619 let volume: Vec<f64> = (0..LEN).map(|i| 1000.0 + (i as f64 * 0.5)).collect();
20620 let timestamp: Vec<i64> = (0..LEN).map(|i| i as i64).collect();
20621 let candles = crate::utilities::data_loader::Candles::new(
20622 timestamp,
20623 open.clone(),
20624 high.clone(),
20625 low.clone(),
20626 close.clone(),
20627 volume.clone(),
20628 );
20629
20630 for info in list_indicators()
20631 .iter()
20632 .filter(|i| i.capabilities.supports_cpu_batch)
20633 {
20634 let Some(params_vec) = build_default_params_for_indicator(info) else {
20635 continue;
20636 };
20637 let combos = [IndicatorParamSet {
20638 params: params_vec.as_slice(),
20639 }];
20640 let data = match info.input_kind {
20641 IndicatorInputKind::Slice => IndicatorDataRef::Slice {
20642 values: close.as_slice(),
20643 },
20644 IndicatorInputKind::Candles => IndicatorDataRef::Candles {
20645 candles: &candles,
20646 source: None,
20647 },
20648 IndicatorInputKind::Ohlc => IndicatorDataRef::Ohlc {
20649 open: open.as_slice(),
20650 high: high.as_slice(),
20651 low: low.as_slice(),
20652 close: close.as_slice(),
20653 },
20654 IndicatorInputKind::Ohlcv => IndicatorDataRef::Ohlcv {
20655 open: open.as_slice(),
20656 high: high.as_slice(),
20657 low: low.as_slice(),
20658 close: close.as_slice(),
20659 volume: volume.as_slice(),
20660 },
20661 IndicatorInputKind::HighLow => IndicatorDataRef::HighLow {
20662 high: high.as_slice(),
20663 low: low.as_slice(),
20664 },
20665 IndicatorInputKind::CloseVolume => IndicatorDataRef::CloseVolume {
20666 close: close.as_slice(),
20667 volume: volume.as_slice(),
20668 },
20669 };
20670
20671 for output in info.outputs.iter() {
20672 let req = IndicatorBatchRequest {
20673 indicator_id: info.id,
20674 output_id: Some(output.id),
20675 data,
20676 combos: &combos,
20677 kernel: Kernel::Auto,
20678 };
20679 let generic = compute_cpu_batch(req).unwrap_or_else(|e| {
20680 panic!(
20681 "generic dispatch failed for {}:{}: {}",
20682 info.id, output.id, e
20683 )
20684 });
20685 let direct = dispatch_cpu_batch_by_indicator(req, info.id, output.id)
20686 .unwrap_or_else(|e| {
20687 panic!("direct route failed for {}:{}: {}", info.id, output.id, e)
20688 });
20689
20690 assert_eq!(
20691 generic.rows, direct.rows,
20692 "rows mismatch for {}:{}",
20693 info.id, output.id
20694 );
20695 assert_eq!(
20696 generic.cols, direct.cols,
20697 "cols mismatch for {}:{}",
20698 info.id, output.id
20699 );
20700 assert_eq!(
20701 generic.output_id, direct.output_id,
20702 "output id mismatch for {}:{}",
20703 info.id, output.id
20704 );
20705
20706 match (
20707 generic.values_f64.as_ref(),
20708 direct.values_f64.as_ref(),
20709 generic.values_i32.as_ref(),
20710 direct.values_i32.as_ref(),
20711 generic.values_bool.as_ref(),
20712 direct.values_bool.as_ref(),
20713 ) {
20714 (Some(g), Some(d), None, None, None, None) => assert_series_eq(g, d, 1e-9),
20715 (None, None, Some(g), Some(d), None, None) => assert_eq!(g, d),
20716 (None, None, None, None, Some(g), Some(d)) => assert_eq!(g, d),
20717 _ => panic!("value type mismatch for {}:{}", info.id, output.id),
20718 }
20719 }
20720 }
20721 }
20722
20723 #[test]
20724 fn compute_cpu_batch_bull_power_vs_bear_power_matches_direct() {
20725 let open: Vec<f64> = (0..256)
20726 .map(|i| 100.0 + (i as f64 * 0.03).sin() + i as f64 * 0.02)
20727 .collect();
20728 let close: Vec<f64> = open
20729 .iter()
20730 .enumerate()
20731 .map(|(i, &o)| o + (i as f64 * 0.025).cos() * 0.8)
20732 .collect();
20733 let high: Vec<f64> = open
20734 .iter()
20735 .zip(close.iter())
20736 .enumerate()
20737 .map(|(i, (&o, &c))| o.max(c) + 0.5 + (i as f64 * 0.013).sin().abs() * 0.2)
20738 .collect();
20739 let low: Vec<f64> = open
20740 .iter()
20741 .zip(close.iter())
20742 .enumerate()
20743 .map(|(i, (&o, &c))| o.min(c) - 0.4 - (i as f64 * 0.017).cos().abs() * 0.15)
20744 .collect();
20745 let params = [ParamKV {
20746 key: "period",
20747 value: ParamValue::Int(5),
20748 }];
20749 let combos = [IndicatorParamSet { params: ¶ms }];
20750 let candles = crate::utilities::data_loader::Candles::new(
20751 vec![0; close.len()],
20752 open.clone(),
20753 high.clone(),
20754 low.clone(),
20755 close.clone(),
20756 vec![0.0; close.len()],
20757 );
20758
20759 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
20760 indicator_id: "bull_power_vs_bear_power",
20761 output_id: Some("value"),
20762 data: IndicatorDataRef::Candles {
20763 candles: &candles,
20764 source: None,
20765 },
20766 combos: &combos,
20767 kernel: Kernel::Auto,
20768 })
20769 .unwrap();
20770
20771 let direct = bull_power_vs_bear_power_with_kernel(
20772 &BullPowerVsBearPowerInput::from_slices(
20773 &open,
20774 &high,
20775 &low,
20776 &close,
20777 BullPowerVsBearPowerParams { period: Some(5) },
20778 ),
20779 Kernel::Auto,
20780 )
20781 .unwrap();
20782
20783 let values = dispatched.values_f64.as_ref().unwrap();
20784 assert_eq!(values.len(), close.len());
20785 assert_series_eq(values, &direct.values, 1e-9);
20786 }
20787
20788 #[test]
20789 fn compute_cpu_batch_advance_decline_line_matches_direct() {
20790 let close: Vec<f64> = (0..256)
20791 .map(|i| ((i as f64) * 0.05).sin() * 100.0 + ((i as f64) * 0.02).cos() * 25.0)
20792 .collect();
20793 let combos = [IndicatorParamSet { params: &[] }];
20794
20795 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
20796 indicator_id: "advance_decline_line",
20797 output_id: Some("value"),
20798 data: IndicatorDataRef::Slice { values: &close },
20799 combos: &combos,
20800 kernel: Kernel::Auto,
20801 })
20802 .unwrap();
20803
20804 let direct = advance_decline_line_with_kernel(
20805 &AdvanceDeclineLineInput::from_slice(&close, AdvanceDeclineLineParams),
20806 Kernel::Auto,
20807 )
20808 .unwrap();
20809
20810 let values = dispatched.values_f64.as_ref().unwrap();
20811 assert_eq!(values.len(), close.len());
20812 assert_series_eq(values, &direct.values, 1e-9);
20813 }
20814
20815 #[test]
20816 fn compute_cpu_batch_decisionpoint_breadth_swenlin_trading_oscillator_matches_direct() {
20817 let advancing: Vec<f64> = (0..256)
20818 .map(|i| 1500.0 + i as f64 * 0.8 + (i as f64 * 0.07).sin() * 120.0 + 40.0)
20819 .collect();
20820 let declining: Vec<f64> = (0..256)
20821 .map(|i| 1300.0 + i as f64 * 0.5 + (i as f64 * 0.05).cos() * 95.0 + 30.0)
20822 .collect();
20823 let combos = [IndicatorParamSet { params: &[] }];
20824
20825 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
20826 indicator_id: "decisionpoint_breadth_swenlin_trading_oscillator",
20827 output_id: Some("value"),
20828 data: IndicatorDataRef::HighLow {
20829 high: &advancing,
20830 low: &declining,
20831 },
20832 combos: &combos,
20833 kernel: Kernel::Auto,
20834 })
20835 .unwrap();
20836
20837 let direct = decisionpoint_breadth_swenlin_trading_oscillator_with_kernel(
20838 &DecisionPointBreadthSwenlinTradingOscillatorInput::from_slices(
20839 &advancing,
20840 &declining,
20841 DecisionPointBreadthSwenlinTradingOscillatorParams,
20842 ),
20843 Kernel::Auto,
20844 )
20845 .unwrap();
20846
20847 let values = dispatched.values_f64.as_ref().unwrap();
20848 assert_eq!(values.len(), advancing.len());
20849 assert_series_eq(values, &direct.values, 1e-9);
20850 }
20851
20852 #[test]
20853 fn compute_cpu_batch_velocity_acceleration_indicator_matches_direct() {
20854 let open: Vec<f64> = (0..256)
20855 .map(|i| 100.0 + i as f64 * 0.04 + (i as f64 * 0.09).sin())
20856 .collect();
20857 let close: Vec<f64> = open
20858 .iter()
20859 .enumerate()
20860 .map(|(i, &o)| o + (i as f64 * 0.11).cos() * 0.9)
20861 .collect();
20862 let high: Vec<f64> = open
20863 .iter()
20864 .zip(close.iter())
20865 .enumerate()
20866 .map(|(i, (&o, &c))| o.max(c) + 0.6 + (i as f64 * 0.03).sin().abs() * 0.2)
20867 .collect();
20868 let low: Vec<f64> = open
20869 .iter()
20870 .zip(close.iter())
20871 .enumerate()
20872 .map(|(i, (&o, &c))| o.min(c) - 0.6 - (i as f64 * 0.05).cos().abs() * 0.2)
20873 .collect();
20874 let candles = crate::utilities::data_loader::Candles::new(
20875 (0..256_i64).collect(),
20876 open,
20877 high,
20878 low,
20879 close,
20880 vec![1_000.0; 256],
20881 );
20882 let params = [
20883 ParamKV {
20884 key: "length",
20885 value: ParamValue::Int(21),
20886 },
20887 ParamKV {
20888 key: "smooth_length",
20889 value: ParamValue::Int(5),
20890 },
20891 ParamKV {
20892 key: "source",
20893 value: ParamValue::EnumString("hlcc4"),
20894 },
20895 ];
20896 let combos = [IndicatorParamSet { params: ¶ms }];
20897
20898 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
20899 indicator_id: "velocity_acceleration_indicator",
20900 output_id: Some("value"),
20901 data: IndicatorDataRef::Candles {
20902 candles: &candles,
20903 source: Some("hlcc4"),
20904 },
20905 combos: &combos,
20906 kernel: Kernel::Auto,
20907 })
20908 .unwrap();
20909
20910 let direct = velocity_acceleration_indicator_with_kernel(
20911 &VelocityAccelerationIndicatorInput::from_candles(
20912 &candles,
20913 "hlcc4",
20914 VelocityAccelerationIndicatorParams {
20915 length: Some(21),
20916 smooth_length: Some(5),
20917 },
20918 ),
20919 Kernel::Auto,
20920 )
20921 .unwrap();
20922
20923 let values = dispatched.values_f64.as_ref().unwrap();
20924 assert_eq!(values.len(), candles.close.len());
20925 assert_series_eq(values, &direct.values, 1e-9);
20926 }
20927
20928 #[test]
20929 fn compute_cpu_batch_normalized_resonator_matches_direct() {
20930 let open: Vec<f64> = (0..256)
20931 .map(|i| 100.0 + i as f64 * 0.03 + (i as f64 * 0.07).sin())
20932 .collect();
20933 let close: Vec<f64> = open
20934 .iter()
20935 .enumerate()
20936 .map(|(i, &o)| o + (i as f64 * 0.11).cos() * 0.8)
20937 .collect();
20938 let high: Vec<f64> = open
20939 .iter()
20940 .zip(close.iter())
20941 .enumerate()
20942 .map(|(i, (&o, &c))| o.max(c) + 0.6 + (i as f64 * 0.05).sin().abs() * 0.2)
20943 .collect();
20944 let low: Vec<f64> = open
20945 .iter()
20946 .zip(close.iter())
20947 .enumerate()
20948 .map(|(i, (&o, &c))| o.min(c) - 0.6 - (i as f64 * 0.03).cos().abs() * 0.2)
20949 .collect();
20950 let candles = crate::utilities::data_loader::Candles::new(
20951 (0..256_i64).collect(),
20952 open,
20953 high,
20954 low,
20955 close,
20956 vec![1_000.0; 256],
20957 );
20958 let params = [
20959 ParamKV {
20960 key: "period",
20961 value: ParamValue::Int(48),
20962 },
20963 ParamKV {
20964 key: "delta",
20965 value: ParamValue::Float(0.4),
20966 },
20967 ParamKV {
20968 key: "lookback_mult",
20969 value: ParamValue::Float(1.2),
20970 },
20971 ParamKV {
20972 key: "signal_length",
20973 value: ParamValue::Int(7),
20974 },
20975 ParamKV {
20976 key: "source",
20977 value: ParamValue::EnumString("hl2"),
20978 },
20979 ];
20980 let combos = [IndicatorParamSet { params: ¶ms }];
20981
20982 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
20983 indicator_id: "normalized_resonator",
20984 output_id: Some("oscillator"),
20985 data: IndicatorDataRef::Candles {
20986 candles: &candles,
20987 source: Some("hl2"),
20988 },
20989 combos: &combos,
20990 kernel: Kernel::Auto,
20991 })
20992 .unwrap();
20993
20994 let direct = normalized_resonator_with_kernel(
20995 &NormalizedResonatorInput::from_candles(
20996 &candles,
20997 "hl2",
20998 NormalizedResonatorParams {
20999 period: Some(48),
21000 delta: Some(0.4),
21001 lookback_mult: Some(1.2),
21002 signal_length: Some(7),
21003 },
21004 ),
21005 Kernel::Auto,
21006 )
21007 .unwrap();
21008
21009 let values = dispatched.values_f64.as_ref().unwrap();
21010 assert_eq!(values.len(), candles.close.len());
21011 assert_series_eq(values, &direct.oscillator, 1e-9);
21012 }
21013
21014 #[test]
21015 fn compute_cpu_batch_monotonicity_index_matches_direct() {
21016 let open: Vec<f64> = (0..256)
21017 .map(|i| 100.0 + i as f64 * 0.04 + (i as f64 * 0.08).sin())
21018 .collect();
21019 let close: Vec<f64> = open
21020 .iter()
21021 .enumerate()
21022 .map(|(i, &o)| o + (i as f64 * 0.13).cos() * 0.9)
21023 .collect();
21024 let high: Vec<f64> = open
21025 .iter()
21026 .zip(close.iter())
21027 .enumerate()
21028 .map(|(i, (&o, &c))| o.max(c) + 0.6 + (i as f64 * 0.05).sin().abs() * 0.2)
21029 .collect();
21030 let low: Vec<f64> = open
21031 .iter()
21032 .zip(close.iter())
21033 .enumerate()
21034 .map(|(i, (&o, &c))| o.min(c) - 0.6 - (i as f64 * 0.03).cos().abs() * 0.2)
21035 .collect();
21036 let candles = crate::utilities::data_loader::Candles::new(
21037 (0..256_i64).collect(),
21038 open,
21039 high,
21040 low,
21041 close,
21042 vec![1_000.0; 256],
21043 );
21044 let params = [
21045 ParamKV {
21046 key: "length",
21047 value: ParamValue::Int(20),
21048 },
21049 ParamKV {
21050 key: "mode",
21051 value: ParamValue::EnumString("efficiency"),
21052 },
21053 ParamKV {
21054 key: "index_smooth",
21055 value: ParamValue::Int(5),
21056 },
21057 ParamKV {
21058 key: "source",
21059 value: ParamValue::EnumString("close"),
21060 },
21061 ];
21062 let combos = [IndicatorParamSet { params: ¶ms }];
21063
21064 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21065 indicator_id: "monotonicity_index",
21066 output_id: Some("index"),
21067 data: IndicatorDataRef::Candles {
21068 candles: &candles,
21069 source: Some("close"),
21070 },
21071 combos: &combos,
21072 kernel: Kernel::Auto,
21073 })
21074 .unwrap();
21075
21076 let direct = monotonicity_index_with_kernel(
21077 &MonotonicityIndexInput::from_candles(
21078 &candles,
21079 "close",
21080 MonotonicityIndexParams {
21081 length: Some(20),
21082 mode: Some(MonotonicityIndexMode::Efficiency),
21083 index_smooth: Some(5),
21084 },
21085 ),
21086 Kernel::Auto,
21087 )
21088 .unwrap();
21089
21090 let values = dispatched.values_f64.as_ref().unwrap();
21091 assert_eq!(values.len(), candles.close.len());
21092 assert_series_eq(values, &direct.index, 1e-9);
21093 }
21094
21095 #[test]
21096 fn compute_cpu_batch_half_causal_estimator_matches_direct() {
21097 let len = 240usize;
21098 let slots_per_day = 60usize;
21099 let close: Vec<f64> = (0..len)
21100 .map(|i| {
21101 let slot = (i % slots_per_day) as f64;
21102 let day = (i / slots_per_day) as f64;
21103 1000.0
21104 + day * 4.0
21105 + (slot * 0.13).sin() * 25.0
21106 + (slot * 0.04).cos() * 9.0
21107 + slot * 0.2
21108 })
21109 .collect();
21110 let params = [
21111 ParamKV {
21112 key: "slots_per_day",
21113 value: ParamValue::Int(slots_per_day as i64),
21114 },
21115 ParamKV {
21116 key: "data_period",
21117 value: ParamValue::Int(5),
21118 },
21119 ParamKV {
21120 key: "filter_length",
21121 value: ParamValue::Int(20),
21122 },
21123 ParamKV {
21124 key: "kernel_width",
21125 value: ParamValue::Float(20.0),
21126 },
21127 ParamKV {
21128 key: "kernel_type",
21129 value: ParamValue::EnumString("epanechnikov"),
21130 },
21131 ParamKV {
21132 key: "confidence_adjust",
21133 value: ParamValue::EnumString("symmetric"),
21134 },
21135 ParamKV {
21136 key: "maximum_confidence_adjust",
21137 value: ParamValue::Float(100.0),
21138 },
21139 ParamKV {
21140 key: "enable_expected_value",
21141 value: ParamValue::Bool(true),
21142 },
21143 ParamKV {
21144 key: "extra_smoothing",
21145 value: ParamValue::Int(0),
21146 },
21147 ];
21148 let combos = [IndicatorParamSet { params: ¶ms }];
21149
21150 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21151 indicator_id: "half_causal_estimator",
21152 output_id: Some("estimate"),
21153 data: IndicatorDataRef::Slice { values: &close },
21154 combos: &combos,
21155 kernel: Kernel::Auto,
21156 })
21157 .unwrap();
21158
21159 let direct = crate::indicators::half_causal_estimator::half_causal_estimator_with_kernel(
21160 &crate::indicators::half_causal_estimator::HalfCausalEstimatorInput::from_slice(
21161 &close,
21162 crate::indicators::half_causal_estimator::HalfCausalEstimatorParams {
21163 slots_per_day: Some(slots_per_day),
21164 data_period: Some(5),
21165 filter_length: Some(20),
21166 kernel_width: Some(20.0),
21167 kernel_type: Some(
21168 crate::indicators::half_causal_estimator::HalfCausalEstimatorKernelType::Epanechnikov,
21169 ),
21170 confidence_adjust: Some(
21171 crate::indicators::half_causal_estimator::HalfCausalEstimatorConfidenceAdjust::Symmetric,
21172 ),
21173 maximum_confidence_adjust: Some(100.0),
21174 enable_expected_value: Some(true),
21175 extra_smoothing: Some(0),
21176 },
21177 ),
21178 Kernel::Auto,
21179 )
21180 .unwrap();
21181
21182 let values = dispatched.values_f64.as_ref().unwrap();
21183 assert_eq!(values.len(), close.len());
21184 assert_series_eq(values, &direct.estimate, 1e-9);
21185 }
21186
21187 #[test]
21188 fn compute_cpu_batch_didi_index_matches_direct() {
21189 let close: Vec<f64> = (0..256)
21190 .map(|i| 100.0 + ((i as f64) * 0.09).sin() * 7.0 + (i as f64) * 0.03)
21191 .collect();
21192 let params = [
21193 ParamKV {
21194 key: "short_length",
21195 value: ParamValue::Int(3),
21196 },
21197 ParamKV {
21198 key: "medium_length",
21199 value: ParamValue::Int(8),
21200 },
21201 ParamKV {
21202 key: "long_length",
21203 value: ParamValue::Int(20),
21204 },
21205 ];
21206 let combos = [IndicatorParamSet { params: ¶ms }];
21207
21208 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21209 indicator_id: "didi_index",
21210 output_id: Some("short"),
21211 data: IndicatorDataRef::Slice { values: &close },
21212 combos: &combos,
21213 kernel: Kernel::Auto,
21214 })
21215 .unwrap();
21216
21217 let direct = didi_index_with_kernel(
21218 &DidiIndexInput::from_slice(
21219 &close,
21220 DidiIndexParams {
21221 short_length: Some(3),
21222 medium_length: Some(8),
21223 long_length: Some(20),
21224 },
21225 ),
21226 Kernel::Auto,
21227 )
21228 .unwrap();
21229
21230 let values = dispatched.values_f64.as_ref().unwrap();
21231 assert_eq!(values.len(), close.len());
21232 assert_series_eq(values, &direct.short, 1e-9);
21233 }
21234
21235 #[test]
21236 fn compute_cpu_batch_ehlers_autocorrelation_periodogram_matches_direct() {
21237 let close: Vec<f64> = (0..256)
21238 .map(|i| {
21239 let phase = 2.0 * std::f64::consts::PI * i as f64 / 20.0;
21240 phase.sin() + 0.15 * (phase * 0.5).cos()
21241 })
21242 .collect();
21243 let params = [
21244 ParamKV {
21245 key: "min_period",
21246 value: ParamValue::Int(8),
21247 },
21248 ParamKV {
21249 key: "max_period",
21250 value: ParamValue::Int(48),
21251 },
21252 ParamKV {
21253 key: "avg_length",
21254 value: ParamValue::Int(3),
21255 },
21256 ParamKV {
21257 key: "enhance",
21258 value: ParamValue::Bool(true),
21259 },
21260 ];
21261 let combos = [IndicatorParamSet { params: ¶ms }];
21262
21263 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21264 indicator_id: "ehlers_autocorrelation_periodogram",
21265 output_id: Some("dominant_cycle"),
21266 data: IndicatorDataRef::Slice { values: &close },
21267 combos: &combos,
21268 kernel: Kernel::Auto,
21269 })
21270 .unwrap();
21271
21272 let direct = ehlers_autocorrelation_periodogram_with_kernel(
21273 &EhlersAutocorrelationPeriodogramInput::from_slice(
21274 &close,
21275 EhlersAutocorrelationPeriodogramParams {
21276 min_period: Some(8),
21277 max_period: Some(48),
21278 avg_length: Some(3),
21279 enhance: Some(true),
21280 },
21281 ),
21282 Kernel::Auto,
21283 )
21284 .unwrap();
21285
21286 let values = dispatched.values_f64.as_ref().unwrap();
21287 assert_eq!(values.len(), close.len());
21288 assert_series_eq(values, &direct.dominant_cycle, 1e-9);
21289 }
21290
21291 #[test]
21292 fn compute_cpu_batch_ehlers_linear_extrapolation_predictor_matches_direct() {
21293 let close: Vec<f64> = (0..256)
21294 .map(|i| 100.0 + ((i as f64) * 0.09).sin() * 2.0 + (i as f64 * 0.03))
21295 .collect();
21296 let params = [
21297 ParamKV {
21298 key: "high_pass_length",
21299 value: ParamValue::Int(125),
21300 },
21301 ParamKV {
21302 key: "low_pass_length",
21303 value: ParamValue::Int(12),
21304 },
21305 ParamKV {
21306 key: "gain",
21307 value: ParamValue::Float(0.7),
21308 },
21309 ParamKV {
21310 key: "bars_forward",
21311 value: ParamValue::Int(5),
21312 },
21313 ParamKV {
21314 key: "signal_mode",
21315 value: ParamValue::EnumString("predict_filter_crosses"),
21316 },
21317 ];
21318 let combos = [IndicatorParamSet { params: ¶ms }];
21319
21320 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21321 indicator_id: "ehlers_linear_extrapolation_predictor",
21322 output_id: Some("prediction"),
21323 data: IndicatorDataRef::Slice { values: &close },
21324 combos: &combos,
21325 kernel: Kernel::Auto,
21326 })
21327 .unwrap();
21328
21329 let direct = ehlers_linear_extrapolation_predictor_with_kernel(
21330 &EhlersLinearExtrapolationPredictorInput::from_slice(
21331 &close,
21332 EhlersLinearExtrapolationPredictorParams {
21333 high_pass_length: Some(125),
21334 low_pass_length: Some(12),
21335 gain: Some(0.7),
21336 bars_forward: Some(5),
21337 signal_mode: Some("predict_filter_crosses".to_string()),
21338 },
21339 ),
21340 Kernel::Auto,
21341 )
21342 .unwrap();
21343
21344 let values = dispatched.values_f64.as_ref().unwrap();
21345 assert_eq!(values.len(), close.len());
21346 assert_series_eq(values, &direct.prediction, 1e-9);
21347 }
21348
21349 #[test]
21350 fn compute_cpu_batch_grover_llorens_cycle_oscillator_matches_direct() {
21351 let mut open = Vec::with_capacity(256);
21352 let mut high = Vec::with_capacity(256);
21353 let mut low = Vec::with_capacity(256);
21354 let mut close = Vec::with_capacity(256);
21355 let mut prev = 100.0;
21356 for i in 0..256 {
21357 let x = i as f64;
21358 let wave = (x * 0.11).sin() * 2.4 + (x * 0.037).cos() * 1.3;
21359 let o = prev + wave * 0.35;
21360 let c = o + (x * 0.19).sin() * 1.1 - (x * 0.07).cos() * 0.4;
21361 let h = o.max(c) + 0.6 + (x * 0.03).sin().abs() * 0.25;
21362 let l = o.min(c) - 0.6 - (x * 0.02).cos().abs() * 0.25;
21363 open.push(o);
21364 high.push(h);
21365 low.push(l);
21366 close.push(c);
21367 prev = c;
21368 }
21369
21370 let params = [
21371 ParamKV {
21372 key: "length",
21373 value: ParamValue::Int(60),
21374 },
21375 ParamKV {
21376 key: "mult",
21377 value: ParamValue::Float(8.0),
21378 },
21379 ParamKV {
21380 key: "source",
21381 value: ParamValue::EnumString("hlc3"),
21382 },
21383 ParamKV {
21384 key: "smooth",
21385 value: ParamValue::Bool(true),
21386 },
21387 ParamKV {
21388 key: "rsi_period",
21389 value: ParamValue::Int(14),
21390 },
21391 ];
21392 let combos = [IndicatorParamSet { params: ¶ms }];
21393
21394 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21395 indicator_id: "grover_llorens_cycle_oscillator",
21396 output_id: Some("value"),
21397 data: IndicatorDataRef::Ohlc {
21398 open: &open,
21399 high: &high,
21400 low: &low,
21401 close: &close,
21402 },
21403 combos: &combos,
21404 kernel: Kernel::Auto,
21405 })
21406 .unwrap();
21407
21408 let direct = grover_llorens_cycle_oscillator_with_kernel(
21409 &GroverLlorensCycleOscillatorInput::from_slices(
21410 &open,
21411 &high,
21412 &low,
21413 &close,
21414 GroverLlorensCycleOscillatorParams {
21415 length: Some(60),
21416 mult: Some(8.0),
21417 source: Some("hlc3".to_string()),
21418 smooth: Some(true),
21419 rsi_period: Some(14),
21420 },
21421 ),
21422 Kernel::Auto,
21423 )
21424 .unwrap();
21425
21426 let values = dispatched.values_f64.as_ref().unwrap();
21427 assert_eq!(values.len(), close.len());
21428 assert_series_eq(values, &direct.values, 1e-9);
21429 }
21430
21431 #[test]
21432 fn compute_cpu_batch_historical_volatility_matches_direct() {
21433 let close: Vec<f64> = (0..256)
21434 .map(|i| 100.0 + ((i as f64) * 0.02).sin() + (i as f64 * 0.1))
21435 .collect();
21436 let params = [
21437 ParamKV {
21438 key: "lookback",
21439 value: ParamValue::Int(20),
21440 },
21441 ParamKV {
21442 key: "annualization_days",
21443 value: ParamValue::Float(252.0),
21444 },
21445 ];
21446 let combos = [IndicatorParamSet { params: ¶ms }];
21447
21448 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21449 indicator_id: "historical_volatility",
21450 output_id: Some("value"),
21451 data: IndicatorDataRef::Slice { values: &close },
21452 combos: &combos,
21453 kernel: Kernel::Auto,
21454 })
21455 .unwrap();
21456
21457 let direct = historical_volatility_with_kernel(
21458 &HistoricalVolatilityInput::from_slice(
21459 &close,
21460 HistoricalVolatilityParams {
21461 lookback: Some(20),
21462 annualization_days: Some(252.0),
21463 },
21464 ),
21465 Kernel::Auto,
21466 )
21467 .unwrap();
21468
21469 let values = dispatched.values_f64.as_ref().unwrap();
21470 assert_eq!(values.len(), close.len());
21471 assert_series_eq(values, &direct.values, 1e-9);
21472 }
21473
21474 #[test]
21475 fn compute_cpu_batch_stochastic_distance_matches_direct() {
21476 let close: Vec<f64> = (0..256)
21477 .map(|i| 100.0 + (i as f64 * 0.07).sin() * 1.3 + i as f64 * 0.03)
21478 .collect();
21479 let params = [
21480 ParamKV {
21481 key: "lookback_length",
21482 value: ParamValue::Int(50),
21483 },
21484 ParamKV {
21485 key: "length1",
21486 value: ParamValue::Int(8),
21487 },
21488 ParamKV {
21489 key: "length2",
21490 value: ParamValue::Int(4),
21491 },
21492 ParamKV {
21493 key: "ob_level",
21494 value: ParamValue::Int(40),
21495 },
21496 ParamKV {
21497 key: "os_level",
21498 value: ParamValue::Int(-40),
21499 },
21500 ];
21501 let combos = [IndicatorParamSet { params: ¶ms }];
21502
21503 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21504 indicator_id: "stochastic_distance",
21505 output_id: Some("oscillator"),
21506 data: IndicatorDataRef::Slice { values: &close },
21507 combos: &combos,
21508 kernel: Kernel::Auto,
21509 })
21510 .unwrap();
21511
21512 let direct = stochastic_distance_with_kernel(
21513 &StochasticDistanceInput::from_slice(
21514 &close,
21515 StochasticDistanceParams {
21516 lookback_length: Some(50),
21517 length1: Some(8),
21518 length2: Some(4),
21519 ob_level: Some(40),
21520 os_level: Some(-40),
21521 },
21522 ),
21523 Kernel::Auto,
21524 )
21525 .unwrap();
21526
21527 let values = dispatched.values_f64.as_ref().unwrap();
21528 assert_eq!(values.len(), close.len());
21529 assert_series_eq(values, &direct.oscillator, 1e-9);
21530 }
21531
21532 #[test]
21533 fn compute_cpu_batch_adaptive_bandpass_trigger_oscillator_matches_direct() {
21534 let close: Vec<f64> = (0..256)
21535 .map(|i| 100.0 + (i as f64 * 0.07).sin() * 1.3 + (i as f64 * 0.03).cos() * 0.6)
21536 .collect();
21537 let params = [
21538 ParamKV {
21539 key: "delta",
21540 value: ParamValue::Float(0.1),
21541 },
21542 ParamKV {
21543 key: "alpha",
21544 value: ParamValue::Float(0.07),
21545 },
21546 ];
21547 let combos = [IndicatorParamSet { params: ¶ms }];
21548
21549 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21550 indicator_id: "adaptive_bandpass_trigger_oscillator",
21551 output_id: Some("in_phase"),
21552 data: IndicatorDataRef::Slice { values: &close },
21553 combos: &combos,
21554 kernel: Kernel::Auto,
21555 })
21556 .unwrap();
21557
21558 let direct = adaptive_bandpass_trigger_oscillator_with_kernel(
21559 &AdaptiveBandpassTriggerOscillatorInput::from_slice(
21560 &close,
21561 AdaptiveBandpassTriggerOscillatorParams {
21562 delta: Some(0.1),
21563 alpha: Some(0.07),
21564 },
21565 ),
21566 Kernel::Auto,
21567 )
21568 .unwrap();
21569
21570 let values = dispatched.values_f64.as_ref().unwrap();
21571 assert_eq!(values.len(), close.len());
21572 assert_series_eq(values, &direct.in_phase, 1e-9);
21573 }
21574
21575 #[test]
21576 fn compute_cpu_batch_squeeze_index_matches_direct() {
21577 let close: Vec<f64> = (0..256)
21578 .map(|i| 100.0 + ((i as f64) * 0.11).sin() * 1.2 + (i as f64 * 0.02))
21579 .collect();
21580 let params = [
21581 ParamKV {
21582 key: "conv",
21583 value: ParamValue::Float(50.0),
21584 },
21585 ParamKV {
21586 key: "length",
21587 value: ParamValue::Int(20),
21588 },
21589 ];
21590 let combos = [IndicatorParamSet { params: ¶ms }];
21591
21592 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21593 indicator_id: "squeeze_index",
21594 output_id: Some("value"),
21595 data: IndicatorDataRef::Slice { values: &close },
21596 combos: &combos,
21597 kernel: Kernel::Auto,
21598 })
21599 .unwrap();
21600
21601 let direct = squeeze_index_with_kernel(
21602 &SqueezeIndexInput::from_slice(
21603 &close,
21604 SqueezeIndexParams {
21605 conv: Some(50.0),
21606 length: Some(20),
21607 },
21608 ),
21609 Kernel::Auto,
21610 )
21611 .unwrap();
21612
21613 let values = dispatched.values_f64.as_ref().unwrap();
21614 assert_eq!(values.len(), close.len());
21615 assert_series_eq(values, &direct.values, 1e-9);
21616 }
21617
21618 #[test]
21619 fn compute_cpu_batch_absolute_strength_index_oscillator_matches_direct() {
21620 let close: Vec<f64> = (0..256)
21621 .map(|i| 100.0 + ((i as f64) * 0.17).sin() * 1.8 + ((i % 7) as f64 - 3.0) * 0.04)
21622 .collect();
21623 let params = [
21624 ParamKV {
21625 key: "ema_length",
21626 value: ParamValue::Int(21),
21627 },
21628 ParamKV {
21629 key: "signal_length",
21630 value: ParamValue::Int(34),
21631 },
21632 ];
21633 let combos = [IndicatorParamSet { params: ¶ms }];
21634
21635 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21636 indicator_id: "absolute_strength_index_oscillator",
21637 output_id: Some("oscillator"),
21638 data: IndicatorDataRef::Slice { values: &close },
21639 combos: &combos,
21640 kernel: Kernel::Auto,
21641 })
21642 .unwrap();
21643
21644 let direct = absolute_strength_index_oscillator_with_kernel(
21645 &AbsoluteStrengthIndexOscillatorInput::from_slice(
21646 &close,
21647 AbsoluteStrengthIndexOscillatorParams {
21648 ema_length: Some(21),
21649 signal_length: Some(34),
21650 },
21651 ),
21652 Kernel::Auto,
21653 )
21654 .unwrap();
21655
21656 let values = dispatched.values_f64.as_ref().unwrap();
21657 assert_eq!(values.len(), close.len());
21658 assert_series_eq(values, &direct.oscillator, 1e-9);
21659 }
21660
21661 #[test]
21662 fn compute_cpu_batch_premier_rsi_oscillator_matches_direct() {
21663 let close: Vec<f64> = (0..256)
21664 .map(|i| 100.0 + ((i as f64) * 0.13).sin() * 1.4 + ((i % 11) as f64 - 5.0) * 0.03)
21665 .collect();
21666 let params = [
21667 ParamKV {
21668 key: "rsi_length",
21669 value: ParamValue::Int(14),
21670 },
21671 ParamKV {
21672 key: "stoch_length",
21673 value: ParamValue::Int(8),
21674 },
21675 ParamKV {
21676 key: "smooth_length",
21677 value: ParamValue::Int(25),
21678 },
21679 ];
21680 let combos = [IndicatorParamSet { params: ¶ms }];
21681
21682 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21683 indicator_id: "premier_rsi_oscillator",
21684 output_id: Some("value"),
21685 data: IndicatorDataRef::Slice { values: &close },
21686 combos: &combos,
21687 kernel: Kernel::Auto,
21688 })
21689 .unwrap();
21690
21691 let direct = premier_rsi_oscillator_with_kernel(
21692 &PremierRsiOscillatorInput::from_slice(
21693 &close,
21694 PremierRsiOscillatorParams {
21695 rsi_length: Some(14),
21696 stoch_length: Some(8),
21697 smooth_length: Some(25),
21698 },
21699 ),
21700 Kernel::Auto,
21701 )
21702 .unwrap();
21703
21704 let values = dispatched.values_f64.as_ref().unwrap();
21705 assert_eq!(values.len(), close.len());
21706 assert_series_eq(values, &direct.values, 1e-9);
21707 }
21708
21709 #[test]
21710 fn compute_cpu_batch_multi_length_stochastic_average_matches_direct() {
21711 let open: Vec<f64> = (0..256)
21712 .map(|i| 100.0 + i as f64 * 0.03 + (i as f64 * 0.09).sin())
21713 .collect();
21714 let close: Vec<f64> = open
21715 .iter()
21716 .enumerate()
21717 .map(|(i, &o)| o + (i as f64 * 0.13).cos() * 0.8)
21718 .collect();
21719 let high: Vec<f64> = open
21720 .iter()
21721 .zip(close.iter())
21722 .enumerate()
21723 .map(|(i, (&o, &c))| o.max(c) + 0.5 + (i as f64 * 0.05).sin().abs() * 0.2)
21724 .collect();
21725 let low: Vec<f64> = open
21726 .iter()
21727 .zip(close.iter())
21728 .enumerate()
21729 .map(|(i, (&o, &c))| o.min(c) - 0.5 - (i as f64 * 0.07).cos().abs() * 0.2)
21730 .collect();
21731 let candles = crate::utilities::data_loader::Candles::new(
21732 (0..256_i64).collect(),
21733 open,
21734 high,
21735 low,
21736 close,
21737 vec![1_000.0; 256],
21738 );
21739 let params = [
21740 ParamKV {
21741 key: "length",
21742 value: ParamValue::Int(14),
21743 },
21744 ParamKV {
21745 key: "presmooth",
21746 value: ParamValue::Int(10),
21747 },
21748 ParamKV {
21749 key: "premethod",
21750 value: ParamValue::EnumString("sma"),
21751 },
21752 ParamKV {
21753 key: "postsmooth",
21754 value: ParamValue::Int(10),
21755 },
21756 ParamKV {
21757 key: "postmethod",
21758 value: ParamValue::EnumString("lsma"),
21759 },
21760 ParamKV {
21761 key: "source",
21762 value: ParamValue::EnumString("hlc3"),
21763 },
21764 ];
21765 let combos = [IndicatorParamSet { params: ¶ms }];
21766
21767 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21768 indicator_id: "multi_length_stochastic_average",
21769 output_id: Some("value"),
21770 data: IndicatorDataRef::Candles {
21771 candles: &candles,
21772 source: Some("hlc3"),
21773 },
21774 combos: &combos,
21775 kernel: Kernel::Auto,
21776 })
21777 .unwrap();
21778
21779 let direct = multi_length_stochastic_average_with_kernel(
21780 &MultiLengthStochasticAverageInput::from_candles(
21781 &candles,
21782 "hlc3",
21783 MultiLengthStochasticAverageParams {
21784 length: Some(14),
21785 presmooth: Some(10),
21786 premethod: Some("sma".to_string()),
21787 postsmooth: Some(10),
21788 postmethod: Some("lsma".to_string()),
21789 },
21790 ),
21791 Kernel::Auto,
21792 )
21793 .unwrap();
21794
21795 let values = dispatched.values_f64.as_ref().unwrap();
21796 assert_eq!(values.len(), candles.close.len());
21797 assert_series_eq(values, &direct.values, 1e-9);
21798 }
21799
21800 #[test]
21801 fn compute_cpu_batch_hull_butterfly_oscillator_matches_direct() {
21802 let open: Vec<f64> = (0..256)
21803 .map(|i| 100.0 + i as f64 * 0.03 + (i as f64 * 0.09).sin())
21804 .collect();
21805 let close: Vec<f64> = open
21806 .iter()
21807 .enumerate()
21808 .map(|(i, &o)| o + (i as f64 * 0.13).cos() * 0.8)
21809 .collect();
21810 let high: Vec<f64> = open
21811 .iter()
21812 .zip(close.iter())
21813 .enumerate()
21814 .map(|(i, (&o, &c))| o.max(c) + 0.5 + (i as f64 * 0.05).sin().abs() * 0.2)
21815 .collect();
21816 let low: Vec<f64> = open
21817 .iter()
21818 .zip(close.iter())
21819 .enumerate()
21820 .map(|(i, (&o, &c))| o.min(c) - 0.5 - (i as f64 * 0.07).cos().abs() * 0.2)
21821 .collect();
21822 let candles = crate::utilities::data_loader::Candles::new(
21823 (0..256_i64).collect(),
21824 open,
21825 high,
21826 low,
21827 close,
21828 vec![1_000.0; 256],
21829 );
21830 let params = [
21831 ParamKV {
21832 key: "length",
21833 value: ParamValue::Int(14),
21834 },
21835 ParamKV {
21836 key: "mult",
21837 value: ParamValue::Float(1.75),
21838 },
21839 ParamKV {
21840 key: "source",
21841 value: ParamValue::EnumString("hlc3"),
21842 },
21843 ];
21844 let combos = [IndicatorParamSet { params: ¶ms }];
21845
21846 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21847 indicator_id: "hull_butterfly_oscillator",
21848 output_id: Some("oscillator"),
21849 data: IndicatorDataRef::Candles {
21850 candles: &candles,
21851 source: Some("hlc3"),
21852 },
21853 combos: &combos,
21854 kernel: Kernel::Auto,
21855 })
21856 .unwrap();
21857
21858 let direct = hull_butterfly_oscillator_with_kernel(
21859 &HullButterflyOscillatorInput::from_candles(
21860 &candles,
21861 "hlc3",
21862 HullButterflyOscillatorParams {
21863 length: Some(14),
21864 mult: Some(1.75),
21865 },
21866 ),
21867 Kernel::Auto,
21868 )
21869 .unwrap();
21870
21871 let values = dispatched.values_f64.as_ref().unwrap();
21872 assert_eq!(values.len(), candles.close.len());
21873 assert_series_eq(values, &direct.oscillator, 1e-9);
21874 }
21875
21876 #[test]
21877 fn compute_cpu_batch_fibonacci_trailing_stop_matches_direct() {
21878 let open: Vec<f64> = (0..256)
21879 .map(|i| 100.0 + i as f64 * 0.03 + (i as f64 * 0.09).sin())
21880 .collect();
21881 let close: Vec<f64> = open
21882 .iter()
21883 .enumerate()
21884 .map(|(i, &o)| o + (i as f64 * 0.13).cos() * 0.8)
21885 .collect();
21886 let high: Vec<f64> = open
21887 .iter()
21888 .zip(close.iter())
21889 .enumerate()
21890 .map(|(i, (&o, &c))| o.max(c) + 0.5 + (i as f64 * 0.05).sin().abs() * 0.2)
21891 .collect();
21892 let low: Vec<f64> = open
21893 .iter()
21894 .zip(close.iter())
21895 .enumerate()
21896 .map(|(i, (&o, &c))| o.min(c) - 0.5 - (i as f64 * 0.07).cos().abs() * 0.2)
21897 .collect();
21898
21899 let params = [
21900 ParamKV {
21901 key: "left_bars",
21902 value: ParamValue::Int(12),
21903 },
21904 ParamKV {
21905 key: "right_bars",
21906 value: ParamValue::Int(2),
21907 },
21908 ParamKV {
21909 key: "level",
21910 value: ParamValue::Float(-0.236),
21911 },
21912 ParamKV {
21913 key: "trigger",
21914 value: ParamValue::EnumString("wick"),
21915 },
21916 ];
21917 let combos = [IndicatorParamSet { params: ¶ms }];
21918
21919 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21920 indicator_id: "fibonacci_trailing_stop",
21921 output_id: Some("trailing_stop"),
21922 data: IndicatorDataRef::Ohlc {
21923 open: &open,
21924 high: &high,
21925 low: &low,
21926 close: &close,
21927 },
21928 combos: &combos,
21929 kernel: Kernel::Auto,
21930 })
21931 .unwrap();
21932
21933 let direct = fibonacci_trailing_stop_with_kernel(
21934 &FibonacciTrailingStopInput::from_slices(
21935 &high,
21936 &low,
21937 &close,
21938 FibonacciTrailingStopParams {
21939 left_bars: Some(12),
21940 right_bars: Some(2),
21941 level: Some(-0.236),
21942 trigger: Some("wick".to_string()),
21943 },
21944 ),
21945 Kernel::Auto,
21946 )
21947 .unwrap();
21948
21949 let values = dispatched.values_f64.as_ref().unwrap();
21950 assert_eq!(values.len(), close.len());
21951 assert_series_eq(values, &direct.trailing_stop, 1e-9);
21952 }
21953
21954 #[test]
21955 fn compute_cpu_batch_volume_energy_reservoirs_matches_direct() {
21956 let open: Vec<f64> = (0..256)
21957 .map(|i| 100.0 + i as f64 * 0.03 + (i as f64 * 0.08).sin())
21958 .collect();
21959 let close: Vec<f64> = open
21960 .iter()
21961 .enumerate()
21962 .map(|(i, &o)| o + (i as f64 * 0.11).cos() * 0.9)
21963 .collect();
21964 let high: Vec<f64> = open
21965 .iter()
21966 .zip(close.iter())
21967 .enumerate()
21968 .map(|(i, (&o, &c))| o.max(c) + 0.6 + (i as f64 * 0.03).sin().abs() * 0.25)
21969 .collect();
21970 let low: Vec<f64> = open
21971 .iter()
21972 .zip(close.iter())
21973 .enumerate()
21974 .map(|(i, (&o, &c))| o.min(c) - 0.6 - (i as f64 * 0.05).cos().abs() * 0.2)
21975 .collect();
21976 let volume: Vec<f64> = (0..256)
21977 .map(|i| 1_000.0 + i as f64 * 4.0 + (i as f64 * 0.09).sin() * 180.0)
21978 .collect();
21979
21980 let params = [
21981 ParamKV {
21982 key: "length",
21983 value: ParamValue::Int(18),
21984 },
21985 ParamKV {
21986 key: "sensitivity",
21987 value: ParamValue::Float(1.7),
21988 },
21989 ];
21990 let combos = [IndicatorParamSet { params: ¶ms }];
21991
21992 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
21993 indicator_id: "volume_energy_reservoirs",
21994 output_id: Some("momentum"),
21995 data: IndicatorDataRef::Ohlcv {
21996 open: &open,
21997 high: &high,
21998 low: &low,
21999 close: &close,
22000 volume: &volume,
22001 },
22002 combos: &combos,
22003 kernel: Kernel::Auto,
22004 })
22005 .unwrap();
22006
22007 let direct = volume_energy_reservoirs_with_kernel(
22008 &VolumeEnergyReservoirsInput::from_slices(
22009 &high,
22010 &low,
22011 &close,
22012 &volume,
22013 VolumeEnergyReservoirsParams {
22014 length: Some(18),
22015 sensitivity: Some(1.7),
22016 },
22017 ),
22018 Kernel::Auto,
22019 )
22020 .unwrap();
22021
22022 let values = dispatched.values_f64.as_ref().unwrap();
22023 assert_eq!(values.len(), close.len());
22024 assert_series_eq(values, &direct.momentum, 1e-9);
22025 }
22026
22027 #[test]
22028 fn compute_cpu_batch_neighboring_trailing_stop_matches_direct() {
22029 let open: Vec<f64> = (0..256)
22030 .map(|i| 100.0 + i as f64 * 0.04 + (i as f64 * 0.07).sin())
22031 .collect();
22032 let close: Vec<f64> = open
22033 .iter()
22034 .enumerate()
22035 .map(|(i, &o)| o + (i as f64 * 0.11).cos() * 0.85)
22036 .collect();
22037 let high: Vec<f64> = open
22038 .iter()
22039 .zip(close.iter())
22040 .enumerate()
22041 .map(|(i, (&o, &c))| o.max(c) + 0.55 + (i as f64 * 0.03).sin().abs() * 0.2)
22042 .collect();
22043 let low: Vec<f64> = open
22044 .iter()
22045 .zip(close.iter())
22046 .enumerate()
22047 .map(|(i, (&o, &c))| o.min(c) - 0.55 - (i as f64 * 0.05).cos().abs() * 0.2)
22048 .collect();
22049
22050 let params = [
22051 ParamKV {
22052 key: "buffer_size",
22053 value: ParamValue::Int(180),
22054 },
22055 ParamKV {
22056 key: "k",
22057 value: ParamValue::Int(30),
22058 },
22059 ParamKV {
22060 key: "percentile",
22061 value: ParamValue::Float(87.5),
22062 },
22063 ParamKV {
22064 key: "smooth",
22065 value: ParamValue::Int(4),
22066 },
22067 ];
22068 let combos = [IndicatorParamSet { params: ¶ms }];
22069
22070 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
22071 indicator_id: "neighboring_trailing_stop",
22072 output_id: Some("trailing_stop"),
22073 data: IndicatorDataRef::Ohlc {
22074 open: &open,
22075 high: &high,
22076 low: &low,
22077 close: &close,
22078 },
22079 combos: &combos,
22080 kernel: Kernel::Auto,
22081 })
22082 .unwrap();
22083
22084 let direct = neighboring_trailing_stop_with_kernel(
22085 &NeighboringTrailingStopInput::from_slices(
22086 &high,
22087 &low,
22088 &close,
22089 NeighboringTrailingStopParams {
22090 buffer_size: Some(180),
22091 k: Some(30),
22092 percentile: Some(87.5),
22093 smooth: Some(4),
22094 },
22095 ),
22096 Kernel::Auto,
22097 )
22098 .unwrap();
22099
22100 let values = dispatched.values_f64.as_ref().unwrap();
22101 assert_eq!(values.len(), close.len());
22102 assert_series_eq(values, &direct.trailing_stop, 1e-9);
22103 }
22104
22105 #[test]
22106 fn compute_cpu_batch_macd_wave_signal_pro_matches_direct() {
22107 let open: Vec<f64> = (0..256)
22108 .map(|i| 100.0 + i as f64 * 0.08 + ((i as f64) * 0.05).sin() * 0.7)
22109 .collect();
22110 let close: Vec<f64> = open
22111 .iter()
22112 .enumerate()
22113 .map(|(i, o)| o + ((i as f64) * 0.09).cos() * 0.9)
22114 .collect();
22115 let high: Vec<f64> = open
22116 .iter()
22117 .zip(close.iter())
22118 .enumerate()
22119 .map(|(i, (&o, &c))| o.max(c) + 0.55 + (i as f64 * 0.03).sin().abs() * 0.2)
22120 .collect();
22121 let low: Vec<f64> = open
22122 .iter()
22123 .zip(close.iter())
22124 .enumerate()
22125 .map(|(i, (&o, &c))| o.min(c) - 0.55 - (i as f64 * 0.05).cos().abs() * 0.2)
22126 .collect();
22127 let combos = [IndicatorParamSet { params: &[] }];
22128
22129 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
22130 indicator_id: "macd_wave_signal_pro",
22131 output_id: Some("line_convergence"),
22132 data: IndicatorDataRef::Ohlc {
22133 open: &open,
22134 high: &high,
22135 low: &low,
22136 close: &close,
22137 },
22138 combos: &combos,
22139 kernel: Kernel::Auto,
22140 })
22141 .unwrap();
22142
22143 let direct = macd_wave_signal_pro_with_kernel(
22144 &MacdWaveSignalProInput::from_slices(&open, &high, &low, &close, Default::default()),
22145 Kernel::Auto,
22146 )
22147 .unwrap();
22148
22149 let values = dispatched.values_f64.as_ref().unwrap();
22150 assert_eq!(values.len(), close.len());
22151 assert_series_eq(values, &direct.line_convergence, 1e-9);
22152 }
22153
22154 #[test]
22155 fn compute_cpu_batch_hema_trend_levels_matches_direct() {
22156 let open: Vec<f64> = (0..256)
22157 .map(|i| 100.0 + i as f64 * 0.05 + ((i as f64) * 0.09).sin() * 1.3)
22158 .collect();
22159 let close: Vec<f64> = open
22160 .iter()
22161 .enumerate()
22162 .map(|(i, o)| o + ((i as f64) * 0.07).cos() * 1.1)
22163 .collect();
22164 let high: Vec<f64> = open
22165 .iter()
22166 .zip(close.iter())
22167 .enumerate()
22168 .map(|(i, (&o, &c))| o.max(c) + 0.65 + (i as f64 * 0.03).sin().abs() * 0.25)
22169 .collect();
22170 let low: Vec<f64> = open
22171 .iter()
22172 .zip(close.iter())
22173 .enumerate()
22174 .map(|(i, (&o, &c))| o.min(c) - 0.65 - (i as f64 * 0.05).cos().abs() * 0.25)
22175 .collect();
22176 let params = [
22177 ParamKV {
22178 key: "fast_length",
22179 value: ParamValue::Int(20),
22180 },
22181 ParamKV {
22182 key: "slow_length",
22183 value: ParamValue::Int(40),
22184 },
22185 ];
22186 let combos = [IndicatorParamSet { params: ¶ms }];
22187
22188 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
22189 indicator_id: "hema_trend_levels",
22190 output_id: Some("bullish_test_level"),
22191 data: IndicatorDataRef::Ohlc {
22192 open: &open,
22193 high: &high,
22194 low: &low,
22195 close: &close,
22196 },
22197 combos: &combos,
22198 kernel: Kernel::Auto,
22199 })
22200 .unwrap();
22201
22202 let direct = hema_trend_levels_with_kernel(
22203 &HemaTrendLevelsInput::from_slices(
22204 &open,
22205 &high,
22206 &low,
22207 &close,
22208 HemaTrendLevelsParams {
22209 fast_length: Some(20),
22210 slow_length: Some(40),
22211 },
22212 ),
22213 Kernel::Auto,
22214 )
22215 .unwrap();
22216
22217 let values = dispatched.values_f64.as_ref().unwrap();
22218 assert_eq!(values.len(), close.len());
22219 assert_series_eq(values, &direct.bullish_test_level, 1e-9);
22220 }
22221
22222 #[test]
22223 fn compute_cpu_batch_fibonacci_entry_bands_matches_direct() {
22224 let open: Vec<f64> = (0..256)
22225 .map(|i| 100.0 + i as f64 * 0.05 + ((i as f64) * 0.09).sin() * 1.3)
22226 .collect();
22227 let close: Vec<f64> = open
22228 .iter()
22229 .enumerate()
22230 .map(|(i, o)| o + ((i as f64) * 0.07).cos() * 1.1)
22231 .collect();
22232 let high: Vec<f64> = open
22233 .iter()
22234 .zip(close.iter())
22235 .enumerate()
22236 .map(|(i, (&o, &c))| o.max(c) + 0.65 + (i as f64 * 0.03).sin().abs() * 0.25)
22237 .collect();
22238 let low: Vec<f64> = open
22239 .iter()
22240 .zip(close.iter())
22241 .enumerate()
22242 .map(|(i, (&o, &c))| o.min(c) - 0.65 - (i as f64 * 0.05).cos().abs() * 0.25)
22243 .collect();
22244 let params = [
22245 ParamKV {
22246 key: "source",
22247 value: ParamValue::EnumString("hlc3"),
22248 },
22249 ParamKV {
22250 key: "length",
22251 value: ParamValue::Int(20),
22252 },
22253 ParamKV {
22254 key: "atr_length",
22255 value: ParamValue::Int(11),
22256 },
22257 ParamKV {
22258 key: "use_atr",
22259 value: ParamValue::Bool(true),
22260 },
22261 ParamKV {
22262 key: "tp_aggressiveness",
22263 value: ParamValue::EnumString("medium"),
22264 },
22265 ];
22266 let combos = [IndicatorParamSet { params: ¶ms }];
22267
22268 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
22269 indicator_id: "fibonacci_entry_bands",
22270 output_id: Some("tp_long_band"),
22271 data: IndicatorDataRef::Ohlc {
22272 open: &open,
22273 high: &high,
22274 low: &low,
22275 close: &close,
22276 },
22277 combos: &combos,
22278 kernel: Kernel::Auto,
22279 })
22280 .unwrap();
22281
22282 let direct = fibonacci_entry_bands_with_kernel(
22283 &FibonacciEntryBandsInput::from_slices(
22284 &open,
22285 &high,
22286 &low,
22287 &close,
22288 FibonacciEntryBandsParams {
22289 source: Some("hlc3".to_string()),
22290 length: Some(20),
22291 atr_length: Some(11),
22292 use_atr: Some(true),
22293 tp_aggressiveness: Some("medium".to_string()),
22294 },
22295 ),
22296 Kernel::Auto,
22297 )
22298 .unwrap();
22299
22300 let values = dispatched.values_f64.as_ref().unwrap();
22301 assert_eq!(values.len(), close.len());
22302 assert_series_eq(values, &direct.tp_long_band, 1e-9);
22303 }
22304
22305 #[test]
22306 fn compute_cpu_batch_vertical_horizontal_filter_matches_direct() {
22307 let close: Vec<f64> = (0..256)
22308 .map(|i| 100.0 + ((i as f64) * 0.02).sin() + (i as f64 * 0.1))
22309 .collect();
22310 let params = [ParamKV {
22311 key: "length",
22312 value: ParamValue::Int(28),
22313 }];
22314 let combos = [IndicatorParamSet { params: ¶ms }];
22315
22316 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
22317 indicator_id: "vertical_horizontal_filter",
22318 output_id: Some("value"),
22319 data: IndicatorDataRef::Slice { values: &close },
22320 combos: &combos,
22321 kernel: Kernel::Auto,
22322 })
22323 .unwrap();
22324
22325 let direct = vertical_horizontal_filter_with_kernel(
22326 &VerticalHorizontalFilterInput::from_slice(
22327 &close,
22328 VerticalHorizontalFilterParams { length: Some(28) },
22329 ),
22330 Kernel::Auto,
22331 )
22332 .unwrap();
22333
22334 let values = dispatched.values_f64.as_ref().unwrap();
22335 assert_eq!(values.len(), close.len());
22336 assert_series_eq(values, &direct.values, 1e-9);
22337 }
22338
22339 #[test]
22340 fn compute_cpu_batch_intraday_momentum_index_matches_direct() {
22341 let open: Vec<f64> = (0..256)
22342 .map(|i| 100.0 + i as f64 * 0.1 + ((i as f64) * 0.05).cos() * 0.2)
22343 .collect();
22344 let high: Vec<f64> = open.iter().map(|v| v + 0.9).collect();
22345 let low: Vec<f64> = open.iter().map(|v| v - 0.8).collect();
22346 let close: Vec<f64> = open
22347 .iter()
22348 .enumerate()
22349 .map(|(i, o)| o + ((i as f64) * 0.09).sin() * 0.6)
22350 .collect();
22351 let params = [
22352 ParamKV {
22353 key: "length",
22354 value: ParamValue::Int(14),
22355 },
22356 ParamKV {
22357 key: "length_ma",
22358 value: ParamValue::Int(6),
22359 },
22360 ParamKV {
22361 key: "mult",
22362 value: ParamValue::Float(2.0),
22363 },
22364 ParamKV {
22365 key: "length_bb",
22366 value: ParamValue::Int(20),
22367 },
22368 ParamKV {
22369 key: "apply_smoothing",
22370 value: ParamValue::Bool(true),
22371 },
22372 ParamKV {
22373 key: "low_band",
22374 value: ParamValue::Int(10),
22375 },
22376 ];
22377 let combos = [IndicatorParamSet { params: ¶ms }];
22378
22379 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
22380 indicator_id: "intraday_momentum_index",
22381 output_id: Some("imi"),
22382 data: IndicatorDataRef::Ohlc {
22383 open: &open,
22384 high: &high,
22385 low: &low,
22386 close: &close,
22387 },
22388 combos: &combos,
22389 kernel: Kernel::Auto,
22390 })
22391 .unwrap();
22392
22393 let direct = intraday_momentum_index_with_kernel(
22394 &IntradayMomentumIndexInput::from_slices(
22395 &open,
22396 &close,
22397 IntradayMomentumIndexParams {
22398 length: Some(14),
22399 length_ma: Some(6),
22400 mult: Some(2.0),
22401 length_bb: Some(20),
22402 apply_smoothing: Some(true),
22403 low_band: Some(10),
22404 },
22405 ),
22406 Kernel::Auto,
22407 )
22408 .unwrap();
22409
22410 let values = dispatched.values_f64.as_ref().unwrap();
22411 assert_eq!(values.len(), close.len());
22412 assert_series_eq(values, &direct.imi, 1e-9);
22413 }
22414
22415 #[test]
22416 fn compute_cpu_batch_atr_percentile_matches_direct() {
22417 let high: Vec<f64> = (0..256)
22418 .map(|i| 100.0 + i as f64 * 0.1 + ((i as f64) * 0.03).sin().abs())
22419 .collect();
22420 let low: Vec<f64> = high
22421 .iter()
22422 .enumerate()
22423 .map(|(i, h)| h - 0.75 - ((i as f64) * 0.02).cos().abs() * 0.2)
22424 .collect();
22425 let close: Vec<f64> = low
22426 .iter()
22427 .zip(high.iter())
22428 .enumerate()
22429 .map(|(i, (l, h))| l + (h - l) * (0.35 + 0.2 * ((i as f64) * 0.05).sin().abs()))
22430 .collect();
22431 let params = [
22432 ParamKV {
22433 key: "atr_length",
22434 value: ParamValue::Int(10),
22435 },
22436 ParamKV {
22437 key: "percentile_length",
22438 value: ParamValue::Int(20),
22439 },
22440 ];
22441 let combos = [IndicatorParamSet { params: ¶ms }];
22442
22443 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
22444 indicator_id: "atr_percentile",
22445 output_id: Some("value"),
22446 data: IndicatorDataRef::Ohlc {
22447 open: &close,
22448 high: &high,
22449 low: &low,
22450 close: &close,
22451 },
22452 combos: &combos,
22453 kernel: Kernel::Auto,
22454 })
22455 .unwrap();
22456
22457 let direct = atr_percentile_with_kernel(
22458 &AtrPercentileInput::from_slices(
22459 &high,
22460 &low,
22461 &close,
22462 AtrPercentileParams {
22463 atr_length: Some(10),
22464 percentile_length: Some(20),
22465 },
22466 ),
22467 Kernel::Auto,
22468 )
22469 .unwrap();
22470
22471 let values = dispatched.values_f64.as_ref().unwrap();
22472 assert_eq!(values.len(), close.len());
22473 assert_series_eq(values, &direct.values, 1e-9);
22474 }
22475
22476 #[test]
22477 fn compute_cpu_batch_demand_index_matches_direct() {
22478 let high: Vec<f64> = (0..256)
22479 .map(|i| 100.0 + i as f64 * 0.15 + ((i as f64) * 0.03).sin().abs())
22480 .collect();
22481 let low: Vec<f64> = high
22482 .iter()
22483 .enumerate()
22484 .map(|(i, h)| h - 0.9 - ((i as f64) * 0.04).cos().abs() * 0.3)
22485 .collect();
22486 let close: Vec<f64> = low
22487 .iter()
22488 .zip(high.iter())
22489 .enumerate()
22490 .map(|(i, (l, h))| l + (h - l) * (0.25 + 0.5 * ((i as f64) * 0.07).sin().abs()))
22491 .collect();
22492 let open: Vec<f64> = close
22493 .iter()
22494 .enumerate()
22495 .map(|(i, c)| c - 0.2 + ((i as f64) * 0.05).cos() * 0.1)
22496 .collect();
22497 let volume: Vec<f64> = (0..256)
22498 .map(|i| 1000.0 + (i as f64) * 3.0 + ((i as f64) * 0.11).sin().abs() * 40.0)
22499 .collect();
22500 let params = [
22501 ParamKV {
22502 key: "len_bs",
22503 value: ParamValue::Int(19),
22504 },
22505 ParamKV {
22506 key: "len_bs_ma",
22507 value: ParamValue::Int(19),
22508 },
22509 ParamKV {
22510 key: "len_di_ma",
22511 value: ParamValue::Int(19),
22512 },
22513 ParamKV {
22514 key: "ma_type",
22515 value: ParamValue::EnumString("ema"),
22516 },
22517 ];
22518 let combos = [IndicatorParamSet { params: ¶ms }];
22519
22520 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
22521 indicator_id: "demand_index",
22522 output_id: Some("demand_index"),
22523 data: IndicatorDataRef::Ohlcv {
22524 open: &open,
22525 high: &high,
22526 low: &low,
22527 close: &close,
22528 volume: &volume,
22529 },
22530 combos: &combos,
22531 kernel: Kernel::Auto,
22532 })
22533 .unwrap();
22534
22535 let direct = demand_index_with_kernel(
22536 &DemandIndexInput::from_slices(
22537 &high,
22538 &low,
22539 &close,
22540 &volume,
22541 DemandIndexParams {
22542 len_bs: Some(19),
22543 len_bs_ma: Some(19),
22544 len_di_ma: Some(19),
22545 ma_type: Some("ema".to_string()),
22546 },
22547 ),
22548 Kernel::Auto,
22549 )
22550 .unwrap();
22551
22552 let values = dispatched.values_f64.as_ref().unwrap();
22553 assert_eq!(values.len(), close.len());
22554 assert_series_eq(values, &direct.demand_index, 1e-9);
22555 }
22556
22557 #[test]
22558 fn compute_cpu_batch_vwap_zscore_with_signals_matches_direct() {
22559 let close: Vec<f64> = (0..192).map(|i| 100.0 + (i as f64 * 0.15)).collect();
22560 let volume: Vec<f64> = (0..192).map(|i| 1_000.0 + (i as f64 * 2.0)).collect();
22561 let req = IndicatorBatchRequest {
22562 indicator_id: "vwap_zscore_with_signals",
22563 output_id: Some("zvwap"),
22564 data: IndicatorDataRef::CloseVolume {
22565 close: &close,
22566 volume: &volume,
22567 },
22568 combos: &[IndicatorParamSet {
22569 params: &[
22570 ParamKV {
22571 key: "length",
22572 value: ParamValue::Int(20),
22573 },
22574 ParamKV {
22575 key: "upper_bottom",
22576 value: ParamValue::Float(2.5),
22577 },
22578 ParamKV {
22579 key: "lower_bottom",
22580 value: ParamValue::Float(-2.5),
22581 },
22582 ],
22583 }],
22584 kernel: Kernel::Auto,
22585 };
22586
22587 let out = compute_cpu_batch(req).unwrap();
22588 let values = out.values_f64.as_ref().unwrap();
22589 let direct = vwap_zscore_with_signals_with_kernel(
22590 &VwapZscoreWithSignalsInput::from_slices(
22591 &close,
22592 &volume,
22593 VwapZscoreWithSignalsParams {
22594 length: Some(20),
22595 upper_bottom: Some(2.5),
22596 lower_bottom: Some(-2.5),
22597 },
22598 ),
22599 Kernel::Auto,
22600 )
22601 .unwrap();
22602 assert_eq!(out.rows, 1);
22603 assert_eq!(out.cols, close.len());
22604 assert_series_eq(values, &direct.zvwap, 1e-9);
22605 }
22606
22607 #[test]
22608 fn compute_cpu_batch_gopalakrishnan_range_index_matches_direct() {
22609 let high: Vec<f64> = (0..256)
22610 .map(|i| 100.0 + i as f64 * 0.1 + ((i as f64) * 0.03).sin().abs())
22611 .collect();
22612 let low: Vec<f64> = high
22613 .iter()
22614 .enumerate()
22615 .map(|(i, h)| h - 0.75 - ((i as f64) * 0.02).cos().abs() * 0.2)
22616 .collect();
22617 let params = [ParamKV {
22618 key: "length",
22619 value: ParamValue::Int(5),
22620 }];
22621 let combos = [IndicatorParamSet { params: ¶ms }];
22622
22623 let dispatched = compute_cpu_batch(IndicatorBatchRequest {
22624 indicator_id: "gopalakrishnan_range_index",
22625 output_id: Some("value"),
22626 data: IndicatorDataRef::HighLow {
22627 high: &high,
22628 low: &low,
22629 },
22630 combos: &combos,
22631 kernel: Kernel::Auto,
22632 })
22633 .unwrap();
22634
22635 let direct = gopalakrishnan_range_index_with_kernel(
22636 &GopalakrishnanRangeIndexInput::from_slices(
22637 &high,
22638 &low,
22639 GopalakrishnanRangeIndexParams { length: Some(5) },
22640 ),
22641 Kernel::Auto,
22642 )
22643 .unwrap();
22644
22645 let values = dispatched.values_f64.as_ref().unwrap();
22646 assert_eq!(values.len(), high.len());
22647 assert_series_eq(values, &direct.values, 1e-9);
22648 }
22649}